From 3ac5222f8b0c56a4ee02bb3f7d0e1bf53e28e113 Mon Sep 17 00:00:00 2001 From: Johannes 'fish' Ziemke Date: Tue, 18 Feb 2014 12:35:11 +0100 Subject: [PATCH] Move exporter to main and listen/interval to flags --- collector/collector.go | 21 +++ {exporter => collector}/ganglia/format.go | 0 {exporter => collector}/gmond_collector.go | 13 +- {exporter => collector}/helper.go | 5 +- {exporter => collector}/native_collector.go | 13 +- {exporter => collector}/runit_collector.go | 8 +- exporter/exporter.go | 167 -------------------- node_exporter.conf | 1 - node_exporter.go | 129 ++++++++++++++- 9 files changed, 164 insertions(+), 193 deletions(-) create mode 100644 collector/collector.go rename {exporter => collector}/ganglia/format.go (100%) rename {exporter => collector}/gmond_collector.go (91%) rename {exporter => collector}/helper.go (85%) rename {exporter => collector}/native_collector.go (97%) rename {exporter => collector}/runit_collector.go (91%) delete mode 100644 exporter/exporter.go diff --git a/collector/collector.go b/collector/collector.go new file mode 100644 index 00000000..15e8a73f --- /dev/null +++ b/collector/collector.go @@ -0,0 +1,21 @@ +// Exporter is a prometheus exporter using multiple Factories to collect and export system metrics. +package collector + +import ( + "github.com/prometheus/client_golang/prometheus" +) + +var Factories []func(Config, prometheus.Registry) (Collector, error) + +// Interface a collector has to implement. +type Collector interface { + // Get new metrics and expose them via prometheus registry. + Update() (n int, err error) + + // Returns the name of the collector + Name() string +} + +type Config struct { + Attributes map[string]string `json:"attributes"` +} diff --git a/exporter/ganglia/format.go b/collector/ganglia/format.go similarity index 100% rename from exporter/ganglia/format.go rename to collector/ganglia/format.go diff --git a/exporter/gmond_collector.go b/collector/gmond_collector.go similarity index 91% rename from exporter/gmond_collector.go rename to collector/gmond_collector.go index 90d73afb..d9c299dc 100644 --- a/exporter/gmond_collector.go +++ b/collector/gmond_collector.go @@ -1,17 +1,18 @@ // +build ganglia -package exporter +package collector import ( "bufio" "encoding/xml" "fmt" - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/node_exporter/exporter/ganglia" "io" "net" "regexp" "time" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/node_exporter/collector/ganglia" ) const ( @@ -23,18 +24,18 @@ const ( type gmondCollector struct { name string Metrics map[string]prometheus.Gauge - config config + config Config registry prometheus.Registry } func init() { - collectorFactories = append(collectorFactories, NewGmondCollector) + Factories = append(Factories, NewGmondCollector) } var illegalCharsRE = regexp.MustCompile(`[^a-zA-Z0-9_]`) // Takes a config struct and prometheus registry and returns a new Collector scraping ganglia. -func NewGmondCollector(config config, registry prometheus.Registry) (Collector, error) { +func NewGmondCollector(config Config, registry prometheus.Registry) (Collector, error) { c := gmondCollector{ name: "gmond_collector", config: config, diff --git a/exporter/helper.go b/collector/helper.go similarity index 85% rename from exporter/helper.go rename to collector/helper.go index 6baa554f..00012a2d 100644 --- a/exporter/helper.go +++ b/collector/helper.go @@ -1,12 +1,15 @@ -package exporter +package collector import ( + "flag" "fmt" "log" "strconv" "strings" ) +var verbose = flag.Bool("verbose", false, "Verbose output.") + func debug(name string, format string, a ...interface{}) { if *verbose { f := fmt.Sprintf("%s: %s", name, format) diff --git a/exporter/native_collector.go b/collector/native_collector.go similarity index 97% rename from exporter/native_collector.go rename to collector/native_collector.go index 281a0e09..a5b5dc07 100644 --- a/exporter/native_collector.go +++ b/collector/native_collector.go @@ -1,11 +1,10 @@ // +build !nonative -package exporter +package collector import ( "bufio" "fmt" - "github.com/prometheus/client_golang/prometheus" "io" "io/ioutil" "os" @@ -13,6 +12,8 @@ import ( "strconv" "strings" "time" + + "github.com/prometheus/client_golang/prometheus" ) const ( @@ -42,16 +43,16 @@ type nativeCollector struct { netStats prometheus.Counter diskStats prometheus.Counter name string - config config + config Config } func init() { - collectorFactories = append(collectorFactories, NewNativeCollector) + Factories = append(Factories, NewNativeCollector) } // Takes a config struct and prometheus registry and returns a new Collector exposing // load, seconds since last login and a list of tags as specified by config. -func NewNativeCollector(config config, registry prometheus.Registry) (Collector, error) { +func NewNativeCollector(config Config, registry prometheus.Registry) (Collector, error) { c := nativeCollector{ name: "native_collector", config: config, @@ -160,7 +161,7 @@ func (c *nativeCollector) Update() (updates int, err error) { updates++ fv, err := strconv.ParseFloat(value, 64) if err != nil { - return updates, fmt.Errorf("Invalid value in interrupts: %s", fv, err) + return updates, fmt.Errorf("Invalid value %s in interrupts: %s", value, err) } labels := map[string]string{ "CPU": strconv.Itoa(cpuNo), diff --git a/exporter/runit_collector.go b/collector/runit_collector.go similarity index 91% rename from exporter/runit_collector.go rename to collector/runit_collector.go index 22f0d707..24b89731 100644 --- a/exporter/runit_collector.go +++ b/collector/runit_collector.go @@ -1,6 +1,6 @@ // +build runit -package exporter +package collector import ( "github.com/prometheus/client_golang/prometheus" @@ -9,17 +9,17 @@ import ( type runitCollector struct { name string - config config + config Config state prometheus.Gauge stateDesired prometheus.Gauge stateNormal prometheus.Gauge } func init() { - collectorFactories = append(collectorFactories, NewRunitCollector) + Factories = append(Factories, NewRunitCollector) } -func NewRunitCollector(config config, registry prometheus.Registry) (Collector, error) { +func NewRunitCollector(config Config, registry prometheus.Registry) (Collector, error) { c := runitCollector{ name: "runit_collector", config: config, diff --git a/exporter/exporter.go b/exporter/exporter.go deleted file mode 100644 index 2e0c4756..00000000 --- a/exporter/exporter.go +++ /dev/null @@ -1,167 +0,0 @@ -// Exporter is a prometheus exporter using multiple collectorFactories to collect and export system metrics. -package exporter - -import ( - "encoding/json" - "flag" - "fmt" - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/exp" - "io/ioutil" - "log" - "net/http" - "os" - "os/signal" - "runtime/pprof" - "sync" - "syscall" - "time" -) - -var verbose = flag.Bool("verbose", false, "Verbose output.") -var collectorFactories []func(config, prometheus.Registry) (Collector, error) - -// Interface a collector has to implement. -type Collector interface { - // Get new metrics and expose them via prometheus registry. - Update() (n int, err error) - - // Returns the name of the collector - Name() string -} - -type config struct { - Attributes map[string]string `json:"attributes"` - ListeningAddress string `json:"listeningAddress"` - ScrapeInterval int `json:"scrapeInterval"` -} - -func (e *exporter) loadConfig() (err error) { - log.Printf("Reading config %s", e.configFile) - bytes, err := ioutil.ReadFile(e.configFile) - if err != nil { - return - } - - return json.Unmarshal(bytes, &e.config) // Make sure this is safe -} - -type exporter struct { - configFile string - listeningAddress string - scrapeInterval time.Duration - scrapeDurations prometheus.Histogram - metricsUpdated prometheus.Gauge - config config - registry prometheus.Registry - Collectors []Collector - MemProfile string -} - -// New takes the path to a config file and returns an exporter instance -func New(configFile string) (e exporter, err error) { - registry := prometheus.NewRegistry() - e = exporter{ - configFile: configFile, - scrapeDurations: prometheus.NewDefaultHistogram(), - metricsUpdated: prometheus.NewGauge(), - listeningAddress: ":8080", - scrapeInterval: 60 * time.Second, - registry: registry, - } - - err = e.loadConfig() - if err != nil { - return e, fmt.Errorf("Couldn't read config: %s", err) - } - for _, fn := range collectorFactories { - c, err := fn(e.config, e.registry) - if err != nil { - return e, err - } - e.Collectors = append(e.Collectors, c) - } - - if e.config.ListeningAddress != "" { - e.listeningAddress = e.config.ListeningAddress - } - if e.config.ScrapeInterval != 0 { - e.scrapeInterval = time.Duration(e.config.ScrapeInterval) * time.Second - } - - registry.Register("node_exporter_scrape_duration_seconds", "node_exporter: Duration of a scrape job.", prometheus.NilLabels, e.scrapeDurations) - registry.Register("node_exporter_metrics_updated", "node_exporter: Number of metrics updated.", prometheus.NilLabels, e.metricsUpdated) - - return e, nil -} - -func (e *exporter) serveStatus() { - exp.Handle(prometheus.ExpositionResource, e.registry.Handler()) - http.ListenAndServe(e.listeningAddress, exp.DefaultCoarseMux) -} - -func (e *exporter) Execute(c Collector) { - begin := time.Now() - updates, err := c.Update() - duration := time.Since(begin) - - label := map[string]string{ - "collector": c.Name(), - } - if err != nil { - log.Printf("ERROR: %s failed after %fs: %s", c.Name(), duration.Seconds(), err) - label["result"] = "error" - } else { - log.Printf("OK: %s success after %fs.", c.Name(), duration.Seconds()) - label["result"] = "success" - } - e.scrapeDurations.Add(label, duration.Seconds()) - e.metricsUpdated.Set(label, float64(updates)) -} - -func (e *exporter) Loop() { - sigHup := make(chan os.Signal) - sigUsr1 := make(chan os.Signal) - signal.Notify(sigHup, syscall.SIGHUP) - signal.Notify(sigUsr1, syscall.SIGUSR1) - - go e.serveStatus() - - tick := time.Tick(e.scrapeInterval) - for { - select { - case <-sigHup: - err := e.loadConfig() - if err != nil { - log.Printf("Couldn't reload config: %s", err) - continue - } - log.Printf("Got new config") - tick = time.Tick(e.scrapeInterval) - - case <-tick: - log.Printf("Starting new scrape interval") - wg := sync.WaitGroup{} - wg.Add(len(e.Collectors)) - for _, c := range e.Collectors { - go func(c Collector) { - e.Execute(c) - wg.Done() - }(c) - } - wg.Wait() - - case <-sigUsr1: - log.Printf("got signal") - if e.MemProfile != "" { - log.Printf("Writing memory profile to %s", e.MemProfile) - f, err := os.Create(e.MemProfile) - if err != nil { - log.Fatal(err) - } - pprof.WriteHeapProfile(f) - f.Close() - } - } - } -} diff --git a/node_exporter.conf b/node_exporter.conf index 261d76b4..6800bac1 100644 --- a/node_exporter.conf +++ b/node_exporter.conf @@ -1,5 +1,4 @@ { - "scrapeInterval": 10, "attributes" : { "web-server" : "1", "zone" : "a", diff --git a/node_exporter.go b/node_exporter.go index 3d14e703..5d76ab54 100644 --- a/node_exporter.go +++ b/node_exporter.go @@ -1,27 +1,140 @@ package main import ( + "encoding/json" "flag" + "io/ioutil" "log" + "net/http" + "os" + "os/signal" + "runtime/pprof" + "sync" + "syscall" + "time" - "github.com/prometheus/node_exporter/exporter" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/exp" + "github.com/prometheus/node_exporter/collector" ) var ( - configFile = flag.String("config", "node_exporter.conf", "config file.") - memprofile = flag.String("memprofile", "", "write memory profile to this file") + configFile = flag.String("config", "node_exporter.conf", "config file.") + memProfile = flag.String("memprofile", "", "write memory profile to this file") + listeningAddress = flag.String("listen", ":8080", "address to listen on") + interval = flag.Duration("interval", 60*time.Second, "refresh interval") + scrapeDurations = prometheus.NewDefaultHistogram() + metricsUpdated = prometheus.NewGauge() ) func main() { flag.Parse() - - exporter, err := exporter.New(*configFile) + registry := prometheus.NewRegistry() + collectors, err := loadCollectors(*configFile, registry) if err != nil { - log.Fatalf("Couldn't instantiate exporter: %s", err) + log.Fatalf("Couldn't load config and collectors: %s", err) } + + registry.Register("node_exporter_scrape_duration_seconds", "node_exporter: Duration of a scrape job.", prometheus.NilLabels, scrapeDurations) + registry.Register("node_exporter_metrics_updated", "node_exporter: Number of metrics updated.", prometheus.NilLabels, metricsUpdated) + log.Printf("Registered collectors:") - for _, c := range exporter.Collectors { + for _, c := range collectors { log.Print(" - ", c.Name()) } - exporter.Loop() + + sigHup := make(chan os.Signal) + sigUsr1 := make(chan os.Signal) + signal.Notify(sigHup, syscall.SIGHUP) + signal.Notify(sigUsr1, syscall.SIGUSR1) + + go serveStatus(registry) + + tick := time.Tick(*interval) + for { + select { + case <-sigHup: + collectors, err = loadCollectors(*configFile, registry) + if err != nil { + log.Fatalf("Couldn't load config and collectors: %s", err) + } + log.Printf("Reload collectors and config") + tick = time.Tick(*interval) + + case <-tick: + log.Printf("Starting new interval") + wg := sync.WaitGroup{} + wg.Add(len(collectors)) + for _, c := range collectors { + go func(c collector.Collector) { + Execute(c) + wg.Done() + }(c) + } + wg.Wait() + + case <-sigUsr1: + log.Printf("got signal") + if *memProfile != "" { + log.Printf("Writing memory profile to %s", *memProfile) + f, err := os.Create(*memProfile) + if err != nil { + log.Fatal(err) + } + pprof.WriteHeapProfile(f) + f.Close() + } + } + } + +} + +func loadCollectors(file string, registry prometheus.Registry) ([]collector.Collector, error) { + collectors := []collector.Collector{} + config, err := getConfig(file) + if err != nil { + log.Fatalf("Couldn't read config %s: %s", file, err) + } + for _, fn := range collector.Factories { + c, err := fn(*config, registry) + if err != nil { + return nil, err + } + collectors = append(collectors, c) + } + return collectors, nil +} + +func getConfig(file string) (*collector.Config, error) { + config := &collector.Config{} + log.Printf("Reading config %s", *configFile) + bytes, err := ioutil.ReadFile(*configFile) + if err != nil { + return nil, err + } + return config, json.Unmarshal(bytes, &config) +} + +func serveStatus(registry prometheus.Registry) { + exp.Handle(prometheus.ExpositionResource, registry.Handler()) + http.ListenAndServe(*listeningAddress, exp.DefaultCoarseMux) +} + +func Execute(c collector.Collector) { + begin := time.Now() + updates, err := c.Update() + duration := time.Since(begin) + + label := map[string]string{ + "collector": c.Name(), + } + if err != nil { + log.Printf("ERROR: %s failed after %fs: %s", c.Name(), duration.Seconds(), err) + label["result"] = "error" + } else { + log.Printf("OK: %s success after %fs.", c.Name(), duration.Seconds()) + label["result"] = "success" + } + scrapeDurations.Add(label, duration.Seconds()) + metricsUpdated.Set(label, float64(updates)) }