Skip to content

Commit 0fecc6e

Browse files
authored
added custom metric sample (temporalio#177)
1 parent 1b6145a commit 0fecc6e

File tree

11 files changed

+200
-0
lines changed

11 files changed

+200
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
.venv
22
.idea
33
__pycache__
4+
.vscode
5+
.DS_Store

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ Some examples require extra dependencies. See each sample's directory for specif
6161
* [context_propagation](context_propagation) - Context propagation through workflows/activities via interceptor.
6262
* [custom_converter](custom_converter) - Use a custom payload converter to handle custom types.
6363
* [custom_decorator](custom_decorator) - Custom decorator to auto-heartbeat a long-running activity.
64+
* [custom_metric](custom_metric) - Custom metric to record the workflow type in the activity schedule to start latency.
6465
* [dsl](dsl) - DSL workflow that executes steps defined in a YAML file.
6566
* [encryption](encryption) - Apply end-to-end encryption for all input/output.
6667
* [gevent_async](gevent_async) - Combine gevent and Temporal.

custom_metric/README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Custom Metric
2+
3+
This sample deminstrates two things: (1) how to make a custom metric, and (2) how to use an interceptor.
4+
The custom metric in this sample is an activity schedule-to-start-latency with a workflow type tag.
5+
6+
Please see the top-level [README](../README.md) for prerequisites such as Python, uv, starting the local temporal development server, etc.
7+
8+
1. Run the worker with `uv run custom_metric/worker.py`
9+
2. Request execution of the workflow with `uv run custom_metric/starter.py`
10+
3. Go to `http://127.0.0.1:9090/metrics` in your browser
11+
12+
You'll get something like the following:
13+
14+
```txt
15+
custom_activity_schedule_to_start_latency_bucket{activity_type="print_and_sleep",namespace="default",service_name="temporal-core-sdk",task_queue="custom-metric-task-queue",workflow_type="StartTwoActivitiesWorkflow",le="100"} 1
16+
custom_activity_schedule_to_start_latency_bucket{activity_type="print_and_sleep",namespace="default",service_name="temporal-core-sdk",task_queue="custom-metric-task-queue",workflow_type="StartTwoActivitiesWorkflow",le="500"} 1
17+
custom_activity_schedule_to_start_latency_bucket{activity_type="print_and_sleep",namespace="default",service_name="temporal-core-sdk",task_queue="custom-metric-task-queue",workflow_type="StartTwoActivitiesWorkflow",le="1000"} 1
18+
custom_activity_schedule_to_start_latency_bucket{activity_type="print_and_sleep",namespace="default",service_name="temporal-core-sdk",task_queue="custom-metric-task-queue",workflow_type="StartTwoActivitiesWorkflow",le="5000"} 2
19+
custom_activity_schedule_to_start_latency_bucket{activity_type="print_and_sleep",namespace="default",service_name="temporal-core-sdk",task_queue="custom-metric-task-queue",workflow_type="StartTwoActivitiesWorkflow",le="10000"} 2
20+
custom_activity_schedule_to_start_latency_bucket{activity_type="print_and_sleep",namespace="default",service_name="temporal-core-sdk",task_queue="custom-metric-task-queue",workflow_type="StartTwoActivitiesWorkflow",le="100000"} 2
21+
custom_activity_schedule_to_start_latency_bucket{activity_type="print_and_sleep",namespace="default",service_name="temporal-core-sdk",task_queue="custom-metric-task-queue",workflow_type="StartTwoActivitiesWorkflow",le="1000000"} 2
22+
custom_activity_schedule_to_start_latency_bucket{activity_type="print_and_sleep",namespace="default",service_name="temporal-core-sdk",task_queue="custom-metric-task-queue",workflow_type="StartTwoActivitiesWorkflow",le="+Inf"} 2
23+
custom_activity_schedule_to_start_latency_sum{activity_type="print_and_sleep",namespace="default",service_name="temporal-core-sdk",task_queue="custom-metric-task-queue",workflow_type="StartTwoActivitiesWorkflow"} 1010
24+
custom_activity_schedule_to_start_latency_count{activity_type="print_and_sleep",namespace="default",service_name="temporal-core-sdk",task_queue="custom-metric-task-queue",workflow_type="StartTwoActivitiesWorkflow"} 2
25+
...
26+
temporal_activity_schedule_to_start_latency_bucket{namespace="default",service_name="temporal-core-sdk",task_queue="custom-metric-task-queue",le="100"} 1
27+
temporal_activity_schedule_to_start_latency_bucket{namespace="default",service_name="temporal-core-sdk",task_queue="custom-metric-task-queue",le="500"} 1
28+
temporal_activity_schedule_to_start_latency_bucket{namespace="default",service_name="temporal-core-sdk",task_queue="custom-metric-task-queue",le="1000"} 1
29+
temporal_activity_schedule_to_start_latency_bucket{namespace="default",service_name="temporal-core-sdk",task_queue="custom-metric-task-queue",le="5000"} 2
30+
temporal_activity_schedule_to_start_latency_bucket{namespace="default",service_name="temporal-core-sdk",task_queue="custom-metric-task-queue",le="10000"} 2
31+
temporal_activity_schedule_to_start_latency_bucket{namespace="default",service_name="temporal-core-sdk",task_queue="custom-metric-task-queue",le="100000"} 2
32+
temporal_activity_schedule_to_start_latency_bucket{namespace="default",service_name="temporal-core-sdk",task_queue="custom-metric-task-queue",le="1000000"} 2
33+
temporal_activity_schedule_to_start_latency_bucket{namespace="default",service_name="temporal-core-sdk",task_queue="custom-metric-task-queue",le="+Inf"} 2
34+
temporal_activity_schedule_to_start_latency_sum{namespace="default",service_name="temporal-core-sdk",task_queue="custom-metric-task-queue"} 1010
35+
temporal_activity_schedule_to_start_latency_count{namespace="default",service_name="temporal-core-sdk",task_queue="custom-metric-task-queue"} 2
36+
```

custom_metric/__init__.py

Whitespace-only changes.

custom_metric/activity.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import time
2+
3+
from temporalio import activity
4+
5+
6+
@activity.defn
7+
def print_and_sleep():
8+
print("In the activity.")
9+
time.sleep(1)

custom_metric/starter.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import asyncio
2+
import uuid
3+
4+
from temporalio.client import Client
5+
6+
from custom_metric.workflow import StartTwoActivitiesWorkflow
7+
8+
9+
async def main():
10+
11+
client = await Client.connect(
12+
"localhost:7233",
13+
)
14+
15+
await client.start_workflow(
16+
StartTwoActivitiesWorkflow.run,
17+
id="execute-activity-workflow-" + str(uuid.uuid4()),
18+
task_queue="custom-metric-task-queue",
19+
)
20+
21+
22+
if __name__ == "__main__":
23+
asyncio.run(main())

custom_metric/worker.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import asyncio
2+
from concurrent.futures import ThreadPoolExecutor
3+
4+
from temporalio import activity
5+
from temporalio.client import Client
6+
from temporalio.runtime import PrometheusConfig, Runtime, TelemetryConfig
7+
from temporalio.worker import (
8+
ActivityInboundInterceptor,
9+
ExecuteActivityInput,
10+
Interceptor,
11+
Worker,
12+
)
13+
14+
from custom_metric.activity import print_and_sleep
15+
from custom_metric.workflow import StartTwoActivitiesWorkflow
16+
17+
18+
class SimpleWorkerInterceptor(Interceptor):
19+
def intercept_activity(
20+
self, next: ActivityInboundInterceptor
21+
) -> ActivityInboundInterceptor:
22+
return CustomScheduleToStartInterceptor(next)
23+
24+
25+
class CustomScheduleToStartInterceptor(ActivityInboundInterceptor):
26+
async def execute_activity(self, input: ExecuteActivityInput):
27+
28+
schedule_to_start = (
29+
activity.info().started_time
30+
- activity.info().current_attempt_scheduled_time
31+
)
32+
# Could do the original schedule time instead of current attempt
33+
# schedule_to_start_second_option = activity.info().started_time - activity.info().scheduled_time
34+
35+
meter = activity.metric_meter()
36+
histogram = meter.create_histogram_timedelta(
37+
"custom_activity_schedule_to_start_latency",
38+
description="Time between activity scheduling and start",
39+
unit="duration",
40+
)
41+
histogram.record(
42+
schedule_to_start, {"workflow_type": activity.info().workflow_type}
43+
)
44+
return await self.next.execute_activity(input)
45+
46+
47+
async def main():
48+
runtime = Runtime(
49+
telemetry=TelemetryConfig(metrics=PrometheusConfig(bind_address="0.0.0.0:9090"))
50+
)
51+
client = await Client.connect(
52+
"localhost:7233",
53+
runtime=runtime,
54+
)
55+
worker = Worker(
56+
client,
57+
task_queue="custom-metric-task-queue",
58+
interceptors=[SimpleWorkerInterceptor()],
59+
workflows=[StartTwoActivitiesWorkflow],
60+
activities=[print_and_sleep],
61+
# only one activity executor with two concurrently scheduled activities
62+
# to force a nontrivial schedule to start times
63+
activity_executor=ThreadPoolExecutor(1),
64+
max_concurrent_activities=1,
65+
)
66+
67+
await worker.run()
68+
69+
70+
if __name__ == "__main__":
71+
asyncio.run(main())

custom_metric/workflow.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import asyncio
2+
from datetime import timedelta
3+
4+
from temporalio import workflow
5+
6+
with workflow.unsafe.imports_passed_through():
7+
from custom_metric.activity import print_and_sleep
8+
9+
10+
@workflow.defn
11+
class StartTwoActivitiesWorkflow:
12+
@workflow.run
13+
async def run(self):
14+
# Request two concurrent activities with only one task slot so
15+
# we can see nontrivial schedule to start times.
16+
activity1 = workflow.execute_activity(
17+
print_and_sleep,
18+
start_to_close_timeout=timedelta(seconds=5),
19+
)
20+
activity2 = workflow.execute_activity(
21+
print_and_sleep,
22+
start_to_close_timeout=timedelta(seconds=5),
23+
)
24+
await asyncio.gather(activity1, activity2)
25+
return None

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ packages = [
8686
"context_propagation",
8787
"custom_converter",
8888
"custom_decorator",
89+
"custom_metric",
8990
"dsl",
9091
"encryption",
9192
"gevent_async",

tests/custom_metric/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)