benchmark-memory 0.1.0

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.
@@ -0,0 +1,35 @@
1
+ require "benchmark/memory/held_results/serializer"
2
+ require "benchmark/memory/held_results/measurement_serializer"
3
+ require "benchmark/memory/report/entry"
4
+
5
+ module Benchmark
6
+ module Memory
7
+ class HeldResults
8
+ # Serialize entrys for holding between runs.
9
+ class EntrySerializer < Serializer
10
+ # Convert a JSON hash into an Entry.
11
+ #
12
+ # @param hash [Hash] A JSON document hash.
13
+ #
14
+ # @return [Report::Entry]
15
+ def load(hash)
16
+ @object = Report::Entry.new(
17
+ hash["item"],
18
+ MeasurementSerializer.load(hash["measurement"])
19
+ )
20
+ self
21
+ end
22
+
23
+ # Convert the entry to a Hash.
24
+ #
25
+ # @return [Hash] The entry as a Hash.
26
+ def to_h
27
+ {
28
+ :item => object.label,
29
+ :measurement => MeasurementSerializer.new(object.measurement).to_h,
30
+ }
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,37 @@
1
+ require "benchmark/memory/held_results/serializer"
2
+ require "benchmark/memory/held_results/metric_serializer"
3
+ require "benchmark/memory/measurement"
4
+
5
+ module Benchmark
6
+ module Memory
7
+ class HeldResults
8
+ # Serialize measurements for holding between runs.
9
+ class MeasurementSerializer < Serializer
10
+ # Convert a JSON hash into a Measurement.
11
+ #
12
+ # @param hash [Hash] A JSON document hash.
13
+ #
14
+ # @return [Measurement]
15
+ def load(hash)
16
+ @object = Measurement.new(
17
+ :memory => MetricSerializer.load(hash["memory"]),
18
+ :objects => MetricSerializer.load(hash["objects"]),
19
+ :strings => MetricSerializer.load(hash["strings"])
20
+ )
21
+ self
22
+ end
23
+
24
+ # Convert the measurement to a Hash.
25
+ #
26
+ # @return [Hash] The measurement as a Hash.
27
+ def to_h
28
+ {
29
+ :memory => MetricSerializer.new(object.memory).to_h,
30
+ :objects => MetricSerializer.new(object.objects).to_h,
31
+ :strings => MetricSerializer.new(object.strings).to_h,
32
+ }
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,37 @@
1
+ require "benchmark/memory/held_results/serializer"
2
+ require "benchmark/memory/measurement/metric"
3
+
4
+ module Benchmark
5
+ module Memory
6
+ class HeldResults
7
+ # Serialize metrics for holding between runs.
8
+ class MetricSerializer < Serializer
9
+ # Convert a JSON hash into a Metric.
10
+ #
11
+ # @param hash [Hash] A JSON document hash.
12
+ #
13
+ # @return [Measurement::Metric]
14
+ #
15
+ def load(hash)
16
+ @object = Measurement::Metric.new(
17
+ hash["type"],
18
+ hash["allocated"],
19
+ hash["retained"]
20
+ )
21
+ self
22
+ end
23
+
24
+ # Convert the metric to a Hash.
25
+ #
26
+ # @return [Hash] The metric as a Hash.
27
+ def to_h
28
+ {
29
+ :allocated => object.allocated,
30
+ :retained => object.retained,
31
+ :type => object.type,
32
+ }
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,64 @@
1
+ require "json"
2
+
3
+ module Benchmark
4
+ module Memory
5
+ class HeldResults
6
+ # Serialize objects for holding between runs.
7
+ class Serializer
8
+ # Load an object from a JSON document.
9
+ #
10
+ # @param json [String] A JSON document as a string.
11
+ #
12
+ # @return [Object] The object converted from the JSON document.
13
+ def self.load(json)
14
+ json = JSON.parse(json) if json.is_a?(String)
15
+ new.load(json).object
16
+ end
17
+
18
+ # Instantiate a new serializer.
19
+ #
20
+ # @param object [Object] The object to serialize.
21
+ def initialize(object = nil)
22
+ @object = object
23
+ end
24
+
25
+ # @return [Object] The object to serialize.
26
+ attr_reader :object
27
+
28
+ # Convert a JSON document into an object.
29
+ #
30
+ # @param _hash [Hash] A JSON document hash.
31
+ #
32
+ # @return [Object]
33
+ # @raise [NotImplementedError]
34
+ # If the inheriting subclass didn't implement.
35
+ def load(_hash)
36
+ fail(
37
+ NotImplementedError,
38
+ "You must implement a concrete version in a subclass"
39
+ )
40
+ end
41
+
42
+ # Convert the object to a Hash.
43
+ #
44
+ # @return [Hash] The object as a Hash.
45
+ # @raise [NotImplementedError]
46
+ # If the inheriting subclass didn't implement.
47
+ def to_h
48
+ fail(
49
+ NotImplementedError,
50
+ "You must implement a concrete version in a subclass"
51
+ )
52
+ end
53
+
54
+ # Convert the object to a JSON document.
55
+ #
56
+ # @return [String] The object as a JSON document.
57
+ def to_json
58
+ JSON.generate(to_h)
59
+ end
60
+ alias_method :to_s, :to_json
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,46 @@
1
+ module Benchmark
2
+ module Memory
3
+ # Helper methods for formatting output.
4
+ module Helpers
5
+ # Right-justifies to a length of 20 or adds a line of padding when longer.
6
+ #
7
+ # @param label [#to_s] The label to justify.
8
+ #
9
+ # @return [String] The justified label.
10
+ def rjust(label)
11
+ label = label.to_s
12
+
13
+ if label.size > 20
14
+ "#{label}\n#{' ' * 20}"
15
+ else
16
+ label.rjust(20)
17
+ end
18
+ end
19
+
20
+ # Scale a value into human-understandable terms.
21
+ #
22
+ # @param value [Integer, Float] The value to scale.
23
+ #
24
+ # @return [String] The scaled value.
25
+ def scale(value)
26
+ scale = Math.log10(value)
27
+ scale = 0 if scale.infinite?
28
+ scale = (scale / 3).to_i
29
+ suffix =
30
+ case scale
31
+ when 1 then "k"
32
+ when 2 then "M"
33
+ when 3 then "B"
34
+ when 4 then "T"
35
+ when 5 then "Q"
36
+ else
37
+ scale = 0
38
+ " "
39
+ end
40
+
41
+ format("%10.3f#{suffix}", value.to_f / (1000**scale))
42
+ end
43
+ module_function :scale
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,135 @@
1
+ require "forwardable"
2
+ require "benchmark/memory/job/task"
3
+ require "benchmark/memory/job/io_output"
4
+ require "benchmark/memory/job/null_output"
5
+ require "benchmark/memory/held_results"
6
+ require "benchmark/memory/report"
7
+
8
+ module Benchmark
9
+ module Memory
10
+ # Encapsulate the memory measurements of reports.
11
+ class Job
12
+ extend Forwardable
13
+
14
+ # Instantiate a job for containing memory performance reports.
15
+ #
16
+ # @param output [#puts] The output to use for showing the job results.
17
+ # @param quiet [Boolean] A flag for stopping output.
18
+ #
19
+ # @return [Job]
20
+ def initialize(output: $stdout, quiet: false)
21
+ @compare = false
22
+ @full_report = Report.new
23
+ @held_results = HeldResults.new
24
+ @quiet = quiet
25
+ @output = quiet? ? NullOutput.new : IOOutput.new(output)
26
+ @tasks = []
27
+ end
28
+
29
+ # @return [Report] the full report of all measurements in the job.
30
+ attr_reader :full_report
31
+
32
+ # @return [Array<Task>] the measurement tasks to run.
33
+ attr_reader :tasks
34
+
35
+ def_delegator :@held_results, :holding?
36
+
37
+ # Check whether the job should do a comparison.
38
+ #
39
+ # @return [Boolean]
40
+ def compare?
41
+ @compare
42
+ end
43
+
44
+ # Enable output of a comparison of the different tasks.
45
+ #
46
+ # @return [void]
47
+ def compare!
48
+ @compare = true
49
+ end
50
+
51
+ # Enable holding results to compare between separate runs.
52
+ #
53
+ # @param held_path [String, IO] The location to save the held results.
54
+ #
55
+ # @return [void]
56
+ def hold!(held_path)
57
+ @held_results.path = held_path
58
+ end
59
+
60
+ # Add a measurement entry to the job to measure the specified block.
61
+ #
62
+ # @param label [String] The label for the measured code.
63
+ # @param block [Proc] Code the measure.
64
+ #
65
+ # @raise [ArgumentError] if no code block is specified.
66
+ def report(label = "", &block)
67
+ unless block_given?
68
+ fail ArgumentError, "You did not specify a block for the item"
69
+ end
70
+
71
+ tasks.push Task.new(label, block)
72
+ end
73
+
74
+ # Run the job and outputs its full report.
75
+ #
76
+ # @return [Report]
77
+ def run
78
+ @output.put_header
79
+ @held_results.load
80
+
81
+ tasks.each do |task|
82
+ held = run_task(task)
83
+
84
+ if held
85
+ @output.put_hold_notice
86
+ break
87
+ end
88
+ end
89
+
90
+ full_report
91
+ end
92
+
93
+ # Run a task.
94
+ #
95
+ # @param task [Task]
96
+ #
97
+ # @return [Boolean] A flag indicating whether to hold or not.
98
+ def run_task(task)
99
+ if @held_results.include?(task)
100
+ measurement = @held_results[task.label]
101
+ full_report.add_entry(task, measurement)
102
+ return false
103
+ else
104
+ measurement = task.call
105
+ entry = full_report.add_entry(task, measurement)
106
+ @output.put_entry(entry)
107
+
108
+ if task == tasks.last
109
+ @held_results.cleanup
110
+ false
111
+ else
112
+ @held_results.add_result(entry)
113
+ @held_results.holding?
114
+ end
115
+ end
116
+ end
117
+
118
+ # Run a comparison of the entries and puts it on the output.
119
+ #
120
+ # @return [void]
121
+ def run_comparison
122
+ if compare? && full_report.comparable?
123
+ @output.put_comparison(full_report.comparison)
124
+ end
125
+ end
126
+
127
+ # Check whether the job is set to quiet.
128
+ #
129
+ # @return [Boolean]
130
+ def quiet?
131
+ @quiet
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,50 @@
1
+ require "benchmark/memory/job/io_output/comparison_formatter"
2
+ require "benchmark/memory/job/io_output/entry_formatter"
3
+
4
+ module Benchmark
5
+ module Memory
6
+ class Job
7
+ # Output the results of jobs into an IO.
8
+ class IOOutput
9
+ # Instantiate a new output that writes to an IO.
10
+ #
11
+ # @param io [#puts] The IO to write on.
12
+ def initialize(io)
13
+ @io = io
14
+ end
15
+
16
+ # Put the entry onto the output.
17
+ #
18
+ # @return [void]
19
+ def put_entry(entry)
20
+ @io.puts EntryFormatter.new(entry)
21
+ end
22
+
23
+ # Put the comparison onto the output.
24
+ #
25
+ # @return [void]
26
+ def put_comparison(comparison)
27
+ @io.puts
28
+ @io.puts "Comparison:"
29
+ @io.puts ComparisonFormatter.new(comparison)
30
+ end
31
+
32
+ # Put the header onto the output.
33
+ #
34
+ # @return [void]
35
+ def put_header
36
+ @io.puts "Calculating -------------------------------------"
37
+ end
38
+
39
+ # Put a notice that the execution is holding for another run.
40
+ #
41
+ # @return [void]
42
+ def put_hold_notice
43
+ @io.puts
44
+ @io.puts "Pausing here -- run Ruby again to " \
45
+ "measure the next benchmark..."
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,69 @@
1
+ require "benchmark/memory/helpers"
2
+ require "benchmark/memory/job/io_output/metric_formatter"
3
+
4
+ module Benchmark
5
+ module Memory
6
+ class Job
7
+ class IOOutput
8
+ # Format a comparison for use with the IOOutput.
9
+ class ComparisonFormatter
10
+ include Helpers
11
+
12
+ # Instantiate a formatter to output an comparison into an IO.
13
+ #
14
+ # @param comparison [Report::Comparison] The comparison to format.
15
+ def initialize(comparison)
16
+ @comparison = comparison
17
+ end
18
+
19
+ # @return [Report::Comparison] The comparison to format.
20
+ attr_reader :comparison
21
+
22
+ # Format comparison to a string to put on the output.
23
+ #
24
+ # @return [String]
25
+ def to_s
26
+ return "" unless comparison.possible?
27
+
28
+ output = StringIO.new
29
+ best, rest = *comparison.entries
30
+ rest = Array(rest)
31
+
32
+ add_best_summary(best, output)
33
+
34
+ rest.each do |entry|
35
+ add_comparison(entry, best, output)
36
+ end
37
+
38
+ output.string
39
+ end
40
+
41
+ private
42
+
43
+ def add_best_summary(best, output)
44
+ output << summary_message("%20s: %10i allocated\n", best)
45
+ end
46
+
47
+ def add_comparison(entry, best, output)
48
+ output << summary_message("%20s: %10i allocated - ", entry)
49
+ output << comparison_between(entry, best)
50
+ end
51
+
52
+ def comparison_between(entry, best)
53
+ ratio = entry.allocated_memory.to_f / best.allocated_memory.to_f
54
+
55
+ if ratio.abs > 1
56
+ format("%.2fx more", ratio)
57
+ else
58
+ "same"
59
+ end
60
+ end
61
+
62
+ def summary_message(message, entry)
63
+ format(message, entry.label, entry.allocated_memory)
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end