rack-healthz 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: 8f2db9eb6e4e77c61d09a7c24c09ed1054be13a9
4
- data.tar.gz: 47d097eda0ea43b797347f8c5c72be986a9adde3
3
+ metadata.gz: a57636e6570937379f557ffe6ae23130896cd754
4
+ data.tar.gz: 4d7f5099aad1b246100297615f972a1c7aa2a223
5
5
  SHA512:
6
- metadata.gz: 6f4c748f5b46b7128b12ad568c873d9dada21b438c885ff2738d591e7c949f48d79e236c8fc0e60635885a887e0b3381703eca359d7affda37158322baecba34
7
- data.tar.gz: ebbd397052a57252725f2edd24f094532d0871de79a476bd1d43d50aabe4c38d03f4d5c28dc422ac77374ca3d7191fbe4d5f351c7691181c8de157d5133c412b
6
+ metadata.gz: 44d8de20b2f9b21afbe0985b87c76cdeabd7b125a9efda9f0b5a3870c55f5326897fbdc2e0117ef467dc5dea36a1a0577d7fbb6bfe7c2da3fee4cf67a71cfe0a
7
+ data.tar.gz: 24f300760fbe30759db3151f8be56604d1800288adc6ddccbe2279c97429e6a69fe14b2f35a181be70c07be0f650a010834abe308ebde6969559182b224be33d
@@ -8,6 +8,11 @@ GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
10
  autoloaded (2.2.1)
11
+ coderay (1.1.2)
12
+ method_source (0.9.2)
13
+ pry (0.12.2)
14
+ coderay (~> 1.1.0)
15
+ method_source (~> 0.9.0)
11
16
  rack (2.0.7)
12
17
  rake (10.5.0)
13
18
 
@@ -16,6 +21,7 @@ PLATFORMS
16
21
 
17
22
  DEPENDENCIES
18
23
  bundler (~> 2.0)
24
+ pry
19
25
  rack
20
26
  rack-healthz!
21
27
  rake (~> 10.0)
@@ -1,86 +1,61 @@
1
- require "rack/healthz/version"
1
+ require 'rack/healthz/version'
2
+
2
3
  require 'autoloaded'
3
4
  require 'json'
5
+ require 'pry'
4
6
 
5
7
  module Rack
6
8
  class Healthz
7
9
  Autoloaded.module {}
8
- class Error < StandardError;
9
- end
10
10
 
11
- def initialize(app, stats_path: "/healthz", max_time_between_requests: 3600)
12
- @app = app
13
- @count = 0
14
- @my_stats = {}
15
- @up_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
16
- @last_success_time = nil
17
- @stats_path = stats_path
18
- @max_time_between_requests = max_time_between_requests
19
- end
11
+ DefaultPath = '/healthz'
20
12
 
21
- def call(env)
22
- if env.fetch("PATH_INFO") == @stats_path
23
- now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
24
- [return_status(now), {'Content-Type' => 'application/json'}, [body_out(now)]]
25
- else
26
- @count += 1
27
- start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
28
- result = @app.call env
29
- end_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
30
- elapsed_time = end_time - start_time
31
- response_code = result.first.to_i
32
- request_category = response_code / 100
33
- if !(@my_stats.key? request_category)
34
- @my_stats[request_category] = RequestAccumulator.new
35
- end
36
- @my_stats[request_category] << elapsed_time
37
- if response_code < 400
38
- @last_success_time = end_time
39
- end
40
- result
41
- end
42
- end
13
+ DefaultTimeBetweenRequests = 3600
14
+
15
+ HealthyStatus = 200
16
+ UnhealthyStatus = 503
43
17
 
44
- def return_status(now)
45
- if healthy?(now)
46
- 200
47
- else
48
- 503
18
+ ContentType = {'Content-Type' => 'application/json'}
19
+
20
+ class << self
21
+ def current_time
22
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
49
23
  end
50
- end
51
24
 
52
- def status(now)
53
- if healthy?(now)
54
- "healthy"
55
- else
56
- "unhealthy"
25
+ def measure(&block)
26
+ start_time = current_time
27
+
28
+ block.call
29
+
30
+ current_time - start_time
57
31
  end
58
32
  end
59
33
 
60
- def healthy?(now)
61
- !time_since_last_success(now).nil? and (time_since_last_success(now) < @max_time_between_requests)
34
+ class Error < StandardError;
62
35
  end
63
36
 
64
- def time_since_last_success(now)
65
- @last_success_time ? now - @last_success_time : nil
66
- end
37
+ attr_reader :accumulator
67
38
 
68
- def uptime(now)
69
- now - @up_at
39
+ def initialize(app, **args)
40
+ @accumulator = Accumulator.new(app, **args)
70
41
  end
71
42
 
72
- def body_out(now)
73
- result = {
74
- 'count' => @count,
75
- 'uptime' => uptime(now),
76
- 'time_since_last_success' => time_since_last_success(now),
77
- 'status' => status(now), # TODO
78
- 'stats' => {},
79
- }
80
- @my_stats.each_pair do |status_category, reqest_accumulator|
81
- result['stats'][status_category.to_s + 'xx'] = reqest_accumulator.to_h
43
+ def call(env)
44
+ with_now do
45
+ if accumulator.status_request?(env)
46
+ accumulator.response.to_a
47
+ else
48
+ accumulator.call(env).to_a
49
+ end
82
50
  end
83
- result.to_json
51
+ end
52
+
53
+ def with_now
54
+ old_now = Thread.current[:now]
55
+ Thread.current[:now] = Healthz.current_time
56
+ result = yield
57
+ Thread.current[:now] = old_now
58
+ result
84
59
  end
85
60
  end
86
61
  end
@@ -0,0 +1,108 @@
1
+ module Rack
2
+ class Healthz
3
+ class Accumulator
4
+ attr_reader :count, :path, :app, :stats, :max_time_between_requests, :up_at, :last_success_time
5
+
6
+ def initialize(app, max_time_between_requests: DefaultTimeBetweenRequests, path: DefaultPath)
7
+ @app = app
8
+ @count = 0
9
+ @stats = {}
10
+ @up_at = Healthz.current_time
11
+ @last_success_time = nil
12
+ @max_time_between_requests = max_time_between_requests
13
+ @path = path
14
+ end
15
+
16
+ def call(env)
17
+ count!
18
+
19
+ response = nil
20
+
21
+ elapsed_time = Healthz.measure do
22
+ response = app.call(env)
23
+ end
24
+
25
+ handle_response! elapsed_time, Rack::Response.new(response)
26
+
27
+ response
28
+ end
29
+
30
+ def status_request?(env)
31
+ env.fetch('PATH_INFO') == path
32
+ end
33
+
34
+ def healthy?
35
+ !time_since_last_success.nil? and (time_since_last_success < max_time_between_requests)
36
+ end
37
+
38
+ def response
39
+ Response.new self
40
+ end
41
+
42
+ def time_since_last_success
43
+ last_success_time ? current_time - last_success_time : nil
44
+ end
45
+
46
+ def uptime
47
+ current_time - up_at
48
+ end
49
+
50
+ def status
51
+ if healthy?
52
+ "healthy"
53
+ else
54
+ "unhealthy"
55
+ end
56
+ end
57
+
58
+ def to_h
59
+ {}.tap do |result|
60
+ result[:count] = count
61
+ result[:uptime] = uptime
62
+ result[:time_since_last_success] = time_since_last_success
63
+ result[:status] = status
64
+ result[:stats] = stats_hash
65
+ end
66
+ end
67
+
68
+ private
69
+
70
+ def stats_hash
71
+ {}.tap do |result|
72
+ stats.values.each do |stat|
73
+ result[stat.category] = stat.to_h
74
+ end
75
+ end
76
+ end
77
+
78
+ #
79
+ # Internal Commands
80
+ #
81
+ def handle_response!(elapsed_time, response)
82
+ response_category = response_category_of response
83
+
84
+ stats[response_category] ||= RequestAccumulator.new(response_category)
85
+ stats[response_category] << elapsed_time
86
+
87
+ successful! if response.successful?
88
+ end
89
+
90
+ def successful!
91
+ @last_success_time = current_time
92
+ end
93
+
94
+ def count!
95
+ @count += 1
96
+ end
97
+
98
+ #Internal Queries
99
+ def current_time
100
+ Thread.current[:now]
101
+ end
102
+
103
+ def response_category_of response
104
+ response.status / 100
105
+ end
106
+ end
107
+ end
108
+ end
@@ -2,9 +2,10 @@ module Rack
2
2
  class Healthz
3
3
  class RequestAccumulator
4
4
 
5
- attr_reader :min_time, :max_time, :count, :std, :mean
5
+ attr_reader :min_time, :max_time, :count, :std, :mean, :category
6
6
 
7
- def initialize
7
+ def initialize(category)
8
+ @category = category
8
9
  @count = 0
9
10
  @sum_of_times = 0.0
10
11
  @sum_of_square_times = 0.0
@@ -19,6 +20,7 @@ module Rack
19
20
  @min_time = [@min_time, elapsed_time].min
20
21
  @max_time = [@max_time, elapsed_time].max
21
22
  @mean = @sum_of_times / @count
23
+
22
24
  @std = if @count > 1
23
25
  Math.sqrt((@sum_of_square_times / @count) - (@sum_of_times / @count) ** 2)
24
26
  elsif @count == 1
@@ -0,0 +1,39 @@
1
+ module Rack
2
+ class Healthz
3
+ class Response
4
+ attr_reader :accumulator
5
+
6
+ def initialize(accumulator)
7
+ @accumulator = accumulator
8
+ end
9
+
10
+ def to_a
11
+ [return_status, content_type, [to_json]]
12
+ end
13
+
14
+ def to_json
15
+ to_h.to_json
16
+ end
17
+
18
+ def to_h
19
+ accumulator.to_h
20
+ end
21
+
22
+ def content_type
23
+ ContentType
24
+ end
25
+
26
+ def healthy?
27
+ accumulator.healthy?
28
+ end
29
+
30
+ def return_status
31
+ if healthy?
32
+ HealthyStatus
33
+ else
34
+ UnhealthyStatus
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -1,5 +1,5 @@
1
1
  module Rack
2
2
  class Healthz
3
- VERSION = "0.1.0"
3
+ VERSION = "0.2.0"
4
4
  end
5
5
  end
@@ -38,6 +38,7 @@ Gem::Specification.new do |spec|
38
38
  spec.add_development_dependency "bundler", "~> 2.0"
39
39
  spec.add_development_dependency "rake", "~> 10.0"
40
40
  spec.add_development_dependency "rack"
41
+ spec.add_development_dependency "pry"
41
42
 
42
43
  spec.add_runtime_dependency "autoloaded"
43
44
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-healthz
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
  - Andrew Eberbach
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2019-07-12 00:00:00.000000000 Z
12
+ date: 2019-07-13 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -53,6 +53,20 @@ dependencies:
53
53
  - - ">="
54
54
  - !ruby/object:Gem::Version
55
55
  version: '0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: pry
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
56
70
  - !ruby/object:Gem::Dependency
57
71
  name: autoloaded
58
72
  requirement: !ruby/object:Gem::Requirement
@@ -84,7 +98,9 @@ files:
84
98
  - bin/setup
85
99
  - config.ru
86
100
  - lib/rack/healthz.rb
101
+ - lib/rack/healthz/accumulator.rb
87
102
  - lib/rack/healthz/request_accumulator.rb
103
+ - lib/rack/healthz/response.rb
88
104
  - lib/rack/healthz/version.rb
89
105
  - rack-healthz.gemspec
90
106
  homepage: