self.data_model = SwitchGraphLegendModel(collection, region_attributes) self.model = QSortFilterProxyModel() self.model.setSourceModel(self.data_model) self.view = QTableView() self.view.setModel(self.model) self.view.setEditTriggers(QAbstractItemView.NoEditTriggers) self.view.verticalHeader().setVisible(False) self.view.sortByColumn(-1, Qt.AscendingOrder) self.view.setSortingEnabled(True) self.view.resizeColumnsToContents() self.view.resizeRowsToContents() self.vbox = VBoxLayout(self.view) self.setLayout(self.vbox) sz1 = self.view.columnWidth(0) + self.view.columnWidth(1) + self.view.columnWidth(2) + 2 sz1 = sz1 + self.view.verticalScrollBar().sizeHint().width() self.saved_size = sz1 def resizeEvent(self, event): self.saved_size = self.size().width() super(SwitchGraphLegend, self).resizeEvent(event) def Highlight(self, highlight_set): self.data_model.Highlight(highlight_set) self.update() def changeEvent(self, event): if event.type() == QEvent.FontChange: self.view.resizeRowsToContents() self.view.resizeColumnsToContents() # Need to resize rows again after column resize self.view.resizeRowsToContents() super(SwitchGraphLegend, self).changeEvent(event) # Random colour generation def RGBColourTooLight(r, g, b): if g > 230: return True if g <= 160: return False if r <= 180 and g <= 180: return False if r < 60: return False return True def GenerateColours(x): cs = [0] for i in xrange(1, x): cs.append(int((255.0 / i) + 0.5)) colours = [] for r in cs: for g in cs: for b in cs: # Exclude black and colours that look too light against a white background if (r, g, b) == (0, 0, 0) or RGBColourTooLight(r, g, b): continue colours.append(QColor(r, g, b)) return colours def GenerateNColours(n): for x in xrange(2, n + 2): colours = GenerateColours(x) if len(colours) >= n: return colours return [] def GenerateNRandomColours(n, seed): colours = GenerateNColours(n) random.seed(seed) random.shuffle(colours) return colours # Graph attributes, in particular the scale and subrange that change when zooming class GraphAttributes(): def __init__(self, scale, subrange, region_attributes, dp): self.scale = scale self.subrange = subrange self.region_attributes = region_attributes # Rounding avoids errors due to finite floating point precision self.dp = dp # data decimal places self.Update() def XToPixel(self, x): return int(round((x - self.subrange.x.lo) * self.scale.x, self.pdp.x)) def YToPixel(self, y): return int(round((y - self.subrange.y.lo) * self.scale.y, self.pdp.y)) def PixelToXRounded(self, px): return round((round(px, 0) / self.scale.x), self.dp.x) + self.subrange.x.lo def PixelToYRounded(self, py): return round((round(py, 0) / self.scale.y), self.dp.y) + self.subrange.y.lo def PixelToX(self, px): x = self.PixelToXRounded(px) if self.pdp.x == 0: rt = self.XToPixel(x) if rt > px: return x - 1 return x def PixelToY(self, py): y = self.PixelToYRounded(py) if self.pdp.y == 0: rt = self.YToPixel(y) if rt > py: return y - 1 return y def ToPDP(self, dp, scale): # Calculate pixel decimal places: # (10 ** dp) is the minimum delta in the data # scale it to get the minimum delta in pixels # log10 gives the number of decimals places negatively # subtrace 1 to divide by 10 # round to the lower negative number # change the sign to get the number of decimals positively x = math.log10((10 ** dp) * scale) if x < 0: x -= 1 x = -int(math.floor(x) - 0.1) else: x = 0 return x def Update(self): x = self.ToPDP(self.dp.x, self.scale.x) y = self.ToPDP(self.dp.y, self.scale.y) self.pdp = XY(x, y) # pixel decimal places # Switch graph splitter which divides the CPU graphs from the legend class SwitchGraphSplitter(QSplitter): def __init__(self, parent=None): super(SwitchGraphSplitter, self).__init__(parent) self.first_time = False def resizeEvent(self, ev): if self.first_time: self.first_time = False sz1 = self.widget(1).view.columnWidth(0) + self.widget(1).view.columnWidth(1) + self.widget(1).view.columnWidth(2) + 2 sz1 = sz1 + self.widget(1).view.verticalScrollBar().sizeHint().width() sz0 = self.size().width() - self.handleWidth() - sz1 self.setSizes([sz0, sz1]) elif not(self.widget(1).saved_size is None): sz1 = self.widget(1).saved_size sz0 = self.size().width() - self.handleWidth() - sz1 self.setSizes([sz0, sz1]) super(SwitchGraphSplitter, self).resizeEvent(ev) # Graph widget base class class GraphWidget(QWidget): graph_title_changed = Signal(object) def __init__(self, parent=None): super(GraphWidget, self).__init__(parent) def GraphTitleChanged(self, title): self.graph_title_changed.emit(title) def Title(self): return "" # Display time in s, ms, us or ns def ToTimeStr(val): val = Decimal(val) if val >= 1000000000: return "{} s".format((val / 1000000000).quantize(Decimal("0.000000001"))) if val >= 1000000: return "{} ms".format((val / 1000000).quantize(Decimal("0.000001"))) if val >= 1000: return "{} us".format((val / 1000).quantize(Decimal("0.001"))) return "{} ns".format(val.quantize(Decimal("1"))) # Switch (i.e. context switch i.e. Time Chart by CPU) graph widget which contains the CPU graphs and the legend and control buttons class SwitchGraphWidget(GraphWidget): def __init__(self, glb, collection, parent=None): super(SwitchGraphWidget, self).__init__(parent) self.glb = glb self.collection = collection self.back_state = [] self.forward_state = [] self.selection_state = (None, None) self.fwd_rect = None self.start_time = self.glb.StartTime(collection.machine_id) i = 0 hregions = collection.hregions.values() colours = GenerateNRandomColours(len(hregions), 1013) region_attributes = {} for hregion in hregions: if hregion.pid == 0 and hregion.tid == 0: region_attributes[hregion.key] = GraphRegionAttribute(QColor(0, 0, 0)) else: region_attributes[hregion.key] = GraphRegionAttribute(colours[i]) i = i + 1 # Default to entire range xsubrange = Subrange(0.0, float(collection.xrangehi - collection.xrangelo) + 1.0) ysubrange = Subrange(0.0, float(collection.yrangehi - collection.yrangelo) + 1.0) subrange = XY(xsubrange, ysubrange) scale = self.GetScaleForRange(subrange) self.attrs = GraphAttributes(scale, subrange, region_attributes, collection.dp) self.item = VertcalGraphSetGraphicsItem(collection, self.attrs, self, SwitchGraphGraphicsItem) self.scene = QGraphicsScene() self.scene.addItem(self.item) self.view = QGraphicsView(self.scene) self.view.centerOn(0, 0) self.view.setAlignment(Qt.AlignLeft | Qt.AlignTop) self.legend = SwitchGraphLegend(collection, region_attributes) self.splitter = SwitchGraphSplitter() self.splitter.addWidget(self.view) self.splitter.addWidget(self.legend) self.point_label = QLabel("") self.point_label.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed) self.back_button = QToolButton() self.back_button.setIcon(self.style().standardIcon(QStyle.SP_ArrowLeft)) self.back_button.setDisabled(True) self.back_button.released.connect(lambda: self.Back()) self.forward_button = QToolButton() self.forward_button.setIcon(self.style().standardIcon(QStyle.SP_ArrowRight)) self.forward_button.setDisabled(True) self.forward_button.released.connect(lambda: self.Forward()) self.zoom_button = QToolButton() self.zoom_button.setText("Zoom") self.zoom_button.setDisabled(True) self.zoom_button.released.connect(lambda: self.Zoom()) self.hbox = HBoxLayout(self.back_button, self.forward_button, self.zoom_button, self.point_label) self.vbox = VBoxLayout(self.splitter, self.hbox) self.setLayout(self.vbox) def GetScaleForRangeX(self, xsubrange): # Default graph 1000 pixels wide dflt = 1000.0 r = xsubrange.hi - xsubrange.lo return dflt / r def GetScaleForRangeY(self, ysubrange): # Default graph 50 pixels high dflt = 50.0 r = ysubrange.hi - ysubrange.lo return dflt / r def GetScaleForRange(self, subrange): # Default graph 1000 pixels wide, 50 pixels high xscale = self.GetScaleForRangeX(subrange.x) yscale = self.GetScaleForRangeY(subrange.y) return XY(xscale, yscale) def PointEvent(self, cpu, time_from, time_to, hregions): text = "CPU: " + str(cpu) time_from = time_from.quantize(Decimal(1)) rel_time_from = time_from - self.glb.StartTime(self.collection.machine_id) text = text + " Time: " + str(time_from) + " (+" + ToTimeStr(rel_time_from) + ")" self.point_label.setText(text) self.legend.Highlight(hregions) def RightClickEvent(self, cpu, hregion_times, pos): if not IsSelectable(self.glb.db, "calls", "WHERE parent_id >= 0"): return menu = QMenu(self.view) for hregion, time in hregion_times: thread_at_time = (hregion.exec_comm_id, hregion.thread_id, time) menu_text = "Show Call Tree for {} {}:{} at {}".format(hregion.comm, hregion.pid, hregion.tid, time) menu.addAction(CreateAction(menu_text, "Show Call Tree", lambda a=None, args=thread_at_time: self.RightClickSelect(args), self.view)) menu.exec_(pos) def RightClickSelect(self, args): CallTreeWindow(self.glb, self.glb.mainwindow, thread_at_time=args) def NoPointEvent(self): self.point_label.setText("") self.legend.Highlight({}) def RangeEvent(self, time_from, time_to): time_from = time_from.quantize(Decimal(1)) time_to = time_to.quantize(Decimal(1)) if time_to <= time_from: self.point_label.setText("") return rel_time_from = time_from - self.start_time rel_time_to = time_to - self.start_time text = " Time: " + str(time_from) + " (+" + ToTimeStr(rel_time_from) + ") to: " + str(time_to) + " (+" + ToTimeStr(rel_time_to) + ")" text = text + " duration: " + ToTimeStr(time_to - time_from) self.point_label.setText(text) def BackState(self): return (self.attrs.subrange, self.attrs.scale, self.selection_state, self.fwd_rect) def PushBackState(self): state = copy.deepcopy(self.BackState()) self.back_state.append(state) self.back_button.setEnabled(True) def PopBackState(self): self.attrs.subrange, self.attrs.scale, self.selection_state, self.fwd_rect = self.back_state.pop() self.attrs.Update() if not self.back_state: self.back_button.setDisabled(True) def PushForwardState(self): state = copy.deepcopy(self.BackState()) self.forward_state.append(state) self.forward_button.setEnabled(True) def PopForwardState(self): self.attrs.subrange, self.attrs.scale, self.selection_state, self.fwd_rect = self.forward_state.pop() self.attrs.Update() if not self.forward_state: self.forward_button.setDisabled(True) def Title(self): time_from = self.collection.xrangelo + Decimal(self.attrs.subrange.x.lo) time_to = self.collection.xrangelo + Decimal(self.attrs.subrange.x.hi) rel_time_from = time_from - self.start_time rel_time_to = time_to - self.start_time title = "+" + ToTimeStr(rel_time_from) + " to +" + ToTimeStr(rel_time_to) title = title + " (" + ToTimeStr(time_to - time_from) + ")" return title def Update(self): selected_subrange, selection_state = self.selection_state self.item.SetSelection(selection_state) self.item.SetBracket(self.fwd_rect) self.zoom_button.setDisabled(selected_subrange is None) self.GraphTitleChanged(self.Title()) self.item.update(self.item.boundingRect()) def Back(self): if not self.back_state: return self.PushForwardState() self.PopBackState() self.Update() def Forward(self): if not self.forward_state: return self.PushBackState() self.PopForwardState() self.Update() def SelectEvent(self, x0, x1, selection_state): if selection_state is None: selected_subrange = None else: if x1 - x0 < 1.0: x1 += 1.0 selected_subrange = Subrange(x0, x1) self.selection_state = (selected_subrange, selection_state) self.zoom_button.setDisabled(selected_subrange is None) def Zoom(self): selected_subrange, selection_state = self.selection_state if selected_subrange is None: return self.fwd_rect = selection_state self.item.SetSelection(None) self.PushBackState() self.attrs.subrange.x = selected_subrange self.forward_state = [] self.forward_button.setDisabled(True) self.selection_state = (None, None) self.fwd_rect = None self.attrs.scale.x = self.GetScaleForRangeX(self.attrs.subrange.x) self.attrs.Update() self.Update() # Slow initialization - perform non-GUI initialization in a separate thread and put up a modal message box while waiting class SlowInitClass(): def __init__(self, glb, title, init_fn): self.init_fn = init_fn self.done = False self.result = None self.msg_box = QMessageBox(glb.mainwindow) self.msg_box.setText("Initializing " + title + ". Please wait.") self.msg_box.setWindowTitle("Initializing " + title) self.msg_box.setWindowIcon(glb.mainwindow.style().standardIcon(QStyle.SP_MessageBoxInformation)) self.init_thread = Thread(self.ThreadFn, glb) self.init_thread.done.connect(lambda: self.Done(), Qt.QueuedConnection) self.init_thread.start() def Done(self): self.msg_box.done(0) def ThreadFn(self, glb): conn_name = "SlowInitClass" + str(os.getpid()) db, dbname = glb.dbref.Open(conn_name) self.result = self.init_fn(db) self.done = True return (True, 0) def Result(self): while not self.done: self.msg_box.exec_() self.init_thread.wait() return self.result def SlowInit(glb, title, init_fn): init = SlowInitClass(glb, title, init_fn) return init.Result() # Time chart by CPU window class TimeChartByCPUWindow(QMdiSubWindow): def __init__(self, glb, parent=None): super(TimeChartByCPUWindow, self).__init__(parent) self.glb = glb self.machine_id = glb.HostMachineId() self.collection_name = "SwitchGraphDataCollection " + str(self.machine_id) collection = LookupModel(self.collection_name) if collection is None: collection = SlowInit(glb, "Time Chart", self.Init) self.widget = SwitchGraphWidget(glb, collection, self) self.view = self.widget self.base_title = "Time Chart by CPU" self.setWindowTitle(self.base_title + self.widget.Title()) self.widget.graph_title_changed.connect(self.GraphTitleChanged) self.setWidget(self.widget) AddSubWindow(glb.mainwindow.mdi_area, self, self.windowTitle()) def Init(self, db): return LookupCreateModel(self.collection_name, lambda : SwitchGraphDataCollection(self.glb, db, self.machine_id)) def GraphTitleChanged(self, title): self.setWindowTitle(self.base_title + " : " + title) # Child data item finder class ChildDataItemFinder(): def __init__(self, root): self.root = root self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (None,) * 5 self.rows = [] self.pos = 0 def FindSelect(self): self.rows = [] if self.pattern: pattern = re.compile(self.value) for child in self.root.child_items: for column_data in child.data: if re.search(pattern, str(column_data)) is not None: self.rows.append(child.row) break else: for child in self.root.child_items: for column_data in child.data: if self.value in str(column_data): self.rows.append(child.row) break def FindValue(self): self.pos = 0 if self.last_value != self.value or self.pattern != self.last_pattern:Lines 2001–2500 of 5030. Previous lines Load more
self.data_model = SwitchGraphLegendModel(collection, region_attributes) self.model = QSortFilterProxyModel() self.model.setSourceModel(self.data_model) self.view = QTableView() self.view.setModel(self.model) self.view.setEditTriggers(QAbstractItemView.NoEditTriggers) self.view.verticalHeader().setVisible(False) self.view.sortByColumn(-1, Qt.AscendingOrder) self.view.setSortingEnabled(True) self.view.resizeColumnsToContents() self.view.resizeRowsToContents() self.vbox = VBoxLayout(self.view) self.setLayout(self.vbox) sz1 = self.view.columnWidth(0) + self.view.columnWidth(1) + self.view.columnWidth(2) + 2 sz1 = sz1 + self.view.verticalScrollBar().sizeHint().width() self.saved_size = sz1 def resizeEvent(self, event): self.saved_size = self.size().width() super(SwitchGraphLegend, self).resizeEvent(event) def Highlight(self, highlight_set): self.data_model.Highlight(highlight_set) self.update() def changeEvent(self, event): if event.type() == QEvent.FontChange: self.view.resizeRowsToContents() self.view.resizeColumnsToContents() # Need to resize rows again after column resize self.view.resizeRowsToContents() super(SwitchGraphLegend, self).changeEvent(event) # Random colour generation def RGBColourTooLight(r, g, b): if g > 230: return True if g <= 160: return False if r <= 180 and g <= 180: return False if r < 60: return False return True def GenerateColours(x): cs = [0] for i in xrange(1, x): cs.append(int((255.0 / i) + 0.5)) colours = [] for r in cs: for g in cs: for b in cs: # Exclude black and colours that look too light against a white background if (r, g, b) == (0, 0, 0) or RGBColourTooLight(r, g, b): continue colours.append(QColor(r, g, b)) return colours def GenerateNColours(n): for x in xrange(2, n + 2): colours = GenerateColours(x) if len(colours) >= n: return colours return [] def GenerateNRandomColours(n, seed): colours = GenerateNColours(n) random.seed(seed) random.shuffle(colours) return colours # Graph attributes, in particular the scale and subrange that change when zooming class GraphAttributes(): def __init__(self, scale, subrange, region_attributes, dp): self.scale = scale self.subrange = subrange self.region_attributes = region_attributes # Rounding avoids errors due to finite floating point precision self.dp = dp # data decimal places self.Update() def XToPixel(self, x): return int(round((x - self.subrange.x.lo) * self.scale.x, self.pdp.x)) def YToPixel(self, y): return int(round((y - self.subrange.y.lo) * self.scale.y, self.pdp.y)) def PixelToXRounded(self, px): return round((round(px, 0) / self.scale.x), self.dp.x) + self.subrange.x.lo def PixelToYRounded(self, py): return round((round(py, 0) / self.scale.y), self.dp.y) + self.subrange.y.lo def PixelToX(self, px): x = self.PixelToXRounded(px) if self.pdp.x == 0: rt = self.XToPixel(x) if rt > px: return x - 1 return x def PixelToY(self, py): y = self.PixelToYRounded(py) if self.pdp.y == 0: rt = self.YToPixel(y) if rt > py: return y - 1 return y def ToPDP(self, dp, scale): # Calculate pixel decimal places: # (10 ** dp) is the minimum delta in the data # scale it to get the minimum delta in pixels # log10 gives the number of decimals places negatively # subtrace 1 to divide by 10 # round to the lower negative number # change the sign to get the number of decimals positively x = math.log10((10 ** dp) * scale) if x < 0: x -= 1 x = -int(math.floor(x) - 0.1) else: x = 0 return x def Update(self): x = self.ToPDP(self.dp.x, self.scale.x) y = self.ToPDP(self.dp.y, self.scale.y) self.pdp = XY(x, y) # pixel decimal places # Switch graph splitter which divides the CPU graphs from the legend class SwitchGraphSplitter(QSplitter): def __init__(self, parent=None): super(SwitchGraphSplitter, self).__init__(parent) self.first_time = False def resizeEvent(self, ev): if self.first_time: self.first_time = False sz1 = self.widget(1).view.columnWidth(0) + self.widget(1).view.columnWidth(1) + self.widget(1).view.columnWidth(2) + 2 sz1 = sz1 + self.widget(1).view.verticalScrollBar().sizeHint().width() sz0 = self.size().width() - self.handleWidth() - sz1 self.setSizes([sz0, sz1]) elif not(self.widget(1).saved_size is None): sz1 = self.widget(1).saved_size sz0 = self.size().width() - self.handleWidth() - sz1 self.setSizes([sz0, sz1]) super(SwitchGraphSplitter, self).resizeEvent(ev) # Graph widget base class class GraphWidget(QWidget): graph_title_changed = Signal(object) def __init__(self, parent=None): super(GraphWidget, self).__init__(parent) def GraphTitleChanged(self, title): self.graph_title_changed.emit(title) def Title(self): return "" # Display time in s, ms, us or ns def ToTimeStr(val): val = Decimal(val) if val >= 1000000000: return "{} s".format((val / 1000000000).quantize(Decimal("0.000000001"))) if val >= 1000000: return "{} ms".format((val / 1000000).quantize(Decimal("0.000001"))) if val >= 1000: return "{} us".format((val / 1000).quantize(Decimal("0.001"))) return "{} ns".format(val.quantize(Decimal("1"))) # Switch (i.e. context switch i.e. Time Chart by CPU) graph widget which contains the CPU graphs and the legend and control buttons class SwitchGraphWidget(GraphWidget): def __init__(self, glb, collection, parent=None): super(SwitchGraphWidget, self).__init__(parent) self.glb = glb self.collection = collection self.back_state = [] self.forward_state = [] self.selection_state = (None, None) self.fwd_rect = None self.start_time = self.glb.StartTime(collection.machine_id) i = 0 hregions = collection.hregions.values() colours = GenerateNRandomColours(len(hregions), 1013) region_attributes = {} for hregion in hregions: if hregion.pid == 0 and hregion.tid == 0: region_attributes[hregion.key] = GraphRegionAttribute(QColor(0, 0, 0)) else: region_attributes[hregion.key] = GraphRegionAttribute(colours[i]) i = i + 1 # Default to entire range xsubrange = Subrange(0.0, float(collection.xrangehi - collection.xrangelo) + 1.0) ysubrange = Subrange(0.0, float(collection.yrangehi - collection.yrangelo) + 1.0) subrange = XY(xsubrange, ysubrange) scale = self.GetScaleForRange(subrange) self.attrs = GraphAttributes(scale, subrange, region_attributes, collection.dp) self.item = VertcalGraphSetGraphicsItem(collection, self.attrs, self, SwitchGraphGraphicsItem) self.scene = QGraphicsScene() self.scene.addItem(self.item) self.view = QGraphicsView(self.scene) self.view.centerOn(0, 0) self.view.setAlignment(Qt.AlignLeft | Qt.AlignTop) self.legend = SwitchGraphLegend(collection, region_attributes) self.splitter = SwitchGraphSplitter() self.splitter.addWidget(self.view) self.splitter.addWidget(self.legend) self.point_label = QLabel("") self.point_label.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed) self.back_button = QToolButton() self.back_button.setIcon(self.style().standardIcon(QStyle.SP_ArrowLeft)) self.back_button.setDisabled(True) self.back_button.released.connect(lambda: self.Back()) self.forward_button = QToolButton() self.forward_button.setIcon(self.style().standardIcon(QStyle.SP_ArrowRight)) self.forward_button.setDisabled(True) self.forward_button.released.connect(lambda: self.Forward()) self.zoom_button = QToolButton() self.zoom_button.setText("Zoom") self.zoom_button.setDisabled(True) self.zoom_button.released.connect(lambda: self.Zoom()) self.hbox = HBoxLayout(self.back_button, self.forward_button, self.zoom_button, self.point_label) self.vbox = VBoxLayout(self.splitter, self.hbox) self.setLayout(self.vbox) def GetScaleForRangeX(self, xsubrange): # Default graph 1000 pixels wide dflt = 1000.0 r = xsubrange.hi - xsubrange.lo return dflt / r def GetScaleForRangeY(self, ysubrange): # Default graph 50 pixels high dflt = 50.0 r = ysubrange.hi - ysubrange.lo return dflt / r def GetScaleForRange(self, subrange): # Default graph 1000 pixels wide, 50 pixels high xscale = self.GetScaleForRangeX(subrange.x) yscale = self.GetScaleForRangeY(subrange.y) return XY(xscale, yscale) def PointEvent(self, cpu, time_from, time_to, hregions): text = "CPU: " + str(cpu) time_from = time_from.quantize(Decimal(1)) rel_time_from = time_from - self.glb.StartTime(self.collection.machine_id) text = text + " Time: " + str(time_from) + " (+" + ToTimeStr(rel_time_from) + ")" self.point_label.setText(text) self.legend.Highlight(hregions) def RightClickEvent(self, cpu, hregion_times, pos): if not IsSelectable(self.glb.db, "calls", "WHERE parent_id >= 0"): return menu = QMenu(self.view) for hregion, time in hregion_times: thread_at_time = (hregion.exec_comm_id, hregion.thread_id, time) menu_text = "Show Call Tree for {} {}:{} at {}".format(hregion.comm, hregion.pid, hregion.tid, time) menu.addAction(CreateAction(menu_text, "Show Call Tree", lambda a=None, args=thread_at_time: self.RightClickSelect(args), self.view)) menu.exec_(pos) def RightClickSelect(self, args): CallTreeWindow(self.glb, self.glb.mainwindow, thread_at_time=args) def NoPointEvent(self): self.point_label.setText("") self.legend.Highlight({}) def RangeEvent(self, time_from, time_to): time_from = time_from.quantize(Decimal(1)) time_to = time_to.quantize(Decimal(1)) if time_to <= time_from: self.point_label.setText("") return rel_time_from = time_from - self.start_time rel_time_to = time_to - self.start_time text = " Time: " + str(time_from) + " (+" + ToTimeStr(rel_time_from) + ") to: " + str(time_to) + " (+" + ToTimeStr(rel_time_to) + ")" text = text + " duration: " + ToTimeStr(time_to - time_from) self.point_label.setText(text) def BackState(self): return (self.attrs.subrange, self.attrs.scale, self.selection_state, self.fwd_rect) def PushBackState(self): state = copy.deepcopy(self.BackState()) self.back_state.append(state) self.back_button.setEnabled(True) def PopBackState(self): self.attrs.subrange, self.attrs.scale, self.selection_state, self.fwd_rect = self.back_state.pop() self.attrs.Update() if not self.back_state: self.back_button.setDisabled(True) def PushForwardState(self): state = copy.deepcopy(self.BackState()) self.forward_state.append(state) self.forward_button.setEnabled(True) def PopForwardState(self): self.attrs.subrange, self.attrs.scale, self.selection_state, self.fwd_rect = self.forward_state.pop() self.attrs.Update() if not self.forward_state: self.forward_button.setDisabled(True) def Title(self): time_from = self.collection.xrangelo + Decimal(self.attrs.subrange.x.lo) time_to = self.collection.xrangelo + Decimal(self.attrs.subrange.x.hi) rel_time_from = time_from - self.start_time rel_time_to = time_to - self.start_time title = "+" + ToTimeStr(rel_time_from) + " to +" + ToTimeStr(rel_time_to) title = title + " (" + ToTimeStr(time_to - time_from) + ")" return title def Update(self): selected_subrange, selection_state = self.selection_state self.item.SetSelection(selection_state) self.item.SetBracket(self.fwd_rect) self.zoom_button.setDisabled(selected_subrange is None) self.GraphTitleChanged(self.Title()) self.item.update(self.item.boundingRect()) def Back(self): if not self.back_state: return self.PushForwardState() self.PopBackState() self.Update() def Forward(self): if not self.forward_state: return self.PushBackState() self.PopForwardState() self.Update() def SelectEvent(self, x0, x1, selection_state): if selection_state is None: selected_subrange = None else: if x1 - x0 < 1.0: x1 += 1.0 selected_subrange = Subrange(x0, x1) self.selection_state = (selected_subrange, selection_state) self.zoom_button.setDisabled(selected_subrange is None) def Zoom(self): selected_subrange, selection_state = self.selection_state if selected_subrange is None: return self.fwd_rect = selection_state self.item.SetSelection(None) self.PushBackState() self.attrs.subrange.x = selected_subrange self.forward_state = [] self.forward_button.setDisabled(True) self.selection_state = (None, None) self.fwd_rect = None self.attrs.scale.x = self.GetScaleForRangeX(self.attrs.subrange.x) self.attrs.Update() self.Update() # Slow initialization - perform non-GUI initialization in a separate thread and put up a modal message box while waiting class SlowInitClass(): def __init__(self, glb, title, init_fn): self.init_fn = init_fn self.done = False self.result = None self.msg_box = QMessageBox(glb.mainwindow) self.msg_box.setText("Initializing " + title + ". Please wait.") self.msg_box.setWindowTitle("Initializing " + title) self.msg_box.setWindowIcon(glb.mainwindow.style().standardIcon(QStyle.SP_MessageBoxInformation)) self.init_thread = Thread(self.ThreadFn, glb) self.init_thread.done.connect(lambda: self.Done(), Qt.QueuedConnection) self.init_thread.start() def Done(self): self.msg_box.done(0) def ThreadFn(self, glb): conn_name = "SlowInitClass" + str(os.getpid()) db, dbname = glb.dbref.Open(conn_name) self.result = self.init_fn(db) self.done = True return (True, 0) def Result(self): while not self.done: self.msg_box.exec_() self.init_thread.wait() return self.result def SlowInit(glb, title, init_fn): init = SlowInitClass(glb, title, init_fn) return init.Result() # Time chart by CPU window class TimeChartByCPUWindow(QMdiSubWindow): def __init__(self, glb, parent=None): super(TimeChartByCPUWindow, self).__init__(parent) self.glb = glb self.machine_id = glb.HostMachineId() self.collection_name = "SwitchGraphDataCollection " + str(self.machine_id) collection = LookupModel(self.collection_name) if collection is None: collection = SlowInit(glb, "Time Chart", self.Init) self.widget = SwitchGraphWidget(glb, collection, self) self.view = self.widget self.base_title = "Time Chart by CPU" self.setWindowTitle(self.base_title + self.widget.Title()) self.widget.graph_title_changed.connect(self.GraphTitleChanged) self.setWidget(self.widget) AddSubWindow(glb.mainwindow.mdi_area, self, self.windowTitle()) def Init(self, db): return LookupCreateModel(self.collection_name, lambda : SwitchGraphDataCollection(self.glb, db, self.machine_id)) def GraphTitleChanged(self, title): self.setWindowTitle(self.base_title + " : " + title) # Child data item finder class ChildDataItemFinder(): def __init__(self, root): self.root = root self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (None,) * 5 self.rows = [] self.pos = 0 def FindSelect(self): self.rows = [] if self.pattern: pattern = re.compile(self.value) for child in self.root.child_items: for column_data in child.data: if re.search(pattern, str(column_data)) is not None: self.rows.append(child.row) break else: for child in self.root.child_items: for column_data in child.data: if self.value in str(column_data): self.rows.append(child.row) break def FindValue(self): self.pos = 0 if self.last_value != self.value or self.pattern != self.last_pattern: