01.12.2021
Logo10

График счетчиков производительности в консоли Powershell

В данной статье рассмотрим возможность визуализации набора данных в консоли 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. В реальном же случае график выглядит более естественным. Так же Вы можете самостоятельно добавлять другие метрики по аналогичной методике.

Игорь Чердаков

Мне 32 года. На текущий момент я являюсь администратором систем Microsoft в одной из крупнейших компаний в России. За моими плечами практически 7 лет опыта работы в IT, последние 3 из которых в администрировании. Сопровождая немалую инфраструктуру, в ежедневной деятельности я сталкиваюсь с необходимостью автоматизации рутинных процессов, в чем мне успешно помогает Powershell. Данный блог основывается на моем личном опыте и знаниях. Мои статьи не являются истиной в последней инстанции и я положительно отношусь к обоснованной и конструктивной критике. По этому я приглашаю Вас обсуждать волнующие нас вопросы, связанные с Powershell.

Посмотреть все записи автора Игорь Чердаков →

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *