diff --git a/Documentation/components/filesystem/index.rst b/Documentation/components/filesystem/index.rst index 92cf01765e665..a384354e69345 100644 --- a/Documentation/components/filesystem/index.rst +++ b/Documentation/components/filesystem/index.rst @@ -543,6 +543,7 @@ NuttX provides support for a variety of file systems out of the box. nxffs.rst partition.rst procfs.rst + profiler.rst romfs.rst rpmsgfs.rst smartfs.rst diff --git a/Documentation/components/filesystem/profiler.rst b/Documentation/components/filesystem/profiler.rst new file mode 100644 index 0000000000000..2413dd386a5fa --- /dev/null +++ b/Documentation/components/filesystem/profiler.rst @@ -0,0 +1,38 @@ +============================== +VFS Performance Profiler +============================== + +The Virtual File System (VFS) Performance Profiler provides a simple, in-kernel +mechanism to track execution times and invocation counts for core VFS operations +(read, write, open, close) seamlessly. This is highly suitable for +CI/CD automated regression testing and performance bottleneck identification. + +Configuration +============= + +To enable the profiler, select ``CONFIG_FS_PROFILER`` in your Kconfig. +To expose the metrics dynamically via procfs, ensure ``CONFIG_FS_PROCFS`` is enabled, and +the profiler node is included via ``CONFIG_FS_PROCFS_PROFILER``. + +Usage +===== + +When enabled, the profiler automatically intercepts calls to the underlying +inode operations and records the execution elapsed times using ``perf_gettime()``. +Since no blocking mutexes are used during updates (fast ``atomic.h`` operations +are utilized instead), the overhead is extremely minimal and safely scales on SMP. + +To view the current statistics collectively from the NuttShell (NSH), simply +read the node: + +.. code-block:: bash + + nsh> cat /proc/fs/profile + VFS Performance Profile: + Reads: 12 (Total time: 4500120 ns) + Writes: 3 (Total time: 95050 ns) + Opens: 15 (Total time: 1005000 ns) + Closes: 15 (Total time: 45000 ns) + +The reported times are in the raw ticks/units provided by ``perf_gettime()`` on +your specific architecture. diff --git a/fs/Kconfig b/fs/Kconfig index ac9202e49a8c9..506632ad5a9c5 100644 --- a/fs/Kconfig +++ b/fs/Kconfig @@ -5,6 +5,14 @@ comment "File system configuration" +config FS_PROFILER + bool "VFS Performance Profiler" + default n + ---help--- + Enable nanosecond/microsecond-level profiling for the Virtual File + System (VFS) operations (open, close, read, write). The profile stats + can be read via /proc/fs/profile if PROCFS is enabled. + config DISABLE_MOUNTPOINT bool "Disable support for mount points" default n diff --git a/fs/procfs/CMakeLists.txt b/fs/procfs/CMakeLists.txt index 8cd17b80a40f3..3c53a1e87af25 100644 --- a/fs/procfs/CMakeLists.txt +++ b/fs/procfs/CMakeLists.txt @@ -40,6 +40,10 @@ if(NOT CONFIG_DISABLE_MOUNTPOINT) list(APPEND SRCS fs_procfspressure.c) endif() + if(CONFIG_FS_PROCFS_PROFILER) + list(APPEND SRCS fs_procfsprofile.c) + endif() + target_sources(fs PRIVATE ${SRCS}) endif() diff --git a/fs/procfs/Kconfig b/fs/procfs/Kconfig index b06e4313ff924..2ef81db868fcf 100644 --- a/fs/procfs/Kconfig +++ b/fs/procfs/Kconfig @@ -148,6 +148,16 @@ config FS_PROCFS_EXCLUDE_VERSION bool "Exclude version" default DEFAULT_SMALL +config FS_PROCFS_PROFILER + bool "Include fs/profile information" + depends on FS_PROFILER && !DEFAULT_SMALL + default n + ---help--- + Enable the VFS performance profiler procfs node at /proc/fs/profile. + This node provides real-time dynamic statistics (call counts, elapsed + time) for core VFS operations (read, write, open, close) to help + identify filesystem performance bottlenecks and regressions. + config FS_PROCFS_INCLUDE_PRESSURE bool "Include memory pressure notification" default n diff --git a/fs/procfs/Make.defs b/fs/procfs/Make.defs index f4e1a996c2c9f..26a638f8734e8 100644 --- a/fs/procfs/Make.defs +++ b/fs/procfs/Make.defs @@ -32,6 +32,10 @@ ifeq ($(CONFIG_FS_PROCFS_INCLUDE_PRESSURE),y) CSRCS += fs_procfspressure.c endif +ifeq ($(CONFIG_FS_PROCFS_PROFILER),y) +CSRCS += fs_procfsprofile.c +endif + # Include procfs build support DEPPATH += --dep-path procfs diff --git a/fs/procfs/fs_procfs.c b/fs/procfs/fs_procfs.c index 10b564df037d3..19f1322417e5e 100644 --- a/fs/procfs/fs_procfs.c +++ b/fs/procfs/fs_procfs.c @@ -74,6 +74,9 @@ extern const struct procfs_operations g_thermal_operations; extern const struct procfs_operations g_uptime_operations; extern const struct procfs_operations g_version_operations; extern const struct procfs_operations g_pressure_operations; +#if defined(CONFIG_FS_PROFILER) && defined(CONFIG_FS_PROCFS_PROFILER) +extern const struct procfs_operations g_vfs_profile_operations; +#endif /* This is not good. These are implemented in other sub-systems. Having to * deal with them here is not a good coupling. What is really needed is a @@ -208,6 +211,9 @@ static const struct procfs_entry_s g_procfs_entries[] = #ifndef CONFIG_FS_PROCFS_EXCLUDE_VERSION { "version", &g_version_operations, PROCFS_FILE_TYPE }, #endif +#if defined(CONFIG_FS_PROFILER) && defined(CONFIG_FS_PROCFS_PROFILER) + { "profile", &g_vfs_profile_operations, PROCFS_FILE_TYPE }, +#endif }; #ifdef CONFIG_FS_PROCFS_REGISTER diff --git a/fs/procfs/fs_procfsprofile.c b/fs/procfs/fs_procfsprofile.c new file mode 100644 index 0000000000000..67b53e0b8de49 --- /dev/null +++ b/fs/procfs/fs_procfsprofile.c @@ -0,0 +1,126 @@ +/**************************************************************************** + * fs/procfs/fs_procfsprofile.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#if defined(CONFIG_FS_PROCFS) && defined(CONFIG_FS_PROFILER) && \ + defined(CONFIG_FS_PROCFS_PROFILER) + +#include +#include +#include +#include + +#include +#include +#include "../vfs/vfs.h" + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int profile_open(FAR struct file *filep, FAR const char *relpath, + int oflags, mode_t mode) +{ + return OK; +} + +static int profile_close(FAR struct file *filep) +{ + return OK; +} + +static ssize_t profile_read(FAR struct file *filep, FAR char *buffer, + size_t buflen) +{ + char buf[256]; + size_t linesize; + off_t offset = filep->f_pos; + + if (offset > 0) + { + return 0; + } + + procfs_snprintf(buf, sizeof(buf), + "VFS Performance Profile:\n" + " Reads: %10lu (Total time: %llu ns)\n" + " Writes: %10lu (Total time: %llu ns)\n" + " Opens: %10lu (Total time: %llu ns)\n" + " Closes: %10lu (Total time: %llu ns)\n", + (unsigned long)g_vfs_profile.reads, + (unsigned long long)g_vfs_profile.total_read_time, + (unsigned long)g_vfs_profile.writes, + (unsigned long long)g_vfs_profile.total_write_time, + (unsigned long)g_vfs_profile.opens, + (unsigned long long)g_vfs_profile.total_open_time, + (unsigned long)g_vfs_profile.closes, + (unsigned long long)g_vfs_profile.total_close_time); + + linesize = strlen(buf); + if (linesize > buflen) + { + linesize = buflen; + } + + memcpy(buffer, buf, linesize); + filep->f_pos += linesize; + return linesize; +} + +static int profile_dup(FAR const struct file *oldp, FAR struct file *newp) +{ + return OK; +} + +static int profile_stat(FAR const char *relpath, FAR struct stat *buf) +{ + memset(buf, 0, sizeof(struct stat)); + buf->st_mode = S_IFREG | S_IROTH | S_IRGRP | S_IRUSR; + return OK; +} + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +const struct procfs_operations g_vfs_profile_operations = +{ + profile_open, /* open */ + profile_close, /* close */ + profile_read, /* read */ + NULL, /* write */ + NULL, /* poll */ + profile_dup, /* dup */ + NULL, /* opendir */ + NULL, /* closedir */ + NULL, /* readdir */ + NULL, /* rewinddir */ + profile_stat /* stat */ +}; + +#endif + diff --git a/fs/vfs/CMakeLists.txt b/fs/vfs/CMakeLists.txt index 40d31ff0fbc00..f1b6b45f74399 100644 --- a/fs/vfs/CMakeLists.txt +++ b/fs/vfs/CMakeLists.txt @@ -93,4 +93,10 @@ if(CONFIG_SIGNAL_FD) list(APPEND SRCS fs_signalfd.c) endif() +# Support for profiler + +if(CONFIG_FS_PROFILER) + list(APPEND SRCS fs_profile.c) +endif() + target_sources(fs PRIVATE ${SRCS}) diff --git a/fs/vfs/Make.defs b/fs/vfs/Make.defs index e1b54a238945a..75b7ad168c4d0 100644 --- a/fs/vfs/Make.defs +++ b/fs/vfs/Make.defs @@ -65,6 +65,10 @@ ifeq ($(CONFIG_SIGNAL_FD),y) CSRCS += fs_signalfd.c endif +ifeq ($(CONFIG_FS_PROFILER),y) +CSRCS += fs_profile.c +endif + # Include vfs build support DEPPATH += --dep-path vfs diff --git a/fs/vfs/fs_close.c b/fs/vfs/fs_close.c index 575d3c1e3f70e..17a7171732d86 100644 --- a/fs/vfs/fs_close.c +++ b/fs/vfs/fs_close.c @@ -119,9 +119,16 @@ int file_close(FAR struct file *filep) if (inode->u.i_ops && inode->u.i_ops->close) { + clock_t start_time; + + VFS_PROFILE_START(start_time); + /* Perform the close operation */ ret = inode->u.i_ops->close(filep); + + VFS_PROFILE_STOP(start_time, g_vfs_profile.total_close_time, + g_vfs_profile.closes); } /* And release the inode */ diff --git a/fs/vfs/fs_open.c b/fs/vfs/fs_open.c index f85c1eec24ae1..74f414ac5917c 100644 --- a/fs/vfs/fs_open.c +++ b/fs/vfs/fs_open.c @@ -236,6 +236,10 @@ static int file_vopen(FAR struct file *filep, FAR const char *path, * because it may also be closed that many times. */ + clock_t start_time; + + VFS_PROFILE_START(start_time); + if (oflags & O_DIRECTORY) { ret = dir_allocate(filep, desc.relpath); @@ -261,6 +265,9 @@ static int file_vopen(FAR struct file *filep, FAR const char *path, ret = -ENXIO; } + VFS_PROFILE_STOP(start_time, g_vfs_profile.total_open_time, + g_vfs_profile.opens); + if (ret == -EISDIR && ((oflags & O_WRONLY) == 0)) { ret = dir_allocate(filep, desc.relpath); diff --git a/fs/vfs/fs_profile.c b/fs/vfs/fs_profile.c new file mode 100644 index 0000000000000..eb9c53c5f1ba8 --- /dev/null +++ b/fs/vfs/fs_profile.c @@ -0,0 +1,55 @@ +/**************************************************************************** + * fs/vfs/fs_profile.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include "vfs.h" + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +struct vfs_profile_s g_vfs_profile; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +void vfs_profile_start(FAR clock_t *start) +{ + *start = perf_gettime(); +} + +void vfs_profile_stop(FAR clock_t *start, FAR uint64_t *total, + FAR uint32_t *count) +{ + clock_t stop = perf_gettime(); + clock_t delta = stop - *start; + + atomic64_fetch_add((FAR atomic64_t *)total, delta); + atomic_fetch_add((FAR atomic_t *)count, 1); +} diff --git a/fs/vfs/fs_read.c b/fs/vfs/fs_read.c index 857dcfcdd500a..54e0d2df6c079 100644 --- a/fs/vfs/fs_read.c +++ b/fs/vfs/fs_read.c @@ -206,6 +206,10 @@ ssize_t file_readv(FAR struct file *filep, else if (inode != NULL && inode->u.i_ops) { + clock_t start_time; + + VFS_PROFILE_START(start_time); + if (inode->u.i_ops->readv) { struct uio uio; @@ -220,6 +224,9 @@ ssize_t file_readv(FAR struct file *filep, { ret = file_readv_compat(filep, iov, iovcnt); } + + VFS_PROFILE_STOP(start_time, g_vfs_profile.total_read_time, + g_vfs_profile.reads); } /* Return the number of bytes read (or possibly an error code) */ diff --git a/fs/vfs/fs_write.c b/fs/vfs/fs_write.c index 168538b569f39..538c1e8aa341e 100644 --- a/fs/vfs/fs_write.c +++ b/fs/vfs/fs_write.c @@ -188,6 +188,10 @@ ssize_t file_writev(FAR struct file *filep, inode = filep->f_inode; if (inode != NULL && inode->u.i_ops) { + clock_t start_time; + + VFS_PROFILE_START(start_time); + if (inode->u.i_ops->writev) { struct uio uio; @@ -202,6 +206,9 @@ ssize_t file_writev(FAR struct file *filep, { ret = file_writev_compat(filep, iov, iovcnt); } + + VFS_PROFILE_STOP(start_time, g_vfs_profile.total_write_time, + g_vfs_profile.writes); } #ifdef CONFIG_FS_NOTIFY diff --git a/fs/vfs/vfs.h b/fs/vfs/vfs.h index a412bcd6a8b49..4d63249a0776a 100644 --- a/fs/vfs/vfs.h +++ b/fs/vfs/vfs.h @@ -30,6 +30,10 @@ #include #include +#ifdef CONFIG_FS_PROFILER +#include +#endif + /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ @@ -123,4 +127,35 @@ void notify_rename(FAR const char *oldpath, bool oldisdir, void notify_initialize(void); #endif /* CONFIG_FS_NOTIFY */ +#ifdef CONFIG_FS_PROFILER + +struct vfs_profile_s +{ + uint32_t reads; + uint32_t writes; + uint32_t opens; + uint32_t closes; + uint64_t total_read_time; + uint64_t total_write_time; + uint64_t total_open_time; + uint64_t total_close_time; +}; + +extern struct vfs_profile_s g_vfs_profile; + +void vfs_profile_start(FAR clock_t *start); +void vfs_profile_stop(FAR clock_t *start, FAR uint64_t *total, + FAR uint32_t *count); + +#define VFS_PROFILE_START(start_time) vfs_profile_start(&start_time) +#define VFS_PROFILE_STOP(start_time, total_time, count) \ + vfs_profile_stop(&start_time, &total_time, &count) + +#else + +#define VFS_PROFILE_START(start_time) ((void)(start_time)) +#define VFS_PROFILE_STOP(start_time, total_time, count) ((void)(start_time)) + +#endif /* CONFIG_FS_PROFILER */ + #endif /* __FS_VFS_VFS_H */