-
Notifications
You must be signed in to change notification settings - Fork 4.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
New module windows for metricbeat #3758
Changes from 11 commits
43ee390
569e7f4
a02bdc9
331a56f
30a1966
d287fa9
7e6b88c
9047b36
8ab1da4
793bc1b
24aa90d
6c01c1b
251bc9c
f670912
267bf17
88b0a79
6cadedf
dd2b539
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
#- module: windows | ||
# metricsets: ["perfmon"] | ||
# enabled: true | ||
# period: 10s | ||
# perfmon.counters: |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
== windows Module | ||
|
||
This is the windows Module. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
- key: windows | ||
title: "Windows" | ||
description: > | ||
[]beta | ||
Module for Windows | ||
fields: | ||
- name: windows | ||
type: group | ||
description: > | ||
fields: |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
/* | ||
Package windows is a Metricbeat module that contains MetricSets. | ||
*/ | ||
package windows |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
{ | ||
"@timestamp":"2016-05-23T08:05:34.853Z", | ||
"beat":{ | ||
"hostname":"beathost", | ||
"name":"beathost" | ||
}, | ||
"metricset":{ | ||
"host":"localhost", | ||
"module":"mysql", | ||
"name":"status", | ||
"rtt":44269 | ||
}, | ||
"windows":{ | ||
"perfmon":{ | ||
"example": "perfmon" | ||
} | ||
}, | ||
"type":"metricsets" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
=== windows perfmon MetricSet | ||
|
||
This is the perfmon metricset of the module windows. | ||
|
||
[float] | ||
=== Features and configuration | ||
|
||
Metricset to collect performance counters. | ||
Example configuration: | ||
``` | ||
- module: windows | ||
metricsets: ["perfmon"] | ||
enabled: true | ||
period: 10s | ||
perfmon.counters: | ||
- group: "processor" | ||
collectors: | ||
- alias: "processor_perfomance" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for sharing the event output: #3758 (comment) Based on the output I was thinking if we should this Do we need the "group" part, or could we just let the user define Other thing I'm thinking about is how far we can "align" the configs of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I need the group param to get the name. See here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As far as I understand the code, the group name is only needed to create the Event but not for the API request to fetch the event. If you use So my question is if it would also work, if the config would look as following:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's right. Group name is only for the event. To added to the alias field would be an option There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But you are right, it looks to complicated. I remove the field and built the event with the alias. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That is definitively an issue with my suggestion. The benefit is that the user as the full flexibility on where to put events and groups are not strictly required. Also there is less indentation yaml mess that can happen. If it is natural to have a group for the events and users kind of expect that in the result your approach definitively feels more natural. @andrewkroh Any thoughts? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would let you decide which is better because of your experience. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm OK with the current config approach. But I haven't tried adding additional counters yet. Let's get some people using it and see what kind of feedback we get. |
||
query: '\Processor Information(_Total)\% Processor Performance' | ||
- alias: "processor_time" | ||
query: '\Processor Information(_Total)\% Processor Time' | ||
- group: "disk" | ||
collectors: | ||
- alias: "bytes_written" | ||
query: '\FileSystem Disk Activity(_Total)\FileSystem Bytes Written' | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
- name: perfmon | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For dynamic metricsets, this will change so what we nomrally do is just define windows (module) part and leave the rest empty. Check the jmx metricset as an example. |
||
type: group | ||
fields: | ||
- name: counters | ||
type: group | ||
description: > | ||
Grouping of different counters | ||
fields: | ||
- name: group | ||
type: string | ||
description: > | ||
Name of the group. For example `processor` or `disk` | ||
|
||
- name: collectors | ||
type: group | ||
fields: | ||
- name: alias | ||
type: string | ||
description: > | ||
Short form for the query | ||
|
||
- name: query | ||
type: string | ||
description: > | ||
The query. For example `\\Processor Information(_Total)\\% Processor Performance`. Backslashes have to be escaped. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
// +build ignore | ||
|
||
package perfmon | ||
|
||
/* | ||
#include <windows.h> | ||
#include <stdio.h> | ||
#include <conio.h> | ||
#include <pdh.h> | ||
#include <pdhmsg.h> | ||
#cgo LDFLAGS: -lpdh | ||
*/ | ||
import "C" | ||
|
||
const ( | ||
ERROR_SUCCESS = C.ERROR_SUCCESS | ||
PDH_STATUS_VALID_DATA = C.PDH_CSTATUS_VALID_DATA | ||
PDH_STATUS_NEW_DATA = C.PDH_CSTATUS_NEW_DATA | ||
PDH_NO_DATA = C.PDH_NO_DATA | ||
PDH_STATUS_NO_OBJECT = C.PDH_CSTATUS_NO_OBJECT | ||
PDH_STATUS_NO_COUNTER = C.PDH_CSTATUS_NO_COUNTER | ||
PDH_STATUS_INVALID_DATA = C.PDH_CSTATUS_INVALID_DATA | ||
PDH_INVALID_HANDLE = C.PDH_INVALID_HANDLE | ||
PDH_INVALID_DATA = C.PDH_INVALID_DATA | ||
PDH_NO_MORE_DATA = C.PDH_NO_MORE_DATA | ||
PdhFmtDouble = C.PDH_FMT_DOUBLE | ||
PdhFmtLarge = C.PDH_FMT_LARGE | ||
PdhFmtLong = C.PDH_FMT_LONG | ||
) | ||
|
||
type PdhCounterValue C.PDH_FMT_COUNTERVALUE |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package perfmon | ||
|
||
const ( | ||
ERROR_SUCCESS = 0x0 | ||
PDH_STATUS_VALID_DATA = 0x0 | ||
PDH_STATUS_NEW_DATA = 0x1 | ||
PDH_NO_DATA = 0x800007d5 | ||
PDH_STATUS_NO_OBJECT = 0xc0000bb8 | ||
PDH_STATUS_NO_COUNTER = 0xc0000bb9 | ||
PDH_STATUS_INVALID_DATA = 0xc0000bba | ||
PDH_INVALID_HANDLE = 0xc0000bbc | ||
PDH_INVALID_DATA = 0xc0000bc6 | ||
PDH_NO_MORE_DATA = 0xc0000bcc | ||
PdhFmtDouble = 0x00000200 | ||
PdhFmtLarge = 0x00000400 | ||
PdhFmtLong = 0x00000100 | ||
) | ||
|
||
type PdhCounterValue struct { | ||
CStatus uint32 | ||
Pad_cgo_0 [4]byte | ||
LongValue int32 | ||
Pad_cgo_1 [4]byte | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package perfmon | ||
|
||
const ( | ||
ERROR_SUCCESS = 0x0 | ||
PDH_STATUS_VALID_DATA = 0x0 | ||
PDH_STATUS_NEW_DATA = 0x1 | ||
PDH_NO_DATA = 0x800007d5 | ||
PDH_STATUS_NO_OBJECT = 0xc0000bb8 | ||
PDH_STATUS_NO_COUNTER = 0xc0000bb9 | ||
PDH_STATUS_INVALID_DATA = 0xc0000bba | ||
PDH_INVALID_HANDLE = 0xc0000bbc | ||
PDH_INVALID_DATA = 0xc0000bc6 | ||
PDH_NO_MORE_DATA = 0xc0000bcc | ||
PdhFmtDouble = 0x00000200 | ||
PdhFmtLarge = 0x00000400 | ||
PdhFmtLong = 0x00000100 | ||
) | ||
|
||
type PdhCounterValue struct { | ||
CStatus uint32 | ||
Pad_cgo_0 [4]byte | ||
LongValue int32 | ||
Pad_cgo_1 [4]byte | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
package perfmon | ||
|
||
import ( | ||
"strconv" | ||
"unsafe" | ||
|
||
"time" | ||
|
||
"github.com/elastic/beats/libbeat/common" | ||
) | ||
|
||
type Handle struct { | ||
status error | ||
query uintptr | ||
counterType int | ||
counters []CounterGroup | ||
} | ||
|
||
type CounterGroup struct { | ||
GroupName string | ||
Counters []Counter | ||
} | ||
|
||
type Counter struct { | ||
counterName string | ||
counter uintptr | ||
counterPath string | ||
displayValue PdhCounterValue | ||
} | ||
|
||
var errorMapping = map[uint32]string{ | ||
PDH_INVALID_DATA: `PDH_INVALID_DATA`, | ||
PDH_INVALID_HANDLE: `PDH_INVALID_HANDLE`, | ||
PDH_NO_DATA: `PDH_NO_DATA`, | ||
PDH_NO_MORE_DATA: `PDH_NO_MORE_DATA`, | ||
PDH_STATUS_INVALID_DATA: `PDH_STATUS_INVALID_DATA`, | ||
PDH_STATUS_NEW_DATA: `PDH_STATUS_NEW_DATA`, | ||
PDH_STATUS_NO_COUNTER: `PDH_STATUS_NO_COUNTER`, | ||
PDH_STATUS_NO_OBJECT: `PDH_STATUS_NO_OBJECT`, | ||
} | ||
|
||
func GetHandle(config []CounterConfig) (*Handle, uint32) { | ||
q := &Handle{} | ||
err := _PdhOpenQuery(0, 0, &q.query) | ||
if err != ERROR_SUCCESS { | ||
return nil, err | ||
} | ||
|
||
counterGroups := make([]CounterGroup, len(config)) | ||
q.counters = counterGroups | ||
|
||
for i, v := range config { | ||
counterGroups[i] = CounterGroup{GroupName: v.Name, Counters: make([]Counter, len(v.Group))} | ||
for j, v1 := range v.Group { | ||
counterGroups[i].Counters[j] = Counter{counterName: v1.Alias, counterPath: v1.Query} | ||
err := _PdhAddCounter(q.query, counterGroups[i].Counters[j].counterPath, 0, &counterGroups[i].Counters[j].counter) | ||
if err != ERROR_SUCCESS { | ||
return nil, err | ||
} | ||
} | ||
} | ||
|
||
return q, 0 | ||
} | ||
|
||
func (q *Handle) ReadData(firstFetch bool) (common.MapStr, uint32) { | ||
|
||
err := _PdhCollectQueryData(q.query) | ||
|
||
if firstFetch { | ||
// Most counters require two sample values in order to compute a displayable value. So wait and then collect the second value | ||
time.Sleep(2000) | ||
err = _PdhCollectQueryData(q.query) | ||
} | ||
|
||
if err != ERROR_SUCCESS { | ||
return nil, err | ||
} | ||
|
||
result := common.MapStr{} | ||
|
||
for _, v := range q.counters { | ||
groupVal := make(map[string]interface{}) | ||
for _, v1 := range v.Counters { | ||
err := _PdhGetFormattedCounterValue(v1.counter, PdhFmtDouble, q.counterType, &v1.displayValue) | ||
if err != ERROR_SUCCESS { | ||
return nil, err | ||
} | ||
doubleValue := (*float64)(unsafe.Pointer(&v1.displayValue.LongValue)) | ||
groupVal[v1.counterName] = *doubleValue | ||
|
||
} | ||
result[v.GroupName] = groupVal | ||
} | ||
return result, 0 | ||
} | ||
|
||
func CloseQuery(q uintptr) uint32 { | ||
err := _PdhCloseQuery(q) | ||
if err != ERROR_SUCCESS { | ||
return err | ||
} | ||
|
||
return 0 | ||
} | ||
|
||
func GetError(err uint32) string { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are close to defining your own error type here. Consider doing something like this:
Change Any place you have a uint32 error code you can cast it like |
||
if val, ok := errorMapping[err]; ok { | ||
return val | ||
} | ||
return strconv.FormatUint(uint64(err), 10) | ||
} | ||
|
||
//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output syscall_windows.go pdh.go | ||
// Windows API calls | ||
//sys _PdhOpenQuery(dataSource uintptr, userData uintptr, query *uintptr) (err uint32) = pdh.PdhOpenQuery | ||
//sys _PdhAddCounter(query uintptr, counterPath string, userData uintptr, counter *uintptr) (err uint32) = pdh.PdhAddEnglishCounterW | ||
//sys _PdhCollectQueryData(query uintptr) (err uint32) = pdh.PdhCollectQueryData | ||
//sys _PdhGetFormattedCounterValue(counter uintptr, format uint32, counterType int, value *PdhCounterValue) (err uint32) = pdh.PdhGetFormattedCounterValue | ||
//sys _PdhCloseQuery(query uintptr) (err uint32) = pdh.PdhCloseQuery |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
//+build integration windows | ||
|
||
package perfmon | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestExistingCounter(t *testing.T) { | ||
config := make([]CounterConfig, 1) | ||
config[0].Name = "process" | ||
config[0].Group = make([]CounterConfigGroup, 1) | ||
config[0].Group[0].Alias = "processor_performance" | ||
config[0].Group[0].Query = "\\Processor Information(_Total)\\% Processor Performance" | ||
handle, err := GetHandle(config) | ||
|
||
assert.Empty(t, err) | ||
|
||
err = CloseQuery(handle.query) | ||
|
||
assert.Empty(t, err) | ||
} | ||
|
||
func TestNonExistingCounter(t *testing.T) { | ||
config := make([]CounterConfig, 1) | ||
config[0].Name = "process" | ||
config[0].Group = make([]CounterConfigGroup, 1) | ||
config[0].Group[0].Alias = "processor_performance" | ||
config[0].Group[0].Query = "\\Processor Information(_Total)\\not existing counter" | ||
handle, err := GetHandle(config) | ||
|
||
assert.Equal(t, 3221228473, int(err)) | ||
|
||
if handle != nil { | ||
err = CloseQuery(handle.query) | ||
|
||
assert.Empty(t, err) | ||
} | ||
} | ||
|
||
func TestNonExistingObject(t *testing.T) { | ||
config := make([]CounterConfig, 1) | ||
config[0].Name = "process" | ||
config[0].Group = make([]CounterConfigGroup, 1) | ||
config[0].Group[0].Alias = "processor_performance" | ||
config[0].Group[0].Query = "\\non existing object\\% Processor Performance" | ||
handle, err := GetHandle(config) | ||
|
||
assert.Equal(t, 3221228472, int(err)) | ||
|
||
if handle != nil { | ||
err = CloseQuery(handle.query) | ||
|
||
assert.Empty(t, err) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please mark this as beta: https://github.com/elastic/beats/blob/master/metricbeat/module/jolokia/_meta/fields.yml#L4