* [PATCH 0/3] tools/accounting: refactor delay fields and share format_timespec()
@ 2026-07-11 9:31 wang.yaxin
2026-07-11 9:32 ` [PATCH 1/3] delaytop: refactor repetitive delay fields into array with enum wang.yaxin
` (3 more replies)
0 siblings, 4 replies; 9+ messages in thread
From: wang.yaxin @ 2026-07-11 9:31 UTC (permalink / raw)
To: akpm, fan.yu9, yang.yang29
Cc: corbet, linux-kernel, linux-doc, xu.xin16, wang.yaxin
From: Wang Yaxin <wang.yaxin@zte.com.cn>
- Convert per-field delay members in struct task_info to an array indexed
by enum delay_type, eliminating offsetof() pointer arithmetic.
- Factor out a common format_timespec() implementation shared by getdelays
and delaytop, using strftime for cleaner timestamp formatting.
- Replace the complex sizeof/ULL/shift Y2038 guard with a direct narrowing
truncation check ((long long)time_sec != ts->tv_sec).
Wang Yaxin (3):
delaytop: refactor repetitive delay fields into array with enum
tools/accounting: factor out shared format_timespec() implementation
tools/accounting: simplify 32-bit time_t overflow check in
format_timespec()
tools/accounting/Makefile | 12 +-
tools/accounting/delaytop.c | 261 +++++++++-----------------
tools/accounting/format_timespec.c | 37 ++++
tools/accounting/format_timespec.h | 9 +
tools/accounting/getdelays.c | 32 +---
tools/include/uapi/linux/time_types.h | 18 ++
6 files changed, 169 insertions(+), 200 deletions(-)
create mode 100644 tools/accounting/format_timespec.c
create mode 100644 tools/accounting/format_timespec.h
create mode 100644 tools/include/uapi/linux/time_types.h
--
2.27.0
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH 1/3] delaytop: refactor repetitive delay fields into array with enum 2026-07-11 9:31 [PATCH 0/3] tools/accounting: refactor delay fields and share format_timespec() wang.yaxin @ 2026-07-11 9:32 ` wang.yaxin 2026-07-14 3:07 ` xu.xin16 2026-07-11 9:33 ` [PATCH 2/3] tools/accounting: factor out shared format_timespec() implementation wang.yaxin ` (2 subsequent siblings) 3 siblings, 1 reply; 9+ messages in thread From: wang.yaxin @ 2026-07-11 9:32 UTC (permalink / raw) To: wang.yaxin Cc: akpm, fan.yu9, yang.yang29, corbet, linux-kernel, linux-doc, xu.xin16 From: Wang Yaxin <wang.yaxin@zte.com.cn> Replace the 9 groups of (count, delay_total, delay_max, delay_max_ts) named fields in struct task_info with a struct delay_metrics array indexed by enum delay_type. This eliminates all unsafe pointer arithmetic via offsetof() from compare_tasks(), field_delay_max_and_ts(), and get_field_delay_values(). The struct field_desc now stores an enum delay_type index instead of four separate unsigned long offset values. Signed-off-by: Wang Yaxin <wang.yaxin@zte.com.cn> --- tools/accounting/delaytop.c | 220 +++++++++++++++--------------------- 1 file changed, 88 insertions(+), 132 deletions(-) diff --git a/tools/accounting/delaytop.c b/tools/accounting/delaytop.c index 1c40bb477320..f1d26ff98792 100644 --- a/tools/accounting/delaytop.c +++ b/tools/accounting/delaytop.c @@ -61,31 +61,51 @@ #define MAX_MSG_SIZE 1024 #define MAX_TASKS 1000 #define MAX_BUF_LEN 256 -#define SET_TASK_STAT(task_count, field) tasks[task_count].field = stats.field #define BOOL_FPRINT(stream, fmt, ...) \ ({ \ int ret = fprintf(stream, fmt, ##__VA_ARGS__); \ ret >= 0; \ }) -#define TASK_AVG(task, field) average_ms((task).field##_delay_total, (task).field##_count) +#define TASK_AVG(task, type) \ + average_ms((task).delays[DELAY_##type].delay_total, \ + (task).delays[DELAY_##type].count) #define PSI_LINE_FORMAT "%-12s %6.1f%%/%6.1f%%/%6.1f%%/%8llu(ms)\n" #define DELAY_FMT_DEFAULT "%8.2f %8.2f %8.2f %8.2f\n" #define DELAY_FMT_MEMVERBOSE "%8.2f %8.2f %8.2f %8.2f %8.2f %8.2f\n" -#define SORT_FIELD(name, cmd, modes) \ - {#name, #cmd, \ - offsetof(struct task_info, name##_delay_total), \ - offsetof(struct task_info, name##_count), \ - offsetof(struct task_info, name##_delay_max), \ - offsetof(struct task_info, name##_delay_max_ts), \ - modes} -#define SORT_FIELD_NO_MAX(name, cmd, modes) \ - {#name, #cmd, \ - offsetof(struct task_info, name##_delay_total), \ - offsetof(struct task_info, name##_count), \ - 0, \ - 0, \ - modes} -#define END_FIELD {NULL, 0, 0, 0, 0, 0, 0} +#define COPY_DELAY(task_idx, type, stats_prefix) \ + do { \ + tasks[task_idx].delays[DELAY_##type].count = \ + stats.stats_prefix##_count; \ + tasks[task_idx].delays[DELAY_##type].delay_total = \ + stats.stats_prefix##_delay_total; \ + tasks[task_idx].delays[DELAY_##type].delay_max = \ + stats.stats_prefix##_delay_max; \ + tasks[task_idx].delays[DELAY_##type].delay_max_ts = \ + stats.stats_prefix##_delay_max_ts; \ + } while (0) +#define SORT_FIELD(name, cmd, type, modes, has_max) \ + {#name, #cmd, type, modes, has_max} +#define END_FIELD {NULL, 0, 0, 0, false} + +enum delay_type { + DELAY_CPU, + DELAY_BLKIO, + DELAY_SWAPIN, + DELAY_FREEPAGES, + DELAY_THRASHING, + DELAY_COMPACT, + DELAY_WPCOPY, + DELAY_IRQ, + DELAY_MEM, + NUM_DELAY_TYPES +}; + +struct delay_metrics { + unsigned long long count; + unsigned long long delay_total; + unsigned long long delay_max; + struct __kernel_timespec delay_max_ts; +}; /* Display mode types */ #define MODE_TYPE_ALL (0xFFFFFFFF) @@ -116,40 +136,7 @@ struct task_info { int pid; int tgid; char command[TASK_COMM_LEN]; - unsigned long long cpu_count; - unsigned long long cpu_delay_total; - unsigned long long cpu_delay_max; - struct __kernel_timespec cpu_delay_max_ts; - unsigned long long blkio_count; - unsigned long long blkio_delay_total; - unsigned long long blkio_delay_max; - struct __kernel_timespec blkio_delay_max_ts; - unsigned long long swapin_count; - unsigned long long swapin_delay_total; - unsigned long long swapin_delay_max; - struct __kernel_timespec swapin_delay_max_ts; - unsigned long long freepages_count; - unsigned long long freepages_delay_total; - unsigned long long freepages_delay_max; - struct __kernel_timespec freepages_delay_max_ts; - unsigned long long thrashing_count; - unsigned long long thrashing_delay_total; - unsigned long long thrashing_delay_max; - struct __kernel_timespec thrashing_delay_max_ts; - unsigned long long compact_count; - unsigned long long compact_delay_total; - unsigned long long compact_delay_max; - struct __kernel_timespec compact_delay_max_ts; - unsigned long long wpcopy_count; - unsigned long long wpcopy_delay_total; - unsigned long long wpcopy_delay_max; - struct __kernel_timespec wpcopy_delay_max_ts; - unsigned long long irq_count; - unsigned long long irq_delay_total; - unsigned long long irq_delay_max; - struct __kernel_timespec irq_delay_max_ts; - unsigned long long mem_count; - unsigned long long mem_delay_total; + struct delay_metrics delays[NUM_DELAY_TYPES]; }; /* Container statistics structure */ @@ -165,11 +152,9 @@ struct container_stats { struct field_desc { const char *name; /* Field name for cmdline argument */ const char *cmd_char; /* Interactive command */ - unsigned long total_offset; /* Offset of total delay in task_info */ - unsigned long count_offset; /* Offset of count in task_info */ - unsigned long max_offset; /* Offset of max delay in task_info */ - unsigned long max_ts_offset; /* Offset of max delay timestamp in task_info */ + enum delay_type type; /* Index into task_info.delays[] */ size_t supported_modes; /* Supported display modes */ + bool has_max; /* Whether this field has max/ts */ }; /* Program settings structure */ @@ -193,15 +178,15 @@ static int task_count; static int running = 1; static struct container_stats container_stats; static const struct field_desc sort_fields[] = { - SORT_FIELD(cpu, c, MODE_DEFAULT | MODE_TYPE), - SORT_FIELD(blkio, i, MODE_DEFAULT | MODE_TYPE), - SORT_FIELD(irq, q, MODE_DEFAULT | MODE_TYPE), - SORT_FIELD_NO_MAX(mem, m, MODE_DEFAULT | MODE_MEMVERBOSE), - SORT_FIELD(swapin, s, MODE_MEMVERBOSE | MODE_TYPE), - SORT_FIELD(freepages, r, MODE_MEMVERBOSE | MODE_TYPE), - SORT_FIELD(thrashing, t, MODE_MEMVERBOSE | MODE_TYPE), - SORT_FIELD(compact, p, MODE_MEMVERBOSE | MODE_TYPE), - SORT_FIELD(wpcopy, w, MODE_MEMVERBOSE | MODE_TYPE), + SORT_FIELD(cpu, c, DELAY_CPU, MODE_DEFAULT | MODE_TYPE, true), + SORT_FIELD(blkio, i, DELAY_BLKIO, MODE_DEFAULT | MODE_TYPE, true), + SORT_FIELD(irq, q, DELAY_IRQ, MODE_DEFAULT | MODE_TYPE, true), + SORT_FIELD(mem, m, DELAY_MEM, MODE_DEFAULT | MODE_MEMVERBOSE, false), + SORT_FIELD(swapin, s, DELAY_SWAPIN, MODE_MEMVERBOSE | MODE_TYPE, true), + SORT_FIELD(freepages, r, DELAY_FREEPAGES, MODE_MEMVERBOSE | MODE_TYPE, true), + SORT_FIELD(thrashing, t, DELAY_THRASHING, MODE_MEMVERBOSE | MODE_TYPE, true), + SORT_FIELD(compact, p, DELAY_COMPACT, MODE_MEMVERBOSE | MODE_TYPE, true), + SORT_FIELD(wpcopy, w, DELAY_WPCOPY, MODE_MEMVERBOSE | MODE_TYPE, true), END_FIELD }; static int sort_selected; @@ -433,20 +418,20 @@ static void parse_args(int argc, char **argv) /* Calculate average delay in milliseconds for overall memory */ static void set_mem_delay_total(struct task_info *t) { - t->mem_delay_total = t->swapin_delay_total + - t->freepages_delay_total + - t->thrashing_delay_total + - t->compact_delay_total + - t->wpcopy_delay_total; + t->delays[DELAY_MEM].delay_total = t->delays[DELAY_SWAPIN].delay_total + + t->delays[DELAY_FREEPAGES].delay_total + + t->delays[DELAY_THRASHING].delay_total + + t->delays[DELAY_COMPACT].delay_total + + t->delays[DELAY_WPCOPY].delay_total; } static void set_mem_count(struct task_info *t) { - t->mem_count = t->swapin_count + - t->freepages_count + - t->thrashing_count + - t->compact_count + - t->wpcopy_count; + t->delays[DELAY_MEM].count = t->delays[DELAY_SWAPIN].count + + t->delays[DELAY_FREEPAGES].count + + t->delays[DELAY_THRASHING].count + + t->delays[DELAY_COMPACT].count + + t->delays[DELAY_WPCOPY].count; } /* Create a raw netlink socket and bind */ @@ -758,38 +743,14 @@ static void fetch_and_fill_task_info(int pid, const char *comm) strncpy(tasks[task_count].command, comm, TASK_COMM_LEN - 1); tasks[task_count].command[TASK_COMM_LEN - 1] = '\0'; - SET_TASK_STAT(task_count, cpu_count); - SET_TASK_STAT(task_count, cpu_delay_total); - SET_TASK_STAT(task_count, cpu_delay_max); - SET_TASK_STAT(task_count, cpu_delay_max_ts); - SET_TASK_STAT(task_count, blkio_count); - SET_TASK_STAT(task_count, blkio_delay_total); - SET_TASK_STAT(task_count, blkio_delay_max); - SET_TASK_STAT(task_count, blkio_delay_max_ts); - SET_TASK_STAT(task_count, swapin_count); - SET_TASK_STAT(task_count, swapin_delay_total); - SET_TASK_STAT(task_count, swapin_delay_max); - SET_TASK_STAT(task_count, swapin_delay_max_ts); - SET_TASK_STAT(task_count, freepages_count); - SET_TASK_STAT(task_count, freepages_delay_total); - SET_TASK_STAT(task_count, freepages_delay_max); - SET_TASK_STAT(task_count, freepages_delay_max_ts); - SET_TASK_STAT(task_count, thrashing_count); - SET_TASK_STAT(task_count, thrashing_delay_total); - SET_TASK_STAT(task_count, thrashing_delay_max); - SET_TASK_STAT(task_count, thrashing_delay_max_ts); - SET_TASK_STAT(task_count, compact_count); - SET_TASK_STAT(task_count, compact_delay_total); - SET_TASK_STAT(task_count, compact_delay_max); - SET_TASK_STAT(task_count, compact_delay_max_ts); - SET_TASK_STAT(task_count, wpcopy_count); - SET_TASK_STAT(task_count, wpcopy_delay_total); - SET_TASK_STAT(task_count, wpcopy_delay_max); - SET_TASK_STAT(task_count, wpcopy_delay_max_ts); - SET_TASK_STAT(task_count, irq_count); - SET_TASK_STAT(task_count, irq_delay_total); - SET_TASK_STAT(task_count, irq_delay_max); - SET_TASK_STAT(task_count, irq_delay_max_ts); + COPY_DELAY(task_count, CPU, cpu); + COPY_DELAY(task_count, BLKIO, blkio); + COPY_DELAY(task_count, SWAPIN, swapin); + COPY_DELAY(task_count, FREEPAGES, freepages); + COPY_DELAY(task_count, THRASHING, thrashing); + COPY_DELAY(task_count, COMPACT, compact); + COPY_DELAY(task_count, WPCOPY, wpcopy); + COPY_DELAY(task_count, IRQ, irq); set_mem_count(&tasks[task_count]); set_mem_delay_total(&tasks[task_count]); task_count++; @@ -912,10 +873,10 @@ static int compare_tasks(const void *a, const void *b) return 0; } - total1 = *(unsigned long long *)((char *)t1 + cfg.sort_field->total_offset); - total2 = *(unsigned long long *)((char *)t2 + cfg.sort_field->total_offset); - count1 = *(unsigned long long *)((char *)t1 + cfg.sort_field->count_offset); - count2 = *(unsigned long long *)((char *)t2 + cfg.sort_field->count_offset); + total1 = t1->delays[cfg.sort_field->type].delay_total; + total2 = t2->delays[cfg.sort_field->type].delay_total; + count1 = t1->delays[cfg.sort_field->type].count; + count2 = t2->delays[cfg.sort_field->type].count; avg1 = average_ms(total1, count1); avg2 = average_ms(total2, count2); @@ -929,22 +890,17 @@ static int compare_tasks(const void *a, const void *b) static void field_delay_max_and_ts(const struct task_info *task, const struct field_desc *field, unsigned long long *max_ns, struct __kernel_timespec *max_ts) { - if (!field || !field->max_offset) { + if (!field || !field->has_max) { *max_ns = 0; if (max_ts) memset(max_ts, 0, sizeof(*max_ts)); return; } - *max_ns = *(unsigned long long *)((char *)task + field->max_offset); + *max_ns = task->delays[field->type].delay_max; - if (max_ts) { - if (field->max_ts_offset) - *max_ts = *(struct __kernel_timespec *)((char *)task + - field->max_ts_offset); - else - memset(max_ts, 0, sizeof(*max_ts)); - } + if (max_ts) + *max_ts = task->delays[field->type].delay_max_ts; } /* Get delay values for a specific field */ @@ -954,15 +910,15 @@ static void get_field_delay_values(const struct task_info *task, const struct fi { unsigned long long total, count, max; - if (!field || !field->max_offset) { + if (!field) { *avg_ms = 0; *max_ms = 0; memset(max_ts, 0, sizeof(*max_ts)); return; } - total = *(unsigned long long *)((char *)task + field->total_offset); - count = *(unsigned long long *)((char *)task + field->count_offset); + total = task->delays[field->type].delay_total; + count = task->delays[field->type].count; *avg_ms = average_ms(total, count); field_delay_max_and_ts(task, field, &max, max_ts); @@ -1180,18 +1136,18 @@ static void display_results(int psi_ret) avg_ms, max_ms, format_kernel_timespec(&max_ts)); } else if (cfg.display_mode == MODE_MEMVERBOSE) { suc &= BOOL_FPRINT(out, DELAY_FMT_MEMVERBOSE, - TASK_AVG(tasks[i], mem), - TASK_AVG(tasks[i], swapin), - TASK_AVG(tasks[i], freepages), - TASK_AVG(tasks[i], thrashing), - TASK_AVG(tasks[i], compact), - TASK_AVG(tasks[i], wpcopy)); + TASK_AVG(tasks[i], MEM), + TASK_AVG(tasks[i], SWAPIN), + TASK_AVG(tasks[i], FREEPAGES), + TASK_AVG(tasks[i], THRASHING), + TASK_AVG(tasks[i], COMPACT), + TASK_AVG(tasks[i], WPCOPY)); } else { suc &= BOOL_FPRINT(out, DELAY_FMT_DEFAULT, - TASK_AVG(tasks[i], cpu), - TASK_AVG(tasks[i], blkio), - TASK_AVG(tasks[i], irq), - TASK_AVG(tasks[i], mem)); + TASK_AVG(tasks[i], CPU), + TASK_AVG(tasks[i], BLKIO), + TASK_AVG(tasks[i], IRQ), + TASK_AVG(tasks[i], MEM)); } } -- 2.27.0 ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH 1/3] delaytop: refactor repetitive delay fields into array with enum 2026-07-11 9:32 ` [PATCH 1/3] delaytop: refactor repetitive delay fields into array with enum wang.yaxin @ 2026-07-14 3:07 ` xu.xin16 0 siblings, 0 replies; 9+ messages in thread From: xu.xin16 @ 2026-07-14 3:07 UTC (permalink / raw) To: wang.yaxin, akpm Cc: wang.yaxin, fan.yu9, yang.yang29, corbet, linux-kernel, linux-doc > Replace the 9 groups of (count, delay_total, delay_max, delay_max_ts) > named fields in struct task_info with a struct delay_metrics array > indexed by enum delay_type. This eliminates all unsafe pointer > arithmetic via offsetof() from compare_tasks(), > field_delay_max_and_ts(), and get_field_delay_values(). > > The struct field_desc now stores an enum delay_type index instead of > four separate unsigned long offset values. > > Signed-off-by: Wang Yaxin <wang.yaxin@zte.com.cn> Acked-by: Xu Xin <xu.xin16@zte.com.cn> ^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH 2/3] tools/accounting: factor out shared format_timespec() implementation 2026-07-11 9:31 [PATCH 0/3] tools/accounting: refactor delay fields and share format_timespec() wang.yaxin 2026-07-11 9:32 ` [PATCH 1/3] delaytop: refactor repetitive delay fields into array with enum wang.yaxin @ 2026-07-11 9:33 ` wang.yaxin 2026-07-11 9:34 ` [PATCH 3/3] tools/accounting: simplify 32-bit time_t overflow check in format_timespec() wang.yaxin 2026-07-14 2:48 ` [PATCH 0/3] tools/accounting: refactor delay fields and share format_timespec() Andrew Morton 3 siblings, 0 replies; 9+ messages in thread From: wang.yaxin @ 2026-07-11 9:33 UTC (permalink / raw) To: wang.yaxin Cc: akpm, fan.yu9, yang.yang29, corbet, linux-kernel, linux-doc, xu.xin16 From: Wang Yaxin <wang.yaxin@zte.com.cn> The same __kernel_timespec formatting logic existed independently in both getdelays.c and delaytop.c with minor differences (strftime vs snprintf, __kernel_time64_t vs time_t). Create a shared format_timespec.c/h with a canonical implementation (strftime + time_t), remove the static copies from both files, and link both programs against the common object. Also simplify the Makefile with a pattern rule for %.o and a static pattern rule for the two programs that need format_timespec.o. Signed-off-by: Wang Yaxin <wang.yaxin@zte.com.cn> --- tools/accounting/Makefile | 12 ++++++++- tools/accounting/delaytop.c | 39 +++------------------------ tools/accounting/format_timespec.c | 39 +++++++++++++++++++++++++++ tools/accounting/format_timespec.h | 9 +++++++ tools/accounting/getdelays.c | 32 ++-------------------- tools/include/uapi/linux/time_types.h | 18 +++++++++++++ 6 files changed, 82 insertions(+), 67 deletions(-) create mode 100644 tools/accounting/format_timespec.c create mode 100644 tools/accounting/format_timespec.h create mode 100644 tools/include/uapi/linux/time_types.h diff --git a/tools/accounting/Makefile b/tools/accounting/Makefile index 007c0bb8cbbb..22e690c853a5 100644 --- a/tools/accounting/Makefile +++ b/tools/accounting/Makefile @@ -3,8 +3,18 @@ CC := $(CROSS_COMPILE)gcc CFLAGS := -I../include/uapi/ PROGS := getdelays procacct delaytop +OBJS := format_timespec.o all: $(PROGS) +getdelays delaytop: %: %.o $(OBJS) + $(CC) $(CFLAGS) -o $@ $^ + +procacct: procacct.o + $(CC) $(CFLAGS) -o $@ $^ + +%.o: %.c + $(CC) $(CFLAGS) -c -o $@ $< + clean: - rm -fr $(PROGS) + rm -fr $(PROGS) *.o diff --git a/tools/accounting/delaytop.c b/tools/accounting/delaytop.c index f1d26ff98792..1144ca325447 100644 --- a/tools/accounting/delaytop.c +++ b/tools/accounting/delaytop.c @@ -44,6 +44,8 @@ #include <linux/cgroupstats.h> #include <stddef.h> +#include "format_timespec.h" + #define PSI_PATH "/proc/pressure" #define PSI_CPU_PATH "/proc/pressure/cpu" #define PSI_MEMORY_PATH "/proc/pressure/memory" @@ -817,41 +819,6 @@ static double average_ms(unsigned long long total, unsigned long long count) return (double)total / 1000000.0 / count; } -/* - * Format __kernel_timespec to human readable string (YYYY-MM-DDTHH:MM:SS) - * Returns formatted string or "N/A" if timestamp is zero - */ -static const char *format_kernel_timespec(struct __kernel_timespec *ts) -{ - static char buffer[32]; - time_t time_sec; - struct tm tm_info; - - /* Check if timestamp is zero (not set) */ - if (ts->tv_sec == 0 && ts->tv_nsec == 0) - return "N/A"; - - /* Avoid Y2038 truncation: check if timestamp fits in time_t on 32-bit platforms */ - if (sizeof(time_t) < sizeof(ts->tv_sec) && - ts->tv_sec > (__u64)((1ULL << (sizeof(time_t) * 8 - 1)) - 1)) - return "N/A"; - - time_sec = (time_t)ts->tv_sec; - - if (localtime_r(&time_sec, &tm_info) == NULL) - return "N/A"; - - snprintf(buffer, sizeof(buffer), "%04d-%02d-%02dT%02d:%02d:%02d", - tm_info.tm_year + 1900, - tm_info.tm_mon + 1, - tm_info.tm_mday, - tm_info.tm_hour, - tm_info.tm_min, - tm_info.tm_sec); - - return buffer; -} - /* Comparison function for sorting tasks */ static int compare_tasks(const void *a, const void *b) { @@ -1133,7 +1100,7 @@ static void display_results(int psi_ret) &max_ms, &max_ts); suc &= BOOL_FPRINT(out, "%12.2f %12.2f %20s\n", - avg_ms, max_ms, format_kernel_timespec(&max_ts)); + avg_ms, max_ms, format_timespec(&max_ts)); } else if (cfg.display_mode == MODE_MEMVERBOSE) { suc &= BOOL_FPRINT(out, DELAY_FMT_MEMVERBOSE, TASK_AVG(tasks[i], MEM), diff --git a/tools/accounting/format_timespec.c b/tools/accounting/format_timespec.c new file mode 100644 index 000000000000..1dba50cac895 --- /dev/null +++ b/tools/accounting/format_timespec.c @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Shared __kernel_timespec formatting for tools/accounting/ + * + * Formats a __kernel_timespec to an ISO 8601 timestamp string + * (YYYY-MM-DDTHH:MM:SS). Returns "N/A" if the timestamp is zero + * or does not fit in time_t. + */ +#include <time.h> +#include <linux/time_types.h> +#include "format_timespec.h" + +const char *format_timespec(const struct __kernel_timespec *ts) +{ + static char buffer[32]; + struct tm tm_info; + time_t time_sec; + + if (ts->tv_sec == 0 && ts->tv_nsec == 0) + return "N/A"; + + /* + * On 32-bit platforms time_t is 32-bit and cannot represent + * dates beyond Y2038. The kernel timestamp is always 64-bit, + * so reject values that would overflow. + */ + if (sizeof(time_t) < sizeof(ts->tv_sec) && + ts->tv_sec > (__u64)((1ULL << (sizeof(time_t) * 8 - 1)) - 1)) + return "N/A"; + + time_sec = ts->tv_sec; + + if (!localtime_r(&time_sec, &tm_info)) + return "N/A"; + + strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%S", &tm_info); + + return buffer; +} diff --git a/tools/accounting/format_timespec.h b/tools/accounting/format_timespec.h new file mode 100644 index 000000000000..960496ebf5c2 --- /dev/null +++ b/tools/accounting/format_timespec.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef FORMAT_TIMESPEC_H +#define FORMAT_TIMESPEC_H + +#include <linux/time_types.h> + +const char *format_timespec(const struct __kernel_timespec *ts); + +#endif diff --git a/tools/accounting/getdelays.c b/tools/accounting/getdelays.c index 6ac30d4f96f7..d3193f670d89 100644 --- a/tools/accounting/getdelays.c +++ b/tools/accounting/getdelays.c @@ -30,6 +30,8 @@ #include <linux/taskstats.h> #include <linux/cgroupstats.h> +#include "format_timespec.h" + /* * Generic macros for dealing with netlink sockets. Might be duplicated * elsewhere. It is recommended that commercial grade applications use @@ -221,36 +223,6 @@ static int get_family_id(int sd) #define average_ms(t, c) (t / 1000000ULL / (c ? c : 1)) #define delay_ms(t) (t / 1000000ULL) -/* - * Format __kernel_timespec to human readable string (YYYY-MM-DD HH:MM:SS) - * Returns formatted string or "N/A" if timestamp is zero - */ -static const char *format_timespec(struct __kernel_timespec *ts) -{ - static char buffer[32]; - struct tm tm_info; - __kernel_time_t time_sec; - - /* Check if timestamp is zero (not set) */ - if (ts->tv_sec == 0 && ts->tv_nsec == 0) - return "N/A"; - - /* Avoid Y2038 truncation on 32-bit platforms */ - if (sizeof(time_sec) < sizeof(ts->tv_sec) && - ts->tv_sec > (__u64)((1ULL << (sizeof(time_sec) * 8 - 1)) - 1)) - return "N/A"; - - time_sec = ts->tv_sec; - - /* Use thread-safe localtime_r */ - if (localtime_r(&time_sec, &tm_info) == NULL) - return "N/A"; - - strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%S", &tm_info); - - return buffer; -} - /* * Version compatibility note: * Field availability depends on taskstats version (t->version), diff --git a/tools/include/uapi/linux/time_types.h b/tools/include/uapi/linux/time_types.h new file mode 100644 index 000000000000..375cfbdd8387 --- /dev/null +++ b/tools/include/uapi/linux/time_types.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef _TOOLS_UAPI_LINUX_TIME_TYPES_H +#define _TOOLS_UAPI_LINUX_TIME_TYPES_H + +#include <linux/types.h> + +/* + * Minimal definition for use by tools/. + * __kernel_time64_t is always 'long long' on all architectures, + * so we avoid pulling in kernel-private type definitions. + */ + +struct __kernel_timespec { + long long tv_sec; + long long tv_nsec; +}; + +#endif /* _TOOLS_UAPI_LINUX_TIME_TYPES_H */ -- 2.27.0 ^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH 3/3] tools/accounting: simplify 32-bit time_t overflow check in format_timespec() 2026-07-11 9:31 [PATCH 0/3] tools/accounting: refactor delay fields and share format_timespec() wang.yaxin 2026-07-11 9:32 ` [PATCH 1/3] delaytop: refactor repetitive delay fields into array with enum wang.yaxin 2026-07-11 9:33 ` [PATCH 2/3] tools/accounting: factor out shared format_timespec() implementation wang.yaxin @ 2026-07-11 9:34 ` wang.yaxin 2026-07-14 2:48 ` [PATCH 0/3] tools/accounting: refactor delay fields and share format_timespec() Andrew Morton 3 siblings, 0 replies; 9+ messages in thread From: wang.yaxin @ 2026-07-11 9:34 UTC (permalink / raw) To: wang.yaxin Cc: akpm, fan.yu9, yang.yang29, corbet, linux-kernel, linux-doc, xu.xin16 From: Wang Yaxin <wang.yaxin@zte.com.cn> Replace the Y2038 overflow guard in format_timespec() with a direct narrowing truncation check ((long long)time_sec != ts->tv_sec), which is both simpler and more robust across platforms. Also move the time_sec assignment earlier to avoid duplication. While at it, fix a minor alignment issue in delaytop.c by adding a leading space to the output format string. Signed-off-by: Wang Yaxin <wang.yaxin@zte.com.cn> --- tools/accounting/delaytop.c | 2 +- tools/accounting/format_timespec.c | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/tools/accounting/delaytop.c b/tools/accounting/delaytop.c index 1144ca325447..097b57887147 100644 --- a/tools/accounting/delaytop.c +++ b/tools/accounting/delaytop.c @@ -1099,7 +1099,7 @@ static void display_results(int psi_ret) get_field_delay_values(&tasks[i], cfg.type_field, &avg_ms, &max_ms, &max_ts); - suc &= BOOL_FPRINT(out, "%12.2f %12.2f %20s\n", + suc &= BOOL_FPRINT(out, " %12.2f %12.2f %20s\n", avg_ms, max_ms, format_timespec(&max_ts)); } else if (cfg.display_mode == MODE_MEMVERBOSE) { suc &= BOOL_FPRINT(out, DELAY_FMT_MEMVERBOSE, diff --git a/tools/accounting/format_timespec.c b/tools/accounting/format_timespec.c index 1dba50cac895..d7bfd307c00b 100644 --- a/tools/accounting/format_timespec.c +++ b/tools/accounting/format_timespec.c @@ -14,22 +14,20 @@ const char *format_timespec(const struct __kernel_timespec *ts) { static char buffer[32]; struct tm tm_info; - time_t time_sec; + time_t time_sec = ts->tv_sec; if (ts->tv_sec == 0 && ts->tv_nsec == 0) return "N/A"; /* * On 32-bit platforms time_t is 32-bit and cannot represent - * dates beyond Y2038. The kernel timestamp is always 64-bit, - * so reject values that would overflow. + * timestamps outside [INT32_MIN, INT32_MAX]. A 64-bit kernel + * timestamp that does not survive the narrowing truncation is + * rejected to avoid silent data corruption. */ - if (sizeof(time_t) < sizeof(ts->tv_sec) && - ts->tv_sec > (__u64)((1ULL << (sizeof(time_t) * 8 - 1)) - 1)) + if ((long long)time_sec != ts->tv_sec) return "N/A"; - time_sec = ts->tv_sec; - if (!localtime_r(&time_sec, &tm_info)) return "N/A"; -- 2.27.0 ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH 0/3] tools/accounting: refactor delay fields and share format_timespec() 2026-07-11 9:31 [PATCH 0/3] tools/accounting: refactor delay fields and share format_timespec() wang.yaxin ` (2 preceding siblings ...) 2026-07-11 9:34 ` [PATCH 3/3] tools/accounting: simplify 32-bit time_t overflow check in format_timespec() wang.yaxin @ 2026-07-14 2:48 ` Andrew Morton 3 siblings, 0 replies; 9+ messages in thread From: Andrew Morton @ 2026-07-14 2:48 UTC (permalink / raw) To: wang.yaxin Cc: fan.yu9, yang.yang29, corbet, linux-kernel, linux-doc, xu.xin16 On Sat, 11 Jul 2026 17:31:12 +0800 (CST) <wang.yaxin@zte.com.cn> wrote: > - Convert per-field delay members in struct task_info to an array indexed > by enum delay_type, eliminating offsetof() pointer arithmetic. > > - Factor out a common format_timespec() implementation shared by getdelays > and delaytop, using strftime for cleaner timestamp formatting. > > - Replace the complex sizeof/ULL/shift Y2038 guard with a direct narrowing > truncation check ((long long)time_sec != ts->tv_sec). Sounds great. AI review might have found a couple of things. Please check it out? https://sashiko.dev/#/patchset/20260711173112482SCQEM08VED2PT1pxUYOXk@zte.com.cn ^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v2 0/3] tools/accounting: refactor delay fields and share format_timespec()
@ 2026-07-15 4:47 wang.yaxin
2026-07-15 4:50 ` [PATCH 2/3] tools/accounting: factor out shared format_timespec() implementation wang.yaxin
0 siblings, 1 reply; 9+ messages in thread
From: wang.yaxin @ 2026-07-15 4:47 UTC (permalink / raw)
To: akpm, fan.yu9, yang.yang29
Cc: corbet, linux-kernel, linux-doc, xu.xin16, wang.yaxin
From: Wang Yaxin <wang.yaxin@zte.com.cn>
- Convert per-field delay members in struct task_info to an array indexed
by enum delay_type, eliminating offsetof() pointer arithmetic.
- Factor out a common format_timespec() implementation shared by getdelays
and delaytop, using strftime for cleaner timestamp formatting.
- Replace the complex sizeof/ULL/shift Y2038 guard with a direct narrowing
truncation check ((long long)time_sec != ts->tv_sec).
Change Log
==========
v1->v2:
Only update patch 2/3 according to the suggestion:
https://sashiko.dev/#/patchset/20260711173112482SCQEM08VED2PT1pxUYOXk@zte.com.cn
1. tools/include/uapi/linux/time_types.h: expand stub to full set of
6 structs from kernel UAPI to avoid shadowing system header
2. tools/accounting/Makefile: add $(LDFLAGS), $(LDLIBS), $(CPPFLAGS)
to restore compatibility with standard build variables
Wang Yaxin (3):
delaytop: refactor repetitive delay fields into array with enum
tools/accounting: factor out shared format_timespec() implementation
tools/accounting: simplify 32-bit time_t overflow check in
format_timespec()
tools/accounting/Makefile | 12 +-
tools/accounting/delaytop.c | 261 +++++++++-----------------
tools/accounting/format_timespec.c | 37 ++++
tools/accounting/format_timespec.h | 9 +
tools/accounting/getdelays.c | 32 +---
tools/include/uapi/linux/time_types.h | 46 +++++
6 files changed, 197 insertions(+), 200 deletions(-)
create mode 100644 tools/accounting/format_timespec.c
create mode 100644 tools/accounting/format_timespec.h
create mode 100644 tools/include/uapi/linux/time_types.h
--
2.27.0
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH 2/3] tools/accounting: factor out shared format_timespec() implementation 2026-07-15 4:47 [PATCH v2 " wang.yaxin @ 2026-07-15 4:50 ` wang.yaxin 0 siblings, 0 replies; 9+ messages in thread From: wang.yaxin @ 2026-07-15 4:50 UTC (permalink / raw) To: wang.yaxin Cc: akpm, fan.yu9, yang.yang29, corbet, linux-kernel, linux-doc, xu.xin16 From: Wang Yaxin <wang.yaxin@zte.com.cn> The same __kernel_timespec formatting logic existed independently in both getdelays.c and delaytop.c with minor differences (strftime vs snprintf, __kernel_time64_t vs time_t). Create a shared format_timespec.c/h with a canonical implementation (strftime + time_t), remove the static copies from both files, and link both programs against the common object. Also simplify the Makefile with a pattern rule for %.o and a static pattern rule for the two programs that need format_timespec.o. Signed-off-by: Wang Yaxin <wang.yaxin@zte.com.cn> --- tools/accounting/Makefile | 12 ++++++- tools/accounting/delaytop.c | 39 ++--------------------- tools/accounting/format_timespec.c | 39 +++++++++++++++++++++++ tools/accounting/format_timespec.h | 9 ++++++ tools/accounting/getdelays.c | 32 ++----------------- tools/include/uapi/linux/time_types.h | 46 +++++++++++++++++++++++++++ 6 files changed, 110 insertions(+), 67 deletions(-) create mode 100644 tools/accounting/format_timespec.c create mode 100644 tools/accounting/format_timespec.h create mode 100644 tools/include/uapi/linux/time_types.h diff --git a/tools/accounting/Makefile b/tools/accounting/Makefile index 007c0bb8cbbb..5224e03f5e94 100644 --- a/tools/accounting/Makefile +++ b/tools/accounting/Makefile @@ -3,8 +3,18 @@ CC := $(CROSS_COMPILE)gcc CFLAGS := -I../include/uapi/ PROGS := getdelays procacct delaytop +OBJS := format_timespec.o all: $(PROGS) +getdelays delaytop: %: %.o $(OBJS) + $(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS) + +procacct: procacct.o + $(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS) + +%.o: %.c + $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< + clean: - rm -fr $(PROGS) + rm -fr $(PROGS) *.o diff --git a/tools/accounting/delaytop.c b/tools/accounting/delaytop.c index f1d26ff98792..1144ca325447 100644 --- a/tools/accounting/delaytop.c +++ b/tools/accounting/delaytop.c @@ -44,6 +44,8 @@ #include <linux/cgroupstats.h> #include <stddef.h> +#include "format_timespec.h" + #define PSI_PATH "/proc/pressure" #define PSI_CPU_PATH "/proc/pressure/cpu" #define PSI_MEMORY_PATH "/proc/pressure/memory" @@ -817,41 +819,6 @@ static double average_ms(unsigned long long total, unsigned long long count) return (double)total / 1000000.0 / count; } -/* - * Format __kernel_timespec to human readable string (YYYY-MM-DDTHH:MM:SS) - * Returns formatted string or "N/A" if timestamp is zero - */ -static const char *format_kernel_timespec(struct __kernel_timespec *ts) -{ - static char buffer[32]; - time_t time_sec; - struct tm tm_info; - - /* Check if timestamp is zero (not set) */ - if (ts->tv_sec == 0 && ts->tv_nsec == 0) - return "N/A"; - - /* Avoid Y2038 truncation: check if timestamp fits in time_t on 32-bit platforms */ - if (sizeof(time_t) < sizeof(ts->tv_sec) && - ts->tv_sec > (__u64)((1ULL << (sizeof(time_t) * 8 - 1)) - 1)) - return "N/A"; - - time_sec = (time_t)ts->tv_sec; - - if (localtime_r(&time_sec, &tm_info) == NULL) - return "N/A"; - - snprintf(buffer, sizeof(buffer), "%04d-%02d-%02dT%02d:%02d:%02d", - tm_info.tm_year + 1900, - tm_info.tm_mon + 1, - tm_info.tm_mday, - tm_info.tm_hour, - tm_info.tm_min, - tm_info.tm_sec); - - return buffer; -} - /* Comparison function for sorting tasks */ static int compare_tasks(const void *a, const void *b) { @@ -1133,7 +1100,7 @@ static void display_results(int psi_ret) &max_ms, &max_ts); suc &= BOOL_FPRINT(out, "%12.2f %12.2f %20s\n", - avg_ms, max_ms, format_kernel_timespec(&max_ts)); + avg_ms, max_ms, format_timespec(&max_ts)); } else if (cfg.display_mode == MODE_MEMVERBOSE) { suc &= BOOL_FPRINT(out, DELAY_FMT_MEMVERBOSE, TASK_AVG(tasks[i], MEM), diff --git a/tools/accounting/format_timespec.c b/tools/accounting/format_timespec.c new file mode 100644 index 000000000000..1dba50cac895 --- /dev/null +++ b/tools/accounting/format_timespec.c @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Shared __kernel_timespec formatting for tools/accounting/ + * + * Formats a __kernel_timespec to an ISO 8601 timestamp string + * (YYYY-MM-DDTHH:MM:SS). Returns "N/A" if the timestamp is zero + * or does not fit in time_t. + */ +#include <time.h> +#include <linux/time_types.h> +#include "format_timespec.h" + +const char *format_timespec(const struct __kernel_timespec *ts) +{ + static char buffer[32]; + struct tm tm_info; + time_t time_sec; + + if (ts->tv_sec == 0 && ts->tv_nsec == 0) + return "N/A"; + + /* + * On 32-bit platforms time_t is 32-bit and cannot represent + * dates beyond Y2038. The kernel timestamp is always 64-bit, + * so reject values that would overflow. + */ + if (sizeof(time_t) < sizeof(ts->tv_sec) && + ts->tv_sec > (__u64)((1ULL << (sizeof(time_t) * 8 - 1)) - 1)) + return "N/A"; + + time_sec = ts->tv_sec; + + if (!localtime_r(&time_sec, &tm_info)) + return "N/A"; + + strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%S", &tm_info); + + return buffer; +} diff --git a/tools/accounting/format_timespec.h b/tools/accounting/format_timespec.h new file mode 100644 index 000000000000..960496ebf5c2 --- /dev/null +++ b/tools/accounting/format_timespec.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef FORMAT_TIMESPEC_H +#define FORMAT_TIMESPEC_H + +#include <linux/time_types.h> + +const char *format_timespec(const struct __kernel_timespec *ts); + +#endif diff --git a/tools/accounting/getdelays.c b/tools/accounting/getdelays.c index 6ac30d4f96f7..d3193f670d89 100644 --- a/tools/accounting/getdelays.c +++ b/tools/accounting/getdelays.c @@ -30,6 +30,8 @@ #include <linux/taskstats.h> #include <linux/cgroupstats.h> +#include "format_timespec.h" + /* * Generic macros for dealing with netlink sockets. Might be duplicated * elsewhere. It is recommended that commercial grade applications use @@ -221,36 +223,6 @@ static int get_family_id(int sd) #define average_ms(t, c) (t / 1000000ULL / (c ? c : 1)) #define delay_ms(t) (t / 1000000ULL) -/* - * Format __kernel_timespec to human readable string (YYYY-MM-DD HH:MM:SS) - * Returns formatted string or "N/A" if timestamp is zero - */ -static const char *format_timespec(struct __kernel_timespec *ts) -{ - static char buffer[32]; - struct tm tm_info; - __kernel_time_t time_sec; - - /* Check if timestamp is zero (not set) */ - if (ts->tv_sec == 0 && ts->tv_nsec == 0) - return "N/A"; - - /* Avoid Y2038 truncation on 32-bit platforms */ - if (sizeof(time_sec) < sizeof(ts->tv_sec) && - ts->tv_sec > (__u64)((1ULL << (sizeof(time_sec) * 8 - 1)) - 1)) - return "N/A"; - - time_sec = ts->tv_sec; - - /* Use thread-safe localtime_r */ - if (localtime_r(&time_sec, &tm_info) == NULL) - return "N/A"; - - strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%S", &tm_info); - - return buffer; -} - /* * Version compatibility note: * Field availability depends on taskstats version (t->version), diff --git a/tools/include/uapi/linux/time_types.h b/tools/include/uapi/linux/time_types.h new file mode 100644 index 000000000000..90347781af54 --- /dev/null +++ b/tools/include/uapi/linux/time_types.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef _TOOLS_UAPI_LINUX_TIME_TYPES_H +#define _TOOLS_UAPI_LINUX_TIME_TYPES_H + +#include <linux/types.h> + +/* + * Copied from include/uapi/linux/time_types.h + * + * __kernel_time64_t is always 'long long' on all architectures. + * __kernel_long_t / __kernel_old_time_t is always 'long' on Linux. + * The concrete types are used here to avoid pulling in arch-specific + * kernel-private type definitions. + */ + +struct __kernel_timespec { + long long tv_sec; + long long tv_nsec; +}; + +struct __kernel_itimerspec { + struct __kernel_timespec it_interval; + struct __kernel_timespec it_value; +}; + +struct __kernel_old_timeval { + long tv_sec; + long tv_usec; +}; + +struct __kernel_old_timespec { + long tv_sec; + long tv_nsec; +}; + +struct __kernel_old_itimerval { + struct __kernel_old_timeval it_interval; + struct __kernel_old_timeval it_value; +}; + +struct __kernel_sock_timeval { + __s64 tv_sec; + __s64 tv_usec; +}; + +#endif /* _TOOLS_UAPI_LINUX_TIME_TYPES_H */ -- 2.27.0 ^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v3 0/3] tools/accounting: refactor delay fields and share format_timespec()
@ 2026-07-18 5:34 wang.yaxin
2026-07-18 5:37 ` [PATCH 2/3] tools/accounting: factor out shared format_timespec() implementation wang.yaxin
0 siblings, 1 reply; 9+ messages in thread
From: wang.yaxin @ 2026-07-18 5:34 UTC (permalink / raw)
To: akpm, fan.yu9, yang.yang29
Cc: corbet, linux-kernel, linux-doc, xu.xin16, wang.yaxin
From: Wang Yaxin <wang.yaxin@zte.com.cn>
- Convert per-field delay members in struct task_info to an array indexed
by enum delay_type, eliminating offsetof() pointer arithmetic.
- Factor out a common format_timespec() implementation shared by getdelays
and delaytop, using strftime for cleaner timestamp formatting.
- Replace the complex sizeof/ULL/shift Y2038 guard with a direct narrowing
truncation check ((long long)time_sec != ts->tv_sec).
Change Log
==========
v2->v3:
update patch 2/3 and 3/3 according to the suggestion:
https://sashiko.dev/#/patchset/20260715124740929HC7tDDb2SK3kRxbuPruHd@zte.com.cn
1. tools/include/uapi/linux/time_types.h: use __kernel_long_t instead of
long to fix ABI mismatch on x32
2. tools/accounting: add nla_len < NLA_HDRLEN checks in netlink attribute
parsing loops to prevent infinite loop on malformed messages
v1->v2:
Only update patch 2/3 according to the suggestion:
https://sashiko.dev/#/patchset/20260711173112482SCQEM08VED2PT1pxUYOXk@zte.com.cn
1. tools/include/uapi/linux/time_types.h: expand stub to full set of
6 structs from kernel UAPI to avoid shadowing system header
2. tools/accounting/Makefile: add $(LDFLAGS), $(LDLIBS), $(CPPFLAGS)
to restore compatibility with standard build variables
Wang Yaxin (3):
delaytop: refactor repetitive delay fields into array with enum
tools/accounting: factor out shared format_timespec() implementation
tools/accounting: simplify 32-bit time_t overflow check in
format_timespec()
tools/accounting/Makefile | 12 +-
tools/accounting/delaytop.c | 265 ++++++++++----------------
tools/accounting/format_timespec.c | 37 ++++
tools/accounting/format_timespec.h | 9 +
tools/accounting/getdelays.c | 36 +---
tools/include/uapi/linux/time_types.h | 49 +++++
6 files changed, 208 insertions(+), 200 deletions(-)
create mode 100644 tools/accounting/format_timespec.c
create mode 100644 tools/accounting/format_timespec.h
create mode 100644 tools/include/uapi/linux/time_types.h
--
2.27.0
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH 2/3] tools/accounting: factor out shared format_timespec() implementation 2026-07-18 5:34 [PATCH v3 0/3] tools/accounting: refactor delay fields and share format_timespec() wang.yaxin @ 2026-07-18 5:37 ` wang.yaxin 2026-07-18 13:27 ` xu.xin16 0 siblings, 1 reply; 9+ messages in thread From: wang.yaxin @ 2026-07-18 5:37 UTC (permalink / raw) To: wang.yaxin Cc: akpm, fan.yu9, yang.yang29, corbet, linux-kernel, linux-doc, xu.xin16 From: Wang Yaxin <wang.yaxin@zte.com.cn> The same __kernel_timespec formatting logic existed independently in both getdelays.c and delaytop.c with minor differences (strftime vs snprintf, __kernel_time64_t vs time_t). Create a shared format_timespec.c/h with a canonical implementation (strftime + time_t), remove the static copies from both files, and link both programs against the common object. Also simplify the Makefile with a pattern rule for %.o and a static pattern rule for the two programs that need format_timespec.o. Signed-off-by: Wang Yaxin <wang.yaxin@zte.com.cn> --- tools/accounting/Makefile | 12 ++++++- tools/accounting/delaytop.c | 39 ++------------------- tools/accounting/format_timespec.c | 39 +++++++++++++++++++++ tools/accounting/format_timespec.h | 9 +++++ tools/accounting/getdelays.c | 32 ++--------------- tools/include/uapi/linux/time_types.h | 49 +++++++++++++++++++++++++++ 6 files changed, 113 insertions(+), 67 deletions(-) create mode 100644 tools/accounting/format_timespec.c create mode 100644 tools/accounting/format_timespec.h create mode 100644 tools/include/uapi/linux/time_types.h diff --git a/tools/accounting/Makefile b/tools/accounting/Makefile index 007c0bb8cbbb..5224e03f5e94 100644 --- a/tools/accounting/Makefile +++ b/tools/accounting/Makefile @@ -3,8 +3,18 @@ CC := $(CROSS_COMPILE)gcc CFLAGS := -I../include/uapi/ PROGS := getdelays procacct delaytop +OBJS := format_timespec.o all: $(PROGS) +getdelays delaytop: %: %.o $(OBJS) + $(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS) + +procacct: procacct.o + $(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS) + +%.o: %.c + $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< + clean: - rm -fr $(PROGS) + rm -fr $(PROGS) *.o diff --git a/tools/accounting/delaytop.c b/tools/accounting/delaytop.c index f1d26ff98792..1144ca325447 100644 --- a/tools/accounting/delaytop.c +++ b/tools/accounting/delaytop.c @@ -44,6 +44,8 @@ #include <linux/cgroupstats.h> #include <stddef.h> +#include "format_timespec.h" + #define PSI_PATH "/proc/pressure" #define PSI_CPU_PATH "/proc/pressure/cpu" #define PSI_MEMORY_PATH "/proc/pressure/memory" @@ -817,41 +819,6 @@ static double average_ms(unsigned long long total, unsigned long long count) return (double)total / 1000000.0 / count; } -/* - * Format __kernel_timespec to human readable string (YYYY-MM-DDTHH:MM:SS) - * Returns formatted string or "N/A" if timestamp is zero - */ -static const char *format_kernel_timespec(struct __kernel_timespec *ts) -{ - static char buffer[32]; - time_t time_sec; - struct tm tm_info; - - /* Check if timestamp is zero (not set) */ - if (ts->tv_sec == 0 && ts->tv_nsec == 0) - return "N/A"; - - /* Avoid Y2038 truncation: check if timestamp fits in time_t on 32-bit platforms */ - if (sizeof(time_t) < sizeof(ts->tv_sec) && - ts->tv_sec > (__u64)((1ULL << (sizeof(time_t) * 8 - 1)) - 1)) - return "N/A"; - - time_sec = (time_t)ts->tv_sec; - - if (localtime_r(&time_sec, &tm_info) == NULL) - return "N/A"; - - snprintf(buffer, sizeof(buffer), "%04d-%02d-%02dT%02d:%02d:%02d", - tm_info.tm_year + 1900, - tm_info.tm_mon + 1, - tm_info.tm_mday, - tm_info.tm_hour, - tm_info.tm_min, - tm_info.tm_sec); - - return buffer; -} - /* Comparison function for sorting tasks */ static int compare_tasks(const void *a, const void *b) { @@ -1133,7 +1100,7 @@ static void display_results(int psi_ret) &max_ms, &max_ts); suc &= BOOL_FPRINT(out, "%12.2f %12.2f %20s\n", - avg_ms, max_ms, format_kernel_timespec(&max_ts)); + avg_ms, max_ms, format_timespec(&max_ts)); } else if (cfg.display_mode == MODE_MEMVERBOSE) { suc &= BOOL_FPRINT(out, DELAY_FMT_MEMVERBOSE, TASK_AVG(tasks[i], MEM), diff --git a/tools/accounting/format_timespec.c b/tools/accounting/format_timespec.c new file mode 100644 index 000000000000..1dba50cac895 --- /dev/null +++ b/tools/accounting/format_timespec.c @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Shared __kernel_timespec formatting for tools/accounting/ + * + * Formats a __kernel_timespec to an ISO 8601 timestamp string + * (YYYY-MM-DDTHH:MM:SS). Returns "N/A" if the timestamp is zero + * or does not fit in time_t. + */ +#include <time.h> +#include <linux/time_types.h> +#include "format_timespec.h" + +const char *format_timespec(const struct __kernel_timespec *ts) +{ + static char buffer[32]; + struct tm tm_info; + time_t time_sec; + + if (ts->tv_sec == 0 && ts->tv_nsec == 0) + return "N/A"; + + /* + * On 32-bit platforms time_t is 32-bit and cannot represent + * dates beyond Y2038. The kernel timestamp is always 64-bit, + * so reject values that would overflow. + */ + if (sizeof(time_t) < sizeof(ts->tv_sec) && + ts->tv_sec > (__u64)((1ULL << (sizeof(time_t) * 8 - 1)) - 1)) + return "N/A"; + + time_sec = ts->tv_sec; + + if (!localtime_r(&time_sec, &tm_info)) + return "N/A"; + + strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%S", &tm_info); + + return buffer; +} diff --git a/tools/accounting/format_timespec.h b/tools/accounting/format_timespec.h new file mode 100644 index 000000000000..960496ebf5c2 --- /dev/null +++ b/tools/accounting/format_timespec.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef FORMAT_TIMESPEC_H +#define FORMAT_TIMESPEC_H + +#include <linux/time_types.h> + +const char *format_timespec(const struct __kernel_timespec *ts); + +#endif diff --git a/tools/accounting/getdelays.c b/tools/accounting/getdelays.c index 6ac30d4f96f7..d3193f670d89 100644 --- a/tools/accounting/getdelays.c +++ b/tools/accounting/getdelays.c @@ -30,6 +30,8 @@ #include <linux/taskstats.h> #include <linux/cgroupstats.h> +#include "format_timespec.h" + /* * Generic macros for dealing with netlink sockets. Might be duplicated * elsewhere. It is recommended that commercial grade applications use @@ -221,36 +223,6 @@ static int get_family_id(int sd) #define average_ms(t, c) (t / 1000000ULL / (c ? c : 1)) #define delay_ms(t) (t / 1000000ULL) -/* - * Format __kernel_timespec to human readable string (YYYY-MM-DD HH:MM:SS) - * Returns formatted string or "N/A" if timestamp is zero - */ -static const char *format_timespec(struct __kernel_timespec *ts) -{ - static char buffer[32]; - struct tm tm_info; - __kernel_time_t time_sec; - - /* Check if timestamp is zero (not set) */ - if (ts->tv_sec == 0 && ts->tv_nsec == 0) - return "N/A"; - - /* Avoid Y2038 truncation on 32-bit platforms */ - if (sizeof(time_sec) < sizeof(ts->tv_sec) && - ts->tv_sec > (__u64)((1ULL << (sizeof(time_sec) * 8 - 1)) - 1)) - return "N/A"; - - time_sec = ts->tv_sec; - - /* Use thread-safe localtime_r */ - if (localtime_r(&time_sec, &tm_info) == NULL) - return "N/A"; - - strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%S", &tm_info); - - return buffer; -} - /* * Version compatibility note: * Field availability depends on taskstats version (t->version), diff --git a/tools/include/uapi/linux/time_types.h b/tools/include/uapi/linux/time_types.h new file mode 100644 index 000000000000..1b9d576d7cc5 --- /dev/null +++ b/tools/include/uapi/linux/time_types.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef _TOOLS_UAPI_LINUX_TIME_TYPES_H +#define _TOOLS_UAPI_LINUX_TIME_TYPES_H + +#include <linux/types.h> +#include <asm/posix_types.h> + +/* + * Copied from include/uapi/linux/time_types.h + * + * Uses the arch-dependent __kernel_long_t rather than a hardcoded + * 'long' so that x32 (ILP32 on x86_64), where the kernel defines + * __kernel_long_t as 'long long', gets the correct struct layout. + * + * __kernel_long_t is provided by <asm/posix_types.h> - included + * below via <linux/types.h> on most systems, or directly here. + */ + +struct __kernel_timespec { + long long tv_sec; + long long tv_nsec; +}; + +struct __kernel_itimerspec { + struct __kernel_timespec it_interval; + struct __kernel_timespec it_value; +}; + +struct __kernel_old_timeval { + __kernel_long_t tv_sec; + __kernel_long_t tv_usec; +}; + +struct __kernel_old_timespec { + __kernel_long_t tv_sec; + long tv_nsec; +}; + +struct __kernel_old_itimerval { + struct __kernel_old_timeval it_interval; + struct __kernel_old_timeval it_value; +}; + +struct __kernel_sock_timeval { + __s64 tv_sec; + __s64 tv_usec; +}; + +#endif /* _TOOLS_UAPI_LINUX_TIME_TYPES_H */ -- 2.27.0 ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH 2/3] tools/accounting: factor out shared format_timespec() implementation 2026-07-18 5:37 ` [PATCH 2/3] tools/accounting: factor out shared format_timespec() implementation wang.yaxin @ 2026-07-18 13:27 ` xu.xin16 0 siblings, 0 replies; 9+ messages in thread From: xu.xin16 @ 2026-07-18 13:27 UTC (permalink / raw) To: wang.yaxin Cc: wang.yaxin, akpm, fan.yu9, yang.yang29, corbet, linux-kernel, linux-doc > From: Wang Yaxin <wang.yaxin@zte.com.cn> > > The same __kernel_timespec formatting logic existed independently in > both getdelays.c and delaytop.c with minor differences (strftime vs > snprintf, __kernel_time64_t vs time_t). > > Create a shared format_timespec.c/h with a canonical implementation > (strftime + time_t), remove the static copies from both files, and > link both programs against the common object. > > Also simplify the Makefile with a pattern rule for %.o and a static > pattern rule for the two programs that need format_timespec.o. > > Signed-off-by: Wang Yaxin <wang.yaxin@zte.com.cn> Acked-by: Xu Xin <xu.xin16@zte.com.cn> ^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-07-18 13:27 UTC | newest] Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2026-07-11 9:31 [PATCH 0/3] tools/accounting: refactor delay fields and share format_timespec() wang.yaxin 2026-07-11 9:32 ` [PATCH 1/3] delaytop: refactor repetitive delay fields into array with enum wang.yaxin 2026-07-14 3:07 ` xu.xin16 2026-07-11 9:33 ` [PATCH 2/3] tools/accounting: factor out shared format_timespec() implementation wang.yaxin 2026-07-11 9:34 ` [PATCH 3/3] tools/accounting: simplify 32-bit time_t overflow check in format_timespec() wang.yaxin 2026-07-14 2:48 ` [PATCH 0/3] tools/accounting: refactor delay fields and share format_timespec() Andrew Morton 2026-07-15 4:47 [PATCH v2 " wang.yaxin 2026-07-15 4:50 ` [PATCH 2/3] tools/accounting: factor out shared format_timespec() implementation wang.yaxin 2026-07-18 5:34 [PATCH v3 0/3] tools/accounting: refactor delay fields and share format_timespec() wang.yaxin 2026-07-18 5:37 ` [PATCH 2/3] tools/accounting: factor out shared format_timespec() implementation wang.yaxin 2026-07-18 13:27 ` xu.xin16
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®