-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix client-side metrics processing (#200)
* Add more robust client-side metrics helper * Improve client-side metrics processing
- Loading branch information
Showing
6 changed files
with
115 additions
and
33 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
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,46 @@ | ||
from datetime import datetime | ||
from collections import deque | ||
from typing import Deque, Tuple, TypeVar, Generic | ||
|
||
T = TypeVar("T", int, float) | ||
|
||
|
||
class StreamingMetric(Generic[T]): | ||
""" | ||
A simple wrapper around a time-series metric. The assumption is that this | ||
metric is updated at some regular frequency. | ||
""" | ||
|
||
def __init__(self, window_size: int = 10) -> None: | ||
self._metric_data: Deque[Tuple[T, datetime]] = deque() | ||
self._window_size = window_size | ||
|
||
def add_sample(self, value: T, timestamp: datetime) -> None: | ||
self._metric_data.append((value, timestamp)) | ||
self._trim_metric_data() | ||
|
||
def average_since(self, timestamp: datetime) -> float: | ||
if len(self._metric_data) == 0: | ||
return 0.0 | ||
|
||
# Assumption is that `metric_data` is sorted in ascending timestamp order. | ||
total = None | ||
num_samples = 0 | ||
for value, val_timestamp in reversed(self._metric_data): | ||
if total is None: | ||
total = value | ||
else: | ||
total += value | ||
num_samples += 1 | ||
|
||
if val_timestamp <= timestamp: | ||
# We want to add the first value with a timestamp less than or | ||
# equal to the given timestamp. | ||
break | ||
|
||
assert total is not None | ||
return total / num_samples | ||
|
||
def _trim_metric_data(self) -> None: | ||
while len(self._metric_data) > self._window_size: | ||
self._metric_data.popleft() |
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,46 @@ | ||
from datetime import datetime, timedelta | ||
from typing import List, Tuple | ||
|
||
from brad.utils.streaming_metric import StreamingMetric | ||
|
||
|
||
def get_value_stream(start: datetime) -> List[Tuple[float, datetime]]: | ||
return [ | ||
(3.0, start), | ||
(10.0, start + timedelta(seconds=30)), | ||
(20.0, start + timedelta(seconds=60)), | ||
] | ||
|
||
|
||
def test_empty(): | ||
start = datetime(year=2023, month=7, day=19) | ||
sm = StreamingMetric[float]() | ||
val = sm.average_since(start) | ||
assert val == 0.0 | ||
|
||
|
||
def test_multiple(): | ||
start = datetime(year=2023, month=7, day=19) | ||
sm = StreamingMetric[float]() | ||
for val, ts in get_value_stream(start): | ||
sm.add_sample(val, ts) | ||
val = sm.average_since(start + timedelta(seconds=45)) | ||
# Average of 10.0 and 20.0 | ||
assert val == 15.0 | ||
|
||
|
||
def test_all(): | ||
start = datetime(year=2023, month=7, day=19) | ||
sm = StreamingMetric[float]() | ||
for val, ts in get_value_stream(start): | ||
sm.add_sample(val, ts) | ||
|
||
val = sm.average_since(start) | ||
# Average of 3.0, 10.0, and 20.0 | ||
assert val == 11.0 | ||
|
||
val = sm.average_since(start - timedelta(days=1)) | ||
assert val == 11.0 | ||
|
||
val = sm.average_since(start + timedelta(seconds=10)) | ||
assert val == 11.0 |