33import java .io .IOException ;
44import java .io .InputStream ;
55import java .io .UnsupportedEncodingException ;
6+ import java .time .Instant ;
7+ import java .time .ZoneId ;
8+ import java .time .ZonedDateTime ;
69import java .util .Arrays ;
710import java .util .HashMap ;
811import java .util .List ;
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
@@ -267,7 +298,7 @@ private AbstractGCEvent<?> handleTail(ParseContext context, AbstractGCEvent<?> e
267298 returnEvent = handleTagGcHeapTail (context , event , tail );
268299 // ZGC heap capacity, break out and handle next event
269300 if (returnEvent == null ) {
270- break ;
301+ break ;
271302 }
272303 // fallthrough -> same handling as for METASPACE event
273304 case TAG_GC_METASPACE :
@@ -277,8 +308,8 @@ private AbstractGCEvent<?> handleTail(ParseContext context, AbstractGCEvent<?> e
277308 returnEvent = handleTagGcTail (context , event , tail );
278309 break ;
279310 case TAG_GC_PHASES :
280- returnEvent = handleTagGcPhasesTail (context , event , tail );
281- break ;
311+ returnEvent = handleTagGcPhasesTail (context , event , tail );
312+ break ;
282313 default :
283314 getLogger ().warning (String .format ("Unexpected tail present in the end of line number %d (tail=\" %s\" ; line=\" %s\" )" , in .getLineNumber (), tail , context .getLine ()));
284315 }
@@ -485,7 +516,7 @@ private void parseGcMemoryPercentageTail(ParseContext context, AbstractGCEvent<?
485516 // the end Garbage Collection tags in ZGC contain details of memory cleaned up
486517 // and the percentage of memory used before and after clean. The details can be used to
487518 // determine Allocation rate.
488- setMemoryWithPercentage (event , memoryPercentageMatcher );
519+ setMemoryWithPercentage (event , memoryPercentageMatcher );
489520 } else {
490521 getLogger ().warning (String .format ("Expected memory percentage in the end of line number %d (line=\" %s\" )" , in .getLineNumber (), context .getLine ()));
491522 }
@@ -518,8 +549,41 @@ private AbstractGCEvent<?> createGcEventWithStandardDecorators(Matcher decorator
518549 if (decoratorsMatcher .group (GROUP_DECORATORS_GC_NUMBER ) != null ) {
519550 event .setNumber (Integer .parseInt (decoratorsMatcher .group (GROUP_DECORATORS_GC_NUMBER )));
520551 }
552+
553+ boolean hasTime = decoratorsMatcher .group (GROUP_DECORATORS_TIME ) != null ;
554+ boolean hasUptime = decoratorsMatcher .group (GROUP_DECORATORS_UPTIME ) != null ;
555+
521556 setDateStampIfPresent (event , decoratorsMatcher .group (GROUP_DECORATORS_TIME ));
522- setTimeStampIfPresent (event , decoratorsMatcher .group (GROUP_DECORATORS_UPTIME ), decoratorsMatcher .group (GROUP_DECORATORS_UPTIME_UNIT ));
557+ setTimeStampIfPresent (event , decoratorsMatcher .group (GROUP_DECORATORS_UPTIME ), "s" );
558+
559+ // The second time is the uptime for sure when the text contains two pairs of millisecond time
560+ if (decoratorsMatcher .group (GROUP_DECORATORS_TIME_MILLIS ) != null && decoratorsMatcher .group (GROUP_DECORATORS_UPTIME_MILLIS ) != null ) {
561+ hasUptime = true ;
562+ setTimeStampIfPresent (event , decoratorsMatcher .group (GROUP_DECORATORS_UPTIME_MILLIS ), "ms" );
563+ } else if (decoratorsMatcher .group (GROUP_DECORATORS_TIME_MILLIS ) != null ) {
564+ // If the first millisecond time below the valid unix time, it may be uptime, otherwise it may be unix time
565+ long millisecond = Long .parseLong (decoratorsMatcher .group (GROUP_DECORATORS_TIME_MILLIS ));
566+
567+ if (millisecond < MIN_VALID_UNIX_TIME_MILLIS ) {
568+ hasUptime = true ;
569+ setTimeStampIfPresent (event , String .valueOf (millisecond ), "ms" );
570+ } else {
571+ hasTime = true ;
572+ setDateStampIfPresent (event , DateHelper .formatDate (ZonedDateTime .ofInstant (Instant .ofEpochMilli (millisecond ), ZoneId .systemDefault ())));
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 = true ;
579+ setTimeStampIfPresent (event , decoratorsMatcher .group (GROUP_DECORATORS_UPTIME_NANOS ), "ns" );
580+ }
581+
582+ if (!hasTime && !hasUptime ) {
583+ getLogger ().warning (String .format ("Failed to parse line number %d (no valid time or timestamp; line=\" %s\" )" , in .getLineNumber (), line ));
584+ return null ;
585+ }
586+
523587 return event ;
524588 } else {
525589 getLogger ().warning (String .format ("Failed to parse line number %d (no match; line=\" %s\" )" , in .getLineNumber (), line ));
@@ -588,6 +652,8 @@ private void setTimeStampIfPresent(AbstractGCEvent<?> event, String timeStampAsS
588652 double timestamp = NumberParser .parseDouble (timeStampAsString );
589653 if ("ms" .equals (timeUnit )) {
590654 timestamp = timestamp / 1000 ;
655+ } else if ("ns" .equals (timeUnit )) {
656+ timestamp = timestamp / 1000000000 ;
591657 }
592658 event .setTimestamp (timestamp );
593659 }
0 commit comments