// 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
}
Beispiel #2
0
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)
	}
}
Beispiel #5
0
// 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
}