Skip to content

Commit c295ad4

Browse files
committed
Prototype thread cpu time
1 parent 5039103 commit c295ad4

File tree

2 files changed

+105
-1
lines changed

2 files changed

+105
-1
lines changed

javaagent/build.gradle.kts

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ val extension = configurations.create("extension")
2121
dependencies {
2222
implementation(platform(SpringBootPlugin.BOM_COORDINATES))
2323
implementation("io.opentelemetry:opentelemetry-api")
24+
implementation("io.opentelemetry:opentelemetry-sdk-common")
2425

2526
//spring modules
2627
implementation("org.springframework.boot:spring-boot-starter-web")

javaagent/src/main/java/io/opentelemetry/example/javagent/Application.java

+104-1
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,122 @@
22

33
import io.opentelemetry.api.GlobalOpenTelemetry;
44
import io.opentelemetry.api.OpenTelemetry;
5+
import io.opentelemetry.api.common.AttributeKey;
6+
import io.opentelemetry.api.common.Attributes;
7+
import io.opentelemetry.api.metrics.Meter;
8+
import io.opentelemetry.sdk.internal.GlobUtil;
9+
import java.lang.management.ManagementFactory;
10+
import java.lang.management.ThreadInfo;
11+
import java.lang.management.ThreadMXBean;
12+
import java.util.HashMap;
13+
import java.util.List;
14+
import java.util.Map;
15+
import java.util.function.Predicate;
16+
import java.util.stream.Stream;
17+
import org.slf4j.Logger;
18+
import org.slf4j.LoggerFactory;
519
import org.springframework.boot.SpringApplication;
620
import org.springframework.boot.autoconfigure.SpringBootApplication;
721
import org.springframework.context.annotation.Bean;
822

923
@SpringBootApplication
1024
public class Application {
1125

26+
private static final Logger logger = LoggerFactory.getLogger(Application.class);
27+
1228
public static void main(String[] args) {
1329
SpringApplication.run(Application.class, args);
1430
}
1531

1632
@Bean
1733
public OpenTelemetry openTelemetry() {
18-
return GlobalOpenTelemetry.get();
34+
OpenTelemetry openTelemetry = GlobalOpenTelemetry.get();
35+
36+
registerThreadUsageMonitor(openTelemetry);
37+
38+
return openTelemetry;
39+
}
40+
41+
private static void registerThreadUsageMonitor(OpenTelemetry openTelemetry) {
42+
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
43+
Meter meter = openTelemetry.getMeter("meter");
44+
AttributeKey<String> cpuModeKey = AttributeKey.stringKey("cpu.mode");
45+
AttributeKey<String> threadNameTemplateKey = AttributeKey.stringKey("thread.name_template");
46+
47+
meter
48+
.counterBuilder("jvm.thread.time")
49+
.setUnit("s")
50+
.ofDoubles()
51+
.buildWithCallback(
52+
observable -> {
53+
Map<String, Long> userCpuTimeByThreadTemplate = new HashMap<>();
54+
Map<String, Long> systemCputTimeByThreadTemplate = new HashMap<>();
55+
56+
for (ThreadInfo threadInfo : threadMXBean.getThreadInfo(threadMXBean.getAllThreadIds(), 0)) {
57+
String threadNameTemplate = threadNameTemplate(threadInfo.getThreadName());
58+
long threadId = threadInfo.getThreadId();
59+
long totalCpuTime = threadMXBean.getThreadCpuTime(threadId);
60+
long userCpuTime = threadMXBean.getThreadUserTime(threadId);
61+
if (totalCpuTime > -1 && userCpuTime > -1) {
62+
userCpuTimeByThreadTemplate.compute(
63+
threadNameTemplate,
64+
(attributes, value) -> value == null ? userCpuTime : userCpuTime + value);
65+
66+
long systemCpuTime = totalCpuTime - userCpuTime;
67+
systemCputTimeByThreadTemplate.compute(
68+
threadNameTemplate,
69+
(attributes, value) -> value == null ? systemCpuTime : systemCpuTime + value);
70+
}
71+
}
72+
73+
userCpuTimeByThreadTemplate.forEach(
74+
(threadNameTemplate, value) ->
75+
observable.record(
76+
toSeconds(value),
77+
Attributes.of(
78+
cpuModeKey, "user", threadNameTemplateKey, threadNameTemplate)));
79+
systemCputTimeByThreadTemplate.forEach(
80+
(threadNameTemplate, value) ->
81+
observable.record(
82+
toSeconds(value),
83+
Attributes.of(
84+
cpuModeKey, "system", threadNameTemplateKey, threadNameTemplate)));
85+
});
86+
}
87+
88+
private static double toSeconds(long ns) {
89+
return ns / 1.0e9;
90+
}
91+
92+
// Collection of well known thread name patterns to match against
93+
private static final List<GlobPatternAndPredicate> patternAndPredicates =
94+
Stream.of(
95+
"http-nio-*-exec-*",
96+
"http-nio-*-*",
97+
"BatchSpanProcessor_WorkerThread-*",
98+
"BatchLogRecordProcessor_WorkerThread-*",
99+
"okhttp-dispatch-*",
100+
"Catalina-utility-*")
101+
.map(GlobPatternAndPredicate::new)
102+
.toList();
103+
104+
private static String threadNameTemplate(String threadName) {
105+
for (GlobPatternAndPredicate patternAndPredicate : patternAndPredicates) {
106+
if (patternAndPredicate.predicate.test(threadName)) {
107+
return patternAndPredicate.globPattern;
108+
}
109+
}
110+
logger.info("Unmatched thread name: " + threadName);
111+
return "other";
112+
}
113+
114+
private static class GlobPatternAndPredicate {
115+
private final String globPattern;
116+
private final Predicate<String> predicate;
117+
118+
private GlobPatternAndPredicate(String globPattern) {
119+
this.globPattern = globPattern;
120+
this.predicate = GlobUtil.toGlobPatternPredicate(globPattern);
121+
}
19122
}
20123
}

0 commit comments

Comments
 (0)