255 lines
7.2 KiB
C++
255 lines
7.2 KiB
C++
/*
|
|
* Stats.h
|
|
*
|
|
* This source file is part of the FoundationDB open source project
|
|
*
|
|
* Copyright 2013-2018 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
|
|
#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 : 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();
|
|
|
|
std::string const& getName() const { return name; }
|
|
|
|
Value getIntervalDelta() const { return interval_delta; }
|
|
Value getValue() const { return interval_start_value + interval_delta; }
|
|
|
|
// dValue / dt
|
|
double getRate() const;
|
|
|
|
// 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;
|
|
|
|
bool hasRate() const { return true; }
|
|
bool hasRoughness() const { 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 { return f(); }
|
|
|
|
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());
|
|
|
|
class LatencyBands {
|
|
public:
|
|
LatencyBands(std::string name, UID id, double loggingInterval) : name(name), id(id), loggingInterval(loggingInterval), cc(nullptr), filteredCount(nullptr) {}
|
|
|
|
void addThreshold(double value) {
|
|
if(value > 0 && bands.count(value) == 0) {
|
|
if(bands.size() == 0) {
|
|
ASSERT(!cc && !filteredCount);
|
|
cc = new CounterCollection(name, id.toString());
|
|
logger = traceCounters(name, id, loggingInterval, cc, id.toString() + "/" + name);
|
|
filteredCount = new 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();
|
|
|
|
for(auto itr : bands) {
|
|
delete itr.second;
|
|
}
|
|
|
|
bands.clear();
|
|
|
|
delete filteredCount;
|
|
delete cc;
|
|
|
|
filteredCount = nullptr;
|
|
cc = nullptr;
|
|
}
|
|
|
|
~LatencyBands() {
|
|
clearBands();
|
|
}
|
|
|
|
private:
|
|
std::map<double, Counter*> bands;
|
|
Counter *filteredCount;
|
|
|
|
std::string name;
|
|
UID id;
|
|
double loggingInterval;
|
|
|
|
CounterCollection *cc;
|
|
Future<Void> logger;
|
|
|
|
void insertBand(double value) {
|
|
bands.insert(std::make_pair(value, new Counter(format("Band%f", value), *cc)));
|
|
}
|
|
};
|
|
|
|
class LatencySample {
|
|
public:
|
|
LatencySample(std::string name, UID id, double loggingInterval, int sampleSize) : name(name), id(id), sample(sampleSize), sampleStart(now()) {
|
|
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;
|
|
|
|
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(id.toString() + "/" + name);
|
|
|
|
sample.clear();
|
|
sampleStart = now();
|
|
}
|
|
};
|
|
|
|
#endif
|