-
Notifications
You must be signed in to change notification settings - Fork 8.1k
Experimental feature: Implicit remoting batching perf improvement #8038
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
Experimental feature: Implicit remoting batching perf improvement #8038
Conversation
daxian-dbw
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two quick comments from a glance. Will look more closely later.
|
|
||
| // Experimental: | ||
| // Check for implicit remoting commands that can be batched, and execute as batched if able. | ||
| if (ExperimentalFeature.EnabledExperimentalFeatureNames.TryGetValue("PSImplicitRemotingBatching", out string actualValue)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use ExperimentalFeature.IsEnabled("PSImplicitRemotingBatching")
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we collect all constants like "PSImplicitRemotingBatching" for all code base in one place?
| new ExperimentalFeature(name: "PSImplicitRemotingBatching", | ||
| description: "Batch implicit remoting proxy commands to improve performance", | ||
| source: "EngineSource", | ||
| isEnabled: true) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use isEnabled: false here. When the feature is enabled in powershell.config.json, the feature will be shown as Enabled.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will do.
| return AstVisitAction.Continue; | ||
| } | ||
|
|
||
| /* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove commented block.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this is experimental, I want to leave it in for now. We may need some kind of script block restriction.
| foreach (var cmdInfo in cmdInfoList) | ||
| { | ||
| // Check for allowed command | ||
| var aliasInfo = cmdInfo as AliasInfo; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about the following:
string cmdName = (cmdInfo is AliasInfo aliasInfo) ? aliasInfo.ReferencedCommand.Name : cmdInfo.Name;There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed. Thanks.
| continue; | ||
| } | ||
|
|
||
| if (cmdInfo.Module == null || string.IsNullOrEmpty(cmdInfo.ModuleName)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
== null and string.IsNullOrEmpty check seems redundant.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually Module and ModuleName properties seem redundant to me. I am checking both properties to be safe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, misread it as ModuleName twice.
| break; | ||
| } | ||
|
|
||
| var privateData = cmdInfo.Module.PrivateData as System.Collections.Hashtable; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar pattern as above:
if (cmdInfo.Module.PrivateData is System.Collections.Hashtable privateData)
{
success = false;
break;
}There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed.
| if (psSession == null || ps.Streams.Error.Count > 0) | ||
| { | ||
| // Cannot continue | ||
| throw new PSInvalidOperationException(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need a better exception message here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will add.
|
@PaulHigin Can we add some tests here as well? |
|
Since this is a perf improvement for special command lines, I don't see a good way to add reliable tests. |
|
Actually, offline we decided that adding a test hook to test the TryRunAsImplicitBatch() method should be sufficient to test the code path. I'll work on adding that. |
|
Does this avoid the intermediate serialization step entirely? If so, then it’s more than a perf improvement, it also enables things that are currently problematic. In many cases a dead serialized property bag isn’t valid pipeline input, but a live object is. So Get | Set scenarios that didn’t work with implicit remoting might start to work. |
iSazonov
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will the feature work if remote point is Windows PowerShell or previous PowerShell Core version?
| { | ||
| psSessionId = new Guid(privateData["ImplicitSessionId"] as string); | ||
| } | ||
| else if (!psSessionId.ToString().Equals(privateData["ImplicitSessionId"] as string, StringComparison.OrdinalIgnoreCase)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we replace the string constant with nameof()?
| // If this is just a single command, there is no point in batching it | ||
| if (checker.Commands.Count < 2) | ||
| { | ||
| throw new ImplicitRemotingBatchingNotSupportedException(ConsoleHostStrings.ImplicitBatchSingleCommand); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we really need the exception? Can we return false?
More general question: why we throw exceptions in "Try" method (TryRunAsImplicitBatch)? Why we have exceptionThrown out parameter?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At one point I was thinking of emitting compatibility information through tracing, and do that with exceptions. But since this is such a rare condition it doesn't seem worth it. I'll remove them as needed.
| success = false; | ||
| break; | ||
| } | ||
| if (!privateData.ContainsKey("ImplicitSessionId")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could use TryGet().
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually hash table doesn't support TryGet().
| } | ||
| if (psSessionId == Guid.Empty) | ||
| { | ||
| psSessionId = new Guid(privateData["ImplicitSessionId"] as string); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why ImplicitSessionId is not Guid type?
|
@iSazonov Yes, batching will work for any remote target that supports implicit remoting. |
| private const int MaxModuleNestingDepth = 10; | ||
|
|
||
| /// <summary> | ||
| /// True when an implicit remoting module is loaded |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment should start with "Gets and sets" and has final dot.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will do.
| } | ||
|
|
||
| /// <summary> | ||
| /// TestImplicitRemotingBatching |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add more useful summary and final dot.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will do.
test/powershell/engine/Remoting/ImplicitRemotingBatching.Tests.ps1
Outdated
Show resolved
Hide resolved
|
@mattpwhite , Whoops missed this comment. Yes, batching the pipeline means "live" objects are passed and implicit commands that didn't work with de-serialized objects will likely work as batched. |
|
@adityapatwardhan Can you update your review? |
| */ | ||
| new ExperimentalFeature(name: "PSImplicitRemotingBatching", | ||
| description: "Batch implicit remoting proxy commands to improve performance", | ||
| source: "EngineSource", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be EngineSource without quotes as it's a const string defined on line 22 of this file
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will do.
| }, | ||
| @{ | ||
| Name = 'Two implicit commands with Where-Object should be successfully batched' | ||
| CommandLine = 'Get-Process | Write-Output | Where-Object { $_.Name -like "*pwsh*" }' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about aliases like ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know what you mean. If you have a scenario then let me know. But I don't intend to cover all scenarios on this initial check in. This is an experimental feature that will evolve over time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe you have code above to see if the command is an alias and convert it to the cmdlet. So you should just add a test variation where:
CommandLine = 'Get-Process | Write-Output | ? { $_.Name -like "*pwsh*" }'There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we'll use the alias we should add a comment to exclude a unneeded change later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I understand now. Sure, I can add that test case.
test/powershell/engine/Remoting/ImplicitRemotingBatching.Tests.ps1
Outdated
Show resolved
Hide resolved
test/powershell/engine/Remoting/ImplicitRemotingBatching.Tests.ps1
Outdated
Show resolved
Hide resolved
test/powershell/engine/Remoting/ImplicitRemotingBatching.Tests.ps1
Outdated
Show resolved
Hide resolved
|
@adityapatwardhan Can you please merge these approved changes? We have users who would like to try out the experimental feature. |
|
@adityapatwardhan Thanks! |
|
It will be nice to share config file where the feature is turned on. |
|
@SteveL-MSFT Thanks! I meant that attaching a sample file to the PR will help users. |
|
@iSazonov added example to the PR description |
|
Something is wrong here. PS C:\temp\PowerShell-6.2.0-preview.859-win-x64> Get-ExperimentalFeature
Name Enabled Source Description
---- ------- ------ -----------
PSImplicitRemotingBatching True PSEngine Batch implicit remoting proxy comman...
PS C:\temp\PowerShell-6.2.0-preview.859-win-x64> Import-PSSession $s -CommandName get-Process -Prefix r -verbose -AllowClobber
ModuleType Version Name ExportedCommands
---------- ------- ---- ----------------
Script 1.0 tmp_a4ox23yj.min Get-rProcess
PS C:\temp\PowerShell-6.2.0-preview.859-win-x64> get-RProcess |where Name -like *ss
PS C:\temp\PowerShell-6.2.0-preview.859-win-x64> $test = get-RProcess
PS C:\temp\PowerShell-6.2.0-preview.859-win-x64> $test |where Name -like *ss
NPM(K) PM(M) WS(M) CPU(s) Id SI ProcessName
------ ----- ----- ------ -- -- -----------
25 1.90 1.86 7.20 752 0 csrss
32 11.32 5.30 72.31 868 1 csrss
85 19.81 24.61 935.81 104 0 lsass
3 0.57 0.62 0.27 416 0 smss
PS C:\temp\PowerShell-6.2.0-preview.859-win-x64> |
|
You need to enable the feature via the powershell.config.json file. I am not sure about applying a prefix to the proxy command, it seems like that should be Ok. But the other two involving assignment and piping from a variable fail the "simple pipeline" checker used to determine if batching is allowed. I am adding a verbose output that reports the checker errors so it is easier to determine if the command line is batched or not, and make it easier to address these scenarios. |
|
Whoops, left out the powershell.config.json example: |
|
Sorry that the formatting got screwed up but if you look at my post, the very first command confirms that the feature is enabled. |
|
@jpsnover I reformatted your comment by wrapping in: Looks like PS /Users/steve/repos/PowerShell> Get-rProcess | where name -like "*pwsh*"
NPM(K) PM(M) WS(M) CPU(s) Id SI ProcessName
------ ----- ----- ------ -- -- -----------
50 55.58 84.09 32.27 3728 0 pwsh
63 49.50 96.32 2.48 10152 2 pwsh
61 46.90 92.68 2.36 14056 2 pwsh
|
|
Turning on verbose output helps to see what is going on: This was batched successfully, however, Get-rProcess proxy command is not on the target machine. I forgot to merge error stream so this error is not showing. It is easy to fix the error stream merge, but I have to think about how to convert Get-rProcess proxy wrapper command to the name of the actual wrapped command. Neither of these are pipelines that round trip data and don't need to be batched. However, The last two lines are not being batched because they do not pass the simple pipeline check. They are more complex and require parsing the AST and re-building complex commands, but I think they should be supported. |
PR Summary
Implicit remoting is a feature in PowerShell where commands intended to be run on remote machines via Invoke-Command, are wrapped in proxy script commands. That way the remote command can be run by simply typing it on the command line, and the proxy script takes care of invoking it on the remote target.
The Import-PSSession cmdlet creates proxy command scripts for commands or module commands to be run on remote targets.
This is very convenient, but there can be performance problems with proxy commands. If the output of one proxy command is piped to another proxy command, then the data makes an unnecessary round trip to the client and back to the target machine. For example:
If there is a lot of data involved, or the data is complex (such as a Process object), then the round tripping of data can take up a significant amount of time.
This experimental feature examines a command typed in the shell, and if all the commands are implicit remoting proxy commands that form a simple pipeline, then the commands are batched together and invoked as a single remote pipeline.
Example:
As seen above, with the batching experimental feature enabled, all three implicit remoting proxy commands, Get-AllProcesses, Select-Custom, Group-Stuff, run in the remote session and the only data returned to the client is the result from the pipeline. This greatly decreases the amount of data sent back and forth between client and remote session, and also greatly reduces the amount of object serialization and de-serialization.
To enable this feature, create this file as
experimental.json{ "ExperimentalFeatures": [ "PSImplicitRemotingBatching" ] }Then start PowerShell with:
pwsh -settingsfile ./experimental.jsonPR Checklist
.h,.cpp,.cs,.ps1and.psm1files have the correct copyright headerWIP:to the beginning of the title and remove the prefix when the PR is ready.[feature]if the change is significant or affects feature tests