f44a31c337
This is a memory monitoring interface implemented with reference to Linux's PSI (Pressure Stall Information), which can send notifications when the system's remaining memory is below the threshold. The following example code sets two different thresholds. When the system memory is below 10MB, a notification is triggered. When the system memory is below 20 MB, a notification (POLLPRI event) is triggered every 1s. ``` int main(int argc, FAR char *argv[]) { struct pollfd fds[2]; int ret; if (argc == 2) { char *ptr = malloc(1024*1024*atoi(argv[1])); printf("Allocating %d MB\n", atoi(argv[1])); ptr[0] = 0; return 0; } fds[0].fd = open("/proc/pressure/memory", O_RDWR); fds[1].fd = open("/proc/pressure/memory", O_RDWR); fds[0].events = POLLPRI; fds[1].events = POLLPRI; dprintf(fds[0].fd, "%llu -1", 1024LLU*1024 * 10); dprintf(fds[1].fd, "%llu 1000000", 1024LLU*1024 * 20); while (1) { ret = poll(fds, 2, -1); if (ret > 0) { printf("Memory pressure: POLLPRI, %d\n", ret); } } return 0; } ``` https://docs.kernel.org/accounting/psi.html Signed-off-by: yinshengkai <yinshengkai@xiaomi.com>
454 lines
14 KiB
C
454 lines
14 KiB
C
/****************************************************************************
|
|
* fs/procfs/fs_procfspressure.c
|
|
*
|
|
* 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 <debug.h>
|
|
#include <errno.h>
|
|
#include <poll.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include <nuttx/fs/procfs.h>
|
|
#include <nuttx/fs/fs.h>
|
|
#include <nuttx/kmalloc.h>
|
|
#include <nuttx/nuttx.h>
|
|
#include <nuttx/queue.h>
|
|
#include <nuttx/spinlock.h>
|
|
|
|
/****************************************************************************
|
|
* Private Types
|
|
****************************************************************************/
|
|
|
|
struct pressure_file_s
|
|
{
|
|
struct procfs_file_s base; /* Base open file structure */
|
|
dq_entry_t entry; /* Supports a linked list */
|
|
FAR struct pollfd *fds; /* Polling structure of waiting thread */
|
|
size_t threshold; /* Memory notification threshold */
|
|
clock_t lasttick; /* Last time notified */
|
|
clock_t interval; /* Notification interval in us */
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Data
|
|
****************************************************************************/
|
|
|
|
static dq_queue_t g_pressure_memory_queue;
|
|
static spinlock_t g_pressure_lock;
|
|
static size_t g_remaining;
|
|
|
|
/****************************************************************************
|
|
* Private Function Prototypes
|
|
****************************************************************************/
|
|
|
|
static int pressure_open(FAR struct file *filep, FAR const char *relpath,
|
|
int oflags, mode_t mode);
|
|
static int pressure_close(FAR struct file *filep);
|
|
static ssize_t pressure_read(FAR struct file *filep, FAR char *buffer,
|
|
size_t buflen);
|
|
static ssize_t pressure_write(FAR struct file *filep, FAR const char *buffer,
|
|
size_t buflen);
|
|
static int pressure_poll(FAR struct file *filep, FAR struct pollfd *fds,
|
|
bool setup);
|
|
static int pressure_dup(FAR const struct file *oldp,
|
|
FAR struct file *newp);
|
|
static int pressure_opendir(FAR const char *relpath,
|
|
FAR struct fs_dirent_s **dir);
|
|
static int pressure_closedir(FAR struct fs_dirent_s *dir);
|
|
static int pressure_readdir(FAR struct fs_dirent_s *dir,
|
|
FAR struct dirent *entry);
|
|
static int pressure_rewinddir(FAR struct fs_dirent_s *dir);
|
|
static int pressure_stat(FAR const char *relpath, FAR struct stat *buf);
|
|
|
|
/****************************************************************************
|
|
* Public Data
|
|
****************************************************************************/
|
|
|
|
const struct procfs_operations g_pressure_operations =
|
|
{
|
|
pressure_open, /* open */
|
|
pressure_close, /* close */
|
|
pressure_read, /* read */
|
|
pressure_write, /* write */
|
|
pressure_poll, /* poll */
|
|
pressure_dup, /* dup */
|
|
pressure_opendir, /* opendir */
|
|
pressure_closedir, /* closedir */
|
|
pressure_readdir, /* readdir */
|
|
pressure_rewinddir, /* rewinddir */
|
|
pressure_stat /* stat */
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: pressure_open
|
|
****************************************************************************/
|
|
|
|
static int pressure_open(FAR struct file *filep, FAR const char *relpath,
|
|
int oflags, mode_t mode)
|
|
{
|
|
FAR struct pressure_file_s *priv;
|
|
uint32_t flags;
|
|
|
|
if (strcmp(relpath, "pressure/memory") != 0)
|
|
{
|
|
ferr("ERROR: relpath is invalid: %s\n", relpath);
|
|
return -ENOENT;
|
|
}
|
|
|
|
priv = kmm_zalloc(sizeof(struct pressure_file_s));
|
|
if (!priv)
|
|
{
|
|
return -ENOMEM;
|
|
}
|
|
|
|
flags = spin_lock_irqsave(&g_pressure_lock);
|
|
priv->interval = CLOCK_MAX;
|
|
filep->f_priv = priv;
|
|
dq_addfirst(&priv->entry, &g_pressure_memory_queue);
|
|
spin_unlock_irqrestore(&g_pressure_lock, flags);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pressure_close
|
|
****************************************************************************/
|
|
|
|
static int pressure_close(FAR struct file *filep)
|
|
{
|
|
FAR struct pressure_file_s *priv = filep->f_priv;
|
|
uint32_t flags;
|
|
|
|
flags = spin_lock_irqsave(&g_pressure_lock);
|
|
dq_rem(&priv->entry, &g_pressure_memory_queue);
|
|
spin_unlock_irqrestore(&g_pressure_lock, flags);
|
|
free(priv);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pressure_read
|
|
****************************************************************************/
|
|
|
|
static ssize_t pressure_read(FAR struct file *filep, FAR char *buffer,
|
|
size_t buflen)
|
|
{
|
|
char buf[128];
|
|
uint32_t flags;
|
|
ssize_t ret;
|
|
|
|
flags = spin_lock_irqsave(&g_pressure_lock);
|
|
ret = procfs_snprintf(buf, sizeof(buf), "remaining %zu\n",
|
|
g_remaining);
|
|
spin_unlock_irqrestore(&g_pressure_lock, flags);
|
|
|
|
if (ret > buflen)
|
|
{
|
|
return -ENOMEM;
|
|
}
|
|
|
|
memcpy(buffer, buf, ret);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pressure_write
|
|
****************************************************************************/
|
|
|
|
static ssize_t pressure_write(FAR struct file *filep, FAR const char *buffer,
|
|
size_t buflen)
|
|
{
|
|
FAR struct pressure_file_s *priv = filep->f_priv;
|
|
FAR char *endptr;
|
|
size_t threshold;
|
|
clock_t interval;
|
|
uint32_t flags;
|
|
|
|
if (buffer == NULL)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
threshold = strtoul(buffer, &endptr, 0);
|
|
if (threshold == 0)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
interval = strtol(endptr + 1, NULL, 0);
|
|
|
|
/* Check if the interval is valid, -1 means only notify once */
|
|
|
|
if (interval == -1)
|
|
{
|
|
interval = CLOCK_MAX;
|
|
}
|
|
else
|
|
{
|
|
interval = USEC2TICK(interval);
|
|
}
|
|
|
|
flags = spin_lock_irqsave(&g_pressure_lock);
|
|
|
|
/* We should trigger the first event immediately */
|
|
|
|
priv->lasttick = CLOCK_MAX;
|
|
priv->threshold = threshold;
|
|
priv->interval = interval;
|
|
spin_unlock_irqrestore(&g_pressure_lock, flags);
|
|
return buflen;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pressure_poll
|
|
****************************************************************************/
|
|
|
|
static int pressure_poll(FAR struct file *filep, FAR struct pollfd *fds,
|
|
bool setup)
|
|
{
|
|
FAR struct pressure_file_s *priv = filep->f_priv;
|
|
clock_t current = clock_systime_ticks();
|
|
uint32_t flags;
|
|
|
|
flags = spin_lock_irqsave(&g_pressure_lock);
|
|
if (setup)
|
|
{
|
|
if (priv->fds == NULL)
|
|
{
|
|
priv->fds = fds;
|
|
fds->priv = &priv->fds;
|
|
|
|
/* If the remaining memory is less than the threshold and
|
|
* lasttick is CLOCK_MAX, it means the event is triggered for
|
|
* the first time and we should always send a notification.
|
|
*/
|
|
|
|
if (g_remaining <= priv->threshold && (priv->lasttick ==
|
|
CLOCK_MAX || current - priv->lasttick >= priv->interval))
|
|
{
|
|
priv->lasttick = current;
|
|
spin_unlock_irqrestore(&g_pressure_lock, flags);
|
|
poll_notify(&priv->fds, 1, POLLPRI);
|
|
return OK;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
spin_unlock_irqrestore(&g_pressure_lock, flags);
|
|
return -EBUSY;
|
|
}
|
|
}
|
|
else if (fds->priv)
|
|
{
|
|
priv->fds = NULL;
|
|
fds->priv = NULL;
|
|
}
|
|
|
|
spin_unlock_irqrestore(&g_pressure_lock, flags);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pressure_dup
|
|
****************************************************************************/
|
|
|
|
static int pressure_dup(FAR const struct file *oldp, FAR struct file *newp)
|
|
{
|
|
FAR struct pressure_file_s *oldpriv = oldp->f_priv;
|
|
FAR struct pressure_file_s *newpriv;
|
|
uint32_t flags;
|
|
|
|
newpriv = kmm_zalloc(sizeof(struct pressure_file_s));
|
|
if (newpriv == NULL)
|
|
{
|
|
return -ENOMEM;
|
|
}
|
|
|
|
flags = spin_lock_irqsave(&g_pressure_lock);
|
|
memcpy(newpriv, oldpriv, sizeof(struct pressure_file_s));
|
|
dq_addfirst(&newpriv->entry, &g_pressure_memory_queue);
|
|
newpriv->fds = NULL;
|
|
newp->f_priv = newpriv;
|
|
spin_unlock_irqrestore(&g_pressure_lock, flags);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pressure_opendir
|
|
****************************************************************************/
|
|
|
|
static int pressure_opendir(FAR const char *relpath,
|
|
FAR struct fs_dirent_s **dir)
|
|
{
|
|
FAR struct procfs_dir_priv_s *level;
|
|
|
|
finfo("relpath: \"%s\"\n", relpath ? relpath : "NULL");
|
|
DEBUGASSERT(relpath);
|
|
|
|
level = kmm_zalloc(sizeof(struct procfs_dir_priv_s));
|
|
if (level == NULL)
|
|
{
|
|
return -ENOMEM;
|
|
}
|
|
|
|
level->level = 1;
|
|
level->nentries = 1;
|
|
|
|
*dir = (FAR struct fs_dirent_s *)level;
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pressure_closedir
|
|
****************************************************************************/
|
|
|
|
static int pressure_closedir(FAR struct fs_dirent_s *dir)
|
|
{
|
|
DEBUGASSERT(dir);
|
|
kmm_free(dir);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pressure_readdir
|
|
****************************************************************************/
|
|
|
|
static int pressure_readdir(FAR struct fs_dirent_s *dir,
|
|
FAR struct dirent *entry)
|
|
{
|
|
FAR struct procfs_dir_priv_s *level;
|
|
|
|
DEBUGASSERT(dir);
|
|
level = (FAR struct procfs_dir_priv_s *)dir;
|
|
if (level->index >= level->nentries)
|
|
{
|
|
finfo("No more entries\n");
|
|
return -ENOENT;
|
|
}
|
|
|
|
entry->d_type = DTYPE_FILE;
|
|
strncpy(entry->d_name, "memory", sizeof(entry->d_name));
|
|
level->index++;
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pressure_rewinddir
|
|
****************************************************************************/
|
|
|
|
static int pressure_rewinddir(FAR struct fs_dirent_s *dir)
|
|
{
|
|
FAR struct procfs_dir_priv_s *level;
|
|
|
|
DEBUGASSERT(dir);
|
|
level = (FAR struct procfs_dir_priv_s *)dir;
|
|
level->index = 0;
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pressure_stat
|
|
****************************************************************************/
|
|
|
|
static int pressure_stat(const char *relpath, struct stat *buf)
|
|
{
|
|
memset(buf, 0, sizeof(struct stat));
|
|
|
|
if (strcmp(relpath, "pressure") == 0 || strcmp(relpath, "pressure/") == 0)
|
|
{
|
|
buf->st_mode = S_IFDIR | S_IROTH | S_IRGRP | S_IRUSR;
|
|
}
|
|
else if (strcmp(relpath, "pressure/memory") == 0)
|
|
{
|
|
buf->st_mode = S_IFREG | S_IROTH | S_IRGRP | S_IRUSR | S_IWOTH |
|
|
S_IWGRP | S_IWUSR;
|
|
}
|
|
else
|
|
{
|
|
ferr("ERROR: No such file or directory: %s\n", relpath);
|
|
return -ENOENT;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* mm_notify_pressure
|
|
****************************************************************************/
|
|
|
|
void mm_notify_pressure(size_t remaining)
|
|
{
|
|
clock_t current = clock_systime_ticks();
|
|
FAR dq_entry_t *entry;
|
|
FAR dq_entry_t *tmp;
|
|
uint32_t flags;
|
|
|
|
flags = spin_lock_irqsave(&g_pressure_lock);
|
|
g_remaining = remaining;
|
|
dq_for_every_safe(&g_pressure_memory_queue, entry, tmp)
|
|
{
|
|
FAR struct pressure_file_s *pressure =
|
|
container_of(entry, struct pressure_file_s, entry);
|
|
|
|
if (remaining > pressure->threshold)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
/* If lasttick is CLOCK_MAX, it means that the event is triggered
|
|
* for the first time and we should always send notifications.
|
|
*/
|
|
|
|
if (pressure->lasttick != CLOCK_MAX && current - pressure->lasttick <
|
|
pressure->interval)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
/* If fds is NULL, it means no one is listening for the event and
|
|
* we should delay sending the notification.
|
|
*/
|
|
|
|
if (pressure->fds == NULL)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
pressure->lasttick = current;
|
|
spin_unlock_irqrestore(&g_pressure_lock, flags);
|
|
poll_notify(&pressure->fds, 1, POLLPRI);
|
|
flags = spin_lock_irqsave(&g_pressure_lock);
|
|
}
|
|
|
|
spin_unlock_irqrestore(&g_pressure_lock, flags);
|
|
}
|
|
|