Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions lib/appear/instance.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
56 changes: 55 additions & 1 deletion lib/appear/lsof.rb
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,16 @@ def join_via_tty(tree, panes)
# @param files [Array<String>] files to query
# @return [Hash<String, Array<Connection>>] 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|
Expand All @@ -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
Expand Down Expand Up @@ -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
22 changes: 16 additions & 6 deletions lib/appear/processes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -55,9 +59,15 @@ def alive?(pid)
# @return [Array<ProcessInfo>]
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

Expand All @@ -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
3 changes: 3 additions & 0 deletions lib/appear/revealers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down