~andrewjbeach/juju-ci-tools/isolate-autoload

1485.2.4 by Christopher Lee
Initial log chunking.
1
from datetime import datetime, timedelta
2
import gzip
3
4
LOG_BREAKDOWN_SECONDS = 20
5
dt_format = '%Y-%m-%d %H:%M:%S'
6
7
1485.2.25 by Christopher Lee
log breakdown details revamp
8
def breakdown_log_by_timeframes(log_file, event_timestamps):
9
    """event_timestamps is a list of TimingData objects."""
10
    # for each event break the time span into chunks of x seconds. the result
11
    # being a list of tuples of start/end timestamps
12
1485.2.4 by Christopher Lee
Initial log chunking.
13
    all_log_breakdown = dict()
1485.2.25 by Christopher Lee
log breakdown details revamp
14
    for event in event_timestamps:
15
        event_range_breakdown = _chunk_event_range(event)
16
        breakdown = get_timerange_logs(log_file, event_range_breakdown)
17
        range_name = _render_ds_string(event.start, event.end)
1485.2.4 by Christopher Lee
Initial log chunking.
18
        all_log_breakdown[range_name] = breakdown
19
20
    return all_log_breakdown
21
22
1485.2.25 by Christopher Lee
log breakdown details revamp
23
def _chunk_event_range(event):
24
    range_breakdown = []
25
    range_start = datetime.strptime(event.start, dt_format)
26
    range_end = datetime.strptime(event.end, dt_format)
27
28
    next_step = range_start + timedelta(seconds=LOG_BREAKDOWN_SECONDS)
29
30
    if next_step > range_end:
31
        range_breakdown.append((range_start, range_end))
32
    else:
33
        while next_step < range_end:
34
            range_breakdown.append((range_start, next_step))
35
36
            range_start = next_step
37
            next_step = range_start + timedelta(
38
                seconds=LOG_BREAKDOWN_SECONDS)
39
            # Otherwise there will be overlap.
40
            range_start += timedelta(seconds=1)
41
42
            if next_step >= range_end:
43
                range_breakdown.append((range_start, range_end))
44
45
    return range_breakdown
46
47
48
def _render_ds_string(start, end):
49
        return '{} - {}'.format(start, end)
50
51
1485.2.4 by Christopher Lee
Initial log chunking.
52
def get_timerange_logs(log_file, timestamps):
53
    log_breakdown = dict()
54
    previous_line = None
1485.2.6 by Christopher Lee
Compressed chunks of logs that contain no details.
55
    no_content = None
1485.2.4 by Christopher Lee
Initial log chunking.
56
    with gzip.open(log_file, 'rt') as f:
1485.2.6 by Christopher Lee
Compressed chunks of logs that contain no details.
57
        log_lines = []
1485.2.4 by Christopher Lee
Initial log chunking.
58
        for log_range in timestamps:
59
            range_end = log_range[1]
1485.2.6 by Christopher Lee
Compressed chunks of logs that contain no details.
60
            if no_content is not None:
61
                # Extend the range until we get something in the logs.
62
                range_start = no_content
63
                no_content = None
64
                range_str = '{} - {} (condensed)'.format(
1485.2.8 by Christopher Lee
Remove daet from log chunks, only time.
65
                    range_start.strftime('%T'), range_end.strftime('%T'))
1485.2.6 by Christopher Lee
Compressed chunks of logs that contain no details.
66
                # Don't reset log_lines as it may contain previous details.
67
            else:
68
                log_lines = []
69
                range_start = log_range[0]
1485.2.8 by Christopher Lee
Remove daet from log chunks, only time.
70
                range_str = '{} - {}'.format(
71
                    range_start.strftime('%T'), range_end.strftime('%T'))
1485.2.4 by Christopher Lee
Initial log chunking.
72
73
            if previous_line:
74
                if log_line_within_start_range(previous_line, range_start):
75
                    log_lines.append(previous_line)
76
                previous_line = None
77
78
            for line in f:
79
                if log_line_within_start_range(line, range_start):
80
                    break
81
            else:
1485.2.6 by Christopher Lee
Compressed chunks of logs that contain no details.
82
                # Likely because the log cuts off before the action is
83
                # considered complete (i.e. teardown).
84
                print('LOG: failed to find start line.')
1485.2.4 by Christopher Lee
Initial log chunking.
85
                break
86
87
            # It it's out of range of the end range then there is nothing for
88
            # this time period.
89
            if not log_line_within_end_range(line, range_end):
90
                previous_line = line
1485.2.6 by Christopher Lee
Compressed chunks of logs that contain no details.
91
                no_content = range_start
1485.2.4 by Christopher Lee
Initial log chunking.
92
                continue
93
94
            log_lines.append(line)
95
96
            for line in f:
97
                if log_line_within_end_range(line, range_end):
98
                    log_lines.append(line)
99
                else:
100
                    previous_line = line
101
                    break
102
            log_breakdown[range_str] = log_lines
103
104
    return log_breakdown
105
106
107
def log_line_within_start_range(line, range_start):
1485.2.26 by Christopher Lee
Touch up and tests for logbreakdown
108
    datestamp = extract_date_from_line(line)
1485.2.4 by Christopher Lee
Initial log chunking.
109
    try:
110
        ds = datetime.strptime(datestamp, dt_format)
111
    except ValueError:
112
        # Don't want an early entry point to the logging.
113
        return False
114
115
    if ds > range_start or ds == range_start:
116
        return True
117
    return False
118
119
1485.2.26 by Christopher Lee
Touch up and tests for logbreakdown
120
def log_line_within_end_range(line, range_end):
121
    datestamp = extract_date_from_line(line)
1485.2.4 by Christopher Lee
Initial log chunking.
122
    try:
123
        ds = datetime.strptime(datestamp, dt_format)
124
    except ValueError:
1485.2.26 by Christopher Lee
Touch up and tests for logbreakdown
125
        # Fine to collect this line as the line doesn't start with a date and
126
        # is thus a continuation or undated message.
1485.2.4 by Christopher Lee
Initial log chunking.
127
        return True
128
1485.2.26 by Christopher Lee
Touch up and tests for logbreakdown
129
    if ds < range_end or ds == range_end:
1485.2.4 by Christopher Lee
Initial log chunking.
130
        return True
131
    return False
1485.2.26 by Christopher Lee
Touch up and tests for logbreakdown
132
133
134
def extract_date_from_line(line):
135
    return " ".join(line.split()[0:2])