analyzer 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: db0ce1c214e7feed07483dd08de6bbdbc7cb9161
4
+ data.tar.gz: f233a3477a1295f3264cef66df74c093e887d2c2
5
+ SHA512:
6
+ metadata.gz: 338cafc259ccd43b47185da1dabbe9ff1a35a36d79c846c851da72302055a169f43cbddf2170a7d2d28fcf2e64868c9df1f89d41f25acefbbf24f0753bd54386
7
+ data.tar.gz: 65331528d9a72d2e0ccba60521db9673063d25bd177f5349200500b1791cb8a06d339340dcc6f46b68da42e45adf4629372e698d1540dc0adde881430dffd1af
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2017 Jon Bracy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,36 @@
1
+ # Analyzer
2
+ A Performance analyzer for Ruby
3
+
4
+ Synopsis
5
+ --------
6
+
7
+ ```bash
8
+ $ analyze test.rb ...
9
+ ```
10
+
11
+ Description
12
+ -----------
13
+
14
+ Analyzer runs the provided ruby file(s) to test Iterations per second,
15
+ allocations and memory usage.
16
+
17
+ The scripts can be a normal ruby script or can be split into two parts by the
18
+ line `__SETUP__`, everything before will be run once and everything after will
19
+ be what is tested.
20
+
21
+ Options
22
+ -------
23
+
24
+ `-b`, `--bootstrap`: A ruby script to run once before all the test.
25
+
26
+ `-l`, `--lib`: A script to be included before the `bootstrap` script and all `test` scripts.
27
+
28
+ `-o`, `--output`: The destination for the results. The result is a `png` file.
29
+
30
+
31
+ Example Report
32
+ --------------
33
+
34
+ This report was generated by the command: `analyzer turbostreamer.rb jbuilder.rb -l setup.rb`
35
+
36
+ ![Viking.js](/example_report.png)
@@ -0,0 +1,22 @@
1
+ require File.expand_path("../lib/analyzer/version", __FILE__)
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "analyzer"
5
+ s.version = Analyzer::VERSION
6
+ s.licenses = ['MIT']
7
+ s.authors = ["Jon Bracy"]
8
+ s.email = ["jonbracy@gmail.com"]
9
+ s.homepage = "https://github.com/malomalo/analyzer"
10
+ s.summary = %q{A Performance analyzer for Ruby}
11
+ s.metadata = { "source_code_uri" => "https://github.com/malomalo/analyzer" }
12
+
13
+ s.files = `git ls-files`.split("\n")
14
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
15
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
16
+ s.require_paths = ["lib"]
17
+
18
+ s.required_ruby_version = '>= 2'
19
+ s.requirements << 'gnuplot'
20
+
21
+ s.add_runtime_dependency 'benchmark-ips', '~> 2.7'
22
+ end
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env ruby
2
+ require 'optparse'
3
+ require File.expand_path('./../../lib/analyzer', __FILE__)
4
+
5
+ options = {}
6
+ OptionParser.new do |opts|
7
+ opts.banner = "Usage: analyze [switches] script [script] ... [report.png]"
8
+
9
+ opts.on("-b", "--bootstrap SCRIPT", "Script to run before all test") do |v|
10
+ options[:bootstrap] = v
11
+ end
12
+
13
+ opts.on("-l", "--lib SCRIPT", "Script require before the bootstrap and all test scripts") do |v|
14
+ options[:lib] = v
15
+ end
16
+
17
+ opts.on("-n x,y,z", Array, "Example 'list' of arguments") do |list|
18
+ options[:n] = list.map(&:to_i)
19
+ end
20
+
21
+ opts.on('-o', '--output DESTINATION', 'File where output will be placed') do |v|
22
+ options[:output] = v
23
+ end
24
+ end.parse!
25
+ files = ARGV.clone
26
+
27
+ analyzer = Analyzer.new(*files, **options)
28
+ analyzer.plot(File.expand_path(options[:output] || 'report.png'))
Binary file
@@ -0,0 +1,107 @@
1
+ require 'erb'
2
+ require 'json'
3
+ require 'tmpdir'
4
+
5
+ class Analyzer
6
+
7
+ def initialize(*test_files, bootstrap: nil, lib: nil, n: nil)
8
+ @bootstrap = bootstrap
9
+ @lib = lib
10
+ @n = n
11
+ @test_files = test_files
12
+ end
13
+
14
+ def key(value)
15
+ File.basename(value, ".*")
16
+ end
17
+
18
+ def tests
19
+ @test_files.map{ |f| key(f) }
20
+ end
21
+
22
+ def bootstrap
23
+ if @bootstrap
24
+ system("ruby", "-r", "#{File.expand_path(@lib)}", File.expand_path(@bootstrap))
25
+ if $?.exitstatus > 0
26
+ exit 1
27
+ end
28
+ end
29
+ end
30
+
31
+ def write_results_for(type, dir)
32
+ Dir.mkdir(File.join(dir, type))
33
+ @test_files.each do |src|
34
+ File.open(File.join(dir, type, key(src)), 'w') do |file|
35
+ send(:"output_#{type}_results", calculate_results_for(type, File.read(src)), file)
36
+ end
37
+ end
38
+ end
39
+
40
+ def calculate_results_for(type, src)
41
+ lib = @lib ? File.read(File.expand_path(@lib)) : ''
42
+
43
+ if src.index("\n__SETUP__\n")
44
+ src = src.split("\n__SETUP__\n")
45
+ setup = src[0]
46
+ src = src[1]
47
+ else
48
+ setup = ''
49
+ end
50
+
51
+ analyzer_dir = File.expand_path("../", __FILE__)
52
+ template = File.read(File.expand_path("../templates/#{type}.rb.erb", __FILE__))
53
+ _script = ''
54
+ ERB.new(template, nil, nil, "_script").result(binding)
55
+ results = `ruby <<'TESTSCRIPT'\n#{_script}\nTESTSCRIPT`
56
+
57
+ if $? == 0
58
+ JSON.parse(results)
59
+ else
60
+ puts results
61
+ exit
62
+ end
63
+ end
64
+
65
+ def output_ips_results(results, output)
66
+ output.write(results.join("\n"))
67
+ end
68
+
69
+ def output_gc_results(results, output)
70
+ last_row = results.shift
71
+ first_row = last_row
72
+ results.each do |row|
73
+ output.puts [row[0], row[0] - first_row[0], row[1], row[2], row[1] - last_row[1], row[2] - last_row[2], row[3], row[4], row[3] - last_row[3], row[4] - last_row[4]].join(' ')
74
+ last_row = row
75
+ end
76
+ end
77
+
78
+ def output_rss_results(results, output)
79
+ last_row = results.shift
80
+ first_row = last_row
81
+ results.each do |row|
82
+ output.puts [row[0], row[0] - first_row[0], row[1], row[1] - last_row[1]].join(' ')
83
+ last_row = row
84
+ end
85
+ end
86
+
87
+ def escape(value)
88
+ value.gsub('_', '\\\\_')
89
+ end
90
+
91
+ def plot(output)
92
+ Dir.mktmpdir do |dir|
93
+ write_results_for('ips', dir)
94
+ write_results_for('gc', dir)
95
+ write_results_for('rss', dir)
96
+
97
+ template = File.read(File.expand_path('../templates/gnuplot.sh.erb', __FILE__))
98
+ _script = ''
99
+ data_dir = dir
100
+ ERB.new(template, nil, nil, "_script").result(binding)
101
+ `gnuplot <<-GNUPLOT\n#{_script}\nGNUPLOT`
102
+ end
103
+ end
104
+
105
+ # ttfb
106
+ # ttlb
107
+ end
@@ -0,0 +1,46 @@
1
+ require 'bigdecimal'
2
+
3
+ class Analyzer
4
+ class Rss
5
+
6
+ KB_TO_BYTE = 1024 # 2**10 = 1024
7
+ MB_TO_BYTE = 1_048_576 # 1024**2 = 1_048_576
8
+ GB_TO_BYTE = 1_073_741_824 # 1024**3 = 1_073_741_824
9
+ CONVERSION = { "kb" => KB_TO_BYTE, "mb" => MB_TO_BYTE, "gb" => GB_TO_BYTE }
10
+
11
+ def initialize(pid = Process.pid)
12
+ @pid = pid
13
+ @status_file = "/proc/#{@pid}/status"
14
+ @linux = File.exists?(@status_file)
15
+ end
16
+
17
+ def bytes
18
+ @linux ? (linux_status_memory || ps_memory) : ps_memory
19
+ end
20
+
21
+ def inspect
22
+ "#<#{self.class}:0x%08x @bytes=#{bytes}>" % (object_id * 2)
23
+ end
24
+
25
+ # linux stores memory info in a file "/proc/#{@pid}/status"
26
+ # If it's available it uses less resources than shelling out to ps
27
+ def linux_status_memory
28
+ line = File.new(@status_file).each_line.detect { |l| l.start_with?('VmRSS'.freeze) }
29
+ if line
30
+ line = line.split(nil)
31
+ if line.length == 3
32
+ CONVERSION[line[1].downcase!] * line[2].to_i
33
+ end
34
+ end
35
+ rescue Errno::EACCES, Errno::ENOENT
36
+ 0
37
+ end
38
+
39
+ # Pull memory from `ps` command, takes more resources and can freeze
40
+ # in low memory situations
41
+ def ps_memory
42
+ KB_TO_BYTE * BigDecimal.new(`ps -o rss= -p #{@pid}`)
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,3 @@
1
+ class Analyzer
2
+ VERSION = '1.0.0'
3
+ end
@@ -0,0 +1,61 @@
1
+ <%= lib %>
2
+ <%= setup %>
3
+
4
+ __analyzer_not_done = true
5
+ __analyzer_t = Time.now.to_f
6
+ __analyzer_gc_stat = GC.stat
7
+
8
+ STDOUT.write("[[".freeze)
9
+ STDOUT.write(__analyzer_t.to_s)
10
+ STDOUT.write(",".freeze)
11
+ STDOUT.write(__analyzer_gc_stat[:total_allocated_objects].to_s)
12
+ STDOUT.write(",".freeze)
13
+ STDOUT.write(__analyzer_gc_stat[:total_freed_objects].to_s)
14
+ STDOUT.write(",".freeze)
15
+ STDOUT.write(__analyzer_gc_stat[:minor_gc_count].to_s)
16
+ STDOUT.write(",".freeze)
17
+ STDOUT.write(__analyzer_gc_stat[:major_gc_count].to_s)
18
+ STDOUT.write("]".freeze)
19
+
20
+ __analyzer_mt = Thread.new do
21
+ while __analyzer_not_done do
22
+ __analyzer_sleep_t = 0.02 - (Time.now.to_f - __analyzer_t)
23
+ sleep(__analyzer_sleep_t) if __analyzer_sleep_t > 0
24
+
25
+ __analyzer_t = Time.now.to_f
26
+ __analyzer_gc_stat = GC.stat
27
+ STDOUT.write(",[".freeze)
28
+ STDOUT.write(__analyzer_t.to_s)
29
+ STDOUT.write(",".freeze)
30
+ STDOUT.write(__analyzer_gc_stat[:total_allocated_objects].to_s)
31
+ STDOUT.write(",".freeze)
32
+ STDOUT.write(__analyzer_gc_stat[:total_freed_objects].to_s)
33
+ STDOUT.write(",".freeze)
34
+ STDOUT.write(__analyzer_gc_stat[:minor_gc_count].to_s)
35
+ STDOUT.write(",".freeze)
36
+ STDOUT.write(__analyzer_gc_stat[:major_gc_count].to_s)
37
+ STDOUT.write("]".freeze)
38
+ end
39
+
40
+ __analyzer_t = Time.now.to_f
41
+ __analyzer_gc_stat = GC.stat
42
+ STDOUT.write(",[".freeze)
43
+ STDOUT.write(__analyzer_t.to_s)
44
+ STDOUT.write(",".freeze)
45
+ STDOUT.write(__analyzer_gc_stat[:total_allocated_objects].to_s)
46
+ STDOUT.write(",".freeze)
47
+ STDOUT.write(__analyzer_gc_stat[:total_freed_objects].to_s)
48
+ STDOUT.write(",".freeze)
49
+ STDOUT.write(__analyzer_gc_stat[:minor_gc_count].to_s)
50
+ STDOUT.write(",".freeze)
51
+ STDOUT.write(__analyzer_gc_stat[:major_gc_count].to_s)
52
+ STDOUT.write("]]".freeze)
53
+
54
+ end
55
+
56
+ __analyzer_start = Time.now.to_f
57
+ while Time.now.to_f - __analyzer_start < 5
58
+ <%= src %>
59
+ end
60
+ __analyzer_not_done = false
61
+ __analyzer_mt.join
@@ -0,0 +1,76 @@
1
+ reset
2
+ set terminal pngcairo enhanced font "arial,12" fontscale 1.0 size 900, 1300
3
+ set output '<%= output %>'
4
+
5
+ # define axis
6
+ # remove border on top and right and set color to gray
7
+ set style line 11 lc rgb '#808080' lt 1
8
+ set border 3 back ls 11
9
+ set tics nomirror
10
+ # define grid
11
+ set style line 12 lc rgb '#808080' lt 0 lw 1
12
+ set grid back ls 12
13
+
14
+ set key center bottom horizontal
15
+
16
+ set style line 1 linetype 1 linecolor rgb '#5e64ff' pointtype 1 pointsize 1 linewidth 1 # blue
17
+ set style line 2 linetype 1 linecolor rgb '#ffa00a' pointtype 1 pointsize 1 linewidth 1 # orange
18
+ set style line 3 linetype 1 linecolor rgb '#28a745' pointtype 1 pointsize 1 linewidth 1 # green
19
+ set style line 4 linetype 1 linecolor rgb '#b554ff' pointtype 1 pointsize 1 linewidth 1 # purple
20
+ set style line 5 linetype 1 linecolor rgb '#ff5858' pointtype 1 pointsize 1 linewidth 1 # red
21
+ set style line 6 linetype 1 linecolor rgb '#FEEF72' pointtype 1 pointsize 1 linewidth 1 # yellow
22
+ set style line 7 linetype 1 linecolor rgb '#7CD6FD' pointtype 1 pointsize 1 linewidth 1 # light-blue
23
+ set style line 8 linetype 1 linecolor rgb '#8b1a0e' pointtype 1 pointsize 1 linewidth 1 # dark-red
24
+
25
+
26
+ set multiplot layout 5, 1
27
+ set tmargin 4
28
+ set bmargin 4
29
+
30
+ set title "Iterations / Sec" font ",14"
31
+ set key off
32
+ set format y '%.1s %c'
33
+
34
+
35
+
36
+ set boxwidth 0.15 absolute
37
+
38
+ set style data boxplot
39
+ set style boxplot range 2 nooutliers candlesticks
40
+ set style fill solid 0.2
41
+ set xtics (<%= tests.map.with_index { |f, i| "'#{escape(f)}' #{i+1}"}.join(', ') %>) scale 0,0
42
+
43
+ plot <%= tests.map.with_index { |t, i| "'#{File.join(data_dir, 'ips', t)}' using (#{i+1}.0):1 with boxplot linestyle #{i+1}" }.join(', ') %>
44
+
45
+
46
+ set title "GC Objects Allocated"
47
+ set key on
48
+ set yrange [0:*]
49
+ set xrange [0:5]
50
+ set xtics autofreq
51
+
52
+
53
+ plot <%= tests.map.with_index { |k, i| "'#{File.join(data_dir, 'gc', k)}' using 2:5 title '#{escape(k)}' with steps linestyle #{i+1}" }.join(', ') %>
54
+
55
+ set title "GC Objects Freed"
56
+ set key on
57
+ set yrange [0:*]
58
+ set xrange [0:5]
59
+
60
+ plot <%= tests.map.with_index { |k, i| "'#{File.join(data_dir, 'gc', k)}' using 2:6 title '#{escape(k)}' with steps linestyle #{i+1}" }.join(', ') %>
61
+
62
+ set title "GC Retained"
63
+ set key on
64
+ set yrange [0:*]
65
+ set xrange [0:5]
66
+
67
+ plot <%= tests.map.with_index { |k, i| "'#{File.join(data_dir, 'gc', k)}' using 2:(\\$3-\\$4) title '#{escape(k)}' with steps linestyle #{i+1}" }.join(', ') %>
68
+
69
+ # , \\$alloc#{k} using 1:8 with impulses linestyle 7 title 'minor' axes x1y2, \\$alloc#{k} using 1:9 with impulses linestyle 8 title 'major' axes x1y2
70
+
71
+ set title "RSS"
72
+ set key on
73
+ set yrange [0:*]
74
+ set xrange [0:5]
75
+
76
+ plot <%= tests.map.with_index { |k, i| "'#{File.join(data_dir, 'rss', k)}' using 2:3 title '#{escape(k)}' with steps linestyle #{i+1}" }.join(', ') %>
@@ -0,0 +1,23 @@
1
+ <%= lib %>
2
+ <%= setup %>
3
+
4
+ require 'benchmark/ips'
5
+
6
+ class Benchmark::IPS::Job
7
+ def create_stats(samples)
8
+ samples
9
+ end
10
+
11
+ def create_report(label, measured_us, iter, samples, cycles)
12
+ require 'json'
13
+ STDOUT.puts JSON.generate(samples)
14
+ @full_report.add_entry label, measured_us, iter, Benchmark::IPS::Stats::SD.new(samples), cycles
15
+ end
16
+ end
17
+
18
+ Benchmark.ips(quiet: true) do |x|
19
+ x.config(stats: :bootstrap, confidence: 98)
20
+ x.report("analyze", <<-CODEFILE)
21
+ <%= src %>
22
+ CODEFILE
23
+ end
@@ -0,0 +1,49 @@
1
+ <%= lib %>
2
+ <%= setup %>
3
+
4
+ __analyzer_read, __analyzer_write = IO.pipe
5
+ if child_pid = fork
6
+ __analyzer_read.close
7
+ require '<%= File.join(analyzer_dir, 'analyzer/rss') %>'
8
+
9
+ not_done = true
10
+ bytes = 0
11
+ monitor = Analyzer::Rss.new(child_pid)
12
+
13
+
14
+
15
+ time = Time.now.to_f
16
+ bytes = monitor.bytes
17
+
18
+ STDOUT.write("[[".freeze)
19
+ STDOUT.write(time.to_s)
20
+ STDOUT.write(",".freeze)
21
+ STDOUT.write(bytes.to_s)
22
+ STDOUT.write("]".freeze)
23
+
24
+ __analyzer_write.puts "go".freeze
25
+ __analyzer_write.close
26
+ while bytes != 0 do
27
+ sleep_time = 0.02 - (Time.now.to_f - time)
28
+ sleep(sleep_time) if sleep_time > 0
29
+
30
+ time = Time.now.to_f
31
+ bytes = monitor.bytes
32
+ STDOUT.write(",[".freeze)
33
+ STDOUT.write(time.to_s)
34
+ STDOUT.write(",".freeze)
35
+ STDOUT.write(bytes.to_s)
36
+ STDOUT.write("]".freeze)
37
+ end
38
+
39
+ STDOUT.write("]".freeze)
40
+ else
41
+ __analyzer_write.close
42
+ raise 'fail' if __analyzer_read.gets != "go\n".freeze
43
+ __analyzer_read.close
44
+
45
+ __analyzer_start = Time.now.to_f
46
+ while Time.now.to_f - __analyzer_start < 5
47
+ <%= src %>
48
+ end
49
+ end
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: analyzer
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Jon Bracy
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-11-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: benchmark-ips
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.7'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.7'
27
+ description:
28
+ email:
29
+ - jonbracy@gmail.com
30
+ executables:
31
+ - analyze
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - LICENSE
36
+ - README.md
37
+ - analyzer.gemspec
38
+ - bin/analyze
39
+ - example_report.png
40
+ - lib/analyzer.rb
41
+ - lib/analyzer/rss.rb
42
+ - lib/analyzer/version.rb
43
+ - lib/templates/gc.rb.erb
44
+ - lib/templates/gnuplot.sh.erb
45
+ - lib/templates/ips.rb.erb
46
+ - lib/templates/rss.rb.erb
47
+ homepage: https://github.com/malomalo/analyzer
48
+ licenses:
49
+ - MIT
50
+ metadata:
51
+ source_code_uri: https://github.com/malomalo/analyzer
52
+ post_install_message:
53
+ rdoc_options: []
54
+ require_paths:
55
+ - lib
56
+ required_ruby_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '2'
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ requirements:
67
+ - gnuplot
68
+ rubyforge_project:
69
+ rubygems_version: 2.6.11
70
+ signing_key:
71
+ specification_version: 4
72
+ summary: A Performance analyzer for Ruby
73
+ test_files: []