using System.Diagnostics; using ZB.MOM.WW.ScadaBridge.Commons.Entities.Instances; using ZB.MOM.WW.ScadaBridge.Commons.Entities.Sites; using ZB.MOM.WW.ScadaBridge.Commons.Entities.Templates; using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums; using ZB.MOM.WW.ScadaBridge.Commons.Types.Flattening; using ZB.MOM.WW.ScadaBridge.TemplateEngine.Flattening; using ZB.MOM.WW.ScadaBridge.TemplateEngine.Validation; namespace ZB.MOM.WW.ScadaBridge.ResidualBench; /// /// [xc-5] Template flattening and validation complete within acceptable time for large /// templates. Runs the production , /// (the deploy gate, including the Roslyn trust check + compile of every script) and /// in-process over synthetic template graphs of three sizes. /// The configuration-database read that FlatteningPipeline does first is not in frame. /// internal static class FlattenBench { private sealed record Shape( string Name, int Depth, int AttrsPerLevel, int AlarmsPerLevel, int ScriptsPerLevel, int Modules, int ModuleAttrs, int ModuleAlarms, int ModuleScripts, int SubModuleAttrs); private static readonly Shape[] Shapes = [ new("typical", Depth: 2, AttrsPerLevel: 40, AlarmsPerLevel: 5, ScriptsPerLevel: 3, Modules: 2, ModuleAttrs: 10, ModuleAlarms: 1, ModuleScripts: 1, SubModuleAttrs: 0), new("large", Depth: 5, AttrsPerLevel: 100, AlarmsPerLevel: 10, ScriptsPerLevel: 6, Modules: 10, ModuleAttrs: 50, ModuleAlarms: 5, ModuleScripts: 3, SubModuleAttrs: 10), new("extra-large", Depth: 8, AttrsPerLevel: 250, AlarmsPerLevel: 25, ScriptsPerLevel: 15, Modules: 20, ModuleAttrs: 100, ModuleAlarms: 10, ModuleScripts: 5, SubModuleAttrs: 20), ]; public static int Run(BenchArgs args) { var instances = args.GetInt("instances", 500); var repeats = args.GetInt("repeats", 20); args.RejectUnknown(); var flattener = new FlatteningService(); var validator = new ValidationService(); var hasher = new RevisionHashService(); // Warm the JIT and Roslyn once on a throwaway graph so the first shape doesn't pay it. { var warm = Build(Shapes[0], salt: "jit-warmup"); var w = flattener.Flatten(warm.Instance, warm.Chain, warm.Compositions, warm.ComposedChains, warm.Connections).Value; validator.Validate(w); hasher.ComputeHash(w); } Console.WriteLine(); Console.WriteLine("| shape | chain depth | flattened attrs / alarms / scripts | flatten P50 ms | validate, cold compile ms | validate, warm cache P50 ms | validate, compile off P50 ms | hash P50 ms | errors |"); Console.WriteLine("|---|---|---|---|---|---|---|---|---|"); foreach (var shape in Shapes) { var g = Build(shape, salt: shape.Name); FlattenedConfiguration config = null!; var flatten = new Samples(); for (var i = 0; i < repeats; i++) flatten.Add(Samples.Time(() => config = Flatten(flattener, g))); ScriptCompileVerdictCache.Clear(); ValidationResult result = null!; var cold = Samples.Time(() => result = validator.Validate(config)); var warm = new Samples(); for (var i = 0; i < repeats; i++) warm.Add(Samples.Time(() => validator.Validate(config))); var noCompile = new Samples(); for (var i = 0; i < repeats; i++) noCompile.Add(Samples.Time(() => validator.Validate(config, validateScriptCompilation: false))); var hash = new Samples(); for (var i = 0; i < repeats; i++) hash.Add(Samples.Time(() => hasher.ComputeHash(config))); Console.WriteLine($"| {shape.Name} | {shape.Depth} | {config.Attributes.Count:N0} / {config.Alarms.Count} / {config.Scripts.Count} | " + $"{flatten.Percentile(50):F2} | {cold:F0} | {warm.Percentile(50):F2} | {noCompile.Percentile(50):F2} | {hash.Percentile(50):F2} | {result.Errors.Count} |"); if (result.Errors.Count > 0) Console.WriteLine($" first errors: {result.SummarizeErrors(3)}"); } // Bulk-deploy CPU: N instances of one template (scripts shared, so after the first // instance every compile is a verdict-cache hit — the production shape). Console.WriteLine(); foreach (var shape in Shapes.Skip(1)) { var graphs = Enumerable.Range(0, instances).Select(i => Build(shape, salt: "bulk", instanceIndex: i)).ToList(); ScriptCompileVerdictCache.Clear(); var per = new Samples(); var sw = Stopwatch.StartNew(); foreach (var g in graphs) { per.Add(Samples.Time(() => { var c = Flatten(flattener, g); var v = validator.Validate(c); if (!v.IsValid) throw new InvalidOperationException(v.SummarizeErrors(3)); hasher.ComputeHash(c); })); } Console.WriteLine($"{shape.Name}: flatten+validate+hash of {instances} instances sharing one template, serial: " + $"{sw.Elapsed.TotalSeconds:F2} s total; per instance {per.Summary()}"); } return 0; } private static FlattenedConfiguration Flatten(FlatteningService flattener, Graph g) { var r = flattener.Flatten(g.Instance, g.Chain, g.Compositions, g.ComposedChains, g.Connections); return r.IsSuccess ? r.Value : throw new InvalidOperationException(r.Error); } private sealed record Graph( Instance Instance, IReadOnlyList