/
carbon.go
159 lines (134 loc) · 3.8 KB
/
carbon.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
// Package graphite implements sending metrics to Graphite via the Carbon text API
//
// This can be done by sending metrics one at a time or by buffering them through a channel.
package graphite
import (
"bytes"
"fmt"
"log"
"net"
"time"
)
// Graphite is the main interface to the library
type Graphite interface {
SendMetric()
Sendall()
chanRecv()
chanSend()
sendMetric()
}
// GraphiteServer is used to store the Graphite parameters
type GraphiteServer struct {
Host string
Port uint16
Timeout time.Duration
conn net.Conn
}
// Metric contains fields used for sending metrics to Graphite. If Timestamp is not set,
// one is generated when sent.
type Metric struct {
Name string
Value string
Timestamp int64
}
// defaultTimeout is the default connection timeout used by DialTimeout.
const (
defaultTimeout = 30
initialWaitTime = 30
)
// Connect is a factory function for Graphite connection
func Connect(graphite GraphiteServer) GraphiteServer {
return graphite.getconn()
}
// SendMetric is used to send a single metric to Graphite.
// Sets metric.Timestamp to current Unix time if necessary.
func (g *GraphiteServer) SendMetric(metric Metric) {
if metric.Timestamp == 0 {
metric.Timestamp = time.Now().Unix()
}
g.sendMetric(metric)
}
// Sendall is used to buffered metrics to Graphite via a channel and go routines.
func (g *GraphiteServer) Sendall(buf []Metric) {
ch := make(chan Metric, len(buf))
done := make(chan bool)
go g.chanSend(ch, buf)
go g.chanRecv(ch, done)
<-done
}
// chanRecvMetrics reads `bufsz` numbered metrics off of the given channel and
// sends them to Graphite.
// If a panic() is received within the stack, log the error and stop
// receiving metrics on the channel (preferably to can try and recover).
func (g *GraphiteServer) chanRecv(rch chan Metric, done chan bool) {
defer func() {
if r := recover(); r != nil {
log.Println(r)
done <- true
return
}
}()
for item := range rch {
g.sendMetric(item)
}
done <- true
}
// chanSendMetrics sends a Metric slice to the given channel
func (g *GraphiteServer) chanSend(ch chan Metric, buffer []Metric) {
for _, item := range buffer {
if len(item.Name) > 0 {
ch <- item
}
}
close(ch)
}
// getconn is the unexported function used to connect to Graphite.
// If there is a problem with the connection it retries with an incremental
// sleep time.
// Returns a pointer to the GraphiteServer struct.
func (g *GraphiteServer) getconn() GraphiteServer {
var (
err error
waitTime time.Duration
)
connectAddr := fmt.Sprintf("%s:%d", g.Host, g.Port)
if g.Timeout == 0 {
g.Timeout = defaultTimeout * time.Second
}
waitTime = initialWaitTime
g.conn, err = net.DialTimeout("tcp", connectAddr, g.Timeout)
for err != nil {
log.Printf(err.Error())
log.Printf(fmt.Sprintf("error connecting, retrying in %d seconds", waitTime))
time.Sleep(waitTime * time.Second)
waitTime += 5
g.conn, err = net.DialTimeout("tcp", connectAddr, g.Timeout)
}
return *g
}
// sendMetric is the unexported function to send a single metric to Graphite.
// If there is a problem, call panic(). If the problem occurs during a Write() call,
// try to reconnect to Graphite.
func (g *GraphiteServer) sendMetric(metric Metric) error {
if g.conn == nil {
panic("no graphite connection?")
}
if metric.HasEmptyField() {
panic("badly formed metric")
}
buf := bytes.NewBufferString(fmt.Sprintf("%s %s %d\n", metric.Name, metric.Value, metric.Timestamp))
_, err := g.conn.Write(buf.Bytes())
for err != nil {
log.Printf("trouble writing metric; retrying: %s", err)
g.getconn()
err = g.sendMetric(metric)
}
return nil
}
// HasEmptyField is a helper function to determine if any Metric fields are empty.
func (m *Metric) HasEmptyField() bool {
if len(m.Name) == 0 || len(m.Value) == 0 {
return true
}
return false
}