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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +16 -0
- data/CONTRIBUTING.md +50 -0
- data/LICENSE.md +21 -0
- data/README.md +183 -0
- data/Rakefile +29 -0
- data/benchmark-memory.gemspec +24 -0
- data/lib/benchmark-memory.rb +2 -0
- data/lib/benchmark/memory.rb +34 -0
- data/lib/benchmark/memory/errors.rb +7 -0
- data/lib/benchmark/memory/held_results.rb +110 -0
- data/lib/benchmark/memory/held_results/entry_serializer.rb +35 -0
- data/lib/benchmark/memory/held_results/measurement_serializer.rb +37 -0
- data/lib/benchmark/memory/held_results/metric_serializer.rb +37 -0
- data/lib/benchmark/memory/held_results/serializer.rb +64 -0
- data/lib/benchmark/memory/helpers.rb +46 -0
- data/lib/benchmark/memory/job.rb +135 -0
- data/lib/benchmark/memory/job/io_output.rb +50 -0
- data/lib/benchmark/memory/job/io_output/comparison_formatter.rb +69 -0
- data/lib/benchmark/memory/job/io_output/entry_formatter.rb +39 -0
- data/lib/benchmark/memory/job/io_output/metric_formatter.rb +34 -0
- data/lib/benchmark/memory/job/null_output.rb +26 -0
- data/lib/benchmark/memory/job/task.rb +50 -0
- data/lib/benchmark/memory/measurement.rb +80 -0
- data/lib/benchmark/memory/measurement/metric.rb +41 -0
- data/lib/benchmark/memory/report.rb +45 -0
- data/lib/benchmark/memory/report/comparison.rb +25 -0
- data/lib/benchmark/memory/report/entry.rb +32 -0
- data/lib/benchmark/memory/version.rb +5 -0
- metadata +101 -0
@@ -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
|