Merge lp:~injaon/indicator-sysmonitor/new_sensors into lp:indicator-sysmonitor/trunk
- new_sensors
- Merge into 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 |
Related bugs: | |
Related blueprints: |
Support for several CPU
(Medium)
Add swap Usage
(Medium)
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Alex Eftimie | Approve | ||
Review via email: mp+129980@code.launchpad.net |
Commit message
Description of the change
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) |