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}.";
}
}
}