Table of Contents

Profiler Launch Override Hook

Adjust the resolved profiler launch config in code, on top of file/env, without giving up zero-code defaults.

Status: โœ… Implemented ยท Visibility: Public ยท Level: ๐ŸŸฃ Advanced ยท Category: Hosting

๐ŸŽฏ What it solves

The profiler self-wires from typhon.telemetry.json (+ TYPHON__PROFILER__* env vars) with zero host code. Some hosts still need to decide the trace path or live port in code โ€” e.g. layering --trace/ --live CLI args on top of the file config, or computing a per-run trace path (timestamped, per-match, per-test-case) that a static JSON value can't express. This hook adds that one escape hatch without requiring every host to hand-roll profiler bootstrap.

โš™๏ธ How it works (in brief)

AddTyphonProfiler registers a delegate that maps the config resolved from file + environment to the effective ProfilerLaunchConfig. The engine's runtime bootstrap (TyphonRuntime.Create) resolves and applies it automatically when profiling starts โ€” no other wiring is needed. If the delegate returns null, the resolved config is used unchanged. Precedence is fixed: JSON file โ†’ environment โ†’ your delegate. If you never call AddTyphonProfiler, nothing changes โ€” the zero-code self-wiring path runs exactly as before.

๐Ÿ’ป Usage

using Microsoft.Extensions.DependencyInjection;
using Typhon.Engine;

var services = new ServiceCollection();
services.AddDatabaseEngine();

// Layer CLI args on top of typhon.telemetry.json / env โ€” CLI wins where it sets a value.
services.AddTyphonProfiler(resolved => resolved.MergedWith(ProfilerLaunchConfig.FromArgs(args)));

var provider = services.BuildServiceProvider();
var engine = provider.GetRequiredService<DatabaseEngine>();

// serviceProvider must be passed here for the override to be resolved and applied.
var runtime = TyphonRuntime.Create(engine, sched => { /* register systems */ }, serviceProvider: provider);
Registration Effect
AddTyphonProfiler(resolved => resolved.MergedWith(ProfilerLaunchConfig.FromArgs(args))) CLI flags (--trace, --live [port], --live-wait <ms>) override unset-only fields of the file/env config.
AddTyphonProfiler(resolved => resolved with { TraceFilePath = ComputePath() }) Fully computed trace path, ignoring whatever the file/env supplied.

โš ๏ธ Guarantees & limits

  • Opt-in, additive. Calling AddTyphonProfiler never changes behavior unless the IServiceProvider is also passed to TyphonRuntime.Create โ€” the override is only resolved from that container.
  • โš ๏ธ Zero-arg call resolves to a different, unrelated overload. Typhon.Engine also declares TelemetryServiceExtensions.AddTyphonProfiler(IServiceCollection) (forces early TelemetryConfig init; no delegate parameter). C# overload resolution prefers that exact-arity match over this hook's optional-parameter overload, so services.AddTyphonProfiler() with no argument silently calls the other method and never registers a ProfilerLaunchOverride. Always pass a delegate explicitly to reach this hook.
  • Cannot enable profiling from a closed master gate. The delegate only runs once typhon.telemetry.json's master Typhon:Profiler:Enabled is already on; it can add or change where output goes (trace file / live port), not flip profiling on for a session where it's off. If the resulting config still has no output channel, nothing is exported.
  • Best-effort. Profiler startup โ€” including your delegate โ€” runs inside a try/catch that never crashes the host; a throwing delegate disables profiling for that session with a logged diagnostic, it does not fault application startup.
  • Runs once per process. The hook fires the first time TyphonRuntime.Create self-wires the profiler; it is not re-invoked per runtime instance.

๐Ÿงช Tests

  • ProfilerLaunchConfigTests โ€” MergedWith precedence (MergedWith_OverrideTraceWinsWhenSet, _BaseRetainedWhenOverrideUnset, _NullOverride_ReturnsBase) and TypicalLayering_ConfigFirstThenArgsOverride, which its own comment ties directly to "the AddTyphonProfiler hook"; no fixture calls AddTyphonProfiler itself โ€” this covers the merge logic the delegate composes over