-
Notifications
You must be signed in to change notification settings - Fork 522
Refactor GaugeBucketCollector metrics to be homeserver-scoped
#18715
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
Changes from 1 commit
e4dbc0d
551a010
e57678b
f5a008f
5dd08ec
d4c1a48
ef39a3d
ffd4b6b
01cca7a
3fdac55
cdfb737
202ef03
26eef52
cb7f9bb
ba03525
94f1960
bb0bb01
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 |
|---|---|---|
|
|
@@ -20,53 +20,55 @@ | |
| # | ||
| # | ||
|
|
||
| import itertools | ||
| import logging | ||
| import os | ||
| import platform | ||
| import threading | ||
| from importlib import metadata | ||
| from typing import ( | ||
| Callable, | ||
| Dict, | ||
| Generic, | ||
| Iterable, | ||
| Mapping, | ||
| Optional, | ||
| Sequence, | ||
| Set, | ||
| Tuple, | ||
| Type, | ||
| TypeVar, | ||
| Union, | ||
| cast, | ||
| ) | ||
|
|
||
| import attr | ||
| from pkg_resources import parse_version | ||
| from prometheus_client import ( | ||
| CollectorRegistry, | ||
| Counter, | ||
| Gauge, | ||
| Histogram, | ||
| Metric, | ||
| generate_latest, | ||
| ) | ||
| from prometheus_client.core import ( | ||
| REGISTRY, | ||
| GaugeHistogramMetricFamily, | ||
| GaugeMetricFamily, | ||
| Timestamp, | ||
| Sample, | ||
| ) | ||
|
|
||
| from twisted.python.threadpool import ThreadPool | ||
| from twisted.web.resource import Resource | ||
| from twisted.web.server import Request | ||
|
|
||
| # This module is imported for its side effects; flake8 needn't warn that it's unused. | ||
| import synapse.metrics._reactor_metrics # noqa: F401 | ||
| from synapse.metrics._gc import MIN_TIME_BETWEEN_GCS, install_gc_manager | ||
| from synapse.metrics._types import Collector | ||
| from synapse.types import StrSequence | ||
| from synapse.util import SYNAPSE_VERSION | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
@@ -342,6 +344,85 @@ | |
| all_gauges[self.name] = self | ||
|
|
||
|
|
||
| class GaugeHistogramMetricFamilyWithLabels(Metric): | ||
| """ | ||
| Custom version of `GaugeHistogramMetricFamily` from `prometheus_client` that allows | ||
| specifying labels and label values. | ||
|
|
||
| A single gauge histogram and its samples. | ||
|
|
||
| For use by custom collectors. | ||
| """ | ||
|
|
||
| def __init__( | ||
| self, | ||
| *, | ||
| name: str, | ||
| documentation: str, | ||
| buckets: Optional[Sequence[Tuple[str, float]]] = None, | ||
| gsum_value: Optional[float] = None, | ||
| labelnames: StrSequence = (), | ||
| labelvalues: StrSequence = (), | ||
| unit: str = "", | ||
| ): | ||
| Metric.__init__(self, name, documentation, "gaugehistogram", unit) | ||
|
|
||
| # Sanity check the number of label values matches the number of label names. | ||
| if len(labelvalues) != len(labelnames): | ||
| raise ValueError("Incorrect label count") | ||
|
|
||
| self._labelnames = tuple(labelnames) | ||
|
Member
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. Since we are relying on the inheritance now to apply the labels, we should add a comment here to describe that risk / fragility.
Contributor
Author
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. As an alternative, I've updated to use the |
||
|
|
||
| # Create a gauge for each bucket. | ||
| if buckets is not None: | ||
| self.add_metric( | ||
| labelvalues=labelvalues, buckets=buckets, gsum_value=gsum_value | ||
| ) | ||
|
|
||
| def add_metric( | ||
| self, | ||
| labelvalues: StrSequence, | ||
| buckets: Sequence[Tuple[str, float]], | ||
| gsum_value: Optional[float], | ||
| timestamp: Optional[Union[float, Timestamp]] = None, | ||
| ) -> None: | ||
| """Add a metric to the metric family. | ||
|
|
||
| Args: | ||
| labelvalues: A list of label values | ||
| buckets: A list of pairs of bucket names and values. | ||
| The buckets must be sorted, and +Inf present. | ||
| gsum_value: The sum value of the metric. | ||
| """ | ||
| for bucket, value in buckets: | ||
| self.samples.append( | ||
| Sample( | ||
| self.name + "_bucket", | ||
| dict(list(zip(self._labelnames, labelvalues)) + [("le", bucket)]), | ||
| value, | ||
| timestamp, | ||
| ) | ||
| ) | ||
| # +Inf is last and provides the count value. | ||
| self.samples.extend( | ||
| [ | ||
| Sample( | ||
| self.name + "_gcount", | ||
| dict(zip(self._labelnames, labelvalues)), | ||
| buckets[-1][1], | ||
| timestamp, | ||
| ), | ||
| # TODO: Handle None gsum_value correctly. Currently a None will fail exposition but is allowed here. | ||
| Sample( | ||
| self.name + "_gsum", | ||
| dict(zip(self._labelnames, labelvalues)), | ||
| gsum_value, | ||
| timestamp, | ||
| ), # type: ignore | ||
| ] | ||
| ) | ||
|
|
||
|
|
||
| class GaugeBucketCollector(Collector): | ||
| """Like a Histogram, but the buckets are Gauges which are updated atomically. | ||
|
|
||
|
|
@@ -354,14 +435,17 @@ | |
| __slots__ = ( | ||
| "_name", | ||
| "_documentation", | ||
| "_labelnames", | ||
| "_bucket_bounds", | ||
| "_metric", | ||
| ) | ||
|
|
||
| def __init__( | ||
| self, | ||
| *, | ||
| name: str, | ||
| documentation: str, | ||
| labelnames: Optional[StrSequence], | ||
| buckets: Iterable[float], | ||
| registry: CollectorRegistry = REGISTRY, | ||
| ): | ||
|
|
@@ -375,6 +459,7 @@ | |
| """ | ||
| self._name = name | ||
| self._documentation = documentation | ||
| self._labelnames = labelnames | ||
|
|
||
| # the tops of the buckets | ||
| self._bucket_bounds = [float(b) for b in buckets] | ||
|
|
@@ -386,7 +471,7 @@ | |
|
|
||
| # We initially set this to None. We won't report metrics until | ||
| # this has been initialised after a successful data update | ||
| self._metric: Optional[GaugeHistogramMetricFamily] = None | ||
| self._metric: Optional[GaugeHistogramMetricFamilyWithLabels] = None | ||
|
|
||
| registry.register(self) | ||
|
|
||
|
|
@@ -395,15 +480,26 @@ | |
| if self._metric is not None: | ||
| yield self._metric | ||
|
|
||
| def update_data(self, values: Iterable[float]) -> None: | ||
| def update_data(self, values: Iterable[float], labels: StrSequence = ()) -> None: | ||
| """Update the data to be reported by the metric | ||
|
|
||
| The existing data is cleared, and each measurement in the input is assigned | ||
| to the relevant bucket. | ||
|
|
||
| Args: | ||
| values | ||
| labels | ||
| """ | ||
| self._metric = self._values_to_metric(values) | ||
| self._metric = self._values_to_metric(values, labels) | ||
|
|
||
| def _values_to_metric(self, values: Iterable[float]) -> GaugeHistogramMetricFamily: | ||
| def _values_to_metric( | ||
| self, values: Iterable[float], labels: StrSequence = () | ||
| ) -> GaugeHistogramMetricFamilyWithLabels: | ||
| """ | ||
| Args: | ||
| values | ||
| labels | ||
| """ | ||
| total = 0.0 | ||
| bucket_values = [0 for _ in self._bucket_bounds] | ||
|
|
||
|
|
@@ -421,9 +517,11 @@ | |
| # that bucket or below. | ||
| accumulated_values = itertools.accumulate(bucket_values) | ||
|
|
||
| return GaugeHistogramMetricFamily( | ||
| self._name, | ||
| self._documentation, | ||
| return GaugeHistogramMetricFamilyWithLabels( | ||
| name=self._name, | ||
| documentation=self._documentation, | ||
| labelnames=self._labelnames, | ||
| labelvalues=labels, | ||
| buckets=list( | ||
| zip((str(b) for b in self._bucket_bounds), accumulated_values) | ||
| ), | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.