ddmemoize 1.0.0a1 → 1.0.0a2

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: 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