OSzone.netMicrosoftPowerShellИспользование класса .NET StopWatch в сценарии Windows PowerShell для определения времени, затраченного на действие
Использование класса .NET StopWatch в сценарии Windows PowerShell для определения времени, затраченного на действие
Посетителей: 3604
| Просмотров: 4937 (сегодня 0)
Шрифт:
Что мне всегда нравилось насчет работы в Мексике, так это местное отношение к времени. Работа всегда выполнялась, но никто никуда не спешил. Если мой друг Мигель по дороге в офис предлагал заглянуть в новую кофейню, то так мы и делали. Если мы и опаздывали в офис на несколько минут, что с того? Мы выпили по чашке хорошего кофе, хорошо провели время в обществе друг друга и увидели прекрасный рассвет.
Как бы мне ни нравился такой образ жизни, с тех пор многое изменилось, и моя личная жизнь теперь идет по довольно жесткому распорядку. Я встаю в шесть часов утра, час занимаюсь упражнениями, принимаю душ, ем завтрак и прихожу в офис к половине восьмого. Я ловлю себя на том, что гляжу на часы десятки раз в день, чтобы проверить, соблюдаю ли я свой распорядок. В Мексике я даже не утруждал себя ношением часов. Казалось, о времени просто не нужно думать.
Напротив, сетевым администраторам и консультантам всегда необходимо учитывать время: от нас требуется создавать расписания резервного копирования, вычислять доступность служб, а также определять периоды работы, простоя и время ответа системы. Для отслеживания времени может быть достаточно щелкнуть значок, поглядеть на часы и отсчитать секунды или минуты до открытия приложения. Но что если придется на что-нибудь отвлечься в ходе расчета времени события? Не знаю как у вас, а из моей памяти нередко что-нибудь вылетает, когда я отвлекаюсь от выполнения какого-то процесса.
Хотя я и могу использовать вращающуюся оправу на своих часах для подводного плавания, чтобы отметить время запуска, сохраняется необходимость вычитать все эти числа – и если я сталкиваюсь с заминками, результаты могут быть ненадежны. Вместо обычных часов или даже часов для подводного плавания мне нужен секундомер, который сообщал бы, сколько времени заняло действие сейчас и сколько времени оно заняло в последний раз. Это позволит получить некоторое представление о том, что мои текущие результаты значат с точки зрения производительности.
К счастью, в Microsoft .NET Framework имеется класс StopWatch («Секундомер»), который я приспособил к делу в сценарии, называемом Set-StopWatchRecordValues.ps1. Этот сценарий замеряет время действия и записывает результаты в реестр. При последующих запусках сценария он извлекает предыдущее значение из реестра и сообщает как текущее, так и предшествовавшее время.
Класс StopWatch входит в пространство имен System.Diagnostics. У него есть статический метод под названием StartNew, который создает и запускает новый StopWatch. Для вызова статического метода из класса .NET Framework путь к пространству имен и имя класса помещаются в квадратные скобки, за которыми следуют два двоеточия и имя метода. В нашем примере пространство имен .NET Framework – это System.Diagnostics, а класс – это StopWatch. Нам также нужна переменная, в которой будет содержаться объект System.Diagnostics.StopWatch, возвращаемый методом:
$sw = [system.diagnostics.stopwatch]::startNew()
После этого переменную $sw можно использовать для отображения информации о работающем StopWatch; например, результаты можно передать командлету Format-List для отображения значения всех свойств, содержащихся в объекте:
У StopWatch имеется ряд полезных методов, которые отображены на рис. 1. Наиболее важен из них метод Stop, поскольку при использовании статического метода StartNew объект StopWatch одновременно создается и запускается. Следовательно, следующим наиболее вероятным действием является остановка работы объекта StopWatch чтобы увидеть, сколько времени заняло действие.
Рис. 1. Методы StopWatch
Название
Определение
Equals
System.Boolean Equals(Object obj)
GetHashCode
System.Int32 GetHashCode()
GetType
System.Type GetType()
get_Elapsed
System.TimeSpan get_Elapsed()
get_ElapsedMilliseconds
System.Int64 get_ElapsedMilliseconds()
get_ElapsedTicks
System.Int64 get_ElapsedTicks()
get_IsRunning
System.Boolean get_IsRunning()
Reset (Сброс)
System.Void Reset()
Start (Начало)
System.Void Start()
Stop
System.Void Stop()
ToString
System.String ToString()
Напишем четыре функции в сценарии Set-StopWatchRecordValues.ps1 для создания и управления объектом StopWatch, а также для записи затраченного времени в реестр. При первом выполнении сценарий создает раздел реестра и информирует пользователя об отсутствии сохраненной информации, как можно увидеть на рис. 2.
Рис. 2. В новом разделе реестра нет сохраненной информации
Затем сценарий создает свойство реестра и сохраняет текущее время выполнения в реестре, как показано на рис. 3. При последующих выполнениях сценария данные реестра извлекаются, отображаются и обновляются, как показано на рис. 4.
Рис. 3 Раздел реестра, где хранится текущее время выполнения
Рис. 4. При повторном выполнении сценария данные извлекаются, обновляются и отображаются
Сценарий использует единственный параметр под названием $debug. Это переключаемый параметр, то есть он влияет на поведение сценария только тогда, когда присутствует в командной строке. При запуске с ключом –debug сценарий распечатывает подробную информацию, которую можно увидеть на рис. 5 – был ли обнаружен раздел реестра, какое действие сценарий готовится выполнить и когда он завершил выполнение действия.
Для создания параметра командной строки используется оператор Param, как показано здесь:
Param([switch]$debug)
Вслед за оператором Param, который должен находиться в первой строке сценария, не снабженной комментариями, мы создаем первую функцию. Эта функция называется Set-StopWatch и используется для создания и запуска либо для остановки StopWatch. Значение, передаваемое входному параметру $action, определяет, что сделает функция. Объявление этой функции показано здесь:
Function Set-StopWatch($action)
Оператор switch используется для выбора выполняемого действия. Здесь же мы сталкиваемся с нашим первым отладочным сообщением. Чтобы вывести отладочную информацию в командную строку консоли, мы используем командлет Write-Debug. Командлет Write-Debug хорош тем, что он автоматически выделяет текст желтым и черным (цвета можно настроить), а также тем, что он выводит данные в консоль, только если ему указано это сделать. По умолчанию Write-Debug не печатает на консоль ничего. Входная переменная для функции Set-StopWatch – это переменная $action. Оператор Switch проверяет значение переменной $action, чтобы определить, какое действие необходимо выполнить:
Write-Debug "Set-StopWatch action is $action
Switch ($action)
Теперь нам нужно определить действие, которое произойдет. Если значение переменной $action равно Start, то выполнится первое действие. Оно состоит в том, что в сценарии используется статический метод StartNew класса System.Diagnostic.StopWatch, а получившийся объект StopWatch сохраняется в переменной $sw. Кроме того, создается отладочное сообщение, которое указывает, что мы запускаем таймер. Однако помните: это сообщение будет выведено только в том случае, если сценарий будет запущен с ключом –debug:
Когда переменная $action принимает значение «stop», выводится отладочное сообщение, которое указывает, что мы останавливаем таймер. Затем проверяем, существует ли раздел реестра. Если да, то создаем переменную $swv и устанавливаем ее равной $null. Затем распечатываем еще одно отладочное сообщение:
Затем мы вызываем функцию Get-StopWatchvalue и передаем ей только что созданную переменную $swv. В этом коде переменная передается по ссылке, что позволяет внутри функции присвоить переменной новое значение, а затем использовать его вне функции, которая присваивала значение. Чтобы передать переменную по ссылке, нам необходимо преобразовать $swv в ссылочный тип. Для этого преобразования используется [ref]. Следует отметить, что нам необходимо было создать переменную $swv ранее, поскольку переменную нельзя преобразовать к ссылочному типу, если она не существует:
Если раздела реестра не существует, то выведем пару отладочных сообщений, создадим переменную $swv и присвоим ей значение null, а затем вызовем функцию New-StopWatchKey, которая создает раздел реестра:
Else
{
Write-Debug "$path was not found"
$swv = $null
Write-Debug "$swv is null. Calling New-StopWatchKey"
New-StopWatchKey
После возвращения из функции New-StopWatchKey выведем еще одно отладочное сообщение и вызовем функцию Get-StopWatchvalue. Переменная $swv передается по ссылке ($swv будет использована позже для отображения данных, которые получены из реестра):
Write-Debug "Obtaining default value from get-StopWatchvalue"
et-StopWatchvalue([ref]$swv)
}
Теперь нужно остановить StopWatch. Сначала мы выводим отладочное сообщение об остановке секундомера, после чего вызовем метод Stop из объекта StopWatch, сохраненного в переменной $sw. После остановки StopWatch нам нужно преобразовать объект System.TimeSpan, который был получен с помощью свойства Elapsed, в строку, чтобы его значение можно было сохранить в реестре. Строковое представление объекта timespan (прошедшего времени) передается функции Set-StopWatchValue, как можно увидеть здесь:
Write-Debug "Stopping the stop watch"
$script:sw.Stop()
Write-Debug "Converting stop watch to string, and calling
Set-StopWatchValue"
Set-StopWatchValue($script:
sw.Elapsed.toString())
Теперь все готово к выводу данных на консоль. В первую очередь выведем, сколько времени заняло исполнение команды. Используем переменную области видимости сценария $sw и получим свойство Elapsed. Подвыражение предназначено для того, чтобы в результате вычисления значения свойства Elapsed был возвращен объект timespan.
Задача может показаться несколько запутанной, если не знать, что в Windows PowerShell существует два вида строковых символов. Первый – это строковые литералы, представленные одинарными кавычками. В случае строкового литерала действует принцип «что видишь, то и получишь». Это значит, что внутри одинарных кавычек переменная не будет расширена:
PS C:\> $a = "this is a string"
PS C:\> "This is what is in $a"
This is what is in $a
При использовании строк, представленных двойными кавычками, значения переменных расширяются и распечатываются:
PS C:\> $a = "this is a string"
PS C:\> "This is what is in $a"
This is what is in this is a string
Хотя поначалу это свойство и раздражает, его можно использовать, чтобы избежать сцепления строк и переменных. Зная о двух типах строк, можно использовать символ обратной кавычки, чтобы предотвратить расширение переменной, когда это необходимо:
PS C:\> $a = "this is a string"
PS C:\> "This is what is in "$a: $a"
This is what is in $a: this is a string
Если расширяемые строки не используются, то выходные данные необходимо сцепить следующим образом:
PS C:\> $a = "this is a string"
PS C:\> "This is what is in $a: " + $a
This is what is in $a: this is a string
Так какая здесь связь с нашим кодом? При расширении объекта в расширяемой строке выведется имя объекта. Если же нужно получить его значение, то необходимо выделить часть выражения, заключив объект в скобки и поместив перед ним знак доллара. Это вызовет вычисление значения объекта и возвращение свойства по умолчанию, как показано здесь:
Мы выводим сообщение, которое показывает, сколько времени ушло на выполнение команды, а также значение, сохраненное в переменной $swv, как показано здесь:
"The command took $($script:sw.Elapsed)"
"Previous command took: " + $swv
При использовании оператора Switch рекомендуется всегда создавать действие по умолчанию. Если сценарий запущен без ключа –debug, он введет значение переменной $action и укажет пользователю использовать Start или Stop. Если он запущен с ключом debug, то вводится несколько строк. В любом случае сценарий закончит работу, вызвав команду выхода, как показано здесь:
Default {
if(!$debug) {"$action is not understood. Specify Start / Stop"}
Write-Debug "Entering default action. $action was specified."
Write-Debug "Action value must be either Start or Stop"
Write-Debug "Exiting script now"
Exit
}
} #end Switch
} #end Set-StopWatch
Теперь рассмотрим функцию New-StopWatchKey, которая вызывается, если сценарий обнаруживает, что раздел реестра сценария еще не создан. В первую очередь новая функция New-StopWatchKey выводит отладочное сообщение. Затем она использует командлет New-Item для создания раздела реестра. Получившееся сообщение передается командлету Out-Null, чтобы не засорять экран.
Вводится еще одно отладочное сообщение, после чего создается свойство реестра, в котором хранится время предыдущего выполнения. Для этого используется командлет New-ItemProperty. Получившееся сообщение этого командлета также передается командлету Out-Null. Мы вводим еще одно отладочное сообщение и выходим из функции:
Теперь необходимо создать функцию Get-StopWatchvalue для чтения данных, хранящихся в реестре, и присвоения их переменной $swv, переданной по ссылке. Для этого выведем несколько отладочных сообщений, а затем используем командлет Get-ItemProperty для создания специального объекта Windows PowerShell, который мы сохраняем в переменной $swValue. Как можно увидеть ниже, в объекте содержится ряд свойств:
Использовать промежуточную переменную для получения свойства предыдущего значения не обязательно. Можно использовать скобки и запросить свойство напрямую. Это немного сбивает с толку, но, как можно убедиться, синтаксис верен:
Когда объект создан, можно запросить свойство PreviousCommand и присвоить его свойству «value» переменной ссылочного типа $swv. Еще несколько отладочных сообщений, и работа с функцией Get-StopWatchvalue закончена:
Далее нужно записать в реестр время, ушедшее на выполнение команды, так что мы создаем функцию Set-StopWatchvalue. Эта функция принимает значение, которое следует записать в реестр, в переменной $newSwv. Мы записываем несколько отладочных сообщений и вызываем командлет Set-ItemProperty, передавая ему путь, сохраненный в переменной $path, имя свойства реестра, сохраненное в переменной $property, и значение, сохраненное в переменной $newSwv. Затем выводим еще одно отладочное сообщение и выходим из функции Set-StopWatchValue.
Теперь все функции, необходимые для сценария, созданы. Точка входа в сценарий сперва проверяет наличие переменной $debug. Если она установлена, то сценарий был запущен с ключом –debug, поэтому значение автоматической переменной $DebugPreference меняется на «continue». По умолчанию переменная $DebugPreference установлена на автоматическое продолжение, а это значит, что командлет Write-Debug не будет отображать отладочных сообщений.
В нашем сценарии записи времени, сделанные StopWatch, будут храниться в разделе реестра под названием Scripting\Stopwatch в кусте HKEY_Current_User. Используем свойство PreviousCommand. Каждая запись присваивается соответствующим переменным. После инициализации переменных в первую очередь вызывается функция Set-StopWatch со значением параметра «action» равным «Start». После выполнения этой функции вызывается командлет Get-Process:
В случае использования данного сценария для измерения времени, которое тратится на различные действия, то именно сюда можно добавить то, что нужно. На этом этапе можно даже вызвать другой сценарий или включить любое необходимое количество команд.
Если переменная $debug отсутствует, сценарий работает в нормальном режиме. В этом случае нам не нужно видеть результатов вызова командлета Get-Process на экране, поскольку нас интересует лишь измерение времени действия. Поэтому используется оператор not (восклицательный знак, бита и мяч, называйте как хотите), чтобы указать, что в случае отсутствия отладочной переменной экран будет очищен с помощью функции Windows PowerShell Clear-Host. Наконец вызывается функция Set-StopWatch с параметром «Stop», чтобы остановить процесс:
if(!$debug) {Clear-Host}
Set-StopWatch "Stop"
На рис. 6 показан законченный сценарий Set-StopWatchRecordValues.ps1.
Рис. 6. Set-StopWatchRecordValues.ps1
Param([switch]$debug)
Function Set-StopWatch($action)
{
Write-Debug “Set-StopWatch action is $action”
Switch ($action)
{
“Start” {
Write-Debug “Starting Timer”
$script:sw = [system.diagnostics.stopwatch]::StartNew()
}
“Stop” {
Write-Debug “Stopping Timer”
If(Test-Path -path $path)
{
$swv = $null
Write-Debug “$path was found. Calling Get-StopWatchvalue”
Get-StopWatchvalue([ref]$swv)
Write-Debug “Get-StopWatchvalue swv was: $($swv)”
}
Else
{
Write-Debug “$path was not found”
$swv = $null
Write-Debug “$swv is null. Calling New-StopWatchKey”
New-StopWatchKey
Write-Debug “Obtaining default value from get-StopWatchvalue”
Get-StopWatchvalue([ref]$swv)
}
Write-Debug “Stopping the stop watch”
$script:sw.Stop()
Write-Debug “Converting stop watch to string, and calling Set-StopWatchValue”
Set-StopWatchValue($script:sw.Elapsed.toString())
“The command took $($script:sw.Elapsed)”
“Previous command took: “ + $swv
}
Default {
if(!$debug) {“$action is not understood. Specify Start / Stop”}
Write-Debug “Entering default action. $action was specified.”
Write-Debug “Action value must be either Start or Stop”
Write-Debug “Exiting script now”
Exit
}
} #end Switch
} #end Set-StopWatch
Function New-StopWatchKey()
{
Write-Debug “Inside New-StopWatchKey. Creating new item $path”
New-Item -path $path -force |
out-null
Write-Debug “Creating new item property $property”
New-ItemProperty -Path $path `
-Name $property -PropertyType string -value “no data” |
out-null
Write-Debug “Leaving New-StopWatchkey”
} #end New-StopWatchKey
Function Get-StopWatchvalue([ref]$swv)
{
Write-Debug “Inside Get-StopWatchvalue.”
Write-Debug “Obtaining $path\$property”
$swValue = Get-ItemProperty -Path $path `
-Name $property
$swv.value = $swValue.PreviousCommand
Write-Debug “Value of `$swv is $($swv.value)”
Write-Debug “Leaving Get-StopWatchvalue”
} #end Get-StopWatchvalue
Function Set-StopWatchvalue($newSwv)
{
Write-Debug “Inside Set-StopWatchvalue”
Write-Debug “setting $path\$property to $newSwv”
Set-ItemProperty -Path $path `
-Name $property -Value $newSwv
Write-Debug “Leaving Set-StopWatchvalue”
} #end Set-StopWatchvalue
# *** entry point ***
if($debug) { $DebugPreference = “continue” }
$path = “HKCU:\Scripting\Stopwatch”
$property = “PreviousCommand”
Set-StopWatch “Start”
Get-Process
if(!$debug) {Clear-Host}
Set-StopWatch “Stop”
Я надеюсь, что читателям понравился этот экскурс в функции класса StopWatch. Посещайте TechNet Script Center и знакомьтесь с нашими ежедневными статьями из серии «Эй, сценарист!»