Skip to content

Commit 7addd49

Browse files
committed
Add allocation tests
1 parent 68327bb commit 7addd49

File tree

2 files changed

+178
-4
lines changed

2 files changed

+178
-4
lines changed

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: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
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+
* Tests expected allocation (or lack thereof) for methods expected to be used on hot
32+
* paths 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")
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+
// test allocation from internal implementation (non-public API)
86+
assertNoAllocation(() -> registry.timer(id, AbstractTimerBuilder.DEFAULT_DISTRIBUTION_CONFIG,
87+
NoPauseDetector.INSTANCE));
88+
}
89+
90+
@Issue("#6670")
91+
@Test
92+
void retrieveExistingLongTaskTimer_noAllocation() {
93+
Meter.Id id = new Meter.Id("ltt", tags, null, null, Meter.Type.LONG_TASK_TIMER);
94+
// test allocation from internal implementation (non-public API)
95+
assertNoAllocation(
96+
() -> registry.more().longTaskTimer(id, LongTaskTimer.Builder.DEFAULT_DISTRIBUTION_CONFIG));
97+
}
98+
99+
@Issue("#6670")
100+
@Test
101+
void retrieveExistingCounter_noAllocation() {
102+
Meter.Id id = new Meter.Id("counter", tags, null, null, Meter.Type.COUNTER);
103+
// test allocation from internal implementation (non-public API)
104+
assertNoAllocation(() -> registry.counter(id));
105+
}
106+
107+
}
108+
109+
@Nested
110+
@DisplayName("Public API available to users")
111+
class PublicApi {
112+
113+
@Issue("#6670")
114+
@Test
115+
void retrieveExistingTimer_onlyIdAllocation() {
116+
assertBytesAllocated(newIdBytes, () -> registry.timer("timer", tags));
117+
}
118+
119+
@Issue("#6670")
120+
@Test
121+
void retrieveExistingLongTaskTimer_onlyIdAllocation() {
122+
assertBytesAllocated(newIdBytes, () -> registry.more().longTaskTimer("ltt", tags));
123+
}
124+
125+
@Issue("#6670")
126+
@Test
127+
void retrieveExistingCounter_onlyIdAllocation() {
128+
assertBytesAllocated(newIdBytes, () -> registry.counter("counter", tags));
129+
}
130+
131+
}
132+
133+
private void assertBytesAllocated(long bytes, Runnable runnable) {
134+
assertThat(measureAllocatedBytes(runnable)).isEqualTo(bytes);
135+
}
136+
137+
private void assertNoAllocation(Runnable runnable) {
138+
assertBytesAllocated(0, runnable);
139+
}
140+
141+
}

0 commit comments

Comments
 (0)