-
Notifications
You must be signed in to change notification settings - Fork 53
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Extend PyFunc API to allow adding custom metrics to exporter (#574)
* #567 extend PyFunc with custom metrics * #567 add an example of PyFunc with custom metrics * #567 add docs for PyFunc metrics * #567 move MetricsRegistry to a separate module
- Loading branch information
Showing
15 changed files
with
416 additions
and
83 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,4 +17,5 @@ API Reference | |
param_storage | ||
utils | ||
libs | ||
metrics | ||
client |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
Metrics | ||
======= | ||
|
||
.. currentmodule:: savant.metrics | ||
|
||
.. autosummary:: | ||
:toctree: generated | ||
:nosignatures: | ||
:template: autosummary/class.rst | ||
|
||
Counter | ||
Gauge |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
savant-rs==0.1.83 | ||
savant-rs==0.1.84 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
65 changes: 65 additions & 0 deletions
65
samples/pass_through_processing/py_func_metrics_example.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
"""Example of how to use metrics in PyFunc.""" | ||
from savant.deepstream.meta.frame import NvDsFrameMeta | ||
from savant.deepstream.pyfunc import NvDsPyFuncPlugin | ||
from savant.gstreamer import Gst | ||
from savant.metrics import Counter, Gauge | ||
|
||
|
||
class PyFuncMetricsExample(NvDsPyFuncPlugin): | ||
"""Example of how to use metrics in PyFunc. | ||
Metrics values example: | ||
.. code-block:: text | ||
# HELP frames_per_source_total Number of processed frames per source | ||
# TYPE frames_per_source_total counter | ||
frames_per_source_total{module_stage="tracker",source_id="city-traffic"} 748.0 1700803467794 | ||
# HELP total_queue_length The total queue length for the pipeline | ||
# TYPE total_queue_length gauge | ||
total_queue_length{module_stage="tracker",source_id="city-traffic"} 36.0 1700803467794 | ||
Note: the "module_stage" label is configured in docker-compose file and added to all metrics. | ||
""" | ||
|
||
# Called when the new source is added | ||
def on_source_add(self, source_id: str): | ||
# Check if the metric is not registered yet | ||
if 'frames_per_source' not in self.metrics: | ||
# Register the counter metric | ||
self.metrics['frames_per_source'] = Counter( | ||
name='frames_per_source', | ||
description='Number of processed frames per source', | ||
# Labels are optional, by default there are no labels | ||
labelnames=('source_id',), | ||
) | ||
self.logger.info('Registered metric: %s', 'frames_per_source') | ||
if 'total_queue_length' not in self.metrics: | ||
# Register the gauge metric | ||
self.metrics['total_queue_length'] = Gauge( | ||
name='total_queue_length', | ||
description='The total queue length for the pipeline', | ||
# There are no labels for this metric | ||
) | ||
self.logger.info('Registered metric: %s', 'total_queue_length') | ||
|
||
def process_frame(self, buffer: Gst.Buffer, frame_meta: NvDsFrameMeta): | ||
# Count the frame for this source | ||
self.metrics['frames_per_source'].inc( | ||
# 1, # Default increment value | ||
# Labels should be a tuple and must match the labelnames | ||
labels=(frame_meta.source_id,), | ||
) | ||
try: | ||
last_runtime_metric = self.get_runtime_metrics(1)[0] | ||
queue_length = sum( | ||
stage.queue_length for stage in last_runtime_metric.stage_stats | ||
) | ||
except IndexError: | ||
queue_length = 0 | ||
|
||
# Set the total queue length for this source | ||
self.metrics['total_queue_length'].set( | ||
queue_length, # The new gauge value | ||
# There are no labels for this metric | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
import time | ||
from typing import Dict, Optional, Tuple | ||
|
||
|
||
class Metric: | ||
"""Base class for metrics. | ||
:param name: Metric name. | ||
:param description: Metric description. | ||
:param labelnames: Metric label names. | ||
""" | ||
|
||
def __init__( | ||
self, | ||
name: str, | ||
description: str = '', | ||
labelnames: Tuple[str, ...] = (), | ||
): | ||
self._name = name | ||
self._description = description or name | ||
self._labelnames = labelnames | ||
self._values: Dict[Tuple[str, ...], Tuple[float, float]] = {} | ||
|
||
@property | ||
def name(self) -> str: | ||
"""Metric name.""" | ||
return self._name | ||
|
||
@property | ||
def description(self) -> str: | ||
"""Metric description.""" | ||
return self._description | ||
|
||
@property | ||
def labelnames(self) -> Tuple[str, ...]: | ||
"""Metric label names.""" | ||
return self._labelnames | ||
|
||
@property | ||
def values(self) -> Dict[Tuple[str, ...], Tuple[float, float]]: | ||
"""Metric values. | ||
:return: Dictionary: labels -> (value, timestamp). | ||
""" | ||
return self._values | ||
|
||
|
||
class Counter(Metric): | ||
"""Counter metric. | ||
Usage example: | ||
.. code-block:: python | ||
counter = Counter( | ||
name='frames_per_source', | ||
description='Number of processed frames per source', | ||
labelnames=('source_id',), | ||
) | ||
counter.inc(labels=('camera-1',)) | ||
""" | ||
|
||
def inc( | ||
self, | ||
amount=1, | ||
labels: Tuple[str, ...] = (), | ||
timestamp: Optional[float] = None, | ||
): | ||
"""Increment counter by amount. | ||
:param amount: Increment amount. | ||
:param labels: Labels values. | ||
:param timestamp: Metric timestamp. | ||
""" | ||
|
||
assert len(labels) == len(self._labelnames), 'Labels must match label names' | ||
assert amount > 0, 'Counter increment amount must be positive' | ||
last_value = self._values.get(labels, (0, 0))[0] | ||
if timestamp is None: | ||
timestamp = time.time() | ||
self._values[labels] = last_value + amount, timestamp | ||
|
||
def set( | ||
self, | ||
value, | ||
labels: Tuple[str, ...] = (), | ||
timestamp: Optional[float] = None, | ||
): | ||
"""Set counter to specific value. | ||
:param value: Counter value. Must be non-decreasing. | ||
:param labels: Labels values. | ||
:param timestamp: Metric timestamp. | ||
""" | ||
|
||
assert len(labels) == len(self._labelnames), 'Labels must match label names' | ||
last_value = self._values.get(labels, (0, 0))[0] | ||
assert value >= last_value, 'Counter value must be non-decreasing' | ||
if timestamp is None: | ||
timestamp = time.time() | ||
self._values[labels] = value, timestamp | ||
|
||
|
||
class Gauge(Metric): | ||
"""Gauge metric. | ||
Usage example: | ||
.. code-block:: python | ||
gauge = Gauge( | ||
name='total_queue_length', | ||
description='The total queue length for the pipeline', | ||
) | ||
gauge.set(123) | ||
""" | ||
|
||
def set( | ||
self, | ||
value, | ||
labels: Tuple[str, ...] = (), | ||
timestamp: Optional[float] = None, | ||
): | ||
"""Set gauge to specific value. | ||
:param value: Gauge value. | ||
:param labels: Labels values. | ||
:param timestamp: Metric timestamp. | ||
""" | ||
|
||
assert len(labels) == len(self._labelnames), 'Labels must match label names' | ||
if timestamp is None: | ||
timestamp = time.time() | ||
self._values[labels] = value, timestamp |
Oops, something went wrong.