From ea13dac1eac93a889f11b7990dfe140cc4c7d2b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felipe=20Monteiro=20J=C3=A1come?= Date: Fri, 4 Feb 2022 17:53:42 -0300 Subject: [PATCH 01/14] initial commit with some tests and replaced _metrics with pandas DataFrame MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Felipe Monteiro Jácome --- prometheus_client/metrics.py | 298 +++++++++++++++++++++++++++++++++++ tests/test_registry.py | 34 ++++ 2 files changed, 332 insertions(+) create mode 100644 tests/test_registry.py diff --git a/prometheus_client/metrics.py b/prometheus_client/metrics.py index b9780d04..7e6391ff 100644 --- a/prometheus_client/metrics.py +++ b/prometheus_client/metrics.py @@ -15,6 +15,8 @@ from .samples import Exemplar, Sample from .utils import floatToGoString, INF +import pandas as pd + T = TypeVar('T', bound='MetricWrapperBase') F = TypeVar("F", bound=Callable[..., Any]) @@ -714,3 +716,299 @@ def _child_samples(self) -> Iterable[Sample]: for i, s in enumerate(self._states) ] + + +class MetricPandasWrapperBase: + _type: Optional[str] = None + _reserved_labelnames: Sequence[str] = () + + def _is_observable(self): + # Whether this metric is observable, i.e. + # * a metric without label names and values, or + # * the child of a labelled metric. + return not self._labelnames or (self._labelnames and self._labelvalues) + + def _raise_if_not_observable(self): + # Functions that mutate the state of the metric, for example incrementing + # a counter, will fail if the metric is not observable, because only if a + # metric is observable will the value be initialized. + if not self._is_observable(): + raise ValueError('%s metric is missing label values' % str(self._type)) + + def _is_parent(self): + return self._labelnames and not self._labelvalues + + def _get_metric(self): + return Metric(self._name, self._documentation, self._type, self._unit) + + def describe(self): + return [self._get_metric()] + + def collect(self): + metric = self._get_metric() + for suffix, labels, value, timestamp, exemplar in self._samples(): + metric.add_sample(self._name + suffix, labels, value, timestamp, exemplar) + return [metric] + + def __str__(self): + return f"{self._type}:{self._name}" + + def __repr__(self): + metric_type = type(self) + return f"{metric_type.__module__}.{metric_type.__name__}({self._name})" + + def __init__(self: T, + name: str, + documentation: str, + labelnames: Iterable[str] = (), + namespace: str = '', + subsystem: str = '', + unit: str = '', + registry: Optional[CollectorRegistry] = REGISTRY, + _labelvalues: Optional[Sequence[str]] = None, + ) -> None: + self._name = _build_full_name(self._type, name, namespace, subsystem, unit) + self._labelnames = _validate_labelnames(self, labelnames) + self._labelvalues = tuple(_labelvalues or ()) + self._kwargs: Dict[str, Any] = {} + self._documentation = documentation + self._unit = unit + + if not METRIC_NAME_RE.match(self._name): + raise ValueError('Invalid metric name: ' + self._name) + + if self._is_parent(): + # Prepare the fields needed for child metrics. + self._lock = Lock() + self._metrics = pd.DataFrame(columns=labelnames) + + if self._is_observable(): + self._metric_init() + + if not self._labelvalues: + # Register the multi-wrapper parent metric, or if a label-less metric, the whole shebang. + if registry: + registry.register(self) + + def labels(self: T, *labelvalues: Any, **labelkwargs: Any) -> T: + """Return the child for the given labelset. + + All metrics can have labels, allowing grouping of related time series. + Taking a counter as an example: + + from prometheus_client import Counter + + c = Counter('my_requests_total', 'HTTP Failures', ['method', 'endpoint']) + c.labels('get', '/').inc() + c.labels('post', '/submit').inc() + + Labels can also be provided as keyword arguments: + + from prometheus_client import Counter + + c = Counter('my_requests_total', 'HTTP Failures', ['method', 'endpoint']) + c.labels(method='get', endpoint='/').inc() + c.labels(method='post', endpoint='/submit').inc() + + See the best practices on [naming](http://prometheus.io/docs/practices/naming/) + and [labels](http://prometheus.io/docs/practices/instrumentation/#use-labels). + """ + if not self._labelnames: + raise ValueError('No label names were set when constructing %s' % self) + + if self._labelvalues: + raise ValueError('{} already has labels set ({}); can not chain calls to .labels()'.format( + self, + dict(zip(self._labelnames, self._labelvalues)) + )) + + if labelvalues and labelkwargs: + raise ValueError("Can't pass both *args and **kwargs") + + if labelkwargs: + if sorted(labelkwargs) != sorted(self._labelnames): + raise ValueError('Incorrect label names') + labelvalues = tuple(str(labelkwargs[l]) for l in self._labelnames) + else: + if len(labelvalues) != len(self._labelnames): + raise ValueError('Incorrect label count') + labelvalues = tuple(str(l) for l in labelvalues) + with self._lock: + if labelvalues not in self._metrics: + self._metrics[labelvalues] = self.__class__( + self._name, + documentation=self._documentation, + labelnames=self._labelnames, + unit=self._unit, + _labelvalues=labelvalues, + **self._kwargs + ) + return self._metrics[labelvalues] + + def remove(self, *labelvalues): + if not self._labelnames: + raise ValueError('No label names were set when constructing %s' % self) + + """Remove the given labelset from the metric.""" + if len(labelvalues) != len(self._labelnames): + raise ValueError('Incorrect label count (expected %d, got %s)' % (len(self._labelnames), labelvalues)) + labelvalues = tuple(str(l) for l in labelvalues) + with self._lock: + del self._metrics[labelvalues] + + def clear(self) -> None: + """Remove all labelsets from the metric""" + with self._lock: + self._metrics = {} + + def _samples(self) -> Iterable[Sample]: + if self._is_parent(): + return self._multi_samples() + else: + return self._child_samples() + + def _multi_samples(self) -> Iterable[Sample]: + with self._lock: + metrics = self._metrics.copy() + for labels, metric in metrics.items(): + series_labels = list(zip(self._labelnames, labels)) + for suffix, sample_labels, value, timestamp, exemplar in metric._samples(): + yield Sample(suffix, dict(series_labels + list(sample_labels.items())), value, timestamp, exemplar) + + def _child_samples(self) -> Iterable[Sample]: # pragma: no cover + raise NotImplementedError('_child_samples() must be implemented by %r' % self) + + def _metric_init(self): # pragma: no cover + """ + Initialize the metric object as a child, i.e. when it has labels (if any) set. + + This is factored as a separate function to allow for deferred initialization. + """ + raise NotImplementedError('_metric_init() must be implemented by %r' % self) + + +class GaugePandas(MetricPandasWrapperBase): + """Gauge metric, to report instantaneous values. + + Examples of Gauges include: + - Inprogress requests + - Number of items in a queue + - Free memory + - Total memory + - Temperature + + Gauges can go both up and down. + + from prometheus_client import Gauge + + g = Gauge('my_inprogress_requests', 'Description of gauge') + g.inc() # Increment by 1 + g.dec(10) # Decrement by given value + g.set(4.2) # Set to a given value + + There are utilities for common use cases: + + g.set_to_current_time() # Set to current unixtime + + # Increment when entered, decrement when exited. + @g.track_inprogress() + def f(): + pass + + with g.track_inprogress(): + pass + + A Gauge can also take its value from a callback: + + d = Gauge('data_objects', 'Number of objects') + my_dict = {} + d.set_function(lambda: len(my_dict)) + """ + _type = 'gauge' + _MULTIPROC_MODES = frozenset(('min', 'max', 'livesum', 'liveall', 'all')) + + def __init__(self, + name: str, + documentation: str, + labelnames: Iterable[str] = (), + namespace: str = '', + subsystem: str = '', + unit: str = '', + registry: Optional[CollectorRegistry] = REGISTRY, + _labelvalues: Optional[Sequence[str]] = None, + multiprocess_mode: str = 'all', + ): + self._multiprocess_mode = multiprocess_mode + if multiprocess_mode not in self._MULTIPROC_MODES: + raise ValueError('Invalid multiprocess mode: ' + multiprocess_mode) + super().__init__( + name=name, + documentation=documentation, + labelnames=labelnames, + namespace=namespace, + subsystem=subsystem, + unit=unit, + registry=registry, + _labelvalues=_labelvalues, + ) + self._kwargs['multiprocess_mode'] = self._multiprocess_mode + + def _metric_init(self) -> None: + self._value = values.ValueClass( + self._type, self._name, self._name, self._labelnames, self._labelvalues, + multiprocess_mode=self._multiprocess_mode + ) + + def inc(self, amount: float = 1) -> None: + """Increment gauge by the given amount.""" + self._raise_if_not_observable() + self._value.inc(amount) + + def dec(self, amount: float = 1) -> None: + """Decrement gauge by the given amount.""" + self._raise_if_not_observable() + self._value.inc(-amount) + + def set(self, value: float) -> None: + """Set gauge to the given value.""" + self._raise_if_not_observable() + self._value.set(float(value)) + + def set_to_current_time(self) -> None: + """Set gauge to the current unixtime.""" + self.set(time.time()) + + def track_inprogress(self) -> InprogressTracker: + """Track inprogress blocks of code or functions. + + Can be used as a function decorator or context manager. + Increments the gauge when the code is entered, + and decrements when it is exited. + """ + self._raise_if_not_observable() + return InprogressTracker(self) + + def time(self) -> Timer: + """Time a block of code or function, and set the duration in seconds. + + Can be used as a function decorator or context manager. + """ + return Timer(self, 'set') + + def set_function(self, f: Callable[[], float]) -> None: + """Call the provided function to return the Gauge value. + + The function must return a float, and may be called from + multiple threads. All other methods of the Gauge become NOOPs. + """ + + self._raise_if_not_observable() + + def samples(_: Gauge) -> Iterable[Sample]: + return (Sample('', {}, float(f()), None, None),) + + self._child_samples = types.MethodType(samples, self) # type: ignore + + def _child_samples(self) -> Iterable[Sample]: + return (Sample('', {}, self._value.get(), None, None),) + diff --git a/tests/test_registry.py b/tests/test_registry.py new file mode 100644 index 00000000..84bddf26 --- /dev/null +++ b/tests/test_registry.py @@ -0,0 +1,34 @@ +from prometheus_client.registry import CollectorRegistry +from prometheus_client.metrics import Gauge, GaugePandas +import pandas as pd + +def test_collector_registry_init(): + registry = CollectorRegistry() + assert registry._collector_to_names == {} + assert registry._names_to_collectors == {} + assert registry._auto_describe == False + assert str(type(registry._lock)) == "" + assert registry._target_info == None + +def test_collector_registry_gauge(): + registry = CollectorRegistry() + g = Gauge('raid_status', '1 if raid array is okay', registry=registry) + g.set(1) + + assert registry._names_to_collectors['raid_status'] == g + assert registry._names_to_collectors['raid_status']._documentation == '1 if raid array is okay' + assert '_metrics' not in vars(registry._names_to_collectors['raid_status']) + + G = Gauge('raid_status2', '1 if raid array is okay', ['label1'], registry=registry) + #G.labels('a').set(10) + #G.labels('b').set(11) + #G.labels('c').set(12) + #G.labels('c').set(13) + + assert registry._names_to_collectors['raid_status2']._labelnames == ('label1',) + '_metrics' in vars(registry._names_to_collectors['raid_status2']) + + registry2 = CollectorRegistry() + GP = GaugePandas('raid_status2', '1 if raid array is okay', ['label1'], registry=registry2) + assert type(GP._metrics) == pd.core.frame.DataFrame + import pdb; pdb.set_trace() \ No newline at end of file From 5b3c767bab31e8b647d5147dc5de5d01a96c2f76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felipe=20Monteiro=20J=C3=A1come?= Date: Thu, 10 Mar 2022 10:18:57 -0300 Subject: [PATCH 02/14] ajustes pandasGauge. TODO: remover prints remover pdb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Felipe Monteiro Jácome --- prometheus_client/registry.py | 1 + 1 file changed, 1 insertion(+) diff --git a/prometheus_client/registry.py b/prometheus_client/registry.py index fe435cd1..2567cc85 100644 --- a/prometheus_client/registry.py +++ b/prometheus_client/registry.py @@ -71,6 +71,7 @@ def _get_names(self, collector): def collect(self): """Yields metrics from the collectors in the registry.""" + import pdb; pdb.set_trace() collectors = None ti = None with self._lock: From 2ef7e2afc790548ed4fad276771a86704f133209 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felipe=20Monteiro=20J=C3=A1come?= Date: Thu, 10 Mar 2022 10:19:07 -0300 Subject: [PATCH 03/14] ajustes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Felipe Monteiro Jácome --- prometheus_client/__init__.py | 3 +- prometheus_client/exposition.py | 49 +++--- prometheus_client/metrics.py | 261 +++++++------------------------- tests/test_exposition.py | 17 ++- tests/test_registry.py | 8 +- 5 files changed, 106 insertions(+), 232 deletions(-) diff --git a/prometheus_client/__init__.py b/prometheus_client/__init__.py index a8fb88dc..b277b744 100644 --- a/prometheus_client/__init__.py +++ b/prometheus_client/__init__.py @@ -11,7 +11,7 @@ write_to_textfile, ) from .gc_collector import GC_COLLECTOR, GCCollector -from .metrics import Counter, Enum, Gauge, Histogram, Info, Summary +from .metrics import Counter, Enum, Gauge, Histogram, Info, Summary, PandasGauge from .metrics_core import Metric from .platform_collector import PLATFORM_COLLECTOR, PlatformCollector from .process_collector import PROCESS_COLLECTOR, ProcessCollector @@ -27,6 +27,7 @@ 'Histogram', 'Info', 'Enum', + 'PandasGauge', 'CONTENT_TYPE_LATEST', 'generate_latest', 'MetricsHandler', diff --git a/prometheus_client/exposition.py b/prometheus_client/exposition.py index 45161df9..9f0f2994 100644 --- a/prometheus_client/exposition.py +++ b/prometheus_client/exposition.py @@ -186,30 +186,39 @@ def sample_line(line): mtype = 'histogram' elif mtype == 'unknown': mtype = 'untyped' - - output.append('# HELP {} {}\n'.format( - mname, metric.documentation.replace('\\', r'\\').replace('\n', r'\n'))) - output.append(f'# TYPE {mname} {mtype}\n') - - om_samples = {} - for s in metric.samples: - for suffix in ['_created', '_gsum', '_gcount']: - if s.name == metric.name + suffix: - # OpenMetrics specific sample, put in a gauge at the end. - om_samples.setdefault(suffix, []).append(sample_line(s)) - break - else: - output.append(sample_line(s)) + import pdb; pdb.set_trace() + if 'encoder' not in vars(metric) or ('encoder' in vars(metric) and metric.encoder != 'pandas'): + # normal calls + output.append('# HELP {} {}\n'.format( + mname, metric.documentation.replace('\\', r'\\').replace('\n', r'\n'))) + output.append(f'# TYPE {mname} {mtype}\n') + + om_samples = {} + for s in metric.samples: + for suffix in ['_created', '_gsum', '_gcount']: + if s.name == metric.name + suffix: + # OpenMetrics specific sample, put in a gauge at the end. + om_samples.setdefault(suffix, []).append(sample_line(s)) + break + else: + output.append(sample_line(s)) + for suffix, lines in sorted(om_samples.items()): + output.append('# HELP {}{} {}\n'.format(metric.name, suffix, + metric.documentation.replace('\\', r'\\').replace('\n', r'\n'))) + output.append(f'# TYPE {metric.name}{suffix} gauge\n') + output.extend(lines) + else: + output.append('# HELP {} {}\n'.format( + mname, metric.documentation.replace('\\', r'\\').replace('\n', r'\n'))) + output.append(f'# TYPE {mname} {mtype}\n') + import pdb; pdb.set_trace() except Exception as exception: exception.args = (exception.args or ('',)) + (metric,) raise + import pdb; pdb.set_trace() + - for suffix, lines in sorted(om_samples.items()): - output.append('# HELP {}{} {}\n'.format(metric.name, suffix, - metric.documentation.replace('\\', r'\\').replace('\n', r'\n'))) - output.append(f'# TYPE {metric.name}{suffix} gauge\n') - output.extend(lines) - return ''.join(output).encode('utf-8') + return ''.join(output).encode('utf-8') def choose_encoder(accept_header): diff --git a/prometheus_client/metrics.py b/prometheus_client/metrics.py index 7e6391ff..2586b4e7 100644 --- a/prometheus_client/metrics.py +++ b/prometheus_client/metrics.py @@ -718,8 +718,9 @@ def _child_samples(self) -> Iterable[Sample]: ] -class MetricPandasWrapperBase: - _type: Optional[str] = None +class PandasGauge: + _encoder = 'pandas' + _type: Optional[str] = 'gauge' _reserved_labelnames: Sequence[str] = () def _is_observable(self): @@ -739,37 +740,62 @@ def _is_parent(self): return self._labelnames and not self._labelvalues def _get_metric(self): + print("get_metric") return Metric(self._name, self._documentation, self._type, self._unit) def describe(self): + print("describe") return [self._get_metric()] def collect(self): - metric = self._get_metric() - for suffix, labels, value, timestamp, exemplar in self._samples(): - metric.add_sample(self._name + suffix, labels, value, timestamp, exemplar) - return [metric] + return [self._metrics] def __str__(self): return f"{self._type}:{self._name}" def __repr__(self): + print("repr") metric_type = type(self) return f"{metric_type.__module__}.{metric_type.__name__}({self._name})" - def __init__(self: T, - name: str, - documentation: str, - labelnames: Iterable[str] = (), - namespace: str = '', - subsystem: str = '', - unit: str = '', - registry: Optional[CollectorRegistry] = REGISTRY, - _labelvalues: Optional[Sequence[str]] = None, - ) -> None: + #def __init__(self: T, + # name: str, + # documentation: str, + # labelnames: Iterable[str] = (), + # namespace: str = '', + # subsystem: str = '', + # unit: str = '', + # registry: Optional[CollectorRegistry] = REGISTRY, + # _labelvalues: Optional[Sequence[str]] = None, + # ) -> None: + def __init__( + self: T, + df: pd.DataFrame, + namespace: str = '', + subsystem: str = '', + registry: Optional[CollectorRegistry] = REGISTRY + ) -> None: + """ + Esta classe parte do pressuporto que a metrica é trocada com mais eficiencia do que ficar alterando apenas 1 valor + o calculo pode ser feito em outro lugar e passar apenas a estrutura completo pronto em DataFrame + """ + if 'documentation' in vars(df): + documentation = df.documentation + else: + documentation = 'no doc provided' + if 'unit' in vars(df): + unit = df.unit + else: + unit = 'no unit provided' + if 'name' in vars(df): + name = df.name + else: + name = 'no name provided' + df.type = self._type + df.encoder = self._encoder self._name = _build_full_name(self._type, name, namespace, subsystem, unit) - self._labelnames = _validate_labelnames(self, labelnames) - self._labelvalues = tuple(_labelvalues or ()) + self._labelnames = _validate_labelnames(self, df.columns) + self._labelvalues = tuple(None or ()) self._kwargs: Dict[str, Any] = {} self._documentation = documentation self._unit = unit @@ -780,7 +806,7 @@ def __init__(self: T, if self._is_parent(): # Prepare the fields needed for child metrics. self._lock = Lock() - self._metrics = pd.DataFrame(columns=labelnames) + self._metrics = df if self._is_observable(): self._metric_init() @@ -790,60 +816,6 @@ def __init__(self: T, if registry: registry.register(self) - def labels(self: T, *labelvalues: Any, **labelkwargs: Any) -> T: - """Return the child for the given labelset. - - All metrics can have labels, allowing grouping of related time series. - Taking a counter as an example: - - from prometheus_client import Counter - - c = Counter('my_requests_total', 'HTTP Failures', ['method', 'endpoint']) - c.labels('get', '/').inc() - c.labels('post', '/submit').inc() - - Labels can also be provided as keyword arguments: - - from prometheus_client import Counter - - c = Counter('my_requests_total', 'HTTP Failures', ['method', 'endpoint']) - c.labels(method='get', endpoint='/').inc() - c.labels(method='post', endpoint='/submit').inc() - - See the best practices on [naming](http://prometheus.io/docs/practices/naming/) - and [labels](http://prometheus.io/docs/practices/instrumentation/#use-labels). - """ - if not self._labelnames: - raise ValueError('No label names were set when constructing %s' % self) - - if self._labelvalues: - raise ValueError('{} already has labels set ({}); can not chain calls to .labels()'.format( - self, - dict(zip(self._labelnames, self._labelvalues)) - )) - - if labelvalues and labelkwargs: - raise ValueError("Can't pass both *args and **kwargs") - - if labelkwargs: - if sorted(labelkwargs) != sorted(self._labelnames): - raise ValueError('Incorrect label names') - labelvalues = tuple(str(labelkwargs[l]) for l in self._labelnames) - else: - if len(labelvalues) != len(self._labelnames): - raise ValueError('Incorrect label count') - labelvalues = tuple(str(l) for l in labelvalues) - with self._lock: - if labelvalues not in self._metrics: - self._metrics[labelvalues] = self.__class__( - self._name, - documentation=self._documentation, - labelnames=self._labelnames, - unit=self._unit, - _labelvalues=labelvalues, - **self._kwargs - ) - return self._metrics[labelvalues] def remove(self, *labelvalues): if not self._labelnames: @@ -868,12 +840,13 @@ def _samples(self) -> Iterable[Sample]: return self._child_samples() def _multi_samples(self) -> Iterable[Sample]: - with self._lock: - metrics = self._metrics.copy() - for labels, metric in metrics.items(): - series_labels = list(zip(self._labelnames, labels)) - for suffix, sample_labels, value, timestamp, exemplar in metric._samples(): - yield Sample(suffix, dict(series_labels + list(sample_labels.items())), value, timestamp, exemplar) + if 'pandas' not in vars(metrics._encoder): + with self._lock: + metrics = self._metrics.copy() + for labels, metric in metrics.items(): + series_labels = list(zip(self._labelnames, labels)) + for suffix, sample_labels, value, timestamp, exemplar in metric._samples(): + yield Sample(suffix, dict(series_labels + list(sample_labels.items())), value, timestamp, exemplar) def _child_samples(self) -> Iterable[Sample]: # pragma: no cover raise NotImplementedError('_child_samples() must be implemented by %r' % self) @@ -883,132 +856,6 @@ def _metric_init(self): # pragma: no cover Initialize the metric object as a child, i.e. when it has labels (if any) set. This is factored as a separate function to allow for deferred initialization. + # raise NotImplementedError('_metric_init() must be implemented by %r' % self) """ - raise NotImplementedError('_metric_init() must be implemented by %r' % self) - - -class GaugePandas(MetricPandasWrapperBase): - """Gauge metric, to report instantaneous values. - - Examples of Gauges include: - - Inprogress requests - - Number of items in a queue - - Free memory - - Total memory - - Temperature - - Gauges can go both up and down. - - from prometheus_client import Gauge - - g = Gauge('my_inprogress_requests', 'Description of gauge') - g.inc() # Increment by 1 - g.dec(10) # Decrement by given value - g.set(4.2) # Set to a given value - - There are utilities for common use cases: - - g.set_to_current_time() # Set to current unixtime - - # Increment when entered, decrement when exited. - @g.track_inprogress() - def f(): - pass - - with g.track_inprogress(): - pass - - A Gauge can also take its value from a callback: - - d = Gauge('data_objects', 'Number of objects') - my_dict = {} - d.set_function(lambda: len(my_dict)) - """ - _type = 'gauge' - _MULTIPROC_MODES = frozenset(('min', 'max', 'livesum', 'liveall', 'all')) - - def __init__(self, - name: str, - documentation: str, - labelnames: Iterable[str] = (), - namespace: str = '', - subsystem: str = '', - unit: str = '', - registry: Optional[CollectorRegistry] = REGISTRY, - _labelvalues: Optional[Sequence[str]] = None, - multiprocess_mode: str = 'all', - ): - self._multiprocess_mode = multiprocess_mode - if multiprocess_mode not in self._MULTIPROC_MODES: - raise ValueError('Invalid multiprocess mode: ' + multiprocess_mode) - super().__init__( - name=name, - documentation=documentation, - labelnames=labelnames, - namespace=namespace, - subsystem=subsystem, - unit=unit, - registry=registry, - _labelvalues=_labelvalues, - ) - self._kwargs['multiprocess_mode'] = self._multiprocess_mode - - def _metric_init(self) -> None: - self._value = values.ValueClass( - self._type, self._name, self._name, self._labelnames, self._labelvalues, - multiprocess_mode=self._multiprocess_mode - ) - - def inc(self, amount: float = 1) -> None: - """Increment gauge by the given amount.""" - self._raise_if_not_observable() - self._value.inc(amount) - - def dec(self, amount: float = 1) -> None: - """Decrement gauge by the given amount.""" - self._raise_if_not_observable() - self._value.inc(-amount) - - def set(self, value: float) -> None: - """Set gauge to the given value.""" - self._raise_if_not_observable() - self._value.set(float(value)) - - def set_to_current_time(self) -> None: - """Set gauge to the current unixtime.""" - self.set(time.time()) - - def track_inprogress(self) -> InprogressTracker: - """Track inprogress blocks of code or functions. - - Can be used as a function decorator or context manager. - Increments the gauge when the code is entered, - and decrements when it is exited. - """ - self._raise_if_not_observable() - return InprogressTracker(self) - - def time(self) -> Timer: - """Time a block of code or function, and set the duration in seconds. - - Can be used as a function decorator or context manager. - """ - return Timer(self, 'set') - - def set_function(self, f: Callable[[], float]) -> None: - """Call the provided function to return the Gauge value. - - The function must return a float, and may be called from - multiple threads. All other methods of the Gauge become NOOPs. - """ - - self._raise_if_not_observable() - - def samples(_: Gauge) -> Iterable[Sample]: - return (Sample('', {}, float(f()), None, None),) - - self._child_samples = types.MethodType(samples, self) # type: ignore - - def _child_samples(self) -> Iterable[Sample]: - return (Sample('', {}, self._value.get(), None, None),) - + pass diff --git a/tests/test_exposition.py b/tests/test_exposition.py index fd130552..1ba8a99d 100644 --- a/tests/test_exposition.py +++ b/tests/test_exposition.py @@ -8,7 +8,7 @@ from prometheus_client import ( CollectorRegistry, CONTENT_TYPE_LATEST, core, Counter, delete_from_gateway, Enum, Gauge, generate_latest, Histogram, Info, instance_ip_grouping_key, - Metric, push_to_gateway, pushadd_to_gateway, Summary, + Metric, push_to_gateway, pushadd_to_gateway, Summary, PandasGauge ) from prometheus_client.core import GaugeHistogramMetricFamily, Timestamp from prometheus_client.exposition import ( @@ -16,6 +16,7 @@ passthrough_redirect_handler, ) +import pandas as pd class TestGenerateText(unittest.TestCase): def setUp(self): @@ -192,6 +193,20 @@ def collect(self): ts{foo="f"} 0.0 123000 """, generate_latest(self.registry)) + def test_gauge_pandas(self): + df = pd.DataFrame({'a': [1.1,2.2,3.3,4.4], 'b':[5.1,6.2,7.3,8.4]}) + df.name = 'report_pandas' + df.documentation = 'metric description' + df.unit = '' + df2 = pd.DataFrame({'c': [1.1,2.2,3.3,4.4], 'd':[5.1,6.2,7.3,8.4]}) + df2.name = 'report_panda2s' + df2.documentation = 'metric description' + df2.unit = '' + g = PandasGauge(df, registry=self.registry) + g = PandasGauge(df2, registry=self.registry) + import pdb; pdb.set_trace() + self.assertEqual(b'# HELP gg A gauge\n# TYPE gg gauge\ngg 17.0\n', generate_latest(self.registry)) + class TestPushGateway(unittest.TestCase): def setUp(self): diff --git a/tests/test_registry.py b/tests/test_registry.py index 84bddf26..3f023f91 100644 --- a/tests/test_registry.py +++ b/tests/test_registry.py @@ -1,5 +1,5 @@ from prometheus_client.registry import CollectorRegistry -from prometheus_client.metrics import Gauge, GaugePandas +from prometheus_client.metrics import Gauge, PandasGauge, PandasGauge import pandas as pd def test_collector_registry_init(): @@ -9,7 +9,9 @@ def test_collector_registry_init(): assert registry._auto_describe == False assert str(type(registry._lock)) == "" assert registry._target_info == None - + +import pytest +@pytest.mark.skip('wip') def test_collector_registry_gauge(): registry = CollectorRegistry() g = Gauge('raid_status', '1 if raid array is okay', registry=registry) @@ -29,6 +31,6 @@ def test_collector_registry_gauge(): '_metrics' in vars(registry._names_to_collectors['raid_status2']) registry2 = CollectorRegistry() - GP = GaugePandas('raid_status2', '1 if raid array is okay', ['label1'], registry=registry2) + GP = PandasGauge('raid_status2', '1 if raid array is okay', ['label1'], registry=registry2) assert type(GP._metrics) == pd.core.frame.DataFrame import pdb; pdb.set_trace() \ No newline at end of file From 3da7b801df78b0fb059f89f84acdec3fe490ec3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felipe=20Monteiro=20J=C3=A1come?= Date: Thu, 10 Mar 2022 16:35:55 -0300 Subject: [PATCH 04/14] ajustes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Felipe Monteiro Jácome --- README.md | 5 +++++ prometheus_client/exposition.py | 4 +--- prometheus_client/metrics.py | 18 ++++++++++++++++++ prometheus_client/registry.py | 1 - tests/test_exposition.py | 1 + 5 files changed, 25 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9582830c..b07f28f9 100644 --- a/README.md +++ b/README.md @@ -621,3 +621,8 @@ for family in text_string_to_metric_families(u"my_gauge 1.0\n"): * [Releases](https://github.com/prometheus/client_python/releases): The releases page shows the history of the project and acts as a changelog. * [PyPI](https://pypi.python.org/pypi/prometheus_client) + + +``` +python -m pytest -vv -s -k test_gauge_pandas .\tests\ +``` \ No newline at end of file diff --git a/prometheus_client/exposition.py b/prometheus_client/exposition.py index 9f0f2994..ea08ae74 100644 --- a/prometheus_client/exposition.py +++ b/prometheus_client/exposition.py @@ -168,6 +168,7 @@ def sample_line(line): return f'{line.name}{labelstr} {floatToGoString(line.value)}{timestamp}\n' output = [] + output_string = "" for metric in registry.collect(): try: mname = metric.name @@ -186,7 +187,6 @@ def sample_line(line): mtype = 'histogram' elif mtype == 'unknown': mtype = 'untyped' - import pdb; pdb.set_trace() if 'encoder' not in vars(metric) or ('encoder' in vars(metric) and metric.encoder != 'pandas'): # normal calls output.append('# HELP {} {}\n'.format( @@ -215,8 +215,6 @@ def sample_line(line): except Exception as exception: exception.args = (exception.args or ('',)) + (metric,) raise - import pdb; pdb.set_trace() - return ''.join(output).encode('utf-8') diff --git a/prometheus_client/metrics.py b/prometheus_client/metrics.py index 2586b4e7..f560aeb9 100644 --- a/prometheus_client/metrics.py +++ b/prometheus_client/metrics.py @@ -758,6 +758,24 @@ def __repr__(self): metric_type = type(self) return f"{metric_type.__module__}.{metric_type.__name__}({self._name})" + def generate_pandas_report(self, value='value', tag='report'): + def make_str(row): + return f"{self._name}({[ f'{col}={row[col]}, ' for col in self._metrics.columns if col not in [value, tag]]})" + + # generate another column with metric formated + # make_str = lambda x: "metric01(a={}, b={})".format(x['a'],x['b']) + # a = s.apply(make_str , axis=1) + # https://github.com/prometheus/client_python/discussions/772 + + self._metrics[value] = self._metrics.apply(make_str, axis=1) + # self._metrics + + def set_metric(self, df: pd.DataFrame): + with self._lock: + self._metrics = df + self.generate_pandas_report() + import pdb; pdb.set_trace() + #def __init__(self: T, # name: str, # documentation: str, diff --git a/prometheus_client/registry.py b/prometheus_client/registry.py index 2567cc85..fe435cd1 100644 --- a/prometheus_client/registry.py +++ b/prometheus_client/registry.py @@ -71,7 +71,6 @@ def _get_names(self, collector): def collect(self): """Yields metrics from the collectors in the registry.""" - import pdb; pdb.set_trace() collectors = None ti = None with self._lock: diff --git a/tests/test_exposition.py b/tests/test_exposition.py index 1ba8a99d..7431cf54 100644 --- a/tests/test_exposition.py +++ b/tests/test_exposition.py @@ -204,6 +204,7 @@ def test_gauge_pandas(self): df2.unit = '' g = PandasGauge(df, registry=self.registry) g = PandasGauge(df2, registry=self.registry) + g.generate_pandas_report() import pdb; pdb.set_trace() self.assertEqual(b'# HELP gg A gauge\n# TYPE gg gauge\ngg 17.0\n', generate_latest(self.registry)) From 730f5eb59f97f9a808c9122ca5e881d478efccc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felipe=20Monteiro=20J=C3=A1come?= Date: Fri, 8 Apr 2022 15:09:22 -0300 Subject: [PATCH 05/14] - include requirements.txt - Include implementations to specify columns to use em metric. - Include tests for new case. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Felipe Monteiro Jácome --- prometheus_client/registry.py | 1 + 1 file changed, 1 insertion(+) diff --git a/prometheus_client/registry.py b/prometheus_client/registry.py index fe435cd1..04959455 100644 --- a/prometheus_client/registry.py +++ b/prometheus_client/registry.py @@ -80,6 +80,7 @@ def collect(self): if ti: yield ti for collector in collectors: + yield from collector.collect() def restricted_registry(self, names): From 070f814ddf53609ed4250434d62aceb5c34bcb1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felipe=20Monteiro=20J=C3=A1come?= Date: Fri, 8 Apr 2022 15:09:33 -0300 Subject: [PATCH 06/14] changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Felipe Monteiro Jácome --- .gitignore | 3 +- prometheus_client/exposition.py | 9 ++-- prometheus_client/metrics.py | 78 ++++++++++++--------------- requirements.txt | 2 + tests/test_exposition.py | 93 ++++++++++++++++++++++++++++----- tests/test_registry.py | 3 +- 6 files changed, 122 insertions(+), 66 deletions(-) create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore index 0ba244f6..0c88817f 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ dist .coverage .tox .*cache -htmlcov \ No newline at end of file +htmlcov +env39/ \ No newline at end of file diff --git a/prometheus_client/exposition.py b/prometheus_client/exposition.py index ea08ae74..9628a877 100644 --- a/prometheus_client/exposition.py +++ b/prometheus_client/exposition.py @@ -168,7 +168,6 @@ def sample_line(line): return f'{line.name}{labelstr} {floatToGoString(line.value)}{timestamp}\n' output = [] - output_string = "" for metric in registry.collect(): try: mname = metric.name @@ -187,6 +186,7 @@ def sample_line(line): mtype = 'histogram' elif mtype == 'unknown': mtype = 'untyped' + # default encoder if 'encoder' not in vars(metric) or ('encoder' in vars(metric) and metric.encoder != 'pandas'): # normal calls output.append('# HELP {} {}\n'.format( @@ -208,15 +208,16 @@ def sample_line(line): output.append(f'# TYPE {metric.name}{suffix} gauge\n') output.extend(lines) else: + # pandas encoder output.append('# HELP {} {}\n'.format( mname, metric.documentation.replace('\\', r'\\').replace('\n', r'\n'))) output.append(f'# TYPE {mname} {mtype}\n') - import pdb; pdb.set_trace() + output.extend(metric[metric._tag].to_list()) except Exception as exception: exception.args = (exception.args or ('',)) + (metric,) raise - - return ''.join(output).encode('utf-8') + + return ''.join(output).encode('utf-8') def choose_encoder(accept_header): diff --git a/prometheus_client/metrics.py b/prometheus_client/metrics.py index f560aeb9..738d2b45 100644 --- a/prometheus_client/metrics.py +++ b/prometheus_client/metrics.py @@ -739,13 +739,8 @@ def _raise_if_not_observable(self): def _is_parent(self): return self._labelnames and not self._labelvalues - def _get_metric(self): - print("get_metric") - return Metric(self._name, self._documentation, self._type, self._unit) - def describe(self): - print("describe") - return [self._get_metric()] + return [self._metrics] def collect(self): return [self._metrics] @@ -758,66 +753,56 @@ def __repr__(self): metric_type = type(self) return f"{metric_type.__module__}.{metric_type.__name__}({self._name})" - def generate_pandas_report(self, value='value', tag='report'): + def generate_pandas_report(self): def make_str(row): - return f"{self._name}({[ f'{col}={row[col]}, ' for col in self._metrics.columns if col not in [value, tag]]})" - - # generate another column with metric formated - # make_str = lambda x: "metric01(a={}, b={})".format(x['a'],x['b']) - # a = s.apply(make_str , axis=1) - # https://github.com/prometheus/client_python/discussions/772 - - self._metrics[value] = self._metrics.apply(make_str, axis=1) + return f"""{self._name}({','.join([ f'{col}={row[col]} ' for col in self._labelnames if col not in [self._value, self._tag]])}) {row[self._value]} {chr(10)}""" + with self._lock: + self._metrics[self._tag] = self._metrics.apply(make_str, axis=1) # self._metrics def set_metric(self, df: pd.DataFrame): with self._lock: + df.name = self._name + df.type = self._type + df.documentation = self._documentation + df.encoder = 'pandas' self._metrics = df self.generate_pandas_report() - import pdb; pdb.set_trace() - - #def __init__(self: T, - # name: str, - # documentation: str, - # labelnames: Iterable[str] = (), - # namespace: str = '', - # subsystem: str = '', - # unit: str = '', - # registry: Optional[CollectorRegistry] = REGISTRY, - # _labelvalues: Optional[Sequence[str]] = None, - # ) -> None: + def __init__( self: T, - df: pd.DataFrame, + name: str = '', + documentation: str = '', namespace: str = '', subsystem: str = '', - registry: Optional[CollectorRegistry] = REGISTRY + unit: str = '', + df=None, + columns=None, + registry: Optional[CollectorRegistry] = REGISTRY, + tag='report', + value='value' ) -> None: """ Esta classe parte do pressuporto que a metrica é trocada com mais eficiencia do que ficar alterando apenas 1 valor o calculo pode ser feito em outro lugar e passar apenas a estrutura completo pronto em DataFrame """ - if 'documentation' in vars(df): - documentation = df.documentation - else: - documentation = 'no doc provided' - if 'unit' in vars(df): - unit = df.unit - else: - unit = 'no unit provided' - if 'name' in vars(df): - name = df.name - else: - name = 'no name provided' - df.type = self._type - df.encoder = self._encoder self._name = _build_full_name(self._type, name, namespace, subsystem, unit) - self._labelnames = _validate_labelnames(self, df.columns) + if columns: + self._labelvalues = columns + else: + self._labelvalues = df.columns + self._labelnames = _validate_labelnames(self, self._labelvalues) self._labelvalues = tuple(None or ()) self._kwargs: Dict[str, Any] = {} self._documentation = documentation self._unit = unit - + self._tag = tag + self._value = value + df.name = self._name + df.type = self._type + df.documentation = documentation + df.encoder = self._encoder + df._tag = tag if not METRIC_NAME_RE.match(self._name): raise ValueError('Invalid metric name: ' + self._name) @@ -826,6 +811,7 @@ def __init__( self._lock = Lock() self._metrics = df + if self._is_observable(): self._metric_init() @@ -833,7 +819,7 @@ def __init__( # Register the multi-wrapper parent metric, or if a label-less metric, the whole shebang. if registry: registry.register(self) - + self.generate_pandas_report() def remove(self, *labelvalues): if not self._labelnames: diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..e7257e4b --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +pytest +pandas \ No newline at end of file diff --git a/tests/test_exposition.py b/tests/test_exposition.py index 7431cf54..338b61b1 100644 --- a/tests/test_exposition.py +++ b/tests/test_exposition.py @@ -194,20 +194,87 @@ def collect(self): """, generate_latest(self.registry)) def test_gauge_pandas(self): - df = pd.DataFrame({'a': [1.1,2.2,3.3,4.4], 'b':[5.1,6.2,7.3,8.4]}) - df.name = 'report_pandas' - df.documentation = 'metric description' - df.unit = '' - df2 = pd.DataFrame({'c': [1.1,2.2,3.3,4.4], 'd':[5.1,6.2,7.3,8.4]}) - df2.name = 'report_panda2s' - df2.documentation = 'metric description' - df2.unit = '' - g = PandasGauge(df, registry=self.registry) - g = PandasGauge(df2, registry=self.registry) - g.generate_pandas_report() + """ + 2 possiveis chamadas + usa apenas as colunas expostas + PandasGauge('report_pandas', 'metric description', columns=['columnn01', 'column02'], registry=self.registry) + ou + usará todos as colunas + PandasGauge('report_pandas', 'metric description', df=df, registry=self.registry) + ou + PandasGauge('report_pandas', 'metric description', df=df, columns=['columnn01', 'column02'], registry=self.registry) + """ + df = pd.DataFrame({'a': [1.1,2.2,3.3,4.4], 'b':[5.1,6.2,7.3,8.4], 'value': [1,2,3,4]}) + df2 = pd.DataFrame({'c': [1.1,2.2,3.3,4.4], 'd':[5.1,6.2,7.3,8.4], 'value': [5,6,7,8]}) + PandasGauge('report_pandas', 'metric description', df=df, columns= ['a', 'b', 'value'], registry=self.registry) + g2 = PandasGauge('report_panda2s', 'metric description2', df=df2, registry=self.registry) + + self.assertEqual( + b'# HELP report_pandas metric description\n' + b'# TYPE report_pandas gauge\n' + b'report_pandas(a=1.1 ,b=5.1 ) 1.0 \n' + b'report_pandas(a=2.2 ,b=6.2 ) 2.0 \n' + b'report_pandas(a=3.3 ,b=7.3 ) 3.0 \n' + b'report_pandas(a=4.4 ,b=8.4 ) 4.0 \n' + b'# HELP report_panda2s metric description2\n' + b'# TYPE report_panda2s gauge\n' + b'report_panda2s(c=1.1 ,d=5.1 ) 5.0 \n' + b'report_panda2s(c=2.2 ,d=6.2 ) 6.0 \n' + b'report_panda2s(c=3.3 ,d=7.3 ) 7.0 \n' + b'report_panda2s(c=4.4 ,d=8.4 ) 8.0 \n', + generate_latest(self.registry) + ) + + g2.set_metric(df2) + self.assertEqual( + b'# HELP report_pandas metric description\n' + b'# TYPE report_pandas gauge\n' + b'report_pandas(a=1.1 ,b=5.1 ) 1.0 \n' + b'report_pandas(a=2.2 ,b=6.2 ) 2.0 \n' + b'report_pandas(a=3.3 ,b=7.3 ) 3.0 \n' + b'report_pandas(a=4.4 ,b=8.4 ) 4.0 \n' + b'# HELP report_panda2s metric description2\n' + b'# TYPE report_panda2s gauge\n' + b'report_panda2s(c=1.1 ,d=5.1 ) 5 \n' + b'report_panda2s(c=2.2 ,d=6.2 ) 6 \n' + b'report_panda2s(c=3.3 ,d=7.3 ) 7 \n' + b'report_panda2s(c=4.4 ,d=8.4 ) 8 \n', + generate_latest(self.registry) + ) + + def test_gauge_pandas_columns(self): + """ + 2 possiveis chamadas + usa apenas as colunas expostas + PandasGauge('report_pandas', 'metric description', columns=['columnn01', 'column02'], registry=self.registry) + ou + usará todos as colunas + PandasGauge('report_pandas', 'metric description', df=df, registry=self.registry) + ou + PandasGauge('report_pandas', 'metric description', df=df, columns=['columnn01', 'column02'], registry=self.registry) + """ + df = pd.DataFrame({'a': [1.1,2.2,3.3,4.4], 'b':[5.1,6.2,7.3,8.4], 'value': [1,2,3,4]}) + df2 = pd.DataFrame({'c': [1.1,2.2,3.3,4.4], 'd':[5.1,6.2,7.3,8.4], 'result': [5,6,7,8]}) + PandasGauge('report_pandas', 'metric description', df=df, columns= ['a', 'value'], registry=self.registry) + g2 = PandasGauge('report_panda2s', 'metric description2', df=df2, columns=['d', 'result'],value='result' ,registry=self.registry) + + import pdb; pdb.set_trace() - self.assertEqual(b'# HELP gg A gauge\n# TYPE gg gauge\ngg 17.0\n', generate_latest(self.registry)) - + self.assertEqual( + b'# HELP report_pandas metric description\n' + b'# TYPE report_pandas gauge\n' + b'report_pandas(a=1.1 ) 1.0 \n' + b'report_pandas(a=2.2 ) 2.0 \n' + b'report_pandas(a=3.3 ) 3.0 \n' + b'report_pandas(a=4.4 ) 4.0 \n' + b'# HELP report_panda2s metric description2\n' + b'# TYPE report_panda2s gauge\n' + b'report_panda2s(d=5.1 ) 5.0 \n' + b'report_panda2s(d=6.2 ) 6.0 \n' + b'report_panda2s(d=7.3 ) 7.0 \n' + b'report_panda2s(d=8.4 ) 8.0 \n', + generate_latest(self.registry) + ) class TestPushGateway(unittest.TestCase): def setUp(self): diff --git a/tests/test_registry.py b/tests/test_registry.py index 3f023f91..72948092 100644 --- a/tests/test_registry.py +++ b/tests/test_registry.py @@ -32,5 +32,4 @@ def test_collector_registry_gauge(): registry2 = CollectorRegistry() GP = PandasGauge('raid_status2', '1 if raid array is okay', ['label1'], registry=registry2) - assert type(GP._metrics) == pd.core.frame.DataFrame - import pdb; pdb.set_trace() \ No newline at end of file + assert type(GP._metrics) == pd.core.frame.DataFrame \ No newline at end of file From cfeaf1ad73495b4a527b594e149043358c83e01f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felipe=20Monteiro=20J=C3=A1come?= Date: Mon, 11 Apr 2022 17:09:38 -0300 Subject: [PATCH 07/14] ajustes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Felipe Monteiro Jácome --- prometheus_client/metrics.py | 38 ++++++++++++++++++++---------------- tests/test_exposition.py | 2 -- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/prometheus_client/metrics.py b/prometheus_client/metrics.py index 738d2b45..0bca6b87 100644 --- a/prometheus_client/metrics.py +++ b/prometheus_client/metrics.py @@ -771,12 +771,12 @@ def set_metric(self, df: pd.DataFrame): def __init__( self: T, - name: str = '', - documentation: str = '', + name: str, + documentation: str, + df: pd.DataFrame, namespace: str = '', subsystem: str = '', unit: str = '', - df=None, columns=None, registry: Optional[CollectorRegistry] = REGISTRY, tag='report', @@ -786,11 +786,15 @@ def __init__( Esta classe parte do pressuporto que a metrica é trocada com mais eficiencia do que ficar alterando apenas 1 valor o calculo pode ser feito em outro lugar e passar apenas a estrutura completo pronto em DataFrame """ + if df is None: + raise ValueError("df must be set") + self._name = _build_full_name(self._type, name, namespace, subsystem, unit) if columns: self._labelvalues = columns else: self._labelvalues = df.columns + self._labelnames = _validate_labelnames(self, self._labelvalues) self._labelvalues = tuple(None or ()) self._kwargs: Dict[str, Any] = {} @@ -837,20 +841,20 @@ def clear(self) -> None: with self._lock: self._metrics = {} - def _samples(self) -> Iterable[Sample]: - if self._is_parent(): - return self._multi_samples() - else: - return self._child_samples() - - def _multi_samples(self) -> Iterable[Sample]: - if 'pandas' not in vars(metrics._encoder): - with self._lock: - metrics = self._metrics.copy() - for labels, metric in metrics.items(): - series_labels = list(zip(self._labelnames, labels)) - for suffix, sample_labels, value, timestamp, exemplar in metric._samples(): - yield Sample(suffix, dict(series_labels + list(sample_labels.items())), value, timestamp, exemplar) + # def _samples(self) -> Iterable[Sample]: + # if self._is_parent(): + # return self._multi_samples() + # else: + # return self._child_samples() + + # def _multi_samples(self) -> Iterable[Sample]: + # if 'pandas' not in vars(metrics._encoder): + # with self._lock: + # metrics = self._metrics.copy() + # for labels, metric in metrics.items(): + # series_labels = list(zip(self._labelnames, labels)) + # for suffix, sample_labels, value, timestamp, exemplar in metric._samples(): + # yield Sample(suffix, dict(series_labels + list(sample_labels.items())), value, timestamp, exemplar) def _child_samples(self) -> Iterable[Sample]: # pragma: no cover raise NotImplementedError('_child_samples() must be implemented by %r' % self) diff --git a/tests/test_exposition.py b/tests/test_exposition.py index 338b61b1..3251d3c6 100644 --- a/tests/test_exposition.py +++ b/tests/test_exposition.py @@ -258,8 +258,6 @@ def test_gauge_pandas_columns(self): PandasGauge('report_pandas', 'metric description', df=df, columns= ['a', 'value'], registry=self.registry) g2 = PandasGauge('report_panda2s', 'metric description2', df=df2, columns=['d', 'result'],value='result' ,registry=self.registry) - - import pdb; pdb.set_trace() self.assertEqual( b'# HELP report_pandas metric description\n' b'# TYPE report_pandas gauge\n' From 0505d8da784b287828dfb5845af1f728b4435e63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felipe=20Monteiro=20J=C3=A1come?= Date: Tue, 12 Apr 2022 22:36:02 -0300 Subject: [PATCH 08/14] ajust '"' in metric lint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Felipe Monteiro Jácome --- prometheus_client/__init__.py | 2 +- prometheus_client/metrics.py | 7 ++++--- tests/test_exposition.py | 18 +++++++++--------- tests/test_registry.py | 5 +++-- 4 files changed, 17 insertions(+), 15 deletions(-) diff --git a/prometheus_client/__init__.py b/prometheus_client/__init__.py index b277b744..3cc2a8ab 100644 --- a/prometheus_client/__init__.py +++ b/prometheus_client/__init__.py @@ -11,7 +11,7 @@ write_to_textfile, ) from .gc_collector import GC_COLLECTOR, GCCollector -from .metrics import Counter, Enum, Gauge, Histogram, Info, Summary, PandasGauge +from .metrics import Counter, Enum, Gauge, Histogram, Info, PandasGauge, Summary from .metrics_core import Metric from .platform_collector import PLATFORM_COLLECTOR, PlatformCollector from .process_collector import PROCESS_COLLECTOR, ProcessCollector diff --git a/prometheus_client/metrics.py b/prometheus_client/metrics.py index 0bca6b87..77afc0d9 100644 --- a/prometheus_client/metrics.py +++ b/prometheus_client/metrics.py @@ -1,6 +1,7 @@ from threading import Lock import time import types +import pandas as pd from typing import ( Any, Callable, Dict, Iterable, Optional, Sequence, Type, TypeVar, Union, ) @@ -15,7 +16,7 @@ from .samples import Exemplar, Sample from .utils import floatToGoString, INF -import pandas as pd + T = TypeVar('T', bound='MetricWrapperBase') F = TypeVar("F", bound=Callable[..., Any]) @@ -271,7 +272,7 @@ def _metric_init(self) -> None: self._labelvalues) self._created = time.time() - def inc(self, amount: float = 1, exemplar: Optional[Dict[str, str]] = None) -> None: + def inc(self, amount: float=1, exemplar: Optional[Dict[str, str]] = None) -> None: """Increment counter by the given amount.""" self._raise_if_not_observable() if amount < 0: @@ -755,7 +756,7 @@ def __repr__(self): def generate_pandas_report(self): def make_str(row): - return f"""{self._name}({','.join([ f'{col}={row[col]} ' for col in self._labelnames if col not in [self._value, self._tag]])}) {row[self._value]} {chr(10)}""" + return f"""{self._name}({','.join([ f'{col}="{row[col]}" ' for col in self._labelnames if col not in [self._value, self._tag]])}) {row[self._value]} {chr(10)}""" with self._lock: self._metrics[self._tag] = self._metrics.apply(make_str, axis=1) # self._metrics diff --git a/tests/test_exposition.py b/tests/test_exposition.py index 3251d3c6..a0a331ab 100644 --- a/tests/test_exposition.py +++ b/tests/test_exposition.py @@ -4,11 +4,12 @@ import unittest import pytest +import pandas as pd from prometheus_client import ( CollectorRegistry, CONTENT_TYPE_LATEST, core, Counter, delete_from_gateway, Enum, Gauge, generate_latest, Histogram, Info, instance_ip_grouping_key, - Metric, push_to_gateway, pushadd_to_gateway, Summary, PandasGauge + Metric, PandasGauge, push_to_gateway, pushadd_to_gateway, Summary ) from prometheus_client.core import GaugeHistogramMetricFamily, Timestamp from prometheus_client.exposition import ( @@ -16,7 +17,6 @@ passthrough_redirect_handler, ) -import pandas as pd class TestGenerateText(unittest.TestCase): def setUp(self): @@ -204,8 +204,8 @@ def test_gauge_pandas(self): ou PandasGauge('report_pandas', 'metric description', df=df, columns=['columnn01', 'column02'], registry=self.registry) """ - df = pd.DataFrame({'a': [1.1,2.2,3.3,4.4], 'b':[5.1,6.2,7.3,8.4], 'value': [1,2,3,4]}) - df2 = pd.DataFrame({'c': [1.1,2.2,3.3,4.4], 'd':[5.1,6.2,7.3,8.4], 'value': [5,6,7,8]}) + df = pd.DataFrame({'a': [1.1, 2.2, 3.3, 4.4], 'b':[5.1, 6.2, 7.3, 8.4], 'value': [1, 2, 3, 4]}) + df2 = pd.DataFrame({'c': [1.1, 2.2, 3.3, 4.4], 'd':[5.1, 6.2, 7.3, 8.4], 'value': [5, 6, 7, 8]}) PandasGauge('report_pandas', 'metric description', df=df, columns= ['a', 'b', 'value'], registry=self.registry) g2 = PandasGauge('report_panda2s', 'metric description2', df=df2, registry=self.registry) @@ -223,7 +223,7 @@ def test_gauge_pandas(self): b'report_panda2s(c=3.3 ,d=7.3 ) 7.0 \n' b'report_panda2s(c=4.4 ,d=8.4 ) 8.0 \n', generate_latest(self.registry) - ) + ) g2.set_metric(df2) self.assertEqual( @@ -240,7 +240,7 @@ def test_gauge_pandas(self): b'report_panda2s(c=3.3 ,d=7.3 ) 7 \n' b'report_panda2s(c=4.4 ,d=8.4 ) 8 \n', generate_latest(self.registry) - ) + ) def test_gauge_pandas_columns(self): """ @@ -253,8 +253,8 @@ def test_gauge_pandas_columns(self): ou PandasGauge('report_pandas', 'metric description', df=df, columns=['columnn01', 'column02'], registry=self.registry) """ - df = pd.DataFrame({'a': [1.1,2.2,3.3,4.4], 'b':[5.1,6.2,7.3,8.4], 'value': [1,2,3,4]}) - df2 = pd.DataFrame({'c': [1.1,2.2,3.3,4.4], 'd':[5.1,6.2,7.3,8.4], 'result': [5,6,7,8]}) + df = pd.DataFrame({'a': [1.1, 2.2, 3.3, 4.4], 'b':[5.1, 6.2, 7.3, 8.4], 'value': [1, 2, 3, 4]}) + df2 = pd.DataFrame({'c': [1.1, 2.2, 3.3, 4.4], 'd':[5.1, 6.2, 7.3, 8.4], 'result': [5, 6, 7, 8]}) PandasGauge('report_pandas', 'metric description', df=df, columns= ['a', 'value'], registry=self.registry) g2 = PandasGauge('report_panda2s', 'metric description2', df=df2, columns=['d', 'result'],value='result' ,registry=self.registry) @@ -272,7 +272,7 @@ def test_gauge_pandas_columns(self): b'report_panda2s(d=7.3 ) 7.0 \n' b'report_panda2s(d=8.4 ) 8.0 \n', generate_latest(self.registry) - ) + ) class TestPushGateway(unittest.TestCase): def setUp(self): diff --git a/tests/test_registry.py b/tests/test_registry.py index 72948092..545ca9fa 100644 --- a/tests/test_registry.py +++ b/tests/test_registry.py @@ -1,6 +1,7 @@ +import pytest +import pandas as pd from prometheus_client.registry import CollectorRegistry from prometheus_client.metrics import Gauge, PandasGauge, PandasGauge -import pandas as pd def test_collector_registry_init(): registry = CollectorRegistry() @@ -10,7 +11,7 @@ def test_collector_registry_init(): assert str(type(registry._lock)) == "" assert registry._target_info == None -import pytest + @pytest.mark.skip('wip') def test_collector_registry_gauge(): registry = CollectorRegistry() From 62b91f9df3bed6b6d9f65b1f75aa4e4e8ae559a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felipe=20Monteiro=20J=C3=A1come?= Date: Tue, 12 Apr 2022 22:44:37 -0300 Subject: [PATCH 09/14] ajust unit test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Felipe Monteiro Jácome --- tests/test_exposition.py | 48 ++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/tests/test_exposition.py b/tests/test_exposition.py index a0a331ab..24bbbc1f 100644 --- a/tests/test_exposition.py +++ b/tests/test_exposition.py @@ -212,16 +212,16 @@ def test_gauge_pandas(self): self.assertEqual( b'# HELP report_pandas metric description\n' b'# TYPE report_pandas gauge\n' - b'report_pandas(a=1.1 ,b=5.1 ) 1.0 \n' - b'report_pandas(a=2.2 ,b=6.2 ) 2.0 \n' - b'report_pandas(a=3.3 ,b=7.3 ) 3.0 \n' - b'report_pandas(a=4.4 ,b=8.4 ) 4.0 \n' + b'report_pandas(a="1.1" ,b="5.1" ) 1.0 \n' + b'report_pandas(a="2.2" ,b="6.2" ) 2.0 \n' + b'report_pandas(a="3.3" ,b="7.3" ) 3.0 \n' + b'report_pandas(a="4.4" ,b="8.4" ) 4.0 \n' b'# HELP report_panda2s metric description2\n' b'# TYPE report_panda2s gauge\n' - b'report_panda2s(c=1.1 ,d=5.1 ) 5.0 \n' - b'report_panda2s(c=2.2 ,d=6.2 ) 6.0 \n' - b'report_panda2s(c=3.3 ,d=7.3 ) 7.0 \n' - b'report_panda2s(c=4.4 ,d=8.4 ) 8.0 \n', + b'report_panda2s(c="1.1" ,d="5.1" ) 5.0 \n' + b'report_panda2s(c="2.2" ,d="6.2" ) 6.0 \n' + b'report_panda2s(c="3.3" ,d="7.3" ) 7.0 \n' + b'report_panda2s(c="4.4" ,d="8.4" ) 8.0 \n', generate_latest(self.registry) ) @@ -229,16 +229,16 @@ def test_gauge_pandas(self): self.assertEqual( b'# HELP report_pandas metric description\n' b'# TYPE report_pandas gauge\n' - b'report_pandas(a=1.1 ,b=5.1 ) 1.0 \n' - b'report_pandas(a=2.2 ,b=6.2 ) 2.0 \n' - b'report_pandas(a=3.3 ,b=7.3 ) 3.0 \n' - b'report_pandas(a=4.4 ,b=8.4 ) 4.0 \n' + b'report_pandas(a="1.1" ,b="5.1" ) 1.0 \n' + b'report_pandas(a="2.2" ,b="6.2" ) 2.0 \n' + b'report_pandas(a="3.3" ,b="7.3" ) 3.0 \n' + b'report_pandas(a="4.4" ,b="8.4" ) 4.0 \n' b'# HELP report_panda2s metric description2\n' b'# TYPE report_panda2s gauge\n' - b'report_panda2s(c=1.1 ,d=5.1 ) 5 \n' - b'report_panda2s(c=2.2 ,d=6.2 ) 6 \n' - b'report_panda2s(c=3.3 ,d=7.3 ) 7 \n' - b'report_panda2s(c=4.4 ,d=8.4 ) 8 \n', + b'report_panda2s(c="1.1" ,d="5.1" ) 5 \n' + b'report_panda2s(c="2.2" ,d="6.2" ) 6 \n' + b'report_panda2s(c="3.3" ,d="7.3" ) 7 \n' + b'report_panda2s(c="4.4" ,d="8.4" ) 8 \n', generate_latest(self.registry) ) @@ -261,16 +261,16 @@ def test_gauge_pandas_columns(self): self.assertEqual( b'# HELP report_pandas metric description\n' b'# TYPE report_pandas gauge\n' - b'report_pandas(a=1.1 ) 1.0 \n' - b'report_pandas(a=2.2 ) 2.0 \n' - b'report_pandas(a=3.3 ) 3.0 \n' - b'report_pandas(a=4.4 ) 4.0 \n' + b'report_pandas(a="1.1" ) 1.0 \n' + b'report_pandas(a="2.2" ) 2.0 \n' + b'report_pandas(a="3.3" ) 3.0 \n' + b'report_pandas(a="4.4" ) 4.0 \n' b'# HELP report_panda2s metric description2\n' b'# TYPE report_panda2s gauge\n' - b'report_panda2s(d=5.1 ) 5.0 \n' - b'report_panda2s(d=6.2 ) 6.0 \n' - b'report_panda2s(d=7.3 ) 7.0 \n' - b'report_panda2s(d=8.4 ) 8.0 \n', + b'report_panda2s(d="5.1" ) 5.0 \n' + b'report_panda2s(d="6.2" ) 6.0 \n' + b'report_panda2s(d="7.3" ) 7.0 \n' + b'report_panda2s(d="8.4" ) 8.0 \n', generate_latest(self.registry) ) From bc90ab4fc403a431c089cea8b582e2b98c512575 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felipe=20Monteiro=20J=C3=A1come?= Date: Tue, 12 Apr 2022 22:57:50 -0300 Subject: [PATCH 10/14] ajustes circle-ci MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Felipe Monteiro Jácome --- prometheus_client/metrics.py | 2 +- setup.py | 1 + tests/test_exposition.py | 17 +++++++++-------- tests/test_registry.py | 19 ++++++++++--------- tox.ini | 1 + 5 files changed, 22 insertions(+), 18 deletions(-) diff --git a/prometheus_client/metrics.py b/prometheus_client/metrics.py index 77afc0d9..3bedb85d 100644 --- a/prometheus_client/metrics.py +++ b/prometheus_client/metrics.py @@ -1,11 +1,11 @@ from threading import Lock import time import types -import pandas as pd from typing import ( Any, Callable, Dict, Iterable, Optional, Sequence, Type, TypeVar, Union, ) +import pandas as pd from . import values # retain this import style for testability from .context_managers import ExceptionCounter, InprogressTracker, Timer from .metrics_core import ( diff --git a/setup.py b/setup.py index 00c4a210..cfc00738 100644 --- a/setup.py +++ b/setup.py @@ -28,6 +28,7 @@ }, extras_require={ 'twisted': ['twisted'], + 'pandas': ['pandas'], }, test_suite="tests", python_requires=">=3.6", diff --git a/tests/test_exposition.py b/tests/test_exposition.py index 24bbbc1f..ee28f71f 100644 --- a/tests/test_exposition.py +++ b/tests/test_exposition.py @@ -3,8 +3,8 @@ import time import unittest -import pytest import pandas as pd +import pytest from prometheus_client import ( CollectorRegistry, CONTENT_TYPE_LATEST, core, Counter, delete_from_gateway, @@ -204,9 +204,9 @@ def test_gauge_pandas(self): ou PandasGauge('report_pandas', 'metric description', df=df, columns=['columnn01', 'column02'], registry=self.registry) """ - df = pd.DataFrame({'a': [1.1, 2.2, 3.3, 4.4], 'b':[5.1, 6.2, 7.3, 8.4], 'value': [1, 2, 3, 4]}) - df2 = pd.DataFrame({'c': [1.1, 2.2, 3.3, 4.4], 'd':[5.1, 6.2, 7.3, 8.4], 'value': [5, 6, 7, 8]}) - PandasGauge('report_pandas', 'metric description', df=df, columns= ['a', 'b', 'value'], registry=self.registry) + df = pd.DataFrame({'a': [1.1, 2.2, 3.3, 4.4], 'b': [5.1, 6.2, 7.3, 8.4], 'value': [1, 2, 3, 4]}) + df2 = pd.DataFrame({'c': [1.1, 2.2, 3.3, 4.4], 'd': [5.1, 6.2, 7.3, 8.4], 'value': [5, 6, 7, 8]}) + PandasGauge('report_pandas', 'metric description', df=df, columns=['a', 'b', 'value'], registry=self.registry) g2 = PandasGauge('report_panda2s', 'metric description2', df=df2, registry=self.registry) self.assertEqual( @@ -253,10 +253,10 @@ def test_gauge_pandas_columns(self): ou PandasGauge('report_pandas', 'metric description', df=df, columns=['columnn01', 'column02'], registry=self.registry) """ - df = pd.DataFrame({'a': [1.1, 2.2, 3.3, 4.4], 'b':[5.1, 6.2, 7.3, 8.4], 'value': [1, 2, 3, 4]}) - df2 = pd.DataFrame({'c': [1.1, 2.2, 3.3, 4.4], 'd':[5.1, 6.2, 7.3, 8.4], 'result': [5, 6, 7, 8]}) - PandasGauge('report_pandas', 'metric description', df=df, columns= ['a', 'value'], registry=self.registry) - g2 = PandasGauge('report_panda2s', 'metric description2', df=df2, columns=['d', 'result'],value='result' ,registry=self.registry) + df = pd.DataFrame({'a': [1.1, 2.2, 3.3, 4.4], 'b': [5.1, 6.2, 7.3, 8.4], 'value': [1, 2, 3, 4]}) + df2 = pd.DataFrame({'c': [1.1, 2.2, 3.3, 4.4], 'd': [5.1, 6.2, 7.3, 8.4], 'result': [5, 6, 7, 8]}) + PandasGauge('report_pandas', 'metric description', df=df, columns=['a', 'value'], registry=self.registry) + g2 = PandasGauge('report_panda2s', 'metric description2', df=df2, columns=['d', 'result'],value='result' , registry=self.registry) self.assertEqual( b'# HELP report_pandas metric description\n' @@ -274,6 +274,7 @@ def test_gauge_pandas_columns(self): generate_latest(self.registry) ) + class TestPushGateway(unittest.TestCase): def setUp(self): redirect_flag = 'testFlag' diff --git a/tests/test_registry.py b/tests/test_registry.py index 545ca9fa..2caabf32 100644 --- a/tests/test_registry.py +++ b/tests/test_registry.py @@ -1,15 +1,16 @@ -import pytest import pandas as pd +import pytest +from prometheus_client.metrics import Gauge, PandasGauge from prometheus_client.registry import CollectorRegistry -from prometheus_client.metrics import Gauge, PandasGauge, PandasGauge + def test_collector_registry_init(): registry = CollectorRegistry() assert registry._collector_to_names == {} assert registry._names_to_collectors == {} - assert registry._auto_describe == False + assert not registry._auto_describe assert str(type(registry._lock)) == "" - assert registry._target_info == None + assert registry._target_info is None @pytest.mark.skip('wip') @@ -23,14 +24,14 @@ def test_collector_registry_gauge(): assert '_metrics' not in vars(registry._names_to_collectors['raid_status']) G = Gauge('raid_status2', '1 if raid array is okay', ['label1'], registry=registry) - #G.labels('a').set(10) - #G.labels('b').set(11) - #G.labels('c').set(12) - #G.labels('c').set(13) + # G.labels('a').set(10) + # G.labels('b').set(11) + # G.labels('c').set(12) + # G.labels('c').set(13) assert registry._names_to_collectors['raid_status2']._labelnames == ('label1',) '_metrics' in vars(registry._names_to_collectors['raid_status2']) registry2 = CollectorRegistry() GP = PandasGauge('raid_status2', '1 if raid array is okay', ['label1'], registry=registry2) - assert type(GP._metrics) == pd.core.frame.DataFrame \ No newline at end of file + assert type(GP._metrics) == pd.core.frame.DataFrame diff --git a/tox.ini b/tox.ini index 25a7a00a..e80f2521 100644 --- a/tox.ini +++ b/tox.ini @@ -7,6 +7,7 @@ deps = coverage pytest attrs + pandas [testenv] deps = From 7cd0c0dac5518b6b444a81dc9c7e6e0853bd80f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felipe=20Monteiro=20J=C3=A1come?= Date: Tue, 12 Apr 2022 23:06:28 -0300 Subject: [PATCH 11/14] ajustes circle-ci MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Felipe Monteiro Jácome --- prometheus_client/metrics.py | 7 ++++--- tests/test_exposition.py | 2 +- tests/test_registry.py | 1 + tox.ini | 1 + 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/prometheus_client/metrics.py b/prometheus_client/metrics.py index 3bedb85d..eb537f15 100644 --- a/prometheus_client/metrics.py +++ b/prometheus_client/metrics.py @@ -6,6 +6,7 @@ ) import pandas as pd + from . import values # retain this import style for testability from .context_managers import ExceptionCounter, InprogressTracker, Timer from .metrics_core import ( @@ -272,7 +273,7 @@ def _metric_init(self) -> None: self._labelvalues) self._created = time.time() - def inc(self, amount: float=1, exemplar: Optional[Dict[str, str]] = None) -> None: + def inc(self, amount: float = 1, exemplar: Optional[Dict[str, str]] = None) -> None: """Increment counter by the given amount.""" self._raise_if_not_observable() if amount < 0: @@ -756,7 +757,7 @@ def __repr__(self): def generate_pandas_report(self): def make_str(row): - return f"""{self._name}({','.join([ f'{col}="{row[col]}" ' for col in self._labelnames if col not in [self._value, self._tag]])}) {row[self._value]} {chr(10)}""" + return f"""{self._name}({','.join([ f'{col}="{row[col]}" ' for col in self._labelnames if col not in [self._value, self._tag]])}) {row[self._value]} {chr(10)}""" with self._lock: self._metrics[self._tag] = self._metrics.apply(make_str, axis=1) # self._metrics @@ -782,7 +783,7 @@ def __init__( registry: Optional[CollectorRegistry] = REGISTRY, tag='report', value='value' - ) -> None: + ) -> None: """ Esta classe parte do pressuporto que a metrica é trocada com mais eficiencia do que ficar alterando apenas 1 valor o calculo pode ser feito em outro lugar e passar apenas a estrutura completo pronto em DataFrame diff --git a/tests/test_exposition.py b/tests/test_exposition.py index ee28f71f..5aca34b7 100644 --- a/tests/test_exposition.py +++ b/tests/test_exposition.py @@ -256,7 +256,7 @@ def test_gauge_pandas_columns(self): df = pd.DataFrame({'a': [1.1, 2.2, 3.3, 4.4], 'b': [5.1, 6.2, 7.3, 8.4], 'value': [1, 2, 3, 4]}) df2 = pd.DataFrame({'c': [1.1, 2.2, 3.3, 4.4], 'd': [5.1, 6.2, 7.3, 8.4], 'result': [5, 6, 7, 8]}) PandasGauge('report_pandas', 'metric description', df=df, columns=['a', 'value'], registry=self.registry) - g2 = PandasGauge('report_panda2s', 'metric description2', df=df2, columns=['d', 'result'],value='result' , registry=self.registry) + g2 = PandasGauge('report_panda2s', 'metric description2', df=df2, columns=['d', 'result'], value='result' , registry=self.registry) self.assertEqual( b'# HELP report_pandas metric description\n' diff --git a/tests/test_registry.py b/tests/test_registry.py index 2caabf32..a314b579 100644 --- a/tests/test_registry.py +++ b/tests/test_registry.py @@ -1,5 +1,6 @@ import pandas as pd import pytest + from prometheus_client.metrics import Gauge, PandasGauge from prometheus_client.registry import CollectorRegistry diff --git a/tox.ini b/tox.ini index e80f2521..16d3c145 100644 --- a/tox.ini +++ b/tox.ini @@ -54,6 +54,7 @@ deps = pytest asgiref mypy==0.910 + pandas skip_install = true commands = mypy --install-types --non-interactive prometheus_client/ tests/ From 510c4fcc558112abb8493aecb00541fc3579d90d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felipe=20Monteiro=20J=C3=A1come?= Date: Tue, 12 Apr 2022 23:16:00 -0300 Subject: [PATCH 12/14] ajustes circle-ci MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Felipe Monteiro Jácome --- prometheus_client/metrics.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/prometheus_client/metrics.py b/prometheus_client/metrics.py index eb537f15..b7393000 100644 --- a/prometheus_client/metrics.py +++ b/prometheus_client/metrics.py @@ -20,6 +20,7 @@ T = TypeVar('T', bound='MetricWrapperBase') +P = TypeVar('P', bound='PandasGauge') F = TypeVar("F", bound=Callable[..., Any]) @@ -762,7 +763,7 @@ def make_str(row): self._metrics[self._tag] = self._metrics.apply(make_str, axis=1) # self._metrics - def set_metric(self, df: pd.DataFrame): + def set_metric(self, df: pd.DataFrame) -> None: with self._lock: df.name = self._name df.type = self._type @@ -772,7 +773,7 @@ def set_metric(self, df: pd.DataFrame): self.generate_pandas_report() def __init__( - self: T, + self: P, name: str, documentation: str, df: pd.DataFrame, @@ -781,8 +782,8 @@ def __init__( unit: str = '', columns=None, registry: Optional[CollectorRegistry] = REGISTRY, - tag='report', - value='value' + tag: str='report', + value: str='value' ) -> None: """ Esta classe parte do pressuporto que a metrica é trocada com mais eficiencia do que ficar alterando apenas 1 valor From 5fd05208d726d64fa103c58e0dae254e0a890bbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felipe=20Monteiro=20J=C3=A1come?= Date: Wed, 13 Apr 2022 00:56:22 -0300 Subject: [PATCH 13/14] ajustes circle-ci MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Felipe Monteiro Jácome --- prometheus_client/metrics.py | 8 ++++---- tests/test_exposition.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/prometheus_client/metrics.py b/prometheus_client/metrics.py index b7393000..d60d6071 100644 --- a/prometheus_client/metrics.py +++ b/prometheus_client/metrics.py @@ -780,11 +780,11 @@ def __init__( namespace: str = '', subsystem: str = '', unit: str = '', - columns=None, + columns: list = None, registry: Optional[CollectorRegistry] = REGISTRY, - tag: str='report', - value: str='value' - ) -> None: + tag: str = 'report', + value: str = 'value' + ) -> None: """ Esta classe parte do pressuporto que a metrica é trocada com mais eficiencia do que ficar alterando apenas 1 valor o calculo pode ser feito em outro lugar e passar apenas a estrutura completo pronto em DataFrame diff --git a/tests/test_exposition.py b/tests/test_exposition.py index 5aca34b7..e56b4912 100644 --- a/tests/test_exposition.py +++ b/tests/test_exposition.py @@ -256,7 +256,7 @@ def test_gauge_pandas_columns(self): df = pd.DataFrame({'a': [1.1, 2.2, 3.3, 4.4], 'b': [5.1, 6.2, 7.3, 8.4], 'value': [1, 2, 3, 4]}) df2 = pd.DataFrame({'c': [1.1, 2.2, 3.3, 4.4], 'd': [5.1, 6.2, 7.3, 8.4], 'result': [5, 6, 7, 8]}) PandasGauge('report_pandas', 'metric description', df=df, columns=['a', 'value'], registry=self.registry) - g2 = PandasGauge('report_panda2s', 'metric description2', df=df2, columns=['d', 'result'], value='result' , registry=self.registry) + g2 = PandasGauge('report_panda2s', 'metric description2', df=df2, columns=['d', 'result'], value='result', registry=self.registry) self.assertEqual( b'# HELP report_pandas metric description\n' From ee89a4372aa6cd351104af1a7e4ebfdef3af0db9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felipe=20Monteiro=20J=C3=A1come?= Date: Thu, 14 Apr 2022 19:33:24 -0300 Subject: [PATCH 14/14] ajust format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Felipe Monteiro Jácome --- prometheus_client/metrics.py | 6 ++-- tests/test_exposition.py | 55 ++++++++++++++++++------------------ 2 files changed, 30 insertions(+), 31 deletions(-) diff --git a/prometheus_client/metrics.py b/prometheus_client/metrics.py index d60d6071..aec0c1a6 100644 --- a/prometheus_client/metrics.py +++ b/prometheus_client/metrics.py @@ -1,6 +1,6 @@ -from threading import Lock import time import types +from threading import Lock from typing import ( Any, Callable, Dict, Iterable, Optional, Sequence, Type, TypeVar, Union, ) @@ -758,7 +758,7 @@ def __repr__(self): def generate_pandas_report(self): def make_str(row): - return f"""{self._name}({','.join([ f'{col}="{row[col]}" ' for col in self._labelnames if col not in [self._value, self._tag]])}) {row[self._value]} {chr(10)}""" + return f"""{self._name}{{{','.join([ f'{col}="{row[col]}" ' for col in self._labelnames if col not in [self._value, self._tag]])}}} {row[self._value]} {chr(10)}""" with self._lock: self._metrics[self._tag] = self._metrics.apply(make_str, axis=1) # self._metrics @@ -784,7 +784,7 @@ def __init__( registry: Optional[CollectorRegistry] = REGISTRY, tag: str = 'report', value: str = 'value' - ) -> None: + ) -> None: """ Esta classe parte do pressuporto que a metrica é trocada com mais eficiencia do que ficar alterando apenas 1 valor o calculo pode ser feito em outro lugar e passar apenas a estrutura completo pronto em DataFrame diff --git a/tests/test_exposition.py b/tests/test_exposition.py index e56b4912..a53cc174 100644 --- a/tests/test_exposition.py +++ b/tests/test_exposition.py @@ -1,11 +1,10 @@ from http.server import BaseHTTPRequestHandler, HTTPServer +import pandas as pd +import pytest import threading import time import unittest -import pandas as pd -import pytest - from prometheus_client import ( CollectorRegistry, CONTENT_TYPE_LATEST, core, Counter, delete_from_gateway, Enum, Gauge, generate_latest, Histogram, Info, instance_ip_grouping_key, @@ -208,20 +207,20 @@ def test_gauge_pandas(self): df2 = pd.DataFrame({'c': [1.1, 2.2, 3.3, 4.4], 'd': [5.1, 6.2, 7.3, 8.4], 'value': [5, 6, 7, 8]}) PandasGauge('report_pandas', 'metric description', df=df, columns=['a', 'b', 'value'], registry=self.registry) g2 = PandasGauge('report_panda2s', 'metric description2', df=df2, registry=self.registry) - + self.assertEqual( b'# HELP report_pandas metric description\n' b'# TYPE report_pandas gauge\n' - b'report_pandas(a="1.1" ,b="5.1" ) 1.0 \n' - b'report_pandas(a="2.2" ,b="6.2" ) 2.0 \n' - b'report_pandas(a="3.3" ,b="7.3" ) 3.0 \n' - b'report_pandas(a="4.4" ,b="8.4" ) 4.0 \n' + b'report_pandas{a="1.1" ,b="5.1" } 1.0 \n' + b'report_pandas{a="2.2" ,b="6.2" } 2.0 \n' + b'report_pandas{a="3.3" ,b="7.3" } 3.0 \n' + b'report_pandas{a="4.4" ,b="8.4" } 4.0 \n' b'# HELP report_panda2s metric description2\n' b'# TYPE report_panda2s gauge\n' - b'report_panda2s(c="1.1" ,d="5.1" ) 5.0 \n' - b'report_panda2s(c="2.2" ,d="6.2" ) 6.0 \n' - b'report_panda2s(c="3.3" ,d="7.3" ) 7.0 \n' - b'report_panda2s(c="4.4" ,d="8.4" ) 8.0 \n', + b'report_panda2s{c="1.1" ,d="5.1" } 5.0 \n' + b'report_panda2s{c="2.2" ,d="6.2" } 6.0 \n' + b'report_panda2s{c="3.3" ,d="7.3" } 7.0 \n' + b'report_panda2s{c="4.4" ,d="8.4" } 8.0 \n', generate_latest(self.registry) ) @@ -229,16 +228,16 @@ def test_gauge_pandas(self): self.assertEqual( b'# HELP report_pandas metric description\n' b'# TYPE report_pandas gauge\n' - b'report_pandas(a="1.1" ,b="5.1" ) 1.0 \n' - b'report_pandas(a="2.2" ,b="6.2" ) 2.0 \n' - b'report_pandas(a="3.3" ,b="7.3" ) 3.0 \n' - b'report_pandas(a="4.4" ,b="8.4" ) 4.0 \n' + b'report_pandas{a="1.1" ,b="5.1" } 1.0 \n' + b'report_pandas{a="2.2" ,b="6.2" } 2.0 \n' + b'report_pandas{a="3.3" ,b="7.3" } 3.0 \n' + b'report_pandas{a="4.4" ,b="8.4" } 4.0 \n' b'# HELP report_panda2s metric description2\n' b'# TYPE report_panda2s gauge\n' - b'report_panda2s(c="1.1" ,d="5.1" ) 5 \n' - b'report_panda2s(c="2.2" ,d="6.2" ) 6 \n' - b'report_panda2s(c="3.3" ,d="7.3" ) 7 \n' - b'report_panda2s(c="4.4" ,d="8.4" ) 8 \n', + b'report_panda2s{c="1.1" ,d="5.1" } 5 \n' + b'report_panda2s{c="2.2" ,d="6.2" } 6 \n' + b'report_panda2s{c="3.3" ,d="7.3" } 7 \n' + b'report_panda2s{c="4.4" ,d="8.4" } 8 \n', generate_latest(self.registry) ) @@ -261,16 +260,16 @@ def test_gauge_pandas_columns(self): self.assertEqual( b'# HELP report_pandas metric description\n' b'# TYPE report_pandas gauge\n' - b'report_pandas(a="1.1" ) 1.0 \n' - b'report_pandas(a="2.2" ) 2.0 \n' - b'report_pandas(a="3.3" ) 3.0 \n' - b'report_pandas(a="4.4" ) 4.0 \n' + b'report_pandas{a="1.1" } 1.0 \n' + b'report_pandas{a="2.2" } 2.0 \n' + b'report_pandas{a="3.3" } 3.0 \n' + b'report_pandas{a="4.4" } 4.0 \n' b'# HELP report_panda2s metric description2\n' b'# TYPE report_panda2s gauge\n' - b'report_panda2s(d="5.1" ) 5.0 \n' - b'report_panda2s(d="6.2" ) 6.0 \n' - b'report_panda2s(d="7.3" ) 7.0 \n' - b'report_panda2s(d="8.4" ) 8.0 \n', + b'report_panda2s{d="5.1" } 5.0 \n' + b'report_panda2s{d="6.2" } 6.0 \n' + b'report_panda2s{d="7.3" } 7.0 \n' + b'report_panda2s{d="8.4" } 8.0 \n', generate_latest(self.registry) )