Поиск на сайте: Расширенный поиск


Новые программы oszone.net Читать ленту новостей RSS
CheckBootSpeed - это диагностический пакет на основе скриптов PowerShell, создающий отчет о скорости загрузки Windows 7 ...
Вы когда-нибудь хотели создать установочный диск Windows, который бы автоматически установил систему, не задавая вопросо...
Если после установки Windows XP у вас перестала загружаться Windows Vista или Windows 7, вам необходимо восстановить заг...
Программа подготовки документов и ведения учетных и отчетных данных по командировкам. Используются формы, утвержденные п...
Red Button – это мощная утилита для оптимизации и очистки всех актуальных клиентских версий операционной системы Windows...
OSzone.net Microsoft PowerShell Вычисление времени работы сервера RSS

Вычисление времени работы сервера

Текущий рейтинг: 4.88 (проголосовало 8)
 Посетителей: 5832 | Просмотров: 7443 (сегодня 0)  Шрифт: - +
Работает — значит, работает, а не работает — значит, не работает. Вроде бы очевидно — если только речь не идет о времени работы сервера. Чтобы знать время работы, надо знать время простоя. Каждый сетевой администратор беспокоится о времени работы сервера. (Конечно, когда он не беспокоится еще больше о том, что сервер простаивает). Обычно руководство ставит перед администраторами задачи по времени работы серверов; а сами администраторы, в свою очередь, включают эти данные в свои отчеты.

Ну и что в этом особенного? Казалось бы, можно воспользоваться теми двумя свойствами класса инструментария WMI Win32_OperatingSystem, которые с легкостью решают задачу: LastBootUpTime и LocalDateTime. Все, что нужно сделать — вычесть LastBootUptime из LocalDateTime, тогда всё в мире станет хорошо, а перед ужином можно будет сыграть небольшую партию в гольф.

С этими мыслями вы запускаете Windows PowerShell, чтобы выполнить запрос к классу Win32_OperatingSystem WMI и выбрать два его свойства, как показано здесь:

PS C:\> $wmi = Get-WmiObject -Class Win32_OperatingSystem
PS C:\> $wmi.LocalDateTime - $wmi.LastBootUpTime

Но после запуска этой команды вместо бодрого ответа со временем работы сервера вы получите унылое сообщение об ошибке, которое показано на рис. 1.

*

Рис. 1 Ошибка, возвращаемая при попытке вычитания значений времени в формате UTC инструментария WMI

Сообщение об ошибке несколько обескураживает: "Bad numeric constant". Что? Понятно, что такое число; понятно, что такое константа; но какое отношение это имеет ко времени?

Если вы сталкиваетесь со странным сообщением об ошибке, то прежде всего нужно проверить данные, которые пытается анализировать сценарий. Более того, работая с Windows PowerShell, обычно имеет смысл проверить, какие типы данных используются.

Чтобы выяснить, какие типы данных используются в сценарии, их можно просто вывести на экран. Вот что получается:

PS C:\> $wmi.LocalDateTime
20080905184214.290000-240

Число выглядит довольно странно. Как такое значение может быть датой? Воспользуемся методом GetType, чтобы это выяснить. Что хорошо в методе GetType — он почти всегда доступен. Нужно просто вызвать его. А вот и причина ошибки: значение свойства LocalDateTime имеет строковый тип вместо типа System.DateTime:

PS C:\> $wmi.LocalDateTime.gettype()

IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True     True     String
System.Object

Если вам требуется вычесть из одного времени другое, убедитесь сначала, что вы имеете дело со значениями времени, а не строками. Используйте метод ConvertToDateTime, который Windows PowerShell добавляет ко всем классам инструментария WMI:

PS C:\> $wmi = Get-WmiObject -Class Win32_OperatingSystem
PS C:\> $wmi.ConvertToDateTime($wmi.LocalDateTime) –
$wmi.ConvertToDateTime($wmi.LastBootUpTime)

Когда из одной величины типа времени вычитается другая величина такого же типа, получается экземпляр класса System.TimeSpan, что позволяет выбрать способ вывода информации о времени работы сервера на экран, не прибегая к арифметическим вычислениям. Достаточно выбрать, какое свойство выводить на экран (надеемся, что время работы ваших серверов выражается в TotalDays, а не в TotalMilliseconds). Ниже показан вывод объекта System.TimeSpan по умолчанию:

Days              : 0
Hours             : 0
Minutes           : 40
Seconds           : 55
Milliseconds      : 914
Ticks             : 24559148010
TotalDays         : 0.0284249398263889
TotalHours        : 0.682198555833333
TotalMinutes      : 40.93191335
TotalSeconds      : 2455.914801
TotalMilliseconds : 2455914.801

Проблема этого метода расчета в том, что он позволяет узнать только время работы сервера после последней перезагрузки. Этим способом нельзя вычислить время простоя. В этом и состоит сложность задачи: чтобы вычислить время работы, сначала нужно определить время простоя.

Как же определить, сколько времени не работал сервер? Для этого нужно знать, когда сервер включался и выключался. Эта информация находится в журнале системных событий. Служба «журнал событий» — один из самых первых процессов, которые запускаются при загрузке сервера или рабочей станции, и один из самых последних, которые останавливаются при выключении сервера. Каждое из событий включение/выключения создает запись с кодом события 6005 при запуске службы журнала событий и 6006 при остановке службы журнала событий. На рис. 2 показан пример запуска службы журнала событий.

*

Рис. 2 Служба журнала событий запускается вскоре после загрузки компьютера

Собрав события 6005 и 6006 в журнале событий, упорядочив их и вычтя времена запусков из времен выключений, можно определить время между перезапусками, когда сервер находился в простое. Если получившуюся величину отнять от количества минут в данном периоде времени, то можно сосчитать процент рабочего времени сервера. Этот подход и применен в сценарии CalculateSystemUpTimeFromEventLog.ps1, текст которого показан на рис. 3.

 Рис. 3 CalculateSystemUpTimeFromEventLog 3

#---------------------------------------------------------------
# CalculateSystemUpTimeFromEventLog.ps1
# ed wilson, msft, 9/6/2008
#
# Creates a system.TimeSpan object to subtract date values
# Uses a .NET Framework class, system.collections.sortedlist to sort the events from eventlog.
#
#---------------------------------------------------------------
#Requires -version 2.0
Param($NumberOfDays = 30, [switch]$debug)

if($debug) { $DebugPreference = " continue" }

[timespan]$uptime = New-TimeSpan -start 0 -end 0
$currentTime = get-Date
$startUpID = 6005
$shutDownID = 6006
$minutesInPeriod = (24*60)*$NumberOfDays
$startingDate = (Get-Date -Hour 00 -Minute 00 -Second 00).adddays(-$numberOfDays)

Write-debug "'$uptime $uptime" ; start-sleep -s 1
write-debug "'$currentTime $currentTime" ; start-sleep -s 1
write-debug "'$startingDate $startingDate" ; start-sleep -s 1

$events = Get-EventLog -LogName system |
Where-Object { $_.eventID -eq  $startUpID -OR $_.eventID -eq $shutDownID `
  -and $_.TimeGenerated -ge $startingDate }

write-debug "'$events $($events)" ; start-sleep -s 1

$sortedList = New-object system.collections.sortedlist

ForEach($event in $events)
{
 $sortedList.Add( $event.timeGenerated, $event.eventID )
} #end foreach event
$uptime = $currentTime - $sortedList.keys[$($sortedList.Keys.Count-1)]
Write-Debug "Current uptime $uptime"

For($item = $sortedList.Count-2 ; $item -ge 0 ; $item -- )
{
 Write-Debug "$item `t `t $($sortedList.GetByIndex($item)) `t `
   $($sortedList.Keys[$item])"
 if($sortedList.GetByIndex($item) -eq $startUpID)
 {
  $uptime += ($sortedList.Keys[$item+1] - $sortedList.Keys[$item])
  Write-Debug "adding uptime. `t uptime is now: $uptime"
 } #end if
} #end for item

"Total up time on $env:computername since $startingDate is " + "{0:n2}" -f `
  $uptime.TotalMinutes + " minutes."
$UpTimeMinutes = $Uptime.TotalMinutes
$percentDownTime = "{0:n2}" -f (100 - ($UpTimeMinutes/$minutesInPeriod)*100)
$percentUpTime = 100 - $percentDowntime

"$percentDowntime% downtime and $percentUpTime% uptime."

Сценарий начинается с оператора Param, который определяет пару параметров командной строки, величины которых передаются в сценарий. Первый, $NumberOfDays, позволяет указать количество дней для отчета по времени работы сервера. (Значение по умолчанию для этого параметра установлено и равно 30 дням, поэтому сценарий можно вызывать без определения этого параметра. Если нужно, этот параметр можно заменить).

Второй параметр, [switch]$debug, это переключатель, который, будучи указан в командной строке, включает отладочную печать сценария. Эта информация позволяет удостовериться в правильности результатов, выдаваемых сценарием. Возможны ситуации, когда сообщение о событии остановки службы 6006 отсутствует в журнале. Такое может произойти в случае внезапного отказа сервера, когда запись в журнал не попадает, в результате в сценарии из одного времени работы вычитается другое время работы, и результаты расчета искажаются.

Переменная $debug, полученная из командной строки, находится на «накопителе» Variable: . Переменная $debugPreference в таком случае устанавливается в значение «continue», при этом сценарий продолжает работать, а все значения, переданные оператору Write-Debug, выводятся на экран. Обратите внимание, что значение по умолчанию переменной $debugPreference равно «silentlycontinue». В этом случае сценарий выполняется, но никакие значения, переданные оператору Write-Debug, не выводятся на экран.

В выходных данных сценария перечислены все события журнала с кодами 6005 и 6006 (что отражено на рис. 4) и результат вычисления времени работы. С помощью этой информации можно проверить точность результатов.

*

Рис. 4 В отладочном режиме выводится величина каждого отрезка времени, который прибавляется к искомой величине времени работы

Затем создается объект System.TimeSpan. Создать объект типа timespan, который будет использоваться при вычислении разности дат, можно при помощи командлета New-Object:

PS C:\> [timespan]$ts = New-Object system.timespan

Но в Windows PowerShell для создания таких объектов есть специальный командлет New-TimeSpan, поэтому имеет смысл использовать именно его. Использование специализированного командлета облегчает чтение сценария, а созданный с его помощью объект ничем не отличается от объекта, созданного командлетом New-Object.

Теперь нужно инициализировать несколько переменных, начиная с переменной $currentTime, в которой будут храниться текущие дата и время. Текущую дату можно получить командлетом Get-Date:

$currentTime = get-Date

Затем присвоим начальные значения двум переменным, в которых будут храниться идентификаторы событий запуска и останова. Хотя этого можно и не делать, однако намного проще читать и искать ошибки в таком коде, в котором эти идентификаторы представлены символьными переменными.

Затем создается переменная $minutesInPeriod, содержащая количество минут в периоде расчета времени работы:

$minutesInPeriod = (24*60)*$NumberOfDays

И последней создается переменная $startingDate, которой присваивается объект System.DateTime, представляющий собой время начала отчетного периода. Эта дата рассчитывается как дата полночи начала периода:

$startingDate = (Get-Date -Hour 00 -Minute 00 -Second 00).adddays(-$numberOfDays)

После создания всех переменных необходимо извлечь события из журнала событий и сохранить результаты запроса в переменную $events. Для запроса к журналу событий используется командлет Get-EventLog с указанием названия журнала «system». В Windows PowerShell версии 2.0 можно было бы использовать параметр –source, чтобы сократить объем данных, фильтруемых через командлет Where-Object. Но в Windows PowerShell версии 1.0 такой возможности нет, поэтому приходится фильтровать все события, возвращенные запросом. События передаются по конвейеру командлету Where-Object, который и выбирает нужные записи журнала событий. Если вы посмотрите на фильтр Where-Object, то поймете, почему сценаристы рекомендуют для хранения параметров создавать переменные.

Команда читается гораздо лучше, чем при использовании строковых литералов. Идентификаторы событий, которые надо выбрать, равны или $startUpID, или $shutDownID. А значение свойства timeGenerated каждого события из журнала должно быть больше или равно значению $startingDate:

$events = Get-EventLog -LogName system |
Where-Object { $_.eventID -eq  $startUpID -OR $_.eventID -eq $shutDownID -and $_.TimeGenerated -ge $startingDate }

Имейте в виду, что все эти команды выполняются только локально. В Windows PowerShell версии 2.0 для удаленного исполнения команд можно будет использовать параметр –computerName.

Затем нужно создать упорядоченный список объектов. Почему, спросите вы? Потому что при последовательном переборе коллекции событий не гарантируется, что они будут встречаться в том же порядке, в котором происходили. Даже если полученные из журнала объекты передать по конвейеру командлету Sort-Object и сохранить результаты в переменной, выполняя итерацию по объектам и сохраняя результат в таблице хэш-кодов, нельзя быть уверенным, что упорядоченность результата сохранится в списке.

Чтобы обойти эти сложные в отладке проблемы, создадим экземпляр объекта System.Collections.SortedList, используя его конструктор по умолчанию. Конструктор по умолчанию определяет хронологический порядок для дат. Сохраним пустой упорядоченный список объектов в переменной $sortedList:

$sortedList = New-object system.collections.sortedlist

Созданный список объектов надо заполнить. Для этого с помощью оператора ForEach нужно перебрать все элементы коллекции событий журнала, сохраненной в переменной $entries. По мере перебора коллекции переменная $event указывает на текущий элемент. Методом add к объекту System.Collections.SortedList можно добавить новый элемент, который включает в себя два свойства: ключ и значение. (так же, как и к объекту Dictionary, за исключением того, что в списке к объектам можно обращаться по индексу, как в массиве). В качестве ключа будет использовано свойство события timegenerated, а в качестве величины — свойство eventID:

ForEach($event in $events)
{
 $sortedList.Add( $event.timeGenerated,
 $event.eventID )
} #end foreach event

Теперь рассчитаем время работы сервера. Чтобы это сделать, выберем последнее событие журнала в упорядоченном списке. У этого события всегда будет идентификатор 6005, потому что если последним является событие с идентификатором 6006, то сервер ещё не работает. Индекс самого последнего события равен count –1, потому что он считается от 0.

Чтобы получить значение времени, нужно найти соответствующий ключ в упорядоченном списке. Чтобы получить значение индекса, нужно вычесть единицу из значения свойства count. Затем вычтем из текущего времени, сохраненного в переменной $currenttime, то время, когда было создано событие с идентификатором 6005. В отладочном режиме результаты этих вычислений можно распечатать. Код показан здесь:

$uptime = $currentTime -
$sortedList.keys[$($sortedList.Keys.Count-1)]
Write-Debug "Current uptime $uptime"

Теперь можно перебрать упорядоченный список объектов и рассчитать время работы сервера. Использование списка System.Collections.Sorted позволяет обращаться к элементам списка по индексу, для чего и служит оператор for. Индексу присваивается значение count –2, так как count –1 уже использовался для вычисления текущего времени работы.

Для расчета времени работы список будет перебираться в обратном порядке, поэтому условием окончания цикла и вторым элементом оператора for будет «больше или равно нулю». В третьей позиции оператора используется оператор --, который уменьшает величину переменной цикла $item на единицу. Если запустить сценарий с параметром –debug, то командлетом Write-Debug будет выведено значение индекса. При выводе величины времени отступ табуляции можно определить символом `t. Вот соответствующая часть кода

For($item = $sortedList.Count-2 ; $item -ge
  0 ; $item--)
{
 Write-Debug "$item `t `t $($sortedList.
 GetByIndex($item)) `t `
   $($sortedList.Keys[$item])"

Если значение кода события равно 6005, то есть идентификатору события запуска, то значение времени работы рассчитывается вычитанием из времени, когда произошло это событие, времени предыдущего окончания работы. Это значение сохраняется в переменной $uptime. В отладочном режиме командлетом Write-Debug эти значения можно вывести на экран:

if($sortedList.GetByIndex($item) -eq $startUpID)
 {
  $uptime += ($sortedList.Keys[$item+1]
  - $sortedList.Keys[$item])
  Write-Debug "adding uptime. `t uptime is now: $uptime"
 } #end if
} #end for item

Наконец, можно создавать отчет. Название компьютера получается из переменной среды операционной системы «computer». Текущее время сохранено в переменной $startingdate. Время работы для данного периода выводится в минутах. Чтобы вывести число с двумя цифрами, используется описатель формата {0:n2}. Затем путем деления общего времени работы в минутах на продолжительность отчетного периода в минутах вычисляется процент простоя. Для вывода этого же значения с двумя десятичными знаками можно использовать этот же описатель формата. Можно вычислить долю времени работы и вывести обе величины:

"Total up time on $env:computername since $startingDate is " + "{0:n2}" -f `
  $uptime.TotalMinutes + " minutes."
$UpTimeMinutes = $Uptime.TotalMinutes
$percentDownTime = "{0:n2}" -f (100 - ($UpTimeMinutes/$minutesInPeriod)*100)
$percentUpTime = 100 - $percentDowntime
"$percentDowntime% downtime and $percentUpTime% uptime."

А теперь, сценаристы, вернемся к исходному вопросу: Когда работа — это простой? Теперь понятно, что, не зная времени простоя, нельзя ничего утверждать об общем времени работы. Если вам интересна эта тема, почитайте другие статьи в рубрике "Эй, сценаристы!" на TechNet или в Центре сценариев.



Проблемы с версиями

Тестируя сценарий CalculateSystemUptimeFromEventLog.ps1 на своем переносном компьютере, пишущий редактор Майкл Мерголо (Michael Murgolo) столкнулся с самой противной ошибкой. Я дал попробовать сценарий моему другу Джиту, и у него возникла та же ошибка. Какая же это ошибка? Вот она:

PS C:\> C:\fso\CalculateSystemUpTimeFromEventLog.ps1
Cannot index into a null array.
At C:\fso\CalculateSystemUpTimeFromEventLog.ps1:36 char:43 + $uptime =
$currentTime - $sortedList.keys[$ <<<< ($sortedList.Keys.Count-1)]
Total up time on LISBON since 09/02/2008 00:00:00 is 0.00 minutes.
100.00% downtime and 0% uptime.

Эта ошибка, "Cannot index into a null array" («Нельзя проиндексировать пустой массив»), указывает на то, что массив создался неправильно. Я проанализировал код, который создает массив:

ForEach($event in $events)
{
 $sortedList.Add( $event.timeGenerated,
 $event.eventID )
} #end foreach event

С кодом все было в порядке. Из-за чего тогда возникает ошибка?

Затем я внимательно изучил объект SortedList. Чтобы это сделать, я написал простой сценарий, который создает экземпляр класса System.Collections.SortedList и вносит в него какие-то данные. Чтобы распечатать ключи, я воспользовался свойством keys. Вот код:

$aryList = 1,2,3,4,5
$sl = New-Object Collections.SortedList
ForEach($i in $aryList)
{
 $sl.add($i,$i)
}

$sl.keys

На моем компьютере это нормально работает. На компьютере Джита происходил сбой. Скверная история. Но по крайней мере это направило меня в нужную сторону. Как оказалось, проблема в том, что в Windows PowerShell версии 1.0 в классе System.Collections.SortedList есть ошибка. А я запускал последнюю сборку Windows PowerShell версии 2.0, в которой эта ошибка уже исправлена, потому код и работал нормально.

Что же теперь делать с нашим сценарием? Выяснилось, что у класса SortedList есть метод GetKey, который нормально работает в обеих версиях Windows PowerShell. Поэтому для версии 1.0 сценарий нужно изменить так, чтобы вместо обхода всех ключей коллекции использовать метод GetKey. Сценарий для версии 2.0 помечен как требующий Windows PowerShell версии 2.0. Если запустить его на машине с версией Windows PowerShell 1.0, он просто завершится без ошибки.

Майкл указал еще и на то, что не является явной ошибкой, но что нужно принимать во внимание при проектировании. Он заметил, что сценарий неправильно определяет время работы в случае перевода компьютера в режим гибернации или в режим сна. Это так, потому что сценарий пропускает эти события.

Однако на практике я обычно не интересуюсь тем, сколько времени работает мой настольный или переносной компьютер. Важно, сколько времени работает сервер — а я еще не встречал серверов, которые переходят в режим гибернации или сна. Такое, конечно, возможно, и является интересным способом снижения расхода электричества центром данных, но пока я такого не встречал. Сообщите мне, если вы переводите свои серверы в режим гибернации. Мне можно писать на Scripter@Microsoft.com.

Автор: Крэйг Либендорфер  •  Иcточник: TechNet Magazine  •  Опубликована: 15.01.2009
Нашли ошибку в тексте? Сообщите о ней автору: выделите мышкой и нажмите CTRL + ENTER
Теги:   Powershell, скрипты.


Оценить статью:
Вверх
Комментарии посетителей
Комментарии отключены. С вопросами по статьям обращайтесь в форум.