backend: serve single-agent task templates with their status and results, rename the kinds in storage
This commit is contained in:
1 parent
e77880d372
commit
3a9ea46d6f
45 files changed
+763
-216
No files matched your search
@@ -45,3 +45,12 @@ public sealed record CompareResult(
|
||||
string Metric,
|
||||
QueryRange Range,
|
||||
SourcePlan Plan);
|
||||
|
||||
/// <summary>单个 Agent 任务(目标任务)的时序:和线路时序同一套分层与汇总,只是没有线路、事件和评分。</summary>
|
||||
public sealed record TargetSeriesResult(
|
||||
QueryRange Range,
|
||||
SourcePlan Plan,
|
||||
IReadOnlyList<long> T,
|
||||
IReadOnlyDictionary<string, double?[]> Values,
|
||||
SummaryResult Summary,
|
||||
IReadOnlyDictionary<string, string> Versions);
|
||||
@@ -138,6 +138,61 @@ public sealed class QueryService
|
||||
return Result<SeriesResult>.Ok(new SeriesResult(line, p.Range, p.Plan, bucketed.T, values, summary, gaps, events, versions));
|
||||
}
|
||||
|
||||
/// <summary>某个 Agent 上一个单个 Agent 任务的时序(序列 <c>target:<任务 id></c>)。</summary>
|
||||
public async ValueTask<Result<TargetSeriesResult>> TargetSeriesAsync(NodeId agent, TaskId task, RangeRequest request, MetricSelection selection, CancellationToken ct)
|
||||
{
|
||||
Result<(QueryRange Range, SourcePlan Plan)> planned = Plan(request, "3h");
|
||||
if (!planned.TryGet(out (QueryRange Range, SourcePlan Plan) p, out FleetError? error))
|
||||
{
|
||||
return Result<TargetSeriesResult>.Fail(error);
|
||||
}
|
||||
|
||||
Result<IReadOnlyList<SourceRow>> loaded = await _loader.LoadAsync(p.Plan, p.Range, agent, TargetSeries(task), ct);
|
||||
if (!loaded.TryGet(out IReadOnlyList<SourceRow>? rows, out error))
|
||||
{
|
||||
return Result<TargetSeriesResult>.Fail(error);
|
||||
}
|
||||
|
||||
BucketedRows bucketed = BucketedRows.Create(rows, p.Range);
|
||||
int n = p.Range.Points;
|
||||
var values = selection.ExportIds.ToDictionary(id => id, _ => new double?[n], StringComparer.Ordinal);
|
||||
double widthSeconds = p.Plan.LevelWidthMs / 1000.0;
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
if (bucketed.Sets[i] is not { } set)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Dictionary<string, double> exported = ExportEvaluator.Evaluate(set, selection.ExportIds, bucketed.RowCounts[i] * widthSeconds);
|
||||
foreach (string id in selection.ExportIds)
|
||||
{
|
||||
if (exported.TryGetValue(id, out double v))
|
||||
{
|
||||
values[id][i] = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var versions = new Dictionary<string, string>(StringComparer.Ordinal);
|
||||
foreach (string id in selection.ExportIds)
|
||||
{
|
||||
if (MetricRegistry.TryFindExport(id, out MetricDef def, out _))
|
||||
{
|
||||
versions[id] = def.Fingerprint;
|
||||
}
|
||||
}
|
||||
|
||||
SummaryResult summary = Summarize(bucketed.Merge(), bucketed.TotalRows, p.Plan, p.Range, selection, new Dictionary<string, double>());
|
||||
return Result<TargetSeriesResult>.Ok(new TargetSeriesResult(p.Range, p.Plan, bucketed.T, values, summary, versions));
|
||||
}
|
||||
|
||||
/// <summary>这个单个 Agent 任务最后一个样本的时刻(Unix 毫秒);还没有数据返回 null。</summary>
|
||||
public ValueTask<long?> TargetLatestAsync(NodeId agent, TaskId task, CancellationToken ct) =>
|
||||
_loader.LatestSampleMsAsync(agent, TargetSeries(task), _time.GetUtcNow().ToUnixTimeMilliseconds(), ct);
|
||||
|
||||
private static string TargetSeries(TaskId task) => "target:" + task.Value;
|
||||
|
||||
/// <summary>这条线路最早的数据(Unix 毫秒);没有数据返回 null。</summary>
|
||||
public ValueTask<long?> EarliestAsync(LineKey line, CancellationToken ct) =>
|
||||
_loader.EarliestSampleMsAsync(line.Listener, line.Series, _time.GetUtcNow().ToUnixTimeMilliseconds(), ct);
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
using Fleet.Server;
|
||||
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
/// <summary>一个 Agent 上正在(或应该)运行的单个 Agent 任务:这个 Agent 的有效任务集里 <c>scope=target</c> 的那些。</summary>
|
||||
public sealed record TargetView(AgentView Agent, EffectiveTask Task);
|
||||
|
||||
/// <summary>单个 Agent 任务目录:按 Agent 展开模板、覆盖和专属任务,列出每个 Agent 实际执行的目标。</summary>
|
||||
public sealed class TargetService
|
||||
{
|
||||
private readonly AgentService _agents;
|
||||
private readonly ITaskStore _tasks;
|
||||
|
||||
public TargetService(AgentService agents, ITaskStore tasks)
|
||||
{
|
||||
_agents = agents;
|
||||
_tasks = tasks;
|
||||
}
|
||||
|
||||
public async ValueTask<IReadOnlyList<TargetView>> ListAsync(Func<AgentView, bool> include, CancellationToken ct)
|
||||
{
|
||||
IReadOnlyList<TaskRecord> all = await _tasks.ListAsync(ct);
|
||||
var result = new List<TargetView>();
|
||||
foreach (AgentView view in await _agents.ListAsync(ct))
|
||||
{
|
||||
if (view.Agent.Status != NodeStatus.Active || !include(view))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (EffectiveTask task in EffectiveTasks.Compute(all, view.Agent, filterByAgentKinds: false).Where(t => t.Scope == EffectiveTasks.TargetScope))
|
||||
{
|
||||
result.Add(new TargetView(view, task));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user