wavefront-client 3.1.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +8 -8
- data/README-cli.md +289 -0
- data/README.md +3 -101
- data/bin/wavefront +138 -78
- data/lib/wavefront/cli/alerts.rb +1 -0
- data/lib/wavefront/cli/events.rb +220 -0
- data/lib/wavefront/cli/ts.rb +1 -10
- data/lib/wavefront/client/version.rb +1 -1
- data/lib/wavefront/constants.rb +4 -0
- data/lib/wavefront/events.rb +18 -9
- data/lib/wavefront/mixins.rb +17 -1
- data/lib/wavefront/response.rb +76 -15
- data/wavefront-client.gemspec +1 -3
- metadata +9 -13
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
YmVlOTljMDc5ZWJkOTAwYzZiMDlhOTJhYTY3ZTI2YjRjNTBjZDliMw==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
M2Y4ZDExZTE1NDFmNjUxZTI1NGYzOTExYzQ0ZmU2YjVhMTMzMGVmZA==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
NjRmYmU4NjU2NGQ0MGIwMzU4N2Q0MjQwNzQ4NWViYTJkYTZlNjk1Njk0Mzlh
|
10
|
+
MWU1MTYyNDc3NzhlYzNiMDhmMjkwOWNiODExNDMwOWNlYWY5NWMzYWJhNjYw
|
11
|
+
ZTdiMmVlNGYzOTY1MWQxMjZjYTUyZjA4ZjZiODY2MTI0Y2ZmZjE=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
ZjUzOTZlZDlhOGY3M2NkZWU3ZDUwM2E4Y2NmNWJiNmFmZGI4MjQ4NmI1Mzk3
|
14
|
+
NjZiMmQ1MTE5MzAwYmRhN2NkZTI0NTVmYjkyNDM0YzNiNWViMWNiYjExNTE1
|
15
|
+
YmQ2ZTIzNzVmMTY4ZDJmNjI2YmQ1ZDg2NzYxMzIzZjJmYTY4N2M=
|
data/README-cli.md
ADDED
@@ -0,0 +1,289 @@
|
|
1
|
+
# Wavefront CLI
|
2
|
+
|
3
|
+
`wavefront <command> [options]`
|
4
|
+
|
5
|
+
The `wavefront` command provides CLI access to Wavefront. Different
|
6
|
+
command keywords enable different functionality.
|
7
|
+
|
8
|
+
## Global Options
|
9
|
+
|
10
|
+
The following options are valid in almost all contexts.
|
11
|
+
|
12
|
+
```
|
13
|
+
-c, --config=FILE path to configuration file [default: ~/.wavefront]
|
14
|
+
-P, --profile=NAME profile in configuration file [default: default]
|
15
|
+
-E, --endpoint=URI cluster endpoint [default: metrics.wavefront.com]
|
16
|
+
-t, --token=TOKEN Wavefront authentication token
|
17
|
+
-D, --debug enable debug mode
|
18
|
+
-h, --help show help for command
|
19
|
+
```
|
20
|
+
|
21
|
+
## `ts` Mode: Retrieving Timeseries Data
|
22
|
+
|
23
|
+
The `ts` command is used to submit a standard timeseries query to
|
24
|
+
Wavefront. It can output the timeseries data in a number of formats.
|
25
|
+
You must specify a query granularity, and you can timebox your
|
26
|
+
query.
|
27
|
+
|
28
|
+
```
|
29
|
+
Usage:
|
30
|
+
wavefront ts [-c file] [-P profile] [-E endpoint] [-t token] [-OD]
|
31
|
+
[-S | -m | -H | -d] [-s time] [-e time] [-f format] [-p num]
|
32
|
+
[-X bool] <query>
|
33
|
+
|
34
|
+
Options:
|
35
|
+
-S, --seconds query granularity of seconds
|
36
|
+
-m, --minutes query granularity of minutes
|
37
|
+
-H, --hours query granularity of hours
|
38
|
+
-d, --days query granularity of days
|
39
|
+
-s, --start=TIME start of query window in epoch seconds or
|
40
|
+
strptime parseable format
|
41
|
+
-e, --end=TIME end of query window in epoch seconds or
|
42
|
+
strptime parseable format
|
43
|
+
-f, --format=STRING output format (raw, ruby, graphite,
|
44
|
+
highcharts, human)
|
45
|
+
[default: raw]
|
46
|
+
-p, --prefixlength=NUM number of path elements to treat as prefix
|
47
|
+
in schema manipulation. [default: 1]
|
48
|
+
-X, --strict=BOOL Do not return points outside the query
|
49
|
+
window. [default: true]
|
50
|
+
-O, --includeObsoleteMetrics include metrics unreported for > 4 weeks
|
51
|
+
```
|
52
|
+
|
53
|
+
The `-X` flag is now more-or-less obsolete. It was required when the
|
54
|
+
API defaulted to returning data outside the specified query window.
|
55
|
+
|
56
|
+
### Examples
|
57
|
+
|
58
|
+
View ethernet traffic on the host `shark`, in one-minute buckets,
|
59
|
+
starting at noon today, in human-readable format.
|
60
|
+
|
61
|
+
```
|
62
|
+
$ wavefront ts -f human -m --start=12:00 \
|
63
|
+
'ts("lab.generic.host.interface-phys.if_packets.*", source=shark)'
|
64
|
+
query ts("lab.generic.host.interface-phys.if_packets.*", source=shark)
|
65
|
+
timeseries 0
|
66
|
+
label lab.generic.host.interface-phys.if_packets.tx
|
67
|
+
host shark
|
68
|
+
2016-06-27 12:00:00 136.0
|
69
|
+
2016-06-27 12:01:00 15.666666666666668
|
70
|
+
2016-06-27 12:02:00 15.8
|
71
|
+
2016-06-27 12:03:00 15.3
|
72
|
+
2016-06-27 12:04:00 19.35
|
73
|
+
2016-06-27 12:05:00 315.451
|
74
|
+
2016-06-27 12:06:00 110.98316666666668
|
75
|
+
2016-06-27 12:07:00 34.40016666666667
|
76
|
+
2016-06-27 12:08:00 308.667
|
77
|
+
2016-06-27 12:09:00 239.05016666666666
|
78
|
+
2016-06-27 12:10:00 17.883333333333333
|
79
|
+
...
|
80
|
+
```
|
81
|
+
|
82
|
+
Show all events between 6pm and 8pm today:
|
83
|
+
|
84
|
+
```
|
85
|
+
$ ./wavefront ts -f human -m --start=18:00 --end=20:00 'events()'
|
86
|
+
2016-06-27 16:55:59 -> 2016-06-27 16:56:40 (41s) new event [shark,box]
|
87
|
+
2016-06-27 18:41:57 -> 2016-06-27 18:41:57 (inst) info alert-updated Alert Edited: Point Rate
|
88
|
+
2016-06-27 18:42:03 -> 2016-06-27 18:44:09 (2m 6s) severe alert Point Rate []
|
89
|
+
2016-06-27 18:44:09 -> 2016-06-27 18:44:09 (inst) info alert-updated Alert Edited: Point Rate
|
90
|
+
2016-06-27 18:46:33 -> 2016-06-27 18:46:33 (inst) instantaneous_event [box]
|
91
|
+
2016-06-27 18:47:53 -> 2016-06-27 18:47:53 (inst) instantaneous_event [box] something important just happened
|
92
|
+
2016-06-27 19:25:16 -> 2016-06-27 19:26:32 (1m 15s) info puppet_run [box] Puppet run
|
93
|
+
```
|
94
|
+
|
95
|
+
Output is different for event queries. The columns are: start time -> end
|
96
|
+
time, (duration), severity, event type, [source(s)], details.
|
97
|
+
|
98
|
+
## `alerts` Mode: Retrieving Alert Data
|
99
|
+
|
100
|
+
The `alerts` command lets you view alerts. It does not currently
|
101
|
+
allow creation and removal of alerts. Alert data can be presented in
|
102
|
+
a number of formats, but defaults to a human-readable form. If you
|
103
|
+
wish to parse the output, please use the `ruby` or `json`
|
104
|
+
formatters.
|
105
|
+
|
106
|
+
```
|
107
|
+
Usage:
|
108
|
+
wavefront alerts [-c file] [-P profile] [-E endpoint] [-t token]
|
109
|
+
[-f format] [-p tag] [ -s tag] <state>
|
110
|
+
|
111
|
+
Options:
|
112
|
+
-f, --format=STRING output format (ruby, json, human)
|
113
|
+
[default: human]
|
114
|
+
-p, --private=TAG retrieve only alerts with named private tags,
|
115
|
+
comma delimited.
|
116
|
+
-s, --shared=TAG retrieve only alerts with named shared tags, comma
|
117
|
+
delimited.
|
118
|
+
```
|
119
|
+
|
120
|
+
### Examples
|
121
|
+
|
122
|
+
List all alerts in human-readable format. Alerts are separated by a
|
123
|
+
single blank line.
|
124
|
+
|
125
|
+
```
|
126
|
+
$ wavefront alerts -P sysdef all
|
127
|
+
name over memory cap
|
128
|
+
created 2016-06-06 13:35:32 +0100
|
129
|
+
severity SMOKE
|
130
|
+
condition deriv(ts("prod.www.host.tenant.memory_cap.nover")) > 0
|
131
|
+
displayExpression ts("prod.www.host.tenant.memory_cap.nover")
|
132
|
+
minutes 2
|
133
|
+
resolveAfterMinutes 10
|
134
|
+
updated 2016-06-06 13:35:32 +0100
|
135
|
+
alertStates CHECKING
|
136
|
+
metricsUsed
|
137
|
+
hostsUsed
|
138
|
+
additionalInformation A process has pushed the instance over its memory cap.
|
139
|
+
That is, the `memory_cap:nover` counter has been
|
140
|
+
incremented. Check memory pressure.
|
141
|
+
|
142
|
+
name JPC Memory Shortage
|
143
|
+
created 2016-05-16 16:49:20 +0100
|
144
|
+
severity WARN
|
145
|
+
...
|
146
|
+
```
|
147
|
+
|
148
|
+
Show alerts currently firing, in JSON format:
|
149
|
+
|
150
|
+
```
|
151
|
+
$ wavefront alerts -P sysdef --format ruby active
|
152
|
+
"[{\"customerTagsWithCounts\":{},\"userTagsWithCounts\":{},\"created\":1459508340708,\"name\":\"Point Rate\",\"conditionQBEnabled\":false,\"displayExpressionQBEnabled\":false,\"condition\":\"sum(deriv(ts(~collector.points.valid))) > 50000\",\"displayExpression\":\"sum(deriv(ts(~collector.points.valid)))\",\"minutes\":5,\"target\":\"alerts@company.com,\",\"event\":{\"name\":\"Point Rate\",\"startTime\":1467049323203,\"annotations\":{\"severity\":\"severe\",\"type\":\"alert\",\"created\":\"1459508340708\",\"target\":\"alerts@company.com,\"},\"hosts\":[\"\"],\"table\":\"sysdef\"},\"failingHostLabelPairs\":[{\"label\":\"\",\"observed\":5,\"firing\":5}],\"updated\":1467049317802,\"severity\":\"SEVERE\",\"additionalInformation\":\"We have exceeded our agreed point rate.\",\"activeMaintenanceWindows\":[],\"inMaintenanceHostLabelPairs\":[],\"prefiringHostLabelPairs\":[],\"alertStates\":[\"ACTIVE\"],\"inTrash\":false,\"numMetricsUsed\":1,\"numHostsUsed\":1}]"
|
153
|
+
```
|
154
|
+
|
155
|
+
## `event` Mode: Opening and Closing Events
|
156
|
+
|
157
|
+
The `event` command is used to open and close Wavefront events.
|
158
|
+
|
159
|
+
```
|
160
|
+
Usage:
|
161
|
+
wavefront event create [-V] [-c file] [-P profile] [-E endpoint] [-t token]
|
162
|
+
[-d description] [-s time] [-i | -e time] [-l level] [-t type]
|
163
|
+
[-H host] [-n] <event>
|
164
|
+
wavefront event close [-V] [-c file] [-P profile] [-E endpoint] [-t token]
|
165
|
+
[<event>] [<timestamp>]
|
166
|
+
wavefront event show
|
167
|
+
wavefront event --help
|
168
|
+
|
169
|
+
Options:
|
170
|
+
-i, --instant create an instantaneous event
|
171
|
+
-V, --verbose be verbose
|
172
|
+
-s, --start=TIME time at which event begins
|
173
|
+
-e, --end=TIME time at which event ends
|
174
|
+
-l, --level=LEVEL level of event (info, smoke, warn, severe)
|
175
|
+
-T, --type=TYPE type of event
|
176
|
+
-d, --desc=STRING description of event
|
177
|
+
-H, --host=STRING list of hosts to tag with even (comma separated)
|
178
|
+
-n, --nostate do not create a local file recording the event
|
179
|
+
```
|
180
|
+
|
181
|
+
To close an event in the Wavefront API it must be identified by its
|
182
|
+
name and the millisecond time at which it was opened. This
|
183
|
+
information is returned when the event is opened, and the
|
184
|
+
`wavefront` command provides a handy way of caching it locally.
|
185
|
+
|
186
|
+
When a non-instantaneous event is opened and no end time is
|
187
|
+
specified, the CLI will write a file to
|
188
|
+
`/var/tmp/wavefront/event/<username>`. The name of the file
|
189
|
+
is the time the event was opened followed by `::`, followed by the
|
190
|
+
name of the event. Consider the `event/` directory as a stack:
|
191
|
+
a newly opened event is "pushed" onto the "stack". Running
|
192
|
+
`wavefront event close` simply pops the last event off the stack and
|
193
|
+
closes it. You can be more specific by running `wavefront event
|
194
|
+
close <name>`, which will close the last event opened and called `name`.
|
195
|
+
|
196
|
+
You can also specify the open-time when closing and event, bypassing
|
197
|
+
the local caching mechanism altogether.
|
198
|
+
|
199
|
+
The `wavefront event show` command lists the cached events. To
|
200
|
+
properly query events, use the `events()` command in a `ts` query.
|
201
|
+
|
202
|
+
### Examples
|
203
|
+
|
204
|
+
Create an instantaneous alert, bound only to the host making the API
|
205
|
+
call. Show the data returned by Wavefront.
|
206
|
+
|
207
|
+
```
|
208
|
+
$ wavefront event create -d "something important just happened" -i \
|
209
|
+
-V instantaneous_event
|
210
|
+
{
|
211
|
+
"name": "instantaneous_event",
|
212
|
+
"startTime": 1467049673400,
|
213
|
+
"endTime": 1467049673401,
|
214
|
+
"annotations": {
|
215
|
+
"details": "something important just happened"
|
216
|
+
},
|
217
|
+
"hosts": [
|
218
|
+
"box"
|
219
|
+
],
|
220
|
+
"isUserEvent": true,
|
221
|
+
"table": "sysdef"
|
222
|
+
}
|
223
|
+
```
|
224
|
+
|
225
|
+
Mark a Puppet run by opening an event of `info` level, to be closed
|
226
|
+
when the run finishes.
|
227
|
+
|
228
|
+
```
|
229
|
+
$ ./wavefront event create -P sysdef -d 'Puppet run' -l info puppet_run
|
230
|
+
Event state recorded at /var/tmp/wavefront/events/rob/1467051916712::puppet_run.
|
231
|
+
```
|
232
|
+
|
233
|
+
The run has finished, close the event.
|
234
|
+
|
235
|
+
```
|
236
|
+
$ wavefront event close puppet_run
|
237
|
+
Closing event 'puppet_run'. [2016-06-27 19:25:16 +0100]
|
238
|
+
Removing state file /var/tmp/wavefront/events/rob/1467051916712::puppet_run.
|
239
|
+
```
|
240
|
+
|
241
|
+
## Notes on Options
|
242
|
+
|
243
|
+
### Times
|
244
|
+
|
245
|
+
Timeseries query windows and events be defined by using Unix epoch
|
246
|
+
times (as shown by `date "%+s"`) or by entering any Ruby
|
247
|
+
`strptime`-parseable string. For instance:
|
248
|
+
|
249
|
+
```
|
250
|
+
$ wavefront --start 12:15 --end 12:20 ...
|
251
|
+
```
|
252
|
+
|
253
|
+
will request data points between 12:15 and 12:20pm today. If you ran
|
254
|
+
that in the morning, the time would be invalid, and you would get a
|
255
|
+
400 error from Wavefront, so something of the form
|
256
|
+
`2016-04-17T12:25:00` would remove all ambiguity.
|
257
|
+
|
258
|
+
There is no need to include a timezone in your time: the `wavefront`
|
259
|
+
CLI will automatically use your local timezone when it parses the
|
260
|
+
string.
|
261
|
+
|
262
|
+
### Default Configuration
|
263
|
+
|
264
|
+
Passing tokens and endpoints into the `wavefront` command can become
|
265
|
+
tiresome, so you can put such data into an `ini`-style configuration
|
266
|
+
file. By default this file should be located at `${HOME}/.wavefront`,
|
267
|
+
though you can override the location with the `-c` flag.
|
268
|
+
|
269
|
+
You can switch between Wavefront accounts using profile stanzas,
|
270
|
+
selected with the `-P` option. If `-P` is not supplied, the
|
271
|
+
`default` profile will be used. Not having a useable configuration
|
272
|
+
file will not cause an error.
|
273
|
+
|
274
|
+
A configuration file looks like this:
|
275
|
+
|
276
|
+
```
|
277
|
+
[default]
|
278
|
+
token = abcdefab-1234-abcd-1234-abcdefabcdef
|
279
|
+
endpoint = companya.wavefront.com
|
280
|
+
format = human
|
281
|
+
|
282
|
+
[companyb]
|
283
|
+
token = 12345678-abcd-0123-abcd-123456789abc
|
284
|
+
endpoint = metrics.wavefront.com
|
285
|
+
```
|
286
|
+
|
287
|
+
The key for each key-value pair can match any long option show in the
|
288
|
+
command `help`, so you can set, for instance, a default output
|
289
|
+
format, as shown above.
|
data/README.md
CHANGED
@@ -1,6 +1,5 @@
|
|
1
|
-
Wavefront
|
1
|
+
Wavefront [](https://travis-ci.org/wavefrontHQ/ruby-client) [](https://badge.fury.io/rb/wavefront-client) 
|
2
2
|
==========
|
3
|
-
[](https://travis-ci.org/wavefrontHQ/ruby-client)
|
4
3
|
|
5
4
|
This is a ruby gem for speaking to the [Wavefront][1] monitoring and graphing system.
|
6
5
|
|
@@ -165,105 +164,8 @@ response.highcharts[0]['data'].first # [1436849460000, 517160277.3333333]
|
|
165
164
|
```
|
166
165
|
|
167
166
|
### Command-line client
|
168
|
-
A command line client is included too.
|
169
|
-
|
170
|
-
```bash
|
171
|
-
Usage: wavefront COMMAND QUERY (OPTIONS)
|
172
|
-
-h, --help Display this message
|
173
|
-
|
174
|
-
Available commands:
|
175
|
-
|
176
|
-
ts Query the timeseries
|
177
|
-
alerts Query alerts
|
178
|
-
|
179
|
-
See `<command> --help` for more information on a specific command.
|
180
|
-
|
181
|
-
$ wavefront ts --help
|
182
|
-
Usage: wavefront COMMAND QUERY (OPTIONS)
|
183
|
-
-c, --config path to configuration file (default: ${HOME}/.wavefront)
|
184
|
-
-P, --profile profile in configuration file (default: default)
|
185
|
-
-D, --debug Enable debug mode
|
186
|
-
-S, --seconds Query granularity of seconds
|
187
|
-
-m, --minutes Query granularity of minutes
|
188
|
-
-H, --hours Query granularity of hours
|
189
|
-
-d, --days Query granularity of days
|
190
|
-
-s, --start start of query window in epoch seconds or parseable format
|
191
|
-
-e, --end end of query window in epoch seconds or parseable format
|
192
|
-
-t, --token Wavefront authentication token
|
193
|
-
-E, --endpoint Connect to alternative cluster endpoint (default: metrics.wavefront.com)
|
194
|
-
-f, --format Output format (raw, ruby, graphite, highcharts, human) (default: raw)
|
195
|
-
-p, --prefixlength The number of path elements to treat as a prefix when doing schema manipulations (default: 1)
|
196
|
-
-X, --strict Flag to not return points outside the query window [q;s) (default: true)
|
197
|
-
-O, --includeObsoleteMetrics Flag to include metrics that have not been reporting for more than 4 weeks,defaults to false
|
198
|
-
-h, --help Display this message
|
199
|
-
|
200
|
-
$ wavefront ts "ts(my.metric.path.host.cpu-0.percent-idle)" -t SECRET -m -f ruby
|
201
|
-
#<Wavefront::Response::Ruby:0x007f02f6bf9d70
|
202
|
-
@granularity=60,
|
203
|
-
@name="Unknown",
|
204
|
-
@options=
|
205
|
-
{:response_format=>:ruby,
|
206
|
-
:prefix_length=>1,
|
207
|
-
|
208
|
-
...
|
209
|
-
|
210
|
-
$ wavefront alerts snoozed -t TOKEN -f json --shared ops
|
211
|
-
[
|
212
|
-
{
|
213
|
-
"customerTagsWithCounts": {
|
214
|
-
|
215
|
-
...
|
216
|
-
```
|
217
|
-
|
218
|
-
#### Notes on Options
|
219
|
-
|
220
|
-
##### Times
|
221
|
-
|
222
|
-
The query window can be defined by using Unix epoch times (as shown
|
223
|
-
by `date "%+s"`) or by entering any Ruby `strptime`-parseable string.
|
224
|
-
For instance:
|
225
|
-
|
226
|
-
```bash
|
227
|
-
$ wavefront --start 12:15 --end 12:20 ...
|
228
|
-
```
|
229
|
-
|
230
|
-
will request data points between 12:15 and 12:20pm today. If you ran
|
231
|
-
that in the morning, the time would be invalid, and you would get a
|
232
|
-
400 error from Wavefront, so something of the form
|
233
|
-
`2016-04-17T12:25:00` would remove all ambiguity.
|
234
|
-
|
235
|
-
There is no need to include a timezone in your parseable string: the
|
236
|
-
`wavefront` CLI will automatically use your local timezone when it
|
237
|
-
parses the string.
|
238
|
-
|
239
|
-
#### Default Configuration
|
240
|
-
|
241
|
-
Passing tokens and endpoints into the `wavefront` command can become
|
242
|
-
tiresome, so you can put such data into an `ini`-style configuration
|
243
|
-
file. By default this file should be located at `${HOME}/.wavefront`,
|
244
|
-
though you can override the location with the `-c` flag.
|
245
|
-
|
246
|
-
You can switch between Wavefront accounts using profile stanzas,
|
247
|
-
selected with the `-P` option. If `-P` is not supplied, the
|
248
|
-
`default` profile will be used. Not having a useable configuration
|
249
|
-
file will not cause an error.
|
250
|
-
|
251
|
-
A configuration file looks like this:
|
252
|
-
|
253
|
-
```
|
254
|
-
[default]
|
255
|
-
token = abcdefab-1234-abcd-1234-abcdefabcdef
|
256
|
-
endpoint = companya.wavefront.com
|
257
|
-
format = human
|
258
|
-
|
259
|
-
[companyb]
|
260
|
-
token = 12345678-abcd-0123-abcd-123456789abc
|
261
|
-
endpoint = metrics.wavefront.com
|
262
|
-
```
|
263
|
-
|
264
|
-
The key for each key-value pair can match any long option show in the
|
265
|
-
command `help`, so you can set, for instance, a default output
|
266
|
-
format, as shown above.
|
167
|
+
A command line client is included too. Please see [README-cli.md]
|
168
|
+
for details.
|
267
169
|
|
268
170
|
## Building and installing
|
269
171
|
|
data/bin/wavefront
CHANGED
@@ -13,93 +13,153 @@
|
|
13
13
|
# See the License for the specific language governing permissions and
|
14
14
|
# limitations under the License.
|
15
15
|
#
|
16
|
-
|
17
|
-
require 'slop'
|
16
|
+
|
18
17
|
require 'pathname'
|
18
|
+
require 'wavefront/client'
|
19
19
|
require 'wavefront/cli'
|
20
|
+
require 'docopt'
|
21
|
+
include Wavefront::Constants
|
20
22
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
on 'h', 'help', 'Display this message'
|
25
|
-
|
26
|
-
command :version do
|
27
|
-
description 'Display the client version and exit'
|
28
|
-
run do
|
29
|
-
puts Wavefront::Client::VERSION
|
30
|
-
exit 0
|
31
|
-
end
|
23
|
+
def sanitize_keys(hash)
|
24
|
+
hash.each_with_object({}) do |(k, v), aggr|
|
25
|
+
aggr[k.gsub(/-/, '').to_sym] = v
|
32
26
|
end
|
27
|
+
end
|
33
28
|
|
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
|
-
|
29
|
+
ME = Pathname.new(__FILE__).basename
|
30
|
+
DEF_CF = Pathname.new(ENV['HOME']) + '.wavefront'
|
31
|
+
|
32
|
+
# The global_opts are available in every command.
|
33
|
+
#
|
34
|
+
global_opts = %Q(
|
35
|
+
Global options:
|
36
|
+
-c, --config=FILE path to configuration file [default: #{DEF_CF}]
|
37
|
+
-P, --profile=NAME profile in configuration file [default: default]
|
38
|
+
-E, --endpoint=URI cluster endpoint [default: #{DEFAULT_HOST}]
|
39
|
+
-t, --token=TOKEN Wavefront authentication token
|
40
|
+
-D, --debug enable debug mode
|
41
|
+
-h, --help show this message
|
42
|
+
)
|
43
|
+
|
44
|
+
# The following hash contains the docopt strings defining all the
|
45
|
+
# commands we offer. They must include the global_opts.
|
46
|
+
#
|
47
|
+
usage = {
|
48
|
+
ts: %Q(
|
49
|
+
Usage:
|
50
|
+
#{ME} ts [-c file] [-P profile] [-E endpoint] [-t token] [-OD]
|
51
|
+
[-S | -m | -H | -d] [-s time] [-e time] [-f format] [-p num]
|
52
|
+
[-X bool] <query>
|
53
|
+
#{global_opts}
|
54
|
+
Options:
|
55
|
+
-S, --seconds query granularity of seconds
|
56
|
+
-m, --minutes query granularity of minutes
|
57
|
+
-H, --hours query granularity of hours
|
58
|
+
-d, --days query granularity of days
|
59
|
+
-s, --start=TIME start of query window in epoch seconds or
|
60
|
+
strptime parseable format
|
61
|
+
-e, --end=TIME end of query window in epoch seconds or
|
62
|
+
strptime parseable format
|
63
|
+
-f, --format=STRING output format (#{FORMATS.join(', ')})
|
64
|
+
[default: #{DEFAULT_FORMAT}]
|
65
|
+
-p, --prefixlength=NUM number of path elements to treat as prefix
|
66
|
+
in schema manipulation. [default: #{DEFAULT_PREFIX_LENGTH}]
|
67
|
+
-X, --strict=BOOL Do not return points outside the query
|
68
|
+
window. [default: #{DEFAULT_STRICT}]
|
69
|
+
-O, --includeObsoleteMetrics include metrics unreported for > 4 weeks
|
70
|
+
),
|
71
|
+
|
72
|
+
alerts: %Q(
|
73
|
+
Usage:
|
74
|
+
#{ME} alerts [-c file] [-P profile] [-E endpoint] [-t token]
|
75
|
+
[-f format] [-p tag] [ -s tag] <state>
|
76
|
+
#{global_opts}
|
77
|
+
Options:
|
78
|
+
-f, --format=STRING output format (#{ALERT_FORMATS.join(', ')})
|
79
|
+
[default: #{DEFAULT_ALERT_FORMAT}]
|
80
|
+
-p, --private=TAG retrieve only alerts with named private tags,
|
81
|
+
comma delimited.
|
82
|
+
-s, --shared=TAG retrieve only alerts with named shared tags, comma
|
83
|
+
delimited.
|
84
|
+
),
|
66
85
|
|
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
|
-
|
86
|
+
event: %Q(
|
87
|
+
Usage:
|
88
|
+
#{ME} event create [-V] [-c file] [-P profile] [-E endpoint] [-t token]
|
89
|
+
[-d description] [-s time] [-i | -e time] [-l level] [-t type]
|
90
|
+
[-H host] [-n] <event>
|
91
|
+
#{ME} event close [-V] [-c file] [-P profile] [-E endpoint] [-t token]
|
92
|
+
[<event>] [<timestamp>]
|
93
|
+
#{ME} event show
|
94
|
+
#{ME} event --help
|
95
|
+
#{global_opts}
|
96
|
+
Options:
|
97
|
+
-i, --instant create an instantaneous event
|
98
|
+
-V, --verbose be verbose
|
99
|
+
-s, --start=TIME time at which event begins
|
100
|
+
-e, --end=TIME time at which event ends
|
101
|
+
-l, --level=LEVEL level of event (#{EVENT_LEVELS.join(', ')})
|
102
|
+
-T, --type=TYPE type of event
|
103
|
+
-d, --desc=STRING description of event
|
104
|
+
-H, --host=STRING list of hosts to tag with even (comma separated)
|
105
|
+
-n, --nostate do not create a local file recording the event
|
106
|
+
|
107
|
+
View events in detail using the 'ts' command with the 'events()' function.
|
108
|
+
),
|
109
|
+
default: %Q(
|
110
|
+
Wavefront CLI
|
111
|
+
|
112
|
+
Usage:
|
113
|
+
#{ME} [options] command [options]
|
114
|
+
#{ME} --version
|
115
|
+
#{ME} --help
|
116
|
+
|
117
|
+
Commands:
|
118
|
+
ts view timeseries data
|
119
|
+
alerts view alerts
|
120
|
+
event open and close events
|
121
|
+
|
122
|
+
Use '#{ME} <command> --help' for further information.)
|
123
|
+
}
|
124
|
+
|
125
|
+
# Parse the input. We have to do this twice because of the nested
|
126
|
+
# help/option parser generation.
|
127
|
+
#
|
128
|
+
begin
|
129
|
+
opts = Docopt::docopt(usage[:default], version: '3.2.0')
|
130
|
+
rescue Docopt::Exit => e
|
131
|
+
cmd = ARGV.length > 0 ? ARGV.first.to_sym : nil
|
132
|
+
|
133
|
+
if usage.keys.include?(cmd)
|
134
|
+
begin
|
135
|
+
opts = sanitize_keys(Docopt::docopt(usage[cmd]))
|
136
|
+
rescue Docopt::Exit => e
|
137
|
+
abort e.message
|
92
138
|
end
|
139
|
+
else
|
140
|
+
abort e.message
|
93
141
|
end
|
142
|
+
end
|
94
143
|
|
144
|
+
# Load the config file. Values in there take priority. Probably
|
145
|
+
# should be the other way round.
|
146
|
+
#
|
147
|
+
opts.merge!(Wavefront::Cli.new(opts, nil).load_profile || {})
|
95
148
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
149
|
+
case cmd
|
150
|
+
when :ts
|
151
|
+
require 'wavefront/cli/ts'
|
152
|
+
cli = Wavefront::Cli::Ts.new(opts, [opts[:'<query>']])
|
153
|
+
when :event
|
154
|
+
require 'wavefront/cli/events'
|
155
|
+
cli = Wavefront::Cli::Events.new(opts, [opts[:'<query>']])
|
156
|
+
when :alerts
|
157
|
+
require 'wavefront/cli/alerts'
|
158
|
+
cli = Wavefront::Cli::Alerts.new(opts, [opts[:'<state>']])
|
103
159
|
end
|
104
160
|
|
105
|
-
|
161
|
+
begin
|
162
|
+
cli.run
|
163
|
+
rescue => e
|
164
|
+
abort "#{cmd} query failed. #{e}"
|
165
|
+
end
|
data/lib/wavefront/cli/alerts.rb
CHANGED
@@ -0,0 +1,220 @@
|
|
1
|
+
require 'wavefront/events'
|
2
|
+
require 'wavefront/cli'
|
3
|
+
require 'json'
|
4
|
+
require 'time'
|
5
|
+
require 'fileutils'
|
6
|
+
require 'etc'
|
7
|
+
require 'socket'
|
8
|
+
#
|
9
|
+
# Open and close events via the Wavefront API.
|
10
|
+
#
|
11
|
+
# It is straightforward to create instantaneous events, or events
|
12
|
+
# with a defined start and end time: one API call is all you need,
|
13
|
+
# and the job is done. But often when you open an event you don't
|
14
|
+
# know when you want to close it, and closing requires very specific
|
15
|
+
# information which the API returns when the event is opened.
|
16
|
+
#
|
17
|
+
# To help the user close these events, we use a files in a local
|
18
|
+
# directory to record the state of any events we open. The
|
19
|
+
# directory behaves like an event "stack": a freshly created event
|
20
|
+
# is "pushed" onto the "stack", and telling the script to close an
|
21
|
+
# event with no further specification "pops" the last even off the
|
22
|
+
# "stack", and closes it. We give each user their own state
|
23
|
+
# directory, under /var/tmp/wavefront/events.
|
24
|
+
#
|
25
|
+
# We also provide a way of seeing which events the CLI thinks are
|
26
|
+
# open. This is in no way a substitute for using an events() query
|
27
|
+
# in a timeseries API call.
|
28
|
+
#
|
29
|
+
class Wavefront::Cli::Events < Wavefront::Cli
|
30
|
+
attr_accessor :state_dir, :hosts, :hostname, :t_start, :t_end, :wf_event
|
31
|
+
|
32
|
+
include Wavefront::Constants
|
33
|
+
include Wavefront::Mixins
|
34
|
+
|
35
|
+
def run
|
36
|
+
@state_dir = EVENT_STATE_DIR + Etc.getlogin
|
37
|
+
@hostname = Socket.gethostname
|
38
|
+
@hosts = prep_hosts(options[:host])
|
39
|
+
@t_start = prep_time(:start)
|
40
|
+
@t_end = prep_time(:end)
|
41
|
+
|
42
|
+
@wf_event = Wavefront::Events.new(options[:token])
|
43
|
+
|
44
|
+
if options[:create]
|
45
|
+
create_event_handler
|
46
|
+
elsif options[:close]
|
47
|
+
close_event_handler
|
48
|
+
elsif options[:show]
|
49
|
+
show_open_events
|
50
|
+
else
|
51
|
+
fail 'undefined event error.'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def prep_time(t)
|
56
|
+
#
|
57
|
+
# Wavefront would like times in epoch milliseconds, so whatever
|
58
|
+
# we have got, turn them into that.
|
59
|
+
#
|
60
|
+
options[t] ? time_to_ms(parse_time(options[t])) : false
|
61
|
+
end
|
62
|
+
|
63
|
+
def prep_hosts(hosts)
|
64
|
+
#
|
65
|
+
# We allow the user to associate an event with multiple hosts,
|
66
|
+
# or to pass in some identifer other than the hostname. If they
|
67
|
+
# have not done this, hostname is used
|
68
|
+
#
|
69
|
+
hosts = hostname unless hosts
|
70
|
+
hosts.split(',')
|
71
|
+
end
|
72
|
+
|
73
|
+
def create_event_handler
|
74
|
+
#
|
75
|
+
# Wrapper around the method calls which actually create events.
|
76
|
+
#
|
77
|
+
output = create_event
|
78
|
+
|
79
|
+
unless options[:end] || options[:instant]
|
80
|
+
create_state_dir
|
81
|
+
create_state_file(output) unless options[:nostate]
|
82
|
+
end
|
83
|
+
|
84
|
+
puts JSON.pretty_generate(JSON.parse(output)) if options[:verbose]
|
85
|
+
end
|
86
|
+
|
87
|
+
def create_event
|
88
|
+
#
|
89
|
+
# Make the API call and pass back the response.
|
90
|
+
#
|
91
|
+
req_obj = {
|
92
|
+
n: options[:'<event>'],
|
93
|
+
d: options[:desc],
|
94
|
+
h: hosts,
|
95
|
+
c: options[:instant],
|
96
|
+
l: options[:level]
|
97
|
+
}
|
98
|
+
|
99
|
+
req_obj[:s] = t_start if t_start
|
100
|
+
req_obj[:e] = t_end if t_end
|
101
|
+
|
102
|
+
begin
|
103
|
+
response = wf_event.create(req_obj)
|
104
|
+
rescue RestClient::Unauthorized
|
105
|
+
raise 'Cannot connect to Wavefront API'
|
106
|
+
rescue => e
|
107
|
+
puts e
|
108
|
+
raise 'Cannot create event'
|
109
|
+
end
|
110
|
+
|
111
|
+
response
|
112
|
+
end
|
113
|
+
|
114
|
+
def close_event_handler
|
115
|
+
#
|
116
|
+
# The user can specify all, some, or none of the information
|
117
|
+
# needed to close an event. If we have all of it, we can jump
|
118
|
+
# straight to the API call. Otherwise, we have to go and look
|
119
|
+
# for a state fuile
|
120
|
+
#
|
121
|
+
if options[:'<timestamp>'] && options[:'<event>']
|
122
|
+
close_event(options[:'<event>'], options[:'<timestamp>'])
|
123
|
+
else
|
124
|
+
ev_file = pop_event(options[:'<event>'])
|
125
|
+
|
126
|
+
if ev_file
|
127
|
+
close_event(ev_file[1], ev_file[0])
|
128
|
+
else
|
129
|
+
fail "No event '#{options[:'<event>']}' to close."
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def close_event(ev_name, ts)
|
135
|
+
puts "Closing event '#{ev_name}'. [#{Time.at(ts.to_i / 1000)}]"
|
136
|
+
|
137
|
+
begin
|
138
|
+
wf_event.close(
|
139
|
+
s: ts,
|
140
|
+
n: ev_name
|
141
|
+
)
|
142
|
+
rescue RestClient::Unauthorized
|
143
|
+
raise 'Not authorized to connect to Wavefront.'
|
144
|
+
rescue => e
|
145
|
+
puts e
|
146
|
+
raise
|
147
|
+
end
|
148
|
+
|
149
|
+
# Remove the state file, if there was one
|
150
|
+
#
|
151
|
+
state_file = state_filename(ev_name, ts)
|
152
|
+
|
153
|
+
return unless state_file.exist?
|
154
|
+
|
155
|
+
puts "Removing state file #{state_file}."
|
156
|
+
File.unlink state_file
|
157
|
+
end
|
158
|
+
|
159
|
+
def show_open_events
|
160
|
+
#
|
161
|
+
# Everything we need to know about an event is in the name of
|
162
|
+
# its state file.
|
163
|
+
#
|
164
|
+
events = state_dir.children
|
165
|
+
|
166
|
+
if events.length == 0
|
167
|
+
puts 'No open events.'
|
168
|
+
else
|
169
|
+
events.each { |e| puts e.basename }
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def pop_event(name = false)
|
174
|
+
#
|
175
|
+
# Get the last event this script created. If you supply a name,
|
176
|
+
# you get the last event with that name. If not, you get the
|
177
|
+
# last event. Chances are you'll only ever have one in-play at
|
178
|
+
# once.
|
179
|
+
#
|
180
|
+
# Returns an array of [timestamp, event_name]
|
181
|
+
#
|
182
|
+
list = state_dir.children
|
183
|
+
list.select! { |f| f.basename.to_s.split('::').last == name } if name
|
184
|
+
return false if list.length == 0
|
185
|
+
list.sort.last.basename.to_s.split('::')
|
186
|
+
end
|
187
|
+
|
188
|
+
def create_state_dir
|
189
|
+
FileUtils.mkdir_p(state_dir)
|
190
|
+
unless state_dir.exist? && state_dir.directory? && state_dir.writable?
|
191
|
+
fail 'Cannot create state directory.'
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def state_filename(ev_name, ts)
|
196
|
+
#
|
197
|
+
# Consistently generate event state file names
|
198
|
+
#
|
199
|
+
state_dir + [ts, ev_name].join('::')
|
200
|
+
end
|
201
|
+
|
202
|
+
def create_state_file(response)
|
203
|
+
#
|
204
|
+
# Write a state file. We put the hosts bound to the event into
|
205
|
+
# the file. These aren't currently used by anything in the CLI,
|
206
|
+
# but they might be useful to someone, somewhere, someday.
|
207
|
+
#
|
208
|
+
ts = JSON.parse(response)['startTime']
|
209
|
+
fname = state_filename(options[:'<event>'], ts)
|
210
|
+
|
211
|
+
begin
|
212
|
+
File.open(fname, 'w') { hosts.to_s }
|
213
|
+
rescue
|
214
|
+
JSON.pretty_generate(JSON.parse(response))
|
215
|
+
raise 'Event was created but state file was not.'
|
216
|
+
end
|
217
|
+
|
218
|
+
puts "Event state recorded at #{fname}."
|
219
|
+
end
|
220
|
+
end
|
data/lib/wavefront/cli/ts.rb
CHANGED
@@ -20,18 +20,9 @@ require 'json'
|
|
20
20
|
require 'date'
|
21
21
|
|
22
22
|
class Wavefront::Cli::Ts < Wavefront::Cli
|
23
|
-
|
23
|
+
include Wavefront::Mixins
|
24
24
|
attr_accessor :options, :arguments
|
25
25
|
|
26
|
-
def parse_time(t)
|
27
|
-
return Time.at(t.to_i) if t.match(/^\d+$/)
|
28
|
-
begin
|
29
|
-
return DateTime.parse("#{t} #{Time.now.getlocal.zone}").to_time.utc
|
30
|
-
rescue
|
31
|
-
raise "cannot parse timestamp '#{t}'."
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
26
|
def run
|
36
27
|
raise 'Please supply a query.' if @arguments.empty?
|
37
28
|
query = @arguments[0]
|
data/lib/wavefront/constants.rb
CHANGED
@@ -14,6 +14,8 @@ See the License for the specific language governing permissions and
|
|
14
14
|
|
15
15
|
=end
|
16
16
|
|
17
|
+
require 'pathname'
|
18
|
+
|
17
19
|
module Wavefront
|
18
20
|
module Constants
|
19
21
|
DEFAULT_HOST = 'metrics.wavefront.com'
|
@@ -26,5 +28,7 @@ module Wavefront
|
|
26
28
|
ALERT_FORMATS = [:ruby, :json, :human]
|
27
29
|
DEFAULT_ALERT_FORMAT = :human
|
28
30
|
GRANULARITIES = %w( s m h d )
|
31
|
+
EVENT_STATE_DIR = Pathname.new('/var/tmp/wavefront/events')
|
32
|
+
EVENT_LEVELS = %w(info smoke warn severe)
|
29
33
|
end
|
30
34
|
end
|
data/lib/wavefront/events.rb
CHANGED
@@ -21,10 +21,12 @@ module Wavefront
|
|
21
21
|
include Wavefront::Constants
|
22
22
|
DEFAULT_PATH = '/api/events/'
|
23
23
|
|
24
|
-
attr_reader :
|
24
|
+
attr_reader :headers
|
25
25
|
|
26
26
|
def initialize(token)
|
27
|
-
@
|
27
|
+
@headers = {
|
28
|
+
'X-AUTH-TOKEN': token,
|
29
|
+
}
|
28
30
|
end
|
29
31
|
|
30
32
|
def create(payload = {}, options = {})
|
@@ -34,10 +36,16 @@ module Wavefront
|
|
34
36
|
uri = URI::HTTPS.build(
|
35
37
|
host: options[:host],
|
36
38
|
path: options[:path],
|
37
|
-
query: "t=#{@token}"
|
38
39
|
)
|
39
40
|
|
40
|
-
|
41
|
+
# It seems that posting the hash means the 'host' data is
|
42
|
+
# lost. Making a query string works though, so let's do that.
|
43
|
+
#
|
44
|
+
hosts = payload[:h]
|
45
|
+
payload.delete(:h)
|
46
|
+
query = mk_qs(payload)
|
47
|
+
hosts.each { |host| query.<< "&h=#{host}" }
|
48
|
+
RestClient.post(uri.to_s, query, headers)
|
41
49
|
end
|
42
50
|
|
43
51
|
def close(payload = {}, options = {})
|
@@ -51,17 +59,18 @@ module Wavefront
|
|
51
59
|
uri = URI::HTTPS.build(
|
52
60
|
host: options[:host],
|
53
61
|
path: options[:path] + 'close',
|
54
|
-
|
55
|
-
payload.map { |k, v| [k, v].join('=') }.join('&') + '&t=' + @token
|
56
|
-
)
|
62
|
+
|
57
63
|
)
|
58
|
-
puts uri.to_s
|
59
64
|
|
60
|
-
RestClient.post(uri.to_s, payload)
|
65
|
+
RestClient.post(uri.to_s, mk_qs(payload), headers)
|
61
66
|
end
|
62
67
|
|
63
68
|
private
|
64
69
|
|
70
|
+
def mk_qs(payload)
|
71
|
+
URI.escape(payload.map { |k, v| [k, v].join('=') }.join('&'))
|
72
|
+
end
|
73
|
+
|
65
74
|
def debug(enabled)
|
66
75
|
RestClient.log = 'stdout' if enabled
|
67
76
|
end
|
data/lib/wavefront/mixins.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
=begin
|
1
|
+
=begin
|
2
2
|
Copyright 2015 Wavefront Inc.
|
3
3
|
Licensed under the Apache License, Version 2.0 (the "License");
|
4
4
|
you may not use this file except in compliance with the License.
|
@@ -25,5 +25,21 @@ module Wavefront
|
|
25
25
|
interpolated.flatten!
|
26
26
|
return interpolated.join('.')
|
27
27
|
end
|
28
|
+
|
29
|
+
def parse_time(t)
|
30
|
+
return Time.at(t.to_i) if t.match(/^\d+$/)
|
31
|
+
begin
|
32
|
+
return DateTime.parse("#{t} #{Time.now.getlocal.zone}").to_time.utc
|
33
|
+
rescue
|
34
|
+
raise "cannot parse timestamp '#{t}'."
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def time_to_ms(t)
|
39
|
+
#
|
40
|
+
# Return the time as milliseconds since the epoch
|
41
|
+
#
|
42
|
+
(t.to_f * 1000).round
|
43
|
+
end
|
28
44
|
end
|
29
45
|
end
|
data/lib/wavefront/response.rb
CHANGED
@@ -121,21 +121,11 @@ module Wavefront
|
|
121
121
|
def initialize(response, options={})
|
122
122
|
super
|
123
123
|
|
124
|
-
if self.
|
125
|
-
|
126
|
-
|
127
|
-
self.
|
128
|
-
out
|
129
|
-
out += ts.select{|k,v| k != 'data' }.map do |k, v|
|
130
|
-
if k == 'tags'
|
131
|
-
v.map { |tk, tv| 'tag.%-16s%s' % [tk, tv] }
|
132
|
-
else
|
133
|
-
'%-20s%s' % [k, v]
|
134
|
-
end
|
135
|
-
end
|
136
|
-
out += ts['data'].map do |t, v|
|
137
|
-
[Time.at(t).strftime('%F %T'), v].join(' ')
|
138
|
-
end
|
124
|
+
if self.response
|
125
|
+
if self.respond_to?(:timeseries)
|
126
|
+
out = process_timeseries
|
127
|
+
elsif self.respond_to?(:events)
|
128
|
+
out = process_events
|
139
129
|
end
|
140
130
|
else
|
141
131
|
out = self.warnings
|
@@ -143,6 +133,77 @@ module Wavefront
|
|
143
133
|
|
144
134
|
@human = out.join("\n")
|
145
135
|
end
|
136
|
+
|
137
|
+
def process_timeseries
|
138
|
+
out = ['%-20s%s' % ['query', self.query]]
|
139
|
+
|
140
|
+
self.timeseries.each_with_index do |ts, i|
|
141
|
+
out.<< '%-20s%s' % ['timeseries', i]
|
142
|
+
out += ts.select{|k,v| k != 'data' }.map do |k, v|
|
143
|
+
if k == 'tags'
|
144
|
+
v.map { |tk, tv| 'tag.%-16s%s' % [tk, tv] }
|
145
|
+
else
|
146
|
+
'%-20s%s' % [k, v]
|
147
|
+
end
|
148
|
+
end
|
149
|
+
out += ts['data'].map do |t, v|
|
150
|
+
[Time.at(t).strftime('%F %T'), v].join(' ')
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
out
|
155
|
+
end
|
156
|
+
|
157
|
+
def process_events
|
158
|
+
sorted = self.events.sort_by { |k| k['start'] }
|
159
|
+
|
160
|
+
sorted.each_with_object([]) do |e, out|
|
161
|
+
hosts = e['hosts'] ? '[' + e['hosts'].join(',') + ']' : ''
|
162
|
+
|
163
|
+
if e['tags']
|
164
|
+
severity = e['tags']['severity']
|
165
|
+
type = e['tags']['type']
|
166
|
+
details = e['tags']['details']
|
167
|
+
else
|
168
|
+
severity = type = details = ''
|
169
|
+
end
|
170
|
+
|
171
|
+
t = [format_event_time(e['start']), '->',
|
172
|
+
format_event_time(e['end']),
|
173
|
+
'%-9s' % ('(' + format_event_duration(e['start'],
|
174
|
+
e['end']) + ')'),
|
175
|
+
'%-7s' % severity,
|
176
|
+
'%-15s' % type,
|
177
|
+
'%-25s' % e['name'],
|
178
|
+
hosts,
|
179
|
+
details,
|
180
|
+
].join(' ')
|
181
|
+
|
182
|
+
out.<< t
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def format_event_time(tms)
|
187
|
+
Time.at(tms / 1000).strftime('%F %T')
|
188
|
+
end
|
189
|
+
|
190
|
+
def format_event_duration(ts, te)
|
191
|
+
#
|
192
|
+
# turn an event start and end into a human-readable,
|
193
|
+
# approximate, time. Truncates after the first two parts in
|
194
|
+
# the interests of space.
|
195
|
+
#
|
196
|
+
dur = (te - ts) / 1000
|
197
|
+
|
198
|
+
return 'inst' if dur == 0
|
199
|
+
|
200
|
+
{s: 60, m: 60, h: 24, d: 1000 }.map do |sfx, val|
|
201
|
+
next unless dur > 0
|
202
|
+
dur, n = dur.divmod(val)
|
203
|
+
n.to_s + sfx.to_s
|
204
|
+
end.compact.reverse[0..1].join(' ')
|
205
|
+
end
|
206
|
+
|
146
207
|
end
|
147
208
|
end
|
148
209
|
end
|
data/wavefront-client.gemspec
CHANGED
@@ -39,9 +39,7 @@ Gem::Specification.new do |spec|
|
|
39
39
|
spec.add_development_dependency "rspec"
|
40
40
|
|
41
41
|
spec.add_dependency "rest-client", ">= 1.6.7", "<= 1.8"
|
42
|
-
spec.add_dependency "
|
42
|
+
spec.add_dependency "docopt", "~> 0.5.0"
|
43
43
|
spec.add_dependency 'inifile', '3.0.0'
|
44
44
|
spec.required_ruby_version = Gem::Requirement.new(">= 1.9.3")
|
45
|
-
|
46
45
|
end
|
47
|
-
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: wavefront-client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sam Pointer
|
@@ -13,7 +13,7 @@ authors:
|
|
13
13
|
autorequire:
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
|
-
date: 2016-
|
16
|
+
date: 2016-07-18 00:00:00.000000000 Z
|
17
17
|
dependencies:
|
18
18
|
- !ruby/object:Gem::Dependency
|
19
19
|
name: bundler
|
@@ -78,25 +78,19 @@ dependencies:
|
|
78
78
|
- !ruby/object:Gem::Version
|
79
79
|
version: '1.8'
|
80
80
|
- !ruby/object:Gem::Dependency
|
81
|
-
name:
|
81
|
+
name: docopt
|
82
82
|
requirement: !ruby/object:Gem::Requirement
|
83
83
|
requirements:
|
84
|
-
- -
|
85
|
-
- !ruby/object:Gem::Version
|
86
|
-
version: 3.4.7
|
87
|
-
- - <=
|
84
|
+
- - ~>
|
88
85
|
- !ruby/object:Gem::Version
|
89
|
-
version:
|
86
|
+
version: 0.5.0
|
90
87
|
type: :runtime
|
91
88
|
prerelease: false
|
92
89
|
version_requirements: !ruby/object:Gem::Requirement
|
93
90
|
requirements:
|
94
|
-
- -
|
95
|
-
- !ruby/object:Gem::Version
|
96
|
-
version: 3.4.7
|
97
|
-
- - <=
|
91
|
+
- - ~>
|
98
92
|
- !ruby/object:Gem::Version
|
99
|
-
version:
|
93
|
+
version: 0.5.0
|
100
94
|
- !ruby/object:Gem::Dependency
|
101
95
|
name: inifile
|
102
96
|
requirement: !ruby/object:Gem::Requirement
|
@@ -125,12 +119,14 @@ files:
|
|
125
119
|
- Gemfile
|
126
120
|
- LICENSE
|
127
121
|
- NOTICE
|
122
|
+
- README-cli.md
|
128
123
|
- README.md
|
129
124
|
- Rakefile
|
130
125
|
- bin/wavefront
|
131
126
|
- lib/wavefront/alerting.rb
|
132
127
|
- lib/wavefront/cli.rb
|
133
128
|
- lib/wavefront/cli/alerts.rb
|
129
|
+
- lib/wavefront/cli/events.rb
|
134
130
|
- lib/wavefront/cli/ts.rb
|
135
131
|
- lib/wavefront/client.rb
|
136
132
|
- lib/wavefront/client/version.rb
|