2
# Copyright (C) 2016 Canonical Ltd.
4
# Author: Ryan Harper <ryan.harper@canonical.com>
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.
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
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
29
"description": "executing late commands",
30
"event_type": "start",
32
"name": "cmd-install/stage-late"
34
"timestamp": 1461164249.1590767,
38
"description": "executing late commands",
39
"event_type": "finish",
41
"name": "cmd-install/stage-late",
44
"timestamp": 1461164249.1590767
62
formatting_help = " ".join(["{}: {}".format(k.replace('%', '%%'), v)
63
for k, v in format_key.items()])
66
def format_record(msg, event):
67
for i, j in format_key.items():
69
msg = msg.replace(i, "{%s}" % j)
70
return msg.format(**event)
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']
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:
83
saved.append(fn_local)
88
def generate_records(j, blame_sort=False, print_format="%d seconds in %I%D",
89
dump_files=False, log_datafiles=False):
94
timestamps = OrderedDict()
97
name = event.get('name')
98
event['indent'] = '|' + ' ' * (name.count('/') - 1) + '`->'
101
dumped_files += dump_event_files(event, dump_files)
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(
112
elapsed = start - start_time
113
event['elapsed'] = "{:08.5f}".format(elapsed.total_seconds())
114
records.append(format_record('[%d] %D', event))
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))
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'])
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))
138
records.append(' ---\n%3.5f seconds total time' % total_time)
140
with open('timestamps.json', 'w') as fh:
141
fh.write(json.dumps(timestamps, indent=4))
143
with open('records.json', 'w') as fh:
144
fh.write(json.dumps(records, indent=4))
147
records.append('\nSaved event file(s):')
148
for f in dumped_files:
149
records.append(' %s' % f)
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',
161
help='dump content of any posted files')
162
parser.add_argument('--save-processing-data', action='store_true',
164
dest='log_datafiles',
165
help='save the processing data')
166
parser.add_argument('--format', action='store',
168
default='%I%D @%Es +%ds',
169
help='specify formatting of output. ' +
171
parser.add_argument('infile', nargs='?', type=argparse.FileType('r'),
172
help='Path to log to parse. Use - for stdin')
174
opts = parser.parse_args(sys.argv[1:])
180
j = json.load(opts.infile)
181
except json.JSONDecodeError:
182
print("Input must be valid JSON")
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.')
193
print("\n".join(records))
196
if __name__ == '__main__':