1 /*
2 *
3 * Conky, a system monitor, based on torsmo
4 *
5 * Any original torsmo code is licensed under the BSD license
6 *
7 * All code written since the fork of torsmo is licensed under the GPL
8 *
9 * Please see COPYING for details
10 *
11 * Copyright (c) 2005 Adi Zaimi, Dan Piponi <dan@tanelorn.demon.co.uk>,
12 * Dave Clark <clarkd@skynet.ca>
13 * Copyright (c) 2005-2021 Brenden Matthews, Philip Kovacs, et. al.
14 * (see AUTHORS)
15 * All rights reserved.
16 *
17 * This program is free software: you can redistribute it and/or modify
18 * it under the terms of the GNU General Public License as published by
19 * the Free Software Foundation, either version 3 of the License, or
20 * (at your option) any later version.
21 *
22 * This program is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 * You should have received a copy of the GNU General Public License
27 * along with this program. If not, see <http://www.gnu.org/licenses/>.
28 *
29 */
30
31 #include "top.h"
32 #include "logging.h"
33 #include "prioqueue.h"
34
35 /* hash table size - always a power of 2 */
36 #define HTABSIZE 256
37
38 struct process *first_process = nullptr;
39
40 unsigned long g_time = 0;
41
42 /* a simple hash table to speed up find_process() */
43 struct proc_hash_entry {
44 struct proc_hash_entry *next;
45 struct process *proc;
46 };
47 static struct proc_hash_entry proc_hash_table[HTABSIZE];
48
hash_process(struct process * p)49 static void hash_process(struct process *p) {
50 struct proc_hash_entry *phe;
51 static char first_run = 1;
52 int bucket;
53
54 /* better make sure all next pointers are zero upon first access */
55 if (first_run != 0) {
56 memset(proc_hash_table, 0, sizeof(struct proc_hash_entry) * HTABSIZE);
57 first_run = 0;
58 }
59
60 /* get the bucket index */
61 bucket = p->pid & (HTABSIZE - 1);
62
63 /* insert a new element on bucket's top */
64 phe = static_cast<struct proc_hash_entry *>(
65 malloc(sizeof(struct proc_hash_entry)));
66 phe->proc = p;
67 phe->next = proc_hash_table[bucket].next;
68 proc_hash_table[bucket].next = phe;
69 }
70
unhash_process(struct process * p)71 static void unhash_process(struct process *p) {
72 struct proc_hash_entry *phe, *tmp;
73
74 /* get the bucket head */
75 phe = &proc_hash_table[p->pid & (HTABSIZE - 1)];
76 /* find the entry pointing to p and drop it */
77 while (phe->next != nullptr) {
78 if (phe->next->proc == p) {
79 tmp = phe->next;
80 phe->next = phe->next->next;
81 free(tmp);
82 return;
83 }
84 phe = phe->next;
85 }
86 }
87
__unhash_all_processes(struct proc_hash_entry * phe)88 static void __unhash_all_processes(struct proc_hash_entry *phe) {
89 if (phe->next != nullptr) { __unhash_all_processes(phe->next); }
90 free(phe->next);
91 }
92
unhash_all_processes()93 static void unhash_all_processes() {
94 int i;
95
96 for (i = 0; i < HTABSIZE; i++) {
97 __unhash_all_processes(&proc_hash_table[i]);
98 proc_hash_table[i].next = nullptr;
99 }
100 }
101
get_first_process()102 struct process *get_first_process() {
103 return first_process;
104 }
105
free_all_processes()106 void free_all_processes() {
107 struct process *next = nullptr, *pr = first_process;
108
109 while (pr != nullptr) {
110 next = pr->next;
111 free_and_zero(pr->name);
112 free_and_zero(pr->basename);
113 free(pr);
114 pr = next;
115 }
116 first_process = nullptr;
117
118 /* drop the whole hash table */
119 unhash_all_processes();
120 }
121
get_process_by_name(const char * name)122 struct process *get_process_by_name(const char *name) {
123 struct process *p = first_process;
124
125 while (p != nullptr) {
126 /* Try matching against the full command line first. If that fails,
127 * fall back to the basename.
128 */
129 if (((p->name != nullptr) && (strcmp(p->name, name) == 0)) ||
130 ((p->basename != nullptr) && (strcmp(p->basename, name) == 0))) {
131 return p;
132 }
133 p = p->next;
134 }
135 return nullptr;
136 }
137
find_process(pid_t pid)138 static struct process *find_process(pid_t pid) {
139 struct proc_hash_entry *phe;
140
141 phe = &proc_hash_table[pid & (HTABSIZE - 1)];
142 while (phe->next != nullptr) {
143 if (phe->next->proc->pid == pid) { return phe->next->proc; }
144 phe = phe->next;
145 }
146 return nullptr;
147 }
148
new_process(pid_t pid)149 static struct process *new_process(pid_t pid) {
150 auto *p = static_cast<struct process *>(malloc(sizeof(struct process)));
151
152 /* Do stitching necessary for doubly linked list */
153 p->previous = nullptr;
154 p->next = first_process;
155 if (p->next != nullptr) { p->next->previous = p; }
156 first_process = p;
157
158 p->pid = pid;
159 p->name = nullptr;
160 p->basename = nullptr;
161 p->amount = 0;
162 p->user_time = 0;
163 p->total = 0;
164 p->kernel_time = 0;
165 p->previous_user_time = ULONG_MAX;
166 p->previous_kernel_time = ULONG_MAX;
167 p->total_cpu_time = 0;
168 p->vsize = 0;
169 p->rss = 0;
170 #ifdef BUILD_IOSTATS
171 p->read_bytes = 0;
172 p->previous_read_bytes = ULLONG_MAX;
173 p->write_bytes = 0;
174 p->previous_write_bytes = ULLONG_MAX;
175 p->io_perc = 0;
176 #endif /* BUILD_IOSTATS */
177 p->time_stamp = 0;
178 p->counted = 1;
179 p->changed = 0;
180
181 /* process_find_name(p); */
182
183 /* add the process to the hash table */
184 hash_process(p);
185
186 return p;
187 }
188
189 /* Get / create a new process object and insert it into the process list */
get_process(pid_t pid)190 struct process *get_process(pid_t pid) {
191 struct process *p = find_process(pid);
192 return p != nullptr ? p : new_process(pid);
193 }
194
195 /******************************************
196 * Functions *
197 ******************************************/
198
199 /******************************************
200 * Destroy and remove a process *
201 ******************************************/
202
delete_process(struct process * p)203 static void delete_process(struct process *p) {
204 #if defined(PARANOID)
205 assert(p->id == 0x0badfeed);
206
207 /*
208 * Ensure that deleted processes aren't reused.
209 */
210 p->id = 0x007babe;
211 #endif /* defined(PARANOID) */
212
213 /*
214 * Maintain doubly linked list.
215 */
216 if (p->next != nullptr) { p->next->previous = p->previous; }
217 if (p->previous != nullptr) {
218 p->previous->next = p->next;
219 } else {
220 first_process = p->next;
221 }
222
223 free_and_zero(p->name);
224 free_and_zero(p->basename);
225 /* remove the process from the hash table */
226 unhash_process(p);
227 free(p);
228 }
229
230 /******************************************
231 * Strip dead process entries *
232 ******************************************/
233
process_cleanup()234 static void process_cleanup() {
235 struct process *p = first_process;
236
237 while (p != nullptr) {
238 struct process *current = p;
239
240 #if defined(PARANOID)
241 assert(p->id == 0x0badfeed);
242 #endif /* defined(PARANOID) */
243
244 p = p->next;
245 /* Delete processes that have died */
246 if (current->time_stamp != g_time) {
247 delete_process(current);
248 if (current == first_process) { first_process = nullptr; }
249 current = nullptr;
250 }
251 }
252 }
253
254 /******************************************
255 * Find the top processes *
256 ******************************************/
257
258 /* cpu comparison function for prio queue */
compare_cpu(void * va,void * vb)259 static int compare_cpu(void *va, void *vb) {
260 auto *a = static_cast<struct process *>(va),
261 *b = static_cast<struct process *>(vb);
262
263 if (b->amount > a->amount) { return 1; }
264 if (a->amount > b->amount) { return -1; }
265 return 0;
266 }
267
268 /* mem comparison function for prio queue */
compare_mem(void * va,void * vb)269 static int compare_mem(void *va, void *vb) {
270 auto *a = static_cast<struct process *>(va),
271 *b = static_cast<struct process *>(vb);
272
273 if (b->rss > a->rss) { return 1; }
274 if (a->rss > b->rss) { return -1; }
275 return 0;
276 }
277
278 /* CPU time comparison function for prio queue */
compare_time(void * va,void * vb)279 static int compare_time(void *va, void *vb) {
280 auto *a = static_cast<struct process *>(va),
281 *b = static_cast<struct process *>(vb);
282
283 if (b->total_cpu_time > a->total_cpu_time) { return 1; }
284 if (b->total_cpu_time < a->total_cpu_time) { return -1; }
285 return 0;
286 }
287
288 #ifdef BUILD_IOSTATS
289 /* I/O comparison function for prio queue */
compare_io(void * va,void * vb)290 static int compare_io(void *va, void *vb) {
291 auto *a = static_cast<struct process *>(va),
292 *b = static_cast<struct process *>(vb);
293
294 if (b->io_perc > a->io_perc) { return 1; }
295 if (a->io_perc > b->io_perc) { return -1; }
296 return 0;
297 }
298 #endif /* BUILD_IOSTATS */
299
300 /* ****************************************************************** *
301 * Get a sorted list of the top cpu hogs and top mem hogs. * Results are stored
302 * in the cpu,mem arrays in decreasing order[0-9]. *
303 * ****************************************************************** */
304
process_find_top(struct process ** cpu,struct process ** mem,struct process ** ptime,struct process ** io)305 static void process_find_top(struct process **cpu, struct process **mem,
306 struct process **ptime
307 #ifdef BUILD_IOSTATS
308 ,
309 struct process **io
310 #endif /* BUILD_IOSTATS */
311 ) {
312 prio_queue_t cpu_queue, mem_queue, time_queue;
313 #ifdef BUILD_IOSTATS
314 prio_queue_t io_queue;
315 #endif
316 struct process *cur_proc = nullptr;
317 int i;
318
319 if ((top_cpu == 0) && (top_mem == 0) && (top_time == 0)
320 #ifdef BUILD_IOSTATS
321 && (top_io == 0)
322 #endif /* BUILD_IOSTATS */
323 && (top_running == 0)) {
324 return;
325 }
326
327 cpu_queue = init_prio_queue();
328 pq_set_compare(cpu_queue, &compare_cpu);
329 pq_set_max_size(cpu_queue, MAX_SP);
330
331 mem_queue = init_prio_queue();
332 pq_set_compare(mem_queue, &compare_mem);
333 pq_set_max_size(mem_queue, MAX_SP);
334
335 time_queue = init_prio_queue();
336 pq_set_compare(time_queue, &compare_time);
337 pq_set_max_size(time_queue, MAX_SP);
338
339 #ifdef BUILD_IOSTATS
340 io_queue = init_prio_queue();
341 pq_set_compare(io_queue, &compare_io);
342 pq_set_max_size(io_queue, MAX_SP);
343 #endif
344
345 /* g_time is the time_stamp entry for process. It is updated when the
346 * process information is updated to indicate that the process is still
347 * alive (and must not be removed from the process list in
348 * process_cleanup()) */
349 ++g_time;
350
351 /* OS-specific function updating process list */
352 get_top_info();
353
354 process_cleanup(); /* cleanup list from exited processes */
355
356 cur_proc = first_process;
357
358 while (cur_proc != nullptr) {
359 if (top_cpu != 0) { insert_prio_elem(cpu_queue, cur_proc); }
360 if (top_mem != 0) { insert_prio_elem(mem_queue, cur_proc); }
361 if (top_time != 0) { insert_prio_elem(time_queue, cur_proc); }
362 #ifdef BUILD_IOSTATS
363 if (top_io != 0) { insert_prio_elem(io_queue, cur_proc); }
364 #endif /* BUILD_IOSTATS */
365 cur_proc = cur_proc->next;
366 }
367
368 for (i = 0; i < MAX_SP; i++) {
369 if (top_cpu != 0) {
370 cpu[i] = static_cast<process *>(pop_prio_elem(cpu_queue));
371 }
372 if (top_mem != 0) {
373 mem[i] = static_cast<process *>(pop_prio_elem(mem_queue));
374 }
375 if (top_time != 0) {
376 ptime[i] = static_cast<process *>(pop_prio_elem(time_queue));
377 }
378 #ifdef BUILD_IOSTATS
379 if (top_io != 0) {
380 io[i] = static_cast<process *>(pop_prio_elem(io_queue));
381 }
382 #endif /* BUILD_IOSTATS */
383 }
384 free_prio_queue(cpu_queue);
385 free_prio_queue(mem_queue);
386 free_prio_queue(time_queue);
387 #ifdef BUILD_IOSTATS
388 free_prio_queue(io_queue);
389 #endif /* BUILD_IOSTATS */
390 }
391
update_top()392 int update_top() {
393 process_find_top(info.cpu, info.memu, info.time
394 #ifdef BUILD_IOSTATS
395 ,
396 info.io
397 #endif
398 );
399 info.first_process = get_first_process();
400 return 0;
401 }
402
format_time(unsigned long timeval,const int width)403 static char *format_time(unsigned long timeval, const int width) {
404 char buf[10];
405 unsigned long nt; // narrow time, for speed on 32-bit
406 unsigned cc; // centiseconds
407 unsigned nn; // multi-purpose whatever
408
409 nt = timeval;
410 cc = nt % 100; // centiseconds past second
411 nt /= 100; // total seconds
412 nn = nt % 60; // seconds past the minute
413 nt /= 60; // total minutes
414 if (width >= snprintf(buf, sizeof buf, "%lu:%02u.%02u", nt, nn, cc)) {
415 return strndup(buf, text_buffer_size.get(*state));
416 }
417 if (width >= snprintf(buf, sizeof buf, "%lu:%02u", nt, nn)) {
418 return strndup(buf, text_buffer_size.get(*state));
419 }
420 nn = nt % 60; // minutes past the hour
421 nt /= 60; // total hours
422 if (width >= snprintf(buf, sizeof buf, "%lu,%02u", nt, nn)) {
423 return strndup(buf, text_buffer_size.get(*state));
424 }
425 nn = nt; // now also hours
426 if (width >= snprintf(buf, sizeof buf, "%uh", nn)) {
427 return strndup(buf, text_buffer_size.get(*state));
428 }
429 nn /= 24; // now days
430 if (width >= snprintf(buf, sizeof buf, "%ud", nn)) {
431 return strndup(buf, text_buffer_size.get(*state));
432 }
433 nn /= 7; // now weeks
434 if (width >= snprintf(buf, sizeof buf, "%uw", nn)) {
435 return strndup(buf, text_buffer_size.get(*state));
436 }
437 // well shoot, this outta' fit...
438 return strndup("<inf>", text_buffer_size.get(*state));
439 }
440
441 struct top_data {
442 struct process **list;
443 int num;
444 int was_parsed;
445 char *s;
446 };
447
448 static conky::range_config_setting<unsigned int> top_name_width(
449 "top_name_width", 0, std::numeric_limits<unsigned int>::max(), 15, true);
450 static conky::simple_config_setting<bool> top_name_verbose("top_name_verbose",
451 false, true);
452
print_top_name(struct text_object * obj,char * p,unsigned int p_max_size)453 static void print_top_name(struct text_object *obj, char *p,
454 unsigned int p_max_size) {
455 auto *td = static_cast<struct top_data *>(obj->data.opaque);
456 int width;
457
458 if ((td == nullptr) || (td->list == nullptr) ||
459 (td->list[td->num] == nullptr)) {
460 return;
461 }
462
463 width = std::min(p_max_size,
464 static_cast<unsigned int>(top_name_width.get(*state)) + 1);
465 if (top_name_verbose.get(*state)) {
466 /* print the full command line */
467 snprintf(p, width + 1, "%-*s", width, td->list[td->num]->name);
468 } else {
469 /* print only the basename (i.e. executable name) */
470 snprintf(p, width + 1, "%-*s", width, td->list[td->num]->basename);
471 }
472 }
473
print_top_mem(struct text_object * obj,char * p,unsigned int p_max_size)474 static void print_top_mem(struct text_object *obj, char *p,
475 unsigned int p_max_size) {
476 auto *td = static_cast<struct top_data *>(obj->data.opaque);
477 int width;
478
479 if ((td == nullptr) || (td->list == nullptr) ||
480 (td->list[td->num] == nullptr)) {
481 return;
482 }
483
484 width = std::min(p_max_size, static_cast<unsigned int>(7));
485 snprintf(p, width, "%6.2f",
486 (static_cast<float>(td->list[td->num]->rss) / info.memmax) / 10);
487 }
488
print_top_time(struct text_object * obj,char * p,unsigned int p_max_size)489 static void print_top_time(struct text_object *obj, char *p,
490 unsigned int p_max_size) {
491 auto *td = static_cast<struct top_data *>(obj->data.opaque);
492 int width;
493 char *timeval;
494
495 if ((td == nullptr) || (td->list == nullptr) ||
496 (td->list[td->num] == nullptr)) {
497 return;
498 }
499
500 width = std::min(p_max_size, static_cast<unsigned int>(10));
501 timeval = format_time(td->list[td->num]->total_cpu_time, 9);
502 snprintf(p, width, "%9s", timeval);
503 free(timeval);
504 }
505
print_top_user(struct text_object * obj,char * p,unsigned int p_max_size)506 static void print_top_user(struct text_object *obj, char *p,
507 unsigned int p_max_size) {
508 auto *td = static_cast<struct top_data *>(obj->data.opaque);
509 struct passwd *pw;
510
511 if ((td == nullptr) || (td->list == nullptr) ||
512 (td->list[td->num] == nullptr)) {
513 return;
514 }
515
516 pw = getpwuid(td->list[td->num]->uid);
517 if (pw != nullptr) {
518 snprintf(p, p_max_size, "%.8s", pw->pw_name);
519 } else {
520 snprintf(p, p_max_size, "%d", td->list[td->num]->uid);
521 }
522 }
523
524 #define PRINT_TOP_GENERATOR(name, width, fmt, field) \
525 static void print_top_##name(struct text_object *obj, char *p, \
526 unsigned int p_max_size) { \
527 struct top_data *td = (struct top_data *)obj->data.opaque; \
528 if (!td || !td->list || !td->list[td->num]) return; \
529 snprintf(p, std::min(p_max_size, width), fmt, td->list[td->num]->field); \
530 }
531
532 #define PRINT_TOP_HR_GENERATOR(name, field, denom) \
533 static void print_top_##name(struct text_object *obj, char *p, \
534 unsigned int p_max_size) { \
535 struct top_data *td = (struct top_data *)obj->data.opaque; \
536 if (!td || !td->list || !td->list[td->num]) return; \
537 human_readable(td->list[td->num]->field / (denom), p, p_max_size); \
538 }
539
540 PRINT_TOP_GENERATOR(cpu, (unsigned int)7, "%6.2f", amount)
541 PRINT_TOP_GENERATOR(pid, (unsigned int)8, "%7i", pid)
542 PRINT_TOP_GENERATOR(uid, (unsigned int)6, "%5i", uid)
543 PRINT_TOP_HR_GENERATOR(mem_res, rss, 1)
544 PRINT_TOP_HR_GENERATOR(mem_vsize, vsize, 1)
545 #ifdef BUILD_IOSTATS
PRINT_TOP_HR_GENERATOR(read_bytes,read_bytes,active_update_interval ())546 PRINT_TOP_HR_GENERATOR(read_bytes, read_bytes, active_update_interval())
547 PRINT_TOP_HR_GENERATOR(write_bytes, write_bytes, active_update_interval())
548 PRINT_TOP_GENERATOR(io_perc, (unsigned int)7, "%6.2f", io_perc)
549 #endif /* BUILD_IOSTATS */
550
551 static void free_top(struct text_object *obj) {
552 auto *td = static_cast<struct top_data *>(obj->data.opaque);
553
554 if (td == nullptr) { return; }
555 free_and_zero(td->s);
556 free_and_zero(obj->data.opaque);
557 }
558
parse_top_args(const char * s,const char * arg,struct text_object * obj)559 int parse_top_args(const char *s, const char *arg, struct text_object *obj) {
560 struct top_data *td;
561 char buf[64];
562 int n;
563
564 if (arg == nullptr) {
565 NORM_ERR("top needs arguments");
566 return 0;
567 }
568
569 obj->data.opaque = td =
570 static_cast<struct top_data *>(malloc(sizeof(struct top_data)));
571 memset(td, 0, sizeof(struct top_data));
572
573 if (s[3] == 0) {
574 td->list = info.cpu;
575 top_cpu = 1;
576 } else if (strcmp(&s[3], "_mem") == EQUAL) {
577 td->list = info.memu;
578 top_mem = 1;
579 } else if (strcmp(&s[3], "_time") == EQUAL) {
580 td->list = info.time;
581 top_time = 1;
582 #ifdef BUILD_IOSTATS
583 } else if (strcmp(&s[3], "_io") == EQUAL) {
584 td->list = info.io;
585 top_io = 1;
586 #endif /* BUILD_IOSTATS */
587 } else {
588 #ifdef BUILD_IOSTATS
589 NORM_ERR("Must be top, top_mem, top_time or top_io");
590 #else /* BUILD_IOSTATS */
591 NORM_ERR("Must be top, top_mem or top_time");
592 #endif /* BUILD_IOSTATS */
593 free_and_zero(obj->data.opaque);
594 return 0;
595 }
596
597 td->s = strndup(arg, text_buffer_size.get(*state));
598
599 if (sscanf(arg, "%63s %i", buf, &n) == 2) {
600 if (strcmp(buf, "name") == EQUAL) {
601 obj->callbacks.print = &print_top_name;
602 } else if (strcmp(buf, "cpu") == EQUAL) {
603 obj->callbacks.print = &print_top_cpu;
604 } else if (strcmp(buf, "pid") == EQUAL) {
605 obj->callbacks.print = &print_top_pid;
606 } else if (strcmp(buf, "mem") == EQUAL) {
607 obj->callbacks.print = &print_top_mem;
608 } else if (strcmp(buf, "time") == EQUAL) {
609 obj->callbacks.print = &print_top_time;
610 } else if (strcmp(buf, "mem_res") == EQUAL) {
611 obj->callbacks.print = &print_top_mem_res;
612 } else if (strcmp(buf, "mem_vsize") == EQUAL) {
613 obj->callbacks.print = &print_top_mem_vsize;
614 } else if (strcmp(buf, "uid") == EQUAL) {
615 obj->callbacks.print = &print_top_uid;
616 } else if (strcmp(buf, "user") == EQUAL) {
617 obj->callbacks.print = &print_top_user;
618 #ifdef BUILD_IOSTATS
619 } else if (strcmp(buf, "io_read") == EQUAL) {
620 obj->callbacks.print = &print_top_read_bytes;
621 } else if (strcmp(buf, "io_write") == EQUAL) {
622 obj->callbacks.print = &print_top_write_bytes;
623 } else if (strcmp(buf, "io_perc") == EQUAL) {
624 obj->callbacks.print = &print_top_io_perc;
625 #endif /* BUILD_IOSTATS */
626 } else {
627 NORM_ERR("invalid type arg for top");
628 #ifdef BUILD_IOSTATS
629 NORM_ERR(
630 "must be one of: name, cpu, pid, mem, time, mem_res, mem_vsize, "
631 "io_read, io_write, io_perc");
632 #else /* BUILD_IOSTATS */
633 NORM_ERR("must be one of: name, cpu, pid, mem, time, mem_res, mem_vsize");
634 #endif /* BUILD_IOSTATS */
635 free_and_zero(td->s);
636 free_and_zero(obj->data.opaque);
637 return 0;
638 }
639 if (n < 1 || n > MAX_SP) {
640 NORM_ERR("invalid num arg for top. Must be between 1 and %d.", MAX_SP);
641 free_and_zero(td->s);
642 free_and_zero(obj->data.opaque);
643 return 0;
644 }
645 td->num = n - 1;
646
647 } else {
648 NORM_ERR("invalid argument count for top");
649 free_and_zero(td->s);
650 free_and_zero(obj->data.opaque);
651 return 0;
652 }
653 obj->callbacks.free = &free_top;
654 return 1;
655 }
656