From 352cde6d200cf93a4fd4bd35df37c978afe849fc Mon Sep 17 00:00:00 2001 From: Brian Brazil Date: Sun, 25 Jan 2015 12:28:58 +0000 Subject: [PATCH] Add text file exporter This allows static metrics (e.g. an attributes collector replacement), and cronjobs to expose stats by echoing into a file. For example: echo "my_metric 123" > mycronjob.prom.$$ mv mycronjob.prom.$$ mycronjob.prom --- collector/textfile.go | 125 ++++++++++++++++++++++++++++++++++++++++++ node_exporter.go | 2 +- 2 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 collector/textfile.go diff --git a/collector/textfile.go b/collector/textfile.go new file mode 100644 index 00000000..82c97186 --- /dev/null +++ b/collector/textfile.go @@ -0,0 +1,125 @@ +// +build !notextfile + +package collector + +import ( + "flag" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + "time" + + dto "github.com/prometheus/client_model/go" + + "code.google.com/p/goprotobuf/proto" + "github.com/golang/glog" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/text" +) + +var ( + textFileDirectory = flag.String("textfile.directory", "", "Directory to read text files with metrics from.") +) + +type textFileCollector struct { +} + +func init() { + Factories["textfile"] = NewTextFileCollector +} + +// Takes a config struct and registers a +// SetMetricFamilyInjectionHook. +func NewTextFileCollector(config Config) (Collector, error) { + if *textFileDirectory == "" { + // This collector is enabled by default, so do not fail if + // the flag is not passed. + glog.Infof("No directory specified, see --textfile.directory") + } else { + prometheus.SetMetricFamilyInjectionHook(parseTextFiles) + } + + return &textFileCollector{}, nil +} + +// textFile collector works via SetMetricFamilyInjectionHook in parseTextFiles. +func (c *textFileCollector) Update(ch chan<- prometheus.Metric) (err error) { + return nil +} + +func parseTextFiles() []*dto.MetricFamily { + var parser text.Parser + error := 0.0 + metricFamilies := make([]*dto.MetricFamily, 0) + mtimes := map[string]time.Time{} + + // Iterate over files and accumulate their metrics. + files, _ := ioutil.ReadDir(*textFileDirectory) + for _, f := range files { + if !strings.HasSuffix(f.Name(), ".prom") { + continue + } + path := filepath.Join(*textFileDirectory, f.Name()) + file, err := os.Open(path) + if err != nil { + glog.Errorf("Error opening %s: %v", path, err) + error = 1.0 + continue + } + parsedFamilies, err := parser.TextToMetricFamilies(file) + if err != nil { + glog.Errorf("Error parsing %s: %v", path, err) + error = 1.0 + continue + } + // Only set this once it has been parsed, so that + // a failure does not appear fresh. + mtimes[f.Name()] = f.ModTime() + for _, mf := range parsedFamilies { + if mf.Help == nil { + help := fmt.Sprintf("Metric read from %s", path) + mf.Help = &help + } + metricFamilies = append(metricFamilies, mf) + } + } + + // Export the mtimes of the successful files. + if len(mtimes) > 0 { + mtimeMetricFamily := dto.MetricFamily{ + Name: proto.String("node_textfile_mtime"), + Help: proto.String("Unixtime mtime of textfiles successfully read."), + Type: dto.MetricType_GAUGE.Enum(), + Metric: []*dto.Metric{}, + } + for name, mtime := range mtimes { + mtimeMetricFamily.Metric = append(mtimeMetricFamily.Metric, + &dto.Metric{ + Label: []*dto.LabelPair{ + &dto.LabelPair{ + Name: proto.String("file"), + Value: &name, + }, + }, + Gauge: &dto.Gauge{Value: proto.Float64(float64(mtime.UnixNano()) / 1e9)}, + }, + ) + } + metricFamilies = append(metricFamilies, &mtimeMetricFamily) + } + // Export if there were errors. + metricFamilies = append(metricFamilies, &dto.MetricFamily{ + Name: proto.String("node_textfile_scrape_error"), + Help: proto.String("1 if there was an error opening or reading a file, 0 otherwise"), + Type: dto.MetricType_GAUGE.Enum(), + Metric: []*dto.Metric{ + &dto.Metric{ + Gauge: &dto.Gauge{Value: &error}, + }, + }, + }) + + return metricFamilies +} diff --git a/node_exporter.go b/node_exporter.go index 1be34c1b..691e8860 100644 --- a/node_exporter.go +++ b/node_exporter.go @@ -25,7 +25,7 @@ var ( configFile = flag.String("config", "", "Path to config file.") memProfile = flag.String("memprofile", "", "Write memory profile to this file.") listeningAddress = flag.String("listen", ":8080", "Address to listen on.") - enabledCollectors = flag.String("enabledCollectors", "attributes,diskstats,filesystem,loadavg,meminfo,stat,time,netdev,netstat", "Comma-separated list of collectors to use.") + enabledCollectors = flag.String("enabledCollectors", "attributes,diskstats,filesystem,loadavg,meminfo,stat,textfile,time,netdev,netstat", "Comma-separated list of collectors to use.") printCollectors = flag.Bool("printCollectors", false, "If true, print available collectors and exit.") authUser = flag.String("auth.user", "", "Username for basic auth.") authPass = flag.String("auth.pass", "", "Password for basic auth.")