PowerShell İşlem Bellek Aracı Oluşturma
Bu hafta, Firefox'un yerine geçebilecek yeni bir tarayıcı olan Brave'i test ediyorum. Chrome'dan Firefox'a geçmemin nedenlerinden biri performans ve daha iyi kaynak kullanımıydı. Cesur artık daha iyi bir seçim olabilir ancak bu makalenin konusu bu değil. Kaynak kullanımını değerlendirmek için Get-Process PowerShell cmdlet'ine başvurdum. Bazı çok spesifik sonuçlar elde etmem gerektiğini fark ettiğim için süreci basitleştirmek amacıyla bir PowerShell işlevi yazmaya karar verdim. Bu nedenle sizin iş ihtiyaçlarınızı çözmek için kendi araçlarınızı oluşturmak üzere PowerShell'i öğrenmeli ve kullanmalısınız. İşte gelişim sürecimle ilgili bazı hikayeler.
Çalışma kümesi özelliğini kullanarak işlem bilgilerini görüntülemek için ilk olarak temel bir PowerShell ifadesiyle başladım.
Get-Process brave | Measure-Object Workingset -sum -average |
Select-object Count,Sum,Average
Benzer sonuçları görmek için krom gibi istediğiniz herhangi bir işlemi değiştirin. Bu basit ifade, tüm cesur süreçleri alır ve bunları çalışma seti özelliğinin toplamını ve ortalamasını hesaplayan Measure-Object'e yönlendirir. Özellikle Firefox ile karşılaştırma yapmayı düşündüğüm için çıktıda işlem adını görmek faydalı olacaktır. Bunu yaparsam:
Get-Process brave,firefox | Measure-Object Workingset -sum -average |
Select-object Count,Sum,Average
Her iki tarayıcının toplamını alıyorum. Hayır. Her işlem için Get-Process ve Measure-Object'i çalıştırmam gerekir. Bunun bir yolu ForEach-Object'i kullanmak olacaktır.
"brave","firefox" | foreach-object {
Get-Process -name $_ -PipelineVariable pv |
Measure-Object Workingset -sum -average |
Select-object @{Name="Name";Expression = {$pv.name}},
Count,Sum,Average
}
Bu daha iyi. İşlem adını almak için ortak PipelineVariable parametresini kullanıyorum. Measure-Object'in çıktısı Get-Process'teki işlem adını içermiyor, bu nedenle Select-Object'in çalışması için boru hattının daha önceki bir kısmına erişmenin bir yoluna ihtiyacım var. PipelineVariable'ın başardığı şey budur.
Seni bilmem ama ben tüm bu baytları kolayca daha anlamlı bir şeye dönüştüremem. Ancak PowerShell bunu yapabilir.
"brave","firefox" | foreach-object {
Get-Process -name $_ -PipelineVariable pv |
Measure-Object Workingset -sum -average |
Select-object @{Name="Name";Expression = {$pv.name}},
Count,
@{Name="SumMB";Expression = {[math]::round($_.Sum/1MB,2)}},
@{Name="AvgMB";Expression = {[math]::round($_.Average/1MB,2)}}
}
Artık ihtiyacım olan dostane sonucu üreten kodum var.
Ancak bunu esnek ve yeniden kullanılabilir hale getirmem gerekiyor. İşte bu noktada işlevler devreye giriyor. Bunu hızla bir fonksiyona dönüştürebilirim.
Function Get-MyProcess {
[cmdletbinding()]
Param([string[]]$Name)
$Name | foreach-object {
Get-Process -name $_ -PipelineVariable pv |
Measure-Object Workingset -sum -average |
Select-object @{Name="Name";Expression = {$pv.name}},
Count,
@{Name="SumMB";Expression = {[math]::round($_.Sum/1MB,2)}},
@{Name="AvgMB";Expression = {[math]::round($_.Average/1MB,2)}}
}
}
Ve çalışıyor.
Bu noktada bunun tamamlanmış olduğunu düşünebilirim. Ancak PowerShell işlevlerini yazdığımda, özellikle de bir şey değer veya veri alırken, genellikle bunun uzak bilgisayarlarla çalışmasını sağlamak isterim. İlk düşüncem, Get-Process'in -Computername parametresine sahip olması nedeniyle tek yapmam gereken onu fonksiyonuma eklemek ve kodda iletmekti. Bu yaklaşımın potansiyel dezavantajı, uzak bir bilgisayara bu şekilde bağlandığınızda eski uzaktan bağlantı bağlantıları üzerinden bağlanıyor olmanızdır. Mümkün olduğunca eski bağlantıları kullanmaktan kaçınmaya çalışıyorum, bu da PowerShell uzaktan iletişiminden yararlanmam gerektiği anlamına geliyor. Çözümlerden biri Get-Process komutumu bir Invoke-Command ifadesinin içine sarmak olabilir.
Function Get-MyProcess {
[cmdletbinding()]
Param([string[]]$Name,[string]$Computername = $env:computername)
Invoke-Command -ScriptBlock {
$using:Name | foreach-object {
Get-Process -name $_ -PipelineVariable pv |
Measure-Object Workingset -sum -average |
Select-object @{Name="Name";Expression = {$pv.name}},
Count,
@{Name="SumMB";Expression = {[math]::round($_.Sum/1MB,2)}},
@{Name="AvgMB";Expression = {[math]::round($_.Average/1MB,2)}}
}
} -ComputerName $computername
}
Bu, olgun bir PowerShell işlevine yaklaşıyor.
Gördüğünüz gibi bu fonksiyon herhangi bir hata işlemeyi içermiyor. RunspaceID özelliğini hiçbir zaman görmem gerekmediği için, işlevimi kaldırmak için her zaman başka bir PowerShell ifadesine sarmam gerekecekti. Fonksiyonun boru hattına sadece görmek istediğim değerleri içeren bir nesne yazması daha iyi olurdu. Diğer olası dezavantaj ise toplam ve ortalama değerlerimin MB cinsinden biçimlendirilmiş olmasıdır. Bu fonksiyonun esnekliğini sınırlayan bir varsayımda bulundum. Başlangıçta tembeldim. Daha iyi yaklaşım, ardışık düzene ham, biçimlendirilmemiş verilerle bir nesne yazmaktır.
Fonksiyonu revize ettikçe bu, scriptblock'un özü haline geldi:
foreach ($item in $ProcessName) {
Get-Process -Name $item -PipelineVariable pv -OutVariable ov |
Measure-Object -Property WorkingSet -Sum -Average |
Select-Object -Property @{Name = "Name"; Expression = {$pv.name}},
Count,
@{Name = "Threads"; Expression = {$ov.threads.count}},
Average, Sum,
@{Name = "Computername"; Expression = {$env:computername}}
}
Çıkış değerleri artık baytlara geri döndü. Ama biliyorsunuz, bunların varsayılan olarak MB olarak biçimlendirilmiş olmasını gerçekten isterim. PowerShell bunu yapabilir ve eğer gerçekte bunu her zaman yapıyorsa. Get-Process'i çalıştırdığınızda varsayılan ekran sizin için biçimlendirilir. Gördüğünüz şey, temeldeki süreç nesnelerinin ham değeri değildir. PowerShell bir format yönergesi kullanıyor. Sen de yapabilirsin.
Öncelikle çıktı nesnenize bir tür adı vermeniz gerekir. PowerShell sınıfını kullanarak bir işlev oluşturabilirdim. Ancak bir tür adı eklemek de aynı derecede kolaydır.
Invoke-Command @PSBoundParameters | Select-Object -Property * -ExcludeProperty RunspaceID,PS* |
ForEach-Object {
#insert a custom type name for the format directive
$_.psobject.typenames.insert(0, "myProcessMemory") | Out-Null
$_
}
Şimdi işin zor kısmı. Biçim yönergeleri, .ps1xml dosyasıyla kaydedilen özel XML dosyalarıdır. Yapabileceğim şeylerden biri özel bir başlık ve değer tanımlamaktır.
<TableColumnItem>
<ScriptBlock>[math]::Round($_.average/1MB,4)</ScriptBlock>
</TableColumnItem>
Varsayılan çıktı, biçimlendirme yönergelerini kullanır ancak ben yine de temeldeki gerçek özellik adlarını kullanabilirim.
Bu arada, Firefox'un çok sayıda uzantısı ve Brave'in de çok sayıda açık sekmesi olduğundan, bu sonuçları çok fazla okumayın.
Ayrı bir .ps1xml dosyası oluşturmak yerine, fonksiyonumla birlikte içeriği betik dosyama bir burada dizesi olarak ekledim ve daha sonra Update-FormatData kullanılarak içe aktarılan geçici bir dosya oluşturdum. Dosyanın tamamı Github'da.
Get-ProcessMemory.ps1:
Function Get-ProcessMemory {
<#
.SYNOPSIS
Get a snapshot of a process' memory usage.
.DESCRIPTION
Get a snapshot of a process' memory usage based on its workingset value. You can get the same information using Get-Process or by querying the Win32_Process WMI class with Get-CimInstance. This command uses Invoke-Command to gather the information remotely. Many of the parameters are from that cmdlet.
.EXAMPLE
PS C:\> get-processmemory code,powershell*
Name Count Threads AvgMB SumMB Computername
---- ----- ------- ----- ----- ------------
Code 9 154 112.8199 1015.3789 BOVINE320
powershell 3 77 179.1367 537.4102 BOVINE320
powershell_ise 1 21 242.0586 242.0586 BOVINE320
The default output displays the process name, the total process count, the total number of threads, the average workingset value per process in MB and the total workingset size of all processes also formatted in MB.
.EXAMPLE
PS C:\> get-processmemory -computername srv1,srv2,dom1 -Name lsass -cred company\artd
Name Count Threads AvgMB SumMB Computername
---- ----- ------- ----- ----- ------------
lsass 1 30 60.1719 60.1719 DOM1
lsass 1 7 10.8594 10.8594 SRV1
lsass 1 7 9.6953 9.6953 SRV2
.EXAMPLE
PS C:\> get-processmemory *ActiveDirectory* -Computername dom1 | select-object *
Name : Microsoft.ActiveDirectory.WebServices
Count : 1
Threads : 10
Average : 46940160
Sum : 46940160
Computername : DOM1
This example uses a wildcard for the process name because the domain controller only has one related process. The output shows the raw values.
.EXAMPLE
PS C:\> get-processmemory *edge*,firefox,chrome | sort sum -Descending
Name Count Threads AvgMB SumMB Computername
---- ----- ------- ----- ----- ------------
firefox 6 189 180.4134 1082.4805 BOVINE320
chrome 8 124 67.1377 537.1016 BOVINE320
MicrosoftEdgeCP 4 171 66.1846 264.7383 BOVINE320
MicrosoftEdge 1 37 70.1367 70.1367 BOVINE320
MicrosoftEdgeSH 1 10 8.2734 8.2734 BOVINE320
Get browser processes and sort on the underlying SUM property in descending order.
.NOTES
Learn more about PowerShell: http://jdhitsolutions.com/blog/essential-powershell-resources/
.LINK
Get-Process
Invoke-Command
.INPUTS
[string[]]
#>
[CmdletBinding()]
[OutputType("myProcessMemory")]
Param
(
[Parameter(Mandatory, ValueFromPipeline, Position = 0)]
[string[]]$Name,
[ValidateNotNullOrEmpty()]
[string[]]$Computername = $env:COMPUTERNAME,
[PSCredential]$Credential,
[switch]$UseSSL,
[Int32]$ThrottleLimit,
[ValidateSet('Default', 'Basic', 'Credssp', 'Digest', 'Kerberos', 'Negotiate', 'NegotiateWithImplicitCredential')]
[ValidateNotNullorEmpty()]
[string]$Authentication = "default"
)
Begin {
Write-Verbose "Starting $($MyInvocation.Mycommand)"
$sb = {
Param([string[]]$ProcessName)
# a process might have multiple instances so get each one by name
#group the processes to accomodate the use of wildcards
$data = Get-Process -Name $ProcessName | Group-Object -Property Name
foreach ($item in $data) {
$item.group | Measure-Object -Property WorkingSet -Sum -Average |
Select-Object -Property @{Name = "Name"; Expression = {$item.name}},
Count,
@{Name = "Threads"; Expression = {$item.group.threads.count}},
Average, Sum,
@{Name = "Computername"; Expression = {$env:computername}}
}
} #close ScriptBlock
#update PSBoundparamters so it can be splatted to Invoke-Command
$PSBoundParameters.Add("ScriptBlock", $sb) | Out-Null
$PSBoundParameters.add("HideComputername", $True) | Out-Null
} #begin
Process {
$PSBoundParameters.Remove("Name") | Out-Null
Write-Verbose "Querying processes $($name -join ',') on $($Computername -join ',')"
#need to make sure argument is treated as an array
$PSBoundParameters.ArgumentList = , @($Name)
if (-Not $PSBoundParameters.ContainsKey("Computername")) {
#add the default value if nothing was specified
$PSBoundParameters.Add("Computername", $Computername) | Out-Null
}
$PSBoundParameters | Out-String | Write-Verbose
Invoke-Command @PSBoundParameters | Select-Object -Property * -ExcludeProperty RunspaceID, PS* |
ForEach-Object {
#insert a custom type name for the format directive
$_.psobject.typenames.insert(0, "myProcessMemory") | Out-Null
$_
}
} #process
End {
Write-Verbose "Ending $($MyInvocation.Mycommand)"
} #end
} #close function
#region formatting directives for the custom object
[xml]$format = @'
default
myProcessMemory
20
left
7
right
7
right
10
right
10
right
16
right
Name
TableColumnIt
Count
TableColumnIt
Threads
TableColumnIt
[math]::Round($_.average/1MB,4)
TableColumnIt
[math]::Round($_.sum/1MB,4)
TableColumnIt
Computername
TableColumnIt
'@
$format.Save("$env:temp\myProcessMemory.format.ps1xml")
Update-FormatData -AppendPath $env:temp\myProcessMemory.format.ps1xml
#endregion
Evet, bunun geliştirilmesi biraz zaman aldı ancak artık yeniden kullanılabilir ve esnek bir PowerShell işlevim var. ve gelecekteki işlevler için kullanabileceğim bir model. Nihai ürün kadar geliştirme sürecini de görmenin faydalı olduğunu düşünüyorum. Geri bildiriminiz veya sorularınız varsa, lütfen yorum bırakmaktan çekinmeyin. Eğlence!
Güncelleme 2 Ocak 2019
Joker karakterlerin kullanımını daha iyi ele almak için Github özetindeki kodu güncelledim. Ayrıca biçimlendirme yönergelerini de değiştirdim.