|
| 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