History

perf scripts python: exported-sql-viewer.py: Add Time chart by CPU

Add a time chart based on context switch information. Context switch information was added to the database export fairly recently, so the chart menu option will only appear if context switch information is in the database. Refer to the Exported SQL Viewer Help option for more information about the chart.

Adrian Hunter committed 7 years ago

Commit b3700f21 parent e69d5df1 file · +1331 −2Browse files

Showing the changes to tools/perf/scripts/python (1 of 1 file). Show all

Unified · Split

tools/perf/scripts/python/exported-sql-viewer.py+1331−2
@@ -105,6 +105,9 @@ except ImportError: · 104 unmodified lines
105 glb_nsz = 16105 glb_nsz = 16
106import re106import re
107import os107import os
108import random
109import copy
110import math
108 111
109pyside_version_1 = True112pyside_version_1 = True
110if not "--pyside-version-1" in sys.argv:113if not "--pyside-version-1" in sys.argv:
@@ -1154,6 +1157,1301 @@ class CallTreeWindow(TreeWindowBase): · 1043 unmodified lines
1154 self.view.setCurrentIndex(last_child)1157 self.view.setCurrentIndex(last_child)
1155 parent = last_child1158 parent = last_child
1156 1159
1160# ExecComm() gets the comm_id of the command string that was set when the process exec'd i.e. the program name
1161
1162def ExecComm(db, thread_id, time):
1163 query = QSqlQuery(db)
1164 QueryExec(query, "SELECT comm_threads.comm_id, comms.c_time, comms.exec_flag"
1165 " FROM comm_threads"
1166 " INNER JOIN comms ON comms.id = comm_threads.comm_id"
1167 " WHERE comm_threads.thread_id = " + str(thread_id) +
1168 " ORDER BY comms.c_time, comms.id")
1169 first = None
1170 last = None
1171 while query.next():
1172 if first is None:
1173 first = query.value(0)
1174 if query.value(2) and Decimal(query.value(1)) <= Decimal(time):
1175 last = query.value(0)
1176 if not(last is None):
1177 return last
1178 return first
1179
1180# Container for (x, y) data
1181
1182class XY():
1183 def __init__(self, x=0, y=0):
1184 self.x = x
1185 self.y = y
1186
1187 def __str__(self):
1188 return "XY({}, {})".format(str(self.x), str(self.y))
1189
1190# Container for sub-range data
1191
1192class Subrange():
1193 def __init__(self, lo=0, hi=0):
1194 self.lo = lo
1195 self.hi = hi
1196
1197 def __str__(self):
1198 return "Subrange({}, {})".format(str(self.lo), str(self.hi))
1199
1200# Graph data region base class
1201
1202class GraphDataRegion(object):
1203
1204 def __init__(self, key, title = "", ordinal = ""):
1205 self.key = key
1206 self.title = title
1207 self.ordinal = ordinal
1208
1209# Function to sort GraphDataRegion
1210
1211def GraphDataRegionOrdinal(data_region):
1212 return data_region.ordinal
1213
1214# Attributes for a graph region
1215
1216class GraphRegionAttribute():
1217
1218 def __init__(self, colour):
1219 self.colour = colour
1220
1221# Switch graph data region represents a task
1222
1223class SwitchGraphDataRegion(GraphDataRegion):
1224
1225 def __init__(self, key, exec_comm_id, pid, tid, comm, thread_id, comm_id):
1226 super(SwitchGraphDataRegion, self).__init__(key)
1227
1228 self.title = str(pid) + " / " + str(tid) + " " + comm
1229 # Order graph legend within exec comm by pid / tid / time
1230 self.ordinal = str(pid).rjust(16) + str(exec_comm_id).rjust(8) + str(tid).rjust(16)
1231 self.exec_comm_id = exec_comm_id
1232 self.pid = pid
1233 self.tid = tid
1234 self.comm = comm
1235 self.thread_id = thread_id
1236 self.comm_id = comm_id
1237
1238# Graph data point
1239
1240class GraphDataPoint():
1241
1242 def __init__(self, data, index, x, y, altx=None, alty=None, hregion=None, vregion=None):
1243 self.data = data
1244 self.index = index
1245 self.x = x
1246 self.y = y
1247 self.altx = altx
1248 self.alty = alty
1249 self.hregion = hregion
1250 self.vregion = vregion
1251
1252# Graph data (single graph) base class
1253
1254class GraphData(object):
1255
1256 def __init__(self, collection, xbase=Decimal(0), ybase=Decimal(0)):
1257 self.collection = collection
1258 self.points = []
1259 self.xbase = xbase
1260 self.ybase = ybase
1261 self.title = ""
1262
1263 def AddPoint(self, x, y, altx=None, alty=None, hregion=None, vregion=None):
1264 index = len(self.points)
1265
1266 x = float(Decimal(x) - self.xbase)
1267 y = float(Decimal(y) - self.ybase)
1268
1269 self.points.append(GraphDataPoint(self, index, x, y, altx, alty, hregion, vregion))
1270
1271 def XToData(self, x):
1272 return Decimal(x) + self.xbase
1273
1274 def YToData(self, y):
1275 return Decimal(y) + self.ybase
1276
1277# Switch graph data (for one CPU)
1278
1279class SwitchGraphData(GraphData):
1280
1281 def __init__(self, db, collection, cpu, xbase):
1282 super(SwitchGraphData, self).__init__(collection, xbase)
1283
1284 self.cpu = cpu
1285 self.title = "CPU " + str(cpu)
1286 self.SelectSwitches(db)
1287
1288 def SelectComms(self, db, thread_id, last_comm_id, start_time, end_time):
1289 query = QSqlQuery(db)
1290 QueryExec(query, "SELECT id, c_time"
1291 " FROM comms"
1292 " WHERE c_thread_id = " + str(thread_id) +
1293 " AND exec_flag = TRUE"
1294 " AND c_time >= " + str(start_time) +
1295 " AND c_time <= " + str(end_time) +
1296 " ORDER BY c_time, id")
1297 while query.next():
1298 comm_id = query.value(0)
1299 if comm_id == last_comm_id:
1300 continue
1301 time = query.value(1)
1302 hregion = self.HRegion(db, thread_id, comm_id, time)
1303 self.AddPoint(time, 1000, None, None, hregion)
1304
1305 def SelectSwitches(self, db):
1306 last_time = None
1307 last_comm_id = None
1308 last_thread_id = None
1309 query = QSqlQuery(db)
1310 QueryExec(query, "SELECT time, thread_out_id, thread_in_id, comm_out_id, comm_in_id, flags"
1311 " FROM context_switches"
1312 " WHERE machine_id = " + str(self.collection.machine_id) +
1313 " AND cpu = " + str(self.cpu) +
1314 " ORDER BY time, id")
1315 while query.next():
1316 flags = int(query.value(5))
1317 if flags & 1:
1318 # Schedule-out: detect and add exec's
1319 if last_thread_id == query.value(1) and last_comm_id is not None and last_comm_id != query.value(3):
1320 self.SelectComms(db, last_thread_id, last_comm_id, last_time, query.value(0))
1321 continue
1322 # Schedule-in: add data point
1323 if len(self.points) == 0:
1324 start_time = self.collection.glb.StartTime(self.collection.machine_id)
1325 hregion = self.HRegion(db, query.value(1), query.value(3), start_time)
1326 self.AddPoint(start_time, 1000, None, None, hregion)
1327 time = query.value(0)
1328 comm_id = query.value(4)
1329 thread_id = query.value(2)
1330 hregion = self.HRegion(db, thread_id, comm_id, time)
1331 self.AddPoint(time, 1000, None, None, hregion)
1332 last_time = time
1333 last_comm_id = comm_id
1334 last_thread_id = thread_id
1335
1336 def NewHRegion(self, db, key, thread_id, comm_id, time):
1337 exec_comm_id = ExecComm(db, thread_id, time)
1338 query = QSqlQuery(db)
1339 QueryExec(query, "SELECT pid, tid FROM threads WHERE id = " + str(thread_id))
1340 if query.next():
1341 pid = query.value(0)
1342 tid = query.value(1)
1343 else:
1344 pid = -1
1345 tid = -1
1346 query = QSqlQuery(db)
1347 QueryExec(query, "SELECT comm FROM comms WHERE id = " + str(comm_id))
1348 if query.next():
1349 comm = query.value(0)
1350 else:
1351 comm = ""
1352 return SwitchGraphDataRegion(key, exec_comm_id, pid, tid, comm, thread_id, comm_id)
1353
1354 def HRegion(self, db, thread_id, comm_id, time):
1355 key = str(thread_id) + ":" + str(comm_id)
1356 hregion = self.collection.LookupHRegion(key)
1357 if hregion is None:
1358 hregion = self.NewHRegion(db, key, thread_id, comm_id, time)
1359 self.collection.AddHRegion(key, hregion)
1360 return hregion
1361
1362# Graph data collection (multiple related graphs) base class
1363
1364class GraphDataCollection(object):
1365
1366 def __init__(self, glb):
1367 self.glb = glb
1368 self.data = []
1369 self.hregions = {}
1370 self.xrangelo = None
1371 self.xrangehi = None
1372 self.yrangelo = None
1373 self.yrangehi = None
1374 self.dp = XY(0, 0)
1375
1376 def AddGraphData(self, data):
1377 self.data.append(data)
1378
1379 def LookupHRegion(self, key):
1380 if key in self.hregions:
1381 return self.hregions[key]
1382 return None
1383
1384 def AddHRegion(self, key, hregion):
1385 self.hregions[key] = hregion
1386
1387# Switch graph data collection (SwitchGraphData for each CPU)
1388
1389class SwitchGraphDataCollection(GraphDataCollection):
1390
1391 def __init__(self, glb, db, machine_id):
1392 super(SwitchGraphDataCollection, self).__init__(glb)
1393
1394 self.machine_id = machine_id
1395 self.cpus = self.SelectCPUs(db)
1396
1397 self.xrangelo = glb.StartTime(machine_id)
1398 self.xrangehi = glb.FinishTime(machine_id)
1399
1400 self.yrangelo = Decimal(0)
1401 self.yrangehi = Decimal(1000)
1402
1403 for cpu in self.cpus:
1404 self.AddGraphData(SwitchGraphData(db, self, cpu, self.xrangelo))
1405
1406 def SelectCPUs(self, db):
1407 cpus = []
1408 query = QSqlQuery(db)
1409 QueryExec(query, "SELECT DISTINCT cpu"
1410 " FROM context_switches"
1411 " WHERE machine_id = " + str(self.machine_id))
1412 while query.next():
1413 cpus.append(int(query.value(0)))
1414 return sorted(cpus)
1415
1416# Switch graph data graphics item displays the graphed data
1417
1418class SwitchGraphDataGraphicsItem(QGraphicsItem):
1419
1420 def __init__(self, data, graph_width, graph_height, attrs, event_handler, parent=None):
1421 super(SwitchGraphDataGraphicsItem, self).__init__(parent)
1422
1423 self.data = data
1424 self.graph_width = graph_width
1425 self.graph_height = graph_height
1426 self.attrs = attrs
1427 self.event_handler = event_handler
1428 self.setAcceptHoverEvents(True)
1429
1430 def boundingRect(self):
1431 return QRectF(0, 0, self.graph_width, self.graph_height)
1432
1433 def PaintPoint(self, painter, last, x):
1434 if not(last is None or last.hregion.pid == 0 or x < self.attrs.subrange.x.lo):
1435 if last.x < self.attrs.subrange.x.lo:
1436 x0 = self.attrs.subrange.x.lo
1437 else:
1438 x0 = last.x
1439 if x > self.attrs.subrange.x.hi:
1440 x1 = self.attrs.subrange.x.hi
1441 else:
1442 x1 = x - 1
1443 x0 = self.attrs.XToPixel(x0)
1444 x1 = self.attrs.XToPixel(x1)
1445
1446 y0 = self.attrs.YToPixel(last.y)
1447
1448 colour = self.attrs.region_attributes[last.hregion.key].colour
1449
1450 width = x1 - x0 + 1
1451 if width < 2:
1452 painter.setPen(colour)
1453 painter.drawLine(x0, self.graph_height - y0, x0, self.graph_height)
1454 else:
1455 painter.fillRect(x0, self.graph_height - y0, width, self.graph_height - 1, colour)
1456
1457 def paint(self, painter, option, widget):
1458 last = None
1459 for point in self.data.points:
1460 self.PaintPoint(painter, last, point.x)
1461 if point.x > self.attrs.subrange.x.hi:
1462 break;
1463 last = point
1464 self.PaintPoint(painter, last, self.attrs.subrange.x.hi + 1)
1465
1466 def BinarySearchPoint(self, target):
1467 lower_pos = 0
1468 higher_pos = len(self.data.points)
1469 while True:
1470 pos = int((lower_pos + higher_pos) / 2)
1471 val = self.data.points[pos].x
1472 if target >= val:
1473 lower_pos = pos
1474 else:
1475 higher_pos = pos
1476 if higher_pos <= lower_pos + 1:
1477 return lower_pos
1478
1479 def XPixelToData(self, x):
1480 x = self.attrs.PixelToX(x)
1481 if x < self.data.points[0].x:
1482 x = 0
1483 pos = 0
1484 low = True
1485 else:
1486 pos = self.BinarySearchPoint(x)
1487 low = False
1488 return (low, pos, self.data.XToData(x))
1489
1490 def EventToData(self, event):
1491 no_data = (None,) * 4
1492 if len(self.data.points) < 1:
1493 return no_data
1494 x = event.pos().x()
1495 if x < 0:
1496 return no_data
1497 low0, pos0, time_from = self.XPixelToData(x)
1498 low1, pos1, time_to = self.XPixelToData(x + 1)
1499 hregions = set()
1500 hregion_times = []
1501 if not low1:
1502 for i in xrange(pos0, pos1 + 1):
1503 hregion = self.data.points[i].hregion
1504 hregions.add(hregion)
1505 if i == pos0:
1506 time = time_from
1507 else:
1508 time = self.data.XToData(self.data.points[i].x)
1509 hregion_times.append((hregion, time))
1510 return (time_from, time_to, hregions, hregion_times)
1511
1512 def hoverMoveEvent(self, event):
1513 time_from, time_to, hregions, hregion_times = self.EventToData(event)
1514 if time_from is not None:
1515 self.event_handler.PointEvent(self.data.cpu, time_from, time_to, hregions)
1516
1517 def hoverLeaveEvent(self, event):
1518 self.event_handler.NoPointEvent()
1519
1520 def mousePressEvent(self, event):
1521 if event.button() != Qt.RightButton:
1522 super(SwitchGraphDataGraphicsItem, self).mousePressEvent(event)
1523 return
1524 time_from, time_to, hregions, hregion_times = self.EventToData(event)
1525 if hregion_times:
1526 self.event_handler.RightClickEvent(self.data.cpu, hregion_times, event.screenPos())
1527
1528# X-axis graphics item
1529
1530class XAxisGraphicsItem(QGraphicsItem):
1531
1532 def __init__(self, width, parent=None):
1533 super(XAxisGraphicsItem, self).__init__(parent)
1534
1535 self.width = width
1536 self.max_mark_sz = 4
1537 self.height = self.max_mark_sz + 1
1538
1539 def boundingRect(self):
1540 return QRectF(0, 0, self.width, self.height)
1541
1542 def Step(self):
1543 attrs = self.parentItem().attrs
1544 subrange = attrs.subrange.x
1545 t = subrange.hi - subrange.lo
1546 s = (3.0 * t) / self.width
1547 n = 1.0
1548 while s > n:
1549 n = n * 10.0
1550 return n
1551
1552 def PaintMarks(self, painter, at_y, lo, hi, step, i):
1553 attrs = self.parentItem().attrs
1554 x = lo
1555 while x <= hi:
1556 xp = attrs.XToPixel(x)
1557 if i % 10:
1558 if i % 5:
1559 sz = 1
1560 else:
1561 sz = 2
1562 else:
1563 sz = self.max_mark_sz
1564 i = 0
1565 painter.drawLine(xp, at_y, xp, at_y + sz)
1566 x += step
1567 i += 1
1568
1569 def paint(self, painter, option, widget):
1570 # Using QPainter::drawLine(int x1, int y1, int x2, int y2) so x2 = width -1
1571 painter.drawLine(0, 0, self.width - 1, 0)
1572 n = self.Step()
1573 attrs = self.parentItem().attrs
1574 subrange = attrs.subrange.x
1575 if subrange.lo:
1576 x_offset = n - (subrange.lo % n)
1577 else:
1578 x_offset = 0.0
1579 x = subrange.lo + x_offset
1580 i = (x / n) % 10
1581 self.PaintMarks(painter, 0, x, subrange.hi, n, i)
1582
1583 def ScaleDimensions(self):
1584 n = self.Step()
1585 attrs = self.parentItem().attrs
1586 lo = attrs.subrange.x.lo
1587 hi = (n * 10.0) + lo
1588 width = attrs.XToPixel(hi)
1589 if width > 500:
1590 width = 0
1591 return (n, lo, hi, width)
1592
1593 def PaintScale(self, painter, at_x, at_y):
1594 n, lo, hi, width = self.ScaleDimensions()
1595 if not width:
1596 return
1597 painter.drawLine(at_x, at_y, at_x + width, at_y)
1598 self.PaintMarks(painter, at_y, lo, hi, n, 0)
1599
1600 def ScaleWidth(self):
1601 n, lo, hi, width = self.ScaleDimensions()
1602 return width
1603
1604 def ScaleHeight(self):
1605 return self.height
1606
1607 def ScaleUnit(self):
1608 return self.Step() * 10
1609
1610# Scale graphics item base class
1611
1612class ScaleGraphicsItem(QGraphicsItem):
1613
1614 def __init__(self, axis, parent=None):
1615 super(ScaleGraphicsItem, self).__init__(parent)
1616 self.axis = axis
1617
1618 def boundingRect(self):
1619 scale_width = self.axis.ScaleWidth()
1620 if not scale_width:
1621 return QRectF()
1622 return QRectF(0, 0, self.axis.ScaleWidth() + 100, self.axis.ScaleHeight())
1623
1624 def paint(self, painter, option, widget):
1625 scale_width = self.axis.ScaleWidth()
1626 if not scale_width:
1627 return
1628 self.axis.PaintScale(painter, 0, 5)
1629 x = scale_width + 4
1630 painter.drawText(QPointF(x, 10), self.Text())
1631
1632 def Unit(self):
1633 return self.axis.ScaleUnit()
1634
1635 def Text(self):
1636 return ""
1637
1638# Switch graph scale graphics item
1639
1640class SwitchScaleGraphicsItem(ScaleGraphicsItem):
1641
1642 def __init__(self, axis, parent=None):
1643 super(SwitchScaleGraphicsItem, self).__init__(axis, parent)
1644
1645 def Text(self):
1646 unit = self.Unit()
1647 if unit >= 1000000000:
1648 unit = int(unit / 1000000000)
1649 us = "s"
1650 elif unit >= 1000000:
1651 unit = int(unit / 1000000)
1652 us = "ms"
1653 elif unit >= 1000:
1654 unit = int(unit / 1000)
1655 us = "us"
1656 else:
1657 unit = int(unit)
1658 us = "ns"
1659 return " = " + str(unit) + " " + us
1660
1661# Switch graph graphics item contains graph title, scale, x/y-axis, and the graphed data
1662
1663class SwitchGraphGraphicsItem(QGraphicsItem):
1664
1665 def __init__(self, collection, data, attrs, event_handler, first, parent=None):
1666 super(SwitchGraphGraphicsItem, self).__init__(parent)
1667 self.collection = collection
1668 self.data = data
1669 self.attrs = attrs
1670 self.event_handler = event_handler
1671
1672 margin = 20
1673 title_width = 50
1674
1675 self.title_graphics = QGraphicsSimpleTextItem(data.title, self)
1676
1677 self.title_graphics.setPos(margin, margin)
1678 graph_width = attrs.XToPixel(attrs.subrange.x.hi) + 1
1679 graph_height = attrs.YToPixel(attrs.subrange.y.hi) + 1
1680
1681 self.graph_origin_x = margin + title_width + margin
1682 self.graph_origin_y = graph_height + margin
1683
1684 x_axis_size = 1
1685 y_axis_size = 1
1686 self.yline = QGraphicsLineItem(0, 0, 0, graph_height, self)
1687
1688 self.x_axis = XAxisGraphicsItem(graph_width, self)
1689 self.x_axis.setPos(self.graph_origin_x, self.graph_origin_y + 1)
1690
1691 if first:
1692 self.scale_item = SwitchScaleGraphicsItem(self.x_axis, self)
1693 self.scale_item.setPos(self.graph_origin_x, self.graph_origin_y + 10)
1694
1695 self.yline.setPos(self.graph_origin_x - y_axis_size, self.graph_origin_y - graph_height)
1696
1697 self.axis_point = QGraphicsLineItem(0, 0, 0, 0, self)
1698 self.axis_point.setPos(self.graph_origin_x - 1, self.graph_origin_y +1)
1699
1700 self.width = self.graph_origin_x + graph_width + margin
1701 self.height = self.graph_origin_y + margin
1702
1703 self.graph = SwitchGraphDataGraphicsItem(data, graph_width, graph_height, attrs, event_handler, self)
1704 self.graph.setPos(self.graph_origin_x, self.graph_origin_y - graph_height)
1705
1706 if parent and 'EnableRubberBand' in dir(parent):
1707 parent.EnableRubberBand(self.graph_origin_x, self.graph_origin_x + graph_width - 1, self)
1708
1709 def boundingRect(self):
1710 return QRectF(0, 0, self.width, self.height)
1711
1712 def paint(self, painter, option, widget):
1713 pass
1714
1715 def RBXToPixel(self, x):
1716 return self.attrs.PixelToX(x - self.graph_origin_x)
1717
1718 def RBXRangeToPixel(self, x0, x1):
1719 return (self.RBXToPixel(x0), self.RBXToPixel(x1 + 1))
1720
1721 def RBPixelToTime(self, x):
1722 if x < self.data.points[0].x:
1723 return self.data.XToData(0)
1724 return self.data.XToData(x)
1725
1726 def RBEventTimes(self, x0, x1):
1727 x0, x1 = self.RBXRangeToPixel(x0, x1)
1728 time_from = self.RBPixelToTime(x0)
1729 time_to = self.RBPixelToTime(x1)
1730 return (time_from, time_to)
1731
1732 def RBEvent(self, x0, x1):
1733 time_from, time_to = self.RBEventTimes(x0, x1)
1734 self.event_handler.RangeEvent(time_from, time_to)
1735
1736 def RBMoveEvent(self, x0, x1):
1737 if x1 < x0:
1738 x0, x1 = x1, x0
1739 self.RBEvent(x0, x1)
1740
1741 def RBReleaseEvent(self, x0, x1, selection_state):
1742 if x1 < x0:
1743 x0, x1 = x1, x0
1744 x0, x1 = self.RBXRangeToPixel(x0, x1)
1745 self.event_handler.SelectEvent(x0, x1, selection_state)
1746
1747# Graphics item to draw a vertical bracket (used to highlight "forward" sub-range)
1748
1749class VerticalBracketGraphicsItem(QGraphicsItem):
1750
1751 def __init__(self, parent=None):
1752 super(VerticalBracketGraphicsItem, self).__init__(parent)
1753
1754 self.width = 0
1755 self.height = 0
1756 self.hide()
1757
1758 def SetSize(self, width, height):
1759 self.width = width + 1
1760 self.height = height + 1
1761
1762 def boundingRect(self):
1763 return QRectF(0, 0, self.width, self.height)
1764
1765 def paint(self, painter, option, widget):
1766 colour = QColor(255, 255, 0, 32)
1767 painter.fillRect(0, 0, self.width, self.height, colour)
1768 x1 = self.width - 1
1769 y1 = self.height - 1
1770 painter.drawLine(0, 0, x1, 0)
1771 painter.drawLine(0, 0, 0, 3)
1772 painter.drawLine(x1, 0, x1, 3)
1773 painter.drawLine(0, y1, x1, y1)
1774 painter.drawLine(0, y1, 0, y1 - 3)
1775 painter.drawLine(x1, y1, x1, y1 - 3)
1776
1777# Graphics item to contain graphs arranged vertically
1778
1779class VertcalGraphSetGraphicsItem(QGraphicsItem):
1780
1781 def __init__(self, collection, attrs, event_handler, child_class, parent=None):
1782 super(VertcalGraphSetGraphicsItem, self).__init__(parent)
1783
1784 self.collection = collection
1785
1786 self.top = 10
1787
1788 self.width = 0
1789 self.height = self.top
1790
1791 self.rubber_band = None
1792 self.rb_enabled = False
1793
1794 first = True
1795 for data in collection.data:
1796 child = child_class(collection, data, attrs, event_handler, first, self)
1797 child.setPos(0, self.height + 1)
1798 rect = child.boundingRect()
1799 if rect.right() > self.width:
1800 self.width = rect.right()
1801 self.height = self.height + rect.bottom() + 1
1802 first = False
1803
1804 self.bracket = VerticalBracketGraphicsItem(self)
1805
1806 def EnableRubberBand(self, xlo, xhi, rb_event_handler):
1807 if self.rb_enabled:
1808 return
1809 self.rb_enabled = True
1810 self.rb_in_view = False
1811 self.setAcceptedMouseButtons(Qt.LeftButton)
1812 self.rb_xlo = xlo
1813 self.rb_xhi = xhi
1814 self.rb_event_handler = rb_event_handler
1815 self.mousePressEvent = self.MousePressEvent
1816 self.mouseMoveEvent = self.MouseMoveEvent
1817 self.mouseReleaseEvent = self.MouseReleaseEvent
1818
1819 def boundingRect(self):
1820 return QRectF(0, 0, self.width, self.height)
1821
1822 def paint(self, painter, option, widget):
1823 pass
1824
1825 def RubberBandParent(self):
1826 scene = self.scene()
1827 view = scene.views()[0]
1828 viewport = view.viewport()
1829 return viewport
1830
1831 def RubberBandSetGeometry(self, rect):
1832 scene_rectf = self.mapRectToScene(QRectF(rect))
1833 scene = self.scene()
1834 view = scene.views()[0]
1835 poly = view.mapFromScene(scene_rectf)
1836 self.rubber_band.setGeometry(poly.boundingRect())
1837
1838 def SetSelection(self, selection_state):
1839 if self.rubber_band:
1840 if selection_state:
1841 self.RubberBandSetGeometry(selection_state)
1842 self.rubber_band.show()
1843 else:
1844 self.rubber_band.hide()
1845
1846 def SetBracket(self, rect):
1847 if rect:
1848 x, y, width, height = rect.x(), rect.y(), rect.width(), rect.height()
1849 self.bracket.setPos(x, y)
1850 self.bracket.SetSize(width, height)
1851 self.bracket.show()
1852 else:
1853 self.bracket.hide()
1854
1855 def RubberBandX(self, event):
1856 x = event.pos().toPoint().x()
1857 if x < self.rb_xlo:
1858 x = self.rb_xlo
1859 elif x > self.rb_xhi:
1860 x = self.rb_xhi
1861 else:
1862 self.rb_in_view = True
1863 return x
1864
1865 def RubberBandRect(self, x):
1866 if self.rb_origin.x() <= x:
1867 width = x - self.rb_origin.x()
1868 rect = QRect(self.rb_origin, QSize(width, self.height))
1869 else:
1870 width = self.rb_origin.x() - x
1871 top_left = QPoint(self.rb_origin.x() - width, self.rb_origin.y())
1872 rect = QRect(top_left, QSize(width, self.height))
1873 return rect
1874
1875 def MousePressEvent(self, event):
1876 self.rb_in_view = False
1877 x = self.RubberBandX(event)
1878 self.rb_origin = QPoint(x, self.top)
1879 if self.rubber_band is None:
1880 self.rubber_band = QRubberBand(QRubberBand.Rectangle, self.RubberBandParent())
1881 self.RubberBandSetGeometry(QRect(self.rb_origin, QSize(0, self.height)))
1882 if self.rb_in_view:
1883 self.rubber_band.show()
1884 self.rb_event_handler.RBMoveEvent(x, x)
1885 else:
1886 self.rubber_band.hide()
1887
1888 def MouseMoveEvent(self, event):
1889 x = self.RubberBandX(event)
1890 rect = self.RubberBandRect(x)
1891 self.RubberBandSetGeometry(rect)
1892 if self.rb_in_view:
1893 self.rubber_band.show()
1894 self.rb_event_handler.RBMoveEvent(self.rb_origin.x(), x)
1895
1896 def MouseReleaseEvent(self, event):
1897 x = self.RubberBandX(event)
1898 if self.rb_in_view:
1899 selection_state = self.RubberBandRect(x)
1900 else:
1901 selection_state = None
1902 self.rb_event_handler.RBReleaseEvent(self.rb_origin.x(), x, selection_state)
1903
1904# Switch graph legend data model
1905
1906class SwitchGraphLegendModel(QAbstractTableModel):
1907
1908 def __init__(self, collection, region_attributes, parent=None):
1909 super(SwitchGraphLegendModel, self).__init__(parent)
1910
1911 self.region_attributes = region_attributes
1912
1913 self.child_items = sorted(collection.hregions.values(), key=GraphDataRegionOrdinal)
1914 self.child_count = len(self.child_items)
1915
1916 self.highlight_set = set()
1917
1918 self.column_headers = ("pid", "tid", "comm")
1919
1920 def rowCount(self, parent):
1921 return self.child_count
1922
1923 def headerData(self, section, orientation, role):
1924 if role != Qt.DisplayRole:
1925 return None
1926 if orientation != Qt.Horizontal:
1927 return None
1928 return self.columnHeader(section)
1929
1930 def index(self, row, column, parent):
1931 return self.createIndex(row, column, self.child_items[row])
1932
1933 def columnCount(self, parent=None):
1934 return len(self.column_headers)
1935
1936 def columnHeader(self, column):
1937 return self.column_headers[column]
1938
1939 def data(self, index, role):
1940 if role == Qt.BackgroundRole:
1941 child = self.child_items[index.row()]
1942 if child in self.highlight_set:
1943 return self.region_attributes[child.key].colour
1944 return None
1945 if role == Qt.ForegroundRole:
1946 child = self.child_items[index.row()]
1947 if child in self.highlight_set:
1948 return QColor(255, 255, 255)
1949 return self.region_attributes[child.key].colour
1950 if role != Qt.DisplayRole:
1951 return None
1952 hregion = self.child_items[index.row()]
1953 col = index.column()
1954 if col == 0:
1955 return hregion.pid
1956 if col == 1:
1957 return hregion.tid
1958 if col == 2:
1959 return hregion.comm
1960 return None
1961
1962 def SetHighlight(self, row, set_highlight):
1963 child = self.child_items[row]
1964 top_left = self.createIndex(row, 0, child)
1965 bottom_right = self.createIndex(row, len(self.column_headers) - 1, child)
1966 self.dataChanged.emit(top_left, bottom_right)
1967
1968 def Highlight(self, highlight_set):
1969 for row in xrange(self.child_count):
1970 child = self.child_items[row]
1971 if child in self.highlight_set:
1972 if child not in highlight_set:
1973 self.SetHighlight(row, False)
1974 elif child in highlight_set:
1975 self.SetHighlight(row, True)
1976 self.highlight_set = highlight_set
1977
1978# Switch graph legend is a table
1979
1980class SwitchGraphLegend(QWidget):
1981
1982 def __init__(self, collection, region_attributes, parent=None):
1983 super(SwitchGraphLegend, self).__init__(parent)
1984
1985 self.data_model = SwitchGraphLegendModel(collection, region_attributes)
1986
1987 self.model = QSortFilterProxyModel()
1988 self.model.setSourceModel(self.data_model)
1989
1990 self.view = QTableView()
1991 self.view.setModel(self.model)
1992 self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
1993 self.view.verticalHeader().setVisible(False)
1994 self.view.sortByColumn(-1, Qt.AscendingOrder)
1995 self.view.setSortingEnabled(True)
1996 self.view.resizeColumnsToContents()
1997 self.view.resizeRowsToContents()
1998
1999 self.vbox = VBoxLayout(self.view)
2000 self.setLayout(self.vbox)
2001
2002 sz1 = self.view.columnWidth(0) + self.view.columnWidth(1) + self.view.columnWidth(2) + 2
2003 sz1 = sz1 + self.view.verticalScrollBar().sizeHint().width()
2004 self.saved_size = sz1
2005
2006 def resizeEvent(self, event):
2007 self.saved_size = self.size().width()
2008 super(SwitchGraphLegend, self).resizeEvent(event)
2009
2010 def Highlight(self, highlight_set):
2011 self.data_model.Highlight(highlight_set)
2012 self.update()
2013
2014 def changeEvent(self, event):
2015 if event.type() == QEvent.FontChange:
2016 self.view.resizeRowsToContents()
2017 self.view.resizeColumnsToContents()
2018 # Need to resize rows again after column resize
2019 self.view.resizeRowsToContents()
2020 super(SwitchGraphLegend, self).changeEvent(event)
2021
2022# Random colour generation
2023
2024def RGBColourTooLight(r, g, b):
2025 if g > 230:
2026 return True
2027 if g <= 160:
2028 return False
2029 if r <= 180 and g <= 180:
2030 return False
2031 if r < 60:
2032 return False
2033 return True
2034
2035def GenerateColours(x):
2036 cs = [0]
2037 for i in xrange(1, x):
2038 cs.append(int((255.0 / i) + 0.5))
2039 colours = []
2040 for r in cs:
2041 for g in cs:
2042 for b in cs:
2043 # Exclude black and colours that look too light against a white background
2044 if (r, g, b) == (0, 0, 0) or RGBColourTooLight(r, g, b):
2045 continue
2046 colours.append(QColor(r, g, b))
2047 return colours
2048
2049def GenerateNColours(n):
2050 for x in xrange(2, n + 2):
2051 colours = GenerateColours(x)
2052 if len(colours) >= n:
2053 return colours
2054 return []
2055
2056def GenerateNRandomColours(n, seed):
2057 colours = GenerateNColours(n)
2058 random.seed(seed)
2059 random.shuffle(colours)
2060 return colours
2061
2062# Graph attributes, in particular the scale and subrange that change when zooming
2063
2064class GraphAttributes():
2065
2066 def __init__(self, scale, subrange, region_attributes, dp):
2067 self.scale = scale
2068 self.subrange = subrange
2069 self.region_attributes = region_attributes
2070 # Rounding avoids errors due to finite floating point precision
2071 self.dp = dp # data decimal places
2072 self.Update()
2073
2074 def XToPixel(self, x):
2075 return int(round((x - self.subrange.x.lo) * self.scale.x, self.pdp.x))
2076
2077 def YToPixel(self, y):
2078 return int(round((y - self.subrange.y.lo) * self.scale.y, self.pdp.y))
2079
2080 def PixelToXRounded(self, px):
2081 return round((round(px, 0) / self.scale.x), self.dp.x) + self.subrange.x.lo
2082
2083 def PixelToYRounded(self, py):
2084 return round((round(py, 0) / self.scale.y), self.dp.y) + self.subrange.y.lo
2085
2086 def PixelToX(self, px):
2087 x = self.PixelToXRounded(px)
2088 if self.pdp.x == 0:
2089 rt = self.XToPixel(x)
2090 if rt > px:
2091 return x - 1
2092 return x
2093
2094 def PixelToY(self, py):
2095 y = self.PixelToYRounded(py)
2096 if self.pdp.y == 0:
2097 rt = self.YToPixel(y)
2098 if rt > py:
2099 return y - 1
2100 return y
2101
2102 def ToPDP(self, dp, scale):
2103 # Calculate pixel decimal places:
2104 # (10 ** dp) is the minimum delta in the data
2105 # scale it to get the minimum delta in pixels
2106 # log10 gives the number of decimals places negatively
2107 # subtrace 1 to divide by 10
2108 # round to the lower negative number
2109 # change the sign to get the number of decimals positively
2110 x = math.log10((10 ** dp) * scale)
2111 if x < 0:
2112 x -= 1
2113 x = -int(math.floor(x) - 0.1)
2114 else:
2115 x = 0
2116 return x
2117
2118 def Update(self):
2119 x = self.ToPDP(self.dp.x, self.scale.x)
2120 y = self.ToPDP(self.dp.y, self.scale.y)
2121 self.pdp = XY(x, y) # pixel decimal places
2122
2123# Switch graph splitter which divides the CPU graphs from the legend
2124
2125class SwitchGraphSplitter(QSplitter):
2126
2127 def __init__(self, parent=None):
2128 super(SwitchGraphSplitter, self).__init__(parent)
2129
2130 self.first_time = False
2131
2132 def resizeEvent(self, ev):
2133 if self.first_time:
2134 self.first_time = False
2135 sz1 = self.widget(1).view.columnWidth(0) + self.widget(1).view.columnWidth(1) + self.widget(1).view.columnWidth(2) + 2
2136 sz1 = sz1 + self.widget(1).view.verticalScrollBar().sizeHint().width()
2137 sz0 = self.size().width() - self.handleWidth() - sz1
2138 self.setSizes([sz0, sz1])
2139 elif not(self.widget(1).saved_size is None):
2140 sz1 = self.widget(1).saved_size
2141 sz0 = self.size().width() - self.handleWidth() - sz1
2142 self.setSizes([sz0, sz1])
2143 super(SwitchGraphSplitter, self).resizeEvent(ev)
2144
2145# Graph widget base class
2146
2147class GraphWidget(QWidget):
2148
2149 graph_title_changed = Signal(object)
2150
2151 def __init__(self, parent=None):
2152 super(GraphWidget, self).__init__(parent)
2153
2154 def GraphTitleChanged(self, title):
2155 self.graph_title_changed.emit(title)
2156
2157 def Title(self):
2158 return ""
2159
2160# Display time in s, ms, us or ns
2161
2162def ToTimeStr(val):
2163 val = Decimal(val)
2164 if val >= 1000000000:
2165 return "{} s".format((val / 1000000000).quantize(Decimal("0.000000001")))
2166 if val >= 1000000:
2167 return "{} ms".format((val / 1000000).quantize(Decimal("0.000001")))
2168 if val >= 1000:
2169 return "{} us".format((val / 1000).quantize(Decimal("0.001")))
2170 return "{} ns".format(val.quantize(Decimal("1")))
2171
2172# Switch (i.e. context switch i.e. Time Chart by CPU) graph widget which contains the CPU graphs and the legend and control buttons
2173
2174class SwitchGraphWidget(GraphWidget):
2175
2176 def __init__(self, glb, collection, parent=None):
2177 super(SwitchGraphWidget, self).__init__(parent)
2178
2179 self.glb = glb
2180 self.collection = collection
2181
2182 self.back_state = []
2183 self.forward_state = []
2184 self.selection_state = (None, None)
2185 self.fwd_rect = None
2186 self.start_time = self.glb.StartTime(collection.machine_id)
2187
2188 i = 0
2189 hregions = collection.hregions.values()
2190 colours = GenerateNRandomColours(len(hregions), 1013)
2191 region_attributes = {}
2192 for hregion in hregions:
2193 if hregion.pid == 0 and hregion.tid == 0:
2194 region_attributes[hregion.key] = GraphRegionAttribute(QColor(0, 0, 0))
2195 else:
2196 region_attributes[hregion.key] = GraphRegionAttribute(colours[i])
2197 i = i + 1
2198
2199 # Default to entire range
2200 xsubrange = Subrange(0.0, float(collection.xrangehi - collection.xrangelo) + 1.0)
2201 ysubrange = Subrange(0.0, float(collection.yrangehi - collection.yrangelo) + 1.0)
2202 subrange = XY(xsubrange, ysubrange)
2203
2204 scale = self.GetScaleForRange(subrange)
2205
2206 self.attrs = GraphAttributes(scale, subrange, region_attributes, collection.dp)
2207
2208 self.item = VertcalGraphSetGraphicsItem(collection, self.attrs, self, SwitchGraphGraphicsItem)
2209
2210 self.scene = QGraphicsScene()
2211 self.scene.addItem(self.item)
2212
2213 self.view = QGraphicsView(self.scene)
2214 self.view.centerOn(0, 0)
2215 self.view.setAlignment(Qt.AlignLeft | Qt.AlignTop)
2216
2217 self.legend = SwitchGraphLegend(collection, region_attributes)
2218
2219 self.splitter = SwitchGraphSplitter()
2220 self.splitter.addWidget(self.view)
2221 self.splitter.addWidget(self.legend)
2222
2223 self.point_label = QLabel("")
2224 self.point_label.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
2225
2226 self.back_button = QToolButton()
2227 self.back_button.setIcon(self.style().standardIcon(QStyle.SP_ArrowLeft))
2228 self.back_button.setDisabled(True)
2229 self.back_button.released.connect(lambda: self.Back())
2230
2231 self.forward_button = QToolButton()
2232 self.forward_button.setIcon(self.style().standardIcon(QStyle.SP_ArrowRight))
2233 self.forward_button.setDisabled(True)
2234 self.forward_button.released.connect(lambda: self.Forward())
2235
2236 self.zoom_button = QToolButton()
2237 self.zoom_button.setText("Zoom")
2238 self.zoom_button.setDisabled(True)
2239 self.zoom_button.released.connect(lambda: self.Zoom())
2240
2241 self.hbox = HBoxLayout(self.back_button, self.forward_button, self.zoom_button, self.point_label)
2242
2243 self.vbox = VBoxLayout(self.splitter, self.hbox)
2244
2245 self.setLayout(self.vbox)
2246
2247 def GetScaleForRangeX(self, xsubrange):
2248 # Default graph 1000 pixels wide
2249 dflt = 1000.0
2250 r = xsubrange.hi - xsubrange.lo
2251 return dflt / r
2252
2253 def GetScaleForRangeY(self, ysubrange):
2254 # Default graph 50 pixels high
2255 dflt = 50.0
2256 r = ysubrange.hi - ysubrange.lo
2257 return dflt / r
2258
2259 def GetScaleForRange(self, subrange):
2260 # Default graph 1000 pixels wide, 50 pixels high
2261 xscale = self.GetScaleForRangeX(subrange.x)
2262 yscale = self.GetScaleForRangeY(subrange.y)
2263 return XY(xscale, yscale)
2264
2265 def PointEvent(self, cpu, time_from, time_to, hregions):
2266 text = "CPU: " + str(cpu)
2267 time_from = time_from.quantize(Decimal(1))
2268 rel_time_from = time_from - self.glb.StartTime(self.collection.machine_id)
2269 text = text + " Time: " + str(time_from) + " (+" + ToTimeStr(rel_time_from) + ")"
2270 self.point_label.setText(text)
2271 self.legend.Highlight(hregions)
2272
2273 def RightClickEvent(self, cpu, hregion_times, pos):
2274 if not IsSelectable(self.glb.db, "calls", "WHERE parent_id >= 0"):
2275 return
2276 menu = QMenu(self.view)
2277 for hregion, time in hregion_times:
2278 thread_at_time = (hregion.exec_comm_id, hregion.thread_id, time)
2279 menu_text = "Show Call Tree for {} {}:{} at {}".format(hregion.comm, hregion.pid, hregion.tid, time)
2280 menu.addAction(CreateAction(menu_text, "Show Call Tree", lambda a=None, args=thread_at_time: self.RightClickSelect(args), self.view))
2281 menu.exec_(pos)
2282
2283 def RightClickSelect(self, args):
2284 CallTreeWindow(self.glb, self.glb.mainwindow, thread_at_time=args)
2285
2286 def NoPointEvent(self):
2287 self.point_label.setText("")
2288 self.legend.Highlight({})
2289
2290 def RangeEvent(self, time_from, time_to):
2291 time_from = time_from.quantize(Decimal(1))
2292 time_to = time_to.quantize(Decimal(1))
2293 if time_to <= time_from:
2294 self.point_label.setText("")
2295 return
2296 rel_time_from = time_from - self.start_time
2297 rel_time_to = time_to - self.start_time
2298 text = " Time: " + str(time_from) + " (+" + ToTimeStr(rel_time_from) + ") to: " + str(time_to) + " (+" + ToTimeStr(rel_time_to) + ")"
2299 text = text + " duration: " + ToTimeStr(time_to - time_from)
2300 self.point_label.setText(text)
2301
2302 def BackState(self):
2303 return (self.attrs.subrange, self.attrs.scale, self.selection_state, self.fwd_rect)
2304
2305 def PushBackState(self):
2306 state = copy.deepcopy(self.BackState())
2307 self.back_state.append(state)
2308 self.back_button.setEnabled(True)
2309
2310 def PopBackState(self):
2311 self.attrs.subrange, self.attrs.scale, self.selection_state, self.fwd_rect = self.back_state.pop()
2312 self.attrs.Update()
2313 if not self.back_state:
2314 self.back_button.setDisabled(True)
2315
2316 def PushForwardState(self):
2317 state = copy.deepcopy(self.BackState())
2318 self.forward_state.append(state)
2319 self.forward_button.setEnabled(True)
2320
2321 def PopForwardState(self):
2322 self.attrs.subrange, self.attrs.scale, self.selection_state, self.fwd_rect = self.forward_state.pop()
2323 self.attrs.Update()
2324 if not self.forward_state:
2325 self.forward_button.setDisabled(True)
2326
2327 def Title(self):
2328 time_from = self.collection.xrangelo + Decimal(self.attrs.subrange.x.lo)
2329 time_to = self.collection.xrangelo + Decimal(self.attrs.subrange.x.hi)
2330 rel_time_from = time_from - self.start_time
2331 rel_time_to = time_to - self.start_time
2332 title = "+" + ToTimeStr(rel_time_from) + " to +" + ToTimeStr(rel_time_to)
2333 title = title + " (" + ToTimeStr(time_to - time_from) + ")"
2334 return title
2335
2336 def Update(self):
2337 selected_subrange, selection_state = self.selection_state
2338 self.item.SetSelection(selection_state)
2339 self.item.SetBracket(self.fwd_rect)
2340 self.zoom_button.setDisabled(selected_subrange is None)
2341 self.GraphTitleChanged(self.Title())
2342 self.item.update(self.item.boundingRect())
2343
2344 def Back(self):
2345 if not self.back_state:
2346 return
2347 self.PushForwardState()
2348 self.PopBackState()
2349 self.Update()
2350
2351 def Forward(self):
2352 if not self.forward_state:
2353 return
2354 self.PushBackState()
2355 self.PopForwardState()
2356 self.Update()
2357
2358 def SelectEvent(self, x0, x1, selection_state):
2359 if selection_state is None:
2360 selected_subrange = None
2361 else:
2362 if x1 - x0 < 1.0:
2363 x1 += 1.0
2364 selected_subrange = Subrange(x0, x1)
2365 self.selection_state = (selected_subrange, selection_state)
2366 self.zoom_button.setDisabled(selected_subrange is None)
2367
2368 def Zoom(self):
2369 selected_subrange, selection_state = self.selection_state
2370 if selected_subrange is None:
2371 return
2372 self.fwd_rect = selection_state
2373 self.item.SetSelection(None)
2374 self.PushBackState()
2375 self.attrs.subrange.x = selected_subrange
2376 self.forward_state = []
2377 self.forward_button.setDisabled(True)
2378 self.selection_state = (None, None)
2379 self.fwd_rect = None
2380 self.attrs.scale.x = self.GetScaleForRangeX(self.attrs.subrange.x)
2381 self.attrs.Update()
2382 self.Update()
2383
2384# Slow initialization - perform non-GUI initialization in a separate thread and put up a modal message box while waiting
2385
2386class SlowInitClass():
2387
2388 def __init__(self, glb, title, init_fn):
2389 self.init_fn = init_fn
2390 self.done = False
2391 self.result = None
2392
2393 self.msg_box = QMessageBox(glb.mainwindow)
2394 self.msg_box.setText("Initializing " + title + ". Please wait.")
2395 self.msg_box.setWindowTitle("Initializing " + title)
2396 self.msg_box.setWindowIcon(glb.mainwindow.style().standardIcon(QStyle.SP_MessageBoxInformation))
2397
2398 self.init_thread = Thread(self.ThreadFn, glb)
2399 self.init_thread.done.connect(lambda: self.Done(), Qt.QueuedConnection)
2400
2401 self.init_thread.start()
2402
2403 def Done(self):
2404 self.msg_box.done(0)
2405
2406 def ThreadFn(self, glb):
2407 conn_name = "SlowInitClass" + str(os.getpid())
2408 db, dbname = glb.dbref.Open(conn_name)
2409 self.result = self.init_fn(db)
2410 self.done = True
2411 return (True, 0)
2412
2413 def Result(self):
2414 while not self.done:
2415 self.msg_box.exec_()
2416 self.init_thread.wait()
2417 return self.result
2418
2419def SlowInit(glb, title, init_fn):
2420 init = SlowInitClass(glb, title, init_fn)
2421 return init.Result()
2422
2423# Time chart by CPU window
2424
2425class TimeChartByCPUWindow(QMdiSubWindow):
2426
2427 def __init__(self, glb, parent=None):
2428 super(TimeChartByCPUWindow, self).__init__(parent)
2429
2430 self.glb = glb
2431 self.machine_id = glb.HostMachineId()
2432 self.collection_name = "SwitchGraphDataCollection " + str(self.machine_id)
2433
2434 collection = LookupModel(self.collection_name)
2435 if collection is None:
2436 collection = SlowInit(glb, "Time Chart", self.Init)
2437
2438 self.widget = SwitchGraphWidget(glb, collection, self)
2439 self.view = self.widget
2440
2441 self.base_title = "Time Chart by CPU"
2442 self.setWindowTitle(self.base_title + self.widget.Title())
2443 self.widget.graph_title_changed.connect(self.GraphTitleChanged)
2444
2445 self.setWidget(self.widget)
2446
2447 AddSubWindow(glb.mainwindow.mdi_area, self, self.windowTitle())
2448
2449 def Init(self, db):
2450 return LookupCreateModel(self.collection_name, lambda : SwitchGraphDataCollection(self.glb, db, self.machine_id))
2451
2452 def GraphTitleChanged(self, title):
2453 self.setWindowTitle(self.base_title + " : " + title)
2454
1157# Child data item finder2455# Child data item finder
1158 2456
1159class ChildDataItemFinder():2457class ChildDataItemFinder():
@@ -3025,7 +4323,9 @@ p.c2 { · 1865 unmodified lines
3025<p class=c2><a href=#allbranches>1.3 All branches</a></p>4323<p class=c2><a href=#allbranches>1.3 All branches</a></p>
3026<p class=c2><a href=#selectedbranches>1.4 Selected branches</a></p>4324<p class=c2><a href=#selectedbranches>1.4 Selected branches</a></p>
3027<p class=c2><a href=#topcallsbyelapsedtime>1.5 Top calls by elapsed time</a></p>4325<p class=c2><a href=#topcallsbyelapsedtime>1.5 Top calls by elapsed time</a></p>
3028<p class=c1><a href=#tables>2. Tables</a></p>4326<p class=c1><a href=#charts>2. Charts</a></p>
4327<p class=c2><a href=#timechartbycpu>2.1 Time chart by CPU</a></p>
4328<p class=c1><a href=#tables>3. Tables</a></p>
3029<h1 id=reports>1. Reports</h1>4329<h1 id=reports>1. Reports</h1>
3030<h2 id=callgraph>1.1 Context-Sensitive Call Graph</h2>4330<h2 id=callgraph>1.1 Context-Sensitive Call Graph</h2>
3031The result is a GUI window with a tree representing a context-sensitive4331The result is a GUI window with a tree representing a context-sensitive
@@ -3113,7 +4413,29 @@ N.B. Due to the granularity of timestamps, there could be no branches in any giv · 81 unmodified lines
3113The Top calls by elapsed time report displays calls in descending order of time elapsed between when the function was called and when it returned.4413The Top calls by elapsed time report displays calls in descending order of time elapsed between when the function was called and when it returned.
3114The data is reduced by various selection criteria. A dialog box displays available criteria which are AND'ed together.4414The data is reduced by various selection criteria. A dialog box displays available criteria which are AND'ed together.
3115If not all data is fetched, a Fetch bar is provided. Ctrl-F displays a Find bar.4415If not all data is fetched, a Fetch bar is provided. Ctrl-F displays a Find bar.
3116<h1 id=tables>2. Tables</h1>4416<h1 id=charts>2. Charts</h1>
4417<h2 id=timechartbycpu>2.1 Time chart by CPU</h2>
4418This chart displays context switch information when that data is available. Refer to context_switches_view on the Tables menu.
4419<h3>Features</h3>
4420<ol>
4421<li>Mouse over to highight the task and show the time</li>
4422<li>Drag the mouse to select a region and zoom by pushing the Zoom button</li>
4423<li>Go back and forward by pressing the arrow buttons</li>
4424<li>If call information is available, right-click to show a call tree opened to that task and time.
4425Note, the call tree may take some time to appear, and there may not be call information for the task or time selected.
4426</li>
4427</ol>
4428<h3>Important</h3>
4429The graph can be misleading in the following respects:
4430<ol>
4431<li>The graph shows the first task on each CPU as running from the beginning of the time range.
4432Because tracing might start on different CPUs at different times, that is not necessarily the case.
4433Refer to context_switches_view on the Tables menu to understand what data the graph is based upon.</li>
4434<li>Similarly, the last task on each CPU can be showing running longer than it really was.
4435Again, refer to context_switches_view on the Tables menu to understand what data the graph is based upon.</li>
4436<li>When the mouse is over a task, the highlighted task might not be visible on the legend without scrolling if the legend does not fit fully in the window</li>
4437</ol>
4438<h1 id=tables>3. Tables</h1>
3117The Tables menu shows all tables and views in the database. Most tables have an associated view4439The Tables menu shows all tables and views in the database. Most tables have an associated view
3118which displays the information in a more friendly way. Not all data for large tables is fetched4440which displays the information in a more friendly way. Not all data for large tables is fetched
3119immediately. More records can be fetched using the Fetch bar provided. Columns can be sorted,4441immediately. More records can be fetched using the Fetch bar provided. Columns can be sorted,
@@ -3309,6 +4631,10 @@ class MainWindow(QMainWindow): · 189 unmodified lines
3309 if IsSelectable(glb.db, "calls"):4631 if IsSelectable(glb.db, "calls"):
3310 reports_menu.addAction(CreateAction("&Top calls by elapsed time", "Create a new window displaying top calls by elapsed time", self.NewTopCalls, self))4632 reports_menu.addAction(CreateAction("&Top calls by elapsed time", "Create a new window displaying top calls by elapsed time", self.NewTopCalls, self))
3311 4633
4634 if IsSelectable(glb.db, "context_switches"):
4635 charts_menu = menu.addMenu("&Charts")
4636 charts_menu.addAction(CreateAction("&Time chart by CPU", "Create a new window displaying time charts by CPU", self.TimeChartByCPU, self))
4637
3312 self.TableMenu(GetTableList(glb), menu)4638 self.TableMenu(GetTableList(glb), menu)
3313 4639
3314 self.window_menu = WindowMenu(self.mdi_area, menu)4640 self.window_menu = WindowMenu(self.mdi_area, menu)
@@ -3369,6 +4695,9 @@ class MainWindow(QMainWindow): · 54 unmodified lines
3369 label = "Selected branches" if branches_events == 1 else "Selected branches " + "(id=" + dbid + ")"4695 label = "Selected branches" if branches_events == 1 else "Selected branches " + "(id=" + dbid + ")"
3370 reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda a=None,x=dbid: self.NewSelectedBranchView(x), self))4696 reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda a=None,x=dbid: self.NewSelectedBranchView(x), self))
3371 4697
4698 def TimeChartByCPU(self):
4699 TimeChartByCPUWindow(self.glb, self)
4700
3372 def TableMenu(self, tables, menu):4701 def TableMenu(self, tables, menu):
3373 table_menu = menu.addMenu("&Tables")4702 table_menu = menu.addMenu("&Tables")
3374 for table in tables:4703 for table in tables: