-
Notifications
You must be signed in to change notification settings - Fork 0
/
poke.go
161 lines (132 loc) · 4.39 KB
/
poke.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
package main
import (
"crypto/tls"
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"log"
"net/http"
"strings"
"time"
)
const (
Ok = 0
Error = 1
)
var (
influxdbEndpoint string
endpointsFile string
measurementName string
timeout int64
interval int64
counter int64
)
type Poke struct {
Endpoint string `json:"endpoint"`
Tags map[string]string `json:"tags"`
}
func init() {
flag.StringVar(&influxdbEndpoint, "influxdbEndpoint", "", "Which InfluxDB endpoint to post the results to (required)")
flag.StringVar(&endpointsFile, "endpoints", "", "JSON-file containing your endpoints (required)")
flag.StringVar(&measurementName, "measurement-name", "pokes", "Name of InfluxDB measurement to write data to")
flag.Int64Var(&timeout, "timeout", 2, "default request timeout (seconds)")
flag.Int64Var(&interval, "interval", 0, "At what interval you want the pokes to be performed (run once if omitted)")
flag.Parse()
}
func main() {
flag.Parse()
if len(influxdbEndpoint) == 0 || len(endpointsFile) == 0 {
flag.Usage()
log.Fatal("missing required configuration")
}
pokes, err := pokes(endpointsFile)
if err != nil {
log.Fatalf("unable to extract endpoints to poke from file: %s: %s", endpointsFile, err)
}
client := http.Client{
Timeout: time.Second * time.Duration(timeout),
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}}
for {
timestamp := time.Now().Unix()
var payloadElements []string
for _, poke := range pokes {
resultCode := Error
errorMsg := ""
var elapsed int64
start := time.Now()
resp, err := client.Get(withCounter(poke.Endpoint))
if err != nil {
errorMsg = fmt.Sprintf("unable to perform request: %s", err)
log.Printf("error: unable to perform request to endpoint %s: %s", poke.Endpoint, err)
} else {
elapsed = time.Since(start).Milliseconds()
if resp.StatusCode == 200 {
resultCode = Ok
} else {
log.Printf("got an unsuccessful statuscode %d for endpoint %s\n", resp.StatusCode, poke.Endpoint)
errorMsg = fmt.Sprintf("got an unsuccessful statuscode: %d", resp.StatusCode)
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
errorMsg += fmt.Sprintf(". Unable to read body: %s", err)
} else {
errorMsg += fmt.Sprintf(". Response body: %s", string(b))
_ = resp.Body.Close()
}
}
}
elem := fmt.Sprintf("%s,%s latency_ms=%d,value=%d,counter=%d,err=\"%s\" %d", measurementName, tags(poke), elapsed, resultCode, counter, errorMsg, timestamp)
payloadElements = append(payloadElements, elem)
}
if err := postToInfluxDB(strings.Join(payloadElements, "\n")); err != nil {
log.Print(err)
} else {
log.Printf("Successfully posted %d pokes to InfluxDB\n", len(pokes))
}
// if no interval is provided, we only run once
if interval == 0 {
return
}
counter++
time.Sleep(time.Duration(interval) * time.Second)
}
}
func withCounter(endpoint string) string {
return strings.ReplaceAll(fmt.Sprintf("%s/?counter=%d", endpoint, counter), "//?", "/?")
}
func tags(poke Poke) string {
pairs := []string{fmt.Sprintf("endpoint=%s", escapeSpecialChars(poke.Endpoint))}
for key, value := range poke.Tags {
pairs = append(pairs, fmt.Sprintf("%s=%s", escapeSpecialChars(key), escapeSpecialChars(value)))
}
return strings.Join(pairs, ",")
}
func pokes(endpointsFile string) (pokes []Poke, err error) {
data, err := ioutil.ReadFile(endpointsFile)
if err != nil {
return nil, fmt.Errorf("unable to read endpoints file: %s: %s", endpointsFile, err)
}
if err := json.Unmarshal(data, &pokes); err != nil {
return nil, fmt.Errorf("unable to unmarshal endpoint config: %s", err)
}
return
}
func postToInfluxDB(payload string) error {
log.Printf("Posting the following payload to InfluxDB (%s)\n%s", influxdbEndpoint, payload)
resp, err := http.Post(influxdbEndpoint, "text/plain", strings.NewReader(payload))
if err != nil {
return fmt.Errorf("unable to post pokes to InfluxDB: %s", err)
}
if resp != nil && resp.StatusCode != 204 {
body, _ := ioutil.ReadAll(resp.Body)
defer resp.Body.Close()
return fmt.Errorf("unable to post pokes to InfluxDB, got HTTP status code %d and body: %s", resp.StatusCode, string(body))
}
return nil
}
// escapeSpecialChars escapes '=' and ','
func escapeSpecialChars(string string) string {
return strings.Replace(strings.Replace(string, "=", "\\=", -1), ",", "\\,", -1)
}