network_resiliency 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d085dba12da295ad4c4bb4d540acb3b865c8d8486b9bdf6d812e74430efcd55f
4
+ data.tar.gz: 24d61f1205ecba15f7acbda327d91be3ca0c74dd0955c2fe03338f873435d478
5
+ SHA512:
6
+ metadata.gz: 91e9bcd5141345e0117be83f1e01951a11f6ffc8b100c1776a0ba3a2ac2bde19642369875ee79d995f8c4f3bbc0742f72c67102295bdecb2e9afd681597f555c
7
+ data.tar.gz: df13161c53527c10143d7c11b56ac7cc2a75189eb58b2d698d2f9940dd50a894a3e6dae8680764a6773c68c6dc107faf8613b457c5edeb1dc7c65200fe97e491
data/CHANGELOG.md ADDED
@@ -0,0 +1,11 @@
1
+ ### v0.0.2 (2023-05-09)
2
+ - rename to Network Resiliency
3
+ - fix sample variance
4
+ - upgrade / fix stats
5
+ - github actions
6
+ - register middleware
7
+ - stats
8
+
9
+ ### v0.0.1 (2021-09-10)
10
+ - init
11
+
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,67 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ network_resiliency (0.0.2)
5
+ faraday
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ byebug (11.1.3)
11
+ codecov (0.5.2)
12
+ simplecov (>= 0.15, < 0.22)
13
+ connection_pool (2.3.0)
14
+ diff-lcs (1.4.4)
15
+ docile (1.4.0)
16
+ faraday (2.7.4)
17
+ faraday-net_http (>= 2.0, < 3.1)
18
+ ruby2_keywords (>= 0.0.4)
19
+ faraday-net_http (3.0.2)
20
+ faraday-rack (1.0.0)
21
+ rack (3.0.2)
22
+ rack-test (2.0.2)
23
+ rack (>= 1.3)
24
+ redis (5.0.5)
25
+ redis-client (>= 0.9.0)
26
+ redis-client (0.11.2)
27
+ connection_pool
28
+ rspec (3.10.0)
29
+ rspec-core (~> 3.10.0)
30
+ rspec-expectations (~> 3.10.0)
31
+ rspec-mocks (~> 3.10.0)
32
+ rspec-core (3.10.1)
33
+ rspec-support (~> 3.10.0)
34
+ rspec-expectations (3.10.1)
35
+ diff-lcs (>= 1.2.0, < 2.0)
36
+ rspec-support (~> 3.10.0)
37
+ rspec-mocks (3.10.2)
38
+ diff-lcs (>= 1.2.0, < 2.0)
39
+ rspec-support (~> 3.10.0)
40
+ rspec-support (3.10.2)
41
+ ruby2_keywords (0.0.5)
42
+ simplecov (0.21.2)
43
+ docile (~> 1.1)
44
+ simplecov-html (~> 0.11)
45
+ simplecov_json_formatter (~> 0.1)
46
+ simplecov-html (0.12.3)
47
+ simplecov_json_formatter (0.1.3)
48
+
49
+ PLATFORMS
50
+ x86_64-darwin-19
51
+ x86_64-darwin-20
52
+ x86_64-darwin-21
53
+
54
+ DEPENDENCIES
55
+ byebug
56
+ codecov
57
+ faraday-rack
58
+ network_resiliency!
59
+ rack
60
+ rack-test
61
+ redis
62
+ redis-client (>= 0.10)
63
+ rspec
64
+ simplecov
65
+
66
+ BUNDLED WITH
67
+ 2.2.20
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 Daniel Pepper
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,26 @@
1
+ ApiAvenger
2
+ ======
3
+ ...
4
+
5
+
6
+ ```ruby
7
+ require "network_resiliency"
8
+ ```
9
+
10
+
11
+ ----
12
+ ## Contributing
13
+
14
+ Yes please :)
15
+
16
+ 1. Fork it
17
+ 1. Create your feature branch (`git checkout -b my-feature`)
18
+ 1. Ensure the tests pass (`bundle exec rspec`)
19
+ 1. Commit your changes (`git commit -am 'awesome new feature'`)
20
+ 1. Push your branch (`git push origin my-feature`)
21
+ 1. Create a Pull Request
22
+
23
+
24
+ ----
25
+ ![Gem](https://img.shields.io/gem/dt/network_resiliency?style=plastic)
26
+ [![codecov](https://codecov.io/gh/dpep/network_resiliency_rb/branch/main/graph/badge.svg)](https://codecov.io/gh/dpep/network_resiliency_rb)
@@ -0,0 +1,59 @@
1
+ require "faraday"
2
+
3
+ module NetworkResiliency
4
+ module Adapter
5
+ class Faraday < ::Faraday::Middleware
6
+ def call(env)
7
+ puts "NetworkResiliency called: #{env.url}"
8
+ # env.options.timeout = 0.001
9
+ # open_timeout
10
+
11
+ # url => id
12
+ # predict time
13
+ # get dynamic timeout
14
+
15
+ if NetworkResiliency.enabled?
16
+ # temp update timeout
17
+ # with_timeout(...) { super }
18
+ else
19
+ super
20
+ end
21
+ rescue ::Faraday::Error => e
22
+ # note exception for ensure block
23
+ ensure
24
+ # record time taken
25
+
26
+ # reraise if applicable
27
+ raise e if e
28
+ end
29
+
30
+ def normalized_id(env)
31
+ env.url
32
+ end
33
+
34
+ def with_timeout(env, timeout)
35
+ old_timeouts = [
36
+ env.options.timeout,
37
+ env.options.open_timeout,
38
+ env.options.read_timeout,
39
+ env.options.write_timeout,
40
+ ]
41
+
42
+ env.options.timeout = [
43
+ env.options.timeout,
44
+ timeout,
45
+ ].compact.min
46
+
47
+ # env.options.open_timeout = [ env.options.open_timeout, timeout ].compact.min
48
+ # ...
49
+ ensure
50
+ env.options.timeout = old_timeouts[0]
51
+ # ...
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ Faraday::Request.register_middleware(
58
+ network_resiliency: NetworkResiliency::Adapter::Faraday,
59
+ )
@@ -0,0 +1,63 @@
1
+ require "redis"
2
+
3
+ module NetworkResiliency
4
+ module Adapter
5
+ module Redis
6
+ def connect(redis_config)
7
+ puts "RedisClient.connect(#{client.connect_timeout})"
8
+
9
+ old_timeout = client.connect_timeout
10
+
11
+ already_connected = client.connected?
12
+
13
+ # client.connect_timeout = 0.1
14
+ ts = -NetworkResiliency.timestamp
15
+ super
16
+ ensure
17
+ client.connect_timeout = old_timeout
18
+
19
+ ts += NetworkResiliency.timestamp
20
+
21
+ note = already_connected ? " (already_connected)" : ""
22
+
23
+ puts "connect time: #{ts}#{note}"
24
+ end
25
+
26
+ def call(command, redis_config)
27
+ puts "RedisClient.call"
28
+
29
+ key = "#{id}:call"
30
+ ts = -NetworkResiliency.timestamp
31
+ with_timeout(1) { super }
32
+ ensure
33
+ ts += NetworkResiliency.timestamp
34
+ puts "call time: #{ts}"
35
+
36
+ NetworkResiliency.record(self, id, ts)
37
+ end
38
+
39
+ def id
40
+ "redis:#{client.host}"
41
+ end
42
+
43
+ def with_timeout(timeout)
44
+ old_timeouts = [
45
+ client.connect_timeout,
46
+ client.read_timeout,
47
+ client.write_timeout,
48
+ ]
49
+
50
+ client.connect_timeout = timeout
51
+ client.read_timeout = timeout
52
+ client.write_timeout = timeout
53
+
54
+ yield
55
+ ensure
56
+ client.connect_timeout, client.read_timeout, client.write_timeout = *old_timeouts
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ # RedisClient.register(RedisAvenger)
63
+ # Redis.new(middlewares: [RedisAvenger])
@@ -0,0 +1,90 @@
1
+ module NetworkResiliency
2
+ class Stats
3
+ attr_reader :n, :avg
4
+
5
+ def self.from(n, avg, sq_dist)
6
+ new.tap do |instance|
7
+ instance.instance_eval do
8
+ @n = n
9
+ @avg = avg
10
+ @sq_dist = sq_dist
11
+ end
12
+ end
13
+ end
14
+
15
+ def initialize(values = [])
16
+ @n = 0
17
+ @avg = 0.0
18
+ @sq_dist = 0.0 # sum of squared distance from mean
19
+
20
+ values.each {|x| update(x) }
21
+ end
22
+
23
+ def <<(value)
24
+ case value
25
+ when Array
26
+ value.each {|x| update(x) }
27
+ when self.class
28
+ merge!(value)
29
+ else
30
+ update(value)
31
+ end
32
+
33
+ self
34
+ end
35
+
36
+ def variance(sample: false)
37
+ @sq_dist / (sample ? (@n - 1) : @n)
38
+ end
39
+
40
+ def stdev
41
+ Math.sqrt(variance)
42
+ end
43
+
44
+ def merge(other)
45
+ dup.merge!(other)
46
+ end
47
+ alias_method :+, :merge
48
+
49
+ def merge!(other)
50
+ raise ArgumentError unless other.is_a?(self.class)
51
+
52
+ prev_n = n
53
+ @n += other.n
54
+
55
+ delta = other.avg - avg
56
+ @avg += delta * other.n / n
57
+
58
+ @sq_dist += other.instance_variable_get(:@sq_dist)
59
+ @sq_dist += (delta ** 2) * prev_n * other.n / n
60
+
61
+ self
62
+ end
63
+
64
+ private
65
+
66
+ def update(value)
67
+ raise ArgumentError unless value.is_a?(Numeric)
68
+
69
+ @n += 1
70
+
71
+ prev_avg = @avg
72
+ @avg += (value - @avg) / @n
73
+
74
+ @sq_dist += (value - prev_avg) * (value - @avg)
75
+ # @sq_dist += (sq_dist - @sq_dist) / @n
76
+
77
+ # for x, w in data_weight_pairs:
78
+ # w_sum = w_sum + w
79
+ # mean_old = mean
80
+ # mean = mean_old + (w / w_sum) * (x - mean_old)
81
+ # S = S + w * (x - mean_old) * (x - mean)
82
+
83
+ # count += 1
84
+ # delta = newValue - mean
85
+ # mean += delta / count
86
+ # delta2 = newValue - mean
87
+ # M2 += delta * delta2
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,3 @@
1
+ module NetworkResiliency
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,123 @@
1
+ require "network_resiliency/stats"
2
+ require "network_resiliency/version"
3
+
4
+ module NetworkResiliency
5
+ extend self
6
+
7
+ def enabled?
8
+ true
9
+ end
10
+
11
+ def sample?
12
+ enabled? || rand < sample_rate
13
+ end
14
+
15
+ # def mode=(mode)
16
+ # unless [ :avg, :x10, :sig1, :sig2, :sig3 ]
17
+ # end
18
+
19
+ def timeout(adapter, key)
20
+ stats = self.stats(adapter, key)
21
+
22
+ stats.avg * 10 if stats.n >= 100
23
+ end
24
+
25
+ def stats(adapter, key)
26
+ store.get(
27
+ [
28
+ adapter.class.to_s.split("::")[-1],
29
+ key,
30
+ ].join(":"),
31
+ )
32
+ end
33
+
34
+ def record(adapter, key, milliseconds)
35
+ compound_key = [
36
+ adapter.class.to_s.split("::")[-1],
37
+ key,
38
+ ].join(":")
39
+
40
+ # normalize timestamp
41
+ milliseconds = [ milliseconds.round, 1 ].max
42
+
43
+ store.record(compound_key, milliseconds)
44
+ end
45
+
46
+ def timestamp
47
+ Process.clock_gettime(Process::CLOCK_MONOTONIC) * 1_000
48
+ end
49
+
50
+ def time
51
+ # if block_given?
52
+ ts = -timestamp
53
+ yield
54
+
55
+ ts += timestamp
56
+ end
57
+
58
+ def store
59
+ @store ||= MemoryStore.new
60
+ end
61
+
62
+ class Store
63
+ def get(key)
64
+ raise NotImplemented
65
+ end
66
+
67
+ def record(key, time)
68
+ raise NotImplemented
69
+ end
70
+
71
+ def flush
72
+ raise NotImplemented
73
+ end
74
+ end
75
+
76
+ class MemoryStore < Store
77
+ def initialize(substore = nil)
78
+ @substore = substore
79
+ @data = {}
80
+ end
81
+
82
+ def get(key)
83
+ @data[key] ||= @substore&.get(key) || Stats.new
84
+ end
85
+
86
+ def record(key, time)
87
+ get(key) << time
88
+ @substore&.record(key, time)
89
+
90
+ self
91
+ end
92
+
93
+ def flush
94
+ @substore&.flush
95
+ end
96
+ end
97
+
98
+ class RedisStore < Store
99
+ def initialize(redis)
100
+ @redis = redis
101
+ end
102
+
103
+ def cachekey(key)
104
+ [ NetworkResiliency, key ].join(":")
105
+ end
106
+ end
107
+ end
108
+
109
+
110
+ require "network_resiliency/adapter/faraday"
111
+ require "network_resiliency/adapter/redis"
112
+
113
+ # ms granularity, round up, floor(1)
114
+ #
115
+
116
+ # storage
117
+ # get(id) / record(id, time) / flush
118
+
119
+ # local storage
120
+ # - get(id) / save(id, time)
121
+ # remote storage
122
+ # - get(id) - sync
123
+ # - save(id, time) - buffer and async flush
@@ -0,0 +1,25 @@
1
+ require_relative "lib/network_resiliency/version"
2
+ package = NetworkResiliency
3
+
4
+ Gem::Specification.new do |s|
5
+ s.authors = ["Daniel Pepper"]
6
+ s.description = "..."
7
+ s.files = `git ls-files * ':!:spec'`.split("\n")
8
+ s.homepage = "https://github.com/dpep/network_resiliency_rb"
9
+ s.license = "MIT"
10
+ s.name = File.basename(__FILE__).split(".")[0]
11
+ s.summary = package.to_s
12
+ s.version = package.const_get "VERSION"
13
+
14
+ s.add_dependency "faraday"
15
+
16
+ s.add_development_dependency "byebug"
17
+ s.add_development_dependency "codecov"
18
+ s.add_development_dependency "faraday-rack"
19
+ s.add_development_dependency "rack"
20
+ s.add_development_dependency "rack-test"
21
+ s.add_development_dependency "redis"
22
+ s.add_development_dependency "redis-client", ">= 0.10"
23
+ s.add_development_dependency "rspec"
24
+ s.add_development_dependency "simplecov"
25
+ end
metadata ADDED
@@ -0,0 +1,193 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: network_resiliency
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Daniel Pepper
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-05-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: faraday
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: byebug
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: codecov
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: faraday-rack
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rack
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rack-test
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: redis
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: redis-client
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0.10'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0.10'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rspec
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: simplecov
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ description: "..."
154
+ email:
155
+ executables: []
156
+ extensions: []
157
+ extra_rdoc_files: []
158
+ files:
159
+ - CHANGELOG.md
160
+ - Gemfile
161
+ - Gemfile.lock
162
+ - LICENSE.txt
163
+ - README.md
164
+ - lib/network_resiliency.rb
165
+ - lib/network_resiliency/adapter/faraday.rb
166
+ - lib/network_resiliency/adapter/redis.rb
167
+ - lib/network_resiliency/stats.rb
168
+ - lib/network_resiliency/version.rb
169
+ - network_resiliency.gemspec
170
+ homepage: https://github.com/dpep/network_resiliency_rb
171
+ licenses:
172
+ - MIT
173
+ metadata: {}
174
+ post_install_message:
175
+ rdoc_options: []
176
+ require_paths:
177
+ - lib
178
+ required_ruby_version: !ruby/object:Gem::Requirement
179
+ requirements:
180
+ - - ">="
181
+ - !ruby/object:Gem::Version
182
+ version: '0'
183
+ required_rubygems_version: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - ">="
186
+ - !ruby/object:Gem::Version
187
+ version: '0'
188
+ requirements: []
189
+ rubygems_version: 3.3.7
190
+ signing_key:
191
+ specification_version: 4
192
+ summary: NetworkResiliency
193
+ test_files: []