From c0b133d1576b7ffd51af52f85da51567ecd718a0 Mon Sep 17 00:00:00 2001 From: Calle Pettersson Date: Sat, 24 Sep 2016 11:49:09 +0200 Subject: [PATCH] Add a collector generator script --- .gitignore | 2 +- tools/collector-generator/New-Collector.ps1 | 18 ++++++ tools/collector-generator/README.md | 17 +++++ tools/collector-generator/collector.template | 62 +++++++++++++++++++ .../collector-generator/generate-collector.go | 60 ++++++++++++++++++ 5 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 tools/collector-generator/New-Collector.ps1 create mode 100644 tools/collector-generator/README.md create mode 100644 tools/collector-generator/collector.template create mode 100644 tools/collector-generator/generate-collector.go diff --git a/.gitignore b/.gitignore index 47efd8754..b883f1fdc 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -/wmi_exporter.exe +*.exe diff --git a/tools/collector-generator/New-Collector.ps1 b/tools/collector-generator/New-Collector.ps1 new file mode 100644 index 000000000..a07711986 --- /dev/null +++ b/tools/collector-generator/New-Collector.ps1 @@ -0,0 +1,18 @@ +Param( + [Parameter(Mandatory=$true)] + $Class, + [Parameter(Mandatory=$false)] + $CollectorName = ($Class -replace 'Win32_PerfRawData_Perf','') +) +$members = Get-WMIObject $Class ` + | Get-Member -MemberType Properties ` + | Where-Object { $_.Definition -Match '^u?int' -and $_.Name -NotMatch '_' } ` + | Select-Object Name, @{Name="Type";Expression={$_.Definition.Split(" ")[0]}} +$input = @{ + "Class"=$Class; + "CollectorName"=$CollectorName; + "Members"=$members +} | ConvertTo-Json +$outFileName = "..\..\collector\$CollectorName.go".ToLower() +$input | .\collector-generator.exe | Out-File -NoClobber -Encoding UTF8 $outFileName +go fmt $outFileName diff --git a/tools/collector-generator/README.md b/tools/collector-generator/README.md new file mode 100644 index 000000000..3dbe1491e --- /dev/null +++ b/tools/collector-generator/README.md @@ -0,0 +1,17 @@ +# Collector generator +Generates a collector skeleton implementation from a WMI class. + +## Usage +Build the generator: + +```bash +go build . +``` + +Run the script to query the WMI service and send the output to the generator: + +```powershell +.\New-Collector.ps1 -Class Win32_PerfRawData_PerfOS_Processor +``` + +This will generate a collector. The collector name is generated by first removing `Win32_PerfRawData_Perf` and lower-casing, so `Win32_PerfRawData_PerfOS_Processor` will generate `os_processor.go`. This can be overridden by passing `-CollectorName` to the script. diff --git a/tools/collector-generator/collector.template b/tools/collector-generator/collector.template new file mode 100644 index 000000000..fd31f843c --- /dev/null +++ b/tools/collector-generator/collector.template @@ -0,0 +1,62 @@ +// returns data points from {{ .Class }} +// - {{ .Class }} class +package collector +import ( + "log" + + "github.com/StackExchange/wmi" + "github.com/prometheus/client_golang/prometheus" +) +func init() { + Factories["{{ .CollectorName | toLower }}"] = New{{ .CollectorName }}Collector +} +// A {{ .CollectorName }}Collector is a Prometheus collector for WMI {{ .Class }} metrics +type {{ .CollectorName }}Collector struct { +{{- range $m := .Members }} + {{ $m.Name }} *prometheus.Desc +{{- end }} +} +// New{{ .CollectorName }}Collector ... +func New{{ .CollectorName }}Collector() (Collector, error) { + const subsystem = "{{ .CollectorName | toLower }}" + return &{{ .CollectorName }}Collector{ +{{- range $m := .Members }} + {{ $m.Name }}: prometheus.NewDesc( + prometheus.BuildFQName(Namespace, subsystem, "{{ $m.Name | toSnakeCase }}"), + "({{ $m.Name }})", + nil, + nil, + ), +{{- end }} + }, nil +} +// Collect sends the metric values for each metric +// to the provided prometheus Metric channel. +func (c *{{ .CollectorName }}Collector) Collect(ch chan<- prometheus.Metric) error { + if desc, err := c.collect(ch); err != nil { + log.Println("[ERROR] failed collecting {{ .CollectorName | toLower }} metrics:", desc, err) + return err + } + return nil +} +type {{ .Class }} struct { + Name string +{{ range $m := .Members }} + {{ $m.Name }} {{ $m.Type }} +{{- end }} +} +func (c *{{ .CollectorName }}Collector) collect(ch chan<- prometheus.Metric) (*prometheus.Desc, error) { + var dst []{{ .Class }} + q := wmi.CreateQuery(&dst, "") + if err := wmi.Query(q, &dst); err != nil { + return nil, err + } + {{ range $m := .Members }} + ch <- prometheus.MustNewConstMetric( + c.{{ $m.Name }}, + prometheus.GaugeValue, + float64(dst[0].{{ $m.Name }}), + ) + {{ end }} + return nil, nil +} diff --git a/tools/collector-generator/generate-collector.go b/tools/collector-generator/generate-collector.go new file mode 100644 index 000000000..1c929aca9 --- /dev/null +++ b/tools/collector-generator/generate-collector.go @@ -0,0 +1,60 @@ +package main + +import ( + "encoding/json" + "io/ioutil" + "os" + "strings" + "text/template" + "unicode" +) + +type TemplateData struct { + CollectorName string + Class string + Members []Member +} +type Member struct { + Name string + Type string +} + +func main() { + bytes, err := ioutil.ReadAll(os.Stdin) + if err != nil { + panic(err) + } + var data TemplateData + if err := json.Unmarshal(bytes, &data); err != nil { + panic(err) + } + + funcs := template.FuncMap{ + "toLower": strings.ToLower, + "toSnakeCase": toSnakeCase, + } + template, err := template.New("template").Funcs(funcs).ParseFiles("collector.template") + if err != nil { + panic(err) + } + err = template.ExecuteTemplate(os.Stdout, "collector.template", data) + if err != nil { + panic(err) + } +} + +// https://gist.github.com/elwinar/14e1e897fdbe4d3432e1 +func toSnakeCase(in string) string { + runes := []rune(in) + length := len(runes) + + var out []rune + for i := 0; i < length; i++ { + if i > 0 && unicode.IsUpper(runes[i]) && ((i+1 < length && unicode.IsLower(runes[i+1])) || unicode.IsLower(runes[i-1])) { + out = append(out, '_') + } + out = append(out, unicode.ToLower(runes[i])) + } + + return string(out) +}