root / tools / profiling / munin-profile-node.py @ bde90ba9
Historique | Voir | Annoter | Télécharger (3,18 ko)
| 1 |
#!/usr/bin/python
|
|---|---|
| 2 |
# Copyright (C) 2013 Helmut Grohne <helmut@subdivi.de>
|
| 3 |
# License: GPLv2 like the rest of munin
|
| 4 |
"""
|
| 5 |
Usage:
|
| 6 |
tcpdump -npi lo "tcp port 4949" -w munin.pcap
|
| 7 |
# wait for one munin run, then press Ctrl-C
|
| 8 |
./munin-profile-node.py munin.pcap
|
| 9 |
"""
|
| 10 |
|
| 11 |
import collections |
| 12 |
import sys |
| 13 |
from scapy.utils import rdpcap |
| 14 |
import scapy.layers.l2 |
| 15 |
from scapy.layers.inet import IP, TCP |
| 16 |
|
| 17 |
class ConnectionProfile: |
| 18 |
"""
|
| 19 |
@ivar times: mapping of commands to durations waiting for answers in
|
| 20 |
seconds
|
| 21 |
@type times: {str: [float]}
|
| 22 |
@ivar idles: list of durations waiting for client in seconds
|
| 23 |
@type idles: [float]
|
| 24 |
"""
|
| 25 |
def __init__(self): |
| 26 |
self.times = dict() |
| 27 |
self.idles = []
|
| 28 |
self.curcommand = None |
| 29 |
self.commandstart = None |
| 30 |
|
| 31 |
def handle_to_node(self, timestamp, line): |
| 32 |
if self.curcommand is None and self.commandstart is not None: |
| 33 |
self.idles.append(timestamp - self.commandstart) |
| 34 |
self.curcommand = line
|
| 35 |
self.commandstart = timestamp
|
| 36 |
|
| 37 |
def handle_from_node(self, timestamp, line): |
| 38 |
if line != ".": |
| 39 |
return
|
| 40 |
if self.curcommand is None: |
| 41 |
return
|
| 42 |
duration = timestamp - self.commandstart
|
| 43 |
self.times.setdefault(self.curcommand, []).append(duration) |
| 44 |
self.curcommand = None |
| 45 |
self.commandstart = timestamp
|
| 46 |
|
| 47 |
class MuninProfiler: |
| 48 |
def __init__(self): |
| 49 |
self.to_node = "" |
| 50 |
self.from_node = "" |
| 51 |
self.connprof = collections.defaultdict(ConnectionProfile)
|
| 52 |
|
| 53 |
def handle_packet(self, packet): |
| 54 |
payload = str(packet[TCP].payload)
|
| 55 |
if not payload: |
| 56 |
return
|
| 57 |
if packet[TCP].dport == 4949: |
| 58 |
self.to_node += payload
|
| 59 |
conn = (packet[IP].src, packet[TCP].sport, packet[IP].dst) |
| 60 |
elif packet[TCP].sport == 4949: |
| 61 |
self.from_node += payload
|
| 62 |
conn = (packet[IP].dst, packet[TCP].dport, packet[IP].src) |
| 63 |
else:
|
| 64 |
return
|
| 65 |
lines = self.to_node.split("\n") |
| 66 |
self.to_node = lines.pop()
|
| 67 |
for line in lines: |
| 68 |
self.connprof[conn].handle_to_node(packet.time, line)
|
| 69 |
lines = self.from_node.split("\n") |
| 70 |
self.from_node = lines.pop()
|
| 71 |
for line in lines: |
| 72 |
self.connprof[conn].handle_from_node(packet.time, line)
|
| 73 |
|
| 74 |
@property
|
| 75 |
def times(self): |
| 76 |
times = dict()
|
| 77 |
for prof in self.connprof.values(): |
| 78 |
for com, durations in prof.times.items(): |
| 79 |
times.setdefault(com, []).extend(durations) |
| 80 |
return times
|
| 81 |
|
| 82 |
@property
|
| 83 |
def idles(self): |
| 84 |
return sum((prof.idles for prof in self.connprof.values()), []) |
| 85 |
|
| 86 |
def main(): |
| 87 |
mp = MuninProfiler() |
| 88 |
for pkt in rdpcap(sys.argv[1]): |
| 89 |
mp.handle_packet(pkt) |
| 90 |
print("Client idle time during connection: %.2fs" % sum(mp.idles)) |
| 91 |
times = [(key, sum(value)) for key, value in mp.times.items()] |
| 92 |
times.sort(key=lambda tpl: -tpl[1]) |
| 93 |
total = sum(value for key, value in times) |
| 94 |
print("Total time waiting for the node: %.2fs" % total)
|
| 95 |
print("Top 10 plugins using most of the time")
|
| 96 |
for key, value in times[:10]: |
| 97 |
print("%-40s: %.2fs (%d%%)" % (key, value, 100 * value / total)) |
| 98 |
|
| 99 |
if __name__ == '__main__': |
| 100 |
main() |
