Skip to content

Commit 0fa38f2

Browse files
committed
update metrics collector
1 parent d00f0d8 commit 0fa38f2

34 files changed

Lines changed: 1120 additions & 99 deletions

AGENTS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ Update guidelines:
3131
- **Graph Transitions**: When extending the fluent builders, preserve existing transition sets—never clear prior rules when adding new ones—so multiple method constraints remain effective.
3232
- **Call Tracking**: Resolve outgoing call identities from the Orleans context (`SourceId`/interface metadata) instead of guessing via reflection order to avoid mislabelling callers.
3333
- **Configuration Flags**: Treat `AllowAll`/`DisallowAll` on `GrainCallsBuilder` as authoritative defaults; ensure runtime checks respect the builder’s chosen baseline before reporting violations.
34+
- **Runtime Graph Telemetry**: For live graph features, have filters report observed calls to stateless worker aggregators that periodically flush to an in-memory grain; do not rely on per-request `CallHistory` alone for a global runtime graph.
35+
- **Telemetry Filtering**: Do not track Orleans.Graph internal telemetry calls by default; expose a configuration switch to include them, and test both filtered and full-tracking modes.
3436
- **Testing Scope**: Exercise new behavior through the Orleans-hosted integration tests in `ManagedCode.Orleans.Graph.Tests`, covering both positive and negative paths to mirror real cluster flows.
3537
- **Test Framework**: Use TUnit for tests and Shouldly for assertions; do not introduce FluentAssertions, so the test style stays aligned with the newer ManagedCode Orleans projects.
3638
- **Migration Releases**: For major framework/package migrations, complete three strict code-review-and-fix iterations before README polish, feature additions, commit, push, and CI verification, so release branches are hardened before publication.

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
<AnalysisLevel>latest-recommended</AnalysisLevel>
1212
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
1313
<NoWarn>$(NoWarn);CS1591;CA1707;CA1848;CA1859;CA1873</NoWarn>
14-
<Version>10.0.0</Version>
14+
<Version>10.0.1</Version>
1515
<PackageVersion>$(Version)</PackageVersion>
1616
</PropertyGroup>
1717

Directory.Packages.props

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,15 @@
22
<PropertyGroup>
33
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
44
</PropertyGroup>
5-
65
<ItemGroup>
7-
<PackageVersion Include="coverlet.collector" Version="10.0.0" />
8-
<PackageVersion Include="coverlet.msbuild" Version="10.0.0" />
6+
<PackageVersion Include="coverlet.collector" Version="10.0.1" />
7+
<PackageVersion Include="coverlet.msbuild" Version="10.0.1" />
98
<PackageVersion Include="DotNet.ReproducibleBuilds" Version="2.0.2" />
109
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="5.3.0" />
1110
<PackageVersion Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="5.3.0" />
1211
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.8" />
1312
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.8" />
14-
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.5.1" />
13+
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.6.0" />
1514
<PackageVersion Include="Microsoft.Orleans.Analyzers" Version="10.1.0" />
1615
<PackageVersion Include="Microsoft.Orleans.Client" Version="10.1.0" />
1716
<PackageVersion Include="Microsoft.Orleans.Runtime" Version="10.1.0" />
@@ -22,6 +21,6 @@
2221
<PackageVersion Include="Microsoft.Orleans.TestingHost" Version="10.1.0" />
2322
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="10.0.300" />
2423
<PackageVersion Include="Shouldly" Version="4.3.0" />
25-
<PackageVersion Include="TUnit" Version="1.44.39" />
24+
<PackageVersion Include="TUnit" Version="1.47.0" />
2625
</ItemGroup>
27-
</Project>
26+
</Project>

ManagedCode.Orleans.Graph.Tests/DirectedGraphTests.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using ManagedCode.Orleans.Graph.Interfaces;
12
using ManagedCode.Orleans.Graph.Models;
23

34
namespace ManagedCode.Orleans.Graph.Tests;
@@ -19,7 +20,7 @@ public void AddTransition_PreservesMultipleRules()
1920
public void HasReentrantTransition_ReturnsTrueForSelfLoop()
2021
{
2122
var graph = new DirectedGraph(true);
22-
graph.AddTransition("source", "source", new GrainTransition("*", "*", IsReentrant: true));
23+
graph.AddTransition("source", "source", new GrainTransition(Constants.AnyMethod, Constants.AnyMethod, IsReentrant: true));
2324

2425
graph.HasReentrantTransition("source", "source").ShouldBeTrue();
2526
}
@@ -28,8 +29,8 @@ public void HasReentrantTransition_ReturnsTrueForSelfLoop()
2829
public void AddTransition_AllowsCycleThroughReentrantEdge()
2930
{
3031
var graph = new DirectedGraph(true);
31-
graph.AddTransition("source", "target", new GrainTransition("*", "*", IsReentrant: true));
32-
graph.AddTransition("target", "source", new GrainTransition("*", "*"));
32+
graph.AddTransition("source", "target", new GrainTransition(Constants.AnyMethod, Constants.AnyMethod, IsReentrant: true));
33+
graph.AddTransition("target", "source", new GrainTransition(Constants.AnyMethod, Constants.AnyMethod));
3334

3435
graph.HasCycle().ShouldBeFalse();
3536
}

ManagedCode.Orleans.Graph.Tests/GrainTransitionManagerTests.cs

Lines changed: 131 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,80 @@ public void IsTransitionAllowed_AllowAllByDefault_AllowsMissingTransitions()
118118
graph.IsTransitionAllowed(callHistory).ShouldBeTrue();
119119
}
120120

121+
[Test]
122+
public void IsTransitionAllowed_UsesSourceIncomingMethodForOutgoingGrainCall()
123+
{
124+
var graph = GrainCallsBuilder.Create()
125+
.AllowClientCallGrain<IGrainA>()
126+
.And()
127+
.From<IGrainA>()
128+
.To<IGrainB>()
129+
.MethodByName(nameof(IGrainA.MethodB2), nameof(IGrainB.MethodC2))
130+
.And()
131+
.Build();
132+
133+
var grainAId = GrainId.Create("graina", "source-method");
134+
var grainBId = GrainId.Create("grainb", "source-method");
135+
136+
var callHistory = new CallHistory();
137+
callHistory.Push(new OutCall(null, grainAId, Constants.ClientCallerId, typeof(IGrainA).FullName!, nameof(IGrainA.MethodB2)));
138+
callHistory.Push(new InCall(null, grainAId, typeof(IGrainA).FullName!, nameof(IGrainA.MethodB2)));
139+
callHistory.Push(new OutCall(grainAId, grainBId, typeof(IGrainA).FullName!, typeof(IGrainB).FullName!, nameof(IGrainB.MethodC2)));
140+
callHistory.Push(new InCall(grainAId, grainBId, typeof(IGrainB).FullName!, nameof(IGrainB.MethodC2)));
141+
142+
graph.IsTransitionAllowed(callHistory).ShouldBeTrue();
143+
}
144+
145+
[Test]
146+
public void GetObservedEdges_ReturnsClientAndNestedGrainEdges()
147+
{
148+
var grainAId = GrainId.Create("graina", "observed");
149+
var grainBId = GrainId.Create("grainb", "observed");
150+
151+
var callHistory = new CallHistory();
152+
callHistory.Push(new OutCall(null, grainAId, Constants.ClientCallerId, typeof(IGrainA).FullName!, nameof(IGrainA.MethodB1)));
153+
callHistory.Push(new InCall(null, grainAId, typeof(IGrainA).FullName!, nameof(IGrainA.MethodB1)));
154+
callHistory.Push(new OutCall(grainAId, grainBId, typeof(IGrainA).FullName!, typeof(IGrainB).FullName!, nameof(IGrainB.MethodB1)));
155+
callHistory.Push(new InCall(grainAId, grainBId, typeof(IGrainB).FullName!, nameof(IGrainB.MethodB1)));
156+
157+
var edges = GrainTransitionManager.GetObservedEdges(callHistory);
158+
159+
edges.Count.ShouldBe(2);
160+
edges.ShouldContain(edge =>
161+
edge.Source == Constants.ClientCallerId &&
162+
edge.Target == typeof(IGrainA).FullName &&
163+
edge.SourceMethod == Constants.AnyMethod &&
164+
edge.TargetMethod == nameof(IGrainA.MethodB1) &&
165+
edge.Count == 1);
166+
edges.ShouldContain(edge =>
167+
edge.Source == typeof(IGrainA).FullName &&
168+
edge.Target == typeof(IGrainB).FullName &&
169+
edge.SourceMethod == nameof(IGrainA.MethodB1) &&
170+
edge.TargetMethod == nameof(IGrainB.MethodB1) &&
171+
edge.Count == 1);
172+
}
173+
174+
[Test]
175+
public void GetLatestObservedEdge_ReturnsCurrentIncomingPair()
176+
{
177+
var grainAId = GrainId.Create("graina", "latest");
178+
var grainBId = GrainId.Create("grainb", "latest");
179+
180+
var callHistory = new CallHistory();
181+
callHistory.Push(new OutCall(null, grainAId, Constants.ClientCallerId, typeof(IGrainA).FullName!, nameof(IGrainA.MethodB1)));
182+
callHistory.Push(new InCall(null, grainAId, typeof(IGrainA).FullName!, nameof(IGrainA.MethodB1)));
183+
callHistory.Push(new OutCall(grainAId, grainBId, typeof(IGrainA).FullName!, typeof(IGrainB).FullName!, nameof(IGrainB.MethodB1)));
184+
callHistory.Push(new InCall(grainAId, grainBId, typeof(IGrainB).FullName!, nameof(IGrainB.MethodB1)));
185+
186+
var edge = GrainTransitionManager.GetLatestObservedEdge(callHistory);
187+
188+
edge.ShouldNotBeNull();
189+
edge.Source.ShouldBe(typeof(IGrainA).FullName);
190+
edge.Target.ShouldBe(typeof(IGrainB).FullName);
191+
edge.SourceMethod.ShouldBe(nameof(IGrainA.MethodB1));
192+
edge.TargetMethod.ShouldBe(nameof(IGrainB.MethodB1));
193+
}
194+
121195
[Test]
122196
public void IsTransitionAllowed_DisallowAllAfterAllowAll_DeniesMissingTransitions()
123197
{
@@ -371,7 +445,7 @@ public void GetPolicyEdges_ReturnsOrderedSnapshot()
371445
edges.Length.ShouldBe(2);
372446
edges[0].Source.ShouldBe(typeof(IGrainA).FullName);
373447
edges[0].Target.ShouldBe(typeof(IGrainB).FullName);
374-
edges[0].Transitions.Single().ShouldBe(new GrainTransition("*", "*"));
448+
edges[0].Transitions.Single().ShouldBe(new GrainTransition(Constants.AnyMethod, Constants.AnyMethod));
375449
edges[1].Source.ShouldBe(typeof(IGrainB).FullName);
376450
edges[1].Target.ShouldBe(typeof(IGrainC).FullName);
377451
edges[1].Transitions.Single().ShouldBe(new GrainTransition(nameof(IGrainB.MethodB1), nameof(IGrainC.MethodC1)));
@@ -387,13 +461,67 @@ public void GenerateLiveMermaidDiagram_HighlightsActiveEdges()
387461
.And()
388462
.Build();
389463

464+
var grainAId = GrainId.Create("graina", "live-policy");
465+
var grainBId = GrainId.Create("grainb", "live-policy");
466+
390467
var callHistory = new CallHistory();
391-
callHistory.Push(new OutCall(null, null, typeof(IGrainA).FullName!, typeof(IGrainB).FullName!, nameof(IGrainA.MethodB1)));
392-
callHistory.Push(new InCall(null, null, typeof(IGrainB).FullName!, nameof(IGrainB.MethodB1)));
468+
callHistory.Push(new OutCall(null, grainAId, Constants.ClientCallerId, typeof(IGrainA).FullName!, nameof(IGrainA.MethodB1)));
469+
callHistory.Push(new InCall(null, grainAId, typeof(IGrainA).FullName!, nameof(IGrainA.MethodB1)));
470+
callHistory.Push(new OutCall(grainAId, grainBId, typeof(IGrainA).FullName!, typeof(IGrainB).FullName!, nameof(IGrainB.MethodB1)));
471+
callHistory.Push(new InCall(grainAId, grainBId, typeof(IGrainB).FullName!, nameof(IGrainB.MethodB1)));
472+
473+
var diagram = graph.GenerateLiveMermaidDiagram(callHistory);
474+
475+
diagram.ShouldContain("==>");
476+
diagram.ShouldContain(nameof(IGrainB.MethodB1));
477+
diagram.ShouldNotContain("all");
478+
}
479+
480+
[Test]
481+
public void GenerateLiveMermaidDiagram_RendersObservedEdgesWithoutPolicyEdges()
482+
{
483+
var graph = GrainCallsBuilder.Create()
484+
.AllowAll()
485+
.Build();
486+
487+
var callHistory = new CallHistory();
488+
callHistory.Push(new OutCall(null, null, Constants.ClientCallerId, typeof(IGrainA).FullName!, nameof(IGrainA.MethodB1)));
489+
callHistory.Push(new InCall(null, null, typeof(IGrainA).FullName!, nameof(IGrainA.MethodB1)));
393490

394491
var diagram = graph.GenerateLiveMermaidDiagram(callHistory);
395492

493+
diagram.ShouldContain("ORLEANS_GRAIN_CLIENT");
494+
diagram.ShouldContain("IGrainA");
396495
diagram.ShouldContain("==>");
496+
diagram.ShouldContain("hits: 1");
497+
}
498+
499+
[Test]
500+
public void GenerateObservedMermaidDiagram_RendersObservedOnlyMethodLabelsAndUsageCounts()
501+
{
502+
var edges = new[]
503+
{
504+
ObservedGrainCallEdge.Create(
505+
typeof(IGrainA).FullName!,
506+
typeof(IGrainB).FullName!,
507+
nameof(IGrainA.MethodA1),
508+
nameof(IGrainB.MethodB1)),
509+
ObservedGrainCallEdge.Create(
510+
typeof(IGrainA).FullName!,
511+
typeof(IGrainB).FullName!,
512+
nameof(IGrainA.MethodB1),
513+
nameof(IGrainB.MethodC2))
514+
};
515+
516+
var diagram = GrainTransitionManager.GenerateObservedMermaidDiagram(edges);
517+
518+
diagram.ShouldContain("graph LR");
519+
diagram.ShouldContain("IGrainA");
520+
diagram.ShouldContain("IGrainB");
521+
diagram.ShouldContain("==>");
522+
diagram.ShouldContain($"{nameof(IGrainA.MethodA1)}->{nameof(IGrainB.MethodB1)}");
523+
diagram.ShouldContain($"{nameof(IGrainA.MethodB1)}->{nameof(IGrainB.MethodC2)}");
524+
diagram.ShouldContain("hits: 2");
397525
}
398526

399527
[Test]
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using ManagedCode.Orleans.Graph.Extensions;
2+
using Microsoft.Extensions.Configuration;
3+
using Orleans.TestingHost;
4+
5+
namespace ManagedCode.Orleans.Graph.Tests.RuntimeGraphCluster;
6+
7+
public class TestRuntimeGraphClientConfigurations : IClientBuilderConfigurator
8+
{
9+
public void Configure(IConfiguration configuration, IClientBuilder clientBuilder)
10+
{
11+
clientBuilder.AddOrleansGraph();
12+
}
13+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using Orleans.TestingHost;
2+
3+
namespace ManagedCode.Orleans.Graph.Tests.RuntimeGraphCluster;
4+
5+
public class TestRuntimeGraphClusterApplication : IDisposable, IAsyncDisposable
6+
{
7+
private bool _disposed;
8+
9+
public TestRuntimeGraphClusterApplication()
10+
{
11+
var builder = new TestClusterBuilder();
12+
builder.AddSiloBuilderConfigurator<TestRuntimeGraphSiloConfigurations>();
13+
builder.AddClientBuilderConfigurator<TestRuntimeGraphClientConfigurations>();
14+
Cluster = builder.Build();
15+
Cluster.Deploy();
16+
}
17+
18+
public TestCluster Cluster { get; }
19+
20+
public void Dispose()
21+
{
22+
if (_disposed)
23+
{
24+
return;
25+
}
26+
27+
_disposed = true;
28+
Cluster.Dispose();
29+
GC.SuppressFinalize(this);
30+
}
31+
32+
public async ValueTask DisposeAsync()
33+
{
34+
if (_disposed)
35+
{
36+
return;
37+
}
38+
39+
_disposed = true;
40+
await Cluster.DisposeAsync();
41+
GC.SuppressFinalize(this);
42+
}
43+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using ManagedCode.Orleans.Graph.Extensions;
2+
using Microsoft.Extensions.Configuration;
3+
using Orleans.TestingHost;
4+
5+
namespace ManagedCode.Orleans.Graph.Tests.RuntimeGraphCluster;
6+
7+
public class TestRuntimeGraphInternalClientConfigurations : IClientBuilderConfigurator
8+
{
9+
public void Configure(IConfiguration configuration, IClientBuilder clientBuilder)
10+
{
11+
clientBuilder.AddOrleansGraph(filters => filters.TrackOrleansGraphInternalCalls = true);
12+
}
13+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using Orleans.TestingHost;
2+
3+
namespace ManagedCode.Orleans.Graph.Tests.RuntimeGraphCluster;
4+
5+
public class TestRuntimeGraphInternalClusterApplication : IDisposable, IAsyncDisposable
6+
{
7+
private bool _disposed;
8+
9+
public TestRuntimeGraphInternalClusterApplication()
10+
{
11+
var builder = new TestClusterBuilder();
12+
builder.AddSiloBuilderConfigurator<TestRuntimeGraphInternalSiloConfigurations>();
13+
builder.AddClientBuilderConfigurator<TestRuntimeGraphInternalClientConfigurations>();
14+
Cluster = builder.Build();
15+
Cluster.Deploy();
16+
}
17+
18+
public TestCluster Cluster { get; }
19+
20+
public void Dispose()
21+
{
22+
if (_disposed)
23+
{
24+
return;
25+
}
26+
27+
_disposed = true;
28+
Cluster.Dispose();
29+
GC.SuppressFinalize(this);
30+
}
31+
32+
public async ValueTask DisposeAsync()
33+
{
34+
if (_disposed)
35+
{
36+
return;
37+
}
38+
39+
_disposed = true;
40+
await Cluster.DisposeAsync();
41+
GC.SuppressFinalize(this);
42+
}
43+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using ManagedCode.Orleans.Graph.Extensions;
2+
using Orleans.TestingHost;
3+
4+
namespace ManagedCode.Orleans.Graph.Tests.RuntimeGraphCluster;
5+
6+
public class TestRuntimeGraphInternalSiloConfigurations : ISiloConfigurator
7+
{
8+
public void Configure(ISiloBuilder siloBuilder)
9+
{
10+
siloBuilder.AddOrleansGraph(
11+
configureFilters: filters =>
12+
{
13+
filters.LiveGraphFlushPeriod = TimeSpan.FromMilliseconds(50);
14+
filters.TrackOrleansGraphInternalCalls = true;
15+
},
16+
configureGraph: graph => graph.AllowAll());
17+
}
18+
}

0 commit comments

Comments
 (0)