-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Profiling] Add infra to run port forwarding tunnels
Dive, RGP and Renderdoc expect SSH port forwarding processes to be present when the game starts. Right now, these processes are started from YetiDebugTransport. This code is only run when a debugger is attached. However, it is not a good idea to profile with debugger attached, as that deteriorates performance. Hence, the port forwarding processes should be started whenever the game is started, whether a debugger is attached or not. This CL is prep work for that. It adds - SshTunnelProcess: Wraps an SSH port forwarding process. - GameLifetimeWatcher: Triggers a callback when the game shuts down. When the game shuts down, the tunnels are shut down as well. SshTunnelManager is the overall manager class that coordinates spinning up and shutting down tunnels. Also moves the ManagedProcess.OnExit call to after logging, as the handlers might dispose the process, so ExitCode isn't valid anymore. This CL does not affect current behavior, the new code is not yet hooked up. GitOrigin-RevId: ca4627d6473daf5a5906f4b77b37d8a89219066d
- Loading branch information
1 parent
8e5de8d
commit 74520cb
Showing
11 changed files
with
1,037 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
// Copyright 2022 Google LLC | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
using System; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using GgpGrpc.Models; | ||
using Microsoft.VisualStudio.Threading; | ||
using NSubstitute; | ||
using NUnit.Framework; | ||
using YetiVSI.GameLaunch; | ||
using YetiVSI.Profiling; | ||
|
||
namespace YetiVSI.Test.Profiling | ||
{ | ||
class GameLifetimeWatcherTests | ||
{ | ||
static readonly TimeSpan _startupTimeout = TimeSpan.FromSeconds(60); | ||
|
||
static readonly GgpGrpc.Models.GameLaunch _launchPrepping = new GgpGrpc.Models.GameLaunch | ||
{ GameLaunchState = GameLaunchState.ReadyToPlay, GameletName = "gamelet" }; | ||
|
||
static readonly GgpGrpc.Models.GameLaunch _launchRunning = new GgpGrpc.Models.GameLaunch | ||
{ GameLaunchState = GameLaunchState.RunningGame, GameletName = "gamelet" }; | ||
|
||
static readonly GgpGrpc.Models.GameLaunch _launchEnded = new GgpGrpc.Models.GameLaunch | ||
{ | ||
GameLaunchState = GameLaunchState.GameLaunchEnded, | ||
GameLaunchEnded = new GameLaunchEnded(EndReason.ExitedByUser), | ||
GameletName = "gamelet" | ||
}; | ||
|
||
IVsiGameLaunch _launch; | ||
AsyncManualResetEvent _onDoneCalled; | ||
GameLifetimeWatcher.DoneHandler _onDone; | ||
GameLifetimeWatcher _watcher; | ||
|
||
[SetUp] | ||
public void SetUp() | ||
{ | ||
_launch = Substitute.For<IVsiGameLaunch>(); | ||
_onDoneCalled = new AsyncManualResetEvent(); | ||
_watcher = new GameLifetimeWatcher(); | ||
_onDone = Substitute.For<GameLifetimeWatcher.DoneHandler>(); | ||
_onDone.When(x => x.Invoke(_watcher, Arg.Any<bool>(), Arg.Any<string>())) | ||
.Do(x => _onDoneCalled.Set()); | ||
} | ||
|
||
[Test] | ||
public void StopWithoutStart() | ||
{ | ||
_watcher.Stop(); | ||
} | ||
|
||
[Test] | ||
public void StartStartThrows() | ||
{ | ||
_launch.GetLaunchStateAsync(null).Returns(Task.FromResult(_launchPrepping)); | ||
_watcher.Start(_launch, _startupTimeout, _onDone); | ||
Assert.Throws(typeof(InvalidOperationException), | ||
() => _watcher.Start(_launch, _startupTimeout, _onDone)); | ||
} | ||
|
||
[Test] | ||
public void StartStopNotLaunched() | ||
{ | ||
_launch.GetLaunchStateAsync(null).Returns(Task.FromResult(_launchPrepping)); | ||
|
||
_watcher.Start(_launch, _startupTimeout, _onDone); | ||
_watcher.Stop(); | ||
|
||
_onDone.DidNotReceive().Invoke(_watcher, Arg.Any<bool>(), Arg.Any<string>()); | ||
} | ||
|
||
[Test] | ||
public void DoubleStop() | ||
{ | ||
_launch.GetLaunchStateAsync(null).Returns(Task.FromResult(_launchPrepping)); | ||
_watcher.Start(_launch, _startupTimeout, _onDone); | ||
|
||
_watcher.Stop(); | ||
_watcher.Stop(); | ||
} | ||
|
||
[Test] | ||
public async Task StartLaunchedSucceedsAsync() | ||
{ | ||
_launch.GetLaunchStateAsync(null).Returns(Task.FromResult(_launchEnded)); | ||
|
||
_watcher.Start(_launch, _startupTimeout, _onDone); | ||
|
||
await _onDoneCalled.WaitAsync().WithTimeout(TimeSpan.FromSeconds(5)); | ||
_onDone.Received().Invoke(_watcher, true, null); | ||
} | ||
|
||
[Test] | ||
public async Task StartupTimesOutWhenNotRunningAsync() | ||
{ | ||
_launch.GetLaunchStateAsync(null).Returns(Task.FromResult(_launchPrepping)); | ||
|
||
_watcher.Start(_launch, TimeSpan.Zero, _onDone); | ||
|
||
await _onDoneCalled.WaitAsync().WithTimeout(TimeSpan.FromSeconds(5)); | ||
_onDone.Received().Invoke(_watcher, false, | ||
Arg.Do<string>(errorMsg => | ||
StringAssert.Contains( | ||
"Timed out", errorMsg))); | ||
} | ||
|
||
[Test] | ||
public void StartupDoesNotTimeOutWhenRunning() | ||
{ | ||
_launch.GetLaunchStateAsync(null).Returns(Task.FromResult(_launchRunning)); | ||
|
||
_watcher.Start(_launch, TimeSpan.Zero, _onDone); | ||
|
||
// How do you test that something will never happen? | ||
Thread.Sleep(10); | ||
Assert.False(_onDoneCalled.IsSet); | ||
} | ||
} | ||
} |
Oops, something went wrong.