我将性能计数器的值发送到 Graphite 的方法非常慢。瓶颈是啥?以及如何改进?

Posted

技术标签:

【中文标题】我将性能计数器的值发送到 Graphite 的方法非常慢。瓶颈是啥?以及如何改进?【英文标题】:My method to send values of performance counters to Graphite is very slow. What is the bottleneck? And how to improve?我将性能计数器的值发送到 Graphite 的方法非常慢。瓶颈是什么?以及如何改进? 【发布时间】:2014-01-13 15:51:50 【问题描述】:

下面我有一些代码来获取性能计数器实例的值(一旦访问页面就会实例化)并将它们发送到 Graphite 以显示以下格式的图形:

[Path in Graphite (e.g., metric.pages.Counter1)] [value of counter] [epoch time]

为此,我编写了以下代码,其中 writer 配置正确:

# Get all paths to MultipleInstance counters and averages that start with "BLABLA" and 
# put them into an array and get the epoch time 
$pathsWithInstances = (get-counter -ListSet BLABLA*) | select -ExpandProperty PathsWithInstances
$epochtime = [int][double]::Parse((Get-Date -UFormat %s))

# This functions splits the path (e.g., \BLABLA Web(welcome)\Page Requests) into three 
# parts: the part before the 
# opening brace (the CounterCategory, e.g., "\BLABLA Web"), the part in between the braces 
# (the page or 
# service, e.g., "welcome"), and the part after the closing brace (the name of the test, 
# e.g., 
# "\Page Requests"). We obtain the metric out of this information and send it to 
# Graphite.
enter code here
foreach ($pathWithInstance in $pathsWithInstances)
   
    $instanceProperties = $pathWithInstance.Split('()')
    $counterCategory = $instanceProperties[0]

    if ($counterCategory -eq ("\BLABLA Web") )
    
        # Replace the * with nothing so that counters that are used to display the 
        # average (e.g., \BLABLAWeb(*)\Page Requests) are displayed on top in the  
        # Graphite directory.

        $pagePath = $instanceProperties[1].Replace('*','')
        $nameOfTheTest = $instanceProperties[2]

        # Countername which is used in Graphite path gets whitespace and backslash 
        # removed in the name used for the path in Graphite (naming conventions)
        $counterName = $nameOfTheTest.Replace(' ','').Replace('\','')
        $pathToPerfCounter = $pathWithInstance
        $pathInGraphite = "metrics.Pages." + $pagePath + $counterName

        #Invoked like this since otherwise the get-counter [path] does not seem to work
       $metricValue = [int] ((Get-Counter "$pathToPerfCounter").countersamples | select -
             property cookedvalue).cookedvalue           
        $metric = ($pathInGraphite + " " + $metricValue +  " " + $epochTime)

        $writer.WriteLine($metric) 
        $writer.Flush() 

    


不幸的是,这段代码非常慢。每个计数器发送一个值大约需要一秒钟。有人知道它为什么这么慢以及如何改进吗?

【问题讨论】:

【参考方案1】:

您一次获取一个计数器,Get-Counter 需要一秒钟来获取并“烹饪”这些值。 Get-Counter 将接受一个计数器数组,并将采样、“烹饪”并在同一秒内将它们全部返回。您可以通过一次全部采样,然后从结果数组中解析值来加快速度:

$CounterPaths = (
 '\\Server1\Memory\Page Faults/sec',
 '\\Server1\Memory\Available Bytes'
 )


(Measure-Command 
foreach ($CounterPath in $CounterPaths)
Get-Counter -counter $counterpath
).TotalMilliseconds

(Measure-Command 
 Get-Counter $CounterPaths
 ).TotalMilliseconds


2017.4693
1012.3012

例子:

foreach ($CounterSample in (Get-Counter $CounterPaths).Countersamples)

  "Path = $($CounterSample.path)"
  "Metric = $([int]$CounterSample.CookedValue)"


Path = \\Server1\memory\page faults/sec
Metric = 193
Path = \\Server1\memory\available bytes
Metric = 1603678208

【讨论】:

嗯嗯,我只需要名称中带有BLABLA 的计数器...我什么时候进行解析? 这似乎是 真正的 解决方案。我会尽力的。 是否可以使用其他东西然后煮熟的价值来加速它? 您的 $pathInGraphite 似乎是从计数器路径名称派生的,因此您只需要从每个样本中获取该名称和指标的熟值即可。您可以通过循环进行解析以读取示例,或者您可以预先进行解析,创建一个哈希表以将计数器路径名称与 Graphite 路径名称相关联。 它适用于我,您可以在示例代码中替换您自己的计算机名称来验证它。【参考方案2】:

使用Start-Job cmdlet,为每个计数器创建单独的线程。

这是一个简单的例子,说明如何获取计数器路径并将它们传递到异步 ScriptBlock:

$CounterPathList = (Get-Counter -ListSet Processor).PathsWithInstances.Where( $PSItem -like '*% Processor Time' );

foreach ($CounterPath in $CounterPathList) 
    Start-Job -ScriptBlock  (Get-Counter -Counter $args[0]).CounterSamples.CookedValue;  -ArgumentList $CounterPath;


# Call Receive-Job down here, once all jobs are finished

重要提示:上面的示例使用 PowerShell 4.0 版的“方法语法”来过滤对象。请确保您运行的是 PowerShell 4.0 版,或将 Where 方法更改为使用传统的 Where-Object

【讨论】:

在您的 foreach 循环中,不要简单地执行每个计数器的代码,而是将命令包装在 Start-Job 中。如果你有很多柜台,这应该会加快这个过程。 在 if 语句中(我在实际代码中有两个 if 语句来检查类别名称)还是在 foreach 循环中? 你可以把它放在任何一个地方,这取决于你想在哪里进行处理。传递给Start-Job cmdlet 的ScriptBlock 可以解析计数器路径,或者您可以在“主”线程中进行解析,然后将计数器传递给ScriptBlock 以进行检索并提交给Graphite。 嘿,我刚刚发布了一个示例,向您展示了如何使用-ArgumentList 参数将计数器路径传递到ScriptBlock 一位同事正在使用CounterCategory 类和GetInstance 然后循环遍历它们,这样更快。可能也可以使用这个类。

以上是关于我将性能计数器的值发送到 Graphite 的方法非常慢。瓶颈是啥?以及如何改进?的主要内容,如果未能解决你的问题,请参考以下文章

石墨:显示与先前值的变化

使用刷新对象收集 wmi 性能计数器

如何使用 Graphite 比较累积计数器与最佳、平均和最差?

石墨——如何将数据发送到时间戳超过一年的碳

如何将 RDS 性能洞察的计数器指标发送到 cloudwatch 和 Grafana

Graphite如何根据选定的间隔进行汇总