ddmemoize 1.0.0a1 → 1.0.0a2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7674a9402beca0c7c7aa4bcf22c5095856bd4c77cd8994ff5677fa0784fd5f5c
4
- data.tar.gz: '090adce10054d2c7f63725947ce96ccb2eff67be093dd6042968600994515191'
3
+ metadata.gz: eecfd413a7d098193d530f6b890934993b41a9bc5ce83caddf6c34a94309db22
4
+ data.tar.gz: a5f07ddba37a86bf0dc6af6d34426ca9d1b16003f7ae0e20116de174be73afdf
5
5
  SHA512:
6
- metadata.gz: 1a0df32a98e11ea5280e6d15191057e62397dde1796b73e3fe17b264133f47f8b454da468daf854f0ae3102261206141449832e41d19f83274cb11f4c9bcaecc
7
- data.tar.gz: cb30c1743351a11dfcdbaea5a9a907f4587c68cfe1b9265a78a654fcea6bc94a2e495e1628240edfbba0ced548ca20cf6da56484ef843e1a7f0cb0f3d30a7a78
6
+ metadata.gz: e3dcd35b56f321b3abfffe266d765d78ff969622dff3ec7930a580f2023a32ade26a9dcb642715caca800a9eaf466aae6ec66a6e1c091b01dae3fec901f060e3
7
+ data.tar.gz: ed990b6c32a034c7aab031a634ca7331556863a4eea6aa5e623c545f8de22a594bc70e4f8a7a336c5555dff9da73c44021064106697035a6a3705314a5471fe6
data/NEWS.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # DDMemoize news
2
2
 
3
+ ## 1.0.0a2 (2017-12-10)
4
+
5
+ Features:
6
+
7
+ * Added telemetry support (#1)
8
+
9
+ Fixes:
10
+
11
+ * Fixed handling of objects that override `#equal?` (but why would you do that anyway?) (#2)
12
+
3
13
  ## 1.0.0a1 (2017-12-02)
4
14
 
5
15
  Initial release.
data/README.md CHANGED
@@ -30,6 +30,7 @@ Features:
30
30
 
31
31
  * Supports memoizing functions on frozen objects
32
32
  * Releases memoized values when needed in order to reduce memory pressure
33
+ * Optionally records telemetry
33
34
 
34
35
  ## Installation
35
36
 
@@ -78,7 +79,29 @@ Alternatively, prepend `memoized` to the function definition:
78
79
  end
79
80
  ```
80
81
 
81
- Do not memoie functions that depend on mutable state.
82
+ Do not memoize functions that depend on mutable state.
83
+
84
+ ### Telemetry
85
+
86
+ To activate telemetry for a given class, call `DDMemoize.activate` with a `telemetry` keyword argument, passing in a telemetry instance:
87
+
88
+ ```ruby
89
+ require 'ddmemoize'
90
+
91
+ TELEMETRY = DDTelemetry.new
92
+
93
+ class FibFast
94
+ DDMemoize.activate(self, telemetry: TELEMETRY)
95
+
96
+ # …
97
+ end
98
+ ```
99
+
100
+ To print the collected metrics, call `DDMemoize.print_telemetry`, passing in the telemetry instance:
101
+
102
+ ```ruby
103
+ DDMemoize.print_telemetry(TELEMETRY)
104
+ ```
82
105
 
83
106
  ## Development
84
107
 
@@ -14,6 +14,7 @@ Gem::Specification.new do |spec|
14
14
 
15
15
  spec.required_ruby_version = '>= 2.3.0'
16
16
 
17
+ spec.add_runtime_dependency('ddtelemetry', '= 1.0.0a1') # TODO: upgrade
17
18
  spec.add_runtime_dependency('ref', '~> 2.0')
18
19
 
19
20
  spec.files = `git ls-files -z`.split("\x0")
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'ref'
4
+ require 'ddtelemetry'
5
+ require 'singleton'
4
6
 
5
7
  require_relative 'ddmemoize/version'
6
8
 
@@ -13,10 +15,53 @@ module DDMemoize
13
15
  end
14
16
  end
15
17
 
18
+ # @api private
19
+ class TelemetryMap
20
+ include Singleton
21
+
22
+ def initialize
23
+ @map = {}
24
+ end
25
+
26
+ def [](mod)
27
+ @map[mod]
28
+ end
29
+
30
+ def []=(mod, telemetry)
31
+ @map[mod] = telemetry
32
+ end
33
+ end
34
+
16
35
  NONE = Object.new
17
36
 
18
- def self.activate(mod)
37
+ def self.activate(mod, telemetry: nil)
19
38
  mod.extend(Mixin)
39
+ TelemetryMap.instance[mod] = telemetry
40
+ end
41
+
42
+ def self.telemetry_for(mod)
43
+ TelemetryMap.instance[mod]
44
+ end
45
+
46
+ def self.print_telemetry(telemetry)
47
+ headers = %w[memoization hit miss %]
48
+
49
+ rows_raw = telemetry.counter(:memoization).map do |(name, type), counter|
50
+ { name: name, type: type, count: counter.value }
51
+ end
52
+
53
+ rows = rows_raw.group_by { |r| r[:name] }.map do |name, rows_for_name|
54
+ rows_by_type = rows_for_name.group_by { |r| r[:type] }
55
+
56
+ num_hit = rows_by_type.fetch(:hit, []).fetch(0, {}).fetch(:count, 0)
57
+ num_miss = rows_by_type.fetch(:miss, []).fetch(0, {}).fetch(:count, 0)
58
+ pct = num_hit.to_f / (num_hit + num_miss).to_f
59
+
60
+ [name, num_hit.to_s, num_miss.to_s, "#{format('%3.1f', pct * 100)}%"]
61
+ end
62
+
63
+ all_rows = [headers] + rows
64
+ puts DDTelemetry::Table.new(all_rows).to_s
20
65
  end
21
66
 
22
67
  module Mixin
@@ -25,6 +70,7 @@ module DDMemoize
25
70
  alias_method original_method_name, method_name
26
71
 
27
72
  instance_cache = Hash.new { |hash, key| hash[key] = {} }
73
+ telemetry = DDMemoize.telemetry_for(self)
28
74
 
29
75
  define_method(method_name) do |*args|
30
76
  instance_method_cache = instance_cache[self]
@@ -35,6 +81,16 @@ module DDMemoize
35
81
  value = object ? object.value : NONE
36
82
  end
37
83
 
84
+ if telemetry
85
+ counter_label = is_a?(Class) ? "#{self}.#{method_name}" : "#{self.class}##{method_name}"
86
+
87
+ if NONE.equal?(value)
88
+ telemetry.counter(:memoization).increment([counter_label, :miss])
89
+ else
90
+ telemetry.counter(:memoization).increment([counter_label, :hit])
91
+ end
92
+ end
93
+
38
94
  if value.equal?(NONE)
39
95
  send(original_method_name, *args).tap do |r|
40
96
  instance_method_cache[args] = Ref::SoftReference.new(Value.new(r))
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DDMemoize
4
- VERSION = '1.0.0a1'
4
+ VERSION = '1.0.0a2'
5
5
  end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ddmemoize'
4
+
5
+ TELEMETRY = DDTelemetry.new
6
+
7
+ class FibFast
8
+ DDMemoize.activate(self, telemetry: TELEMETRY)
9
+
10
+ memoized def fib(n)
11
+ case n
12
+ when 0
13
+ 0
14
+ when 1
15
+ 1
16
+ else
17
+ fib(n - 1) + fib(n - 2)
18
+ end
19
+ end
20
+ end
21
+
22
+ p FibFast.new.fib(1000)
23
+
24
+ DDMemoize.print_telemetry(TELEMETRY)
@@ -40,6 +40,22 @@ describe DDMemoize do
40
40
  memoize :run
41
41
  end
42
42
 
43
+ class MemoizationSpecEqual
44
+ TELEMETRY = ::DDTelemetry.new
45
+ DDMemoize.activate(self, telemetry: TELEMETRY)
46
+
47
+ class EqualToEverythingValue
48
+ def equal?(*)
49
+ true
50
+ end
51
+ end
52
+
53
+ def run
54
+ EqualToEverythingValue.new
55
+ end
56
+ memoize :run
57
+ end
58
+
43
59
  class MemoizationSpecUpcaserInlineSyntax
44
60
  DDMemoize.activate(self)
45
61
 
@@ -62,6 +78,16 @@ describe DDMemoize do
62
78
  record memoized def run; end
63
79
  end
64
80
 
81
+ class MemoizationSpecWithTelemetry
82
+ TELEMETRY = ::DDTelemetry.new
83
+ DDMemoize.activate(self, telemetry: TELEMETRY)
84
+
85
+ def run(value)
86
+ value.upcase
87
+ end
88
+ memoize :run
89
+ end
90
+
65
91
  example do
66
92
  sample1a = MemoizationSpecSample1.new(10)
67
93
  sample1b = MemoizationSpecSample1.new(15)
@@ -82,6 +108,20 @@ describe DDMemoize do
82
108
  sample.run(5)
83
109
  end
84
110
 
111
+ it 'supports objects that bizarrely override #equal?' do
112
+ sample = MemoizationSpecEqual.new
113
+ sample.freeze
114
+ sample.run
115
+ sample.run
116
+ sample.run
117
+
118
+ telemetry = MemoizationSpecEqual::TELEMETRY
119
+ counter = telemetry.counter(:memoization)
120
+
121
+ expect(counter.value(['MemoizationSpecEqual#run', :miss])).to eq(1)
122
+ expect(counter.value(['MemoizationSpecEqual#run', :hit])).to eq(2)
123
+ end
124
+
85
125
  it 'supports memoized def … syntax' do
86
126
  upcaser = MemoizationSpecUpcaserInlineSyntax.new
87
127
  expect(upcaser.run('hi')).to eq('HI')
@@ -102,4 +142,35 @@ describe DDMemoize do
102
142
  it 'returns method name' do
103
143
  expect(MemoizationSpecInlineSyntaxReturn.sym).to eq(:run)
104
144
  end
145
+
146
+ it 'records telemetry' do
147
+ sample = MemoizationSpecWithTelemetry.new
148
+
149
+ sample.run('denis')
150
+ sample.run('denis')
151
+ sample.run('defreyne')
152
+
153
+ telemetry = MemoizationSpecWithTelemetry::TELEMETRY
154
+ counter = telemetry.counter(:memoization)
155
+
156
+ expect(counter.value(['MemoizationSpecWithTelemetry#run', :miss])).to eq(2)
157
+ expect(counter.value(['MemoizationSpecWithTelemetry#run', :hit])).to eq(1)
158
+ end
159
+
160
+ it 'prints recorded telemetry' do
161
+ sample = MemoizationSpecWithTelemetry.new
162
+
163
+ sample.run('denis')
164
+ sample.run('denis')
165
+ sample.run('defreyne')
166
+
167
+ expected = <<~OUTPUT
168
+ memoization │ hit miss %
169
+ ─────────────────────────────────┼───────────────────
170
+ MemoizationSpecWithTelemetry#run │ 2 4 33.3%
171
+ OUTPUT
172
+
173
+ telemetry = MemoizationSpecWithTelemetry::TELEMETRY
174
+ expect { DDMemoize.print_telemetry(telemetry) }.to output(expected).to_stdout
175
+ end
105
176
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ddmemoize
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0a1
4
+ version: 1.0.0a2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Denis Defreyne
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-12-02 00:00:00.000000000 Z
11
+ date: 2017-12-10 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: ddtelemetry
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: 1.0.0a1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 1.0.0a1
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: ref
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -44,6 +58,7 @@ files:
44
58
  - ddmemoize.gemspec
45
59
  - lib/ddmemoize.rb
46
60
  - lib/ddmemoize/version.rb
61
+ - samples/fib.rb
47
62
  - scripts/release
48
63
  - spec/ddmemoize_spec.rb
49
64
  - spec/spec_helper.rb