tsdb_time_series 4.1.2
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 +15 -0
- data/.gitignore +22 -0
- data/.rubocop.yml +17 -0
- data/.rvmrc +1 -0
- data/CHANGELOG.md +38 -0
- data/CONTRIBUTING.md +22 -0
- data/Gemfile +4 -0
- data/Guardfile +27 -0
- data/README.md +232 -0
- data/Rakefile +44 -0
- data/lib/time_series.rb +7 -0
- data/lib/time_series/metric.rb +56 -0
- data/lib/time_series/query.rb +170 -0
- data/lib/time_series/results/result.rb +40 -0
- data/lib/time_series/results/synthetic_result.rb +103 -0
- data/lib/time_series/ts_client.rb +169 -0
- data/lib/time_series/version.rb +9 -0
- data/spec/acceptance/lib/time_series/metric_spec.rb +27 -0
- data/spec/acceptance/lib/time_series/synthetic_result_spec.rb +27 -0
- data/spec/acceptance/lib/time_series/ts_client_spec.rb +146 -0
- data/spec/fixtures.rb +17 -0
- data/spec/fixtures/errors/no_metric.json +6 -0
- data/spec/fixtures/errors/no_tag_key.json +6 -0
- data/spec/fixtures/query/sys.numa.allocation.json +17 -0
- data/spec/fixtures/query/sys.numa.allocation.rate.json +17 -0
- data/spec/fixtures/query/sys.numa.zoneallocs.json +17 -0
- data/spec/fixtures/suggest/sys.json +1 -0
- data/spec/integration/lib/time_series/integration_spec.rb +129 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/unit/lib/time_series/metric_spec.rb +48 -0
- data/spec/unit/lib/time_series/query_spec.rb +49 -0
- data/spec/unit/lib/time_series/synthetic_result_spec.rb +39 -0
- data/spec/unit/lib/time_series/ts_client_spec.rb +70 -0
- data/tsdb_time_series.gemspec +38 -0
- data/tsdb_time_series.reek +0 -0
- metadata +323 -0
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'time_series'
|
5
|
+
|
6
|
+
describe Opower::TimeSeries::TSClient do
|
7
|
+
describe '#write' do
|
8
|
+
subject { Opower::TimeSeries::TSClient.new('127.0.0.1', 60000) }
|
9
|
+
let(:config) { { name: 'test1.test2', timestamp: 12132342, value: 1, tags: { host: 'localhost' } } }
|
10
|
+
let(:metric) { Opower::TimeSeries::Metric.new(config) }
|
11
|
+
|
12
|
+
context 'in dry run mode' do
|
13
|
+
it 'returns the put string' do
|
14
|
+
subject.configure(dry_run: true)
|
15
|
+
call = subject.write(metric)
|
16
|
+
expect(call).to eq("echo \"put test1.test2 12132342 1 host=localhost\" | nc -w 30 127.0.0.1 60000")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context 'in normal mode' do
|
21
|
+
it 'errors on failing to insert data' do
|
22
|
+
message = "Failed to insert metric #{metric.name} with value of #{metric.value} into OpenTSDB."
|
23
|
+
expect { subject.write(metric) }.to raise_error(IOError, message)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'time_series'
|
5
|
+
|
6
|
+
describe Opower::TimeSeries::TSClient do
|
7
|
+
describe '#run_synthetic_query' do
|
8
|
+
subject { Opower::TimeSeries::TSClient.new('opentsdb.foo.com', 4242) }
|
9
|
+
|
10
|
+
it 'computes a simple formula correctly' do
|
11
|
+
m = [{ metric: 'sys.numa.allocation', tags: { host: 'opentsdb.foo.com' } }]
|
12
|
+
config = { format: :json, start: 1421676714, finish: 1421676774, m: m }
|
13
|
+
@query_one = Opower::TimeSeries::Query.new(config)
|
14
|
+
|
15
|
+
m = [{ metric: 'sys.numa.zoneallocs', tags: { host: 'opentsdb.foo.com' } }]
|
16
|
+
config = { format: :json, start: 1421676714, finish: 1421676774, m: m }
|
17
|
+
@query_two = Opower::TimeSeries::Query.new(config)
|
18
|
+
|
19
|
+
# stub requests
|
20
|
+
stub_request(:get, subject.query_uri(@query_one)).to_return(status: 200, body: Fixtures::SYS_ALLOCATION)
|
21
|
+
stub_request(:get, subject.query_uri(@query_two)).to_return(status: 200, body: Fixtures::SYS_ZONE_ALLOCS)
|
22
|
+
|
23
|
+
synthetic_results = subject.run_synthetic_query('test', 'x / y', x: @query_one, y: @query_two)
|
24
|
+
expect(synthetic_results.length).not_to eq(0)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'time_series'
|
5
|
+
|
6
|
+
describe Opower::TimeSeries::TSClient do
|
7
|
+
describe '#suggest' do
|
8
|
+
subject { Opower::TimeSeries::TSClient.new('opentsdb.foo.com', 4242) }
|
9
|
+
|
10
|
+
before do
|
11
|
+
stub_request(:get, subject.suggest_uri('mtest')).to_return(body: '[]')
|
12
|
+
stub_request(:get, subject.suggest_uri('sys')).to_return(body: Fixtures::SUGGEST_SYS)
|
13
|
+
end
|
14
|
+
|
15
|
+
context 'in dry run mode' do
|
16
|
+
subject do
|
17
|
+
super().configure(dry_run: true)
|
18
|
+
super()
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'returns the proper URI' do
|
22
|
+
url = subject.suggest_uri('mtest')
|
23
|
+
expect(url).to eq('http://opentsdb.foo.com:4242/api/suggest?type=metrics&q=mtest&max=25')
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'in normal mode' do
|
28
|
+
subject do
|
29
|
+
super().configure(dry_run: false)
|
30
|
+
super()
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'returns an empty array for a query with no expected results' do
|
34
|
+
suggestions = subject.suggest('mtest')
|
35
|
+
expect(suggestions).to eq([])
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'returns data for a query with expected results' do
|
39
|
+
suggestions = subject.suggest('sys')
|
40
|
+
expect(suggestions).to eq(JSON.parse(Fixtures::SUGGEST_SYS))
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe '#run_query' do
|
46
|
+
subject { Opower::TimeSeries::TSClient.new('opentsdb.foo.com', 4242) }
|
47
|
+
|
48
|
+
context 'with invalid input' do
|
49
|
+
it 'raises an error for a bad metric name' do
|
50
|
+
m = [{ metric: 'mtest' }]
|
51
|
+
config = { format: :json, start: 1421676714, finish: 1421676774, m: m }
|
52
|
+
query = Opower::TimeSeries::Query.new(config)
|
53
|
+
stub_request(:get, subject.query_uri(query)).to_return(status: 500, body: Fixtures::BAD_METRIC)
|
54
|
+
|
55
|
+
results = subject.run_query(query).results
|
56
|
+
expect(results).to include(JSON.parse(Fixtures::BAD_METRIC))
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'raises an error for a bad tagk name ' do
|
60
|
+
m = [{ metric: 'sys.numa.allocation', tags: { bad_tagk: 'opentsdb.foo.com' } }]
|
61
|
+
config = { format: :json, start: 1421676714, finish: 1421676774, m: m }
|
62
|
+
query = Opower::TimeSeries::Query.new(config)
|
63
|
+
stub_request(:get, subject.query_uri(query)).to_return(status: 500, body: Fixtures::BAD_TAGK)
|
64
|
+
|
65
|
+
results = subject.run_query(query).results
|
66
|
+
expect(results).to include(JSON.parse(Fixtures::BAD_TAGK))
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context 'with valid input' do
|
71
|
+
it 'returns an empty JSON array for a query with no expected results' do
|
72
|
+
m = [{ metric: 'sys.numa.allocation' }]
|
73
|
+
config = { format: :json, start: 1420676714, finish: 1420676774, m: m }
|
74
|
+
query = Opower::TimeSeries::Query.new(config)
|
75
|
+
stub_request(:get, subject.query_uri(query)).to_return(status: 200, body: '[]')
|
76
|
+
|
77
|
+
results = subject.run_query(query).results
|
78
|
+
expect(results).to eq([])
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'returns data for a query in JSON format' do
|
82
|
+
m = [{ metric: 'sys.numa.allocation', tags: { host: 'opentsdb.foo.com' } }]
|
83
|
+
config = { format: :json, start: 1420676714, finish: 1420676774, m: m }
|
84
|
+
query = Opower::TimeSeries::Query.new(config)
|
85
|
+
stub_request(:get, subject.query_uri(query)).to_return(body: Fixtures::SYS_ALLOCATION)
|
86
|
+
|
87
|
+
results = subject.run_query(query).results
|
88
|
+
expect(results).to eq(JSON.parse(Fixtures::SYS_ALLOCATION))
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'returns data for a rate query in JSON format' do
|
92
|
+
m = [{ metric: 'sys.numa.allocation', rate: true, tags: { host: 'opentsdb.foo.com' } }]
|
93
|
+
config = { format: :json, start: 1420676714, finish: 1420676774, m: m }
|
94
|
+
query = Opower::TimeSeries::Query.new(config)
|
95
|
+
stub_request(:get, subject.query_uri(query)).to_return(body: Fixtures::SYS_ALLOC_RATE)
|
96
|
+
|
97
|
+
results = subject.run_query(query).results
|
98
|
+
expect(results).to eq(JSON.parse(Fixtures::SYS_ALLOC_RATE))
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'returns a URL for a query in PNG format' do
|
102
|
+
m = [{ metric: 'sys.numa.allocation', tags: { host: 'opentsdb.foo.com' } }]
|
103
|
+
config = { format: :png, start: 1420676714, finish: 1420676774, m: m }
|
104
|
+
query = Opower::TimeSeries::Query.new(config)
|
105
|
+
results = subject.run_query(query)
|
106
|
+
expect(results).not_to eq('')
|
107
|
+
expect(results).not_to include('Internal Server Error')
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'returns data for multiple queries' do
|
111
|
+
queries = []
|
112
|
+
3.times do
|
113
|
+
m = [{ metric: 'sys.numa.allocation', tags: { host: 'opentsdb.foo.com' } }]
|
114
|
+
config = { format: :json, start: '1h-ago', m: m }
|
115
|
+
query = Opower::TimeSeries::Query.new(config)
|
116
|
+
stub_request(:get, subject.query_uri(query)).to_return(body: Fixtures::SYS_ALLOCATION)
|
117
|
+
queries << query
|
118
|
+
end
|
119
|
+
|
120
|
+
results = subject.run_queries(queries)
|
121
|
+
expect(results.length).to eq(3)
|
122
|
+
results.each do |r|
|
123
|
+
expect(r.results).not_to eq('')
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
describe '#valid?' do
|
130
|
+
subject { Opower::TimeSeries::TSClient.new('opentsdb.foo.com', 4242) }
|
131
|
+
|
132
|
+
context 'with a valid connection' do
|
133
|
+
it 'returns true' do
|
134
|
+
stub_request(:get, "#{subject.client}api/version").to_return(status: 200)
|
135
|
+
expect(subject.valid?).to be_truthy
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
context 'with an invalid connection' do
|
140
|
+
it 'returns false' do
|
141
|
+
stub_request(:get, "#{subject.client}api/version").to_timeout
|
142
|
+
expect(subject.valid?).to be_falsey
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
data/spec/fixtures.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# Fixtures wrapper - contains raw data used by webmocks
|
2
|
+
module Fixtures
|
3
|
+
def self.fixture(filename)
|
4
|
+
File.expand_path("../fixtures/#{filename}", __FILE__)
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.read_file(filename)
|
8
|
+
IO.read(fixture(filename))
|
9
|
+
end
|
10
|
+
|
11
|
+
BAD_METRIC = read_file('errors/no_metric.json')
|
12
|
+
BAD_TAGK = read_file('errors/no_tag_key.json')
|
13
|
+
SUGGEST_SYS = read_file('suggest/sys.json')
|
14
|
+
SYS_ALLOCATION = read_file('query/sys.numa.allocation.json')
|
15
|
+
SYS_ALLOC_RATE = read_file('query/sys.numa.allocation.rate.json')
|
16
|
+
SYS_ZONE_ALLOCS = read_file('query/sys.numa.zoneallocs.json')
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
[
|
2
|
+
{
|
3
|
+
"metric": "sys.numa.allocation",
|
4
|
+
"tags": { "host": "opentsdb.foo.com" },
|
5
|
+
"aggregateTags": [
|
6
|
+
"type",
|
7
|
+
"node"
|
8
|
+
],
|
9
|
+
"dps": {
|
10
|
+
"1421676714": 15,
|
11
|
+
"1421676729": 16,
|
12
|
+
"1421676744": 0,
|
13
|
+
"1421676759": 5,
|
14
|
+
"1421676774": 13
|
15
|
+
}
|
16
|
+
}
|
17
|
+
]
|
@@ -0,0 +1,17 @@
|
|
1
|
+
[
|
2
|
+
{
|
3
|
+
"metric": "sys.numa.allocation",
|
4
|
+
"tags": { "host": "opentsdb.foo.com" },
|
5
|
+
"aggregateTags": [
|
6
|
+
"type",
|
7
|
+
"node"
|
8
|
+
],
|
9
|
+
"dps": {
|
10
|
+
"1421676714": 0.02,
|
11
|
+
"1421676729": 0.05,
|
12
|
+
"1421676744": 0,
|
13
|
+
"1421676759": 0.01,
|
14
|
+
"1421676774": 0.02
|
15
|
+
}
|
16
|
+
}
|
17
|
+
]
|
@@ -0,0 +1,17 @@
|
|
1
|
+
[
|
2
|
+
{
|
3
|
+
"metric": "sys.numa.zoneallocs",
|
4
|
+
"tags": {"host": "opentsdb.foo.com"},
|
5
|
+
"aggregateTags": [
|
6
|
+
"type",
|
7
|
+
"node"
|
8
|
+
],
|
9
|
+
"dps": {
|
10
|
+
"1421676714": 3,
|
11
|
+
"1421676729": 2,
|
12
|
+
"1421676744": 55,
|
13
|
+
"1421676759": 1,
|
14
|
+
"1421676774": 3
|
15
|
+
}
|
16
|
+
}
|
17
|
+
]
|
@@ -0,0 +1 @@
|
|
1
|
+
["sys.numa.allocation","sys.numa.foreign_allocs","sys.numa.interleave","sys.numa.zoneallocs","system.context_switches_per_second","system.interrupts_per_second"]
|
@@ -0,0 +1,129 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'time_series'
|
5
|
+
require 'docker'
|
6
|
+
|
7
|
+
describe Opower::TimeSeries::TSClient do
|
8
|
+
# Integration tests run inside a Docker container
|
9
|
+
before :all do
|
10
|
+
# allow HTTP during integration tests
|
11
|
+
WebMock.allow_net_connect!
|
12
|
+
|
13
|
+
@container = Docker::Container.create('Image' => 'opower/opentsdb')
|
14
|
+
@container.start('PortBindings' => { '4242/tcp' => [{ 'HostPort' => '48000' }] })
|
15
|
+
|
16
|
+
# check if we're runing boot2docker
|
17
|
+
if ENV['DOCKER_HOST'].nil?
|
18
|
+
network_settings = @container.json.fetch('NetworkSettings')
|
19
|
+
@ip = network_settings.fetch('IPAddress') # TODO: verify this works for boot2docker
|
20
|
+
@port = 4242
|
21
|
+
else
|
22
|
+
@ip = `boot2docker ip 2> /dev/null`
|
23
|
+
@port = 48000
|
24
|
+
end
|
25
|
+
|
26
|
+
sleep 30
|
27
|
+
attempts = 0
|
28
|
+
client = Opower::TimeSeries::TSClient.new(@ip, @port)
|
29
|
+
|
30
|
+
while client.valid? == false && attempts < 10
|
31
|
+
sleep 5
|
32
|
+
attempts += 1
|
33
|
+
end
|
34
|
+
|
35
|
+
fail RemoteError('Failed to start Docker container!') if attempts > 10 && !client.valid?
|
36
|
+
|
37
|
+
# prewrite expected metrics
|
38
|
+
metrics = [{ name: 'cpu.load', timestamp: 1420676750, value: 1, tags: { host: 'localhost' } },
|
39
|
+
{ name: 'test1.test2', timestamp: 12132343, value: 1, tags: { host: 'localhost' } },
|
40
|
+
{ name: 'metric1', timestamp: 1421676714, value: 1, tags: { host: 'localhost' } },
|
41
|
+
{ name: 'metric2', timestamp: 1421676714, value: 2, tags: { host: 'localhost' } }]
|
42
|
+
|
43
|
+
metrics.each { |metric| client.write(Opower::TimeSeries::Metric.new(metric)) }
|
44
|
+
|
45
|
+
# wait for OpenTSDB to synch
|
46
|
+
sleep 3
|
47
|
+
end
|
48
|
+
|
49
|
+
after :all do
|
50
|
+
@container.stop
|
51
|
+
@container.delete(force: true)
|
52
|
+
|
53
|
+
# resume blocking afterwards (in case acceptance tests afterwards)
|
54
|
+
WebMock.disable_net_connect!
|
55
|
+
end
|
56
|
+
|
57
|
+
describe '#suggest' do
|
58
|
+
subject { Opower::TimeSeries::TSClient.new(@ip, @port) }
|
59
|
+
|
60
|
+
context 'with no expected results' do
|
61
|
+
it 'returns an empty array' do
|
62
|
+
suggestions = subject.suggest('mtest')
|
63
|
+
expect(suggestions).to eq([])
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
context 'with expected results' do
|
68
|
+
it 'returns data' do
|
69
|
+
suggestions = subject.suggest('test1.test2')
|
70
|
+
expect(suggestions).to eq(['test1.test2'])
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe '#run_query' do
|
76
|
+
subject { Opower::TimeSeries::TSClient.new(@ip, @port) }
|
77
|
+
|
78
|
+
context 'with bad input' do
|
79
|
+
it 'raises an error for a bad metric name' do
|
80
|
+
m = [{ metric: 'mtest' }]
|
81
|
+
config = { format: :json, start: 1421676714, finish: 1421676774, m: m }
|
82
|
+
query = Opower::TimeSeries::Query.new(config)
|
83
|
+
|
84
|
+
results = subject.run_query(query)
|
85
|
+
expect(results.errors?).to be_truthy
|
86
|
+
expect(results.error_message).to eq("No such name for 'metrics': 'mtest'")
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'raises an error for a bad tagk name ' do
|
90
|
+
m = [{ metric: 'test1.test2', tags: { bad_tagk: 'opentsdb.foo.com' } }]
|
91
|
+
config = { format: :json, start: 1421676714, finish: 1421676774, m: m }
|
92
|
+
query = Opower::TimeSeries::Query.new(config)
|
93
|
+
|
94
|
+
results = subject.run_query(query)
|
95
|
+
expect(results.errors?).to be_truthy
|
96
|
+
expect(results.error_message).to eq("No such name for 'tagk': 'bad_tagk'")
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context 'with valid input' do
|
101
|
+
it 'returns data' do
|
102
|
+
m = [{ metric: 'cpu.load' }]
|
103
|
+
config = { format: :json, start: 1420676714, finish: 1420676774, m: m }
|
104
|
+
query = Opower::TimeSeries::Query.new(config)
|
105
|
+
|
106
|
+
results = subject.run_query(query).results
|
107
|
+
expect(results.length).not_to eq(0)
|
108
|
+
expect(results[0].fetch('dps')).to include('1420676750' => 1)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
describe '#run_synthetic_query' do
|
114
|
+
subject { Opower::TimeSeries::TSClient.new(@ip, @port) }
|
115
|
+
|
116
|
+
it 'computes a simple formula correctly' do
|
117
|
+
m = [{ metric: 'metric1' }]
|
118
|
+
config = { format: :json, start: 1421676000, finish: 1421676774, m: m }
|
119
|
+
query_one = Opower::TimeSeries::Query.new(config)
|
120
|
+
|
121
|
+
m = [{ metric: 'metric2' }]
|
122
|
+
config = { format: :json, start: 1421676000, finish: 1421676774, m: m }
|
123
|
+
query_two = Opower::TimeSeries::Query.new(config)
|
124
|
+
|
125
|
+
synthetic_results = subject.run_synthetic_query('test', 'x / y', x: query_one, y: query_two)
|
126
|
+
expect(synthetic_results.length).not_to eq(0)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
require 'webmock/rspec'
|
3
|
+
|
4
|
+
SimpleCov.start 'rails' do
|
5
|
+
coverage_dir 'metrics/coverage'
|
6
|
+
end
|
7
|
+
|
8
|
+
pid = Process.pid
|
9
|
+
SimpleCov.at_exit do
|
10
|
+
SimpleCov.result.format! if Process.pid == pid
|
11
|
+
end
|
12
|
+
|
13
|
+
$LOAD_PATH << File.expand_path('../../lib', __FILE__)
|
14
|
+
|
15
|
+
require File.expand_path('../fixtures.rb', __FILE__)
|