|
1 #!/usr/bin/env python |
|
2 # |
|
3 # Copyright (c) 2007-2010 Nokia Corporation and/or its subsidiary(-ies). |
|
4 # All rights reserved. |
|
5 # This component and the accompanying materials are made available |
|
6 # under the terms of the License "Eclipse Public License v1.0" |
|
7 # which accompanies this distribution, and is available |
|
8 # at the URL "http://www.eclipse.org/legal/epl-v10.html". |
|
9 # |
|
10 # Initial Contributors: |
|
11 # Nokia Corporation - initial contribution. |
|
12 # |
|
13 # Contributors: |
|
14 # |
|
15 # Description: |
|
16 # |
|
17 # display summary information about recipes from raptor logs |
|
18 # e.g. total times and so on. |
|
19 |
|
20 import time |
|
21 import __future__ |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 class RecipeStats(object): |
|
27 def __init__(self, name, count, time): |
|
28 self.name=name |
|
29 self.count=count |
|
30 self.time=time |
|
31 |
|
32 def add(self, duration): |
|
33 self.time += duration |
|
34 |
|
35 class BuildStats(object): |
|
36 STAT_OK = 0 |
|
37 |
|
38 |
|
39 def __init__(self): |
|
40 self.stats = {} |
|
41 self.failcount = 0 |
|
42 self.failtime = 0.0 |
|
43 self.failtypes = {} |
|
44 self.retryfails = 0 |
|
45 self.hosts = {} |
|
46 |
|
47 def add(self, starttime, duration, name, status, host, phase): |
|
48 if status != BuildStats.STAT_OK: |
|
49 self.failcount += 1 |
|
50 if name in self.failtypes: |
|
51 self.failtypes[name] += 1 |
|
52 else: |
|
53 self.failtypes[name] = 1 |
|
54 |
|
55 if status == 128: |
|
56 self.retryfails += 1 |
|
57 return |
|
58 |
|
59 if name in self.stats: |
|
60 r = self.stats[name] |
|
61 r.add(duration) |
|
62 else: |
|
63 self.stats[name] = RecipeStats(name,1,duration) |
|
64 |
|
65 hp=host |
|
66 if hp in self.hosts: |
|
67 self.hosts[hp] += 1 |
|
68 else: |
|
69 self.hosts[hp] = 1 |
|
70 |
|
71 def recipe_csv(self): |
|
72 s = '"name", "time", "count"\n' |
|
73 l = sorted(self.stats.values(), key= lambda r: r.time, reverse=True) |
|
74 for r in l: |
|
75 s += '"%s",%s,%d\n' % (r.name, str(r.time), r.count) |
|
76 return s |
|
77 |
|
78 def hosts_csv(self): |
|
79 s='"host","recipecount"\n' |
|
80 hs = self.hosts |
|
81 for h in sorted(hs.keys()): |
|
82 s += '"%s",%d\n' % (h,hs[h]) |
|
83 return s |
|
84 |
|
85 |
|
86 import sys |
|
87 import re |
|
88 import os |
|
89 from optparse import OptionParser # for parsing command line parameters |
|
90 |
|
91 def main(): |
|
92 recipe_re = re.compile(".*<recipe name='([^']+)'.*host='([^']+)'.*") |
|
93 time_re = re.compile(".*<time start='([0-9]+\.[0-9]+)' *elapsed='([0-9]+\.[0-9]+)'.*") |
|
94 status_re = re.compile(".*<status exit='(?P<exit>(ok|failed))'( *code='(?P<code>[0-9]+)')?.*") |
|
95 phase_re = re.compile(".*<info>Making.*?([^\.]+\.[^\.]+)</info>") |
|
96 |
|
97 parser = OptionParser(prog = "recipestats", |
|
98 usage = """%prog --help [-b] [-f <logfilename>]""") |
|
99 |
|
100 parser.add_option("-b","--buildhosts",action="store_true",dest="buildhosts_flag", |
|
101 help="Lists which build hosts were active in each invocation of the build engine and how many recipes ran on each.", default = False) |
|
102 parser.add_option("-f","--logfile",action="store",dest="logfilename", help="Read from the file, not stdin", default = None) |
|
103 |
|
104 |
|
105 (options, stuff) = parser.parse_args(sys.argv[1:]) |
|
106 |
|
107 if options.logfilename is None: |
|
108 f = sys.stdin |
|
109 else: |
|
110 f = open(options.logfilename,"r") |
|
111 |
|
112 st = BuildStats() |
|
113 |
|
114 |
|
115 alternating = 0 |
|
116 start_time = 0.0 |
|
117 |
|
118 phase=None |
|
119 for l in f: |
|
120 l2 = l.rstrip("\n\r") |
|
121 |
|
122 rm = recipe_re.match(l2) |
|
123 |
|
124 if rm is not None: |
|
125 (rname,host) = rm.groups() |
|
126 continue |
|
127 |
|
128 pm = phase_re.match(l2) |
|
129 |
|
130 if pm is not None: |
|
131 if phase is not None: |
|
132 if options.buildhosts_flag: |
|
133 print('"%s"\n' % phase) |
|
134 print(st.hosts_csv()) |
|
135 st.hosts = {} |
|
136 phase = pm.groups()[0] |
|
137 continue |
|
138 |
|
139 tm = time_re.match(l2) |
|
140 if tm is not None: |
|
141 try: |
|
142 s = float(tm.groups()[0]) |
|
143 elapsed = float(tm.groups()[1]) |
|
144 |
|
145 if start_time == 0.0: |
|
146 start_time = s |
|
147 |
|
148 s -= start_time |
|
149 |
|
150 continue |
|
151 except ValueError, e: |
|
152 raise Exception("Parse problem: float conversion on these groups: %s\n%s" %(str(tm.groups()), str(e))) |
|
153 else: |
|
154 if l2.find("<time") is not -1: |
|
155 raise Exception("unparsed timing status: %s\n"%l2) |
|
156 |
|
157 sm = status_re.match(l2) |
|
158 |
|
159 if sm is None: |
|
160 continue |
|
161 |
|
162 if sm.groupdict()['exit'] == 'ok': |
|
163 status = 0 |
|
164 else: |
|
165 status = int(sm.groupdict()['code']) |
|
166 |
|
167 st.add(s, elapsed, rname, status, host, phase) |
|
168 |
|
169 if options.buildhosts_flag: |
|
170 print('"%s"\n' % phase) |
|
171 print(st.hosts_csv()) |
|
172 else: |
|
173 print(st.recipe_csv()) |
|
174 |
|
175 |
|
176 if __name__ == '__main__': main() |