determinator 2.3.1 → 2.5.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 +4 -4
- data/.circleci/config.yml +12 -0
- data/CHANGELOG.md +36 -0
- data/README.md +70 -1
- data/determinator.gemspec +2 -1
- data/lib/determinator.rb +3 -0
- data/lib/determinator/control.rb +34 -7
- data/lib/determinator/feature.rb +36 -6
- data/lib/determinator/fixed_determination.rb +20 -0
- data/lib/determinator/retrieve/in_memory_retriever.rb +1 -1
- data/lib/determinator/retrieve/null_retriever.rb +1 -0
- data/lib/determinator/serializers/json.rb +9 -8
- data/lib/determinator/tracking.rb +74 -0
- data/lib/determinator/tracking/context.rb +15 -0
- data/lib/determinator/tracking/determination.rb +24 -0
- data/lib/determinator/tracking/rack/middleware.rb +43 -0
- data/lib/determinator/tracking/request.rb +22 -0
- data/lib/determinator/tracking/sidekiq/middleware.rb +31 -0
- data/lib/determinator/tracking/tracker.rb +48 -0
- data/lib/determinator/version.rb +1 -1
- metadata +31 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b505247b6902fb3768389da7b5a20e625f95e5ccaec778ae7fb52e1808286b73
|
4
|
+
data.tar.gz: 659b93e86df258535f904bd90d91768767c5c1644cd578d3efe3f45a9c2aba6f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 753968e66ac00b3f0606eed33b5d1873ad23a89cce901b69e261f38263ff14f18b975a0f03e55146e8ea78dc397b1a48878f10c3b1fc6eadd4f278a78f2a7703
|
7
|
+
data.tar.gz: 46d183bc1e87d5031b43115bb63573503725f441a44603577274fef9817cd20722471bcba1b46d018beab4828bbcbf738c4de742aff0de26578178330edd2885
|
data/.circleci/config.yml
CHANGED
@@ -10,6 +10,10 @@ jobs:
|
|
10
10
|
steps:
|
11
11
|
- checkout
|
12
12
|
|
13
|
+
- run:
|
14
|
+
name: Install bundler
|
15
|
+
command: gem install bundler -v 2.1.4
|
16
|
+
|
13
17
|
- run:
|
14
18
|
name: Bundle Install
|
15
19
|
command: bundle install
|
@@ -30,6 +34,10 @@ jobs:
|
|
30
34
|
steps:
|
31
35
|
- checkout
|
32
36
|
|
37
|
+
- run:
|
38
|
+
name: Install bundler
|
39
|
+
command: gem install bundler -v 2.1.4
|
40
|
+
|
33
41
|
- run:
|
34
42
|
name: Bundle Install
|
35
43
|
command: bundle install
|
@@ -50,6 +58,10 @@ jobs:
|
|
50
58
|
steps:
|
51
59
|
- checkout
|
52
60
|
|
61
|
+
- run:
|
62
|
+
name: Install bundler
|
63
|
+
command: gem install bundler -v 2.1.4
|
64
|
+
|
53
65
|
- run:
|
54
66
|
name: Bundle Install
|
55
67
|
command: bundle install
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,39 @@
|
|
1
|
+
# 2.5.0
|
2
|
+
|
3
|
+
Feature:
|
4
|
+
- Add fixed determinations
|
5
|
+
|
6
|
+
# 2.4.4
|
7
|
+
|
8
|
+
Bug fix:
|
9
|
+
- Count repeated determinations instead of tracking separately
|
10
|
+
|
11
|
+
# 2.4.3
|
12
|
+
|
13
|
+
Feature:
|
14
|
+
- Add Sinatra endpoint tracking
|
15
|
+
|
16
|
+
Bug fix:
|
17
|
+
- Remove endpoint tracking of PATH_INFO
|
18
|
+
|
19
|
+
# 2.4.2
|
20
|
+
|
21
|
+
Feature:
|
22
|
+
- Add endpoint information to tracking request
|
23
|
+
|
24
|
+
Bug fix:
|
25
|
+
- Make tracking request "start" attribute an actual time
|
26
|
+
|
27
|
+
# 2.4.1
|
28
|
+
|
29
|
+
Bug fix:
|
30
|
+
- Update "fake" retrievers to match behaviour introduced in `v2.3.1` when a feature is missing
|
31
|
+
|
32
|
+
# 2.4.0
|
33
|
+
|
34
|
+
Feature:
|
35
|
+
- Add tracker middleware
|
36
|
+
|
1
37
|
# v2.3.1 (2019-05-25)
|
2
38
|
|
3
39
|
Feature:
|
data/README.md
CHANGED
@@ -204,7 +204,7 @@ variant = determinator.which_variant(
|
|
204
204
|
app_version: "1.2.3"
|
205
205
|
}
|
206
206
|
)
|
207
|
-
```
|
207
|
+
```
|
208
208
|
The `app_version` constraint for that flag needs to follow ruby gem version constraints. We support the following operators: `>, <, >=, <=, ~>`. For example:
|
209
209
|
`app_version: ">=1.2.0"`
|
210
210
|
|
@@ -220,6 +220,8 @@ Determinator.configure(retrieval: nil)
|
|
220
220
|
|
221
221
|
* Tag your rspec test with `:determinator_support`, so the `forced_determination` helper method will be available.
|
222
222
|
|
223
|
+
Please note, `RSpec::Determinator` mocks a determination _outcome_, not the process of choosing one. Set the `only_for` argument to be the properties you require for Determinator to return the specified outcome. At the moment this mock does not allow for the testing of the `id` or `guid` arguments (only the properties).
|
224
|
+
|
223
225
|
```ruby
|
224
226
|
RSpec.describe "something", :determinator_support do
|
225
227
|
|
@@ -251,6 +253,73 @@ end
|
|
251
253
|
|
252
254
|
* Check out [the specs for `RSpec::Determinator`](spec/rspec/determinator_spec.rb) to find out what you can do!
|
253
255
|
|
256
|
+
## Tracking
|
257
|
+
|
258
|
+
The library includes a middleware to track all determinations being made, allowing logging them at the end of the request
|
259
|
+
(including some useful request metrics).
|
260
|
+
|
261
|
+
To enable it, e.g. in Rails:
|
262
|
+
|
263
|
+
```ruby
|
264
|
+
# config/application.rb
|
265
|
+
|
266
|
+
require 'determinator/tracking/rack/middleware'
|
267
|
+
|
268
|
+
# possibly near the top of your stack, in case other middlewares make determinations
|
269
|
+
config.middleware.use Determinator::Tracking::Rack::Middleware
|
270
|
+
```
|
271
|
+
|
272
|
+
or for Sidekiq:
|
273
|
+
|
274
|
+
```ruby
|
275
|
+
# config/initializers/sidekiq.rb
|
276
|
+
|
277
|
+
require 'determinator/tracking/sidekiq/middleware'
|
278
|
+
|
279
|
+
Sidekiq.configure_server do |config|
|
280
|
+
config.server_middleware do |chain|
|
281
|
+
chain.add Determinator::Tracking::Sidekiq::Middleware
|
282
|
+
end
|
283
|
+
end
|
284
|
+
```
|
285
|
+
|
286
|
+
```ruby
|
287
|
+
# config/initializers/determinator.rb
|
288
|
+
|
289
|
+
require 'determinator/tracking'
|
290
|
+
|
291
|
+
Determinator::Tracking.on_request do |r|
|
292
|
+
Rails.logger.info("tag=determinator_request endpoint=#{r.endpoint} type=#{r.type} request_time=#{r.time} error=#{r.error?} response_status=#{r.attributes[:status]} sidekiq_queue=#{r.attributes[:queue]}")
|
293
|
+
r.determinations.each do |d|
|
294
|
+
Rails.logger.info("tag=determination id=#{d.id} guid=#{d.guid} flag=#{d.feature_id} result=#{d.determination}")
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
# The library sets the "endpoint" with information about the request or sidekiq job. If you
|
299
|
+
# have environment variables that further identify the service, e.g. ENV['APP_NAME'],
|
300
|
+
# you can configure the tracker to prepend it to the endpoint:
|
301
|
+
Determinator::Tracking.endpoint_env_vars = ['APP_NAME']
|
302
|
+
|
303
|
+
# If using an APM, you can provide trace information on the request by providing a get_context hook: e.g.
|
304
|
+
|
305
|
+
Determinator::Tracking.get_context do
|
306
|
+
span = Datadog.tracer.active_root_span
|
307
|
+
return unless span
|
308
|
+
Determinator::Tracking::Context.new(
|
309
|
+
request_id: span.trace_id,
|
310
|
+
service: span.service,
|
311
|
+
resource: span.resource,
|
312
|
+
type: span.type,
|
313
|
+
meta: span.meta
|
314
|
+
)
|
315
|
+
end
|
316
|
+
```
|
317
|
+
|
318
|
+
NOTE: determinations will only be recorded on the threads where Determinator::Tracking is initialised via the middleware. If offloading work away from these thread (for example, by spinning up new threads within a Rack request or a Sidekiq worker), make the determinations before, and pass them through to the new threads; or, if it's not possible, collect them manually and track them in the request's thread with
|
319
|
+
```
|
320
|
+
Determinator::Tracking.track(id, guid, feature, determination)
|
321
|
+
```
|
322
|
+
|
254
323
|
## Testing this library
|
255
324
|
|
256
325
|
This library makes use of the [Determinator Standard Tests](https://github.com/deliveroo/determinator-standard-tests) to ensure that it conforms to the same specification as determinator libraries in other languages. The standard tests can be updated to the latest ones available by updating the submodule:
|
data/determinator.gemspec
CHANGED
@@ -23,11 +23,12 @@ Gem::Specification.new do |spec|
|
|
23
23
|
spec.add_runtime_dependency "faraday"
|
24
24
|
spec.add_runtime_dependency "semantic", "~> 1.6"
|
25
25
|
|
26
|
-
spec.add_development_dependency "bundler", "~> 1.
|
26
|
+
spec.add_development_dependency "bundler", "~> 2.1.4"
|
27
27
|
spec.add_development_dependency "rake", "~> 10.0"
|
28
28
|
spec.add_development_dependency "rspec", "~> 3.0"
|
29
29
|
spec.add_development_dependency "rspec-its", "~> 1.2"
|
30
30
|
spec.add_development_dependency "guard-rspec", "~> 4.7"
|
31
31
|
spec.add_development_dependency "factory_girl", "~> 4.8"
|
32
32
|
spec.add_development_dependency 'webmock'
|
33
|
+
spec.add_development_dependency "sidekiq"
|
33
34
|
end
|
data/lib/determinator.rb
CHANGED
@@ -2,10 +2,12 @@ require 'determinator/version'
|
|
2
2
|
require 'determinator/control'
|
3
3
|
require 'determinator/feature'
|
4
4
|
require 'determinator/target_group'
|
5
|
+
require 'determinator/fixed_determination'
|
5
6
|
require 'determinator/cache/fetch_wrapper'
|
6
7
|
require 'determinator/serializers/json'
|
7
8
|
require 'determinator/missing_response'
|
8
9
|
require 'determinator/error_response'
|
10
|
+
require 'determinator/tracking'
|
9
11
|
|
10
12
|
|
11
13
|
module Determinator
|
@@ -83,6 +85,7 @@ module Determinator
|
|
83
85
|
end
|
84
86
|
|
85
87
|
def notice_determination(id, guid, feature, determination)
|
88
|
+
Determinator::Tracking.track(id, guid, feature, determination)
|
86
89
|
return unless @determination_callback
|
87
90
|
@determination_callback.call(id, guid, feature, determination)
|
88
91
|
end
|
data/lib/determinator/control.rb
CHANGED
@@ -81,6 +81,14 @@ module Determinator
|
|
81
81
|
|
82
82
|
return feature.override_value_for(id) if feature.overridden_for?(id)
|
83
83
|
|
84
|
+
fixed_determination = choose_fixed_determination(feature, properties)
|
85
|
+
# Given constraints have specified that this actor's determination should be fixed
|
86
|
+
if fixed_determination
|
87
|
+
return false unless fixed_determination.feature_on
|
88
|
+
return true unless feature.experiment?
|
89
|
+
return fixed_determination.variant
|
90
|
+
end
|
91
|
+
|
84
92
|
target_group = choose_target_group(feature, properties)
|
85
93
|
# Given constraints have excluded this actor from this experiment
|
86
94
|
return false unless target_group
|
@@ -106,23 +114,34 @@ module Determinator
|
|
106
114
|
false
|
107
115
|
end
|
108
116
|
|
117
|
+
def choose_fixed_determination(feature, properties)
|
118
|
+
# Keys and values must be strings
|
119
|
+
normalised_properties = normalise_properties(properties)
|
120
|
+
|
121
|
+
feature.fixed_determinations.find { |fd|
|
122
|
+
matches_constraints(normalised_properties, fd.constraints)
|
123
|
+
}
|
124
|
+
end
|
125
|
+
|
109
126
|
def choose_target_group(feature, properties)
|
110
127
|
# Keys and values must be strings
|
111
|
-
normalised_properties = properties
|
112
|
-
hash[name.to_s] = [*values].map(&:to_s)
|
113
|
-
end
|
128
|
+
normalised_properties = normalise_properties(properties)
|
114
129
|
|
115
130
|
feature.target_groups.select { |tg|
|
116
131
|
next false unless tg.rollout.between?(1, 65_536)
|
117
132
|
|
118
|
-
tg.constraints
|
119
|
-
present = [*normalised_properties[scope]]
|
120
|
-
fit && matches_requirements?(scope, required, present)
|
121
|
-
end
|
133
|
+
matches_constraints(normalised_properties, tg.constraints)
|
122
134
|
# Must choose target group deterministically, if more than one match
|
123
135
|
}.sort_by { |tg| tg.rollout }.last
|
124
136
|
end
|
125
137
|
|
138
|
+
def matches_constraints(normalised_properties, constraints)
|
139
|
+
constraints.reduce(true) do |fit, (scope, *required)|
|
140
|
+
present = [*normalised_properties[scope]]
|
141
|
+
fit && matches_requirements?(scope, required, present)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
126
145
|
def matches_requirements?(scope, required, present)
|
127
146
|
case scope
|
128
147
|
when "app_version" then has_any_app_version?(required, present)
|
@@ -205,5 +224,13 @@ module Determinator
|
|
205
224
|
|
206
225
|
raise ArgumentError, "A variant should have been found by this point, there is a bug in the code."
|
207
226
|
end
|
227
|
+
|
228
|
+
private
|
229
|
+
|
230
|
+
def normalise_properties(properties)
|
231
|
+
properties.each_with_object({}) do |(name, values), hash|
|
232
|
+
hash[name.to_s] = [*values].map(&:to_s)
|
233
|
+
end
|
234
|
+
end
|
208
235
|
end
|
209
236
|
end
|
data/lib/determinator/feature.rb
CHANGED
@@ -3,13 +3,14 @@ module Determinator
|
|
3
3
|
#
|
4
4
|
# @attr_reader [nil,Hash<String,Integer>] variants The variants for this experiment, with the name of the variant as the key and the weight as the value. Will be nil for non-experiments.
|
5
5
|
class Feature
|
6
|
-
attr_reader :name, :identifier, :bucket_type, :variants, :target_groups, :active, :winning_variant
|
6
|
+
attr_reader :name, :identifier, :bucket_type, :variants, :target_groups, :fixed_determinations, :active, :winning_variant
|
7
7
|
|
8
|
-
def initialize(name:, identifier:, bucket_type:, target_groups:, variants: {}, overrides: {}, active: false, winning_variant: nil)
|
8
|
+
def initialize(name:, identifier:, bucket_type:, target_groups:, fixed_determinations: [], variants: {}, overrides: {}, active: false, winning_variant: nil)
|
9
9
|
@name = name.to_s
|
10
|
-
@identifier =
|
10
|
+
@identifier = identifier.to_s
|
11
11
|
@variants = variants
|
12
12
|
@target_groups = parse_target_groups(target_groups)
|
13
|
+
@fixed_determinations = parse_fixed_determinations(fixed_determinations)
|
13
14
|
@winning_variant = parse_outcome(winning_variant, allow_exclusion: false)
|
14
15
|
@active = active
|
15
16
|
@bucket_type = bucket_type.to_sym
|
@@ -72,14 +73,43 @@ module Determinator
|
|
72
73
|
|
73
74
|
TargetGroup.new(
|
74
75
|
rollout: target_group['rollout'].to_i,
|
75
|
-
constraints: constraints
|
76
|
-
hash[key.to_s] = [*value].map(&:to_s)
|
77
|
-
end
|
76
|
+
constraints: parse_constraints(constraints)
|
78
77
|
)
|
79
78
|
|
80
79
|
# Invalid target groups are ignored
|
81
80
|
rescue
|
82
81
|
nil
|
83
82
|
end
|
83
|
+
|
84
|
+
def parse_fixed_determinations(fixed_determinations)
|
85
|
+
fixed_determinations.map(&method(:parse_fixed_determination)).compact
|
86
|
+
end
|
87
|
+
|
88
|
+
def parse_fixed_determination(fixed_determination)
|
89
|
+
return fixed_determination if fixed_determination.is_a? FixedDetermination
|
90
|
+
|
91
|
+
variant = fixed_determination['variant']
|
92
|
+
return nil if variant && !variants.keys.include?(variant)
|
93
|
+
|
94
|
+
# if a variant is present the fixed determination should always be on
|
95
|
+
return nil if variant && !fixed_determination['feature_on']
|
96
|
+
|
97
|
+
constraints = fixed_determination['constraints'].to_h
|
98
|
+
|
99
|
+
FixedDetermination.new(
|
100
|
+
feature_on: fixed_determination['feature_on'],
|
101
|
+
variant: variant,
|
102
|
+
constraints: parse_constraints(constraints)
|
103
|
+
)
|
104
|
+
# Invalid fixed determinations are ignored
|
105
|
+
rescue
|
106
|
+
nil
|
107
|
+
end
|
108
|
+
|
109
|
+
def parse_constraints(constraints)
|
110
|
+
constraints.each_with_object({}) do |(key, value), hash|
|
111
|
+
hash[key.to_s] = [*value].map(&:to_s)
|
112
|
+
end
|
113
|
+
end
|
84
114
|
end
|
85
115
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Determinator
|
2
|
+
class FixedDetermination
|
3
|
+
attr_reader :feature_on, :variant, :constraints
|
4
|
+
|
5
|
+
def initialize(feature_on:, variant:, constraints: {})
|
6
|
+
@feature_on = feature_on
|
7
|
+
@variant = variant
|
8
|
+
@constraints = constraints
|
9
|
+
end
|
10
|
+
|
11
|
+
def inspect
|
12
|
+
"<feature_on: #{feature_on}, variant: #{variant}, constraints: #{constraints}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def ==(other)
|
16
|
+
return false unless other.is_a?(self.class)
|
17
|
+
other.feature_on == feature_on && other.variant == variant && other.constraints == constraints
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -10,7 +10,7 @@ module Determinator
|
|
10
10
|
|
11
11
|
# @param name [string,symbol] The name of the feature to retrieve
|
12
12
|
def retrieve(name)
|
13
|
-
@features
|
13
|
+
@features.fetch(name.to_s, MissingResponse.new)
|
14
14
|
end
|
15
15
|
|
16
16
|
# @param feature [Determinator::Feature] The feature to store
|
@@ -12,14 +12,15 @@ module Determinator
|
|
12
12
|
obj = string_or_hash.is_a?(Hash) ? string_or_hash : ::JSON.parse(string_or_hash)
|
13
13
|
|
14
14
|
Determinator::Feature.new(
|
15
|
-
name:
|
16
|
-
identifier:
|
17
|
-
bucket_type:
|
18
|
-
active:
|
19
|
-
target_groups:
|
20
|
-
|
21
|
-
|
22
|
-
|
15
|
+
name: obj['name'],
|
16
|
+
identifier: obj['identifier'],
|
17
|
+
bucket_type: obj['bucket_type'],
|
18
|
+
active: (obj['active'] === true),
|
19
|
+
target_groups: obj['target_groups'],
|
20
|
+
fixed_determinations: obj['fixed_determinations'].to_a,
|
21
|
+
variants: obj['variants'].to_h,
|
22
|
+
overrides: obj['overrides'].to_h,
|
23
|
+
winning_variant: obj['winning_variant'].to_s,
|
23
24
|
)
|
24
25
|
end
|
25
26
|
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'determinator/tracking/tracker'
|
2
|
+
require 'determinator/tracking/context'
|
3
|
+
|
4
|
+
module Determinator
|
5
|
+
module Tracking
|
6
|
+
class << self
|
7
|
+
attr_reader :endpoint_env_vars
|
8
|
+
|
9
|
+
def instance
|
10
|
+
Thread.current[:determinator_tracker]
|
11
|
+
end
|
12
|
+
|
13
|
+
def start!(type)
|
14
|
+
Thread.current[:determinator_tracker] = Tracker.new(type)
|
15
|
+
end
|
16
|
+
|
17
|
+
def finish!(endpoint:, error:, **attributes)
|
18
|
+
return false unless started?
|
19
|
+
request = instance.finish!(endpoint: endpoint, error: error, **attributes)
|
20
|
+
clear!
|
21
|
+
report(request)
|
22
|
+
request
|
23
|
+
end
|
24
|
+
|
25
|
+
def clear!
|
26
|
+
Thread.current[:determinator_tracker] = nil
|
27
|
+
end
|
28
|
+
|
29
|
+
def started?
|
30
|
+
!!instance
|
31
|
+
end
|
32
|
+
|
33
|
+
def track(id, guid, feature, determination)
|
34
|
+
return false unless started?
|
35
|
+
instance.track(id, guid, feature, determination)
|
36
|
+
end
|
37
|
+
|
38
|
+
def on_request(&block)
|
39
|
+
@on_request = block
|
40
|
+
end
|
41
|
+
|
42
|
+
def report(request)
|
43
|
+
return unless @on_request
|
44
|
+
@on_request.call(request)
|
45
|
+
end
|
46
|
+
|
47
|
+
def get_context(&block)
|
48
|
+
@get_context = block
|
49
|
+
end
|
50
|
+
|
51
|
+
def context
|
52
|
+
return unless @get_context
|
53
|
+
@get_context.call
|
54
|
+
rescue
|
55
|
+
nil
|
56
|
+
end
|
57
|
+
|
58
|
+
def clear_hooks!
|
59
|
+
@on_request = nil
|
60
|
+
@get_context = nil
|
61
|
+
end
|
62
|
+
|
63
|
+
def endpoint_env_vars=(vars)
|
64
|
+
@endpoint_env_vars = Array(vars)
|
65
|
+
end
|
66
|
+
|
67
|
+
def collect_endpoint_info(parts)
|
68
|
+
endpoint = Array(Determinator::Tracking.endpoint_env_vars).map{ |v| ENV[v] }
|
69
|
+
endpoint += Array(parts)
|
70
|
+
endpoint.reject{ |p| p.nil? || p == ''}.join(' ')
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Determinator
|
2
|
+
module Tracking
|
3
|
+
class Context
|
4
|
+
attr_reader :request_id, :service, :resource
|
5
|
+
|
6
|
+
def initialize(request_id: nil, service: nil, resource: nil, type: nil, meta: {})
|
7
|
+
@request_id = request_id
|
8
|
+
@service = service
|
9
|
+
@resource = resource
|
10
|
+
@type = type
|
11
|
+
@meta = meta
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Determinator
|
2
|
+
module Tracking
|
3
|
+
class Determination
|
4
|
+
attr_reader :id, :guid, :feature_id, :determination
|
5
|
+
|
6
|
+
def initialize(id:, guid:, feature_id:, determination:)
|
7
|
+
@id = id
|
8
|
+
@guid = guid
|
9
|
+
@feature_id = feature_id
|
10
|
+
@determination = determination
|
11
|
+
end
|
12
|
+
|
13
|
+
def ==(other)
|
14
|
+
id == other.id && guid == other.guid && feature_id == other.feature_id && determination == other.determination
|
15
|
+
end
|
16
|
+
|
17
|
+
alias eql? ==
|
18
|
+
|
19
|
+
def hash
|
20
|
+
[id, guid, feature_id, determination].hash
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'determinator/tracking'
|
2
|
+
|
3
|
+
module Determinator
|
4
|
+
module Tracking
|
5
|
+
module Rack
|
6
|
+
class Middleware
|
7
|
+
def initialize(app)
|
8
|
+
@app = app
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(env)
|
12
|
+
Determinator::Tracking.start!(:rack)
|
13
|
+
status, headers, response = @app.call(env)
|
14
|
+
[status, headers, response]
|
15
|
+
rescue
|
16
|
+
error = true
|
17
|
+
raise
|
18
|
+
ensure
|
19
|
+
Determinator::Tracking.finish!(
|
20
|
+
status: status,
|
21
|
+
error: !!error,
|
22
|
+
endpoint: extract_endpoint(env)
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def extract_endpoint(env)
|
29
|
+
parts = if params = env['action_dispatch.request.path_parameters']
|
30
|
+
[env['REQUEST_METHOD'], [params[:controller], params[:action]].join('#')]
|
31
|
+
elsif env['sinatra.route']
|
32
|
+
[env['sinatra.route']]
|
33
|
+
else
|
34
|
+
[env['REQUEST_METHOD']]
|
35
|
+
end
|
36
|
+
Determinator::Tracking.collect_endpoint_info(parts)
|
37
|
+
rescue
|
38
|
+
env['PATH_INFO']
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Determinator
|
2
|
+
module Tracking
|
3
|
+
class Request
|
4
|
+
attr_reader :start, :type, :endpoint, :time, :error, :attributes, :determinations, :context
|
5
|
+
|
6
|
+
def initialize(start:, type:, endpoint:, time:, error:, attributes:, determinations:, context: nil)
|
7
|
+
@start = start
|
8
|
+
@type = type
|
9
|
+
@time = time
|
10
|
+
@error = error
|
11
|
+
@attributes = attributes
|
12
|
+
@determinations = determinations
|
13
|
+
@endpoint = endpoint
|
14
|
+
@context = context
|
15
|
+
end
|
16
|
+
|
17
|
+
def error?
|
18
|
+
error
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'determinator/tracking'
|
2
|
+
|
3
|
+
module Determinator
|
4
|
+
module Tracking
|
5
|
+
module Sidekiq
|
6
|
+
class Middleware
|
7
|
+
# @param [Object] worker the worker instance
|
8
|
+
# @param [Hash] job the full job payload
|
9
|
+
# * @see https://github.com/mperham/sidekiq/wiki/Job-Format
|
10
|
+
# @param [String] queue the name of the queue the job was pulled from
|
11
|
+
# @yield the next middleware in the chain or worker `perform` method
|
12
|
+
# @return [Void]
|
13
|
+
def call(worker, job, queue)
|
14
|
+
begin
|
15
|
+
Determinator::Tracking.start!(:sidekiq)
|
16
|
+
yield
|
17
|
+
rescue => ex
|
18
|
+
error = true
|
19
|
+
raise
|
20
|
+
ensure
|
21
|
+
Determinator::Tracking.finish!(
|
22
|
+
endpoint: Determinator::Tracking.collect_endpoint_info(worker.class.name),
|
23
|
+
queue: queue,
|
24
|
+
error: !!error
|
25
|
+
)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'determinator/tracking/determination'
|
2
|
+
require 'determinator/tracking/request'
|
3
|
+
|
4
|
+
module Determinator
|
5
|
+
module Tracking
|
6
|
+
class Tracker
|
7
|
+
attr_reader :type, :determinations
|
8
|
+
|
9
|
+
def initialize(type)
|
10
|
+
@determinations = Hash.new(0)
|
11
|
+
@type = type
|
12
|
+
@monotonic_start = now
|
13
|
+
@start = Time.now
|
14
|
+
end
|
15
|
+
|
16
|
+
def track(id, guid, feature, determination)
|
17
|
+
determinations[
|
18
|
+
Determinator::Tracking::Determination.new(
|
19
|
+
id: id,
|
20
|
+
guid: guid,
|
21
|
+
feature_id: feature.identifier,
|
22
|
+
determination: determination
|
23
|
+
)
|
24
|
+
] += 1
|
25
|
+
end
|
26
|
+
|
27
|
+
def finish!(endpoint:, error:, **attributes)
|
28
|
+
request_time = now - @monotonic_start
|
29
|
+
Determinator::Tracking::Request.new(
|
30
|
+
start: @start,
|
31
|
+
type: type,
|
32
|
+
time: request_time,
|
33
|
+
endpoint: endpoint,
|
34
|
+
error: error,
|
35
|
+
attributes: attributes,
|
36
|
+
determinations: determinations,
|
37
|
+
context: Determinator::Tracking.context
|
38
|
+
)
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def now
|
44
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/determinator/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: determinator
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- JP Hastings-Spital
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-06-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|
@@ -44,14 +44,14 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
47
|
+
version: 2.1.4
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
54
|
+
version: 2.1.4
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: rake
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -136,7 +136,21 @@ dependencies:
|
|
136
136
|
- - ">="
|
137
137
|
- !ruby/object:Gem::Version
|
138
138
|
version: '0'
|
139
|
-
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: sidekiq
|
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:
|
140
154
|
email:
|
141
155
|
- jp@deliveroo.co.uk
|
142
156
|
executables: []
|
@@ -196,6 +210,7 @@ files:
|
|
196
210
|
- lib/determinator/control.rb
|
197
211
|
- lib/determinator/error_response.rb
|
198
212
|
- lib/determinator/feature.rb
|
213
|
+
- lib/determinator/fixed_determination.rb
|
199
214
|
- lib/determinator/missing_response.rb
|
200
215
|
- lib/determinator/retrieve/dynaconf.rb
|
201
216
|
- lib/determinator/retrieve/file.rb
|
@@ -204,13 +219,20 @@ files:
|
|
204
219
|
- lib/determinator/retrieve/null_retriever.rb
|
205
220
|
- lib/determinator/serializers/json.rb
|
206
221
|
- lib/determinator/target_group.rb
|
222
|
+
- lib/determinator/tracking.rb
|
223
|
+
- lib/determinator/tracking/context.rb
|
224
|
+
- lib/determinator/tracking/determination.rb
|
225
|
+
- lib/determinator/tracking/rack/middleware.rb
|
226
|
+
- lib/determinator/tracking/request.rb
|
227
|
+
- lib/determinator/tracking/sidekiq/middleware.rb
|
228
|
+
- lib/determinator/tracking/tracker.rb
|
207
229
|
- lib/determinator/version.rb
|
208
230
|
- lib/rspec/determinator.rb
|
209
231
|
homepage: https://github.com/deliveroo/determinator
|
210
232
|
licenses:
|
211
233
|
- MIT
|
212
234
|
metadata: {}
|
213
|
-
post_install_message:
|
235
|
+
post_install_message:
|
214
236
|
rdoc_options: []
|
215
237
|
require_paths:
|
216
238
|
- lib
|
@@ -225,9 +247,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
225
247
|
- !ruby/object:Gem::Version
|
226
248
|
version: '0'
|
227
249
|
requirements: []
|
228
|
-
|
229
|
-
|
230
|
-
signing_key:
|
250
|
+
rubygems_version: 3.0.6
|
251
|
+
signing_key:
|
231
252
|
specification_version: 4
|
232
253
|
summary: Determine which experiments and features a specific actor should see.
|
233
254
|
test_files: []
|