znmeb / profiling-ruby-interpreters

This is a project to profile Ruby interpreters on Linux systems

This URL has Read+Write access

profiling-ruby-interpreters / linux_process_timing.rb
100644 260 lines (229 sloc) 7.089 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
# Utilities for getting process data from recent Linux kernels
# M. Edward (Ed) Borasky
 
# do we have a recent enough version of Linux?
# the stuff we want is only in kernels since 2.6.22-rc3
 
def linux_recent_enough?
  return false if RUBY_PLATFORM !~/linux/ # well, duh!
  zrelease = `uname -r`.sub(/-.*$/, "").chomp.split('.')
  return false if zrelease[0] < 2
  return false if zrelease[1] < 6
  return false if zrelease[2] < 22
  return true
end
 
# we need these to convert to human-readable units
def ticks_per_second
  `getconf CLK_TCK`.chomp.to_f
end
 
def page_bytes
  `getconf PAGESIZE`.chomp.to_f
end
 
# Definitions of what's in /proc/<pid>/statm and /proc/<pid>/statm
# (from Gentoo Linux 2.6.25-r6)
 
# See /usr/src/linux/Documentation/filesystems/proc.txt on your system if you
# want to hack on this code (for example, to backport it to "stable" versions
# of Linux). If you don't *have*
# /usr/src/linux/Documentation/filesystems/proc.txt, install the Linux source
# package. :)
 
# Table 1-2: Contents of the statm files (as of 2.6.8-rc3)
# ..............................................................................
# Field Content
# size total program size (pages) (same as VmSize in status)
# resident size of memory portions (pages) (same as VmRSS in status)
# shared number of pages that are shared (i.e. backed by a file)
# trs number of pages that are 'code' (not including libs; broken,
# includes data segment)
# lrs number of pages of library (always 0 on 2.6)
# drs number of pages of data/stack (including libs; broken,
# includes library text)
# dt number of dirty pages (always 0 on 2.6)
# ..............................................................................
 
# field names in /proc/<pid>/statm
STATM_NAMES = %w(
size
resident
shared
trs
lrs
drs
dt
)
 
#
# Table 1-3: Contents of the stat files (as of 2.6.22-rc3)
# ..............................................................................
# Field Content
# pid process id
# tcomm filename of the executable
# state state (R is running, S is sleeping, D is sleeping in an
# uninterruptible wait, Z is zombie, T is traced or stopped)
# ppid process id of the parent process
# pgrp pgrp of the process
# sid session id
# tty_nr tty the process uses
# tty_pgrp pgrp of the tty
# flags task flags
# min_flt number of minor faults
# cmin_flt number of minor faults with child's
# maj_flt number of major faults
# cmaj_flt number of major faults with child's
# utime user mode jiffies
# stime kernel mode jiffies
# cutime user mode jiffies with child's
# cstime kernel mode jiffies with child's
# priority priority level
# nice nice level
# num_threads number of threads
# it_real_value (obsolete, always 0)
# start_time time the process started after system boot
# vsize virtual memory size
# rss resident set memory size
# rsslim current limit in bytes on the rss
# start_code address above which program text can run
# end_code address below which program text can run
# start_stack address of the start of the stack
# esp current value of ESP
# eip current value of EIP
# pending bitmap of pending signals (obsolete)
# blocked bitmap of blocked signals (obsolete)
# sigign bitmap of ignored signals (obsolete)
# sigcatch bitmap of catched signals (obsolete)
# wchan address where process went to sleep
# 0 (place holder)
# 0 (place holder)
# exit_signal signal to send to parent thread on exit
# task_cpu which CPU the task is scheduled on
# rt_priority realtime priority
# policy scheduling policy (man sched_setscheduler)
# blkio_ticks time spent waiting for block IO
 
# field names in /proc/<pid>/stat
STAT_NAMES = %w(
pid
tcomm
state
ppid
pgrp
sid
tty_nr
tty_pgrp
flags
min_flt
cmin_flt
maj_flt
cmaj_flt
utime
stime
cutime
cstime
priority
nice
num_threads
it_real_value
start_time
vsize
rss
rsslim
start_code
end_code
start_stack
esp
eip
pending
blocked
sigign
sigcatch
wchan
unused1
unused2
exit_signal
task_cpu
rt_priority
policy
blkio_ticks
)
 
class Proc_file_sample
  attr_reader :sample # the file we read
  def initialize(filename)
    @sample = IO.read(filename)
  end
 
  # method to return sample as a hash, given names of the fields
  def sample_hash(names)
    result = Hash.new
    fields = @sample.chomp.split(' ')
    names.each do |name|
      result[name] = fields.shift
    end
  return result
  end
 
end
 
class Stat_pid < Proc_file_sample
  def initialize(pid)
    super("/proc/#{pid}/stat")
  end
 
  def to_hash
    self.sample_hash(STAT_NAMES)
  end
end
 
class Statm_pid < Proc_file_sample
  def initialize(pid)
    super("/proc/#{pid}/statm")
  end
  def to_hash
    self.sample_hash(STATM_NAMES)
  end
end
 
def to_megabytes(pages)
  pages.to_f*page_bytes/1024.0/1024.0
end
 
def to_seconds(ticks)
  ticks.to_f/ticks_per_second
end
 
def average_megabytes(stat_before, stat_after)
  0.5*(
    to_megabytes(stat_after.to_hash['rss']) +
    to_megabytes(stat_before.to_hash['rss'])
  )
end
 
def delta(stat_before, stat_after, field)
  stat_after.to_hash[field].to_i -
  stat_before.to_hash[field].to_i
end
 
def major_faults(stat_before, stat_after)
  delta(stat_before, stat_after, 'maj_flt')
end
 
def minor_faults(stat_before, stat_after)
  delta(stat_before, stat_after, 'min_flt')
end
 
def blkio_seconds(stat_before, stat_after)
  to_seconds(delta(stat_before, stat_after, 'blkio_ticks'))
end
 
def user_seconds(stat_before, stat_after)
  to_seconds(delta(stat_before, stat_after, 'utime'))
end
 
def system_seconds(stat_before, stat_after)
  to_seconds(delta(stat_before, stat_after, 'stime'))
end
 
def cpu_seconds(stat_before, stat_after)
  user_seconds(stat_before, stat_after) +
  system_seconds(stat_before, stat_after)
end
 
def megabyte_seconds(stat_before, stat_after)
  cpu_seconds(stat_before, stat_after) *
  average_megabytes(stat_before, stat_after)
end
 
TESTING = false
if (TESTING)
  stat_before = Stat_pid.new("self")
  (1..10000000).each do |i|
    print "." if i%100000 == 0
  end
  stat_after = Stat_pid.new("self")
 
  print "\n"
  print "average megabytes=", average_megabytes(stat_before, stat_after),
    "\n"
 
  print "major faults=", major_faults(stat_before, stat_after), "\n"
  print "minor faults=", minor_faults(stat_before, stat_after), "\n"
  print "blkio seconds=", blkio_seconds(stat_before, stat_after), "\n"
  print "user seconds=", user_seconds(stat_before, stat_after), "\n"
  print "system seconds=", system_seconds(stat_before, stat_after), "\n"
  print "cpu seconds=", cpu_seconds(stat_before, stat_after), "\n"
  print "megabyte-seconds=", megabyte_seconds(stat_before, stat_after), "\n"
end