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