Skip to content

Commit 037f537

Browse files
ZOOKEEPER-5036: Add Timeline Metrics Provider for external metrics collection systems
1 parent 6c4fabf commit 037f537

6 files changed

Lines changed: 1069 additions & 0 deletions

File tree

zookeeper-assembly/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@
7575
<artifactId>zookeeper-prometheus-metrics</artifactId>
7676
<version>${project.version}</version>
7777
</dependency>
78+
<dependency>
79+
<groupId>org.apache.zookeeper</groupId>
80+
<artifactId>zookeeper-timeline-metrics</artifactId>
81+
<version>${project.version}</version>
82+
</dependency>
7883
<dependency>
7984
<groupId>org.apache.zookeeper</groupId>
8085
<artifactId>zookeeper-recipes</artifactId>

zookeeper-metrics-providers/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333

3434
<modules>
3535
<module>zookeeper-prometheus-metrics</module>
36+
<module>zookeeper-timeline-metrics</module>
3637
</modules>
3738

3839
</project>
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
3+
<!--
4+
/**
5+
* Licensed to the Apache Software Foundation (ASF) under one
6+
* or more contributor license agreements. See the NOTICE file
7+
* distributed with this work for additional information
8+
* regarding copyright ownership. The ASF licenses this file
9+
* to you under the Apache License, Version 2.0 (the
10+
* "License"); you may not use this file except in compliance
11+
* with the License. You may obtain a copy of the License at
12+
*
13+
* http://www.apache.org/licenses/LICENSE-2.0
14+
*
15+
* Unless required by applicable law or agreed to in writing, software
16+
* distributed under the License is distributed on an "AS IS" BASIS,
17+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18+
* See the License for the specific language governing permissions and
19+
* limitations under the License.
20+
*/
21+
-->
22+
<modelVersion>4.0.0</modelVersion>
23+
<parent>
24+
<groupId>org.apache.zookeeper</groupId>
25+
<artifactId>zookeeper-metrics-providers</artifactId>
26+
<version>3.9.3</version>
27+
</parent>
28+
29+
<artifactId>zookeeper-timeline-metrics</artifactId>
30+
<packaging>jar</packaging>
31+
<name>Apache ZooKeeper - Timeline Metrics Provider</name>
32+
<description>ZooKeeper Timeline Metrics Provider implementation</description>
33+
34+
<properties>
35+
<!-- No external dependencies required - pure JDK implementation -->
36+
</properties>
37+
<dependencies>
38+
<!-- Core ZooKeeper dependency for metrics interfaces -->
39+
<dependency>
40+
<groupId>org.apache.zookeeper</groupId>
41+
<artifactId>zookeeper</artifactId>
42+
<version>${project.version}</version>
43+
</dependency>
44+
45+
<!-- SLF4J for logging -->
46+
<dependency>
47+
<groupId>org.slf4j</groupId>
48+
<artifactId>slf4j-api</artifactId>
49+
</dependency>
50+
51+
<!-- Test dependencies -->
52+
<dependency>
53+
<groupId>org.mockito</groupId>
54+
<artifactId>mockito-core</artifactId>
55+
<scope>test</scope>
56+
</dependency>
57+
<dependency>
58+
<groupId>org.junit.jupiter</groupId>
59+
<artifactId>junit-jupiter-engine</artifactId>
60+
<scope>test</scope>
61+
</dependency>
62+
<dependency>
63+
<groupId>org.junit.platform</groupId>
64+
<artifactId>junit-platform-runner</artifactId>
65+
<scope>test</scope>
66+
</dependency>
67+
</dependencies>
68+
69+
<build>
70+
<plugins>
71+
<plugin>
72+
<groupId>org.apache.maven.plugins</groupId>
73+
<artifactId>maven-dependency-plugin</artifactId>
74+
<executions>
75+
<execution>
76+
<id>copy-dependencies</id>
77+
<phase>package</phase>
78+
<goals>
79+
<goal>copy-dependencies</goal>
80+
</goals>
81+
<configuration>
82+
<outputDirectory>${project.build.directory}/lib</outputDirectory>
83+
<overWriteReleases>false</overWriteReleases>
84+
<overWriteSnapshots>true</overWriteSnapshots>
85+
<excludeTransitive>false</excludeTransitive>
86+
</configuration>
87+
</execution>
88+
</executions>
89+
</plugin>
90+
</plugins>
91+
</build>
92+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package org.apache.zookeeper.metrics.timeline;
19+
20+
import java.util.HashMap;
21+
import java.util.Map;
22+
23+
/**
24+
* Represents a point-in-time snapshot of ZooKeeper metrics.
25+
*
26+
* <p>This class is a data transfer object that captures metric values at a specific
27+
* timestamp for export to Timeline/Ambari Metrics Collector. It contains three types
28+
* of metrics:</p>
29+
* <ul>
30+
* <li><b>Counters</b> - Monotonically increasing values (e.g., request_count)</li>
31+
* <li><b>Gauges</b> - Current values that can go up or down (e.g., num_alive_connections)</li>
32+
* <li><b>Summaries</b> - Computed statistics like avg, min, max, percentiles (e.g., latency_avg)</li>
33+
* </ul>
34+
*
35+
* <p>Instances of this class are immutable after creation and are sent to the
36+
* Timeline sink for persistence and visualization.</p>
37+
*
38+
* @see TimelineMetricsProvider
39+
* @see TimelineMetricsSink
40+
*/
41+
public class MetricSnapshot {
42+
43+
private final long timestamp;
44+
private final String hostname;
45+
private final String appId;
46+
47+
// Separate collections for different metric types
48+
private final Map<String, Long> counters = new HashMap<>();
49+
private final Map<String, Double> gauges = new HashMap<>();
50+
private final Map<String, Double> summaries = new HashMap<>();
51+
52+
/**
53+
* Creates a new metric snapshot.
54+
*
55+
* @param timestamp the timestamp in milliseconds since epoch
56+
* @param hostname the hostname of the ZooKeeper server
57+
* @param appId the application ID (typically "zookeeper")
58+
*/
59+
public MetricSnapshot(long timestamp, String hostname, String appId) {
60+
this.timestamp = timestamp;
61+
this.hostname = hostname;
62+
this.appId = appId;
63+
}
64+
65+
/**
66+
* Adds a counter metric to the snapshot.
67+
*
68+
* <p>Counters represent monotonically increasing values such as total requests,
69+
* total bytes received, etc.</p>
70+
*
71+
* @param name the metric name
72+
* @param value the counter value
73+
*/
74+
public void addCounter(String name, long value) {
75+
counters.put(name, value);
76+
}
77+
78+
/**
79+
* Adds a gauge metric to the snapshot.
80+
*
81+
* <p>Gauges represent current values that can increase or decrease, such as
82+
* number of active connections, queue size, etc.</p>
83+
*
84+
* @param name the metric name
85+
* @param value the gauge value
86+
*/
87+
public void addGauge(String name, double value) {
88+
gauges.put(name, value);
89+
}
90+
91+
/**
92+
* Adds a summary metric to the snapshot.
93+
*
94+
* <p>Summaries represent computed statistics such as averages, minimums, maximums,
95+
* and percentiles. The existing {@link org.apache.zookeeper.server.metric.AvgMinMaxCounter}
96+
* and {@link org.apache.zookeeper.server.metric.AvgMinMaxPercentileCounter} classes
97+
* already compute these values and provide them as separate metrics (e.g., "latency_avg",
98+
* "latency_min", "latency_max", "latency_p99").</p>
99+
*
100+
* @param name the metric name (e.g., "request_latency_avg")
101+
* @param value the computed statistic value
102+
*/
103+
public void addSummary(String name, double value) {
104+
summaries.put(name, value);
105+
}
106+
107+
/**
108+
* Returns the total number of metrics in this snapshot.
109+
*
110+
* @return the sum of counters, gauges, and summaries
111+
*/
112+
public int getMetricCount() {
113+
return counters.size() + gauges.size() + summaries.size();
114+
}
115+
116+
/**
117+
* Returns the timestamp of this snapshot.
118+
*
119+
* @return timestamp in milliseconds since epoch
120+
*/
121+
public long getTimestamp() {
122+
return timestamp;
123+
}
124+
125+
/**
126+
* Returns the hostname of the ZooKeeper server.
127+
*
128+
* @return the hostname
129+
*/
130+
public String getHostname() {
131+
return hostname;
132+
}
133+
134+
/**
135+
* Returns the application ID.
136+
*
137+
* @return the application ID (typically "zookeeper")
138+
*/
139+
public String getAppId() {
140+
return appId;
141+
}
142+
143+
/**
144+
* Returns all counter metrics in this snapshot.
145+
*
146+
* @return an unmodifiable view of the counters map
147+
*/
148+
public Map<String, Long> getCounters() {
149+
return counters;
150+
}
151+
152+
/**
153+
* Returns all gauge metrics in this snapshot.
154+
*
155+
* @return an unmodifiable view of the gauges map
156+
*/
157+
public Map<String, Double> getGauges() {
158+
return gauges;
159+
}
160+
161+
/**
162+
* Returns all summary metrics in this snapshot.
163+
*
164+
* @return an unmodifiable view of the summaries map
165+
*/
166+
public Map<String, Double> getSummaries() {
167+
return summaries;
168+
}
169+
170+
@Override
171+
public String toString() {
172+
return String.format("MetricSnapshot{timestamp=%d, hostname='%s', appId='%s', " +
173+
"counters=%d, gauges=%d, summaries=%d}",
174+
timestamp, hostname, appId, counters.size(), gauges.size(), summaries.size());
175+
}
176+
177+
/**
178+
* Helper method to repeat a character n times (Java 8 compatible).
179+
*/
180+
private String repeatChar(char c, int count) {
181+
StringBuilder sb = new StringBuilder(count);
182+
for (int i = 0; i < count; i++) {
183+
sb.append(c);
184+
}
185+
return sb.toString();
186+
}
187+
188+
/**
189+
* Prints all metrics in this snapshot to a formatted string.
190+
*
191+
* <p>This method is useful for debugging and logging. It prints all counters,
192+
* gauges, and summaries in a human-readable format.</p>
193+
*
194+
* @return a formatted string containing all metrics
195+
*/
196+
public String printAllMetrics() {
197+
StringBuilder sb = new StringBuilder();
198+
sb.append(repeatChar('=', 80)).append("\n");
199+
sb.append("MetricSnapshot Details\n");
200+
sb.append(repeatChar('=', 80)).append("\n");
201+
sb.append(String.format("Timestamp: %d (%s)\n", timestamp,
202+
new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new java.util.Date(timestamp))));
203+
sb.append(String.format("Hostname: %s\n", hostname));
204+
sb.append(String.format("AppId: %s\n", appId));
205+
sb.append(String.format("Total Metrics: %d (Counters: %d, Gauges: %d, Summaries: %d)\n",
206+
getMetricCount(), counters.size(), gauges.size(), summaries.size()));
207+
sb.append(repeatChar('=', 80)).append("\n\n");
208+
209+
// Print Counters
210+
if (!counters.isEmpty()) {
211+
sb.append("COUNTERS (").append(counters.size()).append("):\n");
212+
sb.append(repeatChar('-', 80)).append("\n");
213+
counters.entrySet().stream()
214+
.sorted(Map.Entry.comparingByKey())
215+
.forEach(entry -> sb.append(String.format(" %-50s : %,d\n",
216+
entry.getKey(), entry.getValue())));
217+
sb.append("\n");
218+
}
219+
220+
// Print Gauges
221+
if (!gauges.isEmpty()) {
222+
sb.append("GAUGES (").append(gauges.size()).append("):\n");
223+
sb.append(repeatChar('-', 80)).append("\n");
224+
gauges.entrySet().stream()
225+
.sorted(Map.Entry.comparingByKey())
226+
.forEach(entry -> sb.append(String.format(" %-50s : %.2f\n",
227+
entry.getKey(), entry.getValue())));
228+
sb.append("\n");
229+
}
230+
231+
// Print Summaries
232+
if (!summaries.isEmpty()) {
233+
sb.append("SUMMARIES (").append(summaries.size()).append("):\n");
234+
sb.append(repeatChar('-', 80)).append("\n");
235+
summaries.entrySet().stream()
236+
.sorted(Map.Entry.comparingByKey())
237+
.forEach(entry -> sb.append(String.format(" %-50s : %.2f\n",
238+
entry.getKey(), entry.getValue())));
239+
sb.append("\n");
240+
}
241+
242+
sb.append(repeatChar('=', 80)).append("\n");
243+
return sb.toString();
244+
}
245+
}

0 commit comments

Comments
 (0)