@@ -26,10 +26,11 @@ public class AndroidLogcatLogParser : LogParserBase<AndroidLogcatLogParsedEntry,
2626 /// Per https://developer.android.com/studio/debug/am-logcat
2727 /// date time PID TID priority tag: message
2828 /// Example: "12-13 10:32:24.869 26 26 I Checkpoint: cp_prepareCheckpoint called"
29- public static Regex AndroidLogCatRegex = new Regex ( @"^(.{18} )\s+(\d+)\s+(\d+)\s+(\S) (.+?)\s?: (.*)$" ) ;
29+ public static Regex AndroidLogCatRegex = new Regex ( @"^([0-9-]+ [0-9:.]+ )\s+(\d+)\s+(\d+)\s+(\S) (.+?)\s?: (.*)$" ) ;
3030
31- const int SECONDS_TO_NS = 1000000000 ;
32- const int MS_TO_NS = 1000000 ;
31+ const long SECONDS_TO_NS = 1000000000 ;
32+ const long MS_TO_NS = 1000000 ;
33+ static readonly TimestampDelta OneNanoSecondTimestampDelta = new TimestampDelta ( 1 ) ; // At least 1ns timestamp for duration
3334
3435 public AndroidLogcatLogParser ( string [ ] filePaths ) : base ( filePaths )
3536 {
@@ -45,7 +46,6 @@ public override void ProcessSource(
4546 long startNanoSeconds = 0 ;
4647 DateTime fileStartTime = default ;
4748 var dateTimeCultureInfo = new CultureInfo ( "en-US" ) ;
48- var oneNanoSecondTimestampDelta = new TimestampDelta ( 1 ) ; // At least 1ns timestamp for duration
4949 double ? utcOffsetInHours = null ;
5050
5151 foreach ( var path in FilePaths )
@@ -57,6 +57,8 @@ public override void ProcessSource(
5757 var durationLogEntries = new List < DurationLogEntry > ( ) ;
5858 LogEntry logEntry = null ;
5959
60+ string timeDateFormat = null ;
61+
6062 var rootFolder = Path . GetDirectoryName ( path ) ;
6163 var utcOffsetFilePath = Path . Combine ( rootFolder , "utcoffset.txt" ) ;
6264 if ( File . Exists ( utcOffsetFilePath ) )
@@ -79,10 +81,28 @@ public override void ProcessSource(
7981
8082 var androidLogCatMatch = AndroidLogCatRegex . Match ( line ) ;
8183
84+ if ( androidLogCatMatch . Success && timeDateFormat == null )
85+ {
86+ const string monthDayFormat = "MM-dd HH:mm:ss.fff" ;
87+ const string monthDayYearFormat = "MM-dd-yyyy HH:mm:ss.fff" ;
88+ if ( DateTime . TryParseExact ( androidLogCatMatch . Groups [ 1 ] . Value , monthDayFormat , dateTimeCultureInfo , DateTimeStyles . None , out _ ) )
89+ {
90+ timeDateFormat = monthDayFormat ;
91+ }
92+ else if ( DateTime . TryParseExact ( androidLogCatMatch . Groups [ 1 ] . Value , monthDayYearFormat , dateTimeCultureInfo , DateTimeStyles . None , out _ ) )
93+ {
94+ timeDateFormat = monthDayYearFormat ;
95+ }
96+ else
97+ {
98+ throw new Exception ( $ "Invalid date/time format: { androidLogCatMatch . Groups [ 1 ] . Value } ") ;
99+ }
100+ }
101+
82102 // First, we check if the line is a new log entry if it matched Regex and by trying to parse its timestamp
83103 if ( androidLogCatMatch . Success &&
84104 androidLogCatMatch . Groups . Count >= 7 &&
85- DateTime . TryParseExact ( androidLogCatMatch . Groups [ 1 ] . Value , "MM-dd HH:mm:ss.fff" , dateTimeCultureInfo , DateTimeStyles . None , out DateTime parsedTime ) )
105+ DateTime . TryParseExact ( androidLogCatMatch . Groups [ 1 ] . Value , timeDateFormat , dateTimeCultureInfo , DateTimeStyles . None , out DateTime parsedTime ) )
86106 {
87107 var timeStamp = Timestamp . FromNanoseconds ( parsedTime . Ticks * 100 ) ;
88108
@@ -115,39 +135,36 @@ public override void ProcessSource(
115135 {
116136 var messageSplit = logEntry . Message . Split ( ) ;
117137 var firstSingleQuoteIdx = logEntry . Message . IndexOf ( '\' ' ) ;
118- var secondSingleQuoteIdx = logEntry . Message . IndexOf ( '\' ' , firstSingleQuoteIdx + 1 ) ;
119- var name = logEntry . Message . Substring ( firstSingleQuoteIdx + 1 , secondSingleQuoteIdx - firstSingleQuoteIdx - 1 ) ;
138+ var secondSingleQuoteIdx = logEntry . Message . IndexOf ( '\' ' , firstSingleQuoteIdx + 1 ) ;
139+ var name = logEntry . Message . Substring ( firstSingleQuoteIdx + 1 , secondSingleQuoteIdx - firstSingleQuoteIdx - 1 ) ;
120140 // Command 'write /dev/cpuctl/cpu.rt_period_us 1000000' action=init (/system/etc/init/hw/init.rc:271) took 0ms and failed: ....
121141 if ( logEntry . Message . Contains ( "0ms" ) )
122142 {
123- durationLogEntry = new DurationLogEntry ( logEntry , logEntry . Timestamp - oneNanoSecondTimestampDelta , oneNanoSecondTimestampDelta , name ) ;
143+ durationLogEntry = LogEntryFromDurationNs ( 0 , logEntry , name ) ;
124144 }
125145 // Service 'boringssl_self_test32_vendor' (pid 18) exited with status 0 waiting took 0.022000 seconds
126146
127147 if ( messageSplit [ ^ 3 ] == "took" && messageSplit [ ^ 1 ] == "seconds" )
128148 {
129149 if ( double . TryParse ( messageSplit [ ^ 2 ] , out double durationSeconds ) )
130150 {
131- var duration = new TimestampDelta ( ( long ) ( durationSeconds * SECONDS_TO_NS ) ) ;
132- durationLogEntry = new DurationLogEntry ( logEntry , logEntry . Timestamp - duration , duration , name ) ;
151+ durationLogEntry = LogEntryFromDurationS ( durationSeconds , logEntry , name ) ;
133152 }
134153 }
135154 // Command 'mount_all /fstab.${ro.hardware}' action=fs (/vendor/etc/init/init.windows_x86_64.rc:14) took 1054ms and succeeded
136- else if ( messageSplit [ ^ 4 ] == "took" && messageSplit [ ^ 1 ] == "succeeded" )
155+ else if ( messageSplit [ ^ 4 ] == "took" && messageSplit [ ^ 1 ] == "succeeded" )
137156 {
138157 if ( int . TryParse ( messageSplit [ ^ 3 ] . Replace ( "ms" , String . Empty ) , out int durationMs ) )
139158 {
140- var duration = new TimestampDelta ( durationMs * MS_TO_NS ) ;
141- durationLogEntry = new DurationLogEntry ( logEntry , logEntry . Timestamp - duration , duration , name ) ;
159+ durationLogEntry = LogEntryFromDurationMs ( durationMs , logEntry , name ) ;
142160 }
143161 }
144162 // Service 'disable_lro' (pid 39) exited with status 0 oneshot service took 0.006000 seconds in background
145163 else if ( messageSplit [ ^ 5 ] == "took" && messageSplit [ ^ 1 ] == "background" )
146164 {
147165 if ( double . TryParse ( messageSplit [ ^ 4 ] , out double durationSeconds ) )
148166 {
149- var duration = new TimestampDelta ( ( long ) ( durationSeconds * SECONDS_TO_NS ) ) ;
150- durationLogEntry = new DurationLogEntry ( logEntry , logEntry . Timestamp - duration , duration , name ) ;
167+ durationLogEntry = LogEntryFromDurationS ( durationSeconds , logEntry , name ) ;
151168 }
152169 }
153170 // Command 'rm /data/user/0' action=post-fs-data (/system/etc/init/hw/init.rc:706) took 1ms and failed: unlink() failed: Is a directory
@@ -159,8 +176,7 @@ public override void ProcessSource(
159176 {
160177 if ( int . TryParse ( afterTook . Replace ( "ms" , String . Empty ) , out int durationMs ) )
161178 {
162- var duration = new TimestampDelta ( durationMs * MS_TO_NS ) ;
163- durationLogEntry = new DurationLogEntry ( logEntry , logEntry . Timestamp - duration , duration , name ) ;
179+ durationLogEntry = LogEntryFromDurationMs ( durationMs , logEntry , name ) ;
164180 }
165181 }
166182 }
@@ -172,14 +188,73 @@ public override void ProcessSource(
172188 var name = messageSplit [ 0 ] ;
173189 if ( int . TryParse ( messageSplit [ ^ 1 ] . Replace ( "ms" , String . Empty ) , out int durationMs ) )
174190 {
175- if ( durationMs == 0 )
191+ durationLogEntry = LogEntryFromDurationMs ( durationMs , logEntry , name ) ;
192+ }
193+ }
194+ else if ( logEntry . Tag == "SurfaceFlinger" && logEntry . Message . Contains ( "Boot is finished" ) )
195+ {
196+ var messageSplit = logEntry . Message . Split ( ) ;
197+ if ( int . TryParse ( messageSplit [ ^ 2 ] . Replace ( "(" , String . Empty ) , out int durationMs ) )
198+ {
199+ durationLogEntry = LogEntryFromDurationMs ( durationMs , logEntry , "Boot is finished" ) ;
200+ }
201+ }
202+ else if ( logEntry . Tag == "Looper" && logEntry . Message . StartsWith ( "Slow" ) )
203+ {
204+ var messageSplit = logEntry . Message . Split ( ) ;
205+ if ( messageSplit . Length >= 6 && int . TryParse ( messageSplit [ 3 ] . Replace ( "ms" , String . Empty ) , out int durationMs ) )
206+ {
207+ durationLogEntry = LogEntryFromDurationMs ( durationMs , logEntry , $ "{ messageSplit [ 4 ] } { messageSplit [ 5 ] } ") ;
208+ }
209+ }
210+ else if ( logEntry . Tag == "OpenGLRenderer" && logEntry . Message . StartsWith ( "Davey!" ) ) // Jank & Perf
211+ {
212+ var messageSplit = logEntry . Message . Split ( ) ;
213+ if ( messageSplit . Length >= 2 && int . TryParse ( messageSplit [ 1 ] . Replace ( "duration=" , String . Empty ) . Replace ( "ms;" , String . Empty ) , out int durationMs ) )
214+ {
215+ durationLogEntry = LogEntryFromDurationMs ( durationMs , logEntry , "OpenGLRenderer Jank Perf" ) ;
216+ }
217+ }
218+ else if ( logEntry . Tag == "Zygote" && logEntry . Message . EndsWith ( "ms." ) )
219+ {
220+ var messageSplit = logEntry . Message . Split ( ) ;
221+ if ( int . TryParse ( messageSplit [ ^ 1 ] . Replace ( "ms." , String . Empty ) , out int durationMs ) )
222+ {
223+ durationLogEntry = LogEntryFromDurationMs ( durationMs , logEntry , logEntry . Tag ) ;
224+ }
225+ }
226+ // Android 12
227+ else if ( logEntry . Tag == "ServiceManager" && logEntry . Message . Contains ( "successful after waiting" ) )
228+ {
229+ var messageSplit = logEntry . Message . Split ( ) ;
230+ if ( messageSplit . Length >= 3 && int . TryParse ( messageSplit [ ^ 1 ] . Replace ( "ms" , String . Empty ) , out int durationMs ) )
231+ {
232+ durationLogEntry = LogEntryFromDurationMs ( durationMs , logEntry , messageSplit [ 3 ] . Replace ( "'" , String . Empty ) ) ;
233+ }
234+ }
235+ else if ( logEntry . Tag == "PackageManager" && logEntry . Message . StartsWith ( "Finished scanning" ) )
236+ {
237+ var messageSplit = logEntry . Message . Split ( ) ;
238+ if ( messageSplit . Length >= 6 && int . TryParse ( messageSplit [ 5 ] , out int durationMs ) )
239+ {
240+ durationLogEntry = LogEntryFromDurationMs ( durationMs , logEntry , messageSplit [ 2 ] + messageSplit [ 3 ] ) ;
241+ }
242+ }
243+ else if ( logEntry . Tag == "dex2oat32" && logEntry . Message . StartsWith ( "dex2oat took" ) )
244+ {
245+ var messageSplit = logEntry . Message . Split ( ) ;
246+ if ( messageSplit [ 2 ] . EndsWith ( "ms" ) )
247+ {
248+ if ( messageSplit . Length >= 3 && double . TryParse ( messageSplit [ 2 ] . Replace ( "ms" , String . Empty ) , out double durationMs ) )
176249 {
177- durationLogEntry = new DurationLogEntry ( logEntry , logEntry . Timestamp - oneNanoSecondTimestampDelta , oneNanoSecondTimestampDelta , name ) ;
250+ durationLogEntry = LogEntryFromDurationMs ( durationMs , logEntry , logEntry . Tag ) ;
178251 }
179- else
252+ }
253+ else
254+ {
255+ if ( messageSplit . Length >= 3 && double . TryParse ( messageSplit [ 2 ] . Replace ( "s" , String . Empty ) , out double durationS ) )
180256 {
181- var duration = new TimestampDelta ( durationMs * MS_TO_NS ) ;
182- durationLogEntry = new DurationLogEntry ( logEntry , logEntry . Timestamp - duration , duration , name ) ;
257+ durationLogEntry = LogEntryFromDurationS ( durationS , logEntry , logEntry . Tag ) ;
183258 }
184259 }
185260 }
@@ -245,5 +320,34 @@ public override void ProcessSource(
245320 dataSourceInfo = new DataSourceInfo ( 0 , offsetEndTimestamp . ToNanoseconds , DateTime . FromFileTimeUtc ( fileStartTime . ToFileTime ( ) ) ) ; // Treat as current locale local time (default)
246321 }
247322 }
323+
324+ private DurationLogEntry LogEntryFromDurationS ( double durationS , LogEntry logEntry , string name )
325+ {
326+ return LogEntryFromDurationNs ( ( long ) ( durationS * SECONDS_TO_NS ) , logEntry , name ) ;
327+ }
328+
329+
330+ private DurationLogEntry LogEntryFromDurationMs ( int durationMs , LogEntry logEntry , string name )
331+ {
332+ return LogEntryFromDurationNs ( durationMs * MS_TO_NS , logEntry , name ) ;
333+ }
334+
335+ private DurationLogEntry LogEntryFromDurationMs ( double durationMs , LogEntry logEntry , string name )
336+ {
337+ return LogEntryFromDurationNs ( ( long ) ( durationMs * MS_TO_NS ) , logEntry , name ) ;
338+ }
339+
340+ private DurationLogEntry LogEntryFromDurationNs ( long durationNs , LogEntry logEntry , string name )
341+ {
342+ if ( durationNs == 0 )
343+ {
344+ return new DurationLogEntry ( logEntry , logEntry . Timestamp - OneNanoSecondTimestampDelta , OneNanoSecondTimestampDelta , name ) ;
345+ }
346+ else
347+ {
348+ var duration = new TimestampDelta ( durationNs ) ;
349+ return new DurationLogEntry ( logEntry , logEntry . Timestamp - duration , duration , name ) ;
350+ }
351+ }
248352 }
249353}
0 commit comments