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
1=== modified file 'indicator-sysmonitor'
2--- indicator-sysmonitor 2012-09-26 17:53:59 +0000
3+++ indicator-sysmonitor 2012-10-16 20:22:18 +0000
4@@ -21,7 +21,6 @@
5 import subprocess
6 import psutil as ps
7 import re
8-import gobject
9 import gtk
10 gtk.gdk.threads_init()
11 import appindicator
12@@ -29,7 +28,7 @@
13 logging.basicConfig(file=sys.stderr, level=logging.INFO)
14
15 B_UNITS = ['', 'KB', 'MB', 'GB', 'TB']
16-VERSION = '0.5.0~unreleased'
17+VERSION = '0.5.1~unreleased'
18 HELP_MSG = """<span underline="single" size="x-large">{title}</span>
19
20 {introduction}
21@@ -47,16 +46,36 @@
22 CPU {{cpu}} | MEM {{mem}} | root {{fs///}}
23 """.format(
24 title=_("Help Page"),
25- introduction=_("The sensors are the names of the devices you want to retrieve information from. They must be placed between brackets."),
26+ introduction=_("The sensors are the names of the devices you want to \
27+ retrieve information from. They must be placed between brackets."),
28 basic=_("The basics are:"),
29 cpu_desc=_("It shows the average of CPU usage."),
30 mem_desc=_("It shows the physical memory in use."),
31 bat_desc=_("It shows the available battery which id is %d."),
32- net_desc=_("It shows the amount of data you are downloading and uploading through your network."),
33- compose=_("Also there are the following sensors that are composed with two parts divided by two slashes."),
34+ net_desc=_("It shows the amount of data you are downloading and uploading \
35+ through your network."),
36+ compose=_("Also there are the following sensors that are composed with \
37+ two parts divided by two slashes."),
38 fs_desc=_("Show available space in the file system."),
39 example=_("Example:"))
40
41+supported_sensors = re.compile("\A(mem|swap|cpu\d*|net|bat\d*|fs//.+)\Z")
42+settings = {
43+ 'custom_text': 'cpu: {cpu} mem: {mem}',
44+ 'interval': 2,
45+ 'on_startup': False,
46+ 'sensors': {
47+ # 'name' => (desc, cmd)
48+ 'cpu\d*': (_('Average CPU usage'), True),
49+ 'mem': (_('Physical memory in use.'), True),
50+ 'net': (_('Network activity.'), True),
51+ 'bat\d*': (_('Network activity.'), True),
52+ 'fs//.+': (_('Available space in file system.'), True),
53+ "swap": (_("Average swap usage"), True)
54+
55+ }
56+ }
57+
58
59 class ISMError(Exception):
60 """General exception."""
61@@ -72,25 +91,12 @@
62 dialog.run()
63 dialog.destroy()
64
65-supported_sensors = re.compile("mem|cpu|net|bat\d+|fs//.+?")
66-settings = {
67- 'custom_text': 'cpu: {cpu} mem: {mem}',
68- 'interval': 2,
69- 'on_startup': False,
70- 'sensors': {
71- # 'name' => (desc, cmd)
72- 'cpu': (_('Average CPU usage'), True),
73- 'mem': (_('Physical memory in use.'), True),
74- 'net': (_('Network activity.'), True),
75- 'bat\d+': (_('Network activity.'), True),
76- 'fs//.+': (_('Available space in file system.'), True)
77- }
78- }
79-
80
81 class Sensor(object):
82 """Singleton"""
83 _instance = None
84+ bat = re.compile("\Abat\d*\Z")
85+ cpus = re.compile("\Acpu\d+\Z")
86
87 def __init__(self):
88 """It must not be called. Use Sensor.get_instace()
89@@ -101,6 +107,16 @@
90 Sensor._instance = self
91 self.update_regex()
92
93+ @staticmethod
94+ def update_regex(names=None):
95+ if names is None:
96+ names = settings["sensors"].keys()
97+
98+ reg = '|'.join(names)
99+ reg = "\A({})\Z".format(reg)
100+ global supported_sensors
101+ supported_sensors = re.compile("{}".format(reg))
102+
103 @classmethod
104 def get_instance(cls):
105 """Returns the unique instance of Sensor."""
106@@ -114,6 +130,23 @@
107 """Checks if the sensor name exists"""
108 return bool(supported_sensors.match(name))
109
110+ @staticmethod
111+ def check(sensor):
112+ if sensor.startswith("fs//"):
113+ path = sensor.split("//")[1]
114+ if not os.path.exists(path):
115+ raise ISMError(_("Path: {} doesn't exists.").format(path))
116+
117+ elif Sensor.cpus.match(sensor):
118+ nber = int(sensor[3:])
119+ if nber >= ps.NUM_CPUS:
120+ raise ISMError(_("Invalid number of CPU."))
121+
122+ elif Sensor.bat.match(sensor):
123+ bat_id = int(sensor[3:]) if len(sensor) > 3 else 0
124+ if bat_id >= len(os.listdir("/proc/acpi/battery/")):
125+ raise ISMError(_("Invalid number of Batterry."))
126+
127 def add(self, name, desc, cmd):
128 """Adds a custom sensors."""
129 if Sensor.exists(name):
130@@ -122,15 +155,6 @@
131 settings["sensors"][name] = (desc, cmd)
132 self.update_regex()
133
134- @staticmethod
135- def update_regex(names=None):
136- if names is None:
137- names = settings["sensors"].keys()
138-
139- reg = '|'.join(names)
140- global supported_sensors
141- supported_sensors = re.compile("{}".format(reg))
142-
143 def delete(self, name):
144 """Deletes a custom sensors."""
145 sensors = settings['sensors']
146@@ -176,7 +200,10 @@
147 self._parent = parent
148 self.last = ps.cpu_times()
149
150- def _fetch_cpu(self):
151+ def _fetch_cpu(self, percpu=False):
152+ if percpu:
153+ return ps.cpu_percent(interval=0, percpu=True)
154+
155 last = self.last
156 current = ps.cpu_times()
157
158@@ -198,30 +225,61 @@
159 else:
160 return 0
161
162+ def _fetch_swap(self):
163+ """Return the swap usage in percent"""
164+ usage = 0
165+ total = 0
166+ try:
167+ with open("/proc/swaps") as swaps:
168+ swaps.readline()
169+ for line in swaps.readlines():
170+ dummy, dummy, total_, usage_, dummy = line.split()
171+ total += int(total_)
172+ usage += int(usage_)
173+
174+ return usage * 100 / total
175+
176+ except IOError:
177+ return "N/A"
178+
179 def _fetch_mem(self):
180- """It gets the total memory info and return the used in percentaje."""
181+ """It gets the total memory info and return the used in percent."""
182 with open('/proc/meminfo') as meminfo:
183 total = StatusFetcher.digit_regex.findall(meminfo.readline()).pop()
184 free = StatusFetcher.digit_regex.findall(meminfo.readline()).pop()
185 meminfo.readline()
186- cached = StatusFetcher.digit_regex.findall(meminfo.readline()).pop()
187+ cached = StatusFetcher.digit_regex.findall(
188+ meminfo.readline()).pop()
189 free = int(free) + int(cached)
190 return 100 - 100 * free / float(total)
191
192- def _fetch_bat(self, bat_id):
193- current_bat = subprocess.Popen(
194- "grep 'remaining capacity:' /proc/acpi/battery/BAT%s/state |awk '{print $3}' |grep [0-9] || echo 10" % bat_id,
195- stdout=subprocess.PIPE, shell=True).communicate()[0].strip()
196- #total_bat = subprocess.Popen("cat /proc/acpi/battery/BAT0/info |grep -i 'design capacity:' |awk '{print $3}'",
197- total_bat = subprocess.Popen(
198- "grep 'last full capacity:' /proc/acpi/battery/BAT%s/info | awk '{print $4}' |grep [0-9] || echo 65130" % bat_id,
199- stdout=subprocess.PIPE, shell=True).communicate()[0].strip()
200- return 100 * float(current_bat) / float(total_bat)
201+ def _fetch_bat(self, batid):
202+ """Fetch the the amount of remaining battery"""
203+ try:
204+ with open("/proc/acpi/battery/BAT{}/state".format(batid)) as state:
205+ while True:
206+ line = state.readline()
207+ if "remaining capacity:" in line:
208+ remaining = int(line.split()[2])
209+ break
210+
211+ with open("/proc/acpi/battery/BAT{}/info".format(batid)) as info:
212+ while True:
213+ line = info.readline()
214+ if "last full capacity" in line:
215+ capacity = int(line.split()[3])
216+ break
217+
218+ except IOError:
219+ return "N/A"
220+
221+ return remaining * 100.0 / capacity
222
223 def _fetch_net(self):
224 """It use the command "ifstat" to know the total net usage"""
225 total_net = subprocess.Popen(
226- "ifstat -a -n -q -S -T 0.5 1 | tail -1 | awk '{ printf(\"↓%dkB/s ↑%dkB/s\", $6, $7) }'",
227+ "ifstat -a -n -q -S -T 0.5 1 | tail -1 | awk \
228+ '{ printf(\"↓%dkB/s ↑%dkB/s\", $6, $7) }'",
229 stdout=subprocess.PIPE, shell=True).communicate()[0].strip()
230 return total_net
231
232@@ -229,29 +287,33 @@
233 """Return a dict whose element are the sensors
234 and their values"""
235 res = {}
236- for sensor in Preferences.sensors_regex.findall(settings["custom_text"]):
237+ cpus = None
238+ for sensor in Preferences.sensors_regex.findall(
239+ settings["custom_text"]):
240 sensor = sensor[1:-1]
241- if not Sensor.exists(sensor):
242- continue
243 if sensor == 'cpu':
244 res['cpu'] = "{:02.0f}%".format(self._fetch_cpu())
245+ elif Sensor.cpus.match(sensor):
246+ if cpus is None:
247+ cpus = self._fetch_cpu(percpu=True)
248+ res[sensor] = "{:02.0f}%".format(cpus[int(sensor[3:])])
249+
250 elif sensor == 'mem':
251 res['mem'] = '{:02.0f}%'.format(self._fetch_mem())
252 elif sensor == 'net':
253 res['net'] = self._fetch_net()
254
255- elif sensor.startswith('bat'):
256- bat_id = sensor[4:]
257- try:
258- bat_id = int(bat_id)
259- except:
260- bat_id = 0
261+ elif Sensor.bat.match(sensor):
262+ bat_id = int(sensor[3:]) if len(sensor) > 3 else 0
263 res[sensor] = '{:02.0f}%'.format(self._fetch_bat(bat_id))
264
265 elif sensor.startswith('fs//'):
266 parts = sensor.split('//')
267 res[sensor] = self._fetch_fs(parts[1])
268
269+ elif sensor == "swap":
270+ res[sensor] = '{:02.0f}%'.format(self._fetch_swap())
271+
272 else: # custom sensor
273 res[sensor] = self._exec(settings["sensors"][sensor][1])
274
275@@ -468,8 +530,8 @@
276
277 class Preferences(gtk.Dialog):
278 """It define the the Preferences Dialog and its operations."""
279- AUTOSTART_PATH = '{}/.config/autostart/indicator-sysmonitor.desktop'.format(
280- os.getenv("HOME"))
281+ AUTOSTART_PATH = '{}/.config/autostart/indicator-sysmonitor.desktop'\
282+ .format(os.getenv("HOME"))
283 DESKTOP_PATH = '/usr/share/applications/indicator-sysmonitor.desktop'
284 sensors_regex = re.compile("{.+?}")
285
286@@ -477,16 +539,18 @@
287 """It creates the widget of the dialogs"""
288 gtk.Dialog.__init__(self)
289 self.ind_parent = parent
290- self.connect('delete-event', self.on_cancel)
291- self.set_title(_('Preferences'))
292- self.resize(400, 350)
293- self.set_position(gtk.WIN_POS_CENTER_ALWAYS)
294+ self.custom_entry = None
295+ self.interval_entry = None
296 self._create_content()
297 self.set_data()
298 self.show_all()
299
300 def _create_content(self):
301 """It creates the content for this dialog."""
302+ self.connect('delete-event', self.on_cancel)
303+ self.set_title(_('Preferences'))
304+ self.resize(400, 350)
305+ self.set_position(gtk.WIN_POS_CENTER_ALWAYS)
306 notebook = gtk.Notebook()
307 notebook.set_border_width(4)
308
309@@ -504,10 +568,10 @@
310 vbox.pack_start(hbox, expand=False, fill=False)
311
312 hbox = gtk.HBox()
313- l = gtk.Label(
314+ label = gtk.Label(
315 _('This is indicator-sysmonitor version: {}').format(VERSION))
316- l.set_alignment(0.5, 0.5)
317- hbox.pack_start(l)
318+ label.set_alignment(0.5, 0.5)
319+ hbox.pack_start(label)
320 vbox.pack_end(hbox)
321 notebook.append_page(vbox, gtk.Label(_('General')))
322 # }}}
323@@ -587,14 +651,17 @@
324 for sensor in sensors:
325 sensor = sensor[1:-1]
326 if not Sensor.exists(sensor):
327- raise ISMError(_("{} sensor not supported.").format(sensor))
328+ raise ISMError(_("{{{}}} sensor not supported.").
329+ format(sensor))
330+ # Check if the sensor is well-formed
331+ Sensor.check(sensor)
332
333 try:
334 interval = float(self.interval_entry.get_text())
335 if interval <= 0:
336 raise ISMError(_("Interval value is not valid."))
337
338- except ValueError, ex:
339+ except ValueError:
340 raise ISMError(_("Interval value is not valid."))
341
342 settings["custom_text"] = custom_text
343@@ -618,8 +685,8 @@
344 try:
345 shutil.copy(Preferences.DESKTOP_PATH,
346 Preferences.AUTOSTART_PATH)
347- except Exception as e:
348- logging.exception(e)
349+ except Exception, ex:
350+ logging.exception(ex)
351
352 def get_autostart(self):
353 return os.path.exists(Preferences.AUTOSTART_PATH)
354@@ -725,8 +792,8 @@
355 Sensor.update_regex()
356 self.update_indicator_guide()
357
358- except Exception as e:
359- logging.exception(e)
360+ except Exception as ex:
361+ logging.exception(ex)
362 logging.error('Reading settings failed')
363
364 @staticmethod
365@@ -737,8 +804,8 @@
366 with open(IndicatorSysmonitor.SETTINGS_FILE, 'w') as f:
367 f.write(json.dumps(settings))
368
369- except Exception as e:
370- logging.exception(e)
371+ except Exception as ex:
372+ logging.exception(ex)
373 logging.error('Writting settings failed')
374
375 # actions raised from menu
376@@ -789,7 +856,7 @@
377 self._help_dialog = None
378
379
380-from optparse import OptionParser
381+from optparse import OptionParser # TODO: optparse is deprecated
382
383 if __name__ == "__main__":
384 parser = OptionParser("usage: %prog [options]", version="%prog " + VERSION)

Subscribers

People subscribed via source and target branches

to all changes: