-
Notifications
You must be signed in to change notification settings - Fork 358
/
Program.cs
108 lines (97 loc) · 4.34 KB
/
Program.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using NodaTime;
using NodaTime.Text;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Google.Cloud.Tools.ProcessBuildTimingLog
{
/// <summary>
/// Processes a build timing log created by the log_build_action function in toolversions.sh.
/// This makes it easy to see how long each part of the build takes.
/// </summary>
class Program
{
private static readonly DurationPattern s_durationPattern = DurationPattern.CreateWithInvariantCulture("-HH:mm:ss");
// The text to use instead of a duration for entries with no duration
private const string NoDurationText = "--------";
private const string StartPrefix = "(Start) ";
private const string EndPrefix = "(End) ";
private static int Main(string[] args)
{
if (args.Length != 1)
{
Console.WriteLine("Required single argument: log file path");
return 1;
}
string[] lines = File.ReadAllLines(args[0]);
var logEntries = lines.Where(line => line.Length > 0).Select(LogEntry.FromLogLine).ToList();
for (int i = 0; i < logEntries.Count; i++)
{
ProcessEntry(logEntries, i);
}
return 0;
}
private static void ProcessEntry(List<LogEntry> entries, int index)
{
LogEntry entry = entries[index];
// Ignore any "end" actions. Assume they've already been handled (or are useless if they're orphans)
if (entry.Action.StartsWith(EndPrefix))
{
return;
}
// If we have a "start" action, find the next matching "start" or "end", and handle appropriately
if (entry.Action.StartsWith(StartPrefix))
{
string unprefixedAction = entry.Action.Substring(StartPrefix.Length);
string expectedEndAction = EndPrefix + unprefixedAction;
// Note: in theory we should probably do this by index rather than timing, but it's very unlikely
// to cause issues.
var nextStart = entries.Skip(index + 1).FirstOrDefault(e => e.Action.StartsWith(entry.Action));
var nextEnd = entries.Skip(index + 1).FirstOrDefault(e => e.Action.StartsWith(expectedEndAction));
if (nextEnd == null)
{
PrintEntry(entry.Timestamp, null, $"No matching end: {unprefixedAction}");
return;
}
if (nextStart != null && nextStart.Timestamp < nextEnd.Timestamp)
{
PrintEntry(entry.Timestamp, null, $"Matching start before end: {unprefixedAction}");
return;
}
// We report the action in parentheses as a simple indication that it's a compound action.
PrintEntry(entry.Timestamp, nextEnd.Timestamp, $"({unprefixedAction})");
return;
}
// Simple case: just one line for this action. Use the next entry if we have one.
if (index == entries.Count)
{
PrintEntry(entry.Timestamp, null, $"Final line of file: {entry.Action}");
}
else
{
PrintEntry(entry.Timestamp, entries[index + 1].Timestamp, entry.Action);
}
void PrintEntry(Instant start, Instant? end, string action)
{
string formattedDuration = end != null
? s_durationPattern.Format(end.Value - start)
: NoDurationText;
Console.WriteLine($"{InstantPattern.General.Format(start)} {formattedDuration} {action}");
}
}
}
}