Mercurial > hg > orthanc
changeset 5341:990cfa101803
merge
author | Alain Mazy <am@osimis.io> |
---|---|
date | Wed, 28 Jun 2023 11:11:47 +0200 |
parents | 0854cc13b4d5 (current diff) cb11e5ced4e3 (diff) |
children | 65d55cc86a41 |
files | |
diffstat | 16 files changed, 656 insertions(+), 323 deletions(-) [+] |
line wrap: on
line diff
--- a/NEWS Wed Jun 28 11:11:34 2023 +0200 +++ b/NEWS Wed Jun 28 11:11:47 2023 +0200 @@ -31,7 +31,7 @@ (https://discourse.orthanc-server.org/t/issue-with-deleting-incoming-dicoms-when-maximumstoragesize-is-reached/3510) * When deleting a resource, its parents LastUpdate metadata are now updated * Reduced the memory usage when downloading archives when "ZipLoaderThreads" > 0 -* Metrics are now stored as integers instead of floats to avoid precision loss in increments +* Metrics can be stored either as floating-point numbers, or as integers * Upgraded dependencies for static builds: - boost 1.82.0
--- a/OrthancFramework/Sources/FileStorage/StorageAccessor.cpp Wed Jun 28 11:11:34 2023 +0200 +++ b/OrthancFramework/Sources/FileStorage/StorageAccessor.cpp Wed Jun 28 11:11:47 2023 +0200 @@ -125,7 +125,7 @@ if (metrics_ != NULL) { - metrics_->IncrementValue(METRICS_WRITTEN_BYTES, size); + metrics_->IncrementIntegerValue(METRICS_WRITTEN_BYTES, size); } if (cache_ != NULL) @@ -165,7 +165,7 @@ if (metrics_ != NULL) { - metrics_->IncrementValue(METRICS_WRITTEN_BYTES, compressed.size()); + metrics_->IncrementIntegerValue(METRICS_WRITTEN_BYTES, compressed.size()); } if (cache_ != NULL) @@ -211,7 +211,7 @@ if (metrics_ != NULL) { - metrics_->IncrementValue(METRICS_READ_BYTES, buffer->GetSize()); + metrics_->IncrementIntegerValue(METRICS_READ_BYTES, buffer->GetSize()); } buffer->MoveToString(content); @@ -232,7 +232,7 @@ if (metrics_ != NULL) { - metrics_->IncrementValue(METRICS_READ_BYTES, compressed->GetSize()); + metrics_->IncrementIntegerValue(METRICS_READ_BYTES, compressed->GetSize()); } zlib.Uncompress(content, compressed->GetData(), compressed->GetSize()); @@ -271,7 +271,7 @@ if (metrics_ != NULL) { - metrics_->IncrementValue(METRICS_READ_BYTES, buffer->GetSize()); + metrics_->IncrementIntegerValue(METRICS_READ_BYTES, buffer->GetSize()); } buffer->MoveToString(content); @@ -317,7 +317,7 @@ if (metrics_ != NULL) { - metrics_->IncrementValue(METRICS_READ_BYTES, buffer->GetSize()); + metrics_->IncrementIntegerValue(METRICS_READ_BYTES, buffer->GetSize()); } buffer->MoveToString(target);
--- a/OrthancFramework/Sources/HttpServer/HttpContentNegociation.cpp Wed Jun 28 11:11:34 2023 +0200 +++ b/OrthancFramework/Sources/HttpServer/HttpContentNegociation.cpp Wed Jun 28 11:11:47 2023 +0200 @@ -49,28 +49,68 @@ { return true; } - - if (subtype == "*" && type == type_) + else if (subtype == "*" && type == type_) { return true; } - - return type == type_ && subtype == subtype_; + else + { + return type == type_ && subtype == subtype_; + } } - struct HttpContentNegociation::Reference : public boost::noncopyable + class HttpContentNegociation::Reference : public boost::noncopyable { + private: const Handler& handler_; uint8_t level_; float quality_; + Dictionary parameters_; + static float GetQuality(const Dictionary& parameters) + { + Dictionary::const_iterator found = parameters.find("q"); + + if (found != parameters.end()) + { + float quality; + bool ok = false; + + try + { + quality = boost::lexical_cast<float>(found->second); + ok = (quality >= 0.0f && quality <= 1.0f); + } + catch (boost::bad_lexical_cast&) + { + } + + if (ok) + { + return quality; + } + else + { + throw OrthancException( + ErrorCode_BadRequest, + "Quality parameter out of range in a HTTP request (must be between 0 and 1): " + found->second); + } + } + else + { + return 1.0f; // Default quality + } + } + + public: Reference(const Handler& handler, const std::string& type, const std::string& subtype, - float quality) : + const Dictionary& parameters) : handler_(handler), - quality_(quality) + quality_(GetQuality(parameters)), + parameters_(parameters) { if (type == "*" && subtype == "*") { @@ -85,6 +125,11 @@ level_ = 2; } } + + void Call() const + { + handler_.Call(parameters_); + } bool operator< (const Reference& other) const { @@ -92,13 +137,14 @@ { return true; } - - if (level_ > other.level_) + else if (level_ > other.level_) { return false; } - - return quality_ < other.quality_; + else + { + return quality_ < other.quality_; + } } }; @@ -123,58 +169,21 @@ } - float HttpContentNegociation::GetQuality(const Tokens& parameters) - { - for (size_t i = 1; i < parameters.size(); i++) - { - std::string key, value; - if (SplitPair(key, value, parameters[i], '=') && - key == "q") - { - float quality; - bool ok = false; - - try - { - quality = boost::lexical_cast<float>(value); - ok = (quality >= 0.0f && quality <= 1.0f); - } - catch (boost::bad_lexical_cast&) - { - } - - if (ok) - { - return quality; - } - else - { - throw OrthancException( - ErrorCode_BadRequest, - "Quality parameter out of range in a HTTP request (must be between 0 and 1): " + value); - } - } - } - - return 1.0f; // Default quality - } - - - void HttpContentNegociation::SelectBestMatch(std::unique_ptr<Reference>& best, + void HttpContentNegociation::SelectBestMatch(std::unique_ptr<Reference>& target, const Handler& handler, const std::string& type, const std::string& subtype, - float quality) + const Dictionary& parameters) { - std::unique_ptr<Reference> match(new Reference(handler, type, subtype, quality)); + std::unique_ptr<Reference> match(new Reference(handler, type, subtype, parameters)); - if (best.get() == NULL || - *best < *match) + if (target.get() == NULL || + *target < *match) { #if __cplusplus < 201103L - best.reset(match.release()); + target.reset(match.release()); #else - best = std::move(match); + target = std::move(match); #endif } } @@ -198,9 +207,9 @@ } - bool HttpContentNegociation::Apply(const HttpHeaders& headers) + bool HttpContentNegociation::Apply(const Dictionary& headers) { - HttpHeaders::const_iterator accept = headers.find("accept"); + Dictionary::const_iterator accept = headers.find("accept"); if (accept != headers.end()) { return Apply(accept->second); @@ -222,26 +231,49 @@ Toolbox::TokenizeString(mediaRanges, accept, ','); std::unique_ptr<Reference> bestMatch; + Dictionary bestParameters; for (Tokens::const_iterator it = mediaRanges.begin(); it != mediaRanges.end(); ++it) { - Tokens parameters; - Toolbox::TokenizeString(parameters, *it, ';'); + Tokens tokens; + Toolbox::TokenizeString(tokens, *it, ';'); - if (parameters.size() > 0) + if (tokens.size() > 0) { - float quality = GetQuality(parameters); + Dictionary parameters; + for (size_t i = 1; i < tokens.size(); i++) + { + std::string key, value; + + if (SplitPair(key, value, tokens[i], '=')) + { + // Remove the enclosing quotes, if present + if (!value.empty() && + value[0] == '"' && + value[value.size() - 1] == '"') + { + value = value.substr(1, value.size() - 2); + } + } + else + { + key = Toolbox::StripSpaces(tokens[i]); + value = ""; + } + parameters[key] = value; + } + std::string type, subtype; - if (SplitPair(type, subtype, parameters[0], '/')) + if (SplitPair(type, subtype, tokens[0], '/')) { for (Handlers::const_iterator it2 = handlers_.begin(); it2 != handlers_.end(); ++it2) { if (it2->IsMatch(type, subtype)) { - SelectBestMatch(bestMatch, *it2, type, subtype, quality); + SelectBestMatch(bestMatch, *it2, type, subtype, parameters); } } } @@ -254,7 +286,7 @@ } else { - bestMatch->handler_.Call(); + bestMatch->Call(); return true; } }
--- a/OrthancFramework/Sources/HttpServer/HttpContentNegociation.h Wed Jun 28 11:11:34 2023 +0200 +++ b/OrthancFramework/Sources/HttpServer/HttpContentNegociation.h Wed Jun 28 11:11:47 2023 +0200 @@ -39,7 +39,7 @@ class ORTHANC_PUBLIC HttpContentNegociation : public boost::noncopyable { public: - typedef std::map<std::string, std::string> HttpHeaders; + typedef std::map<std::string, std::string> Dictionary; class IHandler : public boost::noncopyable { @@ -49,7 +49,8 @@ } virtual void Handle(const std::string& type, - const std::string& subtype) = 0; + const std::string& subtype, + const Dictionary& parameters) = 0; }; private: @@ -66,9 +67,9 @@ bool IsMatch(const std::string& type, const std::string& subtype) const; - void Call() const + void Call(const Dictionary& parameters) const { - handler_.Handle(type_, subtype_); + handler_.Handle(type_, subtype_, parameters); } }; @@ -86,19 +87,17 @@ const std::string& source, char separator); - static float GetQuality(const Tokens& parameters); - - static void SelectBestMatch(std::unique_ptr<Reference>& best, + static void SelectBestMatch(std::unique_ptr<Reference>& target, const Handler& handler, const std::string& type, const std::string& subtype, - float quality); + const Dictionary& parameters); public: void Register(const std::string& mime, IHandler& handler); - bool Apply(const HttpHeaders& headers); + bool Apply(const Dictionary& headers); bool Apply(const std::string& accept); };
--- a/OrthancFramework/Sources/MetricsRegistry.cpp Wed Jun 28 11:11:34 2023 +0200 +++ b/OrthancFramework/Sources/MetricsRegistry.cpp Wed Jun 28 11:11:47 2023 +0200 @@ -28,6 +28,8 @@ #include "Compatibility.h" #include "OrthancException.h" +#include <boost/math/special_functions/round.hpp> + namespace Orthanc { static const boost::posix_time::ptime GetNow() @@ -35,135 +37,277 @@ return boost::posix_time::microsec_clock::universal_time(); } + namespace + { + template <typename T> + class TimestampedValue : public boost::noncopyable + { + private: + boost::posix_time::ptime time_; + bool hasValue_; + T value_; + + void SetValue(const T& value, + const boost::posix_time::ptime& now) + { + hasValue_ = true; + value_ = value; + time_ = now; + } + + bool IsLargerOverPeriod(const T& value, + int duration, + const boost::posix_time::ptime& now) const + { + if (hasValue_) + { + return (value > value_ || + (now - time_).total_seconds() > duration /* old value has expired */); + } + else + { + return true; // No value yet + } + } + + bool IsSmallerOverPeriod(const T& value, + int duration, + const boost::posix_time::ptime& now) const + { + if (hasValue_) + { + return (value < value_ || + (now - time_).total_seconds() > duration /* old value has expired */); + } + else + { + return true; // No value yet + } + } + + public: + explicit TimestampedValue() : + hasValue_(false), + value_(0) + { + } + + void Update(const T& value, + const MetricsUpdatePolicy& policy) + { + const boost::posix_time::ptime now = GetNow(); + + switch (policy) + { + case MetricsUpdatePolicy_Directly: + SetValue(value, now); + break; + + case MetricsUpdatePolicy_MaxOver10Seconds: + if (IsLargerOverPeriod(value, 10, now)) + { + SetValue(value, now); + } + break; + + case MetricsUpdatePolicy_MaxOver1Minute: + if (IsLargerOverPeriod(value, 60, now)) + { + SetValue(value, now); + } + break; + + case MetricsUpdatePolicy_MinOver10Seconds: + if (IsSmallerOverPeriod(value, 10, now)) + { + SetValue(value, now); + } + break; + + case MetricsUpdatePolicy_MinOver1Minute: + if (IsSmallerOverPeriod(value, 60, now)) + { + SetValue(value, now); + } + break; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } + + void Increment(const T& delta) + { + if (hasValue_) + { + value_ += delta; + } + else + { + value_ = delta; + } + } + + bool HasValue() const + { + return hasValue_; + } + + const boost::posix_time::ptime& GetTime() const + { + if (hasValue_) + { + return time_; + } + else + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + } + + const T& GetValue() const + { + if (hasValue_) + { + return value_; + } + else + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + } + }; + } + + class MetricsRegistry::Item : public boost::noncopyable { private: - MetricsUpdate update_; - boost::posix_time::ptime time_; - bool hasValue_; - int64_t value_; + MetricsUpdatePolicy policy_; - void SetValue(int64_t value, - const boost::posix_time::ptime& now) - { - hasValue_ = true; - value_ = value; - time_ = now; - } - - bool IsLargerOverPeriod(int64_t value, - int duration, - const boost::posix_time::ptime& now) const + public: + Item(MetricsUpdatePolicy policy) : + policy_(policy) { - if (hasValue_) - { - return (value > value_ || - (now - time_).total_seconds() > duration /* old value has expired */); - } - else - { - return true; // No value yet - } } - - bool IsSmallerOverPeriod(int64_t value, - int duration, - const boost::posix_time::ptime& now) const - { - if (hasValue_) - { - return (value < value_ || - (now - time_).total_seconds() > duration /* old value has expired */); - } - else - { - return true; // No value yet - } - } - - public: - explicit Item(MetricsUpdate update) : - update_(update), - hasValue_(false), - value_(0) + + virtual ~Item() { } - MetricsUpdate GetUpdate() const + MetricsUpdatePolicy GetPolicy() const { - return update_; + return policy_; + } + + virtual void UpdateFloat(float value) = 0; + + virtual void UpdateInteger(int64_t value) = 0; + + virtual void IncrementInteger(int64_t delta) = 0; + + virtual MetricsDataType GetDataType() const = 0; + + virtual bool HasValue() const = 0; + + virtual const boost::posix_time::ptime& GetTime() const = 0; + + virtual std::string FormatValue() const = 0; + }; + + + class MetricsRegistry::FloatItem : public Item + { + private: + TimestampedValue<float> value_; + + public: + FloatItem(MetricsUpdatePolicy policy) : + Item(policy) + { + } + + virtual void UpdateFloat(float value) ORTHANC_OVERRIDE + { + value_.Update(value, GetPolicy()); + } + + virtual void UpdateInteger(int64_t value) ORTHANC_OVERRIDE + { + value_.Update(static_cast<float>(value), GetPolicy()); + } + + virtual void IncrementInteger(int64_t delta) ORTHANC_OVERRIDE + { + value_.Increment(static_cast<float>(delta)); + } + + virtual MetricsDataType GetDataType() const ORTHANC_OVERRIDE + { + return MetricsDataType_Float; + } + + virtual bool HasValue() const ORTHANC_OVERRIDE + { + return value_.HasValue(); } - void Update(int64_t value) + virtual const boost::posix_time::ptime& GetTime() const ORTHANC_OVERRIDE { - const boost::posix_time::ptime now = GetNow(); - - switch (update_) - { - case MetricsUpdate_Directly: - SetValue(value, now); - break; - - case MetricsUpdate_MaxOver10Seconds: - if (IsLargerOverPeriod(value, 10, now)) - { - SetValue(value, now); - } - break; + return value_.GetTime(); + } + + virtual std::string FormatValue() const ORTHANC_OVERRIDE + { + return boost::lexical_cast<std::string>(value_.GetValue()); + } + }; - case MetricsUpdate_MaxOver1Minute: - if (IsLargerOverPeriod(value, 60, now)) - { - SetValue(value, now); - } - break; + + class MetricsRegistry::IntegerItem : public Item + { + private: + TimestampedValue<int64_t> value_; - case MetricsUpdate_MinOver10Seconds: - if (IsSmallerOverPeriod(value, 10, now)) - { - SetValue(value, now); - } - break; - - case MetricsUpdate_MinOver1Minute: - if (IsSmallerOverPeriod(value, 60, now)) - { - SetValue(value, now); - } - break; - - default: - throw OrthancException(ErrorCode_NotImplemented); - } + public: + IntegerItem(MetricsUpdatePolicy policy) : + Item(policy) + { + } + + virtual void UpdateFloat(float value) ORTHANC_OVERRIDE + { + value_.Update(boost::math::llround(value), GetPolicy()); } - bool HasValue() const + virtual void UpdateInteger(int64_t value) ORTHANC_OVERRIDE { - return hasValue_; + value_.Update(value, GetPolicy()); + } + + virtual void IncrementInteger(int64_t delta) ORTHANC_OVERRIDE + { + value_.Increment(delta); } - const boost::posix_time::ptime& GetTime() const + virtual MetricsDataType GetDataType() const ORTHANC_OVERRIDE { - if (hasValue_) - { - return time_; - } - else - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } + return MetricsDataType_Integer; } - int64_t GetValue() const + virtual bool HasValue() const ORTHANC_OVERRIDE + { + return value_.HasValue(); + } + + virtual const boost::posix_time::ptime& GetTime() const ORTHANC_OVERRIDE { - if (hasValue_) - { - return value_; - } - else - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } + return value_.GetTime(); + } + + virtual std::string FormatValue() const ORTHANC_OVERRIDE + { + return boost::lexical_cast<std::string>(value_.GetValue()); } }; @@ -191,39 +335,46 @@ void MetricsRegistry::Register(const std::string& name, - MetricsUpdate update) + MetricsUpdatePolicy policy, + MetricsDataType type) { boost::mutex::scoped_lock lock(mutex_); - Content::iterator found = content_.find(name); - - if (found == content_.end()) + if (content_.find(name) != content_.end()) { - content_[name] = new Item(update); + throw OrthancException(ErrorCode_BadSequenceOfCalls, "Cannot register twice the same metrics: " + name); } else { - assert(found->second != NULL); - - // This metrics already exists: Only recreate it if there is a - // mismatch in the type of metrics - if (found->second->GetUpdate() != update) - { - delete found->second; - found->second = new Item(update); - } - } + GetItemInternal(name, policy, type); + } } MetricsRegistry::Item& MetricsRegistry::GetItemInternal(const std::string& name, - MetricsUpdate update) + MetricsUpdatePolicy policy, + MetricsDataType type) { Content::iterator found = content_.find(name); if (found == content_.end()) { - Item* item = new Item(update); + Item* item = NULL; + + switch (type) + { + case MetricsDataType_Float: + item = new FloatItem(policy); + break; + + case MetricsDataType_Integer: + item = new IntegerItem(policy); + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + content_[name] = item; return *item; } @@ -240,45 +391,49 @@ } - void MetricsRegistry::SetValue(const std::string &name, - int64_t value, - MetricsUpdate update) + void MetricsRegistry::SetFloatValue(const std::string& name, + float value, + MetricsUpdatePolicy policy) { // Inlining to avoid loosing time if metrics are disabled if (enabled_) { boost::mutex::scoped_lock lock(mutex_); - GetItemInternal(name, update).Update(value); + GetItemInternal(name, policy, MetricsDataType_Float).UpdateFloat(value); } } - + - void MetricsRegistry::IncrementValue(const std::string &name, - int64_t delta) + void MetricsRegistry::SetIntegerValue(const std::string &name, + int64_t value, + MetricsUpdatePolicy policy) { // Inlining to avoid loosing time if metrics are disabled if (enabled_) { boost::mutex::scoped_lock lock(mutex_); - Item& item = GetItemInternal(name, MetricsUpdate_Directly); - - if (item.HasValue()) - { - item.Update(item.GetValue() + delta); - } - else - { - item.Update(delta); - } + GetItemInternal(name, policy, MetricsDataType_Integer).UpdateInteger(value); } } - MetricsUpdate MetricsRegistry::GetMetricsUpdate(const std::string& name) + void MetricsRegistry::IncrementIntegerValue(const std::string &name, + int64_t delta) + { + // Inlining to avoid loosing time if metrics are disabled + if (enabled_) + { + boost::mutex::scoped_lock lock(mutex_); + GetItemInternal(name, MetricsUpdatePolicy_Directly, MetricsDataType_Integer).IncrementInteger(delta); + } + } + + + MetricsUpdatePolicy MetricsRegistry::GetUpdatePolicy(const std::string& metrics) { boost::mutex::scoped_lock lock(mutex_); - Content::const_iterator found = content_.find(name); + Content::const_iterator found = content_.find(metrics); if (found == content_.end()) { @@ -287,7 +442,25 @@ else { assert(found->second != NULL); - return found->second->GetUpdate(); + return found->second->GetPolicy(); + } + } + + + MetricsDataType MetricsRegistry::GetDataType(const std::string& metrics) + { + boost::mutex::scoped_lock lock(mutex_); + + Content::const_iterator found = content_.find(metrics); + + if (found == content_.end()) + { + throw OrthancException(ErrorCode_InexistentItem); + } + else + { + assert(found->second != NULL); + return found->second->GetDataType(); } } @@ -318,7 +491,7 @@ boost::posix_time::time_duration diff = it->second->GetTime() - EPOCH; std::string line = (it->first + " " + - boost::lexical_cast<std::string>(it->second->GetValue()) + " " + + it->second->FormatValue() + " " + boost::lexical_cast<std::string>(diff.total_milliseconds()) + "\n"); buffer.AddChunk(line); @@ -331,7 +504,7 @@ MetricsRegistry::SharedMetrics::SharedMetrics(MetricsRegistry ®istry, const std::string &name, - MetricsUpdate update) : + MetricsUpdatePolicy policy) : registry_(registry), name_(name), value_(0) @@ -342,7 +515,7 @@ { boost::mutex::scoped_lock lock(mutex_); value_ += delta; - registry_.SetValue(name_, value_); + registry_.SetIntegerValue(name_, value_); } @@ -376,7 +549,7 @@ const std::string &name) : registry_(registry), name_(name), - update_(MetricsUpdate_MaxOver10Seconds) + policy_(MetricsUpdatePolicy_MaxOver10Seconds) { Start(); } @@ -384,10 +557,10 @@ MetricsRegistry::Timer::Timer(MetricsRegistry ®istry, const std::string &name, - MetricsUpdate update) : + MetricsUpdatePolicy policy) : registry_(registry), name_(name), - update_(update) + policy_(policy) { Start(); } @@ -398,8 +571,7 @@ if (active_) { boost::posix_time::time_duration diff = GetNow() - start_; - registry_.SetValue( - name_, static_cast<int64_t>(diff.total_milliseconds()), update_); + registry_.SetIntegerValue(name_, static_cast<int64_t>(diff.total_milliseconds()), policy_); } } }
--- a/OrthancFramework/Sources/MetricsRegistry.h Wed Jun 28 11:11:34 2023 +0200 +++ b/OrthancFramework/Sources/MetricsRegistry.h Wed Jun 28 11:11:47 2023 +0200 @@ -39,19 +39,27 @@ namespace Orthanc { - enum MetricsUpdate + enum MetricsUpdatePolicy { - MetricsUpdate_Directly, - MetricsUpdate_MaxOver10Seconds, - MetricsUpdate_MaxOver1Minute, - MetricsUpdate_MinOver10Seconds, - MetricsUpdate_MinOver1Minute + MetricsUpdatePolicy_Directly, + MetricsUpdatePolicy_MaxOver10Seconds, + MetricsUpdatePolicy_MaxOver1Minute, + MetricsUpdatePolicy_MinOver10Seconds, + MetricsUpdatePolicy_MinOver1Minute + }; + + enum MetricsDataType + { + MetricsDataType_Float, + MetricsDataType_Integer }; class ORTHANC_PUBLIC MetricsRegistry : public boost::noncopyable { private: class Item; + class FloatItem; + class IntegerItem; typedef std::map<std::string, Item*> Content; @@ -61,7 +69,8 @@ // The mutex must be locked Item& GetItemInternal(const std::string& name, - MetricsUpdate update); + MetricsUpdatePolicy policy, + MetricsDataType type); public: MetricsRegistry(); @@ -73,22 +82,35 @@ void SetEnabled(bool enabled); void Register(const std::string& name, - MetricsUpdate update); + MetricsUpdatePolicy policy, + MetricsDataType type); - void SetValue(const std::string& name, - int64_t value, - MetricsUpdate update); + void SetFloatValue(const std::string& name, + float value, + MetricsUpdatePolicy policy /* only used if this is a new metrics */); + + void SetFloatValue(const std::string& name, + float value) + { + SetFloatValue(name, value, MetricsUpdatePolicy_Directly); + } - void SetValue(const std::string& name, - int64_t value) + void SetIntegerValue(const std::string& name, + int64_t value, + MetricsUpdatePolicy policy /* only used if this is a new metrics */); + + void SetIntegerValue(const std::string& name, + int64_t value) { - SetValue(name, value, MetricsUpdate_Directly); + SetIntegerValue(name, value, MetricsUpdatePolicy_Directly); } + + void IncrementIntegerValue(const std::string& name, + int64_t delta); - void IncrementValue(const std::string& name, - int64_t delta); + MetricsUpdatePolicy GetUpdatePolicy(const std::string& metrics); - MetricsUpdate GetMetricsUpdate(const std::string& name); + MetricsDataType GetDataType(const std::string& metrics); // https://prometheus.io/docs/instrumenting/exposition_formats/#text-based-format void ExportPrometheusText(std::string& s); @@ -105,7 +127,7 @@ public: SharedMetrics(MetricsRegistry& registry, const std::string& name, - MetricsUpdate update); + MetricsUpdatePolicy policy); void Add(int64_t delta); }; @@ -128,7 +150,7 @@ private: MetricsRegistry& registry_; std::string name_; - MetricsUpdate update_; + MetricsUpdatePolicy policy_; bool active_; boost::posix_time::ptime start_; @@ -140,7 +162,7 @@ Timer(MetricsRegistry& registry, const std::string& name, - MetricsUpdate update); + MetricsUpdatePolicy policy); ~Timer(); };
--- a/OrthancFramework/UnitTestsSources/FrameworkTests.cpp Wed Jun 28 11:11:34 2023 +0200 +++ b/OrthancFramework/UnitTestsSources/FrameworkTests.cpp Wed Jun 28 11:11:47 2023 +0200 @@ -1312,7 +1312,7 @@ { MetricsRegistry m; m.SetEnabled(false); - m.SetValue("hello.world", 42); + m.SetIntegerValue("hello.world", 42); std::string s; m.ExportPrometheusText(s); @@ -1321,7 +1321,7 @@ { MetricsRegistry m; - m.Register("hello.world", MetricsUpdate_Directly); + m.Register("hello.world", MetricsUpdatePolicy_Directly, MetricsDataType_Integer); std::string s; m.ExportPrometheusText(s); @@ -1330,9 +1330,9 @@ { MetricsRegistry m; - m.SetValue("hello.world", -42); - ASSERT_EQ(MetricsUpdate_Directly, m.GetMetricsUpdate("hello.world")); - ASSERT_THROW(m.GetMetricsUpdate("nope"), OrthancException); + m.SetIntegerValue("hello.world", -42); + ASSERT_EQ(MetricsUpdatePolicy_Directly, m.GetUpdatePolicy("hello.world")); + ASSERT_THROW(m.GetUpdatePolicy("nope"), OrthancException); std::string s; m.ExportPrometheusText(s); @@ -1346,27 +1346,27 @@ { MetricsRegistry m; - m.Register("hello.max", MetricsUpdate_MaxOver10Seconds); - m.SetValue("hello.max", 10); - m.SetValue("hello.max", 20); - m.SetValue("hello.max", -10); - m.SetValue("hello.max", 5); + m.Register("hello.max", MetricsUpdatePolicy_MaxOver10Seconds, MetricsDataType_Integer); + m.SetIntegerValue("hello.max", 10); + m.SetIntegerValue("hello.max", 20); + m.SetIntegerValue("hello.max", -10); + m.SetIntegerValue("hello.max", 5); - m.Register("hello.min", MetricsUpdate_MinOver10Seconds); - m.SetValue("hello.min", 10); - m.SetValue("hello.min", 20); - m.SetValue("hello.min", -10); - m.SetValue("hello.min", 5); + m.Register("hello.min", MetricsUpdatePolicy_MinOver10Seconds, MetricsDataType_Integer); + m.SetIntegerValue("hello.min", 10); + m.SetIntegerValue("hello.min", 20); + m.SetIntegerValue("hello.min", -10); + m.SetIntegerValue("hello.min", 5); - m.Register("hello.directly", MetricsUpdate_Directly); - m.SetValue("hello.directly", 10); - m.SetValue("hello.directly", 20); - m.SetValue("hello.directly", -10); - m.SetValue("hello.directly", 5); + m.Register("hello.directly", MetricsUpdatePolicy_Directly, MetricsDataType_Integer); + m.SetIntegerValue("hello.directly", 10); + m.SetIntegerValue("hello.directly", 20); + m.SetIntegerValue("hello.directly", -10); + m.SetIntegerValue("hello.directly", 5); - ASSERT_EQ(MetricsUpdate_MaxOver10Seconds, m.GetMetricsUpdate("hello.max")); - ASSERT_EQ(MetricsUpdate_MinOver10Seconds, m.GetMetricsUpdate("hello.min")); - ASSERT_EQ(MetricsUpdate_Directly, m.GetMetricsUpdate("hello.directly")); + ASSERT_EQ(MetricsUpdatePolicy_MaxOver10Seconds, m.GetUpdatePolicy("hello.max")); + ASSERT_EQ(MetricsUpdatePolicy_MinOver10Seconds, m.GetUpdatePolicy("hello.min")); + ASSERT_EQ(MetricsUpdatePolicy_Directly, m.GetUpdatePolicy("hello.directly")); std::string s; m.ExportPrometheusText(s); @@ -1392,19 +1392,19 @@ { MetricsRegistry m; - m.SetValue("a", 10); - m.SetValue("b", 10, MetricsUpdate_MinOver10Seconds); + m.SetIntegerValue("a", 10); + m.SetIntegerValue("b", 10, MetricsUpdatePolicy_MinOver10Seconds); - m.Register("c", MetricsUpdate_MaxOver10Seconds); - m.SetValue("c", 10, MetricsUpdate_MinOver10Seconds); + m.Register("c", MetricsUpdatePolicy_MaxOver10Seconds, MetricsDataType_Integer); + m.SetIntegerValue("c", 10, MetricsUpdatePolicy_MinOver10Seconds); - m.Register("d", MetricsUpdate_MaxOver10Seconds); - m.Register("d", MetricsUpdate_Directly); + m.Register("d", MetricsUpdatePolicy_MaxOver10Seconds, MetricsDataType_Integer); + ASSERT_THROW(m.Register("d", MetricsUpdatePolicy_Directly, MetricsDataType_Integer), OrthancException); - ASSERT_EQ(MetricsUpdate_Directly, m.GetMetricsUpdate("a")); - ASSERT_EQ(MetricsUpdate_MinOver10Seconds, m.GetMetricsUpdate("b")); - ASSERT_EQ(MetricsUpdate_MaxOver10Seconds, m.GetMetricsUpdate("c")); - ASSERT_EQ(MetricsUpdate_Directly, m.GetMetricsUpdate("d")); + ASSERT_EQ(MetricsUpdatePolicy_Directly, m.GetUpdatePolicy("a")); + ASSERT_EQ(MetricsUpdatePolicy_MinOver10Seconds, m.GetUpdatePolicy("b")); + ASSERT_EQ(MetricsUpdatePolicy_MaxOver10Seconds, m.GetUpdatePolicy("c")); + ASSERT_EQ(MetricsUpdatePolicy_MaxOver10Seconds, m.GetUpdatePolicy("d")); } { @@ -1412,11 +1412,47 @@ { MetricsRegistry::Timer t1(m, "a"); - MetricsRegistry::Timer t2(m, "b", MetricsUpdate_MinOver10Seconds); + MetricsRegistry::Timer t2(m, "b", MetricsUpdatePolicy_MinOver10Seconds); } - ASSERT_EQ(MetricsUpdate_MaxOver10Seconds, m.GetMetricsUpdate("a")); - ASSERT_EQ(MetricsUpdate_MinOver10Seconds, m.GetMetricsUpdate("b")); + ASSERT_EQ(MetricsUpdatePolicy_MaxOver10Seconds, m.GetUpdatePolicy("a")); + ASSERT_EQ(MetricsUpdatePolicy_MinOver10Seconds, m.GetUpdatePolicy("b")); + } + + { + MetricsRegistry m; + m.Register("c", MetricsUpdatePolicy_MaxOver10Seconds, MetricsDataType_Integer); + m.SetFloatValue("c", 100, MetricsUpdatePolicy_MinOver10Seconds); + + ASSERT_EQ(MetricsUpdatePolicy_MaxOver10Seconds, m.GetUpdatePolicy("c")); + ASSERT_EQ(MetricsDataType_Integer, m.GetDataType("c")); + } + + { + MetricsRegistry m; + m.Register("c", MetricsUpdatePolicy_MaxOver10Seconds, MetricsDataType_Float); + m.SetIntegerValue("c", 100, MetricsUpdatePolicy_MinOver10Seconds); + + ASSERT_EQ(MetricsUpdatePolicy_MaxOver10Seconds, m.GetUpdatePolicy("c")); + ASSERT_EQ(MetricsDataType_Float, m.GetDataType("c")); + } + + { + MetricsRegistry m; + m.SetIntegerValue("c", 100, MetricsUpdatePolicy_MinOver10Seconds); + m.SetFloatValue("c", 101, MetricsUpdatePolicy_MaxOver10Seconds); + + ASSERT_EQ(MetricsUpdatePolicy_MinOver10Seconds, m.GetUpdatePolicy("c")); + ASSERT_EQ(MetricsDataType_Integer, m.GetDataType("c")); + } + + { + MetricsRegistry m; + m.SetIntegerValue("c", 100); + m.SetFloatValue("c", 101, MetricsUpdatePolicy_MaxOver10Seconds); + + ASSERT_EQ(MetricsUpdatePolicy_Directly, m.GetUpdatePolicy("c")); + ASSERT_EQ(MetricsDataType_Integer, m.GetDataType("c")); } } #endif
--- a/OrthancFramework/UnitTestsSources/RestApiTests.cpp Wed Jun 28 11:11:34 2023 +0200 +++ b/OrthancFramework/UnitTestsSources/RestApiTests.cpp Wed Jun 28 11:11:47 2023 +0200 @@ -384,6 +384,7 @@ private: std::string type_; std::string subtype_; + HttpContentNegociation::Dictionary parameters_; public: AcceptHandler() @@ -393,7 +394,8 @@ void Reset() { - Handle("nope", "nope"); + HttpContentNegociation::Dictionary parameters; + Handle("nope", "nope", parameters); } const std::string& GetType() const @@ -406,11 +408,18 @@ return subtype_; } + HttpContentNegociation::Dictionary& GetParameters() + { + return parameters_; + } + virtual void Handle(const std::string& type, - const std::string& subtype) ORTHANC_OVERRIDE + const std::string& subtype, + const HttpContentNegociation::Dictionary& parameters) ORTHANC_OVERRIDE { type_ = type; subtype_ = subtype; + parameters_ = parameters; } }; } @@ -430,22 +439,29 @@ ASSERT_TRUE(d.Apply("audio/*; q=0.2, audio/basic")); ASSERT_EQ("audio", h.GetType()); ASSERT_EQ("basic", h.GetSubType()); + ASSERT_EQ(0u, h.GetParameters().size()); - ASSERT_TRUE(d.Apply("audio/*; q=0.2, audio/nope")); + ASSERT_TRUE(d.Apply("audio/*; q=0.2 ; type = test ; hello , audio/nope")); ASSERT_EQ("audio", h.GetType()); ASSERT_EQ("mp3", h.GetSubType()); + ASSERT_EQ(3u, h.GetParameters().size()); + ASSERT_EQ("0.2", h.GetParameters() ["q"]); + ASSERT_EQ("test", h.GetParameters() ["type"]); + ASSERT_EQ("", h.GetParameters() ["hello"]); ASSERT_FALSE(d.Apply("application/*; q=0.2, application/pdf")); - ASSERT_TRUE(d.Apply("*/*; application/*; q=0.2, application/pdf")); + ASSERT_TRUE(d.Apply("*/*; hello=world, application/*; q=0.2, application/pdf")); ASSERT_EQ("audio", h.GetType()); + ASSERT_EQ(1u, h.GetParameters().size()); + ASSERT_EQ("world", h.GetParameters() ["hello"]); } // "This would be interpreted as "text/html and text/x-c are the // preferred media types, but if they do not exist, then send the // text/x-dvi entity, and if that does not exist, send the // text/plain entity."" - const std::string T1 = "text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c"; + const std::string T1 = "text/plain; q=0.5, text/html ; hello = \"world\" , text/x-dvi; q=0.8, text/x-c"; { HttpContentNegociation d; @@ -455,6 +471,8 @@ ASSERT_TRUE(d.Apply(T1)); ASSERT_EQ("text", h.GetType()); ASSERT_EQ("html", h.GetSubType()); + ASSERT_EQ(1u, h.GetParameters().size()); + ASSERT_EQ("world", h.GetParameters() ["hello"]); } { @@ -465,6 +483,7 @@ ASSERT_TRUE(d.Apply(T1)); ASSERT_EQ("text", h.GetType()); ASSERT_EQ("x-c", h.GetSubType()); + ASSERT_EQ(0u, h.GetParameters().size()); } { @@ -476,6 +495,15 @@ ASSERT_TRUE(d.Apply(T1)); ASSERT_EQ("text", h.GetType()); ASSERT_TRUE(h.GetSubType() == "x-c" || h.GetSubType() == "html"); + if (h.GetSubType() == "x-c") + { + ASSERT_EQ(0u, h.GetParameters().size()); + } + else + { + ASSERT_EQ(1u, h.GetParameters().size()); + ASSERT_EQ("world", h.GetParameters() ["hello"]); + } } { @@ -485,6 +513,8 @@ ASSERT_TRUE(d.Apply(T1)); ASSERT_EQ("text", h.GetType()); ASSERT_EQ("x-dvi", h.GetSubType()); + ASSERT_EQ(1u, h.GetParameters().size()); + ASSERT_EQ("0.8", h.GetParameters() ["q"]); } { @@ -493,6 +523,51 @@ ASSERT_TRUE(d.Apply(T1)); ASSERT_EQ("text", h.GetType()); ASSERT_EQ("plain", h.GetSubType()); + ASSERT_EQ(1u, h.GetParameters().size()); + ASSERT_EQ("0.5", h.GetParameters() ["q"]); + } + + // Below are the tests from issue 216: + // https://bugs.orthanc-server.com/show_bug.cgi?id=216 + + { + HttpContentNegociation d; + d.Register("application/dicom+json", h); + ASSERT_TRUE(d.Apply("image/webp, */*;q=0.8, text/html, application/xhtml+xml, application/xml;q=0.9")); + ASSERT_EQ("application", h.GetType()); + ASSERT_EQ("dicom+json", h.GetSubType()); + ASSERT_EQ(1u, h.GetParameters().size()); + ASSERT_EQ("0.8", h.GetParameters() ["q"]); + } + + { + HttpContentNegociation d; + d.Register("application/dicom+json", h); + ASSERT_TRUE(d.Apply("image/webp, */*; q = \"0.8\" , text/html, application/xhtml+xml, application/xml;q=0.9")); + ASSERT_EQ("application", h.GetType()); + ASSERT_EQ("dicom+json", h.GetSubType()); + ASSERT_EQ(1u, h.GetParameters().size()); + ASSERT_EQ("0.8", h.GetParameters() ["q"]); + } + + { + HttpContentNegociation d; + d.Register("application/dicom+json", h); + ASSERT_TRUE(d.Apply("text/html, application/xhtml+xml, application/xml, image/webp, */*;q=0.8")); + ASSERT_EQ("application", h.GetType()); + ASSERT_EQ("dicom+json", h.GetSubType()); + ASSERT_EQ(1u, h.GetParameters().size()); + ASSERT_EQ("0.8", h.GetParameters() ["q"]); + } + + { + HttpContentNegociation d; + d.Register("application/dicom+json", h); + ASSERT_TRUE(d.Apply("text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2")); + ASSERT_EQ("application", h.GetType()); + ASSERT_EQ("dicom+json", h.GetSubType()); + ASSERT_EQ(1u, h.GetParameters().size()); + ASSERT_EQ(".2", h.GetParameters() ["q"]); } }
--- a/OrthancServer/Plugins/Engine/OrthancPlugins.cpp Wed Jun 28 11:11:34 2023 +0200 +++ b/OrthancServer/Plugins/Engine/OrthancPlugins.cpp Wed Jun 28 11:11:47 2023 +0200 @@ -5299,8 +5299,7 @@ { PImpl::ServerContextLock lock(*pimpl_); - lock.GetContext().GetMetricsRegistry().SetValue(p.name, boost::math::llround(p.value), - Plugins::Convert(p.type)); + lock.GetContext().GetMetricsRegistry().SetFloatValue(p.name, p.value, Plugins::Convert(p.type)); } return true; @@ -5313,7 +5312,7 @@ { PImpl::ServerContextLock lock(*pimpl_); - lock.GetContext().GetMetricsRegistry().SetValue(p.name, p.value, Plugins::Convert(p.type)); + lock.GetContext().GetMetricsRegistry().SetIntegerValue(p.name, p.value, Plugins::Convert(p.type)); } return true;
--- a/OrthancServer/Plugins/Engine/PluginsEnumerations.cpp Wed Jun 28 11:11:34 2023 +0200 +++ b/OrthancServer/Plugins/Engine/PluginsEnumerations.cpp Wed Jun 28 11:11:47 2023 +0200 @@ -580,15 +580,15 @@ } - MetricsUpdate Convert(OrthancPluginMetricsType type) + MetricsUpdatePolicy Convert(OrthancPluginMetricsType type) { switch (type) { case OrthancPluginMetricsType_Default: - return MetricsUpdate_Directly; + return MetricsUpdatePolicy_Directly; case OrthancPluginMetricsType_Timer: - return MetricsUpdate_MaxOver10Seconds; + return MetricsUpdatePolicy_MaxOver10Seconds; default: throw OrthancException(ErrorCode_ParameterOutOfRange);
--- a/OrthancServer/Plugins/Engine/PluginsEnumerations.h Wed Jun 28 11:11:34 2023 +0200 +++ b/OrthancServer/Plugins/Engine/PluginsEnumerations.h Wed Jun 28 11:11:47 2023 +0200 @@ -73,7 +73,7 @@ StorageCommitmentFailureReason Convert(OrthancPluginStorageCommitmentFailureReason reason); - MetricsUpdate Convert(OrthancPluginMetricsType type); + MetricsUpdatePolicy Convert(OrthancPluginMetricsType type); } }
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestApi.cpp Wed Jun 28 11:11:34 2023 +0200 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestApi.cpp Wed Jun 28 11:11:47 2023 +0200 @@ -259,7 +259,7 @@ resetRequestReceived_(false), activeRequests_(context.GetMetricsRegistry(), "orthanc_rest_api_active_requests", - MetricsUpdate_MaxOver10Seconds) + MetricsUpdatePolicy_MaxOver10Seconds) { RegisterSystem(orthancExplorerEnabled);
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp Wed Jun 28 11:11:34 2023 +0200 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp Wed Jun 28 11:11:47 2023 +0200 @@ -639,7 +639,8 @@ } virtual void Handle(const std::string& type, - const std::string& subtype) ORTHANC_OVERRIDE + const std::string& subtype, + const HttpContentNegociation::Dictionary& parameters) ORTHANC_OVERRIDE { assert(type == "image"); assert(subtype == "png"); @@ -658,7 +659,8 @@ } virtual void Handle(const std::string& type, - const std::string& subtype) ORTHANC_OVERRIDE + const std::string& subtype, + const HttpContentNegociation::Dictionary& parameters) ORTHANC_OVERRIDE { assert(type == "image"); assert(subtype == "x-portable-arbitrarymap"); @@ -698,7 +700,8 @@ } virtual void Handle(const std::string& type, - const std::string& subtype) ORTHANC_OVERRIDE + const std::string& subtype, + const HttpContentNegociation::Dictionary& parameters) ORTHANC_OVERRIDE { assert(type == "image"); assert(subtype == "jpeg");
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestSystem.cpp Wed Jun 28 11:11:34 2023 +0200 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestSystem.cpp Wed Jun 28 11:11:47 2023 +0200 @@ -32,7 +32,6 @@ #include "../ServerContext.h" #include <boost/algorithm/string/predicate.hpp> -#include <boost/math/special_functions/round.hpp> namespace Orthanc @@ -914,19 +913,19 @@ context.GetIndex().GetLastChange(lastChange); MetricsRegistry& registry = context.GetMetricsRegistry(); - registry.SetValue("orthanc_disk_size_mb", boost::math::llround(static_cast<float>(diskSize) / MEGA_BYTES)); - registry.SetValue("orthanc_uncompressed_size_mb", boost::math::llround(static_cast<float>(diskSize) / MEGA_BYTES)); - registry.SetValue("orthanc_count_patients", static_cast<int64_t>(countPatients)); - registry.SetValue("orthanc_count_studies", static_cast<int64_t>(countStudies)); - registry.SetValue("orthanc_count_series", static_cast<int64_t>(countSeries)); - registry.SetValue("orthanc_count_instances", static_cast<int64_t>(countInstances)); - registry.SetValue("orthanc_jobs_pending", jobsPending); - registry.SetValue("orthanc_jobs_running", jobsRunning); - registry.SetValue("orthanc_jobs_completed", jobsSuccess + jobsFailed); - registry.SetValue("orthanc_jobs_success", jobsSuccess); - registry.SetValue("orthanc_jobs_failed", jobsFailed); - registry.SetValue("orthanc_up_time_s", serverUpTime); - registry.SetValue("orthanc_last_change", lastChange["Last"].asInt64()); + registry.SetFloatValue("orthanc_disk_size_mb", static_cast<float>(diskSize) / MEGA_BYTES); + registry.SetFloatValue("orthanc_uncompressed_size_mb", static_cast<float>(diskSize) / MEGA_BYTES); + registry.SetIntegerValue("orthanc_count_patients", static_cast<int64_t>(countPatients)); + registry.SetIntegerValue("orthanc_count_studies", static_cast<int64_t>(countStudies)); + registry.SetIntegerValue("orthanc_count_series", static_cast<int64_t>(countSeries)); + registry.SetIntegerValue("orthanc_count_instances", static_cast<int64_t>(countInstances)); + registry.SetIntegerValue("orthanc_jobs_pending", jobsPending); + registry.SetIntegerValue("orthanc_jobs_running", jobsRunning); + registry.SetIntegerValue("orthanc_jobs_completed", jobsSuccess + jobsFailed); + registry.SetIntegerValue("orthanc_jobs_success", jobsSuccess); + registry.SetIntegerValue("orthanc_jobs_failed", jobsFailed); + registry.SetIntegerValue("orthanc_up_time_s", serverUpTime); + registry.SetIntegerValue("orthanc_last_change", lastChange["Last"].asInt64()); std::string s; registry.ExportPrometheusText(s);
--- a/OrthancServer/Sources/ServerContext.cpp Wed Jun 28 11:11:34 2023 +0200 +++ b/OrthancServer/Sources/ServerContext.cpp Wed Jun 28 11:11:47 2023 +0200 @@ -46,7 +46,6 @@ #include "ServerToolbox.h" #include "StorageCommitmentReports.h" -#include <boost/math/special_functions/round.hpp> #include <dcmtk/dcmdata/dcfilefo.h> #include <dcmtk/dcmnet/dimse.h> @@ -298,9 +297,9 @@ void ServerContext::PublishDicomCacheMetrics() { - metricsRegistry_->SetValue("orthanc_dicom_cache_size", - boost::math::llround(static_cast<float>(dicomCache_.GetCurrentSize()) / static_cast<float>(1024 * 1024))); - metricsRegistry_->SetValue("orthanc_dicom_cache_count", dicomCache_.GetNumberOfItems()); + metricsRegistry_->SetFloatValue("orthanc_dicom_cache_size", + static_cast<float>(dicomCache_.GetCurrentSize()) / static_cast<float>(1024 * 1024)); + metricsRegistry_->SetIntegerValue("orthanc_dicom_cache_count", dicomCache_.GetNumberOfItems()); }
--- a/TODO Wed Jun 28 11:11:34 2023 +0200 +++ b/TODO Wed Jun 28 11:11:47 2023 +0200 @@ -37,9 +37,6 @@ * Add configurations to enable/disable warnings: - Modifying an instance while keeping its original SOPInstanceUID: This should be avoided! - Modifying a study while keeping its original StudyInstanceUID: This should be avoided! -* In Orthanc <= 1.12.0, all the metrics were floating-point. - Since Orthanc >= 1.12.1, all the metrics are integer. - => Add a data type in class MetricsRegistry. ============================