rails_autoscale_agent 0.1.0 → 0.2.0
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 +4 -4
- data/README.md +1 -1
- data/lib/rails_autoscale_agent/autoscale_api.rb +26 -11
- data/lib/rails_autoscale_agent/collector.rb +3 -4
- data/lib/rails_autoscale_agent/config.rb +1 -1
- data/lib/rails_autoscale_agent/middleware.rb +8 -12
- data/lib/rails_autoscale_agent/registration.rb +2 -1
- data/lib/rails_autoscale_agent/report.rb +17 -5
- data/lib/rails_autoscale_agent/reporter.rb +35 -29
- data/lib/rails_autoscale_agent/request.rb +3 -2
- data/lib/rails_autoscale_agent/store.rb +4 -16
- data/lib/rails_autoscale_agent/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a664bf586cfca775c6746bf7a9ec73ceacda9a35
|
4
|
+
data.tar.gz: 3851b8c106b566f8af42ad08bcb7481f07780f09
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ddefca39f67065285ef5fc6fb04d9a1c7e5ed4b2a1f40ba7cd29aaaea1f086fc945e72c8aef4ee061a39d30509407f887968447e7d8af8b66c608c312bcda40e
|
7
|
+
data.tar.gz: 3fced23c559e9ef89306cf05d72f85d05c57a5bfc346ca38abefa782f0b00c8ed97a72ec564694b847d09a1c631ad5ae67f33cf9e03f672ff7c95f6130f44188
|
data/README.md
CHANGED
@@ -7,7 +7,7 @@ and periodically posts this data asynchronously to the Rails Autoscale service.
|
|
7
7
|
|
8
8
|
## Requirements
|
9
9
|
|
10
|
-
We've tested this with Rails versions 3.2
|
10
|
+
We've tested this with Rails versions 3.2 and higher and Ruby versions 1.9.3 and higher.
|
11
11
|
|
12
12
|
## Getting Started
|
13
13
|
|
@@ -13,36 +13,51 @@ module RailsAutoscaleAgent
|
|
13
13
|
@api_url_base = api_url_base
|
14
14
|
end
|
15
15
|
|
16
|
-
def report_metrics!(report_params)
|
17
|
-
|
16
|
+
def report_metrics!(report_params, timings_csv)
|
17
|
+
query = URI.encode_www_form(report_params)
|
18
|
+
post_csv "/v2/reports?#{query}", timings_csv
|
18
19
|
end
|
19
20
|
|
20
21
|
def register_reporter!(registration_params)
|
21
|
-
|
22
|
+
post_json '/registrations', registration: registration_params
|
22
23
|
end
|
23
24
|
|
24
25
|
private
|
25
26
|
|
26
|
-
def
|
27
|
-
|
28
|
-
|
27
|
+
def post_json(path, data)
|
28
|
+
headers = {'Content-Type' => 'application/json'}
|
29
|
+
post_raw path: path, body: JSON.dump(data), headers: headers
|
30
|
+
end
|
31
|
+
|
32
|
+
def post_csv(path, data)
|
33
|
+
headers = {'Content-Type' => 'text/csv'}
|
34
|
+
post_raw path: path, body: data, headers: headers
|
35
|
+
end
|
36
|
+
|
37
|
+
def post_raw(options)
|
38
|
+
uri = URI.parse("#{@api_url_base}#{options.fetch(:path)}")
|
29
39
|
ssl = uri.scheme == 'https'
|
30
40
|
|
31
41
|
response = Net::HTTP.start(uri.host, uri.port, use_ssl: ssl) do |http|
|
32
|
-
request = Net::HTTP::Post.new(uri.request_uri,
|
33
|
-
request.body =
|
42
|
+
request = Net::HTTP::Post.new(uri.request_uri, options[:headers] || {})
|
43
|
+
request.body = options.fetch(:body)
|
34
44
|
|
35
|
-
logger.debug "
|
45
|
+
logger.debug "Posting #{request.body.size} bytes to #{uri}"
|
36
46
|
http.request(request)
|
37
47
|
end
|
38
48
|
|
39
49
|
case response.code.to_i
|
40
|
-
when 200...300 then SuccessResponse.new
|
50
|
+
when 200...300 then SuccessResponse.new(response.body)
|
41
51
|
else FailureResponse.new(response.message)
|
42
52
|
end
|
43
53
|
end
|
44
54
|
|
45
|
-
class SuccessResponse
|
55
|
+
class SuccessResponse < Struct.new(:body)
|
56
|
+
def data
|
57
|
+
JSON.parse(body)
|
58
|
+
rescue TypeError
|
59
|
+
{}
|
60
|
+
end
|
46
61
|
end
|
47
62
|
|
48
63
|
class FailureResponse < Struct.new(:failure_message)
|
@@ -8,14 +8,13 @@ module RailsAutoscaleAgent
|
|
8
8
|
if request.entered_queue_at
|
9
9
|
if request.entered_queue_at < (Time.now - 60 * 10)
|
10
10
|
# ignore unreasonable values
|
11
|
-
logger.
|
11
|
+
logger.info "request queued for more than 10 minutes... skipping collection"
|
12
12
|
else
|
13
|
-
queue_time_millis = (Time.now - request.entered_queue_at) * 1000
|
13
|
+
queue_time_millis = ((Time.now - request.entered_queue_at) * 1000).to_i
|
14
14
|
queue_time_millis = 0 if queue_time_millis < 0
|
15
15
|
store.push(queue_time_millis)
|
16
|
+
logger.info "Collected queue_time=#{queue_time_millis}ms request_id=#{request.id}"
|
16
17
|
end
|
17
|
-
else
|
18
|
-
logger.debug "[Collector] no wait time data to collect"
|
19
18
|
end
|
20
19
|
end
|
21
20
|
|
@@ -16,18 +16,14 @@ module RailsAutoscaleAgent
|
|
16
16
|
def call(env)
|
17
17
|
config = Config.new(ENV)
|
18
18
|
|
19
|
-
logger.tagged '
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
Collector.collect(request, store)
|
28
|
-
else
|
29
|
-
logger.info "[Middleware] RAILS_AUTOSCALE_URL is not set"
|
30
|
-
end
|
19
|
+
logger.tagged 'RailsAutoscale' do
|
20
|
+
request = Request.new(env, config)
|
21
|
+
|
22
|
+
logger.debug "Middleware entered - request_id=#{request.id} path=#{request.path}"
|
23
|
+
|
24
|
+
store = Store.instance
|
25
|
+
Reporter.start(config, store)
|
26
|
+
Collector.collect(request, store)
|
31
27
|
end
|
32
28
|
|
33
29
|
@app.call(env)
|
@@ -1,17 +1,29 @@
|
|
1
1
|
module RailsAutoscaleAgent
|
2
|
-
class Report
|
2
|
+
class Report
|
3
3
|
|
4
|
-
|
5
|
-
|
4
|
+
attr_reader :measurements
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@measurements = []
|
6
8
|
end
|
7
9
|
|
8
10
|
def to_params(config)
|
9
11
|
{
|
10
|
-
time: time.iso8601,
|
11
12
|
dyno: config.dyno,
|
12
13
|
pid: config.pid,
|
13
|
-
measurements: values,
|
14
14
|
}
|
15
15
|
end
|
16
|
+
|
17
|
+
def to_csv
|
18
|
+
''.tap do |result|
|
19
|
+
@measurements.each do |measurement|
|
20
|
+
result << measurement.time.to_i.to_s
|
21
|
+
result << ','.freeze
|
22
|
+
result << measurement.value.to_s
|
23
|
+
result << "\n".freeze
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
16
28
|
end
|
17
29
|
end
|
@@ -12,32 +12,32 @@ module RailsAutoscaleAgent
|
|
12
12
|
include Logger
|
13
13
|
|
14
14
|
def self.start(config, store)
|
15
|
-
|
15
|
+
if config.api_base_url
|
16
|
+
instance.start!(config, store) unless instance.running?
|
17
|
+
else
|
18
|
+
instance.logger.debug "Reporter not started: RAILS_AUTOSCALE_URL is not set"
|
19
|
+
end
|
16
20
|
end
|
17
21
|
|
18
22
|
def start!(config, store)
|
19
|
-
logger.info "[Reporter] starting reporter, will report every minute"
|
20
|
-
|
21
23
|
@running = true
|
24
|
+
@report_interval = 60 # this default will be overwritten during #register!
|
22
25
|
|
23
26
|
Thread.new do
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
# https://ruby-doc.org/core-2.2.0/Thread.html#class-Thread-label-Exception+handling
|
39
|
-
logger.error "[Reporter] #{ex.inspect}"
|
40
|
-
logger.error ex.backtrace.join("\n")
|
27
|
+
logger.tagged 'RailsAutoscale' do
|
28
|
+
register!(config)
|
29
|
+
|
30
|
+
loop do
|
31
|
+
sleep @report_interval
|
32
|
+
|
33
|
+
begin
|
34
|
+
report!(config, store)
|
35
|
+
rescue => ex
|
36
|
+
# Exceptions in threads other than the main thread will fail silently
|
37
|
+
# https://ruby-doc.org/core-2.2.0/Thread.html#class-Thread-label-Exception+handling
|
38
|
+
logger.error "Reporter error: #{ex.inspect}"
|
39
|
+
logger.error ex.backtrace.join("\n")
|
40
|
+
end
|
41
41
|
end
|
42
42
|
end
|
43
43
|
end
|
@@ -48,29 +48,35 @@ module RailsAutoscaleAgent
|
|
48
48
|
end
|
49
49
|
|
50
50
|
def report!(config, store)
|
51
|
-
|
52
|
-
|
51
|
+
report = store.pop_report
|
52
|
+
|
53
|
+
if report.measurements.any?
|
54
|
+
logger.info "Reporting queue times for #{report.measurements.size} requests"
|
53
55
|
|
54
56
|
params = report.to_params(config)
|
55
|
-
result = AutoscaleApi.new(config.api_base_url).report_metrics!(params)
|
57
|
+
result = AutoscaleApi.new(config.api_base_url).report_metrics!(params, report.to_csv)
|
56
58
|
|
57
59
|
case result
|
58
60
|
when AutoscaleApi::SuccessResponse
|
59
|
-
logger.info "
|
61
|
+
logger.info "Reported successfully"
|
60
62
|
when AutoscaleApi::FailureResponse
|
61
|
-
logger.error "
|
63
|
+
logger.error "Reporter failed: #{result.failure_message}"
|
62
64
|
end
|
65
|
+
else
|
66
|
+
logger.debug "Reporter has nothing to report"
|
63
67
|
end
|
64
|
-
|
65
|
-
logger.debug "[Reporter] nothing to report" unless result
|
66
68
|
end
|
67
69
|
|
68
70
|
def register!(config)
|
69
71
|
params = Registration.new(config).to_params
|
70
72
|
result = AutoscaleApi.new(config.api_base_url).register_reporter!(params)
|
71
73
|
|
72
|
-
|
73
|
-
|
74
|
+
case result
|
75
|
+
when AutoscaleApi::SuccessResponse
|
76
|
+
@report_interval = result.data['report_interval'] || @report_interval
|
77
|
+
logger.info "Reporter starting, will report every #{@report_interval} seconds"
|
78
|
+
when AutoscaleApi::FailureResponse
|
79
|
+
logger.error "Reporter failed to register: #{result.failure_message}"
|
74
80
|
end
|
75
81
|
end
|
76
82
|
|
@@ -1,9 +1,10 @@
|
|
1
1
|
module RailsAutoscaleAgent
|
2
2
|
class Request
|
3
|
-
attr_reader :entered_queue_at, :
|
3
|
+
attr_reader :id, :entered_queue_at, :path
|
4
4
|
|
5
5
|
def initialize(env, config)
|
6
|
-
@
|
6
|
+
@id = env['HTTP_X_REQUEST_ID']
|
7
|
+
@path = env['PATH_INFO']
|
7
8
|
@entered_queue_at = if unix_millis = env['HTTP_X_REQUEST_START']
|
8
9
|
Time.at(unix_millis.to_f / 1000)
|
9
10
|
elsif config.fake_mode?
|
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'singleton'
|
2
|
-
require 'rails_autoscale_agent/logger'
|
3
2
|
require 'rails_autoscale_agent/time_rounder'
|
4
3
|
require 'rails_autoscale_agent/measurement'
|
5
4
|
require 'rails_autoscale_agent/report'
|
@@ -7,34 +6,23 @@ require 'rails_autoscale_agent/report'
|
|
7
6
|
module RailsAutoscaleAgent
|
8
7
|
class Store
|
9
8
|
include Singleton
|
10
|
-
include Logger
|
11
9
|
|
12
10
|
def initialize
|
13
11
|
@measurements = []
|
14
12
|
end
|
15
13
|
|
16
14
|
def push(value, time = Time.now)
|
17
|
-
logger.debug "[Collector] queue time: #{value}"
|
18
15
|
@measurements << Measurement.new(time, value)
|
19
16
|
end
|
20
17
|
|
21
18
|
def pop_report
|
22
|
-
|
23
|
-
boundary = TimeRounder.beginning_of_minute(Time.now)
|
19
|
+
report = Report.new
|
24
20
|
|
25
|
-
while
|
26
|
-
|
27
|
-
|
28
|
-
if result.nil?
|
29
|
-
report_time = TimeRounder.beginning_of_minute(measurement.time)
|
30
|
-
boundary = report_time + 60
|
31
|
-
result = Report.new(report_time)
|
32
|
-
end
|
33
|
-
|
34
|
-
result.values << measurement.value
|
21
|
+
while measurement = @measurements.shift
|
22
|
+
report.measurements << measurement
|
35
23
|
end
|
36
24
|
|
37
|
-
|
25
|
+
report
|
38
26
|
end
|
39
27
|
|
40
28
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rails_autoscale_agent
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Adam McCrea
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-03
|
11
|
+
date: 2017-07-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|