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 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