1#!/usr/bin/python3
2# -*- coding: UTF-8 -*-
3
4"""
5This script defines the function to do cpu usage analysis
6"""
7import sys
8import string
9import struct
10import csv
11import os
12
13cpu_id = 0
14stat_tsc = 0
15
16# map event TRACE_SCHED_NEXT defined in file trace.h
17SCHED_NEXT = 0x20
18
19# max number of vm is 16, another 1 is for hv
20VM_NUM = 16
21time_vm_running = [0] * (VM_NUM + 1)
22count_all_trace = 0
23count_sched_trace = 0
24
25# Q: 64-bit tsc, Q: 64-bit event, 16c: char name[16]
26TRCREC = "QQ16c"
27
28def process_trace_data(ifile):
29    """parse the trace data file
30    Args:
31        ifile: input trace data file
32    Return:
33        None
34    """
35
36    global stat_tsc, cpu_id, time_vm_running, count_all_trace, count_sched_trace
37
38    # The duration of cpu running is tsc_end - tsc_begin
39    tsc_begin = 0
40    tsc_end = 0
41    time_ambiguous = 0
42
43    fd = open(ifile, 'rb')
44    while True:
45        try:
46            count_all_trace += 1
47            line = fd.read(struct.calcsize(TRCREC))
48            if not line:
49                break
50            x = struct.unpack(TRCREC, line)
51            if x[0] == 0:
52                break
53            tsc_end = x[0]
54            if count_all_trace == 1:
55                tsc_begin = tsc_end
56                tsc_last_sched = tsc_begin
57            event = x[1]
58            cpu_id = event >> 56
59            event = event & 0xffffffffffff
60            if event == SCHED_NEXT:
61                count_sched_trace += 1
62                s=''
63                for _ in list(x[2:]):
64                    d = _.decode('ascii', errors='ignore')
65                    s += d
66
67                if s[:2] == "vm":
68                    vm_prev = int(s[2])
69                    if s[3] != ":":
70                        vm_prev = vm_prev*10 + int(s[3])
71                elif s[:4] == "idle":
72                    vm_prev = VM_NUM
73                else:
74                    print("Error: trace data is not correct!")
75                    return
76
77                if s[4:6] == "vm":
78                    vm_next = int(s[6])
79                    if s[7] != ":":
80                        vm_next = vm_next*10 + int(s[7])
81                elif s[4:8] == "idle":
82                    vm_next = VM_NUM
83                else:
84                    print("Error: trace data is not correct!")
85                    return
86
87                if (count_sched_trace == 1) or (vm_prev == vm_prev_last):
88                    time_vm_running[vm_prev] += tsc_end - tsc_last_sched
89                else:
90                    print("line %d: last_next =vm%d, current_prev=vm%d" % (count_all_trace, vm_prev, vm_prev_last))
91                    print("Warning: last schedule next is not the current task. Trace log is lost.")
92                    time_ambiguous += tsc_end - tsc_last_sched
93
94                tsc_last_sched = tsc_end
95                vm_prev_last = vm_next
96
97        except (IOError, struct.error) as e:
98            sys.exit()
99
100    print ("Start trace %d tsc cycle" % (tsc_begin))
101    print ("End trace %d tsc cycle" % (tsc_end))
102    stat_tsc = tsc_end - tsc_begin
103    assert stat_tsc != 0, "total_run_time in statistic is 0,\
104                           tsc_end %d, tsc_begin %d"\
105                           % (tsc_end, tsc_begin)
106
107    if count_sched_trace == 0:
108        print ("There is no context switch in HV scheduling during this period. "
109               "This CPU may be exclusively owned by one vm.\n"
110               "The CPU usage is 100%")
111        return
112    if time_ambiguous > 0:
113        print("Warning: ambiguous running time: %d tsc cycle, occupying %2.2f%% cpu."
114                % (time_ambiguous, float(time_ambiguous)*100/stat_tsc))
115
116    # the last time
117    time_vm_running[vm_next] += tsc_end - tsc_last_sched
118
119
120def generate_report(ofile, freq):
121    """ generate analysis report
122    Args:
123        ofile: output report
124        freq: TSC frequency of the device trace data from
125    Return:
126        None
127    """
128    global stat_tsc, cpu_id, time_vm_running, count_all_trace, count_sched_trace
129
130    if (count_sched_trace == 0):
131        return
132
133    csv_name = ofile + '.csv'
134    try:
135        with open(csv_name, 'a') as filep:
136            f_csv = csv.writer(filep)
137
138            stat_sec = float(stat_tsc) / (float(freq) * 1000 * 1000)
139            print ("Total run time: %d cpu cycles" % (stat_tsc))
140            print ("TSC Freq: %s MHz" % (freq))
141            print ("Total run time: %.2f sec" % (stat_sec))
142            print ("Total trace items: %d" % (count_all_trace))
143            print ("Total scheduling trace: %d" % (count_sched_trace))
144
145            f_csv.writerow(['Total run time(tsc cycles)', 'Total run time(sec)', 'Freq(MHz)'])
146            f_csv.writerow(['%d' % (stat_tsc),
147                            '%.2f' % (stat_sec),
148                            '%s' % (freq)])
149
150            print ("%-28s\t%-12s\t%-12s\t%-24s\t%-16s" % ("PCPU ID", "VM ID",
151                   "VM Running/sec", "VM Running(tsc cycles)", "CPU Usage"))
152            f_csv.writerow(['PCPU ID',
153                            'VM_ID',
154                            'Time Consumed/sec',
155                            'Time Consumed(tsc cycles)',
156                            'CPU Usage%'])
157
158            for vmid, tsc in enumerate(time_vm_running):
159                run_tsc = tsc
160                run_per = float(run_tsc) * 100 / float(stat_tsc)
161                run_sec = float(run_tsc) / (float(freq) * 1000 * 1000)
162                if vmid != VM_NUM:
163                    print ("%-28d\t%-12d\t%-10.2f\t%-24d\t%-2.2f%%" %
164                           (cpu_id, vmid, run_sec, run_tsc, run_per))
165                    row = [cpu_id, vmid, '%.2f' % (run_sec), run_tsc, '%2.2f' % (run_per)]
166                else:
167                    print ("%-28d\t%-12s\t%-10.2f\t%-24d\t%-2.2f%%" %
168                           (cpu_id, 'Idle', run_sec, run_tsc, run_per))
169                    row = [cpu_id, 'Idle', '%.2f' % (run_sec), run_tsc, '%2.2f' % (run_per)]
170                f_csv.writerow(row)
171
172    except IOError as err:
173        print ("Output File Error: " + str(err))
174
175def analyze_cpu_usage(ifile, ofile, freq):
176    """do cpu usage analysis of each vm
177    Args:
178        ifile: input trace data file
179        ofile: output report file
180        freq: TSC frequency of the host where we capture the trace data
181    Return:
182        None
183    """
184
185    print("VM CPU usage analysis started... \n\tinput file: %s\n"
186          "\toutput file: %s.csv" % (ifile, ofile))
187
188    if os.stat(ifile).st_size == 0:
189        print("The input trace file is empty. The corresponding CPU may be offline.")
190        return
191    if float(freq) == 0.0:
192        print("The input cpu frequency cannot be zero!")
193        return
194    process_trace_data(ifile)
195    # print cpu usage of each vm
196    generate_report(ofile, freq)
197