1 /**
2 * This file is a part of the Cairo-Dock project
3 *
4 * Copyright : (C) see the 'copyright' file.
5 * E-mail : see the 'copyright' file.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 3
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include <stdlib.h>
21 #include <stdio.h>
22 #include <string.h>
23 #include <math.h>
24
25 #include <fcntl.h>
26 #include <unistd.h>
27
28 #include "applet-struct.h"
29 #include "applet-cpusage.h" // cd_sysmonitor_get_cpu_info
30 #include "applet-top.h"
31
32
_cd_sysmonitor_free_process(CDProcess * pProcess)33 static void _cd_sysmonitor_free_process (CDProcess *pProcess)
34 {
35 if (pProcess == NULL)
36 return ;
37 g_free (pProcess->cName);
38 g_free (pProcess);
39 }
40
41
_cd_sysmonitor_insert_process_in_top_list(CDTopSharedMemory * pSharedMemory,CDProcess * pProcess)42 static inline void _cd_sysmonitor_insert_process_in_top_list (CDTopSharedMemory *pSharedMemory, CDProcess *pProcess)
43 {
44 int i, j;
45 if (pSharedMemory->bSortTopByRam)
46 {
47 if (pProcess->iMemAmount > 0)
48 {
49 i = pSharedMemory->iNbDisplayedProcesses - 1;
50 while (i >= 0 && (pSharedMemory->pTopList[i] == NULL || pProcess->iMemAmount > pSharedMemory->pTopList[i]->iMemAmount))
51 i --;
52 if (i != pSharedMemory->iNbDisplayedProcesses - 1)
53 {
54 i ++;
55 for (j = pSharedMemory->iNbDisplayedProcesses - 2; j >= i; j --)
56 pSharedMemory->pTopList[j+1] = pSharedMemory->pTopList[j];
57 pSharedMemory->pTopList[i] = pProcess;
58 }
59 }
60 }
61 else
62 {
63 if (pProcess->fCpuPercent > 0)
64 {
65 i = pSharedMemory->iNbDisplayedProcesses - 1;
66 while (i >= 0 && (pSharedMemory->pTopList[i] == NULL || pProcess->fCpuPercent > pSharedMemory->pTopList[i]->fCpuPercent))
67 i --;
68 if (i != pSharedMemory->iNbDisplayedProcesses - 1)
69 {
70 i ++;
71 //g_print (" fCpuPercent:%.2f%% => rang %d\n", 100*pProcess->fCpuPercent, i);
72 for (j = pSharedMemory->iNbDisplayedProcesses - 2; j >= i; j --)
73 pSharedMemory->pTopList[j+1] = pSharedMemory->pTopList[j];
74 pSharedMemory->pTopList[i] = pProcess;
75 }
76 }
77 }
78 }
79
80 #define jump_to_next_value(tmp) \
81 while (*tmp != ' ' && *tmp != '\0') \
82 tmp ++; \
83 if (*tmp == '\0') { \
84 cd_warning ("system monitor : problem when reading pipe"); \
85 break ; \
86 } \
87 while (*tmp == ' ') \
88 tmp ++; \
89
_cd_sysmonitor_get_process_data(CDTopSharedMemory * pSharedMemory,double fTime,double fTimeElapsed)90 static void _cd_sysmonitor_get_process_data (CDTopSharedMemory *pSharedMemory, double fTime, double fTimeElapsed)
91 {
92 static gchar cFilePathBuffer[20+1]; // /proc/12345/stat + 4octets de marge.
93 static gchar cContent[512+1];
94
95 cd_debug ("%s (%.2f)", __func__, fTimeElapsed);
96 GError *erreur = NULL;
97 GDir *dir = g_dir_open (CD_SYSMONITOR_PROC_FS, 0, &erreur);
98 if (erreur != NULL)
99 {
100 cd_warning ("sysmonitor : %s", erreur->message);
101 g_error_free (erreur);
102 return ;
103 }
104
105 if (pSharedMemory->pProcessTable == NULL)
106 pSharedMemory->pProcessTable = g_hash_table_new_full (g_int_hash, g_int_equal, NULL, (GDestroyNotify) _cd_sysmonitor_free_process); // a table of (pid*, process*), the pid* points directly into the process.
107 if (pSharedMemory->pTopList == NULL)
108 pSharedMemory->pTopList = g_new0 (CDProcess *, pSharedMemory->iNbDisplayedProcesses); // list of the processes to be displayed (points directly into the table).
109 else
110 memset (pSharedMemory->pTopList, 0, pSharedMemory->iNbDisplayedProcesses * sizeof (CDProcess *));
111 if (pSharedMemory->iMemPageSize == 0)
112 pSharedMemory->iMemPageSize = sysconf(_SC_PAGESIZE);
113
114 const gchar *cPid;
115 gchar *tmp;
116 CDProcess *pProcess;
117 int iNewCpuTime;
118 unsigned long long iVmRSS, iTotalMemory; // Quantite de memoire totale utilisee / (Virtual Memory Resident Stack Size) Taille de la pile en memoire.
119 while ((cPid = g_dir_read_name (dir)) != NULL)
120 {
121 if (! g_ascii_isdigit (*cPid))
122 continue;
123
124 snprintf (cFilePathBuffer, 20, "/proc/%s/stat", cPid);
125 int pipe = open (cFilePathBuffer, O_RDONLY);
126 int iPid = atoi (cPid);
127 if (pipe <= 0) // pas de pot le process s'est termine depuis qu'on a ouvert le repertoire.
128 {
129 g_hash_table_remove (pSharedMemory->pProcessTable, &iPid);
130 continue ;
131 }
132
133 if (read (pipe, cContent, sizeof (cContent)) <= 0)
134 {
135 cd_warning ("sysmonitor : can't read %s", cFilePathBuffer);
136 close (pipe);
137 continue;
138 }
139 close (pipe);
140
141 pProcess = g_hash_table_lookup (pSharedMemory->pProcessTable, &iPid);
142 if (pProcess == NULL)
143 {
144 pProcess = g_new0 (CDProcess, 1);
145 pProcess->iPid = iPid;
146 g_hash_table_insert (pSharedMemory->pProcessTable, &pProcess->iPid, pProcess);
147 }
148 pProcess->fLastCheckTime = fTime;
149
150 tmp = cContent;
151 jump_to_next_value (tmp); // on saute le pid.
152 if (pProcess->cName == NULL)
153 {
154 tmp ++; // on saute la '('.
155 gchar *str = tmp;
156 while (*str != ')' && *str != '\0')
157 str ++;
158 pProcess->cName = g_strndup (tmp, str - tmp);
159 }
160 jump_to_next_value (tmp); // on saute le nom.
161
162 jump_to_next_value (tmp); // on saute l'etat.
163 jump_to_next_value (tmp);
164 jump_to_next_value (tmp);
165 jump_to_next_value (tmp);
166 jump_to_next_value (tmp);
167 jump_to_next_value (tmp);
168 jump_to_next_value (tmp);
169 jump_to_next_value (tmp);
170 jump_to_next_value (tmp);
171 jump_to_next_value (tmp);
172 jump_to_next_value (tmp);
173 iNewCpuTime = atoll (tmp); // user.
174 jump_to_next_value (tmp);
175 iNewCpuTime += atoll (tmp); // system.
176 jump_to_next_value (tmp);
177 jump_to_next_value (tmp);
178 jump_to_next_value (tmp);
179 jump_to_next_value (tmp);
180 jump_to_next_value (tmp); // on saute le nice.
181 jump_to_next_value (tmp);
182 jump_to_next_value (tmp);
183 jump_to_next_value (tmp);
184 // iVmSize = atoll (tmp);
185 jump_to_next_value (tmp);
186 iVmRSS = atoll (tmp);
187 iTotalMemory = iVmRSS * pSharedMemory->iMemPageSize;
188
189 //g_print ("%s : %d -> %d\n", pProcess->cName, pProcess->iCpuTime, iNewCpuTime);
190 if (pProcess->iCpuTime != 0 && fTimeElapsed != 0)
191 pProcess->fCpuPercent = (iNewCpuTime - pProcess->iCpuTime) / pSharedMemory->fUserHZ / pSharedMemory->iNbCPU / fTimeElapsed;
192 pProcess->iCpuTime = iNewCpuTime;
193 pProcess->iMemAmount = iTotalMemory;
194
195 _cd_sysmonitor_insert_process_in_top_list (pSharedMemory, pProcess);
196 }
197
198 g_dir_close (dir);
199 }
200
_clean_one_old_processes(int * iPid,CDProcess * pProcess,double * fTime)201 static gboolean _clean_one_old_processes (int *iPid, CDProcess *pProcess, double *fTime)
202 {
203 if (pProcess->fLastCheckTime < *fTime)
204 return TRUE;
205 return FALSE;
206 }
207
_cd_sysmonitor_get_top_list(CDTopSharedMemory * pSharedMemory)208 static void _cd_sysmonitor_get_top_list (CDTopSharedMemory *pSharedMemory)
209 {
210 // get the elapsed time since the last 'top'.
211 double fTimeElapsed;
212 if (pSharedMemory->pTopClock == NULL)
213 {
214 pSharedMemory->pTopClock = g_timer_new ();
215 fTimeElapsed = 0.;
216 }
217 else
218 {
219 g_timer_stop (pSharedMemory->pTopClock);
220 fTimeElapsed = g_timer_elapsed (pSharedMemory->pTopClock, NULL);
221 g_timer_start (pSharedMemory->pTopClock);
222 }
223
224 // get the current time.
225 GTimeVal time_val;
226 g_get_current_time (&time_val); // on pourrait aussi utiliser un compteur statique a la fonction ...
227 double fTime = time_val.tv_sec + time_val.tv_usec * 1e-6;
228
229 // get the data for all processes.
230 _cd_sysmonitor_get_process_data (pSharedMemory, fTime, fTimeElapsed);
231
232 // clean the table from old processes.
233 g_hash_table_foreach_remove (pSharedMemory->pProcessTable, (GHRFunc) _clean_one_old_processes, &fTime);
234 }
235
236
_cd_sysmonitor_update_top_list(CDTopSharedMemory * pSharedMemory)237 static gboolean _cd_sysmonitor_update_top_list (CDTopSharedMemory *pSharedMemory)
238 {
239 GldiModuleInstance *myApplet = pSharedMemory->pApplet;
240 CD_APPLET_ENTER;
241
242 // determine the max length of process names.
243 CDProcess *pProcess;
244 int i;
245 guint iNameLength = 0;
246 for (i = 0; i < pSharedMemory->iNbDisplayedProcesses; i ++)
247 {
248 pProcess = pSharedMemory->pTopList[i];
249 if (pProcess == NULL || pProcess->cName == NULL)
250 break;
251 iNameLength = MAX (iNameLength, strlen (pProcess->cName));
252 }
253
254 // write the processes in the form "name (pid) : 15.2% - 12.3Mb".
255 gchar *cSpaces = g_new0 (gchar, iNameLength+6+1); // name + pid(<1e6) + '\0'
256 memset (cSpaces, ' ', iNameLength);
257 int iNbSpaces;
258 GString *sTopInfo = g_string_new ("");
259 for (i = 0; i < pSharedMemory->iNbDisplayedProcesses; i ++)
260 {
261 pProcess = pSharedMemory->pTopList[i];
262 if (pProcess == NULL || pProcess->cName == NULL)
263 break;
264 // determine the number of spaces needed to have a correct alignment.
265 iNbSpaces = iNameLength - strlen (pProcess->cName);
266 if (pProcess->iPid < 1e5) // no PID is >= 1e6; if ever it was, there would just be a small offset of the cpu and ram values displayed.
267 {
268 if (pProcess->iPid < 1e4)
269 {
270 if (pProcess->iPid < 1e3)
271 {
272 if (pProcess->iPid < 1e2)
273 {
274 if (pProcess->iPid < 1e1)
275 iNbSpaces += 5;
276 else
277 iNbSpaces += 4;
278 }
279 else
280 iNbSpaces += 3;
281 }
282 else
283 iNbSpaces += 2;
284 }
285 else
286 iNbSpaces += 1;
287 }
288 cSpaces[iNbSpaces] = '\0';
289 g_string_append_printf (sTopInfo, " %s (%d)%s: %.1f%% %s- %.1f%s\n",
290 pProcess->cName,
291 pProcess->iPid,
292 cSpaces,
293 100 * pProcess->fCpuPercent,
294 (pProcess->fCpuPercent > .1 ? "" : " "),
295 (double) pProcess->iMemAmount / (myConfig.bTopInPercent && myData.ramTotal ? 10.24 * myData.ramTotal : 1024 * 1024),
296 (myConfig.bTopInPercent && myData.ramTotal ? "%" : D_("Mb")));
297 cSpaces[iNbSpaces] = ' ';
298 }
299 g_free (cSpaces);
300
301 // display the info on the dialog.
302 if (sTopInfo->len == 0) // empty list, let the default message ("loading").
303 {
304 g_string_free (sTopInfo, TRUE);
305 CD_APPLET_LEAVE (TRUE);
306 }
307 else // remove the trailing \n.
308 {
309 sTopInfo->str[sTopInfo->len-1] = '\0';
310 }
311
312 cairo_dock_render_dialog_with_new_data (myData.pTopDialog, (CairoDialogRendererDataPtr) sTopInfo->str);
313 g_string_free (sTopInfo, TRUE);
314
315 // update the dialog title with the total number of processes if it has changed.
316 if (myData.iNbProcesses != g_hash_table_size (pSharedMemory->pProcessTable))
317 {
318 myData.iNbProcesses = g_hash_table_size (pSharedMemory->pProcessTable);
319 gchar *cTitle = g_strdup_printf (" [ Top %d / %d ] :", pSharedMemory->iNbDisplayedProcesses, myData.iNbProcesses);
320 gldi_dialog_set_message (myData.pTopDialog, cTitle);
321 g_free (cTitle);
322 }
323
324 // update the sort for the next step.
325 pSharedMemory->bSortTopByRam = myData.bSortTopByRam;
326
327 CD_APPLET_LEAVE (TRUE);
328 }
329
330
_free_shared_memory(CDTopSharedMemory * pSharedMemory)331 static void _free_shared_memory (CDTopSharedMemory *pSharedMemory)
332 {
333 g_hash_table_destroy (pSharedMemory->pProcessTable);
334 g_free (pSharedMemory->pTopList);
335 g_timer_destroy (pSharedMemory->pTopClock);
336 g_free (pSharedMemory);
337 }
cd_sysmonitor_launch_top_task(GldiModuleInstance * myApplet)338 static void cd_sysmonitor_launch_top_task (GldiModuleInstance *myApplet)
339 {
340 g_return_if_fail (myData.pTopTask == NULL);
341
342 myData.iNbProcesses = 0;
343 if (myData.iNbCPU == 0)
344 cd_sysmonitor_get_cpu_info (myApplet, NULL);
345
346 CDTopSharedMemory *pSharedMemory = g_new0 (CDTopSharedMemory, 1);
347 pSharedMemory->iNbDisplayedProcesses = myConfig.iNbDisplayedProcesses;
348 pSharedMemory->fUserHZ = myConfig.fUserHZ;
349 pSharedMemory->iNbCPU = myData.iNbCPU;
350 pSharedMemory->pApplet = myApplet;
351
352 myData.pTopTask = gldi_task_new_full (myConfig.iProcessCheckInterval,
353 (GldiGetDataAsyncFunc) _cd_sysmonitor_get_top_list,
354 (GldiUpdateSyncFunc) _cd_sysmonitor_update_top_list,
355 (GFreeFunc) _free_shared_memory,
356 pSharedMemory);
357 gldi_task_launch (myData.pTopTask);
358 }
359
_sort_one_process(int * iPid,CDProcess * pProcess,CDTopSharedMemory * pSharedMemory)360 static void _sort_one_process (int *iPid, CDProcess *pProcess, CDTopSharedMemory *pSharedMemory)
361 {
362 _cd_sysmonitor_insert_process_in_top_list (pSharedMemory, pProcess);
363 }
_on_change_order(int iClickedButton,GtkWidget * pInteractiveWidget,GldiModuleInstance * myApplet,CairoDialog * pDialog)364 static void _on_change_order (int iClickedButton, GtkWidget *pInteractiveWidget, GldiModuleInstance *myApplet, CairoDialog *pDialog)
365 {
366 if (iClickedButton == 2 || iClickedButton == -2) // 'close' button or Escape, just return and let the dialog be destroyed.
367 {
368 return;
369 }
370 gboolean bSortByRamNew = (iClickedButton == 1);
371 if (bSortByRamNew != myData.bSortTopByRam) // we'll sort the result immediately, so that the user doesn't have to wait until the next measure to see the result.
372 {
373 myData.bSortTopByRam = bSortByRamNew;
374
375 gldi_task_stop (myData.pTopTask); // blocks until the thread terminates.
376
377 CDTopSharedMemory *pSharedMemory = myData.pTopTask->pSharedMemory; // this is ok only because we stopped the task beforehand.
378 pSharedMemory->bSortTopByRam = bSortByRamNew;
379 if (pSharedMemory->pTopList != NULL && pSharedMemory->iNbDisplayedProcesses != 0)
380 {
381 memset (pSharedMemory->pTopList, 0, pSharedMemory->iNbDisplayedProcesses * sizeof (CDProcess *)); // on re-trie tout suivant le nouvel ordre.
382 g_hash_table_foreach (pSharedMemory->pProcessTable, (GHFunc) _sort_one_process, pSharedMemory);
383 _cd_sysmonitor_update_top_list (pSharedMemory); // on redessine.
384 }
385
386 gldi_task_launch_delayed (myData.pTopTask, 1000. * myConfig.iProcessCheckInterval); // restart the task with a delay equal to the interval, to keep the measure accurate.
387 }
388 gldi_object_ref (GLDI_OBJECT (pDialog)); // keep the dialog alive.
389 }
_on_dialog_destroyed(GldiModuleInstance * myApplet)390 static void _on_dialog_destroyed (GldiModuleInstance *myApplet)
391 {
392 // discard the 'top' task.
393 gldi_task_discard (myData.pTopTask);
394 myData.pTopTask = NULL;
395
396 // no more dialog.
397 myData.pTopDialog = NULL;
398 }
cd_sysmonitor_start_top_dialog(GldiModuleInstance * myApplet)399 void cd_sysmonitor_start_top_dialog (GldiModuleInstance *myApplet)
400 {
401 g_return_if_fail (myData.pTopDialog == NULL);
402 gldi_dialogs_remove_on_icon (myIcon);
403 // build an interactive widget that will be used to display the top list.
404 gchar *cTitle = g_strdup_printf (" [ Top %d ] :", myConfig.iNbDisplayedProcesses);
405 GtkWidget *pInteractiveWidget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
406 gtk_widget_set_size_request (pInteractiveWidget,
407 myDialogsParam.dialogTextDescription.iSize * 15,
408 myDialogsParam.dialogTextDescription.iSize * myConfig.iNbDisplayedProcesses); // approximatif au depart.
409
410 // build the dialog.
411 CairoDialogAttr attr;
412 memset (&attr, 0, sizeof (CairoDialogAttr));
413 attr.cText = cTitle;
414 attr.cImageFilePath = MY_APPLET_SHARE_DATA_DIR"/"MY_APPLET_ICON_FILE;
415 attr.pInteractiveWidget = pInteractiveWidget;
416 attr.pActionFunc = (CairoDockActionOnAnswerFunc) _on_change_order;
417 attr.pUserData = myApplet;
418 attr.pFreeDataFunc = (GFreeFunc) _on_dialog_destroyed;
419 const gchar *cButtons[] = {MY_APPLET_SHARE_DATA_DIR"/button-cpu.svg", MY_APPLET_SHARE_DATA_DIR"/button-ram.svg", "cancel", NULL};
420 attr.cButtonsImage = cButtons;
421 attr.pIcon = myIcon;
422 attr.pContainer = myContainer;
423 myData.pTopDialog = gldi_dialog_new (&attr);
424
425 g_free (cTitle);
426 g_return_if_fail (myData.pTopDialog != NULL);
427
428 // set a dialog renderer of type 'text'.
429 const gpointer pConfig[2] = {&myDialogsParam.dialogTextDescription, (const gpointer)D_("Loading")};
430 cairo_dock_set_dialog_renderer_by_name (myData.pTopDialog, "Text", (CairoDialogRendererConfigPtr) pConfig);
431
432 // launch the 'top' task.
433 cd_sysmonitor_launch_top_task (myApplet);
434 }
435