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