#ifndef LLARP_METRICS_CORE_HPP #define LLARP_METRICS_CORE_HPP #include #include #include #include #include #include #include namespace llarp { namespace metrics { template < typename Type > class Collector { public: using RecordType = Record< Type >; private: RecordType m_record GUARDED_BY(m_mutex); mutable util::Mutex m_mutex; Collector(const Collector &) = delete; Collector & operator=(const Collector &) = delete; public: Collector(const Id &id) : m_record(id) { } void clear() { absl::WriterMutexLock l(&m_mutex); m_record.clear(); } RecordType loadAndClear() { absl::WriterMutexLock l(&m_mutex); RecordType rec = m_record; m_record.clear(); return rec; } RecordType load() { absl::ReaderMutexLock l(&m_mutex); return m_record; } void tick(Type value) { absl::WriterMutexLock l(&m_mutex); m_record.count()++; m_record.total() += value; m_record.min() = std::min(m_record.min(), value); m_record.max() = std::max(m_record.max(), value); } void accumulate(size_t count, Type total, Type min, Type max) { absl::WriterMutexLock l(&m_mutex); m_record.count() += count; m_record.total() += total; m_record.min() = std::min(m_record.min(), min); m_record.max() = std::max(m_record.max(), max); } void set(size_t count, Type total, Type min, Type max) { absl::WriterMutexLock l(&m_mutex); m_record.count() = count; m_record.total() = total; m_record.min() = min; m_record.max() = max; } const Id & id() const { absl::ReaderMutexLock l(&m_mutex); return m_record.id(); } }; using IntCollector = Collector< int >; using DoubleCollector = Collector< double >; class Publisher { public: virtual ~Publisher() = default; virtual void publish(const Sample &sample) = 0; }; template < typename LhsType, typename RhsType > static inline void combine(Record< LhsType > &record, const Record< RhsType > &toAdd) { static_assert(std::is_convertible< RhsType, LhsType >::value, ""); record.id() = toAdd.id(); record.count() += toAdd.count(); record.total() += toAdd.total(); record.min() = std::min(record.min(), LhsType(toAdd.min())); record.max() = std::max(record.max(), LhsType(toAdd.max())); } template < typename Type > class Collectors { using CollectorType = Collector< Type >; using CollectorPtr = std::shared_ptr< CollectorType >; using CollectorSet = std::set< CollectorPtr >; CollectorType m_default; CollectorSet m_collectors; Collectors(const Collectors &) = delete; Collectors & operator=(const Collectors &) = delete; public: Collectors(const Id &id) : m_default(id) { } CollectorType * defaultCollector() { return &m_default; } std::shared_ptr< CollectorType > add() { auto ptr = std::make_shared< CollectorType >(m_default.id()); m_collectors.insert(ptr); return ptr; } bool remove(CollectorType *collector) { std::shared_ptr< CollectorType > ptr(collector, [](CollectorType *) {}); size_t count = m_collectors.erase(ptr); return count > 0; } Record< Type > combineAndClear() { Record< Type > rec = m_default.loadAndClear(); for(auto &ptr : m_collectors) { metrics::combine(rec, ptr->loadAndClear()); } return rec; } Record< Type > combine() { Record< Type > rec = m_default.load(); for(auto &ptr : m_collectors) { metrics::combine(rec, ptr->load()); } return rec; } std::vector< std::shared_ptr< CollectorType > > collectors() const { return std::vector< std::shared_ptr< CollectorType > >( m_collectors.begin(), m_collectors.end()); } const Id & id() const { return m_default.id(); } }; using DoubleCollectors = Collectors< double >; using IntCollectors = Collectors< int >; class Registry { using NamedCategory = std::tuple< const char *, const char * >; struct CmpNamedCategory { bool operator()(const NamedCategory &lhs, const NamedCategory &rhs) const { int ret = std::strcmp(std::get< 0 >(lhs), std::get< 0 >(rhs)); if(ret == 0) { ret = std::strcmp(std::get< 1 >(lhs), std::get< 1 >(rhs)); } return ret < 0; } }; struct StrCmp { bool operator()(const char *lhs, const char *rhs) const { return std::strcmp(lhs, rhs) < 0; } }; using MetricMap = std::map< NamedCategory, std::shared_ptr< Description >, CmpNamedCategory >; using CategoryMap = std::map< const char *, std::shared_ptr< Category >, StrCmp >; std::set< std::string > m_stringmem GUARDED_BY(m_mutex); CategoryMap m_categories GUARDED_BY(m_mutex); MetricMap m_metrics GUARDED_BY(m_mutex); bool m_defaultEnabled GUARDED_BY(m_mutex); mutable util::Mutex m_mutex; Registry(const Registry &) = delete; Registry & operator=(const Registry &) = delete; std::tuple< Id, bool > insert(const char *category, const char *name) EXCLUSIVE_LOCKS_REQUIRED(m_mutex); public: Registry() : m_defaultEnabled(true) { } Id add(const char *category, const char *name) LOCKS_EXCLUDED(m_mutex); Id get(const char *category, const char *name) LOCKS_EXCLUDED(m_mutex); const Category * add(const char *category) LOCKS_EXCLUDED(m_mutex); const Category * get(const char *category); void enable(const Category *category, bool value); void enableAll(bool value); void registerContainer(const Category *category, CategoryContainer &container); void publicationType(const Id &id, Publication::Type type); void setFormat(const Id &id, const Format &format); size_t metricCount() const { absl::ReaderMutexLock l(&m_mutex); return m_metrics.size(); } size_t categoryCount() const { absl::ReaderMutexLock l(&m_mutex); return m_categories.size(); } const Category * findCategory(const char *category) const; Id findId(const char *category, const char *name) const; std::vector< const Category * > getAll() const; }; using DoubleRecords = std::vector< Record< double > >; using IntRecords = std::vector< Record< int > >; struct Records { DoubleRecords doubleRecords; IntRecords intRecords; Records() { } Records(const DoubleRecords &d, const IntRecords &i) : doubleRecords(d), intRecords(i) { } }; inline bool operator==(const Records &lhs, const Records &rhs) { return std::tie(lhs.doubleRecords, lhs.intRecords) == std::tie(rhs.doubleRecords, rhs.intRecords); } template < typename Type > class CollectorRepo { using CollectorsPtr = std::shared_ptr< Collectors< Type > >; using IdCollectors = std::map< Id, CollectorsPtr >; using CategoryCollectors = std::map< const Category *, std::vector< Collectors< Type > * > >; Registry *m_registry; IdCollectors m_collectors; CategoryCollectors m_categories; mutable util::Mutex m_mutex; CollectorRepo(const CollectorRepo &) = delete; CollectorRepo & operator=(const CollectorRepo &) = delete; Collectors< Type > & getCollectors(const Id &id) { auto it = m_collectors.find(id); if(it == m_collectors.end()) { assert(id.valid()); const Category *cat = id.category(); auto ptr = std::make_shared< Collectors< Type > >(id); auto &vec = m_categories[cat]; vec.reserve(vec.size() + 1); it = m_collectors.emplace(id, ptr).first; vec.push_back(ptr.get()); } return *it->second.get(); } template < Record< Type > (Collectors< Type >::*func)() > std::vector< Record< Type > > collectOp(const Category *category) { absl::WriterMutexLock l(&m_mutex); auto it = m_categories.find(category); if(it == m_categories.end()) { return {}; } std::vector< Record< Type > > result; auto &collectors = it->second; result.reserve(collectors.size()); std::transform(collectors.begin(), collectors.end(), std::back_inserter(result), [](auto &collector) { return (collector->*func)(); }); return result; } public: explicit CollectorRepo(Registry *registry) : m_registry(registry) { } std::vector< Record< Type > > collectAndClear(const Category *category) { return collectOp< &Collectors< Type >::combineAndClear >(category); } std::vector< Record< Type > > collect(const Category *category) { return collectOp< &Collectors< Type >::combine >(category); } Collector< Type > * defaultCollector(const char *category, const char *name) { return defaultCollector(m_registry->get(category, name)); } Collector< Type > * defaultCollector(const Id &id) { { absl::ReaderMutexLock l(&m_mutex); auto it = m_collectors.find(id); if(it != m_collectors.end()) { return it->second->defaultCollector(); } } { absl::WriterMutexLock l(&m_mutex); return getCollectors(id).defaultCollector(); } } std::shared_ptr< Collector< Type > > addCollector(const char *category, const char *name) { return addCollector(m_registry->get(category, name)); } std::shared_ptr< Collector< Type > > addCollector(const Id &id) { absl::WriterMutexLock l(&m_mutex); return getCollectors(id).add(); } std::vector< std::shared_ptr< Collector< Type > > > allCollectors(const Id &id) { absl::ReaderMutexLock l(&m_mutex); auto it = m_collectors.find(id); if(it == m_collectors.end()) { return {}; } return it->second->collectors(); } Registry & registry() { return *m_registry; } const Registry & registry() const { return *m_registry; } }; class Manager; class PublisherRegistry { using PubPtr = std::shared_ptr< Publisher >; using CatPublishers = std::multimap< const Category *, PubPtr >; using PubSet = std::set< PubPtr >; using PubReg = std::map< const Category *, CatPublishers::iterator >; using RegInfo = std::map< PubPtr, PubReg >; CatPublishers m_categoryPublishers; RegInfo m_registry; PubSet m_publishers; PublisherRegistry(const PublisherRegistry &) = delete; PublisherRegistry & operator=(const PublisherRegistry &) = delete; public: using GlobalIterator = PubSet::iterator; using CatIterator = CatPublishers::iterator; PublisherRegistry() = default; bool addGlobalPublisher(const std::shared_ptr< Publisher > &publisher) { if(m_publishers.find(publisher) != m_publishers.end()) { return false; } if(m_registry.find(publisher) != m_registry.end()) { return false; } m_publishers.insert(publisher); return true; } bool addPublisher(const Category *category, const std::shared_ptr< Publisher > &publisher) { if(m_publishers.find(publisher) != m_publishers.end()) { return false; } auto ® = m_registry[publisher]; if(reg.find(category) != reg.end()) { return false; } auto it = m_categoryPublishers.emplace(category, publisher); reg.emplace(category, it); return true; } bool removePublisher(const Publisher *publisher) { std::shared_ptr< Publisher > ptr(const_cast< Publisher * >(publisher), [](Publisher *) {}); auto allIt = m_publishers.find(ptr); if(allIt != m_publishers.end()) { m_publishers.erase(allIt); return true; } auto regIt = m_registry.find(ptr); if(regIt == m_registry.end()) { return false; } for(auto &spec : regIt->second) { m_categoryPublishers.erase(spec.second); } m_registry.erase(regIt); return true; } GlobalIterator globalBegin() { return m_publishers.begin(); } GlobalIterator globalEnd() { return m_publishers.end(); } CatIterator catBegin() { return m_categoryPublishers.begin(); } CatIterator catEnd() { return m_categoryPublishers.end(); } CatIterator lowerBound(const Category *category) { return m_categoryPublishers.lower_bound(category); } CatIterator upperBound(const Category *category) { return m_categoryPublishers.upper_bound(category); } std::vector< Publisher * > globalPublishers() const { std::vector< Publisher * > result; result.reserve(m_publishers.size()); std::transform(m_publishers.begin(), m_publishers.end(), std::back_inserter(result), [](const auto &p) { return p.get(); }); return result; } std::vector< Publisher * > catPublishers(const Category *category) const { std::vector< Publisher * > result; auto beg = m_categoryPublishers.lower_bound(category); auto end = m_categoryPublishers.upper_bound(category); result.reserve(std::distance(beg, end)); std::transform(beg, end, std::back_inserter(result), [](const auto &p) { return p.second.get(); }); return result; } }; struct PublisherHelper; /// The big dog. /// This class owns everything else, and is responsible for managing the /// gathering and publishing of metrics class Manager { public: // Public callback. If the bool flag is true, clear the metrics back to // their default state. using Handle = uint64_t; private: // Map categories to the times they were last reset using ResetTimes = std::map< const Category *, absl::Duration >; friend struct PublisherHelper; Registry m_registry; CollectorRepo< double > m_doubleRepo; CollectorRepo< int > m_intRepo; PublisherRegistry m_publishers GUARDED_BY(m_mutex); const absl::Duration m_createTime; ResetTimes m_resetTimes; util::Mutex m_publishLock ACQUIRED_BEFORE(m_mutex); mutable util::Mutex m_mutex ACQUIRED_AFTER(m_publishLock); public: static constexpr Handle INVALID_HANDLE = std::numeric_limits< Handle >::max(); Manager() : m_doubleRepo(&m_registry) , m_intRepo(&m_registry) , m_createTime(absl::Now() - absl::UnixEpoch()) { } /// Add a `publisher` which will receive all events bool addGlobalPublisher(const std::shared_ptr< Publisher > &publisher) { absl::WriterMutexLock l(&m_mutex); return m_publishers.addGlobalPublisher(publisher); } /// Add a `publisher` which will receive events for the given /// `categoryName` only bool addPublisher(const char *categoryName, const std::shared_ptr< Publisher > &publisher) { return addPublisher(m_registry.get(categoryName), publisher); } /// Add a `publisher` which will receive events for the given /// `category` only bool addPublisher(const Category *category, const std::shared_ptr< Publisher > &publisher) { absl::WriterMutexLock l(&m_mutex); return m_publishers.addPublisher(category, publisher); } bool removePublisher(const Publisher *publisher) { absl::WriterMutexLock l(&m_mutex); return m_publishers.removePublisher(publisher); } bool removePublisher(const std::shared_ptr< Publisher > &publisher) { absl::WriterMutexLock l(&m_mutex); return m_publishers.removePublisher(publisher.get()); } // clang-format off CollectorRepo& doubleCollectorRepo() { return m_doubleRepo; } CollectorRepo& intCollectorRepo() { return m_intRepo; } Registry& registry() { return m_registry; } const Registry& registry() const { return m_registry; } // clang-format on /// Publish specific categories of metric matching the category/categories Sample collectSample(Records &records, bool clear = false) { std::vector< const Category * > allCategories = m_registry.getAll(); return collectSample( records, absl::Span< const Category * >{allCategories}, clear); } Sample collectSample(Records &records, absl::Span< const Category * > categories, bool clear = false); /// Publish specific categories of metric matching the category/categories void publish(const Category *category, bool clear = true) { publish(absl::Span< const Category * >(&category, 1), clear); } void publish(absl::Span< const Category * > categories, bool clear = true); void publish(const std::set< const Category * > &categories, bool clear = true); void publishAll(bool clear = true) { std::vector< const Category * > allCategories = m_registry.getAll(); publish(absl::Span< const Category * >{allCategories}, clear); } void publishAllExcluding(const std::set< const Category * > &excludeCategories, bool clear = true) { if(excludeCategories.empty()) { publishAll(clear); return; } std::vector< const Category * > allCategories = m_registry.getAll(); std::vector< const Category * > includedCategories; includedCategories.reserve(allCategories.size() - excludeCategories.size()); std::copy_if( allCategories.begin(), allCategories.end(), std::back_inserter(includedCategories), [&](const Category *cat) { return excludeCategories.end() == excludeCategories.find(cat); }); if(!includedCategories.empty()) { publish(absl::Span< const Category * >{includedCategories}, clear); } } void enableCategory(const char *categoryName, bool enable = true) { m_registry.enable(m_registry.get(categoryName), enable); } void enableCategory(const Category *category, bool enable = true) { m_registry.enable(category, enable); } void enableAll(bool enable) { m_registry.enableAll(enable); } std::vector< Publisher * > globalPublishers() const { absl::ReaderMutexLock l(&m_mutex); return m_publishers.globalPublishers(); } std::vector< Publisher * > publishersForCategory(const char *categoryName) const { const Category *category = m_registry.findCategory(categoryName); return category ? publishersForCategory(category) : std::vector< Publisher * >(); } std::vector< Publisher * > publishersForCategory(const Category *category) const { absl::ReaderMutexLock l(&m_mutex); return m_publishers.catPublishers(category); } }; /// Provide a handy mechanism for retrieving the default manager, without /// a painful singleton mechanism class DefaultManager { static Manager *m_manager; public: static Manager * instance() { return m_manager; } static Manager * manager(Manager *value) { return value ? value : m_manager; } static Manager * create() { m_manager = new Manager; return m_manager; } static void destroy() { delete m_manager; m_manager = nullptr; } }; /// Scoped guard to manage the default manager class DefaultManagerGuard { DefaultManagerGuard(const DefaultManagerGuard &) = delete; DefaultManagerGuard & operator=(const DefaultManagerGuard &) = delete; public: DefaultManagerGuard() { DefaultManager::create(); } ~DefaultManagerGuard() { DefaultManager::destroy(); } Manager * instance() { return DefaultManager::instance(); } }; template < typename Collector, typename Value, CollectorRepo< Value > &(Manager::*repoFunc)() > class Metric { Collector *m_collector; // can be null const std::atomic_bool *m_enabled; public: static Collector * lookup(const char *category, const char *name, Manager *manager = nullptr) { manager = DefaultManager::manager(manager); return manager ? (manager->*repoFunc)().defaultCollector(category, name) : 0; } static Collector * lookup(const Id &id, Manager *manager = nullptr) { manager = DefaultManager::manager(manager); return manager ? (manager->*repoFunc)().defaultCollector(id) : 0; } Metric(const char *category, const char *name, Manager *manager) : m_collector(lookup(category, name, manager)) , m_enabled(m_collector ? &m_collector->id().category()->enabledRaw() : nullptr) { } Metric(const Id &id, Manager *manager) : m_collector(lookup(id, manager)) , m_enabled(m_collector ? &m_collector->id().category()->enabledRaw() : nullptr) { } Metric(Collector *collector) : m_collector(collector) , m_enabled(m_collector ? &m_collector->id().category()->enabledRaw() : nullptr) { } bool active() const { return m_enabled ? m_enabled->load(std::memory_order_relaxed) : false; } void tick() { if(active()) { m_collector->tick(static_cast< Value >(1)); } } void update(Value val) { if(active()) { m_collector->tick(val); } } void accumulate(size_t count, Value total, Value min, Value max) { if(active()) { m_collector->accumulate(count, total, min, max); } } void set(size_t count, Value total, Value min, Value max) { if(active()) { m_collector->set(count, total, min, max); } } Id id() const { return m_collector ? m_collector->id() : Id(); } const Collector * collector() const { return m_collector; } Collector * collector() { return m_collector; } static void getCollector(Collector **collector, CategoryContainer *container, const char *category, const char *metric) { Manager *manager = DefaultManager::instance(); *collector = (manager->*repoFunc().defaultCollector)(category, metric); manager->registry().registerContainer((*collector)->id().category(), container); } static void getCollector(Collector **collector, CategoryContainer *container, const char *category, const char *metric, Publication::Type type) { Manager *manager = DefaultManager::instance(); *collector = (manager->*repoFunc().defaultCollector)(category, metric); manager->registry().registerContainer((*collector)->id().category(), container); manager->registry().publicationType((*collector)->id(), type); } }; using DoubleMetric = Metric< DoubleCollector, double, &Manager::doubleCollectorRepo >; using IntMetric = Metric< IntCollector, int, &Manager::intCollectorRepo >; class TimerGuard { private: util::Stopwatch m_stopwatch; DoubleCollector *m_collector; TimerGuard(const TimerGuard &) = delete; TimerGuard & operator=(const TimerGuard &) = delete; public: TimerGuard(DoubleMetric *metric) : m_stopwatch() , m_collector(metric->active() ? metric->collector() : nullptr) { if(m_collector) { m_stopwatch.start(); } } TimerGuard(DoubleCollector *collector) : m_stopwatch() , m_collector(collector && collector->id().category()->enabled() ? collector : nullptr) { if(m_collector) { m_stopwatch.start(); } } TimerGuard(const char *category, const char *name, Manager *manager) : m_stopwatch(), m_collector(nullptr) { DoubleCollector *collector = DoubleMetric::lookup(category, name, manager); m_collector = (collector && collector->id().category()->enabled()) ? collector : nullptr; if(m_collector) { m_stopwatch.start(); } } TimerGuard(const Id &id, Manager *manager) : m_stopwatch(), m_collector(nullptr) { DoubleCollector *collector = DoubleMetric::lookup(id, manager); m_collector = (collector && collector->id().category()->enabled()) ? collector : nullptr; if(m_collector) { m_stopwatch.start(); } } ~TimerGuard() { if(active()) { m_stopwatch.stop(); m_collector->tick(absl::ToDoubleSeconds(m_stopwatch.time())); } } bool active() const { return m_collector ? m_collector->id().category()->enabled() : false; } }; struct PublisherSchedulerData; class PublisherScheduler { friend class PublisherSchedulerGuard; using Categories = std::map< const Category *, absl::Duration >; using Repeaters = std::map< absl::Duration, std::shared_ptr< PublisherSchedulerData > >; thread::Scheduler &m_scheduler; Manager *m_manager; Categories m_categories GUARDED_BY(m_mutex); Repeaters m_repeaters GUARDED_BY(m_mutex); absl::Duration m_defaultInterval GUARDED_BY(m_mutex); mutable util::Mutex m_mutex; void publish(const std::shared_ptr< PublisherSchedulerData > &data) const; void cancel(Categories::iterator it) EXCLUSIVE_LOCKS_REQUIRED(m_mutex); bool cancelDefault() EXCLUSIVE_LOCKS_REQUIRED(m_mutex); public: PublisherScheduler(thread::Scheduler &scheduler, Manager *manager) : m_scheduler(scheduler), m_manager(manager), m_defaultInterval() { } ~PublisherScheduler() { cancelAll(); } void schedule(const char *categoryName, absl::Duration interval) { return schedule(m_manager->registry().get(categoryName), interval); } void schedule(const Category *category, absl::Duration interval); void setDefault(absl::Duration interval); bool cancel(const char *categoryName) { return cancel(m_manager->registry().get(categoryName)); } bool cancel(const Category *category); bool clearDefault(); void cancelAll(); Manager * manager() { return m_manager; } const Manager * manager() const { return m_manager; } absl::optional< absl::Duration > find(const char *categoryName) const { return find(m_manager->registry().get(categoryName)); } absl::optional< absl::Duration > find(const Category *category) const; absl::optional< absl::Duration > getDefault() const; std::vector< std::pair< const Category *, absl::Duration > > getAll() const; }; } // namespace metrics } // namespace llarp #endif