Powershell Get-Counter 问题及 Powershell 的Group by Average实现
目前生产环境是使用Powershell做性能监控的,但是发现使用Get-Counter获取IIS的 \Web Service(_total)\Total Method Requests/sec 时经常取到的值为0,但实际这个时候IIS的请求数并非是0(这点可以通过打开windows的性能计数器来进行对比)。相信很多人有碰到这样的情况,我们也就这个问题咨询过微软,微软给的答复是:性能计数器默认只有部分是打开的,而对于IIS的Total Method Requests/sec则默认是关闭的。当我们主动去获取时它这个性能计数器的值是它开会打开进行数据收集,而这个收集过程需要一小段时间后才能生效(时间不定,但根据经验来看可能需要1-2秒间,有时会小于1秒)。因为使用Get-Counter的获取时间很短暂,所以在执行完Get-Counter后该性能计数器打开并开始收集,但Get-Counter在还没等底层性能计数器收集完就已经返回了该值,所以这个值就经常会出现0。
为了解决这个问题可以采用Get-Counter的-MaxSamples和-SampleInterval来解决,方式如下:
Get-Counter "\Web Service(_total)\Total Method Requests/sec" -MaxSamples 3 -SampleInterval 2
MaxSamples:连续收集多次
SampleInterval:每次收集间隔(秒)
其实一般情况下使用-MaxSamples就足够了,SampleInterval是为了防止底层数据的收集慢于1秒。
使用上面的语句可以很明显看到,返回的结果中第一条经常为0,而后续数据才是正常的。那么我们可能会希望把这个0的数据过滤掉:
Get-Counter "\Web Service(_total)\Total Method Requests/sec" -MaxSamples 3 -SampleInterval 2 | Where-Object {$_.CookedValue -gt 0}
对于目前我们进行收集的脚步是这样的:
$counterResult = Invoke-Command -Session $session -ScriptBlock { param($args) $result = Get-Counter -Counter $args -SampleInterval 2 $result.Timestamp $result.CounterSamples } -ArgumentList ("","\Web Service(_total)\Total Method Requests/sec", "\Processor(_total)\% Processor Time")
这个脚本进行性能计数器的收集,每次只收集一条,并在后续进行处理后插入到数据库中进行分析,可当我们讲MaxSamples添加进去的时候返回的便是多个数据集。这样的话对于后续的处理很有难度,已经我们希望这里返回的数据集依然是每个性能计数器一条记录,并且他需要排除0的情况以及在多次收集的时候进行平均计算。(这里为了测试所以增加了一个CPU的性能计数器)
所以我们需要对上面的这段脚本进行改造,改造如下:
$counterResult = Invoke-Command -Session $session -ScriptBlock {param($args); $result = Get-Counter -Counter $args -MaxSamples 3; ($result | Sort-Object Timestamp -Descending | Select-Object -First 1).Timestamp; $result | ForEach-Object { $_.CounterSamples } | Where-Object {$_.CookedValue -gt 0} | Group-Object Path, InstanceName ` | %{ New-Object PSObject -Property @{ Path = ($_.Group | Select-Object -First 1).Path InstanceName = ($_.Group | Select-Object -First 1).InstanceName CookedValue = ($_.Group | Measure-Object CookedValue -Average).Average } }; } -ArgumentList ("","\Web Service(_total)\Total Method Requests/sec", "\Processor(_total)\% Processor Time")
改造后的代码有点复杂,我们先来看下在实现这段代码前做的测试:
# 首先,连接到服务器上,执行Get-Counter $result = Get-Counter ("\Web Service(_total)\Total Method Requests/sec", "\Processor(_total)\% Processor Time") -MaxSamples 3 $result # 返回结果如下,这是我们的测试样本: Timestamp CounterSamples --------- -------------- 2012/8/3 19:44:48 \\w-7087-sns\web service(_total)\total method requests/sec : 0 \\w-7087-sns\processor(_total)\% processor time : 3.46108853061208 2012/8/3 19:44:49 \\w-7087-sns\web service(_total)\total method requests/sec : 220.905233511705 \\w-7087-sns\processor(_total)\% processor time : 1.92261018891783 2012/8/3 19:44:50 \\w-7087-sns\web service(_total)\total method requests/sec : 211.058777806759 \\w-7087-sns\processor(_total)\% processor time : 4.61494249812969 # 排除时间,我们处理出CounterSamples中的数据,并使用Where-Object排除了CookedValue为0的记录 $result | ForEach-Object { $_.CounterSamples } | Where-Object {$_.CookedValue -gt 0} # 结果如下: Path InstanceName CookedValue ---- ------------ ----------- \\w-7087-sns\processor(_total)\% pro... _total 7.3072651350323 \\w-7087-sns\web service(_total)\tot... _total 254.469941283377 \\w-7087-sns\processor(_total)\% pro... _total 1.73030281901895 \\w-7087-sns\web service(_total)\tot... _total 203.141900641814 # 我们将这个结果进行分组,这里可以得到分组的数据 $result | ForEach-Object { $_.CounterSamples } | Where-Object {$_.CookedValue -gt 0} | Group-Object Path, InstanceName # 结果如下: Count Name Group ----- ---- ----- 2 \\w-7087-sns\processor... {Microsoft.PowerShell.Commands.GetCounter.PerformanceCounterSample, Microsoft.PowerS... 2 \\w-7087-sns\web servi... {Microsoft.PowerShell.Commands.GetCounter.PerformanceCounterSample, Microsoft.PowerS... # 这里的话分组条件会变成Name,这里的Name是由Path + InstanceName 组成的,Counter为分组的记录数,而Group中存储的是分组前的原数据,这正是我们需要的数据 # 我们可以看下Group中的数据是怎么样的 $result | ForEach-Object { $_.CounterSamples } | Where-Object {$_.CookedValue -gt 0} | Group-Object Path, InstanceName | ForEach-Object { $_.Group; "---------" } # 结果如下: Path InstanceName CookedValue ---- ------------ ----------- \\w-7087-sns\processor(_total)\% pro... _total 7.3072651350323 \\w-7087-sns\processor(_total)\% pro... _total 1.73030281901895 --------- \\w-7087-sns\web service(_total)\tot... _total 254.469941283377 \\w-7087-sns\web service(_total)\tot... _total 203.141900641814 --------- # 我们可以看到,我们的数据已经按我们的分组条件区分好了,接下来我们就要对其进行计算。 # 这里我们先介绍下结算函数 Measure-Object , 我们那上面的数据来演示下Powershell中的计算函数 $result | ForEach-Object { $_.CounterSamples } | Where-Object {$_.CookedValue -gt 0} | Measure-Object CookedValue -Average -Maximum -Sum -Minimum # 结果如下, Count : 4 Average : 116.662352469811 Sum : 466.649409879243 Maximum : 254.469941283377 Minimum : 1.73030281901895 Property : CookedValue # 上面就是我们使用Measure-Object对CookedValue进行-Average(平均) -Maximum(最大) -Sum(合计) -Minimum(最小)计算后的结果 # 如果我们只要某个值只需: ($result | ForEach-Object { $_.CounterSamples } | Where-Object {$_.CookedValue -gt 0} | Measure-Object CookedValue -Average -Maximum -Sum -Minimum).Average # 因为数据集经过Measure-Object计算后只剩下结果集,所以我们需要对其进行结果的处理,这里使用了%{},实际上%{}基本等同于ForEach-Object{},这里他们可以相互替换 # 结果集经过Group-Object进行分组后我们对其结果集重新组合(Group-Object后的数据见前面) # %{}执行New-Object创建了一个PSObject类型的对象,这个对象中有Property(属性、也就是数据集中的字段) # 其中Path字段取Group-Object中Group字段的第一个Path,InstanceName也是如此 # CookedValue是对Group字段中的每个数据集进行Measure-Object计算后的平均值 $result | ForEach-Object { $_.CounterSamples } | Where-Object {$_.CookedValue -gt 0} | Group-Object Path, InstanceName ` | % { New-Object PSObject -Property @{ Path = ($_.Group | Select-Object -First 1).Path InstanceName = ($_.Group | Select-Object -First 1).InstanceName CookedValue = ($_.Group | Measure-Object CookedValue -Average).Average } } # 结果如下: CookedValue InstanceName Path ----------- ------------ ---- 4.51878397702563 _total \\w-7087-sns\processor(_total)\% pro... 228.805920962596 _total \\w-7087-sns\web service(_total)\tot... # 我们可以看到这个结果已经是我们想要的数据了,我对上面的这段代码组装后就得到我们改造后的代码段了。
到这里所有的测试都已经完成,相信大家上面改造后的代码段是怎么来的了吧。
希望这篇文章对你有帮助。
参考:http://stackoverflow.com/questions/5999930/powershell-how-to-sum-multiple-items-in-an-object