~lifeless/python-oops-tools/prune

« back to all changes in this revision

Viewing changes to src/oopstools/oops/oopsstore.py

  • Committer: Robert Collins
  • Date: 2011-10-13 20:18:51 UTC
  • Revision ID: robertc@robertcollins.net-20111013201851-ym8jmdhoeol3p83s
Export of cruft-deleted tree.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright 2005-2011 Canonical Ltd.  All rights reserved.
 
2
#
 
3
# This program is free software: you can redistribute it and/or modify
 
4
# it under the terms of the GNU Affero General Public License as published by
 
5
# the Free Software Foundation, either version 3 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU Affero General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU Affero General Public License
 
14
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
15
 
 
16
 
 
17
__metaclass__ = type
 
18
 
 
19
import re
 
20
import os
 
21
import datetime
 
22
import itertools
 
23
 
 
24
from oopstools.oops.models import Oops, OopsReadError, oops_re
 
25
 
 
26
 
 
27
# the section of the OOPS ID before the instance identifier is the
 
28
# days since the epoch, which is defined as the start of 2006.
 
29
epoch = datetime.datetime(2006, 01, 01, 00, 00, 00)
 
30
 
 
31
 
 
32
class OopsNotFound(LookupError):
 
33
    pass
 
34
 
 
35
 
 
36
class OopsStore:
 
37
    """A collection of OOPS reports"""
 
38
 
 
39
    def __init__(self, oopsdir):
 
40
        """Create an OOPS store.
 
41
 
 
42
        The oopsdir parameter is the top directory in the directory tree
 
43
        containing oops reports. Each directory in the dir tree should
 
44
        contain subdirectories of the form YYYY-mm-dd, which in turn contain
 
45
        the OOPS reports.
 
46
        """
 
47
        self.oopsdirs = set()
 
48
        date_pat = re.compile('\d{4}-\d{2}-\d{2}')
 
49
 
 
50
        # This is an optimization to find the directories.
 
51
        # os.walk() was previously used here but it was taking too much time.
 
52
        def find_dirs(dirs):
 
53
            for dir in dirs:
 
54
                if not os.path.isdir(dir):
 
55
                    # XXX: untested
 
56
                    continue
 
57
                try:
 
58
                    # Filter out filenames. We only want directories.
 
59
                    subdirs = [d for d in os.listdir(dir) if os.path.isdir(
 
60
                        os.path.join(dir, d))]
 
61
                except OSError:
 
62
                    # Can't list dir, permission denied. Ignore it.
 
63
                    continue
 
64
                if not subdirs:
 
65
                    # dir is empty. Ignore it.
 
66
                    continue
 
67
                os.chdir(dir)
 
68
                # Sort subdirs so date directories, if any, will show up as
 
69
                # first.
 
70
                sorted_subdirs = sorted(subdirs)
 
71
                if date_pat.match(sorted_subdirs[0]):
 
72
                    self.oopsdirs.add(os.getcwd())
 
73
                else:
 
74
                    find_dirs(subdirs)
 
75
                os.chdir("..")
 
76
 
 
77
        # XXX matsubara: remove the if clause when refactoring this code
 
78
        # to use a single oops directory.
 
79
        if type(oopsdir) == str:
 
80
            oopsdir = [oopsdir,]
 
81
        # XXX matsubara: find_dirs changes directories to do its job and
 
82
        # before calling it, record the original directory the script is
 
83
        # in and after it finishes building
 
84
        # self.oopsdirs set, the script returns to the original place.
 
85
        original_working_dir = os.getcwd()
 
86
        find_dirs(oopsdir)
 
87
        os.chdir(original_working_dir)
 
88
 
 
89
    def find_oops(self, oopsid):
 
90
        """Get an OOPS report by oopsid."""
 
91
        date, oops = self.canonical_name(oopsid).split('/', 1)
 
92
        oops_prefix = oops_re.match(oops).group('oopsprefix')
 
93
        # now find the OOPS:
 
94
        for oopsdir in self.oopsdirs:
 
95
            datedir = os.path.join(oopsdir, date)
 
96
            if not os.path.isdir(datedir):
 
97
                continue
 
98
            for filename in sorted(os.listdir(datedir)):
 
99
                original_filename = filename
 
100
                # Normalize filename
 
101
                filename = filename.upper()
 
102
                if (oops_prefix in filename and
 
103
                    (filename.endswith('.' + oops) or
 
104
                    filename.endswith('.' + oops + '.BZ2') or
 
105
                    filename.endswith('.' + oops + '.GZ') or
 
106
                    # U1/ISD oops reports use different filename scheme.
 
107
                    filename.endswith(oops + '.OOPS') or
 
108
                    filename.endswith(oops + '.OOPS.BZ2'))):
 
109
                    return Oops.from_pathname(
 
110
                        os.path.join(datedir, original_filename))
 
111
        else:
 
112
            raise OopsNotFound("Could not find OOPS")
 
113
 
 
114
    def canonical_name(self, name):
 
115
        """Canonicalise an OOPS report name."""
 
116
        match = oops_re.match(name.upper())
 
117
        if not match:
 
118
            raise OopsNotFound("Invalid OOPS name")
 
119
 
 
120
        now = datetime.datetime.utcnow()
 
121
 
 
122
        date = match.group('date')
 
123
        dse = match.group('dse')
 
124
        oops = match.group('oopsprefix') + match.group('id')
 
125
        if date:
 
126
            pass # got enough data
 
127
        elif dse:
 
128
            # dse is the number of days since the epoch
 
129
            day = epoch + datetime.timedelta(days=int(dse) - 1)
 
130
            date = '%04d-%02d-%02d' % (day.year, day.month, day.day)
 
131
        else:
 
132
            # Assume the OOPS is from today
 
133
            date = '%04d-%02d-%02d' % (now.year, now.month, now.day)
 
134
 
 
135
        return '%s/%s' % (date, oops)
 
136
 
 
137
    def _get_oops_prefix(self, filename):
 
138
        """Return oops prefix for a given filename."""
 
139
        if filename.upper().endswith((".OOPS", ".OOPS.BZ2")):
 
140
            # Got a U1/ISD oops report.
 
141
            oops_prefix = filename.split(".", 1)[0]
 
142
            oops_prefix = re.split("\d+", oops_prefix)[2]
 
143
        else:
 
144
            #XXX: matsubara 2009-11-06 bug=353945 There's a disparity
 
145
            # between the OOPS filename and the OOPS id, that's why
 
146
            # there're two different regex to identify those.
 
147
            oops_prefix = filename.split(".")[1]
 
148
            oops_prefix = re.sub("\d+$", "",  oops_prefix)
 
149
        # Normalize oops prefix
 
150
        return oops_prefix.upper()
 
151
 
 
152
    def _search(self, startdate, enddate, prefixes=None):
 
153
        """Yield each OOPS report between the given two dates."""
 
154
        date = startdate
 
155
        while date <= enddate:
 
156
            datestr = date.strftime('%Y-%m-%d')
 
157
            for oopsdir in self.oopsdirs:
 
158
                datedir = os.path.join(oopsdir, datestr)
 
159
                if not os.path.isdir(datedir):
 
160
                    continue
 
161
                for filename in sorted(os.listdir(datedir)):
 
162
                    oops_prefix = self._get_oops_prefix(filename)
 
163
                    if prefixes:
 
164
                        if oops_prefix not in prefixes:
 
165
                            # Didn't match my prefix, get outta here
 
166
                            continue
 
167
                    try:
 
168
                        yield Oops.from_pathname(
 
169
                            os.path.join(datedir, filename))
 
170
                    except OopsReadError:
 
171
                        # ignore non-OOPS reports
 
172
                        pass
 
173
            date += datetime.timedelta(days=1)
 
174
 
 
175
    def search(self, startdatetime, enddatetime, prefixes=None):
 
176
        """Return a generator with OOPSes between the given two datetimes."""
 
177
        oops_results = self._search(startdatetime, enddatetime, prefixes)
 
178
        if not startdatetime.time() and not enddatetime.time():
 
179
            # Faster to just return the generator from _search() when there's
 
180
            # only date and no time.
 
181
            return oops_results
 
182
        else:
 
183
            return itertools.ifilter(
 
184
                lambda oops: startdatetime <= oops.date <= enddatetime,
 
185
                oops_results)