launchdarkly-server-sdk 6.3.1 → 6.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -15,8 +15,66 @@ module LaunchDarkly
15
15
  ret["X-LaunchDarkly-Wrapper"] = config.wrapper_name +
16
16
  (config.wrapper_version ? "/" + config.wrapper_version : "")
17
17
  end
18
+
19
+ app_value = application_header_value config.application
20
+ ret["X-LaunchDarkly-Tags"] = app_value unless app_value.nil? || app_value.empty?
21
+
18
22
  ret
19
23
  end
24
+
25
+ #
26
+ # Generate an HTTP Header value containing the application meta information (@see #application).
27
+ #
28
+ # @return [String]
29
+ #
30
+ def self.application_header_value(application)
31
+ parts = []
32
+ unless application[:id].empty?
33
+ parts << "application-id/#{application[:id]}"
34
+ end
35
+
36
+ unless application[:version].empty?
37
+ parts << "application-version/#{application[:version]}"
38
+ end
39
+
40
+ parts.join(" ")
41
+ end
42
+
43
+ #
44
+ # @param value [String]
45
+ # @param name [Symbol]
46
+ # @param logger [Logger]
47
+ # @return [String]
48
+ #
49
+ def self.validate_application_value(value, name, logger)
50
+ value = value.to_s
51
+
52
+ return "" if value.empty?
53
+
54
+ if value.length > 64
55
+ logger.warn { "Value of application[#{name}] was longer than 64 characters and was discarded" }
56
+ return ""
57
+ end
58
+
59
+ if value.match(/[^a-zA-Z0-9._-]/)
60
+ logger.warn { "Value of application[#{name}] contained invalid characters and was discarded" }
61
+ return ""
62
+ end
63
+
64
+ value
65
+ end
66
+
67
+ #
68
+ # @param app [Hash]
69
+ # @param logger [Logger]
70
+ # @return [Hash]
71
+ #
72
+ def self.validate_application_info(app, logger)
73
+ {
74
+ id: validate_application_value(app[:id], :id, logger),
75
+ version: validate_application_value(app[:version], :version, logger),
76
+ }
77
+ end
20
78
  end
21
79
  end
22
80
  end
@@ -36,7 +36,7 @@ module LaunchDarkly
36
36
  # @option opts [Integer] :capacity (1000) maximum number of items in the cache
37
37
  # @return [LaunchDarkly::Interfaces::FeatureStore] a feature store object
38
38
  #
39
- def self.new_feature_store(opts, &block)
39
+ def self.new_feature_store(opts = {})
40
40
  core = LaunchDarkly::Impl::Integrations::Consul::ConsulFeatureStoreCore.new(opts)
41
41
  return LaunchDarkly::Integrations::Util::CachingStoreWrapper.new(core, opts)
42
42
  end
@@ -46,7 +46,7 @@ module LaunchDarkly
46
46
  # @option opts [Integer] :capacity (1000) maximum number of items in the cache
47
47
  # @return [LaunchDarkly::Interfaces::FeatureStore] a feature store object
48
48
  #
49
- def self.new_feature_store(table_name, opts)
49
+ def self.new_feature_store(table_name, opts = {})
50
50
  core = LaunchDarkly::Impl::Integrations::DynamoDB::DynamoDBFeatureStoreCore.new(table_name, opts)
51
51
  LaunchDarkly::Integrations::Util::CachingStoreWrapper.new(core, opts)
52
52
  end
@@ -58,7 +58,7 @@ module LaunchDarkly
58
58
  # lifecycle to be independent of the SDK client
59
59
  # @return [LaunchDarkly::Interfaces::FeatureStore] a feature store object
60
60
  #
61
- def self.new_feature_store(opts)
61
+ def self.new_feature_store(opts = {})
62
62
  return RedisFeatureStore.new(opts)
63
63
  end
64
64
 
@@ -1,7 +1,6 @@
1
1
  require "ldclient-rb/impl/big_segments"
2
2
  require "ldclient-rb/impl/diagnostic_events"
3
3
  require "ldclient-rb/impl/evaluator"
4
- require "ldclient-rb/impl/event_factory"
5
4
  require "ldclient-rb/impl/store_client_wrapper"
6
5
  require "concurrent/atomics"
7
6
  require "digest/sha1"
@@ -46,9 +45,6 @@ module LaunchDarkly
46
45
 
47
46
  @sdk_key = sdk_key
48
47
 
49
- @event_factory_default = EventFactory.new(false)
50
- @event_factory_with_reasons = EventFactory.new(true)
51
-
52
48
  # We need to wrap the feature store object with a FeatureStoreClientWrapper in order to add
53
49
  # some necessary logic around updates. Unfortunately, we have code elsewhere that accesses
54
50
  # the feature store through the Config object, so we need to make a new Config that uses
@@ -65,7 +61,7 @@ module LaunchDarkly
65
61
  get_segment = lambda { |key| @store.get(SEGMENTS, key) }
66
62
  get_big_segments_membership = lambda { |key| @big_segment_store_manager.get_user_membership(key) }
67
63
  @evaluator = LaunchDarkly::Impl::Evaluator.new(get_flag, get_segment, get_big_segments_membership, @config.logger)
68
-
64
+
69
65
  if !@config.offline? && @config.send_events && !@config.diagnostic_opt_out?
70
66
  diagnostic_accumulator = Impl::DiagnosticAccumulator.new(Impl::DiagnosticAccumulator.create_diagnostic_id(sdk_key))
71
67
  else
@@ -178,7 +174,7 @@ module LaunchDarkly
178
174
  # Other supported user attributes include IP address, country code, and an arbitrary hash of
179
175
  # custom attributes. For more about the supported user properties and how they work in
180
176
  # LaunchDarkly, see [Targeting users](https://docs.launchdarkly.com/home/flags/targeting-users).
181
- #
177
+ #
182
178
  # The optional `:privateAttributeNames` user property allows you to specify a list of
183
179
  # attribute names that should not be sent back to LaunchDarkly.
184
180
  # [Private attributes](https://docs.launchdarkly.com/home/users/attributes#creating-private-user-attributes)
@@ -202,7 +198,7 @@ module LaunchDarkly
202
198
  # @return the variation to show the user, or the default value if there's an an error
203
199
  #
204
200
  def variation(key, user, default)
205
- evaluate_internal(key, user, default, @event_factory_default).value
201
+ evaluate_internal(key, user, default, false).value
206
202
  end
207
203
 
208
204
  #
@@ -229,7 +225,7 @@ module LaunchDarkly
229
225
  # @return [EvaluationDetail] an object describing the result
230
226
  #
231
227
  def variation_detail(key, user, default)
232
- evaluate_internal(key, user, default, @event_factory_with_reasons)
228
+ evaluate_internal(key, user, default, true)
233
229
  end
234
230
 
235
231
  #
@@ -248,12 +244,12 @@ module LaunchDarkly
248
244
  # @return [void]
249
245
  #
250
246
  def identify(user)
251
- if !user || user[:key].nil?
252
- @config.logger.warn("Identify called with nil user or nil user key!")
247
+ if !user || user[:key].nil? || user[:key].empty?
248
+ @config.logger.warn("Identify called with nil user or empty user key!")
253
249
  return
254
250
  end
255
251
  sanitize_user(user)
256
- @event_processor.add_event(@event_factory_default.new_identify_event(user))
252
+ @event_processor.record_identify_event(user)
257
253
  end
258
254
 
259
255
  #
@@ -284,7 +280,7 @@ module LaunchDarkly
284
280
  return
285
281
  end
286
282
  sanitize_user(user)
287
- @event_processor.add_event(@event_factory_default.new_custom_event(event_name, user, data, metric_value))
283
+ @event_processor.record_custom_event(user, event_name, data, metric_value)
288
284
  end
289
285
 
290
286
  #
@@ -301,7 +297,7 @@ module LaunchDarkly
301
297
  end
302
298
  sanitize_user(current_context)
303
299
  sanitize_user(previous_context)
304
- @event_processor.add_event(@event_factory_default.new_alias_event(current_context, previous_context))
300
+ @event_processor.record_alias_event(current_context, previous_context)
305
301
  end
306
302
 
307
303
  #
@@ -338,6 +334,15 @@ module LaunchDarkly
338
334
  def all_flags_state(user, options={})
339
335
  return FeatureFlagsState.new(false) if @config.offline?
340
336
 
337
+ if !initialized?
338
+ if @store.initialized?
339
+ @config.logger.warn { "Called all_flags_state before client initialization; using last known values from data store" }
340
+ else
341
+ @config.logger.warn { "Called all_flags_state before client initialization. Data store not available; returning empty state" }
342
+ return FeatureFlagsState.new(false)
343
+ end
344
+ end
345
+
341
346
  unless user && !user[:key].nil?
342
347
  @config.logger.error { "[LDClient] User and user key must be specified in all_flags_state" }
343
348
  return FeatureFlagsState.new(false)
@@ -359,14 +364,25 @@ module LaunchDarkly
359
364
  next
360
365
  end
361
366
  begin
362
- result = @evaluator.evaluate(f, user, @event_factory_default)
363
- state.add_flag(f, result.detail.value, result.detail.variation_index, with_reasons ? result.detail.reason : nil,
364
- details_only_if_tracked)
367
+ detail = @evaluator.evaluate(f, user).detail
365
368
  rescue => exn
369
+ detail = EvaluationDetail.new(nil, nil, EvaluationReason::error(EvaluationReason::ERROR_EXCEPTION))
366
370
  Util.log_exception(@config.logger, "Error evaluating flag \"#{k}\" in all_flags_state", exn)
367
- state.add_flag(f, nil, nil, with_reasons ? EvaluationReason::error(EvaluationReason::ERROR_EXCEPTION) : nil,
368
- details_only_if_tracked)
369
371
  end
372
+
373
+ requires_experiment_data = is_experiment(f, detail.reason)
374
+ flag_state = {
375
+ key: f[:key],
376
+ value: detail.value,
377
+ variation: detail.variation_index,
378
+ reason: detail.reason,
379
+ version: f[:version],
380
+ trackEvents: f[:trackEvents] || requires_experiment_data,
381
+ trackReason: requires_experiment_data,
382
+ debugEventsUntilDate: f[:debugEventsUntilDate],
383
+ }
384
+
385
+ state.add_flag(flag_state, with_reasons, details_only_if_tracked)
370
386
  end
371
387
 
372
388
  state
@@ -410,7 +426,7 @@ module LaunchDarkly
410
426
  end
411
427
 
412
428
  # @return [EvaluationDetail]
413
- def evaluate_internal(key, user, default, event_factory)
429
+ def evaluate_internal(key, user, default, with_reasons)
414
430
  if @config.offline?
415
431
  return Evaluator.error_result(EvaluationReason::ERROR_CLIENT_NOT_READY, default)
416
432
  end
@@ -433,7 +449,7 @@ module LaunchDarkly
433
449
  else
434
450
  @config.logger.error { "[LDClient] Client has not finished initializing; feature store unavailable, returning default value" }
435
451
  detail = Evaluator.error_result(EvaluationReason::ERROR_CLIENT_NOT_READY, default)
436
- @event_processor.add_event(event_factory.new_unknown_flag_event(key, user, default, detail.reason))
452
+ record_unknown_flag_eval(key, user, default, detail.reason, with_reasons)
437
453
  return detail
438
454
  end
439
455
  end
@@ -443,32 +459,94 @@ module LaunchDarkly
443
459
  if feature.nil?
444
460
  @config.logger.info { "[LDClient] Unknown feature flag \"#{key}\". Returning default value" }
445
461
  detail = Evaluator.error_result(EvaluationReason::ERROR_FLAG_NOT_FOUND, default)
446
- @event_processor.add_event(event_factory.new_unknown_flag_event(key, user, default, detail.reason))
462
+ record_unknown_flag_eval(key, user, default, detail.reason, with_reasons)
447
463
  return detail
448
464
  end
449
465
 
450
466
  begin
451
- res = @evaluator.evaluate(feature, user, event_factory)
452
- if !res.events.nil?
453
- res.events.each do |event|
454
- @event_processor.add_event(event)
467
+ res = @evaluator.evaluate(feature, user)
468
+ if !res.prereq_evals.nil?
469
+ res.prereq_evals.each do |prereq_eval|
470
+ record_prereq_flag_eval(prereq_eval.prereq_flag, prereq_eval.prereq_of_flag, user, prereq_eval.detail, with_reasons)
455
471
  end
456
472
  end
457
473
  detail = res.detail
458
474
  if detail.default_value?
459
475
  detail = EvaluationDetail.new(default, nil, detail.reason)
460
476
  end
461
- @event_processor.add_event(event_factory.new_eval_event(feature, user, detail, default))
477
+ record_flag_eval(feature, user, detail, default, with_reasons)
462
478
  return detail
463
479
  rescue => exn
464
480
  Util.log_exception(@config.logger, "Error evaluating feature flag \"#{key}\"", exn)
465
481
  detail = Evaluator.error_result(EvaluationReason::ERROR_EXCEPTION, default)
466
- @event_processor.add_event(event_factory.new_default_event(feature, user, default, detail.reason))
482
+ record_flag_eval_error(feature, user, default, detail.reason, with_reasons)
467
483
  return detail
468
484
  end
469
485
  end
470
486
 
471
- def sanitize_user(user)
487
+ private def record_flag_eval(flag, user, detail, default, with_reasons)
488
+ add_experiment_data = is_experiment(flag, detail.reason)
489
+ @event_processor.record_eval_event(
490
+ user,
491
+ flag[:key],
492
+ flag[:version],
493
+ detail.variation_index,
494
+ detail.value,
495
+ (add_experiment_data || with_reasons) ? detail.reason : nil,
496
+ default,
497
+ add_experiment_data || flag[:trackEvents] || false,
498
+ flag[:debugEventsUntilDate],
499
+ nil
500
+ )
501
+ end
502
+
503
+ private def record_prereq_flag_eval(prereq_flag, prereq_of_flag, user, detail, with_reasons)
504
+ add_experiment_data = is_experiment(prereq_flag, detail.reason)
505
+ @event_processor.record_eval_event(
506
+ user,
507
+ prereq_flag[:key],
508
+ prereq_flag[:version],
509
+ detail.variation_index,
510
+ detail.value,
511
+ (add_experiment_data || with_reasons) ? detail.reason : nil,
512
+ nil,
513
+ add_experiment_data || prereq_flag[:trackEvents] || false,
514
+ prereq_flag[:debugEventsUntilDate],
515
+ prereq_of_flag[:key]
516
+ )
517
+ end
518
+
519
+ private def record_flag_eval_error(flag, user, default, reason, with_reasons)
520
+ @event_processor.record_eval_event(user, flag[:key], flag[:version], nil, default, with_reasons ? reason : nil, default,
521
+ flag[:trackEvents], flag[:debugEventsUntilDate], nil)
522
+ end
523
+
524
+ private def record_unknown_flag_eval(flag_key, user, default, reason, with_reasons)
525
+ @event_processor.record_eval_event(user, flag_key, nil, nil, default, with_reasons ? reason : nil, default,
526
+ false, nil, nil)
527
+ end
528
+
529
+ private def is_experiment(flag, reason)
530
+ return false if !reason
531
+
532
+ if reason.in_experiment
533
+ return true
534
+ end
535
+
536
+ case reason[:kind]
537
+ when 'RULE_MATCH'
538
+ index = reason[:ruleIndex]
539
+ if !index.nil?
540
+ rules = flag[:rules] || []
541
+ return index >= 0 && index < rules.length && rules[index][:trackEvents]
542
+ end
543
+ when 'FALLTHROUGH'
544
+ return !!flag[:trackEventsFallthrough]
545
+ end
546
+ false
547
+ end
548
+
549
+ private def sanitize_user(user)
472
550
  if user[:key]
473
551
  user[:key] = user[:key].to_s
474
552
  end
@@ -31,7 +31,7 @@ module LaunchDarkly
31
31
 
32
32
  def request_all_data()
33
33
  all_data = JSON.parse(make_request("/sdk/latest-all"), symbolize_names: true)
34
- Impl::Model.make_all_store_data(all_data)
34
+ Impl::Model.make_all_store_data(all_data, @config.logger)
35
35
  end
36
36
 
37
37
  def stop
@@ -44,7 +44,7 @@ module LaunchDarkly
44
44
  private
45
45
 
46
46
  def request_single_item(kind, path)
47
- Impl::Model.deserialize(kind, make_request(path))
47
+ Impl::Model.deserialize(kind, make_request(path), @config.logger)
48
48
  end
49
49
 
50
50
  def make_request(path)
@@ -47,7 +47,8 @@ module LaunchDarkly
47
47
  headers: headers,
48
48
  read_timeout: READ_TIMEOUT_SECONDS,
49
49
  logger: @config.logger,
50
- socket_factory: @config.socket_factory
50
+ socket_factory: @config.socket_factory,
51
+ reconnect_time: @config.initial_reconnect_delay
51
52
  }
52
53
  log_connection_started
53
54
  @es = SSE::Client.new(@config.stream_uri + "/all", **opts) do |conn|
@@ -85,7 +86,7 @@ module LaunchDarkly
85
86
  @config.logger.debug { "[LDClient] Stream received #{method} message: #{message.data}" }
86
87
  if method == PUT
87
88
  message = JSON.parse(message.data, symbolize_names: true)
88
- all_data = Impl::Model.make_all_store_data(message[:data])
89
+ all_data = Impl::Model.make_all_store_data(message[:data], @config.logger)
89
90
  @feature_store.init(all_data)
90
91
  @initialized.make_true
91
92
  @config.logger.info { "[LDClient] Stream initialized" }
@@ -96,7 +97,7 @@ module LaunchDarkly
96
97
  key = key_for_path(kind, data[:path])
97
98
  if key
98
99
  data = data[:data]
99
- Impl::Model.postprocess_item_after_deserializing!(kind, data)
100
+ Impl::DataModelPreprocessing::Preprocessor.new(@config.logger).preprocess_item!(kind, data)
100
101
  @feature_store.upsert(kind, data)
101
102
  break
102
103
  end
@@ -24,6 +24,15 @@ module LaunchDarkly
24
24
  if config.socket_factory
25
25
  http_client_options["socket_class"] = config.socket_factory
26
26
  end
27
+ proxy = URI.parse(uri_s).find_proxy
28
+ if !proxy.nil?
29
+ http_client_options["proxy"] = {
30
+ proxy_address: proxy.host,
31
+ proxy_port: proxy.port,
32
+ proxy_username: proxy.user,
33
+ proxy_password: proxy.password
34
+ }
35
+ end
27
36
  return HTTP::Client.new(http_client_options)
28
37
  .timeout({
29
38
  read: config.read_timeout,
@@ -1,3 +1,3 @@
1
1
  module LaunchDarkly
2
- VERSION = "6.3.1"
2
+ VERSION = "6.4.0"
3
3
  end
data/lib/ldclient-rb.rb CHANGED
@@ -21,7 +21,6 @@ require "ldclient-rb/polling"
21
21
  require "ldclient-rb/user_filter"
22
22
  require "ldclient-rb/simple_lru_cache"
23
23
  require "ldclient-rb/non_blocking_thread_pool"
24
- require "ldclient-rb/event_summarizer"
25
24
  require "ldclient-rb/events"
26
25
  require "ldclient-rb/requestor"
27
26
  require "ldclient-rb/file_data_source"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: launchdarkly-server-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.3.1
4
+ version: 6.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - LaunchDarkly
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-12-31 00:00:00.000000000 Z
11
+ date: 2022-09-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk-dynamodb
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - '='
32
32
  - !ruby/object:Gem::Version
33
- version: 2.2.10
33
+ version: 2.2.33
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - '='
39
39
  - !ruby/object:Gem::Version
40
- version: 2.2.10
40
+ version: 2.2.33
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rspec
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -198,14 +198,14 @@ dependencies:
198
198
  requirements:
199
199
  - - '='
200
200
  - !ruby/object:Gem::Version
201
- version: 2.2.0
201
+ version: 2.2.1
202
202
  type: :runtime
203
203
  prerelease: false
204
204
  version_requirements: !ruby/object:Gem::Requirement
205
205
  requirements:
206
206
  - - '='
207
207
  - !ruby/object:Gem::Version
208
- version: 2.2.0
208
+ version: 2.2.1
209
209
  - !ruby/object:Gem::Dependency
210
210
  name: json
211
211
  requirement: !ruby/object:Gem::Requirement
@@ -254,7 +254,6 @@ files:
254
254
  - lib/ldclient-rb/cache_store.rb
255
255
  - lib/ldclient-rb/config.rb
256
256
  - lib/ldclient-rb/evaluation_detail.rb
257
- - lib/ldclient-rb/event_summarizer.rb
258
257
  - lib/ldclient-rb/events.rb
259
258
  - lib/ldclient-rb/expiring_cache.rb
260
259
  - lib/ldclient-rb/file_data_source.rb
@@ -264,14 +263,17 @@ files:
264
263
  - lib/ldclient-rb/impl/diagnostic_events.rb
265
264
  - lib/ldclient-rb/impl/evaluator.rb
266
265
  - lib/ldclient-rb/impl/evaluator_bucketing.rb
266
+ - lib/ldclient-rb/impl/evaluator_helpers.rb
267
267
  - lib/ldclient-rb/impl/evaluator_operators.rb
268
- - lib/ldclient-rb/impl/event_factory.rb
269
268
  - lib/ldclient-rb/impl/event_sender.rb
269
+ - lib/ldclient-rb/impl/event_summarizer.rb
270
+ - lib/ldclient-rb/impl/event_types.rb
270
271
  - lib/ldclient-rb/impl/integrations/consul_impl.rb
271
272
  - lib/ldclient-rb/impl/integrations/dynamodb_impl.rb
272
273
  - lib/ldclient-rb/impl/integrations/file_data_source.rb
273
274
  - lib/ldclient-rb/impl/integrations/redis_impl.rb
274
275
  - lib/ldclient-rb/impl/integrations/test_data/test_data_source.rb
276
+ - lib/ldclient-rb/impl/model/preprocessed_data.rb
275
277
  - lib/ldclient-rb/impl/model/serialization.rb
276
278
  - lib/ldclient-rb/impl/repeating_task.rb
277
279
  - lib/ldclient-rb/impl/store_client_wrapper.rb
@@ -319,7 +321,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
319
321
  - !ruby/object:Gem::Version
320
322
  version: '0'
321
323
  requirements: []
322
- rubygems_version: 3.3.4
324
+ rubygems_version: 3.3.22
323
325
  signing_key:
324
326
  specification_version: 4
325
327
  summary: LaunchDarkly SDK for Ruby
@@ -1,55 +0,0 @@
1
-
2
- module LaunchDarkly
3
- # @private
4
- EventSummary = Struct.new(:start_date, :end_date, :counters)
5
-
6
- # Manages the state of summarizable information for the EventProcessor, including the
7
- # event counters and user deduplication. Note that the methods of this class are
8
- # deliberately not thread-safe; the EventProcessor is responsible for enforcing
9
- # synchronization across both the summarizer and the event queue.
10
- #
11
- # @private
12
- class EventSummarizer
13
- def initialize
14
- clear
15
- end
16
-
17
- # Adds this event to our counters, if it is a type of event we need to count.
18
- def summarize_event(event)
19
- if event[:kind] == "feature"
20
- counter_key = {
21
- key: event[:key],
22
- version: event[:version],
23
- variation: event[:variation]
24
- }
25
- c = @counters[counter_key]
26
- if c.nil?
27
- @counters[counter_key] = {
28
- value: event[:value],
29
- default: event[:default],
30
- count: 1
31
- }
32
- else
33
- c[:count] = c[:count] + 1
34
- end
35
- time = event[:creationDate]
36
- if !time.nil?
37
- @start_date = time if @start_date == 0 || time < @start_date
38
- @end_date = time if time > @end_date
39
- end
40
- end
41
- end
42
-
43
- # Returns a snapshot of the current summarized event data, and resets this state.
44
- def snapshot
45
- ret = EventSummary.new(@start_date, @end_date, @counters)
46
- ret
47
- end
48
-
49
- def clear
50
- @start_date = 0
51
- @end_date = 0
52
- @counters = {}
53
- end
54
- end
55
- end