1/*
2 * Copyright (c) 2008 Apple Computer, Inc.  All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * The contents of this file constitute Original Code as defined in and
7 * are subject to the Apple Public Source License Version 1.1 (the
8 * "License").  You may not use this file except in compliance with the
9 * License.  Please obtain a copy of the License at
10 * http://www.apple.com/publicsource and read it before using this file.
11 *
12 * This Original Code and all software distributed under the License are
13 * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
14 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
15 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT.  Please see the
17 * License for the specific language governing rights and limitations
18 * under the License.
19 *
20 * @APPLE_LICENSE_HEADER_END@
21 */
22
23#include <stdbool.h>
24#include <stdio.h>
25#include <stdlib.h>
26#include <string.h>
27#include <curses.h>
28#include "top.h"
29#include "libtop.h"
30#include "globalstats.h"
31#include "layout.h"
32#include "log.h"
33#include "userinput.h"
34#include "preferences.h"
35
36const libtop_tsamp_t *tsamp;
37
38static int sort_subcomp(int a_key, const libtop_psamp_t *a_a,
39			const libtop_psamp_t *a_b);
40
41static bool top_do_relayout = false;
42
43/* This may not be called in common update patterns. */
44void top_relayout_force(void) {
45    top_do_relayout = true;
46}
47
48void top_relayout(struct statistics_controller *c, int type, int maxwidth) {
49    top_do_relayout = true;
50}
51
52bool top_need_relayout(void) {
53    return top_do_relayout;
54}
55
56void *top_create(WINDOW *wmain) {
57    void *gstats;
58    struct statistics_controller *controller;
59
60    gstats = top_globalstats_create(wmain);
61
62    if(NULL == gstats) {
63	endwin();
64	fprintf(stderr, "unable to create global stats!\n");
65	_exit(EXIT_FAILURE);
66    }
67
68    controller = create_statistics_controller(wmain);
69
70    if(NULL == controller) {
71	endwin();
72	fprintf(stderr, "unable to create controller for main window!\n");
73	_exit(EXIT_FAILURE);
74    }
75
76    controller->globalstats = gstats;
77
78    return controller;
79}
80
81
82static int top_sort(void *a_data, const libtop_psamp_t *a, const libtop_psamp_t *b) {
83    int retval;
84
85    retval = sort_subcomp(top_prefs_get_sort(), a, b) *
86	(top_prefs_get_ascending() ? 1 : -1);
87    if (retval == 0) {
88	retval = sort_subcomp(top_prefs_get_secondary_sort(), a, b) *
89	    (top_prefs_get_secondary_ascending() ? 1 : -1);
90    }
91
92    return retval;
93}
94
95#define COMP(a,b) (((a)==(b)?0:	\
96		    ((a)<(b)?-1:1)))
97
98static int sort_subcomp(int a_key, const libtop_psamp_t *a_a,
99		     const libtop_psamp_t *a_b) {
100    struct timeval  tv_a, tv_b;
101    unsigned long long used_ns_a, used_ns_b;
102    const char      *user_a, *user_b;
103
104    switch (a_key) {
105    case STATISTIC_PID: return COMP(a_a->pid, a_b->pid);
106
107    case STATISTIC_COMMAND: return COMP(strcmp(a_a->command, a_b->command),0);
108
109    case STATISTIC_CPU:
110	timersub(&a_a->total_time, &a_a->p_total_time, &tv_a);
111	timersub(&a_b->total_time, &a_b->p_total_time, &tv_b);
112
113	if(tv_a.tv_sec == tv_b.tv_sec) {
114	    return COMP(tv_a.tv_usec, tv_b.tv_usec);
115	} else {
116	    return COMP(tv_a.tv_sec, tv_b.tv_sec);
117	}
118
119    case STATISTIC_CPU_ME:
120	used_ns_a = a_a->cpu_billed_to_me - a_a->p_cpu_billed_to_me;
121	used_ns_b = a_b->cpu_billed_to_me - a_b->p_cpu_billed_to_me;
122	return COMP(used_ns_a, used_ns_b);
123
124    case STATISTIC_CPU_OTHERS:
125	used_ns_a = a_a->cpu_billed_to_others - a_a->p_cpu_billed_to_others;
126	used_ns_b = a_b->cpu_billed_to_others - a_b->p_cpu_billed_to_others;
127	return COMP(used_ns_a, used_ns_b);
128
129    case STATISTIC_BOOSTS:
130	{
131		int res;
132		res = COMP(a_a->boost_last_donating_seq, a_b->boost_last_donating_seq);
133		if (res) return res;
134		res = COMP(!a_a->boost_donating, !a_b->boost_donating);
135		if (res) return res;
136		return COMP(a_a->boosts - a_a->p_boosts, a_b->boosts - a_b->p_boosts);
137	}
138
139
140    case STATISTIC_TIME:
141	 tv_a = a_a->total_time;
142	 tv_b = a_b->total_time;
143
144	 if(tv_a.tv_sec == tv_b.tv_sec) {
145	     return COMP(tv_a.tv_usec, tv_b.tv_usec);
146	 } else {
147	     return COMP(tv_a.tv_sec, tv_b.tv_sec);
148	 }
149
150    case STATISTIC_THREADS: return COMP(a_a->th, a_b->th);
151
152    case STATISTIC_WORKQUEUE: return COMP(a_a->wq_nthreads, a_b->wq_nthreads);
153
154    case STATISTIC_PORTS: return COMP(a_a->prt, a_b->prt);
155
156    case STATISTIC_MREGION: return COMP(a_a->reg, a_b->reg);
157
158#ifdef TOP_ANONYMOUS_MEMORY
159    case STATISTIC_RMEM: return COMP(a_a->anonymous, a_b->anonymous);
160
161    case STATISTIC_RPRVT: return COMP(a_a->rprvt, a_b->rprvt);
162
163    case STATISTIC_PURG: return COMP(a_a->purgeable, a_b->purgeable);
164
165    case STATISTIC_COMPRESSED: return COMP(a_a->compressed, a_b->compressed);
166#else
167    case STATISTIC_RPRVT: return COMP(a_a->rprvt, a_b->rprvt);
168
169    case STATISTIC_RSHRD: return COMP(a_a->rshrd, a_b->rshrd);
170
171    case STATISTIC_RSIZE: return COMP(a_a->rsize, a_b->rsize);
172#endif
173
174    case STATISTIC_VSIZE: return COMP(a_a->vsize, a_b->vsize);
175
176    case STATISTIC_VPRVT: return COMP(a_a->vprvt, a_b->vprvt);
177
178    case STATISTIC_PGRP: return COMP(a_a->pgrp, a_b->pgrp);
179
180    case STATISTIC_PPID: return COMP(a_a->ppid, a_b->ppid);
181
182    case STATISTIC_KPRVT: return COMP(a_a->palloc - a_a->pfree,
183				     a_b->palloc - a_b->pfree);
184
185    case STATISTIC_KSHRD: return COMP(a_a->salloc - a_a->sfree,
186				     a_b->salloc - a_b->sfree);
187
188    case STATISTIC_PSTATE: {
189	const char *a = libtop_state_str(a_a->state);
190	const char *b = libtop_state_str(a_b->state);
191
192	return COMP(strcmp(a, b), 0);
193    }
194    case STATISTIC_UID: return COMP(a_a->uid, a_b->uid);
195
196    case STATISTIC_FAULTS:
197	return COMP(a_a->faults.now, a_b->faults.now);
198
199    case STATISTIC_COW_FAULTS:
200	return COMP(a_a->cow_faults.now, a_b->cow_faults.now);
201
202    case STATISTIC_MESSAGES_SENT:
203	return COMP(a_a->messages_sent.now, a_b->messages_sent.now);
204
205    case STATISTIC_MESSAGES_RECEIVED:
206	return COMP(a_a->messages_recv.now, a_b->messages_recv.now);
207
208    case STATISTIC_SYSBSD:
209	return COMP(a_a->syscalls_bsd.now, a_b->syscalls_bsd.now);
210
211    case STATISTIC_SYSMACH:
212	return COMP(a_a->syscalls_mach.now, a_b->syscalls_mach.now);
213
214    case STATISTIC_CSW:
215	return COMP(a_a->csw.now, a_b->csw.now);
216
217    case STATISTIC_PAGEINS:
218	return COMP(a_a->pageins.now, a_b->pageins.now);
219
220    case STATISTIC_IDLEWAKE:
221	return COMP(a_a->power.task_platform_idle_wakeups, a_b->power.task_platform_idle_wakeups);
222
223    case STATISTIC_POWERSCORE: {
224	uint64_t a_idlew = a_a->power.task_platform_idle_wakeups - a_a->p_power.task_platform_idle_wakeups;
225	uint64_t b_idlew = a_b->power.task_platform_idle_wakeups - a_b->p_power.task_platform_idle_wakeups;
226	timersub(&a_a->total_time, &a_a->p_total_time, &tv_a);
227	timersub(&a_b->total_time, &a_b->p_total_time, &tv_b);
228	uint64_t a_usec = tv_a.tv_usec + (UINT64_C(1000000) * tv_a.tv_sec) + (UINT64_C(500) * a_idlew);
229	uint64_t b_usec = tv_b.tv_usec + (UINT64_C(1000000) * tv_b.tv_sec) + (UINT64_C(500) * b_idlew);
230
231	// kernel gets a free ride
232	if (a_a->pid == 0) {
233		a_usec = UINT64_C(0);
234	} else if (a_b->pid == 0) {
235		b_usec = UINT64_C(0);
236	}
237	return COMP(a_usec, b_usec);
238    }
239
240    case STATISTIC_USER:
241	/* Handle the == case first, since it's common. */
242	if (a_a->uid == a_b->uid) return 0;
243
244	user_a = libtop_username(a_a->uid);
245	user_b = libtop_username(a_b->uid);
246
247	return COMP(strcmp(user_a, user_b),0);
248    }
249
250    return 0;
251}
252
253#undef COMP
254
255void top_sample(void) {
256    if(libtop_sample(/*calculate mreg, vprvt and more see libtop.h*/
257		     top_prefs_get_mmr(),
258		     /*framework stats*/
259		     top_prefs_get_frameworks())) {
260	endwin();
261	fprintf(stderr, "error: while gathering a libtop sample.\n"
262		"The permissions and/or ownership are incorrect "
263		"for this executable, or you are testing without sudo.\n");
264	_exit(EXIT_FAILURE);
265    }
266}
267
268void top_insert(void *ptr) {
269    struct statistics_controller *c = ptr;
270    const libtop_psamp_t *psample;
271    char *user;
272    unsigned long uid = 0;
273    int nprocs;
274    pid_t pid;
275    bool have_pid = false;
276
277    c->reset_insertion(c);
278
279    top_sample();
280
281    /*
282     * The ordering is important here, because the libtop_psort actually
283     * updates the tsamp->nprocs.
284     */
285    libtop_psort(top_sort, NULL);
286
287    tsamp = libtop_tsamp();
288
289    if(top_globalstats_update(c->globalstats, tsamp))
290	top_log("An error occurred while updating global stats.\n");
291
292    /* The user has requested only displaying processes owned by user. */
293    user = top_prefs_get_user();
294
295    if(user)
296	uid = top_prefs_get_user_uid();
297
298    nprocs = top_prefs_get_nprocs();
299
300    if(0 == nprocs)
301	return;
302
303    have_pid = top_prefs_get_pid(&pid);
304
305    for(psample = libtop_piterate(); psample; psample = libtop_piterate()) {
306	if(user && psample->uid != uid)
307	    continue;
308
309	if(have_pid) {
310	    if(pid != psample->pid)
311		continue;
312	}
313
314	c->insert_sample(c, psample);
315
316	/*
317	 * If nprocs is -1 (the default), or otherwise negative,
318	 * then display all.
319	 */
320	if(nprocs > 0)
321	    --nprocs;
322
323	if(0 == nprocs) {
324	    break;
325	}
326    }
327}
328
329
330/* Return true if a non-fatal error occurred. */
331bool top_layout(void *ptr) {
332    struct statistics_controller *c = ptr;
333    int lines = LINES, cols = COLS;
334    int consumed_height = 0;
335    int pstatheight;
336
337    //top_log("%s\n", __func__);
338
339    top_do_relayout = false;
340
341    //fprintf(stderr, "laying out: lines %d cols %d\n", lines, cols);
342    //werase(c->parent);
343
344    if(ERR == wresize(c->parent, lines, cols)) {
345	top_log("error: wresizing parent!\n");
346	return true;
347    }
348
349    top_globalstats_reset(c->globalstats);
350
351    if(top_globalstats_resize(c->globalstats, cols, lines,
352			      &consumed_height)) {
353	top_log("error: performing global stats resize!\n");
354	return true;
355    }
356
357    user_input_set_position(consumed_height, 0);
358
359    pstatheight = lines - consumed_height - 1;
360    if(pstatheight <= 0)
361	return true;
362
363    //fprintf(stderr, "consumed_height %d\n", consumed_height);
364
365    if(layout_statistics(c, cols, pstatheight,
366			 /*y*/ consumed_height + 1)) {
367	top_log("error: performing statistic layout!\n");
368	return true;
369    }
370
371    return false;
372}
373
374struct draw_state {
375    int xoffset;
376};
377
378static bool top_draw_iterator(struct statistic *s, void *ptr) {
379    struct draw_state *state = ptr;
380
381    s->callbacks.draw(s, /*x*/ state->xoffset);
382
383    state->xoffset = 1;
384
385    /* more iterations */
386    return true;
387}
388
389void top_draw(void *ptr) {
390    struct statistics_controller *c = ptr;
391    struct draw_state state;
392
393    werase(c->parent);
394
395    top_globalstats_draw(c->globalstats);
396
397    user_input_draw(ptr, stdscr);
398
399    state.xoffset = 0;
400
401    c->iterate(c, top_draw_iterator, &state);
402
403    /* This moves the insertion cursor to the lower right. */
404    wmove(stdscr, LINES - 1, COLS - 1);
405
406    update_panels();
407    doupdate();
408}
409