Skip to content

Commit 5c4c41e

Browse files
Add Temporal Cloud OpenMetrics Prometheus+Grafana demo
1 parent 8c31358 commit 5c4c41e

File tree

16 files changed

+7160
-0
lines changed

16 files changed

+7160
-0
lines changed
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# Temporal Cloud OpenMetrics → Prometheus → Grafana (Step-by-step)
2+
3+
This demo shows how to **scrape Temporal Cloud OpenMetrics(https://docs.temporal.io/cloud/metrics/openmetrics/)**
4+
with **Prometheus** and **visualize them in Grafana**.
5+
6+
It uses the Grafana Temporal mixin dashboard template:
7+
https://github.com/grafana/jsonnet-libs/blob/master/temporal-mixin/dashboards/temporal-overview.json
8+
9+
Once imported/provisioned, the dashboard lets you view the key Temporal metrics in a ready-made layout.
10+
11+
Grafana dashboard view :-
12+
![Grafana dashboard 1](docs/images/img1.png)
13+
![Grafana dashboard 2](docs/images/img2.png)
14+
15+
Prometheus -
16+
17+
![Prometheus](docs/images/img3.png)
18+
19+
---
20+
21+
## 1) Create Service Account + API Key (Temporal Cloud)
22+
23+
OpenMetrics auth reference:
24+
https://docs.temporal.io/production-deployment/cloud/metrics/openmetrics/api-reference#authentication
25+
26+
In Temporal Cloud UI:
27+
- **Settings → Service Accounts**
28+
- Create a service account with **Metrics Read-Only** role
29+
- Generate an **API key** ( copy this, it will be needed later)
30+
---
31+
32+
33+
## 2) Create the `secrets/` folder + API key file
34+
35+
From the repo root (same folder as `docker-compose.yml`), run:
36+
37+
```
38+
cd temporalcloudopenmetrics
39+
mkdir -p secrets
40+
echo "put your CLOUD API KEY HERE" > secrets/temporal_cloud_api_key
41+
```
42+
now the folder will look like below and temporal_cloud_api_key will have the above api key that we generated in step 1
43+
44+
```
45+
temporalcloudopenmetrics/
46+
├── docker-compose.yml
47+
├── prometheus/
48+
├── grafana/
49+
├── secrets/
50+
│ └── temporal_cloud_api_key
51+
```
52+
53+
## 3) Configure TemporalConnection.java
54+
55+
Edit `TemporalConnection.java` and set your defaults:
56+
57+
```
58+
public static final String NAMESPACE = env("TEMPORAL_NAMESPACE", "<namespace>.<account-id>");
59+
public static final String ADDRESS = env("TEMPORAL_ADDRESS", "<namespace>.<account-id>.tmprl.cloud:7233");
60+
public static final String CERT = env("TEMPORAL_CERT", "/path/to/client.pem");
61+
public static final String KEY = env("TEMPORAL_KEY", "/path/to/client.key");
62+
public static final String TASK_QUEUE = env("TASK_QUEUE", "openmetrics-task-queue");
63+
public static final int WORKER_SECONDS = envInt("WORKER_SECONDS", 60);
64+
```
65+
66+
## 4) 4) Update Prometheus scrape config
67+
68+
prometheus/config.yml
69+
Update it to use your namespace
70+
```
71+
params:
72+
namespaces: [ '<namespace>.<account-id>' ]
73+
```
74+
75+
76+
## 5) Start Prometheus + Grafana
77+
78+
docker compose up -d
79+
docker compose ps
80+
81+
82+
## 6) View Grafana dashboard
83+
84+
http://localhost:3001/
85+
86+
- Username: admin
87+
- Password: admin
88+
89+
You should see the Temporal Cloud OpenMetrics dashboard.
90+
91+
## 7) Verify metrics in Prometheus
92+
93+
Prometheus: http://localhost:9093/
94+
95+
Go to:
96+
Status → Targets (make sure the scrape target is UP)
97+
Graph tab (search for Temporal metrics and run a query)
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package io.temporal.samples.temporalcloudopenmetrics;
2+
3+
import io.temporal.client.WorkflowClient;
4+
import io.temporal.client.WorkflowOptions;
5+
import io.temporal.client.WorkflowStub;
6+
import io.temporal.samples.temporalcloudopenmetrics.workflows.ScenarioWorkflow;
7+
import java.time.Duration;
8+
import java.util.UUID;
9+
10+
public class Starter {
11+
12+
public static void main(String[] args) throws Exception {
13+
WorkflowClient client = TemporalConnection.client();
14+
15+
String name = "Temporal";
16+
String[] scenarios = {"success", "fail", "timeout", "continue", "cancel"};
17+
18+
for (String scenario : scenarios) {
19+
String wid = "scenario-" + scenario + "-" + UUID.randomUUID();
20+
21+
WorkflowOptions.Builder optionsBuilder =
22+
WorkflowOptions.newBuilder()
23+
.setTaskQueue(TemporalConnection.TASK_QUEUE)
24+
.setWorkflowId(wid);
25+
26+
// workflow timeout
27+
if ("timeout".equalsIgnoreCase(scenario)) {
28+
optionsBuilder.setWorkflowRunTimeout(Duration.ofSeconds(3));
29+
}
30+
31+
ScenarioWorkflow wf = client.newWorkflowStub(ScenarioWorkflow.class, optionsBuilder.build());
32+
33+
System.out.println("\n=== Starting scenario: " + scenario + " workflowId=" + wid + " ===");
34+
35+
try {
36+
if ("cancel".equalsIgnoreCase(scenario)) {
37+
WorkflowClient.start(wf::run, scenario, name);
38+
Thread.sleep(2000);
39+
WorkflowStub untyped = client.newUntypedWorkflowStub(wid);
40+
untyped.cancel();
41+
untyped.getResult(String.class);
42+
continue;
43+
}
44+
45+
// normal synchronous execution
46+
String result = wf.run(scenario, name);
47+
System.out.println("Scenario=" + scenario + " Result=" + result);
48+
49+
} catch (Exception e) {
50+
System.out.println("Scenario=" + scenario + " ended: " + e.getClass().getSimpleName() + " - " + e.getMessage());
51+
}
52+
}
53+
}
54+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package io.temporal.samples.temporalcloudopenmetrics;
2+
3+
import io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts;
4+
import io.grpc.netty.shaded.io.netty.handler.ssl.SslContext;
5+
import io.grpc.netty.shaded.io.netty.handler.ssl.SslContextBuilder;
6+
import io.temporal.client.WorkflowClient;
7+
import io.temporal.client.WorkflowClientOptions;
8+
import io.temporal.serviceclient.WorkflowServiceStubs;
9+
import io.temporal.serviceclient.WorkflowServiceStubsOptions;
10+
import java.io.FileInputStream;
11+
import java.io.InputStream;
12+
13+
public final class TemporalConnection {
14+
private TemporalConnection() {}
15+
16+
// add your namespace here
17+
public static final String NAMESPACE = env("TEMPORAL_NAMESPACE", "deepika-test-namespace.a2dd6");
18+
19+
public static final String ADDRESS =
20+
env("TEMPORAL_ADDRESS", "deepika-test-namespace.a2dd6.tmprl.cloud:7233");
21+
22+
public static final String CERT =
23+
env("TEMPORAL_CERT", "/Users/deepikaawasthi/temporal/temporal-certs/client.pem");
24+
25+
public static final String KEY =
26+
env("TEMPORAL_KEY", "/Users/deepikaawasthi/temporal/temporal-certs/client.key");
27+
28+
public static final String TASK_QUEUE = env("TASK_QUEUE", "openmetrics-task-queue");
29+
30+
// default 60s; override with WORKER_SECONDS env var
31+
public static final int WORKER_SECONDS = envInt("WORKER_SECONDS", 60);
32+
33+
public static WorkflowClient client() {
34+
WorkflowServiceStubs service = serviceStubs();
35+
return WorkflowClient.newInstance(
36+
service, WorkflowClientOptions.newBuilder().setNamespace(NAMESPACE).build());
37+
}
38+
39+
// this will create servicestubs and which will be used by both workermain and starter
40+
private static WorkflowServiceStubs serviceStubs() {
41+
try (InputStream clientCert = new FileInputStream(CERT);
42+
InputStream clientKey = new FileInputStream(KEY)) {
43+
44+
SslContext sslContext =
45+
GrpcSslContexts.configure(SslContextBuilder.forClient().keyManager(clientCert, clientKey))
46+
.build();
47+
48+
WorkflowServiceStubsOptions options =
49+
WorkflowServiceStubsOptions.newBuilder()
50+
.setTarget(ADDRESS)
51+
.setSslContext(sslContext)
52+
.build();
53+
54+
return WorkflowServiceStubs.newServiceStubs(options);
55+
} catch (Exception e) {
56+
throw new RuntimeException("Failed to create Temporal TLS connection", e);
57+
}
58+
}
59+
60+
private static String env(String key, String def) {
61+
String v = System.getenv(key);
62+
return (v == null || v.isBlank()) ? def : v;
63+
}
64+
65+
private static int envInt(String key, int def) {
66+
String v = System.getenv(key);
67+
if (v == null || v.isBlank()) return def;
68+
try {
69+
return Integer.parseInt(v.trim());
70+
} catch (Exception e) {
71+
return def;
72+
}
73+
}
74+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package io.temporal.samples.temporalcloudopenmetrics;
2+
3+
import io.temporal.client.WorkflowClient;
4+
import io.temporal.samples.temporalcloudopenmetrics.activities.ScenarioActivitiesImpl;
5+
import io.temporal.samples.temporalcloudopenmetrics.workflows.ScenarioWorkflowImpl;
6+
import io.temporal.worker.WorkerFactory;
7+
8+
public class WorkerMain {
9+
public static void main(String[] args) throws Exception {
10+
WorkflowClient client = TemporalConnection.client();
11+
12+
WorkerFactory factory = WorkerFactory.newInstance(client);
13+
io.temporal.worker.Worker worker = factory.newWorker(TemporalConnection.TASK_QUEUE);
14+
15+
worker.registerWorkflowImplementationTypes(ScenarioWorkflowImpl.class);
16+
worker.registerActivitiesImplementations(new ScenarioActivitiesImpl());
17+
18+
factory.start();
19+
System.out.println(
20+
"Worker started. namespace="
21+
+ TemporalConnection.NAMESPACE
22+
+ " taskQueue="
23+
+ TemporalConnection.TASK_QUEUE);
24+
25+
Thread.sleep(TemporalConnection.WORKER_SECONDS * 1000L);
26+
27+
System.out.println("Stopping worker after " + TemporalConnection.WORKER_SECONDS + "s");
28+
factory.shutdown();
29+
}
30+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package io.temporal.samples.temporalcloudopenmetrics.activities;
2+
3+
import io.temporal.activity.ActivityInterface;
4+
import io.temporal.activity.ActivityMethod;
5+
6+
@ActivityInterface
7+
public interface ScenarioActivities {
8+
@ActivityMethod
9+
String doWork(String name, String scenario);
10+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package io.temporal.samples.temporalcloudopenmetrics.activities;
2+
3+
import io.temporal.activity.Activity;
4+
import io.temporal.failure.ApplicationFailure;
5+
6+
public class ScenarioActivitiesImpl implements ScenarioActivities {
7+
8+
@Override
9+
public String doWork(String name, String scenario) {
10+
if ("fail".equalsIgnoreCase(scenario)) {
11+
throw ApplicationFailure.newNonRetryableFailure(
12+
"Intentional failure for dashboard", "SCENARIO_FAIL");
13+
}
14+
15+
if ("timeout".equalsIgnoreCase(scenario)) {
16+
sleepMs(5_000);
17+
return "unreachable (should have timed out)";
18+
}
19+
20+
if ("cancel".equalsIgnoreCase(scenario)) {
21+
while (true) {
22+
Activity.getExecutionContext().heartbeat("still-running");
23+
sleepMs(1_000);
24+
}
25+
}
26+
27+
// success
28+
return "Hello " + name;
29+
}
30+
31+
private static void sleepMs(long ms) {
32+
try {
33+
Thread.sleep(ms);
34+
} catch (InterruptedException e) {
35+
Thread.currentThread().interrupt();
36+
throw new RuntimeException("Interrupted", e);
37+
}
38+
}
39+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
services:
2+
grafana:
3+
image: grafana/grafana:latest
4+
ports:
5+
- "3001:3000"
6+
depends_on:
7+
- prometheus
8+
networks:
9+
- temporal_network
10+
volumes:
11+
- ./grafana/provisioning:/etc/grafana/provisioning:ro
12+
- ./grafana/dashboards:/var/lib/grafana/dashboards:ro
13+
14+
15+
prometheus:
16+
container_name: prometheus
17+
image: prom/prometheus:v2.37.0
18+
command:
19+
- --web.enable-remote-write-receiver
20+
- --config.file=/etc/prometheus/prometheus.yml
21+
ports:
22+
- "9093:9090" # host port changed to avoid conflicts
23+
volumes:
24+
- type: bind
25+
source: ./prometheus/config.yml
26+
target: /etc/prometheus/prometheus.yml
27+
- type: bind
28+
source: ./secrets/temporal_cloud_api_key
29+
target: /etc/prometheus/temporal_cloud_api_key
30+
read_only: true
31+
networks:
32+
- temporal_network
33+
34+
networks:
35+
temporal_network:
36+
driver: bridge
37+
name: temporal_network
490 KB
Loading
190 KB
Loading
193 KB
Loading

0 commit comments

Comments
 (0)