Line data Source code
1 : #include "fd_proc_interrupts.h"
2 : #include "../../util/tile/fd_tile.h" /* FD_TILE_MAX */
3 : #include "../../util/io/fd_io.h" /* fd_io_buffered_istream */
4 : #include <ctype.h> /* isdigit */
5 :
6 : /* The skip_* helpers consume a run of bytes from is. Each returns 1 if
7 : it stopped at a genuine terminator and 0 if it consumed every
8 : buffered byte without reaching a terminator. */
9 :
10 : static int
11 0 : skip_spaces( fd_io_buffered_istream_t * is ) {
12 0 : char const * peek = fd_io_buffered_istream_peek ( is );
13 0 : ulong peek_sz = fd_io_buffered_istream_peek_sz( is );
14 0 : ulong j;
15 0 : for( j=0UL; j<peek_sz && peek[j]==' '; j++ ) {}
16 0 : fd_io_buffered_istream_skip( is, j );
17 0 : return j<peek_sz; /* complete iff we stopped on a non-space */
18 0 : }
19 :
20 : static int
21 0 : skip_token( fd_io_buffered_istream_t * is ) {
22 0 : char const * peek = fd_io_buffered_istream_peek ( is );
23 0 : ulong peek_sz = fd_io_buffered_istream_peek_sz( is );
24 0 : ulong j;
25 0 : for( j=0UL; j<peek_sz && peek[j]!=' ' && peek[j]!='\n'; j++ ) {}
26 0 : fd_io_buffered_istream_skip( is, j );
27 0 : return j<peek_sz; /* complete iff we stopped on a delimiter */
28 0 : }
29 :
30 : static int
31 0 : skip_line( fd_io_buffered_istream_t * is ) {
32 0 : char const * peek = fd_io_buffered_istream_peek ( is );
33 0 : ulong peek_sz = fd_io_buffered_istream_peek_sz( is );
34 0 : ulong j;
35 0 : for( j=0UL; j<peek_sz && peek[j]!='\n'; j++ ) {}
36 0 : int complete = j<peek_sz; /* found the '\n' */
37 0 : if( complete ) j++; /* consume the '\n' */
38 0 : fd_io_buffered_istream_skip( is, j );
39 0 : return complete;
40 0 : }
41 :
42 : /* read_ulong consumes a decimal ulong from buffered unconsumed chars in
43 : is. Returns ULONG_MAX if parse failed. */
44 :
45 : static ulong
46 0 : read_ulong( fd_io_buffered_istream_t * is ) {
47 0 : char const * peek = fd_io_buffered_istream_peek ( is );
48 0 : ulong peek_sz = fd_io_buffered_istream_peek_sz( is );
49 0 : char num[ 21 ];
50 0 : peek_sz = fd_ulong_min( peek_sz, 20 );
51 0 : ulong num_sz;
52 0 : for( num_sz=0UL; num_sz<peek_sz && isdigit( peek[num_sz] ); num_sz++ ) {}
53 0 : memcpy( num, peek, num_sz );
54 0 : num[ num_sz ] = '\0';
55 0 : fd_io_buffered_istream_skip( is, num_sz );
56 0 : if( FD_UNLIKELY( num_sz==0 ) ) return ULONG_MAX;
57 0 : return fd_cstr_to_ulong( num );
58 0 : }
59 :
60 : static int
61 : read_until( fd_io_buffered_istream_t * is,
62 : ulong min_sz,
63 0 : int (*skip)( fd_io_buffered_istream_t * is ) ) {
64 :
65 0 : ulong buf_sz = fd_io_buffered_istream_rbuf_sz( is );
66 0 : min_sz = fd_ulong_min( min_sz, buf_sz );
67 :
68 : /* Read until 'skip' leaves some bytes */
69 :
70 0 : for(;;) {
71 0 : if( skip( is ) ) break; /* unit complete */
72 0 : int err = fd_io_buffered_istream_fetch( is );
73 0 : if( err==0 ) {
74 0 : continue;
75 0 : }
76 0 : if( err==-1 ) {
77 0 : if( fd_io_buffered_istream_peek_sz( is )==0 ) return -1;
78 0 : break;
79 0 : }
80 0 : return err;
81 0 : }
82 :
83 : /* Read ahead */
84 :
85 0 : while( fd_io_buffered_istream_peek_sz( is )<min_sz ) {
86 0 : int err = fd_io_buffered_istream_fetch( is );
87 0 : if( err==-1 ) break;
88 0 : if( err!=0 ) return err;
89 0 : }
90 :
91 0 : return 0;
92 0 : }
93 :
94 : /* read_cpu_map reads the first line of /proc/interrupts mapping columns
95 : to CPUs. Usually 0,1,2,3,... but has gaps for offline CPUs. Also
96 : used for /proc/softirqs. Formatted like this:
97 : " CPU0 CPU1 CPU2 CPU3\n" */
98 :
99 : static int
100 : read_cpu_map( fd_io_buffered_istream_t * is,
101 : ulong * out_col_cnt,
102 : ulong * out_cpu_cnt,
103 0 : ushort col_cpu[ FD_TILE_MAX ] ) {
104 0 : *out_col_cnt = 0UL;
105 0 : *out_cpu_cnt = 0UL;
106 :
107 : /* Read first line */
108 0 : ulong col_cnt = 0UL;
109 0 : do {
110 0 : int err = read_until( is, 16UL, skip_spaces );
111 0 : if( FD_UNLIKELY( err!=0 ) ) return err;
112 0 : char const * peek = fd_io_buffered_istream_peek ( is );
113 0 : ulong peek_sz = fd_io_buffered_istream_peek_sz( is );
114 0 : if( peek[0]=='\n' ) break;
115 0 : if( peek_sz<4 ) break;
116 :
117 : /* Filter for 'CPU<num>' column */
118 0 : if( 0!=memcmp( peek, "CPU", 3 ) ) break;
119 0 : fd_io_buffered_istream_skip( is, 3 );
120 0 : peek = fd_io_buffered_istream_peek ( is );
121 0 : peek_sz = fd_io_buffered_istream_peek_sz( is );
122 :
123 : /* Parse number */
124 0 : ulong cpu_idx = read_ulong( is );
125 0 : if( FD_UNLIKELY( cpu_idx==ULONG_MAX ) ) {
126 0 : FD_LOG_WARNING(( "Failed to parse first line of /proc/interrupts" ));
127 0 : break;
128 0 : }
129 0 : if( FD_UNLIKELY( cpu_idx>=FD_TILE_MAX ) ) {
130 0 : FD_LOG_WARNING(( "Out of bounds: CPU%lu", cpu_idx ));
131 0 : break;
132 0 : }
133 :
134 0 : col_cpu[ col_cnt++ ] = (ushort)cpu_idx;
135 0 : } while( col_cnt<FD_TILE_MAX );
136 :
137 : /* Skip rest of line */
138 0 : int err = read_until( is, 0UL, skip_line );
139 0 : if( FD_UNLIKELY( err!=0 ) ) return err;
140 :
141 : /* Verify CPU map */
142 0 : if( FD_UNLIKELY( col_cnt==0UL ) ) {
143 0 : FD_LOG_WARNING(( "No CPUs found reading /proc/interrupts" ));
144 0 : return 0;
145 0 : }
146 0 : ulong cpu_cnt = 0UL;
147 0 : for( ulong col_idx=0UL; col_idx<col_cnt; col_idx++ ) {
148 0 : ulong next_cpu_cnt = col_cpu[ col_idx ]+1UL;
149 0 : if( FD_UNLIKELY( next_cpu_cnt<=cpu_cnt ) ) {
150 0 : FD_LOG_WARNING(( "CPU%u out of order reading /proc/interrupts", col_cpu[ col_idx ] ));
151 0 : return 0;
152 0 : }
153 0 : cpu_cnt = next_cpu_cnt;
154 0 : }
155 0 : if( FD_UNLIKELY( cpu_cnt>FD_TILE_MAX ) ) {
156 0 : FD_LOG_WARNING(( "Too many CPUs found reading /proc/interrupts" ));
157 0 : return 0;
158 0 : }
159 :
160 0 : *out_cpu_cnt = cpu_cnt;
161 0 : *out_col_cnt = col_cnt;
162 0 : return 0;
163 0 : }
164 :
165 : ulong
166 : fd_proc_interrupts_colwise( int fd,
167 0 : ulong per_cpu[ FD_TILE_MAX ] ) {
168 0 : fd_io_buffered_istream_t is[1];
169 0 : char buf[ 4096 ];
170 0 : fd_io_buffered_istream_init( is, fd, buf, sizeof(buf) );
171 :
172 : /* Read first line */
173 :
174 0 : ushort col_cpu[ FD_TILE_MAX ];
175 0 : ulong col_cnt;
176 0 : ulong cpu_cnt;
177 0 : int err = read_cpu_map( is, &col_cnt, &cpu_cnt, col_cpu );
178 0 : if( FD_UNLIKELY( err!=0 ) ) goto failed;
179 0 : if( FD_UNLIKELY( !col_cnt || !cpu_cnt ) ) return 0UL;
180 :
181 0 : for( ulong cpu=0UL; cpu<cpu_cnt; cpu++ ) {
182 0 : per_cpu[ cpu ] = 0UL;
183 0 : }
184 :
185 : /* Read interrupt table
186 : Device interrupt counters look like this:
187 : " 123: 41 42 43 44"
188 : Special interrupts look like this:
189 : " NMI: 1 2 3 4" */
190 :
191 0 : for(;;) { /* each line */
192 :
193 : /* Read prefix */
194 0 : err = read_until( is, 64UL, skip_spaces );
195 0 : if( FD_UNLIKELY( err==-1 ) ) return cpu_cnt; /* end of input */
196 0 : if( FD_UNLIKELY( err!=0 ) ) goto failed;
197 0 : if( fd_io_buffered_istream_peek_sz( is )==0 ) return cpu_cnt;
198 0 : if( !isdigit( ((char const *)fd_io_buffered_istream_peek( is ))[0] ) ) {
199 : /* Only count numbered interrupts */
200 0 : goto skip_line;
201 0 : }
202 0 : err = read_until( is, 0UL, skip_token );
203 0 : if( FD_UNLIKELY( err!=0 ) ) goto failed;
204 :
205 : /* Read interrupt counters */
206 0 : for( ulong col_idx=0UL; col_idx<col_cnt; col_idx++ ) {
207 0 : err = read_until( is, 21UL, skip_spaces );
208 0 : if( FD_UNLIKELY( err!=0 ) ) goto failed;
209 :
210 0 : ulong irq_cnt = read_ulong( is );
211 0 : irq_cnt = fd_ulong_if( irq_cnt!=ULONG_MAX, irq_cnt, 0UL );
212 0 : per_cpu[ col_cpu[ col_idx ] ] += irq_cnt;
213 0 : }
214 :
215 0 : skip_line:
216 : /* Ignore rest of line */
217 0 : err = read_until( is, 0UL, skip_line );
218 0 : if( FD_UNLIKELY( err!=0 ) ) {
219 0 : if( err==-1 ) break;
220 0 : goto failed;
221 0 : }
222 :
223 0 : }
224 0 : return cpu_cnt;
225 :
226 0 : failed:
227 0 : if( err!=0 ) {
228 0 : FD_LOG_WARNING(( "read failed (%i-%s)", err, fd_io_strerror( err ) ));
229 0 : }
230 0 : return 0UL;
231 0 : }
232 :
233 : /* proc_interrupts_named_row parses a single named row (e.g. "TLB:" or
234 : "LOC:") in /proc/interrupts. Returns the number of CPUs found. On
235 : return, per_cpu[i] contains the counter for CPU i, or 0 if the row
236 : was not found. */
237 :
238 : static ulong
239 : proc_interrupts_named_row( int fd,
240 : char const * row_prefix,
241 : ulong row_prefix_len,
242 0 : ulong per_cpu[ FD_TILE_MAX ] ) {
243 0 : fd_io_buffered_istream_t is[1];
244 0 : char buf[ 4096 ];
245 0 : fd_io_buffered_istream_init( is, fd, buf, sizeof(buf) );
246 :
247 0 : ushort col_cpu[ FD_TILE_MAX ];
248 0 : ulong col_cnt;
249 0 : ulong cpu_cnt;
250 0 : int err = read_cpu_map( is, &col_cnt, &cpu_cnt, col_cpu );
251 0 : if( FD_UNLIKELY( err!=0 ) ) goto failed;
252 0 : if( FD_UNLIKELY( !col_cnt || !cpu_cnt ) ) return 0UL;
253 :
254 0 : for( ulong cpu=0UL; cpu<cpu_cnt; cpu++ ) {
255 0 : per_cpu[ cpu ] = 0UL;
256 0 : }
257 :
258 0 : for(;;) {
259 0 : err = read_until( is, 64UL, skip_spaces );
260 0 : if( FD_UNLIKELY( err==-1 ) ) return cpu_cnt; /* end of input */
261 0 : if( FD_UNLIKELY( err!=0 ) ) goto failed;
262 0 : if( fd_io_buffered_istream_peek_sz( is )==0 ) return cpu_cnt;
263 :
264 0 : char const * prefix = fd_io_buffered_istream_peek ( is );
265 0 : ulong prefix_max = fd_io_buffered_istream_peek_sz( is );
266 0 : if( FD_UNLIKELY( prefix_max>=row_prefix_len && fd_memeq( prefix, row_prefix, row_prefix_len ) ) ) {
267 0 : err = read_until( is, 0UL, skip_token );
268 0 : if( FD_UNLIKELY( err!=0 ) ) goto failed;
269 :
270 0 : for( ulong col_idx=0UL; col_idx<col_cnt; col_idx++ ) {
271 0 : err = read_until( is, 21UL, skip_spaces );
272 0 : if( FD_UNLIKELY( err!=0 ) ) goto failed;
273 :
274 0 : ulong irq_cnt = read_ulong( is );
275 0 : irq_cnt = fd_ulong_if( irq_cnt!=ULONG_MAX, irq_cnt, 0UL );
276 0 : per_cpu[ col_cpu[ col_idx ] ] = irq_cnt;
277 0 : }
278 0 : return cpu_cnt;
279 0 : }
280 :
281 0 : err = read_until( is, 0UL, skip_line );
282 0 : if( FD_UNLIKELY( err!=0 ) ) {
283 0 : if( err==-1 ) break;
284 0 : goto failed;
285 0 : }
286 0 : }
287 0 : return cpu_cnt;
288 :
289 0 : failed:
290 0 : if( err!=0 ) {
291 0 : FD_LOG_WARNING(( "read failed (%i-%s)", err, fd_io_strerror( err ) ));
292 0 : }
293 0 : return 0UL;
294 0 : }
295 :
296 : ulong
297 : fd_proc_interrupts_tlb( int fd,
298 0 : ulong per_cpu[ FD_TILE_MAX ] ) {
299 0 : return proc_interrupts_named_row( fd, "TLB:", 4UL, per_cpu );
300 0 : }
301 :
302 : ulong
303 : fd_proc_interrupts_loc( int fd,
304 0 : ulong per_cpu[ FD_TILE_MAX ] ) {
305 0 : return proc_interrupts_named_row( fd, "LOC:", 4UL, per_cpu );
306 0 : }
307 :
308 : ulong
309 : fd_proc_softirqs_sum( int fd,
310 0 : ulong per_cpu[ FD_METRICS_ENUM_SOFTIRQ_CNT ][ FD_TILE_MAX ] ) {
311 0 : fd_io_buffered_istream_t is[1];
312 0 : char buf[ 4096 ];
313 0 : fd_io_buffered_istream_init( is, fd, buf, sizeof(buf) );
314 :
315 : /* Read first line */
316 :
317 0 : ushort col_cpu[ FD_TILE_MAX ];
318 0 : ulong col_cnt;
319 0 : ulong cpu_cnt;
320 0 : int err = read_cpu_map( is, &col_cnt, &cpu_cnt, col_cpu );
321 0 : if( FD_UNLIKELY( err!=0 ) ) goto failed;
322 0 : if( FD_UNLIKELY( !col_cnt || !cpu_cnt ) ) return 0UL;
323 0 : for( ulong i=0UL; i<FD_METRICS_ENUM_SOFTIRQ_CNT; i++ ) {
324 0 : for( ulong c=0UL; c<cpu_cnt; c++ ) per_cpu[ i ][ c ] = 0UL;
325 0 : }
326 :
327 : /* Read softirq table
328 : Looks like this:
329 : " NET_TX: 1 2 3 5" */
330 :
331 0 : for(;;) { /* each line */
332 :
333 : /* Read prefix */
334 0 : err = read_until( is, 64UL, skip_spaces );
335 0 : if( FD_UNLIKELY( err==-1 ) ) return cpu_cnt; /* end of input */
336 0 : if( FD_UNLIKELY( err!=0 ) ) goto failed;
337 0 : if( fd_io_buffered_istream_peek_sz( is )==0 ) return cpu_cnt;
338 :
339 : /* Match prefix */
340 0 : int kind = FD_METRICS_ENUM_SOFTIRQ_V_OTHER_IDX;
341 0 : char const * prefix = fd_io_buffered_istream_peek ( is );
342 0 : ulong prefix_max = fd_io_buffered_istream_peek_sz( is );
343 0 : if( prefix_max>=7 && fd_memeq( prefix, "NET_TX:", 7 ) ) {
344 0 : kind = FD_METRICS_ENUM_SOFTIRQ_V_NET_IDX;
345 0 : } else if( prefix_max>=7 && fd_memeq( prefix, "NET_RX:", 7 ) ) {
346 0 : kind = FD_METRICS_ENUM_SOFTIRQ_V_NET_IDX;
347 0 : } else if( prefix_max>=6 && fd_memeq( prefix, "BLOCK:", 6 ) ) {
348 0 : kind = FD_METRICS_ENUM_SOFTIRQ_V_DISK_IDX;
349 0 : }
350 0 : err = read_until( is, 0UL, skip_token );
351 0 : if( FD_UNLIKELY( err!=0 ) ) goto failed;
352 :
353 : /* Read interrupt counters */
354 0 : for( ulong col_idx=0UL; col_idx<col_cnt; col_idx++ ) {
355 0 : err = read_until( is, 21UL, skip_spaces );
356 0 : if( FD_UNLIKELY( err!=0 ) ) goto failed;
357 :
358 0 : ulong irq_cnt = read_ulong( is );
359 0 : irq_cnt = fd_ulong_if( irq_cnt!=ULONG_MAX, irq_cnt, 0UL );
360 0 : per_cpu[ kind ][ col_cpu[ col_idx ] ] += irq_cnt;
361 0 : }
362 :
363 : /* Ignore rest of line */
364 0 : err = read_until( is, 0UL, skip_line );
365 0 : if( FD_UNLIKELY( err!=0 ) ) {
366 0 : if( err==-1 ) break;
367 0 : goto failed;
368 0 : }
369 :
370 0 : }
371 0 : return cpu_cnt;
372 :
373 0 : failed:
374 0 : if( err!=0 ) {
375 0 : FD_LOG_WARNING(( "read failed (%i-%s)", err, fd_io_strerror( err ) ));
376 0 : }
377 0 : return 0UL;
378 0 : }
379 :
380 : ulong
381 : fd_proc_stat_irq_ticks( int fd,
382 0 : ulong per_cpu[ FD_TILE_MAX ] ) {
383 0 : fd_io_buffered_istream_t is[1];
384 0 : char buf[ 4096 ];
385 0 : fd_io_buffered_istream_init( is, fd, buf, sizeof(buf) );
386 :
387 0 : for( ulong cpu=0UL; cpu<FD_TILE_MAX; cpu++ ) {
388 0 : per_cpu[ cpu ] = 0UL;
389 0 : }
390 :
391 : /* Per-CPU rows look like this ("cpu" summary row has no digit):
392 : "cpu7 134688 9390 52162 76129767 4217 3283 2069 0 0 0"
393 : Fields after the cpuN token: user nice system idle iowait irq
394 : softirq steal guest guest_nice. */
395 :
396 0 : ulong cpu_cnt = 0UL;
397 0 : int err;
398 0 : for(;;) {
399 0 : err = read_until( is, 64UL, skip_spaces );
400 0 : if( FD_UNLIKELY( err==-1 ) ) return cpu_cnt; /* end of input */
401 0 : if( FD_UNLIKELY( err!=0 ) ) goto failed;
402 0 : if( fd_io_buffered_istream_peek_sz( is )==0 ) return cpu_cnt;
403 :
404 0 : char const * prefix = fd_io_buffered_istream_peek ( is );
405 0 : ulong prefix_max = fd_io_buffered_istream_peek_sz( is );
406 :
407 : /* Rows are ordered: "cpu", "cpu0".."cpuN", then non-cpu rows. Stop
408 : at the first non-cpu row. */
409 0 : if( FD_UNLIKELY( prefix_max<4UL || !fd_memeq( prefix, "cpu", 3UL ) ) ) return cpu_cnt;
410 0 : if( FD_UNLIKELY( !isdigit( prefix[ 3 ] ) ) ) { /* "cpu" summary row */
411 0 : err = read_until( is, 0UL, skip_line );
412 0 : if( FD_UNLIKELY( err!=0 ) ) { if( err==-1 ) break; goto failed; }
413 0 : continue;
414 0 : }
415 :
416 0 : fd_io_buffered_istream_skip( is, 3UL );
417 0 : ulong cpu_idx = read_ulong( is );
418 0 : if( FD_UNLIKELY( cpu_idx==ULONG_MAX ) ) {
419 0 : FD_LOG_WARNING(( "failed to parse cpu index in /proc/stat" ));
420 0 : return 0UL;
421 0 : }
422 :
423 : /* Sum fields 6 (irq), 7 (softirq) and 8 (steal), 1-indexed after
424 : the cpuN token. */
425 0 : ulong ticks = 0UL;
426 0 : for( ulong field_idx=1UL; field_idx<=8UL; field_idx++ ) {
427 0 : err = read_until( is, 21UL, skip_spaces );
428 0 : if( FD_UNLIKELY( err!=0 ) ) goto failed;
429 :
430 0 : ulong val = read_ulong( is );
431 0 : val = fd_ulong_if( val!=ULONG_MAX, val, 0UL );
432 0 : if( field_idx>=6UL ) ticks += val;
433 0 : }
434 0 : if( FD_LIKELY( cpu_idx<FD_TILE_MAX ) ) {
435 0 : per_cpu[ cpu_idx ] = ticks;
436 0 : cpu_cnt = fd_ulong_max( cpu_cnt, cpu_idx+1UL );
437 0 : }
438 :
439 0 : err = read_until( is, 0UL, skip_line );
440 0 : if( FD_UNLIKELY( err!=0 ) ) {
441 0 : if( err==-1 ) break;
442 0 : goto failed;
443 0 : }
444 0 : }
445 0 : return cpu_cnt;
446 :
447 0 : failed:
448 0 : if( err!=0 ) {
449 0 : FD_LOG_WARNING(( "read failed (%i-%s)", err, fd_io_strerror( err ) ));
450 0 : }
451 0 : return 0UL;
452 0 : }
|