Linux Trace Kernel
 help / color / mirror / Atom feed
From: K Prateek Nayak <kprateek.nayak@amd.com>
To: Pintu Kumar Agarwal <pintu.agarwal@oss.qualcomm.com>,
	<linux-kernel@vger.kernel.org>,
	<linux-trace-kernel@vger.kernel.org>, <hannes@cmpxchg.org>,
	<surenb@google.com>, <rostedt@goodmis.org>, <mhiramat@kernel.org>,
	<peterz@infradead.org>, <mathieu.desnoyers@efficios.com>,
	<mingo@redhat.com>, <juri.lelli@redhat.com>,
	<vincent.guittot@linaro.org>, <dietmar.eggemann@arm.com>,
	<bsegall@google.com>, <mgorman@suse.de>, <vschneid@redhat.com>,
	<pintu.ping@gmail.com>, <nathan@kernel.org>, <ojeda@kernel.org>,
	<nsc@kernel.org>, <gary@garyguo.net>, <tglx@kernel.org>,
	<thomas.weissschuh@linutronix.de>, <aliceryhl@google.com>,
	<dianders@chromium.org>, <linux.amoon@gmail.com>,
	<rdunlap@infradead.org>, <akpm@linux-foundation.org>,
	<shuah@kernel.org>
Subject: Re: [RFC PATCH 1/1] psi: Introduce in-kernel PSI auto monitor feature
Date: Fri, 3 Jul 2026 01:21:03 +0530	[thread overview]
Message-ID: <6c190305-4137-411f-ba25-e0a2d8484db7@amd.com> (raw)
In-Reply-To: <20260702171606.527077-2-pintu.agarwal@oss.qualcomm.com>

Hello Pintu,

On 7/2/2026 10:46 PM, Pintu Kumar Agarwal wrote:
> diff --git a/kernel/sched/build_utility.c b/kernel/sched/build_utility.c
> index e2cf3b08d4e9..30e9800ce947 100644
> --- a/kernel/sched/build_utility.c
> +++ b/kernel/sched/build_utility.c
> @@ -104,3 +104,7 @@
>  #ifdef CONFIG_SCHED_AUTOGROUP
>  # include "autogroup.c"
>  #endif
> +
> +#ifdef CONFIG_PSI_AUTO_MONITOR
> +# include "psi_monitor.c"
> +#endif

Isn't this a module? Why is this being included as a scheduler file?
Based on a quick glance, nothing in this module needs scheduler internal
APIs (and nor it should) so tools/sched/ would probabaly be a better
place to put it in if there is interest for this feature.

> diff --git a/kernel/sched/psi_monitor.c b/kernel/sched/psi_monitor.c
> new file mode 100644
> index 000000000000..e929a0c05494
> --- /dev/null
> +++ b/kernel/sched/psi_monitor.c
> @@ -0,0 +1,307 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * PSI Automatic Monitor with Weighted Task Ranking + Tracepoints
> + *
> + * Periodically samples system PSI (CPU, memory, IO) and, when any
> + * configured threshold is exceeded, ranks tasks using a composite
> + * score based on RSS, I/O activity and CPU time, then logs the
> + * top-N tasks via printk and a tracepoint.
> + *
> + * Sysfs interface:
> + *   /sys/kernel/psi_monitor/cpu_thresh                 (percentage)
> + *   /sys/kernel/psi_monitor/mem_thresh                 (percentage)
> + *   /sys/kernel/psi_monitor/io_thresh          (percentage)
> + *   /sys/kernel/psi_monitor/monitor_interval_ms (milliseconds)
> + *   /sys/kernel/psi_monitor/rss_weight
> + *   /sys/kernel/psi_monitor/io_weight
> + *   /sys/kernel/psi_monitor/cpu_weight
> + *
> + * Author: Pintu Kumar Agarwal
> + */
> +
> +#include <linux/init.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/sched.h>
> +#include <linux/sched/signal.h>
> +#include <linux/sched/loadavg.h>
> +#include <linux/mm.h>
> +#include <linux/delay.h>
> +#include <linux/workqueue.h>
> +#include <linux/psi_types.h>
> +#include <linux/kobject.h>
> +#include <linux/sort.h>
> +#include <linux/jiffies.h>
> +#include <linux/time64.h>
> +#include <linux/sched/cputime.h>
> +
> +/* Create tracepoints defined in include/trace/events/psi_monitor.h */
> +#define CREATE_TRACE_POINTS
> +#include <linux/psi.h>
> +#include <trace/events/psi_monitor.h>
> +
> +
> +/* Sysfs tunables */
> +static unsigned int cpu_thresh = 80;     /* in percent */
> +static unsigned int mem_thresh = 80;     /* in percent */
> +static unsigned int io_thresh  = 80;     /* in percent */
> +static unsigned int monitor_interval_ms = 10000;
> +
> +/* scoring weights */
> +static unsigned int rss_weight = 2;
> +static unsigned int io_weight  = 1;
> +static unsigned int cpu_weight = 5;

Insanely configurable but what makes it easy for developers to know
the right configurations under severe pressure as you put it?

> +
> +static struct delayed_work psi_work;
> +static struct kobject *psi_kobj;
> +
> +#define TOP_N 20
> +
> +struct task_info {
> +       struct task_struct *task;
> +       unsigned long rss;      /* pages */
> +       unsigned long io_kb;    /* kB */
> +       unsigned long cpu_ms;   /* ms */

Isn't the suffix selfexplanatory? Do you really need the comments?

> +       u64 score;
> +};
> +
> +/*
> + * psi_avg10_percent() - derive a rough integer percentage from avg10
> + * for a given PSI state (e.g. PSI_CPU_SOME, PSI_MEM_SOME, PSI_IO_SOME).
> + *
> + * psi_group.avg[state][0] is the avg10 window in fixed-point notation.
> + * The conversion here is approximate but monotonic, which is sufficient
> + * for thresholding and ranking in this internal monitor.
> + */
> +static unsigned long psi_avg10_percent(int state)
> +{
> +       u64 avg10;
> +
> +       if (state < 0 || state >= NR_PSI_STATES)
> +               return 0;
> +
> +       avg10 = READ_ONCE(psi_system.avg[state][0]);
> +       if (!avg10)
> +               return 0;
> +
> +       /* Convert back from loadavg-style fixed-point to an approximate % */
> +       /* Just consider the integer value and ignore fraction */

Why two single line comments?

> +       return LOAD_INT(avg10);
> +}
> +
> +static int compare_score_desc(const void *a, const void *b)
> +{
> +       const struct task_info *ta = a;
> +       const struct task_info *tb = b;
> +
> +       if (tb->score > ta->score)
> +               return 1;
> +       if (tb->score < ta->score)
> +               return -1;
> +       return 0;
> +}
> +
> +static void log_top_tasks(void)
> +{
> +       struct task_info tasks[TOP_N];
> +       struct task_struct *p, *t;
> +       int count = 0;
> +       int i;
> +
> +       rcu_read_lock();
> +       for_each_process_thread(p, t) {

Thats a ton of work every 10s.

> +               struct mm_struct *mm;
> +               unsigned long rss = 0;
> +               unsigned long io_kb = 0;
> +               unsigned long cpu_ms = 0;
> +               u64 score;
> +
> +               /* Ignore tasks that are not on run queue or idle */
> +               if (!t->on_rq && !is_idle_task(t))

Condition doesn't match the comment. Tasks off rq that aren't idle will
still go through.

> +                       continue;
> +
> +               mm = get_task_mm(t);
> +
> +               /* mm could be NULL for kernel threads */
> +               if (mm) {
> +                       rss = mm ? get_mm_rss(mm) : 0;
> +                       mmput_async(mm);
> +               }
> +
> +               /*
> +                * Approximate I/O activity: sum of read + write bytes.
> +                * This uses the task_io_accounting fields in task_struct.
> +                * Values are best-effort and need not be perfectly accurate
> +                * for our ranking purpose.
> +                */
> +               io_kb = (t->ioac.read_bytes + t->ioac.write_bytes) >> 10;
> +
> +               /*
> +                * Approximate CPU usage via task_sched_runtime(), converted
> +                * to milliseconds. This is cumulative since task start, but
> +                * is still useful for comparing hotspots at a given point.
> +                */
> +               cpu_ms = (unsigned long)(task_sched_runtime(t) / NSEC_PER_MSEC);
> +
> +               score = (u64)rss_weight * (u64)rss +
> +                       (u64)io_weight  * (u64)io_kb +
> +                       (u64)cpu_weight * (u64)cpu_ms;
> +
> +               if (count < TOP_N) {
> +                       tasks[count].task   = t;
> +                       tasks[count].rss    = rss;
> +                       tasks[count].io_kb  = io_kb;
> +                       tasks[count].cpu_ms = cpu_ms;
> +                       tasks[count].score  = score;
> +                       count++;
> +               } else {
> +                       /* Maintain a simple streaming top-N: replace smallest */
> +                       int min_idx = 0;
> +                       int j;
> +
> +                       for (j = 1; j < TOP_N; j++) {
> +                               if (tasks[j].score < tasks[min_idx].score)
> +                                       min_idx = j;
> +                       }

Can't you just cache the min_idx and re-compute it when it changes
instead of taking a O(20) iteration for every task?

> +
> +                       if (score > tasks[min_idx].score) {
> +                               tasks[min_idx].task   = t;
> +                               tasks[min_idx].rss    = rss;
> +                               tasks[min_idx].io_kb  = io_kb;
> +                               tasks[min_idx].cpu_ms = cpu_ms;
> +                               tasks[min_idx].score  = score;
> +                       }
> +               }
> +       }
> +       rcu_read_unlock();
> +
> +       sort(tasks, count, sizeof(struct task_info), compare_score_desc, NULL);
> +
> +       pr_info("psi_monitor: logging top %d tasks under pressure:\n", count);
> +
> +       for (i = 0; i < count; i++) {
> +               struct task_struct *ts = tasks[i].task;
> +               unsigned long rss_kb = tasks[i].rss << (PAGE_SHIFT - 10);
> +               char name[128] = {0,};
> +
> +               if (ts->flags & PF_WQ_WORKER)
> +                       wq_worker_comm(name, sizeof(name), ts);
> +               else
> +                       scnprintf(name, sizeof(name) - 1, ts->comm);
> +
> +               trace_psi_monitor_top_task(ts->pid, name,
> +                               tasks[i].cpu_ms,
> +                               rss_kb,
> +                               tasks[i].io_kb,
> +                               tasks[i].score);
> +
> +               pr_info("psi_monitor: pid=%d comm=%s psi_flag=%d oncpu=%d cputime(ms)=%lu rss(kB)=%lu io(kB)=%lu score=%llu\n",
> +                       ts->pid, name, ts->psi_flags, task_cpu(ts),
> +                       tasks[i].cpu_ms, rss_kb, tasks[i].io_kb,
> +                       (unsigned long long)tasks[i].score);

This will unnecessarily dump to dmesg even if you have tracevent
enabled. Why?

> +               }
> +}
> +
> +static void psi_monitor_fn(struct work_struct *work)
> +{
> +       unsigned long cpu_pct, mem_pct, io_pct;
> +       bool trigger = false;
> +
> +       cpu_pct = psi_avg10_percent(PSI_CPU_SOME);
> +       mem_pct = psi_avg10_percent(PSI_MEM_SOME);
> +       io_pct  = psi_avg10_percent(PSI_IO_SOME);
> +
> +       if (cpu_pct >= cpu_thresh || mem_pct >= mem_thresh ||
> +               io_pct >= io_thresh)
> +               trigger = true;
> +
> +       if (trigger) {
> +               pr_info("psi_monitor: pressure high: cpu=%lu%% mem=%lu%% io=%lu%% (thresh cpu=%u mem=%u io=%u)\n",
> +                       cpu_pct, mem_pct, io_pct,
> +                       cpu_thresh, mem_thresh, io_thresh);
> +               log_top_tasks();
> +       }
> +
> +       queue_delayed_work(system_wq, &psi_work,
> +               msecs_to_jiffies(monitor_interval_ms));

If I set monitor_interval_ms to 6 hours, and then change it back to 10s,
it'll only take effect after this callback has fired 6 hours later.

> +}
> +
> +/* Sysfs helpers */
> +#define PSI_ATTR_RW(_name)                                             \
> +static ssize_t _name##_show(struct kobject *kobj,                      \
> +                       struct kobj_attribute *attr, char *buf)         \
> +{                                                                      \
> +       return sysfs_emit(buf, "%u\n", _name);                          \
> +}                                                                      \
> +static ssize_t _name##_store(struct kobject *kobj,                     \
> +                           struct kobj_attribute *attr,                \
> +                           const char *buf, size_t count)              \
> +{                                                                      \
> +       unsigned int val;                                               \
> +       if (kstrtouint(buf, 10, &val))                                  \
> +               return -EINVAL;                                         \
> +       _name = val;                                                    \
> +       return count;                                                   \
> +}                                                                      \
> +static struct kobj_attribute _name##_attr = __ATTR_RW(_name)
> +
> +PSI_ATTR_RW(cpu_thresh);
> +PSI_ATTR_RW(mem_thresh);
> +PSI_ATTR_RW(io_thresh);
> +PSI_ATTR_RW(monitor_interval_ms);
> +PSI_ATTR_RW(rss_weight);
> +PSI_ATTR_RW(io_weight);
> +PSI_ATTR_RW(cpu_weight);
> +
> +static struct attribute *psi_attrs[] = {
> +       &cpu_thresh_attr.attr,
> +       &mem_thresh_attr.attr,
> +       &io_thresh_attr.attr,
> +       &monitor_interval_ms_attr.attr,
> +       &rss_weight_attr.attr,
> +       &io_weight_attr.attr,
> +       &cpu_weight_attr.attr,
> +       NULL,
> +};
> +
> +static const struct attribute_group psi_attr_group = {
> +       .attrs = psi_attrs,
> +};
> +
> +static int __init psi_monitor_init(void)
> +{
> +       int ret;
> +
> +       INIT_DELAYED_WORK(&psi_work, psi_monitor_fn);
> +       queue_delayed_work(system_wq, &psi_work,
> +                       msecs_to_jiffies(monitor_interval_ms));
> +
> +       psi_kobj = kobject_create_and_add("psi_monitor", kernel_kobj);
> +       if (!psi_kobj)
> +               return -ENOMEM;
> +
> +       ret = sysfs_create_group(psi_kobj, &psi_attr_group);
> +       if (ret) {
> +               kobject_put(psi_kobj);
> +               cancel_delayed_work_sync(&psi_work);
> +               return ret;
> +       }
> +
> +       pr_info("psi_monitor: in-kernel PSI auto monitor (weighted + tracepoints) loaded\n");
> +       return 0;
> +}
> +
> +static void __exit psi_monitor_exit(void)
> +{
> +       cancel_delayed_work_sync(&psi_work);
> +       if (psi_kobj)
> +               kobject_put(psi_kobj);
> +       pr_info("psi_monitor: unloaded\n");
> +}
> +
> +module_init(psi_monitor_init);
> +module_exit(psi_monitor_exit);

There is nothing here that warrants putting this in kernel/sched.
Also this gets included by default when config is enabled and starts
dumping a bunch of stats to dmesg without anyone asking. No?

Afaict, almost all of the detail used here is also available from
procfs and people can easily put together a userspace tool if they
need it. Why do we need an in-kernel module?

> +
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("Pintu Kumar Agarwal");
> +MODULE_DESCRIPTION("In-kernel PSI automatic monitor with sysfs, weighted scoring and tracepoints");
> --
> 2.34.1
> 

-- 
Thanks and Regards,
Prateek


  reply	other threads:[~2026-07-02 19:51 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-02 17:16 [RFC PATCH 0/1] psi: Introduce in-kernel PSI auto monitor feature Pintu Kumar Agarwal
2026-07-02 17:16 ` [RFC PATCH 1/1] " Pintu Kumar Agarwal
2026-07-02 19:51   ` K Prateek Nayak [this message]
2026-07-03 15:32     ` Pintu Kumar Agarwal

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=6c190305-4137-411f-ba25-e0a2d8484db7@amd.com \
    --to=kprateek.nayak@amd.com \
    --cc=akpm@linux-foundation.org \
    --cc=aliceryhl@google.com \
    --cc=bsegall@google.com \
    --cc=dianders@chromium.org \
    --cc=dietmar.eggemann@arm.com \
    --cc=gary@garyguo.net \
    --cc=hannes@cmpxchg.org \
    --cc=juri.lelli@redhat.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-trace-kernel@vger.kernel.org \
    --cc=linux.amoon@gmail.com \
    --cc=mathieu.desnoyers@efficios.com \
    --cc=mgorman@suse.de \
    --cc=mhiramat@kernel.org \
    --cc=mingo@redhat.com \
    --cc=nathan@kernel.org \
    --cc=nsc@kernel.org \
    --cc=ojeda@kernel.org \
    --cc=peterz@infradead.org \
    --cc=pintu.agarwal@oss.qualcomm.com \
    --cc=pintu.ping@gmail.com \
    --cc=rdunlap@infradead.org \
    --cc=rostedt@goodmis.org \
    --cc=shuah@kernel.org \
    --cc=surenb@google.com \
    --cc=tglx@kernel.org \
    --cc=thomas.weissschuh@linutronix.de \
    --cc=vincent.guittot@linaro.org \
    --cc=vschneid@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox