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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e36ab348752b0f7b470a47224ccfb8b8375a2355
4
- data.tar.gz: 14d64ad0ed2114955d34f2e9e5a129c763d5ffb3
3
+ metadata.gz: a664bf586cfca775c6746bf7a9ec73ceacda9a35
4
+ data.tar.gz: 3851b8c106b566f8af42ad08bcb7481f07780f09
5
5
  SHA512:
6
- metadata.gz: d68ddb62be312c4afd8ee376c249f9755472c04a90cb8a98409a8b9e806dafaa3ea909c2ff06e2a42c6b48d466b1af1dca134640eef7db0ae67df79251d7017e
7
- data.tar.gz: 59fb44914ffc29746221370e8cbaf3397b5adb725a474905d20fcd42e36172fe0aa7b9736e3f5b354c16e92915040f8238fad0abf8f166c28cac5084019464af
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 through 5.0 and Ruby versions 1.9.3 through 2.3.1.
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
- post '/reports', report: report_params
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
- post '/registrations', registration: registration_params
22
+ post_json '/registrations', registration: registration_params
22
23
  end
23
24
 
24
25
  private
25
26
 
26
- def post(path, data)
27
- header = {'Content-Type' => 'application/json'}
28
- uri = URI.parse("#{@api_url_base}#{path}")
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, header)
33
- request.body = JSON.dump(data)
42
+ request = Net::HTTP::Post.new(uri.request_uri, options[:headers] || {})
43
+ request.body = options.fetch(:body)
34
44
 
35
- logger.debug "[AutoscaleApi] Posting to #{request.body.size} bytes to #{uri}"
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.debug "[Collector] request queued for more than 10 minutes... skipping collection"
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,7 +16,7 @@ module RailsAutoscaleAgent
16
16
  end
17
17
 
18
18
  def to_s
19
- "#{@dyno}-#{@pid}"
19
+ "#{@dyno}##{@pid}"
20
20
  end
21
21
  end
22
22
  end
@@ -16,18 +16,14 @@ module RailsAutoscaleAgent
16
16
  def call(env)
17
17
  config = Config.new(ENV)
18
18
 
19
- logger.tagged 'RailsAutoscaleAgent', config.to_s do
20
- if config.api_base_url
21
- request = Request.new(env, config)
22
-
23
- logger.debug "[Middleware] enter middleware for #{request.fullpath}"
24
-
25
- store = Store.instance
26
- Reporter.start(config, store)
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)
@@ -5,7 +5,8 @@ module RailsAutoscaleAgent
5
5
 
6
6
  def to_params
7
7
  {
8
- pid: Process.pid,
8
+ dyno: config.dyno,
9
+ pid: config.pid,
9
10
  ruby_version: RUBY_VERSION,
10
11
  rails_version: Rails.version,
11
12
  gem_version: VERSION,
@@ -1,17 +1,29 @@
1
1
  module RailsAutoscaleAgent
2
- class Report < Struct.new(:time, :values)
2
+ class Report
3
3
 
4
- def initialize(time, values = [])
5
- super
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
- instance.start!(config, store) unless instance.running?
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
- register!(config)
25
-
26
- loop do
27
- beginning_of_next_minute = TimeRounder.beginning_of_minute(Time.now) + 60
28
-
29
- # add 0-5 seconds to avoid slamming the API at one moment
30
- next_report_time = beginning_of_next_minute + rand * 5
31
-
32
- sleep next_report_time - Time.now
33
-
34
- begin
35
- report!(config, store)
36
- rescue => ex
37
- # Exceptions in threads other than the main thread will fail silently
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
- while report = store.pop_report
52
- logger.info "[Reporter] reporting queue times for #{report.values.size} requests during minute #{report.time.iso8601}"
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 "[Reporter] reported successfully"
61
+ logger.info "Reported successfully"
60
62
  when AutoscaleApi::FailureResponse
61
- logger.error "[Reporter] failed: #{result.failure_message}"
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
- if result.is_a? AutoscaleApi::FailureResponse
73
- logger.error "[Reporter] failed to register: #{result.failure_message}"
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, :fullpath
3
+ attr_reader :id, :entered_queue_at, :path
4
4
 
5
5
  def initialize(env, config)
6
- @fullpath = "#{env['HTTP_HOST']}#{env['PATH_INFO']}"
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
- result = nil
23
- boundary = TimeRounder.beginning_of_minute(Time.now)
19
+ report = Report.new
24
20
 
25
- while @measurements[0] && @measurements[0].time < boundary
26
- measurement = @measurements.shift
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
- result
25
+ report
38
26
  end
39
27
 
40
28
  end
@@ -1,3 +1,3 @@
1
1
  module RailsAutoscaleAgent
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  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.1.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-07 00:00:00.000000000 Z
11
+ date: 2017-07-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport