diff --git a/lib/appear/instance.rb b/lib/appear/instance.rb index 1f8e56f..a691d79 100644 --- a/lib/appear/instance.rb +++ b/lib/appear/instance.rb @@ -44,6 +44,11 @@ def initialize(config) def call(pid) tree = process_tree(pid) + log "Process tree:" + tree.each do |info| + log " #{info}" + end + statuses = ::Appear::REVEALERS.map do |klass| revealer = klass.new(@all_services) revealer.call(tree) diff --git a/lib/appear/lsof.rb b/lib/appear/lsof.rb index ca05107..a28e514 100644 --- a/lib/appear/lsof.rb +++ b/lib/appear/lsof.rb @@ -100,6 +100,16 @@ def join_via_tty(tree, panes) # @param files [Array] files to query # @return [Hash>] map of filename to connections def lsofs(files, opts = {}) + if use_fstat? + fstats(files) + else + lsofs_(file, opts) + end + end + + private + + def lsofs_(files, opts = {}) mutex = Mutex.new results = {} threads = files.map do |file| @@ -114,7 +124,24 @@ def lsofs(files, opts = {}) results end - private + def fstats(files) + results = {} + files.each do |file| + results[file] = fstat(file) + end + results + end + + def use_fstat? + return @use_fstat unless @use_fstat.nil? + + begin + run(['which', 'fstat']) + @use_fstat = true + rescue ExecutionFailure + @use_fstat = false + end + end def lsof(file, opts = {}) error_line = nil @@ -147,5 +174,32 @@ def lsof(file, opts = {}) log("lsof: parse error: #{err}, line: #{error_line}") [] end + + # FreeBSD lsof-like program, that actually runs much faster - so fast that + # I'm unconcerned about memoization or threading. + # + # @param file [String] + def fstat(file) + output = run(['fstat', file]) + return [] if output.empty? + rows = output.lines.map do |line| + user, cmd, pid, fd, mount, inum, mode, sz_dv, rw, *name = line.strip.split(/\s+/) + name = name.join(' ') + Connection.new({ + command_name: cmd, + pid: pid.to_i, + user: user, + fd: fd, + # type: what is that + type: nil, + # device: should we use mount? + device: mount, + size: sz_dv, + node: inum, + file_name: name, + }) + end + rows[1..-1] + end end end diff --git a/lib/appear/processes.rb b/lib/appear/processes.rb index 7300f56..5011a7b 100644 --- a/lib/appear/processes.rb +++ b/lib/appear/processes.rb @@ -20,6 +20,10 @@ def initialize(hash) send("#{key}=", value) end end + + def to_s + 'ProcessInfo' + {pid: pid, command: command, name: name, parent_pid: parent_pid}.inspect + end end def initialize(*args) @@ -55,9 +59,15 @@ def alive?(pid) # @return [Array] def process_tree(pid) tree = [ get_info(pid) ] - while tree.last.pid > 1 && tree.last.parent_pid != 0 - tree << get_info(tree.last.parent_pid) + + begin + while tree.last.pid > 1 && tree.last.parent_pid != 0 + tree << get_info(tree.last.parent_pid) + end + rescue DeadProcess + # that's ok end + tree end @@ -76,10 +86,10 @@ def pgrep(pattern) def fetch_info(pid) raise DeadProcess.new("cannot fetch info for dead PID #{pid}") unless alive?(pid) - output = run(['ps', '-p', pid.to_s, '-o', 'ppid=', '-o', 'command=']) - ppid, *command = output.strip.split(/\s+/).reject(&:empty?) - name = File.basename(command.first) - ProcessInfo.new({:pid => pid.to_i, :parent_pid => ppid.to_i, :command => command, :name => name}) + output = run(['ps', '-p', pid.to_s, '-o', 'ppid=', '-o', 'comm=', '-o', 'args=']) + ppid, command, *args = output.strip.split(/\s+/).reject(&:empty?) + name = File.basename(command) + ProcessInfo.new({:pid => pid.to_i, :parent_pid => ppid.to_i, :command => args, :name => name}) end end end diff --git a/lib/appear/revealers.rb b/lib/appear/revealers.rb index c6d85a7..f162a56 100644 --- a/lib/appear/revealers.rb +++ b/lib/appear/revealers.rb @@ -19,7 +19,10 @@ class BaseRevealer < Service def call(tree) target, *rest = tree if supports_tree?(target, rest) + log("#{self.class.name}: running") return reveal_tree(tree) + else + log("#{self.class.name}: no support") end end