// {{ display_name }} MCP server — generated by mcpify. Do not hand-edit.
//
// Profiles a real, representative invocation of the generated server (a
// one-shot `search` call — local semantic search only, no live credentials
// needed, matching the default this target's other-language counterparts
// use) via Microsoft.Diagnostics.NETCore.Client: a combined CPU +
// GC/allocation trace. Override with `dotnet run --project Tools/Profiler
// -- <command> <args>` for a different operation. Deliberately profiles
// the built server binary
// directly rather than `dotnet test` — VSTest runs the actual test code in
// a separate "testhost" child process, so attaching diagnostics to the
// outer `dotnet test` process itself captures ~0 samples of real work.
// Invoked by scripts/profile.sh; coverage is a separate concern, handled
// by scripts/coverage.sh — run that first for the coverage section of
// profile/bottleneck-report.md.
using System.Diagnostics;
using System.Diagnostics.Tracing;
using Microsoft.Diagnostics.NETCore.Client;

string traceOutputPath = Path.Combine(Directory.GetCurrentDirectory(), "profile", "test_profile.nettrace");
Directory.CreateDirectory(Path.GetDirectoryName(traceOutputPath)!);

Console.WriteLine("Building the server (dotnet build)...");
var buildStatus = Process.Start(new ProcessStartInfo
{
    FileName = "dotnet",
    Arguments = "build --configuration Debug",
    UseShellExecute = false,
})!;
await buildStatus.WaitForExitAsync();
if (buildStatus.ExitCode != 0)
{
    Console.Error.WriteLine("'dotnet build' failed; cannot profile.");
    Environment.Exit(1);
}

string serverDll = Path.Combine("bin", "Debug", "net10.0", "{{ namespace }}.dll");
string[] profiledArgs = args.Length > 0 ? args : ["search", "test query"];

var psi = new ProcessStartInfo
{
    FileName = "dotnet",
    ArgumentList = { serverDll },
    RedirectStandardOutput = true,
    RedirectStandardError = true,
    UseShellExecute = false,
    CreateNoWindow = true,
};
foreach (var arg in profiledArgs) psi.ArgumentList.Add(arg);

using var serverProcess = new Process { StartInfo = psi };
serverProcess.OutputDataReceived += (_, e) => { if (e.Data != null) Console.WriteLine(e.Data); };
serverProcess.ErrorDataReceived += (_, e) => { if (e.Data != null) Console.Error.WriteLine(e.Data); };

Console.WriteLine($"Profiling: dotnet {serverDll} {string.Join(' ', profiledArgs)}");
serverProcess.Start();
serverProcess.BeginOutputReadLine();
serverProcess.BeginErrorReadLine();

int pid = serverProcess.Id;
var providers = new List<EventPipeProvider>
{
    new("Microsoft-DotNETCore-SampleProfiler", EventLevel.Informational),
    // 0x1 = GC events, 0x800000 = object allocation tracking.
    new("Microsoft-Windows-DotNETRuntime", EventLevel.Informational, 0x1 | 0x800000),
};

// A just-started process's diagnostic IPC socket isn't guaranteed to be
// listening yet — attaching immediately after Process.Start() is a known
// race (ServerNotAvailableException). Retry briefly rather than failing.
var client = new DiagnosticsClient(pid);
EventPipeSession? session = null;
for (var attempt = 1; session is null; attempt++)
{
    try
    {
        session = client.StartEventPipeSession(providers, requestRundown: true);
    }
    catch (ServerNotAvailableException) when (attempt < 20)
    {
        await Task.Delay(250);
    }
}

Task copyTraceTask = Task.Run(async () =>
{
    await using FileStream fs = File.OpenWrite(traceOutputPath);
    await session!.EventStream.CopyToAsync(fs);
});

await serverProcess.WaitForExitAsync();
Console.WriteLine($"Profiled process finished with exit code {serverProcess.ExitCode}.");

try
{
    // If the target process already exited (the common case for a fast
    // one-shot CLI call), its diagnostic IPC pipe is already gone and
    // there's nothing to stop; the event stream below already ends on its
    // own when that happens.
    session!.Stop();
}
catch (ServerNotAvailableException)
{
}
await copyTraceTask;
session!.Dispose();

Console.WriteLine($"Trace written to {traceOutputPath}");
