This is an automated email from the ASF dual-hosted git repository.
davidzollo pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/seatunnel.git
The following commit(s) were added to refs/heads/dev by this push:
new b7d3f1515f [Improve][Zeta] Add ReportMetricsOperation observability
exports (#11104)
b7d3f1515f is described below
commit b7d3f1515f015dd2bba498ddcd6218a69ccfdade
Author: Doyeon Kim <[email protected]>
AuthorDate: Sun Jun 21 11:48:54 2026 +0900
[Improve][Zeta] Add ReportMetricsOperation observability exports (#11104)
---
docs/en/engines/zeta/telemetry.md | 9 ++
docs/zh/engines/zeta/telemetry.md | 9 ++
.../engine/server/TaskExecutionService.java | 72 +++++++++++++-
.../metrics/ExportsInstanceInitializer.java | 3 +
.../entity/ReportMetricsOperationStats.java | 39 ++++++++
.../exports/ReportMetricsOperationExports.java | 90 +++++++++++++++++
.../TelemetryCollectorCoordinatorGuardTest.java | 110 +++++++++++++++++++++
7 files changed, 329 insertions(+), 3 deletions(-)
diff --git a/docs/en/engines/zeta/telemetry.md
b/docs/en/engines/zeta/telemetry.md
index a749785a18..7c91348a1d 100644
--- a/docs/en/engines/zeta/telemetry.md
+++ b/docs/en/engines/zeta/telemetry.md
@@ -146,6 +146,15 @@
engine_state_store_connector_jar_total_references{backend="hazelcast"}
| job_thread_pool_task_total | Counter | **address**, server instance
address,for example: "127.0.0.1:5801" | The taskCount of seatunnel coordinator
job's executor cached thread pool |
| job_thread_pool_rejection_total | Counter | **address**, server instance
address,for example: "127.0.0.1:5801" | The rejectionCount of seatunnel
coordinator job's executor cached thread pool |
|
+### Report Metrics Operation
+
+| MetricName | Type | Labels
| DESCRIPTION
|
+|---------------------------------------------------|---------|-----------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------|
+| report_metrics_operation_total | Counter | **address**,
worker instance address,for example: "127.0.0.1:5801". **result**, one of
"success" "failure" "interrupted" | The total number of
`ReportMetricsOperation` invocations sent by a worker
|
+| report_metrics_operation_last_payload_task_count | Gauge | **address**,
worker instance address,for example: "127.0.0.1:5801"
| The number of task metrics included in the most recent
`ReportMetricsOperation` payload sent by a worker |
+| report_metrics_operation_last_invocation_latency_ms | Gauge | **address**,
worker instance address,for example: "127.0.0.1:5801"
| The most recent worker-side `ReportMetricsOperation` reporting latency
in milliseconds, including local metrics collection and worker-to-master
invocation |
+| report_metrics_operation_max_invocation_latency_ms | Gauge | **address**,
worker instance address,for example: "127.0.0.1:5801"
| The maximum observed worker-side `ReportMetricsOperation` reporting
latency in milliseconds since the worker started, including local metrics
collection and worker-to-master invocation |
+
### Job info detail
| MetricName | Type | Labels
|
DESCRIPTION |
diff --git a/docs/zh/engines/zeta/telemetry.md
b/docs/zh/engines/zeta/telemetry.md
index 0da7c4e1b8..e90a320be4 100644
--- a/docs/zh/engines/zeta/telemetry.md
+++ b/docs/zh/engines/zeta/telemetry.md
@@ -145,6 +145,15 @@
engine_state_store_connector_jar_total_references{backend="hazelcast"}
| job_thread_pool_task_total | Counter |
**address**,服务器实例地址,例如:"127.0.0.1:5801" | seatunnel 协调器作业执行器缓存线程池的总任务数 |
| job_thread_pool_rejection_total | Counter |
**address**,服务器实例地址,例如:"127.0.0.1:5801" | seatunnel 协调器作业执行器缓存线程池的拒绝任务总数 |
+### ReportMetricsOperation 指标
+
+| MetricName | Type | Labels
|
描述
|
+|---------------------------------------------------|---------|--------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------|
+| report_metrics_operation_total | Counter |
**address**,worker 实例地址,例如:"127.0.0.1:5801"。**result**,取值包括:"success" "failure"
"interrupted" | worker 发送的 `ReportMetricsOperation` 调用总次数
|
+| report_metrics_operation_last_payload_task_count | Gauge |
**address**,worker 实例地址,例如:"127.0.0.1:5801"
| 最近一次 `ReportMetricsOperation` payload 中包含的 task metrics 数量
|
+| report_metrics_operation_last_invocation_latency_ms | Gauge |
**address**,worker 实例地址,例如:"127.0.0.1:5801"
| worker 侧最近一次 `ReportMetricsOperation` 上报耗时,单位为毫秒,包含本地 metrics 收集和 worker 到
master 的调用时间 |
+| report_metrics_operation_max_invocation_latency_ms | Gauge |
**address**,worker 实例地址,例如:"127.0.0.1:5801"
| worker 启动以来观测到的 `ReportMetricsOperation` 最大上报耗时,单位为毫秒,包含本地 metrics 收集和
worker 到 master 的调用时间 |
+
### 作业信息详细
| MetricName | Type | Labels
| 描述 |
diff --git
a/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/TaskExecutionService.java
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/TaskExecutionService.java
index a7b02766dc..5e011d5391 100644
---
a/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/TaskExecutionService.java
+++
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/TaskExecutionService.java
@@ -53,6 +53,7 @@ import org.apache.seatunnel.engine.server.task.SeaTunnelTask;
import org.apache.seatunnel.engine.server.task.TaskGroupImmutableInformation;
import
org.apache.seatunnel.engine.server.task.operation.NotifyTaskStatusOperation;
import
org.apache.seatunnel.engine.server.task.operation.ReportMetricsOperation;
+import
org.apache.seatunnel.engine.server.telemetry.metrics.entity.ReportMetricsOperationStats;
import org.apache.commons.collections4.CollectionUtils;
@@ -96,6 +97,7 @@ import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Stream;
@@ -196,6 +198,13 @@ public class TaskExecutionService implements
DynamicMetricsProvider {
/** SeaTunnel configuration for this engine. */
private final SeaTunnelConfig seaTunnelConfig;
+ // Track worker-side metrics reporting cost without changing the report
path semantics.
+ private final AtomicLong reportMetricsOperationSuccessCount = new
AtomicLong();
+ private final AtomicLong reportMetricsOperationFailureCount = new
AtomicLong();
+ private final AtomicLong reportMetricsOperationInterruptedCount = new
AtomicLong();
+ private final AtomicLong reportMetricsOperationLastPayloadTaskCount = new
AtomicLong();
+ private final AtomicLong reportMetricsOperationLastInvocationLatencyMs =
new AtomicLong();
+ private final AtomicLong reportMetricsOperationMaxInvocationLatencyMs =
new AtomicLong();
/** Scheduled executor for periodic tasks like metrics backup. */
private final ScheduledExecutorService scheduledExecutorService;
@@ -753,26 +762,83 @@ public class TaskExecutionService implements
DynamicMetricsProvider {
return;
}
+ long invocationStartNanos = System.nanoTime();
+ HashMap<TaskLocation, SeaTunnelMetricsContext> localMetricsMap =
collectLocalMetricsMap();
+ int payloadTaskCount = localMetricsMap.size();
InvocationFuture<Object> invoke =
nodeEngine
.getOperationService()
.createInvocationBuilder(
SeaTunnelServer.SERVICE_NAME,
- new
ReportMetricsOperation(collectLocalMetricsMap()),
+ new ReportMetricsOperation(localMetricsMap),
nodeEngine.getMasterAddress())
.invoke();
try {
invoke.get();
+ recordReportMetricsOperationSuccess(
+ payloadTaskCount,
elapsedMillisSince(invocationStartNanos));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
- logger.severe("update metrics context stopped due to thread
interruption.", e);
+ long elapsedMillis = elapsedMillisSince(invocationStartNanos);
+ recordReportMetricsOperationInterruption(payloadTaskCount,
elapsedMillis);
+ logger.severe(
+ String.format(
+ "update metrics context stopped due to thread
interruption, "
+ + "payloadTaskCount=%d,
invocationLatencyMs=%d.",
+ payloadTaskCount, elapsedMillis),
+ e);
} catch (Exception e) {
- logger.severe("failed to update metrics", e);
+ long elapsedMillis = elapsedMillisSince(invocationStartNanos);
+ recordReportMetricsOperationFailure(payloadTaskCount,
elapsedMillis);
+ logger.severe(
+ String.format(
+ "failed to update metrics, payloadTaskCount=%d, "
+ + "invocationLatencyMs=%d.",
+ payloadTaskCount, elapsedMillis),
+ e);
}
this.printTaskExecutionRuntimeInfo();
}
+ private void recordReportMetricsOperationSuccess(int payloadTaskCount,
long elapsedMillis) {
+ updateReportMetricsOperationObservability(payloadTaskCount,
elapsedMillis);
+ reportMetricsOperationSuccessCount.incrementAndGet();
+ }
+
+ private void recordReportMetricsOperationFailure(int payloadTaskCount,
long elapsedMillis) {
+ updateReportMetricsOperationObservability(payloadTaskCount,
elapsedMillis);
+ reportMetricsOperationFailureCount.incrementAndGet();
+ }
+
+ private void recordReportMetricsOperationInterruption(
+ int payloadTaskCount, long elapsedMillis) {
+ updateReportMetricsOperationObservability(payloadTaskCount,
elapsedMillis);
+ reportMetricsOperationInterruptedCount.incrementAndGet();
+ }
+
+ private void updateReportMetricsOperationObservability(
+ int payloadTaskCount, long elapsedMillis) {
+ reportMetricsOperationLastPayloadTaskCount.set(payloadTaskCount);
+ reportMetricsOperationLastInvocationLatencyMs.set(elapsedMillis);
+
reportMetricsOperationMaxInvocationLatencyMs.accumulateAndGet(elapsedMillis,
Math::max);
+ }
+
+ private long elapsedMillisSince(long startNanos) {
+ return TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNanos);
+ }
+
+ /** Returns the latest worker-side ReportMetricsOperation observability
snapshot. */
+ public ReportMetricsOperationStats getReportMetricsOperationStats() {
+ return new ReportMetricsOperationStats(
+ reportMetricsOperationSuccessCount.get(),
+ reportMetricsOperationFailureCount.get(),
+ reportMetricsOperationInterruptedCount.get(),
+ reportMetricsOperationLastPayloadTaskCount.get(),
+ reportMetricsOperationLastInvocationLatencyMs.get(),
+ reportMetricsOperationMaxInvocationLatencyMs.get());
+ }
+
private HashMap<TaskLocation, SeaTunnelMetricsContext>
collectLocalMetricsMap() {
Map<TaskGroupLocation, TaskGroupContext> contextMap = new HashMap<>();
contextMap.putAll(finishedExecutionContexts);
diff --git
a/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/telemetry/metrics/ExportsInstanceInitializer.java
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/telemetry/metrics/ExportsInstanceInitializer.java
index d4b6cba6ec..4b759aba5f 100644
---
a/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/telemetry/metrics/ExportsInstanceInitializer.java
+++
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/telemetry/metrics/ExportsInstanceInitializer.java
@@ -23,6 +23,7 @@ import
org.apache.seatunnel.engine.server.telemetry.metrics.exports.EngineStateS
import
org.apache.seatunnel.engine.server.telemetry.metrics.exports.JobMetricExports;
import
org.apache.seatunnel.engine.server.telemetry.metrics.exports.JobThreadPoolStatusExports;
import
org.apache.seatunnel.engine.server.telemetry.metrics.exports.NodeMetricExports;
+import
org.apache.seatunnel.engine.server.telemetry.metrics.exports.ReportMetricsOperationExports;
import com.hazelcast.instance.impl.Node;
import io.prometheus.client.CollectorRegistry;
@@ -47,6 +48,8 @@ public final class ExportsInstanceInitializer {
new JobThreadPoolStatusExports(node).register(collectorRegistry);
// Node metrics
new NodeMetricExports(node).register(collectorRegistry);
+ // ReportMetricsOperation metrics
+ new
ReportMetricsOperationExports(node).register(collectorRegistry);
// Engine state store metrics
new
EngineStateStoreMetricExports(node).register(collectorRegistry);
// Engine state store logical metrics
diff --git
a/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/telemetry/metrics/entity/ReportMetricsOperationStats.java
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/telemetry/metrics/entity/ReportMetricsOperationStats.java
new file mode 100644
index 0000000000..ddd80fe89d
--- /dev/null
+++
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/telemetry/metrics/entity/ReportMetricsOperationStats.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.seatunnel.engine.server.telemetry.metrics.entity;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+/** Snapshot of worker-side ReportMetricsOperation observability state. */
+@Data
+@AllArgsConstructor
+public class ReportMetricsOperationStats {
+ /** Total successful report invocations sent by this worker. */
+ private long successCount;
+ /** Total failed report invocations sent by this worker. */
+ private long failureCount;
+ /** Total interrupted report invocations sent by this worker. */
+ private long interruptedCount;
+ /** Task metric count in the most recent report payload. */
+ private long lastPayloadTaskCount;
+ /** Most recent worker-side reporting latency in milliseconds. */
+ private long lastInvocationLatencyMs;
+ /** Maximum observed worker-side reporting latency in milliseconds. */
+ private long maxInvocationLatencyMs;
+}
diff --git
a/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/telemetry/metrics/exports/ReportMetricsOperationExports.java
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/telemetry/metrics/exports/ReportMetricsOperationExports.java
new file mode 100644
index 0000000000..0f46c64765
--- /dev/null
+++
b/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/telemetry/metrics/exports/ReportMetricsOperationExports.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.seatunnel.engine.server.telemetry.metrics.exports;
+
+import org.apache.seatunnel.engine.server.TaskExecutionService;
+import org.apache.seatunnel.engine.server.telemetry.metrics.AbstractCollector;
+import
org.apache.seatunnel.engine.server.telemetry.metrics.entity.ReportMetricsOperationStats;
+
+import com.hazelcast.instance.impl.Node;
+import io.prometheus.client.CounterMetricFamily;
+import io.prometheus.client.GaugeMetricFamily;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ReportMetricsOperationExports extends AbstractCollector {
+
+ public ReportMetricsOperationExports(Node node) {
+ super(node);
+ }
+
+ @Override
+ public List<MetricFamilySamples> collect() {
+ List<MetricFamilySamples> mfs = new ArrayList();
+ TaskExecutionService taskExecutionService =
getServer().getTaskExecutionService();
+ if (taskExecutionService == null) {
+ return mfs;
+ }
+
+ String address = localAddress();
+ ReportMetricsOperationStats stats =
taskExecutionService.getReportMetricsOperationStats();
+
+ CounterMetricFamily totalMetricFamily =
+ new CounterMetricFamily(
+ "report_metrics_operation",
+ "The total number of ReportMetricsOperation
invocations sent by a worker",
+ clusterLabelNames(ADDRESS, "result"));
+ totalMetricFamily.addMetric(labelValues(address, "success"),
stats.getSuccessCount());
+ totalMetricFamily.addMetric(labelValues(address, "failure"),
stats.getFailureCount());
+ totalMetricFamily.addMetric(
+ labelValues(address, "interrupted"),
stats.getInterruptedCount());
+ mfs.add(totalMetricFamily);
+
+ GaugeMetricFamily payloadMetricFamily =
+ new GaugeMetricFamily(
+ "report_metrics_operation_last_payload_task_count",
+ "The number of task metrics included in the most
recent "
+ + "ReportMetricsOperation payload sent by a
worker",
+ clusterLabelNames(ADDRESS));
+ payloadMetricFamily.addMetric(labelValues(address),
stats.getLastPayloadTaskCount());
+ mfs.add(payloadMetricFamily);
+
+ GaugeMetricFamily lastLatencyMetricFamily =
+ new GaugeMetricFamily(
+ "report_metrics_operation_last_invocation_latency_ms",
+ "The most recent worker-side ReportMetricsOperation
reporting latency "
+ + "in milliseconds, including local metrics
collection and "
+ + "worker-to-master invocation",
+ clusterLabelNames(ADDRESS));
+ lastLatencyMetricFamily.addMetric(labelValues(address),
stats.getLastInvocationLatencyMs());
+ mfs.add(lastLatencyMetricFamily);
+
+ GaugeMetricFamily maxLatencyMetricFamily =
+ new GaugeMetricFamily(
+ "report_metrics_operation_max_invocation_latency_ms",
+ "The maximum observed worker-side
ReportMetricsOperation reporting "
+ + "latency in milliseconds since the worker
started, "
+ + "including local metrics collection and
worker-to-master "
+ + "invocation",
+ clusterLabelNames(ADDRESS));
+ maxLatencyMetricFamily.addMetric(labelValues(address),
stats.getMaxInvocationLatencyMs());
+ mfs.add(maxLatencyMetricFamily);
+ return mfs;
+ }
+}
diff --git
a/seatunnel-engine/seatunnel-engine-server/src/test/java/org/apache/seatunnel/engine/server/telemetry/metrics/TelemetryCollectorCoordinatorGuardTest.java
b/seatunnel-engine/seatunnel-engine-server/src/test/java/org/apache/seatunnel/engine/server/telemetry/metrics/TelemetryCollectorCoordinatorGuardTest.java
index c41a923580..03edb148a4 100644
---
a/seatunnel-engine/seatunnel-engine-server/src/test/java/org/apache/seatunnel/engine/server/telemetry/metrics/TelemetryCollectorCoordinatorGuardTest.java
+++
b/seatunnel-engine/seatunnel-engine-server/src/test/java/org/apache/seatunnel/engine/server/telemetry/metrics/TelemetryCollectorCoordinatorGuardTest.java
@@ -19,11 +19,14 @@ package
org.apache.seatunnel.engine.server.telemetry.metrics;
import org.apache.seatunnel.engine.server.CoordinatorService;
import org.apache.seatunnel.engine.server.SeaTunnelServer;
+import org.apache.seatunnel.engine.server.TaskExecutionService;
import org.apache.seatunnel.engine.server.telemetry.metrics.entity.JobCounter;
+import
org.apache.seatunnel.engine.server.telemetry.metrics.entity.ReportMetricsOperationStats;
import
org.apache.seatunnel.engine.server.telemetry.metrics.entity.ThreadPoolStatus;
import
org.apache.seatunnel.engine.server.telemetry.metrics.exports.ClusterMetricExports;
import
org.apache.seatunnel.engine.server.telemetry.metrics.exports.JobMetricExports;
import
org.apache.seatunnel.engine.server.telemetry.metrics.exports.JobThreadPoolStatusExports;
+import
org.apache.seatunnel.engine.server.telemetry.metrics.exports.ReportMetricsOperationExports;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
@@ -181,6 +184,76 @@ public class TelemetryCollectorCoordinatorGuardTest {
Mockito.verify(mockServer, Mockito.never()).isCoordinatorActive();
}
+ @Test
+ void
testReportMetricsOperationExportsReturnsEmptyWhenTaskExecutionServiceMissing() {
+ Mockito.when(mockServer.getTaskExecutionService()).thenReturn(null);
+
+ ReportMetricsOperationExports exports = new
ReportMetricsOperationExports(mockNode);
+ List<Collector.MetricFamilySamples> result = exports.collect();
+
+ Assertions.assertTrue(
+ result.isEmpty(),
+ "collect() must return empty when task execution service is
unavailable");
+ }
+
+ @Test
+ void
testReportMetricsOperationExportsReturnsMetricsWhenTaskExecutionServiceAvailable()
{
+ TaskExecutionService taskExecutionService =
Mockito.mock(TaskExecutionService.class);
+
Mockito.when(mockServer.getTaskExecutionService()).thenReturn(taskExecutionService);
+ Mockito.when(taskExecutionService.getReportMetricsOperationStats())
+ .thenReturn(new ReportMetricsOperationStats(3L, 1L, 1L, 9L,
15L, 27L));
+
+ ReportMetricsOperationExports exports = new
ReportMetricsOperationExports(mockNode);
+ List<Collector.MetricFamilySamples> result = exports.collect();
+
+ Assertions.assertFalse(
+ result.isEmpty(),
+ "collect() must return metrics when task execution service
exists");
+ Collector.MetricFamilySamples totalMetric =
+ result.stream()
+ .filter(s -> "report_metrics_operation".equals(s.name))
+ .findFirst()
+ .orElse(null);
+ Assertions.assertNotNull(totalMetric);
+ Assertions.assertEquals(3, totalMetric.samples.size());
+ assertMetricSample(totalMetric, "report_metrics_operation_total",
"success", 3D);
+ assertMetricSample(totalMetric, "report_metrics_operation_total",
"failure", 1D);
+ assertMetricSample(totalMetric, "report_metrics_operation_total",
"interrupted", 1D);
+
+ Collector.MetricFamilySamples payloadMetric =
+ result.stream()
+ .filter(
+ s ->
+
"report_metrics_operation_last_payload_task_count"
+ .equals(s.name))
+ .findFirst()
+ .orElse(null);
+ Assertions.assertNotNull(payloadMetric);
+ assertSingleMetricSample(payloadMetric, 9D);
+
+ Collector.MetricFamilySamples lastLatencyMetric =
+ result.stream()
+ .filter(
+ s ->
+
"report_metrics_operation_last_invocation_latency_ms"
+ .equals(s.name))
+ .findFirst()
+ .orElse(null);
+ Assertions.assertNotNull(lastLatencyMetric);
+ assertSingleMetricSample(lastLatencyMetric, 15D);
+
+ Collector.MetricFamilySamples maxLatencyMetric =
+ result.stream()
+ .filter(
+ s ->
+
"report_metrics_operation_max_invocation_latency_ms"
+ .equals(s.name))
+ .findFirst()
+ .orElse(null);
+ Assertions.assertNotNull(maxLatencyMetric);
+ assertSingleMetricSample(maxLatencyMetric, 27D);
+ }
+
//
-------------------------------------------------------------------------
// ClusterMetricExports
//
-------------------------------------------------------------------------
@@ -237,4 +310,41 @@ public class TelemetryCollectorCoordinatorGuardTest {
Mockito.eq("Skip cluster_info metric: unable to
resolve master address"),
Mockito.any(UnknownHostException.class));
}
+
+ private void assertMetricSample(
+ Collector.MetricFamilySamples metricFamilySamples,
+ String expectedSampleName,
+ String result,
+ double expectedValue) {
+ Collector.MetricFamilySamples.Sample sample =
+ metricFamilySamples.samples.stream()
+ .filter(
+ s -> {
+ if (!expectedSampleName.equals(s.name)) {
+ return false;
+ }
+ int resultLabelIndex =
s.labelNames.indexOf("result");
+ return resultLabelIndex >= 0
+ &&
result.equals(s.labelValues.get(resultLabelIndex));
+ })
+ .findFirst()
+ .orElse(null);
+ Assertions.assertNotNull(sample);
+ assertAddressLabel(sample);
+ Assertions.assertEquals(expectedValue, sample.value);
+ }
+
+ private void assertSingleMetricSample(
+ Collector.MetricFamilySamples metricFamilySamples, double
expectedValue) {
+ Assertions.assertEquals(1, metricFamilySamples.samples.size());
+ Collector.MetricFamilySamples.Sample sample =
metricFamilySamples.samples.get(0);
+ assertAddressLabel(sample);
+ Assertions.assertEquals(expectedValue, sample.value);
+ }
+
+ private void assertAddressLabel(Collector.MetricFamilySamples.Sample
sample) {
+ int addressLabelIndex = sample.labelNames.indexOf("address");
+ Assertions.assertTrue(addressLabelIndex >= 0, "metric sample must
contain 'address' label");
+ Assertions.assertEquals("127.0.0.1:5801",
sample.labelValues.get(addressLabelIndex));
+ }
}