Skip to content

feat: Extract Metrics from Network Pipeline Stage #1583

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

Merged
merged 7 commits into from
Jan 19, 2022

Conversation

Rosme
Copy link
Contributor

@Rosme Rosme commented Jan 13, 2022

Move the data from pipeline stage to UnityTransport adapter and network metrics.

MTT-1855

Testing and Documentation

  • No tests have been added.
  • No documentation changes or additions were necessary.

@Rosme Rosme requested review from simon-lemay-unity and a team January 13, 2022 20:13
@simon-lemay-unity
Copy link
Contributor

simon-lemay-unity commented Jan 13, 2022

You probably want to reset the counters in the InitializeConnection method of the pipeline. Pipeline buffers either use uninitialized memory, or reuse the buffers from a previous connection. In both cases that could lead to erroneous metrics.



//TODO: Get the correct connection instead of m_ServerConnection
m_Driver.GetPipelineBuffers(m_LastSendPipeline, NetworkPipelineStageCollection.GetStageId(typeof(NetworkMetricsPipelineStage)),
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the reason for doing this only for the last pipeline sent to? There could have been sends on multiple pipelines between two Update calls.

Also for reference, every pipeline gets its own buffers. In UnityTransport there are three pipelines defined (m_UnreliableFragmentedPipeline, m_UnreliableSequencedFragmentedPipeline, and m_ReliableSequencedFragmentedPipeline). They'll each have their own network metrics context. So basically every connection will get three different metrics contexts.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, I was under the impression it was a single pipeline. That's good to know.

@Rosme Rosme changed the title First pass on moving data from pipeline stage to network metrics feat: Extract Metrics from Network Pipeline Stage Jan 19, 2022
@Rosme Rosme marked this pull request as ready for review January 19, 2022 16:59
{
var networkConnection = ParseClientId(transportClientId);

//Two pipelines, unreliable sequence and reliable fragmented
Copy link
Contributor

Choose a reason for hiding this comment

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

There are actually 3 pipelines now. I added another one for unreliable (non-sequenced) delivery. You'll also need the change from PR #1569 for it to work properly with the metrics pipeline stage. (I guess you could just apply the changes to this PR and I'll close the other one.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll apply the change. It makes sense. Will fetch the last develop with the third pipeline.

Copy link
Contributor

Choose a reason for hiding this comment

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

Comment needs updated (or maybe just removed?)

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't see that the change from #1569 has been applied to this branch. Without it, trying to access the metrics pipeline buffers from a development will fail.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, I would just remove the comment. Better to have this kind of thing in an enum than a comment.

@@ -517,13 +517,67 @@ private void Update()
{
;
}

#if MULTIPLAYER_TOOLS
if (NetworkManager.Singleton.IsServer)
Copy link
Contributor

Choose a reason for hiding this comment

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

Might be worth breaking into a named function to avoid overloading Update

{
var pipeline = i == 0 ? m_UnreliableSequencedPipeline : m_ReliableSequencedFragmentedPipeline;

#if ENABLE_UNITY_COLLECTIONS_CHECKS
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this an existing pattern? Definitely gets the job done, a little awkward though

var networkConnection = ParseClientId(transportClientId);

//Two pipelines, unreliable sequence and reliable fragmented
for (byte i = 0; i < 2; ++i)
Copy link
Contributor

Choose a reason for hiding this comment

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

Should 2 be a const instead of leaving the comment?

If there's only 2 pipelines, should this just be two explicit function calls and pass in the pipeline as a parameter? The for loop seems kind of unnecessarily complex control flow since we have a very small and const-size set of pipelines that aren't actually in a collection that we can iterate over.

#endif
{
//Don't need to dispose of the buffers, they are filled with data pointers.
m_Driver.GetPipelineBuffers(pipeline, NetworkPipelineStageCollection.GetStageId(typeof(NetworkMetricsPipelineStage)),
Copy link
Contributor

Choose a reason for hiding this comment

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

nit, suggest breaking into 1 line per parameter for better readability

#if ENABLE_UNITY_COLLECTIONS_CHECKS
catch (InvalidOperationException e)
{
//Can ignore, may happen if a pipeline is not used
Copy link
Contributor

Choose a reason for hiding this comment

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

Concerned about this - we ignore the exception when ENABLE_UNITY_COLLECTION_CHECKS is enabled, but when it's not enabled it will throw right? What happens when it throws?

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 gets throwned only if this is defined.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ohh I see. That's worth a comment I think to avoid others getting confused the same (if you keep the try/catch at least, I know simon has called it into question in another comment)

var pipeline = i == 0 ? m_UnreliableSequencedPipeline : m_ReliableSequencedFragmentedPipeline;

#if ENABLE_UNITY_COLLECTIONS_CHECKS
try
Copy link
Contributor

Choose a reason for hiding this comment

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

Is the try-catch to ignore the exception really necessary? It seems to me the only way to raise InvalidOperationException from GetPipelineBuffers is if the connection is invalid. In the rest of the adapter we usually don't bother catching that one and assume our connections are always valid.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In my early test, it was. I have changed some stuff around, so might not be anymore. Will test.

{
//Don't need to dispose of the buffers, they are filled with data pointers.
m_Driver.GetPipelineBuffers(pipeline, NetworkPipelineStageCollection.GetStageId(typeof(NetworkMetricsPipelineStage)),
networkConnection, out var readProcessingBuffer, out var writeProcessingBuffer, out var sharedBuffer);
Copy link
Contributor

Choose a reason for hiding this comment

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

Minor, but you can explicitly ignore/discard some out parameters by passing out _.

Copy link
Contributor

@0xFA11 0xFA11 left a comment

Choose a reason for hiding this comment

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

generally looks good to me — I'd like to defer to Beck & Simon.

@@ -161,6 +161,7 @@ public struct ConnectionAddressData
private NetworkPipeline m_UnreliableFragmentedPipeline;
private NetworkPipeline m_UnreliableSequencedFragmentedPipeline;
private NetworkPipeline m_ReliableSequencedFragmentedPipeline;
private const byte k_PipelineCount = 3; //Represented the number of existing pipeline
Copy link
Contributor

Choose a reason for hiding this comment

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

Any particular reason to use byte for this constant?

var networkConnection = ParseClientId(transportClientId);

//Two pipelines, unreliable sequence and reliable fragmented
for (byte i = 0; i < k_PipelineCount; ++i)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why are you using byte as the type of the loop variable? Most likely the machine doesn't have registers that small in any case, and this will just result in greater risk of overflow without reducing resource usage 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll defer to @simon-lemay-unity for this. It does represent the small value well enough and I'm fine with it.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't really have any strong opinion on the matter. Usually it's advised to use register-sized variables for loop counters since that's what will be fastest. But here it's a loop with 3 iterations, so unlikely to have any effect. If only for consistency with other loops in the code I'd go with an int.

Copy link
Contributor

Choose a reason for hiding this comment

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

Still not sure I understand why we have a loop in the first place. Why isn't it just:

ProcessPipeline(m_UnreliableFragmentedPipeline);
ProcessPipeline(m_UnreliableSequencedFragmentedPipeline);
ProcessPipeline(m_ReliableSequencedFragmentedPipeline);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Because I forgot your comment and this makes a lot more sense.

Comment on lines 625 to 638
private NetworkPipeline GetPipelineFromIndex(byte index)
{
switch (index)
{
case 0:
return m_UnreliableFragmentedPipeline;
case 1:
return m_UnreliableSequencedFragmentedPipeline;
case 2:
return m_ReliableSequencedFragmentedPipeline;
default:
throw new InvalidOperationException("Invalid Pipeline Index");
}
}
Copy link
Contributor

@ian-m-unity ian-m-unity Jan 19, 2022

Choose a reason for hiding this comment

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

I think this would be better using an enum, rather than cases with the raw integer literals. I added a comment with an enum that could be used above on the k_PipelineCount constant.

@@ -161,6 +161,7 @@ public struct ConnectionAddressData
private NetworkPipeline m_UnreliableFragmentedPipeline;
private NetworkPipeline m_UnreliableSequencedFragmentedPipeline;
private NetworkPipeline m_ReliableSequencedFragmentedPipeline;
private const byte k_PipelineCount = 3; //Represented the number of existing pipeline
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it would be best to have an enum for this, rather than referring to these stages using raw numbers in the switch statement later. Something like:

enum PipelineType
{
  UnreliableFragmented,
  UnreliableSequenceFragmented,
  ReliableSequenceFragmented,
  Count,
}

This would replace count with (int)PipelineType.Count

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure. I just wanted something simple in case things pipeline would change but you're right.

Comment on lines 582 to 586
//Connection 0 when host is self and bypass the transport so would be invalid, skip it.
if (ngoConnectionId > 0 || (ngoConnectionId == 0 && !NetworkManager.Singleton.IsHost))
{
ExtractNetworkMetricsForClient(NetworkManager.Singleton.ClientIdToTransportId(ngoConnectionId));
}
Copy link
Contributor

@ian-m-unity ian-m-unity Jan 19, 2022

Choose a reason for hiding this comment

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

Rather than explaining the logic in a comment, you could write something like the following:

var isConnectionToSelf = ngoConnectionId == 0 && NetworkManager.Singleton.IsHost;
if (isConnectionToSelf)
{
  // Connection 0 when host is self bypasses the transport, so skip it.
 continue;
}
ExtractNetworkMetricsForClient(NetworkManager.Singleton.ClientIdToTransportId(ngoConnectionId));

Copy link
Contributor

@ian-m-unity ian-m-unity Jan 19, 2022

Choose a reason for hiding this comment

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

This also reduces the complexity of the condition a bit.

Comment on lines +410 to +417
public void TrackPacketSent(uint packetCount)
{
}

public void TrackPacketReceived(uint packetCount)
{
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Is this waiting for some changes in the tools package?

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 will be part of the proper JIRA Ticket that dispatch it. With tests and all. It's there just to make sure we have functions.

@Rosme Rosme merged commit e2dcebd into develop Jan 19, 2022
@Rosme Rosme deleted the feature/MTT-1855_metric-translation-layer branch January 19, 2022 19:20
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.

5 participants