Coverage Report

Created: 2026-02-10 06:42

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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