wavefront-client 3.1.0 → 3.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 [![Build Status](https://travis-ci.org/wavefrontHQ/ruby-client.svg?branch=master)](https://travis-ci.org/wavefrontHQ/ruby-client) [![Gem Version](https://badge.fury.io/rb/wavefront-client.svg)](https://badge.fury.io/rb/wavefront-client) ![](http://ruby-gem-downloads-badge.herokuapp.com/wavefront-client?type=total)
|
2
2
|
==========
|
3
|
-
[![Build Status](https://travis-ci.org/wavefrontHQ/ruby-client.svg?branch=v0.5.2)](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
|