yarv-prof 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 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: []