Skip to content

Commit 2eabf20

Browse files
committed
Enhancement Request 37553978 - [37149363->14.1.2.0.2] Metrics generated by Coherence should conform to OpenObservability spec (14.1.2.0 cl 114055 --> ce/14.1.2.0)
[git-p4: depot-paths = "//dev/coherence-ce/release/coherence-ce-v14.1.2.0/": change = 114079]
1 parent db88ae2 commit 2eabf20

File tree

6 files changed

+230
-10
lines changed

6 files changed

+230
-10
lines changed

prj/coherence-core/src/main/java/com/tangosol/internal/metrics/MetricsHttpHandler.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2000, 2024, Oracle and/or its affiliates.
2+
* Copyright (c) 2000, 2025, Oracle and/or its affiliates.
33
*
44
* Licensed under the Universal Permissive License v 1.0 as shown at
55
* https://oss.oracle.com/licenses/upl.
@@ -733,7 +733,7 @@ private void writeTags(Writer writer, Map<String, String> mapTags) throws IOExce
733733
.append('"');
734734
if (iterator.hasNext())
735735
{
736-
writer.append(", ");
736+
writer.append(",");
737737
}
738738
}
739739

prj/test/functional/metrics/pom.xml

+49-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<!--
3-
Copyright (c) 2000, 2024, Oracle and/or its affiliates.
3+
Copyright (c) 2000, 2025, Oracle and/or its affiliates.
44
55
Licensed under the Universal Permissive License v 1.0 as shown at
66
https://oss.oracle.com/licenses/upl.
@@ -27,6 +27,54 @@
2727
<skipTests>false</skipTests>
2828
</properties>
2929
</profile>
30+
<profile>
31+
<id>metrics-format-test</id>
32+
<activation>
33+
<activeByDefault>false</activeByDefault>
34+
</activation>
35+
<properties>
36+
<skipTests>false</skipTests>
37+
</properties>
38+
<build>
39+
<plugins>
40+
<plugin>
41+
<groupId>org.apache.maven.plugins</groupId>
42+
<artifactId>maven-failsafe-plugin</artifactId>
43+
<version>${maven.failsafe.plugin.version}</version>
44+
<executions>
45+
<execution>
46+
<goals>
47+
<goal>integration-test</goal>
48+
<goal>verify</goal>
49+
</goals>
50+
<configuration>
51+
<includes>
52+
<include>**/MetricsFormatTests.java</include>
53+
</includes>
54+
</configuration>
55+
</execution>
56+
</executions>
57+
</plugin>
58+
<plugin>
59+
<groupId>org.codehaus.mojo</groupId>
60+
<artifactId>exec-maven-plugin</artifactId>
61+
<executions>
62+
<execution>
63+
<id>Metrics Format Tests</id>
64+
<phase>verify</phase>
65+
<goals>
66+
<goal>exec</goal>
67+
</goals>
68+
</execution>
69+
</executions>
70+
<configuration>
71+
<skip>${skipTests}</skip>
72+
<executable>${project.basedir}/src/main/python/run_test.sh</executable>
73+
</configuration>
74+
</plugin>
75+
</plugins>
76+
</build>
77+
</profile>
3078
</profiles>
3179

3280
<properties>

prj/test/functional/metrics/src/main/java/metrics/MetricsFormatTests.java

+55-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2000, 2024, Oracle and/or its affiliates.
2+
* Copyright (c) 2000, 2025, Oracle and/or its affiliates.
33
*
44
* Licensed under the Universal Permissive License v 1.0 as shown at
55
* https://oss.oracle.com/licenses/upl.
@@ -20,9 +20,13 @@
2020
import org.junit.runners.Parameterized;
2121

2222
import java.io.BufferedReader;
23+
import java.io.InputStream;
2324
import java.io.InputStreamReader;
25+
import java.io.PrintWriter;
26+
2427
import java.net.HttpURLConnection;
2528
import java.net.URI;
29+
2630
import java.util.ArrayList;
2731
import java.util.Collection;
2832

@@ -132,6 +136,56 @@ public void shouldHaveCorrectNameFormat() throws Exception
132136
}
133137
}
134138

139+
/**
140+
* Just extracts metrics into the file so their format can be verified by an external tool.
141+
*
142+
* @throws Exception
143+
*/
144+
@Test
145+
public void shouldHaveCorrectFormat() throws Exception
146+
{
147+
LocalPlatform platform = LocalPlatform.get();
148+
Capture<Integer> port = new Capture<>(platform.getAvailablePorts());
149+
150+
// start Coherence with metrics configured with the test format
151+
try (CoherenceClusterMember member = platform.launch(CoherenceClusterMember.class,
152+
SystemProperty.of(f_sProperty, f_fPropertyValue),
153+
SystemProperty.of(MetricsHttpHelper.PROP_METRICS_ENABLED, true),
154+
SystemProperty.of("coherence.metrics.http.port", port),
155+
IPv4Preferred.yes(),
156+
LocalHost.only()))
157+
{
158+
Eventually.assertDeferred(() -> member.isServiceRunning(MetricsHttpHelper.getServiceName()), is(true));
159+
160+
String sURL = "http://127.0.0.1:" + port.get() + "/metrics";
161+
162+
HttpURLConnection con = (HttpURLConnection) URI.create(sURL).toURL().openConnection();
163+
con.setRequestProperty("Accept", "text/plain");
164+
con.setRequestMethod("GET");
165+
166+
int responseCode = con.getResponseCode();
167+
assertThat(responseCode, is(200));
168+
169+
try (PrintWriter writer = new PrintWriter("target/" + f_sFormat + ".metrics.txt");
170+
InputStream in = con.getInputStream())
171+
{
172+
BufferedReader isr = new BufferedReader(new InputStreamReader(in));
173+
String line;
174+
int counter = 0;
175+
while ((line = isr.readLine()) != null )
176+
{
177+
writer.print(line + "\n");
178+
// limit example size otherwise verification takes too much time to complete
179+
if (counter++ == 110)
180+
{
181+
break;
182+
}
183+
}
184+
writer.print("# EOF");
185+
}
186+
}
187+
}
188+
135189
// ----- data members ---------------------------------------------------
136190

137191
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#!/usr/bin/env bash
2+
#
3+
# Copyright (c) 2000, 2025, Oracle and/or its affiliates.
4+
#
5+
# Licensed under the Universal Permissive License v 1.0 as shown at
6+
# https://oss.oracle.com/licenses/upl.
7+
#
8+
9+
echo "Metrics Format Test"
10+
echo "$(pwd)"
11+
12+
cd src/main/python || exit 1
13+
rm -rf .pyenv || true
14+
git clone --branch v2.3.22 https://github.com/pyenv/pyenv.git .pyenv
15+
export PYENV_ROOT=$(pwd)/.pyenv
16+
export PATH=${PYENV_ROOT}/bin:${PATH}
17+
eval "$(pyenv init --path)"
18+
echo $PATH
19+
20+
${PYENV_ROOT}/bin/pyenv install 3.11
21+
${PYENV_ROOT}/bin/pyenv versions
22+
${PYENV_ROOT}/bin/pyenv global 3.11
23+
which python
24+
25+
pip install abnf
26+
27+
echo "Current path: $(pwd)"
28+
python ./verify_metrics_format.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
#!/usr/bin/env python3
2+
#
3+
# Copyright (c) 2000, 2025, Oracle and/or its affiliates.
4+
#
5+
# Licensed under the Universal Permissive License v 1.0 as shown at
6+
# https://oss.oracle.com/licenses/upl.
7+
#
8+
9+
import pathlib
10+
import os
11+
import datetime
12+
from abnf.parser import (
13+
ABNFGrammarNodeVisitor,
14+
ABNFGrammarRule,
15+
Alternation,
16+
CharValNodeVisitor,
17+
Concatenation,
18+
GrammarError,
19+
Literal,
20+
LiteralNode,
21+
Match,
22+
Node,
23+
NumValVisitor,
24+
Option,
25+
ParseCache,
26+
ParseError,
27+
Prose,
28+
Repeat,
29+
Repetition,
30+
Rule,
31+
next_longest,
32+
sorted_by_longest_match,
33+
)
34+
from urllib.request import urlopen
35+
36+
class FromFileRule(Rule):
37+
pass
38+
39+
def fetch_abnf_source():
40+
print("Fetching ABNF file...")
41+
with urlopen('https://raw.githubusercontent.com/prometheus/OpenMetrics/refs/heads/main/specification/OpenMetrics.md') as response:
42+
md = response.read().decode('utf-8')
43+
abnf = []
44+
copy = False
45+
for line in md.split('\n'):
46+
if "~~~~ abnf" in line:
47+
copy = True
48+
continue
49+
elif "~~~~" in line and copy:
50+
break
51+
elif copy:
52+
abnf.append(line)
53+
if len(abnf) < 30:
54+
raise Exception("Failed to extract ABNF grammar from the source document")
55+
return '\n'.join(abnf)
56+
57+
def test_file(parser, filename):
58+
print(datetime.datetime.now())
59+
print(f"Processing file: {filename}")
60+
metrics = pathlib.Path(filename).read_text()
61+
parser.parse_all(metrics)
62+
print(datetime.datetime.now())
63+
print(f"Parsed successfully: {filename}")
64+
65+
abnf = fetch_abnf_source()
66+
print("Writing ABNF file...")
67+
with open("../../../target/metrics.abnf", "w") as f:
68+
f.write(abnf)
69+
70+
print("Loading ABNF file...")
71+
path = pathlib.Path("../../../target/metrics.abnf")
72+
73+
print("Creating rule from the file...")
74+
FromFileRule.from_file(path)
75+
76+
print("Creating parser...")
77+
parser = FromFileRule.get("exposition")
78+
79+
files = os.listdir("../../../target/")
80+
print("Searching for metrics files")
81+
matching_files = [f for f in files if f.endswith(".metrics.txt")]
82+
if len(matching_files) == 0:
83+
raise Exception("Metrics files to check are missing")
84+
85+
for filename in matching_files:
86+
if filename == "Dot Delimited.metrics.txt":
87+
# dot delimited metrics don't conform to the specs
88+
continue
89+
print(f"Test file {filename}")
90+
test_file(parser, "../../../target/" + filename)

prj/test/unit/coherence-core-tests/src/test/java/com/tangosol/internal/metrics/PrometheusFormatterTest.java

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
/*
2-
* Copyright (c) 2000, 2022, Oracle and/or its affiliates.
2+
* Copyright (c) 2000, 2025, Oracle and/or its affiliates.
33
*
44
* Licensed under the Universal Permissive License v 1.0 as shown at
5-
* http://oss.oracle.com/licenses/upl.
5+
* https://oss.oracle.com/licenses/upl.
66
*/
77
package com.tangosol.internal.metrics;
88

@@ -42,7 +42,7 @@ public void testMetricWithTags() throws IOException
4242
new PrometheusFormatter(false, MetricsHttpHandler.Format.Legacy, Collections.singletonList(metric))
4343
.writeMetrics(writer);
4444

45-
String expected = "vendor:coherence_cluster_size{cluster=\"testCluster\", node_id=\"1\", site=\"testSite\"} 3\n";
45+
String expected = "vendor:coherence_cluster_size{cluster=\"testCluster\",node_id=\"1\",site=\"testSite\"} 3\n";
4646

4747
assertThat(writer.toString(), equalTo(expected));
4848
}
@@ -56,7 +56,7 @@ public void testMetricWithMicroprofileName() throws IOException
5656
new PrometheusFormatter(false, MetricsHttpHandler.Format.Microprofile, Collections.singletonList(metric))
5757
.writeMetrics(writer);
5858

59-
String expected = "vendor_Coherence_ClusterSize{cluster=\"testCluster\", node_id=\"1\", site=\"testSite\"} 3\n";
59+
String expected = "vendor_Coherence_ClusterSize{cluster=\"testCluster\",node_id=\"1\",site=\"testSite\"} 3\n";
6060

6161
assertThat(writer.toString(), equalTo(expected));
6262
}
@@ -70,7 +70,7 @@ public void testMetricWithDotFormattedName() throws IOException
7070
new PrometheusFormatter(false, MetricsHttpHandler.Format.DotDelimited, Collections.singletonList(metric))
7171
.writeMetrics(writer);
7272

73-
String expected = "coherence.cluster.size{cluster=\"testCluster\", node_id=\"1\", site=\"testSite\"} 3\n";
73+
String expected = "coherence.cluster.size{cluster=\"testCluster\",node_id=\"1\",site=\"testSite\"} 3\n";
7474

7575
assertThat(writer.toString(), equalTo(expected));
7676
}
@@ -86,7 +86,7 @@ public void testExtendedMetricWithTags() throws IOException
8686

8787
String expected = "# TYPE vendor:coherence_cluster_size gauge\n"
8888
+ "# HELP vendor:coherence_cluster_size Cluster size\n"
89-
+ "vendor:coherence_cluster_size{cluster=\"testCluster\", node_id=\"1\", site=\"testSite\"} 3\n";
89+
+ "vendor:coherence_cluster_size{cluster=\"testCluster\",node_id=\"1\",site=\"testSite\"} 3\n";
9090

9191
assertThat(writer.toString(), equalTo(expected));
9292
}

0 commit comments

Comments
 (0)