dtas 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gemtest +0 -0
- data/.gitignore +9 -0
- data/.rsync_doc +3 -0
- data/COPYING +674 -0
- data/Documentation/.gitignore +3 -0
- data/Documentation/GNUmakefile +46 -0
- data/Documentation/dtas-console.txt +42 -0
- data/Documentation/dtas-ctl.txt +64 -0
- data/Documentation/dtas-cueedit.txt +24 -0
- data/Documentation/dtas-enq.txt +29 -0
- data/Documentation/dtas-msinkctl.txt +45 -0
- data/Documentation/dtas-player.txt +110 -0
- data/Documentation/dtas-player_effects.txt +45 -0
- data/Documentation/dtas-player_protocol.txt +181 -0
- data/Documentation/dtas-sinkedit.txt +41 -0
- data/Documentation/dtas-sourceedit.txt +33 -0
- data/Documentation/dtas-xdelay.txt +57 -0
- data/Documentation/troubleshooting.txt +13 -0
- data/GIT-VERSION-GEN +30 -0
- data/GNUmakefile +9 -0
- data/HACKING +12 -0
- data/INSTALL +53 -0
- data/README +103 -0
- data/Rakefile +97 -0
- data/TODO +4 -0
- data/bin/dtas-console +160 -0
- data/bin/dtas-ctl +10 -0
- data/bin/dtas-cueedit +78 -0
- data/bin/dtas-enq +13 -0
- data/bin/dtas-msinkctl +51 -0
- data/bin/dtas-player +34 -0
- data/bin/dtas-sinkedit +58 -0
- data/bin/dtas-sourceedit +48 -0
- data/bin/dtas-xdelay +85 -0
- data/dtas-linux.gemspec +18 -0
- data/dtas-mpris.gemspec +16 -0
- data/examples/dtas_state.yml +18 -0
- data/lib/dtas.rb +7 -0
- data/lib/dtas/buffer.rb +90 -0
- data/lib/dtas/buffer/read_write.rb +102 -0
- data/lib/dtas/buffer/splice.rb +142 -0
- data/lib/dtas/command.rb +43 -0
- data/lib/dtas/compat_onenine.rb +18 -0
- data/lib/dtas/disclaimer.rb +18 -0
- data/lib/dtas/format.rb +151 -0
- data/lib/dtas/pipe.rb +39 -0
- data/lib/dtas/player.rb +393 -0
- data/lib/dtas/player/client_handler.rb +463 -0
- data/lib/dtas/process.rb +87 -0
- data/lib/dtas/replaygain.rb +41 -0
- data/lib/dtas/rg_state.rb +99 -0
- data/lib/dtas/serialize.rb +9 -0
- data/lib/dtas/sigevent.rb +10 -0
- data/lib/dtas/sigevent/efd.rb +20 -0
- data/lib/dtas/sigevent/pipe.rb +28 -0
- data/lib/dtas/sink.rb +121 -0
- data/lib/dtas/source.rb +147 -0
- data/lib/dtas/source/command.rb +40 -0
- data/lib/dtas/source/common.rb +14 -0
- data/lib/dtas/source/mp3.rb +37 -0
- data/lib/dtas/state_file.rb +33 -0
- data/lib/dtas/unix_accepted.rb +76 -0
- data/lib/dtas/unix_client.rb +51 -0
- data/lib/dtas/unix_server.rb +110 -0
- data/lib/dtas/util.rb +15 -0
- data/lib/dtas/writable_iter.rb +22 -0
- data/perl/dtas-graph +129 -0
- data/pkg.mk +26 -0
- data/setup.rb +1586 -0
- data/test/covshow.rb +30 -0
- data/test/helper.rb +76 -0
- data/test/player_integration.rb +121 -0
- data/test/test_buffer.rb +216 -0
- data/test/test_format.rb +61 -0
- data/test/test_format_change.rb +49 -0
- data/test/test_player.rb +47 -0
- data/test/test_player_client_handler.rb +86 -0
- data/test/test_player_integration.rb +220 -0
- data/test/test_rg_integration.rb +117 -0
- data/test/test_rg_state.rb +32 -0
- data/test/test_sink.rb +32 -0
- data/test/test_sink_tee_integration.rb +34 -0
- data/test/test_source.rb +102 -0
- data/test/test_unixserver.rb +66 -0
- data/test/test_util.rb +15 -0
- metadata +208 -0
@@ -0,0 +1,40 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net>
|
3
|
+
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
|
4
|
+
require_relative '../../dtas'
|
5
|
+
require_relative '../source'
|
6
|
+
require_relative '../command'
|
7
|
+
require_relative '../serialize'
|
8
|
+
|
9
|
+
class DTAS::Source::Command # :nodoc:
|
10
|
+
require_relative '../source/common'
|
11
|
+
|
12
|
+
include DTAS::Command
|
13
|
+
include DTAS::Process
|
14
|
+
include DTAS::Source::Common
|
15
|
+
include DTAS::Serialize
|
16
|
+
|
17
|
+
SIVS = %w(command env)
|
18
|
+
|
19
|
+
def initialize(command)
|
20
|
+
command_init(command: command)
|
21
|
+
end
|
22
|
+
|
23
|
+
def source_dup
|
24
|
+
rv = self.class.new
|
25
|
+
SIVS.each { |iv| rv.__send__("#{iv}=", self.__send__(iv)) }
|
26
|
+
rv
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_hash
|
30
|
+
ivars_to_hash(SIVS)
|
31
|
+
end
|
32
|
+
|
33
|
+
alias to_hsh to_hash
|
34
|
+
|
35
|
+
def spawn(format, rg_state, opts)
|
36
|
+
raise "BUG: #{self.inspect}#spawn called twice" if @to_io
|
37
|
+
e = format.to_env
|
38
|
+
@pid = dtas_spawn(e.merge!(@env), command_string, opts)
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net>
|
3
|
+
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
|
4
|
+
module DTAS::Source::Common # :nodoc:
|
5
|
+
attr_reader :dst_zero_byte
|
6
|
+
attr_reader :dst
|
7
|
+
attr_accessor :requeued
|
8
|
+
|
9
|
+
def dst_assoc(buf)
|
10
|
+
@dst = buf
|
11
|
+
@dst_zero_byte = buf.bytes_xfer + buf.inflight
|
12
|
+
@requeued = false
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net>
|
3
|
+
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
|
4
|
+
require_relative '../process'
|
5
|
+
|
6
|
+
module DTAS::Source::Mp3 # :nodoc:
|
7
|
+
include DTAS::Process
|
8
|
+
# we use dBFS = 1.0 as scale (not 32768)
|
9
|
+
def __mp3gain_peak(str)
|
10
|
+
sprintf("%0.8g", str.to_f / 32768.0)
|
11
|
+
end
|
12
|
+
|
13
|
+
# massage mp3gain(1) output
|
14
|
+
def mp3gain_comments
|
15
|
+
tmp = {}
|
16
|
+
case @infile
|
17
|
+
when String
|
18
|
+
@infile =~ /\.mp[g23]\z/i or return
|
19
|
+
qx(%W(mp3gain -s c #@infile)).split(/\n/).each do |line|
|
20
|
+
case line
|
21
|
+
when /^Recommended "(Track|Album)" dB change:\s*(\S+)/
|
22
|
+
tmp["REPLAYGAIN_#{$1.upcase}_GAIN"] = $2
|
23
|
+
when /^Max PCM sample at current gain: (\S+)/
|
24
|
+
tmp["REPLAYGAIN_TRACK_PEAK"] = __mp3gain_peak($1)
|
25
|
+
when /^Max Album PCM sample at current gain: (\S+)/
|
26
|
+
tmp["REPLAYGAIN_ALBUM_PEAK"] = __mp3gain_peak($1)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
tmp
|
30
|
+
else
|
31
|
+
raise TypeError, "unsupported type: #{@infile.inspect}"
|
32
|
+
end
|
33
|
+
rescue => e
|
34
|
+
$DEBUG and
|
35
|
+
warn("mp3gain(#{@infile.inspect}) failed: #{e.message} (#{e.class})")
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net>
|
3
|
+
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
|
4
|
+
require 'yaml'
|
5
|
+
require 'tempfile'
|
6
|
+
class DTAS::StateFile # :nodoc:
|
7
|
+
def initialize(path, do_fsync = false)
|
8
|
+
@path = path
|
9
|
+
@do_fsync = do_fsync
|
10
|
+
end
|
11
|
+
|
12
|
+
def tryload
|
13
|
+
YAML.load(IO.binread(@path)) if File.readable?(@path)
|
14
|
+
end
|
15
|
+
|
16
|
+
def dump(obj, force_fsync = false)
|
17
|
+
yaml = obj.to_hsh.to_yaml.b
|
18
|
+
|
19
|
+
# do not replace existing state file if there are no changes
|
20
|
+
# this will be racy if we ever do async dumps or shared state
|
21
|
+
# files, but we don't do that...
|
22
|
+
return if File.readable?(@path) && IO.binread(@path) == yaml
|
23
|
+
|
24
|
+
dir = File.dirname(@path)
|
25
|
+
Tempfile.open(%w(player.state .tmp), dir) do |tmp|
|
26
|
+
tmp.binmode
|
27
|
+
tmp.write(yaml)
|
28
|
+
tmp.flush
|
29
|
+
tmp.fsync if @do_fsync || force_fsync
|
30
|
+
File.rename(tmp.path, @path)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net>
|
3
|
+
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
|
4
|
+
require 'socket'
|
5
|
+
require 'io/wait'
|
6
|
+
|
7
|
+
class DTAS::UNIXAccepted # :nodoc:
|
8
|
+
attr_reader :to_io
|
9
|
+
|
10
|
+
def initialize(sock)
|
11
|
+
@to_io = sock
|
12
|
+
@send_buf = []
|
13
|
+
end
|
14
|
+
|
15
|
+
# public API (for DTAS::Player)
|
16
|
+
# returns :wait_readable on success
|
17
|
+
def emit(msg)
|
18
|
+
buffered = @send_buf.size
|
19
|
+
if buffered == 0
|
20
|
+
begin
|
21
|
+
@to_io.sendmsg_nonblock(msg, Socket::MSG_EOR)
|
22
|
+
return :wait_readable
|
23
|
+
rescue Errno::EAGAIN
|
24
|
+
@send_buf << msg
|
25
|
+
return :wait_writable
|
26
|
+
rescue => e
|
27
|
+
return e
|
28
|
+
end
|
29
|
+
elsif buffered > 100
|
30
|
+
return RuntimeError.new("too many messages buffered")
|
31
|
+
else # buffered > 0
|
32
|
+
@send_buf << msg
|
33
|
+
return :wait_writable
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# flushes pending data if it got buffered
|
38
|
+
def writable_iter
|
39
|
+
begin
|
40
|
+
msg = @send_buf.shift or return :wait_readable
|
41
|
+
@to_io.send_nonblock(msg, Socket::MSG_EOR)
|
42
|
+
rescue Errno::EAGAIN
|
43
|
+
@send_buf.unshift(msg)
|
44
|
+
return :wait_writable
|
45
|
+
rescue => e
|
46
|
+
return e
|
47
|
+
end while true
|
48
|
+
end
|
49
|
+
|
50
|
+
def readable_iter
|
51
|
+
io = @to_io
|
52
|
+
nread = io.nread
|
53
|
+
|
54
|
+
# EOF, assume no spurious wakeups for SOCK_SEQPACKET
|
55
|
+
return nil if nread == 0
|
56
|
+
|
57
|
+
begin
|
58
|
+
begin
|
59
|
+
msg, _, _ = io.recvmsg_nonblock(nread)
|
60
|
+
rescue EOFError, SystemCallError
|
61
|
+
return nil
|
62
|
+
end
|
63
|
+
yield(self, msg) # DTAS::Player deals with this
|
64
|
+
nread = io.nread
|
65
|
+
end while nread > 0
|
66
|
+
:wait_readable
|
67
|
+
end
|
68
|
+
|
69
|
+
def close
|
70
|
+
@to_io.close
|
71
|
+
end
|
72
|
+
|
73
|
+
def closed?
|
74
|
+
@to_io.closed?
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net>
|
3
|
+
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
|
4
|
+
require 'dtas'
|
5
|
+
require 'socket'
|
6
|
+
require 'io/wait'
|
7
|
+
require 'shellwords'
|
8
|
+
|
9
|
+
class DTAS::UNIXClient # :nodoc:
|
10
|
+
attr_reader :to_io
|
11
|
+
|
12
|
+
def self.default_path
|
13
|
+
(ENV["DTAS_PLAYER_SOCK"] || File.expand_path("~/.dtas/player.sock")).b
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(path = self.class.default_path)
|
17
|
+
@to_io = begin
|
18
|
+
raise if ENV["_DTAS_NOSEQPACKET"]
|
19
|
+
Socket.new(:AF_UNIX, :SOCK_SEQPACKET, 0)
|
20
|
+
rescue
|
21
|
+
warn("get your operating system developers to support " \
|
22
|
+
"SOCK_SEQPACKET for AF_UNIX sockets")
|
23
|
+
warn("falling back to SOCK_DGRAM, reliability possibly compromised")
|
24
|
+
Socket.new(:AF_UNIX, :SOCK_DGRAM, 0)
|
25
|
+
end
|
26
|
+
@to_io.connect(Socket.pack_sockaddr_un(path))
|
27
|
+
end
|
28
|
+
|
29
|
+
def req_start(args)
|
30
|
+
args = Shellwords.join(args) if Array === args
|
31
|
+
@to_io.send(args, Socket::MSG_EOR)
|
32
|
+
end
|
33
|
+
|
34
|
+
def req_ok(args, timeout = nil)
|
35
|
+
res = req(args, timeout)
|
36
|
+
res == "OK" or raise "Unexpected response: #{res}"
|
37
|
+
res
|
38
|
+
end
|
39
|
+
|
40
|
+
def req(args, timeout = nil)
|
41
|
+
req_start(args)
|
42
|
+
res_wait(timeout)
|
43
|
+
end
|
44
|
+
|
45
|
+
def res_wait(timeout = nil)
|
46
|
+
@to_io.wait(timeout)
|
47
|
+
nr = @to_io.nread
|
48
|
+
nr > 0 or raise EOFError, "unexpected EOF from server"
|
49
|
+
@to_io.recvmsg[0]
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net>
|
3
|
+
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
|
4
|
+
require 'socket'
|
5
|
+
require_relative '../dtas'
|
6
|
+
require_relative 'unix_accepted'
|
7
|
+
|
8
|
+
# This uses SOCK_SEQPACKET, unlike ::UNIXServer in Ruby stdlib
|
9
|
+
|
10
|
+
# The programming model for the event loop here aims to be compatible
|
11
|
+
# with EPOLLONESHOT use with epoll, since that fits my brain far better
|
12
|
+
# than existing evented APIs/frameworks.
|
13
|
+
# If we cared about scalability to thousands of clients, we'd really use epoll,
|
14
|
+
# but IO.select can be just as fast (or faster) with few descriptors and
|
15
|
+
# is obviously more portable.
|
16
|
+
|
17
|
+
class DTAS::UNIXServer # :nodoc:
|
18
|
+
attr_reader :to_io
|
19
|
+
|
20
|
+
def close
|
21
|
+
File.unlink(@path)
|
22
|
+
@to_io.close
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(path)
|
26
|
+
@path = path
|
27
|
+
# lock down access by default, arbitrary commands may run as the
|
28
|
+
# same user dtas-player runs as:
|
29
|
+
old_umask = File.umask(0077)
|
30
|
+
@to_io = Socket.new(:AF_UNIX, :SOCK_SEQPACKET, 0)
|
31
|
+
addr = Socket.pack_sockaddr_un(path)
|
32
|
+
begin
|
33
|
+
@to_io.bind(addr)
|
34
|
+
rescue Errno::EADDRINUSE
|
35
|
+
# maybe we have an old path leftover from a killed process
|
36
|
+
tmp = Socket.new(:AF_UNIX, :SOCK_SEQPACKET, 0)
|
37
|
+
begin
|
38
|
+
tmp.connect(addr)
|
39
|
+
raise RuntimeError, "socket `#{path}' is in use", []
|
40
|
+
rescue Errno::ECONNREFUSED
|
41
|
+
# ok, leftover socket, unlink and rebind anyways
|
42
|
+
File.unlink(path)
|
43
|
+
@to_io.bind(addr)
|
44
|
+
ensure
|
45
|
+
tmp.close
|
46
|
+
end
|
47
|
+
end
|
48
|
+
@to_io.listen(1024)
|
49
|
+
@readers = { self => true }
|
50
|
+
@writers = {}
|
51
|
+
ensure
|
52
|
+
File.umask(old_umask)
|
53
|
+
end
|
54
|
+
|
55
|
+
def write_failed(client, e)
|
56
|
+
warn "failed to write to #{client}: #{e.message} (#{e.class})"
|
57
|
+
client.close
|
58
|
+
end
|
59
|
+
|
60
|
+
def readable_iter
|
61
|
+
# we do not do anything with the block passed to us
|
62
|
+
begin
|
63
|
+
sock, _ = @to_io.accept_nonblock
|
64
|
+
@readers[DTAS::UNIXAccepted.new(sock)] = true
|
65
|
+
rescue Errno::ECONNABORTED # ignore this, it happens
|
66
|
+
rescue Errno::EAGAIN
|
67
|
+
return :wait_readable
|
68
|
+
end while true
|
69
|
+
end
|
70
|
+
|
71
|
+
def wait_ctl(io, err)
|
72
|
+
case err
|
73
|
+
when :wait_readable
|
74
|
+
@readers[io] = true
|
75
|
+
when :wait_writable
|
76
|
+
@writers[io] = true
|
77
|
+
when :delete
|
78
|
+
@readers.delete(io)
|
79
|
+
@writers.delete(io)
|
80
|
+
when :ignore
|
81
|
+
# There are 2 cases for :ignore
|
82
|
+
# - DTAS::Buffer was readable before, but all destinations (e.g. sinks)
|
83
|
+
# were blocked, so we stop caring for producer (buffer) readability.
|
84
|
+
# - a consumer (e.g. DTAS::Sink) just became writable, but the
|
85
|
+
# corresponding DTAS::Buffer was already readable in a previous
|
86
|
+
# call.
|
87
|
+
when nil
|
88
|
+
io.close
|
89
|
+
when StandardError
|
90
|
+
io.close
|
91
|
+
else
|
92
|
+
raise "BUG: wait_ctl invalid: #{io} #{err.inspect}"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def run_once
|
97
|
+
begin
|
98
|
+
# give IO.select one-shot behavior, snapshot and replace the watchlist
|
99
|
+
r = IO.select(@readers.keys, @writers.keys) or return
|
100
|
+
r[1].each do |io|
|
101
|
+
@writers.delete(io)
|
102
|
+
wait_ctl(io, io.writable_iter)
|
103
|
+
end
|
104
|
+
r[0].each do |io|
|
105
|
+
@readers.delete(io)
|
106
|
+
wait_ctl(io, io.readable_iter { |_io, msg| yield(_io, msg) })
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
data/lib/dtas/util.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net>
|
3
|
+
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
|
4
|
+
require_relative '../dtas'
|
5
|
+
|
6
|
+
# in case we need to convert DB values to a linear scale
|
7
|
+
module DTAS::Util # :nodoc:
|
8
|
+
def db_to_linear(val)
|
9
|
+
Math.exp(val * Math.log(10) * 0.05)
|
10
|
+
end
|
11
|
+
|
12
|
+
def linear_to_db(val)
|
13
|
+
Math.log10(val) * 20
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net>
|
3
|
+
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
|
4
|
+
require_relative '../dtas'
|
5
|
+
|
6
|
+
module DTAS::WritableIter # :nodoc:
|
7
|
+
attr_accessor :on_writable
|
8
|
+
|
9
|
+
def writable_iter_init
|
10
|
+
@on_writable = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
# this is used to exchange our own writable status for the readable
|
14
|
+
# status of the DTAS::Buffer which triggered us.
|
15
|
+
def writable_iter
|
16
|
+
if owr = @on_writable
|
17
|
+
@on_writable = nil
|
18
|
+
owr.call # this triggers readability watching of DTAS::Buffer
|
19
|
+
end
|
20
|
+
:ignore
|
21
|
+
end
|
22
|
+
end
|
data/perl/dtas-graph
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
#!/usr/bin/perl -w
|
2
|
+
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net>
|
3
|
+
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
|
4
|
+
use strict;
|
5
|
+
use Graph::Easy; # for ASCII-art graphs
|
6
|
+
$^O =~ /linux/ or print STDERR "$0 probably only works on Linux...\n";
|
7
|
+
scalar @ARGV or die "Usage: $0 PID [PID ...]";
|
8
|
+
our $procfs = $ENV{PROCFS} || "/proc";
|
9
|
+
my $cull_self_pipe = 1;
|
10
|
+
|
11
|
+
# returns a list of PIDs which are children of the given PID
|
12
|
+
sub children_of {
|
13
|
+
my ($ppid) = @_;
|
14
|
+
my %rv = map {
|
15
|
+
s/\A\s*//g;
|
16
|
+
s/\s*\z//g;
|
17
|
+
my ($pid, $cmd) = split(/\s+/, $_, 2);
|
18
|
+
$pid => $cmd;
|
19
|
+
} `ps h -o pid,cmd --ppid=$ppid`;
|
20
|
+
\%rv;
|
21
|
+
}
|
22
|
+
|
23
|
+
# pid => [ child pids ]
|
24
|
+
my %pids;
|
25
|
+
|
26
|
+
# pipe_ino => { r => [ [pid, fd], [pid, fd] ], w => [ [pid, fd], ... ] }
|
27
|
+
my %pipes;
|
28
|
+
|
29
|
+
# pid => argv
|
30
|
+
my %cmds;
|
31
|
+
|
32
|
+
my $pipe_nr = 0;
|
33
|
+
# pipe_id -> pipe_ino (we use short pipe IDs to save space on small terms)
|
34
|
+
my %graphed;
|
35
|
+
|
36
|
+
my @to_scan = (@ARGV);
|
37
|
+
|
38
|
+
sub cmd_of {
|
39
|
+
my ($pid) = @_;
|
40
|
+
my $cmd = `ps h -o cmd $pid`;
|
41
|
+
chomp $cmd;
|
42
|
+
$cmd;
|
43
|
+
}
|
44
|
+
|
45
|
+
while (my $pid = shift @to_scan) {
|
46
|
+
my $children = children_of($pid);
|
47
|
+
my @child_pids = keys %$children;
|
48
|
+
push @to_scan, @child_pids;
|
49
|
+
$pids{$pid} = \@child_pids;
|
50
|
+
foreach my $child (keys @child_pids) {
|
51
|
+
$cmds{$child} = $children->{$child};
|
52
|
+
}
|
53
|
+
}
|
54
|
+
|
55
|
+
# build up a hash of pipes and their connectivity to processes:
|
56
|
+
#
|
57
|
+
foreach my $pid (keys %pids) {
|
58
|
+
my @out = `lsof -p $pid`;
|
59
|
+
# output is like this:
|
60
|
+
# play 12739 ew 0r FIFO 0,7 0t0 36924019 pipe
|
61
|
+
foreach my $l (@out) {
|
62
|
+
my @l = split(/\s+/, $l);
|
63
|
+
$l[4] eq "FIFO" or next;
|
64
|
+
|
65
|
+
my $fd = $l[3];
|
66
|
+
my $pipe_ino = $l[7];
|
67
|
+
my $info = $pipes{$pipe_ino} ||= { r => [], w => [] };
|
68
|
+
if ($fd =~ s/r\z//) {
|
69
|
+
push @{$info->{r}}, [ $pid, $fd ];
|
70
|
+
} elsif ($fd =~ s/w\z//) {
|
71
|
+
push @{$info->{w}}, [ $pid, $fd ];
|
72
|
+
}
|
73
|
+
|
74
|
+
}
|
75
|
+
}
|
76
|
+
|
77
|
+
my $graph = Graph::Easy->new();
|
78
|
+
foreach my $pid (keys %pids) {
|
79
|
+
$graph->add_node($pid);
|
80
|
+
}
|
81
|
+
|
82
|
+
foreach my $pipe_ino (keys %pipes) {
|
83
|
+
my $info = $pipes{$pipe_ino};
|
84
|
+
my %pairs;
|
85
|
+
my $pipe_node;
|
86
|
+
|
87
|
+
foreach my $rw (qw(r w)) {
|
88
|
+
foreach my $pidfd (@{$info->{$rw}}) {
|
89
|
+
my ($pid, $fd) = @$pidfd;
|
90
|
+
my $pair = $pairs{$pid} ||= {};
|
91
|
+
my $fds = $pair->{$rw} ||= [];
|
92
|
+
push @$fds, $fd;
|
93
|
+
}
|
94
|
+
}
|
95
|
+
# use Data::Dumper;
|
96
|
+
# print Dumper(\%pairs);
|
97
|
+
my $nr_pids = scalar keys %pairs;
|
98
|
+
|
99
|
+
foreach my $pid (keys %pairs) {
|
100
|
+
my $pair = $pairs{$pid};
|
101
|
+
my $r = $pair->{r} || [];
|
102
|
+
my $w = $pair->{w} || [];
|
103
|
+
next if $cull_self_pipe && $nr_pids == 1 && @$r && @$w;
|
104
|
+
|
105
|
+
unless ($pipe_node) {
|
106
|
+
my $pipe_id = $pipe_nr++;
|
107
|
+
$graphed{$pipe_id} = $pipe_ino;
|
108
|
+
$pipe_node = "|$pipe_id";
|
109
|
+
$graph->add_node($pipe_node);
|
110
|
+
}
|
111
|
+
|
112
|
+
$graph->add_edge($pipe_node, $pid, join(',', @$r)) if @$r;
|
113
|
+
$graph->add_edge($pid, $pipe_node, join(',', @$w)) if @$w;
|
114
|
+
}
|
115
|
+
}
|
116
|
+
|
117
|
+
print " PID COMMAND\n";
|
118
|
+
foreach my $pid (sort { $a <=> $b } keys %pids) {
|
119
|
+
printf "% 6d", $pid;
|
120
|
+
print " ", $cmds{$pid} || cmd_of($pid), "\n";
|
121
|
+
}
|
122
|
+
|
123
|
+
print "\nPIPEID PIPE_INO\n";
|
124
|
+
foreach my $pipe_id (sort { $a <=> $b } keys %graphed) {
|
125
|
+
printf "% 6s", "|$pipe_id";
|
126
|
+
print " ", $graphed{$pipe_id}, "\n";
|
127
|
+
}
|
128
|
+
|
129
|
+
print $graph->as_ascii;
|