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_read( int fd,
167 : ulong * opt_device_per_cpu,
168 : ulong * opt_tlb_per_cpu,
169 0 : ulong * opt_loc_per_cpu ) {
170 0 : fd_io_buffered_istream_t is[1];
171 0 : char buf[ 4096 ];
172 0 : fd_io_buffered_istream_init( is, fd, buf, sizeof(buf) );
173 :
174 : /* Read first line */
175 :
176 0 : ushort col_cpu[ FD_TILE_MAX ];
177 0 : ulong col_cnt;
178 0 : ulong cpu_cnt;
179 0 : int err = read_cpu_map( is, &col_cnt, &cpu_cnt, col_cpu );
180 0 : if( FD_UNLIKELY( err!=0 ) ) goto failed;
181 0 : if( FD_UNLIKELY( !col_cnt || !cpu_cnt ) ) return 0UL;
182 :
183 0 : for( ulong cpu=0UL; cpu<cpu_cnt; cpu++ ) {
184 0 : if( FD_LIKELY( opt_device_per_cpu ) ) opt_device_per_cpu[ cpu ] = 0UL;
185 0 : if( FD_LIKELY( opt_tlb_per_cpu ) ) opt_tlb_per_cpu [ cpu ] = 0UL;
186 0 : if( FD_LIKELY( opt_loc_per_cpu ) ) opt_loc_per_cpu [ cpu ] = 0UL;
187 0 : }
188 :
189 0 : if( FD_UNLIKELY( !opt_device_per_cpu && !opt_tlb_per_cpu && !opt_loc_per_cpu ) ) return cpu_cnt;
190 :
191 : /* Read interrupt table
192 : Device interrupt counters look like this:
193 : " 123: 41 42 43 44"
194 : Special interrupts look like this:
195 : " NMI: 1 2 3 4" */
196 :
197 0 : for(;;) { /* each line */
198 :
199 : /* Read prefix */
200 0 : err = read_until( is, 64UL, skip_spaces );
201 0 : if( FD_UNLIKELY( err==-1 ) ) return cpu_cnt; /* end of input */
202 0 : if( FD_UNLIKELY( err!=0 ) ) goto failed;
203 0 : if( fd_io_buffered_istream_peek_sz( is )==0 ) return cpu_cnt;
204 0 : char const * prefix = fd_io_buffered_istream_peek ( is );
205 0 : ulong prefix_max = fd_io_buffered_istream_peek_sz( is );
206 0 : int row_kind = 0;
207 0 : if( FD_LIKELY( opt_device_per_cpu && isdigit( prefix[0] ) ) ) {
208 0 : row_kind = 1;
209 0 : } else if( FD_UNLIKELY( opt_tlb_per_cpu && prefix_max>=4UL && fd_memeq( prefix, "TLB:", 4UL ) ) ) {
210 0 : row_kind = 2;
211 0 : } else if( FD_UNLIKELY( opt_loc_per_cpu && prefix_max>=4UL && fd_memeq( prefix, "LOC:", 4UL ) ) ) {
212 0 : row_kind = 3;
213 0 : } else {
214 0 : goto skip_line;
215 0 : }
216 :
217 0 : err = read_until( is, 0UL, skip_token );
218 0 : if( FD_UNLIKELY( err!=0 ) ) goto failed;
219 :
220 : /* Read interrupt counters */
221 0 : for( ulong col_idx=0UL; col_idx<col_cnt; col_idx++ ) {
222 0 : err = read_until( is, 21UL, skip_spaces );
223 0 : if( FD_UNLIKELY( err!=0 ) ) goto failed;
224 :
225 0 : ulong irq_cnt = read_ulong( is );
226 0 : irq_cnt = fd_ulong_if( irq_cnt!=ULONG_MAX, irq_cnt, 0UL );
227 0 : ulong cpu_idx = col_cpu[ col_idx ];
228 0 : if( row_kind==1 ) opt_device_per_cpu[ cpu_idx ] += irq_cnt;
229 0 : else if( row_kind==2 ) opt_tlb_per_cpu [ cpu_idx ] = irq_cnt;
230 0 : else opt_loc_per_cpu [ cpu_idx ] = irq_cnt;
231 0 : }
232 :
233 0 : skip_line:
234 : /* Ignore rest of line */
235 0 : err = read_until( is, 0UL, skip_line );
236 0 : if( FD_UNLIKELY( err!=0 ) ) {
237 0 : if( err==-1 ) break;
238 0 : goto failed;
239 0 : }
240 :
241 0 : }
242 0 : return cpu_cnt;
243 :
244 0 : failed:
245 0 : if( err!=0 ) {
246 0 : FD_LOG_WARNING(( "read failed (%i-%s)", err, fd_io_strerror( err ) ));
247 0 : }
248 0 : return 0UL;
249 0 : }
250 :
251 : ulong
252 : fd_proc_softirqs_sum( int fd,
253 0 : ulong per_cpu[ FD_METRICS_ENUM_SOFTIRQ_CNT ][ FD_TILE_MAX ] ) {
254 0 : fd_io_buffered_istream_t is[1];
255 0 : char buf[ 4096 ];
256 0 : fd_io_buffered_istream_init( is, fd, buf, sizeof(buf) );
257 :
258 : /* Read first line */
259 :
260 0 : ushort col_cpu[ FD_TILE_MAX ];
261 0 : ulong col_cnt;
262 0 : ulong cpu_cnt;
263 0 : int err = read_cpu_map( is, &col_cnt, &cpu_cnt, col_cpu );
264 0 : if( FD_UNLIKELY( err!=0 ) ) goto failed;
265 0 : if( FD_UNLIKELY( !col_cnt || !cpu_cnt ) ) return 0UL;
266 0 : for( ulong i=0UL; i<FD_METRICS_ENUM_SOFTIRQ_CNT; i++ ) {
267 0 : for( ulong c=0UL; c<cpu_cnt; c++ ) per_cpu[ i ][ c ] = 0UL;
268 0 : }
269 :
270 : /* Read softirq table
271 : Looks like this:
272 : " NET_TX: 1 2 3 5" */
273 :
274 0 : for(;;) { /* each line */
275 :
276 : /* Read prefix */
277 0 : err = read_until( is, 64UL, skip_spaces );
278 0 : if( FD_UNLIKELY( err==-1 ) ) return cpu_cnt; /* end of input */
279 0 : if( FD_UNLIKELY( err!=0 ) ) goto failed;
280 0 : if( fd_io_buffered_istream_peek_sz( is )==0 ) return cpu_cnt;
281 :
282 : /* Match prefix */
283 0 : int kind = FD_METRICS_ENUM_SOFTIRQ_V_OTHER_IDX;
284 0 : char const * prefix = fd_io_buffered_istream_peek ( is );
285 0 : ulong prefix_max = fd_io_buffered_istream_peek_sz( is );
286 0 : if( prefix_max>=7 && fd_memeq( prefix, "NET_TX:", 7 ) ) {
287 0 : kind = FD_METRICS_ENUM_SOFTIRQ_V_NET_IDX;
288 0 : } else if( prefix_max>=7 && fd_memeq( prefix, "NET_RX:", 7 ) ) {
289 0 : kind = FD_METRICS_ENUM_SOFTIRQ_V_NET_IDX;
290 0 : } else if( prefix_max>=6 && fd_memeq( prefix, "BLOCK:", 6 ) ) {
291 0 : kind = FD_METRICS_ENUM_SOFTIRQ_V_DISK_IDX;
292 0 : }
293 0 : err = read_until( is, 0UL, skip_token );
294 0 : if( FD_UNLIKELY( err!=0 ) ) goto failed;
295 :
296 : /* Read interrupt counters */
297 0 : for( ulong col_idx=0UL; col_idx<col_cnt; col_idx++ ) {
298 0 : err = read_until( is, 21UL, skip_spaces );
299 0 : if( FD_UNLIKELY( err!=0 ) ) goto failed;
300 :
301 0 : ulong irq_cnt = read_ulong( is );
302 0 : irq_cnt = fd_ulong_if( irq_cnt!=ULONG_MAX, irq_cnt, 0UL );
303 0 : per_cpu[ kind ][ col_cpu[ col_idx ] ] += irq_cnt;
304 0 : }
305 :
306 : /* Ignore rest of line */
307 0 : err = read_until( is, 0UL, skip_line );
308 0 : if( FD_UNLIKELY( err!=0 ) ) {
309 0 : if( err==-1 ) break;
310 0 : goto failed;
311 0 : }
312 :
313 0 : }
314 0 : return cpu_cnt;
315 :
316 0 : failed:
317 0 : if( err!=0 ) {
318 0 : FD_LOG_WARNING(( "read failed (%i-%s)", err, fd_io_strerror( err ) ));
319 0 : }
320 0 : return 0UL;
321 0 : }
322 :
323 : ulong
324 : fd_proc_stat_irq_ticks( int fd,
325 0 : ulong per_cpu[ FD_TILE_MAX ] ) {
326 0 : fd_io_buffered_istream_t is[1];
327 0 : char buf[ 4096 ];
328 0 : fd_io_buffered_istream_init( is, fd, buf, sizeof(buf) );
329 :
330 0 : for( ulong cpu=0UL; cpu<FD_TILE_MAX; cpu++ ) {
331 0 : per_cpu[ cpu ] = 0UL;
332 0 : }
333 :
334 : /* Per-CPU rows look like this ("cpu" summary row has no digit):
335 : "cpu7 134688 9390 52162 76129767 4217 3283 2069 0 0 0"
336 : Fields after the cpuN token: user nice system idle iowait irq
337 : softirq steal guest guest_nice. */
338 :
339 0 : ulong cpu_cnt = 0UL;
340 0 : int err;
341 0 : for(;;) {
342 0 : err = read_until( is, 64UL, skip_spaces );
343 0 : if( FD_UNLIKELY( err==-1 ) ) return cpu_cnt; /* end of input */
344 0 : if( FD_UNLIKELY( err!=0 ) ) goto failed;
345 0 : if( fd_io_buffered_istream_peek_sz( is )==0 ) return cpu_cnt;
346 :
347 0 : char const * prefix = fd_io_buffered_istream_peek ( is );
348 0 : ulong prefix_max = fd_io_buffered_istream_peek_sz( is );
349 :
350 : /* Rows are ordered: "cpu", "cpu0".."cpuN", then non-cpu rows. Stop
351 : at the first non-cpu row. */
352 0 : if( FD_UNLIKELY( prefix_max<4UL || !fd_memeq( prefix, "cpu", 3UL ) ) ) return cpu_cnt;
353 0 : if( FD_UNLIKELY( !isdigit( prefix[ 3 ] ) ) ) { /* "cpu" summary row */
354 0 : err = read_until( is, 0UL, skip_line );
355 0 : if( FD_UNLIKELY( err!=0 ) ) { if( err==-1 ) break; goto failed; }
356 0 : continue;
357 0 : }
358 :
359 0 : fd_io_buffered_istream_skip( is, 3UL );
360 0 : ulong cpu_idx = read_ulong( is );
361 0 : if( FD_UNLIKELY( cpu_idx==ULONG_MAX ) ) {
362 0 : FD_LOG_WARNING(( "failed to parse cpu index in /proc/stat" ));
363 0 : return 0UL;
364 0 : }
365 :
366 : /* Sum fields 6 (irq), 7 (softirq) and 8 (steal), 1-indexed after
367 : the cpuN token. */
368 0 : ulong ticks = 0UL;
369 0 : for( ulong field_idx=1UL; field_idx<=8UL; field_idx++ ) {
370 0 : err = read_until( is, 21UL, skip_spaces );
371 0 : if( FD_UNLIKELY( err!=0 ) ) goto failed;
372 :
373 0 : ulong val = read_ulong( is );
374 0 : val = fd_ulong_if( val!=ULONG_MAX, val, 0UL );
375 0 : if( field_idx>=6UL ) ticks += val;
376 0 : }
377 0 : if( FD_LIKELY( cpu_idx<FD_TILE_MAX ) ) {
378 0 : per_cpu[ cpu_idx ] = ticks;
379 0 : cpu_cnt = fd_ulong_max( cpu_cnt, cpu_idx+1UL );
380 0 : }
381 :
382 0 : err = read_until( is, 0UL, skip_line );
383 0 : if( FD_UNLIKELY( err!=0 ) ) {
384 0 : if( err==-1 ) break;
385 0 : goto failed;
386 0 : }
387 0 : }
388 0 : return cpu_cnt;
389 :
390 0 : failed:
391 0 : if( err!=0 ) {
392 0 : FD_LOG_WARNING(( "read failed (%i-%s)", err, fd_io_strerror( err ) ));
393 0 : }
394 0 : return 0UL;
395 0 : }
|