Simple tool for analyzing a dump of a single S3 connection.
[bluesky.git] / parsetrace / analyze-tcp.py
1 #!/usr/bin/python
2 #
3 # Read a pcap dump containing a single TCP connection and analyze it to
4 # determine as much as possible about the performance of that connection.
5 # (Specifically designed for measuring performance of fetches to Amazon S3.)
6
7 import impacket, pcapy, re, sys
8 import impacket.ImpactDecoder, impacket.ImpactPacket
9
10 # Estimate of the network RTT
11 RTT_EST = 0.03
12
13 class Packet:
14     def __init__(self, connection, ts, pkt):
15         self.connection = connection
16         self.ts = ts
17         self.pkt = pkt
18         self.ip = self.pkt.child()
19         self.tcp = self.ip.child()
20
21         self.datalen = self.ip.get_ip_len() - self.ip.get_header_size() \
22                         - self.tcp.get_header_size()
23         self.data = self.tcp.get_data_as_string()[0:self.datalen]
24
25         self.seq = (self.tcp.get_th_seq(), self.tcp.get_th_seq() + self.datalen)
26         self.ack = self.tcp.get_th_ack()
27         self.id = self.ip.get_ip_id()
28
29         if self.tcp.get_th_sport() == 80:
30             # Incoming packet
31             self.direction = -1
32         elif self.tcp.get_th_dport() == 80:
33             # Outgoing packet
34             self.direction = 1
35         else:
36             self.direction = 0
37
38     def __repr__(self):
39         return "<Packet[%s]: id=%d seq=%d..%d ack=%d %s>" % \
40             ({-1: '<', 1: '>', 0: '?'}[self.direction], self.id,
41              self.seq[0], self.seq[1], self.ack, self.ts)
42
43 class TcpAnalysis:
44     def __init__(self):
45         self.start_time = None
46         self.decoder = impacket.ImpactDecoder.EthDecoder()
47         self.packets = []
48
49     def process_file(self, filename):
50         """Load a pcap file and process the packets contained in it."""
51
52         p = pcapy.open_offline(filename)
53         p.setfilter(r"ip proto \tcp")
54         assert p.datalink() == pcapy.DLT_EN10MB
55         p.loop(0, self.packet_handler)
56
57     def packet_handler(self, header, data):
58         """Callback function run by the pcap parser for each packet."""
59
60         (sec, us) = header.getts()
61         ts = sec * 1000000 + us
62         if self.start_time is None:
63             self.start_time = ts
64         ts -= self.start_time
65         pkt = Packet(self, ts * 1e-6, self.decoder.decode(data))
66         self.packets.append(pkt)
67
68 def split_trace(packets, predicate, before=True):
69     """Split a sequence of packets apart where packets satisfy the predicate.
70
71     If before is True (default), the split happens just before the matching
72     packet; otherwise it happens just after.
73     """
74
75     segment = []
76     for p in packets:
77         if predicate(p):
78             if before:
79                 if len(segment) > 0:
80                     yield segment
81                 segment = [p]
82             else:
83                 segment.append(p)
84                 yield segment
85                 segment = []
86         else:
87             segment.append(p)
88     if len(segment) > 0:
89         yield segment
90
91 def analyze_get(packets):
92     packets = iter(packets)
93
94     # First packet is the GET request itself
95     p = packets.next()
96     if not(p.direction > 0 and p.data.startswith('GET')):
97         print "Doesn't seem to be a GET request..."
98         return
99
100     start_ts = p.ts
101     id_out = p.id
102
103     # Find the first response packet containing data
104     while not(p.direction < 0 and p.datalen > 0):
105         p = packets.next()
106
107     resp_ts = p.ts
108     id_in = p.id
109     start_seq = p.seq[0]
110
111     print "Response time:", resp_ts - start_ts
112
113     # Scan through the incoming packets, looking for gaps in either the IP ID
114     # field or in the timing
115     last_ts = resp_ts
116     for p in packets:
117         gap = False
118         if not p.direction < 0: continue
119         if p.id != (id_in + 1) & 0xffff:
120             gap = True
121             print "Sequence number gap at", id_in
122         if p.ts - last_ts > 2 * RTT_EST:
123             gap = True
124             print "Long gap of", p.ts - last_ts
125         elif p.ts - last_ts > RTT_EST / 2:
126             gap = True
127             print "Short gap of", p.ts - last_ts
128         if gap:
129             print "    [occurred after", p.seq[0] - start_seq, "bytes, time", p.ts, "sec]"
130         if p.datalen < 1460:
131             print "Short packet of", p.datalen, "bytes, brings total to", p.seq[1] - start_seq
132         last_ts = p.ts
133         id_in = p.id
134
135 if __name__ == '__main__':
136     for f in sys.argv[1:]:
137         conn = TcpAnalysis()
138         conn.process_file(f)
139         ts = 0.0
140         def request_start(p):
141             return p.direction > 0 and p.datalen > 0
142         for s in split_trace(conn.packets, request_start):
143             s = list(s)
144             if False:
145                 for p in s:
146                     if p.ts - ts > 0.01:
147                         print "----"
148                     if p.ts - ts > 2 * RTT_EST:
149                         print "LONG DELAY\n----"
150                     ts = p.ts
151                     print p
152                     if p.direction > 0 and p.datalen > 0:
153                         print "Request:", repr(p.data)
154             analyze_get(s)
155             print "===="