top_plugin.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 # Copyright (c) 2013, Oregon State University
3 # All rights reserved.
4 
5 # Redistribution and use in source and binary forms, with or without
6 # modification, are permitted provided that the following conditions are met:
7 # * Redistributions of source code must retain the above copyright
8 # notice, this list of conditions and the following disclaimer.
9 # * Redistributions in binary form must reproduce the above copyright
10 # notice, this list of conditions and the following disclaimer in the
11 # documentation and/or other materials provided with the distribution.
12 # * Neither the name of the Oregon State University nor the
13 # names of its contributors may be used to endorse or promote products
14 # derived from this software without specific prior written permission.
15 
16 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 # DISCLAIMED. IN NO EVENT SHALL OREGON STATE UNIVERSITY BE LIABLE FOR ANY
20 # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 
27 # Author Dan Lazewatsky/lazewatd@engr.orst.edu
28 
29 from __future__ import division
30 
31 from qt_gui.plugin import Plugin
32 from python_qt_binding import loadUi
33 from python_qt_binding.QtWidgets import QLabel, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QCheckBox, QWidget, QToolBar, QLineEdit, QPushButton
34 from python_qt_binding.QtCore import Qt, QTimer
35 
36 from rqt_top.node_info import NodeInfo
37 import re
38 from threading import RLock
39 import textwrap
40 
41 
42 class TopWidgetItem(QTreeWidgetItem):
43 
44  def __init__(self, parent=None):
45  super(TopWidgetItem, self).__init__(parent)
46 
47  def __lt__(self, other):
48  col = self.treeWidget().sortColumn()
49  dtype = Top.SORT_TYPE[col]
50  return dtype(self.text(col)) < dtype(other.text(col))
51 
52 
53 class Top(Plugin):
54  NODE_FIELDS = [ 'pid', 'get_cpu_percent', 'get_memory_percent', 'get_num_threads']
55  OUT_FIELDS = ['node_name', 'pid', 'cpu_percent', 'memory_percent', 'num_threads' ]
56  FORMAT_STRS = ['%s', '%s', '%0.2f', '%0.2f', '%s' ]
57  NODE_LABELS = ['Node', 'PID', 'CPU %', 'Mem %', 'Num Threads' ]
58  SORT_TYPE = [str, str, float, float, float ]
59  TOOLTIPS = {
60  0: ('cmdline', lambda x: '\n'.join(textwrap.wrap(' '.join(x)))),
61  3: ('memory_info', lambda x: ('Resident: %0.2f MiB, Virtual: %0.2f MiB' % (x[0] / 2**20, x[1] / 2**20)))
62  }
63 
64  _node_info = NodeInfo()
65 
66  name_filter = re.compile('')
67 
68  def __init__(self, context):
69  super(Top, self).__init__(context)
70  # Give QObjects reasonable names
71  self.setObjectName('Top')
72 
73  # Process standalone plugin command-line arguments
74  from argparse import ArgumentParser
75  parser = ArgumentParser()
76  # Add argument(s) to the parser.
77  parser.add_argument("-q", "--quiet", action="store_true",
78  dest="quiet",
79  help="Put plugin in silent mode")
80  args, unknowns = parser.parse_known_args(context.argv())
81  # if not args.quiet:
82  # print 'arguments: ', args
83  # print 'unknowns: ', unknowns
84 
85  self._selected_node = ''
86  self._selected_node_lock = RLock()
87 
88  # Setup the toolbar
89  self._toolbar = QToolBar()
90  self._filter_box = QLineEdit()
91  self._regex_box = QCheckBox()
92  self._regex_box.setText('regex')
93  self._toolbar.addWidget(QLabel('Filter'))
94  self._toolbar.addWidget(self._filter_box)
95  self._toolbar.addWidget(self._regex_box)
96 
97  self._filter_box.returnPressed.connect(self.update_filter)
98  self._regex_box.stateChanged.connect(self.update_filter)
99 
100  # Create a container widget and give it a layout
101  self._container = QWidget()
102  self._container.setWindowTitle('Process Monitor')
103  self._layout = QVBoxLayout()
104  self._container.setLayout(self._layout)
105 
106  self._layout.addWidget(self._toolbar)
107 
108  # Create the table widget
109  self._table_widget = QTreeWidget()
110  self._table_widget.setObjectName('TopTable')
111  self._table_widget.setColumnCount(len(self.NODE_LABELS))
112  self._table_widget.setHeaderLabels(self.NODE_LABELS)
113  self._table_widget.itemClicked.connect(self._tableItemClicked)
114  self._table_widget.setSortingEnabled(True)
115  self._table_widget.setAlternatingRowColors(True)
116 
117  self._layout.addWidget(self._table_widget)
118  context.add_widget(self._container)
119 
120  # Add a button for killing nodes
121  self._kill_button = QPushButton('Kill Node')
122  self._layout.addWidget(self._kill_button)
123  self._kill_button.clicked.connect(self._kill_node)
124 
125  # Update twice since the first cpu% lookup will always return 0
126  self.update_table()
127  self.update_table()
128 
129  self._table_widget.resizeColumnToContents(0)
130 
131  # Start a timer to trigger updates
132  self._update_timer = QTimer()
133  self._update_timer.setInterval(1000)
134  self._update_timer.timeout.connect(self.update_table)
135  self._update_timer.start()
136 
137  def _tableItemClicked(self, item, column):
138  with self._selected_node_lock:
139  self._selected_node = item.text(0)
140 
141  def update_filter(self, *args):
142  if self._regex_box.isChecked():
143  expr = self._filter_box.text()
144  else:
145  expr = re.escape(self._filter_box.text())
146  self.name_filter = re.compile(expr)
147  self.update_table()
148 
149  def _kill_node(self):
150  self._node_info.kill_node(self._selected_node)
151 
152  def update_one_item(self, row, info):
153  twi = TopWidgetItem()
154  for col, field in enumerate(self.OUT_FIELDS):
155  val = info[field]
156  twi.setText(col, self.FORMAT_STRS[col] % val)
157  self._table_widget.insertTopLevelItem(row, twi)
158 
159  for col, (key, func) in self.TOOLTIPS.items():
160  twi.setToolTip(col, func(info[key]))
161 
162  with self._selected_node_lock:
163  if twi.text(0) == self._selected_node:
164  twi.setSelected(True)
165 
166  twi.setHidden(len(self.name_filter.findall(info['node_name'])) == 0)
167 
168  def update_table(self):
169  self._table_widget.clear()
170  infos = self._node_info.get_all_node_fields(self.NODE_FIELDS)
171  for nx, info in enumerate(infos):
172  self.update_one_item(nx, info)
173 
174  def shutdown_plugin(self):
175  self._update_timer.stop()
176 
177  def save_settings(self, plugin_settings, instance_settings):
178  instance_settings.set_value('filter_text', self._filter_box.text())
179  instance_settings.set_value('is_regex', int(self._regex_box.checkState()))
180 
181  def restore_settings(self, plugin_settings, instance_settings):
182  self._filter_box.setText(instance_settings.value('filter_text'))
183  is_regex_int = instance_settings.value('is_regex')
184  if is_regex_int:
185  self._regex_box.setCheckState(Qt.CheckState(is_regex_int))
186  else:
187  self._regex_box.setCheckState(Qt.CheckState(0))
188  self.update_filter()
189 
190  # def trigger_configuration(self):
191  # Comment in to signal that the plugin has a way to configure it
192  # Usually used to open a configuration dialog
def __init__(self, context)
Definition: top_plugin.py:68
def _tableItemClicked(self, item, column)
Definition: top_plugin.py:137
def update_one_item(self, row, info)
Definition: top_plugin.py:152
def shutdown_plugin(self)
Definition: top_plugin.py:174
def __init__(self, parent=None)
Definition: top_plugin.py:44
def save_settings(self, plugin_settings, instance_settings)
Definition: top_plugin.py:177
def restore_settings(self, plugin_settings, instance_settings)
Definition: top_plugin.py:181
def update_filter(self, args)
Definition: top_plugin.py:141


rqt_top
Author(s): Dan Lazewatsky
autogenerated on Sat Jun 8 2019 04:43:00