Skip to content

Commit 7f68c48

Browse files
add simple actor telemetry (#6294)
* added initial actor telemetry for #6293 * added basic telemetry tests for local actors * added spec to validate that `RemoteActorRef` doesn't influence counters * updated `SpawnActorBenchmarks` to include telemetry impact * converted telemetry events into `sealed class`es with `internal` constructors * removed `Reason`
1 parent 3156272 commit 7f68c48

File tree

9 files changed

+598
-7
lines changed

9 files changed

+598
-7
lines changed

src/benchmark/Akka.Benchmarks/Actor/SpawnActorBenchmarks.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,19 @@ public class SpawnActorBenchmarks
2020
{
2121
[Params(100_000)]
2222
public int ActorCount { get;set; }
23+
24+
[Params(true, false)]
25+
public bool EnableTelemetry { get; set; }
26+
2327
private ActorSystem system;
2428

2529
[IterationSetup]
2630
public void Setup()
2731
{
28-
system = ActorSystem.Create("system");
32+
if(EnableTelemetry) // need to measure the impact of publishing actor start / stop events
33+
system = ActorSystem.Create("system", "akka.actor.telemetry.enabled = true");
34+
else
35+
system = ActorSystem.Create("system");
2936
}
3037

3138
[IterationCleanup]
@@ -38,7 +45,12 @@ public void Cleanup()
3845
public async Task Actor_spawn()
3946
{
4047
var parent = system.ActorOf(Parent.Props);
41-
await parent.Ask<TestDone>(new StartTest(ActorCount), TimeSpan.FromMinutes(2));
48+
49+
// spawn a bunch of actors
50+
await parent.Ask<TestDone>(new StartTest(ActorCount), TimeSpan.FromMinutes(2)).ConfigureAwait(false);
51+
52+
// terminate the hierarchy
53+
await parent.GracefulStop(TimeSpan.FromMinutes(1)).ConfigureAwait(false);
4254
}
4355

4456
#region actors

src/core/Akka.API.Tests/CoreAPISpec.ApproveCore.verified.txt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,12 @@ namespace Akka.Actor
314314
public static readonly Akka.Actor.IActorRef NoSender;
315315
public static readonly Akka.Actor.Nobody Nobody;
316316
}
317+
public sealed class ActorRestarted : Akka.Actor.IActorTelemetryEvent, Akka.Actor.INoSerializationVerificationNeeded, Akka.Actor.INotInfluenceReceiveTimeout
318+
{
319+
public System.Type ActorType { get; }
320+
public System.Exception Reason { get; }
321+
public Akka.Actor.IActorRef Subject { get; }
322+
}
317323
public class ActorSelection : Akka.Actor.ICanTell
318324
{
319325
public ActorSelection() { }
@@ -340,13 +346,23 @@ namespace Akka.Actor
340346
public Akka.Actor.ActorSelectionMessage Copy(object message = null, Akka.Actor.SelectionPathElement[] elements = null, System.Nullable<bool> wildCardFanOut = null) { }
341347
public override string ToString() { }
342348
}
349+
public sealed class ActorStarted : Akka.Actor.IActorTelemetryEvent, Akka.Actor.INoSerializationVerificationNeeded, Akka.Actor.INotInfluenceReceiveTimeout
350+
{
351+
public System.Type ActorType { get; }
352+
public Akka.Actor.IActorRef Subject { get; }
353+
}
343354
public class ActorStashPlugin : Akka.Actor.ActorProducerPluginBase
344355
{
345356
public ActorStashPlugin() { }
346357
public override void AfterIncarnated(Akka.Actor.ActorBase actor, Akka.Actor.IActorContext context) { }
347358
public override void BeforeIncarnated(Akka.Actor.ActorBase actor, Akka.Actor.IActorContext context) { }
348359
public override bool CanBeAppliedTo(System.Type actorType) { }
349360
}
361+
public sealed class ActorStopped : Akka.Actor.IActorTelemetryEvent, Akka.Actor.INoSerializationVerificationNeeded, Akka.Actor.INotInfluenceReceiveTimeout
362+
{
363+
public System.Type ActorType { get; }
364+
public Akka.Actor.IActorRef Subject { get; }
365+
}
350366
public abstract class ActorSystem : Akka.Actor.IActorRefFactory, System.IDisposable
351367
{
352368
protected ActorSystem() { }
@@ -988,6 +1004,11 @@ namespace Akka.Actor
9881004
{
9891005
Akka.Actor.IStash Stash { get; set; }
9901006
}
1007+
public interface IActorTelemetryEvent : Akka.Actor.INoSerializationVerificationNeeded, Akka.Actor.INotInfluenceReceiveTimeout
1008+
{
1009+
System.Type ActorType { get; }
1010+
Akka.Actor.IActorRef Subject { get; }
1011+
}
9911012
public interface IAdvancedScheduler : Akka.Actor.IActionScheduler, Akka.Actor.IRunnableScheduler { }
9921013
public interface IAutoReceivedMessage { }
9931014
public interface ICanTell
@@ -1682,6 +1703,7 @@ namespace Akka.Actor
16821703
public bool DebugRouterMisconfiguration { get; }
16831704
public bool DebugUnhandledMessage { get; }
16841705
public int DefaultVirtualNodesFactor { get; }
1706+
public bool EmitActorTelemetry { get; }
16851707
public bool FsmDebugEvent { get; }
16861708
public bool HasCluster { get; }
16871709
public string Home { get; }
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
//-----------------------------------------------------------------------
2+
// <copyright file="RemoteActorTelemetrySpecs.cs" company="Akka.NET Project">
3+
// Copyright (C) 2009-2022 Lightbend Inc. <http://www.lightbend.com>
4+
// Copyright (C) 2013-2022 .NET Foundation <https://github.com/akkadotnet/akka.net>
5+
// </copyright>
6+
//-----------------------------------------------------------------------
7+
8+
using System;
9+
using System.Threading.Tasks;
10+
using Akka.Actor;
11+
using Akka.TestKit;
12+
using Akka.TestKit.TestActors;
13+
using Akka.Util.Internal;
14+
using Xunit;
15+
using Xunit.Abstractions;
16+
17+
namespace Akka.Remote.Tests
18+
{
19+
public class RemoteActorTelemetrySpecs : AkkaSpec
20+
{
21+
// create HOCON configuraiton that enables telemetry and Akka.Remote
22+
private static readonly string Config = @"
23+
akka {
24+
actor {
25+
provider = ""Akka.Remote.RemoteActorRefProvider, Akka.Remote""
26+
telemetry.enabled = true
27+
}
28+
remote {
29+
log-remote-lifecycle-events = on
30+
dot-netty.tcp {
31+
port = 0
32+
hostname = localhost
33+
}
34+
}
35+
}";
36+
37+
public RemoteActorTelemetrySpecs(ITestOutputHelper outputHelper) : base(Config, outputHelper)
38+
{
39+
40+
}
41+
42+
private class TelemetrySubscriber : ReceiveActor
43+
{
44+
// keep track of integer counters for each event type
45+
private int _actorCreated;
46+
private int _actorStopped;
47+
private int _actorRestarted;
48+
49+
// create a message type that will send the current values of all counters
50+
public sealed class GetTelemetry
51+
{
52+
public int ActorCreated { get; }
53+
public int ActorStopped { get; }
54+
public int ActorRestarted { get; }
55+
56+
public GetTelemetry(int actorCreated, int actorStopped, int actorRestarted)
57+
{
58+
ActorCreated = actorCreated;
59+
ActorStopped = actorStopped;
60+
ActorRestarted = actorRestarted;
61+
}
62+
}
63+
64+
public class GetTelemetryRequest
65+
{
66+
// make singleton
67+
public static readonly GetTelemetryRequest Instance = new GetTelemetryRequest();
68+
69+
private GetTelemetryRequest()
70+
{
71+
}
72+
}
73+
74+
public TelemetrySubscriber()
75+
{
76+
// Receive each type of IActorTelemetryEvent
77+
Receive<ActorStarted>(e => { _actorCreated++; });
78+
Receive<ActorStopped>(e => { _actorStopped++; });
79+
Receive<ActorRestarted>(e => { _actorRestarted++; });
80+
// receive a request for current counter values and return a GetTelemetry result
81+
Receive<GetTelemetryRequest>(e =>
82+
Sender.Tell(new GetTelemetry(_actorCreated, _actorStopped, _actorRestarted)));
83+
}
84+
85+
protected override void PreStart()
86+
{
87+
Context.System.EventStream.Subscribe(Self, typeof(IActorTelemetryEvent));
88+
}
89+
}
90+
91+
// create a unit test where a second ActorSystem connects to Sys and receives an IActorRef from Sys and subscribes to Telemetry events
92+
[Fact]
93+
public async Task RemoteActorRefs_should_not_produce_telemetry()
94+
{
95+
// create a second ActorSystem that connects to Sys
96+
var system2 = ActorSystem.Create(Sys.Name, Sys.Settings.Config);
97+
try
98+
{
99+
// create a subscriber to receive telemetry events
100+
var subscriber = system2.ActorOf(Props.Create<TelemetrySubscriber>());
101+
102+
// send a request for the current telemetry counters
103+
var telemetry = await subscriber
104+
.Ask<TelemetrySubscriber.GetTelemetry>(TelemetrySubscriber.GetTelemetryRequest.Instance);
105+
106+
// verify that the counters are all correct
107+
Assert.Equal(0, telemetry.ActorCreated);
108+
Assert.Equal(0, telemetry.ActorStopped);
109+
Assert.Equal(0, telemetry.ActorRestarted);
110+
111+
// create an actor in Sys
112+
var actor1 = Sys.ActorOf(BlackHoleActor.Props, "actor1");
113+
114+
// resolve the currently bound Akka.Remote address for Sys
115+
var address = Sys.AsInstanceOf<ExtendedActorSystem>().Provider.DefaultAddress;
116+
117+
// create a RootActorPath for actor1 that uses the previous address value
118+
var actor1Path = new RootActorPath(address) / "user" / "actor1";
119+
120+
// have system2 send a request to actor1 via Akka.Remote
121+
var actor2 = await system2.ActorSelection(actor1Path).ResolveOne(RemainingOrDefault);
122+
123+
// send a request for the current telemetry counters
124+
telemetry = await subscriber
125+
.Ask<TelemetrySubscriber.GetTelemetry>(TelemetrySubscriber.GetTelemetryRequest.Instance);
126+
127+
// verify that created actors is greater than 1
128+
var previouslyCreated = telemetry.ActorCreated;
129+
Assert.True(previouslyCreated > 1); // should have had some /system actors started as well
130+
Assert.Equal(0, telemetry.ActorStopped);
131+
Assert.Equal(0, telemetry.ActorRestarted);
132+
133+
// stop the actor in Sys
134+
Sys.Stop(actor1);
135+
136+
// send a request for the current telemetry counters
137+
telemetry = await subscriber
138+
.Ask<TelemetrySubscriber.GetTelemetry>(TelemetrySubscriber.GetTelemetryRequest.Instance);
139+
// verify that the counters are all zero
140+
Assert.Equal(previouslyCreated, telemetry.ActorCreated); // should not have changed
141+
Assert.Equal(0, telemetry.ActorStopped);
142+
Assert.Equal(0, telemetry.ActorRestarted);
143+
}
144+
finally
145+
{
146+
Shutdown(system2);
147+
}
148+
}
149+
}
150+
}

0 commit comments

Comments
 (0)