1#!/usr/bin/python3
2# -*- coding: UTF-8 -*-
3
4"""
5This script defines the function to do the vm_exit analysis
6"""
7
8import csv
9import struct
10
11TSC_BEGIN = 0
12TSC_END = 0
13TOTAL_NR_EXITS = 0
14
15VM_EXIT = 0x10
16VM_ENTER = 0x11
17VMEXIT_ENTRY = 0x10000
18
19LIST_EVENTS = {
20    'VMEXIT_EXCEPTION_OR_NMI':     VMEXIT_ENTRY + 0x00000000,
21    'VMEXIT_EXTERNAL_INTERRUPT':   VMEXIT_ENTRY + 0x00000001,
22    'VMEXIT_INTERRUPT_WINDOW':     VMEXIT_ENTRY + 0x00000002,
23    'VMEXIT_CPUID':                VMEXIT_ENTRY + 0x00000004,
24    'VMEXIT_RDTSC':                VMEXIT_ENTRY + 0x00000010,
25    'VMEXIT_VMCALL':               VMEXIT_ENTRY + 0x00000012,
26    'VMEXIT_CR_ACCESS':            VMEXIT_ENTRY + 0x0000001C,
27    'VMEXIT_IO_INSTRUCTION':       VMEXIT_ENTRY + 0x0000001E,
28    'VMEXIT_RDMSR':                VMEXIT_ENTRY + 0x0000001F,
29    'VMEXIT_WRMSR':                VMEXIT_ENTRY + 0x00000020,
30    'VMEXIT_EPT_VIOLATION':        VMEXIT_ENTRY + 0x00000030,
31    'VMEXIT_EPT_MISCONFIGURATION': VMEXIT_ENTRY + 0x00000031,
32    'VMEXIT_RDTSCP':               VMEXIT_ENTRY + 0x00000033,
33    'VMEXIT_APICV_WRITE':          VMEXIT_ENTRY + 0x00000038,
34    'VMEXIT_APICV_ACCESS':         VMEXIT_ENTRY + 0x00000039,
35    'VMEXIT_APICV_VIRT_EOI':       VMEXIT_ENTRY + 0x0000003A,
36    'VMEXIT_UNHANDLED': 0x20000
37}
38
39NR_EXITS = {
40    'VMEXIT_EXCEPTION_OR_NMI': 0,
41    'VMEXIT_EXTERNAL_INTERRUPT': 0,
42    'VMEXIT_INTERRUPT_WINDOW': 0,
43    'VMEXIT_CPUID': 0,
44    'VMEXIT_RDTSC': 0,
45    'VMEXIT_VMCALL': 0,
46    'VMEXIT_CR_ACCESS': 0,
47    'VMEXIT_IO_INSTRUCTION': 0,
48    'VMEXIT_RDMSR': 0,
49    'VMEXIT_WRMSR': 0,
50    'VMEXIT_APICV_ACCESS': 0,
51    'VMEXIT_APICV_VIRT_EOI': 0,
52    'VMEXIT_EPT_VIOLATION': 0,
53    'VMEXIT_EPT_MISCONFIGURATION': 0,
54    'VMEXIT_RDTSCP': 0,
55    'VMEXIT_APICV_WRITE': 0,
56    'VMEXIT_UNHANDLED': 0
57}
58
59TIME_IN_EXIT = {
60    'VMEXIT_EXCEPTION_OR_NMI': 0,
61    'VMEXIT_EXTERNAL_INTERRUPT': 0,
62    'VMEXIT_INTERRUPT_WINDOW': 0,
63    'VMEXIT_CPUID': 0,
64    'VMEXIT_RDTSC': 0,
65    'VMEXIT_VMCALL': 0,
66    'VMEXIT_CR_ACCESS': 0,
67    'VMEXIT_IO_INSTRUCTION': 0,
68    'VMEXIT_RDMSR': 0,
69    'VMEXIT_WRMSR': 0,
70    'VMEXIT_APICV_ACCESS': 0,
71    'VMEXIT_APICV_VIRT_EOI': 0,
72    'VMEXIT_EPT_VIOLATION': 0,
73    'VMEXIT_EPT_MISCONFIGURATION': 0,
74    'VMEXIT_RDTSCP': 0,
75    'VMEXIT_APICV_WRITE': 0,
76    'VMEXIT_UNHANDLED': 0
77}
78
79# 4 * 64bit per trace entry
80TRCREC = "QQQQ"
81
82def parse_trace_data(ifile):
83    """parse the trace data file
84    Args:
85        ifile: input trace data file
86    Return:
87        None
88    """
89
90    global TSC_BEGIN, TSC_END, TOTAL_NR_EXITS
91    last_ev_id = ''
92    tsc_exit = 0
93
94    fd = open(ifile, 'rb')
95
96    # The duration of one vmexit is tsc_enter - tsc_exit
97    # Here we should find the first vmexit and ignore other entries on top of the first vmexit
98    while True:
99        try:
100            line = fd.read(struct.calcsize(TRCREC))
101            if not line:
102                break
103            (tsc, event, d1, d2) = struct.unpack(TRCREC, line)
104            event = event & 0xffffffffffff
105
106            if event != VM_EXIT:
107                continue
108
109            # We found the first vmexit and should seek back one line as we will read it in the following loop
110            TSC_BEGIN = tsc
111            fd.seek(fd.tell() - len(line))
112            break
113
114        except (IOError, struct.error) as e:
115            sys.exit()
116
117    while True:
118        try:
119            line = fd.read(struct.calcsize(TRCREC))
120            if not line:
121                break
122            (tsc, event, d1, d2) = struct.unpack(TRCREC, line)
123
124            event = event & 0xffffffffffff
125
126            if event == VM_ENTER:
127                TSC_END = tsc
128                # Found one vmenter in pair with the last vmexit
129                TIME_IN_EXIT[last_ev_id] += tsc - tsc_exit
130
131            elif event == VM_EXIT:
132                tsc_exit = tsc
133                TOTAL_NR_EXITS += 1
134
135            else:
136                for key in LIST_EVENTS.keys():
137                    if event == LIST_EVENTS.get(key):
138                        NR_EXITS[key] += 1
139                        last_ev_id = key
140                        break
141
142                    else:
143                        # Skip the non-VMEXIT trace event
144                        pass
145
146        except (IOError, struct.error) as e:
147            sys.exit()
148
149def generate_report(ofile, freq):
150    """ generate analysis report
151    Args:
152        ofile: output report
153        freq: TSC frequency of the device trace data from
154    Return:
155        None
156    """
157    global TSC_BEGIN, TSC_END, TOTAL_NR_EXITS
158
159    csv_name = ofile + '.csv'
160    try:
161        with open(csv_name, 'a') as filep:
162            f_csv = csv.writer(filep)
163
164            total_exit_time = 0
165            rt_cycle = TSC_END - TSC_BEGIN
166            assert rt_cycle != 0, "total_run_time in cycle is 0,\
167                                tsc_end %d, tsc_begin %d"\
168                                % (TSC_END, TSC_BEGIN)
169
170            rt_sec = float(rt_cycle) / (float(freq) * 1000 * 1000)
171
172            for event in LIST_EVENTS:
173                total_exit_time += TIME_IN_EXIT[event]
174
175            print ("Total run time: %d cycles" % (rt_cycle))
176            print ("TSC Freq: %s MHz" % (freq))
177            print ("Total run time: %.6f sec" % (rt_sec))
178
179            f_csv.writerow(['Run time(cycles)', 'Run time(Sec)', 'Freq(MHz)'])
180            f_csv.writerow(['%d' % (rt_cycle),
181                            '%.3f' % (rt_sec),
182                            '%s' % (freq)])
183
184            print ("%-28s\t%-12s\t%-12s\t%-24s\t%-16s" % ("Event", "NR_Exit",
185                   "NR_Exit/Sec", "Time Consumed(cycles)", "Time percentage"))
186            f_csv.writerow(['Exit_Reason',
187                            'NR_Exit',
188                            'NR_Exit/Sec',
189                            'Time Consumed(cycles)',
190                            'Time Percentage'])
191
192            for event in LIST_EVENTS:
193                ev_freq = float(NR_EXITS[event]) / rt_sec
194                pct = float(TIME_IN_EXIT[event]) * 100 / float(rt_cycle)
195
196                print ("%-28s\t%-12d\t%-12.2f\t%-24d\t%-16.2f" %
197                       (event, NR_EXITS[event], ev_freq, TIME_IN_EXIT[event], pct))
198                row = [event, NR_EXITS[event], '%.2f' % ev_freq, TIME_IN_EXIT[event],
199                       '%2.2f' % (pct)]
200                f_csv.writerow(row)
201
202            ev_freq = float(TOTAL_NR_EXITS) / rt_sec
203            pct = float(total_exit_time) * 100 / float(rt_cycle)
204            print("%-28s\t%-12d\t%-12.2f\t%-24d\t%-16.2f"
205                  % ("Total", TOTAL_NR_EXITS, ev_freq, total_exit_time, pct))
206            row = ["Total", TOTAL_NR_EXITS, '%.2f' % ev_freq, total_exit_time,
207                   '%2.2f' % (pct)]
208            f_csv.writerow(row)
209
210    except IOError as err:
211        print ("Output File Error: " + str(err))
212
213def analyze_vm_exit(ifile, ofile, freq):
214    """do the vm exits analysis
215    Args:
216        ifile: input trace data file
217        ofile: output report file
218        freq: TSC frequency of the host where we capture the trace data
219    Return:
220        None
221    """
222
223    print("VM exits analysis started... \n\tinput file: %s\n"
224          "\toutput file: %s.csv" % (ifile, ofile))
225
226    parse_trace_data(ifile)
227    # save report to the output file
228    generate_report(ofile, freq)
229