Merge pull request #5714 from alexischr/update_bockbuild
[mono.git] / acceptance-tests / profiler-stress / runner.cs
1 using System;
2 using System.Collections.Generic;
3 using System.Diagnostics;
4 using System.Globalization;
5 using System.IO;
6 using System.Linq;
7 using System.Text;
8 using System.Text.RegularExpressions;
9 using System.Threading;
10 using System.Xml;
11 using Mono.Unix.Native;
12 using Newtonsoft.Json;
13
14 // Shut up CLS compliance warnings from Json.NET.
15 [assembly: CLSCompliant (true)]
16
17 namespace Mono.Profiling.Tests.Stress {
18
19         // https://github.com/xamarin/benchmarker/blob/master/tools/libdbmodel/Benchmark.cs
20         sealed class Benchmark {
21
22                 public string Name { get; set; }
23                 public string TestDirectory { get; set; }
24                 public bool OnlyExplicit { get; set; }
25                 public string[] CommandLine { get; set; }
26                 public string[] ClientCommandLine { get; set; }
27                 public string[] AOTAssemblies { get; set; }
28
29                 public static Benchmark Load (string file)
30                 {
31                         return JsonConvert.DeserializeObject<Benchmark> (File.ReadAllText (file));
32                 }
33         }
34
35         sealed class TestResult {
36
37                 public Benchmark Benchmark { get; set; }
38                 public ProcessStartInfo StartInfo { get; set; }
39                 public Stopwatch Stopwatch { get; set; } = new Stopwatch ();
40                 public int? ExitCode { get; set; }
41                 public string StandardOutput { get; set; }
42                 public string StandardError { get; set; }
43         }
44
45         static class Program {
46
47                 static readonly string[] _options = new [] {
48                         "exception",
49                         "monitor",
50                         "gc",
51                         "gcalloc",
52                         "gcmove",
53                         "gcroot",
54                         "gchandle",
55                         "finalization",
56                         "counter",
57                         "jit",
58                 };
59
60                 static readonly TimeSpan _timeout = TimeSpan.FromHours (9);
61
62                 static readonly Dictionary<string, Predicate<Benchmark>> _filters = new Dictionary<string, Predicate<Benchmark>> {
63                         { "ironjs-v8", FilterArmArchitecture },
64                 };
65
66                 static readonly Dictionary<string, Action<TestResult>> _processors = new Dictionary<string, Action<TestResult>> {
67                         { "msbiology", Process32BitOutOfMemory },
68                 };
69
70                 static string FilterInvalidXmlChars (string text) {
71                         return Regex.Replace (text, @"[^\x09\x0A\x0D\x20-\uD7FF\uE000-\uFFFD\u10000-\u10FFFF]", string.Empty);
72                 }
73
74                 static bool FilterArmArchitecture (Benchmark benchmark)
75                 {
76 #if ARCH_arm || ARCH_arm64
77                         return false;
78 #else
79                         return true;
80 #endif
81                 }
82
83                 static void Process32BitOutOfMemory (TestResult result)
84                 {
85                         if (Environment.Is64BitProcess)
86                                 return;
87
88                         if (result.ExitCode == null || result.ExitCode == 0)
89                                 return;
90
91                         if (result.StandardError.Contains ("OutOfMemoryException"))
92                                 result.ExitCode = 0;
93                 }
94
95                 static bool IsSupported (Benchmark benchmark)
96                 {
97                         return _filters.TryGetValue (benchmark.Name, out var filter) ? filter (benchmark) : true;
98                 }
99
100                 static int Main ()
101                 {
102                         var depDir = Path.Combine ("..", "external", "benchmarker");
103                         var benchDir = Path.Combine (depDir, "benchmarks");
104                         var testDir = Path.Combine (depDir, "tests");
105
106                         var benchmarks = Directory.EnumerateFiles (benchDir, "*.benchmark")
107                                          .Select (Benchmark.Load)
108                                          .Where (b => !b.OnlyExplicit && b.ClientCommandLine == null && IsSupported (b))
109                                          .OrderBy (b => b.Name)
110                                          .ToArray ();
111
112                         var monoPath = Path.GetFullPath (Path.Combine ("..", "..", "runtime", "mono-wrapper"));
113                         var classDir = Path.GetFullPath (Path.Combine ("..", "..", "mcs", "class", "lib", "net_4_x"));
114
115                         var rand = new Random ();
116                         var cpus = Environment.ProcessorCount;
117
118                         var results = new List<TestResult> (benchmarks.Length);
119
120                         var sw = Stopwatch.StartNew ();
121
122                         for (var i = 0; i < benchmarks.Length; i++) {
123                                 var bench = benchmarks [i];
124
125                                 var sampleFreq = rand.Next (-1000, 1001);
126                                 var sampleMode = rand.Next (0, 2) == 1 ? "-real" : string.Empty;
127                                 var maxSamples = rand.Next (0, cpus * 2000 + 1);
128                                 var heapShotFreq = rand.Next (-10, 11);
129                                 var maxFrames = rand.Next (0, 33);
130                                 var options = _options.ToDictionary (x => x, _ => rand.Next (0, 2) == 1)
131                                                       .Select (x => (x.Value ? string.Empty : "no") + x.Key)
132                                                       .ToArray ();
133
134                                 var profOptions = $"maxframes={maxFrames},{string.Join (",", options)},output=/dev/null";
135
136                                 if (sampleFreq > 0)
137                                         profOptions += $",sample{sampleMode}={sampleFreq},maxsamples={maxSamples}";
138
139                                 if (heapShotFreq > 0)
140                                         profOptions += $",heapshot={heapShotFreq}gc";
141
142                                 var info = new ProcessStartInfo {
143                                         UseShellExecute = false,
144                                         WorkingDirectory = Path.Combine (testDir, bench.TestDirectory),
145                                         FileName = monoPath,
146                                         Arguments = $"--debug --profile=log:{profOptions} " + string.Join (" ", bench.CommandLine),
147                                         RedirectStandardOutput = true,
148                                         RedirectStandardError = true,
149                                 };
150
151                                 info.EnvironmentVariables.Clear ();
152                                 info.EnvironmentVariables.Add ("MONO_PATH", classDir);
153
154                                 var progress = $"({i + 1}/{benchmarks.Length})";
155
156                                 Console.ForegroundColor = ConsoleColor.Blue;
157                                 Console.WriteLine ($"[{sw.Elapsed.ToString ("G")}] {progress} Running {bench.Name} with profiler options: {profOptions}");
158                                 Console.ResetColor ();
159
160                                 var result = new TestResult {
161                                         Benchmark = bench,
162                                         StartInfo = info,
163                                 };
164
165                                 using (var proc = new Process ()) {
166                                         proc.StartInfo = info;
167
168                                         var stdout = new StringBuilder ();
169                                         var stderr = new StringBuilder ();
170
171                                         proc.OutputDataReceived += (sender, args) => {
172                                                 if (args.Data != null)
173                                                         lock (result)
174                                                                 stdout.AppendLine (args.Data);
175                                         };
176
177                                         proc.ErrorDataReceived += (sender, args) => {
178                                                 if (args.Data != null)
179                                                         lock (result)
180                                                                 stderr.AppendLine (args.Data);
181                                         };
182
183                                         result.Stopwatch.Start ();
184
185                                         proc.Start ();
186
187                                         proc.BeginOutputReadLine ();
188                                         proc.BeginErrorReadLine ();
189
190                                         if (!proc.WaitForExit ((int) _timeout.TotalMilliseconds)) {
191                                                 // Force a thread dump.
192                                                 Syscall.kill (proc.Id, Signum.SIGQUIT);
193                                                 Thread.Sleep (1000);
194
195                                                 try {
196                                                         proc.Kill ();
197                                                 } catch (Exception) {
198                                                 }
199                                         } else
200                                                 result.ExitCode = proc.ExitCode;
201
202                                         result.Stopwatch.Stop ();
203
204                                         lock (result) {
205                                                 result.StandardOutput = stdout.ToString ();
206                                                 result.StandardError = stderr.ToString ();
207                                         }
208                                 }
209
210                                 var resultStr = result.ExitCode == null ? "timed out" : $"exited with code: {result.ExitCode}";
211
212                                 Console.ForegroundColor = result.ExitCode != 0 ? ConsoleColor.Red : ConsoleColor.Green;
213                                 Console.WriteLine ($"[{sw.Elapsed.ToString ("G")}] {progress} {bench.Name} took {result.Stopwatch.Elapsed.ToString ("G")} and {resultStr}");
214                                 Console.ResetColor ();
215
216                                 if (result.ExitCode != 0) {
217                                         Console.ForegroundColor = ConsoleColor.Red;
218                                         Console.WriteLine ("===== stdout =====");
219                                         Console.ResetColor ();
220
221                                         Console.WriteLine (result.StandardOutput);
222
223                                         Console.ForegroundColor = ConsoleColor.Red;
224                                         Console.WriteLine ("===== stderr =====");
225                                         Console.ResetColor ();
226
227                                         Console.WriteLine (result.StandardError);
228                                 }
229
230                                 if (_processors.TryGetValue (bench.Name, out var processor))
231                                         processor (result);
232
233                                 results.Add (result);
234                         }
235
236                         sw.Stop ();
237
238                         var successes = results.Count (r => r.ExitCode == 0);
239                         var failures = results.Count (r => r.ExitCode != null && r.ExitCode != 0);
240                         var timeouts = results.Count (r => r.ExitCode == null);
241
242                         var settings = new XmlWriterSettings {
243                                 NewLineOnAttributes = true,
244                                 Indent = true,
245                         };
246
247                         using (var writer = XmlWriter.Create ("TestResult-profiler-stress.xml", settings)) {
248                                 writer.WriteStartDocument ();
249                                 writer.WriteComment ("This file represents the results of running a test suite");
250
251                                 writer.WriteStartElement ("test-results");
252                                 writer.WriteAttributeString ("name", "profiler-stress-tests.dummy");
253                                 writer.WriteAttributeString ("total", results.Count.ToString ());
254                                 writer.WriteAttributeString ("failures", failures.ToString ());
255                                 writer.WriteAttributeString ("not-run", "0");
256                                 writer.WriteAttributeString ("date", DateTime.Now.ToString ("yyyy-MM-dd"));
257                                 writer.WriteAttributeString ("time", DateTime.Now.ToString ("HH:mm:ss"));
258
259                                 writer.WriteStartElement ("environment");
260                                 writer.WriteAttributeString ("nunit-version", "2.4.8.0");
261                                 writer.WriteAttributeString ("clr-version", Environment.Version.ToString ());
262                                 writer.WriteAttributeString ("os-version", Environment.OSVersion.ToString ());
263                                 writer.WriteAttributeString ("platform", Environment.OSVersion.Platform.ToString ());
264                                 writer.WriteAttributeString ("cwd", Environment.CurrentDirectory);
265                                 writer.WriteAttributeString ("machine-name", Environment.MachineName);
266                                 writer.WriteAttributeString ("user", Environment.UserName);
267                                 writer.WriteAttributeString ("user-domain", Environment.UserDomainName);
268                                 writer.WriteEndElement ();
269
270                                 writer.WriteStartElement ("culture-info");
271                                 writer.WriteAttributeString ("current-culture", CultureInfo.CurrentCulture.Name);
272                                 writer.WriteAttributeString ("current-uiculture", CultureInfo.CurrentUICulture.Name);
273                                 writer.WriteEndElement ();
274
275                                 writer.WriteStartElement ("test-suite");
276                                 writer.WriteAttributeString ("name", "profiler-stress-tests.dummy");
277                                 writer.WriteAttributeString ("success", (failures + timeouts == 0).ToString ());
278                                 writer.WriteAttributeString ("time", ((int) sw.Elapsed.TotalSeconds).ToString ());
279                                 writer.WriteAttributeString ("asserts", (failures + timeouts).ToString ());
280                                 writer.WriteStartElement ("results");
281
282                                 writer.WriteStartElement ("test-suite");
283                                 writer.WriteAttributeString ("name", "MonoTests");
284                                 writer.WriteAttributeString ("success", (failures + timeouts == 0).ToString ());
285                                 writer.WriteAttributeString ("time", ((int) sw.Elapsed.TotalSeconds).ToString ());
286                                 writer.WriteAttributeString ("asserts", (failures + timeouts).ToString ());
287                                 writer.WriteStartElement ("results");
288
289                                 writer.WriteStartElement ("test-suite");
290                                 writer.WriteAttributeString ("name", "profiler-stress");
291                                 writer.WriteAttributeString ("success", (failures + timeouts == 0).ToString ());
292                                 writer.WriteAttributeString ("time", ((int) sw.Elapsed.TotalSeconds).ToString ());
293                                 writer.WriteAttributeString ("asserts", (failures + timeouts).ToString ());
294                                 writer.WriteStartElement ("results");
295
296                                 foreach (var result in results) {
297                                         var timeoutStr = result.ExitCode == null ? "_timeout" : string.Empty;
298
299                                         writer.WriteStartElement ("test-case");
300                                         writer.WriteAttributeString ("name", $"MonoTests.profiler-stress.{result.Benchmark.Name}{timeoutStr}");
301                                         writer.WriteAttributeString ("executed", "True");
302                                         writer.WriteAttributeString ("success", (result.ExitCode == 0).ToString ());
303                                         writer.WriteAttributeString ("time", ((int) result.Stopwatch.Elapsed.TotalSeconds).ToString ());
304                                         writer.WriteAttributeString ("asserts", result.ExitCode == 0 ? "0" : "1");
305
306                                         if (result.ExitCode != 0) {
307                                                 writer.WriteStartElement ("failure");
308
309                                                 writer.WriteStartElement ("message");
310                                                 writer.WriteCData (FilterInvalidXmlChars (result.StandardOutput));
311                                                 writer.WriteEndElement ();
312
313                                                 writer.WriteStartElement ("stack-trace");
314                                                 writer.WriteCData (FilterInvalidXmlChars (result.StandardError));
315                                                 writer.WriteEndElement ();
316
317                                                 writer.WriteEndElement ();
318                                         }
319
320                                         writer.WriteEndElement ();
321                                 }
322
323                                 writer.WriteEndElement ();
324                                 writer.WriteEndElement ();
325
326                                 writer.WriteEndElement ();
327                                 writer.WriteEndElement ();
328
329                                 writer.WriteEndElement ();
330                                 writer.WriteEndElement ();
331
332                                 writer.WriteEndElement ();
333
334                                 writer.WriteEndDocument ();
335                         }
336
337                         var failureStr = failures + timeouts != 0 ? $" ({failures} failures, {timeouts} timeouts)" : string.Empty;
338
339                         Console.ForegroundColor = failures + timeouts != 0 ? ConsoleColor.Red : ConsoleColor.Green;
340                         Console.WriteLine ($"[{sw.Elapsed.ToString ("G")}] Finished with {successes}/{results.Count} passing tests{failureStr}");
341                         Console.ResetColor ();
342
343                         return failures + timeouts;
344                 }
345         }
346 }