158 lines
11 KiB
C#
158 lines
11 KiB
C#
using System.Text;
|
|
|
|
namespace QuestionsApi.Services;
|
|
|
|
public sealed class ServiceMetrics
|
|
{
|
|
private long _attemptsCreated;
|
|
private long _attemptDuplicates;
|
|
private long _counterFailures;
|
|
private long _ratingFailures;
|
|
private long _reconciliations;
|
|
private long _lastReconciliationUnixSeconds;
|
|
private long _httpRequests;
|
|
private long _httpDurationMicroseconds;
|
|
private long _answerStatisticsBackfillComplete;
|
|
private readonly System.Collections.Concurrent.ConcurrentDictionary<string, long> _dynamicSelections = new();
|
|
private readonly System.Collections.Concurrent.ConcurrentDictionary<string, long> _dynamicAttempts = new();
|
|
private readonly System.Collections.Concurrent.ConcurrentDictionary<string, long> _dynamicCorrect = new();
|
|
private long _dynamicTargetGapTotal;
|
|
private readonly System.Collections.Concurrent.ConcurrentDictionary<string, long> _creditAwards = new();
|
|
private readonly System.Collections.Concurrent.ConcurrentDictionary<string, long> _creditCoins = new();
|
|
private readonly System.Collections.Concurrent.ConcurrentDictionary<string, long> _creditDuplicates = new();
|
|
private readonly System.Collections.Concurrent.ConcurrentDictionary<string, long> _creditCapped = new();
|
|
private readonly System.Collections.Concurrent.ConcurrentDictionary<string, long> _dailyMissionAssigned = new();
|
|
private readonly System.Collections.Concurrent.ConcurrentDictionary<string, long> _dailyMissionCompleted = new();
|
|
private readonly System.Collections.Concurrent.ConcurrentDictionary<string, long> _dailyMissionExpired = new();
|
|
private long _dailyMissionReplaced;
|
|
private readonly System.Collections.Concurrent.ConcurrentDictionary<string, long> _weeklyGoalAssigned = new();
|
|
private readonly System.Collections.Concurrent.ConcurrentDictionary<string, long> _weeklyGoalCompleted = new();
|
|
private long _weeklyGoalExpiredIncomplete;
|
|
private long _weeklyGoalLateFinalized;
|
|
|
|
public void AttemptCreated() => Interlocked.Increment(ref _attemptsCreated);
|
|
public void AttemptDuplicate() => Interlocked.Increment(ref _attemptDuplicates);
|
|
public void CounterFailure() => Interlocked.Increment(ref _counterFailures);
|
|
public void RatingFailure() => Interlocked.Increment(ref _ratingFailures);
|
|
public void AnswerStatisticsBackfillCompleted() => Interlocked.Exchange(ref _answerStatisticsBackfillComplete, 1);
|
|
public void Reconciled()
|
|
{
|
|
Interlocked.Increment(ref _reconciliations);
|
|
Interlocked.Exchange(ref _lastReconciliationUnixSeconds, DateTimeOffset.UtcNow.ToUnixTimeSeconds());
|
|
}
|
|
public void HttpRequest(TimeSpan duration)
|
|
{
|
|
Interlocked.Increment(ref _httpRequests);
|
|
Interlocked.Add(ref _httpDurationMicroseconds, (long)(duration.TotalMilliseconds * 1000));
|
|
}
|
|
public void DynamicSelected(string tier, int targetGap)
|
|
{
|
|
_dynamicSelections.AddOrUpdate(tier, 1, (_, value) => value + 1);
|
|
Interlocked.Add(ref _dynamicTargetGapTotal, Math.Abs(targetGap));
|
|
}
|
|
public void DynamicAttempt(string tier, bool correct)
|
|
{
|
|
_dynamicAttempts.AddOrUpdate(tier, 1, (_, value) => value + 1);
|
|
if (correct) _dynamicCorrect.AddOrUpdate(tier, 1, (_, value) => value + 1);
|
|
}
|
|
public void CreditAward(string reason, int amount)
|
|
{
|
|
_creditAwards.AddOrUpdate(reason, 1, (_, value) => value + 1);
|
|
_creditCoins.AddOrUpdate(reason, amount, (_, value) => value + amount);
|
|
}
|
|
public void CreditDuplicate(string reason) => _creditDuplicates.AddOrUpdate(reason, 1, (_, value) => value + 1);
|
|
public void CreditCapped(string reason) => _creditCapped.AddOrUpdate(reason, 1, (_, value) => value + 1);
|
|
public void DailyMissionAssigned(string type) => _dailyMissionAssigned.AddOrUpdate(type, 1, (_, value) => value + 1);
|
|
public void DailyMissionCompleted(string type) => _dailyMissionCompleted.AddOrUpdate(type, 1, (_, value) => value + 1);
|
|
public void DailyMissionExpired(string type) => _dailyMissionExpired.AddOrUpdate(type, 1, (_, value) => value + 1);
|
|
public void DailyMissionReplaced() => Interlocked.Increment(ref _dailyMissionReplaced);
|
|
public void WeeklyGoalAssigned(string type) => _weeklyGoalAssigned.AddOrUpdate(type, 1, (_, value) => value + 1);
|
|
public void WeeklyGoalCompleted(string type) => _weeklyGoalCompleted.AddOrUpdate(type, 1, (_, value) => value + 1);
|
|
public void WeeklyGoalExpiredIncomplete() => Interlocked.Increment(ref _weeklyGoalExpiredIncomplete);
|
|
public void WeeklyGoalLateFinalized() => Interlocked.Increment(ref _weeklyGoalLateFinalized);
|
|
|
|
public string RenderPrometheus()
|
|
{
|
|
var output = new StringBuilder();
|
|
AppendCounter(output, "step3_question_attempts_created_total", Interlocked.Read(ref _attemptsCreated));
|
|
AppendCounter(output, "step3_question_attempt_duplicates_total", Interlocked.Read(ref _attemptDuplicates));
|
|
AppendCounter(output, "step3_answer_counter_failures_total", Interlocked.Read(ref _counterFailures));
|
|
AppendCounter(output, "step3_rating_failures_total", Interlocked.Read(ref _ratingFailures));
|
|
AppendCounter(output, "step3_answer_reconciliations_total", Interlocked.Read(ref _reconciliations));
|
|
AppendCounter(output, "step3_http_requests_total", Interlocked.Read(ref _httpRequests));
|
|
output.AppendLine("# TYPE step3_dynamic_selections_total counter");
|
|
output.AppendLine("# TYPE step3_dynamic_attempts_total counter");
|
|
output.AppendLine("# TYPE step3_dynamic_correct_total counter");
|
|
foreach (var tier in _dynamicSelections.Keys.Order())
|
|
{
|
|
AppendLabeledSample(output, "step3_dynamic_selections_total", tier, _dynamicSelections.GetValueOrDefault(tier));
|
|
AppendLabeledSample(output, "step3_dynamic_attempts_total", tier, _dynamicAttempts.GetValueOrDefault(tier));
|
|
AppendLabeledSample(output, "step3_dynamic_correct_total", tier, _dynamicCorrect.GetValueOrDefault(tier));
|
|
}
|
|
AppendCounter(output, "step3_dynamic_target_gap_points_total", Interlocked.Read(ref _dynamicTargetGapTotal));
|
|
output.AppendLine("# TYPE step3_credit_awards_total counter");
|
|
output.AppendLine("# TYPE step3_credit_coins_awarded_total counter");
|
|
output.AppendLine("# TYPE step3_credit_award_duplicates_total counter");
|
|
output.AppendLine("# TYPE step3_credit_award_capped_total counter");
|
|
foreach (var reason in _creditAwards.Keys.Concat(_creditDuplicates.Keys).Concat(_creditCapped.Keys).Distinct().Order())
|
|
{
|
|
AppendReasonSample(output, "step3_credit_awards_total", reason, _creditAwards.GetValueOrDefault(reason));
|
|
AppendReasonSample(output, "step3_credit_coins_awarded_total", reason, _creditCoins.GetValueOrDefault(reason));
|
|
AppendReasonSample(output, "step3_credit_award_duplicates_total", reason, _creditDuplicates.GetValueOrDefault(reason));
|
|
AppendReasonSample(output, "step3_credit_award_capped_total", reason, _creditCapped.GetValueOrDefault(reason));
|
|
}
|
|
output.AppendLine("# TYPE step3_daily_missions_assigned_total counter");
|
|
output.AppendLine("# TYPE step3_daily_missions_completed_total counter");
|
|
output.AppendLine("# TYPE step3_daily_missions_expired_incomplete_total counter");
|
|
foreach (var type in _dailyMissionAssigned.Keys.Concat(_dailyMissionCompleted.Keys).Concat(_dailyMissionExpired.Keys).Distinct().Order())
|
|
{
|
|
AppendMissionSample(output, "step3_daily_missions_assigned_total", type, _dailyMissionAssigned.GetValueOrDefault(type));
|
|
AppendMissionSample(output, "step3_daily_missions_completed_total", type, _dailyMissionCompleted.GetValueOrDefault(type));
|
|
AppendMissionSample(output, "step3_daily_missions_expired_incomplete_total", type, _dailyMissionExpired.GetValueOrDefault(type));
|
|
}
|
|
AppendCounter(output, "step3_daily_missions_replaced_total", Interlocked.Read(ref _dailyMissionReplaced));
|
|
output.AppendLine("# TYPE step3_weekly_goals_assigned_total counter");
|
|
output.AppendLine("# TYPE step3_weekly_goals_completed_total counter");
|
|
foreach (var type in _weeklyGoalAssigned.Keys.Concat(_weeklyGoalCompleted.Keys).Distinct().Order())
|
|
{
|
|
AppendMissionSample(output, "step3_weekly_goals_assigned_total", type, _weeklyGoalAssigned.GetValueOrDefault(type));
|
|
AppendMissionSample(output, "step3_weekly_goals_completed_total", type, _weeklyGoalCompleted.GetValueOrDefault(type));
|
|
}
|
|
AppendCounter(output, "step3_weekly_goals_expired_incomplete_total", Interlocked.Read(ref _weeklyGoalExpiredIncomplete));
|
|
AppendCounter(output, "step3_weekly_goals_late_finalized_total", Interlocked.Read(ref _weeklyGoalLateFinalized));
|
|
output.AppendLine("# TYPE step3_http_request_duration_seconds summary");
|
|
output.Append("step3_http_request_duration_seconds_count ")
|
|
.Append(Interlocked.Read(ref _httpRequests))
|
|
.AppendLine();
|
|
output.Append("step3_http_request_duration_seconds_sum ")
|
|
.Append((Interlocked.Read(ref _httpDurationMicroseconds) / 1_000_000.0).ToString("0.######", System.Globalization.CultureInfo.InvariantCulture))
|
|
.AppendLine();
|
|
output.AppendLine("# TYPE step3_answer_last_reconciliation_unixtime gauge");
|
|
output.Append("step3_answer_last_reconciliation_unixtime ")
|
|
.Append(Interlocked.Read(ref _lastReconciliationUnixSeconds))
|
|
.AppendLine();
|
|
output.AppendLine("# TYPE step3_answer_statistics_v2_backfill_complete gauge");
|
|
output.Append("step3_answer_statistics_v2_backfill_complete ")
|
|
.Append(Interlocked.Read(ref _answerStatisticsBackfillComplete))
|
|
.AppendLine();
|
|
return output.ToString();
|
|
}
|
|
|
|
private static void AppendCounter(StringBuilder output, string name, long value)
|
|
{
|
|
output.Append("# TYPE ").Append(name).AppendLine(" counter");
|
|
output.Append(name).Append(' ').Append(value).AppendLine();
|
|
}
|
|
|
|
private static void AppendLabeledSample(StringBuilder output, string name, string tier, long value)
|
|
{
|
|
output.Append(name).Append("{tier=\"").Append(tier.Replace("\"", "")).Append("\"} ").Append(value).AppendLine();
|
|
}
|
|
|
|
private static void AppendReasonSample(StringBuilder output, string name, string reason, long value) =>
|
|
output.Append(name).Append("{reason=\"").Append(reason.Replace("\"", "")).Append("\"} ").Append(value).AppendLine();
|
|
|
|
private static void AppendMissionSample(StringBuilder output, string name, string type, long value) =>
|
|
output.Append(name).Append("{type=\"").Append(type.Replace("\"", "")).Append("\"} ").Append(value).AppendLine();
|
|
}
|