From dac70ad8057add4ab74b993d9d7faea51cfbb759 Mon Sep 17 00:00:00 2001 From: Martin Lindhe Date: Fri, 26 Aug 2016 08:59:27 +0200 Subject: [PATCH] initial commit --- .gitignore | 1 + LICENSE | 21 ++++++++ README.md | 20 ++++++++ collectors/os.go | 128 ++++++++++++++++++++++++++++++++++++++++++++++ collectors/wmi.go | 5 ++ exporter.go | 75 +++++++++++++++++++++++++++ 6 files changed, 250 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 collectors/os.go create mode 100644 collectors/wmi.go create mode 100644 exporter.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..47efd875 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/wmi_exporter.exe diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..c75ca50f --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Martin Lindhe + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 00000000..1edc14ba --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +# WMI exporter + +Prometheus exporter for Windows machines, using the WMI (Windows Management Instrumentation). + + +## Status + +EXPERIMENTAL, use at your own risk! + + +## Collectors + +Name | Description +---------|------------- +os | Exposes Win32_OperatingSystem statistics (memory, processes, users) + + +## License + +Under [MIT](LICENSE) diff --git a/collectors/os.go b/collectors/os.go new file mode 100644 index 00000000..62466dd5 --- /dev/null +++ b/collectors/os.go @@ -0,0 +1,128 @@ +// returns data points from Win32_OperatingSystem + +package collectors + +import ( + "log" + + "github.com/StackExchange/wmi" + "github.com/prometheus/client_golang/prometheus" +) + +// A OSCollector is a Prometheus collector for WMI OperatingSystem metrics +type OSCollector struct { + FreePhysicalMemory *prometheus.Desc + FreeSpaceInPagingFiles *prometheus.Desc + FreeVirtualMemory *prometheus.Desc + NumberOfProcesses *prometheus.Desc + NumberOfUsers *prometheus.Desc +} + +// NewOSCollector ... +func NewOSCollector() *OSCollector { + + return &OSCollector{ + FreePhysicalMemory: prometheus.NewDesc( + prometheus.BuildFQName(wmiNamespace, "os", "free_physical_memory"), + "Free physical memory.", + nil, + nil, + ), + + FreeSpaceInPagingFiles: prometheus.NewDesc( + prometheus.BuildFQName(wmiNamespace, "os", "free_space_in_paging_files"), + "Free space in paging files.", + nil, + nil, + ), + + FreeVirtualMemory: prometheus.NewDesc( + prometheus.BuildFQName(wmiNamespace, "os", "free_virtual_memory"), + "Free virtual memory.", + nil, + nil, + ), + + NumberOfProcesses: prometheus.NewDesc( + prometheus.BuildFQName(wmiNamespace, "os", "number_of_processes"), + "No. of processes running on the system.", + nil, + nil, + ), + + NumberOfUsers: prometheus.NewDesc( + prometheus.BuildFQName(wmiNamespace, "os", "number_of_users"), + "No. of users logged in.", + nil, + nil, + ), + } +} + +// Collect sends the metric values for each metric +// to the provided prometheus Metric channel. +func (c *OSCollector) Collect(ch chan<- prometheus.Metric) { + if desc, err := c.collect(ch); err != nil { + log.Println("[ERROR] failed collecting process metrics:", desc, err) + return + } +} + +// Describe sends the descriptors of each metric over to the provided channel. +// The corresponding metric values are sent separately. +func (c *OSCollector) Describe(ch chan<- *prometheus.Desc) { + + ch <- c.FreePhysicalMemory + ch <- c.FreeSpaceInPagingFiles + ch <- c.FreeVirtualMemory + ch <- c.NumberOfProcesses + ch <- c.NumberOfUsers +} + +type Win32_OperatingSystem struct { + FreePhysicalMemory uint64 + FreeSpaceInPagingFiles uint64 + FreeVirtualMemory uint64 + NumberOfProcesses uint32 + NumberOfUsers uint32 +} + +func (c *OSCollector) collect(ch chan<- prometheus.Metric) (*prometheus.Desc, error) { + var dst []Win32_OperatingSystem + q := wmi.CreateQuery(&dst, "") + if err := wmi.Query(q, &dst); err != nil { + return nil, err + } + + ch <- prometheus.MustNewConstMetric( + c.FreePhysicalMemory, + prometheus.GaugeValue, + float64(dst[0].FreePhysicalMemory), + ) + + ch <- prometheus.MustNewConstMetric( + c.FreeSpaceInPagingFiles, + prometheus.GaugeValue, + float64(dst[0].FreeSpaceInPagingFiles), + ) + + ch <- prometheus.MustNewConstMetric( + c.FreeVirtualMemory, + prometheus.GaugeValue, + float64(dst[0].FreeVirtualMemory), + ) + + ch <- prometheus.MustNewConstMetric( + c.NumberOfProcesses, + prometheus.GaugeValue, + float64(dst[0].NumberOfProcesses), + ) + + ch <- prometheus.MustNewConstMetric( + c.NumberOfUsers, + prometheus.GaugeValue, + float64(dst[0].NumberOfUsers), + ) + + return nil, nil +} diff --git a/collectors/wmi.go b/collectors/wmi.go new file mode 100644 index 00000000..72552cb2 --- /dev/null +++ b/collectors/wmi.go @@ -0,0 +1,5 @@ +package collectors + +const ( + wmiNamespace = "wmi" +) diff --git a/exporter.go b/exporter.go new file mode 100644 index 00000000..3eb88926 --- /dev/null +++ b/exporter.go @@ -0,0 +1,75 @@ +package main + +import ( + "flag" + "log" + "net/http" + "sync" + + "github.com/martinlindhe/wmi_exporter/collectors" + "github.com/prometheus/client_golang/prometheus" +) + +// WmiExporter wraps all the WMI collectors and provides a single global +// exporter to extracts metrics out of. It also ensures that the collection +// is done in a thread-safe manner, the necessary requirement stated by +// prometheus. It also implements a prometheus.Collector interface in order +// to register it correctly. +type WmiExporter struct { + mu sync.Mutex + collectors []prometheus.Collector +} + +// Verify that the WmiExporter implements the prometheus.Collector interface. +var _ prometheus.Collector = &WmiExporter{} + +// NewWmiExporter creates an instance to WmiExporter and returns a reference +// to it. We can choose to enable a collector to extract stats out of by adding +// it to the list of collectors. +func NewWmiExporter() *WmiExporter { + return &WmiExporter{ + collectors: []prometheus.Collector{ + collectors.NewOSCollector(), + }, + } +} + +// Describe sends all the descriptors of the collectors included to +// the provided channel. +func (c *WmiExporter) Describe(ch chan<- *prometheus.Desc) { + for _, cc := range c.collectors { + cc.Describe(ch) + } +} + +// Collect sends the collected metrics from each of the collectors to +// prometheus. Collect could be called several times concurrently +// and thus its run is protected by a single mutex. +func (c *WmiExporter) Collect(ch chan<- prometheus.Metric) { + c.mu.Lock() + defer c.mu.Unlock() + + for _, cc := range c.collectors { + cc.Collect(ch) + } +} + +func main() { + var ( + addr = flag.String("telemetry.addr", ":9129", "host:port for WMI exporter") + metricsPath = flag.String("telemetry.path", "/metrics", "URL path for surfacing collected metrics") + ) + flag.Parse() + + prometheus.MustRegister(NewWmiExporter()) + + http.Handle(*metricsPath, prometheus.Handler()) + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, *metricsPath, http.StatusMovedPermanently) + }) + + log.Printf("Starting WMI exporter on %q", *addr) + if err := http.ListenAndServe(*addr, nil); err != nil { + log.Fatalf("cannot start WMI exporter: %s", err) + } +}