Skip to content

Commit d8b1574

Browse files
Update temporalmetricsdemo (apikey access only)
1 parent f2b0140 commit d8b1574

File tree

3 files changed

+89
-107
lines changed

3 files changed

+89
-107
lines changed

core/src/main/java/io/temporal/samples/temporalmetricsdemo/README.md

Lines changed: 44 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -33,50 +33,25 @@ Prometheus :-
3333

3434
## 1) Create Service Account + API Key (Temporal Cloud)
3535

36-
OpenMetrics auth reference:
37-
https://docs.temporal.io/production-deployment/cloud/metrics/openmetrics/api-reference#authentication
36+
1. Create a service account with **Metrics Read-Only** role
37+
OpenMetrics auth reference:
38+
https://docs.temporal.io/production-deployment/cloud/metrics/openmetrics/api-reference#authentication
3839

39-
In Temporal Cloud UI:
40-
- **Settings → Service Accounts**
41-
- Create a service account with **Metrics Read-Only** role
42-
- Generate an **API key** ( copy this, it will be needed later)
43-
---
44-
45-
46-
## 2) Create the `secrets/` folder + API key file
47-
48-
From the repo root (same folder as `docker-compose.yml`), run:
49-
50-
```
51-
cd temporalmetricsdemo
52-
mkdir -p secrets
53-
echo "put your CLOUD API KEY HERE" > secrets/temporal_cloud_api_key
54-
```
55-
now the folder will look like below and temporal_cloud_api_key will have the above api key that we generated in step 1
40+
In Temporal Cloud UI:
41+
- **Settings → Service Accounts**
42+
- Create a service account with **Metrics Read-Only** role
43+
- Generate an **API key** ( copy this, it will be needed later)
44+
---
45+
2. Add your namespace and assign namespace permission
5646

57-
```
58-
temporalmetricsdemo/
59-
├── docker-compose.yml
60-
├── prometheus/
61-
├── grafana/
62-
├── secrets/
63-
│ └── temporal_cloud_api_key
64-
```
47+
![metrics read only service account](docs/images/img8.png)
6548

66-
## 3) Configure TemporalConnection.java
6749

68-
Edit `TemporalConnection.java` and set your defaults:
50+
** This approach is only for testing, for production either have 2 service account 1 for running workflows and 1 for metrics reading
51+
to have more control on API keys **
6952

70-
```
71-
public static final String NAMESPACE = env("TEMPORAL_NAMESPACE", "<namespace>.<account-id>");
72-
public static final String ADDRESS = env("TEMPORAL_ADDRESS", "<namespace>.<account-id>.tmprl.cloud:7233");
73-
public static final String CERT = env("TEMPORAL_CERT", "/path/to/client.pem");
74-
public static final String KEY = env("TEMPORAL_KEY", "/path/to/client.key");
75-
public static final String TASK_QUEUE = env("TASK_QUEUE", "openmetrics-task-queue");
76-
public static final int WORKER_SECONDS = envInt("WORKER_SECONDS", 60);
77-
```
7853

79-
## 4) 4) Update Prometheus scrape config
54+
## 2) Update Prometheus scrape config
8055

8156
prometheus/config.yml
8257
Update it to use your namespace
@@ -86,35 +61,29 @@ Update it to use your namespace
8661
```
8762

8863

89-
## 5) Start Prometheus + Grafana
64+
## 3) Start Prometheus + Grafana
9065

9166
docker compose up -d
9267
docker compose ps
9368

69+
## 4) Ran the sample and view the cloud metrics
9470

95-
## 6) View Grafana dashboard
96-
97-
http://localhost:3001/
98-
99-
- Username: admin
100-
- Password: admin
101-
102-
You should see the Temporal Cloud OpenMetrics dashboard.
71+
Terminal 1
72+
**export below env variables in the respective terminal for running WorkerMain**
10373

104-
## 7) Verify metrics in Prometheus
74+
export TEMPORAL_API_KEY=<api_created_above>
75+
export TEMPORAL_NAMESPACE="<namespace>.<account-id>"
76+
export TEMPORAL_ADDRESS="<region>.<cloud-provider>.api.temporal.io:7233" (regional endpoint for API auth)
10577

106-
Prometheus: http://localhost:9093/
78+
- `./gradlew -q execute -PmainClass=io.temporal.samples.temporalmetricsdemo.WorkerMain`
10779

108-
Go to:
109-
Status → Targets (make sure the scrape target is UP)
110-
Graph tab (search for Temporal metrics and run a query)
80+
Terminal 2
11181

112-
## 8) Ran the sample and view the cloud metrics
82+
**export env variables again for running Starter**
11383

114-
- `./gradlew -q execute -PmainClass=io.temporal.samples.temporalmetricsdemo.WorkerMain`
11584
- `METRICS_PORT=9465 ./gradlew -q execute -PmainClass=io.temporal.samples.temporalmetricsdemo.Starter`
11685

117-
starter logs
86+
starter logs
11887
```
11988
➜ samples-java git:(deepika/openmetrics-demo) ✗ METRICS_PORT=9465 ./gradlew -q execute -PmainClass=io.temporal.samples.temporalmetricsdemo.Starter
12089
Worker metrics exposed at http://0.0.0.0:9465/metrics
@@ -138,5 +107,23 @@ Scenario=cancel ended: WorkflowFailedException - Workflow execution {workflowId=
138107
139108
```
140109
there will be some failures in the worker logs as we are intentionally failing workflows for the data generation purpose.
141-
give few seconds to see the data in both the dashboard and try to run couple of workflows so the rate
142-
queries show properly.
110+
give few seconds to see the data in both the dashboard and try to run couple of workflows so the rate
111+
queries show properly.
112+
113+
## 5) View Grafana dashboard
114+
115+
http://localhost:3001/
116+
117+
- Username: admin
118+
- Password: admin
119+
120+
You should see the Temporal Cloud OpenMetrics dashboard.
121+
122+
## 6) Verify metrics in Prometheus
123+
124+
Prometheus: http://localhost:9093/
125+
126+
Go to:
127+
Status → Targets (make sure the scrape target is UP)
128+
Graph tab (search for Temporal metrics and run a query)
129+

core/src/main/java/io/temporal/samples/temporalmetricsdemo/TemporalConnection.java

Lines changed: 45 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -5,98 +5,86 @@
55
import com.uber.m3.tally.Scope;
66
import com.uber.m3.tally.StatsReporter;
77
import com.uber.m3.util.Duration;
8-
import io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts;
9-
import io.grpc.netty.shaded.io.netty.handler.ssl.SslContext;
10-
import io.grpc.netty.shaded.io.netty.handler.ssl.SslContextBuilder;
118
import io.micrometer.prometheus.PrometheusConfig;
129
import io.micrometer.prometheus.PrometheusMeterRegistry;
1310
import io.temporal.client.WorkflowClient;
1411
import io.temporal.client.WorkflowClientOptions;
1512
import io.temporal.common.reporter.MicrometerClientStatsReporter;
1613
import io.temporal.serviceclient.WorkflowServiceStubs;
1714
import io.temporal.serviceclient.WorkflowServiceStubsOptions;
18-
import java.io.FileInputStream;
19-
import java.io.InputStream;
2015
import java.io.OutputStream;
2116
import java.net.InetSocketAddress;
2217
import java.nio.charset.StandardCharsets;
2318

2419
public final class TemporalConnection {
2520
private TemporalConnection() {}
2621

27-
// Read from environment (docker-compose env_file: .env)
28-
public static final String NAMESPACE = env("TEMPORAL_NAMESPACE", "<namespace>.<account-id>");
29-
public static final String ADDRESS =
30-
env("TEMPORAL_ADDRESS", "<namespace>.<account-id>.tmprl.cloud:7233");
31-
public static final String CERT =
32-
env("TEMPORAL_CERT", "path/to/client.pem");
33-
public static final String KEY =
34-
env("TEMPORAL_KEY", "path/to/client.key");
22+
// Required: MUST be set in env. No defaults.
23+
public static final String NAMESPACE = envRequired("TEMPORAL_NAMESPACE");
24+
public static final String ADDRESS = envRequired("TEMPORAL_ADDRESS");
3525
public static final String TASK_QUEUE = env("TASK_QUEUE", "openmetrics-task-queue");
26+
3627
private static final int METRICS_PORT = envInt("METRICS_PORT", 9464);
3728
private static final int METRICS_REPORT_SECONDS = envInt("METRICS_REPORT_SECONDS", 10);
3829

3930
private static volatile WorkflowClient CLIENT;
40-
private static volatile WorkflowServiceStubs SERVICE;
41-
42-
private static volatile boolean METRICS_STARTED = false;
43-
private static volatile PrometheusMeterRegistry PROM_REGISTRY;
31+
private static volatile PrometheusMeterRegistry PROM;
32+
private static volatile boolean METRICS_STARTED;
4433

4534
public static WorkflowClient client() {
4635
if (CLIENT != null) return CLIENT;
4736
synchronized (TemporalConnection.class) {
4837
if (CLIENT != null) return CLIENT;
4938

50-
SERVICE = serviceStubs();
39+
String apiKey = envRequired("TEMPORAL_API_KEY");
40+
41+
// Validation
42+
validate();
43+
System.out.println("TemporalConnection: ADDRESS=" + ADDRESS);
44+
System.out.println("TemporalConnection: NAMESPACE=" + NAMESPACE);
45+
46+
Scope scope = metricsScope();
47+
48+
WorkflowServiceStubs service =
49+
WorkflowServiceStubs.newServiceStubs(
50+
WorkflowServiceStubsOptions.newBuilder()
51+
.setTarget(ADDRESS)
52+
.setEnableHttps(true)
53+
.addApiKey(() -> apiKey)
54+
.setMetricsScope(scope)
55+
.build());
56+
5157
CLIENT =
5258
WorkflowClient.newInstance(
53-
SERVICE, WorkflowClientOptions.newBuilder().setNamespace(NAMESPACE).build());
59+
service, WorkflowClientOptions.newBuilder().setNamespace(NAMESPACE).build());
60+
5461
return CLIENT;
5562
}
5663
}
5764

58-
// create service stubs used by worker + starter
59-
private static WorkflowServiceStubs serviceStubs() {
60-
try (InputStream clientCert = new FileInputStream(CERT);
61-
InputStream clientKey = new FileInputStream(KEY)) {
62-
63-
SslContext sslContext =
64-
GrpcSslContexts.configure(SslContextBuilder.forClient().keyManager(clientCert, clientKey))
65-
.build();
66-
67-
Scope metricsScope = metricsScope(); // ✅ tally scope that writes into Prometheus registry
68-
69-
WorkflowServiceStubsOptions options =
70-
WorkflowServiceStubsOptions.newBuilder()
71-
.setTarget(ADDRESS)
72-
.setSslContext(sslContext)
73-
.setMetricsScope(metricsScope) // ✅ Temporal SDK emits metrics here
74-
.build();
75-
76-
return WorkflowServiceStubs.newServiceStubs(options);
77-
} catch (Exception e) {
78-
throw new RuntimeException("Failed to create Temporal TLS connection", e);
65+
private static void validate() {
66+
if (NAMESPACE.isBlank()) {
67+
throw new IllegalStateException("TEMPORAL_NAMESPACE must be set (non-blank).");
68+
}
69+
if (ADDRESS.isBlank()) {
70+
throw new IllegalStateException("TEMPORAL_ADDRESS must be set (non-blank).");
7971
}
8072
}
8173

8274
private static Scope metricsScope() {
8375
synchronized (TemporalConnection.class) {
84-
if (PROM_REGISTRY == null) {
85-
PROM_REGISTRY = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
86-
}
87-
88-
StatsReporter reporter = new MicrometerClientStatsReporter(PROM_REGISTRY);
76+
if (PROM == null) PROM = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
8977

78+
StatsReporter reporter = new MicrometerClientStatsReporter(PROM);
9079
Scope scope =
9180
new RootScopeBuilder()
9281
.reporter(reporter)
9382
.reportEvery(Duration.ofSeconds(METRICS_REPORT_SECONDS));
9483

9584
if (!METRICS_STARTED) {
9685
METRICS_STARTED = true;
97-
startMetricsHttpServer(PROM_REGISTRY);
86+
startMetricsHttpServer(PROM);
9887
}
99-
10088
return scope;
10189
}
10290
}
@@ -116,17 +104,24 @@ private static void startMetricsHttpServer(PrometheusMeterRegistry registry) {
116104
os.write(body);
117105
}
118106
});
119-
server.setExecutor(null);
120107
server.start();
121-
System.out.println("Worker metrics exposed at http://0.0.0.0:" + METRICS_PORT + "/metrics");
108+
System.out.println("Worker metrics at http://0.0.0.0:" + METRICS_PORT + "/metrics");
122109
} catch (Exception e) {
123110
throw new RuntimeException("Failed to start /metrics endpoint", e);
124111
}
125112
}
126113

127114
private static String env(String key, String def) {
128115
String v = System.getenv(key);
129-
return (v == null || v.isBlank()) ? def : v;
116+
return (v == null || v.isBlank()) ? def : v.trim();
117+
}
118+
119+
private static String envRequired(String key) {
120+
String v = System.getenv(key);
121+
if (v == null || v.isBlank()) {
122+
throw new IllegalStateException("Missing required env var: " + key);
123+
}
124+
return v.trim();
130125
}
131126

132127
private static int envInt(String key, int def) {
85.4 KB
Loading

0 commit comments

Comments
 (0)