appoptics-api-ruby 2.1.3
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 +7 -0
- data/.gitignore +23 -0
- data/.rspec +2 -0
- data/.travis.yml +25 -0
- data/CHANGELOG.md +184 -0
- data/Gemfile +36 -0
- data/LICENSE +24 -0
- data/README.md +271 -0
- data/Rakefile +63 -0
- data/appoptics-api-ruby.gemspec +31 -0
- data/benchmarks/array_vs_set.rb +29 -0
- data/certs/librato-public.pem +20 -0
- data/examples/simple.rb +24 -0
- data/examples/submit_every.rb +27 -0
- data/lib/appoptics/metrics.rb +95 -0
- data/lib/appoptics/metrics/aggregator.rb +138 -0
- data/lib/appoptics/metrics/annotator.rb +145 -0
- data/lib/appoptics/metrics/client.rb +361 -0
- data/lib/appoptics/metrics/collection.rb +43 -0
- data/lib/appoptics/metrics/connection.rb +101 -0
- data/lib/appoptics/metrics/errors.rb +32 -0
- data/lib/appoptics/metrics/middleware/count_requests.rb +28 -0
- data/lib/appoptics/metrics/middleware/expects_status.rb +38 -0
- data/lib/appoptics/metrics/middleware/request_body.rb +18 -0
- data/lib/appoptics/metrics/middleware/retry.rb +31 -0
- data/lib/appoptics/metrics/persistence.rb +2 -0
- data/lib/appoptics/metrics/persistence/direct.rb +73 -0
- data/lib/appoptics/metrics/persistence/test.rb +27 -0
- data/lib/appoptics/metrics/processor.rb +130 -0
- data/lib/appoptics/metrics/queue.rb +191 -0
- data/lib/appoptics/metrics/smart_json.rb +43 -0
- data/lib/appoptics/metrics/util.rb +25 -0
- data/lib/appoptics/metrics/version.rb +5 -0
- data/spec/integration/metrics/annotator_spec.rb +190 -0
- data/spec/integration/metrics/connection_spec.rb +14 -0
- data/spec/integration/metrics/middleware/count_requests_spec.rb +28 -0
- data/spec/integration/metrics/queue_spec.rb +96 -0
- data/spec/integration/metrics_spec.rb +375 -0
- data/spec/rackups/status.ru +30 -0
- data/spec/spec_helper.rb +88 -0
- data/spec/unit/metrics/aggregator_spec.rb +417 -0
- data/spec/unit/metrics/client_spec.rb +127 -0
- data/spec/unit/metrics/connection_spec.rb +113 -0
- data/spec/unit/metrics/queue/autosubmission_spec.rb +57 -0
- data/spec/unit/metrics/queue_spec.rb +593 -0
- data/spec/unit/metrics/smart_json_spec.rb +79 -0
- data/spec/unit/metrics/util_spec.rb +23 -0
- data/spec/unit/metrics_spec.rb +63 -0
- metadata +135 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module Appoptics
|
|
2
|
+
module Metrics
|
|
3
|
+
class SmartJSON
|
|
4
|
+
if defined?(::MultiJson)
|
|
5
|
+
def self.read(json)
|
|
6
|
+
# MultiJSON >= 1.3.0
|
|
7
|
+
if MultiJson.respond_to?(:load)
|
|
8
|
+
MultiJson.load(json)
|
|
9
|
+
else
|
|
10
|
+
MultiJson.decode(json)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.write(json)
|
|
15
|
+
# MultiJSON <= 1.2.0
|
|
16
|
+
if MultiJson.respond_to?(:dump)
|
|
17
|
+
MultiJson.dump(json)
|
|
18
|
+
else
|
|
19
|
+
MultiJson.encode(json)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.handler
|
|
24
|
+
:multi_json
|
|
25
|
+
end
|
|
26
|
+
else
|
|
27
|
+
require "json"
|
|
28
|
+
|
|
29
|
+
def self.read(json)
|
|
30
|
+
JSON.parse(json)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.write(json)
|
|
34
|
+
JSON.generate(json)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.handler
|
|
38
|
+
:json
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Appoptics
|
|
2
|
+
module Metrics
|
|
3
|
+
|
|
4
|
+
class Util
|
|
5
|
+
SEPARATOR = "%%"
|
|
6
|
+
|
|
7
|
+
# Builds a Hash key from metric name and tags.
|
|
8
|
+
#
|
|
9
|
+
# @param metric_name [String] The unique identifying metric name of the property being tracked.
|
|
10
|
+
# @param tags [Hash] A set of name=value tag pairs that describe the particular data stream.
|
|
11
|
+
# @return [String] the Hash key
|
|
12
|
+
def self.build_key_for(metric_name, tags)
|
|
13
|
+
key_name = metric_name
|
|
14
|
+
tags.sort.each do |key, value|
|
|
15
|
+
k = key.to_s.downcase
|
|
16
|
+
v = value.is_a?(String) ? value.downcase : value
|
|
17
|
+
key_name = "#{key_name}#{SEPARATOR}#{k}=#{v}"
|
|
18
|
+
end
|
|
19
|
+
key_name
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
module Appoptics
|
|
4
|
+
module Metrics
|
|
5
|
+
|
|
6
|
+
describe Annotator do
|
|
7
|
+
before(:all) { prep_integration_tests }
|
|
8
|
+
before(:each) { delete_all_annotations }
|
|
9
|
+
|
|
10
|
+
describe "#add" do
|
|
11
|
+
it "creates new annotation" do
|
|
12
|
+
subject.add :deployment, "deployed v68"
|
|
13
|
+
annos = subject.fetch(:deployment, start_time: Time.now.to_i-60)
|
|
14
|
+
expect(annos["events"]["unassigned"].length).to eq(1)
|
|
15
|
+
expect(annos["events"]["unassigned"][0]["title"]).to eq('deployed v68')
|
|
16
|
+
end
|
|
17
|
+
it "supports sources" do
|
|
18
|
+
subject.add :deployment, 'deployed v69', source: 'box1'
|
|
19
|
+
annos = subject.fetch(:deployment, start_time: Time.now.to_i-60)
|
|
20
|
+
expect(annos["events"]["box1"].length).to eq(1)
|
|
21
|
+
first = annos["events"]["box1"][0]
|
|
22
|
+
expect(first['title']).to eq('deployed v69')
|
|
23
|
+
end
|
|
24
|
+
it "supports start and end times" do
|
|
25
|
+
start_time = Time.now.to_i-120
|
|
26
|
+
end_time = Time.now.to_i-30
|
|
27
|
+
subject.add :deployment, 'deployed v70', start_time: start_time,
|
|
28
|
+
end_time: end_time
|
|
29
|
+
annos = subject.fetch(:deployment, start_time: Time.now.to_i-180)
|
|
30
|
+
expect(annos["events"]["unassigned"].length).to eq(1)
|
|
31
|
+
first = annos["events"]["unassigned"][0]
|
|
32
|
+
expect(first['title']).to eq('deployed v70')
|
|
33
|
+
expect(first['start_time']).to eq(start_time)
|
|
34
|
+
expect(first['end_time']).to eq(end_time)
|
|
35
|
+
end
|
|
36
|
+
it "supports description" do
|
|
37
|
+
subject.add :deployment, 'deployed v71', description: 'deployed foobar!'
|
|
38
|
+
annos = subject.fetch(:deployment, start_time: Time.now.to_i-180)
|
|
39
|
+
expect(annos["events"]["unassigned"].length).to eq(1)
|
|
40
|
+
first = annos["events"]["unassigned"][0]
|
|
41
|
+
expect(first['title']).to eq('deployed v71')
|
|
42
|
+
expect(first['description']).to eq('deployed foobar!')
|
|
43
|
+
end
|
|
44
|
+
it "has an id for further use" do
|
|
45
|
+
annotation = subject.add :deployment, "deployed v23"
|
|
46
|
+
expect(annotation['id']).not_to be_nil
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
context "with a block" do
|
|
50
|
+
it "sets both start and end times" do
|
|
51
|
+
annotation = subject.add 'deploys', 'v345' do
|
|
52
|
+
sleep 1.0
|
|
53
|
+
end
|
|
54
|
+
data = subject.fetch_event 'deploys', annotation['id']
|
|
55
|
+
expect(data['start_time']).not_to be_nil
|
|
56
|
+
expect(data['end_time']).not_to be_nil
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
describe "#delete" do
|
|
62
|
+
it "removes annotation streams" do
|
|
63
|
+
subject.add :deployment, "deployed v45"
|
|
64
|
+
subject.fetch :deployment # should exist
|
|
65
|
+
subject.delete :deployment
|
|
66
|
+
expect {
|
|
67
|
+
subject.fetch(:deployment)
|
|
68
|
+
}.to raise_error(Metrics::NotFound)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
describe "#delete_event" do
|
|
73
|
+
it "removes an annotation event" do
|
|
74
|
+
subject.add :deployment, 'deployed v46'
|
|
75
|
+
subject.add :deployment, 'deployed v47'
|
|
76
|
+
events = subject.fetch(:deployment, start_time: Time.now.to_i-60)
|
|
77
|
+
events = events['events']['unassigned']
|
|
78
|
+
ids = events.reduce({}) do |hash, event|
|
|
79
|
+
hash[event['title']] = event['id']
|
|
80
|
+
hash
|
|
81
|
+
end
|
|
82
|
+
subject.delete_event :deployment, ids['deployed v47']
|
|
83
|
+
events = subject.fetch(:deployment, start_time: Time.now.to_i-60)
|
|
84
|
+
events = events['events']['unassigned']
|
|
85
|
+
expect(events.length).to eq(1)
|
|
86
|
+
expect(events[0]['title']).to eq('deployed v46')
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
describe "#fetch" do
|
|
91
|
+
context "without a time frame" do
|
|
92
|
+
it "returns stream properties" do
|
|
93
|
+
subject.add :backups, "backup 21"
|
|
94
|
+
properties = subject.fetch :backups
|
|
95
|
+
expect(properties['name']).to eq('backups')
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
context "with a time frame" do
|
|
100
|
+
it "returns set of annotations" do
|
|
101
|
+
subject.add :backups, "backup 22"
|
|
102
|
+
subject.add :backups, "backup 23"
|
|
103
|
+
annos = subject.fetch :backups, start_time: Time.now.to_i-60
|
|
104
|
+
events = annos['events']['unassigned']
|
|
105
|
+
expect(events[0]['title']).to eq('backup 22')
|
|
106
|
+
expect(events[1]['title']).to eq('backup 23')
|
|
107
|
+
end
|
|
108
|
+
it "respects source limits" do
|
|
109
|
+
subject.add :backups, "backup 24", source: 'server_1'
|
|
110
|
+
subject.add :backups, "backup 25", source: 'server_2'
|
|
111
|
+
subject.add :backups, "backup 26", source: 'server_3'
|
|
112
|
+
annos = subject.fetch :backups, start_time: Time.now.to_i-60,
|
|
113
|
+
sources: %w{server_1 server_3}
|
|
114
|
+
expect(annos['events']['server_1']).not_to be_nil
|
|
115
|
+
expect(annos['events']['server_2']).to be_nil
|
|
116
|
+
expect(annos['events']['server_3']).not_to be_nil
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
it "returns exception if annotation is missing" do
|
|
121
|
+
expect {
|
|
122
|
+
subject.fetch :backups
|
|
123
|
+
}.to raise_error(Metrics::NotFound)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
describe "#fetch_event" do
|
|
128
|
+
context "with existing event" do
|
|
129
|
+
it "returns event properties" do
|
|
130
|
+
annotation = subject.add 'deploys', 'v69'
|
|
131
|
+
data = subject.fetch_event 'deploys', annotation['id']
|
|
132
|
+
expect(data['title']).to eq('v69')
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
context "when event doesn't exist" do
|
|
136
|
+
it "raises NotFound" do
|
|
137
|
+
expect {
|
|
138
|
+
data = subject.fetch_event 'deploys', 324
|
|
139
|
+
}.to raise_error(Metrics::NotFound)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
describe "#list" do
|
|
145
|
+
before(:each) do
|
|
146
|
+
subject.add :backups, 'backup 1'
|
|
147
|
+
subject.add :deployment, 'deployed v74'
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
context "without arguments" do
|
|
151
|
+
it "lists annotation streams" do
|
|
152
|
+
streams = subject.list
|
|
153
|
+
expect(streams['annotations'].length).to eq(2)
|
|
154
|
+
streams = streams['annotations'].map{|i| i['name']}
|
|
155
|
+
expect(streams).to include('backups')
|
|
156
|
+
expect(streams).to include('deployment')
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
context "with an argument" do
|
|
160
|
+
it "lists annotation streams which match" do
|
|
161
|
+
streams = subject.list name: 'back'
|
|
162
|
+
expect(streams['annotations'].length).to eq(1)
|
|
163
|
+
streams = streams['annotations'].map{|i| i['name']}
|
|
164
|
+
expect(streams).to include('backups')
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
describe "#update_event" do
|
|
170
|
+
context "when event exists" do
|
|
171
|
+
it "updates event" do
|
|
172
|
+
end_time = (Time.now + 60).to_i
|
|
173
|
+
annotation = subject.add 'deploys', 'v24'
|
|
174
|
+
subject.update_event 'deploys', annotation['id'],
|
|
175
|
+
end_time: end_time, title: 'v28'
|
|
176
|
+
data = subject.fetch_event 'deploys', annotation['id']
|
|
177
|
+
|
|
178
|
+
expect(data['title']).to eq('v28')
|
|
179
|
+
expect(data['end_time']).to eq(end_time)
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
context "when event does not exist" do
|
|
183
|
+
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
end
|
|
190
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
module Appoptics
|
|
4
|
+
module Metrics
|
|
5
|
+
module Middleware
|
|
6
|
+
|
|
7
|
+
describe CountRequests do
|
|
8
|
+
before(:all) { prep_integration_tests }
|
|
9
|
+
|
|
10
|
+
it "counts requests" do
|
|
11
|
+
CountRequests.reset
|
|
12
|
+
Metrics.submit foo: 123
|
|
13
|
+
Metrics.submit foo: 135
|
|
14
|
+
expect(CountRequests.total_requests).to eq(2)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "is resettable" do
|
|
18
|
+
Metrics.submit foo: 123
|
|
19
|
+
expect(CountRequests.total_requests).to be > 0
|
|
20
|
+
CountRequests.reset
|
|
21
|
+
expect(CountRequests.total_requests).to eq(0)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
module Appoptics
|
|
4
|
+
module Metrics
|
|
5
|
+
|
|
6
|
+
describe Queue do
|
|
7
|
+
before(:all) { prep_integration_tests }
|
|
8
|
+
before(:each) do
|
|
9
|
+
delete_all_metrics
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
context "with a large number of metrics" do
|
|
13
|
+
it "submits them in multiple requests" do
|
|
14
|
+
Middleware::CountRequests.reset
|
|
15
|
+
queue = Queue.new(per_request: 3)
|
|
16
|
+
(1..10).each do |i|
|
|
17
|
+
queue.add "gauge_#{i}" => 1
|
|
18
|
+
end
|
|
19
|
+
queue.submit
|
|
20
|
+
expect(Middleware::CountRequests.total_requests).to eq(4)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "persists all metrics" do
|
|
24
|
+
queue = Queue.new(per_request: 2)
|
|
25
|
+
(1..5).each do |i|
|
|
26
|
+
queue.add "gauge_#{i}" => i
|
|
27
|
+
end
|
|
28
|
+
(1..3).each do |i|
|
|
29
|
+
queue.add "counter_#{i}" => {type: :counter, value: i}
|
|
30
|
+
end
|
|
31
|
+
queue.submit
|
|
32
|
+
|
|
33
|
+
metrics = Metrics.metrics
|
|
34
|
+
expect(metrics.length).to eq(8)
|
|
35
|
+
counter = Metrics.get_measurements :counter_3, count: 1
|
|
36
|
+
expect(counter['unassigned'][0]['value']).to eq(3)
|
|
37
|
+
gauge = Metrics.get_measurements :gauge_5, count: 1
|
|
38
|
+
expect(gauge['unassigned'][0]['value']).to eq(5)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it "applies globals to each request" do
|
|
42
|
+
source = 'yogi'
|
|
43
|
+
measure_time = Time.now.to_i-3
|
|
44
|
+
queue = Queue.new(
|
|
45
|
+
per_request: 3,
|
|
46
|
+
source: source,
|
|
47
|
+
measure_time: measure_time,
|
|
48
|
+
skip_measurement_times: true
|
|
49
|
+
)
|
|
50
|
+
(1..5).each do |i|
|
|
51
|
+
queue.add "gauge_#{i}" => 1
|
|
52
|
+
end
|
|
53
|
+
queue.submit
|
|
54
|
+
|
|
55
|
+
# verify globals have persisted for all requests
|
|
56
|
+
gauge = Metrics.get_measurements :gauge_5, count: 1
|
|
57
|
+
expect(gauge[source][0]["value"]).to eq(1.0)
|
|
58
|
+
expect(gauge[source][0]["measure_time"]).to eq(measure_time)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it "respects default and individual sources" do
|
|
63
|
+
queue = Queue.new(source: 'default')
|
|
64
|
+
queue.add foo: 123
|
|
65
|
+
queue.add bar: {value: 456, source: 'barsource'}
|
|
66
|
+
queue.submit
|
|
67
|
+
|
|
68
|
+
foo = Metrics.get_measurements :foo, count: 2
|
|
69
|
+
expect(foo['default'][0]['value']).to eq(123)
|
|
70
|
+
|
|
71
|
+
bar = Metrics.get_measurements :bar, count: 2
|
|
72
|
+
expect(bar['barsource'][0]['value']).to eq(456)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
context "with tags" do
|
|
76
|
+
let(:queue) { Queue.new(tags: { hostname: "metrics-web-stg-1" }) }
|
|
77
|
+
|
|
78
|
+
it "respects default and individual tags" do
|
|
79
|
+
queue.add test_1: 123
|
|
80
|
+
queue.add test_2: { value: 456, tags: { hostname: "metrics-web-stg-2" }}
|
|
81
|
+
queue.submit
|
|
82
|
+
|
|
83
|
+
test_1 = Appoptics::Metrics.get_series :test_1, resolution: 1, duration: 3600
|
|
84
|
+
expect(test_1[0]["tags"]["hostname"]).to eq("metrics-web-stg-1")
|
|
85
|
+
expect(test_1[0]["measurements"][0]["value"]).to eq(123)
|
|
86
|
+
|
|
87
|
+
test_2 = Appoptics::Metrics.get_series :test_2, resolution: 1, duration: 3600
|
|
88
|
+
expect(test_2[0]["tags"]["hostname"]).to eq("metrics-web-stg-2")
|
|
89
|
+
expect(test_2[0]["measurements"][0]["value"]).to eq(456)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
module Appoptics
|
|
4
|
+
describe Metrics do
|
|
5
|
+
before(:all) { prep_integration_tests }
|
|
6
|
+
|
|
7
|
+
describe "#annotate" do
|
|
8
|
+
before(:all) { @annotator = Metrics::Annotator.new }
|
|
9
|
+
before(:each) { delete_all_annotations }
|
|
10
|
+
|
|
11
|
+
it "creates new annotation" do
|
|
12
|
+
Metrics.annotate :deployment, "deployed v68"
|
|
13
|
+
annos = @annotator.fetch(:deployment, start_time: Time.now.to_i-60)
|
|
14
|
+
expect(annos["events"]["unassigned"].length).to eq(1)
|
|
15
|
+
expect(annos["events"]["unassigned"][0]["title"]).to eq('deployed v68')
|
|
16
|
+
end
|
|
17
|
+
it "supports sources" do
|
|
18
|
+
Metrics.annotate :deployment, 'deployed v69', source: 'box1'
|
|
19
|
+
annos = @annotator.fetch(:deployment, start_time: Time.now.to_i-60)
|
|
20
|
+
expect(annos["events"]["box1"].length).to eq(1)
|
|
21
|
+
first = annos["events"]["box1"][0]
|
|
22
|
+
expect(first['title']).to eq('deployed v69')
|
|
23
|
+
end
|
|
24
|
+
it "supports start and end times" do
|
|
25
|
+
start_time = Time.now.to_i-120
|
|
26
|
+
end_time = Time.now.to_i-30
|
|
27
|
+
Metrics.annotate :deployment, 'deployed v70', start_time: start_time,
|
|
28
|
+
end_time: end_time
|
|
29
|
+
annos = @annotator.fetch(:deployment, start_time: Time.now.to_i-180)
|
|
30
|
+
expect(annos["events"]["unassigned"].length).to eq(1)
|
|
31
|
+
first = annos["events"]["unassigned"][0]
|
|
32
|
+
expect(first['title']).to eq('deployed v70')
|
|
33
|
+
expect(first['start_time']).to eq(start_time)
|
|
34
|
+
expect(first['end_time']).to eq(end_time)
|
|
35
|
+
end
|
|
36
|
+
it "supports description" do
|
|
37
|
+
Metrics.annotate :deployment, 'deployed v71', description: 'deployed foobar!'
|
|
38
|
+
annos = @annotator.fetch(:deployment, start_time: Time.now.to_i-180)
|
|
39
|
+
expect(annos["events"]["unassigned"].length).to eq(1)
|
|
40
|
+
first = annos["events"]["unassigned"][0]
|
|
41
|
+
expect(first['title']).to eq('deployed v71')
|
|
42
|
+
expect(first['description']).to eq('deployed foobar!')
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
describe "#delete_metrics" do
|
|
47
|
+
before(:each) { delete_all_metrics }
|
|
48
|
+
|
|
49
|
+
context 'with names' do
|
|
50
|
+
|
|
51
|
+
context "with a single argument" do
|
|
52
|
+
it "deletes named metric" do
|
|
53
|
+
Metrics.submit foo: 123
|
|
54
|
+
expect(Metrics.metrics(name: :foo)).not_to be_empty
|
|
55
|
+
Metrics.delete_metrics :foo
|
|
56
|
+
expect(Metrics.metrics(name: :foo)).to be_empty
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
context "with multiple arguments" do
|
|
61
|
+
it "deletes named metrics" do
|
|
62
|
+
Metrics.submit foo: 123, bar: 345, baz: 567
|
|
63
|
+
Metrics.delete_metrics :foo, :bar
|
|
64
|
+
expect(Metrics.metrics(name: :foo)).to be_empty
|
|
65
|
+
expect(Metrics.metrics(name: :bar)).to be_empty
|
|
66
|
+
expect(Metrics.metrics(name: :baz)).not_to be_empty
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
context "with missing metric" do
|
|
71
|
+
it "runs cleanly" do
|
|
72
|
+
# the API currently returns success even if
|
|
73
|
+
# the metric has already been deleted or is absent.
|
|
74
|
+
Metrics.delete_metrics :missing
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
context "with no arguments" do
|
|
79
|
+
it "does not make request" do
|
|
80
|
+
expect {
|
|
81
|
+
Metrics.delete_metrics
|
|
82
|
+
}.to raise_error(Metrics::NoMetricsProvided)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
context 'with patterns' do
|
|
89
|
+
it "filters properly" do
|
|
90
|
+
Metrics.submit foo: 1, foobar: 2, foobaz: 3, bar: 4
|
|
91
|
+
Metrics.delete_metrics names: 'fo*', exclude: ['foobar']
|
|
92
|
+
|
|
93
|
+
%w{foo foobaz}.each do |name|
|
|
94
|
+
expect {
|
|
95
|
+
Metrics.get_metric name
|
|
96
|
+
}.to raise_error(Appoptics::Metrics::NotFound)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
%w{foobar bar}.each do |name|
|
|
100
|
+
Metrics.get_metric name # stil exist
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
describe "#get_metric" do
|
|
107
|
+
before(:all) do
|
|
108
|
+
delete_all_metrics
|
|
109
|
+
Metrics.submit my_counter: {type: :counter, value: 0, measure_time: Time.now.to_i-60}
|
|
110
|
+
1.upto(2).each do |i|
|
|
111
|
+
measure_time = Time.now.to_i - (5+i)
|
|
112
|
+
opts = {measure_time: measure_time, type: :counter}
|
|
113
|
+
Metrics.submit my_counter: opts.merge(value: i)
|
|
114
|
+
Metrics.submit my_counter: opts.merge(source: 'baz', value: i+1)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
context "without arguments" do
|
|
119
|
+
it "gets metric attributes" do
|
|
120
|
+
metric = Metrics.get_metric :my_counter
|
|
121
|
+
expect(metric['name']).to eq('my_counter')
|
|
122
|
+
expect(metric['type']).to eq('counter')
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
context "with a start_time" do
|
|
127
|
+
it "returns entries since that time" do
|
|
128
|
+
# 1 hr ago
|
|
129
|
+
metric = Metrics.get_metric :my_counter, start_time: Time.now-3600
|
|
130
|
+
data = metric['measurements']
|
|
131
|
+
expect(data['unassigned'].length).to eq(3)
|
|
132
|
+
expect(data['baz'].length).to eq(2)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
context "with a count limit" do
|
|
137
|
+
it "returns that number of entries per source" do
|
|
138
|
+
metric = Metrics.get_metric :my_counter, count: 2
|
|
139
|
+
data = metric['measurements']
|
|
140
|
+
expect(data['unassigned'].length).to eq(2)
|
|
141
|
+
expect(data['baz'].length).to eq(2)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
context "with a source limit" do
|
|
146
|
+
it "only returns that source" do
|
|
147
|
+
metric = Metrics.get_metric :my_counter, source: 'baz', start_time: Time.now-3600
|
|
148
|
+
data = metric['measurements']
|
|
149
|
+
expect(data['baz'].length).to eq(2)
|
|
150
|
+
expect(data['unassigned']).to be_nil
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
describe "#metrics" do
|
|
157
|
+
before(:all) do
|
|
158
|
+
delete_all_metrics
|
|
159
|
+
Metrics.submit foo: 123, bar: 345, baz: 678, foo_2: 901
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
context "without arguments" do
|
|
163
|
+
it "lists all metrics" do
|
|
164
|
+
metric_names = Metrics.metrics.map { |metric| metric['name'] }
|
|
165
|
+
expect(metric_names.sort).to eq(%w{foo bar baz foo_2}.sort)
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
context "with a name argument" do
|
|
170
|
+
it "lists metrics that match" do
|
|
171
|
+
metric_names = Metrics.metrics(name: 'foo').map { |metric| metric['name'] }
|
|
172
|
+
expect(metric_names.sort).to eq(%w{foo foo_2}.sort)
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
describe "#submit" do
|
|
179
|
+
|
|
180
|
+
context "with a gauge" do
|
|
181
|
+
before(:all) do
|
|
182
|
+
delete_all_metrics
|
|
183
|
+
Metrics.submit foo: 123
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
it "creates the metrics" do
|
|
187
|
+
metric = Metrics.metrics[0]
|
|
188
|
+
expect(metric['name']).to eq('foo')
|
|
189
|
+
expect(metric['type']).to eq('gauge')
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
it "stores their data" do
|
|
193
|
+
data = Metrics.get_measurements :foo, count: 1
|
|
194
|
+
expect(data).not_to be_empty
|
|
195
|
+
data['unassigned'][0]['value'] == 123.0
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
context "with a counter" do
|
|
200
|
+
before(:all) do
|
|
201
|
+
delete_all_metrics
|
|
202
|
+
Metrics.submit bar: {type: :counter, source: 'baz', value: 456}
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
it "creates the metrics" do
|
|
206
|
+
metric = Metrics.metrics[0]
|
|
207
|
+
expect(metric['name']).to eq('bar')
|
|
208
|
+
expect(metric['type']).to eq('counter')
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
it "stores their data" do
|
|
212
|
+
data = Metrics.get_measurements :bar, count: 1
|
|
213
|
+
expect(data).not_to be_empty
|
|
214
|
+
data['baz'][0]['value'] == 456.0
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
it "does not retain errors" do
|
|
219
|
+
delete_all_metrics
|
|
220
|
+
Metrics.submit foo: {type: :counter, value: 12}
|
|
221
|
+
expect {
|
|
222
|
+
Metrics.submit foo: 15 # submitting as gauge
|
|
223
|
+
}.to raise_error(Appoptics::Metrics::ClientError)
|
|
224
|
+
expect {
|
|
225
|
+
Metrics.submit foo: {type: :counter, value: 17}
|
|
226
|
+
}.not_to raise_error
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
describe "#update_metric[s]" do
|
|
232
|
+
|
|
233
|
+
context 'with a single metric' do
|
|
234
|
+
context "with an existing metric" do
|
|
235
|
+
before do
|
|
236
|
+
delete_all_metrics
|
|
237
|
+
Metrics.submit foo: 123
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
it "updates the metric" do
|
|
241
|
+
Metrics.update_metric :foo, display_name: "Foo Metric",
|
|
242
|
+
period: 15,
|
|
243
|
+
attributes: {
|
|
244
|
+
display_max: 1000
|
|
245
|
+
}
|
|
246
|
+
foo = Metrics.get_metric :foo
|
|
247
|
+
expect(foo['display_name']).to eq('Foo Metric')
|
|
248
|
+
expect(foo['period']).to eq(15)
|
|
249
|
+
expect(foo['attributes']['display_max']).to eq(1000)
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
context "without an existing metric" do
|
|
254
|
+
it "creates the metric if type specified" do
|
|
255
|
+
delete_all_metrics
|
|
256
|
+
Metrics.update_metric :foo, display_name: "Foo Metric",
|
|
257
|
+
type: 'gauge',
|
|
258
|
+
period: 15,
|
|
259
|
+
attributes: {
|
|
260
|
+
display_max: 1000
|
|
261
|
+
}
|
|
262
|
+
foo = Metrics.get_metric :foo
|
|
263
|
+
expect(foo['display_name']).to eq('Foo Metric')
|
|
264
|
+
expect(foo['period']).to eq(15)
|
|
265
|
+
expect(foo['attributes']['display_max']).to eq(1000)
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
it "raises error if no type specified" do
|
|
269
|
+
delete_all_metrics
|
|
270
|
+
expect {
|
|
271
|
+
Metrics.update_metric :foo, display_name: "Foo Metric",
|
|
272
|
+
period: 15,
|
|
273
|
+
attributes: {
|
|
274
|
+
display_max: 1000
|
|
275
|
+
}
|
|
276
|
+
}.to raise_error(Appoptics::Metrics::ClientError)
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
context 'with multiple metrics' do
|
|
283
|
+
before do
|
|
284
|
+
delete_all_metrics
|
|
285
|
+
Metrics.submit 'my.1' => 1, 'my.2' => 2, 'my.3' => 3, 'my.4' => 4
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
it "supports named list" do
|
|
289
|
+
names = ['my.1', 'my.3']
|
|
290
|
+
Metrics.update_metrics names: names, period: 60
|
|
291
|
+
|
|
292
|
+
names.each do |name|
|
|
293
|
+
metric = Metrics.get_metric name
|
|
294
|
+
expect(metric['period']).to eq(60)
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
it "supports patterns" do
|
|
299
|
+
Metrics.update_metrics names: 'my.*', exclude: ['my.3'],
|
|
300
|
+
display_max: 100
|
|
301
|
+
|
|
302
|
+
%w{my.1 my.2 my.4}.each do |name|
|
|
303
|
+
metric = Metrics.get_metric name
|
|
304
|
+
expect(metric['attributes']['display_max']).to eq(100)
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
excluded = Metrics.get_metric 'my.3'
|
|
308
|
+
expect(excluded['attributes']['display_max']).not_to eq(100)
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
describe "Sources API" do
|
|
314
|
+
before do
|
|
315
|
+
Metrics.update_source("sources_api_test", display_name: "Sources Api Test")
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
describe "#sources" do
|
|
319
|
+
it "works" do
|
|
320
|
+
sources = Metrics.sources
|
|
321
|
+
expect(sources).to be_an(Array)
|
|
322
|
+
test_source = sources.detect { |s| s["name"] == "sources_api_test" }
|
|
323
|
+
expect(test_source["display_name"]).to eq("Sources Api Test")
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
it "allows filtering by name" do
|
|
327
|
+
sources = Metrics.sources name: 'sources_api_test'
|
|
328
|
+
expect(sources.all? {|s| s['name'] =~ /sources_api_test/}).to be_truthy
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
describe "#get_source" do
|
|
333
|
+
it "works" do
|
|
334
|
+
test_source = Metrics.get_source("sources_api_test")
|
|
335
|
+
expect(test_source["display_name"]).to eq("Sources Api Test")
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
describe "#update_source" do
|
|
340
|
+
it "updates an existing source" do
|
|
341
|
+
Metrics.update_source("sources_api_test", display_name: "Updated Source Name")
|
|
342
|
+
|
|
343
|
+
test_source = Metrics.get_source("sources_api_test")
|
|
344
|
+
expect(test_source["display_name"]).to eq("Updated Source Name")
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
it "creates new sources" do
|
|
348
|
+
source_name = "sources_api_test_#{Time.now.to_f}"
|
|
349
|
+
expect {
|
|
350
|
+
no_source = Metrics.get_source(source_name)
|
|
351
|
+
}.to raise_error(Appoptics::Metrics::NotFound)
|
|
352
|
+
|
|
353
|
+
Metrics.update_source(source_name, display_name: "New Source")
|
|
354
|
+
|
|
355
|
+
test_source = Metrics.get_source(source_name)
|
|
356
|
+
expect(test_source).not_to be_nil
|
|
357
|
+
expect(test_source["display_name"]).to eq("New Source")
|
|
358
|
+
end
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
describe "#get_series" do
|
|
364
|
+
before { Metrics.submit test_series: { value: 123, tags: { hostname: "metrics-web-stg-1" } } }
|
|
365
|
+
|
|
366
|
+
it "gets series" do
|
|
367
|
+
series = Metrics.get_series :test_series, resolution: 1, duration: 3600
|
|
368
|
+
|
|
369
|
+
expect(series[0]["tags"]["hostname"]).to eq("metrics-web-stg-1")
|
|
370
|
+
expect(series[0]["measurements"][0]["value"]).to eq(123)
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
end
|
|
375
|
+
end
|