Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
cbd91bc
implement summary events
eli-darkly Mar 17, 2018
8156e4f
fix debugEventsUntilDate logic to use whichever cutoff time is later
eli-darkly Mar 20, 2018
19ee62f
unit tests for last known server time logic
eli-darkly Mar 20, 2018
3f5cb37
Merge pull request #42 from launchdarkly/eb/ch12904/summary-events
eli-darkly Mar 22, 2018
12c7a56
misc fixes to summary events
eli-darkly Mar 22, 2018
0e03c20
Merge branch 'summary-events' into eb/ch12904/summary-events-fixes
eli-darkly Mar 22, 2018
638a178
create classes for message types
eli-darkly Mar 22, 2018
05b5a1b
Merge pull request #45 from launchdarkly/eb/ch12904/summary-events-fixes
eli-darkly Mar 22, 2018
ccbbaee
Merge branch 'summary-events' into eb/ch12904/summary-events-fixes-2
eli-darkly Mar 26, 2018
25dd051
misc reorganization of event processor/summarizer classes
eli-darkly Mar 26, 2018
a79b604
Merge branch 'master' into summary-events
eli-darkly Mar 26, 2018
e9d7e6e
Merge branch 'summary-events' into eb/ch12904/summary-events-fixes-2
eli-darkly Mar 26, 2018
71627bd
more specific rescue
eli-darkly Mar 27, 2018
71399ef
Merge pull request #46 from launchdarkly/eb/ch12904/summary-events-fi…
eli-darkly Mar 27, 2018
98cc54e
make flushes asynchronous, but shutdown must wait till everything is …
eli-darkly Mar 28, 2018
7fe114b
Merge branch 'master' into summary-events
eli-darkly Mar 30, 2018
a1e78de
Merge branch 'summary-events' into eb/ch12904/summary-events-fixes-3
eli-darkly Mar 30, 2018
b836b41
Merge pull request #47 from launchdarkly/eb/ch12904/summary-events-fi…
eli-darkly Apr 3, 2018
4b83648
misc reorganization
eli-darkly Apr 3, 2018
eb41520
don't queue a flush task if we've reached pool limit
eli-darkly Apr 3, 2018
4781d84
rm debugging, misc fixes
eli-darkly Apr 3, 2018
1eff6d7
encapsulate thread pool logic in a class
eli-darkly Apr 4, 2018
115a2fc
Merge pull request #49 from launchdarkly/eb/summary-events-fixes-4
eli-darkly Apr 4, 2018
ba74bb6
Merge branch 'master' into summary-events
eli-darkly Apr 4, 2018
836a85d
fix behavior of debug & index events
eli-darkly Apr 9, 2018
3d87ad2
add unit test
eli-darkly Apr 9, 2018
59a1811
Merge pull request #51 from launchdarkly/eb/summary-events-debug-index
eli-darkly Apr 10, 2018
bda8a00
retry event post once after 5xx status or connection error
eli-darkly Apr 10, 2018
7c3e9b0
narrower begin/rescue block
eli-darkly Apr 10, 2018
18e8aa4
uncomment tests
eli-darkly Apr 10, 2018
fc7da77
use each instead of do
eli-darkly Apr 11, 2018
d5e7a04
rm unused
eli-darkly Apr 11, 2018
1195fa0
Merge pull request #52 from launchdarkly/eb/ch15679/retry-event-post
eli-darkly Apr 11, 2018
ef85149
set schema header in event payload
eli-darkly Apr 18, 2018
b80b4b0
Merge pull request #54 from launchdarkly/eb/event-schema
eli-darkly Apr 19, 2018
befade0
fix error in client initialization
eli-darkly Apr 20, 2018
d714744
Merge pull request #55 from launchdarkly/eb/fix-initialization
eli-darkly Apr 20, 2018
3bfc752
fix bug in evaluation when prerequisite is missing
eli-darkly Apr 20, 2018
d1b6060
Merge pull request #56 from launchdarkly/eb/fix-evaluation
eli-darkly Apr 20, 2018
6d872dc
fixed omission of variation in feature request event
eli-darkly Apr 21, 2018
8446266
Merge pull request #57 from launchdarkly/eb/fix-summary-events-variation
eli-darkly Apr 21, 2018
6533867
re-add redundant key property to identify event
eli-darkly Apr 23, 2018
9ad3a14
nil guard on user
eli-darkly Apr 23, 2018
f478922
Merge pull request #58 from launchdarkly/eb/fix-identify-event
eli-darkly Apr 24, 2018
5134d8f
fix timed event flushing
eli-darkly Apr 24, 2018
d3e6d9f
fix user cache clearing
eli-darkly Apr 24, 2018
41c02d0
tolerate missing user in event
eli-darkly Apr 24, 2018
d94c058
fix value in feature event
eli-darkly Apr 24, 2018
c15593d
Merge pull request #59 from launchdarkly/eb/fix-event-flush
eli-darkly Apr 24, 2018
185f2d4
send as much of a feature event as possible even if user is invalid
eli-darkly Apr 25, 2018
e1f3696
Merge pull request #60 from launchdarkly/eb/events-for-bad-users
eli-darkly Apr 25, 2018
2ce4971
use our own lightweight expiring cache instead of moneta
eli-darkly Apr 26, 2018
072217d
rm unnecessary instance vars and obsolete comment
eli-darkly Apr 27, 2018
d343a88
Merge pull request #62 from launchdarkly/eb/ch16609/remove-moneta-2
eli-darkly Apr 27, 2018
778eb66
Merge branch 'master' into summary-events
eli-darkly Apr 27, 2018
7358fac
include variation index in events and bump schema version to 3
eli-darkly Apr 30, 2018
957a5bc
Merge pull request #63 from launchdarkly/eb/var-index-and-version
eli-darkly Apr 30, 2018
11c23b5
don't build in JRuby 1.7
eli-darkly May 4, 2018
593e006
Merge pull request #64 from launchdarkly/eb/ch15972/drop-jruby-1.7
eli-darkly May 4, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ jobs:
machine:
image: circleci/classic:latest
environment:
- RUBIES: "ruby-2.1.9 ruby-2.0.0 ruby-1.9.3 jruby-1.7.22 jruby-9.0.5.0"
- RUBIES: "ruby-2.1.9 ruby-2.0.0 ruby-1.9.3 jruby-9.0.5.0"
steps:
- run: sudo apt-get -q update
- run: sudo apt-get -qy install redis-server
Expand Down
2 changes: 1 addition & 1 deletion ldclient-rb.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ Gem::Specification.new do |spec|
spec.add_development_dependency "codeclimate-test-reporter", "~> 0"
spec.add_development_dependency "redis", "~> 3.3.5"
spec.add_development_dependency "connection_pool", ">= 2.1.2"
spec.add_development_dependency "moneta", "~> 1.0.0"
if RUBY_VERSION >= "2.0.0"
spec.add_development_dependency "rake", "~> 10.0"
spec.add_development_dependency "rspec_junit_formatter", "~> 0.3.0"
else
spec.add_development_dependency "rake", "12.1.0"
# higher versions of rake fail to install in JRuby 1.7
end
spec.add_development_dependency "timecop", "~> 0.9.1"

spec.add_runtime_dependency "json", [">= 1.8", "< 3"]
if RUBY_VERSION >= "2.1.0"
Expand Down
6 changes: 5 additions & 1 deletion lib/ldclient-rb.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@
require "ldclient-rb/evaluation"
require "ldclient-rb/ldclient"
require "ldclient-rb/cache_store"
require "ldclient-rb/expiring_cache"
require "ldclient-rb/memoized_value"
require "ldclient-rb/in_memory_store"
require "ldclient-rb/config"
require "ldclient-rb/newrelic"
require "ldclient-rb/stream"
require "ldclient-rb/polling"
require "ldclient-rb/event_serializer"
require "ldclient-rb/user_filter"
require "ldclient-rb/simple_lru_cache"
require "ldclient-rb/non_blocking_thread_pool"
require "ldclient-rb/event_summarizer"
require "ldclient-rb/events"
require "ldclient-rb/redis_store"
require "ldclient-rb/requestor"
42 changes: 41 additions & 1 deletion lib/ldclient-rb/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,15 @@ class Config
# @option opts [Boolean] :send_events (true) Whether or not to send events back to LaunchDarkly.
# This differs from `offline` in that it affects only the sending of client-side events, not
# streaming or polling for events from the server.
#
# @option opts [Integer] :user_keys_capacity (1000) The number of user keys that the event processor
# can remember at any one time, so that duplicate user details will not be sent in analytics events.
# @option opts [Float] :user_keys_flush_interval (300) The interval in seconds at which the event
# processor will reset its set of known user keys.
# @option opts [Boolean] :inline_users_in_events (false) Whether to include full user details in every
# analytics event. By default, events will only include the user key, except for one "index" event
# that provides the full details for the user.
# @option opts [Object] :update_processor An object that will receive feature flag data from LaunchDarkly.
# Defaults to either the streaming or the polling processor, can be customized for tests.
# @return [type] [description]
# rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity
def initialize(opts = {})
Expand All @@ -76,6 +84,10 @@ def initialize(opts = {})
@all_attributes_private = opts[:all_attributes_private] || false
@private_attribute_names = opts[:private_attribute_names] || []
@send_events = opts.has_key?(:send_events) ? opts[:send_events] : Config.default_send_events
@user_keys_capacity = opts[:user_keys_capacity] || Config.default_user_keys_capacity
@user_keys_flush_interval = opts[:user_keys_flush_interval] || Config.default_user_keys_flush_interval
@inline_users_in_events = opts[:inline_users_in_events] || false
@update_processor = opts[:update_processor]
end

#
Expand Down Expand Up @@ -186,6 +198,26 @@ def offline?
#
attr_reader :send_events

#
# The number of user keys that the event processor can remember at any one time, so that
# duplicate user details will not be sent in analytics events.
#
attr_reader :user_keys_capacity

#
# The interval in seconds at which the event processor will reset its set of known user keys.
#
attr_reader :user_keys_flush_interval

#
# Whether to include full user details in every
# analytics event. By default, events will only include the user key, except for one "index" event
# that provides the full details for the user.
#
attr_reader :inline_users_in_events

attr_reader :update_processor

#
# The default LaunchDarkly client configuration. This configuration sets
# reasonable defaults for most users.
Expand Down Expand Up @@ -264,5 +296,13 @@ def self.default_poll_interval
def self.default_send_events
true
end

def self.default_user_keys_capacity
1000
end

def self.default_user_keys_flush_interval
300
end
end
end
50 changes: 33 additions & 17 deletions lib/ldclient-rb/evaluation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ class EvaluationError < StandardError
# generated during prerequisite evaluation. Raises EvaluationError if the flag is not well-formed
# Will return nil, but not raise an exception, indicating that the rules (including fallthrough) did not match
# In that case, the caller should return the default value.
def evaluate(flag, user, store)
def evaluate(flag, user, store, logger)
if flag.nil?
raise EvaluationError, "Flag does not exist"
end
Expand All @@ -126,20 +126,23 @@ def evaluate(flag, user, store)
events = []

if flag[:on]
res = eval_internal(flag, user, store, events)

return { value: res, events: events } if !res.nil?
res = eval_internal(flag, user, store, events, logger)
if !res.nil?
res[:events] = events
return res
end
end

if !flag[:offVariation].nil? && flag[:offVariation] < flag[:variations].length
value = flag[:variations][flag[:offVariation]]
return { value: value, events: events }
offVariation = flag[:offVariation]
if !offVariation.nil? && offVariation < flag[:variations].length
value = flag[:variations][offVariation]
return { variation: offVariation, value: value, events: events }
end

{ value: nil, events: events }
{ variation: nil, value: nil, events: events }
end

def eval_internal(flag, user, store, events)
def eval_internal(flag, user, store, events, logger)
failed_prereq = false
# Evaluate prerequisites, if any
(flag[:prerequisites] || []).each do |prerequisite|
Expand All @@ -149,14 +152,23 @@ def eval_internal(flag, user, store, events)
failed_prereq = true
else
begin
prereq_res = eval_internal(prereq_flag, user, store, events)
variation = get_variation(prereq_flag, prerequisite[:variation])
events.push(kind: "feature", key: prereq_flag[:key], value: prereq_res, version: prereq_flag[:version], prereqOf: flag[:key])
if prereq_res.nil? || prereq_res != variation
prereq_res = eval_internal(prereq_flag, user, store, events, logger)
event = {
kind: "feature",
key: prereq_flag[:key],
variation: prereq_res.nil? ? nil : prereq_res[:variation],
value: prereq_res.nil? ? nil : prereq_res[:value],
version: prereq_flag[:version],
prereqOf: flag[:key],
trackEvents: prereq_flag[:trackEvents],
debugEventsUntilDate: prereq_flag[:debugEventsUntilDate]
}
events.push(event)
if prereq_res.nil? || prereq_res[:variation] != prerequisite[:variation]
failed_prereq = true
end
rescue => exn
@config.logger.error { "[LDClient] Error evaluating prerequisite: #{exn.inspect}" }
logger.error { "[LDClient] Error evaluating prerequisite: #{exn.inspect}" }
failed_prereq = true
end
end
Expand All @@ -175,7 +187,9 @@ def eval_rules(flag, user, store)
# Check user target matches
(flag[:targets] || []).each do |target|
(target[:values] || []).each do |value|
return get_variation(flag, target[:variation]) if value == user[:key]
if value == user[:key]
return { variation: target[:variation], value: get_variation(flag, target[:variation]) }
end
end
end

Expand Down Expand Up @@ -245,15 +259,17 @@ def clause_match_user_no_segments(clause, user)

def variation_for_user(rule, user, flag)
if !rule[:variation].nil? # fixed variation
return get_variation(flag, rule[:variation])
return { variation: rule[:variation], value: get_variation(flag, rule[:variation]) }
elsif !rule[:rollout].nil? # percentage rollout
rollout = rule[:rollout]
bucket_by = rollout[:bucketBy].nil? ? "key" : rollout[:bucketBy]
bucket = bucket_user(user, flag[:key], bucket_by, flag[:salt])
sum = 0;
rollout[:variations].each do |variate|
sum += variate[:weight].to_f / 100000.0
return get_variation(flag, variate[:variation]) if bucket < sum
if bucket < sum
return { variation: variate[:variation], value: get_variation(flag, variate[:variation]) }
end
end
nil
else # the rule isn't well-formed
Expand Down
52 changes: 52 additions & 0 deletions lib/ldclient-rb/event_summarizer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@

module LaunchDarkly
EventSummary = Struct.new(:start_date, :end_date, :counters)

# Manages the state of summarizable information for the EventProcessor, including the
# event counters and user deduplication. Note that the methods of this class are
# deliberately not thread-safe; the EventProcessor is responsible for enforcing
# synchronization across both the summarizer and the event queue.
class EventSummarizer
def initialize
clear
end

# Adds this event to our counters, if it is a type of event we need to count.
def summarize_event(event)
if event[:kind] == "feature"
counter_key = {
key: event[:key],
version: event[:version],
variation: event[:variation]
}
c = @counters[counter_key]
if c.nil?
@counters[counter_key] = {
value: event[:value],
default: event[:default],
count: 1
}
else
c[:count] = c[:count] + 1
end
time = event[:creationDate]
if !time.nil?
@start_date = time if @start_date == 0 || time < @start_date
@end_date = time if time > @end_date
end
end
end

# Returns a snapshot of the current summarized event data, and resets this state.
def snapshot
ret = EventSummary.new(@start_date, @end_date, @counters)
ret
end

def clear
@start_date = 0
@end_date = 0
@counters = {}
end
end
end
Loading