Skip to content

Commit efa848b

Browse files
authored
User/ivberg/logcat improve durations (#71)
* Improve logcat durations for retail logs by adding - SurfaceFlinger Boot, Looper, and OpenGLRender durations * Add some more retail log improvements seen in Android12 * Logcat - Support for handling optional year + bug fixes
1 parent f8423bf commit efa848b

File tree

5 files changed

+146
-30
lines changed

5 files changed

+146
-30
lines changed

LinuxLogParsers/LinuxLogParser/AndroidLogcat/AndroidLogcatLogParser.cs

Lines changed: 126 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -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
}

LinuxLogParsers/LinuxLogParsersUnitTest/LinuxLogParsersUnitTest.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,12 +116,16 @@ public void AndroidLogcat()
116116
Assert.IsTrue(eventData.DurationLogEntries[1].Duration.ToMilliseconds == 53);
117117
Assert.IsTrue((eventData.DurationLogEntries[1].EndTimestamp - eventData.DurationLogEntries[1].StartTimestamp).ToMilliseconds == 53);
118118

119-
Assert.IsTrue(eventData.DurationLogEntries.Count == 1011);
119+
Assert.IsTrue(eventData.DurationLogEntries.Count == 1025);
120120

121121
// MakeWindowManagerServiceReady took to complete: 3ms
122-
Assert.IsTrue(eventData.DurationLogEntries[443].Name == "MakeWindowManagerServiceReady");
123-
Assert.IsTrue(eventData.DurationLogEntries[443].Duration.ToMilliseconds == 3);
124-
Assert.IsTrue((eventData.DurationLogEntries[443].EndTimestamp - eventData.DurationLogEntries[443].StartTimestamp).ToMilliseconds == 3);
122+
Assert.IsTrue(eventData.DurationLogEntries[455].Name == "MakeWindowManagerServiceReady");
123+
Assert.IsTrue(eventData.DurationLogEntries[455].Duration.ToMilliseconds == 3);
124+
Assert.IsTrue((eventData.DurationLogEntries[455].EndTimestamp - eventData.DurationLogEntries[455].StartTimestamp).ToMilliseconds == 3);
125+
126+
// Looper : Slow delivery took 2033ms main h=android.app.ActivityThread$H c=null m=156
127+
Assert.IsTrue(eventData.DurationLogEntries[921].Name == "main h=android.app.ActivityThread$H");
128+
Assert.IsTrue(eventData.DurationLogEntries[921].Duration.ToMilliseconds == 2033);
125129
}
126130

127131
[TestMethod]

LinuxLogParsers/LinuxPlugins-MicrosoftPerformanceToolkSDK/AndroidLogCat/Tables/DurationTable.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public static class DurationTable
4444

4545
private static readonly ColumnConfiguration DurationColumn = new ColumnConfiguration(
4646
new ColumnMetadata(new Guid("{3894B186-D168-4E46-B0B5-0091BD5807A1}"), "Duration", "Duration of the event"),
47-
new UIHints { Width = 80, CellFormat = "ms" });
47+
new UIHints { Width = 80, CellFormat = TimestampFormatter.FormatMillisecondsGrouped });
4848

4949
private static readonly ColumnConfiguration DurationColumnOrderedMax = new ColumnConfiguration(
5050
new ColumnMetadata(new Guid("{3894B186-D168-4E46-B0B5-0091BD5807A1}"), "Duration", "Duration of the event"),
@@ -141,14 +141,14 @@ public static void BuildTable(ITableBuilder tableBuilder, IDataExtensionRetrieva
141141
MessageColumn,
142142
DurationColumnOrderedMax,
143143
TableConfiguration.GraphColumn,
144-
StartTimestampColumnSorted,
144+
StartTimestampColumn,
145145
EndTimestampColumn
146146
},
147147
};
148148

149149
longestDurationConfig.AddColumnRole(ColumnRole.StartTime, StartTimestampColumnSorted);
150150
longestDurationConfig.AddColumnRole(ColumnRole.EndTime, EndTimestampColumn);
151-
longestDurationConfig.AddColumnRole(ColumnRole.Duration, DurationColumn);
151+
longestDurationConfig.AddColumnRole(ColumnRole.Duration, DurationColumnOrderedMax);
152152

153153
tableBuilder.AddTableConfiguration(timeOrderConfig)
154154
.AddTableConfiguration(longestDurationConfig)

LinuxLogParsers/LinuxPlugins-MicrosoftPerformanceToolkSDK/AndroidLogCat/Tables/LogTable.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ public static void BuildTable(ITableBuilder tableBuilder, IDataExtensionRetrieva
8888
PriorityColumn,
8989
TagColumn,
9090
MessageColumn,
91+
TableConfiguration.RightFreezeColumn,
9192
TableConfiguration.GraphColumn,
9293
TimestampColumn
9394
},

0 commit comments

Comments
 (0)