rails_autoscale_agent 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|