in elasticapm/metrics/base_metrics.py [0:0]
def collect(self):
"""
Collects all metrics attached to this metricset, and returns it as a generator
with one or more elements. More than one element is returned if labels are used.
The format of the return value should be
{
"samples": {"metric.name": {"value": some_float}, ...},
"timestamp": unix epoch in microsecond precision
}
"""
self.before_collect()
timestamp = int(time.time() * 1000000)
samples = defaultdict(dict)
if self._counters:
# iterate over a copy of the dict to avoid threading issues, see #717
for (name, labels), counter in self._counters.copy().items():
if counter is not noop_metric:
val = counter.val
if val or not counter.reset_on_collect:
samples[labels].update({name: {"value": val}})
if counter.reset_on_collect:
counter.reset()
if self._gauges:
for (name, labels), gauge in self._gauges.copy().items():
if gauge is not noop_metric:
val = gauge.val
if val or not gauge.reset_on_collect:
samples[labels].update({name: {"value": val, "type": "gauge"}})
if gauge.reset_on_collect:
gauge.reset()
if self._timers:
for (name, labels), timer in self._timers.copy().items():
if timer is not noop_metric:
val, count = timer.val
if val or not timer.reset_on_collect:
sum_name = ".sum"
if timer._unit:
sum_name += "." + timer._unit
samples[labels].update({name + sum_name: {"value": val}})
samples[labels].update({name + ".count": {"value": count}})
if timer.reset_on_collect:
timer.reset()
if self._histograms:
for (name, labels), histo in self._histograms.copy().items():
if histo is not noop_metric:
counts = histo.val
if counts or not histo.reset_on_collect:
# For the bucket values, we follow the approach described by Prometheus's
# histogram_quantile function
# (https://prometheus.io/docs/prometheus/latest/querying/functions/#histogram_quantile)
# to achieve consistent percentile aggregation results:
#
# "The histogram_quantile() function interpolates quantile values by assuming a linear
# distribution within a bucket. (...) If a quantile is located in the highest bucket,
# the upper bound of the second highest bucket is returned. A lower limit of the lowest
# bucket is assumed to be 0 if the upper bound of that bucket is greater than 0. In that
# case, the usual linear interpolation is applied within that bucket. Otherwise, the upper
# bound of the lowest bucket is returned for quantiles located in the lowest bucket."
bucket_midpoints = []
for i, bucket_le in enumerate(histo.buckets):
if i == 0:
if bucket_le > 0:
bucket_le /= 2.0
elif i == len(histo.buckets) - 1:
bucket_le = histo.buckets[i - 1]
else:
bucket_le = histo.buckets[i - 1] + (bucket_le - histo.buckets[i - 1]) / 2.0
bucket_midpoints.append(bucket_le)
samples[labels].update(
{name: {"counts": counts, "values": bucket_midpoints, "type": "histogram"}}
)
if histo.reset_on_collect:
histo.reset()
if samples:
for labels, sample in samples.items():
result = {"samples": sample, "timestamp": timestamp}
if labels:
result["tags"] = {k: v for k, v in labels}
yield self.before_yield(result)