Merge lp:~injaon/indicator-sysmonitor/new_sensors into lp:indicator-sysmonitor/trunk

Proposed by Gabriel Lopez
Status: Merged
Approved by: Gabriel Lopez
Approved revision: 23
Merged at revision: 22
Proposed branch: lp:~injaon/indicator-sysmonitor/new_sensors
Merge into: lp:indicator-sysmonitor/trunk
Diff against target: 384 lines (+136/-69)
1 file modified
indicator-sysmonitor (+136/-69)
To merge this branch: bzr merge lp:~injaon/indicator-sysmonitor/new_sensors
Reviewer Review Type Date Requested Status
Alex Eftimie Approve
Review via email: mp+129980@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Alex Eftimie (alexeftimie) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'indicator-sysmonitor'
--- indicator-sysmonitor 2012-09-26 17:53:59 +0000
+++ indicator-sysmonitor 2012-10-16 20:22:18 +0000
@@ -21,7 +21,6 @@
21import subprocess21import subprocess
22import psutil as ps22import psutil as ps
23import re23import re
24import gobject
25import gtk24import gtk
26gtk.gdk.threads_init()25gtk.gdk.threads_init()
27import appindicator26import appindicator
@@ -29,7 +28,7 @@
29logging.basicConfig(file=sys.stderr, level=logging.INFO)28logging.basicConfig(file=sys.stderr, level=logging.INFO)
3029
31B_UNITS = ['', 'KB', 'MB', 'GB', 'TB']30B_UNITS = ['', 'KB', 'MB', 'GB', 'TB']
32VERSION = '0.5.0~unreleased'31VERSION = '0.5.1~unreleased'
33HELP_MSG = """<span underline="single" size="x-large">{title}</span>32HELP_MSG = """<span underline="single" size="x-large">{title}</span>
3433
35{introduction}34{introduction}
@@ -47,16 +46,36 @@
47CPU {{cpu}} | MEM {{mem}} | root {{fs///}}46CPU {{cpu}} | MEM {{mem}} | root {{fs///}}
48""".format(47""".format(
49 title=_("Help Page"),48 title=_("Help Page"),
50 introduction=_("The sensors are the names of the devices you want to retrieve information from. They must be placed between brackets."),49 introduction=_("The sensors are the names of the devices you want to \
50 retrieve information from. They must be placed between brackets."),
51 basic=_("The basics are:"),51 basic=_("The basics are:"),
52 cpu_desc=_("It shows the average of CPU usage."),52 cpu_desc=_("It shows the average of CPU usage."),
53 mem_desc=_("It shows the physical memory in use."),53 mem_desc=_("It shows the physical memory in use."),
54 bat_desc=_("It shows the available battery which id is %d."),54 bat_desc=_("It shows the available battery which id is %d."),
55 net_desc=_("It shows the amount of data you are downloading and uploading through your network."),55 net_desc=_("It shows the amount of data you are downloading and uploading \
56 compose=_("Also there are the following sensors that are composed with two parts divided by two slashes."),56 through your network."),
57 compose=_("Also there are the following sensors that are composed with \
58 two parts divided by two slashes."),
57 fs_desc=_("Show available space in the file system."),59 fs_desc=_("Show available space in the file system."),
58 example=_("Example:"))60 example=_("Example:"))
5961
62supported_sensors = re.compile("\A(mem|swap|cpu\d*|net|bat\d*|fs//.+)\Z")
63settings = {
64 'custom_text': 'cpu: {cpu} mem: {mem}',
65 'interval': 2,
66 'on_startup': False,
67 'sensors': {
68 # 'name' => (desc, cmd)
69 'cpu\d*': (_('Average CPU usage'), True),
70 'mem': (_('Physical memory in use.'), True),
71 'net': (_('Network activity.'), True),
72 'bat\d*': (_('Network activity.'), True),
73 'fs//.+': (_('Available space in file system.'), True),
74 "swap": (_("Average swap usage"), True)
75
76 }
77 }
78
6079
61class ISMError(Exception):80class ISMError(Exception):
62 """General exception."""81 """General exception."""
@@ -72,25 +91,12 @@
72 dialog.run()91 dialog.run()
73 dialog.destroy()92 dialog.destroy()
7493
75supported_sensors = re.compile("mem|cpu|net|bat\d+|fs//.+?")
76settings = {
77 'custom_text': 'cpu: {cpu} mem: {mem}',
78 'interval': 2,
79 'on_startup': False,
80 'sensors': {
81 # 'name' => (desc, cmd)
82 'cpu': (_('Average CPU usage'), True),
83 'mem': (_('Physical memory in use.'), True),
84 'net': (_('Network activity.'), True),
85 'bat\d+': (_('Network activity.'), True),
86 'fs//.+': (_('Available space in file system.'), True)
87 }
88 }
89
9094
91class Sensor(object):95class Sensor(object):
92 """Singleton"""96 """Singleton"""
93 _instance = None97 _instance = None
98 bat = re.compile("\Abat\d*\Z")
99 cpus = re.compile("\Acpu\d+\Z")
94100
95 def __init__(self):101 def __init__(self):
96 """It must not be called. Use Sensor.get_instace()102 """It must not be called. Use Sensor.get_instace()
@@ -101,6 +107,16 @@
101 Sensor._instance = self107 Sensor._instance = self
102 self.update_regex()108 self.update_regex()
103109
110 @staticmethod
111 def update_regex(names=None):
112 if names is None:
113 names = settings["sensors"].keys()
114
115 reg = '|'.join(names)
116 reg = "\A({})\Z".format(reg)
117 global supported_sensors
118 supported_sensors = re.compile("{}".format(reg))
119
104 @classmethod120 @classmethod
105 def get_instance(cls):121 def get_instance(cls):
106 """Returns the unique instance of Sensor."""122 """Returns the unique instance of Sensor."""
@@ -114,6 +130,23 @@
114 """Checks if the sensor name exists"""130 """Checks if the sensor name exists"""
115 return bool(supported_sensors.match(name))131 return bool(supported_sensors.match(name))
116132
133 @staticmethod
134 def check(sensor):
135 if sensor.startswith("fs//"):
136 path = sensor.split("//")[1]
137 if not os.path.exists(path):
138 raise ISMError(_("Path: {} doesn't exists.").format(path))
139
140 elif Sensor.cpus.match(sensor):
141 nber = int(sensor[3:])
142 if nber >= ps.NUM_CPUS:
143 raise ISMError(_("Invalid number of CPU."))
144
145 elif Sensor.bat.match(sensor):
146 bat_id = int(sensor[3:]) if len(sensor) > 3 else 0
147 if bat_id >= len(os.listdir("/proc/acpi/battery/")):
148 raise ISMError(_("Invalid number of Batterry."))
149
117 def add(self, name, desc, cmd):150 def add(self, name, desc, cmd):
118 """Adds a custom sensors."""151 """Adds a custom sensors."""
119 if Sensor.exists(name):152 if Sensor.exists(name):
@@ -122,15 +155,6 @@
122 settings["sensors"][name] = (desc, cmd)155 settings["sensors"][name] = (desc, cmd)
123 self.update_regex()156 self.update_regex()
124157
125 @staticmethod
126 def update_regex(names=None):
127 if names is None:
128 names = settings["sensors"].keys()
129
130 reg = '|'.join(names)
131 global supported_sensors
132 supported_sensors = re.compile("{}".format(reg))
133
134 def delete(self, name):158 def delete(self, name):
135 """Deletes a custom sensors."""159 """Deletes a custom sensors."""
136 sensors = settings['sensors']160 sensors = settings['sensors']
@@ -176,7 +200,10 @@
176 self._parent = parent200 self._parent = parent
177 self.last = ps.cpu_times()201 self.last = ps.cpu_times()
178202
179 def _fetch_cpu(self):203 def _fetch_cpu(self, percpu=False):
204 if percpu:
205 return ps.cpu_percent(interval=0, percpu=True)
206
180 last = self.last207 last = self.last
181 current = ps.cpu_times()208 current = ps.cpu_times()
182209
@@ -198,30 +225,61 @@
198 else:225 else:
199 return 0226 return 0
200227
228 def _fetch_swap(self):
229 """Return the swap usage in percent"""
230 usage = 0
231 total = 0
232 try:
233 with open("/proc/swaps") as swaps:
234 swaps.readline()
235 for line in swaps.readlines():
236 dummy, dummy, total_, usage_, dummy = line.split()
237 total += int(total_)
238 usage += int(usage_)
239
240 return usage * 100 / total
241
242 except IOError:
243 return "N/A"
244
201 def _fetch_mem(self):245 def _fetch_mem(self):
202 """It gets the total memory info and return the used in percentaje."""246 """It gets the total memory info and return the used in percent."""
203 with open('/proc/meminfo') as meminfo:247 with open('/proc/meminfo') as meminfo:
204 total = StatusFetcher.digit_regex.findall(meminfo.readline()).pop()248 total = StatusFetcher.digit_regex.findall(meminfo.readline()).pop()
205 free = StatusFetcher.digit_regex.findall(meminfo.readline()).pop()249 free = StatusFetcher.digit_regex.findall(meminfo.readline()).pop()
206 meminfo.readline()250 meminfo.readline()
207 cached = StatusFetcher.digit_regex.findall(meminfo.readline()).pop()251 cached = StatusFetcher.digit_regex.findall(
252 meminfo.readline()).pop()
208 free = int(free) + int(cached)253 free = int(free) + int(cached)
209 return 100 - 100 * free / float(total)254 return 100 - 100 * free / float(total)
210255
211 def _fetch_bat(self, bat_id):256 def _fetch_bat(self, batid):
212 current_bat = subprocess.Popen(257 """Fetch the the amount of remaining battery"""
213 "grep 'remaining capacity:' /proc/acpi/battery/BAT%s/state |awk '{print $3}' |grep [0-9] || echo 10" % bat_id,258 try:
214 stdout=subprocess.PIPE, shell=True).communicate()[0].strip()259 with open("/proc/acpi/battery/BAT{}/state".format(batid)) as state:
215 #total_bat = subprocess.Popen("cat /proc/acpi/battery/BAT0/info |grep -i 'design capacity:' |awk '{print $3}'",260 while True:
216 total_bat = subprocess.Popen(261 line = state.readline()
217 "grep 'last full capacity:' /proc/acpi/battery/BAT%s/info | awk '{print $4}' |grep [0-9] || echo 65130" % bat_id,262 if "remaining capacity:" in line:
218 stdout=subprocess.PIPE, shell=True).communicate()[0].strip()263 remaining = int(line.split()[2])
219 return 100 * float(current_bat) / float(total_bat)264 break
265
266 with open("/proc/acpi/battery/BAT{}/info".format(batid)) as info:
267 while True:
268 line = info.readline()
269 if "last full capacity" in line:
270 capacity = int(line.split()[3])
271 break
272
273 except IOError:
274 return "N/A"
275
276 return remaining * 100.0 / capacity
220277
221 def _fetch_net(self):278 def _fetch_net(self):
222 """It use the command "ifstat" to know the total net usage"""279 """It use the command "ifstat" to know the total net usage"""
223 total_net = subprocess.Popen(280 total_net = subprocess.Popen(
224 "ifstat -a -n -q -S -T 0.5 1 | tail -1 | awk '{ printf(\"↓%dkB/s ↑%dkB/s\", $6, $7) }'",281 "ifstat -a -n -q -S -T 0.5 1 | tail -1 | awk \
282 '{ printf(\"↓%dkB/s ↑%dkB/s\", $6, $7) }'",
225 stdout=subprocess.PIPE, shell=True).communicate()[0].strip()283 stdout=subprocess.PIPE, shell=True).communicate()[0].strip()
226 return total_net284 return total_net
227285
@@ -229,29 +287,33 @@
229 """Return a dict whose element are the sensors287 """Return a dict whose element are the sensors
230 and their values"""288 and their values"""
231 res = {}289 res = {}
232 for sensor in Preferences.sensors_regex.findall(settings["custom_text"]):290 cpus = None
291 for sensor in Preferences.sensors_regex.findall(
292 settings["custom_text"]):
233 sensor = sensor[1:-1]293 sensor = sensor[1:-1]
234 if not Sensor.exists(sensor):
235 continue
236 if sensor == 'cpu':294 if sensor == 'cpu':
237 res['cpu'] = "{:02.0f}%".format(self._fetch_cpu())295 res['cpu'] = "{:02.0f}%".format(self._fetch_cpu())
296 elif Sensor.cpus.match(sensor):
297 if cpus is None:
298 cpus = self._fetch_cpu(percpu=True)
299 res[sensor] = "{:02.0f}%".format(cpus[int(sensor[3:])])
300
238 elif sensor == 'mem':301 elif sensor == 'mem':
239 res['mem'] = '{:02.0f}%'.format(self._fetch_mem())302 res['mem'] = '{:02.0f}%'.format(self._fetch_mem())
240 elif sensor == 'net':303 elif sensor == 'net':
241 res['net'] = self._fetch_net()304 res['net'] = self._fetch_net()
242305
243 elif sensor.startswith('bat'):306 elif Sensor.bat.match(sensor):
244 bat_id = sensor[4:]307 bat_id = int(sensor[3:]) if len(sensor) > 3 else 0
245 try:
246 bat_id = int(bat_id)
247 except:
248 bat_id = 0
249 res[sensor] = '{:02.0f}%'.format(self._fetch_bat(bat_id))308 res[sensor] = '{:02.0f}%'.format(self._fetch_bat(bat_id))
250309
251 elif sensor.startswith('fs//'):310 elif sensor.startswith('fs//'):
252 parts = sensor.split('//')311 parts = sensor.split('//')
253 res[sensor] = self._fetch_fs(parts[1])312 res[sensor] = self._fetch_fs(parts[1])
254313
314 elif sensor == "swap":
315 res[sensor] = '{:02.0f}%'.format(self._fetch_swap())
316
255 else: # custom sensor317 else: # custom sensor
256 res[sensor] = self._exec(settings["sensors"][sensor][1])318 res[sensor] = self._exec(settings["sensors"][sensor][1])
257319
@@ -468,8 +530,8 @@
468530
469class Preferences(gtk.Dialog):531class Preferences(gtk.Dialog):
470 """It define the the Preferences Dialog and its operations."""532 """It define the the Preferences Dialog and its operations."""
471 AUTOSTART_PATH = '{}/.config/autostart/indicator-sysmonitor.desktop'.format(533 AUTOSTART_PATH = '{}/.config/autostart/indicator-sysmonitor.desktop'\
472 os.getenv("HOME"))534 .format(os.getenv("HOME"))
473 DESKTOP_PATH = '/usr/share/applications/indicator-sysmonitor.desktop'535 DESKTOP_PATH = '/usr/share/applications/indicator-sysmonitor.desktop'
474 sensors_regex = re.compile("{.+?}")536 sensors_regex = re.compile("{.+?}")
475537
@@ -477,16 +539,18 @@
477 """It creates the widget of the dialogs"""539 """It creates the widget of the dialogs"""
478 gtk.Dialog.__init__(self)540 gtk.Dialog.__init__(self)
479 self.ind_parent = parent541 self.ind_parent = parent
480 self.connect('delete-event', self.on_cancel)542 self.custom_entry = None
481 self.set_title(_('Preferences'))543 self.interval_entry = None
482 self.resize(400, 350)
483 self.set_position(gtk.WIN_POS_CENTER_ALWAYS)
484 self._create_content()544 self._create_content()
485 self.set_data()545 self.set_data()
486 self.show_all()546 self.show_all()
487547
488 def _create_content(self):548 def _create_content(self):
489 """It creates the content for this dialog."""549 """It creates the content for this dialog."""
550 self.connect('delete-event', self.on_cancel)
551 self.set_title(_('Preferences'))
552 self.resize(400, 350)
553 self.set_position(gtk.WIN_POS_CENTER_ALWAYS)
490 notebook = gtk.Notebook()554 notebook = gtk.Notebook()
491 notebook.set_border_width(4)555 notebook.set_border_width(4)
492556
@@ -504,10 +568,10 @@
504 vbox.pack_start(hbox, expand=False, fill=False)568 vbox.pack_start(hbox, expand=False, fill=False)
505569
506 hbox = gtk.HBox()570 hbox = gtk.HBox()
507 l = gtk.Label(571 label = gtk.Label(
508 _('This is indicator-sysmonitor version: {}').format(VERSION))572 _('This is indicator-sysmonitor version: {}').format(VERSION))
509 l.set_alignment(0.5, 0.5)573 label.set_alignment(0.5, 0.5)
510 hbox.pack_start(l)574 hbox.pack_start(label)
511 vbox.pack_end(hbox)575 vbox.pack_end(hbox)
512 notebook.append_page(vbox, gtk.Label(_('General')))576 notebook.append_page(vbox, gtk.Label(_('General')))
513 # }}}577 # }}}
@@ -587,14 +651,17 @@
587 for sensor in sensors:651 for sensor in sensors:
588 sensor = sensor[1:-1]652 sensor = sensor[1:-1]
589 if not Sensor.exists(sensor):653 if not Sensor.exists(sensor):
590 raise ISMError(_("{} sensor not supported.").format(sensor))654 raise ISMError(_("{{{}}} sensor not supported.").
655 format(sensor))
656 # Check if the sensor is well-formed
657 Sensor.check(sensor)
591658
592 try:659 try:
593 interval = float(self.interval_entry.get_text())660 interval = float(self.interval_entry.get_text())
594 if interval <= 0:661 if interval <= 0:
595 raise ISMError(_("Interval value is not valid."))662 raise ISMError(_("Interval value is not valid."))
596663
597 except ValueError, ex:664 except ValueError:
598 raise ISMError(_("Interval value is not valid."))665 raise ISMError(_("Interval value is not valid."))
599666
600 settings["custom_text"] = custom_text667 settings["custom_text"] = custom_text
@@ -618,8 +685,8 @@
618 try:685 try:
619 shutil.copy(Preferences.DESKTOP_PATH,686 shutil.copy(Preferences.DESKTOP_PATH,
620 Preferences.AUTOSTART_PATH)687 Preferences.AUTOSTART_PATH)
621 except Exception as e:688 except Exception, ex:
622 logging.exception(e)689 logging.exception(ex)
623690
624 def get_autostart(self):691 def get_autostart(self):
625 return os.path.exists(Preferences.AUTOSTART_PATH)692 return os.path.exists(Preferences.AUTOSTART_PATH)
@@ -725,8 +792,8 @@
725 Sensor.update_regex()792 Sensor.update_regex()
726 self.update_indicator_guide()793 self.update_indicator_guide()
727794
728 except Exception as e:795 except Exception as ex:
729 logging.exception(e)796 logging.exception(ex)
730 logging.error('Reading settings failed')797 logging.error('Reading settings failed')
731798
732 @staticmethod799 @staticmethod
@@ -737,8 +804,8 @@
737 with open(IndicatorSysmonitor.SETTINGS_FILE, 'w') as f:804 with open(IndicatorSysmonitor.SETTINGS_FILE, 'w') as f:
738 f.write(json.dumps(settings))805 f.write(json.dumps(settings))
739806
740 except Exception as e:807 except Exception as ex:
741 logging.exception(e)808 logging.exception(ex)
742 logging.error('Writting settings failed')809 logging.error('Writting settings failed')
743810
744 # actions raised from menu811 # actions raised from menu
@@ -789,7 +856,7 @@
789 self._help_dialog = None856 self._help_dialog = None
790857
791858
792from optparse import OptionParser859from optparse import OptionParser # TODO: optparse is deprecated
793860
794if __name__ == "__main__":861if __name__ == "__main__":
795 parser = OptionParser("usage: %prog [options]", version="%prog " + VERSION)862 parser = OptionParser("usage: %prog [options]", version="%prog " + VERSION)

Subscribers

People subscribed via source and target branches

to all changes: