derwowusste
Goto Top

Skript zum Messen der CPU-Last für Windows

Moin Kollegen,

ich suche nach einem Skript, welches ich alle 10 Minuten vom Taskplaner aus starten lassen will, um auf einigen Servern (2008R2/2012R2) die CPU-Nutzung aufzuzeichnen.
Auf http://stackoverflow.com/questions/6298941/how-do-i-find-the-cpu-and-ra ... fand ich
Get-Counter -ComputerName localhost '\Process(*)\% Processor Time' `  
    | Select-Object -ExpandProperty countersamples `
    | Select-Object -Property instancename, cookedvalue `
    | Sort-Object -Property cookedvalue -Descending | Select-Object -First 20 `
    | ft InstanceName,@{L='CPU';E={($_.Cookedvalue/100).toString('P')}} -AutoSize  
was schon nahezu perfekt ist. Leider listet es nicht die 32-Bit-Prozesse mit auf. Kann mir jemand sagen, ob ich das mit einem Skript hinbekomme, oder muss ich ein zweites von der 32-Bit-Powershell.exe starten und die Ergebnisse verheiraten?

Content-Key: 305733

Url: https://administrator.de/contentid/305733

Ausgedruckt am: 19.03.2024 um 06:03 Uhr

Mitglied: emeriks
emeriks 30.05.2016 um 15:52:10 Uhr
Goto Top
Hi DWW,
könnte es sein, dass Du verwechselst?
Ich kenne das nur so, dass man aus einem 32bit-Prozess nicht die 64bit-Prozesse anzeigen kann. Aber umgekehrt sollte das schon gehen.

E.
Mitglied: DerWoWusste
DerWoWusste 30.05.2016 um 15:53:54 Uhr
Goto Top
Hi E.
Nö, ich sehe hier trotz starker Last durch einen 32-Bit-Prozess nur die 64er, wenn ich die 64er ISE nutze und nur die 32er, wenn ich die 32er ISE nehme.
Mitglied: colinardo
colinardo 30.05.2016 aktualisiert um 17:05:24 Uhr
Goto Top
Hallo DWW,
also ich sehe hier mit einer 64Bit Konsole als Admin alle Prozesse sowohl 64 als auch 32bit (getestet unter Server 2012R2 als auch mal unter einem Windows 7)

Die ISE ist hier manchmal buggy benutze stattdessen für den Test eine normale Konsole, dann klappt das auch wie gewünscht.

Grüße Uwe
Mitglied: DerWoWusste
DerWoWusste 30.05.2016 um 18:18:43 Uhr
Goto Top
Hallo Uwe.

Tja, auf 2x 2008R2 hatte ich es getestet - bei beiden geht es nicht auf der ISE (x64), auf 2012R2 geht es in der Tat. Hm.
Und es ist wie Du sagst, rufe ich das Skript von der Kommandozeile über
powershell test.ps1
auf, dann funktioniert es auch auf Server 2008R2... na super.

Ich danke Dir mal wieder!

Kannst Du mir noch einen Tipp geben, wie ich es mit out-file hinbekomme, so dass es auch vom Taskplaner aus gestartet in eine Datei schreibt?
Mitglied: colinardo
colinardo 30.05.2016 aktualisiert um 18:27:00 Uhr
Goto Top
Zitat von @DerWoWusste:
Ich danke Dir mal wieder!
Keine Ursache face-smile
Kannst Du mir noch einen Tipp geben, wie ich es mit out-file hinbekomme, so dass es auch vom Taskplaner aus gestartet in eine Datei schreibt?
Dazu pappst du an den obigen Code hinter -AutoSize einfach noch ein
| out-file 'C:\log\cpu.log'
Mitglied: DerWoWusste
DerWoWusste 30.05.2016 aktualisiert um 18:42:11 Uhr
Goto Top
Hatte ich so auch vorgehabt, jedoch ist die Ausgabedatei leer, wenn vom Taskplaner aus aufgerufen (Systemkonto). Auch auf der Kommandozeile misslingt es, wenn diese zuvor über psexec -s -i cmd als System gestartet wurde. Fehlermeldung:
Get-Counter : Das angegebene Objekt wurde nicht auf dem Computer gefunden.
At C:\temp\test.ps1:1 char:1
+ Get-Counter -ComputerName localhost '\Process(*)\% Processor Time' `  
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidResult: (:) [Get-Counter], Exception
    + FullyQualifiedErrorId : CounterApiError,Microsoft.PowerShell.Commands.Ge
   tCounterCommand
Mitglied: colinardo
colinardo 30.05.2016 aktualisiert um 18:57:39 Uhr
Goto Top
Du musst die 64Bit-Version der Powershell im Taskplaner angeben, in der 32Bit-Variante ist die Bezeichnung der Counter unter Umständen anders.

Klappt dann hier einwandfrei aus dem Taskplaner heraus.
Mitglied: DerWoWusste
DerWoWusste 30.05.2016 um 18:57:33 Uhr
Goto Top
Und auf welchem OS testest Du? 2008R2 scheint ja offensichtlich Eigenheiten zu haben diesbezüglich - hier geht es so nicht.
Mitglied: colinardo
Lösung colinardo 30.05.2016 aktualisiert um 19:18:30 Uhr
Goto Top
Gerade nur einen 2012R2er hier, teste nachher mal auf einem 2008R2er.

Die Counter lauten hier eventuell einfach anders.

Das angegebene Objekt wurde nicht auf dem Computer gefunden

Die verfügbaren Counter kannst du dir mit:
Get-Counter -ListSet process | select -Expand Counter
anzeigen lassen.
Wenn es ein deutsches System ist muss 'process' stattdessen 'prozess' lauten.

Die Meldung ist aber ein möglicher Indiz das dein Skript stattdessen in einer 32-Bit Konsole läuft.

-edit- auf einem 2008R2 einwandfrei im Taskplaner als SYSTEM getestet, hier ist es aber ein deutsches System auf dem der Performance-Counter:

\Prozess\Prozessorzeit (%)

lautet. Es kann bei dir eigentlich nur am Namen des Counters liegen.

Das mal wieder zum Thema "sprachunabhängige" Counternamen ... Danke MS face-confused
Mitglied: DerWoWusste
DerWoWusste 30.05.2016 um 19:21:08 Uhr
Goto Top
In der Tat, die Systemsprache war deutsch (und damit auch das Systemaccount), die Sitzungssprache englisch (ich nehme immer für mich englisch, bin aber nicht der einzige Admin dieses Servers).
Läuft, vielen Dank für's Augenöffnen!
Mitglied: DerWoWusste
DerWoWusste 30.05.2016 um 19:29:58 Uhr
Goto Top
Ach, wo Du Dich gerade schon mal warmgelaufen hast:
wie würdest Du eine simple Fallunterscheidung machen, um dies Skript unabhängig von der Sprache deploybar zu machen?
Mitglied: colinardo
colinardo 30.05.2016 aktualisiert um 20:28:04 Uhr
Goto Top
Mitglied: emeriks
emeriks 30.05.2016 um 20:29:37 Uhr
Goto Top
Sag ich doch. face-wink
Mitglied: colinardo
colinardo 30.05.2016 um 20:32:31 Uhr
Goto Top
Zitat von @emeriks:
Sag ich doch. face-wink
Lag aber im Endeffekt an der ISE. Die hat manchmal so ihre Eigenheiten.
Mitglied: DerWoWusste
DerWoWusste 30.05.2016 um 20:52:51 Uhr
Goto Top
Die dortigen Codeschnipsel laufen auf Win10 meist nicht korrekt, beispielsweise der Erste gibt zurück:
Get-Counter : Internal performance counter API call failed. Error: c0000bb8.
At line:2 char:10
+ $load = (Get-Counter "\Processor(_total)\% Processor Time" -SampleInt ...  
+          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidResult: (:) [Get-Counter], Exception
    + FullyQualifiedErrorId : CounterApiError,Microsoft.PowerShell.Commands.GetCounterCommand
Dein mittlerweile zurückgezogener Code lief auf 2012 und gab das erwartete zurück, auf 2008R2 gab er nichts zurück, auch keinen Fehler, und auch auf 10 brachte er
Exception calling "Substring" with "2" argument(s): "Index and length must refer to a location within the string.  
Parameter name: length"  
At line:21 char:5
+     $Buffer.ToString().Substring(0, $BufferSize-1)
+     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) , MethodInvocationException
    + FullyQualifiedErrorId : ArgumentOutOfRangeException
 
Exception calling "Substring" with "2" argument(s): "Index and length must refer to a location within the string.  
Parameter name: length"  
At line:21 char:5
+     $Buffer.ToString().Substring(0, $BufferSize-1)
+     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) , MethodInvocationException
    + FullyQualifiedErrorId : ArgumentOutOfRangeException
 
Get-Counter : Internal performance counter API call failed. Error: c0000bb8.
At line:32 char:2
+ (Get-Counter "\$processor(_total)\$percentProcessorTime" -SampleInter ...  
+  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidResult: (:) [Get-Counter], Exception
    + FullyQualifiedErrorId : CounterApiError,Microsoft.PowerShell.Commands.GetCounterCommand

Da bin ich raus, das ist doch weit hinter meinem PS-Horizont.
Mitglied: colinardo
colinardo 30.05.2016 aktualisiert um 21:09:33 Uhr
Goto Top
Ich hatte den Code zurückgezogen da die IDs auf jeder Platform anders sind (wusste ich nicht), d.h. will man ein Skript für eine Platform universell machen muss man sich erst die IDs der jeweiligen Platform (WIN10/2008R2/2021R2) holen, d.h die Zeilen 25 und 26 mit der jeweiligen Funktion Get-PerformanceCounterIdByName ausführen. Hierbei ist der lokalisierte String des aktuellen Systems zu nehmen auf dem man das gerade ausführt. Hinterher kann man auf der selben Platform stattdessen die ermittelten IDs benutzen.
D.h. Zeilen 14-26 führt man einmalig auf der Platform aus und kann dann für $root und $sub die ermittelten IDs verwenden.

Funktioniert hier auf diesen Systemen: WIN7/WIN10/2008R2/2012R2
(ACHTUNG: habe hier die deutschen Strings in Zeilen 25 und 26 verwendet. Und wie gesagt Zeilen 14-26 sind nur einmalig auf einem System der selben Platform auszuführen, nur um an die IDs zu kommen)
Function Get-PerformanceCounterNameByID {
  param([UInt32]$id)
  $buff = New-Object System.Text.StringBuilder(1024)
  [UInt32]$bSize = $buff.Capacity
  Add-Type -MemberDefinition '[DllImport("pdh.dll", SetLastError=true, CharSet=CharSet.Unicode)] public static extern UInt32 PdhLookupPerfNameByIndex(string szMachineName, uint dwNameIndex, System.Text.StringBuilder szNameBuffer, ref uint pcchNameBufferSize);' -Name PerfCounter -Namespace Util  
  $result = [Util.PerfCounter]::PdhLookupPerfNameByIndex($env:COMPUTERNAME, $id, $buff, [Ref]$bSize)
  if ($result -eq 0){
    $buff.ToString().Substring(0, $bSize-1)
  }else{
    Throw 'Get-PerformanceCounterNameById : Konnte lokalisierten Namen nicht finden. Überprüfen sie die übergebene ID.'  
  }
}

function Get-PerformanceCounterIdByName {
    param([Parameter(Mandatory=$true)][string]$Name)
    $counters = (Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Perflib\CurrentLanguage' -Name Counter).Counter  
    $index = [array]::IndexOf($counters,$Name)
    if ($index -ne -1){
        return $counters[($index-1)]
    }else{
        throw 'Get-PerformanceCounterIdByName: ID für diesen Namen wurde nicht gefunden'  
    }
}

$root = Get-PerformanceCounterIdByName -Name 'Prozess'  
$sub = Get-PerformanceCounterIdByName -Name 'Prozessorzeit (%)'  

$counter = "\$(Get-PerformanceCounterNameById $root)(*)\$(Get-PerformanceCounterNameByID $sub)"  

Get-Counter -Counter $counter -EA Ignore | select -Expand CounterSamples | sort CookedValue -Desc | ft InstanceName,@{n='CPU';e={($_.Cookedvalue/100).toString('P')}} -AutoSize