~curtin-dev/curtin/trunk

« back to all changes in this revision

Viewing changes to tools/curtin-log-print

  • Committer: Scott Moser
  • Date: 2017-12-20 17:33:03 UTC
  • Revision ID: smoser@ubuntu.com-20171220173303-29gha5qb8wpqrd40
README: Mention move of revision control to git.

curtin development has moved its revision control to git.
It is available at
  https://code.launchpad.net/curtin

Clone with
  git clone https://git.launchpad.net/curtin
or
  git clone git+ssh://git.launchpad.net/curtin

For more information see
  http://curtin.readthedocs.io/en/latest/topics/development.html

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/env python3
2
 
#   Copyright (C) 2016 Canonical Ltd.
3
 
#
4
 
#   Author: Ryan Harper <ryan.harper@canonical.com>
5
 
#
6
 
#   Curtin is free software: you can redistribute it and/or modify it under
7
 
#   the terms of the GNU Affero General Public License as published by the
8
 
#   Free Software Foundation, either version 3 of the License, or (at your
9
 
#   option) any later version.
10
 
#
11
 
#   Curtin is distributed in the hope that it will be useful, but WITHOUT ANY
12
 
#   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13
 
#   FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for
14
 
#   more details.
15
 
#
16
 
#   You should have received a copy of the GNU Affero General Public License
17
 
#   along with Curtin.  If not, see <http://www.gnu.org/licenses/>.
18
 
from collections import OrderedDict
19
 
import argparse
20
 
import base64
21
 
import datetime
22
 
import json
23
 
import os
24
 
import sys
25
 
 
26
 
#  An event:
27
 
'''
28
 
{
29
 
        "description": "executing late commands",
30
 
        "event_type": "start",
31
 
        "level": "INFO",
32
 
        "name": "cmd-install/stage-late"
33
 
        "origin": "curtin",
34
 
        "timestamp": 1461164249.1590767,
35
 
},
36
 
 
37
 
    {
38
 
        "description": "executing late commands",
39
 
        "event_type": "finish",
40
 
        "level": "INFO",
41
 
        "name": "cmd-install/stage-late",
42
 
        "origin": "curtin",
43
 
        "result": "SUCCESS",
44
 
        "timestamp": 1461164249.1590767
45
 
    }
46
 
 
47
 
'''
48
 
format_key = {
49
 
    '%d': 'delta',
50
 
    '%D': 'description',
51
 
    '%E': 'elapsed',
52
 
    '%e': 'event_type',
53
 
    '%I': 'indent',
54
 
    '%l': 'level',
55
 
    '%n': 'name',
56
 
    '%o': 'origin',
57
 
    '%r': 'result',
58
 
    '%t': 'timestamp',
59
 
    '%T': 'total_time',
60
 
}
61
 
 
62
 
formatting_help = " ".join(["{}: {}".format(k.replace('%', '%%'), v)
63
 
                           for k, v in format_key.items()])
64
 
 
65
 
 
66
 
def format_record(msg, event):
67
 
    for i, j in format_key.items():
68
 
        if i in msg:
69
 
            msg = msg.replace(i, "{%s}" % j)
70
 
    return msg.format(**event)
71
 
 
72
 
 
73
 
def dump_event_files(event, dump_folder):
74
 
    content = {k: v for k, v in event.items() if k not in ['content']}
75
 
    files = content['files']
76
 
    saved = []
77
 
    for f in files:
78
 
        fname = f['path']
79
 
        fn_local = os.path.join(dump_folder, os.path.basename(fname))
80
 
        fcontent = base64.b64decode(f['content']).decode('utf-8')
81
 
        with open(fn_local, 'w') as fh:
82
 
            fh.write(fcontent)
83
 
        saved.append(fn_local)
84
 
 
85
 
    return saved
86
 
 
87
 
 
88
 
def generate_records(j, blame_sort=False, print_format="%d seconds in %I%D",
89
 
                     dump_files=False, log_datafiles=False):
90
 
 
91
 
    dumped_files = []
92
 
    records = []
93
 
    start_time = None
94
 
    timestamps = OrderedDict()
95
 
    total_time = 0.0
96
 
    for event in j:
97
 
        name = event.get('name')
98
 
        event['indent'] = '|' + ' ' * (name.count('/') - 1) + '`->'
99
 
        if 'files' in event:
100
 
            if dump_files:
101
 
                dumped_files += dump_event_files(event, dump_files)
102
 
 
103
 
        if event['event_type'] == 'start':
104
 
            timestamps[name] = {'start': event['timestamp']}
105
 
            start = datetime.datetime.utcfromtimestamp(
106
 
                timestamps[name]['start'])
107
 
            event['delta'] = "{:08.5f}".format(total_time)
108
 
            # Initial start event has different record format
109
 
            if '/' not in event['name']:
110
 
                start_time = datetime.datetime.utcfromtimestamp(
111
 
                    event['timestamp'])
112
 
                elapsed = start - start_time
113
 
                event['elapsed'] = "{:08.5f}".format(elapsed.total_seconds())
114
 
                records.append(format_record('[%d] %D', event))
115
 
            else:
116
 
                elapsed = start - start_time
117
 
                event['elapsed'] = "{:08.5f}".format(elapsed.total_seconds())
118
 
                if '%e' in print_format:
119
 
                    records.append(format_record(print_format, event))
120
 
        else:
121
 
            timestamps[name].update({'finish': event['timestamp']})
122
 
            start = datetime.datetime.utcfromtimestamp(
123
 
                timestamps[name]['start'])
124
 
            end = datetime.datetime.utcfromtimestamp(
125
 
                timestamps[name]['finish'])
126
 
            delta = end - start
127
 
            elapsed = start - start_time + delta
128
 
            event['elapsed'] = "{:08.5f}".format(elapsed.total_seconds())
129
 
            # don't the main event's time to total time, it's already counted
130
 
            if '/' not in event['name']:
131
 
                total_time += delta.total_seconds()
132
 
            event['delta'] = "{:008.5f}".format(delta.total_seconds())
133
 
            timestamps[name].update({
134
 
                'delta': event['delta'],
135
 
                'elapsed': event['elapsed']})
136
 
            records.append(format_record(print_format, event))
137
 
 
138
 
    records.append(' ---\n%3.5f seconds total time' % total_time)
139
 
    if log_datafiles:
140
 
        with open('timestamps.json', 'w') as fh:
141
 
            fh.write(json.dumps(timestamps, indent=4))
142
 
 
143
 
        with open('records.json', 'w') as fh:
144
 
            fh.write(json.dumps(records, indent=4))
145
 
 
146
 
    if dumped_files:
147
 
        records.append('\nSaved event file(s):')
148
 
        for f in dumped_files:
149
 
            records.append('  %s' % f)
150
 
 
151
 
    return records
152
 
 
153
 
 
154
 
def main():
155
 
    parser = argparse.ArgumentParser(
156
 
        description='curtin-print-log - pretty print and sort curtin logs',
157
 
        prog='curtin-print-log')
158
 
    parser.add_argument('--dumpfiles', action='store',
159
 
                        default=False,
160
 
                        dest='dump_files',
161
 
                        help='dump content of any posted files')
162
 
    parser.add_argument('--save-processing-data', action='store_true',
163
 
                        default=False,
164
 
                        dest='log_datafiles',
165
 
                        help='save the processing data')
166
 
    parser.add_argument('--format', action='store',
167
 
                        dest='print_format',
168
 
                        default='%I%D @%Es +%ds',
169
 
                        help='specify formatting of output. ' +
170
 
                        formatting_help)
171
 
    parser.add_argument('infile', nargs='?', type=argparse.FileType('r'),
172
 
                        help='Path to log to parse. Use - for stdin')
173
 
 
174
 
    opts = parser.parse_args(sys.argv[1:])
175
 
    if not opts.infile:
176
 
        parser.print_help()
177
 
        sys.exit(1)
178
 
 
179
 
    try:
180
 
        j = json.load(opts.infile)
181
 
    except json.JSONDecodeError:
182
 
        print("Input must be valid JSON")
183
 
        sys.exit(1)
184
 
 
185
 
    sj = sorted(j, key=lambda x: x['timestamp'])
186
 
    records = generate_records(sj, print_format=opts.print_format,
187
 
                               dump_files=opts.dump_files,
188
 
                               log_datafiles=opts.log_datafiles)
189
 
    print('The total time elapsed since completing an event is printed'
190
 
          ' after the "@" character.')
191
 
    print('The time the event takes is printed after the "+" character.')
192
 
    print()
193
 
    print("\n".join(records))
194
 
 
195
 
 
196
 
if __name__ == '__main__':
197
 
    main()