// Dial and scrape the basic service parameters func (s *BasicService) dialAndScrape() (net.Conn, error) { if s.Timeout == 0 { log.Warnln("0 deadline set for service. This is probably not what you want as services will flap.") } // Set absolute deadline deadline := time.Now().Add(time.Duration(s.Timeout)) // Dialer deadline dialer := net.Dialer{ Deadline: deadline, } var err error var conn net.Conn conn, err = dialer.Dial(s.Protocol, fmt.Sprintf("%s:%d", s.Host().Hostname, s.Port())) if err != nil { s.portOpen = FAILED } else { s.portOpen = SUCCESS } if conn != nil { // Connection deadline conn.SetDeadline(deadline) } return conn, err }
func main() { flag.Parse() if *showVersion { fmt.Fprintln(os.Stdout, version.Print("statsd_exporter")) os.Exit(0) } if *addSuffix { log.Warnln("Warning: Using -statsd.add-suffix is discouraged. We recommend explicitly naming metrics appropriately in the mapping configuration.") } log.Infoln("Starting StatsD -> Prometheus Exporter", version.Info()) log.Infoln("Build context", version.BuildContext()) log.Infoln("Accepting StatsD Traffic on", *statsdListenAddress) log.Infoln("Accepting Prometheus Requests on", *listenAddress) go serveHTTP() events := make(chan Events, 1024) defer close(events) listenAddr := udpAddrFromString(*statsdListenAddress) conn, err := net.ListenUDP("udp", listenAddr) if err != nil { log.Fatal(err) } if *readBuffer != 0 { err = conn.SetReadBuffer(*readBuffer) if err != nil { log.Fatal("Error setting UDP read buffer:", err) } } l := &StatsDListener{conn: conn} go l.Listen(events) mapper := &metricMapper{} if *mappingConfig != "" { err := mapper.initFromFile(*mappingConfig) if err != nil { log.Fatal("Error loading config:", err) } go watchConfig(*mappingConfig, mapper) } exporter := NewExporter(mapper, *addSuffix) exporter.Listen(events) }
// Convert the query override file to the version-specific query override file // for the exporter. func makeQueryOverrideMap(pgVersion semver.Version, queryOverrides map[string][]OverrideQuery) map[string]string { resultMap := make(map[string]string) for name, overrideDef := range queryOverrides { // Find a matching semver. We make it an error to have overlapping // ranges at test-time, so only 1 should ever match. matched := false for _, queryDef := range overrideDef { if queryDef.versionRange(pgVersion) { resultMap[name] = queryDef.query matched = true break } } if !matched { log.Warnln("No query matched override for", name, "- disabling metric space.") resultMap[name] = "" } } return resultMap }
func (e *Exporter) scrape(ch chan<- prometheus.Metric) { defer func(begun time.Time) { e.duration.Set(time.Since(begun).Seconds()) }(time.Now()) e.error.Set(0) e.totalScrapes.Inc() db, err := sql.Open("postgres", e.dsn) if err != nil { log.Infoln("Error opening connection to database:", err) e.error.Set(1) return } defer db.Close() // Check if map versions need to be updated if err := e.checkMapVersions(ch, db); err != nil { log.Warnln("Postgres version could not be determined. Proceeding with outdated query maps.") e.error.Set(1) } // Lock the exporter maps e.mappingMtx.RLock() defer e.mappingMtx.RUnlock() // Handle querying the show variables nonFatalErrors := queryShowVariables(ch, db, e.variableMap) if len(nonFatalErrors) > 0 { e.error.Set(1) } errMap := queryNamespaceMappings(ch, db, e.metricMap, e.queryOverrides) if len(errMap) > 0 { e.error.Set(1) } }
// Processes lines through the regexes we have loaded func (c *TailCollector) lineProcessor(lineCh chan string, cfg config.MetricParser) { // Run all regexes labelValues := make([]string, len(cfg.Labels)) processloop: for line := range lineCh { m := cfg.Regex.MatcherString(line, 0) if !m.Matches() { continue } // Got a match. See if we have this metric already. var metricVec *prometheus.MetricVec var ok bool metricVec, ok = c.metrics[cfg.Name] if !ok { metricVec = c.initalizeMetric(&cfg) } // Calculate label values for idx, v := range cfg.Labels { switch v.Value.FieldType { case config.LVALUE_LITERAL: labelValues[idx] = v.Value.Literal case config.LVALUE_CAPTUREGROUP_NAMED: if !m.NamedPresent(v.Value.CaptureGroupName) { if v.HasDefault { labelValues[idx] = v.Default } else { log.With("name", cfg.Name). With("group_name", v.Value.CaptureGroupName). With("line", line). Warnln("Dropping unconvertible capture value") continue processloop } } labelValues[idx] = m.NamedString(v.Value.CaptureGroupName) case config.LVALUE_CAPTUREGROUP: if v.HasDefault && (m.GroupString(v.Value.CaptureGroup) == "") { labelValues[idx] = v.Default } else { labelValues[idx] = m.GroupString(v.Value.CaptureGroup) } default: log.Warnln("Got no recognized type - assuming literal") labelValues[idx] = v.Value.Literal } } // Get the metric we will adjust metric := metricVec.WithLabelValues(labelValues...) switch cfg.Value.FieldType { case config.VALUE_LITERAL: switch t := metric.(type) { case prometheus.Gauge: t.Set(cfg.Value.Literal) case prometheus.Counter: t.Set(cfg.Value.Literal) case prometheus.Untyped: t.Set(cfg.Value.Literal) default: log.With("name", cfg.Name).Errorf("Unknown type for metric: %T", t) } case config.VALUE_CAPTUREGROUP: valstr := m.GroupString(cfg.Value.CaptureGroup) val, err := strconv.ParseFloat(valstr, 64) if err != nil { log.With("name", cfg.Name). With("group_name", cfg.Value.CaptureGroup). With("line", line). With("value", valstr). Warnln("Dropping line with unconvertible capture value") continue } switch t := metric.(type) { case prometheus.Gauge: t.Set(val) case prometheus.Counter: t.Set(val) case prometheus.Untyped: t.Set(val) default: log.With("name", cfg.Name).Errorf("Unknown type for metric: %T", t) } case config.VALUE_CAPTUREGROUP_NAMED: if !m.NamedPresent(cfg.Value.CaptureGroupName) { log.With("name", cfg.Name). With("group_name", cfg.Value.CaptureGroup). With("line", line). Warnln("Dropping line with missing capture value") continue } valstr := m.NamedString(cfg.Value.CaptureGroupName) val, err := strconv.ParseFloat(valstr, 64) if err != nil { log.With("name", cfg.Name). With("group_name", cfg.Value.CaptureGroupName). With("line", line). With("value", valstr). Warnln("Dropping line with unconvertible capture value") continue } switch t := metric.(type) { case prometheus.Gauge: t.Set(val) case prometheus.Counter: t.Set(val) case prometheus.Untyped: t.Set(val) default: log.With("name", cfg.Name).Errorf("Unknown type for metric: %T", t) } case config.VALUE_INC: switch t := metric.(type) { case prometheus.Gauge: t.Inc() case prometheus.Counter: t.Inc() case prometheus.Untyped: t.Inc() default: log.With("name", cfg.Name).Errorf("Unknown type for metric: %T", t) } case config.VALUE_SUB: switch t := metric.(type) { case prometheus.Gauge: t.Dec() case prometheus.Counter: t.Set(0) // Subtract means reset for a counter case prometheus.Untyped: t.Dec() default: log.With("name", cfg.Name).Errorf("Unknown type for metric: %T", t) } } } }
func main() { rand.Seed(time.Now().Unix()) flag.Parse() // This is only used when we're running in -dev mode with bindata rootDir, _ = osext.ExecutableFolder() rootDir = path.Join(rootDir, "web") // Parse configuration cfg, err := config.LoadFromFile(*configFile) if err != nil { log.Fatalln("Error loading config", err) } // Templates amberTmpl, err := Asset("templates/index.amber") if err != nil { log.Fatalln("Could not load index template:", err) } tmpl := amber.MustCompile(string(amberTmpl), amber.Options{}) // Setup the web UI router := httprouter.New() router.Handler("GET", *metricsPath, prometheus.Handler()) // Prometheus // Static asset handling router.GET("/static/*filepath", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { reqpath := ps.ByName("filepath") realpath := path.Join("static", reqpath) b, err := Asset(realpath) if err != nil { log.Debugln("Could not find asset: ", err) return } else { w.Write(b) } }) var monitoredHosts []*pollers.Host router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { data := struct { Cfg *config.Config Hosts *[]*pollers.Host }{ Cfg: cfg, Hosts: &monitoredHosts, } err := tmpl.Execute(w, &data) if err != nil { log.Errorln("Error rendering template", err) } }) // Initialize the host pollers monitoredHosts = make([]*pollers.Host, len(cfg.Hosts)) // We don't allow duplicate hosts, but also don't want to panic just due // to a typo, so keep track and skip duplicates here. seenHosts := make(map[string]bool) realidx := 0 for _, hostCfg := range cfg.Hosts { log.Debugln("Setting up poller for: ", hostCfg.Hostname) if *skipPing { hostCfg.PingDisable = true } if _, ok := seenHosts[hostCfg.Hostname]; ok { log.Warnln("Discarding repeat configuration of same hostname", hostCfg.Hostname) continue } host := pollers.NewHost(hostCfg) monitoredHosts[realidx] = host prometheus.MustRegister(host) seenHosts[hostCfg.Hostname] = true realidx++ } // Trim monitoredHosts to the number we actually used monitoredHosts = monitoredHosts[0:realidx] // This is the dispatcher. It is responsible for invoking the doPoll method // of hosts. connectionLimiter := pollers.NewLimiter(*maxConnections) hostQueue := make(chan *pollers.Host) // Start the host dispatcher go func() { for host := range hostQueue { go host.Poll(connectionLimiter, hostQueue) } }() // Do the initial host dispatch go func() { for _, host := range monitoredHosts { log.Debugln("Starting polling for hosts") hostQueue <- host } }() var handler http.Handler // If basic auth is requested, enable it for the interface. if cfg.BasicAuthUsername != "" && cfg.BasicAuthPassword != "" { basicauth := httpauth.SimpleBasicAuth(cfg.BasicAuthUsername, cfg.BasicAuthPassword) handler = basicauth(router) } else { handler = router } // If TLS certificates are specificed, use TLS if cfg.TLSCertificatePath != "" && cfg.TLSKeyPath != "" { log.Infof("Listening on (TLS-enabled) %s", *listenAddress) err = http.ListenAndServeTLS(*listenAddress, cfg.TLSCertificatePath, cfg.TLSKeyPath, handler) } else { log.Infof("Listening on %s", *listenAddress) err = http.ListenAndServe(*listenAddress, handler) } if err != nil { log.Fatal(err) } }
// ScrapeInnodbMetrics collects from `information_schema.innodb_metrics`. func ScrapeInnodbMetrics(db *sql.DB, ch chan<- prometheus.Metric) error { innodbMetricsRows, err := db.Query(infoSchemaInnodbMetricsQuery) if err != nil { return err } defer innodbMetricsRows.Close() var ( name, subsystem, metricType, comment string value float64 ) for innodbMetricsRows.Next() { if err := innodbMetricsRows.Scan( &name, &subsystem, &metricType, &comment, &value, ); err != nil { return err } // Special handling of the "buffer_page_io" subsystem. if subsystem == "buffer_page_io" { match := bufferPageRE.FindStringSubmatch(name) if len(match) != 3 { log.Warnln("innodb_metrics subsystem buffer_page_io returned an invalid name:", name) continue } switch match[1] { case "read": ch <- prometheus.MustNewConstMetric( infoSchemaBufferPageReadTotalDesc, prometheus.CounterValue, value, match[2], ) case "written": ch <- prometheus.MustNewConstMetric( infoSchemaBufferPageWrittenTotalDesc, prometheus.CounterValue, value, match[2], ) } continue } metricName := "innodb_metrics_" + subsystem + "_" + name // MySQL returns counters named two different ways. "counter" and "status_counter" // value >= 0 is necessary due to upstream bugs: http://bugs.mysql.com/bug.php?id=75966 if (metricType == "counter" || metricType == "status_counter") && value >= 0 { description := prometheus.NewDesc( prometheus.BuildFQName(namespace, informationSchema, metricName+"_total"), comment, nil, nil, ) ch <- prometheus.MustNewConstMetric( description, prometheus.CounterValue, value, ) } else { description := prometheus.NewDesc( prometheus.BuildFQName(namespace, informationSchema, metricName), comment, nil, nil, ) ch <- prometheus.MustNewConstMetric( description, prometheus.GaugeValue, value, ) } } return nil }