scientist 1.6.4 → 1.6.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5553615794cee9e0fbdb4c22b3dd528885b30a75c9c8c2bd1d5d4f8ebd4bc0ba
4
- data.tar.gz: ff1c3e2b2f23defc19ad663df792da483926406aa98261d403fa7c008c03be85
3
+ metadata.gz: 426765524d161804bb79075cb364ff3101c630da521af30c6f7f79e7e65244a8
4
+ data.tar.gz: a25d075270905c82d63eff97ae773205c3a1b7f3ddb1fc766ee822dd5ee1edfd
5
5
  SHA512:
6
- metadata.gz: ffef7682cdd59612ca44b33ba3678467251c2ccabfdecda41022cc95c0d07673abd38ca3a33c357fa40358398691ba0f8ecf8ec04ddff14800a25a915f011f4d
7
- data.tar.gz: 06e06b1428967829d9b330eba28bd1bd2ce47eb5b12b6317eb6c6e8ea53e4e6434337803b88fdadbccdcb23a1b8cafe2fbe98a38756ea53d81ed21afe0ec369e
6
+ metadata.gz: c48b5826e463c8c1d59c147509f4ced8e371b022da15da5f8d63b038d2d9ef42945951fbf1901924a8570f24de9c95f4c1b33ca430ed47ac2e243383bde4652c
7
+ data.tar.gz: 19af496de9ceb3f2d2bdb933656d60830941489ab0702c4753247c606886184a5d1c8c7e25ceda26bc2f8ed5cdf6ec3ee833a74c44ec081a814584e0a5e8b677
@@ -24,8 +24,9 @@ jobs:
24
24
  - "3.0"
25
25
  - "3.1"
26
26
  - "3.2"
27
+ - "3.3"
27
28
  steps:
28
- - uses: actions/checkout@v3
29
+ - uses: actions/checkout@v4
29
30
  - uses: ruby/setup-ruby@v1
30
31
  with:
31
32
  ruby-version: ${{ matrix.ruby_version }}
data/README.md CHANGED
@@ -12,7 +12,7 @@ require "scientist"
12
12
  class MyWidget
13
13
  def allows?(user)
14
14
  experiment = Scientist::Default.new "widget-permissions"
15
- experiment.use { model.check_user?(user).valid? } # old way
15
+ experiment.use { model.check_user(user).valid? } # old way
16
16
  experiment.try { user.can?(:read, model) } # new way
17
17
 
18
18
  experiment.run
@@ -24,7 +24,7 @@ Wrap a `use` block around the code's original behavior, and wrap `try` around th
24
24
 
25
25
  * It decides whether or not to run the `try` block,
26
26
  * Randomizes the order in which `use` and `try` blocks are run,
27
- * Measures the durations of all behaviors in seconds,
27
+ * Measures the wall time and cpu time of all behaviors in seconds,
28
28
  * Compares the result of `try` to the result of `use`,
29
29
  * Swallow and record exceptions raised in the `try` block when overriding `raised`, and
30
30
  * Publishes all this information.
@@ -121,7 +121,7 @@ class MyWidget
121
121
  e.try { UserService.slug_from_login login } # returns String instance or ArgumentError
122
122
 
123
123
  compare_error_message_and_class = -> (control, candidate) do
124
- control.class == candidate.class &&
124
+ control.class == candidate.class &&
125
125
  control.message == candidate.message
126
126
  end
127
127
 
@@ -129,7 +129,7 @@ class MyWidget
129
129
  control.class == ArgumentError &&
130
130
  candidate.class == ArgumentError &&
131
131
  control.message.start_with?("Input has invalid characters") &&
132
- candidate.message.start_with?("Invalid characters in input")
132
+ candidate.message.start_with?("Invalid characters in input")
133
133
  end
134
134
 
135
135
  e.compare_errors do |control, candidate|
@@ -241,6 +241,19 @@ end
241
241
 
242
242
  Note that the `#clean` method will discard the previous cleaner block if you call it again. If for some reason you need to access the currently configured cleaner block, `Scientist::Experiment#cleaner` will return the block without further ado. _(This probably won't come up in normal usage, but comes in handy if you're writing, say, a custom experiment runner that provides default cleaners.)_
243
243
 
244
+ The `#clean` method will not be used for comparison of the results, so in the following example it is not possible to remove the `#compare` method without the experiment failing:
245
+
246
+ ```ruby
247
+ def user_ids
248
+ science "user_ids" do
249
+ e.use { [1,2,3] }
250
+ e.try { [1,3,2] }
251
+ e.clean { |value| value.sort }
252
+ e.compare { |a, b| a.sort == b.sort }
253
+ end
254
+ end
255
+ ```
256
+
244
257
  ### Ignoring mismatches
245
258
 
246
259
  During the early stages of an experiment, it's possible that some of your code will always generate a mismatch for reasons you know and understand but haven't yet fixed. Instead of these known cases always showing up as mismatches in your metrics or analysis, you can tell an experiment whether or not to ignore a mismatch using the `ignore` method. You may include more than one block if needed:
@@ -321,11 +334,18 @@ class MyExperiment
321
334
 
322
335
  def publish(result)
323
336
 
337
+ # Wall time
324
338
  # Store the timing for the control value,
325
339
  $statsd.timing "science.#{name}.control", result.control.duration
326
340
  # for the candidate (only the first, see "Breaking the rules" below,
327
341
  $statsd.timing "science.#{name}.candidate", result.candidates.first.duration
328
342
 
343
+ # CPU time
344
+ # Store the timing for the control value,
345
+ $statsd.timing "science.cpu.#{name}.control", result.control.cpu_time
346
+ # for the candidate (only the first, see "Breaking the rules" below,
347
+ $statsd.timing "science.cpu.#{name}.candidate", result.candidates.first.cpu_time
348
+
329
349
  # and counts for match/ignore/mismatch:
330
350
  if result.matched?
331
351
  $statsd.increment "science.#{name}.matched"
@@ -530,17 +550,22 @@ end
530
550
 
531
551
  #### Providing fake timing data
532
552
 
533
- If you're writing tests that depend on specific timing values, you can provide canned durations using the `fabricate_durations_for_testing_purposes` method, and Scientist will report these in `Scientist::Observation#duration` instead of the actual execution times.
553
+ If you're writing tests that depend on specific timing values, you can provide canned durations using the `fabricate_durations_for_testing_purposes` method, and Scientist will report these in `Scientist::Observation#duration` and `Scientist::Observation#cpu_time` instead of the actual execution times.
534
554
 
535
555
  ```ruby
536
556
  science "absolutely-nothing-suspicious-happening-here" do |e|
537
557
  e.use { ... } # "control"
538
558
  e.try { ... } # "candidate"
539
- e.fabricate_durations_for_testing_purposes( "control" => 1.0, "candidate" => 0.5 )
559
+ e.fabricate_durations_for_testing_purposes({
560
+ "control" => { "duration" => 1.0, "cpu_time" => 0.9 },
561
+ "candidate" => { "duration" => 0.5, "cpu_time" => 0.4 }
562
+ })
540
563
  end
541
564
  ```
542
565
 
543
- `fabricate_durations_for_testing_purposes` takes a Hash of duration values, keyed by behavior names. (By default, Scientist uses `"control"` and `"candidate"`, but if you override these as shown in [Trying more than one thing](#trying-more-than-one-thing) or [No control, just candidates](#no-control-just-candidates), use matching names here.) If a name is not provided, the actual execution time will be reported instead.
566
+ `fabricate_durations_for_testing_purposes` takes a Hash of duration & cpu_time values, keyed by behavior names. (By default, Scientist uses `"control"` and `"candidate"`, but if you override these as shown in [Trying more than one thing](#trying-more-than-one-thing) or [No control, just candidates](#no-control-just-candidates), use matching names here.) If a name is not provided, the actual execution time will be reported instead.
567
+
568
+ We should mention these durations will be used both for the `duration` field and the `cpu_time` field.
544
569
 
545
570
  _Like `Scientist::Experiment#cleaner`, this probably won't come up in normal usage. It's here to make it easier to test code that extends Scientist._
546
571
 
data/doc/changelog.md CHANGED
@@ -1,5 +1,49 @@
1
1
  # Changes
2
2
 
3
+ ## v1.6.5 (16 December 2024)
4
+
5
+ - New: measure CPU time alongside wall time for experiments #275
6
+
7
+ ## v1.6.4 (5 April 2023)
8
+
9
+ - New: GitHub Actions for CI #171
10
+ - New: add ruby 3.1 support #175
11
+ - Fix: `compare_errors` in docs #178
12
+ - Fix: remove outdated travis configs #179
13
+ - Fix: typos #191
14
+ - New: add support for `after_run` blocks #211
15
+
16
+ ## v1.6.3 (9 December 2021)
17
+
18
+ - Fix: improve marshaling implementation #169
19
+
20
+ ## v1.6.2 (4 November 2021)
21
+
22
+ - New: make `MismatchError` marshalable #168
23
+
24
+ ## v1.6.1 (22 October 2021)
25
+
26
+ - Fix: moving supported ruby versions from <=2.3 to >=2.6 #150
27
+ - Fix: update docs to explain timeout handling #159
28
+ - New: add support for comparing errors #77
29
+
30
+ ## v1.6.0 (8 March 2021)
31
+
32
+ - Fix: clarify unit for observations #124
33
+ - New: enable support for truffleruby #143
34
+ - Fix: don't default experiment when included in a module #144
35
+
36
+ ## v1.5.0 (8 September 2020)
37
+
38
+ - Fix: clearer explanation of exception handling #110
39
+ - Fix: remove unused attribute from `Scientist::Observation` #119
40
+ - New: Added internal extension point for generating experinet results #121
41
+ - New: Add `Scientist::Experiment.register` helper #104
42
+
43
+ ## v1.4.0 (20 September 2019)
44
+
45
+ - New: Make `MismatchError` a base `Exception` #107
46
+
3
47
  ## v1.3.0 (2 April 2019)
4
48
 
5
49
  - New: Drop support for ruby <2.3
@@ -345,7 +345,7 @@ module Scientist::Experiment
345
345
  [@name, @result, @raise_on_mismatches]
346
346
  end
347
347
 
348
- def marshal_load
348
+ def marshal_load(array)
349
349
  @name, @result, @raise_on_mismatches = array
350
350
  end
351
351
  end
@@ -20,19 +20,32 @@ class Scientist::Observation
20
20
  # The Float seconds elapsed.
21
21
  attr_reader :duration
22
22
 
23
+ # The Float CPU time elapsed, in seconds
24
+ attr_reader :cpu_time
25
+
23
26
  def initialize(name, experiment, fabricated_duration: nil, &block)
24
27
  @name = name
25
28
  @experiment = experiment
26
29
 
27
- starting = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_second) unless fabricated_duration
30
+ start_wall_time, start_cpu_time = capture_times unless fabricated_duration
31
+
28
32
  begin
29
33
  @value = block.call
30
34
  rescue *RESCUES => e
31
35
  @exception = e
32
36
  end
33
37
 
34
- @duration = fabricated_duration ||
35
- Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_second) - starting
38
+ if fabricated_duration.is_a?(Hash)
39
+ @duration = fabricated_duration["duration"]
40
+ @cpu_time = fabricated_duration["cpu_time"]
41
+ elsif fabricated_duration
42
+ @duration = fabricated_duration
43
+ @cpu_time = 0.0 # setting a default value
44
+ else
45
+ end_wall_time, end_cpu_time = capture_times
46
+ @duration = end_wall_time - start_wall_time
47
+ @cpu_time = end_cpu_time - start_cpu_time
48
+ end
36
49
 
37
50
  freeze
38
51
  end
@@ -89,4 +102,13 @@ class Scientist::Observation
89
102
  def raised?
90
103
  !exception.nil?
91
104
  end
105
+
106
+ private
107
+
108
+ def capture_times
109
+ [
110
+ Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_second),
111
+ Process.clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID, :float_second)
112
+ ]
113
+ end
92
114
  end
@@ -1,3 +1,3 @@
1
1
  module Scientist
2
- VERSION = '1.6.4'
2
+ VERSION = '1.6.5'
3
3
  end
@@ -512,6 +512,10 @@ describe Scientist::Experiment do
512
512
  assert_kind_of(String, Marshal.dump(mismatch))
513
513
  end
514
514
 
515
+ it "can be marshal loaded" do
516
+ assert_kind_of(Fake, Marshal.load(Marshal.dump(@ex)))
517
+ end
518
+
515
519
  describe "#raise_on_mismatches?" do
516
520
  it "raises when there is a mismatch if the experiment instance's raise on mismatches is enabled" do
517
521
  Fake.raise_on_mismatches = false
@@ -667,7 +671,7 @@ candidate:
667
671
  end
668
672
 
669
673
  describe "testing hooks for extending code" do
670
- it "allows a user to provide fabricated durations for testing purposes" do
674
+ it "allows a user to provide fabricated durations for testing purposes (old version)" do
671
675
  @ex.use { true }
672
676
  @ex.try { true }
673
677
  @ex.fabricate_durations_for_testing_purposes( "control" => 0.5, "candidate" => 1.0 )
@@ -680,7 +684,28 @@ candidate:
680
684
  assert_in_delta 1.0, cand.duration, 0.01
681
685
  end
682
686
 
683
- it "returns actual durations if fabricated ones are omitted for some blocks" do
687
+ it "allows a user to provide fabricated durations for testing purposes (new version)" do
688
+ @ex.use { true }
689
+ @ex.try { true }
690
+ @ex.fabricate_durations_for_testing_purposes({
691
+ "control" => { "duration" => 0.5, "cpu_time" => 0.4 },
692
+ "candidate" => { "duration" => 1.0, "cpu_time" => 0.9 }
693
+ })
694
+ @ex.run
695
+
696
+ cont = @ex.published_result.control
697
+ cand = @ex.published_result.candidates.first
698
+
699
+ # Wall Time
700
+ assert_in_delta 0.5, cont.duration, 0.01
701
+ assert_in_delta 1.0, cand.duration, 0.01
702
+
703
+ # CPU Time
704
+ assert_equal 0.4, cont.cpu_time
705
+ assert_equal 0.9, cand.cpu_time
706
+ end
707
+
708
+ it "returns actual durations if fabricated ones are omitted for some blocks (old version)" do
684
709
  @ex.use { true }
685
710
  @ex.try { sleep 0.1; true }
686
711
  @ex.fabricate_durations_for_testing_purposes( "control" => 0.5 )
@@ -692,5 +717,30 @@ candidate:
692
717
  assert_in_delta 0.5, cont.duration, 0.01
693
718
  assert_in_delta 0.1, cand.duration, 0.01
694
719
  end
720
+
721
+ it "returns actual durations if fabricated ones are omitted for some blocks (new version)" do
722
+ @ex.use { true }
723
+ @ex.try do
724
+ start_time = Time.now
725
+ while Time.now - start_time < 0.1
726
+ # Perform some CPU-intensive work
727
+ (1..1000).each { |i| i * i }
728
+ end
729
+ true
730
+ end
731
+ @ex.fabricate_durations_for_testing_purposes({ "control" => { "duration" => 0.5, "cpu_time" => 0.4 }})
732
+ @ex.run
733
+
734
+ cont = @ex.published_result.control
735
+ cand = @ex.published_result.candidates.first
736
+
737
+ # Fabricated durations
738
+ assert_in_delta 0.5, cont.duration, 0.01
739
+ assert_in_delta 0.4, cont.cpu_time, 0.01
740
+
741
+ # Measured durations
742
+ assert_in_delta 0.1, cand.duration, 0.01
743
+ assert_in_delta 0.1, cand.cpu_time, 0.01
744
+ end
695
745
  end
696
746
  end
@@ -6,13 +6,18 @@ describe Scientist::Observation do
6
6
 
7
7
  it "observes and records the execution of a block" do
8
8
  ob = Scientist::Observation.new("test", @experiment) do
9
- sleep 0.1
9
+ start_time = Time.now
10
+ while Time.now - start_time < 0.1
11
+ # Perform some CPU-intensive work
12
+ (1..1000).each { |i| i * i }
13
+ end
10
14
  "ret"
11
15
  end
12
16
 
13
17
  assert_equal "ret", ob.value
14
18
  refute ob.raised?
15
19
  assert_in_delta 0.1, ob.duration, 0.01
20
+ assert_in_delta 0.1, ob.cpu_time, 0.01
16
21
  end
17
22
 
18
23
  it "stashes exceptions" do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: scientist
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.4
4
+ version: 1.6.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - GitHub Open Source
@@ -12,7 +12,7 @@ authors:
12
12
  autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
- date: 2023-04-05 00:00:00.000000000 Z
15
+ date: 2024-12-16 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: minitest
@@ -98,7 +98,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
98
98
  - !ruby/object:Gem::Version
99
99
  version: '0'
100
100
  requirements: []
101
- rubygems_version: 3.3.7
101
+ rubygems_version: 3.5.10
102
102
  signing_key:
103
103
  specification_version: 4
104
104
  summary: Carefully test, measure, and track refactored code.