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 &registry,
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 &registry,
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 &registry,
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