В данной статье рассмотрим возможность визуализации набора данных в консоли Powershell в виде графика. В качестве набора данных будут выступать значения счетчиков производительности локального или удаленного сервера.

Для чего я буду использовать функцию для визуализации, которую я обнаружил на просторах интернета. Ее автором является – Singh Prateik. В его примере набор данных формируется с помощью командлета Get-Random. Мы же будем брать реальные данные о производительности сервера и строить на их основе дашборд. Листинг функции следующий (Листинг 1):
<# .SYNOPSIS Draws graph in the Powershell console .DESCRIPTION Consumes datapoints and draws colored coded fully customizable graph in the Powershell console. .PARAMETER Datapoints Array of data points which is to be plotted on the graph .PARAMETER XAxisTitle Label on the X-Axis .PARAMETER YAxisTitle Label on the Y-Axis .EXAMPLE Show-Graph -Datapoints $Datapoints .EXAMPLE Show-Graph -Datapoints $Datapoints -XAxisTitle "Avg. CPU utilization" -YAxisTitle "Percentage" .NOTES Blog: https://geekeefy.wordpress.com/ Author: https://twitter.com/SinghPrateik Features and Benefits: * Color-coded output depending upon the Value of Datapoint * Custom X an Y-Axis labels * Graph in console is independent and fully customizable, not like Task Manager (Performance Tab) * Could be incorporated in Powershell scripts * Can consume datapoints generated during script run or Pre stored data like in a file or database. #> Function Show-Graph { [cmdletbinding()] [alias("Graph")] Param( # Parameter help description [Parameter(Mandatory=$true)] [int[]] $Datapoints, [String] $XAxisTitle = 'X-Axis', [String] $YAxisTitle = 'Y Axis' ) Write-Host " " $NumOfDatapoints = $Datapoints.Count $NumOfLabelsOnYAxis = 10 $XAxis = " "+"-"*($NumOfDatapoints+3) $YAxisTitleAlphabetCounter = 0 $YAxisTitleStartIdx = 1 $YAxisTitleEndIdx = $YAxisTitleStartIdx + $YAxisTitle.Length -1 If($YAxisTitle.Length -gt $NumOfLabelsOnYAxis){ Write-Warning "No. Alphabets in YAxisTitle [$($YAxisTitle.Length)] can't be greator than no. of Labels on Y-Axis [$NumOfLabelsOnYAxis]" Write-Warning "YAxisTitle will be cropped" } If($XAxisTitle.Length -gt $XAxis.length-3){ $XAxisLabel = " "+$XAxisTitle }else{ $XAxisLabel = " "+(" "*(($XAxis.Length - $XAxisTitle.Length)/2))+$XAxisTitle } # Create a 2D Array to save datapoints in a 2D format $Array = New-Object 'object[,]' ($NumOfLabelsOnYAxis+1),$NumOfDatapoints $Count = 0 $Datapoints | ForEach-Object { $r = [Math]::Floor($_/10) $Array[$r,$Count] = [char] 9608 1..$R | ForEach-Object {$Array[$_,$Count] = [char] 9608} $Count++ } # Draw graph For($i=10;$i -gt 0;$i--){ $Row = '' For($j=0;$j -lt $NumOfDatapoints;$j++){ $Cell = $Array[$i,$j] $String = If([String]::IsNullOrWhiteSpace($Cell)){' '}else{$Cell} $Row = [string]::Concat($Row,$String) } $YAxisLabel = $i*10 # Condition to fix the spacing issue of a 3 digit vs 2 digit number [like 100 vs 90] on the Y-Axis If("$YAxisLabel".length -lt 3){$YAxisLabel = (" "*(3-("$YAxisLabel".length)))+$YAxisLabel} If($i -in $YAxisTitleStartIdx..$YAxisTitleEndIdx){ $YAxisLabelAlphabet = $YAxisTitle[$YAxisTitleAlphabetCounter]+" " $YAxisTitleAlphabetCounter++ } else { $YAxisLabelAlphabet = ' ' } # To color the graph depending upon the datapoint value If ($i -gt 7) {Write-Host $YAxisLabelAlphabet -ForegroundColor DarkYellow -NoNewline; Write-Host "$YAxisLabel|" -NoNewline; Write-Host $Row -ForegroundColor Red} elseif ($i -le 7 -and $i -gt 4) {Write-Host $YAxisLabelAlphabet -ForegroundColor DarkYellow -NoNewline; Write-Host "$YAxisLabel|" -NoNewline; Write-Host $Row -ForegroundColor Yellow} elseif($i -le 4 -and $i -ge 1) {Write-Host $YAxisLabelAlphabet -ForegroundColor DarkYellow -NoNewline; Write-Host "$YAxisLabel|" -NoNewline; Write-Host $Row -ForegroundColor Green} else {Write-Host "$YAxisLabel|"} } $XAxis # Prints X-Axis horizontal line Write-Host $XAxisLabel -ForegroundColor DarkYellow # Prints XAxisTitle }
Как мы видим у функции есть три параметра:
- Datapoints – Массив целочисленных данных для отображения на графике. (Обязательный);
- XAxisTitle – Наименование оси X. (Опциональный);
- YAxisTitle – Наименование оси Y. (Опциональный);
Отрисовка счетчиков производительности
Для отрисовки значений счетчиков производительности функция рисования у нас уже есть. Осталось научиться получать эти значения.
Первым делом необходимо для каждого счетчика создать пустой массив данных типа [System.Collections.ArrayList]. Массив данного типа позволяет удалить элемент в начале и добавить новый элемент в конце. Это понадобится для обновления данных на графике.
[System.Collections.ArrayList]$CPUDatapoints = @() [System.Collections.ArrayList]$RAMDatapoints = @()
Затем необходимо определить локализацию операционной системы, так как от нее зависят названия счетчиков. Делать мы это будем с помощью Get-Culture. А в случае с удаленным сервером мы будем ее запускать через Invoke-Command. Потому как у Get-Culture отсутствует параметр -ComputerName. Как получить справку по команде можно посмотреть тут.
if($ComputerName -ne "localhost") { $localization = Invoke-Command -ComputerName $ComputerName -ScriptBlock {Get-Culture} } else {$localization = Get-Culture}
Далее с помощью ветвления задаем названия счетчиков в зависимости от локализации. Используем Switch:
switch ($localization.name) { "en-US" { $CPUcounterName = "\processor(_total)\% processor time" $RAMcounterName = "\memory\Available bytes" } "ru-RU" { $CPUcounterName = "\процессор(_total)\% загруженности процессора" $RAMcounterName = "\память\Доступно байт" } }
Получаем значения счетчиков и добавляем их в соответствующий набор данных. Отмечу, что функция отрисовки в текущем виде работает только с процентными метриками. По этому в случае с объемом используемой памяти значения необходимо перевести в проценты. Для непрерывного обновления графика используем бесконечный цикл While. В код добавлены комментарии:
While ($a -ne 1) { # График утилизации CPU if($ComputerName -ne "localhost") { $CPUvalue = (Get-Counter -ComputerName $ComputerName -Counter "$CPUcounterName").CounterSamples.CookedValue # Если сервер удаленный } else {$CPUvalue = (Get-Counter -Counter "$CPUcounterName").CounterSamples.CookedValue} # Если сервер локальный if($CPUDatapoints.count -eq $GraphLength) # $GraphLength - переменная содержит число элементов, отображаемых на графике { $CPUDatapoints.RemoveAt(0) # Удаляем самое старое значение массива $CPUDatapoints += $CPUvalue # Добавляем новое значение } else {$CPUDatapoints += $CPUvalue} # Добавляем новое значение # График утилизации ОЗУ if($ComputerName -ne "localhost") { $RAMavailable = (Get-Counter -ComputerName $ComputerName -Counter "$RAMcounterName").CounterSamples.CookedValue $RAMsize = (Get-CimInstance -class CIM_PhysicalMemory -ComputerName $ComputerName | Measure-Object -Property capacity -sum).sum } else { $RAMavailable = (Get-Counter -Counter "$RAMcounterName").CounterSamples.CookedValue $RAMsize = (Get-CimInstance -class CIM_PhysicalMemory | Measure-Object -Property capacity -sum).sum } $RAMusingPercent = 100 - ($RAMavailable * 100 / $RAMsize) # Получаем значение в процентах if($RAMDatapoints.count -eq $GraphLength) # $GraphLength - переменная содержит число элементов, отображаемых на графике { $RAMDatapoints.RemoveAt(0) # Удаляем самое старое значение массива $RAMDatapoints += $RAMusingPercent # Добавляем новое значение } else {$RAMDatapoints += $RAMusingPercent} # Добавляем новое значение # ... ... ... } # Окончание цикла While
Таким образом мы получили два набора данных об утилизации CPU и RAM. Осталось только передать их в функцию отрисовки:
While($a -ne 1) { # ... ... ... cls Write-Host "Server name - $ComputerName" Show-Graph -Datapoints $CPUDatapoints -XAxisTitle "Avg. CPU loading" -YAxisTitle "Percentage" Show-Graph -Datapoints $RAMDatapoints -XAxisTitle "Memory used" -YAxisTitle "Percentage" Start-Sleep 2 # устанавливаем периодичность обновления графика в секундах } # Завершение цикла While
Чтобы этим можно было удобно пользоваться, завернем весь этот код в новую функцию – Get-Performance. А так же добавим параметры -ComputerName и -GraphLength (будет отвечать за количество элементов на графике). Итак, листинг всей функции ниже (Листинг 2):
Function Get-Performance { param ( [string]$ComputerName = "localhost", [int32]$GraphLength = 50 ) [System.Collections.ArrayList]$CPUDatapoints = @() [System.Collections.ArrayList]$RAMDatapoints = @() if($ComputerName -ne "localhost") { $localization = Invoke-Command -ComputerName $ComputerName -ScriptBlock {Get-Culture} } else {$localization = Get-Culture} switch ($localization.name) { "en-US" { $CPUcounterName = "\processor(_total)\% processor time" $RAMcounterName = "\memory\Available bytes" } "ru-RU" { $CPUcounterName = "\процессор(_total)\% загруженности процессора" $RAMcounterName = "\память\Доступно байт" } } While ($a -ne 1) { if($ComputerName -ne "localhost") { $CPUvalue = (Get-Counter -ComputerName $ComputerName -Counter "$CPUcounterName").CounterSamples.CookedValue } else {$CPUvalue = (Get-Counter -Counter "$CPUcounterName").CounterSamples.CookedValue} if($CPUDatapoints.count -eq $GraphLength) { $CPUDatapoints.RemoveAt(0) $CPUDatapoints += $CPUvalue } else {$CPUDatapoints += $CPUvalue} if($ComputerName -ne "localhost") { $RAMavailable = (Get-Counter -ComputerName $ComputerName -Counter "$RAMcounterName").CounterSamples.CookedValue $RAMsize = (Get-CimInstance -class CIM_PhysicalMemory -ComputerName $ComputerName | Measure-Object -Property capacity -sum).sum } else { $RAMavailable = (Get-Counter -Counter "$RAMcounterName").CounterSamples.CookedValue $RAMsize = (Get-CimInstance -class CIM_PhysicalMemory | Measure-Object -Property capacity -sum).sum } $RAMusingPercent = 100 - ($RAMavailable * 100 / $RAMsize) if($RAMDatapoints.count -eq $GraphLength) { $RAMDatapoints.RemoveAt(0) $RAMDatapoints += $RAMusingPercent } else {$RAMDatapoints += $RAMusingPercent} cls Write-Host "Server name - $ComputerName" Show-Graph -Datapoints $CPUDatapoints -XAxisTitle "Avg. CPU loading" -YAxisTitle "Percentage" Show-Graph -Datapoints $RAMDatapoints -XAxisTitle "Memory used" -YAxisTitle "Percentage" Start-Sleep 2 } }
И самый последний этап. Чтобы каждый раз не объявлять обе функции, их необходимо добавить в $profile. $profile – это файл .ps1, который выполняется при каждом запуске консоли. По умолчанию он отсутствует и в таком случае его необходимо создать вручную. Для каждого типа консоли он свой. Чтобы посмотреть необходимое имя файла, достаточно в консоли просто выполнить $profile. Для классической консоли это – C:\Users\username\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1. Таким образом при запуске консоли обе функции будут проинициализированы автоматически. И ими уже можно будет пользоваться. Хотя фактически мы будем запускать только вторую.
Результат
После сохранения обоих функций (“Листинг 1” и “Листинг 2”) в $profile необходимо перезапустить консоль. Выполняем Get-Performance -ComputerName ServerName:

После получения 50ти значений график начнет сдвигаться влево. В данном случае нагрузка была синтетической. По этому произошел резкий рост утилизации CPU. В реальном же случае график выглядит более естественным. Так же Вы можете самостоятельно добавлять другие метрики по аналогичной методике.