Skip to content

Commit b1336c5

Browse files
committed
Add allocation tests
1 parent 68327bb commit b1336c5

File tree

3 files changed

+180
-9
lines changed

3 files changed

+180
-9
lines changed

benchmarks/benchmarks-core/src/jmh/java/io/micrometer/benchmark/core/ObservationBenchmark.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,11 @@ public boolean supportsContext(Observation.Context context) {
8080
};
8181
}
8282

83-
// @TearDown
84-
// public void tearDown() {
85-
// System.out.println("Meters:");
86-
// System.out.println(meterRegistry.getMetersAsString());
87-
// }
83+
@TearDown
84+
public void tearDown() {
85+
System.out.println("Meters:");
86+
System.out.println(meterRegistry.getMetersAsString());
87+
}
8888

8989
@Benchmark
9090
public void baseline() {

micrometer-core/src/main/java/io/micrometer/core/instrument/MeterRegistry.java

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -456,7 +456,7 @@ public RequiredSearch get(String name) {
456456
* @return A new or existing counter.
457457
*/
458458
public Counter counter(String name, Iterable<Tag> tags) {
459-
return Counter.builder(name).tags(tags).register(this);
459+
return counter(name, Tags.of(tags));
460460
}
461461

462462
/**
@@ -470,6 +470,17 @@ public Counter counter(String name, String... tags) {
470470
return counter(name, Tags.of(tags));
471471
}
472472

473+
/**
474+
* Tracks a monotonically increasing value.
475+
* @param name The base metric name
476+
* @param tags Sequence of dimensions for breaking down the name.
477+
* @return A new or existing counter.
478+
* @since 1.16.0
479+
*/
480+
public Counter counter(String name, Tags tags) {
481+
return counter(new Meter.Id(name, tags, null, null, Meter.Type.COUNTER));
482+
}
483+
473484
/**
474485
* Measures the distribution of samples.
475486
* @param name The base metric name
@@ -498,8 +509,7 @@ public DistributionSummary summary(String name, String... tags) {
498509
* @return A new or existing timer.
499510
*/
500511
public Timer timer(String name, Iterable<Tag> tags) {
501-
return this.timer(new Meter.Id(name, Tags.of(tags), null, null, Meter.Type.TIMER),
502-
AbstractTimerBuilder.DEFAULT_DISTRIBUTION_CONFIG, pauseDetector);
512+
return this.timer(name, Tags.of(tags));
503513
}
504514

505515
/**
@@ -513,6 +523,18 @@ public Timer timer(String name, String... tags) {
513523
return timer(name, Tags.of(tags));
514524
}
515525

526+
/**
527+
* Measures the time taken for short tasks and the count of these tasks.
528+
* @param name The base metric name
529+
* @param tags Sequence of dimensions for breaking down the name.
530+
* @return A new or existing timer.
531+
* @since 1.16.0
532+
*/
533+
public Timer timer(String name, Tags tags) {
534+
return this.timer(new Meter.Id(name, tags, null, null, Meter.Type.TIMER),
535+
AbstractTimerBuilder.DEFAULT_DISTRIBUTION_CONFIG, pauseDetector);
536+
}
537+
516538
/**
517539
* Access to less frequently used meter types and patterns.
518540
* @return Access to additional meter types and patterns.
@@ -1093,7 +1115,18 @@ public LongTaskTimer longTaskTimer(String name, String... tags) {
10931115
* @return A new or existing long task timer.
10941116
*/
10951117
public LongTaskTimer longTaskTimer(String name, Iterable<Tag> tags) {
1096-
return longTaskTimer(new Meter.Id(name, Tags.of(tags), null, null, Meter.Type.LONG_TASK_TIMER),
1118+
return longTaskTimer(name, Tags.of(tags));
1119+
}
1120+
1121+
/**
1122+
* Measures the time taken for long tasks.
1123+
* @param name Name of the long task timer being registered.
1124+
* @param tags Sequence of dimensions for breaking down the name.
1125+
* @return A new or existing long task timer.
1126+
* @since 1.16.0
1127+
*/
1128+
public LongTaskTimer longTaskTimer(String name, Tags tags) {
1129+
return longTaskTimer(new Meter.Id(name, tags, null, null, Meter.Type.LONG_TASK_TIMER),
10971130
LongTaskTimer.Builder.DEFAULT_DISTRIBUTION_CONFIG);
10981131
}
10991132

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/*
2+
* Copyright 2025 VMware, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.micrometer.core.instrument;
17+
18+
import com.sun.management.ThreadMXBean;
19+
import io.micrometer.core.Issue;
20+
import io.micrometer.core.instrument.distribution.pause.NoPauseDetector;
21+
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
22+
import org.junit.jupiter.api.*;
23+
import org.junit.jupiter.api.condition.DisabledIfSystemProperty;
24+
25+
import java.lang.management.ManagementFactory;
26+
27+
import static io.micrometer.core.instrument.MeterRegistryAllocationTest.JAVA_VM_NAME_J9_REGEX;
28+
import static org.assertj.core.api.Assertions.assertThat;
29+
30+
/**
31+
* Test expected allocation (or lack thereof) for methods expected to be used on hot paths
32+
* from {@link MeterRegistry}.
33+
*/
34+
@DisabledIfSystemProperty(named = "java.vm.name", matches = JAVA_VM_NAME_J9_REGEX,
35+
disabledReason = "Sun ThreadMXBean with allocation counter not available on J9 JVM")
36+
class MeterRegistryAllocationTest {
37+
38+
// Should match "Eclipse OpenJ9 VM" and "IBM J9 VM"
39+
static final String JAVA_VM_NAME_J9_REGEX = ".*J9 VM$";
40+
41+
MeterRegistry registry = new SimpleMeterRegistry();
42+
43+
Tags tags = Tags.of("a", "b", "c", "d");
44+
45+
static ThreadMXBean threadMXBean = (ThreadMXBean) ManagementFactory.getThreadMXBean();
46+
47+
/**
48+
* Number of bytes allocated when constructing a Meter.Id. This can vary by JVM and
49+
* configuration, which is why we measure it programmatically. For most common cases,
50+
* it should be 40 bytes.
51+
*/
52+
static long newIdBytes;
53+
54+
@BeforeAll
55+
static void setup() {
56+
// Construct once here so any static initialization doesn't get measured.
57+
new Meter.Id("name", Tags.empty(), null, null, Meter.Type.OTHER);
58+
newIdBytes = measureAllocatedBytes(() -> new Meter.Id("name", Tags.empty(), null, null, Meter.Type.OTHER));
59+
}
60+
61+
private static long measureAllocatedBytes(Runnable runnable) {
62+
long currentThreadId = Thread.currentThread().getId();
63+
long allocatedBytesBefore = threadMXBean.getThreadAllocatedBytes(currentThreadId);
64+
65+
runnable.run();
66+
67+
return threadMXBean.getThreadAllocatedBytes(currentThreadId) - allocatedBytesBefore;
68+
}
69+
70+
@BeforeEach
71+
void setUp() {
72+
registry.timer("timer", tags);
73+
LongTaskTimer.builder("ltt").tags(tags).register(registry);
74+
Counter.builder("counter").tags(tags).register(registry);
75+
}
76+
77+
@Nested
78+
@DisplayName("Internal implementation details")
79+
class Internal {
80+
81+
@Issue("#6670")
82+
@Test
83+
void retrieveExistingTimer_noAllocation() {
84+
Meter.Id id = new Meter.Id("timer", tags, null, null, Meter.Type.TIMER);
85+
assertNoAllocation(() -> registry.timer(id, AbstractTimerBuilder.DEFAULT_DISTRIBUTION_CONFIG,
86+
NoPauseDetector.INSTANCE));
87+
}
88+
89+
@Issue("#6670")
90+
@Test
91+
void retrieveExistingLongTaskTimer_noAllocation() {
92+
Meter.Id id = new Meter.Id("ltt", tags, null, null, Meter.Type.LONG_TASK_TIMER);
93+
assertNoAllocation(
94+
() -> registry.more().longTaskTimer(id, LongTaskTimer.Builder.DEFAULT_DISTRIBUTION_CONFIG));
95+
}
96+
97+
@Issue("#6670")
98+
@Test
99+
void retrieveExistingCounter_noAllocation() {
100+
Meter.Id id = new Meter.Id("counter", tags, null, null, Meter.Type.COUNTER);
101+
assertNoAllocation(() -> registry.counter(id));
102+
}
103+
104+
}
105+
106+
@Nested
107+
@DisplayName("Public API available to users")
108+
class PublicApi {
109+
110+
@Issue("#6670")
111+
@Test
112+
void retrieveExistingTimer_onlyIdAllocation() {
113+
assertBytesAllocated(newIdBytes, () -> registry.timer("timer", tags));
114+
}
115+
116+
@Issue("#6670")
117+
@Test
118+
void retrieveExistingLongTaskTimer_onlyIdAllocation() {
119+
assertBytesAllocated(newIdBytes, () -> registry.more().longTaskTimer("ltt", tags));
120+
}
121+
122+
@Issue("#6670")
123+
@Test
124+
void retrieveExistingCounter_onlyIdAllocation() {
125+
assertBytesAllocated(newIdBytes, () -> registry.counter("counter", tags));
126+
}
127+
128+
}
129+
130+
private void assertBytesAllocated(long bytes, Runnable runnable) {
131+
assertThat(measureAllocatedBytes(runnable)).isEqualTo(bytes);
132+
}
133+
134+
private void assertNoAllocation(Runnable runnable) {
135+
assertBytesAllocated(0, runnable);
136+
}
137+
138+
}

0 commit comments

Comments
 (0)