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 (
2018-09-22 01:09:36 +00:00
"bufio"
2013-07-02 16:16:39 +00:00
"net"
"net/http"
2019-04-22 11:17:06 +00:00
_ "net/http/pprof"
2018-09-22 01:09:36 +00:00
"os"
2019-04-22 23:31:24 +00:00
"os/signal"
2013-07-02 16:16:39 +00:00
"strconv"
2019-04-22 23:31:24 +00:00
"syscall"
2013-07-02 16:16:39 +00:00
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"
2019-06-11 10:05:21 +00:00
"github.com/prometheus/client_golang/prometheus/promhttp"
2016-05-04 19:16:17 +00:00
"github.com/prometheus/common/log"
"github.com/prometheus/common/version"
2018-08-20 20:12:45 +00:00
"gopkg.in/alecthomas/kingpin.v2"
2018-08-10 12:28:38 +00:00
2018-08-14 09:20:00 +00:00
"github.com/prometheus/statsd_exporter/pkg/mapper"
2013-07-02 16:16:39 +00:00
)
2016-05-04 19:16:17 +00:00
func init ( ) {
prometheus . MustRegister ( version . NewCollector ( "statsd_exporter" ) )
}
2016-05-03 17:16:44 +00:00
2018-08-20 20:12:45 +00:00
func serveHTTP ( listenAddress , metricsEndpoint string ) {
2019-06-11 10:05:21 +00:00
http . Handle ( metricsEndpoint , promhttp . Handler ( ) )
2015-10-10 00:26:05 +00:00
http . HandleFunc ( "/" , func ( w http . ResponseWriter , r * http . Request ) {
w . Write ( [ ] byte ( ` < html >
2018-01-30 20:01:29 +00:00
< head > < title > StatsD Exporter < / title > < / head >
2015-10-10 00:26:05 +00:00
< body >
2018-01-30 20:01:29 +00:00
< h1 > StatsD Exporter < / h1 >
2018-08-20 20:12:45 +00:00
< p > < a href = "` + metricsEndpoint + `" > Metrics < / a > < / p >
2015-10-10 00:26:05 +00:00
< / body >
< / html > ` ) )
} )
2018-08-20 20:12:45 +00:00
log . Fatal ( http . ListenAndServe ( listenAddress , nil ) )
2013-07-02 16:16:39 +00:00
}
2017-08-01 10:21:00 +00:00
func ipPortFromString ( addr string ) ( * net . IPAddr , int ) {
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
}
2017-08-01 10:21:00 +00:00
return ip , port
}
func udpAddrFromString ( addr string ) * net . UDPAddr {
ip , port := ipPortFromString ( addr )
2013-07-02 16:16:39 +00:00
return & net . UDPAddr {
IP : ip . IP ,
Port : port ,
Zone : ip . Zone ,
}
}
2017-08-01 10:21:00 +00:00
func tcpAddrFromString ( addr string ) * net . TCPAddr {
ip , port := ipPortFromString ( addr )
return & net . TCPAddr {
IP : ip . IP ,
Port : port ,
Zone : ip . Zone ,
}
}
2019-04-03 15:36:34 +00:00
func watchConfig ( fileName string , mapper * mapper . MetricMapper , cacheSize int ) {
2013-07-08 18:37:12 +00:00
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 :
2016-05-04 19:16:17 +00:00
log . Infof ( "Config file changed (%s), attempting reload" , ev )
2019-04-03 15:36:34 +00:00
err = mapper . InitFromFile ( fileName , cacheSize )
2013-07-08 18:37:12 +00:00
if err != nil {
2016-05-04 19:16:17 +00:00
log . Errorln ( "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 {
2016-05-04 19:16:17 +00:00
log . Infoln ( "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.
2018-05-23 10:38:30 +00:00
_ = watcher . WatchFlags ( fileName , fsnotify . FSN_MODIFY )
2013-07-08 18:37:12 +00:00
case err := <- watcher . Error :
2016-05-04 19:16:17 +00:00
log . Errorln ( "Error watching config:" , err )
2013-07-08 18:37:12 +00:00
}
}
}
2018-09-22 01:09:36 +00:00
func dumpFSM ( mapper * mapper . MetricMapper , dumpFilename string ) error {
f , err := os . Create ( dumpFilename )
if err != nil {
return err
}
log . Infoln ( "Start dumping FSM to" , dumpFilename )
w := bufio . NewWriter ( f )
mapper . FSM . DumpFSM ( w )
w . Flush ( )
f . Close ( )
log . Infoln ( "Finish dumping FSM" )
return nil
}
2013-07-02 16:16:39 +00:00
func main ( ) {
2018-08-20 20:12:45 +00:00
var (
2019-04-05 23:37:23 +00:00
listenAddress = kingpin . Flag ( "web.listen-address" , "The address on which to expose the web interface and generated Prometheus metrics." ) . Default ( ":9102" ) . String ( )
metricsEndpoint = kingpin . Flag ( "web.telemetry-path" , "Path under which to expose metrics." ) . Default ( "/metrics" ) . String ( )
statsdListenUDP = kingpin . Flag ( "statsd.listen-udp" , "The UDP address on which to receive statsd metric lines. \"\" disables it." ) . Default ( ":9125" ) . String ( )
statsdListenTCP = kingpin . Flag ( "statsd.listen-tcp" , "The TCP address on which to receive statsd metric lines. \"\" disables it." ) . Default ( ":9125" ) . String ( )
statsdListenUnixgram = kingpin . Flag ( "statsd.listen-unixgram" , "The Unixgram socket path to receive statsd metric lines in datagram. \"\" disables it." ) . Default ( "" ) . String ( )
// not using Int here because flag diplays default in decimal, 0755 will show as 493
2019-04-09 18:13:33 +00:00
statsdUnixSocketMode = kingpin . Flag ( "statsd.unixsocket-mode" , "The permission mode of the unix socket." ) . Default ( "755" ) . String ( )
mappingConfig = kingpin . Flag ( "statsd.mapping-config" , "Metric mapping configuration file name." ) . String ( )
readBuffer = kingpin . Flag ( "statsd.read-buffer" , "Size (in bytes) of the operating system's transmit read buffer associated with the UDP or Unixgram connection. Please make sure the kernel parameters net.core.rmem_max is set to a value greater than the value specified." ) . Int ( )
2019-04-03 15:36:34 +00:00
cacheSize = kingpin . Flag ( "statsd.cache-size" , "Maximum size of your metric mapping cache. Relies on least recently used replacement policy if max size is reached." ) . Default ( "1000" ) . Int ( )
2019-05-26 13:08:54 +00:00
eventQueueSize = kingpin . Flag ( "statsd.event-queue-size" , "Size of internal queue for processing events" ) . Default ( "10000" ) . Int ( )
eventFlushThreshold = kingpin . Flag ( "statsd.event-flush-threshold" , "Number of events to hold in queue before flushing" ) . Default ( "1000" ) . Int ( )
eventFlushInterval = kingpin . Flag ( "statsd.event-flush-interval" , "Number of events to hold in queue before flushing" ) . Default ( "200ms" ) . Duration ( )
2019-04-09 18:13:33 +00:00
dumpFSMPath = kingpin . Flag ( "debug.dump-fsm" , "The path to dump internal FSM generated for glob matching as Dot file." ) . Default ( "" ) . String ( )
2018-08-20 20:12:45 +00:00
)
log . AddFlags ( kingpin . CommandLine )
kingpin . Version ( version . Print ( "statsd_exporter" ) )
kingpin . HelpFlag . Short ( 'h' )
kingpin . Parse ( )
2017-08-01 10:21:00 +00:00
2019-04-05 23:37:23 +00:00
if * statsdListenUDP == "" && * statsdListenTCP == "" && * statsdListenUnixgram == "" {
log . Fatalln ( "At least one of UDP/TCP/Unixgram listeners must be specified." )
2017-08-01 10:21:00 +00:00
}
2016-05-04 19:16:17 +00:00
log . Infoln ( "Starting StatsD -> Prometheus Exporter" , version . Info ( ) )
log . Infoln ( "Build context" , version . BuildContext ( ) )
2019-04-05 23:37:23 +00:00
log . Infof ( "Accepting StatsD Traffic: UDP %v, TCP %v, Unixgram %v" , * statsdListenUDP , * statsdListenTCP , * statsdListenUnixgram )
2016-05-04 19:16:17 +00:00
log . Infoln ( "Accepting Prometheus Requests on" , * listenAddress )
2013-07-03 13:25:52 +00:00
2018-08-20 20:12:45 +00:00
go serveHTTP ( * listenAddress , * metricsEndpoint )
2013-07-02 16:16:39 +00:00
2019-05-26 13:08:54 +00:00
events := make ( chan Events , * eventQueueSize )
2013-07-02 16:16:39 +00:00
defer close ( events )
2019-05-26 13:08:54 +00:00
eventQueue := newEventQueue ( events , * eventFlushThreshold , * eventFlushInterval )
2013-07-02 16:16:39 +00:00
2017-08-01 10:21:00 +00:00
if * statsdListenUDP != "" {
udpListenAddr := udpAddrFromString ( * statsdListenUDP )
uconn , err := net . ListenUDP ( "udp" , udpListenAddr )
if err != nil {
log . Fatal ( err )
}
if * readBuffer != 0 {
err = uconn . SetReadBuffer ( * readBuffer )
if err != nil {
log . Fatal ( "Error setting UDP read buffer:" , err )
}
}
2019-05-26 13:08:54 +00:00
ul := & StatsDUDPListener { conn : uconn , eventHandler : eventQueue }
go ul . Listen ( )
2013-07-02 16:16:39 +00:00
}
2015-09-09 11:58:18 +00:00
2017-08-01 10:21:00 +00:00
if * statsdListenTCP != "" {
tcpListenAddr := tcpAddrFromString ( * statsdListenTCP )
tconn , err := net . ListenTCP ( "tcp" , tcpListenAddr )
2015-09-09 11:58:18 +00:00
if err != nil {
2017-08-01 10:21:00 +00:00
log . Fatal ( err )
2015-09-09 11:58:18 +00:00
}
2017-08-01 10:21:00 +00:00
defer tconn . Close ( )
2015-09-09 11:58:18 +00:00
2019-06-14 12:29:24 +00:00
tl := & StatsDTCPListener { conn : tconn , eventHandler : eventQueue }
2019-05-26 13:08:54 +00:00
go tl . Listen ( )
2017-08-01 10:21:00 +00:00
}
2013-07-02 16:16:39 +00:00
2019-04-05 23:37:23 +00:00
if * statsdListenUnixgram != "" {
2019-04-22 23:31:24 +00:00
var err error
if _ , err = os . Stat ( * statsdListenUnixgram ) ; ! os . IsNotExist ( err ) {
2019-04-05 23:37:23 +00:00
log . Fatalf ( "Unixgram socket \"%s\" already exists" , * statsdListenUnixgram )
}
uxgconn , err := net . ListenUnixgram ( "unixgram" , & net . UnixAddr {
Net : "unixgram" ,
Name : * statsdListenUnixgram ,
} )
if err != nil {
log . Fatal ( err )
}
defer uxgconn . Close ( )
if * readBuffer != 0 {
err = uxgconn . SetReadBuffer ( * readBuffer )
if err != nil {
log . Fatal ( "Error setting Unixgram read buffer:" , err )
}
}
ul := & StatsDUnixgramListener { conn : uxgconn }
2019-05-26 13:08:54 +00:00
go ul . Listen ( )
2019-04-05 23:37:23 +00:00
// if it's an abstract unix domain socket, it won't exist on fs
// so we can't chmod it either
if _ , err := os . Stat ( * statsdListenUnixgram ) ; ! os . IsNotExist ( err ) {
2019-04-22 23:31:24 +00:00
defer os . Remove ( * statsdListenUnixgram )
2019-04-05 23:37:23 +00:00
// convert the string to octet
2019-04-09 18:13:33 +00:00
perm , err := strconv . ParseInt ( "0" + string ( * statsdUnixSocketMode ) , 8 , 32 )
2019-04-05 23:37:23 +00:00
if err != nil {
2019-04-09 18:13:33 +00:00
log . Warnf ( "Bad permission %s: %v, ignoring\n" , * statsdUnixSocketMode , err )
2019-04-05 23:37:23 +00:00
} else {
err = os . Chmod ( * statsdListenUnixgram , os . FileMode ( perm ) )
if err != nil {
log . Warnf ( "Failed to change unixgram socket permission: %v" , err )
}
}
}
}
2018-08-14 09:31:38 +00:00
mapper := & mapper . MetricMapper { MappingsCount : mappingsCount }
2013-07-08 18:37:12 +00:00
if * mappingConfig != "" {
2019-04-03 15:36:34 +00:00
err := mapper . InitFromFile ( * mappingConfig , * cacheSize )
2013-07-05 17:31:53 +00:00
if err != nil {
log . Fatal ( "Error loading config:" , err )
}
2018-09-22 01:09:36 +00:00
if * dumpFSMPath != "" {
err := dumpFSM ( mapper , * dumpFSMPath )
if err != nil {
2018-09-28 19:55:53 +00:00
log . Fatal ( "Error dumping FSM:" , err )
2018-09-22 01:09:36 +00:00
}
}
2019-04-03 15:36:34 +00:00
go watchConfig ( * mappingConfig , mapper , * cacheSize )
} else {
mapper . InitCache ( * cacheSize )
2013-07-05 17:31:53 +00:00
}
2017-09-28 18:30:17 +00:00
exporter := NewExporter ( mapper )
2019-04-22 23:31:24 +00:00
2019-04-22 23:35:12 +00:00
signals := make ( chan os . Signal , 1 )
2019-04-22 23:31:24 +00:00
signal . Notify ( signals , os . Interrupt , syscall . SIGTERM )
go exporter . Listen ( events )
<- signals
2013-07-02 16:16:39 +00:00
}