Skip to content

Commit 1952d57

Browse files
authored
apply full metric-kind mapping for prometheus metrics IT assertions (#1293)
1 parent 8bac82a commit 1952d57

3 files changed

Lines changed: 335 additions & 146 deletions

File tree

  • 4-governance/dubbo-samples-metrics-prometheus
    • dubbo-samples-metrics-prometheus-consumer/src/test/java/org/apache/dubbo/samples/metrics/prometheus/consumer
    • dubbo-samples-metrics-prometheus-interface/src/main/java/org/apache/dubbo/samples/metrics/prometheus/util
    • dubbo-samples-metrics-prometheus-provider/src/test/java/org/apache/dubbo/samples/metrics/prometheus/provider

4-governance/dubbo-samples-metrics-prometheus/dubbo-samples-metrics-prometheus-consumer/src/test/java/org/apache/dubbo/samples/metrics/prometheus/consumer/ConsumerMetricsIT.java

Lines changed: 82 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
package org.apache.dubbo.samples.metrics.prometheus.consumer;
1919

20+
import org.apache.dubbo.samples.metrics.prometheus.util.PrometheusMetricAssert;
2021
import org.apache.http.client.methods.CloseableHttpResponse;
2122
import org.apache.http.client.methods.HttpGet;
2223
import org.apache.http.impl.client.CloseableHttpClient;
@@ -40,89 +41,96 @@
4041
public class ConsumerMetricsIT {
4142
private static final Logger logger = LoggerFactory.getLogger(ConsumerMetricsIT.class);
4243

43-
4444
private final String port = "20889";
4545

46-
private final List<String> metricKeys = new ArrayList();
46+
private final List<MetricExpectation> metricExpectations = new ArrayList<>();
4747

4848
@Before
4949
public void setUp() {
50-
add("dubbo_application_info_total");
50+
addCounter("dubbo.application.info.total");
5151

5252
//config
53-
add("dubbo_configcenter_total");
54-
55-
add("dubbo_provider_rt_milliseconds_p99");
56-
add("dubbo_provider_requests_total_aggregate");
57-
add("dubbo_provider_rt_milliseconds_p90");
58-
add("dubbo_provider_rt_max_milliseconds_aggregate");
59-
add("dubbo_provider_requests_succeed_total");
60-
add("dubbo_provider_requests_business_failed_aggregate");
61-
add("dubbo_provider_requests_business_failed_total");
62-
add("dubbo_provider_requests_total");
63-
add("dubbo_provider_requests_failed_service_unavailable_total_aggregate");
64-
add("dubbo_provider_requests_limit_aggregate");
65-
add("dubbo_provider_qps_total");
66-
add("dubbo_provider_rt_milliseconds_p50");
67-
add("dubbo_provider_requests_processing_total");
68-
add("dubbo_provider_requests_failed_total");
69-
add("dubbo_provider_requests_succeed_aggregate");
70-
add("dubbo_provider_rt_milliseconds_p95");
71-
add("dubbo_provider_requests_failed_aggregate");
72-
add("dubbo_provider_rt_avg_milliseconds_aggregate");
73-
add("dubbo_provider_requests_failed_total_aggregate");
74-
add("dubbo_provider_requests_failed_network_total_aggregate");
75-
add("dubbo_provider_rt_min_milliseconds_aggregate");
76-
add("dubbo_provider_requests_failed_codec_total_aggregate");
77-
add("dubbo_provider_requests_timeout_failed_aggregate");
53+
addGauge("dubbo.configcenter.total");
54+
55+
addGauge("dubbo.provider.rt.milliseconds.p99");
56+
addGauge("dubbo.provider.requests.total.aggregate");
57+
addGauge("dubbo.provider.rt.milliseconds.p90");
58+
addGauge("dubbo.provider.rt.max.milliseconds.aggregate");
59+
addCounter("dubbo.provider.requests.succeed.total");
60+
addGauge("dubbo.provider.requests.business.failed.aggregate");
61+
addCounter("dubbo.provider.requests.business.failed.total");
62+
addCounter("dubbo.provider.requests.total");
63+
addGauge("dubbo.provider.requests.failed.service.unavailable.total.aggregate");
64+
addGauge("dubbo.provider.requests.limit.aggregate");
65+
addGauge("dubbo.provider.qps.total");
66+
addGauge("dubbo.provider.rt.milliseconds.p50");
67+
addGauge("dubbo.provider.requests.processing.total");
68+
addCounter("dubbo.provider.requests.failed.total");
69+
addGauge("dubbo.provider.requests.succeed.aggregate");
70+
addGauge("dubbo.provider.rt.milliseconds.p95");
71+
addGauge("dubbo.provider.requests.failed.aggregate");
72+
addGauge("dubbo.provider.rt.avg.milliseconds.aggregate");
73+
addGauge("dubbo.provider.requests.failed.total.aggregate");
74+
addGauge("dubbo.provider.requests.failed.network.total.aggregate");
75+
addGauge("dubbo.provider.rt.min.milliseconds.aggregate");
76+
addGauge("dubbo.provider.requests.failed.codec.total.aggregate");
77+
addGauge("dubbo.provider.requests.timeout.failed.aggregate");
7878

7979
//consumer
80-
add("dubbo_consumer_requests_failed_service_unavailable_total");
80+
addCounter("dubbo.consumer.requests.failed.service.unavailable.total");
8181

8282
//register
83-
add("dubbo_registry_subscribe_num_total");
84-
add("dubbo_registry_register_requests_succeed_total");
85-
add("dubbo_register_rt_milliseconds_last");
86-
add("dubbo_registry_register_requests_total");
87-
add("dubbo_register_rt_milliseconds_avg");
88-
add("dubbo_registry_subscribe_num_succeed_total");
89-
add("dubbo_register_rt_milliseconds_max");
90-
add("dubbo_register_rt_milliseconds_min");
91-
add("dubbo_register_rt_milliseconds_sum");
92-
add("dubbo_registry_notify_requests_total");
93-
add("dubbo_registry_register_requests_failed_total");
94-
add("dubbo_registry_subscribe_num_failed_total");
95-
add("dubbo_register_rt_milliseconds_max");
96-
add("dubbo_registry_register_requests_succeed_total");
83+
addGauge("dubbo.registry.subscribe.num.total");
84+
addGauge("dubbo.registry.register.requests.succeed.total");
85+
addGauge("dubbo.register.rt.milliseconds.last");
86+
addGauge("dubbo.registry.register.requests.total");
87+
addGauge("dubbo.register.rt.milliseconds.avg");
88+
addGauge("dubbo.registry.subscribe.num.succeed.total");
89+
addGauge("dubbo.register.rt.milliseconds.max");
90+
addGauge("dubbo.register.rt.milliseconds.min");
91+
addGauge("dubbo.register.rt.milliseconds.sum");
92+
addGauge("dubbo.registry.notify.requests.total");
93+
addGauge("dubbo.registry.register.requests.failed.total");
94+
addGauge("dubbo.registry.subscribe.num.failed.total");
95+
addGauge("dubbo.register.rt.milliseconds.max");
96+
addGauge("dubbo.registry.register.requests.succeed.total");
9797

9898
//metadata
99-
add("dubbo_metadata_subscribe_num_failed_total");
100-
add("dubbo_metadata_push_num_failed_total");
101-
add("dubbo_metadata_subscribe_num_total");
102-
add("dubbo_metadata_subscribe_num_succeed_total");
103-
add("dubbo_metadata_push_num_succeed_total");
104-
add("dubbo_metadata_push_num_total");
99+
addGauge("dubbo.metadata.subscribe.num.failed.total");
100+
addGauge("dubbo.metadata.push.num.failed.total");
101+
addGauge("dubbo.metadata.subscribe.num.total");
102+
addGauge("dubbo.metadata.subscribe.num.succeed.total");
103+
addGauge("dubbo.metadata.push.num.succeed.total");
104+
addGauge("dubbo.metadata.push.num.total");
105105

106106
//thread
107-
add("dubbo_thread_pool_thread_count");
108-
add("dubbo_thread_pool_largest_size");
109-
add("dubbo_thread_pool_active_size");
110-
add("dubbo_thread_pool_queue_size");
111-
add("dubbo_thread_pool_core_size");
112-
add("dubbo_thread_pool_max_size");
107+
addGauge("dubbo.thread.pool.thread.count");
108+
addGauge("dubbo.thread.pool.largest.size");
109+
addGauge("dubbo.thread.pool.active.size");
110+
addGauge("dubbo.thread.pool.queue.size");
111+
addGauge("dubbo.thread.pool.core.size");
112+
addGauge("dubbo.thread.pool.max.size");
113+
}
114+
115+
// Use explicit kind only when it changes cross-registry compatible names.
116+
// Example: `dubbo.application.info.total` resolves to `dubbo_application_total` on new clients.
117+
private void addCounter(String rawName) {
118+
metricExpectations.add(new MetricExpectation(rawName, PrometheusMetricAssert.MetricKind.COUNTER));
113119
}
114120

115-
private void add(String dubboStoreProviderInterfaceRtMillisecondsMin) {
116-
metricKeys.add(dubboStoreProviderInterfaceRtMillisecondsMin);
121+
// Example: `...qps.total` is exposed without `_total` on new clients when treated as a gauge.
122+
private void addGauge(String rawName) {
123+
metricExpectations.add(new MetricExpectation(rawName, PrometheusMetricAssert.MetricKind.GAUGE));
117124
}
118125

126+
119127
@Test
120128
public void test() throws Exception {
121129
new EmbeddedZooKeeper(2181, false).start();
122130

123131
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("dubbo-demo-consumer.xml");
124132

125-
List<String> notExistedList = new ArrayList<>();
133+
List<MetricExpectation> notExistedList = new ArrayList<>();
126134
HttpGet request = new HttpGet("http://localhost:" + port + "/metrics");
127135
try (CloseableHttpClient client = HttpClients.createDefault()) {
128136
// retry 3 times as all metrics data are not collected immediately.
@@ -132,9 +140,11 @@ public void test() throws Exception {
132140
InputStream inputStream = response.getEntity().getContent();
133141
String text = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)).lines()
134142
.collect(Collectors.joining("\n"));
135-
for (String metricKey : metricKeys) {
136-
if (!text.contains(metricKey)) {
137-
notExistedList.add(metricKey);
143+
for (MetricExpectation expectation : metricExpectations) {
144+
try {
145+
PrometheusMetricAssert.assertDubboMetricExposed(text, expectation.rawName, expectation.kind);
146+
} catch (AssertionError ignored) {
147+
notExistedList.add(expectation);
138148
}
139149
}
140150
if (notExistedList.isEmpty()) {
@@ -146,10 +156,19 @@ public void test() throws Exception {
146156
Assert.fail(e.getMessage());
147157
}
148158
context.stop();
149-
for (String metricKey : notExistedList) {
150-
logger.error("metric key:{} doesn't exist", metricKey);
159+
for (MetricExpectation metric : notExistedList) {
160+
logger.error("metric rawName:{} with kind:{} doesn't exist", metric.rawName, metric.kind);
151161
}
152162
Assert.assertTrue(notExistedList.isEmpty());
153163
}
154164

165+
private static final class MetricExpectation {
166+
private final String rawName;
167+
private final PrometheusMetricAssert.MetricKind kind;
168+
169+
private MetricExpectation(String rawName, PrometheusMetricAssert.MetricKind kind) {
170+
this.rawName = rawName;
171+
this.kind = kind;
172+
}
173+
}
155174
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.dubbo.samples.metrics.prometheus.util;
19+
20+
import java.util.LinkedHashSet;
21+
import java.util.Set;
22+
import java.util.regex.Pattern;
23+
24+
public final class PrometheusMetricAssert {
25+
26+
private static final String[] RESERVED_SUFFIXES = {
27+
"_total", "_created", "_bucket", "_info",
28+
".total", ".created", ".bucket", ".info"
29+
};
30+
31+
private PrometheusMetricAssert() {
32+
}
33+
34+
public enum MetricKind {
35+
COUNTER,
36+
GAUGE,
37+
INFO,
38+
HISTOGRAM,
39+
SUMMARY,
40+
UNKNOWN
41+
}
42+
43+
public static void assertDubboMetricExposed(String scrapeText, String dubboRawName, MetricKind kind) {
44+
Set<String> compatibleNames = resolveCompatibleNames(dubboRawName, kind);
45+
boolean matched = compatibleNames.stream().anyMatch(name -> containsMetricLine(scrapeText, name));
46+
if (!matched) {
47+
throw new AssertionError("Expected dubbo metric to be exposed. rawName=" + dubboRawName
48+
+ ", kind=" + kind
49+
+ ", compatibleNames=" + compatibleNames);
50+
}
51+
}
52+
53+
public static Set<String> resolveCompatibleNames(String dubboRawName, MetricKind kind) {
54+
Set<String> names = new LinkedHashSet<>();
55+
names.add(legacyPrometheusName(dubboRawName, kind));
56+
names.add(newPrometheusName(dubboRawName, kind));
57+
return names;
58+
}
59+
60+
private static boolean containsMetricLine(String scrapeText, String metricName) {
61+
Pattern pattern = Pattern.compile("(?m)^" + Pattern.quote(metricName) + "(\\{|\\s).*");
62+
return pattern.matcher(scrapeText).find();
63+
}
64+
65+
private static String legacyPrometheusName(String rawName, MetricKind kind) {
66+
String conventionName = snakeCase(rawName);
67+
if (kind == MetricKind.COUNTER && !conventionName.endsWith("_total")) {
68+
conventionName += "_total";
69+
}
70+
71+
String sanitized = conventionName.replaceAll("[^a-zA-Z0-9_:]", "_");
72+
if (sanitized.isEmpty()) {
73+
return "m_";
74+
}
75+
if (!Character.isLetter(sanitized.charAt(0))) {
76+
return "m_" + sanitized;
77+
}
78+
return sanitized;
79+
}
80+
81+
private static String snakeCase(String rawName) {
82+
StringBuilder result = new StringBuilder();
83+
char[] chars = rawName.toCharArray();
84+
for (int i = 0; i < chars.length; i++) {
85+
char current = chars[i];
86+
if (Character.isUpperCase(current)) {
87+
if (i > 0 && Character.isLowerCase(chars[i - 1])) {
88+
result.append('_');
89+
}
90+
result.append(Character.toLowerCase(current));
91+
} else if (Character.isLetterOrDigit(current) || current == '_') {
92+
result.append(current);
93+
} else {
94+
result.append('_');
95+
}
96+
}
97+
return result.toString();
98+
}
99+
100+
private static String newPrometheusName(String rawName, MetricKind kind) {
101+
String baseName = stripReservedSuffixesRepeatedly(rawName);
102+
String prometheusName = sanitizePrometheusName(baseName.replace('.', '_'));
103+
104+
switch (kind) {
105+
case COUNTER:
106+
return appendSuffixIfAbsent(prometheusName, "_total");
107+
case INFO:
108+
return appendSuffixIfAbsent(prometheusName, "_info");
109+
case GAUGE:
110+
case UNKNOWN:
111+
case HISTOGRAM:
112+
case SUMMARY:
113+
default:
114+
return prometheusName;
115+
}
116+
}
117+
118+
private static String appendSuffixIfAbsent(String metricName, String suffix) {
119+
if (metricName.endsWith(suffix)) {
120+
return metricName;
121+
}
122+
return metricName + suffix;
123+
}
124+
125+
private static String stripReservedSuffixesRepeatedly(String rawName) {
126+
String value = rawName;
127+
boolean stripped;
128+
do {
129+
stripped = false;
130+
for (String suffix : RESERVED_SUFFIXES) {
131+
if (value.endsWith(suffix)) {
132+
value = value.substring(0, value.length() - suffix.length());
133+
stripped = true;
134+
break;
135+
}
136+
}
137+
} while (stripped);
138+
return value;
139+
}
140+
141+
private static String sanitizePrometheusName(String value) {
142+
String sanitized = value.replaceAll("[^a-zA-Z0-9_:]", "_");
143+
if (sanitized.isEmpty()) {
144+
return "m_";
145+
}
146+
if (!Character.isLetter(sanitized.charAt(0))) {
147+
return "m_" + sanitized;
148+
}
149+
return sanitized;
150+
}
151+
}

0 commit comments

Comments
 (0)