benchmark-memory 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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