|
2 | 2 |
|
3 | 3 | import io.opentelemetry.api.GlobalOpenTelemetry;
|
4 | 4 | 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; |
5 | 19 | import org.springframework.boot.SpringApplication;
|
6 | 20 | import org.springframework.boot.autoconfigure.SpringBootApplication;
|
7 | 21 | import org.springframework.context.annotation.Bean;
|
8 | 22 |
|
9 | 23 | @SpringBootApplication
|
10 | 24 | public class Application {
|
11 | 25 |
|
| 26 | + private static final Logger logger = LoggerFactory.getLogger(Application.class); |
| 27 | + |
12 | 28 | public static void main(String[] args) {
|
13 | 29 | SpringApplication.run(Application.class, args);
|
14 | 30 | }
|
15 | 31 |
|
16 | 32 | @Bean
|
17 | 33 | 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 | + } |
19 | 122 | }
|
20 | 123 | }
|
0 commit comments