Skip to content
9 changes: 9 additions & 0 deletions include/envoy/stats/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ envoy_cc_library(
hdrs = ["stats.h"],
)

envoy_cc_library(
name = "timespan",
hdrs = ["timespan.h"],
deps = [
":stats_interface",
"//include/envoy/common:time_interface",
],
)

envoy_cc_library(
name = "stats_macros",
hdrs = ["stats_macros.h"],
Expand Down
85 changes: 37 additions & 48 deletions include/envoy/stats/stats.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,79 +19,78 @@ class Instance;

namespace Stats {

/**
* General interface for all stats objects.
*/
class Metric {
public:
virtual ~Metric() {}
/**
* Returns the full name of the Metric.
*/
virtual const std::string& name() const PURE;
};

/**
* An always incrementing counter with latching capability. Each increment is added both to a
* global counter as well as periodic counter. Calling latch() returns the periodic counter and
* clears it.
*/
class Counter {
class Counter : public virtual Metric {
public:
virtual ~Counter() {}
virtual void add(uint64_t amount) PURE;
virtual void inc() PURE;
virtual uint64_t latch() PURE;
virtual std::string name() PURE;
virtual void reset() PURE;
virtual bool used() PURE;
virtual uint64_t value() PURE;
virtual bool used() const PURE;
virtual uint64_t value() const PURE;
};

typedef std::shared_ptr<Counter> CounterSharedPtr;

/**
* A gauge that can both increment and decrement.
*/
class Gauge {
class Gauge : public virtual Metric {
public:
virtual ~Gauge() {}

virtual void add(uint64_t amount) PURE;
virtual void dec() PURE;
virtual void inc() PURE;
virtual std::string name() PURE;
virtual void set(uint64_t value) PURE;
virtual void sub(uint64_t amount) PURE;
virtual bool used() PURE;
virtual uint64_t value() PURE;
virtual bool used() const PURE;
virtual uint64_t value() const PURE;
};

typedef std::shared_ptr<Gauge> GaugeSharedPtr;

/**
* An individual timespan that is owned by a timer. The initial time is captured on construction.
* A timespan must be completed via complete() for it to be stored. If the timespan is deleted
* this will be treated as a cancellation.
* A histogram that records values one at a time.
*/
class Timespan {
class Histogram : public virtual Metric {
public:
virtual ~Timespan() {}
virtual ~Histogram() {}

enum ValueType {
Integer,
Duration,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What value does this subtyping provide? Would it be possible to treat everything as just integers at the interface level (and for time, it's just # of ms in the implementation already).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW this would be fine for Lyft, my concern is for implementation where somehow a histogram is different from a timer. I'm not sure if that is a real issue or not... Let's discuss tomorrow.

};

/**
* Complete the span using the default name of the timer that the span was allocated from.
* Informs the user how the values should be interpreted.
*/
virtual void complete() PURE;
virtual ValueType type() const PURE;

/**
* Complete the span using a dynamic name. This is useful if a span needs to get counted
* against a timer with a dynamic name.
* Records an unsigned value. If a timer, values are in units of milliseconds.
*/
virtual void complete(const std::string& dynamic_name) PURE;
virtual void recordValue(uint64_t value) PURE;
};

typedef std::unique_ptr<Timespan> TimespanPtr;

/**
* A timer that can capture timespans.
*/
class Timer {
public:
virtual ~Timer() {}

virtual TimespanPtr allocateSpan() PURE;
virtual std::string name() PURE;
};

typedef std::shared_ptr<Timer> TimerSharedPtr;
typedef std::shared_ptr<Histogram> HistogramSharedPtr;

/**
* A sink for stats. Each sink is responsible for writing stats to a backing store.
Expand All @@ -109,12 +108,12 @@ class Sink {
/**
* Flush a counter delta.
*/
virtual void flushCounter(const std::string& name, uint64_t delta) PURE;
virtual void flushCounter(const Metric& counter, uint64_t delta) PURE;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be Counter (and Gauge) below?


/**
* Flush a gauge value.
*/
virtual void flushGauge(const std::string& name, uint64_t value) PURE;
virtual void flushGauge(const Metric& gauge, uint64_t value) PURE;

/**
* This will be called after beginFlush(), some number of flushCounter(), and some number of
Expand All @@ -125,12 +124,7 @@ class Sink {
/**
* Flush a histogram value.
*/
virtual void onHistogramComplete(const std::string& name, uint64_t value) PURE;

/**
* Flush a timespan value.
*/
virtual void onTimespanComplete(const std::string& name, std::chrono::milliseconds ms) PURE;
virtual void onHistogramComplete(const Histogram& histogram, uint64_t value) PURE;
};

typedef std::unique_ptr<Sink> SinkPtr;
Expand All @@ -157,12 +151,7 @@ class Scope {
/**
* Deliver an individual histogram value to all registered sinks.
*/
virtual void deliverHistogramToSinks(const std::string& name, uint64_t value) PURE;

/**
* Deliver an individual timespan completion to all registered sinks.
*/
virtual void deliverTimingToSinks(const std::string& name, std::chrono::milliseconds ms) PURE;
virtual void deliverHistogramToSinks(const Histogram& histogram, uint64_t value) PURE;

/**
* @return a counter within the scope's namespace.
Expand All @@ -175,9 +164,9 @@ class Scope {
virtual Gauge& gauge(const std::string& name) PURE;

/**
* @return a timer within the scope's namespace.
* @return a histogram within the scope's namespace with a particular value type.
*/
virtual Timer& timer(const std::string& name) PURE;
virtual Histogram& histogram(Histogram::ValueType type, const std::string& name) PURE;
};

/**
Expand Down
7 changes: 5 additions & 2 deletions include/envoy/stats/stats_macros.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,18 @@ namespace Envoy {

#define GENERATE_COUNTER_STRUCT(NAME) Stats::Counter& NAME##_;
#define GENERATE_GAUGE_STRUCT(NAME) Stats::Gauge& NAME##_;
#define GENERATE_TIMER_STRUCT(NAME) Stats::Timer& NAME##_;
#define GENERATE_TIMER_STRUCT(NAME) Stats::Histogram& NAME##_;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we get rid of this distinction in this file and just switch to pure histogram?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this depends on if we decide to treat them differently from the user's perspective as discussed above.

#define GENERATE_HISTOGRAM_STRUCT(NAME) Stats::Histogram& NAME##_;

#define FINISH_STAT_DECL_(X) + std::string(#X)),

#define POOL_COUNTER_PREFIX(POOL, PREFIX) (POOL).counter(PREFIX FINISH_STAT_DECL_
#define POOL_GAUGE_PREFIX(POOL, PREFIX) (POOL).gauge(PREFIX FINISH_STAT_DECL_
#define POOL_TIMER_PREFIX(POOL, PREFIX) (POOL).timer(PREFIX FINISH_STAT_DECL_
#define POOL_TIMER_PREFIX(POOL, PREFIX) (POOL).histogram(Stats::Histogram::ValueType::Duration, PREFIX FINISH_STAT_DECL_
#define POOL_HISTOGRAM_PREFIX(POOL, PREFIX) (POOL).histogram(Stats::Histogram::ValueType::Integer, PREFIX FINISH_STAT_DECL_

#define POOL_COUNTER(POOL) POOL_COUNTER_PREFIX(POOL, "")
#define POOL_GAUGE(POOL) POOL_GAUGE_PREFIX(POOL, "")
#define POOL_TIMER(POOL) POOL_TIMER_PREFIX(POOL, "")
#define POOL_HISTOGRAM(POOL) POOL_HISTOGRAM_PREFIX(POOL, "")
} // Envoy
52 changes: 52 additions & 0 deletions include/envoy/stats/timespan.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#pragma once

#include <chrono>

#include "envoy/common/exception.h"
#include "envoy/common/time.h"
#include "envoy/stats/stats.h"

namespace Envoy {
namespace Stats {

/**
* An individual timespan that flushes its measured value to a Duration histogram. The initial time

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I would probably remove "Duration"

* is captured on construction. A timespan must be completed via complete() for it to be stored. If
* the timespan is deleted this will be treated as a cancellation.
*/
class Timespan {
public:
Timespan(Histogram& histogram) : histogram_(histogram), start_(std::chrono::steady_clock::now()) {
if (histogram.type() != Histogram::ValueType::Duration) {
throw EnvoyException("Cannot intialize a timespan with a non-time valued histogram");
}
}

virtual ~Timespan() {}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: not needed

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. Removed it and made all the methods non-virtual since nothing's inheriting from it.


/**
* Complete the timespan and send the time to the histogram.
*/
virtual void complete() {
histogram_.recordValue(std::chrono::duration_cast<std::chrono::milliseconds>(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this stuff be in the source/common implementation rather than interface?

@mrice32 mrice32 Oct 5, 2017

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As was discussed in #1751, if we move this to source/common everything that wants to create a Timespan having to depend on some stats implementation lib rather than just the set of stats interfaces (everything else in stats currently works this way outside of the creation of the store). The implementation of this class is relatively small. I'm cool changing it if we're okay with those tradeoffs.

std::chrono::steady_clock::now() - start_)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use getRawDuration() here?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. Updated.

.count());
}

/**
* Get duration since the creation of the span.
*/
virtual std::chrono::milliseconds getRawDuration() {
return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() -
start_);
}

private:
Histogram& histogram_;
const MonotonicTime start_;
};

typedef std::unique_ptr<Timespan> TimespanPtr;

} // namespace Stats
} // namespace Envoy
22 changes: 14 additions & 8 deletions source/common/dynamo/dynamo_filter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -181,14 +181,20 @@ void DynamoFilter::chargeStatsPerEntity(const std::string& entity, const std::st
std::to_string(status)))
.inc();

scope_.deliverTimingToSinks(
fmt::format("{}{}.{}.upstream_rq_time", stat_prefix_, entity_type, entity), latency);
scope_.deliverTimingToSinks(
fmt::format("{}{}.{}.upstream_rq_time_{}", stat_prefix_, entity_type, entity, group_string),
latency);
scope_.deliverTimingToSinks(fmt::format("{}{}.{}.upstream_rq_time_{}", stat_prefix_, entity_type,
entity, std::to_string(status)),
latency);
scope_
.histogram(Stats::Histogram::ValueType::Duration,
fmt::format("{}{}.{}.upstream_rq_time", stat_prefix_, entity_type, entity))
.recordValue(latency.count());
scope_
.histogram(Stats::Histogram::ValueType::Duration,
fmt::format("{}{}.{}.upstream_rq_time_{}", stat_prefix_, entity_type, entity,
group_string))
.recordValue(latency.count());
scope_
.histogram(Stats::Histogram::ValueType::Duration,
fmt::format("{}{}.{}.upstream_rq_time_{}", stat_prefix_, entity_type, entity,
std::to_string(status)))
.recordValue(latency.count());
}

void DynamoFilter::chargeUnProcessedKeysStats(const Json::Object& json_body) {
Expand Down
1 change: 1 addition & 0 deletions source/common/filter/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ envoy_cc_library(
"//include/envoy/network:filter_interface",
"//include/envoy/stats:stats_interface",
"//include/envoy/stats:stats_macros",
"//include/envoy/stats:timespan",
"//include/envoy/upstream:cluster_manager_interface",
"//include/envoy/upstream:upstream_interface",
"//source/common/common:assert_lib",
Expand Down
8 changes: 4 additions & 4 deletions source/common/filter/tcp_proxy.cc
Original file line number Diff line number Diff line change
Expand Up @@ -234,10 +234,10 @@ Network::FilterStatus TcpProxy::initializeUpstreamConnection() {
read_callbacks_->upstreamHost()->cluster().stats().upstream_cx_active_.inc();
read_callbacks_->upstreamHost()->stats().cx_total_.inc();
read_callbacks_->upstreamHost()->stats().cx_active_.inc();
connect_timespan_ =
read_callbacks_->upstreamHost()->cluster().stats().upstream_cx_connect_ms_.allocateSpan();
connected_timespan_ =
read_callbacks_->upstreamHost()->cluster().stats().upstream_cx_length_ms_.allocateSpan();
connect_timespan_.reset(new Stats::Timespan(
read_callbacks_->upstreamHost()->cluster().stats().upstream_cx_connect_ms_));
connected_timespan_.reset(new Stats::Timespan(
read_callbacks_->upstreamHost()->cluster().stats().upstream_cx_length_ms_));

return Network::FilterStatus::Continue;
}
Expand Down
1 change: 1 addition & 0 deletions source/common/filter/tcp_proxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "envoy/network/connection.h"
#include "envoy/network/filter.h"
#include "envoy/stats/stats_macros.h"
#include "envoy/stats/timespan.h"
#include "envoy/upstream/cluster_manager.h"
#include "envoy/upstream/upstream.h"

Expand Down
2 changes: 2 additions & 0 deletions source/common/http/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ envoy_cc_library(
"//include/envoy/ssl:connection_interface",
"//include/envoy/stats:stats_interface",
"//include/envoy/stats:stats_macros",
"//include/envoy/stats:timespan",
"//include/envoy/tracing:http_tracer_interface",
"//include/envoy/upstream:upstream_interface",
"//source/common/buffer:buffer_lib",
Expand Down Expand Up @@ -216,6 +217,7 @@ envoy_cc_library(
"//include/envoy/network:connection_interface",
"//include/envoy/stats:stats_interface",
"//include/envoy/stats:stats_macros",
"//include/envoy/stats:timespan",
],
)

Expand Down
37 changes: 24 additions & 13 deletions source/common/http/codes.cc
Original file line number Diff line number Diff line change
Expand Up @@ -81,31 +81,42 @@ void CodeUtility::chargeResponseStat(const ResponseStatInfo& info) {
}

void CodeUtility::chargeResponseTiming(const ResponseTimingInfo& info) {
info.cluster_scope_.deliverTimingToSinks(info.prefix_ + "upstream_rq_time", info.response_time_);
info.cluster_scope_
.histogram(Stats::Histogram::ValueType::Duration, info.prefix_ + "upstream_rq_time")
.recordValue(info.response_time_.count());
if (info.upstream_canary_) {
info.cluster_scope_.deliverTimingToSinks(info.prefix_ + "canary.upstream_rq_time",
info.response_time_);
info.cluster_scope_
.histogram(Stats::Histogram::ValueType::Duration, info.prefix_ + "canary.upstream_rq_time")
.recordValue(info.response_time_.count());
}

if (info.internal_request_) {
info.cluster_scope_.deliverTimingToSinks(info.prefix_ + "internal.upstream_rq_time",
info.response_time_);
info.cluster_scope_
.histogram(Stats::Histogram::ValueType::Duration,
info.prefix_ + "internal.upstream_rq_time")
.recordValue(info.response_time_.count());
} else {
info.cluster_scope_.deliverTimingToSinks(info.prefix_ + "external.upstream_rq_time",
info.response_time_);
info.cluster_scope_
.histogram(Stats::Histogram::ValueType::Duration,
info.prefix_ + "external.upstream_rq_time")
.recordValue(info.response_time_.count());
}

if (!info.request_vcluster_name_.empty()) {
info.global_scope_.deliverTimingToSinks("vhost." + info.request_vhost_name_ + ".vcluster." +
info.request_vcluster_name_ + ".upstream_rq_time",
info.response_time_);
info.global_scope_
.histogram(Stats::Histogram::ValueType::Duration,
"vhost." + info.request_vhost_name_ + ".vcluster." +
info.request_vcluster_name_ + ".upstream_rq_time")
.recordValue(info.response_time_.count());
}

// Handle per zone stats.
if (!info.from_zone_.empty() && !info.to_zone_.empty()) {
info.cluster_scope_.deliverTimingToSinks(
fmt::format("{}zone.{}.{}.upstream_rq_time", info.prefix_, info.from_zone_, info.to_zone_),
info.response_time_);
info.cluster_scope_
.histogram(Stats::Histogram::ValueType::Duration,
fmt::format("{}zone.{}.{}.upstream_rq_time", info.prefix_, info.from_zone_,
info.to_zone_))
.recordValue(info.response_time_.count());
}
}

Expand Down
Loading