Mercurial > hg > orthanc-dicomweb
changeset 745:b4f6c00c343f
added 2 metrics for WADO-RS
| author | Alain Mazy <am@orthanc.team> |
|---|---|
| date | Fri, 13 Mar 2026 18:02:12 +0100 |
| parents | fce05c8b00fb |
| children | a760a8b2c64c |
| files | NEWS Plugin/Plugin.cpp Plugin/WadoRs.cpp Plugin/WadoRs.h Plugin/WeightedAverageMetrics.h Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h |
| diffstat | 7 files changed, 201 insertions(+), 3 deletions(-) [+] |
line wrap: on
line diff
--- a/NEWS Fri Feb 13 14:18:28 2026 +0100 +++ b/NEWS Fri Mar 13 18:02:12 2026 +0100 @@ -3,6 +3,14 @@ * If calling /rendered route on a Encapsulated PDF, the plugin will now return the pdf file instead of generating a 400 error. +* Added metrics: + - orthanc_dicomweb_wadors_average_bandwidth_per_call_mbytes_per_second_5m is the weighted + average bandwidth of each individual call to any WADO-RS route to retrieve instances, series + or studies. This metrics is only updated at the end of the call and averages the bandwidth + values that are also displayed in the logs if you set "EnablePerformanceLogs" to true. + - orthanc_dicomweb_wadors_total_bytes_transferred counts the number of bytes that have + been returned by any WADO-RS route to retrieve instances, series or studies. This is updated + while the response is returned. Version 1.22 (2025-12-02)
--- a/Plugin/Plugin.cpp Fri Feb 13 14:18:28 2026 +0100 +++ b/Plugin/Plugin.cpp Fri Mar 13 18:02:12 2026 +0100 @@ -466,6 +466,11 @@ #endif +static void RefreshMetricsCallback() +{ + RefreshWadoRsMetrics(); +} + static OrthancPluginErrorCode OnChangeCallback(OrthancPluginChangeType changeType, OrthancPluginResourceType resourceType, const char *resourceId) @@ -711,6 +716,7 @@ OrthancPluginRegisterOnChangeCallback(context, OnChangeCallback); + OrthancPluginRegisterRefreshMetricsCallback(context, RefreshMetricsCallback); // Extend the default Orthanc Explorer with custom JavaScript for STOW client std::string explorer;
--- a/Plugin/WadoRs.cpp Fri Feb 13 14:18:28 2026 +0100 +++ b/Plugin/WadoRs.cpp Fri Mar 13 18:02:12 2026 +0100 @@ -43,6 +43,7 @@ #include <boost/thread.hpp> #include <boost/lexical_cast.hpp> #include <boost/algorithm/string/predicate.hpp> +#include "WeightedAverageMetrics.h" static const std::string SERIES_METADATA_ATTACHMENT_ID = "4301"; static std::string WADO_BASE_PLACEHOLDER = "$WADO_BASE_PLACEHOLDER$"; @@ -59,7 +60,10 @@ static boost::mutex preloaderThreadsCounterMutex; static uint32_t preloaderThreadsCounter = 0; +static WeightedAverageMetrics<float> wadorsAverageBandwidth(300); +static boost::mutex wadoRsTotalBytesTransferredMutex; +static int64_t wadoRsTotalBytesTransferred = 0; void SetPluginCanUseExtendedFind(bool enable) { @@ -81,6 +85,12 @@ return isSystemReadOnly_; } +void RefreshWadoRsMetrics() +{ + OrthancPlugins::SetMetricsValue("orthanc_dicomweb_wadors_average_bandwidth_per_call_mbytes_per_second_5m", wadorsAverageBandwidth.GetAverage()); + OrthancPlugins::SetMetricsValue("orthanc_dicomweb_wadors_total_bytes_transferred", wadoRsTotalBytesTransferred); +} + static std::string GetResourceUri(Orthanc::ResourceType level, const std::string& publicId) { @@ -768,6 +778,9 @@ throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); } perfTotalSizeInBytes += dicom->GetSize(); + + boost::mutex::scoped_lock lock(wadoRsTotalBytesTransferredMutex); + wadoRsTotalBytesTransferred += static_cast<int64_t>(dicom->GetSize()); } else { @@ -775,9 +788,13 @@ } } + uint64_t elapsedMicrosends = perfTimer.GetElapsedMicroseconds(); + + float bandwidth = float(perfTotalSizeInBytes) / float(elapsedMicrosends) * 8.0f; // this gives a bandwidth in MBps + wadorsAverageBandwidth.AddValue(bandwidth, float(perfTotalSizeInBytes)); + if (OrthancPlugins::Configuration::IsPerformanceLogsEnabled()) { - uint64_t elapsedMicrosends = perfTimer.GetElapsedMicroseconds(); float instancesPerSeconds = float(perfTotalInstancesCount) / (float(elapsedMicrosends) / 1000000.0f); LOG(INFO) << "WADO-RS: elapsed: " << perfTimer.GetElapsedMicroseconds() << " us, rate: " << std::fixed << std::setprecision(2) << instancesPerSeconds << " instances/s, " << Orthanc::Toolbox::GetHumanTransferSpeed(false, perfTotalSizeInBytes, elapsedMicrosends * 1000); }
--- a/Plugin/WadoRs.h Fri Feb 13 14:18:28 2026 +0100 +++ b/Plugin/WadoRs.h Fri Mar 13 18:02:12 2026 +0100 @@ -118,4 +118,6 @@ void SetPluginCanUseExtendedFind(bool enable); -void SetSystemIsReadOnly(bool isReadOnly); \ No newline at end of file +void SetSystemIsReadOnly(bool isReadOnly); + +void RefreshWadoRsMetrics(); \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugin/WeightedAverageMetrics.h Fri Mar 13 18:02:12 2026 +0100 @@ -0,0 +1,118 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2026 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include <boost/thread/mutex.hpp> +#include <boost/date_time/posix_time/posix_time.hpp> +#include <deque> +#include <stdint.h> + + +template <typename T> +class WeightedAverageMetrics : public boost::noncopyable +{ + struct Value + { + boost::posix_time::ptime time_; + T value_; + T weight_; + + Value(const T& value, const T& weight) : + time_(boost::posix_time::microsec_clock::universal_time()), + value_(value), + weight_(weight) + { + } + }; + +private: + std::deque<Value> values_; + T totalWeightedValue_; + T totalWeight_; + int64_t duration_; + boost::mutex mutex_; + + void RemoveOldest() + { + boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time(); + + if (values_.size() > 0) + { + Value& oldest = values_.front(); + while ((now - oldest.time_).total_seconds() > duration_) + { + totalWeightedValue_ -= oldest.value_ * oldest.weight_; + totalWeight_ -= oldest.weight_; + values_.pop_front(); + if (values_.size() > 0) + { + oldest = values_.front(); + } + } + } + } + +public: + WeightedAverageMetrics(int64_t duration) : + totalWeightedValue_(0), + totalWeight_(0), + duration_(duration) + { + } + + void AddValue(const T& value, const T& weight) + { + boost::mutex::scoped_lock lock(mutex_); + + values_.push_back(Value(value, weight)); + totalWeightedValue_ += value * weight; + totalWeight_ += weight; + boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time(); + + const Value& oldest = values_.front(); + while ((now - oldest.time_).total_seconds() > duration_) + { + totalWeightedValue_ -= oldest.value_ * oldest.weight_; + totalWeight_ -= oldest.weight_; + values_.pop_front(); + } + } + + T GetAverage() + { + boost::mutex::scoped_lock lock(mutex_); + + RemoveOldest(); + + if (totalWeight_ > 0) + { + return totalWeightedValue_ / totalWeight_; + } + else + { + return 0; + } + } +}; +
--- a/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp Fri Feb 13 14:18:28 2026 +0100 +++ b/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp Fri Mar 13 18:02:12 2026 +0100 @@ -1544,11 +1544,25 @@ #endif /* HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1 */ + static void CheckAnswerSizeIsLessThan4GB(const std::string& answer) + { + if (answer.size() > static_cast<size_t>(std::numeric_limits<uint32_t>::max())) + { + #if HAS_ORTHANC_EXCEPTION == 1 + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange, "Cannot send HTTP response larger than 4GB"); + #else + ORTHANC_PLUGINS_LOG_ERROR("Cannot send HTTP response larger than 4GB"); + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange); + #endif + } + } + void AnswerJson(const Json::Value& value, OrthancPluginRestOutput* output) { std::string bodyString; - WriteStyledJson(bodyString, value); + WriteStyledJson(bodyString, value); + CheckAnswerSizeIsLessThan4GB(bodyString); OrthancPluginAnswerBuffer(GetGlobalContext(), output, bodyString.c_str(), bodyString.size(), "application/json"); } @@ -1556,6 +1570,7 @@ const char* mimeType, OrthancPluginRestOutput* output) { + CheckAnswerSizeIsLessThan4GB(answer); OrthancPluginAnswerBuffer(GetGlobalContext(), output, answer.c_str(), answer.size(), mimeType); } @@ -1564,6 +1579,26 @@ OrthancPluginSendHttpStatusCode(GetGlobalContext(), output, httpError); } + void AnswerHttpError(uint16_t httpError, + OrthancPluginRestOutput* output, + const std::string& answer, + const char* mimeType) + { + CheckAnswerSizeIsLessThan4GB(answer); + + OrthancPluginSetHttpHeader(GetGlobalContext(), + output, + "content-type", + mimeType); + + OrthancPluginSendHttpStatus(GetGlobalContext(), + output, + httpError, + answer.c_str(), + static_cast<uint32_t>(answer.size())); + } + + void AnswerMethodNotAllowed(OrthancPluginRestOutput *output, const char* allowedMethods) { OrthancPluginSendMethodNotAllowed(GetGlobalContext(), output, allowedMethods);
--- a/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h Fri Feb 13 14:18:28 2026 +0100 +++ b/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h Fri Mar 13 18:02:12 2026 +0100 @@ -690,6 +690,11 @@ void AnswerHttpError(uint16_t httpError, OrthancPluginRestOutput* output); + void AnswerHttpError(uint16_t httpError, + OrthancPluginRestOutput* output, + const std::string& answer, + const char* mimeType); + void AnswerMethodNotAllowed(OrthancPluginRestOutput* output, const char* allowedMethods); #if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 0) @@ -1026,6 +1031,13 @@ value, OrthancPluginMetricsType_Default); } + inline void SetMetricsValue(const char* name, + int64_t value) + { + OrthancPluginSetMetricsIntegerValue(GetGlobalContext(), name, + value, OrthancPluginMetricsType_Default); + } + class MetricsTimer : public boost::noncopyable { private:
