lib/ddmemoize.rb in ddmemoize-1.0.0a1 vs lib/ddmemoize.rb in ddmemoize-1.0.0a2

- old
+ new

@@ -1,8 +1,10 @@ # frozen_string_literal: true require 'ref' +require 'ddtelemetry' +require 'singleton' require_relative 'ddmemoize/version' module DDMemoize class Value @@ -11,29 +13,83 @@ def initialize(value) @value = value end end + # @api private + class TelemetryMap + include Singleton + + def initialize + @map = {} + end + + def [](mod) + @map[mod] + end + + def []=(mod, telemetry) + @map[mod] = telemetry + end + end + NONE = Object.new - def self.activate(mod) + def self.activate(mod, telemetry: nil) mod.extend(Mixin) + TelemetryMap.instance[mod] = telemetry end + def self.telemetry_for(mod) + TelemetryMap.instance[mod] + end + + def self.print_telemetry(telemetry) + headers = %w[memoization hit miss %] + + rows_raw = telemetry.counter(:memoization).map do |(name, type), counter| + { name: name, type: type, count: counter.value } + end + + rows = rows_raw.group_by { |r| r[:name] }.map do |name, rows_for_name| + rows_by_type = rows_for_name.group_by { |r| r[:type] } + + num_hit = rows_by_type.fetch(:hit, []).fetch(0, {}).fetch(:count, 0) + num_miss = rows_by_type.fetch(:miss, []).fetch(0, {}).fetch(:count, 0) + pct = num_hit.to_f / (num_hit + num_miss).to_f + + [name, num_hit.to_s, num_miss.to_s, "#{format('%3.1f', pct * 100)}%"] + end + + all_rows = [headers] + rows + puts DDTelemetry::Table.new(all_rows).to_s + end + module Mixin def memoize(method_name) original_method_name = '__nonmemoized_' + method_name.to_s alias_method original_method_name, method_name instance_cache = Hash.new { |hash, key| hash[key] = {} } + telemetry = DDMemoize.telemetry_for(self) define_method(method_name) do |*args| instance_method_cache = instance_cache[self] value = NONE if instance_method_cache.key?(args) object = instance_method_cache[args].object value = object ? object.value : NONE + end + + if telemetry + counter_label = is_a?(Class) ? "#{self}.#{method_name}" : "#{self.class}##{method_name}" + + if NONE.equal?(value) + telemetry.counter(:memoization).increment([counter_label, :miss]) + else + telemetry.counter(:memoization).increment([counter_label, :hit]) + end end if value.equal?(NONE) send(original_method_name, *args).tap do |r| instance_method_cache[args] = Ref::SoftReference.new(Value.new(r))