measurement: name the kinds by protocol and action, add the http_request kind and QUIC settings
This commit is contained in:
1 parent
bf2a598891
commit
e77880d372
31 files changed
+938
-141
No files matched your search
@@ -1,3 +1,4 @@
|
||||
using System.Net;
|
||||
using System.Text.Json;
|
||||
using Fleet.Core;
|
||||
using MoShiro.Protocol;
|
||||
@@ -24,11 +25,11 @@ public sealed class MeasurementTests
|
||||
BudgetGuard guard = Guard();
|
||||
double mbps = 0;
|
||||
long pps = 0;
|
||||
Assert.True(guard.Check(Line("udp_echo"), ref mbps, ref pps).Accepted);
|
||||
Assert.True(guard.Check(Line("quic_ping"), ref mbps, ref pps).Accepted);
|
||||
Assert.Equal(RejectReasons.Unsupported, guard.Check(Line("no_such_kind"), ref mbps, ref pps).Reason);
|
||||
Assert.Equal(RejectReasons.InvalidParams, guard.Check(Line("udp_echo", ("rate_hz", "100000")), ref mbps, ref pps).Reason);
|
||||
Assert.Equal(RejectReasons.InvalidParams, guard.Check(Line("udp_echo", ("nope", "1")), ref mbps, ref pps).Reason);
|
||||
Assert.Equal(RejectReasons.PolicyDenied, guard.Check(Line("udp_echo", ("port", "9999")), ref mbps, ref pps).Reason);
|
||||
Assert.Equal(RejectReasons.InvalidParams, guard.Check(Line("quic_ping", ("rate_hz", "100000")), ref mbps, ref pps).Reason);
|
||||
Assert.Equal(RejectReasons.InvalidParams, guard.Check(Line("quic_ping", ("nope", "1")), ref mbps, ref pps).Reason);
|
||||
Assert.Equal(RejectReasons.PolicyDenied, guard.Check(Line("quic_ping", ("port", "9999")), ref mbps, ref pps).Reason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -37,11 +38,11 @@ public sealed class MeasurementTests
|
||||
BudgetGuard guard = Guard(new NodeLimits { MaxTotalMbps = 15, AllowedQuicPorts = [7443] });
|
||||
double mbps = 0;
|
||||
long pps = 0;
|
||||
Assert.True(guard.CheckPhase("udp_train", new Dictionary<string, JsonElement> { ["mbps"] = Json("10") }).Accepted);
|
||||
Assert.Equal(RejectReasons.BudgetExceeded, guard.CheckPhase("udp_train", new Dictionary<string, JsonElement> { ["mbps"] = Json("20") }).Reason);
|
||||
Assert.True(guard.CheckPhase("quic_stream", new Dictionary<string, JsonElement> { ["mbps"] = Json("10") }).Accepted);
|
||||
Assert.Equal(RejectReasons.BudgetExceeded, guard.CheckPhase("quic_stream", new Dictionary<string, JsonElement> { ["mbps"] = Json("20") }).Reason);
|
||||
|
||||
Assert.True(guard.Check(Line("udp_train", ("mbps", "10")), ref mbps, ref pps).Accepted);
|
||||
Assert.Equal(RejectReasons.BudgetExceeded, guard.Check(Line("udp_train", ("mbps", "10")), ref mbps, ref pps).Reason);
|
||||
Assert.True(guard.Check(Line("quic_stream", ("mbps", "10")), ref mbps, ref pps).Accepted);
|
||||
Assert.Equal(RejectReasons.BudgetExceeded, guard.Check(Line("quic_stream", ("mbps", "10")), ref mbps, ref pps).Reason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -54,8 +55,15 @@ public sealed class MeasurementTests
|
||||
Assert.True(some.TargetAllowed("192.0.2.17:443"));
|
||||
Assert.True(some.TargetAllowed("2001:db8::1"));
|
||||
Assert.False(some.TargetAllowed("192.0.3.1"));
|
||||
Assert.False(some.TargetAllowed("example.com"));
|
||||
Assert.False(some.TargetAllowed(null));
|
||||
|
||||
// 域名要解析之后才知道地址:配置过白名单就先接受,每个解析出的地址再由 AddressAllowed 检查
|
||||
Assert.True(some.TargetAllowed("example.com:443"));
|
||||
Assert.True(some.TargetAllowed("https://example.com/health"));
|
||||
Assert.False(Guard().TargetAllowed("example.com"));
|
||||
Assert.True(some.AddressAllowed(IPAddress.Parse("192.0.2.9")));
|
||||
Assert.False(some.AddressAllowed(IPAddress.Parse("10.0.0.1")));
|
||||
Assert.False(some.AddressAllowed(IPAddress.Parse("::ffff:10.0.0.1")));
|
||||
}
|
||||
|
||||
// ---- RowStore ----
|
||||
@@ -63,7 +71,7 @@ public sealed class MeasurementTests
|
||||
[Fact]
|
||||
public void RowStore_RecordsOnlyItsOwnSidesColumns_AndCutsExistingRowsInKeyOrder()
|
||||
{
|
||||
StreamSpec msg = Kinds.UdpEcho.FindStream("msg")!;
|
||||
StreamSpec msg = Kinds.QuicPing.FindStream("msg")!;
|
||||
var dialer = new RowStore(msg, Side.Dialer, 64);
|
||||
Assert.Equal(["size", "w", "t1", "t4"], msg.ColumnsOf(Side.Dialer).Select(c => c.Name));
|
||||
Assert.Equal(["t2", "t3"], msg.ColumnsOf(Side.Listener).Select(c => c.Name));
|
||||
@@ -88,7 +96,7 @@ public sealed class MeasurementTests
|
||||
[Fact]
|
||||
public void RowStore_RingOverwritesOldRows_AndNeverReturnsAStaleOne()
|
||||
{
|
||||
var store = new RowStore(Kinds.UdpEcho.FindStream("msg")!, Side.Listener, 16);
|
||||
var store = new RowStore(Kinds.QuicPing.FindStream("msg")!, Side.Listener, 16);
|
||||
store.SetRow(3, [30, 31]);
|
||||
Assert.True(store.TryGet(3, 0, out long t2));
|
||||
Assert.Equal(30, t2);
|
||||
@@ -117,19 +125,19 @@ public sealed class MeasurementTests
|
||||
msg.Add(seq, 64, 1, t1, t4);
|
||||
}
|
||||
|
||||
return new RawChunk(new RawChunkHeader { Kind = "udp_echo", Clock = new ChunkClock { DialerOffsetUs = 1000 } }, [msg.Build()]);
|
||||
return new RawChunk(new RawChunkHeader { Kind = "quic_ping", Clock = new ChunkClock { DialerOffsetUs = 1000 } }, [msg.Build()]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FragmentMerger_DialerDriven_AddsTheAgentColumnsByKey_AndLeavesMissingRowsEmpty()
|
||||
{
|
||||
var agent = new RowStore(Kinds.UdpEcho.FindStream("msg")!, Side.Listener, 64);
|
||||
var agent = new RowStore(Kinds.QuicPing.FindStream("msg")!, Side.Listener, 64);
|
||||
agent.SetRow(0, [2000, 2050]);
|
||||
agent.SetRow(2, [4000, 4040]);
|
||||
var source = new Source { Stores = { ["msg"] = agent } };
|
||||
RawChunk fragment = EchoFragment((0, 1000, 3000), (1, 2000, null), (2, 3000, 5000));
|
||||
|
||||
Result<IReadOnlyList<RawStream>> merged = FragmentMerger.Merge(Kinds.UdpEcho, fragment, 1, source);
|
||||
Result<IReadOnlyList<RawStream>> merged = FragmentMerger.Merge(Kinds.QuicPing, fragment, 1, source);
|
||||
RawStream msg = Assert.Single(merged.Value);
|
||||
Assert.Equal(["seq", "size", "w", "t1", "t2", "t3", "t4"], msg.Columns.Select(c => c.Name));
|
||||
Assert.Equal([true, false, true], Enumerable.Range(0, 3).Select(i => msg.Find("t2")!.IsValid(i)));
|
||||
@@ -141,7 +149,7 @@ public sealed class MeasurementTests
|
||||
public void FragmentMerger_DialerDriven_WithoutAnyAgentRecords_KeepsTheDialerRows()
|
||||
{
|
||||
RawChunk fragment = EchoFragment((0, 1000, 3000), (1, 2000, null));
|
||||
RawStream msg = Assert.Single(FragmentMerger.Merge(Kinds.UdpEcho, fragment, 1, new Source()).Value);
|
||||
RawStream msg = Assert.Single(FragmentMerger.Merge(Kinds.QuicPing, fragment, 1, new Source()).Value);
|
||||
Assert.Equal(2, msg.Count);
|
||||
Assert.False(msg.Find("t2")!.IsValid(0));
|
||||
Assert.Equal(3000, msg.Find("t4")!.Values[0]);
|
||||
@@ -152,14 +160,14 @@ public sealed class MeasurementTests
|
||||
{
|
||||
var bad = new StreamBuilder(new StreamSpec("msg", [Col.Seq("seq")]));
|
||||
bad.Add(0);
|
||||
var fragment = new RawChunk(new RawChunkHeader { Kind = "udp_echo" }, [bad.Build()]);
|
||||
Assert.False(FragmentMerger.Merge(Kinds.UdpEcho, fragment, 1, new Source()).IsSuccess);
|
||||
var fragment = new RawChunk(new RawChunkHeader { Kind = "quic_ping" }, [bad.Build()]);
|
||||
Assert.False(FragmentMerger.Merge(Kinds.QuicPing, fragment, 1, new Source()).IsSuccess);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FragmentMerger_AgentDriven_TakesRowsFromTheAgent_AndMarksUnreceivedPacketsAsLost()
|
||||
{
|
||||
var agent = new RowStore(Kinds.UdpTrain.FindStream("pkt")!, Side.Listener, 64);
|
||||
var agent = new RowStore(Kinds.QuicStream.FindStream("pkt")!, Side.Listener, 64);
|
||||
for (uint seq = 0; seq < 5; seq++)
|
||||
{
|
||||
agent.SetRow(seq, [1000, 100 + seq]);
|
||||
@@ -167,12 +175,12 @@ public sealed class MeasurementTests
|
||||
|
||||
var pkt = new StreamBuilder(new StreamSpec("pkt", [Col.Seq("seq"), Col.TsDialer("t_recv")]));
|
||||
pkt.Add(1, 5000).Add(1, 9999).Add(3, 6000); // sequence 1 arrives twice (the first one counts); 0, 2 and 4 are lost
|
||||
var header = new RawChunkHeader { Kind = "udp_train" };
|
||||
var header = new RawChunkHeader { Kind = "quic_stream" };
|
||||
header.Params[FragmentKeys.SeqFrom] = Json("0");
|
||||
header.Params[FragmentKeys.SeqTo] = Json("5");
|
||||
var source = new Source { Stores = { ["pkt"] = agent } };
|
||||
|
||||
RawStream merged = Assert.Single(FragmentMerger.Merge(Kinds.UdpTrain, new RawChunk(header, [pkt.Build()]), 1, source).Value);
|
||||
RawStream merged = Assert.Single(FragmentMerger.Merge(Kinds.QuicStream, new RawChunk(header, [pkt.Build()]), 1, source).Value);
|
||||
Assert.Equal(5, merged.Count);
|
||||
Assert.Equal(["seq", "size", "t_send", "t_recv"], merged.Columns.Select(c => c.Name));
|
||||
RawColumn recv = merged.Find("t_recv")!;
|
||||
@@ -181,7 +189,7 @@ public sealed class MeasurementTests
|
||||
Assert.Equal(103, merged.Find("t_send")!.Values[3]);
|
||||
|
||||
// A one-way fragment that does not declare its sequence range is invalid
|
||||
Assert.False(FragmentMerger.Merge(Kinds.UdpTrain, new RawChunk(new RawChunkHeader { Kind = "udp_train" }, [pkt.Build()]), 1, source).IsSuccess);
|
||||
Assert.False(FragmentMerger.Merge(Kinds.QuicStream, new RawChunk(new RawChunkHeader { Kind = "quic_stream" }, [pkt.Build()]), 1, source).IsSuccess);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -189,9 +197,9 @@ public sealed class MeasurementTests
|
||||
{
|
||||
var conn = new StreamBuilder(new StreamSpec("conn", [Col.U8("outcome"), Col.Seq("attempt"), Col.TsDialer("ts")]));
|
||||
conn.Add(0, 0, 100);
|
||||
var fragment = new RawChunk(new RawChunkHeader { Kind = "connect" }, [conn.Build()]);
|
||||
var fragment = new RawChunk(new RawChunkHeader { Kind = "tcp_connect" }, [conn.Build()]);
|
||||
|
||||
RawStream merged = Assert.Single(FragmentMerger.Merge(Kinds.Connect, fragment, 1, new Source()).Value);
|
||||
RawStream merged = Assert.Single(FragmentMerger.Merge(Kinds.TcpConnect, fragment, 1, new Source()).Value);
|
||||
Assert.Equal(["attempt", "ts", "tcp_us", "tls_us", "outcome"], merged.Columns.Select(c => c.Name));
|
||||
Assert.False(merged.Find("tcp_us")!.IsValid(0));
|
||||
}
|
||||
@@ -212,7 +220,7 @@ public sealed class MeasurementTests
|
||||
[Fact]
|
||||
public void FragmentMerger_UnionStreams_ConcatenateBothSidesRows_AndEachAgentRowIsDeliveredOnce()
|
||||
{
|
||||
StreamSpec info = Kinds.TcpEcho.FindStream("tcp_info")!;
|
||||
StreamSpec info = Kinds.TcpPing.FindStream("tcp_info")!;
|
||||
Assert.Equal(info.Columns.Count, info.ColumnsOf(Side.Listener).Count); // each side owns every column
|
||||
var agent = new RowStore(info, Side.Listener, 64);
|
||||
var recorder = new Source { Stores = { ["tcp_info"] = agent } };
|
||||
@@ -222,7 +230,7 @@ public sealed class MeasurementTests
|
||||
var msg = EchoFragment((0, 1000, 3000));
|
||||
var fragment = new RawChunk(msg.Header, [msg.Streams[0], InfoRows(info, (100, 0, 1))]);
|
||||
|
||||
IReadOnlyList<RawStream> first = FragmentMerger.Merge(Kinds.TcpEcho, fragment, 1, recorder).Value;
|
||||
IReadOnlyList<RawStream> first = FragmentMerger.Merge(Kinds.TcpPing, fragment, 1, recorder).Value;
|
||||
RawStream merged = first.Single(x => x.Name == "tcp_info");
|
||||
Assert.Equal(3, merged.Count);
|
||||
Assert.Equal([0L, 1L, 1L], merged.Find("side")!.Values);
|
||||
@@ -231,20 +239,20 @@ public sealed class MeasurementTests
|
||||
Assert.Equal(1, merged.Find("conn")!.Values[1]);
|
||||
|
||||
// The agent's rows were consumed: the next chunk carries only the dialer's own rows
|
||||
IReadOnlyList<RawStream> second = FragmentMerger.Merge(Kinds.TcpEcho, fragment, 1, recorder).Value;
|
||||
IReadOnlyList<RawStream> second = FragmentMerger.Merge(Kinds.TcpPing, fragment, 1, recorder).Value;
|
||||
Assert.Equal(1, second.Single(x => x.Name == "tcp_info").Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FragmentMerger_UnionStreams_AgentRowsAreDeliveredEvenWhenTheDialerHasNone()
|
||||
{
|
||||
StreamSpec info = Kinds.TcpEcho.FindStream("tcp_info")!;
|
||||
StreamSpec info = Kinds.TcpPing.FindStream("tcp_info")!;
|
||||
var agent = new RowStore(info, Side.Listener, 64);
|
||||
agent.SetRow(agent.Append(), [500, 1, 40_000, 5_000, 10, 7, 100, 1_000, 1]);
|
||||
|
||||
// A dialer on a platform without kernel sampling sends the echo rows only
|
||||
RawChunk fragment = EchoFragment((0, 1000, 3000));
|
||||
IReadOnlyList<RawStream> merged = FragmentMerger.Merge(Kinds.TcpEcho, fragment, 1, new Source { Stores = { ["tcp_info"] = agent } }).Value;
|
||||
IReadOnlyList<RawStream> merged = FragmentMerger.Merge(Kinds.TcpPing, fragment, 1, new Source { Stores = { ["tcp_info"] = agent } }).Value;
|
||||
RawStream rows = merged.Single(x => x.Name == "tcp_info");
|
||||
Assert.Equal(1, rows.Count);
|
||||
Assert.Equal(7, rows.Find("retrans_total")!.Values[0]);
|
||||
@@ -253,11 +261,11 @@ public sealed class MeasurementTests
|
||||
[Fact]
|
||||
public void FragmentMerger_LegacyTcpSamples_KeepConnectionIdentityUnknown()
|
||||
{
|
||||
StreamSpec info = Kinds.TcpEcho.FindStream("tcp_info")!;
|
||||
StreamSpec info = Kinds.TcpPing.FindStream("tcp_info")!;
|
||||
RawStream sample = InfoRows(info, (100, 0, 1));
|
||||
var legacy = new RawStream(sample.Name, sample.Count, sample.Columns.Where(c => c.Name != "conn").ToArray());
|
||||
var fragment = new RawChunk(new RawChunkHeader { Kind = "tcp_echo" }, [legacy]);
|
||||
RawStream merged = Assert.Single(FragmentMerger.Merge(Kinds.TcpEcho, fragment, 1, new Source()).Value);
|
||||
var fragment = new RawChunk(new RawChunkHeader { Kind = "tcp_ping" }, [legacy]);
|
||||
RawStream merged = Assert.Single(FragmentMerger.Merge(Kinds.TcpPing, fragment, 1, new Source()).Value);
|
||||
Assert.False(merged.Find("conn")!.IsValid(0));
|
||||
Assert.Equal(1, merged.Find("retrans_total")!.Values[0]);
|
||||
}
|
||||
@@ -282,7 +290,7 @@ public sealed class MeasurementTests
|
||||
[Fact]
|
||||
public void EchoRows_Tcp_WaitsForTheReplyInsteadOfCallingASeveralSecondStallALoss()
|
||||
{
|
||||
var rows = new EchoRows(Kinds.TcpEcho, 64, EchoRows.TcpGraceUs, settleOnReply: true);
|
||||
var rows = new EchoRows(Kinds.TcpPing, 64, EchoRows.TcpGraceUs, settleOnReply: true);
|
||||
uint a = rows.Add(64, 0);
|
||||
uint b = rows.Add(64, 100_000);
|
||||
uint c = rows.Add(64, 200_000);
|
||||
@@ -312,7 +320,7 @@ public sealed class MeasurementTests
|
||||
[Fact]
|
||||
public void EchoRows_Udp_GivesUpOnAMissingReplyAfterTwoSeconds()
|
||||
{
|
||||
var rows = new EchoRows(Kinds.UdpEcho, 64, EchoRows.DatagramGraceUs, settleOnReply: false);
|
||||
var rows = new EchoRows(Kinds.QuicPing, 64, EchoRows.DatagramGraceUs, settleOnReply: false);
|
||||
rows.Add(64, 0);
|
||||
rows.Add(64, 1_000_000);
|
||||
RawStream cut = rows.Take(2_500_000, all: false)!;
|
||||
@@ -324,7 +332,7 @@ public sealed class MeasurementTests
|
||||
[Fact]
|
||||
public void EchoRows_DiscardedLocalSendIsNotReportedAsNetworkLoss()
|
||||
{
|
||||
var rows = new EchoRows(Kinds.UdpEcho, 64, EchoRows.DatagramGraceUs, settleOnReply: false);
|
||||
var rows = new EchoRows(Kinds.QuicPing, 64, EchoRows.DatagramGraceUs, settleOnReply: false);
|
||||
uint unsent = rows.Add(64, 0);
|
||||
Assert.True(rows.Discard(unsent));
|
||||
|
||||
@@ -369,7 +377,7 @@ public sealed class MeasurementTests
|
||||
public void BlockSpec_ConfigSignatureIgnoresTheStreamId_ButSignatureDoesNot()
|
||||
{
|
||||
BlockSpec Create(string json, uint stream = 1, int port = 7443) => BlockSpec.Create(
|
||||
"udp_echo",
|
||||
"quic_ping",
|
||||
JsonDocument.Parse(json).RootElement.EnumerateObject().ToDictionary(p => p.Name, p => p.Value.Clone()),
|
||||
stream,
|
||||
port,
|
||||
@@ -381,7 +389,7 @@ public sealed class MeasurementTests
|
||||
Assert.NotEqual(baseline.Signature, Create("""{"rate_hz":10,"size":64}""", stream: 2).Signature);
|
||||
Assert.Equal(baseline.ConfigSignature, Create("""{"rate_hz":10,"size":64}""", stream: 2).ConfigSignature);
|
||||
Assert.NotEqual(baseline.ConfigSignature, Create("""{"rate_hz":10,"size":64}""", port: 9000).ConfigSignature);
|
||||
Assert.Null(BlockSpec.Create("udp_echo", new Dictionary<string, JsonElement> { ["rate_hz"] = Json("100000") }, 1, 7443, new FragmentScope(null, null, null)));
|
||||
Assert.Null(BlockSpec.Create("quic_ping", new Dictionary<string, JsonElement> { ["rate_hz"] = Json("100000") }, 1, 7443, new FragmentScope(null, null, null)));
|
||||
}
|
||||
|
||||
// ---- 起止时间 ----
|
||||
@@ -389,13 +397,13 @@ public sealed class MeasurementTests
|
||||
[Fact]
|
||||
public void ChunkFinalizer_TakesStartAndDurationFromTheDataTimestamps_NotFromTheDeliveryTime()
|
||||
{
|
||||
var agent = new RowStore(Kinds.UdpEcho.FindStream("msg")!, Side.Listener, 64);
|
||||
var agent = new RowStore(Kinds.QuicPing.FindStream("msg")!, Side.Listener, 64);
|
||||
agent.SetRow(0, [2000, 2050]);
|
||||
RawChunk fragment = EchoFragment((0, 1000, 3000));
|
||||
IReadOnlyList<RawStream> streams = FragmentMerger.Merge(Kinds.UdpEcho, fragment, 1, new Source { Stores = { ["msg"] = agent } }).Value;
|
||||
IReadOnlyList<RawStream> streams = FragmentMerger.Merge(Kinds.QuicPing, fragment, 1, new Source { Stores = { ["msg"] = agent } }).Value;
|
||||
|
||||
var anchor = new ClockAnchor(ListenerMonoUs: 1_000_000, UtcMs: 1_700_000_000_000);
|
||||
RawChunk chunk = ChunkFinalizer.Finalize(Kinds.UdpEcho, fragment, streams, new NodeId("agt_x"), anchor, new Dictionary<string, JsonElement> { ["port"] = Json("7443") }, 7443, new ChunkId(Guid.NewGuid()));
|
||||
RawChunk chunk = ChunkFinalizer.Finalize(Kinds.QuicPing, fragment, streams, new NodeId("agt_x"), anchor, new Dictionary<string, JsonElement> { ["port"] = Json("7443") }, 7443, new ChunkId(Guid.NewGuid()));
|
||||
|
||||
// t1 = 1000 us (拨号方 clock) + offset 1000 = 2000 us on the Agent clock, which is 998 ms before the anchor
|
||||
Assert.Equal(1_700_000_000_000 - 998, chunk.Header.StartUtcMs);
|
||||
|
||||
Reference in new issue
Block a user