Orthanc/OrthancFramework/Sources/MetricsRegistry.cpp
2025-06-23 19:07:37 +05:30

582 lines
14 KiB
C++

/**
* 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-2025 Orthanc Team SRL, Belgium
* Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
*
* This program is free software: you can redistribute it and/or
* modify it under the terms of the GNU Lesser 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/>.
**/
#include "PrecompiledHeaders.h"
#include "MetricsRegistry.h"
#include "ChunkedBuffer.h"
#include "Compatibility.h"
#include "OrthancException.h"
#include <boost/math/special_functions/round.hpp>
namespace Orthanc
{
static const boost::posix_time::ptime GetNow()
{
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)
{
time_ = GetNow();
if (hasValue_)
{
value_ += delta;
}
else
{
value_ = delta;
hasValue_ = true;
}
}
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:
MetricsUpdatePolicy policy_;
public:
explicit Item(MetricsUpdatePolicy policy) :
policy_(policy)
{
}
virtual ~Item()
{
}
MetricsUpdatePolicy GetPolicy() const
{
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:
explicit 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();
}
virtual const boost::posix_time::ptime& GetTime() const ORTHANC_OVERRIDE
{
return value_.GetTime();
}
virtual std::string FormatValue() const ORTHANC_OVERRIDE
{
return boost::lexical_cast<std::string>(value_.GetValue());
}
};
class MetricsRegistry::IntegerItem : public Item
{
private:
TimestampedValue<int64_t> value_;
public:
explicit IntegerItem(MetricsUpdatePolicy policy) :
Item(policy)
{
}
virtual void UpdateFloat(float value) ORTHANC_OVERRIDE
{
value_.Update(boost::math::llround(value), GetPolicy());
}
virtual void UpdateInteger(int64_t value) ORTHANC_OVERRIDE
{
value_.Update(value, GetPolicy());
}
virtual void IncrementInteger(int64_t delta) ORTHANC_OVERRIDE
{
value_.Increment(delta);
}
virtual MetricsDataType GetDataType() const ORTHANC_OVERRIDE
{
return MetricsDataType_Integer;
}
virtual bool HasValue() const ORTHANC_OVERRIDE
{
return value_.HasValue();
}
virtual const boost::posix_time::ptime& GetTime() const ORTHANC_OVERRIDE
{
return value_.GetTime();
}
virtual std::string FormatValue() const ORTHANC_OVERRIDE
{
return boost::lexical_cast<std::string>(value_.GetValue());
}
};
MetricsRegistry::~MetricsRegistry()
{
for (Content::iterator it = content_.begin(); it != content_.end(); ++it)
{
assert(it->second != NULL);
delete it->second;
}
}
bool MetricsRegistry::IsEnabled() const
{
return enabled_;
}
void MetricsRegistry::SetEnabled(bool enabled)
{
boost::mutex::scoped_lock lock(mutex_);
enabled_ = enabled;
}
void MetricsRegistry::Register(const std::string& name,
MetricsUpdatePolicy policy,
MetricsDataType type)
{
boost::mutex::scoped_lock lock(mutex_);
if (content_.find(name) != content_.end())
{
throw OrthancException(ErrorCode_BadSequenceOfCalls, "Cannot register twice the same metrics: " + name);
}
else
{
GetItemInternal(name, policy, type);
}
}
MetricsRegistry::Item& MetricsRegistry::GetItemInternal(const std::string& name,
MetricsUpdatePolicy policy,
MetricsDataType type)
{
Content::iterator found = content_.find(name);
if (found == content_.end())
{
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;
}
else
{
assert(found->second != NULL);
return *found->second;
}
}
MetricsRegistry::MetricsRegistry() :
enabled_(true)
{
}
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, policy, MetricsDataType_Float).UpdateFloat(value);
}
}
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_);
GetItemInternal(name, policy, MetricsDataType_Integer).UpdateInteger(value);
}
}
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(metrics);
if (found == content_.end())
{
throw OrthancException(ErrorCode_InexistentItem);
}
else
{
assert(found->second != NULL);
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();
}
}
void MetricsRegistry::ExportPrometheusText(std::string& s)
{
// https://www.boost.org/doc/libs/1_69_0/doc/html/date_time/examples.html#date_time.examples.seconds_since_epoch
static const boost::posix_time::ptime EPOCH(boost::gregorian::date(1970, 1, 1));
boost::mutex::scoped_lock lock(mutex_);
s.clear();
if (!enabled_)
{
return;
}
ChunkedBuffer buffer;
for (Content::const_iterator it = content_.begin();
it != content_.end(); ++it)
{
assert(it->second != NULL);
if (it->second->HasValue())
{
boost::posix_time::time_duration diff = it->second->GetTime() - EPOCH;
std::string line = (it->first + " " +
it->second->FormatValue() + " " +
boost::lexical_cast<std::string>(diff.total_milliseconds()) + "\n");
buffer.AddChunk(line);
}
}
buffer.Flatten(s);
}
MetricsRegistry::SharedMetrics::SharedMetrics(MetricsRegistry &registry,
const std::string &name,
MetricsUpdatePolicy policy) :
registry_(registry),
name_(name),
value_(0)
{
}
void MetricsRegistry::SharedMetrics::Add(int64_t delta)
{
boost::mutex::scoped_lock lock(mutex_);
value_ += delta;
registry_.SetIntegerValue(name_, value_);
}
MetricsRegistry::ActiveCounter::ActiveCounter(MetricsRegistry::SharedMetrics &metrics) :
metrics_(metrics)
{
metrics_.Add(1);
}
MetricsRegistry::ActiveCounter::~ActiveCounter()
{
metrics_.Add(-1);
}
void MetricsRegistry::Timer::Start()
{
if (registry_.IsEnabled())
{
active_ = true;
start_ = GetNow();
}
else
{
active_ = false;
}
}
MetricsRegistry::Timer::Timer(MetricsRegistry &registry,
const std::string &name) :
registry_(registry),
name_(name),
policy_(MetricsUpdatePolicy_MaxOver10Seconds)
{
Start();
}
MetricsRegistry::Timer::Timer(MetricsRegistry &registry,
const std::string &name,
MetricsUpdatePolicy policy) :
registry_(registry),
name_(name),
policy_(policy)
{
Start();
}
MetricsRegistry::Timer::~Timer()
{
if (active_)
{
boost::posix_time::time_duration diff = GetNow() - start_;
registry_.SetIntegerValue(name_, static_cast<int64_t>(diff.total_milliseconds()), policy_);
}
}
}