3 # parselog.py, written and contributed by Kevin Marks
5 # Copyright (c) 2002-2003 Apple Computer, Inc. All rights reserved.
7 # @APPLE_LICENSE_HEADER_START@
9 # This file contains Original Code and/or Modifications of Original Code
10 # as defined in and that are subject to the Apple Public Source License
11 # Version 2.0 (the 'License'). You may not use this file except in
12 # compliance with the License. Please obtain a copy of the License at
13 # http://www.opensource.apple.com/apsl/ and read it before using this
16 # The Original Code and all software distributed under the License are
17 # distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
18 # EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
19 # INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
20 # FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
21 # Please see the License for the specific language governing rights and
22 # limitations under the License.
24 # @APPLE_LICENSE_HEADER_END@
26 # Requires OS X 10.3 Panther for Python and Core Graphics Python APIs
27 # Invoke from the command line with "parselog.py fname" where fname is a log file made by mDNSNetMonitor
30 # It expects plain ASCII, and doesn't handle spaces in record names very well right now
31 # There's a procedure you can follow to 'sanitize' an mDNSNetMonitor log file to make it more paletable to parselog.py:
32 # 1. Run mDNSNetMonitor in a terminal window.
33 # When you have enough traffic, type Ctrl-C and save the content of the terminal window to disk.
34 # Alternatively, you can use "mDNSNetMonitor > logfile" to write the text directly to a file.
35 # You now have a UTF-8 text file.
36 # 2. Open the UTF-8 text file using BBEdit or some other text editor.
37 # (These instructions are for BBEdit, which I highly recommend you use when doing this.)
38 # 3. Make sure BBEdit correctly interprets the file as UTF-8.
39 # Either set your "Text Files Opening" preference to "UTF-8 no BOM", and drop the file onto BBEdit,
40 # or manually open the File using "File -> Open" and make sure the "Read As" setting is set to "UTF-8 no BOM"
41 # Check in the document pulldown menu in the window toolbar to make sure that it says "Encoding: UTF-8 no BOM"
42 # 4. Use "Tools -> Convert to ASCII" to replace all special characters with their seven-bit ascii equivalents.
43 # (e.g. curly quotes are converted to straight quotes)
44 # 5. Do a grep search and replace. (Cmd-F; make sure Grep checkbox is turned on.)
45 # Enter this search text : ^(.................\(................\S*) (.* -> .*)$
46 # Enter this replacement text: \1-\2
48 # Press Cmd-Opt-= repeatedly until there are no more instances to be replaced.
49 # You now have text file with all spaces in names changed to hyphens
50 # 6. Save the new file. You can save it as "UTF-8 no BOM", or as "Mac Roman". It really doesn't matter which --
51 # the file now contains only seven-bit ascii, so it's all the same no matter how you save it.
52 # 7. Run "parselog.py fname"
53 # 8. Open the resulting fname.pdf file with a PDF viewer like Preview on OS X
55 # Key to what you see:
56 # Time is on the horizontal axis
57 # Individual machines are shown on the vertical axis
58 # Filled red circle: Normal query Hollow red circle: Query requesting unicast reply
59 # Filled orange circle: Probe (service starting) Hollow orange circle: First probe (requesting unicast reply)
60 # Filled green circle: Normal answer Hollow green circle: Goodbye message (record going away)
61 # Hollow blue circle: Legacy query (from old client)
62 # $Log: parselog.py,v $
63 # Revision 1.2 2003/12/01 21:47:44 cheshire
66 # Revision 1.1 2003/10/10 02:14:17 cheshire
67 # First checkin of parselog.py, a tool to create graphical representations of mDNSNetMonitor logs
69 from CoreGraphics
import *
84 spaceExp
= re
.compile(r
'\s+')
85 print "Reading " + inFile
87 lines
= f
.readlines(100000)
92 if (line
== '\n' or line
== '\r' or line
==''):
95 # msg = ("skipped" , line)
97 elif (hunt
== 'getTime'):
100 time
= line
.split(' ')[0].split(':')
102 #print "bad time, skipping",time
106 #print (("getTime:%s" % (line)), time)
107 elif (hunt
== 'getIP'):
108 ip
= line
.split(' ',1)
112 secs
= secs
*60 +float(t
)
117 if (not ip
in ipList
):
118 ipList
[ip
] = [len(ipList
), "", ""]
119 #print (("getIP:%s" % (line)), time, secs)
121 elif (hunt
== 'getQA'):
122 qaList
= spaceExp
.split(line
)
123 # qaList[0] Source Address
124 # qaList[1] Operation type (PU/PM/QU/QM/AN etc.)
125 # qaList[2] Record type (PTR/SRV/TXT etc.)
128 # For PU/PM/AN/AN+/AD/AD+/KA:
131 # qaList[5...] "->" symbol and following rdata
133 if (qaList
[0] == ip
):
134 if (qaList
[1] == '(QU)' or qaList
[1] == '(LQ)' or qaList
[1] == '(PU)'):
135 plotPoints
.append([secs
, ipList
[ip
][0], (qaList
[1])[1:-1]])
136 elif (qaList
[1] == '(QM)'):
137 plotPoints
.append([secs
, ipList
[ip
][0], (qaList
[1])[1:-1]])
138 querySource
[qaList
[3]] = len(plotPoints
)-1
139 elif (qaList
[1] == '(PM)'):
140 plotPoints
.append([secs
, ipList
[ip
][0], (qaList
[1])[1:-1]])
141 querySource
[qaList
[4]] = len(plotPoints
)-1
142 elif (qaList
[1] == '(AN)' or qaList
[1] == '(AN+)' or qaList
[1] == '(DE)'):
143 plotPoints
.append([secs
, ipList
[ip
][0], (qaList
[1])[1:-1]])
145 theQuery
= querySource
[qaList
[4]]
146 theDelta
= secs
- plotPoints
[theQuery
][0]
148 plotPoints
[-1].append(querySource
[qaList
[4]])
149 #print "Answer AN+ %s points to %d" % (qaList[4],querySource[qaList[4]])
151 #print "Couldn't find any preceeding question for", qaList
153 elif (qaList
[1] != '(KA)' and qaList
[1] != '(AD)' and qaList
[1] != '(AD+)'):
154 print "Operation unknown", qaList
156 if (qaList
[1] == '(AN)' or qaList
[1] == '(AN+)' or qaList
[1] == '(AD)' or qaList
[1] == '(AD+)'):
157 if (qaList
[2] == 'HINFO'):
158 ipList
[ip
][1] = qaList
[4]
159 ipList
[ip
][2] = string
.join(qaList
[6:])
161 elif (qaList
[2] == 'AAAA'):
162 if (ipList
[ip
][1] == ""):
163 ipList
[ip
][1] = qaList
[4]
164 ipList
[ip
][2] = "Panther"
165 elif (qaList
[2] == 'Addr'):
166 if (ipList
[ip
][1] == ""):
167 ipList
[ip
][1] = qaList
[4]
168 ipList
[ip
][2] = "Jaguar"
177 #width=20.0*(maxTime-minTime)
178 if (maxTime
< minTime
+ 10.0):
179 maxTime
= minTime
+ 10.0
181 width
=20.0*(maxTime
-minTime
)
182 pageHeight
=(len(ipList
)+1) * typesize
183 scale
= width
/(maxTime
-minTime
)
184 leftMargin
= typesize
* 60
185 bottomMargin
= typesize
186 pageRect
= CGRectMake (-leftMargin
, -bottomMargin
, leftMargin
+ width
, bottomMargin
+ pageHeight
) # landscape
187 outFile
= "%s.pdf" % (".".join(inFile
.split('.')[:-1]))
188 c
= CGPDFContextCreateWithFilename (outFile
, pageRect
)
189 print "Writing " + outFile
190 ourColourSpace
= c
.getColorSpace()
191 # QM/QU red solid/hollow
192 # PM/PU orange solid/hollow
194 # AN/DA green solid/hollow
195 #colourLookup = {"L":(0.0,0.0,.75), "Q":(.75,0.0,0.0), "P":(.75,0.5,0.0), "A":(0.0,0.75,0.0), "D":(0.0,0.75,0.0), "?":(.25,0.25,0.25)}
196 colourLookup
= {"L":(0.0,0.0,1.0), "Q":(1.0,0.0,0.0), "P":(1.0,0.8,0.0), "A":(0.0,1.0,0.0), "D":(0.0,1.0,0.0), "?":(1.0,1.0,1.0)}
197 c
.beginPage (pageRect
)
198 c
.setRGBFillColor(.75,0.0,0.0,1.0)
199 c
.setRGBStrokeColor(.25,0.75,0.25,1.0)
201 for point
in plotPoints
:
202 #c.addArc((point[0]-minTime)*scale,point[1]*typesize+6,5,0,2*math.pi,1)
203 c
.addArc((point
[0]-minTime
)*scale
,point
[1]*typesize
+6,typesize
/4,0,2*math
.pi
,1)
204 theColour
= colourLookup
[(point
[2])[0]]
205 if (((point
[2])[0]) != "L") and (((point
[2])[0]) != "Q") and (((point
[2])[0]) != "P") and (((point
[2])[0]) != "A") and (((point
[2])[0]) != "D"):
206 print "Unknown", point
207 if ((point
[2])[-1] == "M" or (point
[2])[0]== "A"):
208 c
.setRGBFillColor(theColour
[0],theColour
[1],theColour
[2],.5)
211 c
.setRGBStrokeColor(theColour
[0],theColour
[1],theColour
[2],.5)
214 c
.setRGBStrokeColor(.25,0.75,0.25,1.0)
216 for index
in point
[3:]:
218 c
.moveToPoint((point
[0]-minTime
)*scale
,point
[1]*typesize
+6)
219 c
.addLineToPoint(((plotPoints
[index
])[0]-minTime
)*scale
,(plotPoints
[index
])[1]*typesize
+6)
222 c
.setRGBFillColor (0,0,0, 1)
223 c
.setTextDrawingMode (kCGTextFill
)
224 c
.setTextMatrix (CGAffineTransformIdentity
)
225 c
.selectFont ('Gill Sans', typesize
, kCGEncodingMacRoman
)
226 c
.setRGBStrokeColor(0.25,0.0,0.0,1.0)
228 for ip
,[height
,hname
,hinfo
] in ipList
.items():
230 c
.moveToPoint(pageRect
.origin
.x
,height
*typesize
+6)
231 c
.addLineToPoint(width
,height
*typesize
+6)
234 c
.showTextAtPoint(pageRect
.origin
.x
+ 2, height
*typesize
+ 2, ip
, len(ip
))
235 c
.showTextAtPoint(pageRect
.origin
.x
+ 2 + typesize
*8, height
*typesize
+ 2, hname
, len(hname
))
236 c
.showTextAtPoint(pageRect
.origin
.x
+ 2 + typesize
*25, height
*typesize
+ 2, hinfo
, len(hinfo
))
237 for time
in range (int(minTime
),int(maxTime
)+1):
239 c
.moveToPoint((time
-minTime
)*scale
,pageRect
.origin
.y
)
240 c
.addLineToPoint((time
-minTime
)*scale
,pageHeight
)
242 if (int(time
) % 10 == 0):
244 theMinutes
= time
/60 % 60
245 theSeconds
= time
% 60
246 theTimeString
= '%d:%02d:%02d' % (theHours
, theMinutes
, theSeconds
)
247 # Should measure string width, but don't know how to do that
248 theStringWidth
= typesize
* 3.5
249 c
.showTextAtPoint((time
-minTime
)*scale
- theStringWidth
/2, pageRect
.origin
.y
+ 2, theTimeString
, len(theTimeString
))
258 for arg
in sys
.argv
[1:]: