268 lines
7.9 KiB
C++
268 lines
7.9 KiB
C++
/*
|
|
* Stats.h
|
|
*
|
|
* This source file is part of the FoundationDB open source project
|
|
*
|
|
* Copyright 2013-2022 Apple Inc. and the FoundationDB project authors
|
|
*
|
|
* Licensed 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.
|
|
*/
|
|
|
|
#ifndef FDBRPC_STATS_H
|
|
#define FDBRPC_STATS_H
|
|
#include <type_traits>
|
|
#pragma once
|
|
|
|
// Yet another performance statistics interface
|
|
/*
|
|
|
|
struct MyCounters {
|
|
CounterCollection cc;
|
|
Counter foo, bar, baz;
|
|
MyCounters() : foo("foo", cc), bar("bar", cc), baz("baz", cc) {}
|
|
};
|
|
|
|
*/
|
|
|
|
#include <cstdint>
|
|
#include <cstddef>
|
|
#include "flow/flow.h"
|
|
#include "flow/TDMetric.actor.h"
|
|
#include "fdbrpc/ContinuousSample.h"
|
|
|
|
struct ICounter {
|
|
// All counters have a name and value
|
|
virtual std::string const& getName() const = 0;
|
|
virtual int64_t getValue() const = 0;
|
|
|
|
// Counters may also have rate and roughness
|
|
virtual bool hasRate() const = 0;
|
|
virtual double getRate() const = 0;
|
|
virtual bool hasRoughness() const = 0;
|
|
virtual double getRoughness() const = 0;
|
|
|
|
virtual void resetInterval() = 0;
|
|
|
|
virtual void remove() {}
|
|
};
|
|
|
|
template <>
|
|
struct Traceable<ICounter*> : std::true_type {
|
|
static std::string toString(ICounter const* counter) {
|
|
if (counter->hasRate() && counter->hasRoughness()) {
|
|
return format("%g %g %lld", counter->getRate(), counter->getRoughness(), (long long)counter->getValue());
|
|
} else {
|
|
return format("%lld", (long long)counter->getValue());
|
|
}
|
|
}
|
|
};
|
|
|
|
struct CounterCollection {
|
|
CounterCollection(std::string name, std::string id = std::string()) : name(name), id(id) {}
|
|
std::vector<struct ICounter*> counters, counters_to_remove;
|
|
~CounterCollection() {
|
|
for (auto c : counters_to_remove)
|
|
c->remove();
|
|
}
|
|
std::string name;
|
|
std::string id;
|
|
|
|
void logToTraceEvent(TraceEvent& te) const;
|
|
};
|
|
|
|
struct Counter final : ICounter, NonCopyable {
|
|
public:
|
|
typedef int64_t Value;
|
|
|
|
Counter(std::string const& name, CounterCollection& collection);
|
|
|
|
void operator+=(Value delta);
|
|
void operator++() { *this += 1; }
|
|
void clear();
|
|
void resetInterval() override;
|
|
|
|
std::string const& getName() const override { return name; }
|
|
|
|
Value getIntervalDelta() const { return interval_delta; }
|
|
Value getValue() const override { return interval_start_value + interval_delta; }
|
|
|
|
// dValue / dt
|
|
double getRate() const override;
|
|
|
|
// Measures the clumpiness or dispersion of the counter.
|
|
// Computed as a normalized variance of the time between each incrementation of the value.
|
|
// A delta of N is treated as N distinct increments, with N-1 increments having time span 0.
|
|
// Normalization is performed by dividing each time sample by the mean time before taking variance.
|
|
//
|
|
// roughness = Variance(t/mean(T)) for time interval samples t in T
|
|
//
|
|
// A uniformly periodic counter will have roughness of 0
|
|
// A uniformly periodic counter that increases in clumps of N will have roughness of N-1
|
|
// A counter with exponentially distributed incrementations will have roughness of 1
|
|
double getRoughness() const override;
|
|
|
|
bool hasRate() const override { return true; }
|
|
bool hasRoughness() const override { return true; }
|
|
|
|
private:
|
|
std::string name;
|
|
double interval_start, last_event, interval_sq_time, roughness_interval_start;
|
|
Value interval_delta, interval_start_value;
|
|
Int64MetricHandle metric;
|
|
};
|
|
|
|
template <>
|
|
struct Traceable<Counter> : std::true_type {
|
|
static std::string toString(Counter const& counter) {
|
|
return Traceable<ICounter*>::toString((ICounter const*)&counter);
|
|
}
|
|
};
|
|
|
|
template <class F>
|
|
struct SpecialCounter final : ICounter, FastAllocated<SpecialCounter<F>>, NonCopyable {
|
|
SpecialCounter(CounterCollection& collection, std::string const& name, F&& f) : name(name), f(f) {
|
|
collection.counters.push_back(this);
|
|
collection.counters_to_remove.push_back(this);
|
|
}
|
|
void remove() override { delete this; }
|
|
|
|
std::string const& getName() const override { return name; }
|
|
int64_t getValue() const override {
|
|
auto result = f();
|
|
// Disallow conversion from floating point to int64_t, since this has
|
|
// been a source of confusion - e.g. a percentage represented as a
|
|
// fraction between 0 and 1 is not meaningful after conversion to
|
|
// int64_t.
|
|
static_assert(!std::is_floating_point_v<decltype(result)>);
|
|
return result;
|
|
}
|
|
|
|
void resetInterval() override {}
|
|
|
|
bool hasRate() const override { return false; }
|
|
double getRate() const override { throw internal_error(); }
|
|
bool hasRoughness() const override { return false; }
|
|
double getRoughness() const override { throw internal_error(); }
|
|
|
|
std::string name;
|
|
F f;
|
|
};
|
|
template <class F>
|
|
static void specialCounter(CounterCollection& collection, std::string const& name, F&& f) {
|
|
new SpecialCounter<F>(collection, name, std::move(f));
|
|
}
|
|
|
|
Future<Void> traceCounters(
|
|
std::string const& traceEventName,
|
|
UID const& traceEventID,
|
|
double const& interval,
|
|
CounterCollection* const& counters,
|
|
std::string const& trackLatestName = std::string(),
|
|
std::function<void(TraceEvent&)> const& decorator = [](TraceEvent& te) {});
|
|
|
|
class LatencyBands {
|
|
public:
|
|
LatencyBands(std::string name, UID id, double loggingInterval)
|
|
: name(name), id(id), loggingInterval(loggingInterval) {}
|
|
|
|
void addThreshold(double value) {
|
|
if (value > 0 && bands.count(value) == 0) {
|
|
if (bands.size() == 0) {
|
|
ASSERT(!cc && !filteredCount);
|
|
cc = std::make_unique<CounterCollection>(name, id.toString());
|
|
logger = traceCounters(name, id, loggingInterval, cc.get(), id.toString() + "/" + name);
|
|
filteredCount = std::make_unique<Counter>("Filtered", *cc);
|
|
insertBand(std::numeric_limits<double>::infinity());
|
|
}
|
|
|
|
insertBand(value);
|
|
}
|
|
}
|
|
|
|
void addMeasurement(double measurement, bool filtered = false) {
|
|
if (filtered && filteredCount) {
|
|
++(*filteredCount);
|
|
} else if (bands.size() > 0) {
|
|
auto itr = bands.upper_bound(measurement);
|
|
ASSERT(itr != bands.end());
|
|
++(*itr->second);
|
|
}
|
|
}
|
|
|
|
void clearBands() {
|
|
logger = Void();
|
|
bands.clear();
|
|
filteredCount.reset();
|
|
cc.reset();
|
|
}
|
|
|
|
~LatencyBands() { clearBands(); }
|
|
|
|
private:
|
|
std::map<double, std::unique_ptr<Counter>> bands;
|
|
std::unique_ptr<Counter> filteredCount;
|
|
|
|
std::string name;
|
|
UID id;
|
|
double loggingInterval;
|
|
|
|
std::unique_ptr<CounterCollection> cc;
|
|
Future<Void> logger;
|
|
|
|
void insertBand(double value) {
|
|
bands.emplace(std::make_pair(value, std::make_unique<Counter>(format("Band%f", value), *cc)));
|
|
}
|
|
};
|
|
|
|
class LatencySample {
|
|
public:
|
|
LatencySample(std::string name, UID id, double loggingInterval, int sampleSize)
|
|
: name(name), id(id), sampleStart(now()), sample(sampleSize),
|
|
latencySampleEventHolder(makeReference<EventCacheHolder>(id.toString() + "/" + name)) {
|
|
logger = recurring([this]() { logSample(); }, loggingInterval);
|
|
}
|
|
|
|
void addMeasurement(double measurement) { sample.addSample(measurement); }
|
|
|
|
private:
|
|
std::string name;
|
|
UID id;
|
|
double sampleStart;
|
|
|
|
ContinuousSample<double> sample;
|
|
Future<Void> logger;
|
|
|
|
Reference<EventCacheHolder> latencySampleEventHolder;
|
|
|
|
void logSample() {
|
|
TraceEvent(name.c_str(), id)
|
|
.detail("Count", sample.getPopulationSize())
|
|
.detail("Elapsed", now() - sampleStart)
|
|
.detail("Min", sample.min())
|
|
.detail("Max", sample.max())
|
|
.detail("Mean", sample.mean())
|
|
.detail("Median", sample.median())
|
|
.detail("P25", sample.percentile(0.25))
|
|
.detail("P90", sample.percentile(0.9))
|
|
.detail("P95", sample.percentile(0.95))
|
|
.detail("P99", sample.percentile(0.99))
|
|
.detail("P99.9", sample.percentile(0.999))
|
|
.trackLatest(latencySampleEventHolder->trackingKey);
|
|
|
|
sample.clear();
|
|
sampleStart = now();
|
|
}
|
|
};
|
|
|
|
#endif
|