summaryrefslogtreecommitdiffstats
path: root/compass-tasks/log_analyzor/line_matcher.py
blob: ada9ed61d997a53230bce82d4790d5299630172e (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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# Copyright 2014 Huawei Technologies Co. Ltd
#
# 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.

"""Module to get the progress when found match with a line of the log."""
import logging
import re

from abc import ABCMeta

from compass.utils import util


class ProgressCalculator(object):
    """base class to generate progress."""

    __metaclass__ = ABCMeta

    @classmethod
    def update_progress(
        cls, progress_data, message,
        severity, log_history
    ):
        """Update progress with the given progress_data, message and severity.

        :param progress_data: installing progress.
        :type progress_data: float between 0 to 1.
        :param message: installing progress message.
        :param severity: installing message severity.
        :param progress: :class:`Progress` instance to update
        """
        # the progress is only updated when the new progress
        # is greater than the stored progress or the progress
        # to update is the same but the message is different.
        if (
            progress_data > log_history['percentage'] or (
                progress_data == log_history['percentage'] and
                message != log_history['message']
            )
        ):
            log_history['percentage'] = progress_data
            if message:
                log_history['message'] = message
            if severity:
                log_history['severity'] = severity
            logging.debug('update progress to %s', log_history)
        else:
            logging.debug('ignore update progress %s to %s',
                          progress_data, log_history)

    def update(self, message, severity, log_history):
        """vritual method to update progress by message and severity.

        :param message: installing message.
        :param severity: installing severity.
        """
        raise NotImplementedError(str(self))

    def __repr__(self):
        return self.__class__.__name__


class IncrementalProgress(ProgressCalculator):
    """Class to increment the progress."""

    def __init__(self, min_progress,
                 max_progress, incremental_ratio):
        super(IncrementalProgress, self).__init__()
        if not 0.0 <= min_progress <= max_progress <= 1.0:
            raise IndexError(
                '%s restriction is not mat: 0.0 <= min_progress(%s)'
                ' <= max_progress(%s) <= 1.0' % (
                    self.__class__.__name__, min_progress, max_progress))

        if not 0.0 <= incremental_ratio <= 1.0:
            raise IndexError(
                '%s restriction is not mat: '
                '0.0 <= incremental_ratio(%s) <=  1.0' % (
                    self.__class__.__name__, incremental_ratio))

        self.min_progress_ = min_progress
        self.max_progress_ = max_progress
        self.incremental_progress_ = (
            incremental_ratio * (max_progress - min_progress))

    def __str__(self):
        return '%s[%s:%s:%s]' % (
            self.__class__.__name__,
            self.min_progress_,
            self.max_progress_,
            self.incremental_progress_
        )

    def update(self, message, severity, log_history):
        """update progress from message and severity."""
        progress_data = max(
            self.min_progress_,
            min(
                self.max_progress_,
                log_history['percentage'] + self.incremental_progress_
            )
        )
        self.update_progress(progress_data,
                             message, severity, log_history)


class RelativeProgress(ProgressCalculator):
    """class to update progress to the given relative progress."""

    def __init__(self, progress):
        super(RelativeProgress, self).__init__()
        if not 0.0 <= progress <= 1.0:
            raise IndexError(
                '%s restriction is not mat: 0.0 <= progress(%s) <= 1.0' % (
                    self.__class__.__name__, progress))

        self.progress_ = progress

    def __str__(self):
        return '%s[%s]' % (self.__class__.__name__, self.progress_)

    def update(self, message, severity, log_history):
        """update progress from message and severity."""
        self.update_progress(
            self.progress_, message, severity, log_history)


class SameProgress(ProgressCalculator):
    """class to update message and severity for  progress."""

    def update(self, message, severity, log_history):
        """update progress from the message and severity."""
        self.update_progress(log_history['percentage'], message,
                             severity, log_history)


class LineMatcher(object):
    """Progress matcher for each line."""

    def __init__(self, pattern, progress=None,
                 message_template='', severity=None,
                 unmatch_sameline_next_matcher_name='',
                 unmatch_nextline_next_matcher_name='',
                 match_sameline_next_matcher_name='',
                 match_nextline_next_matcher_name=''):
        self.regex_ = re.compile(pattern)
        if not progress:
            self.progress_ = SameProgress()
        elif isinstance(progress, ProgressCalculator):
            self.progress_ = progress
        elif isinstance(progress, (int, long, float)):
            self.progress_ = RelativeProgress(progress)
        else:
            raise TypeError(
                'progress unsupport type %s: %s' % (
                    type(progress), progress))

        self.message_template_ = message_template
        self.severity_ = severity
        self.unmatch_sameline_ = unmatch_sameline_next_matcher_name
        self.unmatch_nextline_ = unmatch_nextline_next_matcher_name
        self.match_sameline_ = match_sameline_next_matcher_name
        self.match_nextline_ = match_nextline_next_matcher_name

    def __repr__(self):
        return '%r[pattern:%r, message_template:%r, severity:%r]' % (
            self.__class__.__name__, self.regex_.pattern,
            self.message_template_, self.severity_)

    def update_progress(self, line, log_history):
        """Update progress by the line.

        :param line: one line in log file to indicate the installing progress.
           .. note::
              The line may be partial if the latest line of the log file is
              not the whole line. But the whole line may be resent
              in the next run.
        :param progress: the :class:`Progress` instance to update.
        """
        mat = self.regex_.search(line)
        if not mat:
            return (
                self.unmatch_sameline_,
                self.unmatch_nextline_)

        try:
            message = self.message_template_ % mat.groupdict()
        except Exception as error:
            logging.error('failed to get message %s %% %s in line matcher %s',
                          self.message_template_, mat.groupdict(), self)
            raise error

        self.progress_.update(message, self.severity_, log_history)
        return (
            self.match_sameline_,
            self.match_nextline_)