2 # Emacs settings: -*- tab-width: 4 -*-
4 # Copyright (c) 2002-2003 Apple Computer, Inc. All rights reserved.
6 # Licensed under the Apache License, Version 2.0 (the "License");
7 # you may not use this file except in compliance with the License.
8 # You may obtain a copy of the License at
10 # http://www.apache.org/licenses/LICENSE-2.0
12 # Unless required by applicable law or agreed to in writing, software
13 # distributed under the License is distributed on an "AS IS" BASIS,
14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 # See the License for the specific language governing permissions and
16 # limitations under the License.
18 # parselog.py, written and contributed by Kevin Marks
20 # Requires OS X 10.3 Panther or later, for Python and Core Graphics Python APIs
21 # Invoke from the command line with "parselog.py fname" where fname is a log file made by mDNSNetMonitor
24 # It expects plain ASCII, and doesn't handle spaces in record names very well right now
25 # There's a procedure you can follow to 'sanitize' an mDNSNetMonitor log file to make it more paletable to parselog.py:
26 # 1. Run mDNSNetMonitor in a terminal window.
27 # When you have enough traffic, type Ctrl-C and save the content of the terminal window to disk.
28 # Alternatively, you can use "mDNSNetMonitor > logfile" to write the text directly to a file.
29 # You now have a UTF-8 text file.
30 # 2. Open the UTF-8 text file using BBEdit or some other text editor.
31 # (These instructions are for BBEdit, which I highly recommend you use when doing this.)
32 # 3. Make sure BBEdit correctly interprets the file as UTF-8.
33 # Either set your "Text Files Opening" preference to "UTF-8 no BOM", and drop the file onto BBEdit,
34 # or manually open the File using "File -> Open" and make sure the "Read As" setting is set to "UTF-8 no BOM"
35 # Check in the document pulldown menu in the window toolbar to make sure that it says "Encoding: UTF-8 no BOM"
36 # 4. Use "Tools -> Convert to ASCII" to replace all special characters with their seven-bit ascii equivalents.
37 # (e.g. curly quotes are converted to straight quotes)
38 # 5. Do a grep search and replace. (Cmd-F; make sure Grep checkbox is turned on.)
39 # Enter this search text : ^(.................\(................\S*) (.* -> .*)$
40 # Enter this replacement text: \1-\2
42 # Press Cmd-Opt-= repeatedly until there are no more instances to be replaced.
43 # You now have text file with all spaces in names changed to hyphens
44 # 6. Save the new file. You can save it as "UTF-8 no BOM", or as "Mac Roman". It really doesn't matter which --
45 # the file now contains only seven-bit ascii, so it's all the same no matter how you save it.
46 # 7. Run "parselog.py fname"
47 # 8. Open the resulting fname.pdf file with a PDF viewer like Preview on OS X
49 # Key to what you see:
50 # Time is on the horizontal axis
51 # Individual machines are shown on the vertical axis
52 # Filled red circle: Normal query Hollow red circle: Query requesting unicast reply
53 # Filled orange circle: Probe (service starting) Hollow orange circle: First probe (requesting unicast reply)
54 # Filled green circle: Normal answer Hollow green circle: Goodbye message (record going away)
55 # Hollow blue circle: Legacy query (from old client)
57 from CoreGraphics
import *
72 spaceExp
= re
.compile(r
'\s+')
73 print "Reading " + inFile
75 lines
= f
.readlines(100000)
80 if (line
== '\n' or line
== '\r' or line
==''):
83 # msg = ("skipped" , line)
85 elif (hunt
== 'getTime'):
88 time
= line
.split(' ')[0].split(':')
90 #print "bad time, skipping",time
94 #print (("getTime:%s" % (line)), time)
95 elif (hunt
== 'getIP'):
96 ip
= line
.split(' ',1)
100 secs
= secs
*60 +float(t
)
105 if (not ip
in ipList
):
106 ipList
[ip
] = [len(ipList
), "", ""]
107 #print (("getIP:%s" % (line)), time, secs)
109 elif (hunt
== 'getQA'):
110 qaList
= spaceExp
.split(line
)
111 # qaList[0] Source Address
112 # qaList[1] Operation type (PU/PM/QU/QM/AN etc.)
113 # qaList[2] Record type (PTR/SRV/TXT etc.)
116 # For PU/PM/AN/AN+/AD/AD+/KA:
119 # qaList[5...] "->" symbol and following rdata
121 if (qaList
[0] == ip
):
122 if (qaList
[1] == '(QU)' or qaList
[1] == '(LQ)' or qaList
[1] == '(PU)'):
123 plotPoints
.append([secs
, ipList
[ip
][0], (qaList
[1])[1:-1]])
124 elif (qaList
[1] == '(QM)'):
125 plotPoints
.append([secs
, ipList
[ip
][0], (qaList
[1])[1:-1]])
126 querySource
[qaList
[3]] = len(plotPoints
)-1
127 elif (qaList
[1] == '(PM)'):
128 plotPoints
.append([secs
, ipList
[ip
][0], (qaList
[1])[1:-1]])
129 querySource
[qaList
[4]] = len(plotPoints
)-1
130 elif (qaList
[1] == '(AN)' or qaList
[1] == '(AN+)' or qaList
[1] == '(DE)'):
131 plotPoints
.append([secs
, ipList
[ip
][0], (qaList
[1])[1:-1]])
133 theQuery
= querySource
[qaList
[4]]
134 theDelta
= secs
- plotPoints
[theQuery
][0]
136 plotPoints
[-1].append(querySource
[qaList
[4]])
137 #print "Answer AN+ %s points to %d" % (qaList[4],querySource[qaList[4]])
139 #print "Couldn't find any preceeding question for", qaList
141 elif (qaList
[1] != '(KA)' and qaList
[1] != '(AD)' and qaList
[1] != '(AD+)'):
142 print "Operation unknown", qaList
144 if (qaList
[1] == '(AN)' or qaList
[1] == '(AN+)' or qaList
[1] == '(AD)' or qaList
[1] == '(AD+)'):
145 if (qaList
[2] == 'HINFO'):
146 ipList
[ip
][1] = qaList
[4]
147 ipList
[ip
][2] = string
.join(qaList
[6:])
149 elif (qaList
[2] == 'AAAA'):
150 if (ipList
[ip
][1] == ""):
151 ipList
[ip
][1] = qaList
[4]
152 ipList
[ip
][2] = "Panther"
153 elif (qaList
[2] == 'Addr'):
154 if (ipList
[ip
][1] == ""):
155 ipList
[ip
][1] = qaList
[4]
156 ipList
[ip
][2] = "Jaguar"
165 #width=20.0*(maxTime-minTime)
166 if (maxTime
< minTime
+ 10.0):
167 maxTime
= minTime
+ 10.0
169 width
=20.0*(maxTime
-minTime
)
170 pageHeight
=(len(ipList
)+1) * typesize
171 scale
= width
/(maxTime
-minTime
)
172 leftMargin
= typesize
* 60
173 bottomMargin
= typesize
174 pageRect
= CGRectMake (-leftMargin
, -bottomMargin
, leftMargin
+ width
, bottomMargin
+ pageHeight
) # landscape
175 outFile
= "%s.pdf" % (".".join(inFile
.split('.')[:-1]))
176 c
= CGPDFContextCreateWithFilename (outFile
, pageRect
)
177 print "Writing " + outFile
178 ourColourSpace
= c
.getColorSpace()
179 # QM/QU red solid/hollow
180 # PM/PU orange solid/hollow
182 # AN/DA green solid/hollow
183 #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)}
184 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)}
185 c
.beginPage (pageRect
)
186 c
.setRGBFillColor(.75,0.0,0.0,1.0)
187 c
.setRGBStrokeColor(.25,0.75,0.25,1.0)
189 for point
in plotPoints
:
190 #c.addArc((point[0]-minTime)*scale,point[1]*typesize+6,5,0,2*math.pi,1)
191 c
.addArc((point
[0]-minTime
)*scale
,point
[1]*typesize
+6,typesize
/4,0,2*math
.pi
,1)
192 theColour
= colourLookup
[(point
[2])[0]]
193 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"):
194 print "Unknown", point
195 if ((point
[2])[-1] == "M" or (point
[2])[0]== "A"):
196 c
.setRGBFillColor(theColour
[0],theColour
[1],theColour
[2],.5)
199 c
.setRGBStrokeColor(theColour
[0],theColour
[1],theColour
[2],.5)
202 c
.setRGBStrokeColor(.25,0.75,0.25,1.0)
204 for index
in point
[3:]:
206 c
.moveToPoint((point
[0]-minTime
)*scale
,point
[1]*typesize
+6)
207 c
.addLineToPoint(((plotPoints
[index
])[0]-minTime
)*scale
,(plotPoints
[index
])[1]*typesize
+6)
210 c
.setRGBFillColor (0,0,0, 1)
211 c
.setTextDrawingMode (kCGTextFill
)
212 c
.setTextMatrix (CGAffineTransformIdentity
)
213 c
.selectFont ('Gill Sans', typesize
, kCGEncodingMacRoman
)
214 c
.setRGBStrokeColor(0.25,0.0,0.0,1.0)
216 for ip
,[height
,hname
,hinfo
] in ipList
.items():
218 c
.moveToPoint(pageRect
.origin
.x
,height
*typesize
+6)
219 c
.addLineToPoint(width
,height
*typesize
+6)
222 c
.showTextAtPoint(pageRect
.origin
.x
+ 2, height
*typesize
+ 2, ip
, len(ip
))
223 c
.showTextAtPoint(pageRect
.origin
.x
+ 2 + typesize
*8, height
*typesize
+ 2, hname
, len(hname
))
224 c
.showTextAtPoint(pageRect
.origin
.x
+ 2 + typesize
*25, height
*typesize
+ 2, hinfo
, len(hinfo
))
225 for time
in range (int(minTime
),int(maxTime
)+1):
227 c
.moveToPoint((time
-minTime
)*scale
,pageRect
.origin
.y
)
228 c
.addLineToPoint((time
-minTime
)*scale
,pageHeight
)
230 if (int(time
) % 10 == 0):
232 theMinutes
= time
/60 % 60
233 theSeconds
= time
% 60
234 theTimeString
= '%d:%02d:%02d' % (theHours
, theMinutes
, theSeconds
)
235 # Should measure string width, but don't know how to do that
236 theStringWidth
= typesize
* 3.5
237 c
.showTextAtPoint((time
-minTime
)*scale
- theStringWidth
/2, pageRect
.origin
.y
+ 2, theTimeString
, len(theTimeString
))
246 for arg
in sys
.argv
[1:]: