2 * Copyright (c) 2010 Stanislav Kozina
3 * Copyright (c) 2010 Martin Decky
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
10 * - Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * - Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * - The name of the author may not be used to endorse or promote products
16 * derived from this software without specific prior written permission.
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
50 #define UPDATE_INTERVAL 1
62 static const column_t task_columns
[] = {
65 { "resident", 'r', 10 },
67 { "virtual", 'v', 9 },
78 TASK_COL_PERCENT_RESIDENT
,
80 TASK_COL_PERCENT_VIRTUAL
,
81 TASK_COL_PERCENT_USER
,
82 TASK_COL_PERCENT_KERNEL
,
87 static const column_t ipc_columns
[] = {
89 { "cls snt", 'c', 9 },
90 { "cls rcv", 'C', 9 },
91 { "ans snt", 'a', 9 },
92 { "ans rcv", 'A', 9 },
93 { "forward", 'f', 9 },
108 static const column_t exception_columns
[] = {
110 { "count", 'n', 10 },
111 { "%count", 'N', 8 },
112 { "cycles", 'c', 10 },
113 { "%cycles", 'C', 9 },
114 { "description", 'd', 0 },
118 EXCEPTION_COL_ID
= 0,
120 EXCEPTION_COL_PERCENT_COUNT
,
121 EXCEPTION_COL_CYCLES
,
122 EXCEPTION_COL_PERCENT_CYCLES
,
123 EXCEPTION_COL_DESCRIPTION
,
124 EXCEPTION_NUM_COLUMNS
,
127 screen_mode_t screen_mode
= SCREEN_TABLE
;
128 static op_mode_t op_mode
= OP_TASKS
;
129 static size_t sort_column
= TASK_COL_PERCENT_USER
;
130 static int sort_reverse
= -1;
131 static bool excs_all
= false;
133 static const char *read_data(data_t
*target
)
135 /* Initialize data */
138 target
->cpus_perc
= NULL
;
139 target
->tasks
= NULL
;
140 target
->tasks_perc
= NULL
;
141 target
->threads
= NULL
;
142 target
->exceptions
= NULL
;
143 target
->exceptions_perc
= NULL
;
144 target
->physmem
= NULL
;
145 target
->ucycles_diff
= NULL
;
146 target
->kcycles_diff
= NULL
;
147 target
->ecycles_diff
= NULL
;
148 target
->ecount_diff
= NULL
;
149 target
->table
.name
= NULL
;
150 target
->table
.num_columns
= 0;
151 target
->table
.columns
= NULL
;
152 target
->table
.num_fields
= 0;
153 target
->table
.fields
= NULL
;
155 /* Get current time */
157 gettimeofday(&time
, NULL
);
159 target
->hours
= (time
.tv_sec
% DAY
) / HOUR
;
160 target
->minutes
= (time
.tv_sec
% HOUR
) / MINUTE
;
161 target
->seconds
= time
.tv_sec
% MINUTE
;
164 struct timeval uptime
;
167 target
->udays
= uptime
.tv_sec
/ DAY
;
168 target
->uhours
= (uptime
.tv_sec
% DAY
) / HOUR
;
169 target
->uminutes
= (uptime
.tv_sec
% HOUR
) / MINUTE
;
170 target
->useconds
= uptime
.tv_sec
% MINUTE
;
173 target
->load
= stats_get_load(&(target
->load_count
));
174 if (target
->load
== NULL
)
175 return "Cannot get system load";
178 target
->cpus
= stats_get_cpus(&(target
->cpus_count
));
179 if (target
->cpus
== NULL
)
180 return "Cannot get CPUs";
183 (perc_cpu_t
*) calloc(target
->cpus_count
, sizeof(perc_cpu_t
));
184 if (target
->cpus_perc
== NULL
)
185 return "Not enough memory for CPU utilization";
188 target
->tasks
= stats_get_tasks(&(target
->tasks_count
));
189 if (target
->tasks
== NULL
)
190 return "Cannot get tasks";
193 (perc_task_t
*) calloc(target
->tasks_count
, sizeof(perc_task_t
));
194 if (target
->tasks_perc
== NULL
)
195 return "Not enough memory for task utilization";
198 target
->threads
= stats_get_threads(&(target
->threads_count
));
199 if (target
->threads
== NULL
)
200 return "Cannot get threads";
203 target
->exceptions
= stats_get_exceptions(&(target
->exceptions_count
));
204 if (target
->exceptions
== NULL
)
205 return "Cannot get exceptions";
207 target
->exceptions_perc
=
208 (perc_exc_t
*) calloc(target
->exceptions_count
, sizeof(perc_exc_t
));
209 if (target
->exceptions_perc
== NULL
)
210 return "Not enough memory for exception utilization";
212 /* Get physical memory */
213 target
->physmem
= stats_get_physmem();
214 if (target
->physmem
== NULL
)
215 return "Cannot get physical memory";
217 target
->ucycles_diff
= calloc(target
->tasks_count
,
219 if (target
->ucycles_diff
== NULL
)
220 return "Not enough memory for user utilization";
222 /* Allocate memory for computed values */
223 target
->kcycles_diff
= calloc(target
->tasks_count
,
225 if (target
->kcycles_diff
== NULL
)
226 return "Not enough memory for kernel utilization";
228 target
->ecycles_diff
= calloc(target
->exceptions_count
,
230 if (target
->ecycles_diff
== NULL
)
231 return "Not enough memory for exception cycles utilization";
233 target
->ecount_diff
= calloc(target
->exceptions_count
,
235 if (target
->ecount_diff
== NULL
)
236 return "Not enough memory for exception count utilization";
241 /** Computes percentage differencies from old_data to new_data
243 * @param old_data Pointer to old data strucutre.
244 * @param new_data Pointer to actual data where percetages are stored.
247 static void compute_percentages(data_t
*old_data
, data_t
*new_data
)
250 * For each CPU: Compute total cycles and divide it between
255 for (i
= 0; i
< new_data
->cpus_count
; i
++) {
257 new_data
->cpus
[i
].idle_cycles
- old_data
->cpus
[i
].idle_cycles
;
259 new_data
->cpus
[i
].busy_cycles
- old_data
->cpus
[i
].busy_cycles
;
260 uint64_t sum
= idle
+ busy
;
262 FRACTION_TO_FLOAT(new_data
->cpus_perc
[i
].idle
, idle
* 100, sum
);
263 FRACTION_TO_FLOAT(new_data
->cpus_perc
[i
].busy
, busy
* 100, sum
);
266 /* For all tasks compute sum and differencies of all cycles */
268 uint64_t virtmem_total
= 0;
269 uint64_t resmem_total
= 0;
270 uint64_t ucycles_total
= 0;
271 uint64_t kcycles_total
= 0;
273 for (i
= 0; i
< new_data
->tasks_count
; i
++) {
274 /* Match task with the previous instance */
278 for (j
= 0; j
< old_data
->tasks_count
; j
++) {
279 if (new_data
->tasks
[i
].task_id
== old_data
->tasks
[j
].task_id
) {
286 /* This is newly borned task, ignore it */
287 new_data
->ucycles_diff
[i
] = 0;
288 new_data
->kcycles_diff
[i
] = 0;
292 new_data
->ucycles_diff
[i
] =
293 new_data
->tasks
[i
].ucycles
- old_data
->tasks
[j
].ucycles
;
294 new_data
->kcycles_diff
[i
] =
295 new_data
->tasks
[i
].kcycles
- old_data
->tasks
[j
].kcycles
;
297 virtmem_total
+= new_data
->tasks
[i
].virtmem
;
298 resmem_total
+= new_data
->tasks
[i
].resmem
;
299 ucycles_total
+= new_data
->ucycles_diff
[i
];
300 kcycles_total
+= new_data
->kcycles_diff
[i
];
303 /* For each task compute percential change */
305 for (i
= 0; i
< new_data
->tasks_count
; i
++) {
306 FRACTION_TO_FLOAT(new_data
->tasks_perc
[i
].virtmem
,
307 new_data
->tasks
[i
].virtmem
* 100, virtmem_total
);
308 FRACTION_TO_FLOAT(new_data
->tasks_perc
[i
].resmem
,
309 new_data
->tasks
[i
].resmem
* 100, resmem_total
);
310 FRACTION_TO_FLOAT(new_data
->tasks_perc
[i
].ucycles
,
311 new_data
->ucycles_diff
[i
] * 100, ucycles_total
);
312 FRACTION_TO_FLOAT(new_data
->tasks_perc
[i
].kcycles
,
313 new_data
->kcycles_diff
[i
] * 100, kcycles_total
);
316 /* For all exceptions compute sum and differencies of cycles */
318 uint64_t ecycles_total
= 0;
319 uint64_t ecount_total
= 0;
321 for (i
= 0; i
< new_data
->exceptions_count
; i
++) {
323 * March exception with the previous instance.
324 * This is quite paranoid since exceptions do not
325 * usually disappear, but it does not hurt.
330 for (j
= 0; j
< old_data
->exceptions_count
; j
++) {
331 if (new_data
->exceptions
[i
].id
== old_data
->exceptions
[j
].id
) {
338 /* This is a new exception, ignore it */
339 new_data
->ecycles_diff
[i
] = 0;
340 new_data
->ecount_diff
[i
] = 0;
344 new_data
->ecycles_diff
[i
] =
345 new_data
->exceptions
[i
].cycles
- old_data
->exceptions
[j
].cycles
;
346 new_data
->ecount_diff
[i
] =
347 new_data
->exceptions
[i
].count
- old_data
->exceptions
[i
].count
;
349 ecycles_total
+= new_data
->ecycles_diff
[i
];
350 ecount_total
+= new_data
->ecount_diff
[i
];
353 /* For each exception compute percential change */
355 for (i
= 0; i
< new_data
->exceptions_count
; i
++) {
356 FRACTION_TO_FLOAT(new_data
->exceptions_perc
[i
].cycles
,
357 new_data
->ecycles_diff
[i
] * 100, ecycles_total
);
358 FRACTION_TO_FLOAT(new_data
->exceptions_perc
[i
].count
,
359 new_data
->ecount_diff
[i
] * 100, ecount_total
);
363 static int cmp_data(void *a
, void *b
, void *arg
)
365 field_t
*fa
= (field_t
*)a
+ sort_column
;
366 field_t
*fb
= (field_t
*)b
+ sort_column
;
368 if (fa
->type
> fb
->type
)
369 return 1 * sort_reverse
;
371 if (fa
->type
< fb
->type
)
372 return -1 * sort_reverse
;
377 case FIELD_UINT_SUFFIX_BIN
: /* fallthrough */
378 case FIELD_UINT_SUFFIX_DEC
: /* fallthrough */
380 if (fa
->uint
> fb
->uint
)
381 return 1 * sort_reverse
;
382 if (fa
->uint
< fb
->uint
)
383 return -1 * sort_reverse
;
386 if (fa
->fixed
.upper
* fb
->fixed
.lower
>
387 fb
->fixed
.upper
* fa
->fixed
.lower
)
388 return 1 * sort_reverse
;
389 if (fa
->fixed
.upper
* fb
->fixed
.lower
<
390 fb
->fixed
.upper
* fa
->fixed
.lower
)
391 return -1 * sort_reverse
;
394 return str_cmp(fa
->string
, fb
->string
) * sort_reverse
;
400 static void sort_table(table_t
*table
)
402 if (sort_column
>= table
->num_columns
)
404 /* stable sort is probably best, so we use gsort */
405 gsort((void *) table
->fields
, table
->num_fields
/ table
->num_columns
,
406 sizeof(field_t
) * table
->num_columns
, cmp_data
, NULL
);
409 static const char *fill_task_table(data_t
*data
)
411 data
->table
.name
= "Tasks";
412 data
->table
.num_columns
= TASK_NUM_COLUMNS
;
413 data
->table
.columns
= task_columns
;
414 data
->table
.num_fields
= data
->tasks_count
* TASK_NUM_COLUMNS
;
415 data
->table
.fields
= calloc(data
->table
.num_fields
,
417 if (data
->table
.fields
== NULL
)
418 return "Not enough memory for table fields";
420 field_t
*field
= data
->table
.fields
;
421 for (size_t i
= 0; i
< data
->tasks_count
; i
++) {
422 stats_task_t
*task
= &data
->tasks
[i
];
423 perc_task_t
*perc
= &data
->tasks_perc
[i
];
424 field
[TASK_COL_ID
].type
= FIELD_UINT
;
425 field
[TASK_COL_ID
].uint
= task
->task_id
;
426 field
[TASK_COL_NUM_THREADS
].type
= FIELD_UINT
;
427 field
[TASK_COL_NUM_THREADS
].uint
= task
->threads
;
428 field
[TASK_COL_RESIDENT
].type
= FIELD_UINT_SUFFIX_BIN
;
429 field
[TASK_COL_RESIDENT
].uint
= task
->resmem
;
430 field
[TASK_COL_PERCENT_RESIDENT
].type
= FIELD_PERCENT
;
431 field
[TASK_COL_PERCENT_RESIDENT
].fixed
= perc
->resmem
;
432 field
[TASK_COL_VIRTUAL
].type
= FIELD_UINT_SUFFIX_BIN
;
433 field
[TASK_COL_VIRTUAL
].uint
= task
->virtmem
;
434 field
[TASK_COL_PERCENT_VIRTUAL
].type
= FIELD_PERCENT
;
435 field
[TASK_COL_PERCENT_VIRTUAL
].fixed
= perc
->virtmem
;
436 field
[TASK_COL_PERCENT_USER
].type
= FIELD_PERCENT
;
437 field
[TASK_COL_PERCENT_USER
].fixed
= perc
->ucycles
;
438 field
[TASK_COL_PERCENT_KERNEL
].type
= FIELD_PERCENT
;
439 field
[TASK_COL_PERCENT_KERNEL
].fixed
= perc
->kcycles
;
440 field
[TASK_COL_NAME
].type
= FIELD_STRING
;
441 field
[TASK_COL_NAME
].string
= task
->name
;
442 field
+= TASK_NUM_COLUMNS
;
448 static const char *fill_ipc_table(data_t
*data
)
450 data
->table
.name
= "IPC";
451 data
->table
.num_columns
= IPC_NUM_COLUMNS
;
452 data
->table
.columns
= ipc_columns
;
453 data
->table
.num_fields
= data
->tasks_count
* IPC_NUM_COLUMNS
;
454 data
->table
.fields
= calloc(data
->table
.num_fields
,
456 if (data
->table
.fields
== NULL
)
457 return "Not enough memory for table fields";
459 field_t
*field
= data
->table
.fields
;
460 for (size_t i
= 0; i
< data
->tasks_count
; i
++) {
461 field
[IPC_COL_TASKID
].type
= FIELD_UINT
;
462 field
[IPC_COL_TASKID
].uint
= data
->tasks
[i
].task_id
;
463 field
[IPC_COL_CLS_SNT
].type
= FIELD_UINT_SUFFIX_DEC
;
464 field
[IPC_COL_CLS_SNT
].uint
= data
->tasks
[i
].ipc_info
.call_sent
;
465 field
[IPC_COL_CLS_RCV
].type
= FIELD_UINT_SUFFIX_DEC
;
466 field
[IPC_COL_CLS_RCV
].uint
= data
->tasks
[i
].ipc_info
.call_received
;
467 field
[IPC_COL_ANS_SNT
].type
= FIELD_UINT_SUFFIX_DEC
;
468 field
[IPC_COL_ANS_SNT
].uint
= data
->tasks
[i
].ipc_info
.answer_sent
;
469 field
[IPC_COL_ANS_RCV
].type
= FIELD_UINT_SUFFIX_DEC
;
470 field
[IPC_COL_ANS_RCV
].uint
= data
->tasks
[i
].ipc_info
.answer_received
;
471 field
[IPC_COL_FORWARD
].type
= FIELD_UINT_SUFFIX_DEC
;
472 field
[IPC_COL_FORWARD
].uint
= data
->tasks
[i
].ipc_info
.forwarded
;
473 field
[IPC_COL_NAME
].type
= FIELD_STRING
;
474 field
[IPC_COL_NAME
].string
= data
->tasks
[i
].name
;
475 field
+= IPC_NUM_COLUMNS
;
481 static const char *fill_exception_table(data_t
*data
)
483 data
->table
.name
= "Exceptions";
484 data
->table
.num_columns
= EXCEPTION_NUM_COLUMNS
;
485 data
->table
.columns
= exception_columns
;
486 data
->table
.num_fields
= data
->exceptions_count
*
487 EXCEPTION_NUM_COLUMNS
;
488 data
->table
.fields
= calloc(data
->table
.num_fields
, sizeof(field_t
));
489 if (data
->table
.fields
== NULL
)
490 return "Not enough memory for table fields";
492 field_t
*field
= data
->table
.fields
;
493 for (size_t i
= 0; i
< data
->exceptions_count
; i
++) {
494 if (!excs_all
&& !data
->exceptions
[i
].hot
)
496 field
[EXCEPTION_COL_ID
].type
= FIELD_UINT
;
497 field
[EXCEPTION_COL_ID
].uint
= data
->exceptions
[i
].id
;
498 field
[EXCEPTION_COL_COUNT
].type
= FIELD_UINT_SUFFIX_DEC
;
499 field
[EXCEPTION_COL_COUNT
].uint
= data
->exceptions
[i
].count
;
500 field
[EXCEPTION_COL_PERCENT_COUNT
].type
= FIELD_PERCENT
;
501 field
[EXCEPTION_COL_PERCENT_COUNT
].fixed
= data
->exceptions_perc
[i
].count
;
502 field
[EXCEPTION_COL_CYCLES
].type
= FIELD_UINT_SUFFIX_DEC
;
503 field
[EXCEPTION_COL_CYCLES
].uint
= data
->exceptions
[i
].cycles
;
504 field
[EXCEPTION_COL_PERCENT_CYCLES
].type
= FIELD_PERCENT
;
505 field
[EXCEPTION_COL_PERCENT_CYCLES
].fixed
= data
->exceptions_perc
[i
].cycles
;
506 field
[EXCEPTION_COL_DESCRIPTION
].type
= FIELD_STRING
;
507 field
[EXCEPTION_COL_DESCRIPTION
].string
= data
->exceptions
[i
].desc
;
508 field
+= EXCEPTION_NUM_COLUMNS
;
511 /* in case any cold exceptions were ignored */
512 data
->table
.num_fields
= field
- data
->table
.fields
;
517 static const char *fill_table(data_t
*data
)
519 if (data
->table
.fields
!= NULL
) {
520 free(data
->table
.fields
);
521 data
->table
.fields
= NULL
;
526 return fill_task_table(data
);
528 return fill_ipc_table(data
);
530 return fill_exception_table(data
);
535 static void free_data(data_t
*target
)
537 if (target
->load
!= NULL
)
540 if (target
->cpus
!= NULL
)
543 if (target
->cpus_perc
!= NULL
)
544 free(target
->cpus_perc
);
546 if (target
->tasks
!= NULL
)
549 if (target
->tasks_perc
!= NULL
)
550 free(target
->tasks_perc
);
552 if (target
->threads
!= NULL
)
553 free(target
->threads
);
555 if (target
->exceptions
!= NULL
)
556 free(target
->exceptions
);
558 if (target
->exceptions_perc
!= NULL
)
559 free(target
->exceptions_perc
);
561 if (target
->physmem
!= NULL
)
562 free(target
->physmem
);
564 if (target
->ucycles_diff
!= NULL
)
565 free(target
->ucycles_diff
);
567 if (target
->kcycles_diff
!= NULL
)
568 free(target
->kcycles_diff
);
570 if (target
->ecycles_diff
!= NULL
)
571 free(target
->ecycles_diff
);
573 if (target
->ecount_diff
!= NULL
)
574 free(target
->ecount_diff
);
576 if (target
->table
.fields
!= NULL
)
577 free(target
->table
.fields
);
580 int main(int argc
, char *argv
[])
584 const char *ret
= NULL
;
587 printf("Reading initial data...\n");
589 if ((ret
= read_data(&data
)) != NULL
)
592 /* Compute some rubbish to have initialised values */
593 compute_percentages(&data
, &data
);
595 /* And paint screen until death */
597 int c
= tgetchar(UPDATE_INTERVAL
);
599 if (c
< 0) { /* timeout */
601 if ((ret
= read_data(&data
)) != NULL
) {
602 free_data(&data_prev
);
606 compute_percentages(&data_prev
, &data
);
607 free_data(&data_prev
);
612 if (screen_mode
== SCREEN_HELP
&& c
>= 0) {
613 if (c
== 'h' || c
== '?')
615 /* go back to table and handle the key */
616 screen_mode
= SCREEN_TABLE
;
619 if (screen_mode
== SCREEN_SORT
&& c
>= 0) {
620 for (size_t i
= 0; i
< data
.table
.num_columns
; i
++) {
621 if (data
.table
.columns
[i
].key
== c
) {
623 screen_mode
= SCREEN_TABLE
;
631 case -1: /* do nothing */
643 screen_mode
= SCREEN_SORT
;
646 sort_reverse
= -sort_reverse
;
650 screen_mode
= SCREEN_HELP
;
655 if (op_mode
== OP_EXCS
) {
656 excs_all
= !excs_all
;
658 show_warning("Showing all exceptions");
660 show_warning("Showing only hot exceptions");
665 show_warning("Unknown command \"%c\", use \"h\" for help", c
);
666 continue; /* don't redraw */
669 if ((ret
= fill_table(&data
)) != NULL
) {
672 sort_table(&data
.table
);
681 fprintf(stderr
, "%s: %s\n", NAME
, ret
);