logger)
{
_mongoService = mongoService;
_configService = configService;
_globalConfig = globalConfig;
_logger = logger;
}
///
/// Prints all the environment variables
///
/// Http result
[HttpGet]
[Route("/api/v1/debug/environment")]
public ActionResult GetServerEnvVars()
{
if (!_globalConfig.Value.Authorize(ServerAclAction.Debug, User))
{
return Forbid(ServerAclAction.Debug);
}
StringBuilder content = new StringBuilder();
content.AppendLine("");
foreach (System.Collections.DictionaryEntry? pair in System.Environment.GetEnvironmentVariables())
{
if (pair != null)
{
content.AppendLine(HttpUtility.HtmlEncode($"{pair.Value.Key}={pair.Value.Value}"));
}
}
content.Append(" ");
return new ContentResult { ContentType = "text/html", StatusCode = (int)HttpStatusCode.OK, Content = content.ToString() };
}
///
/// Converts all legacy pools into config entries
///
[HttpGet]
[Route("/api/v1/debug/aclscopes")]
public ActionResult GetAclScopes()
{
if (!_globalConfig.Value.Authorize(ServerAclAction.Debug, User))
{
return Forbid(ServerAclAction.Debug);
}
return new { scopes = _globalConfig.Value.AclScopes.Keys.ToList() };
}
///
/// Returns the fully parsed config object.
///
[HttpGet]
[Route("/api/v1/debug/appsettings")]
public ActionResult GetAppSettings()
{
if (!_globalConfig.Value.Authorize(ServerAclAction.Debug, User))
{
return Forbid(ServerAclAction.Debug);
}
return _globalConfig.Value.ServerSettings;
}
///
/// Returns the fully parsed config object.
///
[HttpGet]
[Route("/api/v1/debug/config")]
public ActionResult GetConfig()
{
if (!_globalConfig.Value.Authorize(ServerAclAction.Debug, User))
{
return Forbid(ServerAclAction.Debug);
}
// Duplicate the config, so we can redact stuff that we don't want to return through the browser
byte[] data = _configService.Serialize(_globalConfig.Value);
GlobalConfig config = _configService.Deserialize(data, false)!;
return config;
}
///
/// Returns the fully parsed config object.
///
[HttpGet]
[Route("/api/v1/debug/randomdata")]
public async Task GetDataAsync([FromQuery] string size = "1mb", CancellationToken cancellationToken = default)
{
if (!_globalConfig.Value.Authorize(ServerAclAction.Debug, User))
{
return Forbid(ServerAclAction.Debug);
}
long sizeBytes;
try
{
sizeBytes = StringUtils.ParseBytesString(size);
}
catch
{
return BadRequest("Size must have a well-known binary suffix (eg. gb, mb, kb).");
}
// Disable buffering for the response
IHttpResponseBodyFeature? responseBodyFeature = HttpContext.Features.Get();
responseBodyFeature?.DisableBuffering();
// Write the response directly to the writer
HttpResponse response = HttpContext.Response;
response.ContentType = "application/octet-stream";
response.StatusCode = (int)HttpStatusCode.OK;
await response.StartAsync(cancellationToken);
Random rnd = new Random();
for (long offsetBytes = 0; offsetBytes < sizeBytes;)
{
int chunkSize = (int)Math.Min(64 * 1024, sizeBytes - offsetBytes);
Memory chunkData = response.BodyWriter.GetMemory(chunkSize);
rnd.NextBytes(chunkData.Span.Slice(0, chunkSize));
response.BodyWriter.Advance(chunkSize);
offsetBytes += chunkSize;
}
await response.CompleteAsync();
return Empty;
}
///
/// Generate log message of varying size
///
/// Information about the log message generated
[HttpGet]
[Route("/api/v1/debug/generate-log-msg")]
public ActionResult GenerateLogMessage(
[FromQuery] string? logLevel = null,
[FromQuery] int messageLen = 0,
[FromQuery] int exceptionMessageLen = 0,
[FromQuery] int argCount = 0,
[FromQuery] int argLen = 10)
{
if (!_globalConfig.Value.Authorize(ServerAclAction.Debug, User))
{
return Forbid(ServerAclAction.Debug);
}
string RandomString(int length)
{
const string Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
return new string(Enumerable.Repeat(Chars, length).Select(s => s[s_random.Next(s.Length)]).ToArray());
}
if (!Enum.TryParse(logLevel, out LogLevel logLevelInternal))
{
logLevelInternal = LogLevel.Information;
}
Exception? exception = null;
string message = "Message generated by /api/v1/debug/generate-log-msg";
message += RandomString(messageLen);
if (exceptionMessageLen > 0)
{
exception = new Exception("Exception from /api/v1/debug/generate-log-msg " + RandomString(exceptionMessageLen));
}
Dictionary args = new();
if (argCount > 0)
{
for (int i = 0; i < argCount; i++)
{
args["Arg" + i] = "Arg 1 - " + RandomString(argLen);
}
}
using IDisposable? logScope = _logger.BeginScope(args);
// Ignore warning as we explicitly want to build this message manually
#pragma warning disable CA2254 // Template should be a static expression
_logger.Log(logLevelInternal, exception, message);
#pragma warning restore CA2254
return Ok($"Log message generated logLevel={logLevelInternal} messageLen={messageLen} exceptionMessageLen={exceptionMessageLen} argCount={argCount} argLen={argLen}");
}
///
/// Populate the database with test data
///
/// Async task
[HttpGet]
[Route("/api/v1/debug/collections/{Name}")]
public async Task> GetDocumentsAsync(string name, [FromQuery] string? filter = null, [FromQuery] int index = 0, [FromQuery] int count = 10)
{
if (!_globalConfig.Value.Authorize(ServerAclAction.Debug, User))
{
return Forbid(ServerAclAction.Debug);
}
IMongoCollection> collection = _mongoService.GetCollection>(name);
List> documents = await collection.Find(filter ?? "{}").Skip(index).Limit(count).ToListAsync();
return documents;
}
///
/// Start a CPU profiler session using dotTrace
/// Only one profiling session can run at a time.
///
/// Status description
[HttpGet]
[Route("/api/v1/debug/profiler/cpu/start")]
public async Task StartCpuProfilerAsync()
{
if (!_globalConfig.Value.Authorize(ServerAclAction.Debug, User))
{
return Forbid(ServerAclAction.Debug);
}
// Downloads dotTrace executable if not available
Stopwatch sw = Stopwatch.StartNew();
await DotTrace.EnsurePrerequisiteAsync();
_logger.LogInformation("dotTrace prerequisites step finished in {SetupTimeMs} ms", sw.ElapsedMilliseconds);
string snapshotDir = Path.Join(Path.GetTempPath(), "horde-cpu-profiler-snapshots");
if (!Directory.Exists(snapshotDir))
{
Directory.CreateDirectory(snapshotDir);
}
DotTrace.Config config = new();
config.SaveToDir(snapshotDir);
DotTrace.Attach(config);
DotTrace.StartCollectingData();
return new ContentResult { ContentType = "text/plain", StatusCode = (int)HttpStatusCode.OK, Content = "CPU profiling session started. Using dir " + snapshotDir };
}
///
/// Stops a CPU profiler session
///
/// Text message
[HttpGet]
[Route("/api/v1/debug/profiler/cpu/stop")]
public ActionResult StopProfiler()
{
if (!_globalConfig.Value.Authorize(ServerAclAction.Debug, User))
{
return Forbid(ServerAclAction.Debug);
}
DotTrace.SaveData();
DotTrace.Detach();
return new ContentResult { ContentType = "text/plain", StatusCode = (int)HttpStatusCode.OK, Content = "CPU profiling session stopped" };
}
///
/// Downloads the captured CPU profiling snapshots
///
/// A .zip file containing the profiling snapshots
[HttpGet]
[Route("/api/v1/debug/profiler/cpu/download")]
public ActionResult DownloadProfilingData()
{
if (!_globalConfig.Value.Authorize(ServerAclAction.Debug, User))
{
return Forbid(ServerAclAction.Debug);
}
string snapshotZipFile = DotTrace.GetCollectedSnapshotFilesArchive(false);
if (!System.IO.File.Exists(snapshotZipFile))
{
return NotFound("The generated snapshot .zip file was not found");
}
return PhysicalFile(snapshotZipFile, "application/zip", Path.GetFileName(snapshotZipFile));
}
///
/// Take a memory snapshot using dotTrace
///
/// A .dmw file containing the memory snapshot
[HttpGet]
[Route("/api/v1/debug/profiler/mem/snapshot")]
public async Task TakeMemorySnapshotAsync()
{
if (!_globalConfig.Value.Authorize(ServerAclAction.Debug, User))
{
return Forbid(ServerAclAction.Debug);
}
// Downloads dotMemory executable if not available
Stopwatch sw = Stopwatch.StartNew();
await DotMemory.EnsurePrerequisiteAsync();
_logger.LogInformation("dotMemory prerequisites step finished in {SetupTimeMs} ms", sw.ElapsedMilliseconds);
string snapshotDir = Path.Join(Path.GetTempPath(), "horde-mem-profiler-snapshots");
if (!Directory.Exists(snapshotDir))
{
Directory.CreateDirectory(snapshotDir);
}
sw.Restart();
DotMemory.Config config = new();
config.SaveToDir(snapshotDir);
string workspaceFilePath = DotMemory.GetSnapshotOnce(config);
_logger.LogInformation("dotMemory snapshot captured in {CaptureTimeMs} ms", sw.ElapsedMilliseconds);
if (!System.IO.File.Exists(workspaceFilePath))
{
return NotFound("The generated workspace file was not found");
}
return PhysicalFile(workspaceFilePath, "application/octet-stream", Path.GetFileName(workspaceFilePath));
}
///
/// Throws an exception to debug error handling
///
///
[HttpGet]
[Route("/api/v1/debug/exception")]
public ActionResult ThrowException()
{
if (!_globalConfig.Value.Authorize(ServerAclAction.Debug, User))
{
return Forbid(ServerAclAction.Debug);
}
int numberArg = 42;
string stringArg = "hello";
throw new Exception($"Message: numberArg:{numberArg}, stringArg:{stringArg}");
}
}
}