1#!/usr/bin/python3
2#
3# Copyright (C) 2020 Collabora Ltd
4#
5# Permission is hereby granted, free of charge, to any person obtaining a
6# copy of this software and associated documentation files (the "Software"),
7# to deal in the Software without restriction, including without limitation
8# the rights to use, copy, modify, merge, publish, distribute, sublicense,
9# and/or sell copies of the Software, and to permit persons to whom the
10# Software is furnished to do so, subject to the following conditions:
11#
12# The above copyright notice and this permission notice shall be included
13# in all copies or substantial portions of the Software.
14#
15# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
19# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
20# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
21# OTHER DEALINGS IN THE SOFTWARE.
22
23from google import protobuf
24import protos.perfetto.trace.perfetto_trace_pb2
25from protos.perfetto.trace.perfetto_trace_pb2 import BUILTIN_CLOCK_BOOTTIME
26from protos.perfetto.trace.perfetto_trace_pb2 import BUILTIN_CLOCK_REALTIME
27import math
28import sys
29import operator
30import time
31
32def add_ftrace_event(out_message, in_packet, in_event, max_host_sequence_id = 0):
33    out_packet = out_message.packet.add()
34    out_packet.ftrace_events.cpu = in_packet.ftrace_events.cpu
35    out_packet.trusted_uid = in_packet.trusted_uid
36    out_packet.trusted_packet_sequence_id += max_host_sequence_id
37    out_packet.ftrace_events.event.add().CopyFrom(in_event)
38
39virtio_gpu_pids = set()
40
41print('%d Loading host trace' % time.time())
42
43in_message = protos.perfetto.trace.perfetto_trace_pb2.Trace()
44in_message.ParseFromString(open(sys.argv[1], 'rb').read())
45
46print('%d Copying host trace' % time.time())
47
48out_message = protos.perfetto.trace.perfetto_trace_pb2.Trace()
49max_host_sequence_id = 0
50first_host_virtio_gpu_cmd = math.inf
51host_boot_ts = -1
52for in_packet in in_message.packet:
53    max_host_sequence_id = max(max_host_sequence_id,
54                               in_packet.trusted_packet_sequence_id)
55
56    if in_packet.HasField('ftrace_events'):
57        for event in in_packet.ftrace_events.event:
58            if event.HasField('sched_switch'):
59                if 'virtio_gpu' == event.sched_switch.prev_comm:
60                    virtio_gpu_pids.add(event.sched_switch.prev_pid)
61                if 'virtio_gpu' == event.sched_switch.next_comm:
62                    virtio_gpu_pids.add(event.sched_switch.next_pid)
63
64                if event.sched_switch.prev_pid in virtio_gpu_pids or \
65                   event.sched_switch.next_pid in virtio_gpu_pids:
66                    add_ftrace_event(out_message, in_packet, event)
67            elif event.HasField('sched_wakeup'):
68                if 'virtio_gpu' == event.sched_wakeup.comm:
69                    virtio_gpu_pids.add(event.sched_wakeup.pid)
70
71                if event.sched_wakeup.pid in virtio_gpu_pids:
72                    add_ftrace_event(out_message, in_packet, event)
73            elif event.HasField('print'):
74                 event_type, guest_pid, label, cookie = event.print.buf.split('|')
75
76                 # Replace host PID with the guest PID
77                 event.pid = int(guest_pid)
78                 add_ftrace_event(out_message, in_packet, event)
79    else:
80        if in_packet.HasField('track_descriptor'):
81            if in_packet.track_descriptor.HasField('name'):
82               in_packet.track_descriptor.name += ' (Host)'
83        elif in_packet.HasField('track_event'):
84            if in_packet.track_event.type == in_packet.track_event.TYPE_SLICE_BEGIN and \
85               in_packet.track_event.name == 'GetCapset':
86                first_host_virtio_gpu_cmd = min(first_host_virtio_gpu_cmd, in_packet.timestamp)
87        elif host_boot_ts == -1 and in_packet.HasField('clock_snapshot'):
88            for clock in in_packet.clock_snapshot.clocks:
89                if clock.clock_id == BUILTIN_CLOCK_BOOTTIME:
90                    host_boottime = clock.timestamp
91                elif clock.clock_id == BUILTIN_CLOCK_REALTIME:
92                    host_realtime = clock.timestamp
93            host_boot_ts = host_realtime - host_boottime
94        out_packet = out_message.packet.add()
95        out_packet.CopyFrom(in_packet)
96
97print('%d Loading guest trace' % time.time())
98in_message.ParseFromString(open(sys.argv[2], 'rb').read())
99
100#print('%d Writing guest trace txt' % time.time())
101#open('../traces-db/perfetto-guest.txt', 'w').write(str(in_message))
102
103first_guest_virtio_gpu_cmd = math.inf
104guest_boot_ts = -1
105for in_packet in in_message.packet:
106    if guest_boot_ts == -1 and in_packet.HasField('clock_snapshot'):
107        for clock in in_packet.clock_snapshot.clocks:
108            if clock.clock_id == BUILTIN_CLOCK_BOOTTIME:
109                guest_boottime = clock.timestamp
110            elif clock.clock_id == BUILTIN_CLOCK_REALTIME:
111                guest_realtime = clock.timestamp
112        guest_boot_ts = guest_realtime - guest_boottime
113    elif in_packet.HasField('track_event'):
114     if in_packet.track_event.type == in_packet.track_event.TYPE_SLICE_BEGIN and \
115        in_packet.track_event.name == 'DRM_IOCTL_VIRTGPU_GET_CAPS':
116         first_guest_virtio_gpu_cmd = min(first_guest_virtio_gpu_cmd, in_packet.timestamp)
117
118delta = guest_boot_ts - host_boot_ts
119cmd_delta = first_host_virtio_gpu_cmd - first_guest_virtio_gpu_cmd - delta
120print("boottime delta %ds." % (delta / 1000 / 1000 / 1000))
121print("cmd delta %dus." % (cmd_delta / 1000))
122
123for in_packet in in_message.packet:
124    if in_packet.HasField('process_tree') or \
125       in_packet.HasField('service_event') or \
126       in_packet.HasField('track_event') or \
127       in_packet.HasField('trace_packet_defaults') or \
128       in_packet.HasField('track_descriptor'):
129        out_packet = out_message.packet.add()
130        out_packet.CopyFrom(in_packet)
131        out_packet.trusted_packet_sequence_id += max_host_sequence_id
132        out_packet.timestamp += delta
133        if out_packet.HasField('track_descriptor'):
134            if out_packet.track_descriptor.HasField('name'):
135                out_packet.track_descriptor.name += ' (Guest)'
136    elif in_packet.HasField('ftrace_events'):
137         for event in in_packet.ftrace_events.event:
138             event.timestamp += delta
139             add_ftrace_event(out_message, in_packet, event, max_host_sequence_id)
140
141def get_timestamp(packet):
142    if packet.HasField('timestamp'):
143        return packet.timestamp
144    elif packet.HasField('ftrace_events') and \
145         packet.ftrace_events.event:
146        return packet.ftrace_events.event[0].timestamp
147    return 0
148
149out_message.packet.sort(key=get_timestamp)
150print('%d Writing merged trace' % time.time())
151open(sys.argv[3], 'wb').write(out_message.SerializeToString())
152
153#print('%d Writing merged trace txt' % time.time())
154#open('../traces-db/perfetto.txt', 'w').write(str(out_message))
155