mirror of
https://github.com/prometheus/statsd_exporter.git
synced 2024-06-03 05:49:25 +00:00
Add TCP StatsD listener support (#71)
* add TCP StatsD listener support * add listen-tcp flag to control UDP/TCP mode on same port * statsdListenUDP/statsdListenTCP as string, and alias listen-address to listen-udp * add stats for tcp error/line_too_long * add test for TCP listener
This commit is contained in:
parent
8b40f781ef
commit
07543ac557
|
@ -271,24 +271,26 @@ func TestHandlePacket(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
l := StatsDListener{}
|
for k, l := range []statsDPacketHandler{&StatsDUDPListener{}, &mockStatsDTCPListener{}} {
|
||||||
events := make(chan Events, 32)
|
events := make(chan Events, 32)
|
||||||
for i, scenario := range scenarios {
|
for i, scenario := range scenarios {
|
||||||
l.handlePacket([]byte(scenario.in), events)
|
l.handlePacket([]byte(scenario.in), events)
|
||||||
|
|
||||||
// Flatten actual events.
|
le := len(events)
|
||||||
actual := Events{}
|
// Flatten actual events.
|
||||||
for i := 0; i < len(events); i++ {
|
actual := Events{}
|
||||||
actual = append(actual, <-events...)
|
for i := 0; i < le; i++ {
|
||||||
}
|
actual = append(actual, <-events...)
|
||||||
|
}
|
||||||
|
|
||||||
if len(actual) != len(scenario.out) {
|
if len(actual) != len(scenario.out) {
|
||||||
t.Fatalf("%d. Expected %d events, got %d in scenario '%s'", i, len(scenario.out), len(actual), scenario.name)
|
t.Fatalf("%d.%d. Expected %d events, got %d in scenario '%s'", k, i, len(scenario.out), len(actual), scenario.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
for j, expected := range scenario.out {
|
for j, expected := range scenario.out {
|
||||||
if !reflect.DeepEqual(&expected, &actual[j]) {
|
if !reflect.DeepEqual(&expected, &actual[j]) {
|
||||||
t.Fatalf("%d.%d. Expected %#v, got %#v in scenario '%s'", i, j, expected, actual[j], scenario.name)
|
t.Fatalf("%d.%d.%d. Expected %#v, got %#v in scenario '%s'", k, i, j, expected, actual[j], scenario.name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
266
exporter.go
266
exporter.go
|
@ -14,10 +14,12 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"hash/fnv"
|
"hash/fnv"
|
||||||
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -304,10 +306,6 @@ func NewExporter(mapper *metricMapper, addSuffix bool) *Exporter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type StatsDListener struct {
|
|
||||||
conn *net.UDPConn
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildEvent(statType, metric string, value float64, relative bool, labels map[string]string) (Event, error) {
|
func buildEvent(statType, metric string, value float64, relative bool, labels map[string]string) (Event, error) {
|
||||||
switch statType {
|
switch statType {
|
||||||
case "c":
|
case "c":
|
||||||
|
@ -336,17 +334,6 @@ func buildEvent(statType, metric string, value float64, relative bool, labels ma
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *StatsDListener) Listen(e chan<- Events) {
|
|
||||||
buf := make([]byte, 65535)
|
|
||||||
for {
|
|
||||||
n, _, err := l.conn.ReadFromUDP(buf)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
l.handlePacket(buf[0:n], e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseDogStatsDTagsToLabels(component string) map[string]string {
|
func parseDogStatsDTagsToLabels(component string) map[string]string {
|
||||||
labels := map[string]string{}
|
labels := map[string]string{}
|
||||||
networkStats.WithLabelValues("dogstatsd_tags").Inc()
|
networkStats.WithLabelValues("dogstatsd_tags").Inc()
|
||||||
|
@ -366,105 +353,162 @@ func parseDogStatsDTagsToLabels(component string) map[string]string {
|
||||||
return labels
|
return labels
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *StatsDListener) handlePacket(packet []byte, e chan<- Events) {
|
func lineToEvents(line string) Events {
|
||||||
|
events := Events{}
|
||||||
|
if line == "" {
|
||||||
|
return events
|
||||||
|
}
|
||||||
|
|
||||||
|
elements := strings.SplitN(line, ":", 2)
|
||||||
|
if len(elements) < 2 || len(elements[0]) == 0 || !utf8.ValidString(line) {
|
||||||
|
networkStats.WithLabelValues("malformed_line").Inc()
|
||||||
|
log.Errorln("Bad line from StatsD:", line)
|
||||||
|
return events
|
||||||
|
}
|
||||||
|
metric := elements[0]
|
||||||
|
var samples []string
|
||||||
|
if strings.Contains(elements[1], "|#") {
|
||||||
|
// using datadog extensions, disable multi-metrics
|
||||||
|
samples = elements[1:]
|
||||||
|
} else {
|
||||||
|
samples = strings.Split(elements[1], ":")
|
||||||
|
}
|
||||||
|
samples:
|
||||||
|
for _, sample := range samples {
|
||||||
|
components := strings.Split(sample, "|")
|
||||||
|
samplingFactor := 1.0
|
||||||
|
if len(components) < 2 || len(components) > 4 {
|
||||||
|
networkStats.WithLabelValues("malformed_component").Inc()
|
||||||
|
log.Errorln("Bad component on line:", line)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
valueStr, statType := components[0], components[1]
|
||||||
|
|
||||||
|
var relative = false
|
||||||
|
if strings.Index(valueStr, "+") == 0 || strings.Index(valueStr, "-") == 0 {
|
||||||
|
relative = true
|
||||||
|
}
|
||||||
|
|
||||||
|
value, err := strconv.ParseFloat(valueStr, 64)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Bad value %s on line: %s", valueStr, line)
|
||||||
|
networkStats.WithLabelValues("malformed_value").Inc()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
multiplyEvents := 1
|
||||||
|
labels := map[string]string{}
|
||||||
|
if len(components) >= 3 {
|
||||||
|
for _, component := range components[2:] {
|
||||||
|
if len(component) == 0 {
|
||||||
|
log.Errorln("Empty component on line: ", line)
|
||||||
|
networkStats.WithLabelValues("malformed_component").Inc()
|
||||||
|
continue samples
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, component := range components[2:] {
|
||||||
|
switch component[0] {
|
||||||
|
case '@':
|
||||||
|
if statType != "c" && statType != "ms" {
|
||||||
|
log.Errorln("Illegal sampling factor for non-counter metric on line", line)
|
||||||
|
networkStats.WithLabelValues("illegal_sample_factor").Inc()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
samplingFactor, err = strconv.ParseFloat(component[1:], 64)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Invalid sampling factor %s on line %s", component[1:], line)
|
||||||
|
networkStats.WithLabelValues("invalid_sample_factor").Inc()
|
||||||
|
}
|
||||||
|
if samplingFactor == 0 {
|
||||||
|
samplingFactor = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if statType == "c" {
|
||||||
|
value /= samplingFactor
|
||||||
|
} else if statType == "ms" {
|
||||||
|
multiplyEvents = int(1 / samplingFactor)
|
||||||
|
}
|
||||||
|
case '#':
|
||||||
|
labels = parseDogStatsDTagsToLabels(component)
|
||||||
|
default:
|
||||||
|
log.Errorf("Invalid sampling factor or tag section %s on line %s", components[2], line)
|
||||||
|
networkStats.WithLabelValues("invalid_sample_factor").Inc()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < multiplyEvents; i++ {
|
||||||
|
event, err := buildEvent(statType, metric, value, relative, labels)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Error building event on line %s: %s", line, err)
|
||||||
|
networkStats.WithLabelValues("illegal_event").Inc()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
events = append(events, event)
|
||||||
|
}
|
||||||
|
networkStats.WithLabelValues("legal").Inc()
|
||||||
|
}
|
||||||
|
return events
|
||||||
|
}
|
||||||
|
|
||||||
|
type StatsDUDPListener struct {
|
||||||
|
conn *net.UDPConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *StatsDUDPListener) Listen(e chan<- Events) {
|
||||||
|
buf := make([]byte, 65535)
|
||||||
|
for {
|
||||||
|
n, _, err := l.conn.ReadFromUDP(buf)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
l.handlePacket(buf[0:n], e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *StatsDUDPListener) handlePacket(packet []byte, e chan<- Events) {
|
||||||
lines := strings.Split(string(packet), "\n")
|
lines := strings.Split(string(packet), "\n")
|
||||||
events := Events{}
|
events := Events{}
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
if line == "" {
|
events = append(events, lineToEvents(line)...)
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
elements := strings.SplitN(line, ":", 2)
|
|
||||||
if len(elements) < 2 || len(elements[0]) == 0 || !utf8.ValidString(line) {
|
|
||||||
networkStats.WithLabelValues("malformed_line").Inc()
|
|
||||||
log.Errorln("Bad line from StatsD:", line)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
metric := elements[0]
|
|
||||||
var samples []string
|
|
||||||
if strings.Contains(elements[1], "|#") {
|
|
||||||
// using datadog extensions, disable multi-metrics
|
|
||||||
samples = elements[1:]
|
|
||||||
} else {
|
|
||||||
samples = strings.Split(elements[1], ":")
|
|
||||||
}
|
|
||||||
samples:
|
|
||||||
for _, sample := range samples {
|
|
||||||
components := strings.Split(sample, "|")
|
|
||||||
samplingFactor := 1.0
|
|
||||||
if len(components) < 2 || len(components) > 4 {
|
|
||||||
networkStats.WithLabelValues("malformed_component").Inc()
|
|
||||||
log.Errorln("Bad component on line:", line)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
valueStr, statType := components[0], components[1]
|
|
||||||
|
|
||||||
var relative = false
|
|
||||||
if strings.Index(valueStr, "+") == 0 || strings.Index(valueStr, "-") == 0 {
|
|
||||||
relative = true
|
|
||||||
}
|
|
||||||
|
|
||||||
value, err := strconv.ParseFloat(valueStr, 64)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Bad value %s on line: %s", valueStr, line)
|
|
||||||
networkStats.WithLabelValues("malformed_value").Inc()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
multiplyEvents := 1
|
|
||||||
labels := map[string]string{}
|
|
||||||
if len(components) >= 3 {
|
|
||||||
for _, component := range components[2:] {
|
|
||||||
if len(component) == 0 {
|
|
||||||
log.Errorln("Empty component on line: ", line)
|
|
||||||
networkStats.WithLabelValues("malformed_component").Inc()
|
|
||||||
continue samples
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, component := range components[2:] {
|
|
||||||
switch component[0] {
|
|
||||||
case '@':
|
|
||||||
if statType != "c" && statType != "ms" {
|
|
||||||
log.Errorln("Illegal sampling factor for non-counter metric on line", line)
|
|
||||||
networkStats.WithLabelValues("illegal_sample_factor").Inc()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
samplingFactor, err = strconv.ParseFloat(component[1:], 64)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Invalid sampling factor %s on line %s", component[1:], line)
|
|
||||||
networkStats.WithLabelValues("invalid_sample_factor").Inc()
|
|
||||||
}
|
|
||||||
if samplingFactor == 0 {
|
|
||||||
samplingFactor = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if statType == "c" {
|
|
||||||
value /= samplingFactor
|
|
||||||
} else if statType == "ms" {
|
|
||||||
multiplyEvents = int(1 / samplingFactor)
|
|
||||||
}
|
|
||||||
case '#':
|
|
||||||
labels = parseDogStatsDTagsToLabels(component)
|
|
||||||
default:
|
|
||||||
log.Errorf("Invalid sampling factor or tag section %s on line %s", components[2], line)
|
|
||||||
networkStats.WithLabelValues("invalid_sample_factor").Inc()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < multiplyEvents; i++ {
|
|
||||||
event, err := buildEvent(statType, metric, value, relative, labels)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Error building event on line %s: %s", line, err)
|
|
||||||
networkStats.WithLabelValues("illegal_event").Inc()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
events = append(events, event)
|
|
||||||
}
|
|
||||||
networkStats.WithLabelValues("legal").Inc()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
e <- events
|
e <- events
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type StatsDTCPListener struct {
|
||||||
|
conn *net.TCPListener
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *StatsDTCPListener) Listen(e chan<- Events) {
|
||||||
|
for {
|
||||||
|
c, err := l.conn.AcceptTCP()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("AcceptTCP failed: %v", err)
|
||||||
|
}
|
||||||
|
go l.handleConn(c, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *StatsDTCPListener) handleConn(c *net.TCPConn, e chan<- Events) {
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
r := bufio.NewReader(c)
|
||||||
|
for {
|
||||||
|
line, isPrefix, err := r.ReadLine()
|
||||||
|
if err != nil {
|
||||||
|
if err != io.EOF {
|
||||||
|
networkStats.WithLabelValues("tcp_error").Inc()
|
||||||
|
log.Errorf("Read %s failed: %v", c.RemoteAddr(), err)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if isPrefix {
|
||||||
|
networkStats.WithLabelValues("tcp_line_too_long").Inc()
|
||||||
|
log.Errorf("Read %s failed: line too long", c.RemoteAddr())
|
||||||
|
break
|
||||||
|
}
|
||||||
|
e <- lineToEvents(string(line))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ func benchmarkExporter(times int, b *testing.B) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for n := 0; n < b.N; n++ {
|
for n := 0; n < b.N; n++ {
|
||||||
l := StatsDListener{}
|
l := StatsDUDPListener{}
|
||||||
// there are more events than input lines, need bigger buffer
|
// there are more events than input lines, need bigger buffer
|
||||||
events := make(chan Events, len(bytesInput)*times*2)
|
events := make(chan Events, len(bytesInput)*times*2)
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,8 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -56,19 +58,55 @@ func TestNegativeCounter(t *testing.T) {
|
||||||
// It sends the same tags first with a valid value, then with an invalid one.
|
// It sends the same tags first with a valid value, then with an invalid one.
|
||||||
// The exporter should not panic, but drop the invalid event
|
// The exporter should not panic, but drop the invalid event
|
||||||
func TestInvalidUtf8InDatadogTagValue(t *testing.T) {
|
func TestInvalidUtf8InDatadogTagValue(t *testing.T) {
|
||||||
l := StatsDListener{}
|
|
||||||
events := make(chan Events, 2)
|
|
||||||
|
|
||||||
l.handlePacket([]byte("bar:200|c|#tag:value"), events)
|
|
||||||
l.handlePacket([]byte("bar:200|c|#tag:\xc3\x28invalid"), events)
|
|
||||||
|
|
||||||
ex := NewExporter(&metricMapper{}, true)
|
ex := NewExporter(&metricMapper{}, true)
|
||||||
|
for _, l := range []statsDPacketHandler{&StatsDUDPListener{}, &mockStatsDTCPListener{}} {
|
||||||
|
events := make(chan Events, 2)
|
||||||
|
|
||||||
|
l.handlePacket([]byte("bar:200|c|#tag:value\nbar:200|c|#tag:\xc3\x28invalid"), events)
|
||||||
|
|
||||||
|
// Close channel to signify we are done with the listener after a short period.
|
||||||
|
go func() {
|
||||||
|
time.Sleep(time.Millisecond * 100)
|
||||||
|
close(events)
|
||||||
|
}()
|
||||||
|
|
||||||
|
ex.Listen(events)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type statsDPacketHandler interface {
|
||||||
|
handlePacket(packet []byte, e chan<- Events)
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockStatsDTCPListener struct {
|
||||||
|
StatsDTCPListener
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ml *mockStatsDTCPListener) handlePacket(packet []byte, e chan<- Events) {
|
||||||
|
lc, err := net.ListenTCP("tcp", nil)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("mockStatsDTCPListener: listen failed: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
defer lc.Close()
|
||||||
|
|
||||||
// Close channel to signify we are done with the listener after a short period.
|
|
||||||
go func() {
|
go func() {
|
||||||
time.Sleep(time.Millisecond * 100)
|
cc, err := net.DialTCP("tcp", nil, lc.Addr().(*net.TCPAddr))
|
||||||
close(events)
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("mockStatsDTCPListener: dial failed: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
defer cc.Close()
|
||||||
|
|
||||||
|
n, err := cc.Write(packet)
|
||||||
|
if err != nil || n != len(packet) {
|
||||||
|
panic(fmt.Sprintf("mockStatsDTCPListener: write failed: %v,%d", err, n))
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
ex.Listen(events)
|
sc, err := lc.AcceptTCP()
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("mockStatsDTCPListener: accept failed: %v", err))
|
||||||
|
}
|
||||||
|
ml.handleConn(sc, e)
|
||||||
}
|
}
|
||||||
|
|
68
main.go
68
main.go
|
@ -34,7 +34,9 @@ func init() {
|
||||||
var (
|
var (
|
||||||
listenAddress = flag.String("web.listen-address", ":9102", "The address on which to expose the web interface and generated Prometheus metrics.")
|
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.")
|
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.")
|
statsdListenAddress = flag.String("statsd.listen-address", "", "The UDP address on which to receive statsd metric lines. DEPRECATED, use statsd.listen-udp instead.")
|
||||||
|
statsdListenUDP = flag.String("statsd.listen-udp", ":9125", "The UDP address on which to receive statsd metric lines. \"\" disables it.")
|
||||||
|
statsdListenTCP = flag.String("statsd.listen-tcp", ":9125", "The TCP address on which to receive statsd metric lines. \"\" disables it.")
|
||||||
mappingConfig = flag.String("statsd.mapping-config", "", "Metric mapping configuration file name.")
|
mappingConfig = flag.String("statsd.mapping-config", "", "Metric mapping configuration file name.")
|
||||||
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.")
|
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.")
|
||||||
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).")
|
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).")
|
||||||
|
@ -55,7 +57,7 @@ func serveHTTP() {
|
||||||
log.Fatal(http.ListenAndServe(*listenAddress, nil))
|
log.Fatal(http.ListenAndServe(*listenAddress, nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
func udpAddrFromString(addr string) *net.UDPAddr {
|
func ipPortFromString(addr string) (*net.IPAddr, int) {
|
||||||
host, portStr, err := net.SplitHostPort(addr)
|
host, portStr, err := net.SplitHostPort(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Bad StatsD listening address", addr)
|
log.Fatal("Bad StatsD listening address", addr)
|
||||||
|
@ -74,6 +76,11 @@ func udpAddrFromString(addr string) *net.UDPAddr {
|
||||||
log.Fatalf("Bad port %s: %s", portStr, err)
|
log.Fatalf("Bad port %s: %s", portStr, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return ip, port
|
||||||
|
}
|
||||||
|
|
||||||
|
func udpAddrFromString(addr string) *net.UDPAddr {
|
||||||
|
ip, port := ipPortFromString(addr)
|
||||||
return &net.UDPAddr{
|
return &net.UDPAddr{
|
||||||
IP: ip.IP,
|
IP: ip.IP,
|
||||||
Port: port,
|
Port: port,
|
||||||
|
@ -81,6 +88,15 @@ func udpAddrFromString(addr string) *net.UDPAddr {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func tcpAddrFromString(addr string) *net.TCPAddr {
|
||||||
|
ip, port := ipPortFromString(addr)
|
||||||
|
return &net.TCPAddr{
|
||||||
|
IP: ip.IP,
|
||||||
|
Port: port,
|
||||||
|
Zone: ip.Zone,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func watchConfig(fileName string, mapper *metricMapper) {
|
func watchConfig(fileName string, mapper *metricMapper) {
|
||||||
watcher, err := fsnotify.NewWatcher()
|
watcher, err := fsnotify.NewWatcher()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -122,12 +138,22 @@ func main() {
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if *statsdListenAddress != "" {
|
||||||
|
log.Warnln("Warning: statsd.listen-address is DEPRECATED, please use statsd.listen-udp instead.")
|
||||||
|
*statsdListenUDP = *statsdListenAddress
|
||||||
|
}
|
||||||
|
|
||||||
|
if *statsdListenUDP == "" && *statsdListenTCP == "" {
|
||||||
|
log.Fatalln("At least one of UDP/TCP listeners must be specified.")
|
||||||
|
}
|
||||||
|
|
||||||
if *addSuffix {
|
if *addSuffix {
|
||||||
log.Warnln("Warning: Using -statsd.add-suffix is discouraged. We recommend explicitly naming metrics appropriately in the mapping configuration.")
|
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("Starting StatsD -> Prometheus Exporter", version.Info())
|
||||||
log.Infoln("Build context", version.BuildContext())
|
log.Infoln("Build context", version.BuildContext())
|
||||||
log.Infoln("Accepting StatsD Traffic on", *statsdListenAddress)
|
log.Infof("Accepting StatsD Traffic: UDP %v, TCP %v", *statsdListenUDP, *statsdListenTCP)
|
||||||
log.Infoln("Accepting Prometheus Requests on", *listenAddress)
|
log.Infoln("Accepting Prometheus Requests on", *listenAddress)
|
||||||
|
|
||||||
go serveHTTP()
|
go serveHTTP()
|
||||||
|
@ -135,21 +161,35 @@ func main() {
|
||||||
events := make(chan Events, 1024)
|
events := make(chan Events, 1024)
|
||||||
defer close(events)
|
defer close(events)
|
||||||
|
|
||||||
listenAddr := udpAddrFromString(*statsdListenAddress)
|
if *statsdListenUDP != "" {
|
||||||
conn, err := net.ListenUDP("udp", listenAddr)
|
udpListenAddr := udpAddrFromString(*statsdListenUDP)
|
||||||
if err != nil {
|
uconn, err := net.ListenUDP("udp", udpListenAddr)
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if *readBuffer != 0 {
|
|
||||||
err = conn.SetReadBuffer(*readBuffer)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Error setting UDP read buffer:", err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if *readBuffer != 0 {
|
||||||
|
err = uconn.SetReadBuffer(*readBuffer)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error setting UDP read buffer:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ul := &StatsDUDPListener{conn: uconn}
|
||||||
|
go ul.Listen(events)
|
||||||
}
|
}
|
||||||
|
|
||||||
l := &StatsDListener{conn: conn}
|
if *statsdListenTCP != "" {
|
||||||
go l.Listen(events)
|
tcpListenAddr := tcpAddrFromString(*statsdListenTCP)
|
||||||
|
tconn, err := net.ListenTCP("tcp", tcpListenAddr)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer tconn.Close()
|
||||||
|
|
||||||
|
tl := &StatsDTCPListener{conn: tconn}
|
||||||
|
go tl.Listen(events)
|
||||||
|
}
|
||||||
|
|
||||||
mapper := &metricMapper{}
|
mapper := &metricMapper{}
|
||||||
if *mappingConfig != "" {
|
if *mappingConfig != "" {
|
||||||
|
|
Loading…
Reference in a new issue