refine global stats
This commit is contained in:
parent
dac15cee26
commit
db8afebc5c
|
|
@ -3,6 +3,7 @@ using BotSharp.Abstraction.Plugins.Models;
|
|||
using BotSharp.Abstraction.Repositories.Filters;
|
||||
using BotSharp.Abstraction.Roles.Models;
|
||||
using BotSharp.Abstraction.Shared;
|
||||
using BotSharp.Abstraction.Statistics.Enums;
|
||||
using BotSharp.Abstraction.Statistics.Models;
|
||||
using BotSharp.Abstraction.Tasks.Models;
|
||||
using BotSharp.Abstraction.Translation.Models;
|
||||
|
|
@ -120,8 +121,10 @@ public interface IBotSharpRepository : IHaveServiceProvider
|
|||
#endregion
|
||||
|
||||
#region Statistics
|
||||
BotSharpStats? GetGlobalStats(string metric, string dimension, DateTime recordTime) => throw new NotImplementedException();
|
||||
bool SaveGlobalStats(BotSharpStats body) => throw new NotImplementedException();
|
||||
BotSharpStats? GetGlobalStats(string metric, string dimension, DateTime recordTime, StatsInterval interval)
|
||||
=> throw new NotImplementedException();
|
||||
bool SaveGlobalStats(BotSharpStats body)
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
#endregion
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
namespace BotSharp.Abstraction.Statistics.Enums;
|
||||
|
||||
public enum StatsInterval
|
||||
{
|
||||
Hour = 1,
|
||||
Day = 2,
|
||||
Week = 3,
|
||||
Month = 4
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
using BotSharp.Abstraction.Statistics.Enums;
|
||||
|
||||
namespace BotSharp.Abstraction.Statistics.Models;
|
||||
|
||||
public class BotSharpStats
|
||||
|
|
@ -11,24 +13,36 @@ public class BotSharpStats
|
|||
[JsonPropertyName("data")]
|
||||
public IDictionary<string, double> Data { get; set; } = new Dictionary<string, double>();
|
||||
|
||||
private DateTime innerRecordTime;
|
||||
|
||||
[JsonPropertyName("record_time")]
|
||||
public DateTime RecordTime
|
||||
public DateTime RecordTime { get; set; } = DateTime.UtcNow;
|
||||
|
||||
[JsonIgnore]
|
||||
public StatsInterval IntervalType { get; set; }
|
||||
|
||||
[JsonPropertyName("interval")]
|
||||
public string Interval
|
||||
{
|
||||
get
|
||||
{
|
||||
return innerRecordTime;
|
||||
}
|
||||
return IntervalType.ToString();
|
||||
}
|
||||
set
|
||||
{
|
||||
var date = new DateTime(value.Year, value.Month, value.Day, value.Hour, 0, 0);
|
||||
innerRecordTime = DateTime.SpecifyKind(date, DateTimeKind.Utc);
|
||||
if (Enum.TryParse(value, out StatsInterval type))
|
||||
{
|
||||
IntervalType = type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[JsonPropertyName("start_time")]
|
||||
public DateTime StartTime { get; set; }
|
||||
|
||||
[JsonPropertyName("end_time")]
|
||||
public DateTime EndTime { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Metric}-{Dimension}: {Data?.Count ?? 0} ({RecordTime})";
|
||||
return $"{Metric}-{Dimension} ({Interval}): {Data?.Count ?? 0}";
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
using BotSharp.Abstraction.Statistics.Enums;
|
||||
|
||||
namespace BotSharp.Abstraction.Statistics.Models;
|
||||
|
||||
public class BotSharpStatsInput
|
||||
|
|
@ -5,5 +7,6 @@ public class BotSharpStatsInput
|
|||
public string Metric { get; set; }
|
||||
public string Dimension { get; set; }
|
||||
public List<StatsKeyValuePair> Data { get; set; } = [];
|
||||
public DateTime RecordTime { get; set; }
|
||||
public DateTime RecordTime { get; set; } = DateTime.UtcNow;
|
||||
public StatsInterval IntervalType { get; set; } = StatsInterval.Day;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ public class TokenStatistics : ITokenStatistics
|
|||
Metric = StatsCategory.AgentLlmCost,
|
||||
Dimension = message.CurrentAgentId,
|
||||
RecordTime = DateTime.UtcNow,
|
||||
IntervalType = StatsInterval.Day,
|
||||
Data = [
|
||||
new StatsKeyValuePair("prompt_token_count_total", stats.PromptCount),
|
||||
new StatsKeyValuePair("completion_token_count_total", stats.CompletionCount),
|
||||
|
|
|
|||
|
|
@ -4,28 +4,34 @@ namespace BotSharp.Core.Repository;
|
|||
|
||||
public partial class FileRepository
|
||||
{
|
||||
public BotSharpStats? GetGlobalStats(string metric, string dimension, DateTime recordTime)
|
||||
public BotSharpStats? GetGlobalStats(string metric, string dimension, DateTime recordTime, StatsInterval interval)
|
||||
{
|
||||
var baseDir = Path.Combine(_dbSettings.FileRepository, STATS_FOLDER);
|
||||
var dir = Path.Combine(baseDir, metric, recordTime.Year.ToString(), recordTime.Month.ToString("D2"));
|
||||
var (startTime, endTime) = BuildTimeInterval(recordTime, interval);
|
||||
var dir = Path.Combine(baseDir, metric, startTime.Year.ToString(), startTime.Month.ToString("D2"));
|
||||
if (!Directory.Exists(dir)) return null;
|
||||
|
||||
var file = Directory.GetFiles(dir).FirstOrDefault(x => Path.GetFileName(x) == STATS_FILE);
|
||||
if (file == null) return null;
|
||||
|
||||
var time = BuildRecordTime(recordTime);
|
||||
var text = File.ReadAllText(file);
|
||||
var list = JsonSerializer.Deserialize<List<BotSharpStats>>(text, _options);
|
||||
var found = list?.FirstOrDefault(x => x.Metric.IsEqualTo(metric)
|
||||
&& x.Dimension.IsEqualTo(dimension)
|
||||
&& x.RecordTime == time);
|
||||
&& x.StartTime == startTime
|
||||
&& x.EndTime == endTime);
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
public bool SaveGlobalStats(BotSharpStats body)
|
||||
{
|
||||
var baseDir = Path.Combine(_dbSettings.FileRepository, STATS_FOLDER);
|
||||
var dir = Path.Combine(baseDir, body.Metric, body.RecordTime.Year.ToString(), body.RecordTime.Month.ToString("D2"));
|
||||
var (startTime, endTime) = BuildTimeInterval(body.RecordTime, body.IntervalType);
|
||||
body.StartTime = startTime;
|
||||
body.EndTime = endTime;
|
||||
|
||||
var dir = Path.Combine(baseDir, body.Metric, startTime.Year.ToString(), startTime.Month.ToString("D2"));
|
||||
if (!Directory.Exists(dir))
|
||||
{
|
||||
Directory.CreateDirectory(dir);
|
||||
|
|
@ -39,12 +45,12 @@ public partial class FileRepository
|
|||
}
|
||||
else
|
||||
{
|
||||
var time = BuildRecordTime(body.RecordTime);
|
||||
var text = File.ReadAllText(file);
|
||||
var list = JsonSerializer.Deserialize<List<BotSharpStats>>(text, _options);
|
||||
var found = list?.FirstOrDefault(x => x.Metric.IsEqualTo(body.Metric)
|
||||
&& x.Dimension.IsEqualTo(body.Dimension)
|
||||
&& x.RecordTime == time);
|
||||
&& x.StartTime == startTime
|
||||
&& x.EndTime == endTime);
|
||||
|
||||
if (found != null)
|
||||
{
|
||||
|
|
@ -52,6 +58,9 @@ public partial class FileRepository
|
|||
found.Dimension = body.Dimension;
|
||||
found.Data = body.Data;
|
||||
found.RecordTime = body.RecordTime;
|
||||
found.StartTime = body.StartTime;
|
||||
found.EndTime = body.EndTime;
|
||||
found.Interval = body.Interval;
|
||||
}
|
||||
else if (list != null)
|
||||
{
|
||||
|
|
@ -69,10 +78,36 @@ public partial class FileRepository
|
|||
}
|
||||
|
||||
#region Private methods
|
||||
private DateTime BuildRecordTime(DateTime date)
|
||||
private (DateTime, DateTime) BuildTimeInterval(DateTime recordTime, StatsInterval interval)
|
||||
{
|
||||
var recordDate = new DateTime(date.Year, date.Month, date.Day, date.Hour, 0, 0);
|
||||
return DateTime.SpecifyKind(recordDate, DateTimeKind.Utc);
|
||||
DateTime startTime = recordTime;
|
||||
DateTime endTime = DateTime.UtcNow;
|
||||
|
||||
switch (interval)
|
||||
{
|
||||
case StatsInterval.Hour:
|
||||
startTime = new DateTime(recordTime.Year, recordTime.Month, recordTime.Day, recordTime.Hour, 0, 0);
|
||||
endTime = startTime.AddHours(1);
|
||||
break;
|
||||
case StatsInterval.Week:
|
||||
var dayOfWeek = startTime.DayOfWeek;
|
||||
var firstDayOfWeek = startTime.AddDays(-(int)dayOfWeek);
|
||||
startTime = new DateTime(firstDayOfWeek.Year, firstDayOfWeek.Month, firstDayOfWeek.Day, 0, 0, 0);
|
||||
endTime = startTime.AddDays(7);
|
||||
break;
|
||||
case StatsInterval.Month:
|
||||
startTime = new DateTime(recordTime.Year, recordTime.Month, 1);
|
||||
endTime = startTime.AddMonths(1);
|
||||
break;
|
||||
default:
|
||||
startTime = new DateTime(recordTime.Year, recordTime.Month, recordTime.Day, 0, 0, 0);
|
||||
endTime = startTime.AddDays(1);
|
||||
break;
|
||||
}
|
||||
|
||||
startTime = DateTime.SpecifyKind(startTime, DateTimeKind.Utc);
|
||||
endTime = DateTime.SpecifyKind(endTime, DateTimeKind.Utc);
|
||||
return (startTime, endTime);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,8 @@ public class BotSharpStatsService : IBotSharpStatsService
|
|||
|| string.IsNullOrEmpty(resourceKey)
|
||||
|| input == null
|
||||
|| string.IsNullOrEmpty(input.Metric)
|
||||
|| string.IsNullOrEmpty(input.Dimension))
|
||||
|| string.IsNullOrEmpty(input.Dimension)
|
||||
|| input.Data.IsNullOrEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
@ -39,7 +40,7 @@ public class BotSharpStatsService : IBotSharpStatsService
|
|||
var res = locker.Lock(resourceKey, () =>
|
||||
{
|
||||
var db = _services.GetRequiredService<IBotSharpRepository>();
|
||||
var body = db.GetGlobalStats(input.Metric, input.Dimension, input.RecordTime);
|
||||
var body = db.GetGlobalStats(input.Metric, input.Dimension, input.RecordTime, input.IntervalType);
|
||||
if (body == null)
|
||||
{
|
||||
var stats = new BotSharpStats
|
||||
|
|
@ -47,6 +48,7 @@ public class BotSharpStatsService : IBotSharpStatsService
|
|||
Metric = input.Metric,
|
||||
Dimension = input.Dimension,
|
||||
RecordTime = input.RecordTime,
|
||||
IntervalType = input.IntervalType,
|
||||
Data = input.Data.ToDictionary(x => x.Key, x => x.Value)
|
||||
};
|
||||
db.SaveGlobalStats(stats);
|
||||
|
|
|
|||
|
|
@ -33,10 +33,11 @@ public class GlobalStatsConversationHook : ConversationHookBase
|
|||
{
|
||||
Metric = StatsCategory.AgentCall,
|
||||
Dimension = message.CurrentAgentId,
|
||||
RecordTime = DateTime.UtcNow,
|
||||
IntervalType = StatsInterval.Day,
|
||||
Data = [
|
||||
new StatsKeyValuePair("agent_call_count", 1)
|
||||
],
|
||||
RecordTime = DateTime.UtcNow
|
||||
]
|
||||
};
|
||||
globalStats.UpdateStats("global-agent-call", body);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
using BotSharp.Abstraction.Statistics.Enums;
|
||||
|
||||
namespace BotSharp.Plugin.MongoStorage.Collections;
|
||||
|
||||
public class GlobalStatisticsDocument : MongoBase
|
||||
|
|
@ -6,4 +8,7 @@ public class GlobalStatisticsDocument : MongoBase
|
|||
public string Dimension { get; set; }
|
||||
public IDictionary<string, double> Data { get; set; } = new Dictionary<string, double>();
|
||||
public DateTime RecordTime { get; set; }
|
||||
public DateTime StartTime { get; set; }
|
||||
public DateTime EndTime { get; set; }
|
||||
public string Interval { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,21 @@
|
|||
using BotSharp.Abstraction.Statistics.Enums;
|
||||
using BotSharp.Abstraction.Statistics.Models;
|
||||
|
||||
namespace BotSharp.Plugin.MongoStorage.Repository;
|
||||
|
||||
public partial class MongoRepository
|
||||
{
|
||||
public BotSharpStats? GetGlobalStats(string metric, string dimension, DateTime recordTime)
|
||||
public BotSharpStats? GetGlobalStats(string metric, string dimension, DateTime recordTime, StatsInterval interval)
|
||||
{
|
||||
var time = BuildRecordTime(recordTime);
|
||||
var (startTime, endTime) = BuildTimeInterval(recordTime, interval);
|
||||
|
||||
var builder = Builders<GlobalStatisticsDocument>.Filter;
|
||||
var filters = new List<FilterDefinition<GlobalStatisticsDocument>>()
|
||||
{
|
||||
builder.Eq(x => x.Metric, metric),
|
||||
builder.Eq(x => x.Dimension, dimension),
|
||||
builder.Eq(x => x.RecordTime, time)
|
||||
builder.Eq(x => x.StartTime, startTime),
|
||||
builder.Eq(x => x.EndTime, endTime)
|
||||
};
|
||||
|
||||
var filterDef = builder.And(filters);
|
||||
|
|
@ -25,19 +27,27 @@ public partial class MongoRepository
|
|||
Metric = found.Metric,
|
||||
Dimension = found.Dimension,
|
||||
Data = found.Data,
|
||||
RecordTime = found.RecordTime
|
||||
RecordTime = found.RecordTime,
|
||||
StartTime = startTime,
|
||||
EndTime = endTime,
|
||||
Interval = interval.ToString()
|
||||
};
|
||||
}
|
||||
|
||||
public bool SaveGlobalStats(BotSharpStats body)
|
||||
{
|
||||
var time = BuildRecordTime(body.RecordTime);
|
||||
var (startTime, endTime) = BuildTimeInterval(body.RecordTime, body.IntervalType);
|
||||
body.RecordTime = DateTime.SpecifyKind(body.RecordTime, DateTimeKind.Utc);
|
||||
body.StartTime = startTime;
|
||||
body.EndTime = endTime;
|
||||
|
||||
var builder = Builders<GlobalStatisticsDocument>.Filter;
|
||||
var filters = new List<FilterDefinition<GlobalStatisticsDocument>>()
|
||||
{
|
||||
builder.Eq(x => x.Metric, body.Metric),
|
||||
builder.Eq(x => x.Dimension, body.Dimension),
|
||||
builder.Eq(x => x.RecordTime, time)
|
||||
builder.Eq(x => x.StartTime, startTime),
|
||||
builder.Eq(x => x.EndTime, endTime)
|
||||
};
|
||||
|
||||
var filterDef = builder.And(filters);
|
||||
|
|
@ -46,17 +56,46 @@ public partial class MongoRepository
|
|||
.Set(x => x.Metric, body.Metric)
|
||||
.Set(x => x.Dimension, body.Dimension)
|
||||
.Set(x => x.Data, body.Data)
|
||||
.Set(x => x.RecordTime, time);
|
||||
.Set(x => x.StartTime, body.StartTime)
|
||||
.Set(x => x.EndTime, body.EndTime)
|
||||
.Set(x => x.Interval, body.Interval)
|
||||
.Set(x => x.RecordTime, body.RecordTime);
|
||||
|
||||
_dc.GlobalStatistics.UpdateOne(filterDef, updateDef, _options);
|
||||
return true;
|
||||
}
|
||||
|
||||
#region Private methods
|
||||
private DateTime BuildRecordTime(DateTime date)
|
||||
private (DateTime, DateTime) BuildTimeInterval(DateTime recordTime, StatsInterval interval)
|
||||
{
|
||||
var recordDate = new DateTime(date.Year, date.Month, date.Day, date.Hour, 0, 0);
|
||||
return DateTime.SpecifyKind(recordDate, DateTimeKind.Utc);
|
||||
DateTime startTime = recordTime;
|
||||
DateTime endTime = DateTime.UtcNow;
|
||||
|
||||
switch (interval)
|
||||
{
|
||||
case StatsInterval.Hour:
|
||||
startTime = new DateTime(recordTime.Year, recordTime.Month, recordTime.Day, recordTime.Hour, 0, 0);
|
||||
endTime = startTime.AddHours(1);
|
||||
break;
|
||||
case StatsInterval.Week:
|
||||
var dayOfWeek = startTime.DayOfWeek;
|
||||
var firstDayOfWeek = startTime.AddDays(-(int)dayOfWeek);
|
||||
startTime = new DateTime(firstDayOfWeek.Year, firstDayOfWeek.Month, firstDayOfWeek.Day, 0, 0, 0);
|
||||
endTime = startTime.AddDays(7);
|
||||
break;
|
||||
case StatsInterval.Month:
|
||||
startTime = new DateTime(recordTime.Year, recordTime.Month, 1);
|
||||
endTime = startTime.AddMonths(1);
|
||||
break;
|
||||
default:
|
||||
startTime = new DateTime(recordTime.Year, recordTime.Month, recordTime.Day, 0, 0, 0);
|
||||
endTime = startTime.AddDays(1);
|
||||
break;
|
||||
}
|
||||
|
||||
startTime = DateTime.SpecifyKind(startTime, DateTimeKind.Utc);
|
||||
endTime = DateTime.SpecifyKind(endTime, DateTimeKind.Utc);
|
||||
return (startTime, endTime);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue