-
Notifications
You must be signed in to change notification settings - Fork 32
/
throw.go
133 lines (116 loc) · 3.47 KB
/
throw.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
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
"time"
"github.com/jpillora/backoff"
"github.com/mackerelio/mackerel-client-go"
"github.com/mackerelio/mkr/logger"
"github.com/mackerelio/mkr/mackerelclient"
"github.com/urfave/cli"
)
var commandThrow = cli.Command{
Name: "throw",
Usage: "Post metric values",
ArgsUsage: "[--host | -H <hostId>] [--service | -s <service>] [--retry | -r N ] stdin",
Description: `
Post metric values to 'host metric' or 'service metric'.
Output format of metric values are compatible with that of a Sensu plugin.
Requests "POST /api/v0/tsdb". See https://mackerel.io/api-docs/entry/host-metrics#post .
Automatically retries the API request when --retry is specified.
`,
Action: doThrow,
Flags: []cli.Flag{
cli.StringFlag{Name: "host, H", Value: "", Usage: "Post host metric values to <hostID>."},
cli.StringFlag{Name: "service, s", Value: "", Usage: "Post service metric values to <service>."},
cli.IntFlag{Name: "retry, r", Usage: "Retries up to N times when API request fails."},
},
}
func doThrow(c *cli.Context) error {
optHostID := c.String("host")
optService := c.String("service")
optMaxRetry := c.Int("retry")
var metricValues []*(mackerel.MetricValue)
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
line := scanner.Text()
// name, value, timestamp
// ex.) tcp.CLOSING 0 1397031808
items := strings.Fields(line)
if len(items) != 3 {
continue
}
value, err := strconv.ParseFloat(items[1], 64)
if err != nil {
logger.Log("warning", fmt.Sprintf("Failed to parse values: %s", err))
continue
}
time, err := strconv.ParseInt(items[2], 10, 64)
if err != nil {
logger.Log("warning", fmt.Sprintf("Failed to parse values: %s", err))
continue
}
name := items[0]
if optHostID != "" && !strings.HasPrefix(name, "custom.") {
name = "custom." + name
}
metricValue := &mackerel.MetricValue{
Name: name,
Value: value,
Time: time,
}
metricValues = append(metricValues, metricValue)
}
logger.ErrorIf(scanner.Err())
client := mackerelclient.NewFromContext(c)
if optHostID != "" {
logger.DieIf(requestWithRetry(func() error {
return client.PostHostMetricValuesByHostID(optHostID, metricValues)
}, optMaxRetry))
for _, metric := range metricValues {
logger.Log("thrown", fmt.Sprintf("%s '%s\t%f\t%d'", optHostID, metric.Name, metric.Value, metric.Time))
}
} else if optService != "" {
logger.DieIf(requestWithRetry(func() error {
return client.PostServiceMetricValues(optService, metricValues)
}, optMaxRetry))
for _, metric := range metricValues {
logger.Log("thrown", fmt.Sprintf("%s '%s\t%f\t%d'", optService, metric.Name, metric.Value, metric.Time))
}
} else {
cli.ShowCommandHelp(c, "throw")
os.Exit(1)
}
return nil
}
var minInterval = 15 * time.Second
func requestWithRetry(f func() error, maxRetry int) error {
b := &backoff.Backoff{
Min: minInterval,
Max: 5 * time.Minute,
Factor: 2,
Jitter: false,
}
var err error
var delay time.Duration
for int(b.Attempt()) <= maxRetry {
if b.Attempt() > 0 {
logger.Log("warning", fmt.Sprintf("Failed to request. will retry after %.0f seconds. Error: %s", delay.Seconds(), err))
time.Sleep(delay)
}
if err = f(); err == nil {
// SUCCESS!!
break
} else if e, isAPIError := err.(*mackerel.APIError); isAPIError {
// Do not retry when status is 4XX
if e.StatusCode >= 400 && e.StatusCode < 500 {
break
}
}
delay = b.Duration()
}
return err
}