Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
217 changes: 217 additions & 0 deletions OrleansDashboard/Implementation/GrainCallProfilerFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Orleans;
using Orleans.Runtime;

namespace OrleansDashboard.Metrics
{
public class GrainCallProfilerFilter : IIncomingGrainCallFilter, IOutgoingGrainCallFilter
{
private readonly IGrainProfiler profiler;
private readonly ILogger<GrainCallProfilerFilter> logger;
private readonly ConcurrentDictionary<MethodInfo, bool> shouldSkipCache = new ConcurrentDictionary<MethodInfo, bool>();




public GrainCallProfilerFilter(IGrainProfiler profiler, ILogger<GrainCallProfilerFilter> logger)
{
this.profiler = profiler;
this.logger = logger;
}


private bool ShouldSkipProfiling(IGrainCallContext context)
{
var grainMethod = context.InterfaceMethod;

if (grainMethod == null)
{
return false;
}

if (!shouldSkipCache.TryGetValue(grainMethod, out var shouldSkip))
{
try
{
var grainType = context.Grain.GetType();

shouldSkip =
grainType.GetCustomAttribute<NoProfilingAttribute>() != null ||
grainMethod.GetCustomAttribute<NoProfilingAttribute>() != null;
}
catch (Exception ex)
{
logger.LogError(100003, ex, "error reading NoProfilingAttribute attribute for grain");

shouldSkip = false;
}

shouldSkipCache.TryAdd(grainMethod, shouldSkip);
}

return shouldSkip;
}

public async Task Invoke(IIncomingGrainCallContext context)
{
if (ShouldSkipProfiling(context))
{
await context.Invoke();
return;
}

if (IsDebgu(context.InterfaceMethod.Name))
{
//in
var call = GetGrainMethod(context);
TrackBeginInvoke(call.Grain, call.Method);
}

try
{
await context.Invoke();
}
finally
{
if (IsDebgu(context.InterfaceMethod.Name))
{
//in
TrackEndInvoke();
}


}
}

public async Task Invoke(IOutgoingGrainCallContext context)
{
if (ShouldSkipProfiling(context))
{
await context.Invoke();
return;
}


if (IsDebgu(context.InterfaceMethod.Name))
{
//out
var call = GetGrainMethod(context);
TrackBeginInvoke(call.Grain, call.Method);
}

try
{
await context.Invoke();
}
finally
{
if (IsDebgu(context.InterfaceMethod.Name))
{
//out
TrackEndInvoke();
}
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private Stack<GrainInteractionInfoEntry> GetCallStack()
{
return RequestContext.Get(nameof(GrainProfilerFilter)) as Stack<GrainInteractionInfoEntry> ?? new Stack<GrainInteractionInfoEntry>();
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void SaveCallStack(Stack<GrainInteractionInfoEntry> stack)
{
if (stack.Count == 0)
{
RequestContext.Remove(nameof(GrainProfilerFilter));
}
else
{
RequestContext.Set(nameof(GrainProfilerFilter), stack);
}
}

private bool IsDebgu(string name)
{
if (name == "CallSecondInteractionTestGrain" ||
name == "CallThirdInteractionTestGrain" ||
name == "ITestGrain" ||
name == "CallFirstInteractionTestGrain")
return true;

return false;
}

private void TrackBeginInvoke(string grain, string method)
{
var stack = GetCallStack();
if (stack.TryPeek(out var info))
{
info.TargetGrain = grain;
}

stack.Push(new GrainInteractionInfoEntry
{
Grain = grain,
Method = method
});
SaveCallStack(stack);
}

private void TrackEndInvoke()
{
var stack = GetCallStack();
var info = stack.Pop();

//grainInteractionProfiler ??= grainFactory.GetGrain<IInteractionProfiler>(0);
//await grainInteractionProfiler.Track(info);
SaveCallStack(stack);

}


[MethodImpl(MethodImplOptions.AggressiveInlining)]
private (Type GrainType, MethodInfo GrainMethodInfo) GetGrainTypeAndMethodInfo(IIncomingGrainCallContext context)
{
return (context.Grain.GetType(), context.ImplementationMethod);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private (Type GrainType, MethodInfo GrainMethodInfo) GetGrainTypeAndMethodInfo(IOutgoingGrainCallContext context)
{
return (context.Grain.GetType(), context.InterfaceMethod);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private (string Grain, string Method) GetGrainMethod(IIncomingGrainCallContext context)
{
return (context.InterfaceMethod.ReflectedType.Name, context.InterfaceMethod.Name);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private (string Grain, string Method) GetGrainMethod(IOutgoingGrainCallContext context)
{
return (context.InterfaceMethod.ReflectedType.Name, context.InterfaceMethod.Name);
}
}
}

[Serializable]
public class GrainInteractionInfoEntry
{
public string Grain { get; set; }
public string TargetGrain { get; set; }
public string Method { get; set; }
public uint Count { get; set; } = 1;

public string Key => Grain + ":" + (TargetGrain ?? string.Empty) + ":" + Method;
}

4 changes: 4 additions & 0 deletions OrleansDashboard/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ public static IServiceCollection AddDashboard(this IServiceCollection services,
services.AddSingleton<IGrainProfiler, GrainProfiler>();
services.AddSingleton(c => (ILifecycleParticipant<ISiloLifecycle>)c.GetRequiredService<IGrainProfiler>());
services.AddSingleton<IIncomingGrainCallFilter, GrainProfilerFilter>();

services.AddSingleton<IIncomingGrainCallFilter, GrainCallProfilerFilter>();
services.AddSingleton<IOutgoingGrainCallFilter, GrainCallProfilerFilter>();

services.TryAddSingleton<IAssetProvider, CDNAssetProvider>();

services.AddSingleton<ISiloDetailsProvider>(c =>
Expand Down
58 changes: 58 additions & 0 deletions Tests/TestGrains/InteractionTestCalls.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Orleans;

namespace TestGrains
{
public static class InteractionTestCalls
{
public static Task Make(IClusterClient client, CancellationTokenSource tokenSource)
{
return Task.Run(async () =>
{
var random = new Random();

while (!tokenSource.IsCancellationRequested)
{
try
{
// client => First => Second

// client => Second => Third
// client => Second => Test

// client => Third => Test
var rnd = random.Next(1, 3);

switch (rnd)
{
case 1:
{
var testGrain = client.GetGrain<IFirstInteractionTestGrain>(random.Next(100));
await testGrain.CallFirstInteractionTestGrain(random.Next(100));
break;
}
case 2:
{
var testGrain = client.GetGrain<ISecondInteractionTestGrain>(random.Next(100));
await testGrain.CallSecondInteractionTestGrain(random.Next(100));
break;
}
case 3:
{
var testGrain = client.GetGrain<IThirdInteractionTestGrain>(random.Next(100));
await testGrain.CallThirdInteractionTestGrain(random.Next(100));
break;
}
}
}
catch
{
// Grain might throw exception to test error rate.
}
}
});
}
}
}
64 changes: 64 additions & 0 deletions Tests/TestGrains/InteractionTestGrain.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using System;
using System.Threading.Tasks;
using Orleans;
using Orleans.Runtime;

namespace TestGrains
{
public interface IFirstInteractionTestGrain : ITestGrain
{
Task CallFirstInteractionTestGrain(int id);

Task CallSecondInteractionTestGrain(int id);

Task CallThirdInteractionTestGrain(int id);

Task CallTestGrain(int id);
}

public interface ISecondInteractionTestGrain : IFirstInteractionTestGrain
{
}

public interface IThirdInteractionTestGrain : IFirstInteractionTestGrain
{
}

public class FirstInteractionTestGrain : TestGrain, IFirstInteractionTestGrain
{

Random random = new Random();
public Task CallFirstInteractionTestGrain(int id)
{
return GrainFactory.GetGrain<ISecondInteractionTestGrain>(id).CallSecondInteractionTestGrain(id);
}

public async Task CallSecondInteractionTestGrain(int id)
{
await GrainFactory.GetGrain<IThirdInteractionTestGrain>(id).ExampleMethod1();
await GrainFactory.GetGrain<ITestGrain>(id).ExampleMethod1();
}

public Task CallThirdInteractionTestGrain(int id)
{
return GrainFactory.GetGrain<ITestGrain>(id).ExampleMethod1();
}

public Task CallTestGrain(int id)
{
return GrainFactory.GetGrain<ITestGrain>(id).ExampleMethod1();
}
}



public class SecondInteractionTestGrain : FirstInteractionTestGrain, ISecondInteractionTestGrain
{

}

public class ThirdInteractionTestGrain : FirstInteractionTestGrain, IThirdInteractionTestGrain
{

}
}
1 change: 1 addition & 0 deletions Tests/TestHosts/TestHost/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ public static void Main(string[] args)
var cts = new CancellationTokenSource();

TestCalls.Make(client, cts);
InteractionTestCalls.Make(client, cts);

Console.WriteLine("Press key to exit...");
Console.ReadLine();
Expand Down
2 changes: 1 addition & 1 deletion Tests/TestHosts/TestHostCohosted2/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ static void Main(string[] args)

int gatewayPort = 30000;

builder.UseDevelopmentClustering(options => options.PrimarySiloEndpoint = new IPEndPoint(siloAddress, siloPort));
builder.UseDevelopmentClustering(options => options.PrimarySiloEndpoint = new IPEndPoint(siloAddress, siloPort));
builder.UseInMemoryReminderService();
builder.ConfigureEndpoints(siloAddress, siloPort, gatewayPort);
builder.Configure<ClusterOptions>(options =>
Expand Down