/
main.go
177 lines (160 loc) · 4.49 KB
/
main.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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
// Command to collect stats from banstalkd tubes and send to statsd
package main
import (
"errors"
"flag"
"fmt"
"log"
"os"
"runtime"
"strconv"
"strings"
"time"
"github.com/cactus/go-statsd-client/statsd"
"github.com/kr/beanstalk"
)
var config struct {
BeanstalkdAddr string
StatsdAddr string
StatsdPrefix string
Verbosity int
Period time.Duration
Tubes map[string]bool
}
const Version = "1.0"
func main() {
var tubes string
var showver bool
flag.BoolVar(&showver, "version", false, "Show version and exit")
flag.StringVar(&config.BeanstalkdAddr, "beanstalkd", "127.0.0.1:11300", "Beanstalkd address")
flag.StringVar(&config.StatsdAddr, "statsd", "127.0.0.1:8125", "StatsD server address")
flag.StringVar(&config.StatsdPrefix, "prefix", "beanstalk", "StatsD prefix for all stats")
flag.IntVar(&config.Verbosity, "v", 1, "Output verbosity level. Use 0 (quiet), 1 or 2")
flag.DurationVar(&config.Period, "period", time.Second, "How often to send stats. Ex.: 1s (second), 2m (minutes), 400ms (milliseconds)")
flag.StringVar(&tubes, "tubes", "*", "Comma separated list of tubes to watch. Use * to watch all")
flag.Parse()
if showver {
fmt.Fprintf(os.Stderr, "%s %s (%s)\n", os.Args[0], Version, runtime.Version())
return
}
var err error
config.Tubes, err = parseTubesWatch(tubes)
if err != nil {
log.Fatal(err)
}
for {
stats, err := TubesStats()
if err != nil {
log.Print("ERROR (retry): ", err)
continue
}
SendStats(stats)
time.Sleep(config.Period)
}
}
// TubesStats connects to beanstalkd and return a hash of all stats
// for each tube we are watching
//
// Return error if fail to connect to beanstalkd of fail to get stats
// for a specic tube
//
// Panic if a specic stat is not an integer, this should never happen
func TubesStats() (stats map[string]map[string]int, err error) {
conn, err := beanstalk.Dial("tcp", config.BeanstalkdAddr)
if err != nil {
return stats, fmt.Errorf("Failed to connect to beanstalkd: %s", err)
}
tubes, err := conn.ListTubes()
if err != nil {
return stats, fmt.Errorf("Failed to list tubes: %s", err)
}
stats = map[string]map[string]int{}
for _, tubeName := range tubes {
if !watchingTube(tubeName) {
continue
}
tube := &beanstalk.Tube{
Name: tubeName,
Conn: conn,
}
data, err := tube.Stats()
if err != nil {
return stats, fmt.Errorf("Failed to get stats for tube %s: %s", tubeName, err)
}
stats[tubeName] = map[string]int{
"buried": mustInt(data["current-jobs-buried"]),
"ready": mustInt(data["current-jobs-ready"]),
"delayed": mustInt(data["current-jobs-delayed"]),
"reserver": mustInt(data["current-jobs-reserved"]),
"urgent": mustInt(data["current-jobs-urgent"]),
"waiting": mustInt(data["current-waiting"]),
"total": mustInt(data["total-jobs"]),
}
}
return stats, nil
}
// SendStats will send all stats collected by TubesStats() to statsd
//
// Return error if fail to connect to statsd
func SendStats(stats map[string]map[string]int) error {
client, err := statsd.NewClient(config.StatsdAddr, config.StatsdPrefix)
if err != nil {
return fmt.Errorf("Failed to connect to statsd: %s", err)
}
for tube, sts := range stats {
verbose1("sending stats of tube %s", tube)
for stat, value := range sts {
name := fmt.Sprintf("%s.%s", tube, stat)
client.Gauge(name, int64(value), 1.0)
verbose2("%s.%s: %d", config.StatsdPrefix, name, value)
}
}
return nil
}
// parseTubesWatch parses the -tubes command line argument into
// a hash 'tube name' -> true
//
// If we are watching all tubes, the hash will contain '*' -> true
func parseTubesWatch(tubes string) (map[string]bool, error) {
tubes = strings.Trim(tubes, " ")
if tubes == "" {
return map[string]bool{}, errors.New("-tubes can't be blank")
}
if tubes == "*" {
return map[string]bool{"*": true}, nil
}
t := map[string]bool{}
for _, tube := range strings.Split(tubes, ",") {
tube = strings.Trim(tube, " ")
if tube != "" {
t[tube] = true
}
}
if len(t) == 0 {
return map[string]bool{}, errors.New("-tubes can't be blank")
}
return t, nil
}
func watchingTube(name string) bool {
if config.Tubes["*"] == true {
return true
}
return config.Tubes[name] == true
}
func mustInt(n string) int {
i, err := strconv.Atoi(n)
if err != nil {
log.Panic(err)
}
return i
}
func verbose1(format string, v ...interface{}) {
if config.Verbosity >= 1 {
log.Printf(format, v...)
}
}
func verbose2(format string, v ...interface{}) {
if config.Verbosity >= 2 {
log.Printf(format, v...)
}
}