22using ManagedCode . Orleans . Graph . Models ;
33using ManagedCode . Orleans . Graph . Tests . Cluster . Grains . Interfaces ;
44using ManagedCode . Orleans . Graph . Tests . RuntimeGraphCluster ;
5+ using Microsoft . Extensions . DependencyInjection ;
56
67namespace ManagedCode . Orleans . Graph . Tests ;
78
@@ -121,7 +122,7 @@ await WaitForEdgesAsync(_fixture.Cluster.Client, edges =>
121122 edge . TargetMethod == nameof ( IGrainB . MethodB1 ) ) ) ;
122123
123124 var telemetry = _fixture . Cluster . Client . GetGrain < IOrleansGraphTelemetryGrain > ( Constants . LiveGraphTelemetryGrainKey ) ;
124- var diagram = await telemetry . GenerateMermaidDiagramAsync ( ) ;
125+ var diagram = await telemetry . GenerateLiveMermaidDiagramAsync ( ) ;
125126
126127 diagram . ShouldContain ( "graph LR" ) ;
127128 diagram . ShouldContain ( "ORLEANS_GRAIN_CLIENT" ) ;
@@ -132,6 +133,83 @@ await WaitForEdgesAsync(_fixture.Cluster.Client, edges =>
132133 diagram . ShouldContain ( "hits: 1" ) ;
133134 }
134135
136+ [ Test ]
137+ public async Task AllowAll_BuildsExactComplexRuntimeGraphAsync ( )
138+ {
139+ await ResetTelemetryAsync ( _fixture . Cluster . Client ) ;
140+
141+ var result = await _fixture . Cluster . Client
142+ . GetGrain < IGrainA > ( "complex-flow" )
143+ . MethodComplexFlow ( 1 ) ;
144+
145+ result . ShouldBe ( 5 ) ;
146+
147+ var expectedEdges = BuildExpectedComplexFlowEdges ( nameof ( IGrainA . MethodComplexFlow ) , includeClient : true ) ;
148+
149+ var edges = await WaitForEdgesAsync ( _fixture . Cluster . Client , edges =>
150+ edges . Count == expectedEdges . Length && ContainsExpectedEdges ( edges , expectedEdges ) ) ;
151+
152+ AssertExpectedEdges ( edges , expectedEdges ) ;
153+ edges . Any ( IsTelemetryEdge ) . ShouldBeFalse ( ) ;
154+
155+ var telemetry = _fixture . Cluster . Client . GetGrain < IOrleansGraphTelemetryGrain > ( Constants . LiveGraphTelemetryGrainKey ) ;
156+ var diagram = await telemetry . GenerateLiveMermaidDiagramAsync ( ) ;
157+
158+ diagram . ShouldBe ( BuildExpectedComplexLiveMermaidDiagram ( nameof ( IGrainA . MethodComplexFlow ) , includeClient : true ) ) ;
159+ }
160+
161+ [ Test ]
162+ public async Task AllowAll_BuildsExactGrainOnlyRuntimeGraphWithoutClientAsync ( )
163+ {
164+ await ResetTelemetryAsync ( _fixture . Cluster . Client ) ;
165+
166+ var result = await _fixture . Cluster . Client
167+ . GetGrain < IGrainA > ( "grain-only-complex-flow" )
168+ . MethodGrainOnlyComplexFlow ( 1 ) ;
169+
170+ result . ShouldBe ( 5 ) ;
171+
172+ var expectedEdges = BuildExpectedComplexFlowEdges ( nameof ( IGrainA . MethodGrainOnlyComplexFlow ) , includeClient : false ) ;
173+
174+ var edges = await WaitForEdgesAsync ( _fixture . Cluster . Client , edges =>
175+ edges . Count == expectedEdges . Length && ContainsExpectedEdges ( edges , expectedEdges ) ) ;
176+
177+ AssertExpectedEdges ( edges , expectedEdges ) ;
178+ edges . Any ( edge => edge . Source == Constants . ClientCallerId || edge . Target == Constants . ClientCallerId ) . ShouldBeFalse ( ) ;
179+ edges . Any ( IsTelemetryEdge ) . ShouldBeFalse ( ) ;
180+
181+ var telemetry = _fixture . Cluster . Client . GetGrain < IOrleansGraphTelemetryGrain > ( Constants . LiveGraphTelemetryGrainKey ) ;
182+ var diagram = await telemetry . GenerateLiveMermaidDiagramAsync ( ) ;
183+
184+ diagram . ShouldBe ( BuildExpectedComplexLiveMermaidDiagram ( nameof ( IGrainA . MethodGrainOnlyComplexFlow ) , includeClient : false ) ) ;
185+ }
186+
187+ [ Test ]
188+ public async Task AllowAll_BuildsExactGrainFactoryRuntimeGraphWithoutClientAsync ( )
189+ {
190+ var grainFactory = GetPrimarySiloGrainFactory ( ) ;
191+ await ResetTelemetryAsync ( grainFactory ) ;
192+
193+ var result = await grainFactory
194+ . GetGrain < IGrainA > ( "grain-factory-complex-flow" )
195+ . MethodGrainOnlyComplexFlow ( 1 ) ;
196+
197+ result . ShouldBe ( 5 ) ;
198+
199+ var expectedEdges = BuildExpectedComplexFlowEdges ( nameof ( IGrainA . MethodGrainOnlyComplexFlow ) , includeClient : false ) ;
200+ var edges = await WaitForEdgesAsync ( grainFactory , edges =>
201+ edges . Count == expectedEdges . Length && ContainsExpectedEdges ( edges , expectedEdges ) ) ;
202+
203+ AssertExpectedEdges ( edges , expectedEdges ) ;
204+ edges . Any ( edge => edge . Source == Constants . ClientCallerId || edge . Target == Constants . ClientCallerId ) . ShouldBeFalse ( ) ;
205+ edges . Any ( IsTelemetryEdge ) . ShouldBeFalse ( ) ;
206+
207+ var telemetry = grainFactory . GetGrain < IOrleansGraphTelemetryGrain > ( Constants . LiveGraphTelemetryGrainKey ) ;
208+ var diagram = await telemetry . GenerateLiveMermaidDiagramAsync ( ) ;
209+
210+ diagram . ShouldBe ( BuildExpectedComplexLiveMermaidDiagram ( nameof ( IGrainA . MethodGrainOnlyComplexFlow ) , includeClient : false ) ) ;
211+ }
212+
135213 [ Test ]
136214 public async Task Telemetry_DoesNotTrackOrleansGraphInternalCallsByDefaultAsync ( )
137215 {
@@ -149,18 +227,24 @@ await _fixture.Cluster.Client
149227 edges . Any ( IsTelemetryEdge ) . ShouldBeFalse ( ) ;
150228 }
151229
152- private static async Task ResetTelemetryAsync ( IClusterClient client )
230+ private IGrainFactory GetPrimarySiloGrainFactory ( )
153231 {
154- await client . GetGrain < IOrleansGraphTelemetryWorker > ( Constants . LiveGraphTelemetryGrainKey ) . FlushAsync ( ) ;
232+ var serviceProvider = _fixture . Cluster . GetSiloServiceProvider ( _fixture . Cluster . Primary . SiloAddress ) ;
233+ return serviceProvider . GetRequiredService < IGrainFactory > ( ) ;
234+ }
235+
236+ private static async Task ResetTelemetryAsync ( IGrainFactory grainFactory )
237+ {
238+ await grainFactory . GetGrain < IOrleansGraphTelemetryWorker > ( Constants . LiveGraphTelemetryGrainKey ) . FlushAsync ( ) ;
155239 await Task . Delay ( 200 ) ;
156- await client . GetGrain < IOrleansGraphTelemetryGrain > ( Constants . LiveGraphTelemetryGrainKey ) . ClearAsync ( ) ;
240+ await grainFactory . GetGrain < IOrleansGraphTelemetryGrain > ( Constants . LiveGraphTelemetryGrainKey ) . ClearAsync ( ) ;
157241 }
158242
159243 private static async Task < IReadOnlyCollection < ObservedGrainCallEdge > > WaitForEdgesAsync (
160- IClusterClient client ,
244+ IGrainFactory grainFactory ,
161245 Func < IReadOnlyCollection < ObservedGrainCallEdge > , bool > predicate )
162246 {
163- var telemetry = client . GetGrain < IOrleansGraphTelemetryGrain > ( Constants . LiveGraphTelemetryGrainKey ) ;
247+ var telemetry = grainFactory . GetGrain < IOrleansGraphTelemetryGrain > ( Constants . LiveGraphTelemetryGrainKey ) ;
164248
165249 for ( var attempt = 0 ; attempt < 50 ; attempt ++ )
166250 {
@@ -176,6 +260,124 @@ private static async Task<IReadOnlyCollection<ObservedGrainCallEdge>> WaitForEdg
176260 return await telemetry . GetEdgesAsync ( ) ;
177261 }
178262
263+ private static void AssertExpectedEdges (
264+ IReadOnlyCollection < ObservedGrainCallEdge > edges ,
265+ IReadOnlyCollection < ExpectedObservedEdge > expectedEdges )
266+ {
267+ edges . Count . ShouldBe ( expectedEdges . Count ) ;
268+
269+ foreach ( var expected in expectedEdges )
270+ {
271+ edges . ShouldContain ( edge =>
272+ edge . Source == expected . Source &&
273+ edge . Target == expected . Target &&
274+ edge . SourceMethod == expected . SourceMethod &&
275+ edge . TargetMethod == expected . TargetMethod &&
276+ edge . Count == expected . Count ) ;
277+ }
278+ }
279+
280+ private static bool ContainsExpectedEdges (
281+ IReadOnlyCollection < ObservedGrainCallEdge > edges ,
282+ IReadOnlyCollection < ExpectedObservedEdge > expectedEdges )
283+ {
284+ return expectedEdges . All ( expected =>
285+ edges . Any ( edge =>
286+ edge . Source == expected . Source &&
287+ edge . Target == expected . Target &&
288+ edge . SourceMethod == expected . SourceMethod &&
289+ edge . TargetMethod == expected . TargetMethod &&
290+ edge . Count == expected . Count ) ) ;
291+ }
292+
293+ private static ExpectedObservedEdge [ ] BuildExpectedComplexFlowEdges ( string rootMethod , bool includeClient )
294+ {
295+ var edges = new List < ExpectedObservedEdge >
296+ {
297+ new (
298+ typeof ( IGrainA ) . FullName ! ,
299+ typeof ( IGrainB ) . FullName ! ,
300+ rootMethod ,
301+ nameof ( IGrainB . MethodB1 ) ,
302+ 1 ) ,
303+ new (
304+ typeof ( IGrainA ) . FullName ! ,
305+ typeof ( IGrainC ) . FullName ! ,
306+ rootMethod ,
307+ nameof ( IGrainC . MethodBranchingFlow ) ,
308+ 1 ) ,
309+ new (
310+ typeof ( IGrainC ) . FullName ! ,
311+ typeof ( IGrainB ) . FullName ! ,
312+ nameof ( IGrainC . MethodBranchingFlow ) ,
313+ nameof ( IGrainB . MethodB1 ) ,
314+ 1 ) ,
315+ new (
316+ typeof ( IGrainC ) . FullName ! ,
317+ typeof ( IGrainD ) . FullName ! ,
318+ nameof ( IGrainC . MethodBranchingFlow ) ,
319+ nameof ( IGrainD . MethodE2 ) ,
320+ 1 ) ,
321+ new (
322+ typeof ( IGrainA ) . FullName ! ,
323+ typeof ( IGrainD ) . FullName ! ,
324+ rootMethod ,
325+ nameof ( IGrainD . MethodE2 ) ,
326+ 1 ) ,
327+ new (
328+ typeof ( IGrainD ) . FullName ! ,
329+ typeof ( IGrainE ) . FullName ! ,
330+ nameof ( IGrainD . MethodE2 ) ,
331+ nameof ( IGrainE . MethodE1 ) ,
332+ 2 )
333+ } ;
334+
335+ if ( includeClient )
336+ {
337+ edges . Insert ( 0 , new ExpectedObservedEdge (
338+ Constants . ClientCallerId ,
339+ typeof ( IGrainA ) . FullName ! ,
340+ Constants . AnyMethod ,
341+ rootMethod ,
342+ 1 ) ) ;
343+ }
344+
345+ return edges . ToArray ( ) ;
346+ }
347+
348+ private static string BuildExpectedComplexLiveMermaidDiagram ( string rootMethod , bool includeClient )
349+ {
350+ var grainA = MermaidNode < IGrainA > ( ) ;
351+ var grainB = MermaidNode < IGrainB > ( ) ;
352+ var grainC = MermaidNode < IGrainC > ( ) ;
353+ var grainD = MermaidNode < IGrainD > ( ) ;
354+ var grainE = MermaidNode < IGrainE > ( ) ;
355+
356+ var lines = new List < string >
357+ {
358+ "graph LR" ,
359+ $ " { grainA . Id } [\" { grainA . DisplayName } \" ] ==>|{ rootMethod } ->{ nameof ( IGrainB . MethodB1 ) } <br/>hits: 1| { grainB . Id } [\" { grainB . DisplayName } \" ]",
360+ $ " { grainA . Id } [\" { grainA . DisplayName } \" ] ==>|{ rootMethod } ->{ nameof ( IGrainC . MethodBranchingFlow ) } <br/>hits: 1| { grainC . Id } [\" { grainC . DisplayName } \" ]",
361+ $ " { grainA . Id } [\" { grainA . DisplayName } \" ] ==>|{ rootMethod } ->{ nameof ( IGrainD . MethodE2 ) } <br/>hits: 1| { grainD . Id } [\" { grainD . DisplayName } \" ]",
362+ $ " { grainC . Id } [\" { grainC . DisplayName } \" ] ==>|{ nameof ( IGrainC . MethodBranchingFlow ) } ->{ nameof ( IGrainB . MethodB1 ) } <br/>hits: 1| { grainB . Id } [\" { grainB . DisplayName } \" ]",
363+ $ " { grainC . Id } [\" { grainC . DisplayName } \" ] ==>|{ nameof ( IGrainC . MethodBranchingFlow ) } ->{ nameof ( IGrainD . MethodE2 ) } <br/>hits: 1| { grainD . Id } [\" { grainD . DisplayName } \" ]",
364+ $ " { grainD . Id } [\" { grainD . DisplayName } \" ] ==>|{ nameof ( IGrainD . MethodE2 ) } ->{ nameof ( IGrainE . MethodE1 ) } <br/>hits: 2| { grainE . Id } [\" { grainE . DisplayName } \" ]"
365+ } ;
366+
367+ if ( includeClient )
368+ {
369+ lines . Add ( $ " { Constants . ClientCallerId } [\" { Constants . ClientCallerId } \" ] ==>|{ rootMethod } <br/>hits: 1| { grainA . Id } [\" { grainA . DisplayName } \" ]") ;
370+ }
371+
372+ return string . Join ( Environment . NewLine , lines ) + Environment . NewLine ;
373+ }
374+
375+ private static ( string Id , string DisplayName ) MermaidNode < TGrain > ( )
376+ where TGrain : IGrain
377+ {
378+ return ( typeof ( TGrain ) . FullName ! . Replace ( '.' , '_' ) , typeof ( TGrain ) . Name ) ;
379+ }
380+
179381 private static bool IsTelemetryEdge ( ObservedGrainCallEdge edge )
180382 {
181383 return IsTelemetryEndpoint ( edge . Source ) || IsTelemetryEndpoint ( edge . Target ) ;
@@ -186,4 +388,11 @@ private static bool IsTelemetryEndpoint(string endpoint)
186388 return endpoint . Contains ( nameof ( IOrleansGraphTelemetryWorker ) , StringComparison . Ordinal ) ||
187389 endpoint . Contains ( nameof ( IOrleansGraphTelemetryGrain ) , StringComparison . Ordinal ) ;
188390 }
391+
392+ private readonly record struct ExpectedObservedEdge (
393+ string Source ,
394+ string Target ,
395+ string SourceMethod ,
396+ string TargetMethod ,
397+ long Count ) ;
189398}
0 commit comments