blob: a6c6b089c1f29623e5f4f0b76c510159f4f9ee28 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SRC_LIB_METRICS_BUFFER_METRICS_BUFFER_H_
#define SRC_LIB_METRICS_BUFFER_METRICS_BUFFER_H_
#include <fidl/fuchsia.io/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async/cpp/task.h>
#include <lib/sys/cpp/service_directory.h>
#include <memory>
#include <unordered_map>
#include <unordered_set>
#include "src/lib/metrics_buffer/metrics_impl.h"
namespace cobalt {
class MetricsBuffer;
// This class is a convenience interface which remembers a metric_id.
class MetricBuffer {
public:
MetricBuffer(const MetricBuffer& to_copy) = delete;
MetricBuffer& operator=(const MetricBuffer& to_copy) = delete;
MetricBuffer(MetricBuffer&& to_move) = default;
MetricBuffer& operator=(MetricBuffer&& to_move) = default;
~MetricBuffer() = default;
void LogEvent(std::vector<uint32_t> dimension_values);
void LogEventCount(std::vector<uint32_t> dimension_values, uint32_t count);
private:
friend class MetricsBuffer;
MetricBuffer(std::shared_ptr<MetricsBuffer> parent, uint32_t metric_id);
std::shared_ptr<MetricsBuffer> parent_;
uint32_t metric_id_ = 0;
};
// This class is a convenience interface which remembers a metric_id.
class StringMetricBuffer {
public:
StringMetricBuffer(const StringMetricBuffer& to_copy) = delete;
StringMetricBuffer& operator=(const StringMetricBuffer& to_copy) = delete;
StringMetricBuffer(StringMetricBuffer&& to_move) = default;
StringMetricBuffer& operator=(StringMetricBuffer&& to_move) = default;
~StringMetricBuffer() = default;
void LogString(std::vector<uint32_t> dimension_values, std::string string_value);
private:
friend class MetricsBuffer;
StringMetricBuffer(std::shared_ptr<MetricsBuffer> parent, uint32_t metric_id);
std::shared_ptr<MetricsBuffer> parent_;
uint32_t metric_id_ = 0;
};
struct ExponentialIntegerBuckets {
int64_t floor;
uint32_t num_buckets;
uint32_t initial_step;
uint32_t step_multiplier;
};
struct LinearIntegerBuckets {
int64_t floor;
uint32_t num_buckets;
uint32_t step_size;
};
struct HistogramInfo {
uint32_t metric_id = 0;
std::variant<ExponentialIntegerBuckets, LinearIntegerBuckets> buckets;
};
// If metrics.cb.h codegen provides a non-macro way in future, switch to that.
#define COBALT_EXPONENTIAL_HISTOGRAM_INFO(base_name) \
[] { \
::cobalt::ExponentialIntegerBuckets buckets; \
buckets.floor = base_name##IntBucketsFloor; \
buckets.num_buckets = base_name##IntBucketsNumBuckets; \
buckets.initial_step = base_name##IntBucketsInitialStep; \
buckets.step_multiplier = base_name##IntBucketsStepMultiplier; \
return ::cobalt::HistogramInfo{.metric_id = base_name##MetricId, .buckets = buckets}; \
}()
#define COBALT_LINEAR_HISTOGRAM_INFO(base_name) \
[] { \
::cobalt::LinearIntegerBuckets buckets; \
buckets.floor = base_name##IntBucketsFloor; \
buckets.num_buckets = base_name##IntBucketsNumBuckets; \
buckets.step_size = base_name##IntBucketsStepSize; \
return ::cobalt::HistogramInfo{.metric_id = base_name##MetricId, .buckets = buckets}; \
}()
// This class is a convenience interface which remembers metric_id + bucket_config, and calculates
// which bucket for the caller.
class HistogramMetricBuffer {
public:
HistogramMetricBuffer(const HistogramMetricBuffer& to_copy) = delete;
HistogramMetricBuffer& operator=(const HistogramMetricBuffer& to_copy) = delete;
HistogramMetricBuffer(HistogramMetricBuffer&& to_move) = default;
HistogramMetricBuffer& operator=(HistogramMetricBuffer&& to_move) = default;
~HistogramMetricBuffer() = default;
// This computes which histogram bucket, and adds 1 to the tally of that bucket.
void LogValue(std::vector<uint32_t> dimension_values, int64_t value);
private:
friend class MetricsBuffer;
// For the second parameter, see COBALT_EXPONENTIAL_HISTOGRAM_INFO() and
// COBALT_LINEAR_HISTOGRAM_INFO() defined above.
HistogramMetricBuffer(std::shared_ptr<MetricsBuffer> parent, HistogramInfo histogram_info);
std::shared_ptr<MetricsBuffer> parent_;
HistogramInfo histogram_info_;
// This info corresponds to fields of cobalt IntegerBucketConfig. We can't depend on
// cobalt buckets_config from here because it depends on syslog.
std::vector<int64_t> floors_;
};
// The purpose of this class is to ensure the rate of messages to Cobalt stays reasonable, per
// Cobalt's rate requirement/recommendation in the Cobalt docs.
//
// Typically it'll make sense to only have one of these per process, but that's not enforced.
//
// Methods of this class can be called on any thread.
class MetricsBuffer final : public std::enable_shared_from_this<MetricsBuffer> {
public:
// Initially a noop instance, so unit tests don't need to wire up cobalt. Call
// SetServiceDirectory() to enable and start logging.
static std::shared_ptr<MetricsBuffer> Create(uint32_t project_id);
// !service_directory is ok. If !service_directory, the instance will be a nop instance until
// SetServiceDirectory() is called.
static std::shared_ptr<MetricsBuffer> Create(
uint32_t project_id, std::shared_ptr<sys::ServiceDirectory> service_directory);
~MetricsBuffer() __TA_EXCLUDES(lock_);
// Set the ServiceDirectory from which to get fuchsia.metrics.MetricEventLoggerFactory. This can
// be nullptr. This can be called again, regardless of whether there was already a previous
// ServiceDirectory. Previously-queued events may be lost (especially recently-queued events) when
// switching to a new ServiceDirectory.
void SetServiceDirectory(std::shared_ptr<sys::ServiceDirectory> service_directory)
__TA_EXCLUDES(lock_);
// This specifies the minimum amount of time between logging batches to cobalt. If enough
// different metrics have accumulated to force more than one message to cobalt, then more than
// one message is possible, but typically a single message will be sent to cobalt no more often
// than this. In unit tests we use this to turn the min_logging_period way down so that tests can
// finish faster.
void SetMinLoggingPeriod(zx::duration min_logging_period);
// Log the event as EVENT_COUNT, with period_duration_micros 0, possibly aggregating with any
// other calls to this method with the same component and event wihtin a short duration to limit
// the rate of FIDL calls to Cobalt, per the rate requirement/recommendation in the Cobalt docs.
void LogEvent(uint32_t metric_id, std::vector<uint32_t> dimension_values) __TA_EXCLUDES(lock_);
void LogEventCount(uint32_t metric_id, std::vector<uint32_t> dimension_values, uint32_t count);
// The string_value is not an arbitrary string; it must be one of the potential strings defined in
// the metric definition, or the string_value ends up getting ignored.
void LogString(uint32_t metric_id, std::vector<uint32_t> dimension_values,
std::string string_value);
// Use sparingly, only when it's appropriate to force the counts to flush to Cobalt, which will
// typically only be before orderly exit or in situations like driver suspend. Over-use of this
// method will break the purpose of using this class, which is to ensure the rate of messages to
// Cobalt stays reasonable.
void ForceFlush() __TA_EXCLUDES(lock_);
MetricBuffer CreateMetricBuffer(uint32_t metric_id);
StringMetricBuffer CreateStringMetricBuffer(uint32_t metric_id);
HistogramMetricBuffer CreateHistogramMetricBuffer(HistogramInfo histogram_info);
private:
friend class HistogramMetricBuffer;
// This computes which histogram bucket, and adds 1 to the tally of that bucket.
void LogHistogramValue(const HistogramInfo& histogram_info, std::vector<int64_t>& floors,
std::vector<uint32_t> dimension_values, int64_t value);
explicit MetricsBuffer(uint32_t project_id) __TA_EXCLUDES(lock_);
MetricsBuffer(uint32_t project_id, std::shared_ptr<sys::ServiceDirectory> service_directory)
__TA_EXCLUDES(lock_);
class MetricKey {
public:
MetricKey(uint32_t metric_id, std::vector<uint32_t> dimension_values);
uint32_t metric_id() const;
const std::vector<uint32_t>& dimension_values() const;
private:
uint32_t metric_id_ = 0;
std::vector<uint32_t> dimension_values_;
};
struct MetricKeyHash {
size_t operator()(const MetricKey& key) const noexcept;
private:
std::hash<uint32_t> hash_uint32_;
};
struct MetricKeyEqual {
bool operator()(const MetricKey& lhs, const MetricKey& rhs) const noexcept;
};
// We don't keep a count per string; instead we collapse out any additional string instances per
// flush period, since each tally of a string would require an additional item in the batch. We
// want to avoid creating a large number of items in a batch or a large number of batches due to
// a noisy string. We also want to avoid using unbounded memory in the MetricsBuffer. We could
// achieve this with a cap on how many instances of the same string we're willing to buffer and
// batch, but for now we just cap at buffering 1 of a given string at any given time.
class PendingStringsItem {
public:
PendingStringsItem(uint32_t metric_id, std::vector<uint32_t> dimension_values,
std::string string_value);
uint32_t metric_id() const;
const std::vector<uint32_t>& dimension_values() const;
const std::string& string_value() const;
private:
uint32_t metric_id_ = 0;
std::vector<uint32_t> dimension_values_;
std::string string_value_;
};
struct PendingStringsItemHash {
size_t operator()(const PendingStringsItem& item) const noexcept;
private:
std::hash<uint32_t> hash_uint32_;
std::hash<std::string> hash_string_;
};
struct PendingStringsItemEqual {
bool operator()(const PendingStringsItem& lhs, const PendingStringsItem& rhs) const noexcept;
};
void TryPostFlushCountsLocked() __TA_REQUIRES(lock_);
void FlushPendingEventCounts() __TA_EXCLUDES(lock_);
static constexpr zx::duration kDefaultMinLoggingPeriod = zx::sec(5);
std::mutex lock_;
const uint32_t project_id_{};
// For historical reasons, we have a separate async::Loop for each instance of
// cobalt::MetricsImpl. We only SetServiceDirectory() once in non-test scenarios, so outside tests
// we'll only ever create one async::Loop.
std::unique_ptr<async::Loop> loop_;
std::unique_ptr<cobalt::MetricsImpl> cobalt_logger_ __TA_GUARDED(lock_);
zx::time last_flushed_ __TA_GUARDED(lock_) = zx::time::infinite_past();
// From component and event to event count.
using PendingCounts = std::unordered_map<MetricKey, int64_t, MetricKeyHash, MetricKeyEqual>;
PendingCounts pending_counts_ __TA_GUARDED(lock_);
// We don't keep a count per string; instead we collapse out any additional string instances per
// flush period, since each tally of a string would require an additional item in the batch. We
// want to avoid creating a large number of items in a batch or a large number of batches due to
// a noisy string. We also want to avoid using unbounded memory in the MetricsBuffer. We could
// achieve this with a cap on how many instances of the same string we're willing to buffer and
// batch, but for now we just cap at buffering 1 of a given string at any given time.
using PendingStrings =
std::unordered_map<MetricKey, std::unordered_set<std::string>, MetricKeyHash, MetricKeyEqual>;
PendingStrings pending_strings_ __TA_GUARDED(lock_);
// key - bucket index
// value - how many tallies to add to bucket
using PendingBuckets = std::unordered_map<uint32_t, uint32_t>;
using PendingHistograms =
std::unordered_map<MetricKey, PendingBuckets, MetricKeyHash, MetricKeyEqual>;
PendingHistograms pending_histograms_ __TA_GUARDED(lock_);
zx::duration min_logging_period_ = kDefaultMinLoggingPeriod;
};
} // namespace cobalt
#endif // SRC_LIB_METRICS_BUFFER_METRICS_BUFFER_H_