wavefront-client 3.2.0 → 3.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
data/lib/wavefront/cli.rb
CHANGED
@@ -25,29 +25,35 @@ module Wavefront
|
|
25
25
|
@options = options
|
26
26
|
@arguments = arguments
|
27
27
|
|
28
|
-
if
|
29
|
-
puts
|
28
|
+
if options.include?(:help) && options[:help]
|
29
|
+
puts options
|
30
30
|
exit 0
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
34
|
def load_profile
|
35
|
+
#
|
36
|
+
# Load in configuration options from the (optionally) given
|
37
|
+
# section of an ini-style configuration file. If the file's
|
38
|
+
# not there, we don't consider that an error: it's not
|
39
|
+
# entirely reasonable to demand the user has one. Options
|
40
|
+
# passed on the command-line trump the same values in the
|
41
|
+
# file.
|
42
|
+
#
|
43
|
+
return unless options[:config].is_a?(String)
|
35
44
|
cf = Pathname.new(options[:config])
|
36
|
-
|
45
|
+
return unless cf.exist?
|
37
46
|
|
38
|
-
|
39
|
-
|
40
|
-
|
47
|
+
pf = options[:profile] || 'default'
|
48
|
+
raw = IniFile.load(cf)
|
49
|
+
profile = raw[pf]
|
41
50
|
|
42
|
-
|
43
|
-
puts "using #{pf} profile from #{cf}" if options[:debug]
|
44
|
-
return profile.inject({}){|x, (k, v)| x[k.to_sym] = v; x }
|
45
|
-
end
|
51
|
+
puts "using #{pf} profile from #{cf}" if options[:debug]
|
46
52
|
|
47
|
-
|
48
|
-
|
53
|
+
profile.each_with_object({}) do |(k, v), memo|
|
54
|
+
k = k.to_sym
|
55
|
+
memo[k] = (options.include?(k) && options[k]) ? options[k] : v
|
49
56
|
end
|
50
57
|
end
|
51
|
-
|
52
58
|
end
|
53
59
|
end
|
data/lib/wavefront/constants.rb
CHANGED
@@ -30,5 +30,8 @@ module Wavefront
|
|
30
30
|
GRANULARITIES = %w( s m h d )
|
31
31
|
EVENT_STATE_DIR = Pathname.new('/var/tmp/wavefront/events')
|
32
32
|
EVENT_LEVELS = %w(info smoke warn severe)
|
33
|
+
DEFAULT_PROXY = 'wavefront'
|
34
|
+
DEFAULT_PROXY_PORT = 2878
|
35
|
+
DEFAULT_INFILE_FORMAT = 'tmv'
|
33
36
|
end
|
34
37
|
end
|
data/lib/wavefront/events.rb
CHANGED
@@ -24,50 +24,71 @@ module Wavefront
|
|
24
24
|
attr_reader :headers
|
25
25
|
|
26
26
|
def initialize(token)
|
27
|
-
@headers = {
|
28
|
-
'X-AUTH-TOKEN': token,
|
29
|
-
}
|
27
|
+
@headers = { :'X-AUTH-TOKEN' => token }
|
30
28
|
end
|
31
29
|
|
32
30
|
def create(payload = {}, options = {})
|
31
|
+
make_call(create_uri(options), create_qs(payload))
|
32
|
+
end
|
33
|
+
|
34
|
+
def close(payload = {}, options = {})
|
35
|
+
make_call(close_uri(options), hash_to_qs(payload))
|
36
|
+
end
|
37
|
+
|
38
|
+
def create_uri(options = {})
|
39
|
+
#
|
40
|
+
# Build the URI we use to send a 'create' request.
|
41
|
+
#
|
33
42
|
options[:host] ||= DEFAULT_HOST
|
34
43
|
options[:path] ||= DEFAULT_PATH
|
35
44
|
|
36
|
-
|
45
|
+
URI::HTTPS.build(
|
37
46
|
host: options[:host],
|
38
47
|
path: options[:path],
|
39
48
|
)
|
49
|
+
end
|
40
50
|
|
51
|
+
def create_qs(payload = {})
|
52
|
+
#
|
41
53
|
# It seems that posting the hash means the 'host' data is
|
42
54
|
# lost. Making a query string works though, so let's do that.
|
43
55
|
#
|
44
|
-
|
56
|
+
if payload[:h].is_a?(Array)
|
57
|
+
hosts = payload[:h]
|
58
|
+
elsif payload[:h].is_a?(String)
|
59
|
+
hosts = [payload[:h]]
|
60
|
+
else
|
61
|
+
hosts = []
|
62
|
+
end
|
63
|
+
|
45
64
|
payload.delete(:h)
|
46
|
-
query =
|
65
|
+
query = hash_to_qs(payload)
|
47
66
|
hosts.each { |host| query.<< "&h=#{host}" }
|
48
|
-
|
67
|
+
query
|
49
68
|
end
|
50
69
|
|
51
|
-
def
|
70
|
+
def close_uri(options = {})
|
71
|
+
#
|
72
|
+
# Build the URI we use to send a 'close' request
|
73
|
+
#
|
52
74
|
options[:host] ||= DEFAULT_HOST
|
53
75
|
options[:path] ||= DEFAULT_PATH
|
54
76
|
|
55
|
-
|
56
|
-
# getting a 500 when I posted a hash. A map will do the
|
57
|
-
# needful.
|
58
|
-
|
59
|
-
uri = URI::HTTPS.build(
|
77
|
+
URI::HTTPS.build(
|
60
78
|
host: options[:host],
|
61
79
|
path: options[:path] + 'close',
|
62
|
-
|
63
80
|
)
|
64
|
-
|
65
|
-
RestClient.post(uri.to_s, mk_qs(payload), headers)
|
66
81
|
end
|
67
82
|
|
68
|
-
|
83
|
+
def make_call(uri, query)
|
84
|
+
RestClient.post(uri.to_s, query, headers)
|
85
|
+
end
|
69
86
|
|
70
|
-
def
|
87
|
+
def hash_to_qs(payload)
|
88
|
+
#
|
89
|
+
# Make a properly escaped query string out of a key: value
|
90
|
+
# hash.
|
91
|
+
#
|
71
92
|
URI.escape(payload.map { |k, v| [k, v].join('=') }.join('&'))
|
72
93
|
end
|
73
94
|
|
data/lib/wavefront/exception.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
=begin
|
1
|
+
=begin
|
2
2
|
Copyright 2015 Wavefront Inc.
|
3
3
|
Licensed under the Apache License, Version 2.0 (the "License");
|
4
4
|
you may not use this file except in compliance with the License.
|
@@ -24,5 +24,12 @@ module Wavefront
|
|
24
24
|
class EmptyMetricName < ::Exception; end
|
25
25
|
class NotImplemented < ::Exception; end
|
26
26
|
class InvalidPrefixLength < ::Exception; end
|
27
|
+
class InvalidMetricName < ::Exception; end
|
28
|
+
class InvalidMetricValue < ::Exception; end
|
29
|
+
class InvalidTimestamp < ::Exception; end
|
30
|
+
class InvalidTag < ::Exception; end
|
31
|
+
class InvalidHostname < ::Exception; end
|
32
|
+
class InvalidEndpoint < ::Exception; end
|
33
|
+
class InvalidSource < ::Exception; end
|
27
34
|
end
|
28
35
|
end
|
data/lib/wavefront/mixins.rb
CHANGED
@@ -27,9 +27,15 @@ module Wavefront
|
|
27
27
|
end
|
28
28
|
|
29
29
|
def parse_time(t)
|
30
|
-
|
30
|
+
#
|
31
|
+
# Return a time as an integer, however it might come in.
|
32
|
+
#
|
31
33
|
begin
|
32
|
-
return
|
34
|
+
return t if t.is_a?(Integer)
|
35
|
+
return t.to_i if t.is_a?(Time)
|
36
|
+
return t.to_i if t.is_a?(String) && t.match(/^\d+$/)
|
37
|
+
return DateTime.parse("#{t} #{Time.now.getlocal.zone}").
|
38
|
+
to_time.utc.to_i
|
33
39
|
rescue
|
34
40
|
raise "cannot parse timestamp '#{t}'."
|
35
41
|
end
|
@@ -39,6 +45,7 @@ module Wavefront
|
|
39
45
|
#
|
40
46
|
# Return the time as milliseconds since the epoch
|
41
47
|
#
|
48
|
+
return false unless t.is_a?(Integer)
|
42
49
|
(t.to_f * 1000).round
|
43
50
|
end
|
44
51
|
end
|
data/lib/wavefront/writer.rb
CHANGED
@@ -57,8 +57,14 @@ module Wavefront
|
|
57
57
|
append = "host=#{options[:host_name]} #{tags}"
|
58
58
|
end
|
59
59
|
|
60
|
-
|
60
|
+
str = [metric_name, metric_value, options[:timestamp].to_i,
|
61
61
|
append].join(' ')
|
62
|
+
|
63
|
+
if options[:noop]
|
64
|
+
puts "metric to send: #{str}"
|
65
|
+
else
|
66
|
+
@socket.puts(str)
|
67
|
+
end
|
62
68
|
end
|
63
69
|
|
64
70
|
private
|
@@ -66,6 +72,5 @@ module Wavefront
|
|
66
72
|
def get_socket(host, port)
|
67
73
|
TCPSocket.new(host, port)
|
68
74
|
end
|
69
|
-
|
70
75
|
end
|
71
76
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
=begin
|
1
|
+
=begin
|
2
2
|
Copyright 2015 Wavefront Inc.
|
3
3
|
Licensed under the Apache License, Version 2.0 (the "License");
|
4
4
|
you may not use this file except in compliance with the License.
|
@@ -19,6 +19,57 @@ require 'wavefront/client'
|
|
19
19
|
require 'wavefront/writer'
|
20
20
|
require 'wavefront/metadata'
|
21
21
|
require 'wavefront/alerting'
|
22
|
+
require 'wavefront/events'
|
23
|
+
require 'wavefront/batch_writer'
|
24
|
+
require 'wavefront/cli'
|
25
|
+
require 'wavefront/cli/alerts'
|
26
|
+
require 'wavefront/cli/events'
|
27
|
+
require 'wavefront/cli/batch_write'
|
28
|
+
require 'wavefront/cli/write'
|
22
29
|
|
23
30
|
TEST_TOKEN = "test"
|
24
31
|
TEST_HOST = "metrics.wavefront.com"
|
32
|
+
|
33
|
+
# The following RSpec matcher is used to test things which `puts`
|
34
|
+
# (or related), which RSpec can't do by default. It works with RSpec
|
35
|
+
# 3, and was lifted wholesale from
|
36
|
+
# http://stackoverflow.com/questions/6372763/rspec-how-do-i-write-a-test-that-expects-certain-output-but-doesnt-care-about/28258747#28258747
|
37
|
+
|
38
|
+
RSpec::Matchers.define :match_stdout do |check|
|
39
|
+
|
40
|
+
@capture = nil
|
41
|
+
|
42
|
+
match do |block|
|
43
|
+
|
44
|
+
begin
|
45
|
+
stdout_saved = $stdout
|
46
|
+
$stdout = StringIO.new
|
47
|
+
block.call
|
48
|
+
ensure
|
49
|
+
@capture = $stdout
|
50
|
+
$stdout = stdout_saved
|
51
|
+
end
|
52
|
+
|
53
|
+
@capture.string.match check
|
54
|
+
end
|
55
|
+
|
56
|
+
failure_message do
|
57
|
+
"expected to #{description}"
|
58
|
+
end
|
59
|
+
failure_message_when_negated do
|
60
|
+
"expected not to #{description}"
|
61
|
+
end
|
62
|
+
description do
|
63
|
+
"match [#{check}] on stdout [#{@capture.string}]"
|
64
|
+
end
|
65
|
+
|
66
|
+
def supports_block_expectations?
|
67
|
+
true
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class Mocket
|
72
|
+
def puts(str)
|
73
|
+
return true
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,523 @@
|
|
1
|
+
=begin
|
2
|
+
Copyright 2016 Wavefront Inc.
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
you may not use this file except in compliance with the License.
|
5
|
+
You may obtain a copy of the License at
|
6
|
+
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
See the License for the specific language governing permissions and
|
13
|
+
limitations under the License.
|
14
|
+
|
15
|
+
=end
|
16
|
+
|
17
|
+
require 'spec_helper'
|
18
|
+
require 'socket'
|
19
|
+
|
20
|
+
opts = {}
|
21
|
+
|
22
|
+
describe Wavefront::BatchWriter do
|
23
|
+
|
24
|
+
describe '#initialize' do
|
25
|
+
it 'sets @opts correctly' do
|
26
|
+
k = Wavefront::BatchWriter.new({noop: true, proxy: 'myproxy'})
|
27
|
+
expect(k.instance_variable_get(:@opts)).to eq({
|
28
|
+
tags: false,
|
29
|
+
proxy: 'myproxy',
|
30
|
+
port: 2878,
|
31
|
+
noop: true,
|
32
|
+
novalidate: false,
|
33
|
+
verbose: false,
|
34
|
+
debug: false,
|
35
|
+
})
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'allows unset @global_tags' do
|
39
|
+
k = Wavefront::BatchWriter.new
|
40
|
+
expect(k.instance_variable_get(:@global_tags)).to be nil
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'sets @global_tags' do
|
44
|
+
k = Wavefront::BatchWriter.new(tags: { t1: 'v1', t2: 'v2' })
|
45
|
+
expect(k.instance_variable_get(:@global_tags)).to eq({t1: 'v1', t2: 'v2'})
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
describe '#write' do
|
51
|
+
|
52
|
+
# end-to-end test
|
53
|
+
|
54
|
+
context 'without noop set' do
|
55
|
+
it 'should write a single metric to a socket' do
|
56
|
+
|
57
|
+
socket = Mocket.new
|
58
|
+
allow(TCPSocket).to receive(:new).and_return(socket)
|
59
|
+
expect(socket).to receive(:puts).with(
|
60
|
+
'test.metric 1234 1469987572 source=testhost t1="v1" t2="v2"')
|
61
|
+
|
62
|
+
k = Wavefront::BatchWriter.new(opts)
|
63
|
+
k.open_socket
|
64
|
+
expect(k.write(
|
65
|
+
path: 'test.metric',
|
66
|
+
value: 1234,
|
67
|
+
ts: Time.at(1469987572),
|
68
|
+
source: 'testhost',
|
69
|
+
tags: { t1: 'v1', t2: 'v2' },
|
70
|
+
)).to be(true)
|
71
|
+
expect(k.summary[:sent]).to eq(1)
|
72
|
+
expect(k.summary[:unsent]).to eq(0)
|
73
|
+
expect(k.summary[:rejected]).to eq(0)
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'should write multiple metrics to a socket' do
|
77
|
+
socket = Mocket.new
|
78
|
+
allow(TCPSocket).to receive(:new).and_return(socket)
|
79
|
+
k = Wavefront::BatchWriter.new
|
80
|
+
k.open_socket
|
81
|
+
expect(socket).to receive(:puts).with(
|
82
|
+
'test.metric_1 1234 1469987572 source=testhost t1="v1" t2="v2"')
|
83
|
+
expect(socket).to receive(:puts).with(
|
84
|
+
'test.metric_2 2468 1469987572 source=testhost')
|
85
|
+
expect(k.write([
|
86
|
+
{ path: 'test.metric_1',
|
87
|
+
value: 1234,
|
88
|
+
ts: Time.at(1469987572),
|
89
|
+
source: 'testhost',
|
90
|
+
tags: { t1: 'v1', t2: 'v2' },
|
91
|
+
},
|
92
|
+
{ path: 'test.metric_2',
|
93
|
+
value: 2468,
|
94
|
+
ts: Time.at(1469987572),
|
95
|
+
source: 'testhost',
|
96
|
+
}
|
97
|
+
])).to be(true)
|
98
|
+
expect(k.summary[:sent]).to eq(2)
|
99
|
+
expect(k.summary[:unsent]).to eq(0)
|
100
|
+
expect(k.summary[:rejected]).to eq(0)
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'should let good points through and drop bad points' do
|
104
|
+
socket = Mocket.new
|
105
|
+
allow(TCPSocket).to receive(:new).and_return(socket)
|
106
|
+
k = Wavefront::BatchWriter.new(verbose: true)
|
107
|
+
k.open_socket
|
108
|
+
expect(socket).to receive(:puts).with(
|
109
|
+
'test.metric_1 1234 1469987572 source=testhost t1="v1" t2="v2"')
|
110
|
+
expect(socket).to receive(:puts).with(
|
111
|
+
'test.metric_2 2468 1469987572 source=testhost')
|
112
|
+
expect(k.write([
|
113
|
+
{ path: 'test.metric_1',
|
114
|
+
value: 1234,
|
115
|
+
ts: Time.at(1469987572),
|
116
|
+
source: 'testhost',
|
117
|
+
tags: { t1: 'v1', t2: 'v2' },
|
118
|
+
},
|
119
|
+
{ path: 'bogus_metric',
|
120
|
+
},
|
121
|
+
{ path: 'test.metric_2',
|
122
|
+
value: 2468,
|
123
|
+
ts: Time.at(1469987572),
|
124
|
+
source: 'testhost',
|
125
|
+
}
|
126
|
+
])).to be(false)
|
127
|
+
expect(k.summary[:sent]).to eq(2)
|
128
|
+
expect(k.summary[:unsent]).to eq(0)
|
129
|
+
expect(k.summary[:rejected]).to eq(1)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
context 'with noop set' do
|
134
|
+
it 'should not write a metric to a socket object' do
|
135
|
+
m = 'test.metric 1234 1469987572 source=testhost t1="v1" t2="v2"'
|
136
|
+
socket = Mocket.new
|
137
|
+
allow(TCPSocket).to receive(:new).and_return(socket)
|
138
|
+
expect(socket).not_to receive(:puts).with(m)
|
139
|
+
k = Wavefront::BatchWriter.new(noop: true)
|
140
|
+
k.open_socket
|
141
|
+
k.write(
|
142
|
+
path: 'test.metric',
|
143
|
+
value: 1234,
|
144
|
+
ts: Time.at(1469987572),
|
145
|
+
source: 'testhost',
|
146
|
+
tags: { t1: 'v1', t2: 'v2' },
|
147
|
+
)
|
148
|
+
expect(k.summary[:sent]).to eq(0)
|
149
|
+
expect(k.summary[:unsent]).to eq(0)
|
150
|
+
expect(k.summary[:rejected]).to eq(0)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
describe '#setup_options' do
|
156
|
+
defaults = {
|
157
|
+
tags: false,
|
158
|
+
proxy: 'wavefront',
|
159
|
+
port: 2878,
|
160
|
+
noop: false,
|
161
|
+
novalidate: false,
|
162
|
+
verbose: false,
|
163
|
+
debug: false,
|
164
|
+
}
|
165
|
+
|
166
|
+
it 'falls back to all defaults' do
|
167
|
+
k = Wavefront::BatchWriter.new
|
168
|
+
expect(k.setup_options({}, defaults)).to eq(defaults)
|
169
|
+
end
|
170
|
+
|
171
|
+
it 'allows overriding of defaults' do
|
172
|
+
k = Wavefront::BatchWriter.new
|
173
|
+
expect(k.setup_options({noop: true, proxy: 'myproxy'},
|
174
|
+
defaults)).to eq({ tags: false,
|
175
|
+
proxy: 'myproxy',
|
176
|
+
port: 2878,
|
177
|
+
noop: true,
|
178
|
+
novalidate: false,
|
179
|
+
verbose: false,
|
180
|
+
debug: false,
|
181
|
+
})
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
describe '#valid_point?' do
|
186
|
+
context 'novalidate is true' do
|
187
|
+
k = Wavefront::BatchWriter.new({novalidate: true})
|
188
|
+
|
189
|
+
it 'lets through an invalid point' do
|
190
|
+
expect(k.valid_point?({junk: true})).to be(true)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
context 'novalidate is false' do
|
195
|
+
opts[:novalidate] = false
|
196
|
+
k = Wavefront::BatchWriter.new(opts)
|
197
|
+
|
198
|
+
it 'lets through a valid point with all members' do
|
199
|
+
expect(k.valid_point?(
|
200
|
+
path: 'test.metric',
|
201
|
+
value: 123456,
|
202
|
+
ts: Time.now,
|
203
|
+
source: 'testhost',
|
204
|
+
tags: { tag1: 'value 1', tag2: 'value 2' },
|
205
|
+
)).to be(true)
|
206
|
+
end
|
207
|
+
|
208
|
+
it 'lets through a valid point with no timestamp' do
|
209
|
+
expect(k.valid_point?(
|
210
|
+
path: 'test.metric',
|
211
|
+
value: 123456,
|
212
|
+
source: 'testhost',
|
213
|
+
tags: { tag1: 'value 1', tag2: 'value 2' },
|
214
|
+
)).to be(true)
|
215
|
+
end
|
216
|
+
|
217
|
+
it 'lets through a valid point with no tags' do
|
218
|
+
expect(k.valid_point?(
|
219
|
+
path: 'test.metric',
|
220
|
+
value: 123456,
|
221
|
+
ts: Time.now,
|
222
|
+
source: 'testhost',
|
223
|
+
)).to be(true)
|
224
|
+
end
|
225
|
+
|
226
|
+
it 'raises InvalidMetricName on invalid metric name' do
|
227
|
+
expect{k.valid_point?(
|
228
|
+
path: '!n\/@1!d_metric',
|
229
|
+
value: 123456,
|
230
|
+
ts: Time.now,
|
231
|
+
source: 'testhost',
|
232
|
+
tags: { tag1: 'value 1', tag2: 'value 2' },
|
233
|
+
)}.to raise_exception(Wavefront::Exception::InvalidMetricName)
|
234
|
+
end
|
235
|
+
|
236
|
+
it 'raises InvalidMetricValue on invalid metric value' do
|
237
|
+
expect{k.valid_point?(
|
238
|
+
path: 'test.metric',
|
239
|
+
value: 'three_point_one_four',
|
240
|
+
ts: Time.now,
|
241
|
+
source: 'testhost',
|
242
|
+
tags: { tag1: 'value 1', tag2: 'value 2' },
|
243
|
+
)}.to raise_exception(Wavefront::Exception::InvalidMetricValue)
|
244
|
+
end
|
245
|
+
|
246
|
+
it 'raises InvalidTimestamp on invalid timestamp' do
|
247
|
+
expect{k.valid_point?(
|
248
|
+
path: 'test.metric',
|
249
|
+
value: 123456,
|
250
|
+
ts: 'half_past_eleven',
|
251
|
+
source: 'testhost',
|
252
|
+
tags: { tag1: 'value 1', tag2: 'value 2' },
|
253
|
+
)}.to raise_exception(Wavefront::Exception::InvalidTimestamp)
|
254
|
+
end
|
255
|
+
|
256
|
+
it 'raises InvalidHostname on invalid source' do
|
257
|
+
expect{k.valid_point?(
|
258
|
+
path: 'test.metric',
|
259
|
+
value: 123456,
|
260
|
+
ts: Time.now,
|
261
|
+
source: ['source1', 'source2'],
|
262
|
+
tags: { tag1: 'value 1', tag2: 'value 2' },
|
263
|
+
)}.to raise_exception(Wavefront::Exception::InvalidSource)
|
264
|
+
end
|
265
|
+
|
266
|
+
it 'raises InvalidTag on invalid tag' do
|
267
|
+
expect{k.valid_point?(
|
268
|
+
path: 'test.metric',
|
269
|
+
value: 123456,
|
270
|
+
ts: Time.now,
|
271
|
+
source: 'testhost',
|
272
|
+
tags: { :tag1 => 'value 1', :'invalid tag' => 'value 2' },
|
273
|
+
)}.to raise_exception(Wavefront::Exception::InvalidTag)
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
end
|
278
|
+
|
279
|
+
describe '#tag_hash_to_str' do
|
280
|
+
k = Wavefront::BatchWriter.new(opts)
|
281
|
+
|
282
|
+
it 'converts multiple tags to a string' do
|
283
|
+
expect(k.tag_hash_to_str({tag1: 'value 1', tag2: 'value 2' })).
|
284
|
+
to eq('tag1="value 1" tag2="value 2"')
|
285
|
+
end
|
286
|
+
|
287
|
+
it 'converts an empty hash to an empty string' do
|
288
|
+
expect(k.tag_hash_to_str({})).to eq('')
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
describe '#hash_to_wf' do
|
293
|
+
context 'when global tags are not defined' do
|
294
|
+
k = Wavefront::BatchWriter.new(opts)
|
295
|
+
|
296
|
+
it 'converts a known, full, point hash to a known metric' do
|
297
|
+
expect(k.hash_to_wf(
|
298
|
+
path: 'test.metric',
|
299
|
+
value: 123456,
|
300
|
+
ts: Time.at(1469987572),
|
301
|
+
source: 'testhost',
|
302
|
+
tags: { t1: 'v1', t2: 'v2' },
|
303
|
+
)).to eq(
|
304
|
+
'test.metric 123456 1469987572 source=testhost t1="v1" t2="v2"')
|
305
|
+
end
|
306
|
+
|
307
|
+
it 'converts a tag-less point hash to a known metric' do
|
308
|
+
expect(k.hash_to_wf(
|
309
|
+
path: 'test.metric',
|
310
|
+
value: 123456,
|
311
|
+
ts: Time.at(1469987572),
|
312
|
+
source: 'testhost',
|
313
|
+
)).to eq('test.metric 123456 1469987572 source=testhost')
|
314
|
+
end
|
315
|
+
|
316
|
+
it 'converts a timestamp-less point hash to a known metric' do
|
317
|
+
expect(k.hash_to_wf(
|
318
|
+
path: 'test.metric',
|
319
|
+
value: 123456,
|
320
|
+
source: 'testhost',
|
321
|
+
tags: { t1: 'v1', t2: 'v2' },
|
322
|
+
)).to eq('test.metric 123456 source=testhost t1="v1" t2="v2"')
|
323
|
+
end
|
324
|
+
|
325
|
+
it 'raises ArgumentError if vital components are missing' do
|
326
|
+
expect{k.hash_to_wf({})}.to raise_exception(ArgumentError)
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
context 'when global tags are defined' do
|
331
|
+
k = Wavefront::BatchWriter.new(tags: { gtag1: 'gval1', gtag2:
|
332
|
+
'gval2'})
|
333
|
+
|
334
|
+
it 'converts a known, full, point hash to a known metric' do
|
335
|
+
expect(k.hash_to_wf(
|
336
|
+
path: 'test.metric',
|
337
|
+
value: 123456,
|
338
|
+
ts: Time.at(1469987572),
|
339
|
+
source: 'testhost',
|
340
|
+
tags: { t1: 'v1', t2: 'v2' },
|
341
|
+
)).to eq(
|
342
|
+
'test.metric 123456 1469987572 source=testhost t1="v1" ' +
|
343
|
+
't2="v2" gtag1="gval1" gtag2="gval2"')
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
describe '#send_point' do
|
349
|
+
context 'without noop set' do
|
350
|
+
it 'should write a metric to a socket object' do
|
351
|
+
m = 'test.metric 1234 1469987572 source=testhost t1="v1" t2="v2"'
|
352
|
+
socket = Mocket.new
|
353
|
+
allow(TCPSocket).to receive(:new).and_return(socket)
|
354
|
+
expect(socket).to receive(:puts).with(m)
|
355
|
+
k = Wavefront::BatchWriter.new(opts)
|
356
|
+
k.open_socket
|
357
|
+
k.send_point(m)
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
context 'with noop set' do
|
362
|
+
it 'should not write a metric to a socket object' do
|
363
|
+
m = 'test.metric 1234 1469987572 source=testhost t1="v1" t2="v2"'
|
364
|
+
socket = Mocket.new
|
365
|
+
allow(TCPSocket).to receive(:new).and_return(socket)
|
366
|
+
expect(socket).not_to receive(:puts).with(m)
|
367
|
+
k = Wavefront::BatchWriter.new(noop: true)
|
368
|
+
k.open_socket
|
369
|
+
k.send_point(m)
|
370
|
+
expect{k.send_point(m)}.to match_stdout("Would send: #{m}")
|
371
|
+
end
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
describe '#open_socket' do
|
376
|
+
|
377
|
+
context 'without noop set' do
|
378
|
+
it 'creates a socket object with the default parameters' do
|
379
|
+
allow(TCPSocket).to receive(:new)
|
380
|
+
expect(TCPSocket).to receive(:new).with('wfp', 2878)
|
381
|
+
allow_any_instance_of(Wavefront::BatchWriter).to receive(:new)
|
382
|
+
k = Wavefront::BatchWriter.new
|
383
|
+
k.instance_variable_set(:@opts, proxy: 'wfp', port: 2878)
|
384
|
+
k.open_socket
|
385
|
+
end
|
386
|
+
|
387
|
+
it 'raises an exception on an invalid endpoint' do
|
388
|
+
allow_any_instance_of(Wavefront::BatchWriter).to receive(:new)
|
389
|
+
k = Wavefront::BatchWriter.new
|
390
|
+
k.instance_variable_set(:@opts, proxy: 'wfp', port: 2879)
|
391
|
+
expect{k.open_socket}.to raise_exception(
|
392
|
+
Wavefront::Exception::InvalidEndpoint)
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
context 'with noop set' do
|
397
|
+
it 'prints a message but does not open a socket' do
|
398
|
+
allow_any_instance_of(Wavefront::BatchWriter).to receive(:new)
|
399
|
+
k = Wavefront::BatchWriter.new
|
400
|
+
k.instance_variable_set(:@opts, proxy: 'wfp', port:
|
401
|
+
2878, noop: true)
|
402
|
+
expect(k.open_socket).to be(true)
|
403
|
+
expect{k.open_socket}.to match_stdout(
|
404
|
+
'No-op requested. Not opening connection to proxy.')
|
405
|
+
end
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
describe '#close_socket' do
|
410
|
+
end
|
411
|
+
|
412
|
+
describe '#valid_path?' do
|
413
|
+
k = Wavefront::BatchWriter.new(opts)
|
414
|
+
|
415
|
+
it 'accepts a-z, 0-9, _ and -' do
|
416
|
+
expect(k.valid_path?('a.l33t.metric_path-passes')).to be(true)
|
417
|
+
end
|
418
|
+
|
419
|
+
it 'accepts nearly very long paths' do
|
420
|
+
expect(k.valid_path?('a' * 1023)).to be(true)
|
421
|
+
end
|
422
|
+
|
423
|
+
it 'rejects very long paths' do
|
424
|
+
expect{k.valid_path?('a' * 1024)}.to raise_exception(
|
425
|
+
Wavefront::Exception::InvalidMetricName)
|
426
|
+
end
|
427
|
+
|
428
|
+
it 'rejects upper-case letters' do
|
429
|
+
expect{k.valid_path?('NO.NEED.TO.SHOUT')}.to raise_exception(
|
430
|
+
Wavefront::Exception::InvalidMetricName)
|
431
|
+
end
|
432
|
+
|
433
|
+
it 'rejects odd characters' do
|
434
|
+
expect{k.valid_path?('metric.is.(>_<)')}.to raise_exception(
|
435
|
+
Wavefront::Exception::InvalidMetricName)
|
436
|
+
end
|
437
|
+
end
|
438
|
+
|
439
|
+
describe '#valid_value?' do
|
440
|
+
k = Wavefront::BatchWriter.new(opts)
|
441
|
+
|
442
|
+
it 'accepts integers' do
|
443
|
+
expect(k.valid_value?(123456)).to be(true)
|
444
|
+
end
|
445
|
+
|
446
|
+
it 'accepts 0' do
|
447
|
+
expect(k.valid_value?(0)).to be(true)
|
448
|
+
end
|
449
|
+
|
450
|
+
it 'accepts negative integers' do
|
451
|
+
expect(k.valid_value?(-10)).to be(true)
|
452
|
+
end
|
453
|
+
|
454
|
+
it 'accepts decimals' do
|
455
|
+
expect(k.valid_value?(1.2345678)).to be(true)
|
456
|
+
end
|
457
|
+
|
458
|
+
it 'accepts exponential notation' do
|
459
|
+
expect(k.valid_value?(1.23e04)).to be(true)
|
460
|
+
end
|
461
|
+
|
462
|
+
it 'rejects strings which look like numbers' do
|
463
|
+
expect { k.valid_value?('1.23')}.to raise_exception(
|
464
|
+
Wavefront::Exception::InvalidMetricValue)
|
465
|
+
end
|
466
|
+
end
|
467
|
+
|
468
|
+
describe '#valid_ts?' do
|
469
|
+
k = Wavefront::BatchWriter.new(opts)
|
470
|
+
|
471
|
+
it 'rejects integers' do
|
472
|
+
expect { k.valid_ts?(Time.now.to_i) }.to raise_exception(
|
473
|
+
Wavefront::Exception::InvalidTimestamp)
|
474
|
+
end
|
475
|
+
|
476
|
+
it 'accepts Times' do
|
477
|
+
expect(k.valid_ts?(Time.now)).to be(true)
|
478
|
+
end
|
479
|
+
|
480
|
+
it 'accepts DateTimes' do
|
481
|
+
expect(k.valid_ts?(DateTime.now)).to be(true)
|
482
|
+
end
|
483
|
+
|
484
|
+
it 'accepts Dates' do
|
485
|
+
expect(k.valid_ts?(Date.today)).to be(true)
|
486
|
+
end
|
487
|
+
end
|
488
|
+
|
489
|
+
describe '#valid_tags?' do
|
490
|
+
k = Wavefront::BatchWriter.new(opts)
|
491
|
+
|
492
|
+
it 'accepts zero tags' do
|
493
|
+
expect(k.valid_tags?({})).to be(true)
|
494
|
+
end
|
495
|
+
|
496
|
+
it 'accepts nice sensible tags' do
|
497
|
+
expect(k.valid_tags?({tag1: 'val1', tag2: 'val2'})).to be(true)
|
498
|
+
end
|
499
|
+
|
500
|
+
it 'accepts spaces and symbols in values' do
|
501
|
+
expect(k.valid_tags?({tag1: 'val 1', tag2: 'val 2'})).to be(true)
|
502
|
+
expect(k.valid_tags?({tag1: '(>_<)', tag2: '^_^'})).to be(true)
|
503
|
+
end
|
504
|
+
|
505
|
+
it 'rejects spaces and symbols in keys' do
|
506
|
+
expect { k.valid_tags?({'tag 1' => 'val1',
|
507
|
+
'tag 2' => 'val2'}) }.to raise_exception(
|
508
|
+
Wavefront::Exception::InvalidTag)
|
509
|
+
expect { k.valid_tags?({'(>_<)' => 'val1',
|
510
|
+
'^_^' => 'val2'}) }.to raise_exception(
|
511
|
+
Wavefront::Exception::InvalidTag)
|
512
|
+
end
|
513
|
+
|
514
|
+
it 'rejects long keys and/or values' do
|
515
|
+
expect { k.valid_tags?({tag1: 'v' * 255}) }.to raise_exception(
|
516
|
+
Wavefront::Exception::InvalidTag)
|
517
|
+
expect { k.valid_tags?({'k' * 255 => 'val1'}) }.to raise_exception(
|
518
|
+
Wavefront::Exception::InvalidTag)
|
519
|
+
expect { k.valid_tags?({'k' * 130 => 'v' * 130}) }.to raise_exception(
|
520
|
+
Wavefront::Exception::InvalidTag)
|
521
|
+
end
|
522
|
+
end
|
523
|
+
end
|