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.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +23 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +25 -0
  5. data/CHANGELOG.md +184 -0
  6. data/Gemfile +36 -0
  7. data/LICENSE +24 -0
  8. data/README.md +271 -0
  9. data/Rakefile +63 -0
  10. data/appoptics-api-ruby.gemspec +31 -0
  11. data/benchmarks/array_vs_set.rb +29 -0
  12. data/certs/librato-public.pem +20 -0
  13. data/examples/simple.rb +24 -0
  14. data/examples/submit_every.rb +27 -0
  15. data/lib/appoptics/metrics.rb +95 -0
  16. data/lib/appoptics/metrics/aggregator.rb +138 -0
  17. data/lib/appoptics/metrics/annotator.rb +145 -0
  18. data/lib/appoptics/metrics/client.rb +361 -0
  19. data/lib/appoptics/metrics/collection.rb +43 -0
  20. data/lib/appoptics/metrics/connection.rb +101 -0
  21. data/lib/appoptics/metrics/errors.rb +32 -0
  22. data/lib/appoptics/metrics/middleware/count_requests.rb +28 -0
  23. data/lib/appoptics/metrics/middleware/expects_status.rb +38 -0
  24. data/lib/appoptics/metrics/middleware/request_body.rb +18 -0
  25. data/lib/appoptics/metrics/middleware/retry.rb +31 -0
  26. data/lib/appoptics/metrics/persistence.rb +2 -0
  27. data/lib/appoptics/metrics/persistence/direct.rb +73 -0
  28. data/lib/appoptics/metrics/persistence/test.rb +27 -0
  29. data/lib/appoptics/metrics/processor.rb +130 -0
  30. data/lib/appoptics/metrics/queue.rb +191 -0
  31. data/lib/appoptics/metrics/smart_json.rb +43 -0
  32. data/lib/appoptics/metrics/util.rb +25 -0
  33. data/lib/appoptics/metrics/version.rb +5 -0
  34. data/spec/integration/metrics/annotator_spec.rb +190 -0
  35. data/spec/integration/metrics/connection_spec.rb +14 -0
  36. data/spec/integration/metrics/middleware/count_requests_spec.rb +28 -0
  37. data/spec/integration/metrics/queue_spec.rb +96 -0
  38. data/spec/integration/metrics_spec.rb +375 -0
  39. data/spec/rackups/status.ru +30 -0
  40. data/spec/spec_helper.rb +88 -0
  41. data/spec/unit/metrics/aggregator_spec.rb +417 -0
  42. data/spec/unit/metrics/client_spec.rb +127 -0
  43. data/spec/unit/metrics/connection_spec.rb +113 -0
  44. data/spec/unit/metrics/queue/autosubmission_spec.rb +57 -0
  45. data/spec/unit/metrics/queue_spec.rb +593 -0
  46. data/spec/unit/metrics/smart_json_spec.rb +79 -0
  47. data/spec/unit/metrics/util_spec.rb +23 -0
  48. data/spec/unit/metrics_spec.rb +63 -0
  49. 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,5 @@
1
+ module Appoptics
2
+ module Metrics
3
+ VERSION = "2.1.3"
4
+ end
5
+ 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,14 @@
1
+ require 'spec_helper'
2
+
3
+ module Appoptics
4
+ module Metrics
5
+
6
+ describe Connection do
7
+
8
+ # TODO: test retry support
9
+ # TODO: test status code support
10
+
11
+ end
12
+
13
+ end
14
+ 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