blob: a24c387015d854cacbb077970acdff57c16e79df [file] [log] [blame]
// Copyright 2018 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.
#include <float.h>
#include <math.h>
#include <string.h>
#include <cobalt-client/cpp/histogram.h>
#include <cobalt-client/cpp/histogram-internal.h>
#include <cobalt-client/cpp/histogram-options.h>
#include <fbl/limits.h>
#include <fuchsia/cobalt/c/fidl.h>
namespace cobalt_client {
namespace internal {
namespace {
double GetLinearBucketValue(uint32_t bucket_index, const HistogramOptions& options) {
if (bucket_index == 0) {
return -DBL_MAX;
}
return options.scalar * (bucket_index - 1) + options.offset;
}
double GetExponentialBucketValue(uint32_t bucket_index, const HistogramOptions& options) {
if (bucket_index == 0) {
return -DBL_MAX;
}
return options.scalar * pow(options.base, bucket_index - 1) + options.offset;
}
uint32_t GetLinearBucket(double value, const HistogramOptions& options, double max_value) {
if (value < options.offset) {
return 0;
} else if (value >= max_value) {
return options.bucket_count + 1;
}
double unshifted_bucket = (value - options.offset) / options.scalar;
ZX_DEBUG_ASSERT(unshifted_bucket >= fbl::numeric_limits<uint32_t>::min());
ZX_DEBUG_ASSERT(unshifted_bucket <= fbl::numeric_limits<uint32_t>::max());
return static_cast<uint32_t>(unshifted_bucket) + 1;
}
uint32_t GetExponentialBucket(double value, const HistogramOptions& options, double max_value) {
if (value < options.scalar + options.offset) {
return 0;
} else if (value >= max_value) {
return options.bucket_count + 1;
}
// Use bigger size double to perform the calculations to avoid precision errors near boundaries.
double diff = value - options.offset;
uint32_t unshifted_bucket = 0;
// Only use the formula if the difference is positive.
if (diff >= options.scalar) {
unshifted_bucket = static_cast<uint32_t>(floor((log2(diff) - log2(options.scalar)) / log2(options.base)));
}
ZX_DEBUG_ASSERT(unshifted_bucket <= options.bucket_count + 1);
double lower_bound = GetExponentialBucketValue(unshifted_bucket + 1, options);
if (lower_bound > value) {
--unshifted_bucket;
}
return unshifted_bucket + 1;
}
void LoadExponential(HistogramOptions* options) {
double max_value =
options->scalar * pow(options->base, options->bucket_count) + options->offset;
options->map_fn = [max_value](double val, const HistogramOptions& options) {
return internal::GetExponentialBucket(val, options, max_value);
};
options->reverse_map_fn = internal::GetExponentialBucketValue;
}
void LoadLinear(HistogramOptions* options) {
double max_value =
static_cast<double>(options->scalar * options->bucket_count + options->offset);
options->map_fn = [max_value](double val, const HistogramOptions& options) {
return internal::GetLinearBucket(val, options, max_value);
};
options->reverse_map_fn = internal::GetLinearBucketValue;
}
} // namespace
BaseHistogram::BaseHistogram(uint32_t num_buckets) {
buckets_.reserve(num_buckets);
for (uint32_t i = 0; i < num_buckets; ++i) {
buckets_.push_back(BaseCounter());
}
}
BaseHistogram::BaseHistogram(BaseHistogram&& other) = default;
RemoteHistogram::RemoteHistogram(uint32_t num_buckets, uint64_t metric_id,
const fbl::Vector<Metadata>& metadata)
: BaseHistogram(num_buckets), buffer_(metadata), metric_id_(metric_id) {
bucket_buffer_.reserve(num_buckets);
for (uint32_t i = 0; i < num_buckets; ++i) {
HistogramBucket bucket;
bucket.count = 0;
bucket.index = i;
bucket_buffer_.push_back(bucket);
}
auto* buckets = buffer_.mutable_event_data();
buckets->set_data(bucket_buffer_.get());
buckets->set_count(bucket_buffer_.size());
}
RemoteHistogram::RemoteHistogram(RemoteHistogram&& other)
: BaseHistogram(fbl::move(other)), buffer_(fbl::move(other.buffer_)),
bucket_buffer_(fbl::move(other.bucket_buffer_)), metric_id_(other.metric_id_) {}
bool RemoteHistogram::Flush(const RemoteHistogram::FlushFn& flush_handler) {
if (!buffer_.TryBeginFlush()) {
return false;
}
// Sets every bucket back to 0, not all buckets will be at the same instant, but
// eventual consistency in the backend is good enough.
for (uint32_t bucket_index = 0; bucket_index < bucket_buffer_.size(); ++bucket_index) {
bucket_buffer_[bucket_index].count = buckets_[bucket_index].Exchange();
}
flush_handler(metric_id_, buffer_, fbl::BindMember(&buffer_, &EventBuffer::CompleteFlush));
return true;
}
} // namespace internal
HistogramOptions::HistogramOptions(const HistogramOptions& other)
: base(other.base), scalar(other.scalar), offset(other.offset),
bucket_count(other.bucket_count), type(other.type) {
if (type == Type::kLinear) {
internal::LoadLinear(this);
} else {
internal::LoadExponential(this);
}
}
HistogramOptions HistogramOptions::Exponential(uint32_t bucket_count, uint32_t base,
uint32_t scalar, int64_t offset) {
HistogramOptions options;
options.bucket_count = bucket_count;
options.base = base;
options.scalar = scalar;
options.offset = static_cast<double>(offset - scalar);
options.type = Type::kExponential;
internal::LoadExponential(&options);
return options;
}
HistogramOptions HistogramOptions::Linear(uint32_t bucket_count, uint32_t scalar, int64_t offset) {
HistogramOptions options;
options.bucket_count = bucket_count;
options.scalar = scalar;
options.offset = static_cast<double>(offset);
options.type = Type::kLinear;
internal::LoadLinear(&options);
return options;
}
Histogram::Histogram(HistogramOptions* options, internal::RemoteHistogram* remote_histogram)
: options_(options), remote_histogram_(remote_histogram) {}
Histogram::Histogram(const Histogram&) = default;
Histogram::Histogram(Histogram&&) = default;
Histogram& Histogram::operator=(const Histogram&) = default;
Histogram& Histogram::operator=(Histogram&&) = default;
Histogram::~Histogram() = default;
template <typename ValueType> void Histogram::Add(ValueType value, Histogram::Count times) {
double dbl_value = static_cast<double>(value);
uint32_t bucket = options_->map_fn(dbl_value, *options_);
remote_histogram_->IncrementCount(bucket, times);
}
template <typename ValueType> Histogram::Count Histogram::GetRemoteCount(ValueType value) const {
double dbl_value = static_cast<double>(value);
uint32_t bucket = options_->map_fn(dbl_value, *options_);
return remote_histogram_->GetCount(bucket);
}
// Supported template instantiations.
template void Histogram::Add<double>(double, Histogram::Count);
template void Histogram::Add<int32_t>(int32_t, Histogram::Count);
template void Histogram::Add<uint32_t>(uint32_t, Histogram::Count);
template void Histogram::Add<int64_t>(int64_t, Histogram::Count);
template void Histogram::Add<uint64_t>(uint64_t, Histogram::Count);
template Histogram::Count Histogram::GetRemoteCount<double>(double) const;
template Histogram::Count Histogram::GetRemoteCount<int32_t>(int32_t) const;
template Histogram::Count Histogram::GetRemoteCount<uint32_t>(uint32_t) const;
template Histogram::Count Histogram::GetRemoteCount<int64_t>(int64_t) const;
template Histogram::Count Histogram::GetRemoteCount<uint64_t>(uint64_t) const;
} // namespace cobalt_client