2015-02-18 21:40:55 +00:00
// Copyright 2013 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
2013-07-02 16:16:39 +00:00
//
2015-02-18 21:40:55 +00:00
// 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.
2013-07-02 16:16:39 +00:00
package main
import (
"flag"
"net"
"net/http"
"strconv"
2013-07-08 18:37:12 +00:00
"github.com/howeyc/fsnotify"
2013-07-02 16:16:39 +00:00
"github.com/prometheus/client_golang/prometheus"
2015-05-29 11:43:56 +00:00
"github.com/prometheus/log"
2013-07-02 16:16:39 +00:00
)
2013-07-02 18:10:01 +00:00
var (
2015-02-08 22:57:53 +00:00
listenAddress = flag . String ( "web.listen-address" , ":9102" , "The address on which to expose the web interface and generated Prometheus metrics." )
metricsEndpoint = flag . String ( "web.telemetry-path" , "/metrics" , "Path under which to expose metrics." )
statsdListenAddress = flag . String ( "statsd.listen-address" , ":9125" , "The UDP address on which to receive statsd metric lines." )
mappingConfig = flag . String ( "statsd.mapping-config" , "" , "Metric mapping configuration file name." )
2015-09-09 11:58:18 +00:00
readBuffer = flag . Int ( "statsd.read-buffer" , 0 , "Size (in bytes) of the operating system's transmit read buffer associated with the UDP connection. Please make sure the kernel parameters net.core.rmem_max is set to a value greater than the value specified." )
2016-05-02 18:53:00 +00:00
addSuffix = flag . Bool ( "statsd.add-suffix" , true , "Add the metric type (counter/gauge/timer) as suffix to the generated Prometheus metric (NOT recommended, but set by default for backward compatibility)." )
2013-07-02 18:10:01 +00:00
)
2013-07-02 16:16:39 +00:00
func serveHTTP ( ) {
2015-02-08 22:57:53 +00:00
http . Handle ( * metricsEndpoint , prometheus . Handler ( ) )
2015-10-10 00:26:05 +00:00
http . HandleFunc ( "/" , func ( w http . ResponseWriter , r * http . Request ) {
w . Write ( [ ] byte ( ` < html >
< head > < title > StatsD Bridge < / title > < / head >
< body >
< h1 > StatsD Bridge < / h1 >
< p > < a href = "` + *metricsEndpoint + `" > Metrics < / a > < / p >
< / body >
< / html > ` ) )
} )
2015-02-08 22:57:53 +00:00
http . ListenAndServe ( * listenAddress , nil )
2013-07-02 16:16:39 +00:00
}
func udpAddrFromString ( addr string ) * net . UDPAddr {
2015-02-11 21:30:57 +00:00
host , portStr , err := net . SplitHostPort ( addr )
2013-07-02 16:16:39 +00:00
if err != nil {
2015-02-11 21:30:57 +00:00
log . Fatal ( "Bad StatsD listening address" , addr )
2013-07-02 16:16:39 +00:00
}
if host == "" {
host = "0.0.0.0"
}
ip , err := net . ResolveIPAddr ( "ip" , host )
if err != nil {
log . Fatalf ( "Unable to resolve %s: %s" , host , err )
}
port , err := strconv . Atoi ( portStr )
if err != nil || port < 0 || port > 65535 {
2015-02-11 21:31:49 +00:00
log . Fatalf ( "Bad port %s: %s" , portStr , err )
2013-07-02 16:16:39 +00:00
}
return & net . UDPAddr {
IP : ip . IP ,
Port : port ,
Zone : ip . Zone ,
}
}
2013-07-08 18:37:12 +00:00
func watchConfig ( fileName string , mapper * metricMapper ) {
watcher , err := fsnotify . NewWatcher ( )
if err != nil {
log . Fatal ( err )
}
err = watcher . WatchFlags ( fileName , fsnotify . FSN_MODIFY )
if err != nil {
log . Fatal ( err )
}
for {
select {
case ev := <- watcher . Event :
log . Printf ( "Config file changed (%s), attempting reload" , ev )
err = mapper . initFromFile ( fileName )
if err != nil {
log . Println ( "Error reloading config:" , err )
2014-06-26 13:56:21 +00:00
configLoads . WithLabelValues ( "failure" ) . Inc ( )
2013-07-08 18:37:12 +00:00
} else {
log . Println ( "Config reloaded successfully" )
2014-06-26 13:56:21 +00:00
configLoads . WithLabelValues ( "success" ) . Inc ( )
2013-07-08 18:37:12 +00:00
}
// Re-add the file watcher since it can get lost on some changes. E.g.
// saving a file with vim results in a RENAME-MODIFY-DELETE event
// sequence, after which the newly written file is no longer watched.
err = watcher . WatchFlags ( fileName , fsnotify . FSN_MODIFY )
case err := <- watcher . Error :
log . Println ( "Error watching config:" , err )
}
}
}
2013-07-02 16:16:39 +00:00
func main ( ) {
flag . Parse ( )
2016-05-02 18:53:00 +00:00
if * addSuffix {
log . Println ( "Warning: Using -statsd.add-suffix is discouraged. We recommend explicitly naming metrics appropriately in the mapping configuration." )
}
2015-10-10 00:34:28 +00:00
log . Println ( "Starting StatsD -> Prometheus Exporter..." )
2015-02-08 22:57:53 +00:00
log . Println ( "Accepting StatsD Traffic on" , * statsdListenAddress )
log . Println ( "Accepting Prometheus Requests on" , * listenAddress )
2013-07-03 13:25:52 +00:00
2013-07-02 16:16:39 +00:00
go serveHTTP ( )
events := make ( chan Events , 1024 )
defer close ( events )
2015-02-08 22:57:53 +00:00
listenAddr := udpAddrFromString ( * statsdListenAddress )
2013-07-02 16:16:39 +00:00
conn , err := net . ListenUDP ( "udp" , listenAddr )
if err != nil {
log . Fatal ( err )
}
2015-09-09 11:58:18 +00:00
if * readBuffer != 0 {
err = conn . SetReadBuffer ( * readBuffer )
if err != nil {
log . Fatal ( "Error setting UDP read buffer:" , err )
}
}
2013-07-02 16:16:39 +00:00
l := & StatsDListener { conn : conn }
go l . Listen ( events )
2013-07-08 18:37:12 +00:00
mapper := & metricMapper { }
if * mappingConfig != "" {
2013-07-05 17:31:53 +00:00
err := mapper . initFromFile ( * mappingConfig )
if err != nil {
log . Fatal ( "Error loading config:" , err )
}
2013-07-08 18:37:12 +00:00
go watchConfig ( * mappingConfig , mapper )
2013-07-05 17:31:53 +00:00
}
2016-05-02 18:53:00 +00:00
exporter := NewExporter ( mapper , * addSuffix )
2015-10-10 00:34:28 +00:00
exporter . Listen ( events )
2013-07-02 16:16:39 +00:00
}