Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions eng/run-benchmarks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#!/usr/bin/env -S deno run --allow-run --allow-read --allow-env --allow-net --allow-write

import $ from "https://deno.land/x/dax@0.39.2/mod.ts";
import { load } from "https://deno.land/std@0.224.0/dotenv/mod.ts";
import { z } from "https://deno.land/x/zod@v3.22.4/mod.ts";

const env = await load({ export: true });

const config = z.object({
// Game server RCON details
GS_HOST: z.string().min(1, "GS_HOST env var is required"),
GS_PORT: z.string().min(1, "GS_PORT env var is required"),
GS_PASS: z.string().min(1, "GS_PASS env var is required"),
// SFTP connection details
SFTP_HOST: z.string().min(1, "SFTP_HOST env var is required"),
SFTP_USER: z.string().min(1, "SFTP_USER env var is required"),
SFTP_PASS: z.string().min(1, "SFTP_PASS env var is required"),
// Remote plugin path on the game server (under /home/container)
GS_PLUGIN_DIR: z.string().default("/game/csgo/addons/counterstrikesharp/plugins/NativeTestsPlugin"),
}).parse(env);

const HERE = $.path(import.meta).parentOrThrow();
const PROJECT_ROOT = HERE.parentOrThrow();
const NATIVE_TESTS_PROJECT = PROJECT_ROOT.join("managed/CounterStrikeSharp.Tests.Native/NativeTestsPlugin.csproj");
const BUILD_OUTPUT = PROJECT_ROOT.join("managed/CounterStrikeSharp.Tests.Native/bin/Debug/net8.0");
const RESULTS_OUTPUT = PROJECT_ROOT.join("benchmark-results");

// ── Step 1: Build the native tests plugin ───────────────────────────────

$.logStep("Building NativeTestsPlugin...");
await $`dotnet build ${NATIVE_TESTS_PROJECT} -c Debug`;

// ── Step 2: Upload the built plugin to the game server via SFTP ─────────

$.logStep("Uploading NativeTestsPlugin to game server...");
const remotePluginDir = config.GS_PLUGIN_DIR;

await $`lftp -u ${config.SFTP_USER},${config.SFTP_PASS} ${config.SFTP_HOST} -e ${`set xfer:clobber on; mkdir -p ${remotePluginDir}; mirror -R --delete ${BUILD_OUTPUT} ${remotePluginDir}; bye`
}`.quiet();

// ── Step 3: Reload the plugin and run benchmarks via RCON ───────────────

$.logStep("Reloading plugin via RCON...");
const rcon = `${HERE}/rcon`;
try {
await $`${rcon} -a ${config.GS_HOST}:${config.GS_PORT} -p ${config.GS_PASS} "css_plugins load NativeTestsPlugin"`.text();
} catch {
// Plugin may already be loaded; try reloading
await $`${rcon} -a ${config.GS_HOST}:${config.GS_PORT} -p ${config.GS_PASS} "css_plugins reload NativeTestsPlugin"`.text();
}

$.logStep("Running benchmarks via RCON: css_itest benchmark ...");
const benchOutput = await $`${rcon} -a ${config.GS_HOST}:${config.GS_PORT} -p ${config.GS_PASS} -T 120s "css_itest benchmark"`.text();
console.log(benchOutput);

const remoteJsonPath = `${remotePluginDir}/benchmark-results.json`;
const remoteMdPath = `${remotePluginDir}/benchmark-results.md`;

// ── Step 4: Download the benchmark results ──────────────────────────────

$.logStep("Downloading benchmark results...");
await Deno.mkdir(RESULTS_OUTPUT.toString(), { recursive: true });

const localJsonPath = RESULTS_OUTPUT.join("benchmark-results.json").toString();
const localMdPath = RESULTS_OUTPUT.join("benchmark-results.md").toString();

await $`lftp -u ${config.SFTP_USER},${config.SFTP_PASS} ${config.SFTP_HOST} -e ${`set xfer:clobber on; get ${remoteJsonPath} -o ${localJsonPath}; get ${remoteMdPath} -o ${localMdPath}; bye`
}`.quiet();

$.logStep("Benchmark results downloaded successfully!");
$.logLight(` JSON: ${localJsonPath}`);
$.logLight(` MD: ${localMdPath}`);

// Print the markdown summary
const mdContent = await Deno.readTextFile(localMdPath);
console.log("\n" + mdContent);
40 changes: 40 additions & 0 deletions eng/update-schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/usr/bin/env -S deno run --allow-run --allow-read --allow-env --allow-net

import $ from "https://deno.land/x/dax@0.39.2/mod.ts";
import { load } from "https://deno.land/std@0.224.0/dotenv/mod.ts";
import { z } from "https://deno.land/x/zod@v3.22.4/mod.ts";

const env = await load({ export: true });

const config = z.object({
// Game server RCON details
GS_HOST: z.string().min(1, "GS_HOST env var is required"),
GS_PORT: z.string().min(1, "GS_PORT env var is required"),
GS_PASS: z.string().min(1, "GS_PASS env var is required"),
// SFTP connection details
SFTP_HOST: z.string().min(1, "SFTP_HOST env var is required"),
SFTP_USER: z.string().min(1, "SFTP_USER env var is required"),
SFTP_PASS: z.string().min(1, "SFTP_PASS env var is required"),
}).parse(env);

const HERE = $.path('.');

$.logStep("Dumping schema from game server via RCON...");
const output = await $`${HERE}/rcon -a ${config.GS_HOST}:${config.GS_PORT} -p ${config.GS_PASS} "dump_schema all"`.text();

// Extract the file path from RCON output
const match = output.match(/Wrote file output to (.+)/);
if (!match) {
throw new Error("Could not find schema output path in RCON response");
}
const filepath = match[1].trim();
const trimmedPath = filepath.replace(/^\/home\/container/, "");

$.logStep("Downloading schema file from game server via SFTP...");
const schemaOutput = `${HERE}/../managed/CounterStrikeSharp.SchemaGen/Schema/server.json`;
await $`lftp -u ${config.SFTP_USER},${config.SFTP_PASS} ${config.SFTP_HOST} -e ${"set xfer:clobber on; get " + trimmedPath + " -o " + schemaOutput + "; bye"}`.quiet();

const schemaGenProject = `${HERE}/../managed/CounterStrikeSharp.SchemaGen/CounterStrikeSharp.SchemaGen.csproj`;
const generatedSchemaDir = `${HERE}/../managed/CounterStrikeSharp.API/Generated/Schema`;
$.logStep("Generating C# schema classes...");
await $`dotnet run --project ${schemaGenProject} -- ${generatedSchemaDir}`;
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,12 @@ public class NativeTestsPlugin : BasePlugin

public static int gameThreadId;

public static NativeTestsPlugin Instance { get; private set; } = null!;

public override void Load(bool hotReload)
{
gameThreadId = Thread.CurrentThread.ManagedThreadId;
Instance = this;
// Loading blocks the game thread, so we use NextFrame to run our tests asynchronously.
// Uncomment to run the tests on load
// Server.NextWorldUpdate(() => RunTests());
Expand Down Expand Up @@ -135,6 +138,9 @@ public async Task RunTests(string? filter = null)
Console.WriteLine($"[{ModuleName}] Test run finished.");
Console.WriteLine(reporter.GetSummary());
Console.WriteLine("*****************************************************************");

// Export benchmark results if any were collected
ScriptContextBenchmarks.ExportResults();
}
catch (Exception ex)
{
Expand Down
Loading
Loading