-
Notifications
You must be signed in to change notification settings - Fork 14
Expand file tree
/
Copy pathblip.go
More file actions
316 lines (268 loc) · 9.94 KB
/
Copy pathblip.go
File metadata and controls
316 lines (268 loc) · 9.94 KB
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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
// Copyright 2024 Block, Inc.
// Package blip provides high-level data structs and const for integrating with Blip.
package blip
import (
"context"
"database/sql"
"encoding/json"
"fmt"
"log"
"math"
"net/http"
"os"
"path"
"runtime"
"strings"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
)
const VERSION = "2.1.0"
var SHA = ""
// Metric types.
const (
UNKNOWN byte = iota
CUMULATIVE_COUNTER
DELTA_COUNTER
GAUGE
BOOL
EVENT
)
// Metrics are metrics collected for one plan level, from one monitor.
type Metrics struct {
Begin time.Time // when collection started
End time.Time // when collection completed
MonitorId string // ID of monitor
Plan string // plan name
Level string // level name
Interval uint // interval number
State string // state of monitor
Values map[string][]MetricValue // keyed on domain
}
func (m *Metrics) String() string {
return fmt.Sprintf("%s: %s/%s/%d (%s): %s to %s (%d)",
m.MonitorId, m.Plan, m.Level, m.Interval, m.State,
m.Begin.Round(time.Microsecond), m.End.Round(time.Microsecond), m.End.Sub(m.Begin).Round(time.Microsecond))
}
// MetricValue is one metric and its name, type, value, and tags. Tags are optional;
// the other fields are required and always set. This is the lowest-level data struct:
// a Collector reports metric values, which the monitor.Engine organize into Metrics
// by adding the appropriate metadata.
type MetricValue struct {
// Name is the domain-specific metric name, like threads_running from the
// status.global collector. Names are lowercase but otherwise not modified
// (for example, hyphens and underscores are not changed).
Name string
// Value is the value of the metric. String values are not supported.
// Boolean values are reported as 0 and 1.
Value float64
// Type is the metric type: GAUGE, CUMULATIVE_COUNTER, and other const.
Type byte
// Group is the set of name-value pairs that determine the group to which
// the metric value belongs. Only certain domains group metrics.
Group map[string]string
// Meta is optional key-value pairs that annotate or describe the metric value.
Meta map[string]string
}
// Sink sends metrics to an external destination.
type Sink interface {
// Send sends metrics to the sink. It must respect the context timeout, if any.
Send(context.Context, *Metrics) error
// Name returns the sink name (lowercase). It is used for monitor status to
// report sink errors, if any.
Name() string
}
// SinkFactory makes a Sink for a monitor.
type SinkFactory interface {
Make(SinkFactoryArgs) (Sink, error)
}
type SinkFactoryArgs struct {
SinkName string // config.monitor.sinks.name (required)
MonitorId string // config.monitor.id (required)
Options map[string]string // config.monitor.sinks.name: key-value pairs
Tags map[string]string // config.monitor.tags
}
// DbCredentials are database credentials parsed or loaded for a connection.
type DbCredentials struct {
Username string
Password string
TLS ConfigTLS
}
// PasswordSecretParser maps an AWS Secrets Manager payload to database credentials.
// The DbCredentials argument is pre-populated with config defaults and can be modified
// by the parser. The payload is the raw SecretString or SecretBinary value.
type PasswordSecretParser func(context.Context, ConfigMonitor, []byte, *DbCredentials) error
// DefaultPasswordSecretParser parses the default AWS RDS secret shape:
// "password" is required, and "username" is optional.
func DefaultPasswordSecretParser(_ context.Context, cfg ConfigMonitor, payload []byte, credentials *DbCredentials) error {
if credentials == nil {
return fmt.Errorf("credentials destination is nil")
}
credentials.Username = cfg.Username
var secretPayload map[string]interface{}
if err := json.Unmarshal(payload, &secretPayload); err != nil {
return fmt.Errorf("cannot decode secret as JSON object: %s", err)
}
if secretPayload == nil {
return fmt.Errorf("secret value is 'null' literal")
}
username, ok := secretPayload["username"]
if ok {
usernameStr, ok := username.(string)
if ok {
credentials.Username = usernameStr
}
}
password, ok := secretPayload["password"]
if !ok {
return fmt.Errorf("error retrieving 'password' value of secret")
}
passwordStr, ok := password.(string)
if !ok {
return fmt.Errorf("invalid type for 'password' value of secret")
}
credentials.Password = passwordStr
return nil
}
// Plugins are function callbacks that override specific functionality of Blip.
// Plugins are optional, but if specified it overrides the built-in functionality.
type Plugins struct {
// LoadConfig loads the Blip config on startup. It's passed the Blip default
// config that should be applied like:
//
// mycfg.ApplyDefaults(def.DefaultConfig())
//
// mycfg is the custom config loaded by the plugin, and def is the default
// config passed to the plugin. Alternatively, the plugin can set values in
// def (without unsetting default values). Without defaults, Blip might not
// work as expected.
//
// Do not call InterpolateEnvVars. Blip calls that after loading the config.
LoadConfig func(Config) (Config, error)
// LoadMonitors loads monitors on startup and reloads them on POST /monitors/reload.
LoadMonitors func(Config) ([]ConfigMonitor, error)
// LoadPlans loads plans on startup.
LoadPlans func(ConfigPlans) ([]Plan, error)
// ModifyDB modifies the *sql.DB connection pool. Use with caution.
ModifyDB func(*sql.DB, string)
// ParsePasswordSecret maps an AWS Secrets Manager payload to database credentials.
// If nil, Blip uses DefaultPasswordSecretParser.
ParsePasswordSecret PasswordSecretParser
// StartMonitor allows a monitor to start by returning true. Else the monitor
// is loaded but not started. This is used to load all monitors but start only
// certain monitors.
StartMonitor func(ConfigMonitor) bool
// TransformMetrics transforms metrics before they are sent to sinks.
// If it returns an error, all metrics are dropped (not sent). The function
// is shared and called concurrently by all monitors. Use Metrics.MonitorId
// to determine the source of the metrics. Metrics are ordered by Metrics.Interval
// should not be reordered if delta counters are used (doing so will result
// in negative or incorrect values). Otherwise, the slice of metrics can be modified.
TransformMetrics func([]*Metrics) error
}
// Factories are interfaces that override certain object creation of Blip.
// Factories are optional, but if specified the override the built-in factories.
type Factories struct {
AWSConfig AWSConfigFactory
DbConn DbFactory
HTTPClient HTTPClientFactory
}
// Env is the startup environment: command line args and environment variables.
// This is mostly used for testing to override the defaults.
type Env struct {
Args []string
Env []string
}
type AWS struct {
Region string
}
type AWSConfigFactory interface {
Make(AWS, string) (aws.Config, error)
}
type DbFactory interface {
Make(ConfigMonitor) (*sql.DB, string, error)
}
// DbProvider owns the database connections associated with one monitor.
// Primary returns the connection used by existing Blip subsystems and
// collectors. Close releases the primary connection and any additional
// database-specific resources owned by the provider.
type DbProvider interface {
Primary() *sql.DB
Close() error
}
// DbProviderFactory is an optional DbFactory capability for database engines
// that need to own more than one connection pool per monitor. Blip preserves
// the existing DbFactory.Make path for factories that do not implement it.
type DbProviderFactory interface {
DbFactory
MakeProvider(ConfigMonitor) (DbProvider, string, error)
}
type HTTPClientFactory interface {
MakeForSink(sinkName, monitorId string, opts, tags map[string]string) (*http.Client, error)
}
// Monitor states used for plan changing: https://block.github.io/blip/plans/changing/
const (
STATE_NONE = ""
STATE_OFFLINE = "offline"
STATE_STANDBY = "standby"
STATE_READ_ONLY = "read-only"
STATE_ACTIVE = "active"
)
var (
Debugging = false
debugLog = log.New(os.Stderr, "", log.LstdFlags|log.Lmicroseconds)
)
func Debug(msg string, v ...interface{}) {
if !Debugging {
return
}
_, file, line, _ := runtime.Caller(1)
msg = fmt.Sprintf("DEBUG %s:%d %s", path.Base(file), line, msg)
debugLog.Printf(msg, v...)
}
// True returns true if b is non-nil and true.
// This is convenience function related to *bool files in config structs,
// which is required for knowing when a bool config is explicitly set
// or not. If set, it's not changed; if not, it's set to the default value.
// That makes a good config experience but a less than ideal code experience
// because !*b will panic if b is nil, hence the need for this func.
func True(b *bool) bool {
if b == nil {
return false
}
return *b
}
func Bool(s string) bool {
v := strings.ToLower(s)
return v == "true" || v == "yes" || v == "enable" || v == "enabled"
}
func MonitorId(cfg ConfigMonitor) string {
switch {
case cfg.MonitorId != "":
return cfg.MonitorId
case cfg.Hostname != "":
return cfg.Hostname
case cfg.Socket != "":
return cfg.Socket
}
return ""
}
// SetOrDefault returns a if not empty, else it returns b. This is a convenience
// function to define variables with an explicit value or a DEFAULT_* value.
func SetOrDefault(a, b string) string {
if a != "" {
return a
}
return b
}
var FormatTime func(time.Time) string = func(t time.Time) string {
return t.Format(time.RFC3339)
}
// TimeLimit returns d minus p percentage (0.1 = 10%) of time up to max.
// For example, (0.1, 5s, 1s) returns 4.5s: 5000ms - 10% = 4500ms.
// But (0.1, 20s, 1s) returns 29s because 10% of 30s = 3s > 1s max, so the
// buffer is reduced to max. This is used to calculate engine max runtime (EMR)
// and collector max runtime (CMR).
func TimeLimit(p float64, d, max time.Duration) time.Duration {
ns := float64(d)
return time.Duration(ns - math.Min(ns*p, float64(max)))
}