Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Akka.Cluster.Metrics extension implementation #4126

Merged
merged 87 commits into from
Jan 27, 2020

Conversation

IgorFedchenko
Copy link
Contributor

@IgorFedchenko IgorFedchenko commented Dec 24, 2019

This will close #2070 , and maybe some other related issues (like #2069)

The goal of this PR is to port Akka.Cluster.Metrics into Akka.NET. Maybe this will span into several pull requests, will see.

I will keep this in draft state until will have working specs for this (that is, once this will be ready for final reviews/merge).

I will start from implementing the core functions and metric collectors, and then will add adaptive load balancing routers.

@IgorFedchenko
Copy link
Contributor Author

IgorFedchenko commented Dec 27, 2019

Completed port of the extension code base.

Need to add/port specs and verify it is working. And need to implement .NET metrics collector (since scala's are jvm-specific).

Copy link
Member

@Aaronontheweb Aaronontheweb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Long review so far, but I hope you find it helpful

/// Creates new <see cref="ClusterMetrics"/> for given actor system
/// </summary>
/// <param name="system"></param>
public ClusterMetrics(ExtendedActorSystem system)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make this private - force users to call the ClusterMetrics.Get(ActorSystem) method so we can guarantee a maximum of one ClusterMetrics instance per-ActorSystem.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made this internal since it is used in ClusterMetricsExtensionProvider.

/// <summary>
/// DynamicAccess
/// </summary>
internal static class DynamicAccess
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So DynamicAccess, if I am not mistaken, is an internal Scala / Java API that does what the Activator does more or less.

If you want to use it so it makes a port easier, I'd move this into the core Akka module under the Akka/Utils folder since it's not specific to Akka.Cluster.Metrics. Just a nitpick.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea. Also I will add Try class to Akka.Utils, since it is used by DynamicAccess and also may be used somewhere else.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might also be worth getting rid of DynamicAccess altogether and just creating some Activator extension methods - more idiomatic that way. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mmm what do you mean by Activator extension methods? Obviously is can not add custom static method like Activator.CreateInstanceFor<TSomeInterface>(string typeName). Either use (new Activator()).CreateInstanceFor, or create another static class like ActivatorHelper - but I named it DynamicAccess to just simplify porting scala code in the future (sometimes one needs to check if some akka's class is already implemented, and search by name helps in such cases).

We can also add extension method for string, like typeName.CreateInstanceAs<TInterface>(params object[] args), but... It would be hard to find for other developers, and not obvious.

So since DynamicAccess.CreateInstanceFor wraps getting type by name, then instantiating with Activator, and handling exceptions (it returns Try<TResult> instance which may contain error info), it might be useful when porting things in future. But I can rename it to something more intuitive - do you have any suggestions? I agree that nobody would look for DynamicAccess class if only he is not porting dynamicAccess class from scala at the moment.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@IgorFedchenko let's make DynamicAccess internal and then keep it as-is - that way if we decided to clean up the API to make it more idiomatic with .NET in the future we can do that without worrying about user-facing issues.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To keep it in Akka.Utils to make reusable in different modules marked it with [InternalApi] and added INTERNAL USAGE text into class summary.

/// the sampled value resulting from the previous smoothing iteration.
/// This value is always used as the previous EWMA to calculate the new EWMA.
/// </summary>
public sealed partial class EWMA
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make this a value type (struct) if it's going to be used primarily in scoped math operations and not frequently passed between actors.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class is partial, and it may require more efforts to use separate EWMA struct, because it is used by other partial classes... So if it is not a significant improvement, maybe better to keep it as it is - do you agree?

/**
* Defines a remote address.
*/
message Address {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this type already defined somewhere else in our proto folder? Let's just import that if that's the case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It turned out that yes, we have AddressData message defined in protobuf/ContainerFormats.proto (and generated as Akka.Remote.Serialization.Proto.Msg package).
But for some reason generated c# AddressData class is internal there, and I can not reuse it.
Should I regenerate proto messages with public modifier, or just have my own Address message defined as we have now?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add Akka.Cluster.Metrics as a friend assembly of Akka.Remote:

[assembly: Guid("78986bdb-73f7-4532-8e03-1c9ccbe8148e")]
[assembly: InternalsVisibleTo("Akka.Remote.TestKit.Tests")]
[assembly: InternalsVisibleTo("Akka.Remote.TestKit")]
[assembly: InternalsVisibleTo("Akka.Remote.Tests")]
[assembly: InternalsVisibleTo("Akka.Remote.Tests.MultiNode")]
[assembly: InternalsVisibleTo("Akka.Remote.Tests.Performance")]
[assembly: InternalsVisibleTo("Akka.Cluster")]
[assembly: InternalsVisibleTo("Akka.Cluster.Tests")]
[assembly: InternalsVisibleTo("Akka.Cluster.TestKit")]
[assembly: InternalsVisibleTo("Akka.Cluster.Tests.MultiNode")]
[assembly: InternalsVisibleTo("Akka.Cluster.Tools")]
[assembly: InternalsVisibleTo("Akka.Cluster.Sharding")]

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And then remove the duplicate definition afterwards.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, almost forgot about this. Thanks for the tip, fixed now.

public static class StandardMetrics
{
// Constants for the heap related Metric names
public const string HeapMemoryUsed = "heap-memory-used";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@IgorFedchenko so this is where you're going to run into some fun platform portability issues - the CLR doesn't really have a singular "heap" in the same sense that the JVM does: https://stackoverflow.com/questions/10121943/net-process-memory-usage-5x-clr-heap-memory - it uses several different heaps that are set aside for specific purposes.

Heap memory in the JVM is used to measure the total footprint of all Java objects that have been allocated by a specific JVM process: https://www.yourkit.com/docs/kb/sizes.jsp - non-heap memory is used to measure things like native object allocation and etc.

So the term "heap memory" is not something that's going to be immediately clear to .NET users, since the CLR doesn't really assign that term any specific meaning. What I think end-users are interested in, is total memory allocated to the currently running process - and that value CAN fluctuate up and down as objects get garbage collected and the OS frees up memory that has been unused for a sufficiently long period of time.

What do you think? This is a platform difference we're going to need to reconcile somehow, and replacing "heap" memory with "process" memory, without distinguishing between managed object allocation and unmanaged object allocation (i.e. file handles, socket memory, etc...), probably gives .NET users the thing that they're really trying to monitor.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some ways of collecting this information in .NET:

  1. The garbage collector: https://github.com/petabridge/NBench/blob/dev/src/NBench/Collection/Memory/GcTotalMemoryCollector.cs#L34
  2. Performance Counters (Windows only)
  3. Calling into native APIs via P/Invoke - would need different implementations for Windows vs. POSIX

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There might also be some new .NET Core instrumentation that is cross-platform. That's been on the to-do list for CoreCLR for some time now but I think they've made some progress against that since 2.2+

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, indeed - I will need to address this once will work on .NET metrics collector here, in this PR. I totally agree that we should look for something like total allocated memory, and will update collector's name (and metric's name as well) once will get clear understanding of what we are collecting at the end.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for hints about research directions, by the way - and did not know anything about JVM heap :)

src/contrib/cluster/Akka.Cluster.Metrics/reference.conf Outdated Show resolved Hide resolved
src/core/Akka/Util/Option.cs Outdated Show resolved Hide resolved
@IgorFedchenko
Copy link
Contributor Author

@Aaronontheweb big thanks for a deep review! Updated code to reflect changes we discussed - and left questions about few moments. This was very helpful, and I made few notes for the future.

In the meantime, added some specs and made them passing (which required some changes to ported code), and working on next specs.

@Aaronontheweb
Copy link
Member

@IgorFedchenko have this failing in the MNTR: https://dev.azure.com/dotnet/Akka.NET/_build/results?buildId=27573&view=logs&j=f203efd3-114c-5599-f6b4-0fa25ffc9760&t=b10ae0da-ff8b-5c6e-60d8-f20e659bbc17&l=8341

[Node #1(node-1)][ERROR][1/23/2020 8:51:21 PM][Thread 0018][akka://ClusterMetricsEnabledSpec/system/log2-TestEventListener] The type initializer for 'Akka.Cluster.Metrics.Serialization.ClusterMetricsMessagesReflection' threw an exception.

@IgorFedchenko
Copy link
Contributor Author

@IgorFedchenko have this failing in the MNTR: https://dev.azure.com/dotnet/Akka.NET/_build/results?buildId=27573&view=logs&j=f203efd3-114c-5599-f6b4-0fa25ffc9760&t=b10ae0da-ff8b-5c6e-60d8-f20e659bbc17&l=8341

[Node #1(node-1)][ERROR][1/23/2020 8:51:21 PM][Thread 0018][akka://ClusterMetricsEnabledSpec/system/log2-TestEventListener] The type initializer for 'Akka.Cluster.Metrics.Serialization.ClusterMetricsMessagesReflection' threw an exception.

@Aaronontheweb Can not reproduce this locally, and can not find anything about this in Azure logs - url you provided shows "Build not found" error, and didn't find such failures in last CI runs.

But ClusterMetricsMessagesReflection is a static class generated by Protobuf, that is performing some basic setup, so... Do not have an idea of why it could be failing (seems like it's static constructor throws some exception, that is wrapped into TypeInitializationException).

With that said, we could get real exception in our logs from TypeInitializationException's InnerException property - but looks like Akka.NodeTestRunner does not report inner exceptions for non-AggregateException types. Opened an issue about that #4162 , and added closing PR. So after merging that one, the next time we will get real issue logged.

@Aaronontheweb
Copy link
Member

@IgorFedchenko merged in the latest changes - going to re-run the test suite now.

My guess is that it's a mismatched manifest in the serializer - sending one thing but trying to read another.

@IgorFedchenko
Copy link
Contributor Author

My guess is that it's a mismatched manifest in the serializer - sending one thing but trying to read another.

Turned out that Akka.Remote.Serialization.Proto.Msg.AddressData class we are reusing from Akka.Remote project was internal, and related property in some protobuf messages was internal, unlike other properties. So most likely this was an issue (error said that defined property was not found). Fixed it, making Akka.Remote.Serialization.Proto.Msg.AddressData public but with [InternalApi] attribute.

Also, disabled one MNTR spec phase where one of the nodes was allocating memory and we were asserting that this node will receive less messages. This is actually not true in MNTR tests, because all nodes share common system resources and most likely to have same memory capacity from the router point of view. Weird spec, skipped it for now.

And tried to fix another racing spec with increasing timeout of waiting for cluster members to be up.

Actually, as I can see, there were quite a few errors, so @Aaronontheweb thanks again for pointing me out to the logs output.

@Aaronontheweb
Copy link
Member

Fixed it, making Akka.Remote.Serialization.Proto.Msg.AddressData public but with [InternalApi] attribute.

That'll break the next time we generate the protobuf APIs. In that case I'd just create your own AddressData object in the protobuf definition... Which, ironically, I told you not to do in one of the earlier reviews... My bad 😨

@IgorFedchenko
Copy link
Contributor Author

@Aaronontheweb Updated this shared AddressData stuff, now Akka.Remote's version is internal as it was. Actually, that's me who ought to check this visibility issues before reusing existing code.

I have a question about one last MNTR test failing, do not have an idea why it could pass locally but fail on CI.
Basically, here is what I am doing in test (that's the very first line of the spec):

AwaitClusterUp(roles: _config.NodeList.ToArray());

And it fails, with something like this on each node:

[Node #4(node-4)]
[Node #4(node-4)][Node4:node-4][FAIL] Akka.Cluster.Metrics.Tests.MultiNode.ClusterMetricsEnabledSpec.ClusterMetrics_Should_work_when_enabled
[Node #4(node-4)][Node4:node-4][FAIL-EXCEPTION] Type: Akka.TestKit.Xunit2.Internals.AkkaEqualException
[Node #4(node-4)]--> [Node4:node-4][FAIL-EXCEPTION] Message: Assert.Equal() Failure
[Node #4(node-4)]Expected: 5
[Node #4(node-4)]Actual:   0
[Node #4(node-4)]--> [Node4:node-4][FAIL-EXCEPTION] StackTrace:    at Akka.TestKit.Xunit2.XunitAssertions.AssertEqual[T](T expected, T actual, String format, Object[] args) in D:\a\1\s\src\contrib\testkits\Akka.TestKit.Xunit2\XunitAssertions.cs:line 67
[Node #4(node-4)]   at Akka.Cluster.TestKit.MultiNodeClusterSpec.<>c__DisplayClass26_0.<AwaitMembersUp>b__2() in D:\a\1\s\src\core\Akka.Cluster.TestKit\MultiNodeClusterSpec.cs:line 367
[Node #4(node-4)]   at Akka.TestKit.TestKitBase.AwaitAssert(Action assertion, Nullable`1 duration, Nullable`1 interval) in D:\a\1\s\src\core\Akka.TestKit\TestKitBase_AwaitAssert.cs:line 46
[Node #4(node-4)]   at Akka.Cluster.TestKit.MultiNodeClusterSpec.<>c__DisplayClass26_0.<AwaitMembersUp>b__0() in D:\a\1\s\src\core\Akka.Cluster.TestKit\MultiNodeClusterSpec.cs:line 368
[Node #4(node-4)]   at Akka.TestKit.TestKitBase.<>c__DisplayClass151_0.<Within>b__0() in D:\a\1\s\src\core\Akka.TestKit\TestKitBase_Within.cs:line 57
[Node #4(node-4)]   at Akka.TestKit.TestKitBase.Within[T](TimeSpan min, TimeSpan max, Func`1 function, String hint, Nullable`1 epsilonValue) in D:\a\1\s\src\core\Akka.TestKit\TestKitBase_Within.cs:line 134
[Node #4(node-4)]   at Akka.TestKit.TestKitBase.Within(TimeSpan min, TimeSpan max, Action action, String hint, Nullable`1 epsilonValue) in D:\a\1\s\src\core\Akka.TestKit\TestKitBase_Within.cs:line 58
[Node #4(node-4)]   at Akka.TestKit.TestKitBase.Within(TimeSpan max, Action action, Nullable`1 epsilonValue) in D:\a\1\s\src\core\Akka.TestKit\TestKitBase_Within.cs:line 33
[Node #4(node-4)]   at Akka.Cluster.TestKit.MultiNodeClusterSpec.AwaitMembersUp(Int32 numbersOfMembers, ImmutableHashSet`1 canNotBePartOfMemberRing, Nullable`1 timeout) in D:\a\1\s\src\core\Akka.Cluster.TestKit\MultiNodeClusterSpec.cs:line 374
[Node #4(node-4)]   at Akka.Cluster.TestKit.MultiNodeClusterSpec.AwaitClusterUp(RoleName[] roles) in D:\a\1\s\src\core\Akka.Cluster.TestKit\MultiNodeClusterSpec.cs:line 271
[Node #4(node-4)]   at Akka.TestKit.TestKitBase.AwaitAssertAsync(Action assertion, Nullable`1 duration, Nullable`1 interval) in D:\a\1\s\src\core\Akka.TestKit\TestKitBase_AwaitAssert.cs:line 85
[Node #4(node-4)]   at Akka.Cluster.Metrics.Tests.MultiNode.ClusterMetricsEnabledSpec.Should_collect_and_publish_metrics_and_gossip_them_around_the_node_ring() in D:\a\1\s\src\contrib\cluster\Akka.Cluster.Metrics.Tests.MultiNode\ClusterMetricsExtensionSpec.cs:line 99
[Node #4(node-4)]   at Akka.Cluster.Metrics.Tests.MultiNode.ClusterMetricsEnabledSpec.ClusterMetrics_Should_work_when_enabled() in D:\a\1\s\src\contrib\cluster\Akka.Cluster.Metrics.Tests.MultiNode\ClusterMetricsExtensionSpec.cs:line 88
[Node #4(node-4)]   at Xunit.Sdk.TestInvoker`1.<>c__DisplayClass48_1.<<InvokeTestMethodAsync>b__1>d.MoveNext() in C:\Dev\xunit\xunit\src\xunit.execution\Sdk\Frameworks\Runners\TestInvoker.cs:line 264
[Node #4(node-4)]--- End of stack trace from previous location where exception was thrown ---
[Node #4(node-4)]   at Xunit.Sdk.ExecutionTimer.AggregateAsync(Func`1 asyncAction) in C:\Dev\xunit\xunit\src\xunit.execution\Sdk\Frameworks\ExecutionTimer.cs:line 48
[Node #4(node-4)]   at Xunit.Sdk.ExceptionAggregator.RunAsync(Func`1 code) in C:\Dev\xunit\xunit\src\xunit.core\Sdk\ExceptionAggregator.cs:line 90

But before this failure, there is a weird error:

[Node #1(node-1)][ERROR][1/25/2020 1:28:25 PM][Thread 0018][akka://ClusterMetricsEnabledSpec/user/controller/barriers] [[akka://ClusterMetricsEnabledSpec/user/controller/%5B%3A%3A1%5D%3A1782%3A1782-server5#1634863782]] tried to enter 'node-2-started' while we were waiting for 'node-1-started'
[Node #1(node-1)]Cause: Akka.Remote.TestKit.BarrierCoordinator+WrongBarrierException: [[akka://ClusterMetricsEnabledSpec/user/controller/%5B%3A%3A1%5D%3A1782%3A1782-server5#1634863782]] tried to enter 'node-2-started' while we were waiting for 'node-1-started'
[Node #1(node-1)]   at Akka.Remote.TestKit.BarrierCoordinator.<>c__DisplayClass14_6.<InitFSM>b__15(EnterBarrier barrier) in D:\a\1\s\src\core\Akka.Remote.TestKit\BarrierCoordinator.cs:line 576
[Node #1(node-1)]   at Akka.Case.With[TMessage](Action`1 action) in D:\a\1\s\src\core\Akka\PatternMatch.cs:line 107
[Node #1(node-1)]   at Akka.Remote.TestKit.BarrierCoordinator.<InitFSM>b__14_2(Event`1 event) in D:\a\1\s\src\core\Akka.Remote.TestKit\BarrierCoordinator.cs:line 573
[Node #1(node-1)]   at Akka.Actor.FSM`2.ProcessEvent(Event`1 fsmEvent, Object source) in D:\a\1\s\src\core\Akka\Actor\FSM.cs:line 1210
[Node #1(node-1)]   at Akka.Actor.FSM`2.Receive(Object message) in D:\a\1\s\src\core\Akka\Actor\FSM.cs:line 1115
[Node #1(node-1)]   at Akka.Actor.ActorBase.AroundReceive(Receive receive, Object message) in D:\a\1\s\src\core\Akka\Actor\ActorBase.cs:line 158
[Node #1(node-1)]   at Akka.Actor.ActorCell.ReceiveMessage(Object message) in D:\a\1\s\src\core\Akka\Actor\ActorCell.DefaultMessages.cs:line 177
[Node #1(node-1)]   at Akka.Actor.ActorCell.Invoke(Envelope envelope) in D:\a\1\s\src\core\Akka\Actor\ActorCell.DefaultMessages.cs:line 83

So how is it possible that one node is entering next barrier while others are waiting for the first one?
Here is a AwaitClusterUp code in our test suite that contains this barriers:

public void AwaitClusterUp(params RoleName[] roles)
{
	// make sure that the node-to-join is started before other join
	RunOn(StartClusterNode, roles.First());

	EnterBarrier(roles.First().Name + "-started");
	if (roles.Skip(1).Contains(Myself)) Cluster.Join(GetAddress(roles.First()));

	if (roles.Contains(Myself))
	{
		AwaitMembersUp(roles.Length);
	}
	EnterBarrier(roles.Select(r => r.Name).Aggregate((a, b) => a + "-" + b) + "-joined");
}

Do you have an idea why this could happen?

Also, I would not say that this particular multi node test is very useful - it checks that extension is working at all, while other tests are also using this extension (so checking it is working), so I could skip the test. But just not like the idea that something is not working and I do not understand why. But not sure that this is related to Akka.Cluster.Metrics extension at all.

@Aaronontheweb
Copy link
Member

Do you have an idea why this could happen?

lol.... I think I do. A fun but common distributed programming bug. That code assumes that the roles collection has all nodes stored in the same order.... There's no guarantee that this ordering is consistent across all nodes, as far as I can remember. Let me look into this.

Copy link
Member

@Aaronontheweb Aaronontheweb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found a minor bug with the way this spec was written - not bug with the MNTR itself, just the way you were encoding the NodeList on this PR using a HashSet<T>. The built-in List<T> for Roles will solve this specific issue.

public readonly RoleName Node4;
public readonly RoleName Node5;

public IImmutableSet<RoleName> NodeList => ImmutableHashSet.Create(Node1, Node2, Node3, Node4, Node5);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, this is your bug.... The hashset may not list these in a consistent order.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh.... Exactly... They are unordered here... Missed this... Thanks!!

@@ -93,7 +91,7 @@ private async Task Should_collect_and_publish_metrics_and_gossip_them_around_the
{
await AwaitAssertAsync(() =>
{
AwaitClusterUp(roles: _config.NodeList.ToArray());
AwaitClusterUp(Roles.ToArray());
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Aaronontheweb Could you give me a hint for the future: what is the difference here? Isn't Roles just same list of roles stored internally?
I see that I am using this Roles in another spec that passes without issues, and maybe this will work here too - but why?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Roles is something that gets populated automatically by the MultiNodeSpec base class - the content of the list is ordered and populated by the base classes themselves. The issue was that the HashSet<T> you were using could change the roles slightly since I think the HashCodes were different on a process by process basis.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, or course, set does not guarantee same ordering... Great catch, @Aaronontheweb , thanks a lot!

@Aaronontheweb
Copy link
Member

@IgorFedchenko
Copy link
Contributor Author

@IgorFedchenko looks like we need API Approvals again here: https://dev.azure.com/dotnet/Akka.NET/_build/results?buildId=27752&view=ms.vss-test-web.build-test-results-tab&runId=1085134&resultId=100383&paneView=debug - this is due to the changes in Akka.Remote.

Done

@Aaronontheweb Aaronontheweb merged commit f068b09 into akkadotnet:dev Jan 27, 2020
@IgorFedchenko IgorFedchenko deleted the cluster-metrics branch January 27, 2020 20:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement Akka.Cluster.Metrics NuGet module
2 participants