/src/trafficserver/include/tsutil/Metrics.h
Line | Count | Source |
1 | | /** @file |
2 | | |
3 | | A brief file description |
4 | | |
5 | | @section license License |
6 | | |
7 | | Licensed to the Apache Software Foundation (ASF) under one |
8 | | or more contributor license agreements. See the NOTICE file |
9 | | distributed with this work for additional information |
10 | | regarding copyright ownership. The ASF licenses this file |
11 | | to you under the Apache License, Version 2.0 (the |
12 | | "License"); you may not use this file except in compliance |
13 | | with the License. You may obtain a copy of the License at |
14 | | |
15 | | http://www.apache.org/licenses/LICENSE-2.0 |
16 | | |
17 | | Unless required by applicable law or agreed to in writing, software |
18 | | distributed under the License is distributed on an "AS IS" BASIS, |
19 | | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
20 | | See the License for the specific language governing permissions and |
21 | | limitations under the License. |
22 | | */ |
23 | | |
24 | | #pragma once |
25 | | |
26 | | #include <array> |
27 | | #include <unordered_map> |
28 | | #include <tuple> |
29 | | #include <memory> |
30 | | #include <mutex> |
31 | | #include <atomic> |
32 | | #include <cstdint> |
33 | | #include <string> |
34 | | #include <string_view> |
35 | | #include <variant> |
36 | | #include <optional> |
37 | | |
38 | | #include "swoc/MemSpan.h" |
39 | | |
40 | | #include "tsutil/Assert.h" |
41 | | |
42 | | namespace ts |
43 | | { |
44 | | class Metrics |
45 | | { |
46 | | private: |
47 | | using self_type = Metrics; |
48 | | |
49 | | public: |
50 | | class AtomicType |
51 | | { |
52 | | friend class Metrics; |
53 | | |
54 | | public: |
55 | 0 | AtomicType() = default; |
56 | | |
57 | | int64_t |
58 | | load() const |
59 | 0 | { |
60 | 0 | return _value.load(); |
61 | 0 | } |
62 | | |
63 | | void |
64 | | increment(int64_t val) |
65 | 0 | { |
66 | 0 | _value.fetch_add(val, MEMORY_ORDER); |
67 | 0 | } |
68 | | |
69 | | // Use with care ... |
70 | | void |
71 | | store(int64_t val) |
72 | 0 | { |
73 | 0 | _value.store(val); |
74 | 0 | } |
75 | | |
76 | | void |
77 | | decrement(int64_t val) |
78 | 0 | { |
79 | 0 | _value.fetch_sub(val, MEMORY_ORDER); |
80 | 0 | } |
81 | | |
82 | | protected: |
83 | | std::atomic<int64_t> _value{0}; |
84 | | }; |
85 | | |
86 | | enum class MetricType : int { COUNTER = 0, GAUGE }; |
87 | | |
88 | | using IdType = int32_t; // Could be a tuple, but one way or another, they have to be combined to an int32_t. |
89 | | using SpanType = swoc::MemSpan<AtomicType>; |
90 | | |
91 | | static constexpr uint16_t MAX_BLOBS = 8192; |
92 | | static constexpr uint16_t MAX_SIZE = 1024; // For a total of 8M metrics |
93 | | static constexpr IdType NOT_FOUND = std::numeric_limits<IdType>::min(); // <16-bit,16-bit> = <blob-index,offset> |
94 | | static const auto MEMORY_ORDER = std::memory_order_relaxed; |
95 | | static constexpr int METRIC_TYPE_BITS = 29; |
96 | | static constexpr int METRIC_TYPE_MASK = 0x1FFF; |
97 | | |
98 | | private: |
99 | | using NameAndId = std::tuple<std::string, IdType>; |
100 | | using LookupTable = std::unordered_map<std::string_view, IdType>; |
101 | | using NameStorage = std::array<NameAndId, MAX_SIZE>; |
102 | | using AtomicStorage = std::array<AtomicType, MAX_SIZE>; |
103 | | using NamesAndAtomics = std::tuple<NameStorage, AtomicStorage>; |
104 | | using BlobStorage = std::array<std::unique_ptr<NamesAndAtomics>, MAX_BLOBS>; |
105 | | |
106 | | public: |
107 | | Metrics(const self_type &) = delete; |
108 | | self_type &operator=(const self_type &) = delete; |
109 | | Metrics &operator=(Metrics &&) = delete; |
110 | | Metrics(Metrics &&) = delete; |
111 | | |
112 | 0 | virtual ~Metrics() {} |
113 | | |
114 | | // The singleton instance, owned by the Metrics class |
115 | | static Metrics &instance(); |
116 | | |
117 | | // Yes, we don't return objects here, but rather ID's and atomic's directly. Treat |
118 | | // the std::atomic<int64_t> as the underlying class for a single metric, and be happy. |
119 | | IdType |
120 | | lookup(const std::string_view name) const |
121 | 0 | { |
122 | 0 | return _storage->lookup(name); |
123 | 0 | } |
124 | | AtomicType * |
125 | | lookup(const std::string_view name, IdType *out_id) const |
126 | 0 | { |
127 | 0 | return _storage->lookup(name, out_id); |
128 | 0 | } |
129 | | AtomicType * |
130 | | lookup(IdType id, std::string_view *out_name = nullptr, Metrics::MetricType *type = nullptr) const |
131 | 0 | { |
132 | 0 | return _storage->lookup(id, out_name, type); |
133 | 0 | } |
134 | | bool |
135 | | rename(IdType id, const std::string_view name) |
136 | 0 | { |
137 | 0 | return _storage->rename(id, name); |
138 | 0 | } |
139 | | |
140 | | AtomicType & |
141 | | operator[](IdType id) |
142 | 0 | { |
143 | 0 | return *lookup(id); |
144 | 0 | } |
145 | | |
146 | | IdType |
147 | | operator[](const std::string_view name) const |
148 | 0 | { |
149 | 0 | return lookup(name); |
150 | 0 | } |
151 | | |
152 | | int64_t |
153 | | increment(IdType id, uint64_t val = 1) |
154 | 0 | { |
155 | 0 | auto metric = lookup(id); |
156 | 0 |
|
157 | 0 | return (metric ? metric->_value.fetch_add(val, MEMORY_ORDER) : NOT_FOUND); |
158 | 0 | } |
159 | | |
160 | | int64_t |
161 | | decrement(IdType id, uint64_t val = 1) |
162 | 0 | { |
163 | 0 | auto metric = lookup(id); |
164 | 0 |
|
165 | 0 | return (metric ? metric->_value.fetch_sub(val, MEMORY_ORDER) : NOT_FOUND); |
166 | 0 | } |
167 | | |
168 | | std::string_view |
169 | | name(IdType id) const |
170 | 0 | { |
171 | 0 | return _storage->name(id); |
172 | 0 | } |
173 | | |
174 | | MetricType |
175 | | type(IdType id) const |
176 | 0 | { |
177 | 0 | return _storage->type(id); |
178 | 0 | } |
179 | | |
180 | | bool |
181 | | valid(IdType id) const |
182 | 0 | { |
183 | 0 | return _storage->valid(id); |
184 | 0 | } |
185 | | |
186 | | // Static methods to encapsulate access to the atomic's |
187 | | class iterator |
188 | | { |
189 | | public: |
190 | | using iterator_category = std::input_iterator_tag; |
191 | | using value_type = std::tuple<std::string_view, MetricType, int64_t>; |
192 | | using difference_type = ptrdiff_t; |
193 | | using pointer = value_type *; |
194 | | using reference = value_type &; |
195 | | |
196 | 0 | iterator(const Metrics &m, IdType pos) : _metrics(m), _it(pos) {} |
197 | | |
198 | | iterator & |
199 | | operator++() |
200 | 0 | { |
201 | 0 | next(); |
202 | |
|
203 | 0 | return *this; |
204 | 0 | } |
205 | | |
206 | | iterator |
207 | | operator++(int) |
208 | 0 | { |
209 | 0 | iterator result = *this; |
210 | 0 |
|
211 | 0 | next(); |
212 | 0 |
|
213 | 0 | return result; |
214 | 0 | } |
215 | | |
216 | | value_type |
217 | | operator*() const |
218 | 0 | { |
219 | 0 | std::string_view name; |
220 | 0 | MetricType type; |
221 | 0 | auto metric = _metrics.lookup(_it, &name, &type); |
222 | |
|
223 | 0 | return std::make_tuple(name, type, metric->_value.load()); |
224 | 0 | } |
225 | | |
226 | | bool |
227 | | operator==(const iterator &o) const |
228 | 0 | { |
229 | 0 | return _it == o._it && std::addressof(_metrics) == std::addressof(o._metrics); |
230 | 0 | } |
231 | | |
232 | | bool |
233 | | operator!=(const iterator &o) const |
234 | 0 | { |
235 | 0 | return _it != o._it || std::addressof(_metrics) != std::addressof(o._metrics); |
236 | 0 | } |
237 | | |
238 | | private: |
239 | | void next(); |
240 | | |
241 | | const Metrics &_metrics; |
242 | | Metrics::IdType _it; |
243 | | }; |
244 | | |
245 | | iterator |
246 | | begin() const |
247 | 0 | { |
248 | 0 | return iterator(*this, 0); |
249 | 0 | } |
250 | | |
251 | | iterator |
252 | | end() const |
253 | 0 | { |
254 | 0 | auto [blob, offset] = _storage->current(); |
255 | |
|
256 | 0 | return iterator(*this, _makeId(blob, offset, MetricType::COUNTER)); |
257 | 0 | } |
258 | | |
259 | | iterator |
260 | | find(const std::string_view name) const |
261 | 0 | { |
262 | 0 | auto id = lookup(name); |
263 | |
|
264 | 0 | if (id == NOT_FOUND) { |
265 | 0 | return end(); |
266 | 0 | } else { |
267 | 0 | return iterator(*this, id); |
268 | 0 | } |
269 | 0 | } |
270 | | |
271 | | private: |
272 | | // These are private, to assure that we don't use them by accident creating naked metrics |
273 | | IdType |
274 | | _create(const std::string_view name, MetricType type) |
275 | 0 | { |
276 | 0 | return _storage->create(name, type); |
277 | 0 | } |
278 | | |
279 | | SpanType |
280 | | _createSpan(size_t size, MetricType type, IdType *id = nullptr) |
281 | 0 | { |
282 | 0 | return _storage->createSpan(size, type, id); |
283 | 0 | } |
284 | | |
285 | | // These are little helpers around managing the ID's |
286 | | static constexpr std::tuple<uint16_t, uint16_t> |
287 | | _splitID(IdType value) |
288 | 0 | { |
289 | 0 | return std::make_tuple(static_cast<uint16_t>(value >> 16) & METRIC_TYPE_MASK, static_cast<uint16_t>(value & 0xFFFF)); |
290 | 0 | } |
291 | | |
292 | | static constexpr MetricType |
293 | | _extractType(IdType value) |
294 | 0 | { |
295 | 0 | return MetricType{value >> METRIC_TYPE_BITS}; |
296 | 0 | } |
297 | | |
298 | | static constexpr IdType |
299 | | _makeId(uint16_t blob, uint16_t offset, const MetricType type) |
300 | 0 | { |
301 | 0 | int t = static_cast<int>(type); |
302 | 0 | return (t << METRIC_TYPE_BITS | blob << 16 | offset); |
303 | 0 | } |
304 | | |
305 | | class Storage |
306 | | { |
307 | | BlobStorage _blobs; |
308 | | uint16_t _cur_blob = 0; |
309 | | uint16_t _cur_off = 0; |
310 | | LookupTable _lookups; |
311 | | mutable std::mutex _mutex; |
312 | | |
313 | | public: |
314 | | Storage(const Storage &) = delete; |
315 | | Storage &operator=(const Storage &) = delete; |
316 | | |
317 | | Storage() |
318 | 0 | { |
319 | 0 | _blobs[0] = std::make_unique<NamesAndAtomics>(); |
320 | 0 | release_assert(_blobs[0]); |
321 | | // Reserve slot 0 for errors, this should always be 0 |
322 | 0 | release_assert(0 == create("proxy.process.api.metrics.bad_id", MetricType::COUNTER)); |
323 | 0 | } |
324 | | |
325 | 0 | ~Storage() {} |
326 | | |
327 | | IdType create(const std::string_view name, const MetricType type = MetricType::COUNTER); |
328 | | void addBlob(); |
329 | | IdType lookup(const std::string_view name) const; |
330 | | AtomicType *lookup(const std::string_view name, IdType *out_id, MetricType *out_type = nullptr) const; |
331 | | AtomicType *lookup(Metrics::IdType id, std::string_view *out_name = nullptr, MetricType *out_type = nullptr) const; |
332 | | std::string_view name(IdType id) const; |
333 | | MetricType type(IdType id) const; |
334 | | SpanType createSpan(size_t size, const MetricType type = MetricType::COUNTER, IdType *id = nullptr); |
335 | | bool rename(IdType id, const std::string_view name); |
336 | | |
337 | | std::pair<int16_t, int16_t> |
338 | | current() const |
339 | 0 | { |
340 | 0 | std::lock_guard lock(_mutex); |
341 | 0 | return {_cur_blob, _cur_off}; |
342 | 0 | } |
343 | | |
344 | | bool |
345 | | valid(IdType id) const |
346 | 0 | { |
347 | 0 | auto [blob, entry] = _splitID(id); |
348 | 0 |
|
349 | 0 | return (id >= 0 && ((blob < _cur_blob && entry < MAX_SIZE) || (blob == _cur_blob && entry <= _cur_off))); |
350 | 0 | } |
351 | | }; |
352 | | |
353 | 0 | Metrics(std::shared_ptr<Storage> &str) : _storage(str) {} |
354 | | |
355 | | std::shared_ptr<Storage> _storage; |
356 | | |
357 | | public: |
358 | | // These are sort of factory classes, using the Metrics singleton for all storage etc. |
359 | | class Gauge |
360 | | { |
361 | | public: |
362 | | using self_type = Gauge; |
363 | | using SpanType = Metrics::SpanType; |
364 | | |
365 | | class AtomicType : public Metrics::AtomicType |
366 | | { |
367 | | }; |
368 | | |
369 | | static IdType |
370 | | lookup(const std::string_view name) |
371 | 0 | { |
372 | 0 | auto &instance = Metrics::instance(); |
373 | 0 |
|
374 | 0 | return instance.lookup(name); |
375 | 0 | } |
376 | | |
377 | | static AtomicType * |
378 | | lookup(const IdType id, std::string_view *out_name = nullptr) |
379 | 0 | { |
380 | 0 | auto &instance = Metrics::instance(); |
381 | 0 |
|
382 | 0 | return reinterpret_cast<AtomicType *>(instance.lookup(id, out_name)); |
383 | 0 | } |
384 | | |
385 | | static AtomicType * |
386 | | lookup(const std::string_view name, IdType *id) |
387 | 0 | { |
388 | 0 | auto &instance = Metrics::instance(); |
389 | 0 |
|
390 | 0 | return reinterpret_cast<AtomicType *>(instance.lookup(name, id)); |
391 | 0 | } |
392 | | |
393 | | static Metrics::IdType |
394 | | create(const std::string_view name) |
395 | 0 | { |
396 | 0 | auto &instance = Metrics::instance(); |
397 | 0 |
|
398 | 0 | return instance._create(name, MetricType::GAUGE); |
399 | 0 | } |
400 | | |
401 | | static AtomicType * |
402 | | createPtr(const std::string_view name) |
403 | 0 | { |
404 | 0 | auto &instance = Metrics::instance(); |
405 | |
|
406 | 0 | return reinterpret_cast<AtomicType *>(instance.lookup(instance._create(name, MetricType::GAUGE))); |
407 | 0 | } |
408 | | |
409 | | static AtomicType * |
410 | | createPtr(const std::string_view prefix, const std::string_view name) |
411 | 0 | { |
412 | 0 | auto &instance = Metrics::instance(); |
413 | 0 | std::string tmpname = std::string(prefix) + std::string(name); |
414 | 0 |
|
415 | 0 | return reinterpret_cast<AtomicType *>(instance.lookup(instance._create(tmpname, MetricType::GAUGE))); |
416 | 0 | } |
417 | | |
418 | | static Metrics::Gauge::SpanType |
419 | | createSpan(size_t size, IdType *id = nullptr) |
420 | 0 | { |
421 | 0 | auto &instance = Metrics::instance(); |
422 | 0 |
|
423 | 0 | return instance._createSpan(size, MetricType::GAUGE, id); |
424 | 0 | } |
425 | | |
426 | | static void |
427 | | increment(AtomicType *metric, uint64_t val = 1) |
428 | 0 | { |
429 | 0 | debug_assert(metric); |
430 | 0 | metric->_value.fetch_add(val, MEMORY_ORDER); |
431 | 0 | } |
432 | | |
433 | | static void |
434 | | decrement(AtomicType *metric, uint64_t val = 1) |
435 | 0 | { |
436 | 0 | debug_assert(metric); |
437 | 0 | metric->_value.fetch_sub(val, MEMORY_ORDER); |
438 | 0 | } |
439 | | |
440 | | static int64_t |
441 | | load(const AtomicType *metric) |
442 | 0 | { |
443 | 0 | debug_assert(metric); |
444 | 0 | return metric->_value.load(); |
445 | 0 | } |
446 | | |
447 | | static void |
448 | | store(AtomicType *metric, int64_t val) |
449 | 0 | { |
450 | 0 | debug_assert(metric); |
451 | 0 | return metric->_value.store(val); |
452 | 0 | } |
453 | | |
454 | | }; // class Gauge |
455 | | |
456 | | class Counter |
457 | | { |
458 | | public: |
459 | | using self_type = Counter; |
460 | | using SpanType = Metrics::SpanType; |
461 | | |
462 | | class AtomicType : public Metrics::AtomicType |
463 | | { |
464 | | }; |
465 | | |
466 | | static IdType |
467 | | lookup(const std::string_view name) |
468 | 0 | { |
469 | 0 | auto &instance = Metrics::instance(); |
470 | 0 |
|
471 | 0 | return instance.lookup(name); |
472 | 0 | } |
473 | | |
474 | | static AtomicType * |
475 | | lookup(const IdType id, std::string_view *out_name = nullptr) |
476 | 0 | { |
477 | 0 | auto &instance = Metrics::instance(); |
478 | 0 |
|
479 | 0 | return reinterpret_cast<AtomicType *>(instance.lookup(id, out_name)); |
480 | 0 | } |
481 | | |
482 | | static AtomicType * |
483 | | lookup(const std::string_view name, IdType *id) |
484 | 0 | { |
485 | 0 | auto &instance = Metrics::instance(); |
486 | 0 |
|
487 | 0 | return reinterpret_cast<AtomicType *>(instance.lookup(name, id)); |
488 | 0 | } |
489 | | |
490 | | static Metrics::IdType |
491 | | create(const std::string_view name) |
492 | 0 | { |
493 | 0 | auto &instance = Metrics::instance(); |
494 | 0 |
|
495 | 0 | return instance._create(name, MetricType::COUNTER); |
496 | 0 | } |
497 | | |
498 | | static AtomicType * |
499 | | createPtr(const std::string_view name) |
500 | 0 | { |
501 | 0 | auto &instance = Metrics::instance(); |
502 | 0 |
|
503 | 0 | return reinterpret_cast<AtomicType *>(instance.lookup(instance._create(name, MetricType::COUNTER))); |
504 | 0 | } |
505 | | |
506 | | static AtomicType * |
507 | | createPtr(const std::string_view prefix, const std::string_view name) |
508 | 0 | { |
509 | 0 | auto &instance = Metrics::instance(); |
510 | 0 | std::string tmpname = std::string(prefix) + std::string(name); |
511 | 0 |
|
512 | 0 | return reinterpret_cast<AtomicType *>(instance.lookup(instance._create(tmpname, MetricType::COUNTER))); |
513 | 0 | } |
514 | | |
515 | | static Metrics::Counter::SpanType |
516 | | createSpan(size_t size, IdType *id = nullptr) |
517 | 0 | { |
518 | 0 | auto &instance = Metrics::instance(); |
519 | 0 |
|
520 | 0 | return instance._createSpan(size, MetricType::COUNTER, id); |
521 | 0 | } |
522 | | |
523 | | static void |
524 | | increment(AtomicType *metric, uint64_t val = 1) |
525 | 0 | { |
526 | 0 | debug_assert(metric); |
527 | 0 | metric->_value.fetch_add(val, MEMORY_ORDER); |
528 | 0 | } |
529 | | |
530 | | static int64_t |
531 | | load(const AtomicType *metric) |
532 | 0 | { |
533 | 0 | debug_assert(metric); |
534 | 0 | return metric->_value.load(); |
535 | 0 | } |
536 | | |
537 | | }; // class Counter |
538 | | |
539 | | /** |
540 | | * Static string metrics storage. |
541 | | * |
542 | | * All methods are thread-safe. |
543 | | */ |
544 | | class StaticString |
545 | | { |
546 | | public: |
547 | | using StringStorage = std::unordered_map<std::string, std::string>; |
548 | | |
549 | | static void |
550 | | createString(const std::string &name, const std::string_view value) |
551 | 0 | { |
552 | 0 | auto &instance = Metrics::StaticString::instance(); |
553 | 0 | return instance._createString(name, value); |
554 | 0 | } |
555 | | |
556 | | static StaticString &instance(); |
557 | | |
558 | | /** |
559 | | * Thread-safe iteration over all string metrics. |
560 | | * The callback is invoked for each metric while holding the mutex. |
561 | | */ |
562 | | template <typename Func> |
563 | | void |
564 | | for_each(Func &&func) const |
565 | 0 | { |
566 | 0 | std::lock_guard lock(_mutex); |
567 | 0 | for (const auto &[name, value] : _strings) { |
568 | 0 | func(name, value); |
569 | 0 | } |
570 | 0 | } |
571 | | |
572 | | std::optional<std::string_view> lookup(const std::string &name) const; |
573 | | |
574 | | private: |
575 | | void _createString(const std::string &name, const std::string_view value); |
576 | | |
577 | | StringStorage _strings; |
578 | | mutable std::mutex _mutex; |
579 | | }; |
580 | | |
581 | | /** |
582 | | * Derive metrics by summing a set of other metrics. |
583 | | * |
584 | | */ |
585 | | class Derived |
586 | | { |
587 | | public: |
588 | | struct DerivedMetricSpec { |
589 | | using MetricSpec = std::variant<Metrics::AtomicType *, Metrics::IdType, std::string_view>; |
590 | | std::string_view derived_name; |
591 | | Metrics::MetricType derived_type; |
592 | | std::initializer_list<MetricSpec> derived_from; |
593 | | }; |
594 | | |
595 | | /** |
596 | | * Create new metrics derived from existing metrics. |
597 | | * |
598 | | * This function will create new metrics from a list of existing metrics. The existing metric can |
599 | | * be specified by name, id or a pointer to the metric. |
600 | | */ |
601 | | static void derive(const std::initializer_list<DerivedMetricSpec> &metrics); |
602 | | |
603 | | /** |
604 | | * Update derived metrics. |
605 | | * |
606 | | * This static function should be called periodically to update derived metrics. |
607 | | */ |
608 | | static void update_derived(); |
609 | | }; |
610 | | |
611 | | }; // class Metrics |
612 | | |
613 | | } // namespace ts |