5108 lines · 155.9 KB · Python
RawDownload
		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:			self.FindSelect()		if not len(self.rows):			return -1		return self.rows[self.pos] 	def FindThread(self):		if self.direction == 0 or self.value != self.last_value or self.pattern != self.last_pattern:			row = self.FindValue()		elif len(self.rows):			if self.direction > 0:

Lines 2001–2500 of 5108. Previous lines