feat(ops): upload node logs and task traffic [CI]
This commit is contained in:
1 parent
e82b165eb0
commit
6cf6b23585
25 files changed
+714
-13
No files matched your search
@@ -18,6 +18,7 @@ public sealed class ChunkAssembler
|
||||
private readonly NodeId _agent;
|
||||
private readonly TimeProvider _time;
|
||||
private readonly INodeLog _log;
|
||||
private readonly TaskTrafficReporter _traffic;
|
||||
|
||||
public ChunkAssembler(StreamRegistry streams, MonoClock clock, AgentCounters counters, Outbox outbox, NodeId agent, TimeProvider time, INodeLog log)
|
||||
{
|
||||
@@ -28,6 +29,7 @@ public sealed class ChunkAssembler
|
||||
_agent = agent;
|
||||
_time = time;
|
||||
_log = log;
|
||||
_traffic = new TaskTrafficReporter(log, time);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -68,9 +70,20 @@ public sealed class ChunkAssembler
|
||||
return true;
|
||||
}
|
||||
|
||||
public Task RunTrafficReportingAsync(CancellationToken ct) => _traffic.RunAsync(ct);
|
||||
|
||||
public void FlushTraffic() => _traffic.Flush();
|
||||
|
||||
/// <summary>Agent 自己采集的块(目标任务)直接发出。</summary>
|
||||
public void Emit(RawChunk chunk)
|
||||
{
|
||||
_outbox.Enqueue(MoShiroChannels.RawChunk, chunk.Header.ChunkId.Value, RawChunkCodec.Encode(chunk));
|
||||
byte[] payload = RawChunkCodec.Encode(chunk, out int uncompressedBytes);
|
||||
_outbox.Enqueue(MoShiroChannels.RawChunk, chunk.Header.ChunkId.Value, payload);
|
||||
_traffic.Record(
|
||||
chunk.Header.TaskId?.Value ?? chunk.Header.TestId?.Value ?? "unknown",
|
||||
chunk.Header.Kind,
|
||||
chunk.Streams.Sum(stream => (long)stream.Count),
|
||||
uncompressedBytes,
|
||||
payload.Length);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
using Fleet.Core;
|
||||
using Fleet.Rpc;
|
||||
|
||||
namespace MoShiro.Agent;
|
||||
|
||||
/// <summary>Aggregates per-task Agent → Backend chunk sizes into low-volume diagnostic logs.</summary>
|
||||
internal sealed class TaskTrafficReporter
|
||||
{
|
||||
private static readonly TimeSpan s_window = TimeSpan.FromMinutes(1);
|
||||
|
||||
private readonly object _gate = new();
|
||||
private readonly INodeLog _log;
|
||||
private readonly TimeProvider _time;
|
||||
private readonly Dictionary<(string Task, string Kind), Totals> _totals = [];
|
||||
private long _windowStarted;
|
||||
|
||||
public TaskTrafficReporter(INodeLog log, TimeProvider time)
|
||||
{
|
||||
_log = log;
|
||||
_time = time;
|
||||
_windowStarted = time.GetTimestamp();
|
||||
}
|
||||
|
||||
public void Record(string task, string kind, long records, int uncompressedBytes, int compressedBytes)
|
||||
{
|
||||
lock (_gate)
|
||||
{
|
||||
var key = (task, kind);
|
||||
if (!_totals.TryGetValue(key, out Totals? totals))
|
||||
{
|
||||
totals = new Totals();
|
||||
_totals.Add(key, totals);
|
||||
}
|
||||
|
||||
totals.Chunks++;
|
||||
totals.Records += records;
|
||||
totals.UncompressedBytes += uncompressedBytes;
|
||||
totals.CompressedBytes += compressedBytes;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RunAsync(CancellationToken ct)
|
||||
{
|
||||
using var timer = new PeriodicTimer(s_window, _time);
|
||||
try
|
||||
{
|
||||
while (await timer.WaitForNextTickAsync(ct))
|
||||
{
|
||||
Flush();
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// The final snapshot is flushed by the Agent shutdown path.
|
||||
}
|
||||
}
|
||||
|
||||
public void Flush()
|
||||
{
|
||||
List<Snapshot> completed;
|
||||
long windowSeconds;
|
||||
lock (_gate)
|
||||
{
|
||||
if (_totals.Count == 0)
|
||||
{
|
||||
_windowStarted = _time.GetTimestamp();
|
||||
return;
|
||||
}
|
||||
|
||||
TimeSpan elapsed = _time.GetElapsedTime(_windowStarted);
|
||||
windowSeconds = Math.Max(1, (long)elapsed.TotalSeconds);
|
||||
completed = [.. _totals.Select(pair => new Snapshot(pair.Key.Task, pair.Key.Kind, pair.Value.Chunks, pair.Value.Records, pair.Value.UncompressedBytes, pair.Value.CompressedBytes))];
|
||||
_totals.Clear();
|
||||
_windowStarted = _time.GetTimestamp();
|
||||
}
|
||||
|
||||
foreach (Snapshot item in completed)
|
||||
{
|
||||
double savedPercent = item.UncompressedBytes == 0
|
||||
? 0
|
||||
: 100d * (item.UncompressedBytes - item.CompressedBytes) / item.UncompressedBytes;
|
||||
long queuedBytes = item.CompressedBytes + (long)item.Chunks * OutboxFrame.HeaderBytes;
|
||||
_log.Info(
|
||||
"task upload traffic",
|
||||
("link", "agent-backend"),
|
||||
("task", item.Task),
|
||||
("kind", item.Kind),
|
||||
("chunks", item.Chunks),
|
||||
("records", item.Records),
|
||||
("raw_bytes", item.UncompressedBytes),
|
||||
("compressed_bytes", item.CompressedBytes),
|
||||
("queued_bytes", queuedBytes),
|
||||
("saved_pct", Math.Round(savedPercent, 1)),
|
||||
("window_s", windowSeconds));
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class Totals
|
||||
{
|
||||
public long Chunks;
|
||||
public long Records;
|
||||
public long UncompressedBytes;
|
||||
public long CompressedBytes;
|
||||
}
|
||||
|
||||
private sealed record Snapshot(string Task, string Kind, long Chunks, long Records, long UncompressedBytes, long CompressedBytes);
|
||||
}
|
||||
Reference in new issue
Block a user