Reputation: 470
I need to get the following properties for every process as quickly as possible, ideally 5 seconds, maximum 10 seconds: ID, Name, Description, Path, Company, Username, Session ID, StartTime, Memory, CPU (percentage, not time)
To get this data, I put together the following snippet which (I think) is functionally perfect:
$ProcessCPU = Get-WmiObject Win32_PerfFormattedData_PerfProc_Process | Select-Object IDProcess, PercentProcessorTime
$Processes = Get-Process -IncludeUserName |
Select-Object `
@{Name='Id';Expression={[int]$_.Id}},
@{Name='Name';Expression={[string]$_.Name}},
@{Name='Description';Expression={[string]$_.Description}},
@{Name='Path';Expression={[string]$_.Path}},
@{Name='Company';Expression={[string]$_.Company}},
@{Name='Username';Expression={[string]$_.UserName}},
@{Name='SessionId';Expression={[string]$_.SessionId}},
@{Name='StartTime';Expression={[string](($_.StartTime).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))}},
@{Name='MemoryMB';Expression={[int]([math]::Round($_.WorkingSet/1MB,2))}},
@{Name='CPUPercent';Expression={
[int]($ProcessCPU | ?{'IDProcess' -eq $_.Id}).PercentProcessorTime
}}
The issue is that its taking 18-22 seconds to execute, caused by this line (which adds about 16 seconds):
@{Name='CPUPercent';Expression={
[int]($ProcessCPU | ?{'IDProcess' -eq $_.Id}).PercentProcessorTime
}}
PS C:\Windows\system32> Measure-Command -Expression {
$ProcessCPU = Get-WmiObject Win32_PerfFormattedData_PerfProc_Process | Select-Object IDProcess, PercentProcessorTime
$Processes = Get-Process -IncludeUserName |
Select-Object `
@{Name='Id';Expression={[int]$_.Id}},
@{Name='Name';Expression={[string]$_.Name}},
@{Name='Description';Expression={[string]$_.Description}},
@{Name='Path';Expression={[string]$_.Path}},
@{Name='Company';Expression={[string]$_.Company}},
@{Name='Username';Expression={[string]$_.UserName}},
@{Name='SessionId';Expression={[string]$_.SessionId}},
@{Name='StartTime';Expression={[string](($_.StartTime).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))}},
@{Name='MemoryMB';Expression={[int]([math]::Round($_.WorkingSet/1MB,2))}},
@{Name='CPUPercent';Expression={
[int]($ProcessCPU | ?{'IDProcess' -eq $_.Id}).PercentProcessorTime
}}
}
TotalSeconds : 19.061206
When I remove the slow property expression noted above and keep the WMI query, execution takes about 4.5 seconds:
Measure-Command -Expression {
$ProcessCPU = Get-WmiObject Win32_PerfFormattedData_PerfProc_Process | Select-Object IDProcess, PercentProcessorTime
$Processes = Get-Process -IncludeUserName |
Select-Object `
@{Name='Id';Expression={[int]$_.Id}},
@{Name='Name';Expression={[string]$_.Name}},
@{Name='Description';Expression={[string]$_.Description}},
@{Name='Path';Expression={[string]$_.Path}},
@{Name='Company';Expression={[string]$_.Company}},
@{Name='Username';Expression={[string]$_.UserName}},
@{Name='SessionId';Expression={[string]$_.SessionId}},
@{Name='StartTime';Expression={[string](($_.StartTime).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))}},
@{Name='MemoryMB';Expression={[int]([math]::Round($_.WorkingSet/1MB,2))}}
}
TotalSeconds : 4.5202906
I thought that by getting all of the required data in a single query and referring back to the $ProcessCPU
array would be fast - but I appreciate I'm iterating through each of the 250 arrays stored in $Processes
.
Is there a more performant method of joining two objects on a common property rather than using iteration as I have above? I.E. $ProcessCPU.IDProcess on $Processes.Id
?
I tried the following block to test $Output = $ProcessCPU + $Processes | Group-Object -Property Id
, it executed in just 3 seconds, but the output wasn't acceptable:
PS C:\Windows\system32> Measure-Command -Expression {
$ProcessCPU = Get-WmiObject Win32_PerfFormattedData_PerfProc_Process | Select-Object @{Name='Id';Expression={[int]$_.IDProcess}}, PercentProcessorTime
$Processes = Get-Process -IncludeUserName |
Select-Object `
@{Name='Id';Expression={[int]$_.Id}},
@{Name='Name';Expression={[string]$_.Name}},
@{Name='Description';Expression={[string]$_.Description}},
@{Name='Path';Expression={[string]$_.Path}},
@{Name='Company';Expression={[string]$_.Company}},
@{Name='Username';Expression={[string]$_.UserName}},
@{Name='SessionId';Expression={[string]$_.SessionId}},
@{Name='StartTime';Expression={[string](($_.StartTime).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))}},
@{Name='MemoryMB';Expression={[int]([math]::Round($_.WorkingSet/1MB,2))}}
$Output = $ProcessCPU + $Processes | Group-Object -Property Id
}
TotalSeconds : 2.9656969
Upvotes: 2
Views: 225
Reputation: 439193
Use CIM to build up a hashtable that maps process IDs (PIDs) to their CPU percentages first.
Then make the calculated property passed to Select-Object
consult that hashtable for efficient lookups:
Get-CimInstance Win32_PerfFormattedData_PerfProc_Process |
ForEach-Object -Begin { $htCpuPctg=@{} } `
-Process { $htCpuPctg[$_.IdProcess] = $_.PercentProcessorTime } #`
Get-Process -IncludeUserName |
Select-Object Id,
Name,
Description,
Path,
Company,
UserName,
SessionId,
@{Name='StartTime';Expression={[string](($_.StartTime).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))}},
@{Name='MemoryMB';Expression={[int]([math]::Round($_.WorkingSet/1MB,2))}},
@{Name='CPUPercent';Expression={ $htCpuPctg[[uint32] $_.Id] }}
Note:
Get-CimInstance
rather than Get-WimObject
is used, because the CIM cmdlets superseded the WMI cmdlets in PowerShell v3 (released in September 2012). Therefore, the WMI cmdlets should be avoided, not least because PowerShell Core, where all future effort will go, doesn't even have them anymore. For more information, see this answer.
There is usually no need to use calculated properties such as @{Name='Id';Expression={[int]$_.Id}}
to simply extract a property as-is - just use the property's name - Id
- as a Select-Object -Property
argument (but you've since clarified that you're using calculated properties because you want explicit control over the property's data type for sending data to an IoT Gateway via JSON).
Note that CIM reports PIDs as [uint32]
-typed values, whereas Get-Process
uses [int]
values - hence the need to cast to [uint32]
in the hashtable lookup.
Upvotes: 1