using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.Json; using System.Text.Json.Serialization; using System.Threading.Tasks; namespace AxCopilot.Services; public static class AgentStatsService { public record AgentSessionRecord { [JsonPropertyName("ts")] public DateTime Timestamp { get; init; } = DateTime.Now; [JsonPropertyName("tab")] public string Tab { get; init; } = ""; [JsonPropertyName("model")] public string Model { get; init; } = ""; [JsonPropertyName("calls")] public int ToolCalls { get; init; } [JsonPropertyName("ok")] public int SuccessCount { get; init; } [JsonPropertyName("fail")] public int FailCount { get; init; } [JsonPropertyName("itok")] public int InputTokens { get; init; } [JsonPropertyName("otok")] public int OutputTokens { get; init; } [JsonPropertyName("ms")] public long DurationMs { get; init; } [JsonPropertyName("tools")] public List UsedTools { get; init; } = new List(); } public record AgentStatsSummary { public int TotalSessions { get; init; } public int TotalToolCalls { get; init; } public int TotalTokens { get; init; } public int TotalInputTokens { get; init; } public int TotalOutputTokens { get; init; } public long TotalDurationMs { get; init; } public Dictionary ToolFrequency { get; init; } = new Dictionary(); public Dictionary ModelBreakdown { get; init; } = new Dictionary(); public Dictionary TabBreakdown { get; init; } = new Dictionary(); public Dictionary DailySessions { get; init; } = new Dictionary(); public Dictionary DailyTokens { get; init; } = new Dictionary(); } private static readonly string StatsDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "AxCopilot", "stats"); private static readonly string StatsFile = Path.Combine(StatsDir, "agent_stats.json"); private static readonly JsonSerializerOptions _jsonOpts = new JsonSerializerOptions { WriteIndented = false, PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; public static void RecordSession(AgentSessionRecord record) { Task.Run(delegate { try { if (!Directory.Exists(StatsDir)) { Directory.CreateDirectory(StatsDir); } string text = JsonSerializer.Serialize(record, _jsonOpts); File.AppendAllText(StatsFile, text + "\n"); } catch (Exception ex) { LogService.Warn("통계 기록 실패: " + ex.Message); } }); } public static AgentStatsSummary Aggregate(int days = 0) { List records = LoadRecords(days); return BuildSummary(records); } public static List LoadRecords(int days = 0) { if (!File.Exists(StatsFile)) { return new List(); } DateTime dateTime = ((days > 0) ? DateTime.Now.AddDays(-days) : DateTime.MinValue); List list = new List(); foreach (string item in File.ReadLines(StatsFile)) { if (string.IsNullOrWhiteSpace(item)) { continue; } try { AgentSessionRecord agentSessionRecord = JsonSerializer.Deserialize(item, _jsonOpts); if (agentSessionRecord != null && agentSessionRecord.Timestamp >= dateTime) { list.Add(agentSessionRecord); } } catch { } } return list; } private static AgentStatsSummary BuildSummary(List records) { Dictionary dictionary = new Dictionary(); Dictionary dictionary2 = new Dictionary(); Dictionary dictionary3 = new Dictionary(); Dictionary dictionary4 = new Dictionary(); Dictionary dictionary5 = new Dictionary(); int num = 0; int num2 = 0; int num3 = 0; long num4 = 0L; foreach (AgentSessionRecord record in records) { num += record.ToolCalls; num2 += record.InputTokens; num3 += record.OutputTokens; num4 += record.DurationMs; foreach (string usedTool in record.UsedTools) { dictionary[usedTool] = dictionary.GetValueOrDefault(usedTool) + 1; } if (!string.IsNullOrEmpty(record.Model)) { dictionary2[record.Model] = dictionary2.GetValueOrDefault(record.Model) + 1; } if (!string.IsNullOrEmpty(record.Tab)) { dictionary3[record.Tab] = dictionary3.GetValueOrDefault(record.Tab) + 1; } string key = record.Timestamp.ToString("yyyy-MM-dd"); dictionary4[key] = dictionary4.GetValueOrDefault(key) + 1; dictionary5[key] = dictionary5.GetValueOrDefault(key) + record.InputTokens + record.OutputTokens; } return new AgentStatsSummary { TotalSessions = records.Count, TotalToolCalls = num, TotalTokens = num2 + num3, TotalInputTokens = num2, TotalOutputTokens = num3, TotalDurationMs = num4, ToolFrequency = dictionary.OrderByDescending((KeyValuePair kv) => kv.Value).Take(10).ToDictionary((KeyValuePair kv) => kv.Key, (KeyValuePair kv) => kv.Value), ModelBreakdown = dictionary2, TabBreakdown = dictionary3, DailySessions = dictionary4, DailyTokens = dictionary5 }; } public static long GetFileSize() { return File.Exists(StatsFile) ? new FileInfo(StatsFile).Length : 0; } public static void Clear() { try { if (File.Exists(StatsFile)) { File.Delete(StatsFile); } } catch (Exception ex) { LogService.Warn("통계 삭제 실패: " + ex.Message); } } }