Class: Sys::ProcTable

Inherits:
Object
  • Object
show all
Extended by:
FFI::Library, ProcTableFunctions
Includes:
ProcTableConstants, ProcTableStructs
Defined in:
lib/aix/sys/proctable.rb,
lib/linux/sys/proctable.rb,
lib/sunos/sys/proctable.rb,
lib/darwin/sys/proctable.rb,
lib/sys/proctable/version.rb,
lib/windows/sys/proctable.rb,
lib/linux/sys/proctable/smaps.rb,
lib/bsd/sys/freebsd/sys/proctable.rb,
lib/bsd/sys/dragonfly/sys/proctable.rb,
lib/linux/sys/proctable/cgroup_entry.rb

Overview

The ProcTable class encapsulates process table information

Defined Under Namespace

Classes: CgroupEntry, Error, KInfoProc, LWPSInfo, PRUsage, PSInfo, Pargs, Priority, ProcTableMapStruct, Rusage, Smaps, Timeval

Constant Summary collapse

VERSION =

The version of the sys-proctable library

'1.3.0'.freeze
PROC_STRUCT_FIELD_MAP =
all_members.map do |member|
  temp = member.to_s.split('_')
  sproperty = temp.size > 1 ? temp[1..-1].join('_') : temp.first
  [member, sproperty.to_sym]
end.to_h

Constants included from ProcTableConstants

Sys::ProcTableConstants::KERN_PROC_ALL, Sys::ProcTableConstants::MAXLOGNAME, Sys::ProcTableConstants::NGROUPS

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.fieldsObject (readonly)

Returns an array of fields that each ProcTableStruct will contain. This may be useful if you want to know in advance what fields are available without having to perform at least one read of the process table.

Example:

Sys::ProcTable.fields.each{ |field|
   puts "Field: #{field}"
}


454
455
456
# File 'lib/aix/sys/proctable.rb', line 454

def self.fields
  @fields.map{ |f| f.to_s }
end

Class Method Details

.ps(**kwargs) ⇒ Object

In block form, yields a ProcTableStruct for each process entry that you have rights to. This method returns an array of ProcTableStruct’s in non-block form.

If a pid is provided, then only a single ProcTableStruct is yielded or returned, or nil if no process information is found for that pid.

Example:

# Iterate over all processes
ProcTable.ps do |proc_info|
   p proc_info
end

# Print process table information for only pid 1001
p ProcTable.ps(pid: 1001)

# Same as above, but do not include thread information
p ProcTable.ps(pid: 1001, thread_info: false)


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
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
# File 'lib/aix/sys/proctable.rb', line 207

def self.ps(**kwargs)
  pid = kwargs[:pid]

  raise TypeError unless pid.is_a?(Numeric) if pid

  array  = block_given? ? nil : []
  struct = nil

  Dir.foreach("/proc") do |file|
    next if file =~ /\D/ # Skip non-numeric entries under /proc

    # Only return information for a given pid, if provided
    if pid
      next unless file.to_i == pid
    end

    # Skip over any entries we don't have permissions to read
    next unless File.readable?("/proc/#{file}/psinfo")

    psinfo = IO.read("/proc/#{file}/psinfo") rescue next

    psinfo_array = psinfo.unpack(@psinfo_pack_directive)

    struct = ProcTableStruct.new

    struct.flag    = psinfo_array[0]         # pr_flag
    struct.flag2   = psinfo_array[1]         # pr_flag2
    struct.nlwp    = psinfo_array[2]         # pr_nlwp
    # pr__pad1
    struct.uid     = psinfo_array[4]         # pr_uid
    struct.euid    = psinfo_array[5]         # pr_euid
    struct.gid     = psinfo_array[6]         # pr_gid
    struct.egid    = psinfo_array[7]         # pr_egid
    struct.pid     = psinfo_array[8]         # pr_pid
    struct.ppid    = psinfo_array[9]         # pr_ppid
    struct.pgid    = psinfo_array[10]        # pr_pgid
    struct.sid     = psinfo_array[11]        # pr_sid
    struct.ttydev  = psinfo_array[12]        # pr_ttydev

    # convert from 64-bit dev_t to 32-bit dev_t and then map the device
    # number to a name
    ttydev = struct.ttydev
    ttydev = (((ttydev & 0x0000FFFF00000000) >> 16) | (ttydev & 0xFFFF))
    struct.s_ttydev = @devs.has_key?(ttydev) ? @devs[ttydev] : '-'

    struct.addr    = psinfo_array[13]        # pr_addr
    struct.size    = psinfo_array[14] * 1024 # pr_size
    struct.rssize  = psinfo_array[15] * 1024 # pr_rssize
    struct.start   = Time.at(psinfo_array[16], psinfo_array[17]) # pr_start
    # skip pr_start.__pad
    struct.time    = psinfo_array[19]        # pr_time
    # skip pr_time.tv_nsec and pr_time.__pad
    struct.cid     = psinfo_array[22]        # pr_cid
    # skip pr__pad2
    struct.argc    = psinfo_array[24]        # pr_argc
    struct.argv    = psinfo_array[25]        # pr_argv
    struct.envp    = psinfo_array[26]        # pr_envp
    struct.fname   = psinfo_array[27]        # pr_fname
    struct.psargs  = psinfo_array[28]        # pr_psargs
    # skip pr__pad

    ### lwpsinfo_t info

    struct.lwpid   = psinfo_array[37]        # pr_lwpid
    # skip pr_addr
    struct.wchan   = psinfo_array[39]        # pr_wchan
    # skip pr_flag
    struct.wtype   = psinfo_array[41]        # pr_wtype
    struct.state   = psinfo_array[42]        # pr_state
    struct.sname   = psinfo_array[43]        # pr_sname
    struct.nice    = psinfo_array[44]        # pr_nice
    struct.pri     = psinfo_array[45]        # pr_pri
    struct.policy  = psinfo_array[46]        # pr_policy
    struct.clname  = psinfo_array[47]        # pr_clname
    struct.onpro   = psinfo_array[48]        # pr_onpro
    struct.bindpro = psinfo_array[49]        # pr_bindpro
    struct.ptid    = psinfo_array[50]        # pr_ptid
    # skip pr__pad1
    # skip pr__pad

    # Get the full command line out of /proc/<pid>/as.
    begin
      File.open("/proc/#{file}/as", 'rb') do |fd|
        np = fd.sysseek(struct.argv, IO::SEEK_SET)

        if np != struct.argv
          raise Error, "argv seek to #{struct.argv}, result #{np}", caller
        end

        argv = fd.sysread(4).unpack('L')[0]

        np = fd.sysseek(argv, IO::SEEK_SET)

        if np != argv
          raise Error, "*argv seek to #{argv}, result #{np}", caller
        end

        argv = fd.sysread(4 * struct.argc).unpack("L#{struct.argc}")

        struct.cmd_args = []

        argv.each_with_index do |address, i|
          np = fd.sysseek(address, IO::SEEK_SET)

          if np != address
            raise Error, "argv[#{i}] seek to #{address}, result #{np}",
                  caller
          end

          data = fd.sysread(512)[/^[^\0]*/] # Null strip
          struct.cmd_args << data
        end

        # Get the environment hash associated with the process.
        struct.environ = {}

        # First have to go to the address given by struct.envp. That will
        # give us the address of the environment pointer array.

        np = fd.sysseek(struct.envp, IO::SEEK_SET)

        if np != struct.envp
          raise Error, "envp seek to #{struct.envp}, result #{np}", caller
        end

        envloc = fd.sysread(4).unpack('L')[0]
        n = 0

        loop do
          np = fd.sysseek(envloc, IO::SEEK_SET)

          if np != envloc
            raise Error, "envp[#{n}] seek to #{envloc}, result #{np}",
                  caller
          end

          envp = fd.sysread(4).unpack("L")[0]
          break if envp.zero?
          np = fd.sysseek(envp, IO::SEEK_SET)
          data = fd.sysread(1024)[/^[^\0]*/] # Null strip
          key, value = data.split('=')
          struct.environ[key] = value
          envloc += 4
          n += 1
        end
      end
    rescue Errno::EACCES, Errno::EOVERFLOW, EOFError
      # Skip this if we don't have proper permissions, if there's
      # no associated environment, or if there's a largefile issue.
    rescue Errno::ENOENT
      next # The process has terminated. Bail out!
    end

    # Information from /proc/<pid>/fd. This returns an array of
    # numeric file descriptors used by the process.
    struct.fd = Dir["/proc/#{file}/fd/*"].map { |f| File.basename(f).to_i }

    # Use the cmd_args as the cmdline if available. Otherwise use
    # the psargs. This struct member is provided to provide a measure
    # of consistency with the other platform implementations.
    if struct.cmd_args.nil? || struct.cmd_args.empty?
      struct.cmdline = struct.psargs
    else
      struct.cmdline = struct.cmd_args.join(' ')
    end

    # get current working directory from /proc/<pid>/cwd
    struct.cwd = File.readlink("/proc/#{file}/cwd") rescue nil

    # get virtual address map from /proc/<pid>/map
    begin
      struct.map = []

      File.open("/proc/#{file}/map", 'rb') do |fd|
        loop do
          prmap_array = fd.sysread(176).unpack(@prmap_pack_directive)
          break if prmap_array[0].zero?

          map_struct = ProcTableMapStruct.new

          map_struct.size     = prmap_array[0]  # pr_size
          map_struct.vaddr    = prmap_array[1]  # pr_vaddr
          map_struct.mapname  = prmap_array[2]  # pr_mapname
          map_struct.off      = prmap_array[3]  # pr_off
          map_struct.mflags   = prmap_array[4]  # pr_mflags

          # convert pr_mflags value to string sort of like procmap outputs
          mflags = map_struct.mflags
          map_struct.s_mflags = ''
          sep = ''

          PR_MFLAGS.each do |flag|
            if (mflags & flag[0]).nonzero?
              map_struct.s_mflags << sep << flag[1]
              sep = '/'
              mflags &= ~flag[0]
            end
          end

          if mflags.nonzero?
            map_struct.s_mflags << sep << sprintf('%08x', mflags)
          end

          map_struct.pathoff  = prmap_array[5]  # pr_pathoff
          map_struct.alias    = prmap_array[6]  # pr_alias
          map_struct.gp       = prmap_array[7]  # pr_gp

          struct.map << map_struct
        end

        struct.map.each do |m|
          next if m.pathoff.zero?
          fd.sysseek(m.pathoff, IO::SEEK_SET)
          buf = fd.sysread(4096)
          buf =~ /^([^\0]*)\0([^\0]*)\0/
          m.path = $2.empty? ? $1 : "#{$1}(#{$2})"
        end
      end

      struct.map = nil if struct.map.empty?
    rescue
      struct.map = nil
    end

    # This is read-only data
    struct.freeze

    if block_given?
      yield struct
    else
      array << struct
    end
  end

  pid ? struct : array
end