using System.Globalization; namespace MeterVault.Integration.Tests.Performance; /// /// The environment switches of the performance measurement (brief ยง9.10, D-56). Everything is opt-in: without /// METERVAULT_PERF=1 the performance facts are skipped, so the normal suite neither runs nor slows down. /// /// /// /// METERVAULT_PERF1 runs the performance facts. /// METERVAULT_PERF_LABELNames the result files (results-<label>.md); default local. /// METERVAULT_PERF_OUTWhere results go; default %TEMP%/mv_analysis/perf. /// METERVAULT_PERF_RUNSMeasured runs per scenario (default 10), after METERVAULT_PERF_WARMUP warm-ups (default 2). /// METERVAULT_PERF_DBA connection string to use instead of a fresh container. A database that already holds /// the dataset is reused as it is (no load, no rebuild), so a re-run on a quiet machine only measures. /// METERVAULT_PERF_LOAD_DBFor the loader fact: an app-migrated database (old or new schema) to load the raw /// dataset into, for the page-level comparison. /// METERVAULT_PERF_NOWFor the loader fact: the instant the dataset ends at (ISO 8601); default the current time. /// METERVAULT_PERF_SCALEScales every meter group (default 1 = 1,000 meters); only for quick trial runs. /// METERVAULT_PERF_NOTEFree text copied into the report (e.g. "machine busy with other builds"). /// /// internal sealed record PerfSettings( bool Enabled, string Label, string OutputDirectory, int Runs, int Warmup, string? Database, string? LoadDatabase, DateTimeOffset? Now, double Scale, string? Note) { public const string EnabledVariable = "METERVAULT_PERF"; public const string LoadDatabaseVariable = "METERVAULT_PERF_LOAD_DB"; public static PerfSettings Current { get; } = Read(); private static PerfSettings Read() { static string? Env(string name) => Environment.GetEnvironmentVariable(name) is { Length: > 0 } value ? value.Trim() : null; static int Int(string name, int fallback) => int.TryParse(Env(name), NumberStyles.Integer, CultureInfo.InvariantCulture, out var value) && value >= 0 ? value : fallback; var scale = double.TryParse(Env("METERVAULT_PERF_SCALE"), NumberStyles.Float, CultureInfo.InvariantCulture, out var s) && s > 0 ? s : 1; DateTimeOffset? now = DateTimeOffset.TryParse(Env("METERVAULT_PERF_NOW"), CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var n) ? n.ToUniversalTime() : null; return new PerfSettings( Enabled: Env(EnabledVariable) == "1", Label: Env("METERVAULT_PERF_LABEL") ?? "local", OutputDirectory: Env("METERVAULT_PERF_OUT") ?? Path.Combine(Path.GetTempPath(), "mv_analysis", "perf"), Runs: Math.Max(1, Int("METERVAULT_PERF_RUNS", 10)), Warmup: Int("METERVAULT_PERF_WARMUP", 2), Database: Env("METERVAULT_PERF_DB"), LoadDatabase: Env(LoadDatabaseVariable), Now: now, Scale: scale, Note: Env("METERVAULT_PERF_NOTE")); } } /// A fact that runs only with METERVAULT_PERF=1; otherwise it is reported as skipped. [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public sealed class PerfFactAttribute : FactAttribute { public PerfFactAttribute() { if (!PerfSettings.Current.Enabled) { Skip = $"Performance measurement; set {PerfSettings.EnabledVariable}=1 to run it."; } } } /// /// A fact that runs only with METERVAULT_PERF=1 and a target database in METERVAULT_PERF_LOAD_DB: it loads /// the synthetic raw dataset into a database an app has migrated, for the page-level before/after comparison. /// [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public sealed class PerfLoadFactAttribute : FactAttribute { public PerfLoadFactAttribute() { if (!PerfSettings.Current.Enabled || PerfSettings.Current.LoadDatabase is null) { Skip = $"Loads the synthetic dataset into an existing database; set {PerfSettings.EnabledVariable}=1 and {PerfSettings.LoadDatabaseVariable}."; } } }