1 """
2 Implement a grep-like mechanism to extract information out of the log files.
3 """
4
5
6
7
8
9
10 import sys
11 import re
12 import logging
13
14 import qb
15 import qb.backend.utils as backendUtils
16
17 RGX_QUBE_RETRY_MSG = re.compile('qube! - retry/requeue|work\[.*auto-retry\[')
18
19
21 """
22 The base class for log parsers in Qube, it provides all the functionality needed with the
23 exception of calculating in-frame progress.
24
25 Since calculating in-frame progress requires interpreting the extracted string from the logs,
26 derived methods should override this class' calcProgress() method. See the docstring for this
27 class' calcProgress()
28 """
30 """
31 @type job: qb.Job
32 """
33 self.logging = logging.getLogger('%s' % self.__class__.__name__)
34
35 self.regex = {}
36
37 self.dev = backendUtils.getDevBoolean(job)
38 if self.dev:
39 self.logging.warning('Running in "dev" mode, all log matches are skipped.')
40
41
42
43 rgxMatchesEntireString = ['regex_highlights', 'regex_errors']
44
45
46 for jobRegex in [x for x in job.get('package', {}) if x.startswith('regex') and isinstance(job['package'][x], str)]:
47
48
49 patterns = []
50 for pattern in [x.strip() for x in job['package'][jobRegex].splitlines()]:
51
52 if len(pattern):
53 patterns.append(pattern)
54
55 rgxPattern = '|'.join(patterns)
56
57
58 if jobRegex in rgxMatchesEntireString:
59 rgxPattern = '(%s)' % rgxPattern
60
61 self.regex[jobRegex] = re.compile(rgxPattern, re.MULTILINE | re.IGNORECASE)
62
63 - def parse(self, data, qbTokens=None):
64 """
65 Scan the log stream for matches to the job's various regexes.
66
67 Return a dict of lists, key is the regex name as specificied in the job package with the
68 leading 'regex_' stripped, the list is all matches for the regex. Only return non-empty
69 lists.
70
71 @type data: str
72
73 @param qbTokens: a dictionary containing all the QB_FRAME* values to aid in calculation the
74 in-chunk progress.
75
76 @type qbTokens: C{dict}
77
78 @rtype: dict of lists
79 """
80 matches = {}
81
82
83
84 if RGX_QUBE_RETRY_MSG.search(data):
85 self.logging.debug('Log parsing skipping sections that precede a "retry"')
86 self.logging.debug('BEFORE: data length: %s' % len(data))
87
88 data = RGX_QUBE_RETRY_MSG.split(data)[-1]
89
90 self.logging.debug('AFTER: data length: %s' % len(data))
91
92 for jobRegex in self.regex.keys():
93 rgxName = jobRegex.replace('regex_', '')
94 matches[rgxName] = []
95
96
97 for (rgxName, rgx) in self.regex.items():
98
99
100 rgxName = rgxName.replace('regex_', '')
101
102 self.logging.debug('rgx name:%s pattern: %s' % (rgxName, rgx.pattern))
103
104 for m in rgx.finditer(data):
105 if m.groups():
106 for grp in m.groups():
107 if grp is not None and len(grp) > 0 and not self.dev:
108 matches[rgxName].append(grp)
109
110 self.logging.debug('Regex matches: %s' % matches)
111
112
113 for rgxName in matches.keys():
114 if len(matches[rgxName]) == 0:
115 del matches[rgxName]
116 elif rgxName == 'outputPaths':
117
118 matches[rgxName] = ','.join(matches[rgxName])
119
120
121
122 if 'progress' in matches:
123 if qbTokens:
124 matches['progress'] = self.calcProgress(matches['progress'][-1], qbTokens)
125 else:
126 matches['progress'] = self.calcProgress(matches['progress'][-1])
127
128 return matches
129
131 """
132 Calculate the internal progress of a piece of work (or a cmdline job) and return it as
133 a float between 0 and 1.
134
135 The extraction of the progress value via the job's "progress" regular expression is only the
136 first aspect of determining how close a particular chunk is to completing; the other part is
137 figuring out what to do with the frame or the "% complete" string that's been pulled out of
138 the log stream and converting it into the expected numerical range so that the QubeGUI knows
139 what to do with it.
140
141 2-digit precision is preferred, more precision will be truncated in the QubeGUI.
142
143 It is expected that this method be overridden for any log parser that is expected to
144 calculate a "done" value from the log contents.
145
146 @param progressMatchStr: the value used to derive the progress within the chunk. It is
147 simply the progress percentage integer matched in the application output.
148
149 @type progressMatchStr: C{str}
150
151 @return: The amount of work complete for the item, expressed as a float between 0 and 1.0, 1.0 being completely done.
152 @rtype: C{float}
153 """
154 pass
155
156
158 """
159 This LogParser class on of calcProgress() is for the most trivial case:
160
161 A render returns a "% complete" string between 1 and 100. The portion of the string
162 containing the integer is passed to calcProgress as the progressMatchStr.
163
164 This class converts that percentage value to a float between 0 and 1 and returns that float.
165 """
167 """
168 Calculate the internal progress of a piece of work (or a cmdline job) and return it as
169 a float between 0 and 1.
170
171 eg: A progressMatchStr of "23" is converted to 0.23.
172
173 @param progressMatchStr: the value used to derive the progress within the chunk. It is
174 simply the progress percentage integer matched in the application output.
175
176 @type progressMatchStr: C{str}
177
178 @return: The amount of work complete for the item, expressed as a float between 0 and 1.0, 1.0 being completely done.
179 @rtype: C{float}
180 """
181 progress = 0.0
182 try:
183 progress = '%0.2f' % (float(progressMatchStr) / 100,)
184 self.logging.debug('progress = %s' % progress)
185 except:
186 backendUtils.flushPrint('WARNING: the logParser %s did not extract a progress % value: "%s"' % (self.__class__.__name__, progressMatchStr), fhList=sys.stderr)
187 backendUtils.flushPrint(backendUtils.formatExc(), fhList=[sys.stderr])
188
189 return progress
190
191
193 """
194 This LogParser class is expected to be used for jobs whose agenda contains frame chunks or
195 partitions. It returns the value of currentFrame/chunkLength as a float between 0 and 1.
196 """
198 """
199 Calculate the internal progress of a frame chunk or partition.
200
201 This particular example expects to have only a frame number in the progressMatchStr, and
202 compare it to the frame range for the individual work item that was passed to the parse()
203 method.
204
205 Determine the extent to which the chunk has progressed by determining the index of the current
206 frame (QB_FRAME_NUMBER) in a list comprised of all frames for the chunk, then comparing that
207 index against the size of the chunk.
208
209 eg: progressMatchStr = "23", and the frame range for this piece of work (as determined from
210 the qbTokens dict) is 21-30. So the index for this frame would be 3, and 3/10 = 0.3
211
212 @param progressMatchStr: the value used to derive the progress within the chunk. It is
213 simply the progress percentage integer matched in the application output.
214
215 @type progressMatchStr: C{str}
216
217 @param qbTokens: a dictionary containing all the QB_FRAME* values to aid in calculation the
218 in-chunk progress.
219
220 @type qbTokens: C{dict}
221
222 @return: The amount of work complete for the item, expressed as a float between 0 and 1.0,
223 1.0 being completely done.
224
225 @rtype: C{float}
226 """
227 progress = 0.0
228
229 try:
230 chunkFrames = [ x['name'] for x in qb.genframes( qbTokens['QB_FRAME_RANGE']) ]
231 idx = chunkFrames.index(progressMatchStr) + 1
232 progress = float(idx) / len(chunkFrames)
233 progress = '%0.2f' % progress
234 except ValueError:
235 backendUtils.flushPrint('WARNING: the logParser %s did not extract a valid frame number: "%s"' % (self.__class__.__name__, progressMatchStr), fhList=sys.stderr)
236 backendUtils.flushPrint('WARNING: Traceback from error:', fhList=[sys.stderr])
237 backendUtils.flushPrint(backendUtils.formatExc(), fhList=[sys.stderr])
238
239 return progress
240