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

Display Grain interaction with WebGraphviz #344

Open
KSemenenko opened this issue Feb 11, 2022 · 20 comments
Open

Display Grain interaction with WebGraphviz #344

KSemenenko opened this issue Feb 11, 2022 · 20 comments

Comments

@KSemenenko
Copy link

I think it would be good to make diagrams of the real interaction between Grains.

And to display it, use a tool like this:
http://www.webgraphviz.com
Screenshot 2022-02-11 at 12 56 27

I already have a filter that collects data, but I can't do it in the front-end :(
I will prepare a PR

@richorama
Copy link
Member

This looks like a great idea. It would be awesome to overlay call counts, exceptions, latency etc..

@richorama
Copy link
Member

something like the map you get in Application Insights
image

@KSemenenko
Copy link
Author

yes like this

@SebastianStehle
Copy link
Contributor

This would be amazing

@KSemenenko
Copy link
Author

I made a PR, following @ReubenBond advice I use RequestContext and store the call stack in it.
I think there are two parts to the task.

  1. Collect the call history, which I hope I did.
  2. Convert this data to some format for display
  3. A UI that will display this.

I would be grateful for any comments, as well as advice on how to properly write the tests.

I've seen a lot of test projects, but I don't understand how it all works yet.
I will study this question

@KSemenenko
Copy link
Author

you can check some results here: http://www.webgraphviz.com

digraph finite_state_machine {
rankdir=LR;
ratio = fill;
node [style=filled];
    IMembershipTable -> IMembershipTable_self [ label = "ReadAll", color="0.650 0.700 0.700" ];
    IMembershipTable -> IMembershipTable_self [ label = "InsertRow", color="0.650 0.700 0.700" ];
    IMembershipTable -> IMembershipTable_self [ label = "UpdateRow", color="0.650 0.700 0.700" ];
    IDeploymentLoadPublisher -> IDeploymentLoadPublisher_self [ label = "UpdateRuntimeStatistics", color="0.650 0.700 0.700" ];
    IClusterTypeManager -> IClusterTypeManager_self [ label = "GetClusterGrainTypeResolver", color="0.650 0.700 0.700" ];
    IClusterTypeManager -> IClusterTypeManager_self [ label = "GetImplicitStreamSubscriberTable", color="0.650 0.700 0.700" ];
    ITestHooks -> ITestHooks_self [ label = "GetApproximateSiloStatuses", color="0.650 0.700 0.700" ];
    IUserGrain -> IUserGrain_self [ label = "ExistsAsync", color="0.650 0.700 0.700" ];
    IUserGrain -> ISessionGrain [ label = "RegisterAsync", color="0.650 0.700 0.700" ];
    IUserGrain -> IWordsListGrain [ label = "CreateListAsync", color="0.650 0.700 0.700" ];
    IUserGrain -> IUserGrain_self [ label = "CreateListAsync", color="0.650 0.700 0.700" ];
    IUserGrain -> IWordsListGrain [ label = "GetListAsync", color="0.650 0.700 0.700" ];
    IUserGrain -> IUserGrain_self [ label = "GetListAsync", color="0.650 0.700 0.700" ];
    IUserGrain -> ITrainingGrain [ label = "StartTrainingAsync", color="0.650 0.700 0.700" ];
    IUserGrain -> IUserGrain_self [ label = "StartTrainingAsync", color="0.650 0.700 0.700" ];
    IUserGrain -> ITrainingGrain [ label = "UpdateTrainingStatusAsync", color="0.650 0.700 0.700" ];
    IUserGrain -> IUserGrain_self [ label = "UpdateTrainingStatusAsync", color="0.650 0.700 0.700" ];
    IUserGrain -> IUserGrain_self [ label = "RegisterAsync", color="0.650 0.700 0.700" ];
    IUserGrain -> ITrainingGrain [ label = "FinishTrainingAsync", color="0.650 0.700 0.700" ];
    IUserGrain -> IUserGrain_self [ label = "FinishTrainingAsync", color="0.650 0.700 0.700" ];
    IUserGrain -> IUserGrain_self [ label = "GetTrainingAsync", color="0.650 0.700 0.700" ];
    IUserGrain -> ITrainingGrain [ label = "GetTrainingAsync", color="0.650 0.700 0.700" ];
    IUserGrain -> IUserGrain_self [ label = "CancelTrainingAsync", color="0.650 0.700 0.700" ];
    IUserGrain -> ITrainingGrain [ label = "CancelTrainingAsync", color="0.650 0.700 0.700" ];
    IUserGrain -> IUserGrain_self [ label = "PauseSessionAsync", color="0.650 0.700 0.700" ];
    IUserGrain -> IUserGrain_self [ label = "ResumeSessionAsync", color="0.650 0.700 0.700" ];
    IUserGrain -> ISessionGrain [ label = "LogoutAsync", color="0.650 0.700 0.700" ];
    IUserGrain -> IUserGrain_self [ label = "LogoutAsync", color="0.650 0.700 0.700" ];
    IUserGrain -> ISessionGrain [ label = "LoginAsync", color="0.650 0.700 0.700" ];
    IUserGrain -> IUserGrain_self [ label = "LoginAsync", color="0.650 0.700 0.700" ];
    ISessionGrain -> ISessionGrain_self [ label = "CreateSessionAsync", color="0.650 0.700 0.700" ];
    ISessionGrain -> ISessionGrain_self [ label = "ValidateAsync", color="0.650 0.700 0.700" ];
    ISessionGrain -> ISessionGrain_self [ label = "GetSessionAsync", color="0.650 0.700 0.700" ];
    ISessionGrain -> ISessionGrain_self [ label = "CloseSessionAsync", color="0.650 0.700 0.700" ];
    IWordsListGrain -> IWordsListGrain_self [ label = "CreateListAsync", color="0.650 0.700 0.700" ];
    IWordsListGrain -> IWordsListGrain_self [ label = "GetListAsync", color="0.650 0.700 0.700" ];
    IWordsListGrain -> IWordsListGrain_self [ label = "IncreaseWordListCounterAsync", color="0.650 0.700 0.700" ];
    IWordsListGrain -> IWordsListGrain_self [ label = "IncreaseWordCounterAsync", color="0.650 0.700 0.700" ];
    ITrainingGrain -> IWordsListGrain [ label = "StartTrainingAsync", color="0.650 0.700 0.700" ];
    ITrainingGrain -> ITrainingGrain_self [ label = "StartTrainingAsync", color="0.650 0.700 0.700" ];
    ITrainingGrain -> IWordsListGrain [ label = "UpdateTrainingStatusAsync", color="0.650 0.700 0.700" ];
    ITrainingGrain -> ITrainingGrain_self [ label = "UpdateTrainingStatusAsync", color="0.650 0.700 0.700" ];
    ITrainingGrain -> IWordsListGrain [ label = "FinishTrainingAsync", color="0.650 0.700 0.700" ];
    ITrainingGrain -> ITrainingGrain_self [ label = "FinishTrainingAsync", color="0.650 0.700 0.700" ];
    ITrainingGrain -> ITrainingGrain_self [ label = "GetTrainingAsync", color="0.650 0.700 0.700" ];
    ITrainingGrain -> ITrainingGrain_self [ label = "CancelTrainingAsync", color="0.650 0.700 0.700" ];

IMembershipTable [color="0.628 0.227 1.000"];
IDeploymentLoadPublisher [color="0.628 0.227 1.000"];
IClusterTypeManager [color="0.628 0.227 1.000"];
ITestHooks [color="0.628 0.227 1.000"];
IUserGrain [color="0.628 0.227 1.000"];
ISessionGrain [color="0.628 0.227 1.000"];
IWordsListGrain [color="0.628 0.227 1.000"];
ITrainingGrain [color="0.628 0.227 1.000"];
}

@KSemenenko
Copy link
Author

Screenshot 2022-02-11 at 21 59 30

@SebastianStehle
Copy link
Contributor

Hi, great results so far. But would it not be better to use a graph library where we have more control over the UI behavior and can add things like tooltips and so on?

Also some numbers would be great. Calls per second, error rate and so on.

@KSemenenko
Copy link
Author

https://github.com/cytoscape/cytoscape.js
https://github.com/d3/d3

There are a lot of libraries, let’s find the best one

@KSemenenko
Copy link
Author

But I’m not familiar with front end technologies, so I can’t understand which library is better for it

@SebastianStehle
Copy link
Contributor

I have worked with this one: https://github.com/visjs/vis-network

@koenbeuk
Copy link
Contributor

I guess with the dashboard in general but certainly with this proposed feature, this intermixes very well with open telemetry (or telemetry in general). The open PR builds something unique to orleans but the same information can be derived from open telemetry with a much lower overhead cost.

Perhaps instead of introducing something Orleans specific, we can instead consume OT data and use that in the visualization. Not sure yet how that would work, just wanted to have that thrown in here... Will look into this further...

@SebastianStehle
Copy link
Contributor

The open PR builds something unique to orleans but the same information can be derived from open telemetry with a much lower overhead cost.

Yes, could be. I think Open Telemetry already has some rate limiters built to reduce the number of single spans. If you can derive the needed information from these spans, it could work.

@KSemenenko
Copy link
Author

@SebastianStehle can you guid me please
I moved the logic to GrainProfilerFilter.

But I have a question about name formatting, because now we have two Invoke methods with different contexts.
And I would like to discuss the data we will be collecting and its formatting.
At the moment you're collecting Grain type, method and timing.

I need one more parameter that will indicate the type of the calling Grain.
What's the best way to add this? I've been thinking for a couple of days and decided to ask your advice so I don't break the functionality.
Also along the way, I thought it was good to do more than just keep track of all the "queues" of requests.
but also, for example, to keep track of what a particular Grain is doing in a particular query. To get the whole request path.

For example, to add some attribute, so we could track a specific query to understand that why and where my performance degrades?

I see the base class as:

Grain
Method
Timing
Caller Grain (in the case when it is a chain of requests)
Probably some Id of the request chain.

@KSemenenko
Copy link
Author

What do you think about this method?

void Track(string trackId, double elapsedMs, Type grainType, [CallerMemberName] string methodName = null, bool failed = false);

When a new request starts, we put new trackId, if we understand that the request chain continues, we use the same trackId.
In this way we can get all the requests in the chain.
e.g. you start a registration in UserGrain, which creates a session, which also adds a userProfile,
and so on. We can see that all these actions are subordinate to one - the first query.

so it doesn't seem like there will be too many changes

@SebastianStehle
Copy link
Contributor

We just have to introduce a second delegate I think.

@KSemenenko
Copy link
Author

For me it looks like they will do the same thing

@SebastianStehle
Copy link
Contributor

Yes, but if you introduce another parameter it would be a breaking change, isn't it?

@KSemenenko
Copy link
Author

Yes, but I just don't know who uses this method other than the filter.
If there are more consumers, then of course you have to make a new method, and then another storage for the record with traceId.

@SebastianStehle
Copy link
Contributor

Sorry, I thought you talk about the formatter delegate. I should have read it more carefully.

I think right now we aggregate the method calls per Grain+Method. If we also need the caller then we have to group by this as well.

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

No branches or pull requests

4 participants