class AbstractDockWidget(Plugin, QtWidgets.QDockWidget): closed = QtCore.Signal(object) enabled = QtCore.Signal() def __init__(self, parent, name, area=QtCore.Qt.LeftDockWidgetArea): QtWidgets.QDockWidget.__init__(self, name, parent) Plugin.__init__(self) self.parent = parent self.setAllowedAreas(QtCore.Qt.AllDockWidgetAreas) self.setFeatures(QtWidgets.QDockWidget.DockWidgetClosable | QtWidgets.QDockWidget.DockWidgetMovable) self.setObjectName(name) parent.addDockWidget(area, self) # Setup main vertical layout self.__layout = QtWidgets.QVBoxLayout() self.__layout.setContentsMargins(0, 0, 0, 0) # Required to get layout on DockWidget self.setWidget(QtWidgets.QWidget()) self.widget().setLayout(self.__layout) def closeEvent(self, event): self.closed.emit(self) def showEvent(self, event): self.enabled.emit() def layout(self): return self.__layout
def dropEvent(event, format="application/x-job-names"): if event.mimeData().hasFormat(format): item = event.mimeData().data(format) stream = QtCore.QDataStream(item, QtCore.QIODevice.ReadOnly) names = QtCore.QString() stream >> names event.accept() return [name for name in str(names).split(":") if name]
def __init__(self, num_threads, max_queue=20): QtCore.QObject.__init__(self) self.__threads = [] self.__started = False self.__max_queue = max_queue self.__num_threads = num_threads self._q_mutex = QtCore.QMutex() self._q_empty = QtCore.QWaitCondition() self._q_queue = []
def __restoreSettings(self): """Restores the windows settings""" self.__plugins.restoreState() self.setWindowTitle( self.settings.value("%s/Title" % self.name, self.app_name)) self.restoreState( self.settings.value("%s/State" % self.name, QtCore.QByteArray())) self.resize( self.settings.value("%s/Size" % self.name, QtCore.QSize(1280, 1024))) self.move( self.settings.value("%s/Position" % self.name, QtCore.QPoint(0, 0)))
def startDrag(dragSource, dropActions, objects): mimeData = QtCore.QMimeData() mimeData.setText("\n".join(["%s" % job.data.name for job in objects])) mimeDataAdd(mimeData, "application/x-job-names", [object.data.name for object in objects if isJob(object)]) mimeDataAdd(mimeData, "application/x-job-ids", [opencue.id(object) for object in objects if isJob(object)]) mimeDataAdd(mimeData, "application/x-group-names", [object.data.name for object in objects if isGroup(object)]) mimeDataAdd(mimeData, "application/x-group-ids", [opencue.id(object) for object in objects if isGroup(object)]) mimeDataAdd(mimeData, "application/x-host-names", [object.data.name for object in objects if isHost(object)]) mimeDataAdd(mimeData, "application/x-host-ids", [opencue.id(object) for object in objects if isHost(object)]) drag = QtGui.QDrag(dragSource) drag.setMimeData(mimeData) drag.exec_(QtCore.Qt.MoveAction)
class PreviewProcessorWatchThread(QtCore.QThread): """ Waits for preview files to appear and emits the progress every second. This thread times out after 60 seconds, which should only occur if there are serious filer problems. """ existCountChanged = QtCore.Signal(int, int) def __init__(self, items, parent=None): QtCore.QThread.__init__(self, parent) self.__items = items self.__timeout = 60 + (30 * len(items)) def run(self): """ Just check to see how many files exist and emit that back to our parent. """ start_time = time.time() while 1: count = len( [path for path in self.__items if os.path.exists(path)]) self.emit(QtCore.SIGNAL('existCountChanged(int, int)'), count, len(self.__items)) self.existsCountChanged.emit(count, len(self.__items)) if count == len(self.__items): break time.sleep(1) if time.time() > self.__timeout + start_time: self.timeout.emit() print "Timed out waiting for preview server." break
def __filterHardwareStateSetup(self, layout): self.__filterHardwareStateList = sorted( [state for state in dir( opencue.api.host_pb2.HardwareState) if not state.startswith("_")]) btn = QtWidgets.QPushButton("Filter HardwareState") btn.setMaximumHeight(FILTER_HEIGHT) btn.setFocusPolicy(QtCore.Qt.NoFocus) btn.setContentsMargins(0,0,0,0) btn.setFlat(True) menu = QtWidgets.QMenu(self) btn.setMenu(menu) QtCore.QObject.connect(menu, QtCore.SIGNAL("triggered(QAction*)"), self.__filterHardwareStateHandle) for item in ["Clear", None] + self.__filterHardwareStateList: if item: a = QtWidgets.QAction(menu) a.setText(str(item)) if item != "Clear": a.setCheckable(True) menu.addAction(a) else: menu.addSeparator() layout.addWidget(btn) self.__filterHardwareStateButton = btn
def __filterAllocationSetup(self, layout): self.__filterAllocationList = sorted( [alloc.name() for alloc in opencue.api.getAllocations()]) btn = QtWidgets.QPushButton("Filter Allocation") btn.setMaximumHeight(FILTER_HEIGHT) btn.setFocusPolicy(QtCore.Qt.NoFocus) btn.setContentsMargins(0,0,0,0) btn.setFlat(True) menu = QtWidgets.QMenu(self) btn.setMenu(menu) QtCore.QObject.connect(menu, QtCore.SIGNAL("triggered(QAction*)"), self.__filterAllocationHandle) for item in ["Clear", None] + self.__filterAllocationList: if item: a = QtWidgets.QAction(menu) a.setText(str(item)) if item != "Clear": a.setCheckable(True) menu.addAction(a) else: menu.addSeparator() layout.addWidget(btn) self.__filterAllocationButton = btn
def __init__(self, title, function, work, concurrent, cancelTitle, cancelText, parent=None): """Creates, displays and starts the progress bar. @type title: str @param title: The title for the progress bar @type function: callable @param function: The function that the work units should be passed to @type work: list<sequence> @param work: A list of sequences to pass to the function @type concurrent: int @param concurrent: The number of units to submit to threadpool at once @type cancelTitle: string @param cancelTitle: This is displayed as the title of the confirmation dialog box if the user attempts to cancel @type cancelText: string @param cancelText: This is displayed as the text of the confirmation dialog box if the user attempts to cancel @type parent: QObject @param parent: The parent for this object""" QtWidgets.QDialog.__init__(self, parent) self.__work = work self.__function = function self.__workLock = QtCore.QReadWriteLock() self.__count = 0 self.__bar = QtWidgets.QProgressBar(self) self.__bar.setRange(0, len(self.__work)) self.__bar.setValue(0) self.__btn_cancel = QtWidgets.QPushButton("Cancel", self) self.__cancelConfirmation = None self.__cancelTitle = cancelTitle self.__cancelText = cancelText vlayout = QtWidgets.QVBoxLayout(self) vlayout.addWidget(self.__bar) vlayout.addWidget(self.__btn_cancel) self.setLayout(vlayout) self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint) self.setAttribute(QtCore.Qt.WA_DeleteOnClose) self.setSizeGripEnabled(True) self.setFixedSize(300, 100) self.setWindowTitle(title) self.__btn_cancel.clicked.connect(self.cancel) self.show() for thread in range(max(concurrent, 1)): self._submitWork()
def __paintSelection(self, painter): if self.__selectionRange == None: return selection = (min(self.__selectionRange[0], self.__selectionRange[1]), max(self.__selectionRange[0], self.__selectionRange[1])) leftExtent = self.__getTickArea(selection[0]) rightExtent = self.__getTickArea(selection[1] - 1) selectionExtent = QtCore.QRect(leftExtent.left(), leftExtent.top(), rightExtent.right() - leftExtent.left() + 2, leftExtent.height()/2) painter.fillRect(selectionExtent, QtGui.QBrush(QtGui.QColor(75, 75, 75)))
def __init__(self, parent): """Standard method to display a list or tree using QTreeWidget columnInfoByType is a dictionary of lists keyed to opencue.Constants.TYPE_* Each value is a list of lists that each define a column. [<column name>, <width>, <lambda function>, <function name for sorting>, <column delegate class>] Only supported on the primary column: <column name>, <column delegate class>, <width> @type parent: QWidget @param parent: The widget to set as the parent""" QtWidgets.QTreeWidget.__init__(self, parent) self._items = {} self._lastUpdate = 0 self._itemsLock = QtCore.QReadWriteLock() self._timer = QtCore.QTimer(self) self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) self.setUniformRowHeights(True) self.setAutoScroll(False) self.setFocusPolicy(QtCore.Qt.NoFocus) self.setAlternatingRowColors(True) self.setSortingEnabled(True) self.header().setSectionsMovable(False) self.header().setStretchLastSection(True) self.sortByColumn(0, QtCore.Qt.AscendingOrder) self.setItemDelegate(ItemDelegate(self)) self.__setupColumns() self.__setupColumnMenu() self.itemClicked.connect(self.__itemSingleClickedEmitToApp) self.itemDoubleClicked.connect(self.__itemDoubleClickedEmitToApp) self._timer.timeout.connect(self.updateRequest) QtGui.qApp.request_update.connect(self.updateRequest) self.updateRequest() self.setUpdateInterval(10)
def __refreshToggleCheckBoxSetup(self, layout): checkBox = QtWidgets.QCheckBox("Auto-refresh", self) layout.addWidget(checkBox) if self.hostMonitorTree.enableRefresh: checkBox.setCheckState(QtCore.Qt.Checked) QtCore.QObject.connect(checkBox, QtCore.SIGNAL('stateChanged(int)'), self.__refreshToggleCheckBoxHandle) __refreshToggleCheckBoxCheckBox = checkBox
def contextMenuEvent(self, e): """When right clicking on an item, this raises a context menu""" menu = QtWidgets.QMenu() self.__menuActions.subscriptions().addAction(menu, "editSize") self.__menuActions.subscriptions().addAction(menu, "editBurst") menu.addSeparator() self.__menuActions.subscriptions().addAction(menu, "delete") menu.exec_(QtCore.QPoint(e.globalX(),e.globalY()))
def __init__(self, sourceTree, parent = None): """CueStateBar init @type sourceTree: QTreeWidget @param sourceTree: The tree to get the jobs from @type parent: QWidget @param parent: The parent widget""" QtWidgets.QWidget.__init__(self, parent) self.setContentsMargins(8, 1, 1, 1) self.setFixedWidth(22) self.__sourceTree = weakref.proxy(sourceTree) self.__colors = [] self.__baseColor = QtGui.qApp.palette().color(QtGui.QPalette.Base) self.__colorsLock = QtCore.QReadWriteLock() self.__timer = QtCore.QTimer(self) self.__lastUpdate = 0 self.__timer.timeout.connect(self.updateColors) self.__sourceTree.verticalScrollBar().valueChanged.connect(self.update) self.__sourceTree.verticalScrollBar().rangeChanged.connect(self.__updateColors) self.__timer.start(10000)
def contextMenuEvent(self, e): """When right clicking on an item, this raises a context menu""" count = len(self.selectedObjects()) menu = QtWidgets.QMenu() if count: self.__menuActions.shows().addAction(menu, "properties") if count == 1: menu.addSeparator() self.__menuActions.shows().addAction(menu, "createSubscription") menu.exec_(QtCore.QPoint(e.globalX(), e.globalY()))
def __filterByHostNameSetup(self, layout): btn = QtWidgets.QLineEdit(self) btn.setMaximumHeight(FILTER_HEIGHT) btn.setFixedWidth(155) btn.setFocusPolicy(QtCore.Qt.StrongFocus) layout.addWidget(btn) self.__filterByHostName = btn self.__filterByHostNameLastInput = None QtCore.QObject.connect(self.__filterByHostName, QtCore.SIGNAL('editingFinished()'), self.__filterByHostNameHandle) btn = QtWidgets.QPushButton("Clr") btn.setMaximumHeight(FILTER_HEIGHT) btn.setFocusPolicy(QtCore.Qt.NoFocus) btn.setFixedWidth(24) layout.addWidget(btn) QtCore.QObject.connect(btn, QtCore.SIGNAL('clicked()'), self.__filterByHostNameClear) self.__filterByHostNameClearBtn = btn
def startTicksUpdate(self, updateInterval, updateWhenMinimized=False, maxUpdateInterval=None): """A method of updating the display on a one second timer to avoid multiple update requests and reduce excess cuebot calls. You will need to implement self.tick, You do not need to provide locking or unhandled error logging. You will need to implement tick. self.ticksWithoutUpdate = number of seconds since the last update. self.ticksLock = QMutex""" self.updateInterval = updateInterval self.__updateWhenMinimized = updateWhenMinimized self.__maxUpdateInterval = maxUpdateInterval # Stop the default update method if hasattr(self, "_timer"): self._timer.stop() self.ticksLock = QtCore.QMutex() self.__ticksTimer = QtCore.QTimer(self) self.__ticksTimer.timeout.connect(self.__tick) self.__ticksTimer.start(1000) self.ticksWithoutUpdate = 999
def refreshComments(self): """Clears and populates the comment list from the cuebot""" comments = self.__source.getComments() self.__treeSubjects.clear() for comment in comments: item = Comment(comment) item.setSizeHint(0, QtCore.QSize(300, 1)) self.__treeSubjects.addTopLevelItem(item) self.__treeSubjects.resizeColumnToContents(0) last_item = self.__treeSubjects.topLevelItem(len(comments) - 1) if last_item: self.__btnSave.setText(SAVE_EDIT) self.__btnSave.setEnabled(False) last_item.setSelected(True) else: self.__createNewComment()
def __displayColumnMenu(self): point = self.__dropdown.mapToGlobal( QtCore.QPoint(self.__dropdown.width(), self.__dropdown.height())) menu = QtWidgets.QMenu(self) menu.triggered.connect(self.__handleColumnMenu) for col in range(self.columnCount()): if self.columnWidth(col) or self.isColumnHidden(col): name = self.__columnInfoByType[ self.__columnPrimaryType][col][COLUMN_NAME] a = QtWidgets.QAction(menu) a.setText("%s. %s" % (col, name)) a.setCheckable(True) a.setChecked(not self.isColumnHidden(col)) menu.addAction(a) menu.exec_(point)
def __init__(self, parent): QtWidgets.QWidget.__init__(self, parent) self.__color = QtGui.QColor(55, 200, 55) self.__brush = QtGui.QBrush() self.__brush.setColor(self.__color) self.__brush.setStyle(QtCore.Qt.SolidPattern) self.__show = opencue.api.findShow("clo") self.__history = [0] * 100 self.__line = 575 self.__max = max(self.__line * 1.2, 80) self.__timer = QtCore.QTimer(self) self.__timer.timeout.connect(self.addNumber) self.__timer.start(10000)
def run(self): """ Just check to see how many files exist and emit that back to our parent. """ start_time = time.time() while 1: count = len( [path for path in self.__items if os.path.exists(path)]) self.emit(QtCore.SIGNAL('existCountChanged(int, int)'), count, len(self.__items)) self.existsCountChanged.emit(count, len(self.__items)) if count == len(self.__items): break time.sleep(1) if time.time() > self.__timeout + start_time: self.timeout.emit() print "Timed out waiting for preview server." break
class WorkerThread(QtCore.QThread): """WorkerThread A therad for parsing job log files. The log file is parsed using SpiCue.cueprofile and emits a "parsingComplete" signal when complete. """ workComplete = QtCore.Signal(object, object) def __init__(self, name, parent): QtCore.QThread.__init__(self, parent) self.__parent = parent self.__name = name def run(self): self.__running = True while self.__running: work = None self.__parent._q_mutex.lock() try: work = self.__parent._q_queue.pop(0) except: self.__parent._q_empty.wait(self.__parent._q_mutex) self.__parent._q_mutex.unlock() if not work: continue try: if work[3]: result = work[0](*work[3]) else: result = work[0]() if work[1]: self.workComplete.emit(work, result) del result except Exception, e: logger.info("Error processing work:' %s ', %s" % (work[2], e)) logger.info("Done:' %s '" % work[2]) logger.debug("Thread Stopping")
def startup(app_name, app_version, argv): app = CueGuiApplication(argv) # Start splash screen from SplashWindow import SplashWindow splash = SplashWindow(app, app_name, app_version, Constants.RESOURCE_PATH) # Display a splash message with: splash.msg("Message") # Allow ctrl-c to kill the application import signal signal.signal(signal.SIGINT, signal.SIG_DFL) # Load window icon app.setWindowIcon(QtGui.QIcon('%s/images/windowIcon.png' % Constants.RESOURCE_PATH)) app.setApplicationName(app_name) app.lastWindowClosed.connect(app.quit) QtGui.qApp.threadpool = ThreadPool(3) config_path = "/.%s/config" % app_name.lower() settings = QtCore.QSettings(QtCore.QSettings.IniFormat, QtCore.QSettings.UserScope, config_path) QtGui.qApp.settings = settings Style.init() # If the config file does not exist, copy over the default local = "%s%s.ini" % (os.getenv("HOME"), config_path) if not os.path.exists(local): default = os.path.join(Constants.DEFAULT_INI_PATH, "%s.ini" % app_name.lower()) print "Not found: %s\nCopying: %s" % (local, default) try: os.mkdir(os.path.dirname(local)) except Exception, e: logger.debug(e) try: import shutil shutil.copy2(default, local) except Exception, e: logger.debug(e)
def mimeDataAdd(mimeData, format, objects): data = QtCore.QByteArray() stream = QtCore.QDataStream(data, QtCore.QIODevice.WriteOnly) text = QtCore.QString(":".join(objects)) stream << text mimeData.setData(format, data)
class ServiceForm(QtWidgets.QWidget): """ An Widget for displaying and editing a service. """ saved = QtCore.Signal(object) def __init__(self, parent=None): QtWidgets.QWidget.__init__(self, parent) self.__service = None self.gpu_max_mb = 2 * 1024 self.gpu_min_mb = 0 self.gpu_tick_mb = 256 self.name = QtWidgets.QLineEdit(self) self.threadable = QtWidgets.QCheckBox(self) self.min_cores = QtWidgets.QSpinBox(self) self.min_cores.setRange(50, int(self._cfg().get('max_cores', 16)) * 100) self.min_cores.setSingleStep(50) self.min_cores.setValue(100) self.max_cores = QtWidgets.QSpinBox(self) self.max_cores.setRange(0, int(self._cfg().get('max_cores', 16)) * 100) self.max_cores.setSingleStep(100) self.max_cores.setValue(100) self.min_memory = QtWidgets.QSpinBox(self) self.min_memory.setRange(512, int(self._cfg().get('max_memory', 48)) * 1024) self.min_memory.setValue(3276) self.min_gpu = QtWidgets.QSpinBox(self) self.min_gpu.setRange(self.gpu_min_mb, self.gpu_max_mb) self.min_gpu.setValue(0) self.min_gpu.setSingleStep(self.gpu_tick_mb) self.min_gpu.setSuffix(" MB") layout = QtWidgets.QGridLayout(self) layout.addWidget(QtWidgets.QLabel("Name:", self), 0, 0) layout.addWidget(self.name, 0, 1) layout.addWidget(QtWidgets.QLabel("Threadable:", self), 1, 0) layout.addWidget(self.threadable, 1, 1) layout.addWidget( QtWidgets.QLabel("Min Threads (100 = 1 thread):", self), 2, 0) layout.addWidget(self.min_cores, 2, 1) layout.addWidget( QtWidgets.QLabel("Max Threads (100 = 1 thread):", self), 3, 0) layout.addWidget(self.max_cores, 3, 1) layout.addWidget(QtWidgets.QLabel("Min Memory MB:", self), 4, 0) layout.addWidget(self.min_memory, 4, 1) layout.addWidget(QtWidgets.QLabel("Min Gpu Memory MB:", self), 5, 0) layout.addWidget(self.min_gpu, 5, 1) self.__buttons = QtWidgets.QDialogButtonBox( QtWidgets.QDialogButtonBox.Save, QtCore.Qt.Horizontal, self) self.__buttons.setDisabled(True) layout.addWidget(self.__buttons, 8, 1) self.__buttons.accepted.connect(self.save) self._tags_w = TagsWidget(allowed_tags=Constants.ALLOWED_TAGS) layout.addWidget(self._tags_w, 6, 0, 1, 2) def _cfg(self): """ Loads (if necessary) and returns the config values. Warns and returns an empty dict if there's a problem reading the config @return: The keys & values stored in the config file @rtype: dict<str:str> """ if not hasattr(self, '__config'): self.__config = Utils.getResourceConfig() return self.__config def setService(self, service): """ Update the form with data from the given service. """ self.__buttons.setDisabled(False) self.__service = service.data self.name.setText(self.__service.name) self.threadable.setChecked(self.__service.threadable) self.min_cores.setValue(self.__service.min_cores) self.max_cores.setValue(self.__service.max_cores) self.min_memory.setValue(self.__service.min_memory / 1024) self.min_gpu.setValue(self.__service.min_gpu / 1024) self._tags_w.set_tags(self.__service.tags) def new(self): """ Clear the form for a new service. """ self.__buttons.setDisabled(False) self.__service = None self.name.setFocus() self.name.setText("") self.threadable.setChecked(False) self.min_cores.setValue(100) self.max_cores.setValue(100) self.min_memory.setValue(3276) self.min_gpu.setValue(0) self._tags_w.set_tags(['general']) def save(self): """ Create and emit a ServiceData object based on the contents of the form. """ if len(str(self.name.text())) < 3: QtWidgets.QMessageBox.critical( self, "Error", "The service name must be at least 3 characters.") return if not str(self.name.text()).isalnum(): QtWidgets.QMessageBox.critical( self, "Error", "The service name must alphanumeric.") return data = opencue.api.service_pb2.Service() data.name = str(self.name.text()) data.threadable = self.threadable.isChecked() data.min_cores = self.min_cores.value() data.max_cores = self.max_cores.value() data.min_memory = self.min_memory.value() * 1024 data.min_gpu = self.min_gpu.value() * 1024 data.tags.extend(self._tags_w.get_tags()) self.saved.emit(data)
class FrameMonitorTree(AbstractTreeWidget): job_changed = QtCore.Signal() handle_filter_layers_byLayer = QtCore.Signal(list) def __init__(self, parent): self.frameLogDataBuffer = FrameLogDataBuffer() self.frameEtaDataBuffer = FrameEtaDataBuffer() self.startColumnsForType(Constants.TYPE_FRAME) self.addColumn( "Order", 60, id=1, data=lambda job, frame: frame.data.dispatch_order, sort=lambda job, frame: frame.data.dispatch_order, tip= "The order the frame will be rendered in for it's layer if resources " "are available.") self.addColumn("Frame", 70, id=2, data=lambda job, frame: frame.data.number, sort=lambda job, frame: frame.data.number, tip="The number of the frame.") self.addColumn("Layer", 250, id=3, data=lambda job, frame: frame.data.layer_name, tip="The layer that the frame is in.") self.addColumn( "Status", 100, id=4, data=lambda job, frame: job_pb2.FrameState.Name(frame.data.state), tip="The status of the frame:\n" "Succeeded: \t The frame finished without errors.\n" "Running: \t The frame is currently running.\n" "Waiting: \t The frame is ready to be run when resources\n" "\t are available.\n" "Depend: \t The frame depends on another frame or job.\n" "Dead: \t The frame failed with an error.") self.addColumn("Cores", 55, id=5, data=lambda job, frame: (self.getCores(frame, True) or ""), sort=lambda job, frame: (self.getCores(frame)), tip="The number of cores a frame is using") self.addColumn( "Host", 120, id=6, data=lambda job, frame: frame.data.last_resource, tip="The last or current resource that the frame used or is using." ) self.addColumn( "Retries", 55, id=7, data=lambda job, frame: frame.data.retry_count, sort=lambda job, frame: frame.data.retry_count, tip="The number of times that each frame has had to retry.") self.addColumn( "_CheckpointEnabled", 20, id=8, data=lambda job, frame: "", sort=lambda job, frame: (frame.data.checkpoint_state == opencue.api.job_pb2.ENABLED), tip= "A green check mark here indicates the frame has written out at least " "1 checkpoint segment.") self.addColumn( "CheckP", 55, id=9, data=lambda job, frame: frame.data.checkpoint_count, sort=lambda job, frame: frame.data.checkpoint_count, tip="The number of times a frame has been checkpointed.") self.addColumn( "Runtime", 70, id=10, data=lambda job, frame: (Utils.secondsToHMMSS( frame.data.start_time and frame.data.stop_time and frame.data. stop_time - frame.data.start_time or frame.data.start_time and frame.data.stop_time != frame.data.start_time and time.time( ) - frame.data.start_time or 0)), sort=lambda job, frame: (frame.data.start_time and frame.data.stop_time and frame.data. stop_time - frame.data.start_time or frame.data.start_time and frame.data.stop_time != frame.data.start_time and time.time( ) - frame.data.start_time or 0), tip="The amount of HOURS:MINUTES:SECONDS that the frame\n" "has run for or last ran for.\n") self.addColumn( "LLU", 70, id=11, data=lambda job, frame: (frame.data.state == opencue.api. job_pb2.RUNNING and self.frameLogDataBuffer.getLastLineData( job, frame)[FrameLogDataBuffer.LLU] or ""), sort=lambda job, frame: (frame.data.state == opencue.api. job_pb2.RUNNING and self.frameLogDataBuffer.getLastLineData( job, frame)[FrameLogDataBuffer.LLU] or ""), tip="The amount of HOURS:MINUTES:SECONDS since the last\n" "time the log file was written to. A long period of\n" "time without an update is an indication of a stuck\n" "frame for most types of jobs") self.addColumn("Memory", 60, id=12, data=lambda job, frame: (frame.data.state == opencue.api.job_pb2.RUNNING and Utils.memoryToString(frame.data.used_memory) or Utils. memoryToString(frame.data.max_rss)), sort=lambda job, frame: (frame.data.state == opencue.api.job_pb2.RUNNING and frame.data.used_memory or frame.data.max_rss), tip="If a frame is running:\n" "\t The amount of memory currently used by the frame.\n" "If a frame is not running:\n" "\t The most memory this frame has used at one time.") self.addColumn( "Remain", 70, id=13, data=lambda job, frame: (frame.data.state == opencue.api.job_pb2.RUNNING and self. frameEtaDataBuffer.getEtaFormatted(job, frame) or ""), sort=lambda job, frame: (frame.data.state == opencue.api.job_pb2.RUNNING and self. frameEtaDataBuffer.getEta(job, frame) or -1), tip="Hours:Minutes:Seconds remaining.") self.addColumn("Start Time", 100, id=14, data=lambda job, frame: (self.getTimeString(frame.data.start_time) or ""), tip="The time the frame was started or retried.") self.addColumn("Stop Time", 100, id=15, data=lambda job, frame: (self.getTimeString(frame.data.stop_time) or ""), tip="The time that the frame finished or died.") self.addColumn("Last Line", 0, id=16, data=lambda job, frame: (frame.data.state == opencue.api.job_pb2.RUNNING and self.frameLogDataBuffer.getLastLineData( job, frame)[FrameLogDataBuffer.LASTLINE] or ""), tip="The last line of a running frame's log file.") self.frameSearch = opencue.search.FrameSearch() self.__job = None self.__jobState = None AbstractTreeWidget.__init__(self, parent) # Used to build right click context menus self.__menuActions = MenuActions(self, self.updateSoon, self.selectedObjects, self.getJob) self.__sortByColumnCache = {} self.itemClicked.connect(self.__itemSingleClickedCopy) self.itemClicked.connect(self.__itemSingleClickedViewLog) self.itemDoubleClicked.connect(self.__itemDoubleClickedViewLog) self.header().sortIndicatorChanged.connect(self.__sortByColumnSave) self.__load = None self.startTicksUpdate(20) def tick(self): if self.__load: self.__setJob(self.__load) self.__load = None self.ticksWithoutUpdate = 0 self._update() return if self.__job: if self.ticksWithoutUpdate > 9990: logger.info("doing full update") self.ticksWithoutUpdate = 0 self._update() return elif self.ticksWithoutUpdate > self.updateInterval: logger.info("doing changed update") self.ticksWithoutUpdate = 0 self._updateChanged() return if self.ticksWithoutUpdate <= self.updateInterval + 1: self.ticksWithoutUpdate += 1 # Redrawing every even number of seconds to see the current frame # runtime, LLU and last log line changes. Every second was excessive. if not self.ticksWithoutUpdate % 2: self.redraw() def getCores(self, frame, format=False): cores = None m = re.search(".*\/(\d+\.?\d*)", frame.data.last_resource) if m: cores = float(m.group(1)) if format: cores = "{:.2f}".format(cores) return cores def getTimeString(self, timestamp): tstring = None if timestamp and timestamp > 0: tstring = datetime.datetime.fromtimestamp(timestamp).strftime( "%m/%d %H:%M") return tstring def redrawRunning(self): """Forces the running frames to be redrawn with current values""" try: items = self.findItems("Running", QtCore.Qt.MatchExactly, STATUS_COLUMN) if items: self.dataChanged( self.indexFromItem(items[0], RUNTIME_COLUMN), self.indexFromItem(items[-1], LASTLINE_COLUMN)) except Exception, e: map(logger.warning, Utils.exceptionOutput(e))
class LayerMonitorTree(AbstractTreeWidget): handle_filter_layers_byLayer = QtCore.Signal(list) def __init__(self, parent): self.startColumnsForType(Constants.TYPE_LAYER) self.addColumn("dispatchOrder", 0, id=1, data=lambda layer: layer.data.dispatch_order, sort=lambda layer: layer.data.dispatch_order) self.addColumn("Name", 250, id=2, data=lambda layer: layer.data.name, tip="Name of the layer.") self.addColumn( "Services", 100, id=3, data=lambda layer: ",".join(layer.data.services), tip="The underlying application being run within the frames.") self.addColumn("Range", 150, id=4, data=lambda layer: displayRange(layer), tip="The range of frames that the layer should render.") self.addColumn( "Cores", 45, id=5, data=lambda layer: "%.2f" % layer.data.min_cores, sort=lambda layer: layer.data.min_cores, tip="The number of cores that the frames in this layer\n" "will reserve as a minimum.") self.addColumn( "Memory", 60, id=6, data=lambda layer: Utils.memoryToString(layer.data.min_memory), sort=lambda layer: layer.data.min_memory, tip="The amount of memory that each frame in this layer\n" "will reserve for its use. If the frame begins to use\n" "more memory than this, the cuebot will increase this\n" "number.") self.addColumn( "Gpu", 40, id=7, data=lambda layer: Utils.memoryToString(layer.data.min_gpu), sort=lambda layer: layer.data.min_gpu, tip="The amount of gpu memory each frame in this layer\n" "will reserve for its use. Note that we may not have\n" "machines as much gpu memory as you request.") self.addColumn("MaxRss", 60, id=8, data=lambda layer: Utils.memoryToString( layer.data.layer_stats.max_rss), sort=lambda layer: layer.data.layer_stats.max_rss, tip="Maximum amount of memory used by any frame in\n" "this layer at any time since the job was launched.") self.addColumn("Total", 40, id=9, data=lambda layer: layer.data.layer_stats.total_frames, sort=lambda layer: layer.data.layer_stats.total_frames, tip="Total number of frames in this layer.") self.addColumn( "Done", 40, id=10, data=lambda layer: layer.data.layer_stats.succeeded_frames, sort=lambda layer: layer.data.layer_stats.succeeded_frames, tip="Total number of done frames in this layer.") self.addColumn( "Run", 40, id=11, data=lambda layer: layer.data.layer_stats.running_frames, sort=lambda layer: layer.data.layer_stats.running_frames, tip="Total number or running frames in this layer.") self.addColumn("Depend", 53, id=12, data=lambda layer: layer.data.layer_stats.depend_frames, sort=lambda layer: layer.data.layer_stats.depend_frames, tip="Total number of dependent frames in this layer.") self.addColumn( "Wait", 40, id=13, data=lambda layer: layer.data.layer_stats.waiting_frames, sort=lambda layer: layer.data.layer_stats.waiting_frames, tip="Total number of waiting frames in this layer.") self.addColumn("Eaten", 40, id=14, data=lambda layer: layer.data.layer_stats.eaten_frames, sort=lambda layer: layer.data.layer_stats.eaten_frames, tip="Total number of eaten frames in this layer.") self.addColumn("Dead", 40, id=15, data=lambda layer: layer.data.layer_stats.dead_frames, sort=lambda layer: layer.data.layer_stats.dead_frames, tip="Total number of dead frames in this layer.") self.addColumn( "Avg", 65, id=16, data=lambda layer: Utils.secondsToHHMMSS(layer.data.layer_stats. avg_frame_sec), sort=lambda layer: layer.data.layer_stats.avg_frame_sec, tip="Average number of HOURS:MINUTES:SECONDS per frame\n" "in this layer.") self.addColumn("Tags", 100, id=17, data=lambda layer: " | ".join(layer.data.tags), tip="The tags define what resources may be booked on\n" "frames in this layer.") AbstractTreeWidget.__init__(self, parent) self.itemDoubleClicked.connect(self.__itemDoubleClickedFilterLayer) # Used to build right click context menus self.__menuActions = MenuActions(self, self.updateSoon, self.selectedObjects, self.getJob) self.__job = None self.disableUpdate = False self.__load = None self.startTicksUpdate(20, False, 60 * 60 * 24) def tick(self): if self.__load: self.__job = self.__load self.__load = None self.ticksWithoutUpdate = 0 if not self.disableUpdate: self._update() return if self.__job: if self.tickNeedsUpdate() and not self.disableUpdate: self.ticksWithoutUpdate = 0 self._update() return self.ticksWithoutUpdate += 1 def updateRequest(self): """Updates the items in the TreeWidget if sufficient time has passed since last updated""" self.ticksWithoutUpdate = 9999 def setJob(self, job): """Sets the current job @param job: Job can be None, a job object, or a job name. @type job: job, string, None""" if job is None: return self.__setJob(None) job = Utils.findJob(job) if job: self.__load = job def __setJob(self, job): """Sets the current job @param job: Job can be None, a job object, or a job name. @type job: job, string, None""" self.sortByColumn(0, QtCore.Qt.AscendingOrder) self.__job = job self.removeAllItems() def getJob(self): return self.__job def _createItem(self, obj): """Creates and returns the proper item""" return LayerWidgetItem(obj, self) def _getUpdate(self): """Returns the proper data from the cuebot""" if self.__job: try: return self.__job.getLayers() except opencue.exceptions.EntityNotFoundException: self.setJob(None) return [] return [] def contextMenuEvent(self, e): """When right clicking on an item, this raises a context menu""" __selectedObjects = self.selectedObjects() menu = QtWidgets.QMenu() self.__menuActions.layers().addAction(menu, "view") depend_menu = QtWidgets.QMenu("&Dependencies", self) self.__menuActions.layers().addAction(depend_menu, "viewDepends") self.__menuActions.layers().addAction(depend_menu, "dependWizard") depend_menu.addSeparator() self.__menuActions.layers().addAction(depend_menu, "markdone") menu.addMenu(depend_menu) if len(__selectedObjects) == 1: menu.addSeparator() self.__menuActions.layers().addAction(menu, "useLocalCores") if len(set([layer.data.range for layer in __selectedObjects])) == 1: self.__menuActions.layers().addAction(menu, "reorder") self.__menuActions.layers().addAction(menu, "stagger") menu.addSeparator() self.__menuActions.layers().addAction(menu, "setProperties") menu.addSeparator() self.__menuActions.layers().addAction(menu, "kill") self.__menuActions.layers().addAction(menu, "eat") self.__menuActions.layers().addAction(menu, "retry") if [ layer for layer in __selectedObjects if layer.data.layer_stats.dead_frames ]: menu.addSeparator() self.__menuActions.layers().addAction(menu, "retryDead") menu.exec_(e.globalPos()) def __itemDoubleClickedFilterLayer(self, item, col): self.handle_filter_layers_byLayer.emit([item.rpcObject.data.name])
class FrameRangeSelectionWidget(QtWidgets.QWidget): """The Timeline Widget is a rectangular area with ticks to represent units of frames. A frame range may be selected. This widget emits the following signals: * startFrameChanged(int) - user clicks new time or program changes time * endFrameChanged(int) - user clicks new time or program changes time * frameRangeChanged(int,int) - program changes viewed frame range * selectionChanged(int,int) - user releases a drag that selects a frame range """ endFrameChanged = QtCore.Signal(int) startFrameChanged = QtCore.Signal(int) frameRangeChanged = QtCore.Signal(int, int) selectionChanged = QtCore.Signal(int, int) def __init__(self, parent, *args): QtWidgets.QWidget.__init__(self, parent, *args) self.setMinimumWidth(100) self.__layout = QtWidgets.QHBoxLayout(self) self.__layout.addStretch(100) self.__right_margin = 25 self.__layout.setContentsMargins(0,0,0,0) toolButton = QtWidgets.QToolButton() toolButton.setArrowType(QtCore.Qt.UpArrow) toolButton.setFocusPolicy(QtCore.Qt.NoFocus) toolButton.setFixedSize(20,20) #toolButton.setContentsMargins(0,0,0,0) self.__layout.addWidget(toolButton) self.setLayout(self.__layout) self.setFixedHeight(25) self.__endFrame = 5000 self.__endFrameFinal = True self.__startFrame = 1 self.__startFrameFinal = True # The range we are displaying self.__frameRange = (self.__startFrame, self.__endFrame) # The range selected, or None if nothing is selected. self.__selectionRange = None # floatTime is the time the mouse is floating over. self.__floatTime = None # Request mouseover events from QT. self.setMouseTracking(True) # Don't erase the background for us; we redraw everything # with offscreen buffers. self.setAutoFillBackground(False) self.__labelFont = QtGui.QFont('Helvetica', 8) self.__double = False self.default_select_size = 1000 def endFrame(self): return self.__endFrame def setEndTime(self, t, final = True): if (self.__endFrame == t and self.__endFrameFinal == final): return self.__endFrame = int(t) self.__endFrameFinal = final #mine self.__selectionRange = (self.__startFrame, self.__endFrame) self.update() self.endFrameChanged.emit(int(t)) # The current time displayed in the timeline. def startFrame(self): return self.__startFrame def setStartTime(self, t, final = True): if (self.__startFrame == t and self.__startFrameFinal == final): return self.__startFrame = int(t) self.__startFrameFinal = final if self.__endFrame == self.__startFrame: self.setEndTime(min(self.__startFrame + self.default_select_size - 1, self.__frameRange[1]), True) self.__selectionRange = (self.__startFrame, self.__endFrame) self.update() self.startFrameChanged.emit(int(t)) # The viewed range of time in the timeline. def frameRange(self): return self.__frameRange def setFrameRange(self, frameRange): frameRange = tuple(sorted(map(int, frameRange))) self.__floatTime = None self.__frameRange = frameRange self.setStartTime(self.__frameRange[0], False) self.setEndTime(self.__frameRange[1], False) self.frameRangeChanged.emit(self.__frameRange[0], self.__frameRange[1]) self.update() # The selected (highlighted) range of time in the timeline. def selectedFrameRange(self): return self.__selectionRange def setSelectedFrameRange(self, selectedRange): if selectedRange != self.__selectionRange: self.__selectionRange = selectedRange self.selectionChanged.emit(self.__selectionRange[0], self.__selectionRange[1]) # QT event handlers and implementation details below this line. def mousePressEvent(self, mouseEvent): hitTime = self.__getTimeFromLocalPoint(mouseEvent.x()) if mouseEvent.buttons() & QtCore.Qt.LeftButton: self.setStartTime(hitTime, False) self.__selectionRange = (hitTime, hitTime) def mouseMoveEvent(self, mouseEvent): self.__floatTime = None hitTime = self.__getTimeFromLocalPoint(mouseEvent.x()) if mouseEvent.buttons() & QtCore.Qt.LeftButton \ or mouseEvent.buttons() & QtCore.Qt.RightButton: # Update the selection range, but don't send signals. if self.__selectionRange: self.__selectionRange = (self.__selectionRange[0], hitTime) self.setEndTime(hitTime, True) else: self.__selectionRange = (hitTime, hitTime) self.__floatTime = hitTime self.update() def mouseReleaseEvent(self, mouseEvent): if self.__double: self.__double = False self.setStartTime(self.__frameRange[0], True) self.setEndTime(self.__frameRange[1], True) else: hitTime = self.__getTimeFromLocalPoint(mouseEvent.x()) self.setEndTime(max(hitTime, self.startFrame()), True) self.setStartTime(min(hitTime, self.startFrame()), True) self.__selectionRange = (min(self.__selectionRange[0], self.__selectionRange[1]), max(self.__selectionRange[0], self.__selectionRange[1])) self.setSelectedFrameRange(self.__selectionRange) self.update() def mouseDoubleClickEvent(self, mouseEvent): self.__double = True def paintEvent(self, paintEvent): painter = QtGui.QPainter(self) self.__paintBackground(painter) # Put the baseline at 0, 0 painter.translate(0, self.height() / 2) painter.setFont(self.__labelFont) self.__paintSelection(painter) self.__paintTickmarks(painter) self.__paintLabels(painter) self.__paintFloatTime(painter) self.__paintStartTime(painter) self.__paintEndTime(painter) def leaveEvent(self, event): self.__floatTime = None self.update() def __getTickAreaExtent(self): return QtCore.QRect(10, -self.height()/2, self.width() - self.__right_margin - 20, self.height()) def __getTickArea(self, time): tickArea = self.__getTickAreaExtent() tickSpacing = float(self.__getTickAreaExtent().width()) / max(1,(self.__frameRange[1] - self.__frameRange[0])) return QtCore.QRect(tickArea.left() + tickSpacing * (time - self.__frameRange[0]), tickArea.top(), tickSpacing, tickArea.height()) def __getTimeFromLocalPoint(self, x): tickSpacing = float(self.__getTickAreaExtent().width()) / max(1,(self.__frameRange[1] - self.__frameRange[0])) deltaX = x - self.__getTickAreaExtent().left() hitTime = int(deltaX / tickSpacing + 0.5) + self.__frameRange[0] hitTime = int(max(self.__frameRange[0], min(hitTime, self.__frameRange[1]))) return hitTime def __getLabelPeriod(self): delta = self.__frameRange[1] - self.__frameRange[0] if (delta < 20): return 2 if (delta < 10000): base = 5 offset = 2 else: base = 10 offset = 1 scale = math.ceil(math.log(self.__frameRange[1] - self.__frameRange[0]) / math.log(base)) return base ** max(1, scale - offset) def __paintBackground(self, painter): bgBrush = self.palette().window() painter.fillRect(0, 0, self.width() - self.__right_margin, self.height(), bgBrush) highlightBrush = QtGui.QBrush(QtGui.QColor(75, 75, 75)) painter.fillRect(0, self.height()/2, self.width() - self.__right_margin+5, self.height()/2, highlightBrush) def __paintTickmarks(self, painter): tickExtent = self.__getTickAreaExtent() tickHeight = tickExtent.height() / 8 pen = QtGui.QPen(self.palette().window().color()) painter.setPen(pen) # Draw the baseline painter.drawLine(tickExtent.left(), 0, tickExtent.right(), 0) tickArea = self.__getTickArea(self.__frameRange[0]) if tickArea.width() >= 5: for frame in range(self.__frameRange[0], self.__frameRange[1] + 1, 1): xPos = self.__getTickArea(frame).left() painter.drawLine(xPos, -tickHeight, xPos, 0) def __paintLabels(self, painter): tickExtent = self.__getTickAreaExtent() labelHeight = tickExtent.height() / 3 labelPeriod = self.__getLabelPeriod() if labelPeriod == 0: return firstLabel = self.__frameRange[0] + labelPeriod - 1 firstLabel = firstLabel - (firstLabel % labelPeriod) frames = [] for frame in range(int(firstLabel), int(self.__frameRange[1])+1, int(labelPeriod)): frames.append(frame) if frames[0] != self.__frameRange[0]: frames.insert(0, self.__frameRange[0]) if frames[-1] != self.__frameRange[1]: frames.append(self.__frameRange[1]) oldPen = painter.pen() # draw hatches for labelled frames painter.setPen(self.palette().color(QtGui.QPalette.Foreground)) for frame in frames: xPos = self.__getTickArea(frame).left() painter.drawLine(xPos, -labelHeight, xPos, 0) painter.setPen(QtGui.QColor(10, 10, 10)) metric = QtGui.QFontMetrics(painter.font()) yPos = metric.ascent() + 1 rightEdge = -10000 width = metric.width(str(frames[-1])) farEdge = self.__getTickArea(frames[-1]).right() - width / 2 farEdge -= 4 for frame in frames: xPos = self.__getTickArea(frame).left() frameString = str(frame) width = metric.width(frameString) xPos = xPos - width / 2 if (xPos > rightEdge and xPos + width < farEdge) or frame is frames[-1]: painter.drawText(xPos, yPos, frameString) rightEdge = xPos + width + 4 painter.setPen(oldPen) def __paintStartTime(self, painter): startFrame = self.startFrame() timeExtent = self.__getTickArea(startFrame) oldPen = painter.pen() painter.setPen(QtGui.QColor(0, 255, 0)) painter.drawLine(timeExtent.left(), timeExtent.top(), timeExtent.left(), 0) metric = QtGui.QFontMetrics(painter.font()) frameString = str(int(startFrame)) xPos = timeExtent.left() - metric.width(frameString) / 2 yPos = metric.ascent() + 1 painter.drawText(xPos, yPos, frameString) painter.setPen(oldPen) def __paintEndTime(self, painter): endFrame = self.endFrame() timeExtent = self.__getTickArea(endFrame) oldPen = painter.pen() painter.setPen(QtGui.QColor(255, 0, 0)) painter.drawLine(timeExtent.left(), timeExtent.top(), timeExtent.left(), 0) metric = QtGui.QFontMetrics(painter.font()) frameString = str(int(endFrame)) xPos = timeExtent.left() - metric.width(frameString) / 2 yPos = metric.ascent() + 1 painter.drawText(xPos, yPos, frameString) painter.setPen(oldPen) def __paintFloatTime(self, painter): if self.__floatTime == None: return timeExtent = self.__getTickArea(self.__floatTime) oldPen = painter.pen() painter.setPen(QtGui.QColor(90, 90, 90)) painter.drawLine(timeExtent.left(), timeExtent.top(), timeExtent.left(), timeExtent.bottom()) if self.__selectionRange: painter.setPen(QtGui.QColor(255,255,255)) else: painter.setPen(QtGui.QColor(128, 128, 128)) metric = QtGui.QFontMetrics(painter.font()) frameString = str(self.__floatTime) xPos = timeExtent.left() - metric.width(frameString) / 2 yPos = timeExtent.top() + metric.ascent() painter.drawText(xPos, yPos, frameString) painter.setPen(oldPen) def __paintSelection(self, painter): if self.__selectionRange == None: return selection = (min(self.__selectionRange[0], self.__selectionRange[1]), max(self.__selectionRange[0], self.__selectionRange[1])) leftExtent = self.__getTickArea(selection[0]) rightExtent = self.__getTickArea(selection[1] - 1) selectionExtent = QtCore.QRect(leftExtent.left(), leftExtent.top(), rightExtent.right() - leftExtent.left() + 2, leftExtent.height()/2) painter.fillRect(selectionExtent, QtGui.QBrush(QtGui.QColor(75, 75, 75)))
def __getTickArea(self, time): tickArea = self.__getTickAreaExtent() tickSpacing = float(self.__getTickAreaExtent().width()) / max(1,(self.__frameRange[1] - self.__frameRange[0])) return QtCore.QRect(tickArea.left() + tickSpacing * (time - self.__frameRange[0]), tickArea.top(), tickSpacing, tickArea.height())
def __getTickAreaExtent(self): return QtCore.QRect(10, -self.height()/2, self.width() - self.__right_margin - 20, self.height())