-
Notifications
You must be signed in to change notification settings - Fork 4.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
stats: add built-in log linear histogram support #2932
Changes from 36 commits
f0a7faa
1a35b0e
5b66b74
8052ae7
5d30b85
a0bf03c
dede1fd
e744253
0aeac15
c200974
dd79e35
9dcddf6
5e8169a
f35083c
08cc211
a107b0f
dadba03
e41d7ef
9da426e
92620e5
6e6253a
6ca87c4
fa8f646
2b5772f
1032ffc
8266868
a041a3e
c9fef4b
29646d0
df348b2
68039f2
1b75b93
d5d4125
16003e7
05d00a2
9189b9e
1837956
8b1c7ab
2894da8
1e1f494
5ae99d3
7d3844e
5ccad60
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
cc_library( | ||
name = "libcircllhist", | ||
srcs = ["src/circllhist.c"], | ||
hdrs = [ | ||
"src/circllhist.h", | ||
], | ||
includes = ["src"], | ||
visibility = ["//visibility:public"], | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ | |
|
||
#include <chrono> | ||
#include <cstdint> | ||
#include <functional> | ||
#include <list> | ||
#include <memory> | ||
#include <string> | ||
|
@@ -114,6 +115,11 @@ class Metric { | |
* Returns the name of the Metric with the portions designated as tags removed. | ||
*/ | ||
virtual const std::string& tagExtractedName() const PURE; | ||
|
||
/** | ||
* Indicates whether a metric has been used. | ||
*/ | ||
virtual bool used() const PURE; | ||
}; | ||
|
||
/** | ||
|
@@ -128,7 +134,6 @@ class Counter : public virtual Metric { | |
virtual void inc() PURE; | ||
virtual uint64_t latch() PURE; | ||
virtual void reset() PURE; | ||
virtual bool used() const PURE; | ||
virtual uint64_t value() const PURE; | ||
}; | ||
|
||
|
@@ -146,12 +151,34 @@ class Gauge : public virtual Metric { | |
virtual void inc() PURE; | ||
virtual void set(uint64_t value) PURE; | ||
virtual void sub(uint64_t amount) PURE; | ||
virtual bool used() const PURE; | ||
virtual uint64_t value() const PURE; | ||
}; | ||
|
||
typedef std::shared_ptr<Gauge> GaugeSharedPtr; | ||
|
||
/** | ||
* Holds the computed statistics for a histogram. | ||
*/ | ||
class HistogramStatistics { | ||
public: | ||
virtual ~HistogramStatistics() {} | ||
|
||
/** | ||
* Returns summary representation of the histogram. | ||
*/ | ||
virtual std::string summary() const PURE; | ||
|
||
/** | ||
* Returns supported quantiles. | ||
*/ | ||
virtual const std::vector<double>& supportedQuantiles() const PURE; | ||
|
||
/** | ||
* Returns computed quantile values during the period. | ||
*/ | ||
virtual const std::vector<double>& computedQuantiles() const PURE; | ||
}; | ||
|
||
/** | ||
* A histogram that records values one at a time. | ||
* Note: Histograms now incorporate what used to be timers because the only difference between the | ||
|
@@ -167,6 +194,21 @@ class Histogram : public virtual Metric { | |
* Records an unsigned value. If a timer, values are in units of milliseconds. | ||
*/ | ||
virtual void recordValue(uint64_t value) PURE; | ||
|
||
/** | ||
* Merges the histogram values collected during the flush interval. | ||
*/ | ||
virtual void merge() PURE; | ||
|
||
/** | ||
* Returns the interval histogram summary statistics for the flush interval. | ||
*/ | ||
virtual const HistogramStatistics& intervalStatistics() const PURE; | ||
|
||
/** | ||
* Returns the cumulative histogram summary statistics. | ||
*/ | ||
virtual const HistogramStatistics& cumulativeStatistics() const PURE; | ||
}; | ||
|
||
typedef std::shared_ptr<Histogram> HistogramSharedPtr; | ||
|
@@ -194,6 +236,11 @@ class Sink { | |
*/ | ||
virtual void flushGauge(const Gauge& gauge, uint64_t value) PURE; | ||
|
||
/** | ||
* Flush a histogram. | ||
*/ | ||
virtual void flushHistogram(const Histogram& histogram) PURE; | ||
|
||
/** | ||
* This will be called after beginFlush(), some number of flushCounter(), and some number of | ||
* flushGauge(). Sinks can use this to optimize writing if desired. | ||
|
@@ -263,10 +310,20 @@ class Store : public Scope { | |
* @return a list of all known gauges. | ||
*/ | ||
virtual std::list<GaugeSharedPtr> gauges() const PURE; | ||
|
||
/** | ||
* @return a list of all known histograms. | ||
*/ | ||
virtual std::list<HistogramSharedPtr> histograms() const PURE; | ||
}; | ||
|
||
typedef std::unique_ptr<Store> StorePtr; | ||
|
||
/** | ||
* Callback invoked when a store's mergeHistogram() runs. | ||
*/ | ||
typedef std::function<void()> PostMergeCb; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: I think we can probably kill the typedef for this, just use std::function inline below. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have added it because, it is repeated in tests as well. if you are strongly think about removing it, I can remove it. |
||
|
||
/** | ||
* The root of the stat store. | ||
*/ | ||
|
@@ -294,6 +351,12 @@ class StoreRoot : public Store { | |
* down. | ||
*/ | ||
virtual void shutdownThreading() PURE; | ||
|
||
/** | ||
* Called during the flush process to merge all the thread local histograms. The passed in | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I assume that this is going to complete on another thread. If so, it's worth calling out explicitly that mergeHistogram will return before it is complete. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The callback will be called on the main thread, but it will happen after the method returns. Please clarify. :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, please clarify in the comment when the flush will complete, and in what thread. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. clarified. lmk any changes are needed here. |
||
* callback will be called on the main thread, but it will happen after the method returns. | ||
*/ | ||
virtual void mergeHistograms(PostMergeCb merge_complete_cb) PURE; | ||
}; | ||
|
||
typedef std::unique_ptr<StoreRoot> StoreRootPtr; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,7 +21,9 @@ | |
#include "common/common/utility.h" | ||
#include "common/protobuf/protobuf.h" | ||
|
||
#include "absl/strings/str_join.h" | ||
#include "absl/strings/string_view.h" | ||
#include "circllhist.h" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you add a TODO here to get extern "C" in the headers for the library? (Or just do a PR over there). cc @postwait There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed upstream. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @postwait awesome. I updated the latest version and it is no longer needed. |
||
|
||
namespace Envoy { | ||
namespace Stats { | ||
|
@@ -167,9 +169,6 @@ class Utility { | |
* RawStatData::size() instead. | ||
*/ | ||
struct RawStatData { | ||
struct Flags { | ||
static const uint8_t Used = 0x1; | ||
}; | ||
|
||
/** | ||
* Due to the flexible-array-length of name_, c-style allocation | ||
|
@@ -284,6 +283,14 @@ class MetricImpl : public virtual Metric { | |
const std::string& tagExtractedName() const override { return tag_extracted_name_; } | ||
const std::vector<Tag>& tags() const override { return tags_; } | ||
|
||
protected: | ||
/** | ||
* Flags used by all stats types to figure out whether they have been used. | ||
*/ | ||
struct Flags { | ||
static const uint8_t Used = 0x1; | ||
}; | ||
|
||
private: | ||
const std::string name_; | ||
const std::string tag_extracted_name_; | ||
|
@@ -305,13 +312,13 @@ class CounterImpl : public Counter, public MetricImpl { | |
void add(uint64_t amount) override { | ||
data_.value_ += amount; | ||
data_.pending_increment_ += amount; | ||
data_.flags_ |= RawStatData::Flags::Used; | ||
data_.flags_ |= Flags::Used; | ||
} | ||
|
||
void inc() override { add(1); } | ||
uint64_t latch() override { return data_.pending_increment_.exchange(0); } | ||
void reset() override { data_.value_ = 0; } | ||
bool used() const override { return data_.flags_ & RawStatData::Flags::Used; } | ||
bool used() const override { return data_.flags_ & Flags::Used; } | ||
uint64_t value() const override { return data_.value_; } | ||
|
||
private: | ||
|
@@ -333,27 +340,71 @@ class GaugeImpl : public Gauge, public MetricImpl { | |
// Stats::Gauge | ||
virtual void add(uint64_t amount) override { | ||
data_.value_ += amount; | ||
data_.flags_ |= RawStatData::Flags::Used; | ||
data_.flags_ |= Flags::Used; | ||
} | ||
virtual void dec() override { sub(1); } | ||
virtual void inc() override { add(1); } | ||
virtual void set(uint64_t value) override { | ||
data_.value_ = value; | ||
data_.flags_ |= RawStatData::Flags::Used; | ||
data_.flags_ |= Flags::Used; | ||
} | ||
virtual void sub(uint64_t amount) override { | ||
ASSERT(data_.value_ >= amount); | ||
ASSERT(used()); | ||
data_.value_ -= amount; | ||
} | ||
virtual uint64_t value() const override { return data_.value_; } | ||
bool used() const override { return data_.flags_ & RawStatData::Flags::Used; } | ||
bool used() const override { return data_.flags_ & Flags::Used; } | ||
|
||
private: | ||
RawStatData& data_; | ||
RawStatDataAllocator& alloc_; | ||
}; | ||
|
||
/** | ||
* Implementation of HistogramStatistics for circllhist. | ||
*/ | ||
class HistogramStatisticsImpl : public HistogramStatistics { | ||
public: | ||
HistogramStatisticsImpl() : computed_quantiles_(supported_quantiles_.size(), 0.0) {} | ||
|
||
HistogramStatisticsImpl(histogram_t* histogram_ptr) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. move to cc file There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. moved |
||
: computed_quantiles_(supported_quantiles_.size(), 0.0) { | ||
hist_approx_quantile(histogram_ptr, supported_quantiles_.data(), supported_quantiles_.size(), | ||
computed_quantiles_.data()); | ||
} | ||
|
||
HistogramStatisticsImpl(const HistogramStatisticsImpl&) = delete; | ||
HistogramStatisticsImpl& operator=(HistogramStatisticsImpl const&) = delete; | ||
|
||
std::string summary() const override { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. omve to cc file |
||
std::vector<std::string> summary; | ||
for (size_t i = 0; i < supported_quantiles_.size(); ++i) { | ||
summary.push_back( | ||
fmt::format("P{}: {}", 100 * supported_quantiles_[i], | ||
std::isnan(computed_quantiles_[i]) ? 0 : computed_quantiles_[i])); | ||
} | ||
return absl::StrJoin(summary, ", "); | ||
} | ||
|
||
const std::vector<double>& supportedQuantiles() const override { return supported_quantiles_; } | ||
|
||
const std::vector<double>& computedQuantiles() const override { return computed_quantiles_; } | ||
|
||
/** | ||
* Clears the old computed values and refreshes it with values computed from passed histogram. | ||
*/ | ||
void refresh(histogram_t* new_histogram_ptr) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. move to cc file |
||
computed_quantiles_.clear(); | ||
hist_approx_quantile(new_histogram_ptr, supported_quantiles_.data(), | ||
supported_quantiles_.size(), computed_quantiles_.data()); | ||
} | ||
|
||
private: | ||
const std::vector<double> supported_quantiles_ = {0, 0.25, 0.5, 0.75, 0.90, 0.95, 0.99, 0.999, 1}; | ||
std::vector<double> computed_quantiles_; | ||
}; | ||
|
||
/** | ||
* Histogram implementation for the heap. | ||
*/ | ||
|
@@ -366,7 +417,21 @@ class HistogramImpl : public Histogram, public MetricImpl { | |
// Stats::Histogram | ||
void recordValue(uint64_t value) override { parent_.deliverHistogramToSinks(*this, value); } | ||
|
||
void merge() override {} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this ever get called? If not I would do |
||
|
||
bool used() const override { return true; } | ||
|
||
const HistogramStatistics& intervalStatistics() const override { return interval_statistics_; } | ||
|
||
const HistogramStatistics& cumulativeStatistics() const override { | ||
return cumulative_statistics_; | ||
} | ||
|
||
Store& parent_; | ||
|
||
private: | ||
HistogramStatisticsImpl interval_statistics_; | ||
HistogramStatisticsImpl cumulative_statistics_; | ||
}; | ||
|
||
/** | ||
|
@@ -446,6 +511,7 @@ class IsolatedStoreImpl : public Store { | |
// Stats::Store | ||
std::list<CounterSharedPtr> counters() const override { return counters_.toList(); } | ||
std::list<GaugeSharedPtr> gauges() const override { return gauges_.toList(); } | ||
std::list<HistogramSharedPtr> histograms() const override { return histograms_.toList(); } | ||
|
||
private: | ||
struct ScopeImpl : public Scope { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
has been used by what? What does 'used' mean here? From reading the code, does it mean non-empty? If so, could you call this empty() and invert it, per C++ conventions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this actually is a method used in other Metric as well. Used here signifies whether it has been updated/a value has been set because of some thing happening like connection closing etc. This is helpful in flushing only metrics whose value has been modified. I just moved it to the Metric interface now because histogram also uses it. Earlier Counter and Gauge has this method. I am not sure if it is equivalent of empty.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK you shouldn't change the name as that would make the PR bigger.
But could you change to the comment something like:
@mattklein123 confirming that's accurate....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it is not since last flush, it indicates it has actually ever been set a value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No? Then what is it used for? Above you said "flushing only metrics whose value has been modified."
Over time, couldn't you wind up with every metric in this state, if the bit isn't cleared during flush?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So it works like this. If a metric value has ever been set it is considered "used" and it would be pushed to stats sinks on subsequent flusher irrespective of whether it has been updated in that interval or not. I think it is reasonable because and would allow only metrics that are really used in that envoy instance to be pushed rather than all metrics. @mattklein123 may have better idea on the history. So I am not changing any thing here.