analysis: split TCP retransmissions into forward and return, and show both on the line cards
tcp.retrans.up counts retransmitted segments sent by the dialing side, tcp.retrans.down those sent by the listening side. The cards show RTT as a round trip (⇄) with forward (→) and return (←) rows for QUIC loss and TCP retransmission.
This commit is contained in:
1 parent
48ffa56285
commit
9f5a6cf77a
9 files changed
+104
-37
No files matched your search
@@ -12,7 +12,7 @@ public static class MetricRegistry
|
||||
[
|
||||
LinkMetrics.Rtt, LinkMetrics.LossUp, LinkMetrics.LossDown, LinkMetrics.LossTotal,
|
||||
LinkMetrics.JitterUp, LinkMetrics.JitterDown, LinkMetrics.LossRun, LinkMetrics.LossRunUp, LinkMetrics.LossRunDown, LinkMetrics.Reorder, LinkMetrics.Dup, LinkMetrics.SamplesUdp,
|
||||
TcpMetrics.Rtt, TcpMetrics.Slow, TcpMetrics.Stall, TcpMetrics.StallMax, TcpMetrics.Retrans, TcpMetrics.Srtt, TcpMetrics.BulkRate, TcpMetrics.BulkPeak, TcpMetrics.SamplesTcp,
|
||||
TcpMetrics.Rtt, TcpMetrics.Slow, TcpMetrics.Stall, TcpMetrics.StallMax, TcpMetrics.Retrans, TcpMetrics.RetransUp, TcpMetrics.RetransDown, TcpMetrics.Srtt, TcpMetrics.BulkRate, TcpMetrics.BulkPeak, TcpMetrics.SamplesTcp,
|
||||
SessionMetrics.ConnectSuccess, SessionMetrics.ConnectTcp, SessionMetrics.ConnectTls,
|
||||
SessionMetrics.FailTimeout, SessionMetrics.FailReset, SessionMetrics.FailTls,
|
||||
SessionMetrics.HoldResets, SessionMetrics.HoldLifetime,
|
||||
@@ -24,7 +24,7 @@ public static class MetricRegistry
|
||||
[
|
||||
new("quic", "QUIC Datagram", ["loss.up", "loss.down", "loss.total", "rtt.p50", "rtt.p95", "rtt.p99", "jitter.up.p95", "jitter.down.p95", "loss.run.up_max", "loss.run.down_max", "reorder", "dup"]),
|
||||
new("udp", "QUIC Datagram (legacy alias)", ["loss.up", "loss.down", "loss.total", "rtt.p50", "rtt.p95", "rtt.p99", "jitter.up.p95", "jitter.down.p95", "loss.run_max", "reorder", "dup"]),
|
||||
new("tcp", "TCP", ["tcp.rtt.p50", "tcp.rtt.p95", "tcp.rtt.p99", "tcp.rtt.p999", "tcp.rtt.max", "tcp.rtt.tail", "tcp.slow", "tcp.stall.per_min", "tcp.stall_max", "tcp.retrans", "tcp.srtt.p95"]),
|
||||
new("tcp", "TCP", ["tcp.rtt.p50", "tcp.rtt.p95", "tcp.rtt.p99", "tcp.rtt.p999", "tcp.rtt.max", "tcp.rtt.tail", "tcp.slow", "tcp.stall.per_min", "tcp.stall_max", "tcp.retrans", "tcp.retrans.up", "tcp.retrans.down", "tcp.srtt.p95"]),
|
||||
new("connect", "Connect", ["connect.success", "connect.tcp_ms.p95", "connect.tls_ms.p95", "connect.fail.timeout", "connect.fail.rst", "connect.fail.tls"]),
|
||||
new("session", "Session", ["hold.resets.per_h", "hold.lifetime_max"]),
|
||||
new("scores", "Scores", ["score.base", "score.mc", "score.hy2", "score.reality"]),
|
||||
|
||||
@@ -27,6 +27,14 @@ public static class TcpMetrics
|
||||
public static readonly MetricDef Retrans = Metric.Ratio(
|
||||
"tcp.retrans", s_tcpInfo, Ops.KernelDelta("retrans_total", "segs_out"), "TCP retransmissions", "Retransmitted segments as a share of segments sent (kernel counters).", good: 0.5, poor: 3);
|
||||
|
||||
public static readonly MetricDef RetransUp = Metric.Ratio(
|
||||
"tcp.retrans.up", s_tcpInfo, Ops.KernelDelta("retrans_total", "segs_out", TcpSide.Forward), "TCP retransmissions (forward)",
|
||||
"Retransmitted segments as a share of segments sent by the 拨号方 (kernel counters): loss on the 拨号方 to Agent path.", good: 0.5, poor: 3);
|
||||
|
||||
public static readonly MetricDef RetransDown = Metric.Ratio(
|
||||
"tcp.retrans.down", s_tcpInfo, Ops.KernelDelta("retrans_total", "segs_out", TcpSide.Return), "TCP retransmissions (return)",
|
||||
"Retransmitted segments as a share of segments sent by the Agent (kernel counters): loss on the Agent to 拨号方 path.", good: 0.5, poor: 3);
|
||||
|
||||
public static readonly MetricDef Srtt = Metric.Histogram(
|
||||
"tcp.srtt", s_tcpInfo, Ops.Column("srtt_us", 0.001), "Kernel srtt", "Smoothed RTT sampled from the kernel.", Unit.Ms, [Stat.P95], good: 60, poor: 200);
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ public static class Ops
|
||||
|
||||
public static MetricOp RowCount { get; } = new RowCountOp();
|
||||
|
||||
public static MetricOp KernelDelta(string field, string over) => new KernelDeltaOp(field, over);
|
||||
public static MetricOp KernelDelta(string field, string over, TcpSide only = TcpSide.Both) => new KernelDeltaOp(field, over, only);
|
||||
|
||||
public static MetricOp Column(string column, double scale) => new ColumnValueOp(column, scale);
|
||||
|
||||
|
||||
@@ -9,19 +9,32 @@ internal static class TcpInfoTime
|
||||
side is not null && side.Values[row] == 0 ? view.DialerToUtcMs(ts.Values[row]) : view.AgentToUtcMs(ts.Values[row]);
|
||||
}
|
||||
|
||||
/// <summary>两次采样之间累计计数的差值(分子)与另一个累计计数的差值(分母),按 (side, conn) 分别计算;旧数据没有 conn 时按 side 计算。</summary>
|
||||
/// <summary>哪一侧的内核采样:拨号方 一侧发出的段走去程(拨号方到 Agent),Agent 一侧发出的段走回程。</summary>
|
||||
public enum TcpSide
|
||||
{
|
||||
Both,
|
||||
Forward,
|
||||
Return,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 两次采样之间累计计数的差值(分子)与另一个累计计数的差值(分母),按 (side, conn) 分别计算;旧数据没有 conn 时按 side 计算。
|
||||
/// 只取一侧时,另一侧的行整个跳过(基线也不记),所以去程和回程的重传各自只由自己这一侧发出的段算出。
|
||||
/// </summary>
|
||||
internal sealed class KernelDeltaOp : MetricOp
|
||||
{
|
||||
private readonly string _field;
|
||||
private readonly string _over;
|
||||
private readonly TcpSide _only;
|
||||
|
||||
public KernelDeltaOp(string field, string over)
|
||||
public KernelDeltaOp(string field, string over, TcpSide only = TcpSide.Both)
|
||||
{
|
||||
_field = field;
|
||||
_over = over;
|
||||
_only = only;
|
||||
}
|
||||
|
||||
public override string Signature => $"kernel_delta/{_field}/{_over}/2";
|
||||
public override string Signature => _only == TcpSide.Both ? $"kernel_delta/{_field}/{_over}/2" : $"kernel_delta/{_field}/{_over}/{_only}/2";
|
||||
|
||||
public override void Evaluate(ChunkView view, RawStream stream, IObservationSink sink)
|
||||
{
|
||||
@@ -43,7 +56,13 @@ internal sealed class KernelDeltaOp : MetricOp
|
||||
continue;
|
||||
}
|
||||
|
||||
var key = (side?.Values[i] ?? 0, connection?.IsValid(i) == true ? connection.Values[i] : 0);
|
||||
long rowSide = side?.Values[i] ?? 0;
|
||||
if ((_only == TcpSide.Forward && rowSide != 0) || (_only == TcpSide.Return && rowSide == 0))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var key = (rowSide, connection?.IsValid(i) == true ? connection.Values[i] : 0);
|
||||
if (last.TryGetValue(key, out (long Ts, long Field, long Over) previous))
|
||||
{
|
||||
// Ignore delayed/duplicate samples without replacing the newer baseline.
|
||||
|
||||
@@ -260,6 +260,23 @@ public sealed class OpTests
|
||||
Assert.Equal(300UL, retrans.Denominator);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Retrans_ForwardAndReturnAreSplitBySide()
|
||||
{
|
||||
long ts = ChunkFactory.DialerUs(0);
|
||||
var builder = new StreamBuilder(Kinds.TcpEcho.FindStream("tcp_info")!);
|
||||
builder.Add(ts, 0, 40_000, 0, 10, 10, 1000, 0, 1);
|
||||
builder.Add(ts, 1, 40_000, 0, 10, 100, 2000, 0, 1);
|
||||
builder.Add(ts + 1_000_000, 0, 40_000, 0, 10, 11, 1100, 0, 1);
|
||||
builder.Add(ts + 1_000_000, 1, 40_000, 0, 10, 102, 2200, 0, 1);
|
||||
RawChunk chunk = ChunkFactory.Echo(s_agent, "tcp_echo", T0, 10, 100,
|
||||
_ => new EchoPacket(false, false, 20_000, 20_000), extraStreams: [builder.Build()]);
|
||||
var forward = (RatioAcc)EvaluateOneBucket(chunk, "tcp.retrans.up").Items["tcp.retrans.up"];
|
||||
var back = (RatioAcc)EvaluateOneBucket(chunk, "tcp.retrans.down").Items["tcp.retrans.down"];
|
||||
Assert.Equal((1UL, 100UL), (forward.Numerator, forward.Denominator));
|
||||
Assert.Equal((2UL, 200UL), (back.Numerator, back.Denominator));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Connect_Outcomes_AreCountedByClass()
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user