launchdarkly-server-sdk 6.4.0 → 7.0.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/lib/ldclient-rb/config.rb +102 -56
- data/lib/ldclient-rb/context.rb +487 -0
- data/lib/ldclient-rb/evaluation_detail.rb +20 -20
- data/lib/ldclient-rb/events.rb +77 -132
- data/lib/ldclient-rb/flags_state.rb +4 -4
- data/lib/ldclient-rb/impl/big_segments.rb +17 -17
- data/lib/ldclient-rb/impl/context.rb +96 -0
- data/lib/ldclient-rb/impl/context_filter.rb +145 -0
- data/lib/ldclient-rb/impl/diagnostic_events.rb +9 -10
- data/lib/ldclient-rb/impl/evaluator.rb +379 -131
- data/lib/ldclient-rb/impl/evaluator_bucketing.rb +40 -41
- data/lib/ldclient-rb/impl/evaluator_helpers.rb +28 -31
- data/lib/ldclient-rb/impl/evaluator_operators.rb +26 -55
- data/lib/ldclient-rb/impl/event_sender.rb +6 -6
- data/lib/ldclient-rb/impl/event_summarizer.rb +12 -7
- data/lib/ldclient-rb/impl/event_types.rb +18 -30
- data/lib/ldclient-rb/impl/integrations/consul_impl.rb +7 -7
- data/lib/ldclient-rb/impl/integrations/dynamodb_impl.rb +29 -29
- data/lib/ldclient-rb/impl/integrations/file_data_source.rb +8 -8
- data/lib/ldclient-rb/impl/integrations/redis_impl.rb +92 -12
- data/lib/ldclient-rb/impl/model/clause.rb +39 -0
- data/lib/ldclient-rb/impl/model/feature_flag.rb +213 -0
- data/lib/ldclient-rb/impl/model/preprocessed_data.rb +8 -121
- data/lib/ldclient-rb/impl/model/segment.rb +126 -0
- data/lib/ldclient-rb/impl/model/serialization.rb +52 -12
- data/lib/ldclient-rb/impl/repeating_task.rb +1 -1
- data/lib/ldclient-rb/impl/store_data_set_sorter.rb +2 -2
- data/lib/ldclient-rb/impl/unbounded_pool.rb +1 -1
- data/lib/ldclient-rb/impl/util.rb +2 -2
- data/lib/ldclient-rb/in_memory_store.rb +2 -2
- data/lib/ldclient-rb/integrations/consul.rb +1 -1
- data/lib/ldclient-rb/integrations/dynamodb.rb +1 -1
- data/lib/ldclient-rb/integrations/file_data.rb +3 -3
- data/lib/ldclient-rb/integrations/redis.rb +4 -4
- data/lib/ldclient-rb/integrations/test_data/flag_builder.rb +218 -62
- data/lib/ldclient-rb/integrations/test_data.rb +16 -12
- data/lib/ldclient-rb/integrations/util/store_wrapper.rb +9 -9
- data/lib/ldclient-rb/interfaces.rb +14 -14
- data/lib/ldclient-rb/ldclient.rb +94 -144
- data/lib/ldclient-rb/memoized_value.rb +1 -1
- data/lib/ldclient-rb/non_blocking_thread_pool.rb +1 -1
- data/lib/ldclient-rb/polling.rb +2 -2
- data/lib/ldclient-rb/reference.rb +274 -0
- data/lib/ldclient-rb/requestor.rb +5 -5
- data/lib/ldclient-rb/stream.rb +7 -8
- data/lib/ldclient-rb/util.rb +4 -19
- data/lib/ldclient-rb/version.rb +1 -1
- data/lib/ldclient-rb.rb +2 -3
- metadata +34 -17
- data/lib/ldclient-rb/file_data_source.rb +0 -23
- data/lib/ldclient-rb/newrelic.rb +0 -17
- data/lib/ldclient-rb/redis_store.rb +0 -88
- data/lib/ldclient-rb/user_filter.rb +0 -52
@@ -1,4 +1,3 @@
|
|
1
|
-
|
2
1
|
module LaunchDarkly
|
3
2
|
module Impl
|
4
3
|
# Encapsulates the logic for percentage rollouts.
|
@@ -6,64 +5,64 @@ module LaunchDarkly
|
|
6
5
|
# Applies either a fixed variation or a rollout for a rule (or the fallthrough rule).
|
7
6
|
#
|
8
7
|
# @param flag [Object] the feature flag
|
9
|
-
# @param
|
10
|
-
# @param
|
11
|
-
# @return [Number] the variation index, or nil if there is an error
|
12
|
-
|
13
|
-
|
14
|
-
variation =
|
15
|
-
return variation, false
|
16
|
-
rollout =
|
8
|
+
# @param vr [LaunchDarkly::Impl::Model::VariationOrRollout] the variation/rollout properties
|
9
|
+
# @param context [LaunchDarkly::LDContext] the context properties
|
10
|
+
# @return [Array<[Number, nil], Boolean>] the variation index, or nil if there is an error
|
11
|
+
# @raise [InvalidReferenceException]
|
12
|
+
def self.variation_index_for_context(flag, vr, context)
|
13
|
+
variation = vr.variation
|
14
|
+
return variation, false unless variation.nil? # fixed variation
|
15
|
+
rollout = vr.rollout
|
17
16
|
return nil, false if rollout.nil?
|
18
|
-
variations = rollout
|
17
|
+
variations = rollout.variations
|
19
18
|
if !variations.nil? && variations.length > 0 # percentage rollout
|
20
|
-
|
19
|
+
rollout_is_experiment = rollout.is_experiment
|
20
|
+
bucket_by = rollout_is_experiment ? nil : rollout.bucket_by
|
21
|
+
bucket_by = 'key' if bucket_by.nil?
|
21
22
|
|
22
|
-
seed = rollout
|
23
|
-
bucket =
|
24
|
-
|
23
|
+
seed = rollout.seed
|
24
|
+
bucket = bucket_context(context, rollout.context_kind, flag.key, bucket_by, flag.salt, seed) # may not be present
|
25
|
+
in_experiment = rollout_is_experiment && !bucket.nil?
|
26
|
+
sum = 0
|
25
27
|
variations.each do |variate|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
sum += variate[:weight].to_f / 100000.0
|
31
|
-
if bucket < sum
|
32
|
-
return variate[:variation], !!in_experiment
|
28
|
+
sum += variate.weight.to_f / 100000.0
|
29
|
+
if bucket.nil? || bucket < sum
|
30
|
+
return variate.variation, in_experiment && !variate.untracked
|
33
31
|
end
|
34
32
|
end
|
35
|
-
# The
|
33
|
+
# The context's bucket value was greater than or equal to the end of the last bucket. This could happen due
|
36
34
|
# to a rounding error, or due to the fact that we are scaling to 100000 rather than 99999, or the flag
|
37
35
|
# data could contain buckets that don't actually add up to 100000. Rather than returning an error in
|
38
|
-
# this case (or changing the scaling, which would potentially change the results for *all*
|
39
|
-
# will simply put the
|
36
|
+
# this case (or changing the scaling, which would potentially change the results for *all* contexts), we
|
37
|
+
# will simply put the context in the last bucket.
|
40
38
|
last_variation = variations[-1]
|
41
|
-
|
42
|
-
|
43
|
-
[last_variation[:variation], in_experiment]
|
39
|
+
[last_variation.variation, in_experiment && !last_variation.untracked]
|
44
40
|
else # the rule isn't well-formed
|
45
41
|
[nil, false]
|
46
42
|
end
|
47
43
|
end
|
48
44
|
|
49
|
-
# Returns a
|
45
|
+
# Returns a context's bucket value as a floating-point value in `[0, 1)`.
|
50
46
|
#
|
51
|
-
# @param
|
47
|
+
# @param context [LDContext] the context properties
|
48
|
+
# @param context_kind [String, nil] the context kind to match against
|
52
49
|
# @param key [String] the feature flag key (or segment key, if this is for a segment rule)
|
53
|
-
# @param bucket_by [String|Symbol] the name of the
|
50
|
+
# @param bucket_by [String|Symbol] the name of the context attribute to be used for bucketing
|
54
51
|
# @param salt [String] the feature flag's or segment's salt value
|
55
|
-
# @return [
|
56
|
-
|
57
|
-
|
52
|
+
# @return [Float, nil] the bucket value, from 0 inclusive to 1 exclusive
|
53
|
+
# @raise [InvalidReferenceException] Raised if the clause.attribute is an invalid reference
|
54
|
+
def self.bucket_context(context, context_kind, key, bucket_by, salt, seed)
|
55
|
+
matched_context = context.individual_context(context_kind || LaunchDarkly::LDContext::KIND_DEFAULT)
|
56
|
+
return nil if matched_context.nil?
|
58
57
|
|
59
|
-
|
60
|
-
|
61
|
-
return 0.0
|
62
|
-
end
|
58
|
+
reference = (context_kind.nil? || context_kind.empty?) ? Reference.create_literal(bucket_by) : Reference.create(bucket_by)
|
59
|
+
raise InvalidReferenceException.new(reference.error) unless reference.error.nil?
|
63
60
|
|
64
|
-
|
65
|
-
|
66
|
-
|
61
|
+
context_value = matched_context.get_value_for_reference(reference)
|
62
|
+
return 0.0 if context_value.nil?
|
63
|
+
|
64
|
+
id_hash = bucketable_string_value(context_value)
|
65
|
+
return 0.0 if id_hash.nil?
|
67
66
|
|
68
67
|
if seed
|
69
68
|
hash_key = "%d.%s" % [seed, id_hash]
|
@@ -71,7 +70,7 @@ module LaunchDarkly
|
|
71
70
|
hash_key = "%s.%s.%s" % [key, salt, id_hash]
|
72
71
|
end
|
73
72
|
|
74
|
-
hash_val =
|
73
|
+
hash_val = Digest::SHA1.hexdigest(hash_key)[0..14]
|
75
74
|
hash_val.to_i(16) / Float(0xFFFFFFFFFFFFFFF)
|
76
75
|
end
|
77
76
|
|
@@ -6,48 +6,45 @@ require "ldclient-rb/evaluation_detail"
|
|
6
6
|
module LaunchDarkly
|
7
7
|
module Impl
|
8
8
|
module EvaluatorHelpers
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
def self.target_match_result(target, flag, logger = nil)
|
15
|
-
pre = target[:_preprocessed]
|
16
|
-
pre ? pre.match_result : evaluation_detail_for_variation(
|
17
|
-
flag, target[:variation], EvaluationReason::target_match, logger)
|
18
|
-
end
|
19
|
-
|
20
|
-
def self.prerequisite_failed_result(prereq, flag, logger = nil)
|
21
|
-
pre = prereq[:_preprocessed]
|
22
|
-
pre ? pre.failed_result : evaluation_detail_for_off_variation(
|
23
|
-
flag, EvaluationReason::prerequisite_failed(prereq[:key]), logger
|
24
|
-
)
|
25
|
-
end
|
26
|
-
|
27
|
-
def self.fallthrough_precomputed_results(flag)
|
28
|
-
pre = flag[:_preprocessed]
|
29
|
-
pre ? pre.fallthrough_factory : nil
|
30
|
-
end
|
31
|
-
|
32
|
-
def self.rule_precomputed_results(rule)
|
33
|
-
pre = rule[:_preprocessed]
|
34
|
-
pre ? pre.all_match_results : nil
|
35
|
-
end
|
36
|
-
|
9
|
+
#
|
10
|
+
# @param flag [LaunchDarkly::Impl::Model::FeatureFlag]
|
11
|
+
# @param reason [LaunchDarkly::EvaluationReason]
|
12
|
+
#
|
37
13
|
def self.evaluation_detail_for_off_variation(flag, reason, logger = nil)
|
38
|
-
index = flag
|
14
|
+
index = flag.off_variation
|
39
15
|
index.nil? ? EvaluationDetail.new(nil, nil, reason) : evaluation_detail_for_variation(flag, index, reason, logger)
|
40
16
|
end
|
41
17
|
|
18
|
+
#
|
19
|
+
# @param flag [LaunchDarkly::Impl::Model::FeatureFlag]
|
20
|
+
# @param index [Integer]
|
21
|
+
# @param reason [LaunchDarkly::EvaluationReason]
|
22
|
+
#
|
42
23
|
def self.evaluation_detail_for_variation(flag, index, reason, logger = nil)
|
43
|
-
vars = flag
|
24
|
+
vars = flag.variations
|
44
25
|
if index < 0 || index >= vars.length
|
45
|
-
logger.error("[LDClient] Data inconsistency in feature flag \"#{flag
|
26
|
+
logger.error("[LDClient] Data inconsistency in feature flag \"#{flag.key}\": invalid variation index") unless logger.nil?
|
46
27
|
EvaluationDetail.new(nil, nil, EvaluationReason::error(EvaluationReason::ERROR_MALFORMED_FLAG))
|
47
28
|
else
|
48
29
|
EvaluationDetail.new(vars[index], index, reason)
|
49
30
|
end
|
50
31
|
end
|
32
|
+
|
33
|
+
#
|
34
|
+
# @param context [LaunchDarkly::LDContext]
|
35
|
+
# @param kind [String, nil]
|
36
|
+
# @param keys [Enumerable<String>]
|
37
|
+
# @return [Boolean]
|
38
|
+
#
|
39
|
+
def self.context_key_in_target_list(context, kind, keys)
|
40
|
+
return false unless keys.is_a? Enumerable
|
41
|
+
return false if keys.empty?
|
42
|
+
|
43
|
+
matched_context = context.individual_context(kind || LaunchDarkly::LDContext::KIND_DEFAULT)
|
44
|
+
return false if matched_context.nil?
|
45
|
+
|
46
|
+
keys.include? matched_context.key
|
47
|
+
end
|
51
48
|
end
|
52
49
|
end
|
53
50
|
end
|
@@ -9,24 +9,24 @@ module LaunchDarkly
|
|
9
9
|
# Applies an operator to produce a boolean result.
|
10
10
|
#
|
11
11
|
# @param op [Symbol] one of the supported LaunchDarkly operators, as a symbol
|
12
|
-
# @param
|
12
|
+
# @param context_value the value of the context attribute that is referenced in the current clause (left-hand
|
13
13
|
# side of the expression)
|
14
|
-
# @param clause_value the constant value that `
|
14
|
+
# @param clause_value the constant value that `context_value` is being compared to (right-hand side of the
|
15
15
|
# expression)
|
16
16
|
# @return [Boolean] true if the expression should be considered a match; false if it is not a match, or
|
17
17
|
# if the values cannot be compared because they are of the wrong types, or if the operator is unknown
|
18
|
-
def self.apply(op,
|
18
|
+
def self.apply(op, context_value, clause_value)
|
19
19
|
case op
|
20
20
|
when :in
|
21
|
-
|
21
|
+
context_value == clause_value
|
22
22
|
when :startsWith
|
23
|
-
string_op(
|
23
|
+
string_op(context_value, clause_value, lambda { |a, b| a.start_with? b })
|
24
24
|
when :endsWith
|
25
|
-
string_op(
|
25
|
+
string_op(context_value, clause_value, lambda { |a, b| a.end_with? b })
|
26
26
|
when :contains
|
27
|
-
string_op(
|
27
|
+
string_op(context_value, clause_value, lambda { |a, b| a.include? b })
|
28
28
|
when :matches
|
29
|
-
string_op(
|
29
|
+
string_op(context_value, clause_value, lambda { |a, b|
|
30
30
|
begin
|
31
31
|
re = Regexp.new b
|
32
32
|
!re.match(a).nil?
|
@@ -35,76 +35,47 @@ module LaunchDarkly
|
|
35
35
|
end
|
36
36
|
})
|
37
37
|
when :lessThan
|
38
|
-
numeric_op(
|
38
|
+
numeric_op(context_value, clause_value, lambda { |a, b| a < b })
|
39
39
|
when :lessThanOrEqual
|
40
|
-
numeric_op(
|
40
|
+
numeric_op(context_value, clause_value, lambda { |a, b| a <= b })
|
41
41
|
when :greaterThan
|
42
|
-
numeric_op(
|
42
|
+
numeric_op(context_value, clause_value, lambda { |a, b| a > b })
|
43
43
|
when :greaterThanOrEqual
|
44
|
-
numeric_op(
|
44
|
+
numeric_op(context_value, clause_value, lambda { |a, b| a >= b })
|
45
45
|
when :before
|
46
|
-
date_op(
|
46
|
+
date_op(context_value, clause_value, lambda { |a, b| a < b })
|
47
47
|
when :after
|
48
|
-
date_op(
|
48
|
+
date_op(context_value, clause_value, lambda { |a, b| a > b })
|
49
49
|
when :semVerEqual
|
50
|
-
semver_op(
|
50
|
+
semver_op(context_value, clause_value, lambda { |a, b| a == b })
|
51
51
|
when :semVerLessThan
|
52
|
-
semver_op(
|
52
|
+
semver_op(context_value, clause_value, lambda { |a, b| a < b })
|
53
53
|
when :semVerGreaterThan
|
54
|
-
semver_op(
|
54
|
+
semver_op(context_value, clause_value, lambda { |a, b| a > b })
|
55
55
|
when :segmentMatch
|
56
56
|
# We should never reach this; it can't be evaluated based on just two parameters, because it requires
|
57
|
-
# looking up the segment from the data store. Instead, we special-case this operator in
|
57
|
+
# looking up the segment from the data store. Instead, we special-case this operator in clause_match_context.
|
58
58
|
false
|
59
59
|
else
|
60
60
|
false
|
61
61
|
end
|
62
62
|
end
|
63
63
|
|
64
|
-
# Retrieves the value of a user attribute by name.
|
65
|
-
#
|
66
|
-
# Built-in attributes correspond to top-level properties in the user object. They are treated as strings and
|
67
|
-
# non-string values are coerced to strings, except for `anonymous` which is meant to be a boolean if present
|
68
|
-
# and is not currently coerced. This behavior is consistent with earlier versions of the Ruby SDK, but is not
|
69
|
-
# guaranteed to be consistent with other SDKs, since the evaluator specification is based on the strongly-typed
|
70
|
-
# SDKs where it is not possible for an attribute to have the wrong type.
|
71
|
-
#
|
72
|
-
# Custom attributes correspond to properties within the `custom` property, if any, and can be of any type.
|
73
|
-
#
|
74
|
-
# @param user [Object] the user properties
|
75
|
-
# @param attribute [String|Symbol] the attribute to get, for instance `:key` or `:name` or `:some_custom_attr`
|
76
|
-
# @return the attribute value, or nil if the attribute is unknown
|
77
|
-
def self.user_value(user, attribute)
|
78
|
-
attribute = attribute.to_sym
|
79
|
-
if BUILTINS.include? attribute
|
80
|
-
value = user[attribute]
|
81
|
-
return nil if value.nil?
|
82
|
-
(attribute == :anonymous) ? value : value.to_s
|
83
|
-
elsif !user[:custom].nil?
|
84
|
-
user[:custom][attribute]
|
85
|
-
else
|
86
|
-
nil
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
64
|
private
|
91
65
|
|
92
|
-
BUILTINS = Set[:key, :secondary, :ip, :country, :email, :firstName, :lastName, :avatar, :name, :anonymous]
|
93
66
|
NUMERIC_VERSION_COMPONENTS_REGEX = Regexp.new("^[0-9.]*")
|
94
|
-
|
95
|
-
private_constant :BUILTINS
|
96
67
|
private_constant :NUMERIC_VERSION_COMPONENTS_REGEX
|
97
68
|
|
98
|
-
def self.string_op(
|
99
|
-
(
|
69
|
+
def self.string_op(context_value, clause_value, fn)
|
70
|
+
(context_value.is_a? String) && (clause_value.is_a? String) && fn.call(context_value, clause_value)
|
100
71
|
end
|
101
72
|
|
102
|
-
def self.numeric_op(
|
103
|
-
(
|
73
|
+
def self.numeric_op(context_value, clause_value, fn)
|
74
|
+
(context_value.is_a? Numeric) && (clause_value.is_a? Numeric) && fn.call(context_value, clause_value)
|
104
75
|
end
|
105
76
|
|
106
|
-
def self.date_op(
|
107
|
-
ud = to_date(
|
77
|
+
def self.date_op(context_value, clause_value, fn)
|
78
|
+
ud = to_date(context_value)
|
108
79
|
if !ud.nil?
|
109
80
|
cd = to_date(clause_value)
|
110
81
|
!cd.nil? && fn.call(ud, cd)
|
@@ -113,8 +84,8 @@ module LaunchDarkly
|
|
113
84
|
end
|
114
85
|
end
|
115
86
|
|
116
|
-
def self.semver_op(
|
117
|
-
uv = to_semver(
|
87
|
+
def self.semver_op(context_value, clause_value, fn)
|
88
|
+
uv = to_semver(context_value)
|
118
89
|
if !uv.nil?
|
119
90
|
cv = to_semver(clause_value)
|
120
91
|
!cv.nil? && fn.call(uv, cv)
|
@@ -8,7 +8,7 @@ module LaunchDarkly
|
|
8
8
|
EventSenderResult = Struct.new(:success, :must_shutdown, :time_from_server)
|
9
9
|
|
10
10
|
class EventSender
|
11
|
-
CURRENT_SCHEMA_VERSION =
|
11
|
+
CURRENT_SCHEMA_VERSION = 4
|
12
12
|
DEFAULT_RETRY_INTERVAL = 1
|
13
13
|
|
14
14
|
def initialize(sdk_key, config, http_client = nil, retry_interval = DEFAULT_RETRY_INTERVAL)
|
@@ -33,7 +33,7 @@ module LaunchDarkly
|
|
33
33
|
begin
|
34
34
|
http_client = @http_client_pool.acquire()
|
35
35
|
response = nil
|
36
|
-
|
36
|
+
2.times do |attempt|
|
37
37
|
if attempt > 0
|
38
38
|
@logger.warn { "[LDClient] Will retry posting events after #{@retry_interval} second" }
|
39
39
|
sleep(@retry_interval)
|
@@ -43,13 +43,13 @@ module LaunchDarkly
|
|
43
43
|
headers = {}
|
44
44
|
headers["content-type"] = "application/json"
|
45
45
|
Impl::Util.default_http_headers(@sdk_key, @config).each { |k, v| headers[k] = v }
|
46
|
-
|
46
|
+
unless is_diagnostic
|
47
47
|
headers["X-LaunchDarkly-Event-Schema"] = CURRENT_SCHEMA_VERSION.to_s
|
48
48
|
headers["X-LaunchDarkly-Payload-ID"] = payload_id
|
49
49
|
end
|
50
50
|
response = http_client.request("POST", uri, {
|
51
51
|
headers: headers,
|
52
|
-
body: event_data
|
52
|
+
body: event_data,
|
53
53
|
})
|
54
54
|
rescue StandardError => exn
|
55
55
|
@logger.warn { "[LDClient] Error sending events: #{exn.inspect}." }
|
@@ -60,7 +60,7 @@ module LaunchDarkly
|
|
60
60
|
body = response.to_s
|
61
61
|
if status >= 200 && status < 300
|
62
62
|
res_time = nil
|
63
|
-
|
63
|
+
unless response.headers["date"].nil?
|
64
64
|
begin
|
65
65
|
res_time = Time.httpdate(response.headers["date"])
|
66
66
|
rescue ArgumentError
|
@@ -77,7 +77,7 @@ module LaunchDarkly
|
|
77
77
|
end
|
78
78
|
end
|
79
79
|
# used up our retries
|
80
|
-
|
80
|
+
EventSenderResult.new(false, false, nil)
|
81
81
|
ensure
|
82
82
|
@http_client_pool.release(http_client)
|
83
83
|
end
|
@@ -1,15 +1,16 @@
|
|
1
1
|
require "ldclient-rb/impl/event_types"
|
2
|
+
require "set"
|
2
3
|
|
3
4
|
module LaunchDarkly
|
4
5
|
module Impl
|
5
6
|
EventSummary = Struct.new(:start_date, :end_date, :counters)
|
6
7
|
|
7
|
-
EventSummaryFlagInfo = Struct.new(:default, :versions)
|
8
|
+
EventSummaryFlagInfo = Struct.new(:default, :versions, :context_kinds)
|
8
9
|
|
9
10
|
EventSummaryFlagVariationCounter = Struct.new(:value, :count)
|
10
11
|
|
11
12
|
# Manages the state of summarizable information for the EventProcessor, including the
|
12
|
-
# event counters and
|
13
|
+
# event counters and context deduplication. Note that the methods of this class are
|
13
14
|
# deliberately not thread-safe; the EventProcessor is responsible for enforcing
|
14
15
|
# synchronization across both the summarizer and the event queue.
|
15
16
|
class EventSummarizer
|
@@ -22,26 +23,31 @@ module LaunchDarkly
|
|
22
23
|
|
23
24
|
# Adds this event to our counters, if it is a type of event we need to count.
|
24
25
|
def summarize_event(event)
|
25
|
-
return
|
26
|
+
return unless event.is_a?(LaunchDarkly::Impl::EvalEvent)
|
26
27
|
|
27
28
|
counters_for_flag = @counters[event.key]
|
28
29
|
if counters_for_flag.nil?
|
29
|
-
counters_for_flag = EventSummaryFlagInfo.new(event.default, Hash.new)
|
30
|
+
counters_for_flag = EventSummaryFlagInfo.new(event.default, Hash.new, Set.new)
|
30
31
|
@counters[event.key] = counters_for_flag
|
31
32
|
end
|
33
|
+
|
32
34
|
counters_for_flag_version = counters_for_flag.versions[event.version]
|
33
35
|
if counters_for_flag_version.nil?
|
34
36
|
counters_for_flag_version = Hash.new
|
35
37
|
counters_for_flag.versions[event.version] = counters_for_flag_version
|
36
38
|
end
|
39
|
+
|
40
|
+
counters_for_flag.context_kinds.merge(event.context.kinds)
|
41
|
+
|
37
42
|
variation_counter = counters_for_flag_version[event.variation]
|
38
43
|
if variation_counter.nil?
|
39
44
|
counters_for_flag_version[event.variation] = EventSummaryFlagVariationCounter.new(event.value, 1)
|
40
45
|
else
|
41
46
|
variation_counter.count = variation_counter.count + 1
|
42
47
|
end
|
48
|
+
|
43
49
|
time = event.timestamp
|
44
|
-
|
50
|
+
unless time.nil?
|
45
51
|
@start_date = time if @start_date == 0 || time < @start_date
|
46
52
|
@end_date = time if time > @end_date
|
47
53
|
end
|
@@ -49,8 +55,7 @@ module LaunchDarkly
|
|
49
55
|
|
50
56
|
# Returns a snapshot of the current summarized event data, and resets this state.
|
51
57
|
def snapshot
|
52
|
-
|
53
|
-
ret
|
58
|
+
EventSummary.new(@start_date, @end_date, @counters)
|
54
59
|
end
|
55
60
|
|
56
61
|
def clear
|
@@ -1,20 +1,23 @@
|
|
1
1
|
module LaunchDarkly
|
2
2
|
module Impl
|
3
3
|
class Event
|
4
|
-
|
4
|
+
# @param timestamp [Integer]
|
5
|
+
# @param context [LaunchDarkly::LDContext]
|
6
|
+
def initialize(timestamp, context)
|
5
7
|
@timestamp = timestamp
|
6
|
-
@
|
8
|
+
@context = context
|
7
9
|
end
|
8
10
|
|
11
|
+
# @return [Integer]
|
9
12
|
attr_reader :timestamp
|
10
|
-
|
11
|
-
attr_reader :
|
13
|
+
# @return [LaunchDarkly::LDContext]
|
14
|
+
attr_reader :context
|
12
15
|
end
|
13
16
|
|
14
17
|
class EvalEvent < Event
|
15
|
-
def initialize(timestamp,
|
18
|
+
def initialize(timestamp, context, key, version = nil, variation = nil, value = nil, reason = nil, default = nil,
|
16
19
|
track_events = false, debug_until = nil, prereq_of = nil)
|
17
|
-
super(timestamp,
|
20
|
+
super(timestamp, context)
|
18
21
|
@key = key
|
19
22
|
@version = version
|
20
23
|
@variation = variation
|
@@ -39,17 +42,17 @@ module LaunchDarkly
|
|
39
42
|
end
|
40
43
|
|
41
44
|
class IdentifyEvent < Event
|
42
|
-
def initialize(timestamp,
|
43
|
-
super(timestamp,
|
45
|
+
def initialize(timestamp, context)
|
46
|
+
super(timestamp, context)
|
44
47
|
end
|
45
48
|
end
|
46
49
|
|
47
50
|
class CustomEvent < Event
|
48
|
-
def initialize(timestamp,
|
49
|
-
super(timestamp,
|
51
|
+
def initialize(timestamp, context, key, data = nil, metric_value = nil)
|
52
|
+
super(timestamp, context)
|
50
53
|
@key = key
|
51
|
-
@data = data
|
52
|
-
@metric_value = metric_value
|
54
|
+
@data = data unless data.nil?
|
55
|
+
@metric_value = metric_value unless metric_value.nil?
|
53
56
|
end
|
54
57
|
|
55
58
|
attr_reader :key
|
@@ -57,30 +60,15 @@ module LaunchDarkly
|
|
57
60
|
attr_reader :metric_value
|
58
61
|
end
|
59
62
|
|
60
|
-
class AliasEvent < Event
|
61
|
-
def initialize(timestamp, key, context_kind, previous_key, previous_context_kind)
|
62
|
-
super(timestamp, nil)
|
63
|
-
@key = key
|
64
|
-
@context_kind = context_kind
|
65
|
-
@previous_key = previous_key
|
66
|
-
@previous_context_kind = previous_context_kind
|
67
|
-
end
|
68
|
-
|
69
|
-
attr_reader :key
|
70
|
-
attr_reader :context_kind
|
71
|
-
attr_reader :previous_key
|
72
|
-
attr_reader :previous_context_kind
|
73
|
-
end
|
74
|
-
|
75
63
|
class IndexEvent < Event
|
76
|
-
def initialize(timestamp,
|
77
|
-
super(timestamp,
|
64
|
+
def initialize(timestamp, context)
|
65
|
+
super(timestamp, context)
|
78
66
|
end
|
79
67
|
end
|
80
68
|
|
81
69
|
class DebugEvent < Event
|
82
70
|
def initialize(eval_event)
|
83
|
-
super(eval_event.timestamp, eval_event.
|
71
|
+
super(eval_event.timestamp, eval_event.context)
|
84
72
|
@eval_event = eval_event
|
85
73
|
end
|
86
74
|
|
@@ -16,14 +16,14 @@ module LaunchDarkly
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def initialize(opts)
|
19
|
-
|
19
|
+
unless CONSUL_ENABLED
|
20
20
|
raise RuntimeError.new("can't use Consul feature store without the 'diplomat' gem")
|
21
21
|
end
|
22
22
|
|
23
23
|
@prefix = (opts[:prefix] || LaunchDarkly::Integrations::Consul.default_prefix) + '/'
|
24
24
|
@logger = opts[:logger] || Config.default_logger
|
25
|
-
Diplomat.configuration = opts[:consul_config]
|
26
|
-
Diplomat.configuration.url = opts[:url]
|
25
|
+
Diplomat.configuration = opts[:consul_config] unless opts[:consul_config].nil?
|
26
|
+
Diplomat.configuration.url = opts[:url] unless opts[:url].nil?
|
27
27
|
@logger.info("ConsulFeatureStore: using Consul host at #{Diplomat.configuration.url}")
|
28
28
|
end
|
29
29
|
|
@@ -51,10 +51,10 @@ module LaunchDarkly
|
|
51
51
|
unused_old_keys.each do |key|
|
52
52
|
ops.push({ 'KV' => { 'Verb' => 'delete', 'Key' => key } })
|
53
53
|
end
|
54
|
-
|
54
|
+
|
55
55
|
# Now set the special key that we check in initialized_internal?
|
56
56
|
ops.push({ 'KV' => { 'Verb' => 'set', 'Key' => inited_key, 'Value' => '' } })
|
57
|
-
|
57
|
+
|
58
58
|
ConsulUtil.batch_operations(ops)
|
59
59
|
|
60
60
|
@logger.info { "Initialized database with #{num_items} items" }
|
@@ -70,7 +70,7 @@ module LaunchDarkly
|
|
70
70
|
results = Diplomat::Kv.get(kind_key(kind), { recurse: true }, :return)
|
71
71
|
(results == "" ? [] : results).each do |result|
|
72
72
|
value = result[:value]
|
73
|
-
|
73
|
+
unless value.nil?
|
74
74
|
item = Model.deserialize(kind, value)
|
75
75
|
items_out[item[:key].to_sym] = item
|
76
76
|
end
|
@@ -132,7 +132,7 @@ module LaunchDarkly
|
|
132
132
|
def kind_key(kind)
|
133
133
|
@prefix + kind[:namespace] + '/'
|
134
134
|
end
|
135
|
-
|
135
|
+
|
136
136
|
def inited_key
|
137
137
|
@prefix + '$inited'
|
138
138
|
end
|