Hey @jessgarson thanks for all the help !! & apologies for a delayed response.
I tried the gauge
and counter
they are working and are correctly reflecting values on UI, but for my use case here I won't be able to use either of them as counter
would just keep a single value which is the current count which can be incremented or decremented,so I would not be able to keep multiple values/entries like for HTTP Code or processing times
& the same goes for gauge
which can be set to a certain value & when updated that values gets updated to a new value. gauge
can be useful for a metric like health status (viz Good & Bad) & counter
would be useful for showing the current count of request processed or similar.
Let me illustrate with an example for HTTP Code (would be similar for processing time)
@app.get("/health")
async def health_check():
choices = [400, 200, 500, 512]
random_value = random.choice(choices)
# Create a metric
metricset.counter("test_gauge").val = random_value
return {"status": "ok"}
Now here if this API endpoint is hit 5 times in 30s
& random_value generated are in the order [200,500,200,400,512] and if the metrics_interval
(whenever metrics are collected/sent) is set to 30s
, then only the last value is sent to the server i.e. 512
.
class Gauge(BaseMetric):
__slots__ = BaseMetric.__slots__ + ("_val",)
def __init__(self, name, reset_on_collect=False, unit=None) -> None:
"""
Creates a new gauge
:param name: label of the gauge
:param unit of the observed gauge. Unused for gauges
"""
self._val = None
super(Gauge, self).__init__(name, reset_on_collect=reset_on_collect)
@property
def val(self):
return self._val
@val.setter
def val(self, value) -> None:
self._val = value
def reset(self) -> None:
self._val = 0
class Counter(BaseMetric):
__slots__ = BaseMetric.__slots__ + ("_lock", "_initial_value", "_val")
def __init__(self, name, initial_value=0, reset_on_collect=False, unit=None) -> None:
"""
Creates a new counter
:param name: name of the counter
:param initial_value: initial value of the counter, defaults to 0
:param unit: unit of the observed counter. Unused for counters
"""
self._lock = threading.Lock()
self._val = self._initial_value = initial_value
super(Counter, self).__init__(name, reset_on_collect=reset_on_collect)
def inc(self, delta=1):
"""
Increments the counter. If no delta is provided, it is incremented by one
:param delta: the amount to increment the counter by
:returns the counter itself
"""
with self._lock:
self._val += delta
return self
def dec(self, delta=1):
"""
Decrements the counter. If no delta is provided, it is decremented by one
:param delta: the amount to decrement the counter by
:returns the counter itself
"""
with self._lock:
self._val -= delta
return self
def reset(self):
"""
Reset the counter to the initial value
:returns the counter itself
"""
with self._lock:
self._val = self._initial_value
return self
@property
def val(self):
"""Returns the current value of the counter"""
return self._val
@val.setter
def val(self, value) -> None:
with self._lock:
self._val = value
Checking the source code of gauge
and counter
helps me understand that both utilize a
a variable with maybe int
or float
type(maybe since type is not declared).
After looking at the source code of all available metrics, I think none of them can be utilized to store and send list-like (timeseries/mulitple values, like this [200,500,200,400,512] mentioned in above example ) values. histogram
has self._counts
which is of type list, but it saves frequency and not the actual value itself.
I was hoping that I could extend the BaseMetric
class to create a metric which stores values in a list or dict like datastructure to be able to hold time-series like data. Would that be possible ? Also If this is something that would be helpful to the community I can also open a PR in apm-agent-python to a add new metric type. Would you be able ask someone from apm-agent-python team internally, if you have access, about this ?
Looking forward to your response .