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 CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- MjE5OWM5OWIyYjcxNWI2MGJjNGRiOWI1YjBlMjI1OWFiMzIyMWVjYg==
4
+ YmVlOTljMDc5ZWJkOTAwYzZiMDlhOTJhYTY3ZTI2YjRjNTBjZDliMw==
5
5
  data.tar.gz: !binary |-
6
- OGJkZTg1ZTgwMDdjMmZhZDhiYjIyZDVlYmJlYzMzNGZiZDI4ZWEyOQ==
6
+ M2Y4ZDExZTE1NDFmNjUxZTI1NGYzOTExYzQ0ZmU2YjVhMTMzMGVmZA==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- YTBhNmI4OWJmOWJlM2I2NDMwYTNhNjIzMGNjZjM0OWVlMjdjMzU1YWUwNjYy
10
- MTZjYjIwYjI2MDhlNDU3ZmJjYmZjODk1NmY0ZTM0OGJlMzMzMDJjZmNjN2Jk
11
- MDk4N2MxODQzYzQ0MDQxMzRkY2Y2YzMwOWE3ODA3ZGFjZDA2MGM=
9
+ NjRmYmU4NjU2NGQ0MGIwMzU4N2Q0MjQwNzQ4NWViYTJkYTZlNjk1Njk0Mzlh
10
+ MWU1MTYyNDc3NzhlYzNiMDhmMjkwOWNiODExNDMwOWNlYWY5NWMzYWJhNjYw
11
+ ZTdiMmVlNGYzOTY1MWQxMjZjYTUyZjA4ZjZiODY2MTI0Y2ZmZjE=
12
12
  data.tar.gz: !binary |-
13
- YjJhOGNmOTEyYzE1ZmRlN2JhMjNkMTgwMmVmOGU2YWJhYjY2MGRjMGVmNTFm
14
- YjQwZmJhMTRhYTBhYWRmM2ViNGIyZjlhYTQ5Y2JiYzIxOTMyZTQyZDUxNTdl
15
- MGNlMWVhMjNlNjkzM2U2MjAwNjdmY2QxYzgyOWExZGQyYTc2ZDE=
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. You can see the full list of options by typing `wavefront --help` from a prompt.
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
- require 'wavefront/client'
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
- begin
22
- options = Slop.parse(strict: true) do
23
- banner 'Usage: wavefront COMMAND QUERY (OPTIONS)'
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
- command 'ts' do
35
- description "Query the timeseries"
36
- on 'c', 'config=', 'path to configuration file', default:
37
- Pathname.new(ENV['HOME']) + '.wavefront'
38
- on 'P', 'profile=', 'profile in configuration file', default: 'default'
39
- on 'D', 'debug', 'Enable debug mode'
40
- on 'S', 'seconds', 'Query granularity of seconds'
41
- on 'm', 'minutes', 'Query granularity of minutes'
42
- on 'H', 'hours', 'Query granularity of hours'
43
- on 'd', 'days', 'Query granularity of days'
44
- on 's', 'start=', 'start of query window in epoch seconds or parseable format'
45
- on 'e', 'end=', 'end of query window in epoch seconds or parseable format'
46
- on 't', 'token=', 'Wavefront authentication token'
47
- on 'E', 'endpoint=', 'Connect to alternative cluster endpoint', default: Wavefront::Client::DEFAULT_HOST.to_s
48
- on 'f', 'format=', "Output format (#{Wavefront::Client::FORMATS.join(', ')})", default: Wavefront::Client::DEFAULT_FORMAT.to_s
49
- on 'p', 'prefixlength=', 'Number of path elements to treat as a prefix in schema manipulation', default: Wavefront::Client::DEFAULT_PREFIX_LENGTH
50
- on 'X', 'strict=','Flag to not return points outside the query window [q;s) ', default: Wavefront::Client::DEFAULT_STRICT
51
- on 'O', 'includeObsoleteMetrics=', 'Include metrics which have not reported for > 4 weeks', default: Wavefront::Client::DEFAULT_OBSOLETE_METRICS
52
- on 'h', 'help', 'Display this message'
53
- run do |options, arguments|
54
- pf_opts = Wavefront::Cli.new(options, arguments).load_profile || {}
55
- require 'wavefront/cli/ts'
56
- cli = Wavefront::Cli::Ts.new(options.to_hash.merge(pf_opts), arguments)
57
- begin
58
- cli.run
59
- rescue => e
60
- puts 'Timeseries query failed.'
61
- puts e
62
- exit 1
63
- end
64
- end
65
- end
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
- command 'alerts' do
68
- banner 'Usage: wavefront alerts (OPTIONS) ALERT_STATE'
69
- description "Query alerts"
70
- on 'c', 'config=', 'path to configuration file', default:
71
- Pathname.new(ENV['HOME']) + '.wavefront'
72
- on 'P', 'profile=', 'profile in configuration file', default: 'default'
73
- on 'E', 'endpoint=', 'Connect to alternative cluster endpoint', default: Wavefront::Client::DEFAULT_HOST.to_s
74
- on 'f', 'format=', 'Output format (ruby, json)', default: 'ruby'
75
- on 'f', 'format=', "Output format (#{Wavefront::Client::ALERT_FORMATS.join(', ')})", default: Wavefront::Client::DEFAULT_ALERT_FORMAT.to_s
76
- on 'h', 'help', 'Display this message'
77
- on 'p', 'private=', 'Retrieve only alerts with named private tags, comma delimited.'
78
- on 's', 'shared=', 'Retrieve only alerts with named shared tags, comma delimited.'
79
- on 't', 'token=', 'Wavefront authentication token'
80
- run do |options, arguments|
81
- pf_opts = Wavefront::Cli.new(options, arguments).load_profile || {}
82
- require 'wavefront/cli/alerts'
83
- cli = Wavefront::Cli::Alerts.new(options.to_hash.merge(pf_opts),
84
- arguments)
85
- begin
86
- cli.run
87
- rescue => e
88
- puts 'Alert query failed.'
89
- puts e
90
- exit 1
91
- end
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
- end
97
- rescue Slop::InvalidOptionError => e
98
- puts 'invalid option error: '+ e.to_s
99
- exit 1
100
- rescue Slop::MissingArgumentError => e
101
- puts 'option parsing error: '+ e.to_s
102
- exit 1
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
- puts options
161
+ begin
162
+ cli.run
163
+ rescue => e
164
+ abort "#{cmd} query failed. #{e}"
165
+ end
@@ -123,6 +123,7 @@ class Wavefront::Cli::Alerts < Wavefront::Cli
123
123
  #
124
124
  # Put each host on its own line, indented.
125
125
  #
126
+ return k unless v
126
127
  v.sort!
127
128
  [human_line(k, v.shift)] + v.map {|el| human_line('', el)}
128
129
  end
@@ -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
@@ -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]
@@ -16,6 +16,6 @@ See the License for the specific language governing permissions and
16
16
 
17
17
  module Wavefront
18
18
  class Client
19
- VERSION = "3.1.0"
19
+ VERSION = "3.2.0"
20
20
  end
21
21
  end
@@ -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
@@ -21,10 +21,12 @@ module Wavefront
21
21
  include Wavefront::Constants
22
22
  DEFAULT_PATH = '/api/events/'
23
23
 
24
- attr_reader :token
24
+ attr_reader :headers
25
25
 
26
26
  def initialize(token)
27
- @token = token
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
- RestClient.post(uri.to_s, payload)
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
- query: URI.escape(
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
@@ -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
@@ -121,21 +121,11 @@ module Wavefront
121
121
  def initialize(response, options={})
122
122
  super
123
123
 
124
- if self.respond_to?(:timeseries)
125
- out = ['%-20s%s' % ['query', self.query]]
126
-
127
- self.timeseries.each_with_index do |ts, i|
128
- out.<< '%-20s%s' % ['timeseries', i]
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
@@ -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 "slop", ">= 3.4.7", "<= 3.6"
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.1.0
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-05-20 00:00:00.000000000 Z
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: slop
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: '3.6'
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: '3.6'
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