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