2013-02-26 22:42:35 +01:00
|
|
|
#!/usr/bin/python
|
|
|
|
# Copyright (C) 2013 Helmut Grohne <helmut@subdivi.de>
|
|
|
|
# License: GPLv2 like the rest of munin
|
|
|
|
"""
|
|
|
|
Usage:
|
|
|
|
tcpdump -npi lo "tcp port 4949" -w munin.pcap
|
|
|
|
# wait for one munin run, then press Ctrl-C
|
|
|
|
./munin-profile-node.py munin.pcap
|
|
|
|
"""
|
|
|
|
|
2013-02-27 09:06:34 +01:00
|
|
|
import collections
|
2013-02-26 22:42:35 +01:00
|
|
|
import sys
|
|
|
|
from scapy.utils import rdpcap
|
|
|
|
import scapy.layers.l2
|
2013-02-27 09:06:34 +01:00
|
|
|
from scapy.layers.inet import IP, TCP
|
2013-02-26 22:42:35 +01:00
|
|
|
|
2013-02-27 09:06:34 +01:00
|
|
|
class ConnectionProfile:
|
|
|
|
"""
|
|
|
|
@ivar times: mapping of commands to durations waiting for answers in
|
|
|
|
seconds
|
|
|
|
@type times: {str: [float]}
|
|
|
|
@ivar idles: list of durations waiting for client in seconds
|
|
|
|
@type idles: [float]
|
|
|
|
"""
|
2013-02-26 22:42:35 +01:00
|
|
|
def __init__(self):
|
|
|
|
self.times = dict()
|
|
|
|
self.idles = []
|
|
|
|
self.curcommand = None
|
|
|
|
self.commandstart = None
|
|
|
|
|
|
|
|
def handle_to_node(self, timestamp, line):
|
|
|
|
if self.curcommand is None and self.commandstart is not None:
|
|
|
|
self.idles.append(timestamp - self.commandstart)
|
|
|
|
self.curcommand = line
|
|
|
|
self.commandstart = timestamp
|
|
|
|
|
|
|
|
def handle_from_node(self, timestamp, line):
|
|
|
|
if line != ".":
|
|
|
|
return
|
|
|
|
if self.curcommand is None:
|
|
|
|
return
|
|
|
|
duration = timestamp - self.commandstart
|
|
|
|
self.times.setdefault(self.curcommand, []).append(duration)
|
|
|
|
self.curcommand = None
|
|
|
|
self.commandstart = timestamp
|
|
|
|
|
2013-02-27 09:06:34 +01:00
|
|
|
class MuninProfiler:
|
|
|
|
def __init__(self):
|
|
|
|
self.to_node = ""
|
|
|
|
self.from_node = ""
|
|
|
|
self.connprof = collections.defaultdict(ConnectionProfile)
|
|
|
|
|
|
|
|
def handle_packet(self, packet):
|
|
|
|
payload = str(packet[TCP].payload)
|
|
|
|
if not payload:
|
|
|
|
return
|
|
|
|
if packet[TCP].dport == 4949:
|
|
|
|
self.to_node += payload
|
|
|
|
conn = (packet[IP].src, packet[TCP].sport, packet[IP].dst)
|
|
|
|
elif packet[TCP].sport == 4949:
|
|
|
|
self.from_node += payload
|
|
|
|
conn = (packet[IP].dst, packet[TCP].dport, packet[IP].src)
|
|
|
|
else:
|
|
|
|
return
|
|
|
|
lines = self.to_node.split("\n")
|
|
|
|
self.to_node = lines.pop()
|
|
|
|
for line in lines:
|
|
|
|
self.connprof[conn].handle_to_node(packet.time, line)
|
|
|
|
lines = self.from_node.split("\n")
|
|
|
|
self.from_node = lines.pop()
|
|
|
|
for line in lines:
|
|
|
|
self.connprof[conn].handle_from_node(packet.time, line)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def times(self):
|
|
|
|
times = dict()
|
|
|
|
for prof in self.connprof.values():
|
|
|
|
for com, durations in prof.times.items():
|
|
|
|
times.setdefault(com, []).extend(durations)
|
|
|
|
return times
|
|
|
|
|
|
|
|
@property
|
|
|
|
def idles(self):
|
|
|
|
return sum((prof.idles for prof in self.connprof.values()), [])
|
|
|
|
|
2013-02-26 22:42:35 +01:00
|
|
|
def main():
|
|
|
|
mp = MuninProfiler()
|
|
|
|
for pkt in rdpcap(sys.argv[1]):
|
|
|
|
mp.handle_packet(pkt)
|
|
|
|
print("Client idle time during connection: %.2fs" % sum(mp.idles))
|
|
|
|
times = [(key, sum(value)) for key, value in mp.times.items()]
|
|
|
|
times.sort(key=lambda tpl: -tpl[1])
|
|
|
|
total = sum(value for key, value in times)
|
|
|
|
print("Total time waiting for the node: %.2fs" % total)
|
|
|
|
print("Top 10 plugins using most of the time")
|
|
|
|
for key, value in times[:10]:
|
|
|
|
print("%-40s: %.2fs (%d%%)" % (key, value, 100 * value / total))
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
main()
|