summaryrefslogtreecommitdiff
path: root/tools/find_stack_traces.py
blob: 31294849fa79a82a733a90bc607a28f3ff3815bf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4

# 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 re
import StringIO
import sys
import urllib2

import pprint
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<timestamp>%s) (?P<pid>\d+ )?(?P<level>(ERROR|TRACE)) " \
    "(?P<module>[\w\.]+) (?P<msg>.*)" % (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."""
    page = urllib2.urlopen(url)
    buf = StringIO.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 = urllib2.urlopen(url)
    content = page.read()
    logs = re.findall('(screen-[\w-]+\.txt\.gz)</a>', content)
    return logs


def usage():
    print """
Usage: find_stack_traces.py <logurl>

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()