Skip to content

Commit 7f48d7f

Browse files
committed
feat(Bot): add on_metric value threshold task handler
1 parent 24b7443 commit 7f48d7f

File tree

3 files changed

+74
-3
lines changed

3 files changed

+74
-3
lines changed

bots/example.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,11 +107,17 @@ async def exec_event2(log: ContractLog):
107107

108108
# You can run cron jobs in your apps
109109
# (useful for functions that execute at a regular time period e.g. metrics)
110-
@bot.cron("*/1 * * * *")
110+
@bot.cron("* * * * *")
111111
def sample_metric(time: datetime):
112112
return random.random()
113113

114114

115+
# You can trigger tasks when metrics returned by other tasks exceed a threshold
116+
@bot.on_metric("sample_metric", gt=0.5)
117+
def metric_too_high(value: float):
118+
raise ValueError("The metric is too damn high!")
119+
120+
115121
@bot.cron("*/5 * * * *")
116122
# NOTE: You can have multiple handlers for any trigger we support
117123
def stop_running_now(time):

silverback/main.py

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from .exceptions import ContainerTypeMismatchError, InvalidContainerTypeError, NoSignerLoaded
2424
from .settings import Settings
2525
from .state import StateSnapshot
26-
from .types import SilverbackID, TaskType
26+
from .types import ScalarType, SilverbackID, TaskType
2727
from .utils import encode_topics_to_string, parse_hexbytes_dict
2828

2929

@@ -377,8 +377,10 @@ def broker_task_decorator(
377377
self,
378378
task_type: TaskType,
379379
container: BlockContainer | ContractEvent | ContractEventWrapper | None = None,
380-
cron_schedule: str | None = None,
381380
filter_args: dict[str, Any] | None = None,
381+
cron_schedule: str | None = None,
382+
metric_name: str | None = None,
383+
value_threshold: dict[str, ScalarType] | None = None,
382384
) -> Callable[[Callable], AsyncTaskiqDecoratedTask]:
383385
"""
384386
Dynamically create a new broker task that handles tasks of ``task_type``.
@@ -483,6 +485,16 @@ def add_taskiq_task(
483485

484486
labels["cron"] = cron_schedule
485487

488+
elif task_type is TaskType.METRIC_VALUE:
489+
# NOTE: This shouldn't happen to users
490+
assert metric_name, "Must supply `metric_name=`."
491+
labels["metric"] = metric_name
492+
493+
if value_threshold:
494+
labels.update(
495+
{f"value:{lbl}": str(val) for lbl, val in value_threshold.items()}
496+
)
497+
486498
self.tasks[task_type].append(TaskData(name=handler.__name__, labels=labels))
487499

488500
if self.use_fork:
@@ -633,3 +645,55 @@ def cron(self, cron_schedule: str) -> Callable:
633645
cron_schedule (str): A cron-like schedule string.
634646
"""
635647
return self.broker_task_decorator(TaskType.CRON_JOB, cron_schedule=cron_schedule)
648+
649+
def on_metric(
650+
self,
651+
metric_name: str,
652+
ge: ScalarType | None = None,
653+
gt: ScalarType | None = None,
654+
le: ScalarType | None = None,
655+
lt: ScalarType | None = None,
656+
eq: ScalarType | None = None,
657+
ne: ScalarType | None = None,
658+
# TODO: Support `rate_[ge|gt|le|...]` too?
659+
) -> Callable:
660+
"""
661+
Create a task that runs when the value of a specified metric has tripped a threshold.
662+
663+
```{notice}
664+
If no keyword args provided to this decorator, it will trigger on every update of metric.
665+
```
666+
667+
```{notice}
668+
Multiple keyword args provided to this decorator means logical AND of all of them.
669+
```
670+
671+
Args:
672+
metric_name (str): The name of the metric to monitor for threshold exceedence.
673+
ge (ScalarType | None): trigger when metric value is greater than or equal to value.
674+
gt (ScalarType | None): trigger when metric value is greater than value.
675+
le (ScalarType | None): trigger when metric value is less than or equal to value.
676+
lt (ScalarType | None): trigger when metric value is less than value.
677+
eq (ScalarType | None): trigger when metric value is equal to value.
678+
ne (ScalarType | None): trigger when metric value is not equal to value.
679+
"""
680+
value_threshold: dict[str, ScalarType] = {}
681+
if ge is not None:
682+
value_threshold["ge"] = ge
683+
if gt is not None:
684+
value_threshold["gt"] = gt
685+
if le is not None:
686+
value_threshold["le"] = le
687+
if lt is not None:
688+
value_threshold["lt"] = lt
689+
if eq is not None:
690+
value_threshold["eq"] = eq
691+
if ne is not None:
692+
value_threshold["ne"] = ne
693+
694+
return self.broker_task_decorator(
695+
TaskType.METRIC_VALUE,
696+
metric_name=metric_name,
697+
# NOTE: When empty, allow all values
698+
value_threshold=value_threshold or None,
699+
)

silverback/types.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ class TaskType(str, Enum):
2525
CRON_JOB = "user:cron-job"
2626
NEW_BLOCK = "user:new-block"
2727
EVENT_LOG = "user:event-log"
28+
METRIC_VALUE = "user:metric-value"
2829
SHUTDOWN = "user:shutdown"
2930

3031
def __str__(self) -> str:

0 commit comments

Comments
 (0)