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 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