Skip to content

Commit ade73e0

Browse files
AddAnalyzeAndAggregateCommand to GC infra. (#4848)
* AddAnalyzeAndAggregateCommand to GC * remove commandinvoker, using process class
1 parent 5b1fbb1 commit ade73e0

File tree

4 files changed

+484
-0
lines changed

4 files changed

+484
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
using YamlDotNet.Serialization;
2+
3+
namespace GC.Infrastructure.Core.Configurations.ReliabilityFrameworkTest
4+
{
5+
public sealed class ReliabilityFrameworkTestAnalyzeConfiguration
6+
{
7+
public required string DebuggerPath { get; set; }
8+
public required List<string> StackFrameKeyWords { get; set; }
9+
public required string CoreRoot { get; set; }
10+
public required string WSLInstanceLocation { get; set; }
11+
public required string DumpFolder { get; set; }
12+
public required string AnalyzeOutputFolder { get; set; }
13+
}
14+
15+
public static class ReliabilityFrameworkTestAnalyzeConfigurationParser
16+
{
17+
private static readonly IDeserializer _deserializer =
18+
new DeserializerBuilder().IgnoreUnmatchedProperties().Build();
19+
20+
public static ReliabilityFrameworkTestAnalyzeConfiguration Parse(string path)
21+
{
22+
// Preconditions.
23+
ConfigurationChecker.VerifyFile(path, nameof(ReliabilityFrameworkTestAnalyzeConfigurationParser));
24+
25+
string serializedConfiguration = File.ReadAllText(path);
26+
ReliabilityFrameworkTestAnalyzeConfiguration? configuration = null;
27+
28+
// This try catch is here because the exception from the YamlDotNet isn't helpful and must be imbued with more details.
29+
try
30+
{
31+
configuration = _deserializer.Deserialize<ReliabilityFrameworkTestAnalyzeConfiguration>(serializedConfiguration);
32+
}
33+
34+
catch (Exception ex)
35+
{
36+
throw new ArgumentException($"{nameof(ReliabilityFrameworkTestAnalyzeConfiguration)}: Unable to parse the yaml file because of an error in the syntax. Exception: {ex.Message} \n Call Stack: {ex.StackTrace}");
37+
}
38+
39+
if (String.IsNullOrEmpty(configuration.AnalyzeOutputFolder))
40+
{
41+
throw new ArgumentException($"{nameof(ReliabilityFrameworkTestAnalyzeConfiguration)}: Provide a analyze output folder.");
42+
}
43+
44+
if (!Path.Exists(configuration.DumpFolder))
45+
{
46+
throw new ArgumentException($"{nameof(ReliabilityFrameworkTestAnalyzeConfiguration)}: Dump folder doesn't exist.");
47+
}
48+
49+
// Check Core_Root folder
50+
if (!Path.Exists(configuration.CoreRoot))
51+
{
52+
throw new ArgumentException($"{nameof(ReliabilityFrameworkTestAnalyzeConfiguration)}: Core_Root doesn't exist.");
53+
}
54+
bool hasCoreRun = Directory.GetFiles(configuration.CoreRoot)
55+
.Any(filePath => Path.GetFileNameWithoutExtension(filePath) == "corerun");
56+
if (!hasCoreRun)
57+
{
58+
throw new ArgumentException($"{nameof(ReliabilityFrameworkTestAnalyzeConfiguration)}: Provide a valid Core_Root.");
59+
}
60+
61+
return configuration;
62+
}
63+
}
64+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
using System.ComponentModel;
2+
using System.Diagnostics.CodeAnalysis;
3+
using System.Text;
4+
using System.Text.RegularExpressions;
5+
using GC.Infrastructure.Core.Configurations;
6+
using GC.Infrastructure.Core.Configurations.ReliabilityFrameworkTest;
7+
using Spectre.Console;
8+
using Spectre.Console.Cli;
9+
10+
namespace GC.Infrastructure.Commands.ReliabilityFrameworkTest
11+
{
12+
public class ReliabilityFrameworkTestAggregateCommand :
13+
Command<ReliabilityFrameworkTestAggregateCommand.ReliabilityFrameworkTestAggregateSettings>
14+
{
15+
public class ReliabilityFrameworkTestDumpAnalyzeResult
16+
{
17+
public string? AttributedError { get; set; }
18+
public string? DumpName { get; set; }
19+
public string? CallStackForAllThreadsLogName { get; set; }
20+
public string? CallStackLogName { get; set; }
21+
public string? SourceFilePath { get; set; }
22+
public string? LineNumber { get; set; }
23+
}
24+
public sealed class ReliabilityFrameworkTestAggregateSettings : CommandSettings
25+
{
26+
[Description("Path to Configuration.")]
27+
[CommandOption("-c|--configuration")]
28+
public required string ConfigurationPath { get; init; }
29+
}
30+
31+
public override int Execute([NotNull] CommandContext context,
32+
[NotNull] ReliabilityFrameworkTestAggregateSettings settings)
33+
{
34+
AnsiConsole.Write(new Rule("Aggregate Analysis Results For Reliability Framework Test"));
35+
AnsiConsole.WriteLine();
36+
37+
ConfigurationChecker.VerifyFile(settings.ConfigurationPath,
38+
nameof(ReliabilityFrameworkTestAggregateSettings));
39+
ReliabilityFrameworkTestAnalyzeConfiguration configuration =
40+
ReliabilityFrameworkTestAnalyzeConfigurationParser.Parse(settings.ConfigurationPath);
41+
42+
AggregateResult(configuration);
43+
return 0;
44+
}
45+
public static void AggregateResult(ReliabilityFrameworkTestAnalyzeConfiguration configuration)
46+
{
47+
List<ReliabilityFrameworkTestDumpAnalyzeResult> dumpAnalyzeResultList = new List<ReliabilityFrameworkTestDumpAnalyzeResult>();
48+
49+
foreach (string callStackLogPath in Directory.GetFiles(configuration.AnalyzeOutputFolder, "*_callstack.txt"))
50+
{
51+
Console.WriteLine($"====== Extracting information from {callStackLogPath} ======");
52+
53+
string dumpPath = callStackLogPath.Replace("_callstack.txt", ".dmp");
54+
string callStackForAllThreadsLogPath = callStackLogPath.Replace(
55+
"_callstack.txt", "_callstack_allthreads.txt");
56+
57+
try
58+
{
59+
string callStack = File.ReadAllText(callStackLogPath);
60+
61+
// Search for frame contains keywords.
62+
string? frameInfo = FindFrameByKeyWord(configuration.StackFrameKeyWords, callStack);
63+
64+
// If no line contains given keywords, mark it as unknown error
65+
if (String.IsNullOrEmpty(frameInfo))
66+
{
67+
ReliabilityFrameworkTestDumpAnalyzeResult unknownErrorResult = new()
68+
{
69+
AttributedError = "Unknown error",
70+
DumpName = Path.GetFileName(dumpPath),
71+
CallStackLogName = Path.GetFileName(callStackLogPath),
72+
CallStackForAllThreadsLogName = Path.GetFileName(callStackForAllThreadsLogPath),
73+
SourceFilePath = String.Empty,
74+
LineNumber = String.Empty
75+
};
76+
77+
dumpAnalyzeResultList.Add(unknownErrorResult);
78+
continue;
79+
}
80+
81+
// Extract source file path and line number
82+
(string, int)? SrcFileLineNumTuple =
83+
ExtractSrcFilePathAndLineNumberFromFrameInfo(frameInfo);
84+
if (!SrcFileLineNumTuple.HasValue)
85+
{
86+
continue;
87+
}
88+
(string srcFilePath, int lineNumber) = SrcFileLineNumTuple.Value;
89+
90+
int lineIndex = lineNumber - 1;
91+
string realSrcFilePath = string.Empty;
92+
93+
// Convert source file path if it's in wsl.
94+
if (srcFilePath.StartsWith("/"))
95+
{
96+
if (String.IsNullOrEmpty(configuration.WSLInstanceLocation))
97+
{
98+
Console.WriteLine($"Console.WriteLine Provide wsl instance location to access source file. ");
99+
continue;
100+
}
101+
102+
string srcFilePathWithBackSlash = srcFilePath.Replace("/", "\\");
103+
realSrcFilePath = configuration.WSLInstanceLocation + srcFilePathWithBackSlash;
104+
}
105+
else
106+
{
107+
realSrcFilePath = srcFilePath;
108+
}
109+
110+
// Get source code line that throw error.
111+
var srcLineList = File.ReadAllLines(realSrcFilePath);
112+
113+
string srcLine = srcLineList[lineIndex].Trim();
114+
while (String.IsNullOrEmpty(srcLine) || String.IsNullOrWhiteSpace(srcLine))
115+
{
116+
lineIndex = lineIndex - 1;
117+
srcLine = srcLineList[lineIndex].Trim();
118+
}
119+
string error = srcLine;
120+
121+
ReliabilityFrameworkTestDumpAnalyzeResult dumpAnalyzeResult = new()
122+
{
123+
AttributedError = error,
124+
DumpName = Path.GetFileName(dumpPath),
125+
CallStackLogName = Path.GetFileName(callStackLogPath),
126+
CallStackForAllThreadsLogName = Path.GetFileName(callStackForAllThreadsLogPath),
127+
SourceFilePath = realSrcFilePath,
128+
LineNumber = (lineIndex + 1).ToString()
129+
};
130+
131+
dumpAnalyzeResultList.Add(dumpAnalyzeResult);
132+
}
133+
catch (Exception ex)
134+
{
135+
Console.WriteLine($"Console.WriteLine Fail to analyze {callStackLogPath}: {ex.Message}. ");
136+
}
137+
}
138+
139+
GenerateResultTable(dumpAnalyzeResultList, configuration.AnalyzeOutputFolder);
140+
}
141+
private static void GenerateResultTable(List<ReliabilityFrameworkTestDumpAnalyzeResult> dumpAnalyzeResultList,
142+
string analyzeOutputFolder)
143+
{
144+
var resultListGroup = dumpAnalyzeResultList.GroupBy(dumpAnalyzeResult => dumpAnalyzeResult.AttributedError);
145+
146+
StringBuilder sb = new StringBuilder();
147+
// Write title of table
148+
sb.AppendLine("| Attributed Error | Count/Total(percentage%) | Dump Name | Log Name(Call Stacks of All Threads) | Source File Path | Line Number |");
149+
sb.AppendLine("| :---------- | :---------: | :---------- | :---------- | :---------- | :---------: |");
150+
151+
foreach (IGrouping<string?, ReliabilityFrameworkTestDumpAnalyzeResult>? group in resultListGroup)
152+
{
153+
var resultListWithoutFirstItem = group.ToList();
154+
var firstResult = resultListWithoutFirstItem.FirstOrDefault();
155+
if (firstResult == null)
156+
{
157+
continue;
158+
}
159+
resultListWithoutFirstItem.Remove(firstResult);
160+
161+
string? attributedError = firstResult.AttributedError;
162+
string proportion = $"{group.Count()}/{dumpAnalyzeResultList.Count}";
163+
double proportionInPercentage = Convert.ToDouble(group.Count()) / Convert.ToDouble(dumpAnalyzeResultList.Count);
164+
string? dumpName = firstResult.DumpName;
165+
string? callStackForAllThreadsLogName = firstResult.CallStackForAllThreadsLogName;
166+
string? sourceFilePath = firstResult.SourceFilePath;
167+
string? lineNumber = firstResult.LineNumber;
168+
sb.AppendLine($"| {attributedError} | {proportion}({proportionInPercentage * 100}%) | {dumpName} | {callStackForAllThreadsLogName} | {sourceFilePath} | {lineNumber} |");
169+
170+
foreach (ReliabilityFrameworkTestDumpAnalyzeResult? dumpAnalyzeResult in resultListWithoutFirstItem)
171+
{
172+
dumpName = dumpAnalyzeResult.DumpName;
173+
callStackForAllThreadsLogName = dumpAnalyzeResult.CallStackForAllThreadsLogName;
174+
sourceFilePath = dumpAnalyzeResult.SourceFilePath;
175+
lineNumber = dumpAnalyzeResult.LineNumber;
176+
sb.AppendLine($"| | | {dumpName} | {callStackForAllThreadsLogName} | {sourceFilePath} | {lineNumber} |");
177+
}
178+
}
179+
180+
try
181+
{
182+
string outputPath = Path.Combine(analyzeOutputFolder, "Results.md");
183+
File.WriteAllText(outputPath, sb.ToString());
184+
}
185+
catch (Exception ex)
186+
{
187+
throw new Exception($"Fail to write result to markdown: {ex.Message}");
188+
}
189+
190+
}
191+
private static (string, int)? ExtractSrcFilePathAndLineNumberFromFrameInfo(string frameInfo)
192+
{
193+
string pattern = @"\[(.*?)\]";
194+
Match match = Regex.Match(frameInfo, pattern, RegexOptions.Singleline);
195+
196+
if (!match.Success)
197+
{
198+
Console.WriteLine($"The symbol is not available.");
199+
return null;
200+
}
201+
202+
string fileNameWithLineNumber = match.Groups[1].Value.Trim();
203+
string[] splitOutput = fileNameWithLineNumber.Split("@");
204+
205+
string? fileName = splitOutput.FirstOrDefault(String.Empty);
206+
if (String.IsNullOrEmpty(fileName))
207+
{
208+
Console.WriteLine($"Console.WriteLineFail to extract source file path.");
209+
return null;
210+
}
211+
212+
string? lineNumberstr = splitOutput.LastOrDefault(String.Empty).Trim();
213+
if (String.IsNullOrEmpty(lineNumberstr))
214+
{
215+
Console.WriteLine($"Console.WriteLineFail to extract line number.");
216+
return null;
217+
}
218+
219+
bool success = int.TryParse(lineNumberstr, out int lineNumber);
220+
if (!success)
221+
{
222+
Console.WriteLine($"Console.WriteLineFail to parse line number.");
223+
return null;
224+
}
225+
226+
return (fileName, lineNumber);
227+
}
228+
private static string? FindFrameByKeyWord(List<string> keyWordList, string callStack)
229+
{
230+
string[] lines = callStack.Split("\n");
231+
foreach (string line in lines)
232+
{
233+
foreach (string keyWord in keyWordList)
234+
{
235+
if (line.Contains(keyWord))
236+
{
237+
return line;
238+
}
239+
}
240+
}
241+
242+
Console.WriteLine($"Console.WriteLineFail to find keyword.");
243+
return null;
244+
}
245+
}
246+
}

0 commit comments

Comments
 (0)