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
data/Rakefile
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env rake
|
|
2
|
+
begin
|
|
3
|
+
require 'bundler/setup'
|
|
4
|
+
rescue LoadError
|
|
5
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
# Packaging
|
|
9
|
+
Bundler::GemHelper.install_tasks
|
|
10
|
+
|
|
11
|
+
# Gem signing
|
|
12
|
+
task 'before_build' do
|
|
13
|
+
signing_key = File.expand_path("~/.gem/appoptics-private_key.pem")
|
|
14
|
+
if signing_key
|
|
15
|
+
puts "Key found: signing gem..."
|
|
16
|
+
ENV['GEM_SIGNING_KEY'] = signing_key
|
|
17
|
+
else
|
|
18
|
+
puts "WARN: signing key not found, gem not signed"
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
task :build => :before_build
|
|
22
|
+
|
|
23
|
+
# Testing
|
|
24
|
+
require 'rspec/core/rake_task'
|
|
25
|
+
|
|
26
|
+
desc "Run all tests"
|
|
27
|
+
task :spec do
|
|
28
|
+
Rake::Task['spec:unit'].execute
|
|
29
|
+
if ENV['TEST_API_USER'] && ENV['TEST_API_KEY']
|
|
30
|
+
Rake::Task['spec:integration'].execute
|
|
31
|
+
else
|
|
32
|
+
puts "TEST_API_USER and TEST_API_KEY not in environment, skipping integration tests..."
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
namespace :spec do
|
|
37
|
+
RSpec::Core::RakeTask.new(:unit) do |t|
|
|
38
|
+
t.rspec_opts = '--color'
|
|
39
|
+
t.pattern = 'spec/unit/**/*_spec.rb'
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
RSpec::Core::RakeTask.new(:integration) do |t|
|
|
43
|
+
t.rspec_opts = '--color'
|
|
44
|
+
t.pattern = 'spec/integration/**/*_spec.rb'
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
task default: :spec
|
|
49
|
+
task test: :spec
|
|
50
|
+
|
|
51
|
+
# Docs
|
|
52
|
+
require 'yard'
|
|
53
|
+
YARD::Rake::YardocTask.new
|
|
54
|
+
|
|
55
|
+
# IRB
|
|
56
|
+
desc "Open an irb session preloaded with this library"
|
|
57
|
+
task :console do
|
|
58
|
+
if !`which pry`.empty?
|
|
59
|
+
sh "pry -r ./lib/appoptics/metrics.rb"
|
|
60
|
+
else
|
|
61
|
+
sh "irb -rubygems -r ./lib/appoptics/metrics.rb"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
lib = File.expand_path('../lib/', __FILE__)
|
|
2
|
+
$:.unshift lib unless $:.include?(lib)
|
|
3
|
+
|
|
4
|
+
require 'appoptics/metrics/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |s|
|
|
7
|
+
s.name = 'appoptics-api-ruby'
|
|
8
|
+
s.version = '2.1.3'
|
|
9
|
+
s.date = '2017-10-13'
|
|
10
|
+
s.summary = "Ruby bindings for the AppOptics API"
|
|
11
|
+
s.description = "An easy to use ruby wrapper for the AppOptics API"
|
|
12
|
+
s.authors = ["Greg McKeever", "Matt Sanders"]
|
|
13
|
+
s.email = 'greg@solarwinds.cloud'
|
|
14
|
+
s.files = ["lib/appoptics/metrics.rb"]
|
|
15
|
+
s.homepage =
|
|
16
|
+
'https://github.com/AppOptics/appoptics-api-ruby'
|
|
17
|
+
s.license = 'BSD 3-clause'
|
|
18
|
+
s.require_paths = %w[lib]
|
|
19
|
+
|
|
20
|
+
## runtime dependencies
|
|
21
|
+
s.add_dependency 'faraday'
|
|
22
|
+
s.add_dependency 'aggregate', '~> 0.2.2'
|
|
23
|
+
|
|
24
|
+
s.files = `git ls-files`.split("\n")
|
|
25
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
|
26
|
+
|
|
27
|
+
s.cert_chain = ["certs/appoptics-public.pem"]
|
|
28
|
+
if ENV['GEM_SIGNING_KEY']
|
|
29
|
+
s.signing_key = ENV['GEM_SIGNING_KEY']
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'tach'
|
|
3
|
+
require 'set'
|
|
4
|
+
|
|
5
|
+
# Compare speed of simple addition and iteration operations.
|
|
6
|
+
#
|
|
7
|
+
Tach.meter(1000) do
|
|
8
|
+
|
|
9
|
+
tach('array') do
|
|
10
|
+
a = []
|
|
11
|
+
(1..1000).each do |i|
|
|
12
|
+
a << i
|
|
13
|
+
end
|
|
14
|
+
a.each do |i|
|
|
15
|
+
"this is a string with #{i}"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
tach('set') do
|
|
20
|
+
s = Set.new
|
|
21
|
+
(1...1000).each do |i|
|
|
22
|
+
s << i
|
|
23
|
+
end
|
|
24
|
+
s.each do |i|
|
|
25
|
+
"this is a string with #{i}"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
|
2
|
+
MIIDLjCCAhagAwIBAgIBADANBgkqhkiG9w0BAQUFADA9MQ0wCwYDVQQDDARydWJ5
|
|
3
|
+
MRcwFQYKCZImiZPyLGQBGRYHbGlicmF0bzETMBEGCgmSJomT8ixkARkWA2NvbTAe
|
|
4
|
+
Fw0xNzAxMTExODI3MDdaFw0xODAxMTExODI3MDdaMD0xDTALBgNVBAMMBHJ1Ynkx
|
|
5
|
+
FzAVBgoJkiaJk/IsZAEZFgdsaWJyYXRvMRMwEQYKCZImiZPyLGQBGRYDY29tMIIB
|
|
6
|
+
IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA58LirwsWnKL1uuClQ0uwA1XL
|
|
7
|
+
GpxDuFzSosipiPkzZY3hiHazC8SHDREZQXlm7ITX/o+dueNoB7dt0BR3RPVipH7V
|
|
8
|
+
7cvbCUaZNjEXR5Lal6PsmUsbMTrddkvj3e7flmJv+kMj+teeJ7MDeJTU5wXXV9pD
|
|
9
|
+
ThiCDILJMF5CdP8Jru4rSBOP6RmmzYU+0cN5+5pt9xqrycA+Poo2ZuoUMCMsSBvl
|
|
10
|
+
PimM3PPvoWTuE41GTn/bLoOVoXQmdwZIbwUSVH8rCDjZSlttOst+xrBw4KG0dYUp
|
|
11
|
+
2UvEe8iCyqEMQ8fEZ7EXuP2WMVICutFbz8Pt4hIMq+OTnDX+mIfma7GvPaKAFwID
|
|
12
|
+
AQABozkwNzAJBgNVHRMEAjAAMB0GA1UdDgQWBBQBSxu9Jj4VTrXTpLujPwk9Kzwp
|
|
13
|
+
2jALBgNVHQ8EBAMCBLAwDQYJKoZIhvcNAQEFBQADggEBACvNsw1pGv72xp3LiDlZ
|
|
14
|
+
0tphNP/85RcYyJMklslG3tIYblyo71xHW1UbK5ArUK6k0BN43MuDn3pqLJQttVmi
|
|
15
|
+
bUdA3yYi13GeSrrAMlr4nH8Yt/Bn/XpZGliouJUBwh1BjG6dSj1iuR4/9pt9/LtO
|
|
16
|
+
QTdIc+07qGspypT0Uh/w/BodEcGuAaZZFlkU9vottTe6wWNnM6hfRExiSIsr+oVe
|
|
17
|
+
s8s83ObshjuSzjOqS56IBtNlPEL+D6ghjZZLP3lS6l9p70Pcpcl+IcE4veqZmmKC
|
|
18
|
+
sGepgRclC6UbZh+yQ3alXVghM2qsonAwI/rTNmFJN9kQn6nP9+f1Uf/qZFNcjn4L
|
|
19
|
+
9bg=
|
|
20
|
+
-----END CERTIFICATE-----
|
data/examples/simple.rb
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
require 'appoptics-api-ruby'
|
|
2
|
+
|
|
3
|
+
Appoptics::Metrics.authenticate 'my email', 'my api key'
|
|
4
|
+
|
|
5
|
+
# send a measurement of 12 for 'foo'
|
|
6
|
+
Appoptics::Metrics.submit cpu: 54
|
|
7
|
+
|
|
8
|
+
# submit multiple metrics at once
|
|
9
|
+
Appoptics::Metrics.submit cpu: 63, memory: 213
|
|
10
|
+
|
|
11
|
+
# submit a metric with a custom source
|
|
12
|
+
Appoptics::Metrics.submit cpu: {source: 'myapp', value: 75}
|
|
13
|
+
|
|
14
|
+
# if you are sending many metrics it is much more performant
|
|
15
|
+
# to submit them in sets rather than individually:
|
|
16
|
+
|
|
17
|
+
queue = Appoptics::Metrics::Queue.new
|
|
18
|
+
|
|
19
|
+
queue.add 'disk.free' => 1223121
|
|
20
|
+
queue.add memory: 2321
|
|
21
|
+
queue.add cpu: {source: 'myapp', value: 52}
|
|
22
|
+
#...
|
|
23
|
+
|
|
24
|
+
queue.submit
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# send a set of metrics every 60 seconds
|
|
2
|
+
|
|
3
|
+
require 'appoptics-api-ruby'
|
|
4
|
+
|
|
5
|
+
Appoptics::Metrics.authenticate 'my email', 'my api key'
|
|
6
|
+
queue = Appoptics::Metrics::Queue.new
|
|
7
|
+
|
|
8
|
+
def sleep_until(time)
|
|
9
|
+
secs = time - Time.now
|
|
10
|
+
puts "sleeping for #{secs}"
|
|
11
|
+
sleep secs
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
loop do
|
|
15
|
+
start = Time.now
|
|
16
|
+
|
|
17
|
+
queue.add 'my.metric' => 1234
|
|
18
|
+
# do work, add more metrics..
|
|
19
|
+
|
|
20
|
+
begin
|
|
21
|
+
queue.submit
|
|
22
|
+
rescue Exception => e
|
|
23
|
+
$stderr.puts e
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
sleep_until(start+60)
|
|
27
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
|
2
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
|
3
|
+
|
|
4
|
+
require 'base64'
|
|
5
|
+
require 'forwardable'
|
|
6
|
+
|
|
7
|
+
require 'metrics/aggregator'
|
|
8
|
+
require 'metrics/annotator'
|
|
9
|
+
require 'metrics/client'
|
|
10
|
+
require 'metrics/collection'
|
|
11
|
+
require 'metrics/connection'
|
|
12
|
+
require 'metrics/errors'
|
|
13
|
+
require 'metrics/persistence'
|
|
14
|
+
require 'metrics/queue'
|
|
15
|
+
require 'metrics/smart_json'
|
|
16
|
+
require 'metrics/util'
|
|
17
|
+
require 'metrics/version'
|
|
18
|
+
|
|
19
|
+
module Appoptics
|
|
20
|
+
|
|
21
|
+
# Metrics provides a simple wrapper for the Metrics web API with a
|
|
22
|
+
# number of added conveniences for common use cases.
|
|
23
|
+
#
|
|
24
|
+
# See the {file:README.md README} for more information and examples.
|
|
25
|
+
#
|
|
26
|
+
# @example Simple use case
|
|
27
|
+
# Appoptics::Metrics.authenticate 'email', 'api_key'
|
|
28
|
+
#
|
|
29
|
+
# # list current metrics
|
|
30
|
+
# Appoptics::Metrics.metrics
|
|
31
|
+
#
|
|
32
|
+
# # submit a metric immediately
|
|
33
|
+
# Appoptics::Metrics.submit foo: 12712
|
|
34
|
+
#
|
|
35
|
+
# # fetch the last 10 values of foo
|
|
36
|
+
# Appoptics::Metrics.get_measurements :foo, count: 10
|
|
37
|
+
#
|
|
38
|
+
# @example Queuing metrics for submission
|
|
39
|
+
# queue = Appoptics::Metrics::Queue.new
|
|
40
|
+
#
|
|
41
|
+
# # queue some metrics
|
|
42
|
+
# queue.add foo: 12312
|
|
43
|
+
# queue.add bar: 45678
|
|
44
|
+
#
|
|
45
|
+
# # send the metrics
|
|
46
|
+
# queue.submit
|
|
47
|
+
#
|
|
48
|
+
# @example Using a Client object
|
|
49
|
+
# client = Appoptics::Metrics::Client.new
|
|
50
|
+
# client.authenticate 'email', 'api_key'
|
|
51
|
+
#
|
|
52
|
+
# # list client's metrics
|
|
53
|
+
# client.metrics
|
|
54
|
+
#
|
|
55
|
+
# # create an associated queue
|
|
56
|
+
# queue = client.new_queue
|
|
57
|
+
#
|
|
58
|
+
# # queue up some metrics and submit
|
|
59
|
+
# queue.add foo: 12345
|
|
60
|
+
# queue.add bar: 45678
|
|
61
|
+
# queue.submit
|
|
62
|
+
#
|
|
63
|
+
# @note Most of the methods you can call directly on Appoptics::Metrics are
|
|
64
|
+
# delegated to {Client} and are documented there.
|
|
65
|
+
module Metrics
|
|
66
|
+
extend SingleForwardable
|
|
67
|
+
|
|
68
|
+
TYPES = [:counter, :gauge]
|
|
69
|
+
PLURAL_TYPES = TYPES.map { |type| "#{type}s".to_sym }
|
|
70
|
+
MIN_MEASURE_TIME = (Time.now-(3600*24*365)).to_i
|
|
71
|
+
|
|
72
|
+
# Most of the singleton methods of Appoptics::Metrics are actually
|
|
73
|
+
# being called on a global Client instance. See further docs on
|
|
74
|
+
# Client.
|
|
75
|
+
#
|
|
76
|
+
def_delegators :client, :agent_identifier, :annotate,
|
|
77
|
+
:api_endpoint, :api_endpoint=, :authenticate,
|
|
78
|
+
:connection, :create_snapshot, :delete_metrics,
|
|
79
|
+
:faraday_adapter, :faraday_adapter=, :get_composite,
|
|
80
|
+
:get_measurements, :get_metric, :get_series,
|
|
81
|
+
:get_snapshot, :get_source, :metrics,
|
|
82
|
+
:persistence, :persistence=, :persister, :proxy, :proxy=,
|
|
83
|
+
:sources, :submit, :update_metric, :update_metrics,
|
|
84
|
+
:update_source
|
|
85
|
+
|
|
86
|
+
# The Appoptics::Metrics::Client being used by module-level
|
|
87
|
+
# access.
|
|
88
|
+
#
|
|
89
|
+
# @return [Client]
|
|
90
|
+
def self.client
|
|
91
|
+
@client ||= Appoptics::Metrics::Client.new
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
require 'aggregate'
|
|
2
|
+
require 'metrics/processor'
|
|
3
|
+
|
|
4
|
+
module Appoptics
|
|
5
|
+
module Metrics
|
|
6
|
+
|
|
7
|
+
# If you are measuring something very frequently you can sample into
|
|
8
|
+
# an aggregator and it will track and submit a single aggregate
|
|
9
|
+
# measurement
|
|
10
|
+
#
|
|
11
|
+
# @example
|
|
12
|
+
# aggregator = Libato::Metrics::Aggregator.new
|
|
13
|
+
#
|
|
14
|
+
# 40.times do
|
|
15
|
+
# # do work...
|
|
16
|
+
# aggregator.add 'work.time' => work_time
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# # send directly
|
|
20
|
+
# aggregator.submit
|
|
21
|
+
#
|
|
22
|
+
# # or merge into a queue for submission
|
|
23
|
+
# queue.merge!(aggregator)
|
|
24
|
+
#
|
|
25
|
+
class Aggregator
|
|
26
|
+
SEPARATOR = '%%' # must not be in valid tags and/or source criteria
|
|
27
|
+
|
|
28
|
+
include Processor
|
|
29
|
+
|
|
30
|
+
attr_reader :source
|
|
31
|
+
|
|
32
|
+
# @option opts [Integer] :autosubmit_interval If set the aggregator will auto-submit if the given number of seconds has passed when a new metric is added.
|
|
33
|
+
# @option opts [Boolean] :clear_failures Should the aggregator remove all stored data if it runs into problems with a request? (default: false)
|
|
34
|
+
# @option opts [Client] :client The client object to use to connect to Metrics. (default: Appoptics::Metrics.client)
|
|
35
|
+
# @option opts [Time|Integer] :measure_time A default measure_time to use for measurements added.
|
|
36
|
+
# @option opts [String] :prefix If set will apply the given prefix to all metric names of measurements added.
|
|
37
|
+
# @option opts [String] :source The default source to use for measurements added.
|
|
38
|
+
def initialize(opts={})
|
|
39
|
+
@aggregated = {}
|
|
40
|
+
setup_common_options(opts)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Add a metric entry to the metric set:
|
|
44
|
+
#
|
|
45
|
+
# @example Basic use
|
|
46
|
+
# aggregator.add 'request.time' => 30.24
|
|
47
|
+
#
|
|
48
|
+
# @example With a custom source
|
|
49
|
+
# aggregator.add 'request.time' => {value: 20.52, source: 'staging'}
|
|
50
|
+
#
|
|
51
|
+
# @param [Hash] measurements measurements to add
|
|
52
|
+
# @return [Aggregator] returns self
|
|
53
|
+
def add(measurements)
|
|
54
|
+
measurements.each do |metric, data|
|
|
55
|
+
entry = {}
|
|
56
|
+
if @prefix
|
|
57
|
+
metric = "#{@prefix}.#{metric}"
|
|
58
|
+
end
|
|
59
|
+
entry[:name] = metric.to_s
|
|
60
|
+
if data.respond_to?(:each) # hash form
|
|
61
|
+
validate_parameters(data)
|
|
62
|
+
value = data[:value]
|
|
63
|
+
if data[:source]
|
|
64
|
+
metric = "#{metric}#{SEPARATOR}#{data[:source]}"
|
|
65
|
+
entry[:source] = data[:source].to_s
|
|
66
|
+
elsif data[:tags] && data[:tags].respond_to?(:each)
|
|
67
|
+
metric = Appoptics::Metrics::Util.build_key_for(metric.to_s, data[:tags])
|
|
68
|
+
entry[:tags] = data[:tags]
|
|
69
|
+
end
|
|
70
|
+
else
|
|
71
|
+
value = data
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
@aggregated[metric] = {} unless @aggregated[metric]
|
|
75
|
+
@aggregated[metric][:aggregate] ||= Aggregate.new
|
|
76
|
+
@aggregated[metric][:aggregate] << value
|
|
77
|
+
@aggregated[metric].merge!(entry)
|
|
78
|
+
end
|
|
79
|
+
autosubmit_check
|
|
80
|
+
self
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Returns true if aggregate contains no measurements
|
|
84
|
+
#
|
|
85
|
+
# @return [Boolean]
|
|
86
|
+
def empty?
|
|
87
|
+
@aggregated.empty?
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Remove all queued metrics
|
|
91
|
+
#
|
|
92
|
+
def clear
|
|
93
|
+
@aggregated = {}
|
|
94
|
+
end
|
|
95
|
+
alias :flush :clear
|
|
96
|
+
|
|
97
|
+
# Returns currently queued data
|
|
98
|
+
#
|
|
99
|
+
def queued
|
|
100
|
+
entries = []
|
|
101
|
+
multidimensional = has_tags?
|
|
102
|
+
|
|
103
|
+
@aggregated.each_value do |data|
|
|
104
|
+
entry = {
|
|
105
|
+
name: data[:name],
|
|
106
|
+
count: data[:aggregate].count,
|
|
107
|
+
sum: data[:aggregate].sum,
|
|
108
|
+
# TODO: make float/non-float consistent in the gem
|
|
109
|
+
min: data[:aggregate].min.to_f,
|
|
110
|
+
max: data[:aggregate].max.to_f
|
|
111
|
+
# TODO: expose v.sum2 and include
|
|
112
|
+
}
|
|
113
|
+
if data[:source]
|
|
114
|
+
entry[:source] = data[:source]
|
|
115
|
+
elsif data[:tags]
|
|
116
|
+
multidimensional = true
|
|
117
|
+
entry[:tags] = data[:tags]
|
|
118
|
+
end
|
|
119
|
+
multidimensional = true if data[:time]
|
|
120
|
+
entries << entry
|
|
121
|
+
end
|
|
122
|
+
time = multidimensional ? :time : :measure_time
|
|
123
|
+
req =
|
|
124
|
+
if multidimensional
|
|
125
|
+
{ measurements: entries }
|
|
126
|
+
else
|
|
127
|
+
{ gauges: entries }
|
|
128
|
+
end
|
|
129
|
+
req[:source] = @source if @source
|
|
130
|
+
req[:tags] = @tags if has_tags?
|
|
131
|
+
req[time] = @time if @time
|
|
132
|
+
|
|
133
|
+
req
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
module Appoptics::Metrics
|
|
2
|
+
|
|
3
|
+
# Read & write annotation streams for a given client connection.
|
|
4
|
+
class Annotator
|
|
5
|
+
|
|
6
|
+
# @option options [Client] :client Client instance used to connect to Metrics
|
|
7
|
+
def initialize(options={})
|
|
8
|
+
@client = options[:client] || Appoptics::Metrics.client
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Creates a new annotation on the annotation stream
|
|
12
|
+
#
|
|
13
|
+
# @example Simple annotation
|
|
14
|
+
# annotator.add :deployments, 'deployed v45'
|
|
15
|
+
#
|
|
16
|
+
# @example Annotation with start and end times
|
|
17
|
+
# annotator.add :deployments, 'deployed v56', start_time: start,
|
|
18
|
+
# end_time: end_time
|
|
19
|
+
#
|
|
20
|
+
# @example Annotation with a specific source
|
|
21
|
+
# annotator.add :deployments, 'deployed v60', source: 'app12'
|
|
22
|
+
#
|
|
23
|
+
# @example Annotation with a description
|
|
24
|
+
# annotator.add :deployments, 'deployed v61',
|
|
25
|
+
# description: '9b562b2: shipped new feature foo!'
|
|
26
|
+
#
|
|
27
|
+
# @example Annotate with automatic start and end times
|
|
28
|
+
# annotator.add(:deployments, 'deployed v62') do
|
|
29
|
+
# # do work..
|
|
30
|
+
# end
|
|
31
|
+
#
|
|
32
|
+
def add(stream, title, options={})
|
|
33
|
+
options[:title] = title
|
|
34
|
+
options[:start_time] = (options[:start_time] || Time.now).to_i
|
|
35
|
+
if options[:end_time]
|
|
36
|
+
options[:end_time] = options[:end_time].to_i
|
|
37
|
+
end
|
|
38
|
+
payload = SmartJSON.write(options)
|
|
39
|
+
response = connection.post("annotations/#{stream}", payload)
|
|
40
|
+
# will raise exception if not 200 OK
|
|
41
|
+
event = SmartJSON.read(response.body)
|
|
42
|
+
if block_given?
|
|
43
|
+
yield
|
|
44
|
+
update_event stream, event['id'], end_time: Time.now.to_i
|
|
45
|
+
# need to get updated representation
|
|
46
|
+
event = fetch_event stream, event['id']
|
|
47
|
+
end
|
|
48
|
+
event
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# client instance used by this object
|
|
52
|
+
def client
|
|
53
|
+
@client
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Delete an annotation stream
|
|
57
|
+
#
|
|
58
|
+
# @example Delete the 'deployment' annotation stream
|
|
59
|
+
# annotator.delete :deployment
|
|
60
|
+
#
|
|
61
|
+
def delete(stream)
|
|
62
|
+
connection.delete do |request|
|
|
63
|
+
request.url connection.build_url("annotations/#{stream}")
|
|
64
|
+
end
|
|
65
|
+
# expects 204, middleware will raise exception otherwise
|
|
66
|
+
true
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Delete an event from a given annotation stream
|
|
70
|
+
#
|
|
71
|
+
# @example Delete event with id 42 from 'deployment'
|
|
72
|
+
# annotator.delete_event :deployment, 42
|
|
73
|
+
#
|
|
74
|
+
def delete_event(stream, id)
|
|
75
|
+
connection.delete do |request|
|
|
76
|
+
request.url connection.build_url("annotations/#{stream}/#{id}")
|
|
77
|
+
end
|
|
78
|
+
# expects 204, middleware will raise exception otherwise
|
|
79
|
+
true
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Get a list of annotation events on a given annotation stream
|
|
83
|
+
#
|
|
84
|
+
# @example See properties of the 'deployments' annotation stream
|
|
85
|
+
# annotator.fetch :deployments
|
|
86
|
+
#
|
|
87
|
+
# @example Get events on 'deployments' between start and end times
|
|
88
|
+
# annotator.fetch :deployments, start_time: start,
|
|
89
|
+
# end_time: end_time
|
|
90
|
+
#
|
|
91
|
+
# @example Source-limited listing
|
|
92
|
+
# annotator.fetch :deployments, sources: ['foo','bar','baz'],
|
|
93
|
+
# start_time: start, end_time: end_time
|
|
94
|
+
#
|
|
95
|
+
def fetch(stream, options={})
|
|
96
|
+
response = connection.get("annotations/#{stream}", options)
|
|
97
|
+
SmartJSON.read(response.body)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Get properties for a given annotation stream event
|
|
101
|
+
#
|
|
102
|
+
# @example Get event
|
|
103
|
+
# annotator.fetch :deployments, 23
|
|
104
|
+
#
|
|
105
|
+
def fetch_event(stream, id)
|
|
106
|
+
response = connection.get("annotations/#{stream}/#{id}")
|
|
107
|
+
SmartJSON.read(response.body)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# List currently existing annotation streams
|
|
111
|
+
#
|
|
112
|
+
# @example List all annotation streams
|
|
113
|
+
# streams = annotator.list
|
|
114
|
+
#
|
|
115
|
+
# @example List annotator streams with 'deploy' in the name
|
|
116
|
+
# deploy_streams = annotator.list name: 'deploy'
|
|
117
|
+
#
|
|
118
|
+
def list(options={})
|
|
119
|
+
response = connection.get("annotations", options)
|
|
120
|
+
SmartJSON.read(response.body)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Update an event's properties
|
|
124
|
+
#
|
|
125
|
+
# @example Set an end time for a previously submitted event
|
|
126
|
+
# annotator.update_event 'deploys', 'v24', end_time: end_time
|
|
127
|
+
#
|
|
128
|
+
def update_event(stream, id, options={})
|
|
129
|
+
url = "annotations/#{stream}/#{id}"
|
|
130
|
+
connection.put do |request|
|
|
131
|
+
request.url connection.build_url(url)
|
|
132
|
+
request.body = SmartJSON.write(options)
|
|
133
|
+
end
|
|
134
|
+
# expects 204 will raise exception otherwise
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
private
|
|
138
|
+
|
|
139
|
+
def connection
|
|
140
|
+
client.connection
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
end
|