yarv-prof 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c5d67e8ac138faae3a64a5dcfbf51e8884b7f2d1
4
+ data.tar.gz: ccb8273915b075b0b8dec96618438038d269d314
5
+ SHA512:
6
+ metadata.gz: 6a2c20570f872587c569853617f4f3400b6b4ca388996fe72182f2d3ee29504c7a679e5bc19bd1a539eac44a74eaf1d930278bc6ca6b403eef64413a33bbeccf
7
+ data.tar.gz: 0eaeaf451efb07b4363f612a94ff153fa17b6a7ee2cec5965c19493e58d226835d7eca6103a616031f7b93d8d0cb9e799682fc0c48a03966ab35fafce9f5637a
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/README.md ADDED
@@ -0,0 +1,80 @@
1
+ ## Prerequisite
2
+
3
+ - DTrace must be installed on your System
4
+ - You need to edit MRI source code as follows(since pre-compiled MRI doesn't provide `insn` probe, you need to enable this manually, as of Ruby2.4.0)
5
+
6
+ ```
7
+ $ git clone https://github.com/ruby/ruby.git
8
+ $ cd ruby && autoconf
9
+ $ ./configure
10
+ $ vi vm_opts.h # set the VM_COLLECT_USAGE_DETAILS flag to 1 manually
11
+ $ make && make install
12
+ $ ./ruby -v
13
+ ```
14
+
15
+ ## Getting Started
16
+
17
+ Since we use custom built MRI binary, the profiling command will look like following(unless you have installed custom built MRI and rubygems onto your system)
18
+
19
+ ```
20
+ $ gem install yarv-prof
21
+ $ sudo ./ruby -I `gem env gemdir`/gems/yarv-prof*/lib/ -r yarv-prof -e "YarvProf.start; p :hello; YarvProf.end"
22
+ $ yarv-prof --load /tmp/yarv-prof/20161128_214131.dump
23
+ total number of instruction calls: 26
24
+ insn count total_walltime mean variance stdev
25
+ ---------------------------------------------------------------------------------------------------
26
+ opt_send_without_block 4(15%) 298567(33%) 74642 2362231992 48603
27
+ trace 4(15%) 139888(15%) 34972 566184557 23795
28
+ getinlinecache 2(7%) 76616(8%) 38308 1093622912 33070
29
+ getconstant 2(7%) 65426(7%) 32713 674546450 25972
30
+ setinlinecache 2(7%) 51301(5%) 25650 218718612 14789
31
+ leave 2(7%) 46497(5%) 23248 54444612 7379
32
+ putobject 2(7%) 44293(4%) 22146 182576940 13512
33
+ getinstancevariable 2(7%) 42118(4%) 21059 94146642 9703
34
+ pop 2(7%) 41412(4%) 20706 2056392 1434
35
+ opt_not 1(3%) 30059(3%) 30059 NaN NaN
36
+ jump 1(3%) 27644(3%) 27644 NaN NaN
37
+ putself 1(3%) 23844(2%) 23844 NaN NaN
38
+ branchunless 1(3%) 15042(1%) 15042 NaN NaN
39
+ ```
40
+
41
+ ## Usage
42
+
43
+ ```
44
+ YarvProf.start(clock: :cpu, out:'~/log/')
45
+ p :hello
46
+ YarvProf.end
47
+ ```
48
+
49
+ This is the sample usage of YarvProf in your code. YarvProf#start can take 2 keyword args, one is for the flag to switch measurement mode (:cpu or :wall), and the other one is to specify the target directory the dump file will be seved in.
50
+
51
+ ```
52
+ $ yarvprof --load ./result.dump --insn getlocal_OP__WC__1
53
+ ```
54
+
55
+ And this is the sample usage of yarv-prof CLI command, which is specifically designed to parse and view dumped data. yarv-prof command can take following options.
56
+
57
+ ```
58
+ Usage: yarv-prof [options]
59
+ -v, --version Print version
60
+ --load=FILENAME Load .dump file
61
+ --csv Report with csv format
62
+ --raw Show raw log data with time series alignment
63
+ --insn=VALUE Show a specific instruction only
64
+ ```
65
+
66
+ ## TBD in future
67
+
68
+ - More detailed information regarding YARV instructions
69
+ - Improve performance
70
+ - System Tap support for linux environment(only if needed)
71
+ - Sampling profiler mode(if needed)
72
+ - Docker support etc
73
+
74
+ ## Other resources
75
+
76
+ - You may want to refer [my presentation slide](TBD) at RubyConf Taiwan 2016
77
+
78
+ ## License
79
+
80
+ MIT
data/bin/yarv-prof ADDED
@@ -0,0 +1,152 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ if !ARGV.index('-d') then
4
+ require 'rubygems'
5
+ else
6
+ Encoding.default_external = 'UTF-8'
7
+ ['..', '../lib'].each do |path|
8
+ $LOAD_PATH.unshift File.expand_path(File.join(File.dirname(__FILE__), path))
9
+ end
10
+ ARGV.delete('-d')
11
+ end
12
+
13
+ require 'yarv-prof'
14
+ require 'optparse'
15
+ require 'enumerable/statistics'
16
+
17
+ class DatasetBase
18
+ attr_reader :instructions, :total_time, :total_count, :measure_mode
19
+ def initialize
20
+ @total_time = 0
21
+ @total_count = 0
22
+ end
23
+
24
+ def add_entry(insn, elapsed_time)
25
+ @total_time = @total_time + elapsed_time
26
+ @total_count = @total_count + 1
27
+ end
28
+
29
+ def set_measure_mode(label)
30
+ @measure_mode = label == "vtimestamp" ? "cputime" : "walltime"
31
+ end
32
+ end
33
+
34
+ class SummaryDataset < DatasetBase
35
+ def initialize
36
+ @instructions = {}
37
+ super
38
+ end
39
+
40
+ def add_entry(insn, elapsed_time)
41
+ @instructions[insn] = [] if @instructions[insn].nil?
42
+ @instructions[insn] << elapsed_time
43
+ super
44
+ end
45
+ end
46
+
47
+ class TimeSeriesDataset < DatasetBase
48
+ def initialize
49
+ @instructions = []
50
+ super
51
+ end
52
+
53
+ def add_entry(insn, elapsed_time)
54
+ @instructions << [insn, elapsed_time]
55
+ super
56
+ end
57
+ end
58
+
59
+ class DumpLogParser
60
+ attr_reader :dataset
61
+ def initialize(filename, dataset)
62
+ @filename = filename
63
+ @dataset = dataset
64
+ end
65
+ def parse(insn=nil)
66
+ prev = nil
67
+ File.read(@filename).each_line{|line|
68
+ line.chomp!
69
+ next if line.empty?
70
+ if line.split(",")[0] == "insn" then
71
+ @dataset.set_measure_mode line.split(",")[1]
72
+ else
73
+ if !prev.nil? && (insn.nil? || insn==prev[0])
74
+ @dataset.add_entry prev[0], (line.split(",")[1].to_i - prev[1].to_i)
75
+ end
76
+ prev = line.split(",")
77
+ end
78
+ }
79
+ end
80
+
81
+ end
82
+
83
+ class Presentation
84
+ attr_accessor :filename, :csv, :raw, :insn
85
+
86
+ def make
87
+ @parser = DumpLogParser.new(@filename, @raw || @insn ? TimeSeriesDataset.new : SummaryDataset.new )
88
+ @parser.parse(@insn)
89
+ end
90
+
91
+ def display
92
+ ds = @parser.dataset
93
+ if ds.class == TimeSeriesDataset
94
+ if @csv
95
+ puts "insn,#{ds.measure_mode}"
96
+ ds.instructions.each{|e|
97
+ puts "%s,%d" % [e[0], e[1]]
98
+ }
99
+ else
100
+ puts "isns".ljust(30)+ds.measure_mode.rjust(15)
101
+ puts "-"*45
102
+ ds.instructions.each{|e|
103
+ puts ("%s" % e[0]).ljust(30) + ("%d" % e[1]).rjust(15)
104
+ }
105
+ end
106
+ else
107
+ if @csv
108
+ puts "insn,count,total_#{ds.measure_mode},mean,variance,stdev"
109
+ ds.instructions.each{|insn, values|
110
+ puts "%s,%d,%d,%.0f,%.0f,%.0f" % [insn, values.count, values.inject(:+), values.mean, values.variance, values.stdev]
111
+ }
112
+ else
113
+ puts "total number of instruction calls: #{ds.total_count}"
114
+ presentation = []
115
+ puts "insn".ljust(30) + "count".rjust(14) + "total_#{ds.measure_mode}".rjust(20) + "mean".rjust(10) + "variance".rjust(16) + "stdev".rjust(9)
116
+ puts "-"*99
117
+ ds.instructions.each{|insn, values|
118
+ #presentation << [values.inject(:+), "%s:\n count=%d(%d%s), total=%d(%d%s), mean=%.0f, variance=%.0f, stdev=%.0f\n" % [insn, values.count, (values.count*100/@parser.dataset.total_count), "%%", values.inject(:+), (values.inject(:+)*100/@parser.dataset.total_time), "%%", values.mean, values.variance, values.stdev] ]
119
+ presentation << [values.inject(:+), "%s%s%s%s%s%s\n" % [
120
+ insn.ljust(30),
121
+ ("%d(%d%s)" % [values.count, (values.count*100/ds.total_count), "%%"]).rjust(15),
122
+ ("%d(%d%s)" % [values.inject(:+), (values.inject(:+)*100/ds.total_time), "%%"]).rjust(21),
123
+ ("%.0f" % values.mean).rjust(10),
124
+ ("%.0f" % values.variance).rjust(16),
125
+ ("%.0f" % values.stdev).rjust(9)
126
+ ]]
127
+ }
128
+ presentation.sort{|a,b|
129
+ b[0] <=> a[0]
130
+ }.each{|e|
131
+ printf e[1]
132
+ }
133
+ end
134
+ end
135
+ end
136
+ end
137
+
138
+ if ARGV.empty?
139
+ raise "Use -h option to know what commands are available"
140
+ else
141
+ report = Presentation.new
142
+ Options = OptionParser.new do |opts|
143
+ opts.on("-v", "--version", "Print version") {|v| puts YarvProf::VERSION; exit 0}
144
+ opts.on("--load=FILENAME", "Load .dump file") {|filename| report.filename = filename}
145
+ opts.on("--csv", "Report with csv format") {report.csv = true}
146
+ opts.on("--raw", "Show raw log data with time series alignment") {report.raw = true}
147
+ opts.on("--insn=VALUE", "Show a specific instruction only") { |insn| report.insn = insn }
148
+ end
149
+ Options.parse!(ARGV)
150
+ report.make
151
+ report.display
152
+ end
@@ -0,0 +1,3 @@
1
+ class YarvProf
2
+ VERSION = "0.1.0"
3
+ end
data/lib/yarv-prof.rb ADDED
@@ -0,0 +1,37 @@
1
+ require 'yarv-prof/version'
2
+ require 'tempfile'
3
+
4
+ class YarvProf
5
+ class << self
6
+ def start(clock: :wall, out:'/tmp/yarv-prof/')
7
+ at_exit do
8
+ Process.kill(:TERM, @pid) if !@pid.nil?
9
+ FileUtils.remove_entry @file.path if File.exists?(@file.path)
10
+ end
11
+ @measure_mode = clock == :cpu ? "vtimestamp" : "timestamp"
12
+ @dump_to = out
13
+ @file = Tempfile.new('.yarv-prof.d')
14
+ @file.puts <<EOS
15
+ dtrace:::BEGIN{
16
+ printf("insn,#{@measure_mode}\\n");
17
+ }
18
+
19
+ ruby#{Process.pid}:::insn{
20
+ printf("%s,%d\\n", copyinstr(arg0), #{@measure_mode});
21
+ }
22
+ EOS
23
+ @file.close
24
+ FileUtils.mkdir @dump_to if !File.directory?(@dump_to)
25
+ dumpfile = Time.now.strftime('%Y%m%d_%H%M%S.dump')
26
+ @pid = Process.spawn("dtrace -q -s '#{@file.path}'", :err => :out,:out => @dump_to + dumpfile)
27
+ #while File.read(DUMPTO+dumpfile).size < 10 do
28
+ # sleep 0.01
29
+ #end
30
+ sleep 0.5
31
+ end
32
+
33
+ def end
34
+ Process.kill(:TERM, @pid) if !@pid.nil?
35
+ end
36
+ end
37
+ end
data/yarv-prof.gemspec ADDED
@@ -0,0 +1,22 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'yarv-prof/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'yarv-prof'
7
+ s.version = YarvProf::VERSION
8
+ s.homepage = 'https://github.com/remore/yarv-prof'
9
+
10
+ s.authors = 'Kei Sawada(@remore)'
11
+ s.email = 'k@swd.cc'
12
+
13
+ s.files = `git ls-files`.split("\n")
14
+ s.bindir = 'bin'
15
+ s.executables = 'yarv-prof'
16
+
17
+ s.summary = 'A DTrace-based YARV profiler'
18
+ s.description = 'A DTrace-based YARV profiler'
19
+ s.license = 'MIT'
20
+
21
+ s.add_dependency "enumerable-statistics"
22
+ end
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: yarv-prof
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Kei Sawada(@remore)
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-11-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: enumerable-statistics
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: A DTrace-based YARV profiler
28
+ email: k@swd.cc
29
+ executables:
30
+ - yarv-prof
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - Gemfile
35
+ - README.md
36
+ - bin/yarv-prof
37
+ - lib/yarv-prof.rb
38
+ - lib/yarv-prof/version.rb
39
+ - yarv-prof.gemspec
40
+ homepage: https://github.com/remore/yarv-prof
41
+ licenses:
42
+ - MIT
43
+ metadata: {}
44
+ post_install_message:
45
+ rdoc_options: []
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ requirements: []
59
+ rubyforge_project:
60
+ rubygems_version: 2.5.1
61
+ signing_key:
62
+ specification_version: 4
63
+ summary: A DTrace-based YARV profiler
64
+ test_files: []