1// Copyright 2017 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <pretty/sizes.h>
6#include <zircon/device/sysinfo.h>
7#include <zircon/status.h>
8#include <zircon/syscalls.h>
9#include <zircon/syscalls/exception.h>
10#include <zircon/syscalls/object.h>
11#include <zircon/time.h>
12#include <zircon/types.h>
13
14#include <errno.h>
15#include <fcntl.h>
16#include <getopt.h>
17#include <inttypes.h>
18#include <math.h>
19#include <stdbool.h>
20#include <stdio.h>
21#include <stdlib.h>
22#include <string.h>
23#include <time.h>
24#include <unistd.h>
25
26#include "resources.h"
27
28// TODO: dynamically compute this based on what it returns
29#define MAX_CPUS 32
30
31static zx_status_t cpustats(zx_handle_t root_resource, zx_duration_t delay) {
32    static zx_duration_t last_idle_time[MAX_CPUS];
33    static zx_info_cpu_stats_t old_stats[MAX_CPUS];
34    zx_info_cpu_stats_t stats[MAX_CPUS];
35
36    // retrieve the system stats
37    size_t actual, avail;
38    zx_status_t err = zx_object_get_info(root_resource, ZX_INFO_CPU_STATS, &stats, sizeof(stats), &actual, &avail);
39    if (err != ZX_OK) {
40        fprintf(stderr, "ZX_INFO_CPU_STATS returns %d (%s)\n", err, zx_status_get_string(err));
41        return err;
42    }
43
44    if (actual < avail) {
45        fprintf(stderr, "WARNING: actual cpus reported %zu less than available cpus %zu\n",
46                actual, avail);
47    }
48
49    printf("cpu    load"
50           " sched (cs ylds pmpts irq_pmpts)"
51           " excep"
52           " pagef"
53           "  sysc"
54           " ints (hw  tmr tmr_cb)"
55           " ipi (rs  gen)\n");
56    for (size_t i = 0; i < actual; i++) {
57        zx_duration_t idle_time = stats[i].idle_time;
58
59        zx_duration_t delta_time = zx_duration_sub_duration(idle_time, last_idle_time[i]);
60        zx_duration_t busy_time;
61        if (delay > delta_time) {
62            busy_time = zx_duration_sub_duration(delay, delta_time);
63        } else {
64            busy_time = 0;
65        }
66        unsigned int busypercent = zx_duration_mul_int64(busy_time, 10000) / delay;
67
68        printf("%3zu"
69               " %3u.%02u%%"
70               " %9lu %4lu %5lu %9lu"
71               " %6lu"
72               " %5lu"
73               " %5lu"
74               " %8lu %4lu %6lu"
75               " %8lu %4lu"
76               "\n",
77               i,
78               busypercent / 100, busypercent % 100,
79               stats[i].context_switches - old_stats[i].context_switches,
80               stats[i].yields - old_stats[i].yields,
81               stats[i].preempts - old_stats[i].preempts,
82               stats[i].irq_preempts - old_stats[i].irq_preempts,
83               stats[i].exceptions - old_stats[i].exceptions,
84               stats[i].page_faults - old_stats[i].page_faults,
85               stats[i].syscalls - old_stats[i].syscalls,
86               stats[i].ints - old_stats[i].ints,
87               stats[i].timer_ints - old_stats[i].timer_ints,
88               stats[i].timers - old_stats[i].timers,
89               stats[i].reschedule_ipis - old_stats[i].reschedule_ipis,
90               stats[i].generic_ipis - old_stats[i].generic_ipis);
91
92        old_stats[i] = stats[i];
93        last_idle_time[i] = idle_time;
94    }
95
96    return ZX_OK;
97}
98
99static void print_mem_stat(const char* label, size_t bytes) {
100    char buf[MAX_FORMAT_SIZE_LEN];
101    const char unit = 'M';
102    printf("%15s: %8sB / %10zuB\n",
103           label,
104           format_size_fixed(buf, sizeof(buf), bytes, unit),
105           bytes);
106}
107
108static zx_status_t memstats(zx_handle_t root_resource) {
109    zx_info_kmem_stats_t stats;
110    zx_status_t err = zx_object_get_info(
111        root_resource, ZX_INFO_KMEM_STATS, &stats, sizeof(stats), NULL, NULL);
112    if (err != ZX_OK) {
113        fprintf(stderr, "ZX_INFO_KMEM_STATS returns %d (%s)\n",
114                err, zx_status_get_string(err));
115        return err;
116    }
117
118    const int width = 80 / 8 - 1;
119    printf("%*s %*s %*s %*s %*s %*s %*s %*s %*s\n",
120           width, "mem total",
121           width, "free",
122           width, "VMOs",
123           width, "kheap",
124           width, "kfree",
125           width, "wired",
126           width, "mmu",
127           width, "ipc",
128           width, "other");
129
130    const size_t fields[] = {
131        stats.total_bytes,
132        stats.free_bytes,
133        stats.vmo_bytes,
134        stats.total_heap_bytes - stats.free_heap_bytes,
135        stats.free_heap_bytes,
136        stats.wired_bytes,
137        stats.mmu_overhead_bytes,
138        stats.ipc_bytes,
139        stats.other_bytes,
140    };
141    char line[128] = {};
142    for (unsigned int i = 0; i < countof(fields); i++) {
143        const char unit = 'M';
144        char buf[MAX_FORMAT_SIZE_LEN];
145        format_size_fixed(buf, sizeof(buf), fields[i], unit);
146
147        char stage[MAX_FORMAT_SIZE_LEN + 8];
148        snprintf(stage, sizeof(stage), "%*s ", width, buf);
149
150        strlcat(line, stage, sizeof(line));
151
152        // TODO(dbort): Save some history so we can show deltas over time.
153        // Maybe have a few buckets like 1s, 10s, 1m.
154    }
155    printf("%s\n", line);
156    return ZX_OK;
157}
158
159static void print_help(FILE* f) {
160    fprintf(f, "Usage: kstats [options]\n");
161    fprintf(f, "Options:\n");
162    fprintf(f, " -c              Print system CPU stats\n");
163    fprintf(f, " -m              Print system memory stats\n");
164    fprintf(f, " -d <delay>      Delay in seconds (default 1 second)\n");
165    fprintf(f, " -n <times>      Run this many times and then exit\n");
166    fprintf(f, " -t              Print timestamp for each report\n");
167    fprintf(f, "\nCPU stats columns:\n");
168    fprintf(f, "\tcpu:  cpu #\n");
169    fprintf(f, "\tload: percentage load\n");
170    fprintf(f, "\tsched (cs ylds pmpts irq_pmpts): scheduler statistics\n");
171    fprintf(f, "\t\tcs:        context switches\n");
172    fprintf(f, "\t\tylds:      explicit thread yields\n");
173    fprintf(f, "\t\tpmpts:     thread preemption events\n");
174    fprintf(f, "\t\tirq_pmpts: thread preemption events from interrupt\n");
175
176    fprintf(f, "\texcep: exceptions (undefined instruction, bad memory access, etc)\n");
177    fprintf(f, "\tpagef: page faults\n");
178    fprintf(f, "\tsysc:  syscalls\n");
179    fprintf(f, "\tints (hw  tmr tmr_cb): interrupt statistics\n");
180    fprintf(f, "\t\thw:     hardware interrupts\n");
181    fprintf(f, "\t\ttmr:    timer interrupts\n");
182    fprintf(f, "\t\ttmr_cb: kernel timer events\n");
183    fprintf(f, "\tipi (rs  gen): inter-processor-interrupts\n");
184    fprintf(f, "\t\trs:     reschedule events\n");
185    fprintf(f, "\t\tgen:    generic interprocessor interrupts\n");
186}
187
188int main(int argc, char** argv) {
189    bool cpu_stats = false;
190    bool mem_stats = false;
191    zx_duration_t delay = ZX_SEC(1);
192    int num_loops = -1;
193    bool timestamp = false;
194
195    int c;
196    while ((c = getopt(argc, argv, "cd:n:hmt")) > 0) {
197        switch (c) {
198            case 'c':
199                cpu_stats = true;
200                break;
201            case 'd':
202                delay = ZX_SEC(atoi(optarg));
203                if (delay == 0) {
204                    fprintf(stderr, "Bad -d value '%s'\n", optarg);
205                    print_help(stderr);
206                    return 1;
207                }
208                break;
209            case 'n':
210                num_loops = atoi(optarg);
211                if (num_loops == 0) {
212                    fprintf(stderr, "Bad -n value '%s'\n", optarg);
213                    print_help(stderr);
214                    return 1;
215                }
216                break;
217            case 'h':
218                print_help(stdout);
219                return 0;
220            case 'm':
221                mem_stats = true;
222                break;
223            case 't':
224                timestamp = true;
225                break;
226            default:
227                fprintf(stderr, "Unknown option\n");
228                print_help(stderr);
229                return 1;
230        }
231    }
232
233    if (!cpu_stats && !mem_stats) {
234        fprintf(stderr, "No statistics selected\n");
235        print_help(stderr);
236        return 1;
237    }
238
239    zx_handle_t root_resource;
240    zx_status_t ret = get_root_resource(&root_resource);
241    if (ret != ZX_OK) {
242        return ret;
243    }
244
245    // set stdin to non blocking so we can intercept ctrl-c.
246    // TODO: remove once ctrl-c works in the shell
247    fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK);
248
249    for (;;) {
250        zx_time_t next_deadline = zx_deadline_after(delay);
251
252        // Print the current UTC time with milliseconds as
253        // an ISO 8601 string.
254        if (timestamp) {
255            struct timespec now;
256            timespec_get(&now, TIME_UTC);
257            struct tm nowtm;
258            gmtime_r(&now.tv_sec, &nowtm);
259            char tbuf[40];
260            strftime(tbuf, sizeof(tbuf), "%FT%T", &nowtm);
261            printf("\n--- %s.%03ldZ ---\n", tbuf, now.tv_nsec / (1000 * 1000));
262        }
263
264        if (cpu_stats) {
265            ret |= cpustats(root_resource, delay);
266        }
267        if (mem_stats) {
268            ret |= memstats(root_resource);
269        }
270
271        if (ret != ZX_OK)
272            break;
273
274        if (num_loops > 0) {
275            if (--num_loops == 0) {
276                break;
277            }
278        } else {
279            // TODO: replace once ctrl-c works in the shell
280            char c;
281            int err;
282            while ((err = read(STDIN_FILENO, &c, 1)) > 0) {
283                if (c == 0x3)
284                    return 0;
285            }
286        }
287
288        zx_nanosleep(next_deadline);
289    }
290
291    zx_handle_close(root_resource);
292
293    return ret;
294}
295