diff --git a/.gitignore b/.gitignore
index 21f99f37..b9c70563 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,4 @@
*.exe
-VERSION
\ No newline at end of file
+VERSION
+*.swp
+*.un~
diff --git a/README.md b/README.md
index e6aff4be..f7462059 100644
--- a/README.md
+++ b/README.md
@@ -22,6 +22,7 @@ process | [Win32_PerfRawData_PerfProc_Process](https://msdn.microsoft.com/en-us/
service | [Win32_Service](https://msdn.microsoft.com/en-us/library/aa394418(v=vs.85).aspx) metrics (service states) | ✓
system | Win32_PerfRawData_PerfOS_System metrics (system calls) | ✓
tcp | [Win32_PerfRawData_Tcpip_TCPv4](https://msdn.microsoft.com/en-us/library/aa394341(v=vs.85).aspx) metrics (tcp connections) |
+textfile | Read prometheus metrics from a text file | ✓
vmware | Performance counters installed by the Vmware Guest agent |
The HELP texts shows the WMI data source, please see MSDN documentation for details.
@@ -39,6 +40,7 @@ Name | Description
`LISTEN_ADDR` | The IP address to bind to. Defaults to 0.0.0.0
`LISTEN_PORT` | The port to bind to. Defaults to 9182.
`METRICS_PATH` | The path at which to serve metrics. Defaults to `/metrics`
+`TEXTFILE_DIR` | As the `--collector.textfile.directory` flag, provide a directory to read text files with metrics from
Parameters are sent to the installer via `msiexec`. Example invocation:
diff --git a/collector/textfile.go b/collector/textfile.go
new file mode 100644
index 00000000..2a9fd673
--- /dev/null
+++ b/collector/textfile.go
@@ -0,0 +1,255 @@
+// Copyright 2015 The Prometheus Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// +build !notextfile
+
+package collector
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "sort"
+ "strings"
+ "time"
+
+ "github.com/prometheus/client_golang/prometheus"
+ dto "github.com/prometheus/client_model/go"
+ "github.com/prometheus/common/expfmt"
+ "github.com/prometheus/common/log"
+ kingpin "gopkg.in/alecthomas/kingpin.v2"
+)
+
+var (
+ textFileDirectory = kingpin.Flag(
+ "collector.textfile.directory",
+ "Directory to read text files with metrics from.",
+ ).Default("C:\\Program Files\\wmi_exporter\\textfile_inputs").String()
+
+ mtimeDesc = prometheus.NewDesc(
+ "wmi_textfile_mtime_seconds",
+ "Unixtime mtime of textfiles successfully read.",
+ []string{"file"},
+ nil,
+ )
+)
+
+type textFileCollector struct {
+ path string
+ // Only set for testing to get predictable output.
+ mtime *float64
+}
+
+func init() {
+ Factories["textfile"] = NewTextFileCollector
+}
+
+// NewTextFileCollector returns a new Collector exposing metrics read from files
+// in the given textfile directory.
+func NewTextFileCollector() (Collector, error) {
+ return &textFileCollector{
+ path: *textFileDirectory,
+ }, nil
+}
+
+func convertMetricFamily(metricFamily *dto.MetricFamily, ch chan<- prometheus.Metric) {
+ var valType prometheus.ValueType
+ var val float64
+
+ allLabelNames := map[string]struct{}{}
+ for _, metric := range metricFamily.Metric {
+ labels := metric.GetLabel()
+ for _, label := range labels {
+ if _, ok := allLabelNames[label.GetName()]; !ok {
+ allLabelNames[label.GetName()] = struct{}{}
+ }
+ }
+ }
+
+ for _, metric := range metricFamily.Metric {
+ if metric.TimestampMs != nil {
+ log.Warnf("Ignoring unsupported custom timestamp on textfile collector metric %v", metric)
+ }
+
+ labels := metric.GetLabel()
+ var names []string
+ var values []string
+ for _, label := range labels {
+ names = append(names, label.GetName())
+ values = append(values, label.GetValue())
+ }
+
+ for k := range allLabelNames {
+ present := false
+ for _, name := range names {
+ if k == name {
+ present = true
+ break
+ }
+ }
+ if present == false {
+ names = append(names, k)
+ values = append(values, "")
+ }
+ }
+
+ metricType := metricFamily.GetType()
+ switch metricType {
+ case dto.MetricType_COUNTER:
+ valType = prometheus.CounterValue
+ val = metric.Counter.GetValue()
+
+ case dto.MetricType_GAUGE:
+ valType = prometheus.GaugeValue
+ val = metric.Gauge.GetValue()
+
+ case dto.MetricType_UNTYPED:
+ valType = prometheus.UntypedValue
+ val = metric.Untyped.GetValue()
+
+ case dto.MetricType_SUMMARY:
+ quantiles := map[float64]float64{}
+ for _, q := range metric.Summary.Quantile {
+ quantiles[q.GetQuantile()] = q.GetValue()
+ }
+ ch <- prometheus.MustNewConstSummary(
+ prometheus.NewDesc(
+ *metricFamily.Name,
+ metricFamily.GetHelp(),
+ names, nil,
+ ),
+ metric.Summary.GetSampleCount(),
+ metric.Summary.GetSampleSum(),
+ quantiles, values...,
+ )
+ case dto.MetricType_HISTOGRAM:
+ buckets := map[float64]uint64{}
+ for _, b := range metric.Histogram.Bucket {
+ buckets[b.GetUpperBound()] = b.GetCumulativeCount()
+ }
+ ch <- prometheus.MustNewConstHistogram(
+ prometheus.NewDesc(
+ *metricFamily.Name,
+ metricFamily.GetHelp(),
+ names, nil,
+ ),
+ metric.Histogram.GetSampleCount(),
+ metric.Histogram.GetSampleSum(),
+ buckets, values...,
+ )
+ default:
+ log.Errorf("unknown metric type for file")
+ continue
+ }
+ if metricType == dto.MetricType_GAUGE || metricType == dto.MetricType_COUNTER || metricType == dto.MetricType_UNTYPED {
+ ch <- prometheus.MustNewConstMetric(
+ prometheus.NewDesc(
+ *metricFamily.Name,
+ metricFamily.GetHelp(),
+ names, nil,
+ ),
+ valType, val, values...,
+ )
+ }
+ }
+}
+
+func (c *textFileCollector) exportMTimes(mtimes map[string]time.Time, ch chan<- prometheus.Metric) {
+ // Export the mtimes of the successful files.
+ if len(mtimes) > 0 {
+ // Sorting is needed for predictable output comparison in tests.
+ filenames := make([]string, 0, len(mtimes))
+ for filename := range mtimes {
+ filenames = append(filenames, filename)
+ }
+ sort.Strings(filenames)
+
+ for _, filename := range filenames {
+ mtime := float64(mtimes[filename].UnixNano() / 1e9)
+ if c.mtime != nil {
+ mtime = *c.mtime
+ }
+ ch <- prometheus.MustNewConstMetric(mtimeDesc, prometheus.GaugeValue, mtime, filename)
+ }
+ }
+}
+
+// Update implements the Collector interface.
+func (c *textFileCollector) Collect(ch chan<- prometheus.Metric) error {
+ error := 0.0
+ mtimes := map[string]time.Time{}
+
+ // Iterate over files and accumulate their metrics.
+ files, err := ioutil.ReadDir(c.path)
+ if err != nil && c.path != "" {
+ log.Errorf("Error reading textfile collector directory %q: %s", c.path, err)
+ error = 1.0
+ }
+
+fileLoop:
+ for _, f := range files {
+ if !strings.HasSuffix(f.Name(), ".prom") {
+ continue
+ }
+ path := filepath.Join(c.path, f.Name())
+ file, err := os.Open(path)
+ if err != nil {
+ log.Errorf("Error opening %q: %v", path, err)
+ error = 1.0
+ continue
+ }
+ var parser expfmt.TextParser
+ parsedFamilies, err := parser.TextToMetricFamilies(file)
+ file.Close()
+ if err != nil {
+ log.Errorf("Error parsing %q: %v", path, err)
+ error = 1.0
+ continue
+ }
+ for _, mf := range parsedFamilies {
+ for _, m := range mf.Metric {
+ if m.TimestampMs != nil {
+ log.Errorf("Textfile %q contains unsupported client-side timestamps, skipping entire file", path)
+ error = 1.0
+ continue fileLoop
+ }
+ }
+ if mf.Help == nil {
+ help := fmt.Sprintf("Metric read from %s", path)
+ mf.Help = &help
+ }
+ }
+
+ // Only set this once it has been parsed and validated, so that
+ // a failure does not appear fresh.
+ mtimes[f.Name()] = f.ModTime()
+
+ for _, mf := range parsedFamilies {
+ convertMetricFamily(mf, ch)
+ }
+ }
+
+ c.exportMTimes(mtimes, ch)
+
+ // Export if there were errors.
+ ch <- prometheus.MustNewConstMetric(
+ prometheus.NewDesc(
+ "wmi_textfile_scrape_error",
+ "1 if there was an error opening or reading a file, 0 otherwise",
+ nil, nil,
+ ),
+ prometheus.GaugeValue, error,
+ )
+ return nil
+}
diff --git a/exporter.go b/exporter.go
index 5909c36c..3a897820 100644
--- a/exporter.go
+++ b/exporter.go
@@ -26,7 +26,7 @@ type WmiCollector struct {
}
const (
- defaultCollectors = "cpu,cs,logical_disk,net,os,service,system"
+ defaultCollectors = "cpu,cs,logical_disk,net,os,service,system,textfile"
defaultCollectorsPlaceholder = "[defaults]"
serviceName = "wmi_exporter"
)
diff --git a/installer/wmi_exporter.wxs b/installer/wmi_exporter.wxs
index 29e1dde3..ca986cd9 100644
--- a/installer/wmi_exporter.wxs
+++ b/installer/wmi_exporter.wxs
@@ -31,12 +31,15 @@
+
+ TEXTFILE_DIR
+
-
+
@@ -45,4 +48,4 @@
-
\ No newline at end of file
+