Class: Procview::Analyzer

Inherits:
Object
  • Object
show all
Defined in:
lib/procview/analyzer.rb

Defined Under Namespace

Classes: PSTATS

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opts = { debug: nil, missing: true }) ⇒ Analyzer

Returns a new instance of Analyzer.



46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/procview/analyzer.rb', line 46

def initialize opts = { debug: nil, missing: true }
  @calls  = Hash.new {|h,k| h[k] = [0,0.0]}
  @iomap  = Hash.new{|h,k| h[k] = Procview::Stats.new}
  @pstats = Hash.new{|h,k| h[k] = initPstats }
  @ipid   = nil # the initial pid
  @pid    = nil # the current pid (same as above if just one process)
  @sigmap = Hash.new(0)
  @dregs  = 0.0 # store unaccounted for time here
  @pwd    = nil
  @inithandles = nil
  @opts   = opts
  @info   = []
  @warnings = []
end

Instance Attribute Details

#callsObject (readonly)

Returns the value of attribute calls.



13
14
15
# File 'lib/procview/analyzer.rb', line 13

def calls
  @calls
end

#dregsObject (readonly)

Returns the value of attribute dregs.



13
14
15
# File 'lib/procview/analyzer.rb', line 13

def dregs
  @dregs
end

#info(msg) ⇒ Object (readonly)

Returns the value of attribute info.



13
14
15
# File 'lib/procview/analyzer.rb', line 13

def info
  @info
end

#inithandles=(value) ⇒ Object (writeonly)

Sets the attribute inithandles

Parameters:

  • value

    the value to set the attribute inithandles to.



14
15
16
# File 'lib/procview/analyzer.rb', line 14

def inithandles=(value)
  @inithandles = value
end

#iomapObject (readonly)

Returns the value of attribute iomap.



13
14
15
# File 'lib/procview/analyzer.rb', line 13

def iomap
  @iomap
end

#ipidObject (readonly)

Returns the value of attribute ipid.



13
14
15
# File 'lib/procview/analyzer.rb', line 13

def ipid
  @ipid
end

#pstatsObject (readonly)

Returns the value of attribute pstats.



13
14
15
# File 'lib/procview/analyzer.rb', line 13

def pstats
  @pstats
end

#pwdObject

Returns the value of attribute pwd.



15
16
17
# File 'lib/procview/analyzer.rb', line 15

def pwd
  @pwd
end

#sigmapObject (readonly)

Returns the value of attribute sigmap.



13
14
15
# File 'lib/procview/analyzer.rb', line 13

def sigmap
  @sigmap
end

#warningsObject (readonly)

Returns the value of attribute warnings.



13
14
15
# File 'lib/procview/analyzer.rb', line 13

def warnings
  @warnings
end

Instance Method Details

#digest(line) ⇒ Object



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
# File 'lib/procview/analyzer.rb', line 72

def digest line

  # Complex as we cope with traces with no pids
  # and traces where the initial LSOF lines don't
  # know the pid (debug output on a command trace)
  #
  if line.slice! /^(\d+)\s+/
    @pid = $1.to_i
    if @ipid == -1
      @pstats[@pid] = @pstats.delete(@ipid)
      @ipid = @pid
    end
  else
    @pid = @ipid
  end

  case line
  when /^TIME\s+/
    info "TRACED on #{$'}"

  when /^REQUEST\s+/
    info $'

  when /^LSOF/
    fields = line.split /\s+/
    unless fields.size > 9
      warning "Too few fields"
      return 1
    end
    pid = fields[2].to_i
    fname = fields[9..-1].join(' ')
    case fields[4]
    when 'cwd'
      raise "PID set prior to cwd" if @ipid
      @ipid = pid
      @pstats[@ipid].cwd = fname
    when /(\d+)\w/
      handle = $1.to_i
      @pstats[@ipid].hmap[handle] = fname
    else
      warning "Invalid handle #{fields[4]}"
      return 1
    end

  when / <unfinished ...>/
    @pstats[@pid].resume.push $`

  when /<... (\w+) resumed> /
    call = $1.to_sym
    rest = $'.chomp
    prev = @pstats[@pid].resume.find_index {|f| f =~ /^#{call}/}
    unless prev
      warning "Failed to find resumable call #{call}"
      return 1
    end
    first = @pstats[@pid].resume.delete_at(prev)
    return digest (@pid.to_s+" "+first+rest)

  when /^(?<call>\w+)\((?<args>.*)\)\s+=\s+\?/x # process termination has ?
    return 0

  when /^(?<call>\w+)
    \((?<args>.*)\)\s+=\s+(?<rc>-?(\d+|\?))\s*
      (?<ret>.*)\s+
      <(?<duration>.*)>$
    /x
    return handleCall($1.to_sym, $2, $3.to_i, $4, $5.to_f)

  when /^---\s*(\w+)\s*/
    signal = $1.to_sym
    @sigmap[signal] += 1
  when /^+++.*+++$/
  else
    warning "Unknown line format"
    return 1
  end
  return 0
end

#handleCall(call, args, rc, ret, duration) ⇒ Object



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
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
# File 'lib/procview/analyzer.rb', line 181

def handleCall call, args, rc, ret, duration
  pstat = @pstats[@pid]
  case call

  when :accept, :accept4 
    fd = StraceArgs.fd! args
    accumulate pstat.hmap, fd, :other, duration
    pstat.hmap[rc] = (StraceArgs.sockaddr!(args) || "#{call}#{rc}") if rc > 0

  when :clone
    StraceArgs.nextcomma! args
    flags = StraceArgs.flags! args
    @pstats[rc] = initPstats @pstats[@pid], flags.include?(:CLONE_FS), flags.include?(:CLONE_FILES)
    @dregs += duration

  when :stat, :lstat
    file = StraceArgs.name! args
    if rc == -1
      if @opts[:missing]
        @dregs += duration
      else
        @iomap[File.absolute_path(file, pstat.cwd)].acc! :other, duration
      end
    else
      @iomap[File.absolute_path(file, pstat.cwd)].acc! :other, duration
    end

  when :rename, :symlink
    f1 = StraceArgs.name! args
    f2 = StraceArgs.name! args
    @iomap[File.absolute_path(f1, pstat.cwd)].acc! :other, duration/2.0
    @iomap[File.absolute_path(f2, pstat.cwd)].acc! :other, duration/2.0

  when :execve, :access, :readlink, :chmod
    file = StraceArgs.name! args
    @iomap[File.absolute_path(file, pstat.cwd)].acc! :other, duration

  when :open
    file = StraceArgs.name! args
    if rc == -1
      if @opts[:missing]
        @dregs += duration
      else
        @iomap[File.absolute_path(file, pstat.cwd)].acc! :other, duration
      end
    else
      pstat.hmap[rc] = File.absolute_path(file, pstat.cwd)
      accumulate pstat.hmap, rc, :other, duration
    end

  when :mkdir, :rmdir, :getcwd, :statfs, :utimes, :unlink
    file = StraceArgs.name! args
    @iomap[File.absolute_path(file, pstat.cwd)].acc! :other, duration

  when :chdir
    file = StraceArgs.name! args
    pstat.cwd = File.absolute_path(file, pstat.cwd)
    @iomap[pstat.cwd].acc! :other, duration

  when :close
    fd = StraceArgs.fd! args
    inhmap = pstat.hmap.include? fd
    if rc == -1
      @dregs += duration
      raise "Handle map contains non-existent fd #{fd}" if inhmap
    end
    if inhmap
      accumulate pstat.hmap, fd, :other, duration
      pstat.hmap.delete fd
    end
    # Strace IO flags don't collect epoll_create so check assumption it's emap
    unless rc == -1 or inhmap or pstat.emap.include? fd
      warning "Neither handle map or emap include #{fd}"
    end

  when :fcntl
    fd = StraceArgs.fd! args
    op = StraceArgs.op! args
    if [:F_DUPFD, :F_DUPFD_CLOEXEC].include? op
      pstat.hmap[rc] = pstat.hmap[fd]
    end
    accumulate pstat.hmap, fd, :other, duration

  when :pipe, :pipe2
    fds = StraceArgs.afds! args
    pstat.hmap[fds[0]] = call
    accumulate pstat.hmap, fds[0], :other, duration/2.0
    pstat.hmap[fds[1]] = call
    accumulate pstat.hmap, fds[1], :other, duration/2.0

  when :eventfd, :eventfd2
    fd = StraceArgs.fd! args
    pstat.hmap[fd] = call
    accumulate pstat.hmap, fd, :other, duration

  when :socket
    pstat.hmap[rc] = Stats.new
    accumulate pstat.hmap, rc, :other, duration

  when :getsockopt, :setsockopt
    fd = StraceArgs.fd! args
    accumulate pstat.hmap, fd, :other, duration

  when :connect, :getpeername
    fd = StraceArgs.fd! args
    name = StraceArgs.sockaddr! args
    accumulate pstat.hmap, fd, :other, duration
    movetoIOmap name, fd, pstat.hmap

  when :bind
    fd = StraceArgs.fd! args
    name = StraceArgs.sockaddr! args
    accumulate pstat.hmap, fd, :other, duration
    movetoIOmap name, fd, pstat.hmap

  when :listen
    fd = StraceArgs.fd! args
    accumulate pstat.hmap, fd, :other, duration

  when :fsync, :fdatasync
    fd = StraceArgs.fd! args
    accumulate pstat.hmap, fd, :other, duration

  when :dup, :dup2, :dup3
    fd = StraceArgs.fd! args
    pstat.hmap[rc] = pstat.hmap[fd]
    accumulate pstat.hmap, fd, :other, duration

  when :flock, :fstat, :ioctl, :getsockname, :shutdown
    fd = StraceArgs.fd! args
    accumulate pstat.hmap, fd, :other, duration

  # Calls classed as 'wait'

  when :select
    max = StraceArgs.fd! args
    if max > 0
      rfds = StraceArgs.afds!(args).to_set
      wfds = StraceArgs.afds!(args).to_set
      efds = StraceArgs.afds!(args).to_set
      fds = rfds+wfds+efds
    else
      fds = Set.new
    end
    if fds.size > 0
      fds.each{|fd| accumulate pstat.hmap, fd, :wait, duration/fds.size}
    else
      @dregs += duration
    end

  when :poll, :ppoll
    fds = StraceArgs.pollfd! args
    if (rc > 0)
      rds = StraceArgs.pollfd! ret.slice(1..-2)
      rds.each{|rd| accumulate pstat.hmap, rd, :wait, duration/rds.size }
    else
      fds.each{|fd| accumulate pstat.hmap, fd, :wait, duration/fds.size }
    end

  when :epoll_ctl
    epfd = StraceArgs.fd! args
    op = StraceArgs.op! args
    fd  = StraceArgs.fd! args
    case op
    when :EPOLL_CTL_ADD, :EPOLL_CTL_MOD
      pstat.emap[epfd].add fd
    when :EPOLL_CTL_DEL
      pstat.emap[epfd].delete fd
    end

  when :epoll_wait, :epoll_pwait
    epfd = StraceArgs.fd! args
    if rc < 1
      pstat.emap[epfd].each do |fd|
        accumulate pstat.hmap, fd, :wait, duration/pstat.emap[epfd].size
      end
    else
      epfds = StraceArgs.epollevents! args
      epfds.each{|fd| accumulate pstat.hmap, fd, :wait, duration/epfds.size }
    end

  when :pread, :read, :recvfrom, :lseek, :recvmsg, :getdents
    fd = StraceArgs.fd! args
    accumulate pstat.hmap, fd, :read, duration

  when :truncate
    file = StraceArgs.name! args
    absfile = File.absolute_path(file, pstat.cwd)
    @iomap[absfile].acc! :write, duration

  when :pwrite, :write, :writev, :send, :sendmsg, :ftruncate
    fd = StraceArgs.fd! args
    accumulate pstat.hmap, fd, :write, duration

  when :sendto  # TODO the case when a sockaddr is specified
    fd = StraceArgs.fd! args
    accumulate pstat.hmap, fd, :write, duration

  when :arch_prctl, :wait4
    @dregs += duration

  else
    warn "Unhandled call #{call}"
    return 1
  end
  @calls[call][0] += 1
  @calls[call][1] += duration
  return 0
end

#initPstats(parent = nil, same_wd = false, same_fds = false) ⇒ Object



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/procview/analyzer.rb', line 25

def initPstats parent = nil, same_wd = false, same_fds = false
  if parent
    cwd = same_wd ? parent.cwd : parent.cwd.clone
    hmap = same_fds ? parent.hmap : parent.hmap.clone
    emap = same_fds ? parent.emap : parent.emap.clone
    return PSTATS.new( cwd, hmap, emap, [] )
  else
    stats = PSTATS.new(
      @pwd,
      Hash.new do |h,k|
        warning "Unknown handle #{k} (Pid #{@pid})"
        h[k] = "h#{k}".to_sym
      end,
      Hash.new{|h,k| h[k] = Set.new},
      []
    )
    @inithandles.each {|k,v| stats.hmap[k] = v} if @inithandles
    return stats
  end
end

#load(io) ⇒ Object



61
62
63
64
65
66
67
68
69
70
# File 'lib/procview/analyzer.rb', line 61

def load io
  while line = io.gets
    @opts[:debug].puts line if @opts[:debug]
    begin
      warning line if digest(line) > 0
    rescue StandardError => e
      warning "#{e.message} (#{e.backtrace[0]}): #{line}"
    end
  end
end

#warning(msg) ⇒ Object



21
22
23
# File 'lib/procview/analyzer.rb', line 21

def warning msg
  @warnings.push ($. > 0 ? "#{$.}: ":'')+msg
end