new advisories work and features gaps work

This commit is contained in:
master
2026-01-14 18:39:19 +02:00
parent 95d5898650
commit 15aeac8e8b
148 changed files with 16731 additions and 554 deletions

View File

@@ -0,0 +1,322 @@
// Copyright (c) StellaOps. All rights reserved.
// Licensed under AGPL-3.0-or-later. See LICENSE in the project root.
// Sprint: SPRINT_20260112_004_BINIDX_b2r2_lowuir_perf_cache (BINIDX-OPS-04)
// Task: Add ops endpoints for health, bench, cache, and config
using System.Collections.Immutable;
using System.Diagnostics;
using System.Globalization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using StellaOps.BinaryIndex.Cache;
using StellaOps.BinaryIndex.Disassembly.B2R2;
namespace StellaOps.BinaryIndex.WebService.Controllers;
/// <summary>
/// Ops endpoints for BinaryIndex health, benchmarking, cache stats, and configuration.
/// </summary>
[ApiController]
[Route("api/v1/ops/binaryindex")]
[Produces("application/json")]
public sealed class BinaryIndexOpsController : ControllerBase
{
private readonly B2R2LifterPool? _lifterPool;
private readonly FunctionIrCacheService? _cacheService;
private readonly IOptions<B2R2LifterPoolOptions> _poolOptions;
private readonly IOptions<FunctionIrCacheOptions> _cacheOptions;
private readonly TimeProvider _timeProvider;
private readonly ILogger<BinaryIndexOpsController> _logger;
public BinaryIndexOpsController(
ILogger<BinaryIndexOpsController> logger,
TimeProvider timeProvider,
IOptions<B2R2LifterPoolOptions> poolOptions,
IOptions<FunctionIrCacheOptions> cacheOptions,
B2R2LifterPool? lifterPool = null,
FunctionIrCacheService? cacheService = null)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
_poolOptions = poolOptions ?? throw new ArgumentNullException(nameof(poolOptions));
_cacheOptions = cacheOptions ?? throw new ArgumentNullException(nameof(cacheOptions));
_lifterPool = lifterPool;
_cacheService = cacheService;
}
/// <summary>
/// Gets BinaryIndex health status including lifter warmness and cache availability.
/// </summary>
/// <param name="ct">Cancellation token.</param>
/// <returns>Health response with component status.</returns>
[HttpGet("health")]
[ProducesResponseType<BinaryIndexOpsHealthResponse>(StatusCodes.Status200OK)]
[ProducesResponseType<ProblemDetails>(StatusCodes.Status503ServiceUnavailable)]
public ActionResult<BinaryIndexOpsHealthResponse> GetHealth(CancellationToken ct)
{
var lifterStatus = "unavailable";
var lifterWarm = false;
var lifterPoolStats = ImmutableDictionary<string, int>.Empty;
if (_lifterPool != null)
{
var stats = _lifterPool.GetStats();
lifterStatus = stats.IsWarm ? "warm" : "cold";
lifterWarm = stats.IsWarm;
lifterPoolStats = stats.IsaStats
.ToImmutableDictionary(
kv => kv.Key,
kv => kv.Value.PooledCount + kv.Value.ActiveCount);
}
var cacheStatus = "unavailable";
var cacheEnabled = false;
if (_cacheService != null)
{
var cacheStats = _cacheService.GetStats();
cacheStatus = cacheStats.IsEnabled ? "enabled" : "disabled";
cacheEnabled = cacheStats.IsEnabled;
}
var response = new BinaryIndexOpsHealthResponse(
Status: lifterWarm && cacheEnabled ? "healthy" : "degraded",
Timestamp: _timeProvider.GetUtcNow().ToString("o", CultureInfo.InvariantCulture),
LifterStatus: lifterStatus,
LifterWarm: lifterWarm,
LifterPoolStats: lifterPoolStats,
CacheStatus: cacheStatus,
CacheEnabled: cacheEnabled);
return Ok(response);
}
/// <summary>
/// Runs a quick benchmark and returns latency metrics.
/// </summary>
/// <param name="request">Optional bench parameters.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>Benchmark response with latency measurements.</returns>
[HttpPost("bench/run")]
[ProducesResponseType<BinaryIndexBenchResponse>(StatusCodes.Status200OK)]
[ProducesResponseType<ProblemDetails>(StatusCodes.Status400BadRequest)]
public ActionResult<BinaryIndexBenchResponse> RunBench(
[FromBody] BinaryIndexBenchRequest? request,
CancellationToken ct)
{
var iterations = request?.Iterations ?? 10;
if (iterations < 1 || iterations > 1000)
{
return BadRequest(new ProblemDetails
{
Title = "Invalid iterations",
Detail = "Iterations must be between 1 and 1000",
Status = StatusCodes.Status400BadRequest
});
}
_logger.LogInformation("Running BinaryIndex benchmark with {Iterations} iterations", iterations);
var lifterLatencies = new List<double>();
var cacheLatencies = new List<double>();
// Benchmark lifter acquisition if available
if (_lifterPool != null)
{
var isa = new B2R2.ISA(B2R2.Architecture.Intel, B2R2.WordSize.Bit64);
for (var i = 0; i < iterations; i++)
{
ct.ThrowIfCancellationRequested();
var sw = Stopwatch.StartNew();
using (var lifter = _lifterPool.Acquire(isa))
{
// Just acquire and release
}
sw.Stop();
lifterLatencies.Add(sw.Elapsed.TotalMilliseconds);
}
}
// Benchmark cache lookup if available
if (_cacheService != null)
{
var dummyKey = new FunctionCacheKey(
Isa: "intel-64",
B2R2Version: "0.9.1",
NormalizationRecipe: "v1",
CanonicalIrHash: "0000000000000000000000000000000000000000000000000000000000000000");
for (var i = 0; i < iterations; i++)
{
ct.ThrowIfCancellationRequested();
var sw = Stopwatch.StartNew();
// Fire and forget the cache lookup
_ = _cacheService.TryGetAsync(dummyKey, ct).ConfigureAwait(false);
sw.Stop();
cacheLatencies.Add(sw.Elapsed.TotalMilliseconds);
}
}
var lifterStats = ComputeLatencyStats(lifterLatencies);
var cacheStats = ComputeLatencyStats(cacheLatencies);
var response = new BinaryIndexBenchResponse(
Timestamp: _timeProvider.GetUtcNow().ToString("o", CultureInfo.InvariantCulture),
Iterations: iterations,
LifterAcquireLatencyMs: lifterStats,
CacheLookupLatencyMs: cacheStats);
return Ok(response);
}
/// <summary>
/// Gets function IR cache statistics.
/// </summary>
/// <param name="ct">Cancellation token.</param>
/// <returns>Cache statistics.</returns>
[HttpGet("cache")]
[ProducesResponseType<BinaryIndexFunctionCacheStats>(StatusCodes.Status200OK)]
public ActionResult<BinaryIndexFunctionCacheStats> GetCacheStats(CancellationToken ct)
{
if (_cacheService == null)
{
return Ok(new BinaryIndexFunctionCacheStats(
Enabled: false,
Hits: 0,
Misses: 0,
Evictions: 0,
HitRate: 0.0,
KeyPrefix: "",
CacheTtlSeconds: 0));
}
var stats = _cacheService.GetStats();
return Ok(new BinaryIndexFunctionCacheStats(
Enabled: stats.IsEnabled,
Hits: stats.Hits,
Misses: stats.Misses,
Evictions: stats.Evictions,
HitRate: stats.HitRate,
KeyPrefix: stats.KeyPrefix,
CacheTtlSeconds: (long)stats.CacheTtl.TotalSeconds));
}
/// <summary>
/// Gets effective BinaryIndex configuration.
/// </summary>
/// <param name="ct">Cancellation token.</param>
/// <returns>Effective configuration (secrets redacted).</returns>
[HttpGet("config")]
[ProducesResponseType<BinaryIndexEffectiveConfig>(StatusCodes.Status200OK)]
public ActionResult<BinaryIndexEffectiveConfig> GetConfig(CancellationToken ct)
{
var poolOptions = _poolOptions.Value;
var cacheOptions = _cacheOptions.Value;
return Ok(new BinaryIndexEffectiveConfig(
LifterPoolMaxSizePerIsa: poolOptions.MaxPoolSizePerIsa,
LifterPoolWarmPreloadEnabled: poolOptions.EnableWarmPreload,
LifterPoolWarmPreloadIsas: poolOptions.WarmPreloadIsas,
LifterPoolAcquireTimeoutSeconds: (long)poolOptions.AcquireTimeout.TotalSeconds,
CacheEnabled: cacheOptions.Enabled,
CacheKeyPrefix: cacheOptions.KeyPrefix,
CacheTtlSeconds: (long)cacheOptions.CacheTtl.TotalSeconds,
CacheMaxTtlSeconds: (long)cacheOptions.MaxTtl.TotalSeconds,
B2R2Version: cacheOptions.B2R2Version,
NormalizationRecipeVersion: cacheOptions.NormalizationRecipeVersion));
}
private static BinaryIndexLatencyStats ComputeLatencyStats(List<double> latencies)
{
if (latencies.Count == 0)
{
return new BinaryIndexLatencyStats(
Min: 0,
Max: 0,
Mean: 0,
P50: 0,
P95: 0,
P99: 0);
}
latencies.Sort();
var count = latencies.Count;
return new BinaryIndexLatencyStats(
Min: latencies[0],
Max: latencies[^1],
Mean: latencies.Average(),
P50: latencies[count / 2],
P95: latencies[(int)(count * 0.95)],
P99: latencies[(int)(count * 0.99)]);
}
}
#region Response Models
/// <summary>
/// BinaryIndex health response.
/// </summary>
public sealed record BinaryIndexOpsHealthResponse(
string Status,
string Timestamp,
string LifterStatus,
bool LifterWarm,
ImmutableDictionary<string, int> LifterPoolStats,
string CacheStatus,
bool CacheEnabled);
/// <summary>
/// Benchmark request parameters.
/// </summary>
public sealed record BinaryIndexBenchRequest(
int Iterations = 10);
/// <summary>
/// Benchmark response with latency measurements.
/// </summary>
public sealed record BinaryIndexBenchResponse(
string Timestamp,
int Iterations,
BinaryIndexLatencyStats LifterAcquireLatencyMs,
BinaryIndexLatencyStats CacheLookupLatencyMs);
/// <summary>
/// Latency statistics.
/// </summary>
public sealed record BinaryIndexLatencyStats(
double Min,
double Max,
double Mean,
double P50,
double P95,
double P99);
/// <summary>
/// Function IR cache statistics.
/// </summary>
public sealed record BinaryIndexFunctionCacheStats(
bool Enabled,
long Hits,
long Misses,
long Evictions,
double HitRate,
string KeyPrefix,
long CacheTtlSeconds);
/// <summary>
/// Effective BinaryIndex configuration.
/// </summary>
public sealed record BinaryIndexEffectiveConfig(
int LifterPoolMaxSizePerIsa,
bool LifterPoolWarmPreloadEnabled,
ImmutableArray<string> LifterPoolWarmPreloadIsas,
long LifterPoolAcquireTimeoutSeconds,
bool CacheEnabled,
string CacheKeyPrefix,
long CacheTtlSeconds,
long CacheMaxTtlSeconds,
string B2R2Version,
string NormalizationRecipeVersion);
#endregion

View File

@@ -22,6 +22,7 @@
<ProjectReference Include="../__Libraries/StellaOps.BinaryIndex.Persistence/StellaOps.BinaryIndex.Persistence.csproj" />
<ProjectReference Include="../__Libraries/StellaOps.BinaryIndex.VexBridge/StellaOps.BinaryIndex.VexBridge.csproj" />
<ProjectReference Include="../__Libraries/StellaOps.BinaryIndex.GoldenSet/StellaOps.BinaryIndex.GoldenSet.csproj" />
<ProjectReference Include="../__Libraries/StellaOps.BinaryIndex.Disassembly.B2R2/StellaOps.BinaryIndex.Disassembly.B2R2.csproj" />
</ItemGroup>
</Project>