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