205 lines
5.5 KiB
C#
205 lines
5.5 KiB
C#
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<string> UsedTools { get; init; } = new List<string>();
|
|
}
|
|
|
|
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<string, int> ToolFrequency { get; init; } = new Dictionary<string, int>();
|
|
|
|
public Dictionary<string, int> ModelBreakdown { get; init; } = new Dictionary<string, int>();
|
|
|
|
public Dictionary<string, int> TabBreakdown { get; init; } = new Dictionary<string, int>();
|
|
|
|
public Dictionary<string, int> DailySessions { get; init; } = new Dictionary<string, int>();
|
|
|
|
public Dictionary<string, int> DailyTokens { get; init; } = new Dictionary<string, int>();
|
|
}
|
|
|
|
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<AgentSessionRecord> records = LoadRecords(days);
|
|
return BuildSummary(records);
|
|
}
|
|
|
|
public static List<AgentSessionRecord> LoadRecords(int days = 0)
|
|
{
|
|
if (!File.Exists(StatsFile))
|
|
{
|
|
return new List<AgentSessionRecord>();
|
|
}
|
|
DateTime dateTime = ((days > 0) ? DateTime.Now.AddDays(-days) : DateTime.MinValue);
|
|
List<AgentSessionRecord> list = new List<AgentSessionRecord>();
|
|
foreach (string item in File.ReadLines(StatsFile))
|
|
{
|
|
if (string.IsNullOrWhiteSpace(item))
|
|
{
|
|
continue;
|
|
}
|
|
try
|
|
{
|
|
AgentSessionRecord agentSessionRecord = JsonSerializer.Deserialize<AgentSessionRecord>(item, _jsonOpts);
|
|
if (agentSessionRecord != null && agentSessionRecord.Timestamp >= dateTime)
|
|
{
|
|
list.Add(agentSessionRecord);
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
}
|
|
return list;
|
|
}
|
|
|
|
private static AgentStatsSummary BuildSummary(List<AgentSessionRecord> records)
|
|
{
|
|
Dictionary<string, int> dictionary = new Dictionary<string, int>();
|
|
Dictionary<string, int> dictionary2 = new Dictionary<string, int>();
|
|
Dictionary<string, int> dictionary3 = new Dictionary<string, int>();
|
|
Dictionary<string, int> dictionary4 = new Dictionary<string, int>();
|
|
Dictionary<string, int> dictionary5 = new Dictionary<string, int>();
|
|
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<string, int> kv) => kv.Value).Take(10).ToDictionary((KeyValuePair<string, int> kv) => kv.Key, (KeyValuePair<string, int> 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);
|
|
}
|
|
}
|
|
}
|