#!/usr/bin/env python # Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import gzip import pprint import re import sys import six import six.moves.urllib.request as urlreq pp = pprint.PrettyPrinter() NOVA_TIMESTAMP = r"\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d" NOVA_REGEX = r"(?P%s) (?P\d+ )?(?P(ERROR|TRACE)) " \ "(?P[\w\.]+) (?P.*)" % (NOVA_TIMESTAMP) class StackTrace(object): timestamp = None pid = None level = "" module = "" msg = "" def __init__(self, timestamp=None, pid=None, level="", module="", msg=""): self.timestamp = timestamp self.pid = pid self.level = level self.module = module self.msg = msg def append(self, msg): self.msg = self.msg + msg def is_same(self, data): return (data['timestamp'] == self.timestamp and data['level'] == self.level) def not_none(self): return self.timestamp is not None def __str__(self): buff = "<%s %s %s>\n" % (self.timestamp, self.level, self.module) for line in self.msg.splitlines(): buff = buff + line + "\n" return buff def hunt_for_stacktrace(url): """Return TRACE or ERROR lines out of logs.""" req = urlreq.Request(url) req.add_header('Accept-Encoding', 'gzip') page = urlreq.urlopen(req) buf = six.StringIO(page.read()) f = gzip.GzipFile(fileobj=buf) content = f.read() traces = [] trace = StackTrace() for line in content.splitlines(): m = re.match(NOVA_REGEX, line) if m: data = m.groupdict() if trace.not_none() and trace.is_same(data): trace.append(data['msg'] + "\n") else: trace = StackTrace( timestamp=data.get('timestamp'), pid=data.get('pid'), level=data.get('level'), module=data.get('module'), msg=data.get('msg')) else: if trace.not_none(): traces.append(trace) trace = StackTrace() # once more at the end to pick up any stragglers if trace.not_none(): traces.append(trace) return traces def log_url(url, log): return "%s/%s" % (url, log) def collect_logs(url): page = urlreq.urlopen(url) content = page.read() logs = re.findall('(screen-[\w-]+\.txt\.gz)', content) return logs def usage(): print(""" Usage: find_stack_traces.py Hunts for stack traces in a devstack run. Must provide it a base log url from a tempest devstack run. Should start with http and end with /logs/. Returns a report listing stack traces out of the various files where they are found. """) sys.exit(0) def print_stats(items, fname, verbose=False): errors = len(filter(lambda x: x.level == "ERROR", items)) traces = len(filter(lambda x: x.level == "TRACE", items)) print("%d ERRORS found in %s" % (errors, fname)) print("%d TRACES found in %s" % (traces, fname)) if verbose: for item in items: print(item) print("\n\n") def main(): if len(sys.argv) == 2: url = sys.argv[1] loglist = collect_logs(url) # probably wrong base url if not loglist: usage() for log in loglist: logurl = log_url(url, log) traces = hunt_for_stacktrace(logurl) if traces: print_stats(traces, log, verbose=True) else: usage() if __name__ == '__main__': main()