diff options
Diffstat (limited to 'files/stat_log.py')
-rwxr-xr-x | files/stat_log.py | 394 |
1 files changed, 394 insertions, 0 deletions
diff --git a/files/stat_log.py b/files/stat_log.py new file mode 100755 index 0000000..2dbe3ee --- /dev/null +++ b/files/stat_log.py | |||
@@ -0,0 +1,394 @@ | |||
1 | #!/usr/bin/python | ||
2 | import sys | ||
3 | import copy | ||
4 | |||
5 | P_NULL = 0 | ||
6 | P_NEW = 1 | ||
7 | P_READY = 2 | ||
8 | P_RUNNING = 4 | ||
9 | P_WAITING = 8 | ||
10 | P_EXIT = 16 | ||
11 | |||
12 | S_STATE = 0 | ||
13 | S_TIME = 1 | ||
14 | |||
15 | HZ = 100 | ||
16 | |||
17 | graph_title = r""" | ||
18 | -----===< COOL GRAPHIC OF SCHEDULER >===----- | ||
19 | |||
20 | [Symbol] [Meaning] | ||
21 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
22 | number PID or tick | ||
23 | "-" New or Exit | ||
24 | "#" Running | ||
25 | "|" Ready | ||
26 | ":" Waiting | ||
27 | / Running with | ||
28 | "+" -| Ready | ||
29 | \and/or Waiting | ||
30 | |||
31 | -----===< !!!!!!!!!!!!!!!!!!!!!!!!! >===----- | ||
32 | """ | ||
33 | |||
34 | usage = """ | ||
35 | Usage: | ||
36 | %s /path/to/process.log [PID1] [PID2] ... [-x PID1 [PID2] ... ] [-m] [-g] | ||
37 | |||
38 | Example: | ||
39 | # Include process 6, 7, 8 and 9 in statistics only. (Unit: tick) | ||
40 | %s /path/to/process.log 6 7 8 9 | ||
41 | |||
42 | # Exclude process 0 and 1 from statistics. (Unit: tick) | ||
43 | %s /path/to/process.log -x 0 1 | ||
44 | |||
45 | # Include process 6 and 7 only and print a COOL "graphic"! (Unit: millisecond) | ||
46 | %s /path/to/process.log 6 7 -m -g | ||
47 | |||
48 | # Include all processes and print a COOL "graphic"! (Unit: tick) | ||
49 | %s /path/to/process.log -g | ||
50 | """ | ||
51 | |||
52 | class MyError(Exception): | ||
53 | pass | ||
54 | |||
55 | class DuplicateNew(MyError): | ||
56 | def __init__(self, pid): | ||
57 | args = "More than one 'N' for process %d." % pid | ||
58 | MyError.__init__(self, args) | ||
59 | |||
60 | class UnknownState(MyError): | ||
61 | def __init__(self, state): | ||
62 | args = "Unknown state '%s' found." % state | ||
63 | MyError.__init__(self, args) | ||
64 | |||
65 | class BadTime(MyError): | ||
66 | def __init__(self, time): | ||
67 | args = "The time '%d' is bad. It should >= previous line's time." % time | ||
68 | MyError.__init__(self, args) | ||
69 | |||
70 | class TaskHasExited(MyError): | ||
71 | def __init__(self, state): | ||
72 | args = "The process has exited. Why it enter '%s' state again?" % state | ||
73 | MyError.__init__(self, args) | ||
74 | |||
75 | class BadFormat(MyError): | ||
76 | def __init__(self): | ||
77 | args = "Bad log format" | ||
78 | MyError.__init__(self, args) | ||
79 | |||
80 | class RepeatState(MyError): | ||
81 | def __init__(self, pid): | ||
82 | args = "Previous state of process %d is identical with this line." % (pid) | ||
83 | MyError.__init__(self, args) | ||
84 | |||
85 | class SameLine(MyError): | ||
86 | def __init__(self): | ||
87 | args = "It is a clone of previous line." | ||
88 | MyError.__init__(self, args) | ||
89 | |||
90 | class NoNew(MyError): | ||
91 | def __init__(self, pid, state): | ||
92 | args = "The first state of process %d is '%s'. Why not 'N'?" % (pid, state) | ||
93 | MyError.__init__(self, args) | ||
94 | |||
95 | class statistics: | ||
96 | def __init__(self, pool, include, exclude): | ||
97 | if include: | ||
98 | self.pool = process_pool() | ||
99 | for process in pool: | ||
100 | if process.getpid() in include: | ||
101 | self.pool.add(process) | ||
102 | else: | ||
103 | self.pool = copy.copy(pool) | ||
104 | |||
105 | if exclude: | ||
106 | for pid in exclude: | ||
107 | if self.pool.get_process(pid): | ||
108 | self.pool.remove(pid) | ||
109 | |||
110 | def list_pid(self): | ||
111 | l = [] | ||
112 | for process in self.pool: | ||
113 | l.append(process.getpid()) | ||
114 | return l | ||
115 | |||
116 | def average_turnaround(self): | ||
117 | if len(self.pool) == 0: | ||
118 | return 0 | ||
119 | sum = 0 | ||
120 | for process in self.pool: | ||
121 | sum += process.turnaround_time() | ||
122 | return float(sum) / len(self.pool) | ||
123 | |||
124 | def average_waiting(self): | ||
125 | if len(self.pool) == 0: | ||
126 | return 0 | ||
127 | sum = 0 | ||
128 | for process in self.pool: | ||
129 | sum += process.waiting_time() | ||
130 | return float(sum) / len(self.pool) | ||
131 | |||
132 | def begin_time(self): | ||
133 | begin = 0xEFFFFF | ||
134 | for p in self.pool: | ||
135 | if p.begin_time() < begin: | ||
136 | begin = p.begin_time() | ||
137 | return begin | ||
138 | |||
139 | def end_time(self): | ||
140 | end = 0 | ||
141 | for p in self.pool: | ||
142 | if p.end_time() > end: | ||
143 | end = p.end_time() | ||
144 | return end | ||
145 | |||
146 | def throughput(self): | ||
147 | return len(self.pool) * HZ / float(self.end_time() - self.begin_time()) | ||
148 | |||
149 | def print_graphic(self): | ||
150 | begin = self.begin_time() | ||
151 | end = self.end_time() | ||
152 | |||
153 | print graph_title | ||
154 | |||
155 | for i in range(begin, end+1): | ||
156 | line = "%5d " % i | ||
157 | for p in self.pool: | ||
158 | state = p.get_state(i) | ||
159 | if state & P_NEW: | ||
160 | line += "-" | ||
161 | elif state == P_READY or state == P_READY | P_WAITING: | ||
162 | line += "|" | ||
163 | elif state == P_RUNNING: | ||
164 | line += "#" | ||
165 | elif state == P_WAITING: | ||
166 | line += ":" | ||
167 | elif state & P_EXIT: | ||
168 | line += "-" | ||
169 | elif state == P_NULL: | ||
170 | line += " " | ||
171 | elif state & P_RUNNING: | ||
172 | line += "+" | ||
173 | else: | ||
174 | assert False | ||
175 | if p.get_state(i-1) != state and state != P_NULL: | ||
176 | line += "%-3d" % p.getpid() | ||
177 | else: | ||
178 | line += " " | ||
179 | print line | ||
180 | |||
181 | class process_pool: | ||
182 | def __init__(self): | ||
183 | self.list = [] | ||
184 | |||
185 | def get_process(self, pid): | ||
186 | for process in self.list: | ||
187 | if process.getpid() == pid: | ||
188 | return process | ||
189 | return None | ||
190 | |||
191 | def remove(self, pid): | ||
192 | for process in self.list: | ||
193 | if process.getpid() == pid: | ||
194 | self.list.remove(process) | ||
195 | |||
196 | def new(self, pid, time): | ||
197 | p = self.get_process(pid) | ||
198 | if p: | ||
199 | if pid != 0: | ||
200 | raise DuplicateNew(pid) | ||
201 | else: | ||
202 | p.states=[(P_NEW, time)] | ||
203 | else: | ||
204 | p = process(pid, time) | ||
205 | self.list.append(p) | ||
206 | return p | ||
207 | |||
208 | def add(self, p): | ||
209 | self.list.append(p) | ||
210 | |||
211 | def __len__(self): | ||
212 | return len(self.list) | ||
213 | |||
214 | def __iter__(self): | ||
215 | return iter(self.list) | ||
216 | |||
217 | class process: | ||
218 | def __init__(self, pid, time): | ||
219 | self.pid = pid | ||
220 | self.states = [(P_NEW, time)] | ||
221 | |||
222 | def getpid(self): | ||
223 | return self.pid | ||
224 | |||
225 | def change_state(self, state, time): | ||
226 | last_state, last_time = self.states[-1] | ||
227 | if state == P_NEW: | ||
228 | raise DuplicateNew(pid) | ||
229 | if time < last_time: | ||
230 | raise BadTime(time) | ||
231 | if last_state == P_EXIT: | ||
232 | raise TaskHasExited(state) | ||
233 | if last_state == state and self.pid != 0: # task 0 can have duplicate state | ||
234 | raise RepeatState(self.pid) | ||
235 | |||
236 | self.states.append((state, time)) | ||
237 | |||
238 | def get_state(self, time): | ||
239 | rval = P_NULL | ||
240 | combo = P_NULL | ||
241 | if self.begin_time() <= time <= self.end_time(): | ||
242 | for state, s_time in self.states: | ||
243 | if s_time < time: | ||
244 | rval = state | ||
245 | elif s_time == time: | ||
246 | combo |= state | ||
247 | else: | ||
248 | break | ||
249 | if combo: | ||
250 | rval = combo | ||
251 | return rval | ||
252 | |||
253 | def turnaround_time(self): | ||
254 | return self.states[-1][S_TIME] - self.states[0][S_TIME] | ||
255 | |||
256 | def waiting_time(self): | ||
257 | return self.state_last_time(P_READY) | ||
258 | |||
259 | def cpu_time(self): | ||
260 | return self.state_last_time(P_RUNNING) | ||
261 | |||
262 | def io_time(self): | ||
263 | return self.state_last_time(P_WAITING) | ||
264 | |||
265 | def state_last_time(self, state): | ||
266 | time = 0 | ||
267 | state_begin = 0 | ||
268 | for s,t in self.states: | ||
269 | if s == state: | ||
270 | state_begin = t | ||
271 | elif state_begin != 0: | ||
272 | assert state_begin <= t | ||
273 | time += t - state_begin | ||
274 | state_begin = 0 | ||
275 | return time | ||
276 | |||
277 | |||
278 | def begin_time(self): | ||
279 | return self.states[0][S_TIME] | ||
280 | |||
281 | def end_time(self): | ||
282 | return self.states[-1][S_TIME] | ||
283 | |||
284 | # Enter point | ||
285 | if len(sys.argv) < 2: | ||
286 | print usage.replace("%s", sys.argv[0]) | ||
287 | sys.exit(0) | ||
288 | |||
289 | # parse arguments | ||
290 | include = [] | ||
291 | exclude = [] | ||
292 | unit_ms = False | ||
293 | graphic = False | ||
294 | ex_mark = False | ||
295 | |||
296 | try: | ||
297 | for arg in sys.argv[2:]: | ||
298 | if arg == '-m': | ||
299 | unit_ms = True | ||
300 | continue | ||
301 | if arg == '-g': | ||
302 | graphic = True | ||
303 | continue | ||
304 | if not ex_mark: | ||
305 | if arg == '-x': | ||
306 | ex_mark = True | ||
307 | else: | ||
308 | include.append(int(arg)) | ||
309 | else: | ||
310 | exclude.append(int(arg)) | ||
311 | except ValueError: | ||
312 | print "Bad argument '%s'" % arg | ||
313 | sys.exit(-1) | ||
314 | |||
315 | # parse log file and construct processes | ||
316 | processes = process_pool() | ||
317 | |||
318 | f = open(sys.argv[1], "r") | ||
319 | |||
320 | # Patch process 0's New & Run state | ||
321 | processes.new(0, 40).change_state(P_RUNNING, 40) | ||
322 | |||
323 | try: | ||
324 | prev_time = 0 | ||
325 | prev_line = "" | ||
326 | for lineno, line in enumerate(f): | ||
327 | |||
328 | if line == prev_line: | ||
329 | raise SameLine | ||
330 | prev_line = line | ||
331 | |||
332 | fields = line.split("\t") | ||
333 | if len(fields) != 3: | ||
334 | raise BadFormat | ||
335 | |||
336 | pid = int(fields[0]) | ||
337 | s = fields[1].upper() | ||
338 | |||
339 | time = int(fields[2]) | ||
340 | if time < prev_time: | ||
341 | raise BadTime(time) | ||
342 | prev_time = time | ||
343 | |||
344 | p = processes.get_process(pid) | ||
345 | |||
346 | state = P_NULL | ||
347 | if s == 'N': | ||
348 | processes.new(pid, time) | ||
349 | elif s == 'J': | ||
350 | state = P_READY | ||
351 | elif s == 'R': | ||
352 | state = P_RUNNING | ||
353 | elif s == 'W': | ||
354 | state = P_WAITING | ||
355 | elif s == 'E': | ||
356 | state = P_EXIT | ||
357 | else: | ||
358 | raise UnknownState(s) | ||
359 | if state != P_NULL: | ||
360 | if not p: | ||
361 | raise NoNew(pid, s) | ||
362 | p.change_state(state, time) | ||
363 | except MyError, err: | ||
364 | print "Error at line %d: %s" % (lineno+1, err) | ||
365 | sys.exit(0) | ||
366 | |||
367 | # Stats | ||
368 | stats = statistics(processes, include, exclude) | ||
369 | att = stats.average_turnaround() | ||
370 | awt = stats.average_waiting() | ||
371 | if unit_ms: | ||
372 | unit = "ms" | ||
373 | att *= 1000/HZ | ||
374 | awt *= 1000/HZ | ||
375 | else: | ||
376 | unit = "tick" | ||
377 | print "(Unit: %s)" % unit | ||
378 | print "Process Turnaround Waiting CPU Burst I/O Burst" | ||
379 | for pid in stats.list_pid(): | ||
380 | p = processes.get_process(pid) | ||
381 | tt = p.turnaround_time() | ||
382 | wt = p.waiting_time() | ||
383 | cpu = p.cpu_time() | ||
384 | io = p.io_time() | ||
385 | |||
386 | if unit_ms: | ||
387 | print "%7d %10d %7d %9d %9d" % (pid, tt*1000/HZ, wt*1000/HZ, cpu*1000/HZ, io*1000/HZ) | ||
388 | else: | ||
389 | print "%7d %10d %7d %9d %9d" % (pid, tt, wt, cpu, io) | ||
390 | print "Average: %10.2f %7.2f" % (att, awt) | ||
391 | print "Throughout: %.2f/s" % (stats.throughput()) | ||
392 | |||
393 | if graphic: | ||
394 | stats.print_graphic() | ||