1 /** 2 * Orthanc - A Lightweight, RESTful DICOM Store 3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics 4 * Department, University Hospital of Liege, Belgium 5 * Copyright (C) 2017-2021 Osimis S.A., Belgium 6 * 7 * This program is free software: you can redistribute it and/or 8 * modify it under the terms of the GNU Lesser General Public License 9 * as published by the Free Software Foundation, either version 3 of 10 * the License, or (at your option) any later version. 11 * 12 * This program is distributed in the hope that it will be useful, but 13 * WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 * Lesser General Public License for more details. 16 * 17 * You should have received a copy of the GNU Lesser General Public 18 * License along with this program. If not, see 19 * <http://www.gnu.org/licenses/>. 20 **/ 21 22 23 #include "PrecompiledHeaders.h" 24 #include "MetricsRegistry.h" 25 26 #include "ChunkedBuffer.h" 27 #include "Compatibility.h" 28 #include "OrthancException.h" 29 30 namespace Orthanc 31 { GetNow()32 static const boost::posix_time::ptime GetNow() 33 { 34 return boost::posix_time::microsec_clock::universal_time(); 35 } 36 37 class MetricsRegistry::Item 38 { 39 private: 40 MetricsType type_; 41 boost::posix_time::ptime time_; 42 bool hasValue_; 43 float value_; 44 Touch(float value,const boost::posix_time::ptime & now)45 void Touch(float value, 46 const boost::posix_time::ptime& now) 47 { 48 hasValue_ = true; 49 value_ = value; 50 time_ = now; 51 } 52 Touch(float value)53 void Touch(float value) 54 { 55 Touch(value, GetNow()); 56 } 57 UpdateMax(float value,int duration)58 void UpdateMax(float value, 59 int duration) 60 { 61 if (hasValue_) 62 { 63 const boost::posix_time::ptime now = GetNow(); 64 65 if (value > value_ || 66 (now - time_).total_seconds() > duration) 67 { 68 Touch(value, now); 69 } 70 } 71 else 72 { 73 Touch(value); 74 } 75 } 76 UpdateMin(float value,int duration)77 void UpdateMin(float value, 78 int duration) 79 { 80 if (hasValue_) 81 { 82 const boost::posix_time::ptime now = GetNow(); 83 84 if (value < value_ || 85 (now - time_).total_seconds() > duration) 86 { 87 Touch(value, now); 88 } 89 } 90 else 91 { 92 Touch(value); 93 } 94 } 95 96 public: Item(MetricsType type)97 explicit Item(MetricsType type) : 98 type_(type), 99 hasValue_(false), 100 value_(0) 101 { 102 } 103 GetType() const104 MetricsType GetType() const 105 { 106 return type_; 107 } 108 Update(float value)109 void Update(float value) 110 { 111 switch (type_) 112 { 113 case MetricsType_Default: 114 Touch(value); 115 break; 116 117 case MetricsType_MaxOver10Seconds: 118 UpdateMax(value, 10); 119 break; 120 121 case MetricsType_MaxOver1Minute: 122 UpdateMax(value, 60); 123 break; 124 125 case MetricsType_MinOver10Seconds: 126 UpdateMin(value, 10); 127 break; 128 129 case MetricsType_MinOver1Minute: 130 UpdateMin(value, 60); 131 break; 132 133 default: 134 throw OrthancException(ErrorCode_NotImplemented); 135 } 136 } 137 HasValue() const138 bool HasValue() const 139 { 140 return hasValue_; 141 } 142 GetTime() const143 const boost::posix_time::ptime& GetTime() const 144 { 145 if (hasValue_) 146 { 147 return time_; 148 } 149 else 150 { 151 throw OrthancException(ErrorCode_BadSequenceOfCalls); 152 } 153 } 154 GetValue() const155 float GetValue() const 156 { 157 if (hasValue_) 158 { 159 return value_; 160 } 161 else 162 { 163 throw OrthancException(ErrorCode_BadSequenceOfCalls); 164 } 165 } 166 }; 167 168 ~MetricsRegistry()169 MetricsRegistry::~MetricsRegistry() 170 { 171 for (Content::iterator it = content_.begin(); it != content_.end(); ++it) 172 { 173 assert(it->second != NULL); 174 delete it->second; 175 } 176 } 177 IsEnabled() const178 bool MetricsRegistry::IsEnabled() const 179 { 180 return enabled_; 181 } 182 183 SetEnabled(bool enabled)184 void MetricsRegistry::SetEnabled(bool enabled) 185 { 186 boost::mutex::scoped_lock lock(mutex_); 187 enabled_ = enabled; 188 } 189 190 Register(const std::string & name,MetricsType type)191 void MetricsRegistry::Register(const std::string& name, 192 MetricsType type) 193 { 194 boost::mutex::scoped_lock lock(mutex_); 195 196 Content::iterator found = content_.find(name); 197 198 if (found == content_.end()) 199 { 200 content_[name] = new Item(type); 201 } 202 else 203 { 204 assert(found->second != NULL); 205 206 // This metrics already exists: Only recreate it if there is a 207 // mismatch in the type of metrics 208 if (found->second->GetType() != type) 209 { 210 delete found->second; 211 found->second = new Item(type); 212 } 213 } 214 } 215 SetValueInternal(const std::string & name,float value,MetricsType type)216 void MetricsRegistry::SetValueInternal(const std::string& name, 217 float value, 218 MetricsType type) 219 { 220 boost::mutex::scoped_lock lock(mutex_); 221 222 Content::iterator found = content_.find(name); 223 224 if (found == content_.end()) 225 { 226 std::unique_ptr<Item> item(new Item(type)); 227 item->Update(value); 228 content_[name] = item.release(); 229 } 230 else 231 { 232 assert(found->second != NULL); 233 found->second->Update(value); 234 } 235 } 236 MetricsRegistry()237 MetricsRegistry::MetricsRegistry() : 238 enabled_(true) 239 { 240 } 241 242 SetValue(const std::string & name,float value,MetricsType type)243 void MetricsRegistry::SetValue(const std::string &name, 244 float value, 245 MetricsType type) 246 { 247 // Inlining to avoid loosing time if metrics are disabled 248 if (enabled_) 249 { 250 SetValueInternal(name, value, type); 251 } 252 } 253 254 SetValue(const std::string & name,float value)255 void MetricsRegistry::SetValue(const std::string &name, float value) 256 { 257 SetValue(name, value, MetricsType_Default); 258 } 259 260 GetMetricsType(const std::string & name)261 MetricsType MetricsRegistry::GetMetricsType(const std::string& name) 262 { 263 boost::mutex::scoped_lock lock(mutex_); 264 265 Content::const_iterator found = content_.find(name); 266 267 if (found == content_.end()) 268 { 269 throw OrthancException(ErrorCode_InexistentItem); 270 } 271 else 272 { 273 assert(found->second != NULL); 274 return found->second->GetType(); 275 } 276 } 277 278 ExportPrometheusText(std::string & s)279 void MetricsRegistry::ExportPrometheusText(std::string& s) 280 { 281 // https://www.boost.org/doc/libs/1_69_0/doc/html/date_time/examples.html#date_time.examples.seconds_since_epoch 282 static const boost::posix_time::ptime EPOCH(boost::gregorian::date(1970, 1, 1)); 283 284 boost::mutex::scoped_lock lock(mutex_); 285 286 s.clear(); 287 288 if (!enabled_) 289 { 290 return; 291 } 292 293 ChunkedBuffer buffer; 294 295 for (Content::const_iterator it = content_.begin(); 296 it != content_.end(); ++it) 297 { 298 assert(it->second != NULL); 299 300 if (it->second->HasValue()) 301 { 302 boost::posix_time::time_duration diff = it->second->GetTime() - EPOCH; 303 304 std::string line = (it->first + " " + 305 boost::lexical_cast<std::string>(it->second->GetValue()) + " " + 306 boost::lexical_cast<std::string>(diff.total_milliseconds()) + "\n"); 307 308 buffer.AddChunk(line); 309 } 310 } 311 312 buffer.Flatten(s); 313 } 314 315 SharedMetrics(MetricsRegistry & registry,const std::string & name,MetricsType type)316 MetricsRegistry::SharedMetrics::SharedMetrics(MetricsRegistry ®istry, 317 const std::string &name, 318 MetricsType type) : 319 registry_(registry), 320 name_(name), 321 value_(0) 322 { 323 } 324 Add(float delta)325 void MetricsRegistry::SharedMetrics::Add(float delta) 326 { 327 boost::mutex::scoped_lock lock(mutex_); 328 value_ += delta; 329 registry_.SetValue(name_, value_); 330 } 331 332 ActiveCounter(MetricsRegistry::SharedMetrics & metrics)333 MetricsRegistry::ActiveCounter::ActiveCounter(MetricsRegistry::SharedMetrics &metrics) : 334 metrics_(metrics) 335 { 336 metrics_.Add(1); 337 } 338 ~ActiveCounter()339 MetricsRegistry::ActiveCounter::~ActiveCounter() 340 { 341 metrics_.Add(-1); 342 } 343 344 Start()345 void MetricsRegistry::Timer::Start() 346 { 347 if (registry_.IsEnabled()) 348 { 349 active_ = true; 350 start_ = GetNow(); 351 } 352 else 353 { 354 active_ = false; 355 } 356 } 357 358 Timer(MetricsRegistry & registry,const std::string & name)359 MetricsRegistry::Timer::Timer(MetricsRegistry ®istry, 360 const std::string &name) : 361 registry_(registry), 362 name_(name), 363 type_(MetricsType_MaxOver10Seconds) 364 { 365 Start(); 366 } 367 368 Timer(MetricsRegistry & registry,const std::string & name,MetricsType type)369 MetricsRegistry::Timer::Timer(MetricsRegistry ®istry, 370 const std::string &name, 371 MetricsType type) : 372 registry_(registry), 373 name_(name), 374 type_(type) 375 { 376 Start(); 377 } 378 379 ~Timer()380 MetricsRegistry::Timer::~Timer() 381 { 382 if (active_) 383 { 384 boost::posix_time::time_duration diff = GetNow() - start_; 385 registry_.SetValue( 386 name_, static_cast<float>(diff.total_milliseconds()), type_); 387 } 388 } 389 } 390