xref: /openbmc/qemu/tests/migration-stress/guestperf/plot.py (revision bdce9bc9179bd7b6f4e12c759dd3cd6794e26a6b)
1*212c1933SFabiano Rosas#
2*212c1933SFabiano Rosas# Migration test graph plotting
3*212c1933SFabiano Rosas#
4*212c1933SFabiano Rosas# Copyright (c) 2016 Red Hat, Inc.
5*212c1933SFabiano Rosas#
6*212c1933SFabiano Rosas# This library is free software; you can redistribute it and/or
7*212c1933SFabiano Rosas# modify it under the terms of the GNU Lesser General Public
8*212c1933SFabiano Rosas# License as published by the Free Software Foundation; either
9*212c1933SFabiano Rosas# version 2.1 of the License, or (at your option) any later version.
10*212c1933SFabiano Rosas#
11*212c1933SFabiano Rosas# This library is distributed in the hope that it will be useful,
12*212c1933SFabiano Rosas# but WITHOUT ANY WARRANTY; without even the implied warranty of
13*212c1933SFabiano Rosas# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14*212c1933SFabiano Rosas# Lesser General Public License for more details.
15*212c1933SFabiano Rosas#
16*212c1933SFabiano Rosas# You should have received a copy of the GNU Lesser General Public
17*212c1933SFabiano Rosas# License along with this library; if not, see <http://www.gnu.org/licenses/>.
18*212c1933SFabiano Rosas#
19*212c1933SFabiano Rosas
20*212c1933SFabiano Rosasimport sys
21*212c1933SFabiano Rosas
22*212c1933SFabiano Rosas
23*212c1933SFabiano Rosasclass Plot(object):
24*212c1933SFabiano Rosas
25*212c1933SFabiano Rosas    # Generated using
26*212c1933SFabiano Rosas    # http://tools.medialab.sciences-po.fr/iwanthue/
27*212c1933SFabiano Rosas    COLORS = ["#CD54D0",
28*212c1933SFabiano Rosas              "#79D94C",
29*212c1933SFabiano Rosas              "#7470CD",
30*212c1933SFabiano Rosas              "#D2D251",
31*212c1933SFabiano Rosas              "#863D79",
32*212c1933SFabiano Rosas              "#76DDA6",
33*212c1933SFabiano Rosas              "#D4467B",
34*212c1933SFabiano Rosas              "#61923D",
35*212c1933SFabiano Rosas              "#CB9CCA",
36*212c1933SFabiano Rosas              "#D98F36",
37*212c1933SFabiano Rosas              "#8CC8DA",
38*212c1933SFabiano Rosas              "#CE4831",
39*212c1933SFabiano Rosas              "#5E7693",
40*212c1933SFabiano Rosas              "#9B803F",
41*212c1933SFabiano Rosas              "#412F4C",
42*212c1933SFabiano Rosas              "#CECBA6",
43*212c1933SFabiano Rosas              "#6D3229",
44*212c1933SFabiano Rosas              "#598B73",
45*212c1933SFabiano Rosas              "#C8827C",
46*212c1933SFabiano Rosas              "#394427"]
47*212c1933SFabiano Rosas
48*212c1933SFabiano Rosas    def __init__(self,
49*212c1933SFabiano Rosas                 reports,
50*212c1933SFabiano Rosas                 migration_iters,
51*212c1933SFabiano Rosas                 total_guest_cpu,
52*212c1933SFabiano Rosas                 split_guest_cpu,
53*212c1933SFabiano Rosas                 qemu_cpu,
54*212c1933SFabiano Rosas                 vcpu_cpu):
55*212c1933SFabiano Rosas
56*212c1933SFabiano Rosas        self._reports = reports
57*212c1933SFabiano Rosas        self._migration_iters = migration_iters
58*212c1933SFabiano Rosas        self._total_guest_cpu = total_guest_cpu
59*212c1933SFabiano Rosas        self._split_guest_cpu = split_guest_cpu
60*212c1933SFabiano Rosas        self._qemu_cpu = qemu_cpu
61*212c1933SFabiano Rosas        self._vcpu_cpu = vcpu_cpu
62*212c1933SFabiano Rosas        self._color_idx = 0
63*212c1933SFabiano Rosas
64*212c1933SFabiano Rosas    def _next_color(self):
65*212c1933SFabiano Rosas        color = self.COLORS[self._color_idx]
66*212c1933SFabiano Rosas        self._color_idx += 1
67*212c1933SFabiano Rosas        if self._color_idx >= len(self.COLORS):
68*212c1933SFabiano Rosas            self._color_idx = 0
69*212c1933SFabiano Rosas        return color
70*212c1933SFabiano Rosas
71*212c1933SFabiano Rosas    def _get_progress_label(self, progress):
72*212c1933SFabiano Rosas        if progress:
73*212c1933SFabiano Rosas            return "\n\n" + "\n".join(
74*212c1933SFabiano Rosas                ["Status: %s" % progress._status,
75*212c1933SFabiano Rosas                 "Iteration: %d" % progress._ram._iterations,
76*212c1933SFabiano Rosas                 "Throttle: %02d%%" % progress._throttle_pcent,
77*212c1933SFabiano Rosas                 "Dirty rate: %dMB/s" % (progress._ram._dirty_rate_pps * 4 / 1024.0)])
78*212c1933SFabiano Rosas        else:
79*212c1933SFabiano Rosas            return "\n\n" + "\n".join(
80*212c1933SFabiano Rosas                ["Status: %s" % "none",
81*212c1933SFabiano Rosas                 "Iteration: %d" % 0])
82*212c1933SFabiano Rosas
83*212c1933SFabiano Rosas    def _find_start_time(self, report):
84*212c1933SFabiano Rosas        startqemu = report._qemu_timings._records[0]._timestamp
85*212c1933SFabiano Rosas        startguest = report._guest_timings._records[0]._timestamp
86*212c1933SFabiano Rosas        if startqemu < startguest:
87*212c1933SFabiano Rosas            return startqemu
88*212c1933SFabiano Rosas        else:
89*212c1933SFabiano Rosas            return stasrtguest
90*212c1933SFabiano Rosas
91*212c1933SFabiano Rosas    def _get_guest_max_value(self, report):
92*212c1933SFabiano Rosas        maxvalue = 0
93*212c1933SFabiano Rosas        for record in report._guest_timings._records:
94*212c1933SFabiano Rosas            if record._value > maxvalue:
95*212c1933SFabiano Rosas                maxvalue = record._value
96*212c1933SFabiano Rosas        return maxvalue
97*212c1933SFabiano Rosas
98*212c1933SFabiano Rosas    def _get_qemu_max_value(self, report):
99*212c1933SFabiano Rosas        maxvalue = 0
100*212c1933SFabiano Rosas        oldvalue = None
101*212c1933SFabiano Rosas        oldtime = None
102*212c1933SFabiano Rosas        for record in report._qemu_timings._records:
103*212c1933SFabiano Rosas            if oldvalue is not None:
104*212c1933SFabiano Rosas                cpudelta = (record._value - oldvalue) / 1000.0
105*212c1933SFabiano Rosas                timedelta = record._timestamp - oldtime
106*212c1933SFabiano Rosas                if timedelta == 0:
107*212c1933SFabiano Rosas                    continue
108*212c1933SFabiano Rosas                util = cpudelta / timedelta * 100.0
109*212c1933SFabiano Rosas            else:
110*212c1933SFabiano Rosas                util = 0
111*212c1933SFabiano Rosas            oldvalue = record._value
112*212c1933SFabiano Rosas            oldtime = record._timestamp
113*212c1933SFabiano Rosas
114*212c1933SFabiano Rosas            if util > maxvalue:
115*212c1933SFabiano Rosas                maxvalue = util
116*212c1933SFabiano Rosas        return maxvalue
117*212c1933SFabiano Rosas
118*212c1933SFabiano Rosas    def _get_total_guest_cpu_graph(self, report, starttime):
119*212c1933SFabiano Rosas        xaxis = []
120*212c1933SFabiano Rosas        yaxis = []
121*212c1933SFabiano Rosas        labels = []
122*212c1933SFabiano Rosas        progress_idx = -1
123*212c1933SFabiano Rosas        for record in report._guest_timings._records:
124*212c1933SFabiano Rosas            while ((progress_idx + 1) < len(report._progress_history) and
125*212c1933SFabiano Rosas                   report._progress_history[progress_idx + 1]._now < record._timestamp):
126*212c1933SFabiano Rosas                progress_idx = progress_idx + 1
127*212c1933SFabiano Rosas
128*212c1933SFabiano Rosas            if progress_idx >= 0:
129*212c1933SFabiano Rosas                progress = report._progress_history[progress_idx]
130*212c1933SFabiano Rosas            else:
131*212c1933SFabiano Rosas                progress = None
132*212c1933SFabiano Rosas
133*212c1933SFabiano Rosas            xaxis.append(record._timestamp - starttime)
134*212c1933SFabiano Rosas            yaxis.append(record._value)
135*212c1933SFabiano Rosas            labels.append(self._get_progress_label(progress))
136*212c1933SFabiano Rosas
137*212c1933SFabiano Rosas        from plotly import graph_objs as go
138*212c1933SFabiano Rosas        return go.Scatter(x=xaxis,
139*212c1933SFabiano Rosas                          y=yaxis,
140*212c1933SFabiano Rosas                          name="Guest PIDs: %s" % report._scenario._name,
141*212c1933SFabiano Rosas                          mode='lines',
142*212c1933SFabiano Rosas                          line={
143*212c1933SFabiano Rosas                              "dash": "solid",
144*212c1933SFabiano Rosas                              "color": self._next_color(),
145*212c1933SFabiano Rosas                              "shape": "linear",
146*212c1933SFabiano Rosas                              "width": 1
147*212c1933SFabiano Rosas                          },
148*212c1933SFabiano Rosas                          text=labels)
149*212c1933SFabiano Rosas
150*212c1933SFabiano Rosas    def _get_split_guest_cpu_graphs(self, report, starttime):
151*212c1933SFabiano Rosas        threads = {}
152*212c1933SFabiano Rosas        for record in report._guest_timings._records:
153*212c1933SFabiano Rosas            if record._tid in threads:
154*212c1933SFabiano Rosas                continue
155*212c1933SFabiano Rosas            threads[record._tid] = {
156*212c1933SFabiano Rosas                "xaxis": [],
157*212c1933SFabiano Rosas                "yaxis": [],
158*212c1933SFabiano Rosas                "labels": [],
159*212c1933SFabiano Rosas            }
160*212c1933SFabiano Rosas
161*212c1933SFabiano Rosas        progress_idx = -1
162*212c1933SFabiano Rosas        for record in report._guest_timings._records:
163*212c1933SFabiano Rosas            while ((progress_idx + 1) < len(report._progress_history) and
164*212c1933SFabiano Rosas                   report._progress_history[progress_idx + 1]._now < record._timestamp):
165*212c1933SFabiano Rosas                progress_idx = progress_idx + 1
166*212c1933SFabiano Rosas
167*212c1933SFabiano Rosas            if progress_idx >= 0:
168*212c1933SFabiano Rosas                progress = report._progress_history[progress_idx]
169*212c1933SFabiano Rosas            else:
170*212c1933SFabiano Rosas                progress = None
171*212c1933SFabiano Rosas
172*212c1933SFabiano Rosas            threads[record._tid]["xaxis"].append(record._timestamp - starttime)
173*212c1933SFabiano Rosas            threads[record._tid]["yaxis"].append(record._value)
174*212c1933SFabiano Rosas            threads[record._tid]["labels"].append(self._get_progress_label(progress))
175*212c1933SFabiano Rosas
176*212c1933SFabiano Rosas
177*212c1933SFabiano Rosas        graphs = []
178*212c1933SFabiano Rosas        from plotly import graph_objs as go
179*212c1933SFabiano Rosas        for tid in threads.keys():
180*212c1933SFabiano Rosas            graphs.append(
181*212c1933SFabiano Rosas                go.Scatter(x=threads[tid]["xaxis"],
182*212c1933SFabiano Rosas                           y=threads[tid]["yaxis"],
183*212c1933SFabiano Rosas                           name="PID %s: %s" % (tid, report._scenario._name),
184*212c1933SFabiano Rosas                           mode="lines",
185*212c1933SFabiano Rosas                           line={
186*212c1933SFabiano Rosas                               "dash": "solid",
187*212c1933SFabiano Rosas                               "color": self._next_color(),
188*212c1933SFabiano Rosas                               "shape": "linear",
189*212c1933SFabiano Rosas                               "width": 1
190*212c1933SFabiano Rosas                           },
191*212c1933SFabiano Rosas                           text=threads[tid]["labels"]))
192*212c1933SFabiano Rosas        return graphs
193*212c1933SFabiano Rosas
194*212c1933SFabiano Rosas    def _get_migration_iters_graph(self, report, starttime):
195*212c1933SFabiano Rosas        xaxis = []
196*212c1933SFabiano Rosas        yaxis = []
197*212c1933SFabiano Rosas        labels = []
198*212c1933SFabiano Rosas        for progress in report._progress_history:
199*212c1933SFabiano Rosas            xaxis.append(progress._now - starttime)
200*212c1933SFabiano Rosas            yaxis.append(0)
201*212c1933SFabiano Rosas            labels.append(self._get_progress_label(progress))
202*212c1933SFabiano Rosas
203*212c1933SFabiano Rosas        from plotly import graph_objs as go
204*212c1933SFabiano Rosas        return go.Scatter(x=xaxis,
205*212c1933SFabiano Rosas                          y=yaxis,
206*212c1933SFabiano Rosas                          text=labels,
207*212c1933SFabiano Rosas                          name="Migration iterations",
208*212c1933SFabiano Rosas                          mode="markers",
209*212c1933SFabiano Rosas                          marker={
210*212c1933SFabiano Rosas                              "color": self._next_color(),
211*212c1933SFabiano Rosas                              "symbol": "star",
212*212c1933SFabiano Rosas                              "size": 5
213*212c1933SFabiano Rosas                          })
214*212c1933SFabiano Rosas
215*212c1933SFabiano Rosas    def _get_qemu_cpu_graph(self, report, starttime):
216*212c1933SFabiano Rosas        xaxis = []
217*212c1933SFabiano Rosas        yaxis = []
218*212c1933SFabiano Rosas        labels = []
219*212c1933SFabiano Rosas        progress_idx = -1
220*212c1933SFabiano Rosas
221*212c1933SFabiano Rosas        first = report._qemu_timings._records[0]
222*212c1933SFabiano Rosas        abstimestamps = [first._timestamp]
223*212c1933SFabiano Rosas        absvalues = [first._value]
224*212c1933SFabiano Rosas
225*212c1933SFabiano Rosas        for record in report._qemu_timings._records[1:]:
226*212c1933SFabiano Rosas            while ((progress_idx + 1) < len(report._progress_history) and
227*212c1933SFabiano Rosas                   report._progress_history[progress_idx + 1]._now < record._timestamp):
228*212c1933SFabiano Rosas                progress_idx = progress_idx + 1
229*212c1933SFabiano Rosas
230*212c1933SFabiano Rosas            if progress_idx >= 0:
231*212c1933SFabiano Rosas                progress = report._progress_history[progress_idx]
232*212c1933SFabiano Rosas            else:
233*212c1933SFabiano Rosas                progress = None
234*212c1933SFabiano Rosas
235*212c1933SFabiano Rosas            oldvalue = absvalues[-1]
236*212c1933SFabiano Rosas            oldtime = abstimestamps[-1]
237*212c1933SFabiano Rosas
238*212c1933SFabiano Rosas            cpudelta = (record._value - oldvalue) / 1000.0
239*212c1933SFabiano Rosas            timedelta = record._timestamp - oldtime
240*212c1933SFabiano Rosas            if timedelta == 0:
241*212c1933SFabiano Rosas                continue
242*212c1933SFabiano Rosas            util = cpudelta / timedelta * 100.0
243*212c1933SFabiano Rosas
244*212c1933SFabiano Rosas            abstimestamps.append(record._timestamp)
245*212c1933SFabiano Rosas            absvalues.append(record._value)
246*212c1933SFabiano Rosas
247*212c1933SFabiano Rosas            xaxis.append(record._timestamp - starttime)
248*212c1933SFabiano Rosas            yaxis.append(util)
249*212c1933SFabiano Rosas            labels.append(self._get_progress_label(progress))
250*212c1933SFabiano Rosas
251*212c1933SFabiano Rosas        from plotly import graph_objs as go
252*212c1933SFabiano Rosas        return go.Scatter(x=xaxis,
253*212c1933SFabiano Rosas                          y=yaxis,
254*212c1933SFabiano Rosas                          yaxis="y2",
255*212c1933SFabiano Rosas                          name="QEMU: %s" % report._scenario._name,
256*212c1933SFabiano Rosas                          mode='lines',
257*212c1933SFabiano Rosas                          line={
258*212c1933SFabiano Rosas                              "dash": "solid",
259*212c1933SFabiano Rosas                              "color": self._next_color(),
260*212c1933SFabiano Rosas                              "shape": "linear",
261*212c1933SFabiano Rosas                              "width": 1
262*212c1933SFabiano Rosas                          },
263*212c1933SFabiano Rosas                          text=labels)
264*212c1933SFabiano Rosas
265*212c1933SFabiano Rosas    def _get_vcpu_cpu_graphs(self, report, starttime):
266*212c1933SFabiano Rosas        threads = {}
267*212c1933SFabiano Rosas        for record in report._vcpu_timings._records:
268*212c1933SFabiano Rosas            if record._tid in threads:
269*212c1933SFabiano Rosas                continue
270*212c1933SFabiano Rosas            threads[record._tid] = {
271*212c1933SFabiano Rosas                "xaxis": [],
272*212c1933SFabiano Rosas                "yaxis": [],
273*212c1933SFabiano Rosas                "labels": [],
274*212c1933SFabiano Rosas                "absvalue": [record._value],
275*212c1933SFabiano Rosas                "abstime": [record._timestamp],
276*212c1933SFabiano Rosas            }
277*212c1933SFabiano Rosas
278*212c1933SFabiano Rosas        progress_idx = -1
279*212c1933SFabiano Rosas        for record in report._vcpu_timings._records:
280*212c1933SFabiano Rosas            while ((progress_idx + 1) < len(report._progress_history) and
281*212c1933SFabiano Rosas                   report._progress_history[progress_idx + 1]._now < record._timestamp):
282*212c1933SFabiano Rosas                progress_idx = progress_idx + 1
283*212c1933SFabiano Rosas
284*212c1933SFabiano Rosas            if progress_idx >= 0:
285*212c1933SFabiano Rosas                progress = report._progress_history[progress_idx]
286*212c1933SFabiano Rosas            else:
287*212c1933SFabiano Rosas                progress = None
288*212c1933SFabiano Rosas
289*212c1933SFabiano Rosas            oldvalue = threads[record._tid]["absvalue"][-1]
290*212c1933SFabiano Rosas            oldtime = threads[record._tid]["abstime"][-1]
291*212c1933SFabiano Rosas
292*212c1933SFabiano Rosas            cpudelta = (record._value - oldvalue) / 1000.0
293*212c1933SFabiano Rosas            timedelta = record._timestamp - oldtime
294*212c1933SFabiano Rosas            if timedelta == 0:
295*212c1933SFabiano Rosas                continue
296*212c1933SFabiano Rosas            util = cpudelta / timedelta * 100.0
297*212c1933SFabiano Rosas            if util > 100:
298*212c1933SFabiano Rosas                util = 100
299*212c1933SFabiano Rosas
300*212c1933SFabiano Rosas            threads[record._tid]["absvalue"].append(record._value)
301*212c1933SFabiano Rosas            threads[record._tid]["abstime"].append(record._timestamp)
302*212c1933SFabiano Rosas
303*212c1933SFabiano Rosas            threads[record._tid]["xaxis"].append(record._timestamp - starttime)
304*212c1933SFabiano Rosas            threads[record._tid]["yaxis"].append(util)
305*212c1933SFabiano Rosas            threads[record._tid]["labels"].append(self._get_progress_label(progress))
306*212c1933SFabiano Rosas
307*212c1933SFabiano Rosas
308*212c1933SFabiano Rosas        graphs = []
309*212c1933SFabiano Rosas        from plotly import graph_objs as go
310*212c1933SFabiano Rosas        for tid in threads.keys():
311*212c1933SFabiano Rosas            graphs.append(
312*212c1933SFabiano Rosas                go.Scatter(x=threads[tid]["xaxis"],
313*212c1933SFabiano Rosas                           y=threads[tid]["yaxis"],
314*212c1933SFabiano Rosas                           yaxis="y2",
315*212c1933SFabiano Rosas                           name="VCPU %s: %s" % (tid, report._scenario._name),
316*212c1933SFabiano Rosas                           mode="lines",
317*212c1933SFabiano Rosas                           line={
318*212c1933SFabiano Rosas                               "dash": "solid",
319*212c1933SFabiano Rosas                               "color": self._next_color(),
320*212c1933SFabiano Rosas                               "shape": "linear",
321*212c1933SFabiano Rosas                               "width": 1
322*212c1933SFabiano Rosas                           },
323*212c1933SFabiano Rosas                           text=threads[tid]["labels"]))
324*212c1933SFabiano Rosas        return graphs
325*212c1933SFabiano Rosas
326*212c1933SFabiano Rosas    def _generate_chart_report(self, report):
327*212c1933SFabiano Rosas        graphs = []
328*212c1933SFabiano Rosas        starttime = self._find_start_time(report)
329*212c1933SFabiano Rosas        if self._total_guest_cpu:
330*212c1933SFabiano Rosas            graphs.append(self._get_total_guest_cpu_graph(report, starttime))
331*212c1933SFabiano Rosas        if self._split_guest_cpu:
332*212c1933SFabiano Rosas            graphs.extend(self._get_split_guest_cpu_graphs(report, starttime))
333*212c1933SFabiano Rosas        if self._qemu_cpu:
334*212c1933SFabiano Rosas            graphs.append(self._get_qemu_cpu_graph(report, starttime))
335*212c1933SFabiano Rosas        if self._vcpu_cpu:
336*212c1933SFabiano Rosas            graphs.extend(self._get_vcpu_cpu_graphs(report, starttime))
337*212c1933SFabiano Rosas        if self._migration_iters:
338*212c1933SFabiano Rosas            graphs.append(self._get_migration_iters_graph(report, starttime))
339*212c1933SFabiano Rosas        return graphs
340*212c1933SFabiano Rosas
341*212c1933SFabiano Rosas    def _generate_annotation(self, starttime, progress):
342*212c1933SFabiano Rosas        return {
343*212c1933SFabiano Rosas            "text": progress._status,
344*212c1933SFabiano Rosas            "x": progress._now - starttime,
345*212c1933SFabiano Rosas            "y": 10,
346*212c1933SFabiano Rosas        }
347*212c1933SFabiano Rosas
348*212c1933SFabiano Rosas    def _generate_annotations(self, report):
349*212c1933SFabiano Rosas        starttime = self._find_start_time(report)
350*212c1933SFabiano Rosas        annotations = {}
351*212c1933SFabiano Rosas        started = False
352*212c1933SFabiano Rosas        for progress in report._progress_history:
353*212c1933SFabiano Rosas            if progress._status == "setup":
354*212c1933SFabiano Rosas                continue
355*212c1933SFabiano Rosas            if progress._status not in annotations:
356*212c1933SFabiano Rosas                annotations[progress._status] = self._generate_annotation(starttime, progress)
357*212c1933SFabiano Rosas
358*212c1933SFabiano Rosas        return annotations.values()
359*212c1933SFabiano Rosas
360*212c1933SFabiano Rosas    def _generate_chart(self):
361*212c1933SFabiano Rosas        from plotly.offline import plot
362*212c1933SFabiano Rosas        from plotly import graph_objs as go
363*212c1933SFabiano Rosas
364*212c1933SFabiano Rosas        graphs = []
365*212c1933SFabiano Rosas        yaxismax = 0
366*212c1933SFabiano Rosas        yaxismax2 = 0
367*212c1933SFabiano Rosas        for report in self._reports:
368*212c1933SFabiano Rosas            graphs.extend(self._generate_chart_report(report))
369*212c1933SFabiano Rosas
370*212c1933SFabiano Rosas            maxvalue = self._get_guest_max_value(report)
371*212c1933SFabiano Rosas            if maxvalue > yaxismax:
372*212c1933SFabiano Rosas                yaxismax = maxvalue
373*212c1933SFabiano Rosas
374*212c1933SFabiano Rosas            maxvalue = self._get_qemu_max_value(report)
375*212c1933SFabiano Rosas            if maxvalue > yaxismax2:
376*212c1933SFabiano Rosas                yaxismax2 = maxvalue
377*212c1933SFabiano Rosas
378*212c1933SFabiano Rosas        yaxismax += 100
379*212c1933SFabiano Rosas        if not self._qemu_cpu:
380*212c1933SFabiano Rosas            yaxismax2 = 110
381*212c1933SFabiano Rosas        yaxismax2 += 10
382*212c1933SFabiano Rosas
383*212c1933SFabiano Rosas        annotations = []
384*212c1933SFabiano Rosas        if self._migration_iters:
385*212c1933SFabiano Rosas            for report in self._reports:
386*212c1933SFabiano Rosas                annotations.extend(self._generate_annotations(report))
387*212c1933SFabiano Rosas
388*212c1933SFabiano Rosas        layout = go.Layout(title="Migration comparison",
389*212c1933SFabiano Rosas                           xaxis={
390*212c1933SFabiano Rosas                               "title": "Wallclock time (secs)",
391*212c1933SFabiano Rosas                               "showgrid": False,
392*212c1933SFabiano Rosas                           },
393*212c1933SFabiano Rosas                           yaxis={
394*212c1933SFabiano Rosas                               "title": "Memory update speed (ms/GB)",
395*212c1933SFabiano Rosas                               "showgrid": False,
396*212c1933SFabiano Rosas                               "range": [0, yaxismax],
397*212c1933SFabiano Rosas                           },
398*212c1933SFabiano Rosas                           yaxis2={
399*212c1933SFabiano Rosas                               "title": "Hostutilization (%)",
400*212c1933SFabiano Rosas                               "overlaying": "y",
401*212c1933SFabiano Rosas                               "side": "right",
402*212c1933SFabiano Rosas                               "range": [0, yaxismax2],
403*212c1933SFabiano Rosas                               "showgrid": False,
404*212c1933SFabiano Rosas                           },
405*212c1933SFabiano Rosas                           annotations=annotations)
406*212c1933SFabiano Rosas
407*212c1933SFabiano Rosas        figure = go.Figure(data=graphs, layout=layout)
408*212c1933SFabiano Rosas
409*212c1933SFabiano Rosas        return plot(figure,
410*212c1933SFabiano Rosas                    show_link=False,
411*212c1933SFabiano Rosas                    include_plotlyjs=False,
412*212c1933SFabiano Rosas                    output_type="div")
413*212c1933SFabiano Rosas
414*212c1933SFabiano Rosas
415*212c1933SFabiano Rosas    def _generate_report(self):
416*212c1933SFabiano Rosas        pieces = []
417*212c1933SFabiano Rosas        for report in self._reports:
418*212c1933SFabiano Rosas            pieces.append("""
419*212c1933SFabiano Rosas<h3>Report %s</h3>
420*212c1933SFabiano Rosas<table>
421*212c1933SFabiano Rosas""" % report._scenario._name)
422*212c1933SFabiano Rosas
423*212c1933SFabiano Rosas            pieces.append("""
424*212c1933SFabiano Rosas  <tr class="subhead">
425*212c1933SFabiano Rosas    <th colspan="2">Test config</th>
426*212c1933SFabiano Rosas  </tr>
427*212c1933SFabiano Rosas  <tr>
428*212c1933SFabiano Rosas    <th>Emulator:</th>
429*212c1933SFabiano Rosas    <td>%s</td>
430*212c1933SFabiano Rosas  </tr>
431*212c1933SFabiano Rosas  <tr>
432*212c1933SFabiano Rosas    <th>Kernel:</th>
433*212c1933SFabiano Rosas    <td>%s</td>
434*212c1933SFabiano Rosas  </tr>
435*212c1933SFabiano Rosas  <tr>
436*212c1933SFabiano Rosas    <th>Ramdisk:</th>
437*212c1933SFabiano Rosas    <td>%s</td>
438*212c1933SFabiano Rosas  </tr>
439*212c1933SFabiano Rosas  <tr>
440*212c1933SFabiano Rosas    <th>Transport:</th>
441*212c1933SFabiano Rosas    <td>%s</td>
442*212c1933SFabiano Rosas  </tr>
443*212c1933SFabiano Rosas  <tr>
444*212c1933SFabiano Rosas    <th>Host:</th>
445*212c1933SFabiano Rosas    <td>%s</td>
446*212c1933SFabiano Rosas  </tr>
447*212c1933SFabiano Rosas""" % (report._binary, report._kernel,
448*212c1933SFabiano Rosas       report._initrd, report._transport, report._dst_host))
449*212c1933SFabiano Rosas
450*212c1933SFabiano Rosas            hardware = report._hardware
451*212c1933SFabiano Rosas            pieces.append("""
452*212c1933SFabiano Rosas  <tr class="subhead">
453*212c1933SFabiano Rosas    <th colspan="2">Hardware config</th>
454*212c1933SFabiano Rosas  </tr>
455*212c1933SFabiano Rosas  <tr>
456*212c1933SFabiano Rosas    <th>CPUs:</th>
457*212c1933SFabiano Rosas    <td>%d</td>
458*212c1933SFabiano Rosas  </tr>
459*212c1933SFabiano Rosas  <tr>
460*212c1933SFabiano Rosas    <th>RAM:</th>
461*212c1933SFabiano Rosas    <td>%d GB</td>
462*212c1933SFabiano Rosas  </tr>
463*212c1933SFabiano Rosas  <tr>
464*212c1933SFabiano Rosas    <th>Source CPU bind:</th>
465*212c1933SFabiano Rosas    <td>%s</td>
466*212c1933SFabiano Rosas  </tr>
467*212c1933SFabiano Rosas  <tr>
468*212c1933SFabiano Rosas    <th>Source RAM bind:</th>
469*212c1933SFabiano Rosas    <td>%s</td>
470*212c1933SFabiano Rosas  </tr>
471*212c1933SFabiano Rosas  <tr>
472*212c1933SFabiano Rosas    <th>Dest CPU bind:</th>
473*212c1933SFabiano Rosas    <td>%s</td>
474*212c1933SFabiano Rosas  </tr>
475*212c1933SFabiano Rosas  <tr>
476*212c1933SFabiano Rosas    <th>Dest RAM bind:</th>
477*212c1933SFabiano Rosas    <td>%s</td>
478*212c1933SFabiano Rosas  </tr>
479*212c1933SFabiano Rosas  <tr>
480*212c1933SFabiano Rosas    <th>Preallocate RAM:</th>
481*212c1933SFabiano Rosas    <td>%s</td>
482*212c1933SFabiano Rosas  </tr>
483*212c1933SFabiano Rosas  <tr>
484*212c1933SFabiano Rosas    <th>Locked RAM:</th>
485*212c1933SFabiano Rosas    <td>%s</td>
486*212c1933SFabiano Rosas  </tr>
487*212c1933SFabiano Rosas  <tr>
488*212c1933SFabiano Rosas    <th>Huge pages:</th>
489*212c1933SFabiano Rosas    <td>%s</td>
490*212c1933SFabiano Rosas  </tr>
491*212c1933SFabiano Rosas""" % (hardware._cpus, hardware._mem,
492*212c1933SFabiano Rosas       ",".join(hardware._src_cpu_bind),
493*212c1933SFabiano Rosas       ",".join(hardware._src_mem_bind),
494*212c1933SFabiano Rosas       ",".join(hardware._dst_cpu_bind),
495*212c1933SFabiano Rosas       ",".join(hardware._dst_mem_bind),
496*212c1933SFabiano Rosas       "yes" if hardware._prealloc_pages else "no",
497*212c1933SFabiano Rosas       "yes" if hardware._locked_pages else "no",
498*212c1933SFabiano Rosas       "yes" if hardware._huge_pages else "no"))
499*212c1933SFabiano Rosas
500*212c1933SFabiano Rosas            scenario = report._scenario
501*212c1933SFabiano Rosas            pieces.append("""
502*212c1933SFabiano Rosas  <tr class="subhead">
503*212c1933SFabiano Rosas    <th colspan="2">Scenario config</th>
504*212c1933SFabiano Rosas  </tr>
505*212c1933SFabiano Rosas  <tr>
506*212c1933SFabiano Rosas    <th>Max downtime:</th>
507*212c1933SFabiano Rosas    <td>%d milli-sec</td>
508*212c1933SFabiano Rosas  </tr>
509*212c1933SFabiano Rosas  <tr>
510*212c1933SFabiano Rosas    <th>Max bandwidth:</th>
511*212c1933SFabiano Rosas    <td>%d MB/sec</td>
512*212c1933SFabiano Rosas  </tr>
513*212c1933SFabiano Rosas  <tr>
514*212c1933SFabiano Rosas    <th>Max iters:</th>
515*212c1933SFabiano Rosas    <td>%d</td>
516*212c1933SFabiano Rosas  </tr>
517*212c1933SFabiano Rosas  <tr>
518*212c1933SFabiano Rosas    <th>Max time:</th>
519*212c1933SFabiano Rosas    <td>%d secs</td>
520*212c1933SFabiano Rosas  </tr>
521*212c1933SFabiano Rosas  <tr>
522*212c1933SFabiano Rosas    <th>Pause:</th>
523*212c1933SFabiano Rosas    <td>%s</td>
524*212c1933SFabiano Rosas  </tr>
525*212c1933SFabiano Rosas  <tr>
526*212c1933SFabiano Rosas    <th>Pause iters:</th>
527*212c1933SFabiano Rosas    <td>%d</td>
528*212c1933SFabiano Rosas  </tr>
529*212c1933SFabiano Rosas  <tr>
530*212c1933SFabiano Rosas    <th>Post-copy:</th>
531*212c1933SFabiano Rosas    <td>%s</td>
532*212c1933SFabiano Rosas  </tr>
533*212c1933SFabiano Rosas  <tr>
534*212c1933SFabiano Rosas    <th>Post-copy iters:</th>
535*212c1933SFabiano Rosas    <td>%d</td>
536*212c1933SFabiano Rosas  </tr>
537*212c1933SFabiano Rosas  <tr>
538*212c1933SFabiano Rosas    <th>Auto-converge:</th>
539*212c1933SFabiano Rosas    <td>%s</td>
540*212c1933SFabiano Rosas  </tr>
541*212c1933SFabiano Rosas  <tr>
542*212c1933SFabiano Rosas    <th>Auto-converge iters:</th>
543*212c1933SFabiano Rosas    <td>%d</td>
544*212c1933SFabiano Rosas  </tr>
545*212c1933SFabiano Rosas  <tr>
546*212c1933SFabiano Rosas    <th>MT compression:</th>
547*212c1933SFabiano Rosas    <td>%s</td>
548*212c1933SFabiano Rosas  </tr>
549*212c1933SFabiano Rosas  <tr>
550*212c1933SFabiano Rosas    <th>MT compression threads:</th>
551*212c1933SFabiano Rosas    <td>%d</td>
552*212c1933SFabiano Rosas  </tr>
553*212c1933SFabiano Rosas  <tr>
554*212c1933SFabiano Rosas    <th>XBZRLE compression:</th>
555*212c1933SFabiano Rosas    <td>%s</td>
556*212c1933SFabiano Rosas  </tr>
557*212c1933SFabiano Rosas  <tr>
558*212c1933SFabiano Rosas    <th>XBZRLE compression cache:</th>
559*212c1933SFabiano Rosas    <td>%d%% of RAM</td>
560*212c1933SFabiano Rosas  </tr>
561*212c1933SFabiano Rosas""" % (scenario._downtime, scenario._bandwidth,
562*212c1933SFabiano Rosas       scenario._max_iters, scenario._max_time,
563*212c1933SFabiano Rosas       "yes" if scenario._pause else "no", scenario._pause_iters,
564*212c1933SFabiano Rosas       "yes" if scenario._post_copy else "no", scenario._post_copy_iters,
565*212c1933SFabiano Rosas       "yes" if scenario._auto_converge else "no", scenario._auto_converge_step,
566*212c1933SFabiano Rosas       "yes" if scenario._compression_mt else "no", scenario._compression_mt_threads,
567*212c1933SFabiano Rosas       "yes" if scenario._compression_xbzrle else "no", scenario._compression_xbzrle_cache))
568*212c1933SFabiano Rosas
569*212c1933SFabiano Rosas            pieces.append("""
570*212c1933SFabiano Rosas</table>
571*212c1933SFabiano Rosas""")
572*212c1933SFabiano Rosas
573*212c1933SFabiano Rosas        return "\n".join(pieces)
574*212c1933SFabiano Rosas
575*212c1933SFabiano Rosas    def _generate_style(self):
576*212c1933SFabiano Rosas        return """
577*212c1933SFabiano Rosas#report table tr th {
578*212c1933SFabiano Rosas    text-align: right;
579*212c1933SFabiano Rosas}
580*212c1933SFabiano Rosas#report table tr td {
581*212c1933SFabiano Rosas    text-align: left;
582*212c1933SFabiano Rosas}
583*212c1933SFabiano Rosas#report table tr.subhead th {
584*212c1933SFabiano Rosas    background: rgb(192, 192, 192);
585*212c1933SFabiano Rosas    text-align: center;
586*212c1933SFabiano Rosas}
587*212c1933SFabiano Rosas
588*212c1933SFabiano Rosas"""
589*212c1933SFabiano Rosas
590*212c1933SFabiano Rosas    def generate_html(self, fh):
591*212c1933SFabiano Rosas        print("""<html>
592*212c1933SFabiano Rosas  <head>
593*212c1933SFabiano Rosas    <script type="text/javascript" src="plotly.min.js">
594*212c1933SFabiano Rosas    </script>
595*212c1933SFabiano Rosas    <style type="text/css">
596*212c1933SFabiano Rosas%s
597*212c1933SFabiano Rosas    </style>
598*212c1933SFabiano Rosas    <title>Migration report</title>
599*212c1933SFabiano Rosas  </head>
600*212c1933SFabiano Rosas  <body>
601*212c1933SFabiano Rosas    <h1>Migration report</h1>
602*212c1933SFabiano Rosas    <h2>Chart summary</h2>
603*212c1933SFabiano Rosas    <div id="chart">
604*212c1933SFabiano Rosas""" % self._generate_style(), file=fh)
605*212c1933SFabiano Rosas        print(self._generate_chart(), file=fh)
606*212c1933SFabiano Rosas        print("""
607*212c1933SFabiano Rosas    </div>
608*212c1933SFabiano Rosas    <h2>Report details</h2>
609*212c1933SFabiano Rosas    <div id="report">
610*212c1933SFabiano Rosas""", file=fh)
611*212c1933SFabiano Rosas        print(self._generate_report(), file=fh)
612*212c1933SFabiano Rosas        print("""
613*212c1933SFabiano Rosas    </div>
614*212c1933SFabiano Rosas  </body>
615*212c1933SFabiano Rosas</html>
616*212c1933SFabiano Rosas""", file=fh)
617*212c1933SFabiano Rosas
618*212c1933SFabiano Rosas    def generate(self, filename):
619*212c1933SFabiano Rosas        if filename is None:
620*212c1933SFabiano Rosas            self.generate_html(sys.stdout)
621*212c1933SFabiano Rosas        else:
622*212c1933SFabiano Rosas            with open(filename, "w") as fh:
623*212c1933SFabiano Rosas                self.generate_html(fh)
624