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,32 @@
|
|
|
1
|
+
|
|
2
|
+
module Appoptics
|
|
3
|
+
module Metrics
|
|
4
|
+
|
|
5
|
+
class MetricsError < StandardError; end
|
|
6
|
+
|
|
7
|
+
class CredentialsMissing < MetricsError; end
|
|
8
|
+
class NoMetricsProvided < MetricsError; end
|
|
9
|
+
class NoClientProvided < MetricsError; end
|
|
10
|
+
class InvalidMeasureTime < MetricsError; end
|
|
11
|
+
class NotMergeable < MetricsError; end
|
|
12
|
+
class InvalidParameters < MetricsError; end
|
|
13
|
+
|
|
14
|
+
class NetworkError < StandardError
|
|
15
|
+
attr_reader :response
|
|
16
|
+
|
|
17
|
+
def initialize(msg, response = nil)
|
|
18
|
+
super(msg)
|
|
19
|
+
@response = response
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class ClientError < NetworkError; end
|
|
24
|
+
class Unauthorized < ClientError; end
|
|
25
|
+
class Forbidden < ClientError; end
|
|
26
|
+
class NotFound < ClientError; end
|
|
27
|
+
class EntityAlreadyExists < ClientError; end
|
|
28
|
+
|
|
29
|
+
class ServerError < NetworkError; end
|
|
30
|
+
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module Appoptics
|
|
2
|
+
module Metrics
|
|
3
|
+
module Middleware
|
|
4
|
+
|
|
5
|
+
class CountRequests < Faraday::Response::Middleware
|
|
6
|
+
@total_requests = 0
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
attr_reader :total_requests
|
|
10
|
+
|
|
11
|
+
def increment
|
|
12
|
+
@total_requests += 1
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def reset
|
|
16
|
+
@total_requests = 0
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def call(env)
|
|
21
|
+
self.class.increment
|
|
22
|
+
@app.call(env)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module Appoptics
|
|
2
|
+
module Metrics
|
|
3
|
+
module Middleware
|
|
4
|
+
|
|
5
|
+
class ExpectsStatus < Faraday::Response::Middleware
|
|
6
|
+
|
|
7
|
+
def on_complete(env)
|
|
8
|
+
sanitized = sanitize_request(env)
|
|
9
|
+
case env[:status]
|
|
10
|
+
when 401
|
|
11
|
+
raise Unauthorized.new(sanitized.to_s, sanitized)
|
|
12
|
+
when 403
|
|
13
|
+
raise Forbidden.new(sanitized.to_s, sanitized)
|
|
14
|
+
when 404
|
|
15
|
+
raise NotFound.new(sanitized.to_s, sanitized)
|
|
16
|
+
when 422
|
|
17
|
+
raise EntityAlreadyExists.new(sanitized.to_s, sanitized)
|
|
18
|
+
when 400..499
|
|
19
|
+
raise ClientError.new(sanitized.to_s, sanitized)
|
|
20
|
+
when 500..599
|
|
21
|
+
raise ServerError.new(sanitized.to_s, sanitized)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def sanitize_request(env)
|
|
26
|
+
{
|
|
27
|
+
status: env[:status],
|
|
28
|
+
url: env[:url].to_s,
|
|
29
|
+
user_agent: (env[:request_headers] || {})["User-Agent"],
|
|
30
|
+
request_body: env[:request_body],
|
|
31
|
+
response_headers: env[:response_headers],
|
|
32
|
+
response_body: env[:body]
|
|
33
|
+
}
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Appoptics
|
|
2
|
+
module Metrics
|
|
3
|
+
module Middleware
|
|
4
|
+
|
|
5
|
+
class RequestBody < Faraday::Response::Middleware
|
|
6
|
+
|
|
7
|
+
def call(env)
|
|
8
|
+
# duplicate request body so it is preserved through request
|
|
9
|
+
# in case we need it for exception output
|
|
10
|
+
env[:request_body] = env[:body]
|
|
11
|
+
@app.call(env)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module Appoptics
|
|
2
|
+
module Metrics
|
|
3
|
+
module Middleware
|
|
4
|
+
|
|
5
|
+
class Retry < Faraday::Middleware
|
|
6
|
+
|
|
7
|
+
def initialize(app, retries = 3)
|
|
8
|
+
@retries = retries
|
|
9
|
+
super(app)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def call(env)
|
|
13
|
+
retries = @retries
|
|
14
|
+
request_body = env[:body]
|
|
15
|
+
begin
|
|
16
|
+
env[:body] = request_body # after failure is set to response body
|
|
17
|
+
@app.call(env)
|
|
18
|
+
rescue Appoptics::Metrics::ServerError, Timeout::Error,
|
|
19
|
+
Faraday::Error::ConnectionFailed
|
|
20
|
+
if retries > 0
|
|
21
|
+
retries -= 1 and retry
|
|
22
|
+
end
|
|
23
|
+
raise
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
|
|
2
|
+
# Manages direct persistence with the Appoptics API
|
|
3
|
+
|
|
4
|
+
module Appoptics
|
|
5
|
+
module Metrics
|
|
6
|
+
module Persistence
|
|
7
|
+
class Direct
|
|
8
|
+
# Persist the queued metrics directly to the
|
|
9
|
+
# Metrics web API.
|
|
10
|
+
#
|
|
11
|
+
def persist(client, queued, options={})
|
|
12
|
+
per_request = options[:per_request]
|
|
13
|
+
if per_request
|
|
14
|
+
requests = chunk_queued(queued, per_request)
|
|
15
|
+
else
|
|
16
|
+
requests = [queued]
|
|
17
|
+
end
|
|
18
|
+
requests.each do |request|
|
|
19
|
+
resource =
|
|
20
|
+
if queued[:gauges] || queued[:counters]
|
|
21
|
+
"metrics"
|
|
22
|
+
else
|
|
23
|
+
"measurements"
|
|
24
|
+
end
|
|
25
|
+
payload = SmartJSON.write(request)
|
|
26
|
+
# expects 200
|
|
27
|
+
client.connection.post(resource, payload)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def chunk_queued(queued, per_request)
|
|
34
|
+
return [queued] if queue_count(queued) <= per_request
|
|
35
|
+
reqs = []
|
|
36
|
+
# separate metric-containing values from global values
|
|
37
|
+
globals = fetch_globals(queued)
|
|
38
|
+
top_level_keys.each do |key|
|
|
39
|
+
metrics = queued[key]
|
|
40
|
+
next unless metrics
|
|
41
|
+
if metrics.size <= per_request
|
|
42
|
+
# we can fit all of this metric type in a single request
|
|
43
|
+
reqs << build_request(key, metrics, globals)
|
|
44
|
+
else
|
|
45
|
+
# going to have to split things up
|
|
46
|
+
metrics.each_slice(per_request) do |elements|
|
|
47
|
+
reqs << build_request(key, elements, globals)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
reqs
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def build_request(type, metrics, globals)
|
|
55
|
+
{type => metrics}.merge(globals)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def top_level_keys
|
|
59
|
+
[Appoptics::Metrics::PLURAL_TYPES, :measurements].flatten
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def fetch_globals(queued)
|
|
63
|
+
queued.reject { |k, v| top_level_keys.include?(k) }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def queue_count(queued)
|
|
67
|
+
queued.inject(0) { |result, data| result + data.last.size }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Use for testing the interface with persistence methods
|
|
2
|
+
|
|
3
|
+
module Appoptics
|
|
4
|
+
module Metrics
|
|
5
|
+
module Persistence
|
|
6
|
+
class Test
|
|
7
|
+
|
|
8
|
+
# persist the given metrics
|
|
9
|
+
def persist(client, metrics, options={})
|
|
10
|
+
@persisted = metrics
|
|
11
|
+
return !@return_value.nil? ? @return_value : true
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# return what was persisted
|
|
15
|
+
def persisted
|
|
16
|
+
@persisted
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# force a return value from persistence
|
|
20
|
+
def return_value(value)
|
|
21
|
+
@return_value = value
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
require "set"
|
|
2
|
+
|
|
3
|
+
module Appoptics
|
|
4
|
+
module Metrics
|
|
5
|
+
|
|
6
|
+
# Mixin which provides common logic between {Queue} and {Aggregator}
|
|
7
|
+
# objects.
|
|
8
|
+
module Processor
|
|
9
|
+
MEASUREMENTS_PER_REQUEST = 500
|
|
10
|
+
|
|
11
|
+
attr_reader :per_request, :last_submit_time
|
|
12
|
+
attr_accessor :prefix, :tags
|
|
13
|
+
|
|
14
|
+
def tags
|
|
15
|
+
@tags ||= {}
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# The current Client instance this queue is using to authenticate
|
|
19
|
+
# and connect to Appoptics. This will default to the primary
|
|
20
|
+
# client used by the Appoptics::Metrics module unless it has been
|
|
21
|
+
# set to something else.
|
|
22
|
+
#
|
|
23
|
+
# @return [Appoptics::Metrics::Client]
|
|
24
|
+
def client
|
|
25
|
+
@client ||= Appoptics::Metrics.client
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def has_tags?
|
|
29
|
+
!@tags.empty?
|
|
30
|
+
end
|
|
31
|
+
alias :tags? :has_tags?
|
|
32
|
+
|
|
33
|
+
# The object this MetricSet will use to persist
|
|
34
|
+
#
|
|
35
|
+
def persister
|
|
36
|
+
@persister ||= create_persister
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Persist currently queued metrics
|
|
40
|
+
#
|
|
41
|
+
# @return Boolean
|
|
42
|
+
def submit
|
|
43
|
+
return true if self.empty?
|
|
44
|
+
options = {per_request: @per_request}
|
|
45
|
+
if persister.persist(self.client, self.queued, options)
|
|
46
|
+
@last_submit_time = Time.now
|
|
47
|
+
clear and return true
|
|
48
|
+
end
|
|
49
|
+
false
|
|
50
|
+
rescue ClientError
|
|
51
|
+
# clean up if we hit exceptions if asked to
|
|
52
|
+
clear if @clear_on_failure
|
|
53
|
+
raise
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Capture execution time for a block and queue
|
|
57
|
+
# it as the value for a metric. Times are recorded
|
|
58
|
+
# in milliseconds.
|
|
59
|
+
#
|
|
60
|
+
# Options are the same as for #add.
|
|
61
|
+
#
|
|
62
|
+
# @example Queue API request response time
|
|
63
|
+
# queue.time :api_request_time do
|
|
64
|
+
# # API request..
|
|
65
|
+
# end
|
|
66
|
+
#
|
|
67
|
+
# @example Queue API request response time w/ source
|
|
68
|
+
# queue.time :api_request_time, source: 'app1' do
|
|
69
|
+
# # API request..
|
|
70
|
+
# end
|
|
71
|
+
#
|
|
72
|
+
# @param [Symbol|String] name Metric name
|
|
73
|
+
# @param [Hash] options Metric options
|
|
74
|
+
def time(name, options={})
|
|
75
|
+
start = Time.now
|
|
76
|
+
yield.tap do
|
|
77
|
+
duration = (Time.now - start) * 1000.0 # milliseconds
|
|
78
|
+
metric = {name => options.merge({value: duration})}
|
|
79
|
+
add metric
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
alias :benchmark :time
|
|
83
|
+
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
def create_persister
|
|
87
|
+
type = self.client.persistence.to_s.capitalize
|
|
88
|
+
Appoptics::Metrics::Persistence.const_get(type).new
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def epoch_time
|
|
92
|
+
Time.now.to_i
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def setup_common_options(options)
|
|
96
|
+
validate_parameters(options)
|
|
97
|
+
@autosubmit_interval = options[:autosubmit_interval]
|
|
98
|
+
@client = options[:client] || Appoptics::Metrics.client
|
|
99
|
+
@per_request = options[:per_request] || MEASUREMENTS_PER_REQUEST
|
|
100
|
+
@source = options[:source]
|
|
101
|
+
@tags = options.fetch(:tags, {})
|
|
102
|
+
@time = (options[:time] && options[:time].to_i || options[:measure_time] && options[:measure_time].to_i)
|
|
103
|
+
@create_time = Time.now
|
|
104
|
+
@clear_on_failure = options[:clear_failures] || false
|
|
105
|
+
@prefix = options[:prefix]
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def autosubmit_check
|
|
109
|
+
if @autosubmit_interval
|
|
110
|
+
last = @last_submit_time || @create_time
|
|
111
|
+
self.submit if (Time.now - last).to_i >= @autosubmit_interval
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def validate_parameters(options)
|
|
116
|
+
invalid_combinations = [
|
|
117
|
+
[:source, :tags],
|
|
118
|
+
]
|
|
119
|
+
opts = options.keys.to_set
|
|
120
|
+
invalid_combinations.each do |combo|
|
|
121
|
+
if combo.to_set.subset?(opts)
|
|
122
|
+
raise InvalidParameters, "#{combo} cannot be simultaneously set"
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
end
|
|
130
|
+
end
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
require 'metrics/processor'
|
|
2
|
+
|
|
3
|
+
module Appoptics
|
|
4
|
+
module Metrics
|
|
5
|
+
class Queue
|
|
6
|
+
include Processor
|
|
7
|
+
|
|
8
|
+
attr_accessor :skip_measurement_times
|
|
9
|
+
|
|
10
|
+
# @option opts [Integer] :autosubmit_count If set the queue will auto-submit any time it hits this number of measurements.
|
|
11
|
+
# @option opts [Integer] :autosubmit_interval If set the queue will auto-submit if the given number of seconds has passed when a new metric is added.
|
|
12
|
+
# @option opts [Boolean] :clear_failures Should the queue remove any queued measurements from its queue if it runs into problems with a request? (default: false)
|
|
13
|
+
# @option opts [Client] :client The client object to use to connect to Metrics. (default: Appoptics::Metrics.client)
|
|
14
|
+
# @option opts [Time|Integer] :measure_time A default measure_time to use for measurements added.
|
|
15
|
+
# @option opts [String] :prefix If set will apply the given prefix to all metric names of measurements added.
|
|
16
|
+
# @option opts [Boolean] :skip_measurement_times If true will not assign measurement_time to each measure as they are added.
|
|
17
|
+
# @option opts [String] :source The default source to use for measurements added.
|
|
18
|
+
def initialize(opts={})
|
|
19
|
+
@queued = {}
|
|
20
|
+
@autosubmit_count = opts[:autosubmit_count]
|
|
21
|
+
@skip_measurement_times = opts[:skip_measurement_times]
|
|
22
|
+
setup_common_options(opts)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Add a metric entry to the metric set:
|
|
26
|
+
#
|
|
27
|
+
# @param [Hash] measurements measurements to add
|
|
28
|
+
# @return [Queue] returns self
|
|
29
|
+
def add(measurements)
|
|
30
|
+
measurements.each do |key, value|
|
|
31
|
+
multidimensional = has_tags?
|
|
32
|
+
if value.respond_to?(:each)
|
|
33
|
+
validate_parameters(value)
|
|
34
|
+
metric = value
|
|
35
|
+
metric[:name] = key.to_s
|
|
36
|
+
type = metric.delete(:type) || metric.delete('type') || 'gauge'
|
|
37
|
+
else
|
|
38
|
+
metric = {name: key.to_s, value: value}
|
|
39
|
+
type = :gauge
|
|
40
|
+
end
|
|
41
|
+
if @prefix
|
|
42
|
+
metric[:name] = "#{@prefix}.#{metric[:name]}"
|
|
43
|
+
end
|
|
44
|
+
multidimensional = true if metric[:tags] || metric[:time]
|
|
45
|
+
type = ("#{type}s").to_sym
|
|
46
|
+
time_key = multidimensional ? :time : :measure_time
|
|
47
|
+
metric[:time] = metric.delete(:measure_time) if multidimensional && metric[:measure_time]
|
|
48
|
+
|
|
49
|
+
if metric[time_key]
|
|
50
|
+
metric[time_key] = metric[time_key].to_i
|
|
51
|
+
check_measure_time(metric)
|
|
52
|
+
elsif !skip_measurement_times
|
|
53
|
+
metric[time_key] = epoch_time
|
|
54
|
+
end
|
|
55
|
+
if multidimensional
|
|
56
|
+
@queued[:measurements] ||= []
|
|
57
|
+
@queued[:measurements] << metric
|
|
58
|
+
else
|
|
59
|
+
@queued[type] ||= []
|
|
60
|
+
@queued[type] << metric
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
submit_check
|
|
64
|
+
self
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Currently queued counters
|
|
68
|
+
#
|
|
69
|
+
# @return [Array]
|
|
70
|
+
def counters
|
|
71
|
+
@queued[:counters] || []
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Are any metrics currently queued?
|
|
75
|
+
#
|
|
76
|
+
# @return Boolean
|
|
77
|
+
def empty?
|
|
78
|
+
gauges.empty? && counters.empty? && measurements.empty?
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Remove all queued metrics
|
|
82
|
+
#
|
|
83
|
+
def clear
|
|
84
|
+
@queued = {}
|
|
85
|
+
end
|
|
86
|
+
alias :flush :clear
|
|
87
|
+
|
|
88
|
+
# Currently queued gauges
|
|
89
|
+
#
|
|
90
|
+
# @return Array
|
|
91
|
+
def gauges
|
|
92
|
+
@queued[:gauges] || []
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def measurements
|
|
96
|
+
@queued[:measurements] || []
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Combines queueable measures from the given object
|
|
100
|
+
# into this queue.
|
|
101
|
+
#
|
|
102
|
+
# @example Merging queues for more performant submission
|
|
103
|
+
# queue1.merge!(queue2)
|
|
104
|
+
# queue1.submit # submits combined contents
|
|
105
|
+
#
|
|
106
|
+
# @return self
|
|
107
|
+
def merge!(mergeable)
|
|
108
|
+
if mergeable.respond_to?(:queued)
|
|
109
|
+
to_merge = mergeable.queued
|
|
110
|
+
elsif mergeable.respond_to?(:has_key?)
|
|
111
|
+
to_merge = mergeable
|
|
112
|
+
else
|
|
113
|
+
raise NotMergeable
|
|
114
|
+
end
|
|
115
|
+
Metrics::PLURAL_TYPES.each do |type|
|
|
116
|
+
if to_merge[type]
|
|
117
|
+
payload = reconcile(to_merge[type], to_merge[:source])
|
|
118
|
+
if @queued[type]
|
|
119
|
+
@queued[type] += payload
|
|
120
|
+
else
|
|
121
|
+
@queued[type] = payload
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
if to_merge[:measurements]
|
|
127
|
+
payload = reconcile(to_merge[:measurements], to_merge[:tags])
|
|
128
|
+
if @queued[:measurements]
|
|
129
|
+
@queued[:measurements] += payload
|
|
130
|
+
else
|
|
131
|
+
@queued[:measurements] = payload
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
submit_check
|
|
136
|
+
self
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# All currently queued metrics
|
|
140
|
+
#
|
|
141
|
+
# @return Hash
|
|
142
|
+
def queued
|
|
143
|
+
return {} if @queued.empty?
|
|
144
|
+
globals = {}
|
|
145
|
+
time = has_tags? ? :time : :measure_time
|
|
146
|
+
globals[time] = @time if @time
|
|
147
|
+
globals[:source] = @source if @source
|
|
148
|
+
globals[:tags] = @tags if has_tags?
|
|
149
|
+
@queued.merge(globals)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Count of metrics currently queued
|
|
153
|
+
#
|
|
154
|
+
# @return Integer
|
|
155
|
+
def size
|
|
156
|
+
self.queued.inject(0) { |result, data| result + data.last.size }
|
|
157
|
+
end
|
|
158
|
+
alias :length :size
|
|
159
|
+
|
|
160
|
+
private
|
|
161
|
+
|
|
162
|
+
def check_measure_time(data)
|
|
163
|
+
time_keys = [:measure_time, :time]
|
|
164
|
+
|
|
165
|
+
if time_keys.any? { |key| data[key] && data[key] < Metrics::MIN_MEASURE_TIME }
|
|
166
|
+
raise InvalidMeasureTime, "Measure time for submitted metric (#{data}) is invalid."
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def reconcile(measurements, val)
|
|
171
|
+
arr = val.is_a?(Hash) ? [@tags, :tags] : [@source, :source]
|
|
172
|
+
return measurements if !val || val == arr.first
|
|
173
|
+
measurements.map! do |measurement|
|
|
174
|
+
unless measurement[arr.last]
|
|
175
|
+
measurement[arr.last] = val
|
|
176
|
+
end
|
|
177
|
+
measurement
|
|
178
|
+
end
|
|
179
|
+
measurements
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def submit_check
|
|
183
|
+
autosubmit_check # in Processor
|
|
184
|
+
if @autosubmit_count && self.length >= @autosubmit_count
|
|
185
|
+
self.submit
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|