wavefront-client 3.2.0 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +8 -8
- data/.travis.yml +1 -0
- data/README-cli.md +108 -1
- data/bin/wavefront +49 -11
- data/lib/wavefront/batch_writer.rb +247 -0
- data/lib/wavefront/cli/alerts.rb +50 -27
- data/lib/wavefront/cli/batch_write.rb +227 -0
- data/lib/wavefront/cli/events.rb +1 -1
- data/lib/wavefront/cli/ts.rb +2 -2
- data/lib/wavefront/cli/write.rb +89 -0
- data/lib/wavefront/cli.rb +19 -13
- data/lib/wavefront/client/version.rb +1 -1
- data/lib/wavefront/constants.rb +3 -0
- data/lib/wavefront/events.rb +39 -18
- data/lib/wavefront/exception.rb +8 -1
- data/lib/wavefront/mixins.rb +9 -2
- data/lib/wavefront/writer.rb +7 -2
- data/spec/spec_helper.rb +52 -1
- data/spec/wavefront/batch_writer_spec.rb +523 -0
- data/spec/wavefront/cli/alerts_spec.rb +153 -0
- data/spec/wavefront/cli/batch_write_spec.rb +251 -0
- data/spec/wavefront/cli/events_spec.rb +43 -0
- data/spec/wavefront/cli/resources/alert.human.erb +14 -0
- data/spec/wavefront/cli/resources/alert.human2 +14 -0
- data/spec/wavefront/cli/resources/alert.json +38 -0
- data/spec/wavefront/cli/resources/alert.raw +1 -0
- data/spec/wavefront/cli/resources/alert.ruby +1 -0
- data/spec/wavefront/cli/resources/write.parabola +49 -0
- data/spec/wavefront/cli/write_spec.rb +112 -0
- data/spec/wavefront/cli_spec.rb +68 -0
- data/spec/wavefront/events_spec.rb +111 -0
- data/spec/wavefront/mixins_spec.rb +16 -1
- data/spec/wavefront/writer_spec.rb +0 -7
- data/wavefront-client.gemspec +1 -1
- metadata +35 -6
@@ -0,0 +1,153 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'pathname'
|
3
|
+
require 'json'
|
4
|
+
require 'erb'
|
5
|
+
|
6
|
+
# Valid alert states as defined in alerting.rb
|
7
|
+
#
|
8
|
+
states = %w(active affected_by_maintenance all invalid snoozed)
|
9
|
+
formats = %w(ruby json human)
|
10
|
+
|
11
|
+
opts = {
|
12
|
+
token: TEST_TOKEN,
|
13
|
+
}
|
14
|
+
|
15
|
+
describe Wavefront::Cli::Alerts do
|
16
|
+
|
17
|
+
describe '#run' do
|
18
|
+
|
19
|
+
it 'raises an exception if there are no arguments' do
|
20
|
+
k = Wavefront::Cli::Alerts.new(opts, [])
|
21
|
+
expect{k.run}.to raise_exception('Missing query.')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe '#valid_state?' do
|
26
|
+
wfa = Wavefront::Alerting.new(TEST_TOKEN)
|
27
|
+
|
28
|
+
it 'raises an exception if the alert type is invalid' do
|
29
|
+
k = Wavefront::Cli::Alerts.new(opts, ['all'])
|
30
|
+
expect{k.valid_state?(wfa, 'nosuch_alert')}.to raise_exception(
|
31
|
+
RuntimeError, "State must be one of: #{states.join(', ')}.")
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'accepts all valid states' do
|
35
|
+
k = Wavefront::Cli::Alerts.new(opts, ['all'])
|
36
|
+
|
37
|
+
states.each do |state|
|
38
|
+
expect(k.valid_state?(wfa, state.to_sym)).to be(true)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe '#valid_format?' do
|
44
|
+
k = Wavefront::Cli::Alerts.new(opts, ['all'])
|
45
|
+
|
46
|
+
it 'accepts valid formats as symbols and strings' do
|
47
|
+
formats.each do |fmt|
|
48
|
+
expect(k.valid_format?(fmt)).to be(true)
|
49
|
+
expect(k.valid_format?(fmt.to_s)).to be(true)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'raises an exception on an invalid format' do
|
54
|
+
expect{k.valid_format?('junk')}.to raise_exception( RuntimeError,
|
55
|
+
"Output format must be one of: #{formats.join(', ')}.")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe '#format_result' do
|
60
|
+
k = Wavefront::Cli::Alerts.new(opts, ['all'])
|
61
|
+
res = "{\n \"a\": 1,\n \"b\": 2\n}"
|
62
|
+
|
63
|
+
it 'raises an error on unknown type' do
|
64
|
+
expect{k.format_result(res, :nonsense)}.to raise_exception(RuntimeError)
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'pretty-prints when requested' do
|
68
|
+
expect{k.format_result(res, :ruby)}.to match_stdout(
|
69
|
+
"[{\n \"a\": 1,\n \"b\": 2\n}]")
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'prints as JSON when requested' do
|
73
|
+
expect{k.format_result(res, :json)}.to match_stdout(res)
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'reconstructs JSON output' do
|
77
|
+
src = IO.read(Pathname.new(__FILE__).dirname + 'resources' +
|
78
|
+
'alert.raw')
|
79
|
+
out = IO.read(Pathname.new(__FILE__).dirname + 'resources' +
|
80
|
+
'alert.json')
|
81
|
+
expect{k.format_result(src, :json)}.to match_stdout(out)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe '#humanize' do
|
86
|
+
k = Wavefront::Cli::Alerts.new(opts, ['all'])
|
87
|
+
src = IO.read(Pathname.new(__FILE__).dirname + 'resources' +
|
88
|
+
'alert.raw')
|
89
|
+
#
|
90
|
+
# The output has to get munged a bit here because it turns out
|
91
|
+
# that comparing multi-line things isn't anywhere near so
|
92
|
+
# straightforward as I expected.
|
93
|
+
#
|
94
|
+
it 'reconstructs human output' do
|
95
|
+
out = ERB.new(IO.read(Pathname.new(__FILE__).dirname +
|
96
|
+
'resources' + 'alert.human.erb')).result
|
97
|
+
expect(k.humanize(JSON.parse(src)).join("\n") + "\n").to eq(out)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
describe '#human_line' do
|
102
|
+
k = Wavefront::Cli::Alerts.new(opts, ['all'])
|
103
|
+
it 'prints in the correct format' do
|
104
|
+
expect(k.human_line('desc', 123)).to eq('desc 123')
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
describe '#human_line_created' do
|
109
|
+
k = Wavefront::Cli::Alerts.new(opts, ['all'])
|
110
|
+
it 'prints in the correct format' do
|
111
|
+
expect(k.human_line_created('time', 1469804504000)).to eq(
|
112
|
+
"time #{Time.at(1469804504)}")
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
describe '#human_line_hostsUsed' do
|
117
|
+
k = Wavefront::Cli::Alerts.new(opts, ['all'])
|
118
|
+
|
119
|
+
it 'just prints the header if there are no hosts' do
|
120
|
+
expect(k.human_line_hostsUsed('host', false)).to eq('host')
|
121
|
+
expect(k.human_line_hostsUsed('host', [])).to eq('host')
|
122
|
+
expect(k.human_line_hostsUsed('host', {})).to eq('host')
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'prints in the correct format for a single host' do
|
126
|
+
expect(k.human_line_hostsUsed('host', ['hostname'])).to eq(
|
127
|
+
['host hostname'])
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'prints in the correct format for a multiple hosts' do
|
131
|
+
expect(k.human_line_hostsUsed('host', ['host1', 'host2'])).to eq(
|
132
|
+
['host host1', ' host2'])
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
describe '#indent_wrap' do
|
137
|
+
k = Wavefront::Cli::Alerts.new(opts, ['all'])
|
138
|
+
|
139
|
+
it 'leaves short strings unchanged' do
|
140
|
+
expect(k.indent_wrap('short string')).to eq("short string")
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'wraps long strings with a hanging indent' do
|
144
|
+
expect(k.indent_wrap('lots and lots of words which are ' +
|
145
|
+
'altogether far too wide for an 80 column terminal to ' +
|
146
|
+
'display without breaking at least once, especially with ' +
|
147
|
+
'a hanging indent')).to eq(
|
148
|
+
"lots and lots of words which are altogether far too wide
|
149
|
+
for an 80 column terminal to display without breaking at
|
150
|
+
least once, especially with a hanging indent")
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
@@ -0,0 +1,251 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
opts = {}
|
5
|
+
|
6
|
+
describe '#run' do
|
7
|
+
end
|
8
|
+
|
9
|
+
describe '#load_data' do
|
10
|
+
k = Wavefront::Cli::BatchWrite.new(opts, 'write')
|
11
|
+
|
12
|
+
it 'loads in a data file' do
|
13
|
+
expect(k.load_data(Pathname.new(__FILE__).dirname + 'resources' +
|
14
|
+
'write.parabola').length).to eq(784)
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'raises an error if the file does not exist' do
|
18
|
+
expect{k.load_data(Pathname.new('/no/such/file'))}.to raise_exception(
|
19
|
+
RuntimeError)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe '#tags_to_hash' do
|
24
|
+
k = Wavefront::Cli::BatchWrite.new(opts, 'write')
|
25
|
+
|
26
|
+
it 'parses a single tag' do
|
27
|
+
expect(k.tags_to_hash('key=value')).to eq(key: 'value')
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'parses multiple tags' do
|
31
|
+
expect(k.tags_to_hash(['k1=v1', 'k2=v2', 'k3=v3'])).to eq(
|
32
|
+
{k1: 'v1', k2: 'v2', k3: 'v3'})
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'skips tags with no value' do
|
36
|
+
expect(k.tags_to_hash(['k1=v1', 'k2v2', 'k3=v3'])).to eq(
|
37
|
+
{k1: 'v1', k3: 'v3'})
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'handles tags containing an "="' do
|
41
|
+
expect(k.tags_to_hash(['k1=v1', 'k2=^=^', 'k3=v3'])).to eq(
|
42
|
+
{k1: 'v1', k2: '^=^', k3: 'v3'})
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'silently handles things which are not arrays' do
|
46
|
+
expect(k.tags_to_hash({})).to eq({})
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'deals with quoted tags' do
|
50
|
+
expect(k.tags_to_hash(["'k1'='v1'", '"k2"="v2"', 'k3="v3"'])).to eq(
|
51
|
+
{k1: 'v1', k2: 'v2', k3: 'v3'})
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe '#process_filedata' do
|
56
|
+
end
|
57
|
+
|
58
|
+
describe '#valid_format?' do
|
59
|
+
k = Wavefront::Cli::BatchWrite.new(opts, 'write')
|
60
|
+
context 'valid format strings' do
|
61
|
+
%w(v vT tv tvT mv vm mvt mtv tmv mvT vmT mvtT tmvT).each do |fmt|
|
62
|
+
it 'accepts #{fmt}' do
|
63
|
+
expect(k.valid_format?(fmt)).to be true
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context 'invalid format strings' do
|
69
|
+
%w(Tmv m mvTt amv mmv vv mvTm).each do |fmt|
|
70
|
+
it "rejects #{fmt}" do
|
71
|
+
expect(k.valid_format?(fmt)).to be_falsey
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe '#valid_line?' do
|
78
|
+
context 'using four columns including points tags' do
|
79
|
+
k = Wavefront::Cli::BatchWrite.new(opts, 'write')
|
80
|
+
k.setup_fmt('mtvT')
|
81
|
+
|
82
|
+
it 'accepts four columns' do
|
83
|
+
expect(k.valid_line?('metric time value TAG1')).to be true
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'accepts six columns' do
|
87
|
+
expect(k.valid_line?('metric time value TAG1 TAG2 TAG3')).to be true
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'rejects three columns' do
|
91
|
+
expect(k.valid_line?('metric time value')).to be false
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context 'using two columns, not including points tags' do
|
96
|
+
k = Wavefront::Cli::BatchWrite.new(opts, 'write')
|
97
|
+
k.setup_fmt('mv')
|
98
|
+
|
99
|
+
it 'accepts two columns' do
|
100
|
+
expect(k.valid_line?('metric value')).to be true
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'rejects three columns' do
|
104
|
+
expect(k.valid_line?('metric time TAG1')).to be false
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'rejects one column' do
|
108
|
+
expect(k.valid_line?('metric')).to be false
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
describe '#valid_timestamp?' do
|
114
|
+
k = Wavefront::Cli::BatchWrite.new(opts, 'write')
|
115
|
+
|
116
|
+
it 'accepts a valid timestamp as an integer' do
|
117
|
+
expect(k.valid_timestamp?(Time.now.to_i)).to be true
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'accepts a valid timestamp as a string' do
|
121
|
+
expect(k.valid_timestamp?(Time.now.to_i.to_s)).to be true
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'rejects a timestamp too far in the past' do
|
125
|
+
expect(k.valid_timestamp?(Time.new('1999-12-31').to_i)).to be false
|
126
|
+
end
|
127
|
+
|
128
|
+
it 'rejects a timestamp too far in the future' do
|
129
|
+
expect(k.valid_timestamp?((Date.today + 367).to_time.to_i)).to be false
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
describe '#valid_value?' do
|
134
|
+
k = Wavefront::Cli::BatchWrite.new(opts, 'write')
|
135
|
+
context 'numerics' do
|
136
|
+
[-1, 0, 10, 1e6, 3.14].each do |n|
|
137
|
+
it "accepts #{n}" do
|
138
|
+
expect(k.valid_value?(n)).to be true
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
context 'strings' do
|
144
|
+
%w(-1 0 10 1e6 3.14).each do |n|
|
145
|
+
it "accepts #{n}" do
|
146
|
+
expect(k.valid_value?(n)).to be_truthy
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
%w(1-2 a --6 1.2.3).each do |s|
|
151
|
+
it "rejects #{s}" do
|
152
|
+
expect(k.valid_value?(s)).to be_falsey
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
describe '#process_line' do
|
159
|
+
|
160
|
+
context 'without metric prefix' do
|
161
|
+
k = Wavefront::Cli::BatchWrite.new(opts, 'write')
|
162
|
+
k.setup_opts(opts)
|
163
|
+
|
164
|
+
it 'processes a four-field line' do
|
165
|
+
k.setup_fmt('mtvT')
|
166
|
+
expect(k.process_line('test_metric 1470176262 1234 t1="v1"')).to eq(
|
167
|
+
{ path: 'test_metric',
|
168
|
+
ts: Time.at(1470176262),
|
169
|
+
source: HOSTNAME,
|
170
|
+
value: 1234,
|
171
|
+
tags: { t1: 'v1' }
|
172
|
+
})
|
173
|
+
end
|
174
|
+
|
175
|
+
it 'processes a four-field line with multiple tags' do
|
176
|
+
k.setup_fmt('mtvT')
|
177
|
+
expect(k.process_line('test_metric 1470176262 3.14 t1=v1 t2=v2 t3=v3')
|
178
|
+
).to eq(
|
179
|
+
{ path: 'test_metric',
|
180
|
+
ts: Time.at(1470176262),
|
181
|
+
source: HOSTNAME,
|
182
|
+
value: 3.14,
|
183
|
+
tags: { t1: 'v1', t2: 'v2', t3: 'v3' }
|
184
|
+
})
|
185
|
+
end
|
186
|
+
|
187
|
+
it 'skips a blank line' do
|
188
|
+
expect(k.process_line('')).to be true
|
189
|
+
end
|
190
|
+
|
191
|
+
it 'rejects a three-field line when expecting two fields' do
|
192
|
+
k.setup_fmt('mvt')
|
193
|
+
expect(k.process_line('3 1470176262')).to be false
|
194
|
+
expect{k.process_line('3 1470176262')}.to match_stdout(
|
195
|
+
'WARNING: wrong number of fields. Skipping.')
|
196
|
+
end
|
197
|
+
|
198
|
+
it 'ignores dodgy tags' do
|
199
|
+
k.setup_fmt('mtvT')
|
200
|
+
expect(k.process_line('test_metric 1470176262 3.14 bad_tag')).to eq(
|
201
|
+
{ path: 'test_metric',
|
202
|
+
ts: Time.at(1470176262),
|
203
|
+
source: HOSTNAME,
|
204
|
+
value: 3.14,
|
205
|
+
tags: {}
|
206
|
+
})
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
context 'with metric prefix' do
|
211
|
+
k = Wavefront::Cli::BatchWrite.new(opts, 'write')
|
212
|
+
k.setup_opts(metric: 'test_metric_3')
|
213
|
+
|
214
|
+
it 'rejects a two-field line with the fields backwards' do
|
215
|
+
k.setup_fmt('vt')
|
216
|
+
expect(k.process_line('1470176262 3.14')).to be false
|
217
|
+
expect{k.process_line('1470176262 3.14')}.to match_stdout(
|
218
|
+
"WARNING: invalid timestamp '3.14'. Skipping.")
|
219
|
+
end
|
220
|
+
|
221
|
+
it 'rejects a two-field line with an invalid value' do
|
222
|
+
k.setup_fmt('vt')
|
223
|
+
expect(k.process_line('x 1470176262')).to be false
|
224
|
+
expect{k.process_line('x 14701762624')}.to match_stdout(
|
225
|
+
"WARNING: invalid value 'x'. Skipping.")
|
226
|
+
end
|
227
|
+
|
228
|
+
it 'processes a two-field line with a CLI metric path' do
|
229
|
+
k.setup_fmt('tv')
|
230
|
+
expect(k.process_line('1470176262 3.14')
|
231
|
+
).to eq(
|
232
|
+
{ path: 'test_metric_3',
|
233
|
+
ts: Time.at(1470176262),
|
234
|
+
source: HOSTNAME,
|
235
|
+
value: 3.14,
|
236
|
+
})
|
237
|
+
end
|
238
|
+
|
239
|
+
it 'processes a two-field line with spacey tags' do
|
240
|
+
k.setup_fmt('tvT')
|
241
|
+
expect(k.process_line('1470176262 3.14 t1="value 1" t2="value 2"')
|
242
|
+
).to eq(
|
243
|
+
{ path: 'test_metric_3',
|
244
|
+
ts: Time.at(1470176262),
|
245
|
+
source: HOSTNAME,
|
246
|
+
value: 3.14,
|
247
|
+
tags: { t1: 'value 1', t2: 'value 2' }
|
248
|
+
})
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'pathname'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
# Valid alert states as defined in alerting.rb
|
6
|
+
#
|
7
|
+
states = %w(active affected_by_maintenance all invalid snoozed)
|
8
|
+
formats = %w(ruby json human)
|
9
|
+
|
10
|
+
opts = {
|
11
|
+
token: TEST_TOKEN,
|
12
|
+
}
|
13
|
+
|
14
|
+
describe Wavefront::Cli::Alerts do
|
15
|
+
|
16
|
+
describe '#prep_time' do
|
17
|
+
|
18
|
+
[Time.now, 1469826353, 1469826353000,
|
19
|
+
'2016-07-29 22:05:51 +0100', '12:00', '2001-01-01'].each do |t|
|
20
|
+
it "converts time like '#{t}' (#{t.class}) into epoch ms" do
|
21
|
+
opts[:start] = t
|
22
|
+
k = Wavefront::Cli::Events.new(opts, [])
|
23
|
+
expect(k.prep_time(:start)).to be_kind_of(Numeric)
|
24
|
+
expect(k.prep_time(:start)).to be > 978307000000
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe '#prep_hosts' do
|
30
|
+
k = Wavefront::Cli::Events.new(opts, [])
|
31
|
+
k.hostname = 'this-host'
|
32
|
+
|
33
|
+
it 'returns the local hostname if nothing is given' do
|
34
|
+
expect(k.prep_hosts).to eq ['this-host']
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'returns multiple hosts if an array is passed' do
|
38
|
+
expect(k.prep_hosts('host-a,host-b,host-c')).to eq(
|
39
|
+
['host-a', 'host-b', 'host-c'])
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
name Test Alert
|
2
|
+
created <%= Time.at(1469807662271 / 1000) %>
|
3
|
+
severity WARN
|
4
|
+
condition ts("dev.cli.test_metric") > 0
|
5
|
+
displayExpression ts("dev.cli.test_metric")
|
6
|
+
minutes 5
|
7
|
+
resolveAfterMinutes
|
8
|
+
updated <%= Time.at(1469807735018 / 1000) %>
|
9
|
+
alertStates CHECKING
|
10
|
+
metricsUsed
|
11
|
+
hostsUsed
|
12
|
+
additionalInformation This alert has been made up to feed into the test
|
13
|
+
framework for the Ruby CLI. It is not real. Even this
|
14
|
+
text is just placeholder text.
|