Skip to content

Commit 3c2b290

Browse files
authored
Merge pull request #269 from mayswind/develop
support all of unified JVM logging decorations
2 parents 9d63146 + 7118905 commit 3c2b290

File tree

4 files changed

+483
-22
lines changed

4 files changed

+483
-22
lines changed

pom.xml

+4
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,10 @@
111111
<name>Tony Mancill</name>
112112
<url>https://github.com/tmancill</url>
113113
</developer>
114+
<developer>
115+
<name>mayswind</name>
116+
<url>https://github.com/mayswind</url>
117+
</developer>
114118
<developer>
115119
<name>Auston McReynolds</name>
116120
<url>https://github.com/amcrn</url>

src/main/java/com/tagtraum/perf/gcviewer/imp/DataReaderUnifiedJvmLogging.java

+90-21
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
import java.io.IOException;
44
import java.io.InputStream;
55
import java.io.UnsupportedEncodingException;
6+
import java.time.Instant;
7+
import java.time.ZoneId;
8+
import java.time.ZonedDateTime;
69
import java.util.Arrays;
710
import java.util.HashMap;
811
import java.util.List;
@@ -29,13 +32,13 @@
2932
/**
3033
* DataReaderUnifiedJvmLogging can parse all gc events of unified jvm logs with default decorations.
3134
* <p>
32-
* Currently needs the "gc" selector with "info" level and "uptime,level,tags" decorators (Java 9.0.1).
35+
* Currently needs the "gc" selector with "info" level and "uptime,level,tags" (or "time,level,tags") decorators (Java 9.0.1).
3336
* Also supports "gc*" selector with "trace" level and "time,uptime,level,tags" decorators, but will ignore some of
3437
* the debug and all trace level info (evaluates the following tags: "gc", "gc,start", "gc,heap", "gc,metaspace".
3538
* <ul>
3639
* <li>minimum configuration with defaults supported: <code>-Xlog:gc:file="path-to-file"</code></li>
37-
* <li>explicit minimum configuration needed: <code>-Xlog:gc=info:file="path-to-file":tags,uptime,level</code></li>
38-
* <li>maximum detail configuration this parser is able to work with: <code>-Xlog:gc*=trace:file="path-to-file":tags,time,uptime,level</code></li>
40+
* <li>explicit minimum configuration needed: <code>-Xlog:gc=info:file="path-to-file":uptime,level,tags</code> or <code>-Xlog:gc=info:file="path-to-file":time,level,tags</code></li>
41+
* <li>maximum detail configuration this parser is able to work with: <code>-Xlog:gc*=trace:file="path-to-file":time,uptime,timemillis,uptimemillis,timenanos,uptimenanos,pid,tid,level,tags</code></li>
3942
* </ul>
4043
* Only processes the following information format for Serial, Parallel, CMS, G1 and Shenandoah algorithms, everything else is ignored:
4144
* <pre>
@@ -51,28 +54,56 @@ public class DataReaderUnifiedJvmLogging extends AbstractDataReader {
5154
// TODO also parse "Allocation Stall (main)" events
5255

5356
// matches the whole line and extracts decorators from it (decorators always appear between [] and are independent of the gc algorithm being logged)
54-
// Input: [0.693s][info][gc ] GC(0) Pause Init Mark 1.070ms
55-
// Group 1 / time: <empty> (optional group, no full timestamp present)
56-
// Group 2 / uptime: 0.693 (optional group, present in this example)
57-
// Group 3 / level: info
58-
// Group 4 / tags: gc
59-
// Group 5 / gcnumber: 0
60-
// Group 6 / tail: Pause Init Mark 1.070ms
61-
// Regex: ^(?:\[(?<time>[0-9-T:.+]*)])?(?:\[(?<uptime>[^s]*)s])?\[(?<level>[^]]+)]\[(?:(?<tags>[^] ]+)[ ]*)][ ]GC\((?<gcnumber>[0-9]+)\)[ ](?<type>([-.a-zA-Z ()]+|[a-zA-Z1 ()]+))(?:(?:[ ](?<tail>[0-9]{1}.*))|$)
57+
// Input: [2023-01-01T00:00:14.206+0000][14.206s][1672531214206ms][14205ms][1000014205707082ns][14205707082ns][6000][6008][info ][gc ] GC(0) Pause Young (Normal) (G1 Evacuation Pause) 4115M->103M(8192M) 28.115ms
58+
// reference: https://openjdk.org/jeps/158
59+
// Group 1 / time: 2023-01-01T00:00:14.206+0800 (Current time and date in ISO-8601 format)
60+
// Group 2 / uptime: 14.206s (Time since the start of the JVM in seconds and milliseconds)
61+
// Group 3 / timemillis: 1672531214206ms (The same value as generated by System.currentTimeMillis())
62+
// Group 4 / uptimemillis: 14205ms (Milliseconds since the JVM started)
63+
// Group 5 / timenanos: 1000014205707082ns (The same value as generated by System.nanoTime())
64+
// Group 6 / uptimenanos: 14205707082ns (Nanoseconds since the JVM started)
65+
// Group 7 / pid: 6000 (The process identifier)
66+
// Group 8 / tid: 6008 (The thread identifier)
67+
// Group 9 / level: info (The level associated with the log message)
68+
// Group 10 / tags: gc (The tag-set associated with the log message)
69+
// Group 11 / gcnumber: 0
70+
// Group 12 / tail: Pause Init Mark 1.070ms
71+
// Regex: (see the below)
6272
// note for the <type> part: easiest would have been to use [^0-9]+, but the G1 events don't fit there, because of the number in their name
6373
// add sub regex "[a-zA-Z ]+\\(.+\\)" for Allocation Stall and Relocation Stall of ZGC
6474
private static final Pattern PATTERN_DECORATORS = Pattern.compile(
65-
"^(?:\\[(?<time>[0-9-T:.+]*)])?(?:\\[(?<uptime>[^ms]*)(?<uptimeunit>m?s)])?\\[(?<level>[^]]+)]\\[(?:(?<tags>[^] ]+)[ ]*)][ ](GC\\((?<gcnumber>[0-9]+)\\)[ ])?(?<type>(?:Phase [0-9]{1}: [a-zA-Z ]+)|[-.a-zA-Z: ()]+|[a-zA-Z1 ()]+|[a-zA-Z ]+\\(.+\\))(?:(?:[ ](?<tail>[0-9]{1}.*))|$)"
75+
"^" +
76+
"(?:\\[(?<time>[0-9-T:.+]*)])?" +
77+
"(?:\\[(?<uptime>[0-9.,]+)s[ ]*])?" +
78+
"(?:\\[(?<timemillis>[0-9]+)ms[ ]*])?" +
79+
"(?:\\[(?<uptimemillis>[0-9]+)ms[ ]*])?" +
80+
"(?:\\[(?<timenanos>[0-9]+)ns[ ]*])?" +
81+
"(?:\\[(?<uptimenanos>[0-9]+)ns[ ]*])?" +
82+
"(?:\\[(?<pid>[0-9]+)[ ]*])?" +
83+
"(?:\\[(?<tid>[0-9]+)[ ]*])?" +
84+
"\\[(?<level>[^]]+)]" +
85+
"\\[(?:(?<tags>[^] ]+)[ ]*)]" +
86+
"[ ]" +
87+
"(GC\\((?<gcnumber>[0-9]+)\\)[ ])?" +
88+
"(?<type>(?:Phase [0-9]{1}: [a-zA-Z ]+)|[-.a-zA-Z: ()]+|[a-zA-Z1 ()]+|[a-zA-Z ]+\\(.+\\))" +
89+
"(?:(?:[ ](?<tail>[0-9]{1}.*))|$)"
6690
);
6791
private static final String GROUP_DECORATORS_TIME = "time";
6892
private static final String GROUP_DECORATORS_UPTIME = "uptime";
69-
private static final String GROUP_DECORATORS_UPTIME_UNIT = "uptimeunit";
93+
private static final String GROUP_DECORATORS_TIME_MILLIS = "timemillis";
94+
private static final String GROUP_DECORATORS_UPTIME_MILLIS = "uptimemillis";
95+
private static final String GROUP_DECORATORS_TIME_NANOS = "timenanos";
96+
private static final String GROUP_DECORATORS_UPTIME_NANOS = "uptimenanos";
97+
private static final String GROUP_DECORATORS_PID = "pid";
98+
private static final String GROUP_DECORATORS_TID = "tid";
7099
private static final String GROUP_DECORATORS_LEVEL = "level";
71100
private static final String GROUP_DECORATORS_TAGS = "tags";
72101
private static final String GROUP_DECORATORS_GC_NUMBER = "gcnumber";
73102
private static final String GROUP_DECORATORS_GC_TYPE = "type";
74103
private static final String GROUP_DECORATORS_TAIL = "tail";
75104

105+
private static final long MIN_VALID_UNIX_TIME_MILLIS = 1000000000000L; // 2001-09-09 09:46:40
106+
76107
private static final Pattern PATTERN_HEAP_REGION_SIZE = Pattern.compile("^Heap [Rr]egion [Ss]ize: ([0-9]+)M$");
77108
private static final int GROUP_HEAP_REGION_SIZE = 1;
78109

@@ -174,6 +205,7 @@ public class DataReaderUnifiedJvmLogging extends AbstractDataReader {
174205
"[trace",
175206
"gc,heap,coops",
176207
"gc,heap,exit",
208+
"gc,metaspace,freelist,oom",
177209
"[gc,phases,start",
178210
"Trigger: ",
179211
"Failed to allocate",
@@ -267,7 +299,7 @@ private AbstractGCEvent<?> handleTail(ParseContext context, AbstractGCEvent<?> e
267299
returnEvent = handleTagGcHeapTail(context, event, tail);
268300
// ZGC heap capacity, break out and handle next event
269301
if (returnEvent == null) {
270-
break;
302+
break;
271303
}
272304
// fallthrough -> same handling as for METASPACE event
273305
case TAG_GC_METASPACE:
@@ -277,8 +309,8 @@ private AbstractGCEvent<?> handleTail(ParseContext context, AbstractGCEvent<?> e
277309
returnEvent = handleTagGcTail(context, event, tail);
278310
break;
279311
case TAG_GC_PHASES:
280-
returnEvent = handleTagGcPhasesTail(context, event, tail);
281-
break;
312+
returnEvent = handleTagGcPhasesTail(context, event, tail);
313+
break;
282314
default:
283315
getLogger().warning(String.format("Unexpected tail present in the end of line number %d (tail=\"%s\"; line=\"%s\")", in.getLineNumber(), tail, context.getLine()));
284316
}
@@ -485,7 +517,7 @@ private void parseGcMemoryPercentageTail(ParseContext context, AbstractGCEvent<?
485517
// the end Garbage Collection tags in ZGC contain details of memory cleaned up
486518
// and the percentage of memory used before and after clean. The details can be used to
487519
// determine Allocation rate.
488-
setMemoryWithPercentage(event, memoryPercentageMatcher);
520+
setMemoryWithPercentage(event, memoryPercentageMatcher);
489521
} else {
490522
getLogger().warning(String.format("Expected memory percentage in the end of line number %d (line=\"%s\")", in.getLineNumber(), context.getLine()));
491523
}
@@ -518,8 +550,39 @@ private AbstractGCEvent<?> createGcEventWithStandardDecorators(Matcher decorator
518550
if (decoratorsMatcher.group(GROUP_DECORATORS_GC_NUMBER) != null) {
519551
event.setNumber(Integer.parseInt(decoratorsMatcher.group(GROUP_DECORATORS_GC_NUMBER)));
520552
}
521-
setDateStampIfPresent(event, decoratorsMatcher.group(GROUP_DECORATORS_TIME));
522-
setTimeStampIfPresent(event, decoratorsMatcher.group(GROUP_DECORATORS_UPTIME), decoratorsMatcher.group(GROUP_DECORATORS_UPTIME_UNIT));
553+
554+
boolean hasTime = setDateStampIfPresent(event, decoratorsMatcher.group(GROUP_DECORATORS_TIME));
555+
boolean hasUptime = setTimeStampIfPresent(event, decoratorsMatcher.group(GROUP_DECORATORS_UPTIME), "s");
556+
557+
// The second time is the uptime for sure when the text contains two pairs of millisecond time
558+
if (decoratorsMatcher.group(GROUP_DECORATORS_TIME_MILLIS) != null && decoratorsMatcher.group(GROUP_DECORATORS_UPTIME_MILLIS) != null) {
559+
if (!hasTime) {
560+
long timeInMillisecond = Long.parseLong(decoratorsMatcher.group(GROUP_DECORATORS_TIME_MILLIS));
561+
hasTime = setDateStampIfPresent(event, DateHelper.formatDate(ZonedDateTime.ofInstant(Instant.ofEpochMilli(timeInMillisecond), ZoneId.systemDefault()))) || hasTime;
562+
}
563+
564+
hasUptime = setTimeStampIfPresent(event, decoratorsMatcher.group(GROUP_DECORATORS_UPTIME_MILLIS), "ms") || hasUptime;
565+
} else if (decoratorsMatcher.group(GROUP_DECORATORS_TIME_MILLIS) != null) {
566+
// If the first millisecond time below the valid unix time, it may be uptime, otherwise it may be unix time
567+
long millisecond = Long.parseLong(decoratorsMatcher.group(GROUP_DECORATORS_TIME_MILLIS));
568+
569+
if (millisecond < MIN_VALID_UNIX_TIME_MILLIS) {
570+
hasUptime = setTimeStampIfPresent(event, String.valueOf(millisecond), "ms") || hasUptime;
571+
} else {
572+
hasTime = setDateStampIfPresent(event, DateHelper.formatDate(ZonedDateTime.ofInstant(Instant.ofEpochMilli(millisecond), ZoneId.systemDefault()))) || hasTime;
573+
}
574+
}
575+
576+
// The second time is the uptime for sure only if the text contains two pairs of nanosecond time
577+
if (decoratorsMatcher.group(GROUP_DECORATORS_TIME_NANOS) != null && decoratorsMatcher.group(GROUP_DECORATORS_UPTIME_NANOS) != null) {
578+
hasUptime = setTimeStampIfPresent(event, decoratorsMatcher.group(GROUP_DECORATORS_UPTIME_NANOS), "ns") || hasUptime;
579+
}
580+
581+
if (!hasTime && !hasUptime) {
582+
getLogger().warning(String.format("Failed to parse line number %d (no valid time or timestamp; line=\"%s\")", in.getLineNumber(), line));
583+
return null;
584+
}
585+
523586
return event;
524587
} else {
525588
getLogger().warning(String.format("Failed to parse line number %d (no match; line=\"%s\")", in.getLineNumber(), line));
@@ -576,21 +639,27 @@ private void setMemoryWithPercentage(AbstractGCEvent<?> event, Matcher matcher)
576639
}
577640
}
578641

579-
private void setDateStampIfPresent(AbstractGCEvent<?> event, String dateStampAsString) {
642+
private boolean setDateStampIfPresent(AbstractGCEvent<?> event, String dateStampAsString) {
580643
// TODO remove code duplication with AbstractDataReaderSun -> move to DataReaderTools
581644
if (dateStampAsString != null) {
582645
event.setDateStamp(DateHelper.parseDate(dateStampAsString));
646+
return true;
583647
}
648+
return false;
584649
}
585650

586-
private void setTimeStampIfPresent(AbstractGCEvent<?> event, String timeStampAsString, String timeUnit) {
651+
private boolean setTimeStampIfPresent(AbstractGCEvent<?> event, String timeStampAsString, String timeUnit) {
587652
if (timeStampAsString != null && timeStampAsString.length() > 0) {
588653
double timestamp = NumberParser.parseDouble(timeStampAsString);
589654
if ("ms".equals(timeUnit)) {
590655
timestamp = timestamp / 1000;
656+
} else if ("ns".equals(timeUnit)) {
657+
timestamp = timestamp / 1000000000;
591658
}
592659
event.setTimestamp(timestamp);
660+
return true;
593661
}
662+
return false;
594663
}
595664

596665
private boolean isExcludedLine(String line) {

src/main/java/com/tagtraum/perf/gcviewer/view/AboutDialog.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ public class AboutDialog extends ScreenCenteredDialog implements ActionListener
6060
"James Livingston",
6161
"Mart Mägi",
6262
"Tony Mancill",
63+
"mayswind",
6364
"Auston McReynolds",
6465
"Samuel Mendenhall",
6566
"Carl Meyer",
@@ -97,7 +98,7 @@ public AboutDialog(Frame f) {
9798
versionPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
9899
versionPanel.setLayout(new GridBagLayout());
99100

100-
JLabel copyright = new JLabel("\u00A9" + " 2011-2022: Joerg Wuethrich and contributors", JLabel.CENTER);
101+
JLabel copyright = new JLabel("\u00A9" + " 2011-2025: Joerg Wuethrich and contributors", JLabel.CENTER);
101102

102103
JLabel contributorsLabel = new JLabel("contributors (alphabetically ordered):", JLabel.CENTER);
103104
contributorsLabel.setForeground(Color.GRAY);

0 commit comments

Comments
 (0)