2021-12-01 05:14:14 +00:00
|
|
|
|
using System.Reflection;
|
2021-11-29 23:35:23 +00:00
|
|
|
|
using System.Text.Json;
|
|
|
|
|
using Chronic.Core;
|
2020-05-09 03:56:06 +00:00
|
|
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
|
using Microsoft.Extensions.Logging;
|
2020-06-15 04:05:00 +00:00
|
|
|
|
using Nettle;
|
2020-05-09 03:56:06 +00:00
|
|
|
|
using Wabbajack.Common;
|
2021-12-01 05:14:14 +00:00
|
|
|
|
using Wabbajack.DTOs.ServerResponses;
|
2021-11-27 18:31:35 +00:00
|
|
|
|
using Wabbajack.Server.DataModels;
|
2020-05-09 03:56:06 +00:00
|
|
|
|
using Wabbajack.Server.DTOs;
|
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
namespace Wabbajack.BuildServer.Controllers;
|
2020-05-09 13:04:38 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
[ApiController]
|
|
|
|
|
[Route("/metrics")]
|
|
|
|
|
public class MetricsController : ControllerBase
|
|
|
|
|
{
|
|
|
|
|
private static readonly Func<object, string> ReportTemplate = NettleEngine.GetCompiler().Compile(@"
|
2020-06-15 04:05:00 +00:00
|
|
|
|
<html><body>
|
|
|
|
|
<h2>Tar Report for {{$.key}}</h2>
|
|
|
|
|
<h3>Ban Status: {{$.status}}</h3>
|
|
|
|
|
<table>
|
|
|
|
|
{{each $.log }}
|
|
|
|
|
<tr>
|
|
|
|
|
<td>{{$.Timestamp}}</td>
|
|
|
|
|
<td>{{$.Path}}</td>
|
|
|
|
|
<td>{{$.Key}}</td>
|
|
|
|
|
</tr>
|
|
|
|
|
{{/each}}
|
|
|
|
|
</table>
|
|
|
|
|
</body></html>
|
|
|
|
|
");
|
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
private static Func<object, string> _totalListTemplate;
|
|
|
|
|
private readonly AppSettings _settings;
|
|
|
|
|
private ILogger<MetricsController> _logger;
|
2021-11-27 18:31:35 +00:00
|
|
|
|
private readonly Metrics _metricsStore;
|
2020-06-15 04:05:00 +00:00
|
|
|
|
|
2021-11-27 18:31:35 +00:00
|
|
|
|
public MetricsController(ILogger<MetricsController> logger, Metrics metricsStore,
|
2021-10-23 16:51:17 +00:00
|
|
|
|
AppSettings settings)
|
|
|
|
|
{
|
|
|
|
|
_logger = logger;
|
|
|
|
|
_settings = settings;
|
2021-11-27 18:31:35 +00:00
|
|
|
|
_metricsStore = metricsStore;
|
2021-10-23 16:51:17 +00:00
|
|
|
|
}
|
2020-05-09 03:56:06 +00:00
|
|
|
|
|
2021-02-17 05:46:05 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
private static Func<object, string> TotalListTemplate
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
if (_totalListTemplate == null)
|
2021-02-17 05:46:05 +00:00
|
|
|
|
{
|
2021-10-23 16:51:17 +00:00
|
|
|
|
var resource = Assembly.GetExecutingAssembly()
|
|
|
|
|
.GetManifestResourceStream("Wabbajack.Server.Controllers.Templates.TotalListTemplate.html")!
|
|
|
|
|
.ReadAllText();
|
|
|
|
|
_totalListTemplate = NettleEngine.GetCompiler().Compile(resource);
|
2021-02-17 05:46:05 +00:00
|
|
|
|
}
|
2021-10-23 16:51:17 +00:00
|
|
|
|
|
|
|
|
|
return _totalListTemplate;
|
2021-02-17 05:46:05 +00:00
|
|
|
|
}
|
2021-10-23 16:51:17 +00:00
|
|
|
|
}
|
2021-02-17 05:46:05 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
[HttpGet]
|
2021-11-30 03:30:24 +00:00
|
|
|
|
[Route("{subject}/{value}")]
|
|
|
|
|
public async Task<Result> LogMetricAsync(string subject, string value)
|
2021-10-23 16:51:17 +00:00
|
|
|
|
{
|
|
|
|
|
var date = DateTime.UtcNow;
|
|
|
|
|
var metricsKey = Request.Headers[_settings.MetricsKeyHeader].FirstOrDefault();
|
2021-02-17 05:46:05 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
// Used in tests
|
2021-11-30 03:30:24 +00:00
|
|
|
|
if (value is "Default" or "untitled" || subject == "failed_download" || Guid.TryParse(value, out _))
|
2021-10-23 16:51:17 +00:00
|
|
|
|
return new Result {Timestamp = date};
|
2021-02-18 05:44:54 +00:00
|
|
|
|
|
2021-11-27 18:31:35 +00:00
|
|
|
|
await _metricsStore.Ingest(new Metric
|
2021-02-17 05:46:05 +00:00
|
|
|
|
{
|
2021-11-27 18:31:35 +00:00
|
|
|
|
Timestamp = DateTime.UtcNow,
|
2021-11-30 03:30:24 +00:00
|
|
|
|
Action = subject,
|
|
|
|
|
Subject = value,
|
2021-11-27 18:31:35 +00:00
|
|
|
|
MetricsKey = metricsKey,
|
|
|
|
|
UserAgent = Request.Headers.UserAgent.FirstOrDefault() ?? "<unknown>",
|
2021-10-23 16:51:17 +00:00
|
|
|
|
});
|
2021-11-27 18:31:35 +00:00
|
|
|
|
return new Result {Timestamp = date};
|
2021-10-23 16:51:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-11-29 23:35:23 +00:00
|
|
|
|
private static byte[] EOL = {(byte)'\n'};
|
2021-12-01 22:53:32 +00:00
|
|
|
|
private static byte[] LBRACKET = {(byte)'['};
|
|
|
|
|
private static byte[] RBRACKET = {(byte)']'};
|
|
|
|
|
private static byte[] COMMA = {(byte) ','};
|
2021-12-01 05:14:14 +00:00
|
|
|
|
|
2021-11-29 23:35:23 +00:00
|
|
|
|
[HttpGet]
|
2021-12-01 05:14:14 +00:00
|
|
|
|
[Route("dump")]
|
2021-11-29 23:35:23 +00:00
|
|
|
|
public async Task GetMetrics([FromQuery] string action, [FromQuery] string from, [FromQuery] string? to)
|
|
|
|
|
{
|
|
|
|
|
var parser = new Parser();
|
|
|
|
|
|
|
|
|
|
to ??= "now";
|
|
|
|
|
|
|
|
|
|
var toDate = parser.Parse(to).Start;
|
|
|
|
|
var fromDate = parser.Parse(from).Start;
|
|
|
|
|
|
|
|
|
|
var records = _metricsStore.GetRecords(fromDate!.Value, toDate!.Value, action);
|
|
|
|
|
Response.Headers.ContentType = "application/json";
|
|
|
|
|
await foreach (var record in records)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
await JsonSerializer.SerializeAsync(Response.Body, record);
|
|
|
|
|
await Response.Body.WriteAsync(EOL);
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-12-01 05:14:14 +00:00
|
|
|
|
|
|
|
|
|
[HttpGet]
|
|
|
|
|
[Route("report")]
|
2021-12-01 06:06:00 +00:00
|
|
|
|
[ResponseCache(Duration = 60 * 60 * 4, VaryByQueryKeys = new [] {"action", "from", "to"})]
|
2021-12-01 05:14:14 +00:00
|
|
|
|
public async Task GetReport([FromQuery] string action, [FromQuery] string from, [FromQuery] string? to)
|
|
|
|
|
{
|
|
|
|
|
var parser = new Parser();
|
|
|
|
|
|
|
|
|
|
to ??= "now";
|
|
|
|
|
|
|
|
|
|
var toDate = parser.Parse(to).Start!.Value.TruncateToDate();
|
|
|
|
|
|
|
|
|
|
var groupFilterStart = parser.Parse("three days ago").Start!.Value.TruncateToDate();
|
|
|
|
|
toDate = new DateTime(toDate.Year, toDate.Month, toDate.Day);
|
|
|
|
|
|
|
|
|
|
var prefetch = await _metricsStore.GetRecords(groupFilterStart, toDate, action)
|
|
|
|
|
.Where((Func<MetricResult, bool>) (d => d.Action != d.Subject))
|
|
|
|
|
.Select(async d => d.GroupingSubject)
|
|
|
|
|
.ToHashSet();;
|
|
|
|
|
|
|
|
|
|
var fromDate = parser.Parse(from).Start!.Value.TruncateToDate();
|
|
|
|
|
|
|
|
|
|
var counts = new Dictionary<(DateTime, string), long>();
|
|
|
|
|
|
|
|
|
|
await foreach (var record in _metricsStore.GetRecords(fromDate, toDate, action))
|
|
|
|
|
{
|
|
|
|
|
if (record.Subject == record.Action) continue;
|
|
|
|
|
if (!prefetch.Contains(record.GroupingSubject)) continue;
|
|
|
|
|
|
|
|
|
|
var key = (record.Timestamp.TruncateToDate(), record.GroupingSubject);
|
|
|
|
|
if (counts.TryGetValue(key, out var old))
|
|
|
|
|
counts[key] = old + 1;
|
|
|
|
|
else
|
|
|
|
|
counts[key] = 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Response.Headers.ContentType = "application/json";
|
|
|
|
|
var row = new Dictionary<string, object>();
|
2021-12-01 22:53:32 +00:00
|
|
|
|
|
|
|
|
|
await Response.Body.WriteAsync(LBRACKET);
|
2021-12-01 05:14:14 +00:00
|
|
|
|
for (var d = fromDate; d <= toDate; d = d.AddDays(1))
|
|
|
|
|
{
|
|
|
|
|
row["_Timestamp"] = d;
|
|
|
|
|
foreach (var group in prefetch)
|
|
|
|
|
{
|
|
|
|
|
if (counts.TryGetValue((d, group), out var found))
|
|
|
|
|
row[group] = found;
|
|
|
|
|
else
|
|
|
|
|
row[group] = 0;
|
|
|
|
|
}
|
|
|
|
|
await JsonSerializer.SerializeAsync(Response.Body, row);
|
|
|
|
|
await Response.Body.WriteAsync(EOL);
|
2021-12-01 22:53:32 +00:00
|
|
|
|
if (d != toDate)
|
|
|
|
|
await Response.Body.WriteAsync(COMMA);
|
2021-12-01 05:14:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-12-01 22:53:32 +00:00
|
|
|
|
await Response.Body.WriteAsync(RBRACKET);
|
|
|
|
|
|
2021-12-01 05:14:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2021-11-29 23:35:23 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
public class Result
|
|
|
|
|
{
|
|
|
|
|
public DateTime Timestamp { get; set; }
|
|
|
|
|
}
|
|
|
|
|
}
|