Line data Source code
1 : #define _GNU_SOURCE
2 :
3 : #include "fd_diag_tile.h"
4 :
5 : #include "../fd_clock_tile.h"
6 :
7 : #include "../bundle/fd_bundle_tile.h"
8 : #include "../metrics/fd_metrics.h"
9 : #include "../stem/fd_stem.h"
10 : #include "../topo/fd_topo.h"
11 : #include "../topo/fd_cpu_topo.h"
12 : #include "../../util/tile/fd_tile_private.h"
13 : #include "../../util/io/fd_io.h"
14 :
15 : #include <dirent.h>
16 : #include <fcntl.h>
17 : #include <errno.h>
18 : #include <stdlib.h>
19 : #include <sys/types.h> /* SEEK_SET */
20 : #include <sys/stat.h>
21 : #include <sys/syscall.h>
22 : #include <sys/vfs.h>
23 : #include <linux/futex.h>
24 : #include <time.h>
25 : #include <unistd.h>
26 :
27 : #include "fd_proc_interrupts.h"
28 : #include "generated/fd_diag_tile_seccomp.h"
29 :
30 : /* Raw state values exported by the metrics (states= enums) */
31 : FD_STATIC_ASSERT( FD_DIAG_BUNDLE_STATUS_DISCONNECTED==FD_METRICS_ENUM_DIAG_BUNDLE_STATE_V_DISCONNECTED_IDX, metrics_state );
32 : FD_STATIC_ASSERT( FD_DIAG_BUNDLE_STATUS_CONNECTING==FD_METRICS_ENUM_DIAG_BUNDLE_STATE_V_CONNECTING_IDX, metrics_state );
33 : FD_STATIC_ASSERT( FD_DIAG_BUNDLE_STATUS_CONNECTED==FD_METRICS_ENUM_DIAG_BUNDLE_STATE_V_CONNECTED_IDX, metrics_state );
34 : FD_STATIC_ASSERT( FD_DIAG_BUNDLE_STATUS_SLEEPING==FD_METRICS_ENUM_DIAG_BUNDLE_STATE_V_SLEEPING_IDX, metrics_state );
35 : FD_STATIC_ASSERT( FD_DIAG_BUNDLE_STATUS_DISABLED==FD_METRICS_ENUM_DIAG_BUNDLE_STATE_V_DISABLED_IDX, metrics_state );
36 :
37 : /* Each procfs source is read at the rate its consumers need: the CPU
38 : regime split every report (100 ms), /proc/<tid>/sched (only feeds
39 : cumulative counters, ~10 us a read) once a second, /proc/interrupts
40 : (~5 ms on 96 CPUs) every five. */
41 :
42 0 : #define REPORT_INTERVAL_MILLIS (100L)
43 0 : #define SCHED_REPORT_INTERVAL_NANOS (1000000000L)
44 0 : #define IRQ_REPORT_INTERVAL_NANOS (5000000000L)
45 0 : #define SYSTEM_REPORT_INTERVAL_NANOS (30000000000L)
46 :
47 : /* Hardware sensors, read with the IRQ report */
48 : #define DIAG_SENSOR_MAX (128UL)
49 0 : #define DIAG_RAPL_MAX (8UL)
50 0 : #define DIAG_EDAC_MC_MAX (64UL) /* memory controllers */
51 :
52 0 : #define DIAG_SENSOR_CPU_PACKAGE (0U)
53 0 : #define DIAG_SENSOR_NIC (1U)
54 0 : #define DIAG_SENSOR_NVME (2U)
55 0 : #define DIAG_SENSOR_POWER (3U)
56 0 : #define DIAG_SENSOR_ECC_CE (4U)
57 0 : #define DIAG_SENSOR_ECC_UE (5U)
58 : #define DIAG_SENSOR_KIND_CNT (6U)
59 :
60 0 : #define DIAG_WKSP_TILE_IDX_SHARED (ULONG_MAX)
61 :
62 :
63 : struct fd_diag_tile {
64 : fd_clock_tile_t clock[1];
65 : long next_report_nanos;
66 : long next_sched_report_nanos;
67 : long next_irq_report_nanos;
68 :
69 : ulong tile_cnt;
70 : int is_voting;
71 :
72 : struct {
73 : ulong bundle_tile_idx[ FD_TILE_MAX ];
74 : ulong bundle_cnt;
75 : ulong shred_tile_idx[ FD_TILE_MAX ];
76 : ulong shred_cnt;
77 : ulong votor_idx;
78 : ulong replay_idx;
79 : } tiles;
80 :
81 : ulong starttime_nanos[ FD_TILE_MAX ];
82 : long first_seen_died[ FD_TILE_MAX ];
83 :
84 : int stat_fds[ FD_TILE_MAX ];
85 : int sched_fds[ FD_TILE_MAX ];
86 : int schedstat_fds[ FD_TILE_MAX ];
87 :
88 : ulong irq_cnt[ FD_METRICS_ENUM_SOFTIRQ_TYPE_CNT ][ FD_TILE_MAX ];
89 : fd_cpuset_t cpu_live_tile[ fd_cpuset_word_cnt ]; /* CPUs hosting a live pinned tile */
90 : ulong on_tile_softirq[ FD_METRICS_ENUM_SOFTIRQ_TYPE_CNT ]; /* accumulated while the CPU hosts a live tile */
91 : ulong on_tile_device_irq;
92 : int proc_interrupts_fd;
93 : int proc_softirqs_fd;
94 : int proc_stat_fd;
95 : int proc_meminfo_fd;
96 : int proc_loadavg_fd;
97 : int proc_vmstat_fd;
98 : int psi_fd[ 3 ]; /* /proc/pressure/{cpu,memory,io}, -1 if absent */
99 : int disk_fd; /* /sys/block/<dev>/stat of the accounts disk, -1 if none */
100 : ulong disk_ms[ 3 ]; /* write, busy and queue ms, carried across the 32-bit wrap */
101 : ulong disk_ms_base[ 3 ]; /* the same at diag start */
102 : ulong host_cpu_baseline[ FD_METRICS_ENUM_SYSTEM_CPU_STATE_CNT ]; /* /proc/stat ticks at diag start */
103 : ulong host_cpu_ticks [ FD_METRICS_ENUM_SYSTEM_CPU_STATE_CNT ]; /* highest ticks since then published */
104 : ulong device_irq_boot [ FD_TILE_MAX ]; /* /proc/interrupts at diag start */
105 : ulong device_irq_baseline[ FD_TILE_MAX ]; /* at the last sweep */
106 : ulong tlb_baseline[ FD_TILE_MAX ];
107 : ulong loc_baseline[ FD_TILE_MAX ];
108 : ulong irq_ticks_baseline[ FD_TILE_MAX ];
109 : ulong softirq_boot [ FD_METRICS_ENUM_SOFTIRQ_TYPE_CNT ][ FD_TILE_MAX ];
110 : ulong softirq_baseline[ FD_METRICS_ENUM_SOFTIRQ_TYPE_CNT ][ FD_TILE_MAX ];
111 :
112 : ulong volatile * metrics [ FD_TILE_MAX ];
113 : ushort cpu_to_tile[ FD_TILE_MAX ]; /* the one live tile pinned to the CPU, USHORT_MAX if none or shared */
114 : ushort tile_cpu [ FD_TILE_MAX ]; /* pinned CPU of each tile, USHORT_MAX if floating */
115 : fd_cpuset_t irq_rebase [ fd_cpuset_word_cnt ]; /* owner changed, irq_metrics takes new baselines */
116 : fd_cpuset_t tick_rebase[ fd_cpuset_word_cnt ]; /* owner changed, interrupt_metrics takes new baselines */
117 : ulong owner_irq_baseline [ FD_TILE_MAX ]; /* per-CPU tile baselines, start at the boot baselines */
118 : ulong owner_tlb_baseline [ FD_TILE_MAX ];
119 : ulong owner_loc_baseline [ FD_TILE_MAX ];
120 : ulong owner_tick_baseline[ FD_TILE_MAX ];
121 :
122 : struct {
123 : ulong cnt;
124 : int fd [ DIAG_SENSOR_MAX ]; /* hwmon inputs and EDAC counts */
125 : uint kind[ DIAG_SENSOR_MAX ]; /* DIAG_SENSOR_* */
126 :
127 : ulong rapl_cnt;
128 : int rapl_fd [ DIAG_RAPL_MAX ]; /* powercap package energy_uj */
129 : ulong rapl_range[ DIAG_RAPL_MAX ]; /* energy_uj wraps at this */
130 : ulong rapl_prev [ DIAG_RAPL_MAX ];
131 : long rapl_prev_nanos; /* 0 until a baseline is taken */
132 : } sensor;
133 :
134 : int gui_enabled;
135 : long next_system_report_nanos;
136 : ushort numa_entry_idx[ FD_DIAG_SYSTEM_TILE_MAX ][ FD_DIAG_SYSTEM_NUMA_MAX ];
137 : struct {
138 : ulong cnt;
139 : struct {
140 : char name[ FD_SHMEM_NAME_MAX ];
141 : ulong tile_idx;
142 : ulong numa_idx;
143 : ulong bytes;
144 : ushort numa_slot;
145 : } wksp[ FD_TOPO_MAX_WKSPS ];
146 : ushort stack_numa_slot[ FD_DIAG_SYSTEM_TILE_MAX ];
147 : } memory;
148 : struct {
149 : ulong cnt;
150 : struct {
151 : ushort idx;
152 : int meminfo_fd;
153 : } node[ FD_DIAG_SYSTEM_NUMA_MAX ];
154 : } numa;
155 :
156 : struct {
157 : void * mem;
158 : ulong idx;
159 : ulong chunk0;
160 : ulong wmark;
161 : ulong chunk;
162 : } system_out;
163 : fd_diag_system_resources_t system_resources;
164 :
165 : struct {
166 : int fd; /* O_PATH reference to an anonymous regular file */
167 : ulong mnt_id; /* mount ID from /proc/self/fdinfo */
168 : char path[ PATH_MAX ];
169 : } mounts[ FD_DIAG_SYSTEM_FILE_MAX ];
170 : ulong mount_cnt;
171 :
172 : struct {
173 : char path[ PATH_MAX ];
174 : uint category;
175 : uint mount_idx;
176 : int data_fd;
177 : ulong volatile * metric;
178 : } files[ FD_DIAG_SYSTEM_FILE_MAX ];
179 : ulong file_cnt;
180 :
181 : struct {
182 : ulong prev_reset_slot;
183 : long reset_slot_changed_ns;
184 : ulong prev_turbine_slot;
185 : long turbine_slot_changed_ns;
186 :
187 : ulong snapshot_turbine_bytes;
188 : ulong snapshot_repair_bytes;
189 : long byte_snapshot_ns;
190 : int repair_outpacing;
191 : } check_engine;
192 : };
193 :
194 : typedef struct fd_diag_tile fd_diag_tile_t;
195 :
196 : FD_FN_CONST static inline ulong
197 0 : scratch_align( void ) {
198 0 : return alignof(fd_diag_tile_t);
199 0 : }
200 :
201 : FD_FN_PURE static inline ulong
202 0 : scratch_footprint( fd_topo_tile_t const * tile ) {
203 0 : (void)tile;
204 0 : return sizeof(fd_diag_tile_t);
205 0 : }
206 :
207 : static void
208 0 : during_housekeeping( fd_diag_tile_t * ctx ) {
209 0 : if( FD_UNLIKELY( fd_clock_tile_recal_due( ctx->clock ) ) ) fd_clock_tile_recal( ctx->clock );
210 0 : }
211 :
212 : static inline long
213 0 : next_deadline( fd_diag_tile_t * ctx ) {
214 0 : return fd_clock_tile_wallclock_to_tickcount( ctx->clock, ctx->next_report_nanos );
215 0 : }
216 :
217 : static int
218 : read_stat_file( int fd,
219 : ulong ns_per_tick,
220 0 : volatile ulong * metrics ) {
221 0 : if( FD_UNLIKELY( -1==lseek( fd, 0, SEEK_SET ) ) ) FD_LOG_ERR(( "lseek failed (%i-%s)", errno, strerror( errno ) ));
222 :
223 0 : char contents[ 4096 ] = {0};
224 0 : ulong contents_len = 0UL;
225 :
226 0 : while( 1 ) {
227 0 : if( FD_UNLIKELY( contents_len>=sizeof( contents ) ) ) FD_LOG_ERR(( "stat contents overflow" ));
228 0 : long n = read( fd, contents + contents_len, sizeof( contents ) - contents_len );
229 0 : if( FD_UNLIKELY( -1==n ) ) {
230 0 : if( FD_UNLIKELY( errno==ESRCH ) ) return 1;
231 0 : FD_LOG_ERR(( "read failed (%i-%s)", errno, strerror( errno ) ));
232 0 : }
233 0 : if( FD_LIKELY( 0==n ) ) break;
234 0 : contents_len += (ulong)n;
235 0 : }
236 :
237 : /* Parse stat file: fields are space-separated.
238 : Field 10 (1-indexed) = minflt, field 12 = majflt,
239 : field 14 = utime, field 15 = stime (all in clock ticks). */
240 0 : char * saveptr;
241 0 : char * token = strtok_r( contents, " ", &saveptr );
242 0 : ulong field_idx = 0UL;
243 :
244 0 : while( token ) {
245 0 : if( FD_UNLIKELY( 9UL==field_idx ) ) {
246 0 : char * endptr;
247 0 : ulong minflt = strtoul( token, &endptr, 10 );
248 0 : if( FD_UNLIKELY( *endptr!='\0' || minflt==ULONG_MAX ) ) FD_LOG_ERR(( "strtoul failed for minflt" ));
249 0 : metrics[ FD_METRICS_COUNTER_TILE_PAGE_FAULT_MINOR_OFF ] = minflt;
250 0 : } else if( FD_UNLIKELY( 11UL==field_idx ) ) {
251 0 : char * endptr;
252 0 : ulong majflt = strtoul( token, &endptr, 10 );
253 0 : if( FD_UNLIKELY( *endptr!='\0' || majflt==ULONG_MAX ) ) FD_LOG_ERR(( "strtoul failed for majflt" ));
254 0 : metrics[ FD_METRICS_COUNTER_TILE_PAGE_FAULT_MAJOR_OFF ] = majflt;
255 0 : } else if( FD_UNLIKELY( 13UL==field_idx ) ) {
256 0 : char * endptr;
257 0 : ulong utime_ticks = strtoul( token, &endptr, 10 );
258 0 : if( FD_UNLIKELY( *endptr!='\0' || utime_ticks==ULONG_MAX ) ) FD_LOG_ERR(( "strtoul failed for utime" ));
259 0 : metrics[ FD_METRICS_COUNTER_TILE_THREAD_DURATION_NANOS_USER_OFF ] = utime_ticks*ns_per_tick;
260 0 : } else if( FD_UNLIKELY( 14UL==field_idx ) ) {
261 0 : char * endptr;
262 0 : ulong stime_ticks = strtoul( token, &endptr, 10 );
263 0 : if( FD_UNLIKELY( *endptr!='\0' || stime_ticks==ULONG_MAX ) ) FD_LOG_ERR(( "strtoul failed for stime" ));
264 0 : metrics[ FD_METRICS_COUNTER_TILE_THREAD_DURATION_NANOS_SYSTEM_OFF ] = stime_ticks*ns_per_tick;
265 0 : } else if( FD_UNLIKELY( 38UL==field_idx ) ) {
266 0 : char * endptr;
267 0 : ulong last_cpu = strtoul( token, &endptr, 10 );
268 0 : if( FD_UNLIKELY( *endptr!='\0' || last_cpu==ULONG_MAX ) ) FD_LOG_ERR(( "strtoul failed for processor" ));
269 0 : metrics[ FD_METRICS_GAUGE_TILE_LAST_CPU_INDEX_OFF ] = last_cpu;
270 0 : break; /* No need to parse stat further */
271 0 : }
272 0 : token = strtok_r( NULL, " ", &saveptr );
273 0 : field_idx++;
274 0 : }
275 :
276 0 : if( FD_UNLIKELY( field_idx!=38UL ) ) FD_LOG_ERR(( "failed to parse /proc/<pid>/task/<tid>/stat" ));
277 :
278 0 : return 0;
279 0 : }
280 :
281 : /* read_schedstat_file reads /proc/<pid>/task/<tid>/schedstat: run_delay
282 : is the counter /proc/<tid>/sched prints as wait_sum, in nanoseconds. */
283 :
284 : static int
285 : read_schedstat_file( int fd,
286 0 : volatile ulong * metrics ) {
287 0 : if( FD_UNLIKELY( -1==lseek( fd, 0, SEEK_SET ) ) ) FD_LOG_ERR(( "lseek failed (%i-%s)", errno, strerror( errno ) ));
288 :
289 0 : char contents[ 128 ] = {0};
290 0 : ulong contents_len = 0UL;
291 :
292 0 : while( 1 ) {
293 0 : if( FD_UNLIKELY( contents_len>=sizeof( contents ) ) ) FD_LOG_ERR(( "schedstat contents overflow" ));
294 0 : long n = read( fd, contents + contents_len, sizeof( contents ) - contents_len );
295 0 : if( FD_UNLIKELY( -1==n ) ) {
296 0 : if( FD_UNLIKELY( errno==EINTR ) ) continue;
297 0 : if( FD_UNLIKELY( errno==ESRCH ) ) return 1;
298 0 : FD_LOG_ERR(( "read failed (%i-%s)", errno, strerror( errno ) ));
299 0 : }
300 0 : if( FD_LIKELY( 0==n ) ) break;
301 0 : contents_len += (ulong)n;
302 0 : }
303 :
304 0 : char * endptr;
305 0 : ulong run_time = strtoul( contents, &endptr, 10 );
306 0 : if( FD_UNLIKELY( ' '!=*endptr || run_time==ULONG_MAX ) ) FD_LOG_ERR(( "failed to parse schedstat sum_exec_runtime" ));
307 0 : ulong run_delay = strtoul( endptr+1, &endptr, 10 );
308 0 : if( FD_UNLIKELY( ' '!=*endptr || run_delay==ULONG_MAX ) ) FD_LOG_ERR(( "failed to parse schedstat run_delay" ));
309 0 : metrics[ FD_METRICS_COUNTER_TILE_THREAD_DURATION_NANOS_WAIT_OFF ] = run_delay;
310 0 : return 0;
311 0 : }
312 :
313 : static int
314 : read_sched_file( int fd,
315 0 : volatile ulong * metrics ) {
316 0 : if( FD_UNLIKELY( -1==lseek( fd, 0, SEEK_SET ) ) ) FD_LOG_ERR(( "lseek failed (%i-%s)", errno, strerror( errno ) ));
317 :
318 0 : char contents[ 8192 ] = {0};
319 0 : ulong contents_len = 0UL;
320 :
321 0 : while( 1 ) {
322 0 : if( FD_UNLIKELY( contents_len>=sizeof( contents ) ) ) FD_LOG_ERR(( "sched contents overflow" ));
323 0 : long n = read( fd, contents + contents_len, sizeof( contents ) - contents_len );
324 0 : if( FD_UNLIKELY( -1==n ) ) {
325 0 : if( FD_UNLIKELY( errno==ESRCH ) ) return 1;
326 0 : FD_LOG_ERR(( "read failed (%i-%s)", errno, strerror( errno ) ));
327 0 : }
328 0 : if( FD_LIKELY( 0==n ) ) break;
329 0 : contents_len += (ulong)n;
330 0 : }
331 :
332 0 : int found_voluntary = 0;
333 0 : int found_involuntary = 0;
334 :
335 0 : char * line = contents;
336 0 : while( 1 ) {
337 0 : char * next_line = strchr( line, '\n' );
338 0 : if( FD_UNLIKELY( NULL==next_line ) ) break;
339 0 : *next_line = '\0';
340 :
341 0 : if( FD_UNLIKELY( !strncmp( line, "nr_voluntary_switches", 21UL ) ) ) {
342 0 : char * colon = strchr( line, ':' );
343 0 : if( FD_LIKELY( colon ) ) {
344 0 : char * value = colon + 1;
345 0 : while( ' '==*value || '\t'==*value ) value++;
346 0 : char * endptr;
347 0 : ulong voluntary_switches = strtoul( value, &endptr, 10 );
348 0 : if( FD_UNLIKELY( '\0'!=*endptr ) ) FD_LOG_ERR(( "unexpected char after nr_voluntary_switches" ));
349 0 : if( FD_UNLIKELY( voluntary_switches==ULONG_MAX ) ) FD_LOG_ERR(( "strtoul overflow for nr_voluntary_switches" ));
350 0 : metrics[ FD_METRICS_COUNTER_TILE_CONTEXT_SWITCH_VOLUNTARY_OFF ] = voluntary_switches;
351 0 : found_voluntary = 1;
352 0 : }
353 0 : } else if( FD_UNLIKELY( !strncmp( line, "nr_involuntary_switches", 23UL ) ) ) {
354 0 : char * colon = strchr( line, ':' );
355 0 : if( FD_LIKELY( colon ) ) {
356 0 : char * value = colon + 1;
357 0 : while( ' '==*value || '\t'==*value ) value++;
358 0 : char * endptr;
359 0 : ulong involuntary_switches = strtoul( value, &endptr, 10 );
360 0 : if( FD_UNLIKELY( '\0'!=*endptr ) ) FD_LOG_ERR(( "unexpected char after nr_involuntary_switches" ));
361 0 : if( FD_UNLIKELY( involuntary_switches==ULONG_MAX ) ) FD_LOG_ERR(( "strtoul overflow for nr_involuntary_switches" ));
362 0 : metrics[ FD_METRICS_COUNTER_TILE_CONTEXT_SWITCH_INVOLUNTARY_OFF ] = involuntary_switches;
363 0 : found_involuntary = 1;
364 0 : }
365 0 : }
366 :
367 0 : line = next_line + 1;
368 0 : }
369 :
370 0 : if( FD_UNLIKELY( !found_voluntary ) ) FD_LOG_ERR(( "nr_voluntary_switches not found in sched file" ));
371 0 : if( FD_UNLIKELY( !found_involuntary ) ) FD_LOG_ERR(( "nr_involuntary_switches not found in sched file" ));
372 :
373 0 : return 0;
374 0 : }
375 :
376 : static ulong
377 0 : check_builder_status( fd_diag_tile_t * ctx ) {
378 0 : (void)ctx;
379 0 : return FD_DIAG_BUILDER_STATUS_DISABLED;
380 0 : }
381 :
382 : static void
383 0 : check_engine_metric( fd_diag_tile_t * ctx, long now ) {
384 0 : static ulong const replay_distance_threshold = 12UL;
385 0 : static long const replay_stall_threshold_ns = 12L*1000L*1000L*1000L;
386 0 : static long const turbine_stall_threshold_ns = 12L*1000L*1000L*1000L;
387 0 : static long const turbine_byte_cmp_window_ns = 12L*1000L*1000L*1000L;
388 :
389 0 : ulong bundle_cnt = ctx->tiles.bundle_cnt;
390 0 : ulong bundle_status = FD_DIAG_BUNDLE_STATUS_DISABLED;
391 0 : if( FD_LIKELY( bundle_cnt ) ) {
392 : /* Find the best state across all bundle tiles.
393 : Priority: connected > sleeping > connecting > disconnected */
394 0 : int any_connected = 0;
395 0 : int any_sleeping = 0;
396 0 : int any_connecting = 0;
397 0 : for( ulong i=0UL; i<bundle_cnt; i++ ) {
398 0 : volatile ulong * m = ctx->metrics[ ctx->tiles.bundle_tile_idx[ i ] ];
399 0 : ulong state = m[ FD_METRICS_GAUGE_BUNDLE_CONN_STATE_OFF ];
400 0 : if( FD_LIKELY( state==FD_BUNDLE_STATE_CONNECTED ) ) any_connected = 1;
401 0 : else if( state==FD_BUNDLE_STATE_SLEEPING ) any_sleeping = 1;
402 0 : else if( state==FD_BUNDLE_STATE_CONNECTING ) any_connecting = 1;
403 0 : }
404 0 : if( any_connected ) bundle_status = FD_DIAG_BUNDLE_STATUS_CONNECTED;
405 0 : else if( any_sleeping ) bundle_status = FD_DIAG_BUNDLE_STATUS_SLEEPING;
406 0 : else if( any_connecting ) bundle_status = FD_DIAG_BUNDLE_STATUS_CONNECTING;
407 0 : else bundle_status = FD_DIAG_BUNDLE_STATUS_DISCONNECTED;
408 0 : }
409 :
410 0 : ulong votor_idx = ctx->tiles.votor_idx;
411 0 : ulong vote_status = FD_DIAG_VOTE_STATUS_DISABLED;
412 :
413 0 : if( FD_UNLIKELY( ctx->is_voting && votor_idx!=ULONG_MAX ) ) {
414 0 : ulong replay_idx_ag = ctx->tiles.replay_idx;
415 0 : if( FD_UNLIKELY( ctx->metrics[ votor_idx ][ FD_METRICS_GAUGE_TILE_STATE_OFF ]!=1UL || replay_idx_ag==ULONG_MAX ) ) {
416 0 : vote_status = FD_DIAG_VOTE_STATUS_NOT_STARTED;
417 0 : } else if( FD_UNLIKELY( ctx->metrics[ votor_idx ][ FD_METRICS_GAUGE_VOTOR_RANK_OFF ]==ULONG_MAX ) ) {
418 : /* Not in the rank map, so
419 : we cannot be delinquent: report non-voting. */
420 0 : vote_status = FD_DIAG_VOTE_STATUS_DISABLED;
421 0 : } else {
422 0 : volatile ulong * m = ctx->metrics[ replay_idx_ag ];
423 0 : ulong vote_slot = m[ FD_METRICS_GAUGE_REPLAY_LAST_REWARDED_VOTE_SLOT_OFF ];
424 0 : ulong replay_slot = m[ FD_METRICS_GAUGE_REPLAY_RESET_SLOT_OFF ];
425 0 : if( FD_UNLIKELY( !vote_slot || !replay_slot ) ) {
426 0 : vote_status = FD_DIAG_VOTE_STATUS_NOT_STARTED;
427 0 : } else {
428 0 : int current = fd_int_if( replay_slot>=128UL, vote_slot+128UL>replay_slot, vote_slot>0UL );
429 0 : vote_status = fd_ulong_if( current, FD_DIAG_VOTE_STATUS_VOTING, FD_DIAG_VOTE_STATUS_DELINQUENT );
430 0 : }
431 0 : }
432 0 : }
433 :
434 : /* Votes land but earn nothing: the stake is not admitted. */
435 0 : if( FD_UNLIKELY( vote_status==FD_DIAG_VOTE_STATUS_VOTING &&
436 0 : ctx->tiles.replay_idx!=ULONG_MAX &&
437 0 : ctx->metrics[ ctx->tiles.replay_idx ][ FD_METRICS_GAUGE_REPLAY_IS_VOTE_ACCOUNT_INADMISSIBLE_OFF ] ) ) {
438 0 : vote_status = FD_DIAG_VOTE_STATUS_INADMISSIBLE;
439 0 : }
440 :
441 0 : ulong replay_idx = ctx->tiles.replay_idx;
442 0 : int replay_running = replay_idx!=ULONG_MAX && ctx->metrics[ replay_idx ][ FD_METRICS_GAUGE_TILE_STATE_OFF ]==1UL;
443 0 : ulong replay_status = FD_DIAG_REPLAY_STATUS_DISABLED;
444 0 : if( FD_LIKELY( replay_idx!=ULONG_MAX ) ) {
445 0 : if( FD_UNLIKELY( !replay_running ) ) {
446 0 : replay_status = FD_DIAG_REPLAY_STATUS_NOT_STARTED;
447 0 : } else {
448 0 : volatile ulong * m = ctx->metrics[ replay_idx ];
449 0 : ulong turbine_slot = m[ FD_METRICS_GAUGE_REPLAY_LAST_REASSEMBLY_DEQUEUED_SLOT_OFF ];
450 0 : ulong reset_slot = m[ FD_METRICS_GAUGE_REPLAY_RESET_SLOT_OFF ];
451 0 : if( FD_UNLIKELY( reset_slot!=ctx->check_engine.prev_reset_slot ) ) {
452 0 : ctx->check_engine.prev_reset_slot = reset_slot;
453 0 : ctx->check_engine.reset_slot_changed_ns = now;
454 0 : }
455 0 : if( FD_UNLIKELY( (turbine_slot==0UL) || (reset_slot==0UL) ) ) {
456 0 : replay_status = FD_DIAG_REPLAY_STATUS_NOT_STARTED;
457 0 : } else if( FD_UNLIKELY( ((turbine_slot>reset_slot) && (turbine_slot-reset_slot>replay_distance_threshold)) ||
458 0 : (now-ctx->check_engine.reset_slot_changed_ns>replay_stall_threshold_ns) ) ) {
459 0 : replay_status = FD_DIAG_REPLAY_STATUS_BEHIND;
460 0 : } else {
461 0 : replay_status = FD_DIAG_REPLAY_STATUS_RUNNING;
462 0 : }
463 0 : }
464 0 : }
465 :
466 0 : ulong shred_cnt = ctx->tiles.shred_cnt;
467 0 : ulong turbine_status = FD_DIAG_TURBINE_STATUS_DISABLED;
468 0 : if( FD_LIKELY( replay_idx!=ULONG_MAX && shred_cnt>0UL ) ) {
469 0 : if( FD_UNLIKELY( !replay_running ) ) {
470 0 : turbine_status = FD_DIAG_TURBINE_STATUS_NOT_STARTED;
471 0 : } else {
472 0 : int all_shred_running = 1;
473 0 : ulong cur_turbine_bytes = 0UL, cur_repair_bytes = 0UL;
474 0 : for( ulong i=0UL; i<shred_cnt; i++ ) {
475 0 : volatile ulong * sm = ctx->metrics[ ctx->tiles.shred_tile_idx[ i ] ];
476 0 : cur_turbine_bytes += sm[ FD_METRICS_COUNTER_SHRED_TURBINE_RX_BYTES_OFF ];
477 0 : cur_repair_bytes += sm[ FD_METRICS_COUNTER_SHRED_REPAIR_RX_BYTES_OFF ];
478 0 : if( FD_UNLIKELY( sm[ FD_METRICS_GAUGE_TILE_STATE_OFF ]!=1UL ) ) {
479 0 : all_shred_running = 0;
480 0 : break;
481 0 : }
482 0 : }
483 0 : if( FD_UNLIKELY( !all_shred_running ) ) {
484 0 : turbine_status = FD_DIAG_TURBINE_STATUS_NOT_STARTED;
485 0 : } else {
486 0 : ulong turbine_slot = ctx->metrics[ replay_idx ][ FD_METRICS_GAUGE_REPLAY_LAST_REASSEMBLY_DEQUEUED_SLOT_OFF ];
487 0 : if( FD_UNLIKELY( turbine_slot!=ctx->check_engine.prev_turbine_slot ) ) {
488 0 : ctx->check_engine.prev_turbine_slot = turbine_slot;
489 0 : ctx->check_engine.turbine_slot_changed_ns = now;
490 0 : }
491 0 : if( FD_UNLIKELY( now-ctx->check_engine.byte_snapshot_ns>=turbine_byte_cmp_window_ns ) ) {
492 0 : ctx->check_engine.repair_outpacing = (cur_repair_bytes-ctx->check_engine.snapshot_repair_bytes)>(cur_turbine_bytes-ctx->check_engine.snapshot_turbine_bytes);
493 0 : ctx->check_engine.snapshot_turbine_bytes = cur_turbine_bytes;
494 0 : ctx->check_engine.snapshot_repair_bytes = cur_repair_bytes;
495 0 : ctx->check_engine.byte_snapshot_ns = now;
496 0 : }
497 :
498 0 : if( FD_UNLIKELY( turbine_slot==0UL ) ) {
499 0 : turbine_status = FD_DIAG_TURBINE_STATUS_NOT_STARTED;
500 0 : } else if( FD_UNLIKELY( now-ctx->check_engine.turbine_slot_changed_ns>turbine_stall_threshold_ns ) ) {
501 0 : turbine_status = FD_DIAG_TURBINE_STATUS_STALLED;
502 0 : } else if( FD_UNLIKELY( ctx->check_engine.repair_outpacing ) ) {
503 0 : turbine_status = FD_DIAG_TURBINE_STATUS_REPAIR_OUTPACING;
504 0 : } else {
505 0 : turbine_status = FD_DIAG_TURBINE_STATUS_RUNNING;
506 0 : }
507 0 : }
508 0 : }
509 0 : }
510 :
511 0 : FD_MGAUGE_SET( DIAG, BUNDLE_STATE, bundle_status );
512 0 : FD_MGAUGE_SET( DIAG, VOTE_STATE, vote_status );
513 0 : FD_MGAUGE_SET( DIAG, REPLAY_STATE, replay_status );
514 0 : FD_MGAUGE_SET( DIAG, TURBINE_STATE, turbine_status );
515 0 : FD_MGAUGE_SET( DIAG, BUILDER_STATE, check_builder_status( ctx ) );
516 0 : }
517 :
518 : static void
519 : cpu_owner_refresh( fd_diag_tile_t * ctx,
520 0 : fd_topo_t const * topo ) {
521 0 : ushort owner[ FD_TILE_MAX ];
522 0 : ushort cnt [ FD_TILE_MAX ] = {0};
523 0 : fd_cpuset_new( ctx->cpu_live_tile );
524 0 : for( ulong i=0UL; i<ctx->tile_cnt; i++ ) {
525 0 : if( topo ) {
526 0 : ulong cpu_idx = topo->tiles[ i ].cpu_idx;
527 0 : ctx->tile_cpu[ i ] = (ushort)fd_ulong_if( cpu_idx<FD_TILE_MAX && !topo->tiles[ i ].floats, cpu_idx, USHORT_MAX );
528 0 : }
529 0 : ulong cpu_idx = ctx->tile_cpu[ i ];
530 0 : if( cpu_idx==USHORT_MAX || ( !topo && 2UL==ctx->metrics[ i ][ FD_METRICS_GAUGE_TILE_STATE_OFF ] ) ) continue;
531 0 : owner[ cpu_idx ] = (ushort)i;
532 0 : cnt [ cpu_idx ]++;
533 0 : fd_cpuset_insert( ctx->cpu_live_tile, cpu_idx );
534 0 : }
535 0 : for( ulong c=0UL; c<FD_TILE_MAX; c++ ) {
536 0 : ushort o = cnt[ c ]==1 ? owner[ c ] : USHORT_MAX;
537 0 : if( FD_LIKELY( o==ctx->cpu_to_tile[ c ] ) ) continue;
538 0 : ctx->cpu_to_tile[ c ] = o;
539 0 : if( !topo ) { /* the new owner counts from its next sample */
540 0 : fd_cpuset_insert( ctx->irq_rebase, c );
541 0 : fd_cpuset_insert( ctx->tick_rebase, c );
542 0 : }
543 0 : }
544 0 : }
545 :
546 : static void
547 0 : irq_metrics( fd_diag_tile_t * ctx ) {
548 0 : if( FD_UNLIKELY( -1==lseek( ctx->proc_softirqs_fd, 0, SEEK_SET ) ) ) FD_LOG_ERR(( "lseek failed (%i-%s)", errno, strerror( errno ) ));
549 0 : ulong softirq_cpu_cnt = fd_proc_softirqs_sum( ctx->proc_softirqs_fd, ctx->irq_cnt );
550 0 : if( FD_UNLIKELY( !softirq_cpu_cnt ) ) return; /* parse fail */
551 :
552 0 : ulong volatile * softirq_total = &fd_metrics_tl[ MIDX( COUNTER, DIAG, SYSTEM_SOFTIRQ ) ];
553 0 : ulong volatile * softirq_undesired = &fd_metrics_tl[ MIDX( COUNTER, DIAG, SOFTIRQ_ON_TILE_CPU ) ];
554 0 : for( ulong j=0UL; j<FD_METRICS_ENUM_SOFTIRQ_TYPE_CNT; j++ ) {
555 0 : ulong tot_cnt = 0UL;
556 0 : for( ulong i=0UL; i<softirq_cpu_cnt; i++ ) {
557 0 : tot_cnt += fd_ulong_sat_sub( ctx->irq_cnt[ j ][ i ], ctx->softirq_boot[ j ][ i ] );
558 0 : ulong delta = fd_ulong_sat_sub( ctx->irq_cnt[ j ][ i ], ctx->softirq_baseline[ j ][ i ] );
559 0 : if( fd_cpuset_test( ctx->cpu_live_tile, i ) ) ctx->on_tile_softirq[ j ] += delta;
560 0 : ctx->softirq_baseline[ j ][ i ] = ctx->irq_cnt[ j ][ i ];
561 0 : }
562 0 : softirq_total [ j ] = tot_cnt;
563 0 : softirq_undesired[ j ] = ctx->on_tile_softirq[ j ];
564 0 : }
565 :
566 0 : ulong * cpu_irq = ctx->irq_cnt[ 0 ]; /* re-use as scratch memory */
567 0 : ulong * cpu_tlb = ctx->irq_cnt[ 1 ];
568 0 : ulong * cpu_loc = ctx->irq_cnt[ 2 ];
569 0 : if( FD_UNLIKELY( -1==lseek( ctx->proc_interrupts_fd, 0, SEEK_SET ) ) ) FD_LOG_ERR(( "lseek failed (%i-%s)", errno, strerror( errno ) ));
570 0 : ulong cpu_cnt = fd_proc_interrupts_read( ctx->proc_interrupts_fd, cpu_irq, cpu_tlb, cpu_loc );
571 0 : if( FD_UNLIKELY( !cpu_cnt ) ) return; /* parse fail */
572 :
573 0 : ulong tot_cnt = 0UL;
574 0 : for( ulong i=0UL; i<cpu_cnt; i++ ) {
575 0 : tot_cnt += fd_ulong_sat_sub( cpu_irq[ i ], ctx->device_irq_boot[ i ] );
576 0 : ulong delta = fd_ulong_sat_sub( cpu_irq[ i ], ctx->device_irq_baseline[ i ] );
577 0 : if( fd_cpuset_test( ctx->cpu_live_tile, i ) ) ctx->on_tile_device_irq += delta;
578 0 : ctx->device_irq_baseline[ i ] = cpu_irq[ i ];
579 0 : }
580 0 : FD_MCNT_SET( DIAG, SYSTEM_DEVICE_IRQ, tot_cnt );
581 0 : FD_MCNT_SET( DIAG, DEVICE_IRQ_ON_TILE_CPU, ctx->on_tile_device_irq );
582 :
583 0 : for( ulong i=0UL; i<cpu_cnt; i++ ) {
584 0 : ulong tile_id = ctx->cpu_to_tile[ i ];
585 0 : if( tile_id==USHORT_MAX ) continue;
586 0 : if( FD_UNLIKELY( fd_cpuset_test( ctx->irq_rebase, i ) ) ) {
587 0 : ctx->owner_irq_baseline[ i ] = cpu_irq[ i ];
588 0 : ctx->owner_tlb_baseline[ i ] = cpu_tlb[ i ];
589 0 : ctx->owner_loc_baseline[ i ] = cpu_loc[ i ];
590 0 : fd_cpuset_remove( ctx->irq_rebase, i );
591 0 : }
592 0 : ctx->metrics[ tile_id ][ FD_METRICS_COUNTER_TILE_DEVICE_IRQ_OFF ] = fd_ulong_sat_sub( cpu_irq[ i ], ctx->owner_irq_baseline[ i ] );
593 0 : ctx->metrics[ tile_id ][ FD_METRICS_COUNTER_TILE_TLB_SHOOTDOWN_OFF ] = fd_ulong_sat_sub( cpu_tlb[ i ], ctx->owner_tlb_baseline[ i ] );
594 0 : ctx->metrics[ tile_id ][ FD_METRICS_COUNTER_TILE_TIMER_IRQ_OFF ] = fd_ulong_sat_sub( cpu_loc[ i ], ctx->owner_loc_baseline[ i ] );
595 0 : }
596 0 : }
597 :
598 : /* interrupt_metrics reads per-CPU irq+softirq+steal tick counts from
599 : /proc/stat and publishes them as the INTERRUPT CPU regime for
600 : fixed tiles. On kernels with CONFIG_IRQ_TIME_ACCOUNTING (near
601 : universal), these buckets are disjoint from utime/stime so
602 : idle = lifetime - user - system - wait - interrupt
603 : is exact up to sampling granularity; without it, the irq columns
604 : undercount and interrupt reads near zero (degrades gracefully). */
605 :
606 : static void
607 0 : interrupt_metrics( fd_diag_tile_t * ctx ) {
608 0 : ulong cpu_ticks[ FD_TILE_MAX ];
609 0 : if( FD_UNLIKELY( -1==lseek( ctx->proc_stat_fd, 0, SEEK_SET ) ) ) FD_LOG_ERR(( "lseek failed (%i-%s)", errno, strerror( errno ) ));
610 0 : ulong cpu_cnt = fd_proc_stat_irq_ticks( ctx->proc_stat_fd, cpu_ticks );
611 0 : if( FD_UNLIKELY( !cpu_cnt ) ) return; /* parse fail */
612 :
613 0 : for( ulong i=0UL; i<cpu_cnt; i++ ) {
614 0 : ulong tile_id = ctx->cpu_to_tile[ i ];
615 0 : if( tile_id==USHORT_MAX ) continue;
616 0 : if( FD_UNLIKELY( fd_cpuset_test( ctx->tick_rebase, i ) ) ) {
617 0 : ctx->owner_tick_baseline[ i ] = cpu_ticks[ i ];
618 0 : fd_cpuset_remove( ctx->tick_rebase, i );
619 0 : }
620 : /* CLK_TCK is always 100, so 1 tick = 10ms = 10,000,000 ns */
621 0 : ulong since = fd_ulong_sat_sub( cpu_ticks[ i ], ctx->owner_tick_baseline[ i ] );
622 0 : ctx->metrics[ tile_id ][ FD_METRICS_COUNTER_TILE_THREAD_DURATION_NANOS_INTERRUPT_OFF ] = since*10000000UL;
623 0 : }
624 0 : }
625 :
626 : static ulong
627 : read_text( int fd,
628 : char * buf,
629 0 : ulong buf_sz ) {
630 0 : if( FD_UNLIKELY( -1==lseek( fd, 0, SEEK_SET ) ) ) FD_LOG_ERR(( "lseek failed (%i-%s)", errno, strerror( errno ) ));
631 0 : ulong len;
632 0 : int err = fd_io_read( fd, buf, buf_sz-1UL, buf_sz-1UL, &len );
633 0 : if( FD_UNLIKELY( err>0 ) ) FD_LOG_ERR(( "fd_io_read failed (%i-%s)", err, fd_io_strerror( err ) ));
634 0 : buf[ len ] = '\0';
635 0 : return len;
636 0 : }
637 :
638 : static void
639 : add_numa_bytes( fd_diag_tile_t * ctx,
640 : ulong tile_idx,
641 : ulong numa_slot,
642 0 : ulong bytes ) {
643 0 : if( FD_UNLIKELY( tile_idx==DIAG_WKSP_TILE_IDX_SHARED ) ) {
644 0 : ctx->system_resources.numa_mem[ numa_slot ].shared_bytes += bytes;
645 0 : return;
646 0 : }
647 :
648 0 : ushort * entry_idx = &ctx->numa_entry_idx[ tile_idx ][ numa_slot ];
649 0 : if( FD_UNLIKELY( *entry_idx==USHORT_MAX ) ) {
650 0 : FD_TEST( ctx->system_resources.tile_mem_cnt<FD_DIAG_SYSTEM_TILE_MEM_MAX );
651 0 : *entry_idx = (ushort)ctx->system_resources.tile_mem_cnt++;
652 0 : fd_diag_system_tile_mem_t * entry = &ctx->system_resources.tile_mem[ *entry_idx ];
653 0 : entry->tile_idx = (ushort)tile_idx;
654 0 : entry->numa_idx = ctx->numa.node[ numa_slot ].idx;
655 0 : }
656 0 : ctx->system_resources.tile_mem[ *entry_idx ].allocated_bytes += bytes;
657 0 : }
658 :
659 : static void
660 0 : add_configured_memory_bytes( fd_diag_tile_t * ctx ) {
661 : /* Workspaces and tile stacks are hugetlbfs-backed, so their configured
662 : sizes are also their resident physical-memory footprint. */
663 0 : for( ulong i=0UL; i<ctx->memory.cnt; i++ ) {
664 0 : if( FD_UNLIKELY( ctx->memory.wksp[ i ].numa_slot==USHORT_MAX ) ) continue;
665 0 : add_numa_bytes( ctx,
666 0 : ctx->memory.wksp[ i ].tile_idx,
667 0 : ctx->memory.wksp[ i ].numa_slot,
668 0 : ctx->memory.wksp[ i ].bytes );
669 0 : }
670 0 : for( ulong tile_idx=0UL; tile_idx<ctx->tile_cnt; tile_idx++ ) {
671 0 : ushort numa_slot = ctx->memory.stack_numa_slot[ tile_idx ];
672 0 : if( FD_UNLIKELY( numa_slot==USHORT_MAX ) ) continue;
673 0 : add_numa_bytes( ctx, tile_idx, numa_slot, FD_TILE_PRIVATE_STACK_SZ );
674 0 : }
675 0 : }
676 :
677 : static void
678 0 : sample_disk( fd_diag_tile_t * ctx ) {
679 0 : ctx->system_resources.mount_cnt = (uint)ctx->mount_cnt;
680 0 : ctx->system_resources.file_cnt = (uint)ctx->file_cnt;
681 :
682 0 : for( ulong i=0UL; i<ctx->mount_cnt; i++ ) {
683 0 : struct statfs st;
684 0 : if( FD_UNLIKELY( fstatfs( ctx->mounts[ i ].fd, &st ) ) ) FD_LOG_ERR(( "fstatfs failed (%i-%s)", errno, strerror( errno ) ));
685 0 : ulong block_sz = (ulong)( st.f_frsize ? st.f_frsize : st.f_bsize );
686 0 : fd_diag_system_mount_t * mount = &ctx->system_resources.mount[ i ];
687 0 : fd_cstr_ncpy( mount->path, ctx->mounts[ i ].path, sizeof(mount->path) );
688 0 : mount->total_bytes = (ulong)st.f_blocks*block_sz;
689 0 : mount->free_bytes = (ulong)st.f_bfree *block_sz;
690 0 : mount->available_bytes = (ulong)st.f_bavail*block_sz;
691 0 : }
692 :
693 0 : for( ulong i=0UL; i<ctx->file_cnt; i++ ) {
694 0 : fd_diag_system_file_t * file = &ctx->system_resources.file[ i ];
695 0 : fd_cstr_ncpy( file->path, ctx->files[ i ].path, sizeof(file->path) );
696 0 : file->category = ctx->files[ i ].category;
697 0 : file->mount_idx = ctx->files[ i ].mount_idx;
698 0 : if( ctx->files[ i ].metric ) file->bytes = *ctx->files[ i ].metric;
699 0 : else if( ctx->files[ i ].data_fd>=0 ) {
700 0 : struct stat st;
701 0 : if( FD_UNLIKELY( syscall( SYS_fstat, ctx->files[ i ].data_fd, &st ) ) ) FD_LOG_ERR(( "fstat failed (%i-%s)", errno, strerror( errno ) ));
702 0 : file->bytes = (ulong)st.st_size;
703 0 : }
704 0 : }
705 0 : }
706 :
707 : static ulong
708 : read_meminfo_kib( char const * buf,
709 0 : char const * key ) {
710 0 : char const * p = strstr( buf, key );
711 0 : if( FD_UNLIKELY( !p ) ) return 0UL;
712 0 : p += strlen( key );
713 0 : while( *p==' ' || *p=='\t' || *p==':' ) p++;
714 0 : return strtoul( p, NULL, 10 );
715 0 : }
716 :
717 : static void
718 : sample_system( fd_diag_tile_t * ctx,
719 0 : long now ) {
720 0 : ctx->next_system_report_nanos = now + SYSTEM_REPORT_INTERVAL_NANOS;
721 :
722 0 : ctx->system_resources.mem_available_bytes = 0UL;
723 0 : ctx->system_resources.mem_free_bytes = 0UL;
724 0 : ctx->system_resources.numa_mem_cnt = (uint)ctx->numa.cnt;
725 0 : ctx->system_resources.tile_mem_cnt = 0U;
726 0 : ctx->system_resources.mount_cnt = 0U;
727 0 : ctx->system_resources.file_cnt = 0U;
728 0 : fd_memset( ctx->system_resources.numa_mem, 0, sizeof(ctx->system_resources.numa_mem) );
729 0 : fd_memset( ctx->system_resources.tile_mem, 0, sizeof(ctx->system_resources.tile_mem) );
730 0 : fd_memset( ctx->system_resources.mount, 0, sizeof(ctx->system_resources.mount) );
731 0 : fd_memset( ctx->system_resources.file, 0, sizeof(ctx->system_resources.file) );
732 0 : fd_memset( ctx->numa_entry_idx, 0xFF, sizeof(ctx->numa_entry_idx) );
733 0 : char meminfo[ 4096 ];
734 0 : if( FD_LIKELY( read_text( ctx->proc_meminfo_fd, meminfo, sizeof(meminfo) ) ) ) {
735 0 : ctx->system_resources.mem_available_bytes = read_meminfo_kib( meminfo, "MemAvailable" )<<10;
736 0 : ctx->system_resources.mem_free_bytes = read_meminfo_kib( meminfo, "MemFree" )<<10;
737 0 : }
738 0 : for( ulong i=0UL; i<ctx->numa.cnt; i++ ) {
739 0 : ulong numa_idx = ctx->numa.node[ i ].idx;
740 0 : fd_diag_system_numa_mem_t * numa = &ctx->system_resources.numa_mem[ i ];
741 0 : numa->numa_idx = (ushort)numa_idx;
742 :
743 0 : if( FD_UNLIKELY( !read_text( ctx->numa.node[ i ].meminfo_fd, meminfo, sizeof(meminfo) ) ) ) continue;
744 0 : char key[ 64 ];
745 0 : FD_TEST( fd_cstr_printf_check( key, sizeof(key), NULL, "Node %lu MemTotal", numa_idx ) );
746 0 : numa->total_bytes = read_meminfo_kib( meminfo, key )<<10;
747 0 : FD_TEST( fd_cstr_printf_check( key, sizeof(key), NULL, "Node %lu MemFree", numa_idx ) );
748 0 : numa->free_bytes = read_meminfo_kib( meminfo, key )<<10;
749 0 : }
750 0 : add_configured_memory_bytes( ctx );
751 0 : sample_disk( ctx );
752 0 : ctx->system_resources.sample_time_nanos = (ulong)now;
753 0 : }
754 :
755 : static void
756 : publish_system( fd_diag_tile_t * ctx,
757 : fd_stem_context_t * stem,
758 0 : long now ) {
759 0 : if( FD_LIKELY( now<ctx->next_system_report_nanos ) ) return;
760 0 : sample_system( ctx, now );
761 :
762 0 : fd_memcpy( fd_chunk_to_laddr( ctx->system_out.mem, ctx->system_out.chunk ),
763 0 : &ctx->system_resources, sizeof(fd_diag_system_resources_t) );
764 0 : fd_stem_publish( stem, ctx->system_out.idx, sizeof(fd_diag_system_resources_t),
765 0 : ctx->system_out.chunk, sizeof(fd_diag_system_resources_t), 0UL, 0UL,
766 0 : (ulong)fd_frag_meta_ts_comp( fd_tickcount() ) );
767 0 : ctx->system_out.chunk = fd_dcache_compact_next( ctx->system_out.chunk,
768 0 : sizeof(fd_diag_system_resources_t), ctx->system_out.chunk0, ctx->system_out.wmark );
769 0 : }
770 :
771 : static int
772 : sensor_read( int fd,
773 0 : long * out ) {
774 0 : char buf[ 32 ];
775 0 : if( FD_UNLIKELY( -1==lseek( fd, 0, SEEK_SET ) ) ) FD_LOG_ERR(( "lseek failed (%i-%s)", errno, strerror( errno ) ));
776 0 : long n = read( fd, buf, sizeof(buf)-1UL );
777 0 : if( FD_UNLIKELY( n<=0 ) ) return -1;
778 0 : buf[ n ] = '\0';
779 0 : *out = strtol( buf, NULL, 10 );
780 0 : return 0;
781 0 : }
782 :
783 : static int
784 : read_optional( int fd,
785 : char * buf,
786 0 : ulong buf_sz ) {
787 0 : if( FD_UNLIKELY( -1==lseek( fd, 0, SEEK_SET ) ) ) FD_LOG_ERR(( "lseek failed (%i-%s)", errno, strerror( errno ) ));
788 0 : ulong len = 0UL;
789 0 : while( len<buf_sz-1UL ) { /* procfs returns at most a page per read */
790 0 : long n = read( fd, buf+len, buf_sz-1UL-len );
791 0 : if( FD_UNLIKELY( n<0L && errno==EINTR ) ) continue;
792 0 : if( FD_UNLIKELY( n<0L ) ) return -1;
793 0 : if( !n ) break;
794 0 : len += (ulong)n;
795 0 : }
796 0 : buf[ len ] = '\0';
797 0 : return 0;
798 0 : }
799 :
800 : static ulong
801 : psi_total_usec( char const * buf,
802 0 : char const * level ) {
803 0 : char const * p = strstr( buf, level ); /* cpu has no full line before Linux 5.13 */
804 0 : if( FD_UNLIKELY( !p ) ) return 0UL;
805 0 : p = strstr( p, "total=" );
806 0 : if( FD_UNLIKELY( !p ) ) return 0UL;
807 0 : return strtoul( p+6, NULL, 10 );
808 0 : }
809 :
810 : static int
811 : host_cpu_ticks( fd_diag_tile_t * ctx,
812 : char * buf,
813 : ulong buf_sz,
814 0 : ulong * cpu ) {
815 0 : if( FD_UNLIKELY( !read_text( ctx->proc_stat_fd, buf, buf_sz ) || strncmp( buf, "cpu ", 4UL ) ) ) return -1;
816 0 : char * p = buf+4;
817 0 : ulong f[ 8 ];
818 0 : for( ulong i=0UL; i<8UL; i++ ) f[ i ] = strtoul( p, &p, 10 ); /* user nice system idle iowait irq softirq steal */
819 0 : cpu[ FD_METRICS_ENUM_SYSTEM_CPU_STATE_V_USER_IDX ] = f[ 0 ]+f[ 1 ];
820 0 : cpu[ FD_METRICS_ENUM_SYSTEM_CPU_STATE_V_SYSTEM_IDX ] = f[ 2 ];
821 0 : cpu[ FD_METRICS_ENUM_SYSTEM_CPU_STATE_V_IDLE_IDX ] = f[ 3 ];
822 0 : cpu[ FD_METRICS_ENUM_SYSTEM_CPU_STATE_V_IO_WAIT_IDX ] = f[ 4 ];
823 0 : cpu[ FD_METRICS_ENUM_SYSTEM_CPU_STATE_V_INTERRUPT_IDX ] = f[ 5 ]+f[ 6 ];
824 0 : cpu[ FD_METRICS_ENUM_SYSTEM_CPU_STATE_V_STEAL_IDX ] = f[ 7 ];
825 0 : return 0;
826 0 : }
827 :
828 : static void
829 0 : host_metrics( fd_diag_tile_t * ctx ) {
830 0 : char buf[ 16384 ]; /* /proc/vmstat is ~4 KiB and grows with kernel counters */
831 :
832 0 : if( FD_LIKELY( ctx->proc_loadavg_fd>=0 && !read_optional( ctx->proc_loadavg_fd, buf, sizeof(buf) ) ) ) {
833 0 : char * p = buf;
834 0 : ulong load[ FD_METRICS_ENUM_LOAD_AVERAGE_TYPE_CNT ];
835 0 : for( ulong i=0UL; i<FD_METRICS_ENUM_LOAD_AVERAGE_TYPE_CNT; i++ ) { double l = strtod( p, &p ); load[ i ] = l>0.0 ? (ulong)( l*100.0 + 0.5 ) : 0UL; }
836 0 : FD_MGAUGE_ENUM_COPY( DIAG, SYSTEM_LOAD_CENTI, load );
837 0 : FD_MGAUGE_SET( DIAG, THREAD_RUNNABLE_COUNT, strtoul( p, NULL, 10 ) ); /* "running/total" */
838 0 : }
839 :
840 0 : ulong cpu[ FD_METRICS_ENUM_SYSTEM_CPU_STATE_CNT ];
841 0 : if( FD_LIKELY( !host_cpu_ticks( ctx, buf, sizeof(buf), cpu ) ) ) {
842 : /* Since diag start, so nanos cannot overflow. iowait can go down
843 : (proc(5)), clamp in ticks so Prometheus never sees a reset. */
844 0 : for( ulong i=0UL; i<FD_METRICS_ENUM_SYSTEM_CPU_STATE_CNT; i++ ) {
845 0 : ctx->host_cpu_ticks[ i ] = fd_ulong_max( fd_ulong_sat_sub( cpu[ i ], ctx->host_cpu_baseline[ i ] ), ctx->host_cpu_ticks[ i ] );
846 0 : cpu[ i ] = ctx->host_cpu_ticks[ i ]*10000000UL; /* CLK_TCK is 100 */
847 0 : }
848 0 : FD_MCNT_ENUM_COPY( DIAG, SYSTEM_CPU_DURATION_NANOS, cpu );
849 0 : }
850 :
851 0 : if( FD_LIKELY( read_text( ctx->proc_meminfo_fd, buf, sizeof(buf) ) ) ) {
852 0 : FD_MGAUGE_SET( DIAG, MEMORY_AVAILABLE_BYTES, read_meminfo_kib( buf, "MemAvailable" )<<10 );
853 0 : FD_MGAUGE_SET( DIAG, MEMORY_FREE_BYTES, read_meminfo_kib( buf, "MemFree" )<<10 );
854 0 : }
855 :
856 0 : if( FD_LIKELY( ctx->proc_vmstat_fd>=0 && !read_optional( ctx->proc_vmstat_fd, buf, sizeof(buf) ) ) ) {
857 0 : ulong page_sz = (ulong)FD_SHMEM_NORMAL_PAGE_SZ;
858 0 : FD_MCNT_SET( DIAG, SWAP_IN_BYTES, read_meminfo_kib( buf, "\npswpin " )*page_sz );
859 0 : FD_MCNT_SET( DIAG, SWAP_OUT_BYTES, read_meminfo_kib( buf, "\npswpout " )*page_sz );
860 0 : FD_MCNT_SET( DIAG, SYSTEM_PAGE_FAULT_MAJOR, read_meminfo_kib( buf, "\npgmajfault " ) );
861 0 : FD_MCNT_SET( DIAG, OOM_KILLED, read_meminfo_kib( buf, "\noom_kill " ) );
862 0 : }
863 :
864 0 : if( ctx->psi_fd[ 0 ]>=0 && !read_optional( ctx->psi_fd[ 0 ], buf, sizeof(buf) ) ) {
865 0 : FD_MCNT_SET( DIAG, CPU_PRESSURE_DURATION_NANOS, psi_total_usec( buf, "some" )*1000UL );
866 0 : }
867 0 : if( ctx->psi_fd[ 1 ]>=0 && !read_optional( ctx->psi_fd[ 1 ], buf, sizeof(buf) ) ) {
868 0 : FD_MCNT_SET( DIAG, MEMORY_PRESSURE_DURATION_NANOS, psi_total_usec( buf, "some" )*1000UL );
869 0 : FD_MCNT_SET( DIAG, MEMORY_PRESSURE_FULL_DURATION_NANOS, psi_total_usec( buf, "full" )*1000UL );
870 0 : }
871 0 : if( ctx->psi_fd[ 2 ]>=0 && !read_optional( ctx->psi_fd[ 2 ], buf, sizeof(buf) ) ) {
872 0 : FD_MCNT_SET( DIAG, IO_PRESSURE_DURATION_NANOS, psi_total_usec( buf, "some" )*1000UL );
873 0 : FD_MCNT_SET( DIAG, IO_PRESSURE_FULL_DURATION_NANOS, psi_total_usec( buf, "full" )*1000UL );
874 0 : }
875 0 : }
876 :
877 : /* /sys/block/<dev>/stat fields: reads completed, reads merged, sectors
878 : read, read ms, writes completed, writes merged, sectors written,
879 : write ms, requests in flight, io ms, weighted io ms. Newer kernels
880 : append discard and flush fields. Sectors are always 512 bytes. The
881 : ms fields are 32-bit in the kernel and wrap. The low 32 bits of
882 : each running sum are the last value read, so adding the difference
883 : carries the sum across a wrap. */
884 :
885 : static int
886 : disk_stat_read( fd_diag_tile_t * ctx,
887 0 : ulong f[ static 11 ] ) {
888 0 : char buf[ 512 ];
889 0 : if( ctx->disk_fd<0 || read_optional( ctx->disk_fd, buf, sizeof(buf) ) ) return -1;
890 0 : char * p = buf;
891 0 : for( ulong i=0UL; i<11UL; i++ ) f[ i ] = strtoul( p, &p, 10 );
892 0 : return 0;
893 0 : }
894 :
895 : static void
896 0 : disk_metrics( fd_diag_tile_t * ctx ) {
897 0 : ulong f[ 11 ];
898 0 : if( disk_stat_read( ctx, f ) ) return;
899 :
900 0 : ulong * ms = ctx->disk_ms;
901 0 : ulong * base = ctx->disk_ms_base;
902 0 : ms[ 0 ] += (uint)f[ 7 ]-(uint)ms[ 0 ];
903 0 : ms[ 1 ] += (uint)f[ 9 ]-(uint)ms[ 1 ];
904 0 : ms[ 2 ] += (uint)f[ 10 ]-(uint)ms[ 2 ];
905 :
906 0 : FD_MCNT_SET ( DIAG, ACCOUNTS_DISK_READ_BYTES, f[ 2 ]*512UL );
907 0 : FD_MCNT_SET ( DIAG, ACCOUNTS_DISK_WRITTEN_BYTES, f[ 6 ]*512UL );
908 0 : FD_MCNT_SET ( DIAG, ACCOUNTS_DISK_WRITTEN, f[ 4 ] );
909 0 : FD_MCNT_SET ( DIAG, ACCOUNTS_DISK_WRITE_DURATION_NANOS, (ms[ 0 ]-base[ 0 ])*1000000UL );
910 0 : FD_MCNT_SET ( DIAG, ACCOUNTS_DISK_BUSY_DURATION_NANOS, (ms[ 1 ]-base[ 1 ])*1000000UL );
911 0 : FD_MCNT_SET ( DIAG, ACCOUNTS_DISK_REQUEST_IN_FLIGHT_INTEGRAL_NANOS, (ms[ 2 ]-base[ 2 ])*1000000UL );
912 0 : FD_MGAUGE_SET( DIAG, ACCOUNTS_DISK_REQUEST_IN_FLIGHT, f[ 8 ] );
913 0 : }
914 :
915 : static void
916 : sensor_metrics( fd_diag_tile_t * ctx,
917 0 : long now ) {
918 0 : long val [ DIAG_SENSOR_KIND_CNT ] = {0}; /* hottest temperature, summed power and errors */
919 0 : int failed[ DIAG_SENSOR_KIND_CNT ] = {0};
920 0 : for( ulong i=0UL; i<ctx->sensor.cnt; i++ ) {
921 0 : long v;
922 0 : uint kind = ctx->sensor.kind[ i ];
923 0 : if( FD_UNLIKELY( sensor_read( ctx->sensor.fd[ i ], &v ) ) ) { failed[ kind ] = 1; continue; }
924 0 : if( kind>=DIAG_SENSOR_POWER ) val[ kind ] += fd_long_max( v, 0L );
925 0 : else val[ kind ] = fd_long_max( val[ kind ], v );
926 0 : }
927 0 : FD_MGAUGE_SET( DIAG, CPU_PACKAGE_MILLICELSIUS, (ulong)val[ DIAG_SENSOR_CPU_PACKAGE ] );
928 0 : FD_MGAUGE_SET( DIAG, NIC_MILLICELSIUS, (ulong)val[ DIAG_SENSOR_NIC ] );
929 0 : FD_MGAUGE_SET( DIAG, NVME_MILLICELSIUS, (ulong)val[ DIAG_SENSOR_NVME ] );
930 0 : FD_MGAUGE_SET( DIAG, SYSTEM_POWER_MICROWATTS, (ulong)val[ DIAG_SENSOR_POWER ] );
931 0 : if( FD_LIKELY( !failed[ DIAG_SENSOR_ECC_CE ] ) ) FD_MCNT_SET( DIAG, MEMORY_ECC_CORRECTED, (ulong)val[ DIAG_SENSOR_ECC_CE ] );
932 0 : if( FD_LIKELY( !failed[ DIAG_SENSOR_ECC_UE ] ) ) FD_MCNT_SET( DIAG, MEMORY_ECC_UNCORRECTED, (ulong)val[ DIAG_SENSOR_ECC_UE ] );
933 :
934 : /* Package power is the energy counter's rate since the last read */
935 0 : if( !ctx->sensor.rapl_cnt ) return;
936 0 : ulong energy = 0UL;
937 0 : for( ulong i=0UL; i<ctx->sensor.rapl_cnt; i++ ) {
938 0 : long e;
939 0 : if( FD_UNLIKELY( sensor_read( ctx->sensor.rapl_fd[ i ], &e ) || e<0L ) ) {
940 0 : ctx->sensor.rapl_prev_nanos = 0L; /* rebaseline */
941 0 : FD_MGAUGE_SET( DIAG, CPU_POWER_MICROWATTS, 0UL );
942 0 : return;
943 0 : }
944 0 : ulong cur = (ulong)e;
945 0 : ulong prev = ctx->sensor.rapl_prev[ i ];
946 0 : energy += cur>=prev ? cur-prev : cur+ctx->sensor.rapl_range[ i ]-prev;
947 0 : ctx->sensor.rapl_prev[ i ] = cur;
948 0 : }
949 0 : long dt = now - ctx->sensor.rapl_prev_nanos;
950 0 : if( FD_LIKELY( ctx->sensor.rapl_prev_nanos && dt>0L ) ) FD_MGAUGE_SET( DIAG, CPU_POWER_MICROWATTS, (ulong)( (double)energy*1e9/(double)dt ) );
951 0 : ctx->sensor.rapl_prev_nanos = now;
952 0 : }
953 :
954 : static void
955 : before_credit( fd_diag_tile_t * ctx,
956 : fd_stem_context_t * stem,
957 0 : int * charge_busy ) {
958 0 : (void)stem;
959 :
960 0 : long now = fd_clock_tile_now( ctx->clock );
961 0 : if( FD_UNLIKELY( now<ctx->next_report_nanos ) ) return;
962 0 : ctx->next_report_nanos += REPORT_INTERVAL_MILLIS*1000L*1000L;
963 :
964 0 : *charge_busy = 1;
965 0 : if( FD_UNLIKELY( ctx->gui_enabled ) ) publish_system( ctx, stem, now );
966 :
967 0 : struct timespec boottime;
968 0 : if( FD_UNLIKELY( -1==clock_gettime( CLOCK_BOOTTIME, &boottime ) ) ) FD_LOG_ERR(( "clock_gettime(CLOCK_BOOTTIME) failed (%i-%s)", errno, strerror( errno ) ));
969 0 : ulong now_since_boot_nanos = (ulong)boottime.tv_sec*1000000000UL + (ulong)boottime.tv_nsec;
970 :
971 0 : cpu_owner_refresh( ctx, NULL );
972 0 : interrupt_metrics( ctx ); /* before idle computation below, which subtracts it */
973 :
974 0 : int sched_due = now>=ctx->next_sched_report_nanos;
975 0 : if( FD_UNLIKELY( sched_due ) ) ctx->next_sched_report_nanos = now + SCHED_REPORT_INTERVAL_NANOS;
976 :
977 0 : for( ulong i=0UL; i<ctx->tile_cnt; i++ ) {
978 0 : if( FD_UNLIKELY( -1==ctx->stat_fds[ i ] ) ) continue;
979 :
980 : /* CLK_TCK is typically 100, so 1 tick = 10ms = 10,000,000 ns */
981 0 : int process_died1 = read_stat_file( ctx->stat_fds[ i ], 10000000UL, ctx->metrics[ i ] );
982 0 : int process_died2 = -1!=ctx->schedstat_fds[ i ] && read_schedstat_file( ctx->schedstat_fds[ i ], ctx->metrics[ i ] );
983 0 : int process_died3 = sched_due && read_sched_file( ctx->sched_fds[ i ], ctx->metrics[ i ] );
984 :
985 0 : if( FD_UNLIKELY( process_died1 || process_died2 || process_died3 ) ) {
986 0 : ctx->stat_fds[ i ] = -1;
987 0 : continue;
988 0 : }
989 :
990 0 : ulong task_lifetime_nanos = now_since_boot_nanos - ctx->starttime_nanos[ i ];
991 0 : ulong user_nanos = ctx->metrics[ i ][ FD_METRICS_COUNTER_TILE_THREAD_DURATION_NANOS_USER_OFF ];
992 0 : ulong system_nanos = ctx->metrics[ i ][ FD_METRICS_COUNTER_TILE_THREAD_DURATION_NANOS_SYSTEM_OFF ];
993 0 : ulong wait_nanos = ctx->metrics[ i ][ FD_METRICS_COUNTER_TILE_THREAD_DURATION_NANOS_WAIT_OFF ];
994 0 : ulong interrupt_nanos = ctx->metrics[ i ][ FD_METRICS_COUNTER_TILE_THREAD_DURATION_NANOS_INTERRUPT_OFF ];
995 0 : ulong busy_nanos = user_nanos+system_nanos+wait_nanos+interrupt_nanos;
996 0 : ulong idle_nanos = (task_lifetime_nanos>busy_nanos) ? (task_lifetime_nanos-busy_nanos) : 0UL;
997 :
998 : /* Counter can't go backwards in Prometheus else it thinks the
999 : application restarted. Use max to ensure monotonicity. */
1000 0 : ctx->metrics[ i ][ FD_METRICS_COUNTER_TILE_THREAD_DURATION_NANOS_IDLE_OFF ] = fd_ulong_max( idle_nanos, ctx->metrics[ i ][ FD_METRICS_COUNTER_TILE_THREAD_DURATION_NANOS_IDLE_OFF ] );
1001 0 : }
1002 :
1003 0 : for( ulong i=0UL; i<ctx->tile_cnt; i++ ) {
1004 0 : if( FD_LIKELY( -1!=ctx->stat_fds[ i ] ) ) continue;
1005 :
1006 : /* The tile died, but it's a tile which is allowed to shutdown, so
1007 : just stop updating metrics for it. */
1008 0 : if( FD_LIKELY( 2UL==ctx->metrics[ i ][ FD_METRICS_GAUGE_TILE_STATE_OFF ] ) ) continue;
1009 :
1010 : /* Supervisor is going to bring the whole process tree down if any
1011 : of the target PIDs died, so we can ignore this and wait. */
1012 0 : if( FD_UNLIKELY( !ctx->first_seen_died[ i ] ) ) {
1013 0 : ctx->first_seen_died[ i ] = now;
1014 0 : } else if( FD_LIKELY( ctx->first_seen_died[ i ]==LONG_MAX ) ) {
1015 : /* We already reported this, so we can ignore it. */
1016 0 : } else if( FD_UNLIKELY( now-ctx->first_seen_died[ i ] < 10L*1000L*1000L*1000L ) ) {
1017 : /* Wait 10 seconds for supervisor to kill us before reporting WARNING */
1018 0 : } else {
1019 0 : FD_LOG_WARNING(( "cannot get metrics for dead tile idx %lu", i ));
1020 0 : ctx->first_seen_died[ i ] = LONG_MAX;
1021 0 : }
1022 0 : }
1023 :
1024 0 : check_engine_metric( ctx, now );
1025 0 : if( FD_UNLIKELY( sched_due ) ) disk_metrics( ctx );
1026 0 : if( FD_UNLIKELY( now>=ctx->next_irq_report_nanos ) ) {
1027 0 : ctx->next_irq_report_nanos = now + IRQ_REPORT_INTERVAL_NANOS;
1028 0 : irq_metrics( ctx );
1029 0 : sensor_metrics( ctx, now );
1030 0 : host_metrics( ctx );
1031 0 : }
1032 0 : }
1033 :
1034 : /* Disk mount discovery ************************************************/
1035 :
1036 : static int
1037 : fd_mount_id( int fd,
1038 0 : ulong * mnt_id ) {
1039 0 : char fdinfo_path[ 64 ];
1040 0 : if( FD_UNLIKELY( !fd_cstr_printf_check( fdinfo_path, sizeof(fdinfo_path), NULL, "/proc/self/fdinfo/%d", fd ) ) ) return EINVAL;
1041 :
1042 0 : int fdinfo_fd = open( fdinfo_path, O_RDONLY|O_CLOEXEC );
1043 0 : if( FD_UNLIKELY( fdinfo_fd<0 ) ) return errno;
1044 :
1045 0 : char buf[ 256 ];
1046 0 : ulong buf_sz = 0UL;
1047 0 : int err = fd_io_read( fdinfo_fd, buf, sizeof(buf)-1UL, sizeof(buf)-1UL, &buf_sz );
1048 0 : if( FD_UNLIKELY( err<0 ) ) err = 0; /* EOF before the buffer filled */
1049 0 : if( FD_UNLIKELY( close( fdinfo_fd ) && !err ) ) err = errno;
1050 0 : if( FD_UNLIKELY( err ) ) return err;
1051 0 : buf[ buf_sz ] = '\0';
1052 :
1053 0 : char const * line = buf;
1054 0 : while( line ) {
1055 0 : if( FD_UNLIKELY( !strncmp( line, "mnt_id:", 7UL ) ) ) {
1056 0 : char * end;
1057 0 : ulong id = strtoul( line+7UL, &end, 10 );
1058 0 : if( FD_UNLIKELY( end==line+7UL || (*end!='\n' && *end!='\0') || id==ULONG_MAX ) ) return EINVAL;
1059 0 : *mnt_id = id;
1060 0 : return 0;
1061 0 : }
1062 0 : line = strchr( line, '\n' );
1063 0 : if( line ) line++;
1064 0 : }
1065 0 : return ENOENT;
1066 0 : }
1067 :
1068 : static int
1069 : resolve_path_prefix( char const * path,
1070 0 : char resolved[ static PATH_MAX ] ) {
1071 0 : char candidate[ PATH_MAX ];
1072 0 : fd_cstr_ncpy( candidate, path, sizeof(candidate) );
1073 0 : while( !realpath( candidate, resolved ) ) {
1074 0 : char * slash = strrchr( candidate, '/' );
1075 0 : if( FD_UNLIKELY( !slash ) ) return 0;
1076 0 : if( slash==candidate ) slash[ 1 ] = '\0';
1077 0 : else *slash = '\0';
1078 0 : }
1079 0 : return 1;
1080 0 : }
1081 :
1082 : static int
1083 : open_disk_probe_dir( char const * resolved,
1084 : char probe_dir[ static PATH_MAX ],
1085 0 : ulong * mnt_id ) {
1086 : /* `probe_dir` is on the target mount used only to create the probe
1087 : file; the descriptor for `probe_dir` is not retained. */
1088 0 : int path_fd = open( resolved, O_PATH|O_CLOEXEC );
1089 0 : if( FD_UNLIKELY( path_fd<0 ) ) return -1;
1090 :
1091 0 : struct stat path_st;
1092 0 : if( FD_UNLIKELY( fstat( path_fd, &path_st ) ) ) {
1093 0 : close( path_fd );
1094 0 : return -1;
1095 0 : }
1096 :
1097 0 : ulong path_mnt_id;
1098 0 : int err = fd_mount_id( path_fd, &path_mnt_id );
1099 0 : if( FD_UNLIKELY( err ) ) {
1100 0 : close( path_fd );
1101 0 : FD_LOG_WARNING(( "reading mount ID for `%s` failed (%i-%s); omitting filesystem from system resource reporting",
1102 0 : resolved, err, strerror( err ) ));
1103 0 : return -1;
1104 0 : }
1105 :
1106 0 : fd_cstr_ncpy( probe_dir, resolved, PATH_MAX );
1107 0 : if( FD_UNLIKELY( !S_ISDIR( path_st.st_mode ) ) ) {
1108 0 : char * slash = strrchr( probe_dir, '/' );
1109 0 : if( FD_UNLIKELY( !slash ) ) {
1110 0 : close( path_fd );
1111 0 : return -1;
1112 0 : }
1113 0 : if( slash==probe_dir ) slash[ 1 ] = '\0';
1114 0 : else *slash = '\0';
1115 0 : }
1116 :
1117 0 : int dir_fd = open( probe_dir, O_PATH|O_DIRECTORY|O_CLOEXEC );
1118 0 : if( FD_UNLIKELY( dir_fd<0 ) ) {
1119 0 : int open_err = errno;
1120 0 : close( path_fd );
1121 0 : FD_LOG_WARNING(( "open `%s` for disk-capacity probe failed (%i-%s); omitting filesystem from system resource reporting",
1122 0 : probe_dir, open_err, strerror( open_err ) ));
1123 0 : return -1;
1124 0 : }
1125 :
1126 0 : ulong dir_mnt_id;
1127 0 : err = fd_mount_id( dir_fd, &dir_mnt_id );
1128 0 : if( FD_UNLIKELY( err ) ) {
1129 0 : close( path_fd );
1130 0 : close( dir_fd );
1131 0 : FD_LOG_WARNING(( "reading mount ID for `%s` failed (%i-%s); omitting filesystem from system resource reporting",
1132 0 : probe_dir, err, strerror( err ) ));
1133 0 : return -1;
1134 0 : }
1135 0 : close( path_fd );
1136 :
1137 : /* A regular file can itself be a bind mount. In that case its
1138 : containing directory is not on the mount we need to sample,
1139 : and there is nowhere on that mount to create an anonymous file. */
1140 0 : if( FD_UNLIKELY( path_mnt_id!=dir_mnt_id ) ) {
1141 0 : close( dir_fd );
1142 0 : FD_LOG_WARNING(( "cannot create anonymous disk-capacity probe for non-directory mount `%s`; omitting filesystem from system resource reporting",
1143 0 : resolved ));
1144 0 : return -1;
1145 0 : }
1146 :
1147 0 : *mnt_id = dir_mnt_id;
1148 0 : return dir_fd;
1149 0 : }
1150 :
1151 : static int
1152 : resolve_mount_path( char const * probe_dir,
1153 : ulong mnt_id,
1154 0 : char mount_path[ static PATH_MAX ] ) {
1155 0 : fd_cstr_ncpy( mount_path, probe_dir, PATH_MAX );
1156 0 : while( strcmp( mount_path, "/" ) ) {
1157 0 : char parent[ PATH_MAX ];
1158 0 : fd_cstr_ncpy( parent, mount_path, sizeof(parent) );
1159 0 : char * slash = strrchr( parent, '/' );
1160 0 : if( slash==parent ) slash[ 1 ] = '\0';
1161 0 : else *slash = '\0';
1162 :
1163 0 : int parent_fd = open( parent, O_PATH|O_DIRECTORY|O_CLOEXEC );
1164 0 : if( FD_UNLIKELY( parent_fd<0 ) ) {
1165 0 : int open_err = errno;
1166 0 : FD_LOG_WARNING(( "open `%s` while resolving mount point failed (%i-%s); omitting filesystem from system resource reporting",
1167 0 : parent, open_err, strerror( open_err ) ));
1168 0 : return 0;
1169 0 : }
1170 0 : ulong parent_mnt_id;
1171 0 : int err = fd_mount_id( parent_fd, &parent_mnt_id );
1172 0 : close( parent_fd );
1173 0 : if( FD_UNLIKELY( err ) ) {
1174 0 : FD_LOG_WARNING(( "reading mount ID for `%s` failed (%i-%s); omitting filesystem from system resource reporting",
1175 0 : parent, err, strerror( err ) ));
1176 0 : return 0;
1177 0 : }
1178 0 : if( parent_mnt_id!=mnt_id ) break;
1179 0 : fd_cstr_ncpy( mount_path, parent, PATH_MAX );
1180 0 : }
1181 0 : return 1;
1182 0 : }
1183 :
1184 : static int
1185 : create_disk_probe( int dir_fd,
1186 : char const * probe_dir,
1187 0 : ulong mnt_id ) {
1188 : /* Retain no directory fds after sandboxing. O_EXCL ensures
1189 : that the anonymous inode cannot later be linked into the filesystem;
1190 : reopening it O_PATH also removes the temporary read/write authority. */
1191 0 : int tmp_fd = openat( dir_fd, ".", O_TMPFILE|O_EXCL|O_RDWR|O_CLOEXEC, S_IRUSR|S_IWUSR );
1192 0 : if( FD_UNLIKELY( tmp_fd<0 ) ) {
1193 0 : int err = errno;
1194 0 : FD_LOG_WARNING(( "anonymous disk-capacity probe in `%s` failed (%i-%s); omitting filesystem from system resource reporting",
1195 0 : probe_dir, err, strerror( err ) ));
1196 0 : return -1;
1197 0 : }
1198 :
1199 0 : char tmp_path[ 64 ];
1200 0 : FD_TEST( fd_cstr_printf_check( tmp_path, sizeof(tmp_path), NULL, "/proc/self/fd/%d", tmp_fd ) );
1201 0 : int mount_fd = open( tmp_path, O_PATH|O_CLOEXEC );
1202 0 : if( FD_UNLIKELY( mount_fd<0 ) ) {
1203 0 : int err = errno;
1204 0 : close( tmp_fd );
1205 0 : FD_LOG_WARNING(( "reopening anonymous disk-capacity probe in `%s` as O_PATH failed (%i-%s); omitting filesystem from system resource reporting",
1206 0 : probe_dir, err, strerror( err ) ));
1207 0 : return -1;
1208 0 : }
1209 :
1210 0 : struct stat probe_st;
1211 0 : ulong probe_mnt_id;
1212 0 : int probe_err = 0;
1213 0 : if( FD_UNLIKELY( fstat( mount_fd, &probe_st ) ) ) probe_err = errno;
1214 0 : else if( FD_UNLIKELY( !S_ISREG( probe_st.st_mode ) || probe_st.st_nlink ) ) probe_err = EINVAL;
1215 0 : else probe_err = fd_mount_id( mount_fd, &probe_mnt_id );
1216 0 : if( FD_UNLIKELY( !probe_err && probe_mnt_id!=mnt_id ) ) probe_err = EXDEV;
1217 0 : if( FD_UNLIKELY( probe_err ) ) {
1218 0 : close( mount_fd );
1219 0 : close( tmp_fd );
1220 0 : FD_LOG_WARNING(( "anonymous disk-capacity probe in `%s` failed validation (%i-%s); omitting filesystem from system resource reporting",
1221 0 : probe_dir, probe_err, strerror( probe_err ) ));
1222 0 : return -1;
1223 0 : }
1224 0 : close( tmp_fd );
1225 0 : return mount_fd;
1226 0 : }
1227 :
1228 : static uint
1229 : add_disk_mount( fd_diag_tile_t * ctx,
1230 0 : char const * path ) {
1231 0 : if( FD_UNLIKELY( !path[ 0 ] ) ) return UINT_MAX;
1232 :
1233 : /* Find longest prefix of `path` that is a real path. */
1234 0 : char resolved[ PATH_MAX ];
1235 0 : if( FD_UNLIKELY( !resolve_path_prefix( path, resolved ) ) ) return UINT_MAX;
1236 :
1237 : /* Find candidate dir at or above `resolved` where O_TMPFILE can create a probe file. */
1238 0 : char probe_dir[ PATH_MAX ];
1239 0 : ulong mnt_id;
1240 0 : int dir_fd = open_disk_probe_dir( resolved, probe_dir, &mnt_id );
1241 0 : if( FD_UNLIKELY( dir_fd<0 ) ) return UINT_MAX;
1242 :
1243 : /* Share one probe file among all reported paths on the same mount. */
1244 0 : for( ulong i=0UL; i<ctx->mount_cnt; i++ ) {
1245 0 : if( ctx->mounts[ i ].mnt_id==mnt_id ) {
1246 0 : close( dir_fd );
1247 0 : return (uint)i;
1248 0 : }
1249 0 : }
1250 0 : if( FD_UNLIKELY( ctx->mount_cnt>=FD_DIAG_SYSTEM_FILE_MAX ) ) {
1251 0 : close( dir_fd );
1252 0 : return UINT_MAX;
1253 0 : }
1254 :
1255 : /* Get the path of the mount `path` is on. */
1256 0 : char mount_path[ PATH_MAX ];
1257 0 : if( FD_UNLIKELY( !resolve_mount_path( probe_dir, mnt_id, mount_path ) ) ) {
1258 0 : close( dir_fd );
1259 0 : return UINT_MAX;
1260 0 : }
1261 :
1262 : /* Create the probe O_PATH descriptor kept after sandboxing. */
1263 0 : int mount_fd = create_disk_probe( dir_fd, probe_dir, mnt_id );
1264 0 : close( dir_fd );
1265 0 : if( FD_UNLIKELY( mount_fd<0 ) ) return UINT_MAX;
1266 :
1267 0 : ulong idx = ctx->mount_cnt++;
1268 0 : ctx->mounts[ idx ].fd = mount_fd;
1269 0 : ctx->mounts[ idx ].mnt_id = mnt_id;
1270 0 : fd_cstr_ncpy( ctx->mounts[ idx ].path, mount_path, sizeof(ctx->mounts[ idx ].path) );
1271 0 : return (uint)idx;
1272 0 : }
1273 :
1274 : static int
1275 : resolve_file_path( char const * path,
1276 : int data_fd,
1277 0 : char resolved[ static PATH_MAX ] ) {
1278 0 : char fd_path[ 64 ];
1279 0 : if( FD_LIKELY( data_fd>=0 ) ) {
1280 0 : struct stat st;
1281 0 : if( FD_UNLIKELY( fstat( data_fd, &st ) || !S_ISREG( st.st_mode ) ) ) return 0;
1282 0 : FD_TEST( fd_cstr_printf_check( fd_path, sizeof(fd_path), NULL, "/proc/self/fd/%d", data_fd ) );
1283 0 : path = fd_path;
1284 0 : }
1285 0 : if( FD_UNLIKELY( !path || !path[ 0 ] ) ) return 0;
1286 :
1287 0 : if( FD_LIKELY( realpath( path, resolved ) ) ) return 1;
1288 0 : if( FD_LIKELY( path[ 0 ]=='/' ) ) {
1289 0 : if( FD_UNLIKELY( strlen( path )>=PATH_MAX ) ) return 0;
1290 0 : fd_cstr_ncpy( resolved, path, PATH_MAX );
1291 0 : return 1;
1292 0 : }
1293 :
1294 0 : char cwd[ PATH_MAX ];
1295 0 : if( FD_UNLIKELY( !getcwd( cwd, sizeof(cwd) ) ) ) return 0;
1296 0 : return fd_cstr_printf_check( resolved, PATH_MAX, NULL, "%s/%s", cwd, path );
1297 0 : }
1298 :
1299 : static void
1300 : add_file( fd_diag_tile_t * ctx,
1301 : uint category,
1302 : char const * path,
1303 : int data_fd,
1304 0 : ulong volatile * metric ) {
1305 0 : char resolved[ PATH_MAX ];
1306 0 : if( FD_UNLIKELY( !resolve_file_path( path, data_fd, resolved ) ) ) return;
1307 :
1308 0 : uint mount_idx = add_disk_mount( ctx, resolved );
1309 0 : if( FD_UNLIKELY( mount_idx==UINT_MAX || ctx->file_cnt>=FD_DIAG_SYSTEM_FILE_MAX ) ) return;
1310 0 : ulong idx = ctx->file_cnt++;
1311 0 : fd_cstr_ncpy( ctx->files[ idx ].path, resolved, sizeof(ctx->files[ idx ].path) );
1312 0 : ctx->files[ idx ].category = category;
1313 0 : ctx->files[ idx ].mount_idx = mount_idx;
1314 0 : ctx->files[ idx ].data_fd = data_fd;
1315 0 : ctx->files[ idx ].metric = metric;
1316 0 : }
1317 :
1318 : static int
1319 : sysfs_read_cstr( char const * dir,
1320 : char const * file,
1321 : char * out,
1322 0 : ulong out_sz ) {
1323 0 : char path[ PATH_MAX ];
1324 0 : FD_TEST( fd_cstr_printf_check( path, sizeof(path), NULL, "%s/%s", dir, file ) );
1325 0 : int fd = open( path, O_RDONLY|O_CLOEXEC );
1326 0 : if( fd<0 ) return -1;
1327 0 : long n = read( fd, out, out_sz-1UL );
1328 0 : close( fd );
1329 0 : if( n<=0 ) return -1;
1330 0 : out[ n ] = '\0';
1331 0 : out[ strcspn( out, "\n" ) ] = '\0';
1332 0 : return 0;
1333 0 : }
1334 :
1335 : static void
1336 : sensor_add( fd_diag_tile_t * ctx,
1337 : char const * dir,
1338 : char const * file,
1339 0 : uint kind ) {
1340 0 : if( FD_UNLIKELY( ctx->sensor.cnt>=DIAG_SENSOR_MAX ) ) {
1341 0 : FD_LOG_WARNING(( "more than %lu hardware sensors, ignoring %s/%s", DIAG_SENSOR_MAX, dir, file ));
1342 0 : return;
1343 0 : }
1344 :
1345 0 : char path[ PATH_MAX ];
1346 0 : FD_TEST( fd_cstr_printf_check( path, sizeof(path), NULL, "%s/%s", dir, file ) );
1347 0 : int fd = open( path, O_RDONLY|O_CLOEXEC );
1348 0 : if( FD_UNLIKELY( fd<0 ) ) return;
1349 0 : ctx->sensor.fd [ ctx->sensor.cnt ] = fd;
1350 0 : ctx->sensor.kind[ ctx->sensor.cnt ] = kind;
1351 0 : ctx->sensor.cnt++;
1352 0 : }
1353 :
1354 : static void
1355 0 : sensors_init( fd_diag_tile_t * ctx ) {
1356 0 : ctx->sensor.cnt = 0UL;
1357 0 : ctx->sensor.rapl_cnt = 0UL;
1358 :
1359 0 : int dir_fd = open( "/sys/class/hwmon", O_RDONLY|O_DIRECTORY|O_CLOEXEC );
1360 0 : for(;;) {
1361 0 : if( FD_UNLIKELY( dir_fd<0 ) ) break;
1362 0 : uchar buf[ 4096UL ] __attribute__((aligned(alignof(struct dirent))));
1363 0 : long dents_sz = syscall( SYS_getdents64, dir_fd, buf, sizeof(buf) );
1364 0 : if( FD_UNLIKELY( dents_sz<0L ) ) FD_LOG_WARNING(( "getdents64(/sys/class/hwmon) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
1365 0 : if( dents_sz<=0L ) break;
1366 :
1367 0 : ulong off = 0UL;
1368 0 : while( off<(ulong)dents_sz ) {
1369 0 : struct dirent const * ent = (struct dirent const *)(buf+off);
1370 0 : FD_TEST( ent->d_reclen && off+(ulong)ent->d_reclen<=(ulong)dents_sz );
1371 0 : off += ent->d_reclen;
1372 0 : if( strncmp( ent->d_name, "hwmon", 5UL ) ) continue;
1373 0 : char hw[ PATH_MAX ];
1374 0 : char name[ 32 ];
1375 0 : FD_TEST( fd_cstr_printf_check( hw, sizeof(hw), NULL, "/sys/class/hwmon/%s", ent->d_name ) );
1376 0 : if( sysfs_read_cstr( hw, "name", name, sizeof(name) ) ) continue;
1377 :
1378 0 : if( !strcmp( name, "k10temp" ) || !strcmp( name, "coretemp" ) ) {
1379 0 : for( ulong i=1UL; i<512UL; i++ ) { /* coretemp numbers inputs sparsely by core id */
1380 0 : char file [ 32 ];
1381 0 : char label[ 32 ];
1382 0 : FD_TEST( fd_cstr_printf_check( file, sizeof(file), NULL, "temp%lu_label", i ) );
1383 0 : if( sysfs_read_cstr( hw, file, label, sizeof(label) ) ) continue;
1384 0 : if( strcmp( label, "Tctl" ) && strncmp( label, "Package id", 10UL ) ) continue;
1385 0 : FD_TEST( fd_cstr_printf_check( file, sizeof(file), NULL, "temp%lu_input", i ) );
1386 0 : sensor_add( ctx, hw, file, DIAG_SENSOR_CPU_PACKAGE );
1387 0 : }
1388 0 : }
1389 0 : else if( !strncmp( name, "mlx5", 4UL ) ) sensor_add( ctx, hw, "temp1_input", DIAG_SENSOR_NIC ); /* OFED appends the PCI address */
1390 0 : else if( !strncmp( name, "ice", 3UL ) ) sensor_add( ctx, hw, "temp1_input", DIAG_SENSOR_NIC ); /* out-of-tree drivers append the PCI address */
1391 0 : else if( !strcmp( name, "nvme" ) ) sensor_add( ctx, hw, "temp1_input", DIAG_SENSOR_NVME );
1392 0 : else if( !strcmp( name, "power_meter" ) ) sensor_add( ctx, hw, "power1_average", DIAG_SENSOR_POWER );
1393 0 : }
1394 0 : }
1395 0 : if( FD_LIKELY( dir_fd>=0 ) ) close( dir_fd );
1396 :
1397 0 : for( ulong i=0UL; i<DIAG_EDAC_MC_MAX; i++ ) {
1398 0 : char mc[ 64 ];
1399 0 : FD_TEST( fd_cstr_printf_check( mc, sizeof(mc), NULL, "/sys/devices/system/edac/mc/mc%lu", i ) );
1400 0 : if( access( mc, F_OK ) ) continue; /* ids can have gaps */
1401 0 : sensor_add( ctx, mc, "ce_count", DIAG_SENSOR_ECC_CE );
1402 0 : sensor_add( ctx, mc, "ue_count", DIAG_SENSOR_ECC_UE );
1403 0 : }
1404 :
1405 0 : for( ulong i=0UL; i<DIAG_RAPL_MAX; i++ ) {
1406 0 : char rapl [ 64 ];
1407 0 : char name [ 32 ];
1408 0 : char range[ 32 ];
1409 0 : FD_TEST( fd_cstr_printf_check( rapl, sizeof(rapl), NULL, "/sys/class/powercap/intel-rapl:%lu", i ) );
1410 0 : if( sysfs_read_cstr( rapl, "name", name, sizeof(name) ) ) break;
1411 0 : if( strncmp( name, "package", 7UL ) ) continue; /* psys on some Intel parts */
1412 0 : if( sysfs_read_cstr( rapl, "max_energy_range_uj", range, sizeof(range) ) ) continue;
1413 0 : ulong range_uj = strtoul( range, NULL, 10 );
1414 0 : if( FD_UNLIKELY( !range_uj ) ) continue;
1415 0 : char path[ 96 ];
1416 0 : FD_TEST( fd_cstr_printf_check( path, sizeof(path), NULL, "%s/energy_uj", rapl ) );
1417 0 : int fd = open( path, O_RDONLY|O_CLOEXEC );
1418 0 : if( FD_UNLIKELY( fd<0 ) ) continue;
1419 0 : ctx->sensor.rapl_fd [ ctx->sensor.rapl_cnt ] = fd;
1420 0 : ctx->sensor.rapl_range[ ctx->sensor.rapl_cnt ] = range_uj;
1421 0 : ctx->sensor.rapl_cnt++;
1422 0 : }
1423 0 : ctx->sensor.rapl_prev_nanos = 0L;
1424 :
1425 0 : FD_LOG_INFO(( "diag found %lu hardware sensors and %lu powercap packages", ctx->sensor.cnt, ctx->sensor.rapl_cnt ));
1426 0 : }
1427 :
1428 : /* mount_device finds the mount that holds path in /proc/self/mountinfo
1429 : and copies its device numbers, written "major:minor", into devno.
1430 : Returns 0 if the mount is not found. */
1431 :
1432 : static int
1433 : mount_device( char const * path,
1434 0 : char devno[ static 32 ] ) {
1435 0 : int path_fd = open( path, O_PATH|O_CLOEXEC );
1436 0 : if( FD_UNLIKELY( path_fd<0 ) ) return 0;
1437 0 : ulong mnt_id;
1438 0 : int err = fd_mount_id( path_fd, &mnt_id );
1439 0 : close( path_fd );
1440 0 : if( FD_UNLIKELY( err ) ) return 0;
1441 :
1442 0 : int fd = open( "/proc/self/mountinfo", O_RDONLY|O_CLOEXEC );
1443 0 : if( FD_UNLIKELY( fd<0 ) ) return 0;
1444 0 : static char buf[ 262144 ];
1445 0 : ulong len;
1446 0 : err = fd_io_read( fd, buf, sizeof(buf)-1UL, sizeof(buf)-1UL, &len );
1447 0 : close( fd );
1448 : /* error, or more mounts than fit */
1449 0 : if( FD_UNLIKELY( err>=0 ) ) return 0;
1450 0 : buf[ len ] = '\0';
1451 :
1452 : /* fields: id parent major:minor ... */
1453 0 : char * lines;
1454 0 : for( char * line=strtok_r( buf, "\n", &lines ); line; line=strtok_r( NULL, "\n", &lines ) ) {
1455 0 : char * fields;
1456 0 : char * id = strtok_r( line, " ", &fields );
1457 0 : if( !id || fd_cstr_to_ulong( id )!=mnt_id ) continue;
1458 0 : strtok_r( NULL, " ", &fields ); /* parent */
1459 0 : char * numbers = strtok_r( NULL, " ", &fields );
1460 0 : if( FD_UNLIKELY( !numbers ) ) return 0;
1461 0 : fd_cstr_ncpy( devno, numbers, 32UL );
1462 0 : return 1;
1463 0 : }
1464 0 : return 0;
1465 0 : }
1466 :
1467 : /* Opens the I/O stat file of the block device holding path: the whole
1468 : disk for a partition, an md or dm device as is. Returns -1 if path
1469 : is not on a block device. */
1470 :
1471 : static int
1472 0 : open_disk_stat( char const * path ) {
1473 0 : char dev[ PATH_MAX ];
1474 0 : char devno[ 32 ];
1475 0 : if( !resolve_path_prefix( path, dev ) || !mount_device( dev, devno ) ) return -1;
1476 :
1477 0 : char file[ PATH_MAX ];
1478 0 : FD_TEST( fd_cstr_printf_check( file, sizeof(file), NULL, "/sys/dev/block/%s", devno ) );
1479 0 : if( FD_UNLIKELY( !realpath( file, dev ) ) ) return -1;
1480 0 : FD_TEST( fd_cstr_printf_check( file, sizeof(file), NULL, "%s/partition", dev ) );
1481 0 : if( !access( file, F_OK ) ) *strrchr( dev, '/' ) = '\0';
1482 0 : FD_TEST( fd_cstr_printf_check( file, sizeof(file), NULL, "%s/stat", dev ) );
1483 :
1484 0 : int fd = open( file, O_RDONLY|O_CLOEXEC );
1485 0 : if( FD_LIKELY( fd>=0 ) ) FD_LOG_INFO(( "diag reading disk stats of %s for `%s`", strrchr( dev, '/' )+1, path ));
1486 0 : return fd;
1487 0 : }
1488 :
1489 : static void
1490 : privileged_init( fd_topo_t const * topo,
1491 0 : fd_topo_tile_t const * tile ) {
1492 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
1493 :
1494 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
1495 0 : fd_diag_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_diag_tile_t), sizeof(fd_diag_tile_t) );
1496 0 : memset( ctx->host_cpu_ticks, 0, sizeof(ctx->host_cpu_ticks) );
1497 :
1498 0 : FD_TEST( topo->tile_cnt<=FD_DIAG_SYSTEM_TILE_MAX );
1499 :
1500 0 : FD_TEST( 100L == sysconf( _SC_CLK_TCK ) );
1501 :
1502 0 : ctx->tile_cnt = topo->tile_cnt;
1503 0 : ctx->gui_enabled = fd_topo_find_tile_out_link( topo, tile, "diag_gui", 0UL )!=ULONG_MAX;
1504 0 : ctx->memory.cnt = topo->wksp_cnt;
1505 0 : FD_TEST( ctx->memory.cnt<=FD_TOPO_MAX_WKSPS );
1506 0 : for( ulong wksp_idx=0UL; wksp_idx<ctx->memory.cnt; wksp_idx++ ) {
1507 0 : fd_topo_wksp_t const * wksp = &topo->workspaces[ wksp_idx ];
1508 0 : FD_TEST( fd_cstr_printf_check( ctx->memory.wksp[ wksp_idx ].name,
1509 0 : sizeof(ctx->memory.wksp[ wksp_idx ].name), NULL,
1510 0 : "%s_%s.wksp", topo->app_name, wksp->name ) );
1511 0 : ctx->memory.wksp[ wksp_idx ].numa_idx = wksp->numa_idx;
1512 0 : ctx->memory.wksp[ wksp_idx ].bytes = wksp->page_cnt*wksp->page_sz;
1513 0 : ctx->memory.wksp[ wksp_idx ].numa_slot = USHORT_MAX;
1514 :
1515 0 : ushort owners[ FD_DIAG_SYSTEM_TILE_MAX ];
1516 0 : ulong owner_cnt = 0UL;
1517 0 : for( ulong tile_idx=0UL; tile_idx<topo->tile_cnt; tile_idx++ ) {
1518 0 : fd_topo_tile_t const * owner = &topo->tiles[ tile_idx ];
1519 0 : if( topo->objs[ owner->tile_obj_id ].wksp_id!=wksp_idx ) continue;
1520 0 : owners[ owner_cnt++ ] = (ushort)tile_idx;
1521 0 : }
1522 0 : if( FD_LIKELY( owner_cnt==1UL ) ) {
1523 0 : ctx->memory.wksp[ wksp_idx ].tile_idx = owners[ 0 ];
1524 0 : continue;
1525 0 : }
1526 0 : if( FD_UNLIKELY( owner_cnt>1UL ) ) {
1527 0 : ctx->memory.wksp[ wksp_idx ].tile_idx = DIAG_WKSP_TILE_IDX_SHARED;
1528 0 : continue;
1529 0 : }
1530 :
1531 0 : owner_cnt = 0UL;
1532 0 : for( ulong link_idx=0UL; link_idx<topo->link_cnt; link_idx++ ) {
1533 0 : fd_topo_link_t const * link = &topo->links[ link_idx ];
1534 0 : if( topo->objs[ link->mcache_obj_id ].wksp_id!=wksp_idx &&
1535 0 : ( !link->mtu || topo->objs[ link->dcache_obj_id ].wksp_id!=wksp_idx ) ) continue;
1536 0 : ulong producer_idx = fd_topo_find_link_producer( topo, link );
1537 0 : if( producer_idx==ULONG_MAX ) continue;
1538 0 : int found = 0;
1539 0 : for( ulong owner_idx=0UL; owner_idx<owner_cnt; owner_idx++ )
1540 0 : found |= owners[ owner_idx ]==(ushort)producer_idx;
1541 0 : if( !found ) owners[ owner_cnt++ ] = (ushort)producer_idx;
1542 0 : }
1543 0 : if( FD_LIKELY( owner_cnt==1UL ) ) {
1544 0 : ctx->memory.wksp[ wksp_idx ].tile_idx = owners[ 0 ];
1545 0 : continue;
1546 0 : }
1547 0 : if( FD_UNLIKELY( owner_cnt>1UL ) ) {
1548 0 : ctx->memory.wksp[ wksp_idx ].tile_idx = DIAG_WKSP_TILE_IDX_SHARED;
1549 0 : continue;
1550 0 : }
1551 :
1552 0 : for( ulong tile_idx=0UL; tile_idx<topo->tile_cnt; tile_idx++ ) {
1553 0 : fd_topo_tile_t const * owner = &topo->tiles[ tile_idx ];
1554 0 : int uses_writable = 0;
1555 0 : for( ulong obj_idx=0UL; obj_idx<owner->uses_obj_cnt; obj_idx++ ) {
1556 0 : ulong obj_id = owner->uses_obj_id[ obj_idx ];
1557 0 : uses_writable |= topo->objs[ obj_id ].wksp_id==wksp_idx &&
1558 0 : owner->uses_obj_mode[ obj_idx ]==FD_SHMEM_JOIN_MODE_READ_WRITE;
1559 0 : }
1560 0 : if( uses_writable ) owners[ owner_cnt++ ] = (ushort)tile_idx;
1561 0 : }
1562 0 : ctx->memory.wksp[ wksp_idx ].tile_idx = owner_cnt==1UL ? owners[ 0 ] : DIAG_WKSP_TILE_IDX_SHARED;
1563 0 : }
1564 0 : for( ulong tile_idx=0UL; tile_idx<FD_DIAG_SYSTEM_TILE_MAX; tile_idx++ )
1565 0 : ctx->memory.stack_numa_slot[ tile_idx ] = USHORT_MAX;
1566 0 : ctx->mount_cnt = 0UL;
1567 0 : ctx->file_cnt = 0UL;
1568 0 : for( ulong i=0UL; i<FD_TILE_MAX; i++ ) {
1569 0 : ctx->stat_fds[ i ] = -1;
1570 0 : ctx->sched_fds[ i ] = -1;
1571 0 : ctx->schedstat_fds[ i ] = -1;
1572 0 : }
1573 0 : for( ulong i=0UL; i<FD_DIAG_SYSTEM_NUMA_MAX; i++ ) ctx->numa.node[ i ].meminfo_fd = -1;
1574 :
1575 0 : for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
1576 0 : ulong * metrics = fd_metrics_join( fd_topo_obj_laddr( topo, topo->tiles[ i ].metrics_obj_id ) );
1577 :
1578 0 : for(;;) {
1579 0 : ulong pid, tid;
1580 0 : if( FD_UNLIKELY( tile->id==i ) ) {
1581 0 : pid = fd_sandbox_getpid();
1582 0 : tid = fd_sandbox_gettid();
1583 0 : } else {
1584 0 : pid = fd_metrics_tile( metrics )[ FD_METRICS_GAUGE_TILE_PID_OFF ];
1585 0 : tid = fd_metrics_tile( metrics )[ FD_METRICS_GAUGE_TILE_TID_OFF ];
1586 0 : if( FD_UNLIKELY( !pid || !tid ) ) {
1587 0 : FD_SPIN_PAUSE();
1588 0 : continue;
1589 0 : }
1590 0 : }
1591 :
1592 0 : ctx->metrics[ i ] = fd_metrics_tile( metrics );
1593 :
1594 0 : char path[ 64UL ];
1595 0 : FD_TEST( fd_cstr_printf_check( path, sizeof( path ), NULL, "/proc/%lu/task/%lu/stat", pid, tid ) );
1596 0 : ctx->stat_fds[ i ] = open( path, O_RDONLY );
1597 0 : if( FD_UNLIKELY( -1==ctx->stat_fds[ i ] ) ) {
1598 : /* Might be a tile that's allowed to shutdown already did so
1599 : before we got to here, due to a race condition. Just
1600 : proceed, we will not be able to get metrics for the shut
1601 : down process. */
1602 0 : if( FD_LIKELY( 2UL!=ctx->metrics[ i ][ FD_METRICS_GAUGE_TILE_STATE_OFF ] ) ) FD_LOG_ERR(( "open stat failed (%i-%s)", errno, strerror( errno ) ));
1603 0 : break;
1604 0 : }
1605 :
1606 0 : FD_TEST( fd_cstr_printf_check( path, sizeof( path ), NULL, "/proc/%lu/task/%lu/sched", pid, tid ) );
1607 0 : ctx->sched_fds[ i ] = open( path, O_RDONLY );
1608 0 : if( FD_UNLIKELY( -1==ctx->sched_fds[ i ] ) ) {
1609 0 : if( FD_LIKELY( 2UL!=ctx->metrics[ i ][ FD_METRICS_GAUGE_TILE_STATE_OFF ] ) ) FD_LOG_ERR(( "open sched failed (%i-%s)", errno, strerror( errno ) ));
1610 0 : ctx->stat_fds[ i ] = -1;
1611 0 : break;
1612 0 : }
1613 :
1614 : /* Absent on kernels without CONFIG_SCHED_INFO: wait then reads 0,
1615 : as it did without CONFIG_SCHEDSTATS. */
1616 0 : FD_TEST( fd_cstr_printf_check( path, sizeof( path ), NULL, "/proc/%lu/task/%lu/schedstat", pid, tid ) );
1617 0 : ctx->schedstat_fds[ i ] = open( path, O_RDONLY );
1618 0 : if( FD_UNLIKELY( -1==ctx->schedstat_fds[ i ] && errno!=ENOENT ) ) {
1619 0 : if( FD_LIKELY( 2UL!=ctx->metrics[ i ][ FD_METRICS_GAUGE_TILE_STATE_OFF ] ) ) FD_LOG_ERR(( "open schedstat failed (%i-%s)", errno, strerror( errno ) ));
1620 0 : ctx->stat_fds[ i ] = -1;
1621 0 : }
1622 0 : break;
1623 0 : }
1624 0 : }
1625 :
1626 0 : ctx->proc_interrupts_fd = open( "/proc/interrupts", O_RDONLY );
1627 0 : if( FD_UNLIKELY( -1==ctx->proc_interrupts_fd ) ) FD_LOG_ERR(( "open(/proc/interrupts) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
1628 :
1629 0 : ctx->proc_softirqs_fd = open( "/proc/softirqs", O_RDONLY );
1630 0 : if( FD_UNLIKELY( -1==ctx->proc_softirqs_fd ) ) FD_LOG_ERR(( "open(/proc/softirqs) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
1631 :
1632 0 : ctx->proc_stat_fd = open( "/proc/stat", O_RDONLY );
1633 0 : if( FD_UNLIKELY( -1==ctx->proc_stat_fd ) ) FD_LOG_ERR(( "open(/proc/stat) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
1634 :
1635 0 : ctx->proc_meminfo_fd = open( "/proc/meminfo", O_RDONLY );
1636 0 : if( FD_UNLIKELY( -1==ctx->proc_meminfo_fd ) ) FD_LOG_ERR(( "open(/proc/meminfo) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
1637 :
1638 0 : ctx->proc_loadavg_fd = open( "/proc/loadavg", O_RDONLY );
1639 0 : ctx->proc_vmstat_fd = open( "/proc/vmstat", O_RDONLY );
1640 0 : static char const * const psi_path[ 3 ] = { "/proc/pressure/cpu", "/proc/pressure/memory", "/proc/pressure/io" };
1641 0 : for( ulong i=0UL; i<3UL; i++ ) ctx->psi_fd[ i ] = open( psi_path[ i ], O_RDONLY );
1642 :
1643 0 : sensors_init( ctx );
1644 :
1645 : /* The ms counters count from here, so the first sample is not the
1646 : kernel's since-boot value. */
1647 0 : ctx->disk_fd = open_disk_stat( tile->diag.accounts_path );
1648 0 : memset( ctx->disk_ms, 0, sizeof(ctx->disk_ms) );
1649 0 : ulong f[ 11 ];
1650 0 : if( !disk_stat_read( ctx, f ) ) {
1651 0 : ctx->disk_ms[ 0 ] = f[ 7 ];
1652 0 : ctx->disk_ms[ 1 ] = f[ 9 ];
1653 0 : ctx->disk_ms[ 2 ] = f[ 10 ];
1654 0 : }
1655 0 : memcpy( ctx->disk_ms_base, ctx->disk_ms, sizeof(ctx->disk_ms_base) );
1656 :
1657 0 : ctx->numa.cnt = 0UL;
1658 0 : if( FD_UNLIKELY( !ctx->gui_enabled ) ) return;
1659 :
1660 0 : fd_topo_cpus_t cpus[ 1 ];
1661 0 : fd_topo_cpus_init( cpus );
1662 0 : ctx->system_resources.cpu_cnt = (uint)fd_ulong_min( cpus->cpu_cnt, FD_DIAG_SYSTEM_CPU_MAX );
1663 0 : for( ulong i=0UL; i<ctx->system_resources.cpu_cnt; i++ ) {
1664 0 : fd_diag_system_cpu_init( &ctx->system_resources.cpu[ i ], &cpus->cpu[ i ] );
1665 0 : }
1666 :
1667 0 : for( ulong numa_idx=0UL; numa_idx<cpus->numa_node_cnt && ctx->numa.cnt<FD_DIAG_SYSTEM_NUMA_MAX; numa_idx++ ) {
1668 0 : char path[ 128 ];
1669 0 : FD_TEST( fd_cstr_printf_check( path, sizeof(path), NULL, "/sys/devices/system/node/node%lu/meminfo", numa_idx ) );
1670 0 : int fd = open( path, O_RDONLY );
1671 0 : if( FD_UNLIKELY( fd<0 ) ) {
1672 0 : if( FD_UNLIKELY( errno!=ENOENT ) ) FD_LOG_WARNING(( "open `%s` failed (%i-%s)", path, errno, strerror( errno ) ));
1673 0 : continue;
1674 0 : }
1675 0 : ctx->numa.node[ ctx->numa.cnt ].idx = (ushort)numa_idx;
1676 0 : ctx->numa.node[ ctx->numa.cnt ].meminfo_fd = fd;
1677 0 : ctx->numa.cnt++;
1678 0 : }
1679 :
1680 0 : for( ulong wksp_idx=0UL; wksp_idx<ctx->memory.cnt; wksp_idx++ ) {
1681 0 : ulong numa_slot = 0UL;
1682 0 : while( numa_slot<ctx->numa.cnt && ctx->numa.node[ numa_slot ].idx!=ctx->memory.wksp[ wksp_idx ].numa_idx ) numa_slot++;
1683 0 : if( FD_UNLIKELY( numa_slot==ctx->numa.cnt ) ) {
1684 0 : FD_LOG_WARNING(( "workspace `%s` is assigned to unavailable NUMA node %lu; omitting it from system memory reporting",
1685 0 : ctx->memory.wksp[ wksp_idx ].name, ctx->memory.wksp[ wksp_idx ].numa_idx ));
1686 0 : continue;
1687 0 : }
1688 0 : ctx->memory.wksp[ wksp_idx ].numa_slot = (ushort)numa_slot;
1689 0 : }
1690 :
1691 0 : for( ulong tile_idx=0UL; tile_idx<topo->tile_cnt; tile_idx++ ) {
1692 : /* Keep this placement rule in sync with initialize_stacks(). */
1693 0 : ulong stack_cpu_idx = topo->tiles[ tile_idx ].cpu_idx<65535UL ? topo->tiles[ tile_idx ].cpu_idx : 0UL;
1694 0 : FD_TEST( stack_cpu_idx<cpus->cpu_cnt );
1695 0 : ulong stack_numa_idx = cpus->cpu[ stack_cpu_idx ].numa_node;
1696 0 : ulong numa_slot = 0UL;
1697 0 : while( numa_slot<ctx->numa.cnt && ctx->numa.node[ numa_slot ].idx!=stack_numa_idx ) numa_slot++;
1698 0 : if( FD_UNLIKELY( numa_slot==ctx->numa.cnt ) ) {
1699 0 : FD_LOG_WARNING(( "stack for tile %s:%lu is assigned to unavailable NUMA node %lu; omitting it from system memory reporting",
1700 0 : topo->tiles[ tile_idx ].name, topo->tiles[ tile_idx ].kind_id, stack_numa_idx ));
1701 0 : continue;
1702 0 : }
1703 0 : ctx->memory.stack_numa_slot[ tile_idx ] = (ushort)numa_slot;
1704 0 : }
1705 :
1706 0 : ulong accdb_idx = fd_topo_find_tile( topo, "accdb", 0UL );
1707 0 : if( tile->diag.accounts_path[ 0 ] && accdb_idx!=ULONG_MAX )
1708 0 : add_file( ctx, FD_DIAG_SYSTEM_FILE_CATEGORY_ACCOUNTS, tile->diag.accounts_path, -1, ctx->metrics[ accdb_idx ] + FD_METRICS_GAUGE_ACCDB_DISK_ALLOCATED_BYTES_OFF );
1709 0 : ulong rserve_idx = fd_topo_find_tile( topo, "rserve", 0UL );
1710 0 : if( tile->diag.shreds_path[ 0 ] && rserve_idx!=ULONG_MAX )
1711 0 : add_file( ctx, FD_DIAG_SYSTEM_FILE_CATEGORY_SHREDS, tile->diag.shreds_path, -1, ctx->metrics[ rserve_idx ] + FD_METRICS_GAUGE_RSERVE_DISK_PEAK_BYTES_OFF );
1712 0 : ulong snapmk_idx = fd_topo_find_tile( topo, "snapmk", 0UL );
1713 0 : if( tile->diag.snapshots_path[ 0 ] && snapmk_idx!=ULONG_MAX )
1714 0 : add_file( ctx, FD_DIAG_SYSTEM_FILE_CATEGORY_SNAPSHOTS, tile->diag.snapshots_path, -1, ctx->metrics[ snapmk_idx ] + FD_METRICS_GAUGE_SNAPMK_DISK_ALLOCATED_BYTES_OFF );
1715 0 : ulong gui_idx = fd_topo_find_tile( topo, "gui", 0UL );
1716 0 : if( gui_idx!=ULONG_MAX )
1717 0 : add_file( ctx, FD_DIAG_SYSTEM_FILE_CATEGORY_GUI, tile->diag.gui_path, -1, ctx->metrics[ gui_idx ] + FD_METRICS_GAUGE_GUI_DISK_ALLOCATED_BYTES_OFF );
1718 0 : int logfile_fd = fd_log_private_logfile_fd();
1719 0 : if( logfile_fd>=0 ) add_file( ctx, FD_DIAG_SYSTEM_FILE_CATEGORY_LOGS, tile->diag.log_path, logfile_fd, NULL );
1720 0 : }
1721 :
1722 : /* Read starttime (field 22) from stat file. Returns 0 on success, 1 if
1723 : process died (ESRCH). */
1724 :
1725 : static int
1726 : read_starttime( int fd,
1727 : ulong ns_per_tick,
1728 0 : ulong * out_starttime_nanos ) {
1729 0 : char contents[ 4096 ] = {0};
1730 0 : ulong contents_len = 0UL;
1731 :
1732 0 : while( 1 ) {
1733 0 : if( FD_UNLIKELY( contents_len>=sizeof( contents ) ) ) FD_LOG_ERR(( "stat contents overflow" ));
1734 0 : long n = read( fd, contents + contents_len, sizeof( contents ) - contents_len );
1735 0 : if( FD_UNLIKELY( -1==n ) ) {
1736 0 : if( FD_UNLIKELY( errno==ESRCH ) ) return 1;
1737 0 : FD_LOG_ERR(( "read stat failed (%i-%s)", errno, strerror( errno ) ));
1738 0 : }
1739 0 : if( FD_LIKELY( 0L==n ) ) break;
1740 0 : contents_len += (ulong)n;
1741 0 : }
1742 :
1743 : /* Parse field 22 (starttime) from stat file */
1744 0 : char * saveptr;
1745 0 : char * token = strtok_r( contents, " ", &saveptr );
1746 0 : ulong field_idx = 0UL;
1747 :
1748 0 : while( token && field_idx<21UL ) {
1749 0 : token = strtok_r( NULL, " ", &saveptr );
1750 0 : field_idx++;
1751 0 : }
1752 :
1753 0 : if( FD_UNLIKELY( !token || field_idx!=21UL ) ) FD_LOG_ERR(( "starttime (field 22) not found in stat" ));
1754 :
1755 0 : char * endptr;
1756 0 : ulong starttime_ticks = strtoul( token, &endptr, 10 );
1757 0 : if( FD_UNLIKELY( *endptr!=' ' && *endptr!='\0' ) ) FD_LOG_ERR(( "strtoul failed for starttime" ));
1758 0 : if( FD_UNLIKELY( starttime_ticks==ULONG_MAX ) ) FD_LOG_ERR(( "strtoul overflow for starttime" ));
1759 :
1760 0 : *out_starttime_nanos = starttime_ticks * ns_per_tick;
1761 0 : return 0;
1762 0 : }
1763 :
1764 : static void
1765 : unprivileged_init( fd_topo_t const * topo,
1766 0 : fd_topo_tile_t const * tile ) {
1767 0 : fd_diag_tile_t * ctx = fd_topo_obj_laddr( topo, tile->tile_obj_id );
1768 :
1769 0 : memset( ctx->first_seen_died, 0, sizeof( ctx->first_seen_died ) );
1770 0 : fd_clock_tile_init( ctx->clock );
1771 0 : ctx->next_report_nanos = fd_clock_tile_now( ctx->clock );
1772 0 : ctx->next_sched_report_nanos = ctx->next_report_nanos;
1773 0 : ctx->next_irq_report_nanos = ctx->next_report_nanos;
1774 0 : ctx->next_system_report_nanos = ctx->next_report_nanos;
1775 0 : sensor_metrics( ctx, ctx->next_report_nanos ); /* RAPL baseline, so the first report has package power */
1776 0 : if( FD_UNLIKELY( ctx->gui_enabled ) ) {
1777 0 : ulong out_idx = fd_topo_find_tile_out_link( topo, tile, "diag_gui", 0UL );
1778 0 : FD_TEST( out_idx!=ULONG_MAX );
1779 0 : fd_topo_link_t const * link = &topo->links[ tile->out_link_id[ out_idx ] ];
1780 0 : ctx->system_out.idx = out_idx;
1781 0 : ctx->system_out.mem = topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ].wksp;
1782 0 : ctx->system_out.chunk0 = fd_dcache_compact_chunk0( ctx->system_out.mem, link->dcache );
1783 0 : ctx->system_out.wmark = fd_dcache_compact_wmark ( ctx->system_out.mem, link->dcache, link->mtu );
1784 0 : ctx->system_out.chunk = ctx->system_out.chunk0;
1785 0 : }
1786 :
1787 : /* Snapshot the cumulative-since-boot /proc interrupt/softirq counters
1788 : so the metrics we report are counted since process startup. */
1789 0 : memset( ctx->softirq_baseline, 0, sizeof( ctx->softirq_baseline ) );
1790 0 : memset( ctx->device_irq_baseline, 0, sizeof( ctx->device_irq_baseline ) );
1791 0 : memset( ctx->tlb_baseline, 0, sizeof( ctx->tlb_baseline ) );
1792 0 : memset( ctx->loc_baseline, 0, sizeof( ctx->loc_baseline ) );
1793 0 : if( FD_UNLIKELY( -1==lseek( ctx->proc_softirqs_fd, 0, SEEK_SET ) ) ) FD_LOG_ERR(( "lseek failed (%i-%s)", errno, strerror( errno ) ));
1794 0 : ulong softirq_cpu_cnt = fd_proc_softirqs_sum( ctx->proc_softirqs_fd, ctx->softirq_baseline );
1795 0 : if( FD_UNLIKELY( !softirq_cpu_cnt ) ) FD_LOG_WARNING(( "failed to read softirq baseline from /proc/softirqs" ));
1796 :
1797 0 : if( FD_UNLIKELY( -1==lseek( ctx->proc_interrupts_fd, 0, SEEK_SET ) ) ) FD_LOG_ERR(( "lseek failed (%i-%s)", errno, strerror( errno ) ));
1798 0 : ulong interrupt_cpu_cnt = fd_proc_interrupts_read( ctx->proc_interrupts_fd,
1799 0 : ctx->device_irq_baseline,
1800 0 : ctx->tlb_baseline,
1801 0 : ctx->loc_baseline );
1802 0 : if( FD_UNLIKELY( !interrupt_cpu_cnt ) ) FD_LOG_WARNING(( "failed to read IRQ baselines from /proc/interrupts" ));
1803 :
1804 0 : memset( ctx->irq_ticks_baseline, 0, sizeof( ctx->irq_ticks_baseline ) );
1805 0 : if( FD_UNLIKELY( -1==lseek( ctx->proc_stat_fd, 0, SEEK_SET ) ) ) FD_LOG_ERR(( "lseek failed (%i-%s)", errno, strerror( errno ) ));
1806 0 : ulong stat_cpu_cnt = fd_proc_stat_irq_ticks( ctx->proc_stat_fd, ctx->irq_ticks_baseline );
1807 0 : if( FD_UNLIKELY( !stat_cpu_cnt ) ) FD_LOG_WARNING(( "failed to read irq tick baseline from /proc/stat" ));
1808 0 : char stat_buf[ 16384 ];
1809 0 : if( FD_UNLIKELY( host_cpu_ticks( ctx, stat_buf, sizeof(stat_buf), ctx->host_cpu_baseline ) ) ) memset( ctx->host_cpu_baseline, 0, sizeof(ctx->host_cpu_baseline) );
1810 0 : memcpy( ctx->softirq_boot, ctx->softirq_baseline, sizeof(ctx->softirq_boot) );
1811 0 : memcpy( ctx->device_irq_boot, ctx->device_irq_baseline, sizeof(ctx->device_irq_boot) );
1812 0 : memset( ctx->on_tile_softirq, 0, sizeof(ctx->on_tile_softirq) );
1813 0 : ctx->on_tile_device_irq = 0UL;
1814 0 : memcpy( ctx->owner_irq_baseline, ctx->device_irq_baseline, sizeof(ctx->owner_irq_baseline) );
1815 0 : memcpy( ctx->owner_tlb_baseline, ctx->tlb_baseline, sizeof(ctx->owner_tlb_baseline) );
1816 0 : memcpy( ctx->owner_loc_baseline, ctx->loc_baseline, sizeof(ctx->owner_loc_baseline) );
1817 0 : memcpy( ctx->owner_tick_baseline, ctx->irq_ticks_baseline, sizeof(ctx->owner_tick_baseline) );
1818 0 : fd_cpuset_new( ctx->irq_rebase );
1819 0 : fd_cpuset_new( ctx->tick_rebase );
1820 :
1821 : /* Read starttime (field 22) once at init for idle time calculation.
1822 : CLK_TCK is always 100, so 1 tick = 10ms = 10,000,000 ns. */
1823 0 : for( ulong i=0UL; i<ctx->tile_cnt; i++ ) {
1824 0 : if( FD_LIKELY( -1!=ctx->stat_fds[ i ] ) ) {
1825 0 : int died = read_starttime( ctx->stat_fds[ i ], 10000000UL, &ctx->starttime_nanos[ i ] );
1826 0 : if( FD_UNLIKELY( died ) ) ctx->stat_fds[ i ] = -1;
1827 0 : }
1828 0 : }
1829 :
1830 0 : memset( &ctx->check_engine, 0, sizeof(ctx->check_engine) );
1831 :
1832 0 : ctx->tiles.bundle_cnt = fd_topo_tile_name_cnt( topo, "bundle" );
1833 0 : for( ulong i=0UL; i<ctx->tiles.bundle_cnt; i++ ) ctx->tiles.bundle_tile_idx[ i ] = fd_topo_find_tile( topo, "bundle", i );
1834 0 : ctx->tiles.shred_cnt = fd_topo_tile_name_cnt( topo, "shred" );
1835 0 : for( ulong i=0UL; i<ctx->tiles.shred_cnt; i++ ) ctx->tiles.shred_tile_idx[ i ] = fd_topo_find_tile( topo, "shred", i );
1836 0 : ctx->tiles.votor_idx = fd_topo_find_tile( topo, "votor", 0UL );
1837 0 : ctx->tiles.replay_idx = fd_topo_find_tile( topo, "replay", 0UL );
1838 :
1839 0 : for( ulong i=0UL; i<FD_TILE_MAX; i++ ) ctx->cpu_to_tile[ i ] = USHORT_MAX;
1840 0 : cpu_owner_refresh( ctx, topo );
1841 :
1842 0 : ctx->is_voting = tile->diag.is_voting;
1843 0 : ctx->check_engine.reset_slot_changed_ns = ctx->next_report_nanos;
1844 0 : ctx->check_engine.turbine_slot_changed_ns = ctx->next_report_nanos;
1845 0 : ctx->check_engine.byte_snapshot_ns = ctx->next_report_nanos;
1846 0 : }
1847 :
1848 : static ulong
1849 : populate_allowed_seccomp( fd_topo_t const * topo,
1850 : fd_topo_tile_t const * tile,
1851 : ulong out_cnt,
1852 0 : struct sock_filter * out ) {
1853 0 : (void)topo;
1854 0 : (void)tile;
1855 :
1856 0 : populate_sock_filter_policy_fd_diag_tile( out_cnt, out, (uint)fd_log_private_logfile_fd() );
1857 0 : return sock_filter_policy_fd_diag_tile_instr_cnt;
1858 0 : }
1859 :
1860 : static ulong
1861 : populate_allowed_fds( fd_topo_t const * topo,
1862 : fd_topo_tile_t const * tile,
1863 : ulong out_fds_cnt,
1864 0 : int * out_fds ) {
1865 0 : fd_diag_tile_t * ctx = fd_topo_obj_laddr( topo, tile->tile_obj_id );
1866 :
1867 0 : int logfile_fd = fd_log_private_logfile_fd();
1868 0 : ulong required_fds = 11UL+3UL*ctx->tile_cnt+ctx->numa.cnt+ctx->mount_cnt+ctx->sensor.cnt+ctx->sensor.rapl_cnt+(ulong)(-1!=logfile_fd);
1869 0 : for( ulong i=0UL; i<ctx->file_cnt; i++ )
1870 0 : required_fds += (ulong)( ctx->files[ i ].data_fd>=0 && ctx->files[ i ].data_fd!=logfile_fd );
1871 0 : if( FD_UNLIKELY( out_fds_cnt<required_fds ) ) FD_LOG_ERR(( "out_fds_cnt %lu", out_fds_cnt ));
1872 :
1873 0 : ulong out_cnt = 0UL;
1874 0 : out_fds[ out_cnt++ ] = 2; /* stderr */
1875 0 : if( FD_LIKELY( -1!=logfile_fd ) )
1876 0 : out_fds[ out_cnt++ ] = logfile_fd; /* logfile */
1877 0 : out_fds[ out_cnt++ ] = ctx->proc_interrupts_fd; /* /proc/interrupts */
1878 0 : out_fds[ out_cnt++ ] = ctx->proc_softirqs_fd; /* /proc/softirqs */
1879 0 : out_fds[ out_cnt++ ] = ctx->proc_stat_fd; /* /proc/stat */
1880 0 : out_fds[ out_cnt++ ] = ctx->proc_meminfo_fd; /* /proc/meminfo */
1881 0 : if( ctx->proc_loadavg_fd>=0 ) out_fds[ out_cnt++ ] = ctx->proc_loadavg_fd; /* /proc/loadavg */
1882 0 : if( ctx->proc_vmstat_fd >=0 ) out_fds[ out_cnt++ ] = ctx->proc_vmstat_fd; /* /proc/vmstat */
1883 0 : for( ulong i=0UL; i<3UL; i++ ) if( ctx->psi_fd[ i ]>=0 ) out_fds[ out_cnt++ ] = ctx->psi_fd[ i ]; /* /proc/pressure */
1884 0 : if( ctx->disk_fd>=0 ) out_fds[ out_cnt++ ] = ctx->disk_fd; /* /sys/block/<dev>/stat */
1885 0 : for( ulong i=0UL; i<ctx->tile_cnt; i++ ) {
1886 0 : if( -1!=ctx->stat_fds[ i ] ) out_fds[ out_cnt++ ] = ctx->stat_fds[ i ]; /* /proc/<pid>/task/<tid>/stat */
1887 0 : if( -1!=ctx->sched_fds[ i ] ) out_fds[ out_cnt++ ] = ctx->sched_fds[ i ]; /* /proc/<pid>/task/<tid>/sched */
1888 0 : if( -1!=ctx->schedstat_fds[ i ] ) out_fds[ out_cnt++ ] = ctx->schedstat_fds[ i ]; /* /proc/<pid>/task/<tid>/schedstat */
1889 0 : }
1890 0 : for( ulong i=0UL; i<ctx->numa.cnt; i++ )
1891 0 : if( ctx->numa.node[ i ].meminfo_fd>=0 ) out_fds[ out_cnt++ ] = ctx->numa.node[ i ].meminfo_fd;
1892 0 : for( ulong i=0UL; i<ctx->mount_cnt; i++ ) out_fds[ out_cnt++ ] = ctx->mounts[ i ].fd;
1893 0 : for( ulong i=0UL; i<ctx->sensor.cnt; i++ ) out_fds[ out_cnt++ ] = ctx->sensor.fd [ i ]; /* hwmon */
1894 0 : for( ulong i=0UL; i<ctx->sensor.rapl_cnt; i++ ) out_fds[ out_cnt++ ] = ctx->sensor.rapl_fd[ i ]; /* powercap energy_uj */
1895 0 : for( ulong i=0UL; i<ctx->file_cnt; i++ )
1896 0 : if( ctx->files[ i ].data_fd>=0 && ctx->files[ i ].data_fd!=logfile_fd )
1897 0 : out_fds[ out_cnt++ ] = ctx->files[ i ].data_fd;
1898 0 : return out_cnt;
1899 0 : }
1900 :
1901 0 : #define STEM_BURST (1UL)
1902 0 : #define STEM_LAZY ((long)10e6) /* 10ms */
1903 : #define STEM_ALWAYS_PARK 1
1904 :
1905 0 : #define STEM_CALLBACK_CONTEXT_TYPE fd_diag_tile_t
1906 0 : #define STEM_CALLBACK_CONTEXT_ALIGN alignof(fd_diag_tile_t)
1907 :
1908 0 : #define STEM_CALLBACK_NEXT_DEADLINE next_deadline
1909 0 : #define STEM_CALLBACK_DURING_HOUSEKEEPING during_housekeeping
1910 0 : #define STEM_CALLBACK_BEFORE_CREDIT before_credit
1911 :
1912 : #include "../../disco/stem/fd_stem.c"
1913 :
1914 :
1915 : fd_topo_run_tile_t fd_tile_diag = {
1916 : .name = "diag",
1917 : .populate_allowed_seccomp = populate_allowed_seccomp,
1918 : .populate_allowed_fds = populate_allowed_fds,
1919 : .scratch_align = scratch_align,
1920 : .scratch_footprint = scratch_footprint,
1921 : .privileged_init = privileged_init,
1922 : .unprivileged_init = unprivileged_init,
1923 : .run = stem_run,
1924 : };
|