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