| /** @file |
| |
| A brief file description |
| |
| @section license License |
| |
| Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you under the Apache License, Version 2.0 (the |
| "License"); you may not use this file except in compliance |
| with the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| #pragma once |
| |
| #include <array> |
| #include <unordered_map> |
| #include <tuple> |
| #include <memory> |
| #include <mutex> |
| #include <atomic> |
| #include <cstdint> |
| #include <string> |
| #include <string_view> |
| #include <variant> |
| #include <optional> |
| |
| #include "swoc/MemSpan.h" |
| |
| #include "tsutil/Assert.h" |
| |
| namespace ts |
| { |
| class Metrics |
| { |
| private: |
| using self_type = Metrics; |
| |
| public: |
| class AtomicType |
| { |
| friend class Metrics; |
| |
| public: |
| AtomicType() = default; |
| |
| int64_t |
| load() const |
| { |
| return _value.load(); |
| } |
| |
| void |
| increment(int64_t val) |
| { |
| _value.fetch_add(val, MEMORY_ORDER); |
| } |
| |
| // Use with care ... |
| void |
| store(int64_t val) |
| { |
| _value.store(val); |
| } |
| |
| void |
| decrement(int64_t val) |
| { |
| _value.fetch_sub(val, MEMORY_ORDER); |
| } |
| |
| protected: |
| std::atomic<int64_t> _value{0}; |
| }; |
| |
| enum class MetricType : int { COUNTER = 0, GAUGE }; |
| |
| using IdType = int32_t; // Could be a tuple, but one way or another, they have to be combined to an int32_t. |
| using SpanType = swoc::MemSpan<AtomicType>; |
| |
| static constexpr uint16_t MAX_BLOBS = 8192; |
| static constexpr uint16_t MAX_SIZE = 1024; // For a total of 8M metrics |
| static constexpr IdType NOT_FOUND = std::numeric_limits<IdType>::min(); // <16-bit,16-bit> = <blob-index,offset> |
| static const auto MEMORY_ORDER = std::memory_order_relaxed; |
| static constexpr int METRIC_TYPE_BITS = 29; |
| static constexpr int METRIC_TYPE_MASK = 0x1FFF; |
| |
| private: |
| using NameAndId = std::tuple<std::string, IdType>; |
| using LookupTable = std::unordered_map<std::string_view, IdType>; |
| using NameStorage = std::array<NameAndId, MAX_SIZE>; |
| using AtomicStorage = std::array<AtomicType, MAX_SIZE>; |
| using NamesAndAtomics = std::tuple<NameStorage, AtomicStorage>; |
| using BlobStorage = std::array<std::unique_ptr<NamesAndAtomics>, MAX_BLOBS>; |
| |
| public: |
| Metrics(const self_type &) = delete; |
| self_type &operator=(const self_type &) = delete; |
| Metrics &operator=(Metrics &&) = delete; |
| Metrics(Metrics &&) = delete; |
| |
| virtual ~Metrics() {} |
| |
| // The singleton instance, owned by the Metrics class |
| static Metrics &instance(); |
| |
| // Yes, we don't return objects here, but rather ID's and atomic's directly. Treat |
| // the std::atomic<int64_t> as the underlying class for a single metric, and be happy. |
| IdType |
| lookup(const std::string_view name) const |
| { |
| return _storage->lookup(name); |
| } |
| AtomicType * |
| lookup(const std::string_view name, IdType *out_id) const |
| { |
| return _storage->lookup(name, out_id); |
| } |
| AtomicType * |
| lookup(IdType id, std::string_view *out_name = nullptr, Metrics::MetricType *type = nullptr) const |
| { |
| return _storage->lookup(id, out_name, type); |
| } |
| bool |
| rename(IdType id, const std::string_view name) |
| { |
| return _storage->rename(id, name); |
| } |
| |
| AtomicType & |
| operator[](IdType id) |
| { |
| return *lookup(id); |
| } |
| |
| IdType |
| operator[](const std::string_view name) const |
| { |
| return lookup(name); |
| } |
| |
| int64_t |
| increment(IdType id, uint64_t val = 1) |
| { |
| auto metric = lookup(id); |
| |
| return (metric ? metric->_value.fetch_add(val, MEMORY_ORDER) : NOT_FOUND); |
| } |
| |
| int64_t |
| decrement(IdType id, uint64_t val = 1) |
| { |
| auto metric = lookup(id); |
| |
| return (metric ? metric->_value.fetch_sub(val, MEMORY_ORDER) : NOT_FOUND); |
| } |
| |
| std::string_view |
| name(IdType id) const |
| { |
| return _storage->name(id); |
| } |
| |
| MetricType |
| type(IdType id) const |
| { |
| return _storage->type(id); |
| } |
| |
| bool |
| valid(IdType id) const |
| { |
| return _storage->valid(id); |
| } |
| |
| // Static methods to encapsulate access to the atomic's |
| class iterator |
| { |
| public: |
| using iterator_category = std::input_iterator_tag; |
| using value_type = std::tuple<std::string_view, MetricType, int64_t>; |
| using difference_type = ptrdiff_t; |
| using pointer = value_type *; |
| using reference = value_type &; |
| |
| iterator(const Metrics &m, IdType pos) : _metrics(m), _it(pos) {} |
| |
| iterator & |
| operator++() |
| { |
| next(); |
| |
| return *this; |
| } |
| |
| iterator |
| operator++(int) |
| { |
| iterator result = *this; |
| |
| next(); |
| |
| return result; |
| } |
| |
| value_type |
| operator*() const |
| { |
| std::string_view name; |
| MetricType type; |
| auto metric = _metrics.lookup(_it, &name, &type); |
| |
| return std::make_tuple(name, type, metric->_value.load()); |
| } |
| |
| bool |
| operator==(const iterator &o) const |
| { |
| return _it == o._it && std::addressof(_metrics) == std::addressof(o._metrics); |
| } |
| |
| bool |
| operator!=(const iterator &o) const |
| { |
| return _it != o._it || std::addressof(_metrics) != std::addressof(o._metrics); |
| } |
| |
| private: |
| void next(); |
| |
| const Metrics &_metrics; |
| Metrics::IdType _it; |
| }; |
| |
| iterator |
| begin() const |
| { |
| return iterator(*this, 0); |
| } |
| |
| iterator |
| end() const |
| { |
| auto [blob, offset] = _storage->current(); |
| |
| return iterator(*this, _makeId(blob, offset, MetricType::COUNTER)); |
| } |
| |
| iterator |
| find(const std::string_view name) const |
| { |
| auto id = lookup(name); |
| |
| if (id == NOT_FOUND) { |
| return end(); |
| } else { |
| return iterator(*this, id); |
| } |
| } |
| |
| private: |
| // These are private, to assure that we don't use them by accident creating naked metrics |
| IdType |
| _create(const std::string_view name, MetricType type) |
| { |
| return _storage->create(name, type); |
| } |
| |
| SpanType |
| _createSpan(size_t size, MetricType type, IdType *id = nullptr) |
| { |
| return _storage->createSpan(size, type, id); |
| } |
| |
| // These are little helpers around managing the ID's |
| static constexpr std::tuple<uint16_t, uint16_t> |
| _splitID(IdType value) |
| { |
| return std::make_tuple(static_cast<uint16_t>(value >> 16) & METRIC_TYPE_MASK, static_cast<uint16_t>(value & 0xFFFF)); |
| } |
| |
| static constexpr MetricType |
| _extractType(IdType value) |
| { |
| return MetricType{value >> METRIC_TYPE_BITS}; |
| } |
| |
| static constexpr IdType |
| _makeId(uint16_t blob, uint16_t offset, const MetricType type) |
| { |
| int t = static_cast<int>(type); |
| return (t << METRIC_TYPE_BITS | blob << 16 | offset); |
| } |
| |
| class Storage |
| { |
| BlobStorage _blobs; |
| uint16_t _cur_blob = 0; |
| uint16_t _cur_off = 0; |
| LookupTable _lookups; |
| mutable std::mutex _mutex; |
| |
| public: |
| Storage(const Storage &) = delete; |
| Storage &operator=(const Storage &) = delete; |
| |
| Storage() |
| { |
| _blobs[0] = std::make_unique<NamesAndAtomics>(); |
| release_assert(_blobs[0]); |
| // Reserve slot 0 for errors, this should always be 0 |
| release_assert(0 == create("proxy.process.api.metrics.bad_id", MetricType::COUNTER)); |
| } |
| |
| ~Storage() {} |
| |
| IdType create(const std::string_view name, const MetricType type = MetricType::COUNTER); |
| void addBlob(); |
| IdType lookup(const std::string_view name) const; |
| AtomicType *lookup(const std::string_view name, IdType *out_id, MetricType *out_type = nullptr) const; |
| AtomicType *lookup(Metrics::IdType id, std::string_view *out_name = nullptr, MetricType *out_type = nullptr) const; |
| std::string_view name(IdType id) const; |
| MetricType type(IdType id) const; |
| SpanType createSpan(size_t size, const MetricType type = MetricType::COUNTER, IdType *id = nullptr); |
| bool rename(IdType id, const std::string_view name); |
| |
| std::pair<int16_t, int16_t> |
| current() const |
| { |
| std::lock_guard lock(_mutex); |
| return {_cur_blob, _cur_off}; |
| } |
| |
| bool |
| valid(IdType id) const |
| { |
| auto [blob, entry] = _splitID(id); |
| |
| return (id >= 0 && ((blob < _cur_blob && entry < MAX_SIZE) || (blob == _cur_blob && entry <= _cur_off))); |
| } |
| }; |
| |
| Metrics(std::shared_ptr<Storage> &str) : _storage(str) {} |
| |
| std::shared_ptr<Storage> _storage; |
| |
| public: |
| // These are sort of factory classes, using the Metrics singleton for all storage etc. |
| class Gauge |
| { |
| public: |
| using self_type = Gauge; |
| using SpanType = Metrics::SpanType; |
| |
| class AtomicType : public Metrics::AtomicType |
| { |
| }; |
| |
| static IdType |
| lookup(const std::string_view name) |
| { |
| auto &instance = Metrics::instance(); |
| |
| return instance.lookup(name); |
| } |
| |
| static AtomicType * |
| lookup(const IdType id, std::string_view *out_name = nullptr) |
| { |
| auto &instance = Metrics::instance(); |
| |
| return reinterpret_cast<AtomicType *>(instance.lookup(id, out_name)); |
| } |
| |
| static AtomicType * |
| lookup(const std::string_view name, IdType *id) |
| { |
| auto &instance = Metrics::instance(); |
| |
| return reinterpret_cast<AtomicType *>(instance.lookup(name, id)); |
| } |
| |
| static Metrics::IdType |
| create(const std::string_view name) |
| { |
| auto &instance = Metrics::instance(); |
| |
| return instance._create(name, MetricType::GAUGE); |
| } |
| |
| static AtomicType * |
| createPtr(const std::string_view name) |
| { |
| auto &instance = Metrics::instance(); |
| |
| return reinterpret_cast<AtomicType *>(instance.lookup(instance._create(name, MetricType::GAUGE))); |
| } |
| |
| static AtomicType * |
| createPtr(const std::string_view prefix, const std::string_view name) |
| { |
| auto &instance = Metrics::instance(); |
| std::string tmpname = std::string(prefix) + std::string(name); |
| |
| return reinterpret_cast<AtomicType *>(instance.lookup(instance._create(tmpname, MetricType::GAUGE))); |
| } |
| |
| static Metrics::Gauge::SpanType |
| createSpan(size_t size, IdType *id = nullptr) |
| { |
| auto &instance = Metrics::instance(); |
| |
| return instance._createSpan(size, MetricType::GAUGE, id); |
| } |
| |
| static void |
| increment(AtomicType *metric, uint64_t val = 1) |
| { |
| debug_assert(metric); |
| metric->_value.fetch_add(val, MEMORY_ORDER); |
| } |
| |
| static void |
| decrement(AtomicType *metric, uint64_t val = 1) |
| { |
| debug_assert(metric); |
| metric->_value.fetch_sub(val, MEMORY_ORDER); |
| } |
| |
| static int64_t |
| load(const AtomicType *metric) |
| { |
| debug_assert(metric); |
| return metric->_value.load(); |
| } |
| |
| static void |
| store(AtomicType *metric, int64_t val) |
| { |
| debug_assert(metric); |
| return metric->_value.store(val); |
| } |
| |
| }; // class Gauge |
| |
| class Counter |
| { |
| public: |
| using self_type = Counter; |
| using SpanType = Metrics::SpanType; |
| |
| class AtomicType : public Metrics::AtomicType |
| { |
| }; |
| |
| static IdType |
| lookup(const std::string_view name) |
| { |
| auto &instance = Metrics::instance(); |
| |
| return instance.lookup(name); |
| } |
| |
| static AtomicType * |
| lookup(const IdType id, std::string_view *out_name = nullptr) |
| { |
| auto &instance = Metrics::instance(); |
| |
| return reinterpret_cast<AtomicType *>(instance.lookup(id, out_name)); |
| } |
| |
| static AtomicType * |
| lookup(const std::string_view name, IdType *id) |
| { |
| auto &instance = Metrics::instance(); |
| |
| return reinterpret_cast<AtomicType *>(instance.lookup(name, id)); |
| } |
| |
| static Metrics::IdType |
| create(const std::string_view name) |
| { |
| auto &instance = Metrics::instance(); |
| |
| return instance._create(name, MetricType::COUNTER); |
| } |
| |
| static AtomicType * |
| createPtr(const std::string_view name) |
| { |
| auto &instance = Metrics::instance(); |
| |
| return reinterpret_cast<AtomicType *>(instance.lookup(instance._create(name, MetricType::COUNTER))); |
| } |
| |
| static AtomicType * |
| createPtr(const std::string_view prefix, const std::string_view name) |
| { |
| auto &instance = Metrics::instance(); |
| std::string tmpname = std::string(prefix) + std::string(name); |
| |
| return reinterpret_cast<AtomicType *>(instance.lookup(instance._create(tmpname, MetricType::COUNTER))); |
| } |
| |
| static Metrics::Counter::SpanType |
| createSpan(size_t size, IdType *id = nullptr) |
| { |
| auto &instance = Metrics::instance(); |
| |
| return instance._createSpan(size, MetricType::COUNTER, id); |
| } |
| |
| static void |
| increment(AtomicType *metric, uint64_t val = 1) |
| { |
| debug_assert(metric); |
| metric->_value.fetch_add(val, MEMORY_ORDER); |
| } |
| |
| static int64_t |
| load(const AtomicType *metric) |
| { |
| debug_assert(metric); |
| return metric->_value.load(); |
| } |
| |
| }; // class Counter |
| |
| /** |
| * Static string metrics storage. |
| * |
| * All methods are thread-safe. |
| */ |
| class StaticString |
| { |
| public: |
| using StringStorage = std::unordered_map<std::string, std::string>; |
| |
| static void |
| createString(const std::string &name, const std::string_view value) |
| { |
| auto &instance = Metrics::StaticString::instance(); |
| return instance._createString(name, value); |
| } |
| |
| static StaticString &instance(); |
| |
| /** |
| * Thread-safe iteration over all string metrics. |
| * The callback is invoked for each metric while holding the mutex. |
| */ |
| template <typename Func> |
| void |
| for_each(Func &&func) const |
| { |
| std::lock_guard lock(_mutex); |
| for (const auto &[name, value] : _strings) { |
| func(name, value); |
| } |
| } |
| |
| std::optional<std::string_view> lookup(const std::string &name) const; |
| |
| private: |
| void _createString(const std::string &name, const std::string_view value); |
| |
| StringStorage _strings; |
| mutable std::mutex _mutex; |
| }; |
| |
| /** |
| * Derive metrics by summing a set of other metrics. |
| * |
| */ |
| class Derived |
| { |
| public: |
| struct DerivedMetricSpec { |
| using MetricSpec = std::variant<Metrics::AtomicType *, Metrics::IdType, std::string_view>; |
| std::string_view derived_name; |
| Metrics::MetricType derived_type; |
| std::initializer_list<MetricSpec> derived_from; |
| }; |
| |
| /** |
| * Create new metrics derived from existing metrics. |
| * |
| * This function will create new metrics from a list of existing metrics. The existing metric can |
| * be specified by name, id or a pointer to the metric. |
| */ |
| static void derive(const std::initializer_list<DerivedMetricSpec> &metrics); |
| |
| /** |
| * Update derived metrics. |
| * |
| * This static function should be called periodically to update derived metrics. |
| */ |
| static void update_derived(); |
| }; |
| |
| }; // class Metrics |
| |
| } // namespace ts |