Skip to content

Commit dc99f60

Browse files
committed
Genesis commit
1 parent 2ec456d commit dc99f60

File tree

3 files changed

+196
-1
lines changed

3 files changed

+196
-1
lines changed

README.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,17 @@
1-
# lsofgraph-python
1+
Python version of ![zevv/lsofgraph](/example.jpg)
2+
3+
A small utility to convert Unix `lsof` output to a graph showing FIFO and UNIX interprocess communication.
4+
5+
Generate graph:
6+
7+
````shell
8+
sudo lsof -n -F | ./lsofgraph | dot -Tjpg > /tmp/a.jpg
9+
````
10+
11+
or add `unflatten` to the chain for a better layout:
12+
13+
````shell
14+
sudo lsof -n -F | ./lsofgraph | unflatten -l 1 -c 6 | dot -T jpg > /tmp/a.jpg
15+
````
16+
17+
![example output](/example.jpg)

example.jpg

157 KB
Loading

lsofgraph.py

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
#!/usr/bin/env python
2+
# Convert the output of `lsof -F` into PID USER CMD OBJ
3+
import sys
4+
def parse_lsof():
5+
procs = {}
6+
cur = {}
7+
proc = {}
8+
file = {}
9+
10+
for line in sys.stdin:
11+
12+
if line.startswith("COMMAND"):
13+
print 'did u run lsof without -F?'
14+
exit(1)
15+
16+
tag = line[0]
17+
val = line[1:].rstrip('\n')
18+
if tag == 'p':
19+
if val not in procs:
20+
proc = {'files' : []}
21+
file = None
22+
cur = proc
23+
procs[val] = proc
24+
else:
25+
proc = {}
26+
cur = {}
27+
28+
elif tag == 'f' and proc:
29+
file = { 'proc' : proc }
30+
cur = file
31+
proc['files'].append(file)
32+
33+
if cur:
34+
cur[tag] = val
35+
36+
# skip kernel threads
37+
38+
if proc:
39+
if file and all(k in file.keys() for k in ("f","t")):
40+
if file['f'] == "txt" and file['t'] == "unknown":
41+
procs[proc['p']] = {}
42+
proc = {}
43+
file = None
44+
cur = None
45+
46+
return procs
47+
48+
def find_connections(procs):
49+
cs = {
50+
'fifo': {},
51+
'unix': {},
52+
'tcp': {},
53+
'udp': {},
54+
'pipe': {}
55+
}
56+
for pid, proc in procs.iteritems():
57+
if 'files' in proc:
58+
for _, file in enumerate(proc['files']):
59+
60+
if 't' in file and file['t'] == "unix":
61+
if 'i' in file:
62+
i = file['i']
63+
if i in cs['unix']:
64+
cs['unix'][i].append(file)
65+
else:
66+
cs['unix'][i] = []
67+
cs['unix'][i].append(file)
68+
else:
69+
i = file['d']
70+
if i in cs['unix']:
71+
cs['unix'][i].append(file)
72+
else:
73+
cs['unix'][i] = []
74+
cs['unix'][i].append(file)
75+
76+
77+
78+
if 't' in file and file['t'] == "FIFO":
79+
80+
if 'i' in file:
81+
if file['i'] in cs['fifo']:
82+
cs['fifo'][file['i']].append(file)
83+
else:
84+
cs['fifo'][file['i']] = []
85+
cs['fifo'][file['i']].append(file)
86+
87+
88+
89+
if 't' in file and file['t'] == "PIPE":
90+
for n in file['n'].lstrip('->'):
91+
ps = { file['d'], n}
92+
ps = sorted(ps)
93+
if len(ps) == 2:
94+
id = ps[0] + "\\n" + ps[1]
95+
fs = cs['pipe']
96+
if id in fs:
97+
fs[id].append(file)
98+
else:
99+
fs[id] = []
100+
fs[id].append(file)
101+
102+
103+
if 't' in file and (file['t'] == 'IPv4' or file['t'] == 'IPv6'):
104+
if '->' in file['n']:
105+
a,b = file['n'].split("->")
106+
ps = { a, b}
107+
ps = sorted(ps)
108+
if len(ps) == 2:
109+
id = ps[0] + "\\n" + ps[1]
110+
if file['P'] == "TCP":
111+
fs = cs['tcp']
112+
if id in fs:
113+
fs[id].append(file)
114+
else:
115+
fs[id] = []
116+
fs[id].append(file)
117+
else:
118+
fs=cs['udp']
119+
if id in fs:
120+
fs[id].append(file)
121+
else:
122+
fs[id] = []
123+
fs[id].append(file)
124+
125+
126+
return cs
127+
128+
def print_graph(procs, conns):
129+
colors = {
130+
'fifo': "green",
131+
'unix': "purple",
132+
'tcp': "red",
133+
'udp': "orange",
134+
'pipe': "orange"
135+
}
136+
137+
# Generate graph
138+
139+
print("digraph G {")
140+
print("\tgraph [ center=true, margin=0.2, nodesep=0.1, ranksep=0.3, rankdir=LR];")
141+
print("\tnode [ shape=box, style=\"rounded,filled\" width=0, height=0, fontname=Helvetica, fontsize=10];")
142+
print("\tedge [ fontname=Helvetica, fontsize=10];")
143+
144+
# Parent/child relationships
145+
146+
for pid, proc in procs.iteritems():
147+
if 'R' in proc and proc['R'] == "1":
148+
color = "grey70"
149+
else:
150+
color = "white"
151+
if 'p' in proc and 'n' in proc:
152+
print("\tp%s [ label = \"%s\\n%s %s\" fillcolor=%s ];" % (proc['p'], proc['n'], proc['p'], proc['L'], color))
153+
elif 'p' in proc:
154+
print("\tp%s [ label = \"%s\\n%s %s\" fillcolor=%s ];" % (proc['p'], proc['c'], proc['p'], proc['L'], color))
155+
156+
157+
if 'R' in proc and proc['R'] in procs:
158+
proc_parent = procs[proc['R']]
159+
if proc_parent:
160+
if proc_parent['p'] != "1":
161+
print("\tp%s -> p%s [ penwidth=2 weight=100 color=grey60 dir=\"none\" ];" % (proc['R'], proc['p']))
162+
163+
for type, conn in conns.iteritems():
164+
for id, files in conn.items():
165+
166+
if len(files) == 2:
167+
if files[0]['proc'] != files[1]['proc']:
168+
label = type + ":\\n" + id
169+
dir = "both"
170+
if files[0]['a'] == "w":
171+
dir = "forward"
172+
elif files[0]['a'] == "r":
173+
dir = "backward"
174+
print("\tp%s -> p%s [ color=\"%s\" label=\"%s\" dir=\"%s\"];" % (files[0]['proc']['p'], files[1]['proc']['p'], colors[type] or "black", label, dir))
175+
print("}")
176+
177+
procs = parse_lsof()
178+
conns = find_connections(procs)
179+
print_graph(procs, conns)

0 commit comments

Comments
 (0)