[PATCH OLK-6.6 00/10] net: dynthread: dynamic threaded NAPI scheduler
This series adds net/dynthread, a loadable module that adapts the NAPI processing and the RX queue IRQ placement of a set of net devices to the scheduling behaviour of a target application at runtime. Motivation ---------- Interrupt and softirq processing of network RX competes with latency sensitive applications (e.g. mysqld) for CPU time. When such an application runs for a long time on a CPU without blocking, the softirq work of the NIC completion queues can preempt it on the same CPU and inflate its tail latency. The usual remedies are to steer the RX queue IRQs to other CPUs and/or to move NAPI processing into kernel threads bound to spare CPUs, but a static configuration cannot follow the application: once the target goes idle, is scheduled in short bursts, or is displaced, the reserved CPUs are simply wasted. Approach -------- The module periodically samples the scheduling statistics of all threads matching the appname module parameter and classifies them into one of five statuses (DATA_LOSS, NO_TASK, NOT_RUNNING, FRAGMENTATION, LONG_RUNNING_WITH_INTERRUPTS). Acting on that status it: - restores the RX queue IRQs to their original affinity when the target is gone or barely running; - spreads the IRQs over the NUMA node of their queues when processing in softirq context is acceptable again; - switches the NAPI processing of the configured net devices to kernel threads and binds the NAPI threads (and, optionally, the queue IRQs) to the CPUs of their NUMA node that the target tasks are not allowed to use; - scales the number of CPUs used by those threads with their measured load while the target stays in the long-running status. An unusable sample (counters moved backwards: DATA_LOSS) keeps the previous status, so a single bad sample cannot toggle the mode. Everything is controlled through module parameters, and the original IRQ affinity and NAPI mode are restored when the target disappears or the module is unloaded. The module is buildable only as a loadable module; the Kconfig entry currently allows ARM64. Patch breakdown --------------- 1/6 net: dynthread: Add dynamic threaded NAPI scheduler framework Kconfig/Makefile and the module skeleton: record the RX queue IRQs of the configured net devices and their original affinity, and count the NUMA nodes those queues span. 2/6 net: dynthread: Monitor the load of the target tasks The sampling loop and the status judgement. 3/6 net: dynthread: Steer the RX queue IRQs away from the target tasks Manage the affinity of the recorded IRQs based on the status and the placement of the target tasks. 4/6 net: dynthread: Switch NAPI to threads on spare CPUs Enable threaded NAPI and bind the NAPI threads (and optionally the queue IRQs) to the CPUs not used by the target tasks; switch back to softirq mode in the non-long-running statuses. 5/6 net: dynthread: Scale the number of NAPI thread CPUs Adapt the number of CPUs used by the NAPI threads to their load. 6/6 net: dynthread: Add debug switch and instrumentation Add the dyn_threaded_debug module parameter and the ftrace based instrumentation of the sampling, the status transitions and the IRQ/NAPI placement decisions. Amritha Nambiar (2): net: Add queue and napi association net: Add NAPI IRQ support Cai Xinchen (6): net: dynthread: Add dynamic threaded NAPI scheduler framework net: dynthread: Monitor the load of the target tasks net: dynthread: Steer the RX queue IRQs away from the target tasks net: dynthread: Switch NAPI to threads on spare CPUs net: dynthread: Scale the number of NAPI thread CPUs net: dynthread: Add debug switch and instrumentation Carolina Jubran (1): net/mlx5e: Acquire RTNL lock before RQs/SQs activation/deactivation Joe Damato (1): net/mlx5e: link NAPI instances to queues and IRQs .../net/ethernet/mellanox/mlx5/core/en/ptp.c | 5 +- .../mellanox/mlx5/core/en/reporter_tx.c | 7 + .../net/ethernet/mellanox/mlx5/core/en_main.c | 7 + include/linux/netdevice.h | 20 +- include/net/netdev_rx_queue.h | 2 +- net/core/dev.c | 38 + net/dynthread/Kconfig | 13 + net/dynthread/Makefile | 2 + net/dynthread/net_dynthread.c | 1258 +++++++++++++++++ 9 files changed, 1349 insertions(+), 3 deletions(-) create mode 100644 net/dynthread/Kconfig create mode 100644 net/dynthread/Makefile create mode 100644 net/dynthread/net_dynthread.c -- 2.18.0.huawei.25
反馈: 您发送到kernel@openeuler.org的补丁/补丁集,已成功转换为PR! PR链接地址: https://atomgit.com/openeuler/kernel/merge_requests/29737 邮件列表地址:https://mailweb.openeuler.org/archives/list/kernel@openeuler.org/message/VPM... FeedBack: The patch(es) which you have sent to kernel@openeuler.org mailing list has been converted to a pull request successfully! Pull request link: https://atomgit.com/openeuler/kernel/merge_requests/29737 Mailing list address: https://mailweb.openeuler.org/archives/list/kernel@openeuler.org/message/VPM...
From: Amritha Nambiar <amritha.nambiar@intel.com> hulk inclusion category: feature bugzilla: https://atomgit.com/openeuler/kernel/issues/10109 ---------------------------------------- Add the napi pointer in netdev queue for tracking the napi instance for each queue. This achieves the queue<->napi mapping. Signed-off-by: Amritha Nambiar <amritha.nambiar@intel.com> Reviewed-by: Sridhar Samudrala <sridhar.samudrala@intel.com> Link: https://lore.kernel.org/r/170147331483.5260.15723438819994285695.stgit@anamb... Signed-off-by: Jakub Kicinski <kuba@kernel.org> Signed-off-by: Cai Xinchen <caixinchen1@huawei.com> --- include/linux/netdevice.h | 13 ++++++++++++ include/net/netdev_rx_queue.h | 2 +- net/core/dev.c | 37 +++++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index 47a4e2cb9477..43cb840f0f2b 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -348,6 +348,11 @@ struct gro_list { int count; }; +enum netdev_queue_type { + NETDEV_QUEUE_TYPE_RX, + NETDEV_QUEUE_TYPE_TX, +}; + /* * size of gro hash buckets, must less than bit number of * napi_struct::gro_bitmask @@ -656,6 +661,10 @@ struct netdev_queue { #ifdef CONFIG_XDP_SOCKETS struct xsk_buff_pool *pool; #endif + /* NAPI instance for the queue + * Readers and writers must hold RTNL + */ + struct napi_struct *napi; /* * write-mostly part */ @@ -2673,6 +2682,10 @@ static inline void *netdev_priv(const struct net_device *dev) */ #define SET_NETDEV_DEVTYPE(net, devtype) ((net)->dev.type = (devtype)) +void netif_queue_set_napi(struct net_device *dev, unsigned int queue_index, + enum netdev_queue_type type, + struct napi_struct *napi); + /* Default NAPI poll() weight * Device drivers are strongly advised to not use bigger value */ diff --git a/include/net/netdev_rx_queue.h b/include/net/netdev_rx_queue.h index 3fb5d8eb18fc..c98ec60499e8 100644 --- a/include/net/netdev_rx_queue.h +++ b/include/net/netdev_rx_queue.h @@ -23,7 +23,7 @@ struct netdev_rx_queue { #endif KABI_USE(1, void *__rcu oecls_ftb) - KABI_RESERVE(2) + KABI_USE(1, struct napi_struct *napi) KABI_RESERVE(3) KABI_RESERVE(4) } ____cacheline_aligned_in_smp; diff --git a/net/core/dev.c b/net/core/dev.c index 6a03b2cb5f05..a8b10001905c 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -6691,6 +6691,43 @@ int dev_set_threaded(struct net_device *dev, bool threaded) } EXPORT_SYMBOL(dev_set_threaded); +/** + * netif_queue_set_napi - Associate queue with the napi + * @dev: device to which NAPI and queue belong + * @queue_index: Index of queue + * @type: queue type as RX or TX + * @napi: NAPI context, pass NULL to clear previously set NAPI + * + * Set queue with its corresponding napi context. This should be done after + * registering the NAPI handler for the queue-vector and the queues have been + * mapped to the corresponding interrupt vector. + */ +void netif_queue_set_napi(struct net_device *dev, unsigned int queue_index, + enum netdev_queue_type type, struct napi_struct *napi) +{ + struct netdev_rx_queue *rxq; + struct netdev_queue *txq; + + if (WARN_ON_ONCE(napi && !napi->dev)) + return; + if (dev->reg_state >= NETREG_REGISTERED) + ASSERT_RTNL(); + + switch (type) { + case NETDEV_QUEUE_TYPE_RX: + rxq = __netif_get_rx_queue(dev, queue_index); + rxq->napi = napi; + return; + case NETDEV_QUEUE_TYPE_TX: + txq = netdev_get_tx_queue(dev, queue_index); + txq->napi = napi; + return; + default: + return; + } +} +EXPORT_SYMBOL(netif_queue_set_napi); + void netif_napi_add_weight(struct net_device *dev, struct napi_struct *napi, int (*poll)(struct napi_struct *, int), int weight) { -- 2.18.0.huawei.25
From: Amritha Nambiar <amritha.nambiar@intel.com> hulk inclusion category: feature bugzilla: https://atomgit.com/openeuler/kernel/issues/10109 ---------------------------------------- Add support to associate the interrupt vector number for a NAPI instance. Signed-off-by: Amritha Nambiar <amritha.nambiar@intel.com> Reviewed-by: Sridhar Samudrala <sridhar.samudrala@intel.com> Link: https://lore.kernel.org/r/170147334728.5260.13221803396905901904.stgit@anamb... Signed-off-by: Jakub Kicinski <kuba@kernel.org> Signed-off-by: Cai Xinchen <caixinchen1@huawei.com> --- include/linux/netdevice.h | 7 ++++++- net/core/dev.c | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index 43cb840f0f2b..03372828a685 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -394,7 +394,7 @@ struct napi_struct { struct list_head dev_list; struct hlist_node napi_hash_node; - KABI_RESERVE(1) + KABI_USE(1, int irq) KABI_RESERVE(2) KABI_RESERVE(3) KABI_RESERVE(4) @@ -2686,6 +2686,11 @@ void netif_queue_set_napi(struct net_device *dev, unsigned int queue_index, enum netdev_queue_type type, struct napi_struct *napi); +static inline void netif_napi_set_irq(struct napi_struct *napi, int irq) +{ + napi->irq = irq; +} + /* Default NAPI poll() weight * Device drivers are strongly advised to not use bigger value */ diff --git a/net/core/dev.c b/net/core/dev.c index a8b10001905c..9dd9761928d9 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -6763,6 +6763,7 @@ void netif_napi_add_weight(struct net_device *dev, struct napi_struct *napi, */ if (dev->threaded && napi_kthread_create(napi)) dev->threaded = 0; + netif_napi_set_irq(napi, -1); } EXPORT_SYMBOL(netif_napi_add_weight); -- 2.18.0.huawei.25
From: Joe Damato <jdamato@fastly.com> hulk inclusion category: feature bugzilla: https://atomgit.com/openeuler/kernel/issues/10109 ---------------------------------------- Make mlx5 compatible with the newly added netlink queue GET APIs. Signed-off-by: Joe Damato <jdamato@fastly.com> Reviewed-by: Tariq Toukan <tariqt@nvidia.com> Link: https://lore.kernel.org/r/20240209202312.30181-1-jdamato@fastly.com Signed-off-by: Jakub Kicinski <kuba@kernel.org> Signed-off-by: Cai Xinchen <caixinchen1@huawei.com> --- drivers/net/ethernet/mellanox/mlx5/core/en/ptp.c | 5 ++++- drivers/net/ethernet/mellanox/mlx5/core/en_main.c | 7 +++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/ptp.c b/drivers/net/ethernet/mellanox/mlx5/core/en/ptp.c index 15d97c685ad3..b223ddeb2b2d 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en/ptp.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en/ptp.c @@ -929,6 +929,7 @@ void mlx5e_ptp_activate_channel(struct mlx5e_ptp *c) if (test_bit(MLX5E_PTP_STATE_RX, c->state)) { mlx5e_ptp_rx_set_fs(c->priv); mlx5e_activate_rq(&c->rq); + netif_queue_set_napi(c->netdev, c->rq.ix, NETDEV_QUEUE_TYPE_RX, &c->napi); } mlx5e_trigger_napi_sched(&c->napi); } @@ -937,8 +938,10 @@ void mlx5e_ptp_deactivate_channel(struct mlx5e_ptp *c) { int tc; - if (test_bit(MLX5E_PTP_STATE_RX, c->state)) + if (test_bit(MLX5E_PTP_STATE_RX, c->state)) { + netif_queue_set_napi(c->netdev, c->rq.ix, NETDEV_QUEUE_TYPE_RX, NULL); mlx5e_deactivate_rq(&c->rq); + } if (test_bit(MLX5E_PTP_STATE_TX, c->state)) { for (tc = 0; tc < c->num_tc; tc++) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c index 68202858cc81..2fdcc56f3167 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c @@ -1731,6 +1731,7 @@ void mlx5e_activate_txqsq(struct mlx5e_txqsq *sq) set_bit(MLX5E_SQ_STATE_ENABLED, &sq->state); netdev_tx_reset_queue(sq->txq); netif_tx_start_queue(sq->txq); + netif_queue_set_napi(sq->netdev, sq->txq_ix, NETDEV_QUEUE_TYPE_TX, sq->cq.napi); } void mlx5e_tx_disable_queue(struct netdev_queue *txq) @@ -1744,6 +1745,7 @@ void mlx5e_deactivate_txqsq(struct mlx5e_txqsq *sq) { struct mlx5_wq_cyc *wq = &sq->wq; + netif_queue_set_napi(sq->netdev, sq->txq_ix, NETDEV_QUEUE_TYPE_TX, NULL); clear_bit(MLX5E_SQ_STATE_ENABLED, &sq->state); synchronize_net(); /* Sync with NAPI to prevent netif_tx_wake_queue. */ @@ -2480,6 +2482,7 @@ static int mlx5e_open_channel(struct mlx5e_priv *priv, int ix, c->lag_port = mlx5e_enumerate_lag_port(priv->mdev, ix); netif_napi_add(netdev, &c->napi, mlx5e_napi_poll); + netif_napi_set_irq(&c->napi, irq); err = mlx5e_open_queues(c, params, cparam); if (unlikely(err)) @@ -2522,12 +2525,16 @@ static void mlx5e_activate_channel(struct mlx5e_channel *c) mlx5e_activate_xsk(c); else mlx5e_activate_rq(&c->rq); + + netif_queue_set_napi(c->netdev, c->ix, NETDEV_QUEUE_TYPE_RX, &c->napi); } static void mlx5e_deactivate_channel(struct mlx5e_channel *c) { int tc; + netif_queue_set_napi(c->netdev, c->ix, NETDEV_QUEUE_TYPE_RX, NULL); + if (test_bit(MLX5E_CHANNEL_STATE_XSK, c->state)) mlx5e_deactivate_xsk(c); else -- 2.18.0.huawei.25
From: Carolina Jubran <cjubran@nvidia.com> hulk inclusion category: bugfix bugzilla: https://atomgit.com/openeuler/kernel/issues/10109 ---------------------------------------- netif_queue_set_napi asserts whether RTNL lock is held if the netdev is initialized. Acquire the RTNL lock before activating or deactivating RQs/SQs if the lock has not been held before in the flow. Fixes: f25e7b82635f ("net/mlx5e: link NAPI instances to queues and IRQs") Cc: Joe Damato <jdamato@fastly.com> Signed-off-by: Carolina Jubran <cjubran@nvidia.com> Reviewed-by: Rahul Rameshbabu <rrameshbabu@nvidia.com> Signed-off-by: Tariq Toukan <tariqt@nvidia.com> Link: https://lore.kernel.org/r/20240411115444.374475-6-tariqt@nvidia.com Signed-off-by: Jakub Kicinski <kuba@kernel.org> Signed-off-by: Cai Xinchen <caixinchen1@huawei.com> --- drivers/net/ethernet/mellanox/mlx5/core/en/reporter_tx.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en/reporter_tx.c b/drivers/net/ethernet/mellanox/mlx5/core/en/reporter_tx.c index 1319e9ee20fc..9ad9e375315a 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en/reporter_tx.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en/reporter_tx.c @@ -113,7 +113,10 @@ static int mlx5e_tx_reporter_err_cqe_recover(void *ctx) mlx5e_reset_txqsq_cc_pc(sq); sq->stats->recover++; clear_bit(MLX5E_SQ_STATE_RECOVERING, &sq->state); + rtnl_lock(); mlx5e_activate_txqsq(sq); + rtnl_unlock(); + if (sq->channel) mlx5e_trigger_napi_icosq(sq->channel); else @@ -186,12 +189,16 @@ static int mlx5e_tx_reporter_ptpsq_unhealthy_recover(void *ctx) carrier_ok = netif_carrier_ok(netdev); netif_carrier_off(netdev); + rtnl_lock(); mlx5e_deactivate_priv_channels(priv); + rtnl_unlock(); mlx5e_ptp_close(chs->ptp); err = mlx5e_ptp_open(priv, &chs->params, chs->c[0]->lag_port, &chs->ptp); + rtnl_lock(); mlx5e_activate_priv_channels(priv); + rtnl_unlock(); /* return carrier back if needed */ if (carrier_ok) -- 2.18.0.huawei.25
hulk inclusion category: feature bugzilla: https://atomgit.com/openeuler/kernel/issues/10109 ---------------------------------------- Add a new loadable module net/dynthread, which will be used to switch the NAPI processing of the configured net devices between softirq context and kernel threads at runtime, based on the load of a target application. As a first step add the Kconfig entry (restricted to ARM64 and to be built as a module only), the MAINTAINERS entry and the module skeleton. On load the module takes a reference on the net devices given with the ifname module parameter ('#' separated list of names), walks the IRQ descriptor table and records the number and the current affinity CPU of every RX queue IRQ of those devices. IRQs are matched by name: an IRQ belongs to a device when its name contains the device name or the name of its parent device and one of the common RX queue IRQ name patterns, which can be extended via the irqname module parameter. The recorded affinity allows the later patches to restore the original IRQ placement. The net device reference is dropped when the module is unloaded. Co-developed-by: Yue Haibing <yuehaibing@huawei.com> Signed-off-by: Yue Haibing <yuehaibing@huawei.com> Assisted-by: opencode:glm-5.3 Signed-off-by: Cai Xinchen <caixinchen1@huawei.com> --- net/dynthread/Kconfig | 13 ++ net/dynthread/Makefile | 2 + net/dynthread/net_dynthread.c | 273 ++++++++++++++++++++++++++++++++++ 3 files changed, 288 insertions(+) create mode 100644 net/dynthread/Kconfig create mode 100644 net/dynthread/Makefile create mode 100644 net/dynthread/net_dynthread.c diff --git a/net/dynthread/Kconfig b/net/dynthread/Kconfig new file mode 100644 index 000000000000..b7c19723937c --- /dev/null +++ b/net/dynthread/Kconfig @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0-only +config NET_DYNTHREAD + tristate "Dynamic threaded NAPI scheduler" + depends on m && ARM64 + default m + help + Monitor the scheduling behaviour of a target application and, + when it is interrupted too often by network processing, switch + the NAPI processing of the configured net devices from softirq + context to dedicated kernel threads, whose CPU affinity is + adapted dynamically. + + This module can only be built as a loadable module. diff --git a/net/dynthread/Makefile b/net/dynthread/Makefile new file mode 100644 index 000000000000..9c7ab5f5c2e9 --- /dev/null +++ b/net/dynthread/Makefile @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_NET_DYNTHREAD) += net_dynthread.o diff --git a/net/dynthread/net_dynthread.c b/net/dynthread/net_dynthread.c new file mode 100644 index 000000000000..1aa7b35cfd94 --- /dev/null +++ b/net/dynthread/net_dynthread.c @@ -0,0 +1,273 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Dynamic threaded NAPI scheduler + * + * Record the RX queue IRQ information of the net devices configured + * via the ifname module parameter, so that the IRQ placement can be + * reworked at runtime. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/irq.h> +#include <linux/netdevice.h> +#include <linux/topology.h> + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Dynamic threaded NAPI scheduler"); + +#define SCHED_MAX_NETDEV_NUM 8 +#define MAX_DEV_RXQ_NUM 256 + +static char ifname[64]; +module_param_string(ifname, ifname, sizeof(ifname), 0644); +MODULE_PARM_DESC(ifname, "net device names to monitor, separated by '#'"); + +static char irqname[64] = "comp"; +module_param_string(irqname, irqname, sizeof(irqname), 0644); +MODULE_PARM_DESC(irqname, "substring of the NIC RX queue IRQ names, default comp"); + +/** + * struct sched_netdev_queue_info - IRQ information of one RX queue + * @irq: Linux IRQ number of the queue + * @affinity_cpu: CPU the IRQ was affined to when the module was loaded + */ +struct sched_netdev_queue_info { + int irq; + int affinity_cpu; +}; + +/** + * struct sched_netdev - net device to monitor + * @dev_name: interface name + * @netdev: net device with a reference held by this module + * @rxq_num: number of RX queue IRQs recorded + * @node_num: number of NUMA nodes the RX queue IRQs span + * @rxq: IRQ information of the RX queues + */ +struct sched_netdev { + char dev_name[IFNAMSIZ]; + struct net_device *netdev; + int rxq_num; + int node_num; + struct sched_netdev_queue_info rxq[MAX_DEV_RXQ_NUM]; +}; + +static int sched_netdev_num; +static struct sched_netdev sched_netdev_table[SCHED_MAX_NETDEV_NUM]; + +static struct sched_netdev *get_sched_netdev(int index) +{ + if (index >= SCHED_MAX_NETDEV_NUM) + return NULL; + + return &sched_netdev_table[index]; +} + +static struct sched_netdev *alloc_sched_netdev(void) +{ + if (sched_netdev_num >= SCHED_MAX_NETDEV_NUM) + return NULL; + + return &sched_netdev_table[sched_netdev_num++]; +} + +/* Does @irq_name belong to an RX queue of @sched_dev? The default + * patterns match the common NIC completion and RX queue IRQ names, the + * user provided pattern is used as an additional match when not empty. + */ +static bool check_irq_name(const char *irq_name, struct sched_netdev *sched_dev) +{ + if (!strstr(irq_name, "TxRx") && !strstr(irq_name, "comp") && + !strstr(irq_name, "rx") && strlen(irqname) > 0 && + !strstr(irq_name, irqname)) + return false; + + if (strstr(irq_name, sched_dev->dev_name)) + return true; + + if (sched_dev->netdev->dev.parent && + strstr(irq_name, dev_name(sched_dev->netdev->dev.parent))) + return true; + + return false; +} + +static void get_netdev_queue_info(struct sched_netdev *sched_dev) +{ + struct sched_netdev_queue_info *rxq_info; + struct irq_desc *desc; + struct irq_data *data; + unsigned long flags; + char name[64]; + int irq, cpu; + + /* irq_to_desc(), and thus for_each_irq_desc(), is not exported to + * modules when SPARSE_IRQ is enabled; walk the IRQ numbers through + * the exported irq_get_irq_data() instead + */ + for (irq = 0; irq < nr_irqs; irq++) { + data = irq_get_irq_data(irq); + if (!data) + continue; + + desc = irq_data_to_desc(data); + + /* desc->action and the effective affinity are protected by + * desc->lock; snapshot them under the lock so that the + * check runs on stable data and the name cannot be freed + * by a concurrent free_irq() + */ + cpu = nr_cpu_ids; + name[0] = '\0'; + raw_spin_lock_irqsave(&desc->lock, flags); + if (desc->action && desc->action->name && + check_irq_name(desc->action->name, sched_dev)) { + strscpy(name, desc->action->name, sizeof(name)); + cpu = cpumask_first(irq_data_get_effective_affinity_mask( + &desc->irq_data)); + } + raw_spin_unlock_irqrestore(&desc->lock, flags); + + /* unknown affinity, or the rxq array is full: skip */ + if (!name[0] || cpu >= nr_cpu_ids) + continue; + if (sched_dev->rxq_num >= MAX_DEV_RXQ_NUM) + break; + + rxq_info = &sched_dev->rxq[sched_dev->rxq_num++]; + rxq_info->irq = irq; + rxq_info->affinity_cpu = cpu; + } +} + +/* Drop the references taken on the devices and forget them. Used + * both on module unload and to unwind a partially failed init. + */ +static void deinit_netdevinfo(void) +{ + struct sched_netdev *sched_dev; + int id; + + for (id = 0; id < sched_netdev_num; id++) { + sched_dev = get_sched_netdev(id); + if (sched_dev && sched_dev->netdev) + dev_put(sched_dev->netdev); + } + sched_netdev_num = 0; +} + +/* count_sched_netdev_nodes - count the NUMA nodes of the RX queues */ +static void count_sched_netdev_nodes(struct sched_netdev *sched_dev) +{ + nodemask_t nodes; + int rxq_idx, node; + + nodes_clear(nodes); + for (rxq_idx = 0; rxq_idx < sched_dev->rxq_num; rxq_idx++) { + node = cpu_to_node(sched_dev->rxq[rxq_idx].affinity_cpu); + if (node >= 0) + node_set(node, nodes); + } + sched_dev->node_num = nodes_weight(nodes); +} + +static int init_dev(const char *if_name, size_t length) +{ + struct sched_netdev *sched_dev; + struct net_device *netdev; + char dev_name[IFNAMSIZ]; + int ret; + + strscpy(dev_name, if_name, min_t(size_t, length + 1, IFNAMSIZ)); + netdev = dev_get_by_name(&init_net, dev_name); + if (!netdev) + return -ENODEV; + + if (!(netdev->flags & IFF_UP)) { + ret = -ENETDOWN; + goto err_dev_put; + } + + if (netdev->flags & IFF_LOOPBACK) { + ret = -EOPNOTSUPP; + goto err_dev_put; + } + + sched_dev = alloc_sched_netdev(); + if (!sched_dev) { + ret = -ENOMEM; + goto err_dev_put; + } + + memcpy(sched_dev->dev_name, dev_name, IFNAMSIZ); + sched_dev->netdev = netdev; + get_netdev_queue_info(sched_dev); + count_sched_netdev_nodes(sched_dev); + + return 0; + +err_dev_put: + dev_put(netdev); + return ret; +} + +/* Parse the '#' separated list of net device names and record the RX + * queue IRQ information of each device. The reference taken on each + * net device is dropped on module unload. + */ +static int init_netdevinfo(const char *netdev_str) +{ + const char *start = netdev_str, *end; + int err = -ENODEV; + + while (*start != '\0') { + /* skip empty names */ + end = strchr(start, '#'); + if (end == start) { + start++; + continue; + } + + /* the last name has no separator following it */ + if (!end) { + err = init_dev(start, strlen(start)); + break; + } + + err = init_dev(start, end - start); + if (err) + break; + + start = end + 1; + } + + if (err) + deinit_netdevinfo(); + + return err; +} + +static int __init dynthread_init(void) +{ + int ret; + + ret = init_netdevinfo(ifname); + if (ret) + return ret; + + + return 0; +} + +static void __exit dynthread_exit(void) +{ + deinit_netdevinfo(); + +} + +module_init(dynthread_init); +module_exit(dynthread_exit); -- 2.18.0.huawei.25
Add the monitoring part of the module and its runtime loop. Writing 1 to the dynthreaded module parameter starts a repeating hrtimer which schedules a work item every dyn_threaded_internal ms; writing 0 (or unloading the module) stops it again. The work item takes a sample of the scheduling statistics of all threads whose name matches the appname module parameter: their total runtime, the total number of context switches and their voluntary/involuntary split, as well as the union of the CPU masks allowed to those tasks. Each sample is compared with the previous one and judged as: DATA_LOSS counters moved backwards, the sample is dropped and the previous status is kept NO_TASK no thread with the target name exists NOT_RUNNING avg runtime below dyn_threaded_not_running_limit, the tasks hardly get the CPU LONG_RUNNING_WITH_INTERRUPTS avg runtime above dyn_threaded_avg_runtime_limit and a low voluntary switch ratio, i.e. the tasks are disturbed by softirq activity FRAGMENTATION short avg runtime per context switch, processing in softirq context is acceptable For the sample right after entering LONG_RUNNING_WITH_INTERRUPTS, the avg runtime and switch ratio of the last sample before the change are carried over, so that the status decision is not based on the transient sample. The subsequent patches act on this status. Co-developed-by: Yue Haibing <yuehaibing@huawei.com> Signed-off-by: Yue Haibing <yuehaibing@huawei.com> Assisted-by: opencode:glm-5.3 Signed-off-by: Cai Xinchen <caixinchen1@huawei.com> --- net/dynthread/net_dynthread.c | 347 +++++++++++++++++++++++++++++++++- 1 file changed, 344 insertions(+), 3 deletions(-) diff --git a/net/dynthread/net_dynthread.c b/net/dynthread/net_dynthread.c index 1aa7b35cfd94..c600428220f2 100644 --- a/net/dynthread/net_dynthread.c +++ b/net/dynthread/net_dynthread.c @@ -1,9 +1,11 @@ // SPDX-License-Identifier: GPL-2.0-only /* Dynamic threaded NAPI scheduler * - * Record the RX queue IRQ information of the net devices configured - * via the ifname module parameter, so that the IRQ placement can be - * reworked at runtime. + * Monitor the scheduling behaviour of a target application and switch + * the NAPI processing of the configured net devices between softirq + * context and kernel threads, whose CPU affinity is adapted to the + * measured load. The RX queue IRQ placement of the devices is + * adjusted as well. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -12,6 +14,13 @@ #include <linux/module.h> #include <linux/kernel.h> #include <linux/string.h> +#include <linux/rcupdate.h> +#include <linux/cpumask.h> +#include <linux/ktime.h> +#include <linux/hrtimer.h> +#include <linux/workqueue.h> +#include <linux/sched.h> +#include <linux/sched/signal.h> #include <linux/irq.h> #include <linux/netdevice.h> #include <linux/topology.h> @@ -19,8 +28,10 @@ MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Dynamic threaded NAPI scheduler"); +#define ONE_HUNDRED 100 #define SCHED_MAX_NETDEV_NUM 8 #define MAX_DEV_RXQ_NUM 256 +#define DYN_THREAD_INTERVAL (dyn_threaded_internal * NSEC_PER_MSEC) static char ifname[64]; module_param_string(ifname, ifname, sizeof(ifname), 0644); @@ -30,6 +41,66 @@ static char irqname[64] = "comp"; module_param_string(irqname, irqname, sizeof(irqname), 0644); MODULE_PARM_DESC(irqname, "substring of the NIC RX queue IRQ names, default comp"); +static char appname[64] = "mysqld"; +module_param_string(appname, appname, sizeof(appname), 0644); +MODULE_PARM_DESC(appname, "name of the target application threads, default mysqld"); + +static uint dyn_threaded_nvcsw_per_limit = 95; +module_param(dyn_threaded_nvcsw_per_limit, uint, 0644); +MODULE_PARM_DESC(dyn_threaded_nvcsw_per_limit, + "max nvcsw percent for long running status, default 95"); + +static uint dyn_threaded_avg_runtime_limit = 350000; +module_param(dyn_threaded_avg_runtime_limit, uint, 0644); +MODULE_PARM_DESC(dyn_threaded_avg_runtime_limit, + "min avg runtime (ns) for long running status, default 350000"); + +static uint dyn_threaded_not_running_limit = 30000; +module_param(dyn_threaded_not_running_limit, uint, 0644); +MODULE_PARM_DESC(dyn_threaded_not_running_limit, + "max avg runtime (ns) for not running status, default 30000"); + +static uint dyn_threaded_internal = 1000; + +static int dyn_threaded_internal_set(const char *val, + const struct kernel_param *kp) +{ + uint interval; + int ret; + + ret = kstrtouint(val, 0, &interval); + if (ret) + return ret; + + /* a zero interval would divide by zero and spin the timer */ + if (!interval) + return -EINVAL; + + *(uint *)kp->arg = interval; + + return 0; +} + +static int dyn_threaded_internal_get(char *buffer, + const struct kernel_param *kp) +{ + return scnprintf(buffer, PAGE_SIZE, "%u\n", *(uint *)kp->arg); +} + +static const struct kernel_param_ops dyn_threaded_internal_ops = { + .set = dyn_threaded_internal_set, + .get = dyn_threaded_internal_get, +}; + +module_param_cb(dyn_threaded_internal, &dyn_threaded_internal_ops, + &dyn_threaded_internal, 0644); +MODULE_PARM_DESC(dyn_threaded_internal, "sample interval in ms, default 1000"); + +static uint dyn_threaded_avg_runtime_keep_per = 90; +module_param(dyn_threaded_avg_runtime_keep_per, uint, 0644); +MODULE_PARM_DESC(dyn_threaded_avg_runtime_keep_per, + "min percent of avg runtime kept in long running status, default 90"); + /** * struct sched_netdev_queue_info - IRQ information of one RX queue * @irq: Linux IRQ number of the queue @@ -56,6 +127,51 @@ struct sched_netdev { struct sched_netdev_queue_info rxq[MAX_DEV_RXQ_NUM]; }; +/** + * enum task_status - scheduling state of the target tasks + * @DATA_LOSS: the counters moved backwards, the sample is not usable + * @NO_TASK: no task with the target name exists + * @NOT_RUNNING: the tasks hardly get the CPU + * @FRAGMENTATION: the tasks are scheduled out quickly, softirq mode is fine + * @LONG_RUNNING_WITH_INTERRUPTS: the tasks run long between context + * switches and should not be disturbed by softirq activity + */ +enum task_status { + DATA_LOSS, + NO_TASK, + NOT_RUNNING, + FRAGMENTATION, + LONG_RUNNING_WITH_INTERRUPTS, +}; + +/** + * struct sched_task_runtime_avg_info - scheduling statistics of the target tasks + * @total_switches: total context switches since boot + * @total_runtime_ns: total runtime in ns since boot + * @total_nvcsw: total voluntary context switches since boot + * @total_nivcsw: total involuntary context switches since boot + * @avg_runtime: avg runtime of the sample in ns, per context switch + * @nvcsw_per: percent of voluntary context switches of the sample + * @cpuload: total load of the sample in percent, over all allowed CPUs + * @thread_cnt: number of matching threads + * @status: task status judged for this sample + * @last_status: task status of the previous sample + * @comm: name of the target tasks + */ +struct sched_task_runtime_avg_info { + u64 total_switches; + u64 total_runtime_ns; + u64 total_nvcsw; + u64 total_nivcsw; + u64 avg_runtime; + u64 nvcsw_per; + u64 cpuload; + u32 thread_cnt; + enum task_status status; + enum task_status last_status; + char comm[TASK_COMM_LEN]; +}; + static int sched_netdev_num; static struct sched_netdev sched_netdev_table[SCHED_MAX_NETDEV_NUM]; @@ -251,6 +367,228 @@ static int init_netdevinfo(const char *netdev_str) return err; } +/* status of the last sample of the target tasks */ +static struct sched_task_runtime_avg_info snapshot; + +/* union of the CPU masks allowed to the target tasks */ +static struct cpumask target_cpumask; +static u32 target_cpucnt; + +/* Take a sample of the scheduling statistics of all threads whose name + * matches @target_comm, and compute the union of their allowed CPU + * masks. The info must be zeroed by the caller. + */ +static int sched_task_runtime_avg_snapshot(const char *target_comm, + struct sched_task_runtime_avg_info *infos) +{ + struct task_struct *g, *p; + char comm[TASK_COMM_LEN]; + u64 nvcsw, nivcsw, switches; + struct cpumask masks; + + if (!target_comm || !infos) + return -EINVAL; + + cpumask_clear(&masks); + strscpy(infos->comm, target_comm, sizeof(infos->comm)); + + rcu_read_lock(); + for_each_process(g) { + get_task_comm(comm, g); + if (strcmp(comm, infos->comm)) + continue; + + cpumask_or(&masks, &masks, g->cpus_ptr); + + for_each_thread(g, p) { + nvcsw = (u64)READ_ONCE(p->nvcsw); + nivcsw = READ_ONCE(p->nivcsw); + switches = nvcsw + nivcsw; + + infos->total_runtime_ns += READ_ONCE(p->se.sum_exec_runtime); + infos->total_switches += switches; + infos->total_nvcsw += nvcsw; + infos->total_nivcsw += nivcsw; + infos->thread_cnt++; + } + } + rcu_read_unlock(); + + cpumask_copy(&target_cpumask, &masks); + WRITE_ONCE(target_cpucnt, cpumask_weight(&target_cpumask)); + + + return 0; +} + +/* load of the target tasks over the CPUs allowed to them, in percent */ +static inline u64 cal_target_task_cpuload(struct sched_task_runtime_avg_info *infos) +{ + return 100 * (infos->total_runtime_ns - snapshot.total_runtime_ns) / + DYN_THREAD_INTERVAL / READ_ONCE(target_cpucnt); +} + +/* Compare the current sample with the previous one and judge the + * status of the target tasks. A sample whose counters moved backwards + * yields DATA_LOSS and is evaluated by the caller only. + */ +static void judge_target_task_status(struct sched_task_runtime_avg_info *info) +{ + u64 switches; + + if (info->total_switches < snapshot.total_switches || + info->total_runtime_ns < snapshot.total_runtime_ns || + info->total_nvcsw < snapshot.total_nvcsw || + info->total_nivcsw < snapshot.total_nivcsw || + strcmp(info->comm, snapshot.comm)) { + info->status = DATA_LOSS; + return; + } + + if (info->thread_cnt == 0) { + info->status = NO_TASK; + return; + } + + /* a sample may contain no context switch, in which case the + * runtime delta is the runtime of the whole sample; clamp the + * switch count to avoid a division by zero + */ + switches = info->total_switches - snapshot.total_switches; + switches = max_t(u64, switches, 1); + + info->avg_runtime = (info->total_runtime_ns - snapshot.total_runtime_ns) / + switches; + info->nvcsw_per = (info->total_nvcsw - snapshot.total_nvcsw) * 100 / + switches; + info->cpuload = cal_target_task_cpuload(info); + + if (info->avg_runtime < dyn_threaded_not_running_limit) { + info->status = NOT_RUNNING; + } else if (info->avg_runtime >= + snapshot.avg_runtime * dyn_threaded_avg_runtime_keep_per / ONE_HUNDRED && + snapshot.status == LONG_RUNNING_WITH_INTERRUPTS && + snapshot.last_status != LONG_RUNNING_WITH_INTERRUPTS && + snapshot.nvcsw_per < dyn_threaded_nvcsw_per_limit) { + /* the status was reached in the previous sample, carry + * over the baseline statistics of the last change + */ + info->status = LONG_RUNNING_WITH_INTERRUPTS; + info->nvcsw_per = snapshot.nvcsw_per; + info->avg_runtime = snapshot.avg_runtime; + } else if (info->avg_runtime > dyn_threaded_avg_runtime_limit && + info->nvcsw_per <= dyn_threaded_nvcsw_per_limit) { + info->status = LONG_RUNNING_WITH_INTERRUPTS; + } else { + info->status = FRAGMENTATION; + } +} + +static int dynthreaded; + +static struct work_struct dyn_threaded_work; +static struct hrtimer dyn_threaded_timer; + +/* periodic work, judge the load of the target tasks */ +static void dyn_threaded_handler(struct work_struct *work) +{ + struct sched_task_runtime_avg_info info = { }; + + /* take a sample and judge the current status */ + sched_task_runtime_avg_snapshot(appname, &info); + judge_target_task_status(&info); + + + switch (info.status) { + case DATA_LOSS: + /* invalid sample, reuse the previous status and statistics */ + info.status = snapshot.status; + info.nvcsw_per = snapshot.nvcsw_per; + info.avg_runtime = snapshot.avg_runtime; + break; + default: + break; + } + + /* remember the status preceding the current one */ + if (info.status == snapshot.status) + info.last_status = snapshot.last_status; + else + info.last_status = snapshot.status; + + + snapshot = info; +} + +static enum hrtimer_restart dyn_threaded_hrtimer_callback(struct hrtimer *timer) +{ + if (!dynthreaded) + return HRTIMER_NORESTART; + + schedule_work(&dyn_threaded_work); + hrtimer_forward_now(timer, ms_to_ktime(dyn_threaded_internal)); + + return HRTIMER_RESTART; +} + +static void start_worker(void) +{ + ktime_t interval = ms_to_ktime(dyn_threaded_internal); + + /* take the first sample as the baseline */ + sched_task_runtime_avg_snapshot(appname, &snapshot); + + INIT_WORK(&dyn_threaded_work, dyn_threaded_handler); + + hrtimer_init(&dyn_threaded_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + dyn_threaded_timer.function = dyn_threaded_hrtimer_callback; + hrtimer_start(&dyn_threaded_timer, interval, HRTIMER_MODE_REL); + +} + +static void stop_worker(void) +{ + hrtimer_cancel(&dyn_threaded_timer); + cancel_work_sync(&dyn_threaded_work); + +} + +/* enable/disable the scheduler through the dynthreaded module parameter */ +static int dynthreaded_set(const char *val, const struct kernel_param *kp) +{ + int new_val, ret; + + ret = kstrtoint(val, 10, &new_val); + if (ret < 0) + return ret; + + new_val = !!new_val; + if (new_val == dynthreaded) + return 0; + + dynthreaded = new_val; + + if (new_val) + start_worker(); + else + stop_worker(); + + return 0; +} + +static int dynthreaded_get(char *buffer, const struct kernel_param *kp) +{ + return scnprintf(buffer, PAGE_SIZE, "%d\n", dynthreaded); +} + +static const struct kernel_param_ops dynthreaded_ops = { + .set = dynthreaded_set, + .get = dynthreaded_get, +}; + +module_param_cb(dynthreaded, &dynthreaded_ops, NULL, 0644); +MODULE_PARM_DESC(dynthreaded, "enable the scheduler, default 0"); + static int __init dynthread_init(void) { int ret; @@ -265,6 +603,9 @@ static int __init dynthread_init(void) static void __exit dynthread_exit(void) { + /* make sure the worker is stopped before releasing the devices */ + dynthreaded_set("0", NULL); + deinit_netdevinfo(); } -- 2.18.0.huawei.25
hulk inclusion category: feature bugzilla: https://atomgit.com/openeuler/kernel/issues/10109 ---------------------------------------- Manage the affinity of the recorded RX queue IRQs based on the status and the CPU placement of the target tasks. All of this is skipped when the dyn_threaded_bindirq module parameter is off. Once the CPUs allowed to the target tasks all sit in a single NUMA node and at least dyn_threaded_irq_cpucore_limit other CPUs are available in that node, all RX queue IRQs are moved onto those other CPUs, spread by round-robin; a CPU whose binding fails is retried with the next IRQ. This tries to keep the network interrupts away from the node the target application runs on. The handler acts on the target task status: NO_TASK/NOT_RUNNING restore the affinity the IRQs had when the module was loaded FRAGMENTATION the CPUs can afford IRQ work again; spread the IRQs over a subset of the CPUs of their NUMA node, using dyn_threaded_irq_cpucore_per percent of the number of RX queues, so that unrelated CPUs keep their IRQs Bound IRQs are remembered in the affinity_to_one_node state, which suppresses the spreading above until it is recovered. Co-developed-by: Yue Haibing <yuehaibing@huawei.com> Signed-off-by: Yue Haibing <yuehaibing@huawei.com> Assisted-by: opencode:glm-5.3 Signed-off-by: Cai Xinchen <caixinchen1@huawei.com> --- net/dynthread/net_dynthread.c | 201 +++++++++++++++++++++++++++++++++- 1 file changed, 200 insertions(+), 1 deletion(-) diff --git a/net/dynthread/net_dynthread.c b/net/dynthread/net_dynthread.c index c600428220f2..719ca0d563ed 100644 --- a/net/dynthread/net_dynthread.c +++ b/net/dynthread/net_dynthread.c @@ -18,12 +18,13 @@ #include <linux/cpumask.h> #include <linux/ktime.h> #include <linux/hrtimer.h> +#include <linux/interrupt.h> #include <linux/workqueue.h> #include <linux/sched.h> #include <linux/sched/signal.h> +#include <linux/topology.h> #include <linux/irq.h> #include <linux/netdevice.h> -#include <linux/topology.h> MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Dynamic threaded NAPI scheduler"); @@ -101,6 +102,20 @@ module_param(dyn_threaded_avg_runtime_keep_per, uint, 0644); MODULE_PARM_DESC(dyn_threaded_avg_runtime_keep_per, "min percent of avg runtime kept in long running status, default 90"); +static uint dyn_threaded_bindirq = 1; +module_param(dyn_threaded_bindirq, uint, 0644); +MODULE_PARM_DESC(dyn_threaded_bindirq, "bind the device irqs to spare cpus too, default 1"); + +static uint dyn_threaded_irq_cpucore_limit = 8; +module_param(dyn_threaded_irq_cpucore_limit, uint, 0644); +MODULE_PARM_DESC(dyn_threaded_irq_cpucore_limit, + "min number of cpus left for the irqs in the target node, default 8"); + +static uint dyn_threaded_irq_cpucore_per = 50; +module_param(dyn_threaded_irq_cpucore_per, uint, 0644); +MODULE_PARM_DESC(dyn_threaded_irq_cpucore_per, + "percent of rx queues to keep per NUMA node, default 50"); + /** * struct sched_netdev_queue_info - IRQ information of one RX queue * @irq: Linux IRQ number of the queue @@ -484,6 +499,171 @@ static void judge_target_task_status(struct sched_task_runtime_avg_info *info) } } +/* true once the RX queue IRQs have been bound to a single NUMA node */ +static bool affinity_to_one_node; + +/* select_napi_cpus - set the first @core_num CPUs of @allowed in @result + * @core_num: desired weight of @result + * @allowed: allowed CPUs + * @result: resulting CPU mask + */ +static void select_napi_cpus(u32 core_num, const struct cpumask *allowed, + struct cpumask *result) +{ + int cpu; + int count = 0; + + cpumask_clear(result); + for_each_cpu(cpu, allowed) { + if (count >= core_num) + break; + cpumask_set_cpu(cpu, result); + count++; + } +} + +/* Bind @irq to the CPU chosen by round-robin position @index in @bind_mask. */ +static void bind_irq_in_cpumask(int irq, int index, const struct cpumask *bind_mask) +{ + unsigned int len = cpumask_weight(bind_mask); + int cpu; + + if (!len) + return; + + cpu = cpumask_nth(index % len, bind_mask); + if (cpu >= nr_cpu_ids) + return; + + irq_set_affinity(irq, cpumask_of(cpu)); +} + +/* Spread the RX queue IRQs of every monitored device over the CPUs of + * their NUMA node, round-robin over all queues of all devices. The + * number of CPUs used per node comes from the number of RX queues, + * reduced to dyn_threaded_irq_cpucore_per percent. Skipped when IRQ + * binding is disabled or after the IRQs were bound to a single node. + */ +static void bind_irq(void) +{ + struct sched_netdev *sched_dev; + const struct cpumask *irq_mask; + const struct cpumask *node_mask; + struct cpumask bind_mask; + int id, rxq_idx, irq; + u32 cpumask_idx = 0; + u32 node_num; + u32 num_per_numa; + + if (!dyn_threaded_bindirq || affinity_to_one_node) + return; + + for (id = 0; id < sched_netdev_num; id++) { + sched_dev = get_sched_netdev(id); + if (!sched_dev) + continue; + + node_num = sched_dev->node_num ? sched_dev->node_num : 1; + num_per_numa = (sched_dev->rxq_num + node_num - 1) / + node_num * dyn_threaded_irq_cpucore_per / + ONE_HUNDRED; + for (rxq_idx = 0; rxq_idx < sched_dev->rxq_num; rxq_idx++) { + irq = sched_dev->rxq[rxq_idx].irq; + irq_mask = irq_get_effective_affinity_mask(irq); + if (unlikely(!irq_mask) || cpumask_empty(irq_mask)) + node_mask = cpu_online_mask; + else + node_mask = cpu_cpu_mask(cpumask_first(irq_mask)); + + select_napi_cpus(num_per_numa, node_mask, &bind_mask); + bind_irq_in_cpumask(irq, cpumask_idx++, &bind_mask); + } + } +} + +/* Is the CPU mask, if not empty, contained in a single NUMA node? */ +static inline bool cpumask_in_one_numa(const struct cpumask *mask) +{ + const struct cpumask *node_mask; + int first_cpu; + + if (cpumask_empty(mask)) + return false; + + /* the mask is in one node if it is a subset of the node of its + * first CPU + */ + first_cpu = cpumask_first(mask); + node_mask = cpu_cpu_mask(first_cpu); + + return cpumask_subset(mask, node_mask); +} + +/* Move all RX queue IRQs to the CPUs of the NUMA node of the target + * tasks that are not allowed to those tasks, spread by round-robin. + * Only done if at least dyn_threaded_irq_cpucore_limit CPUs are left + * for the IRQs in that node; on a failed binding the CPU is retried + * with the next IRQ. + */ +static void set_irq_node_affinity(const struct cpumask *tasks_cpumask) +{ + struct sched_netdev *sched_dev; + const struct cpumask *node_mask; + struct cpumask irq_prefer_mask; + int cpu; + int dev_id, rxq_id; + + cpumask_clear(&irq_prefer_mask); + cpu = cpumask_first(tasks_cpumask); + node_mask = cpu_cpu_mask(cpu); + + /* candidates are the CPUs of the node without the target tasks' ones */ + cpumask_andnot(&irq_prefer_mask, node_mask, tasks_cpumask); + if (cpumask_weight(&irq_prefer_mask) < dyn_threaded_irq_cpucore_limit) + return; + + + cpu = cpumask_first(&irq_prefer_mask); + for (dev_id = 0; dev_id < sched_netdev_num; dev_id++) { + sched_dev = get_sched_netdev(dev_id); + for (rxq_id = 0; rxq_id < sched_dev->rxq_num; rxq_id++) { + if (!irq_set_affinity(sched_dev->rxq[rxq_id].irq, + cpumask_of(cpu))) { + cpu = cpumask_next(cpu, &irq_prefer_mask); + if (cpu >= nr_cpu_ids) + cpu = cpumask_first(&irq_prefer_mask); + } else { + } + } + } + + affinity_to_one_node = true; +} + +/* Restore the affinity the RX queue IRQs had when the module was + * loaded, as changed by set_irq_node_affinity() or bind_irq(). + */ +static void recover_irq_affinity(void) +{ + struct sched_netdev *sched_dev; + int dev_id, rxq_id; + + if (!dyn_threaded_bindirq) + return; + + for (dev_id = 0; dev_id < sched_netdev_num; dev_id++) { + sched_dev = get_sched_netdev(dev_id); + for (rxq_id = 0; rxq_id < sched_dev->rxq_num; rxq_id++) { + if (sched_dev->rxq[rxq_id].affinity_cpu >= nr_cpu_ids) + continue; + irq_set_affinity(sched_dev->rxq[rxq_id].irq, + cpumask_of(sched_dev->rxq[rxq_id].affinity_cpu)); + } + } + + affinity_to_one_node = false; +} + static int dynthreaded; static struct work_struct dyn_threaded_work; @@ -498,14 +678,33 @@ static void dyn_threaded_handler(struct work_struct *work) sched_task_runtime_avg_snapshot(appname, &info); judge_target_task_status(&info); + /* bind the irqs to one node if the target tasks sit in one node */ + if (!affinity_to_one_node && cpumask_in_one_numa(&target_cpumask) && + dyn_threaded_bindirq) + set_irq_node_affinity(&target_cpumask); + switch (info.status) { + case NO_TASK: + /* no target tasks: restore the original irq affinity */ + recover_irq_affinity(); + break; case DATA_LOSS: /* invalid sample, reuse the previous status and statistics */ info.status = snapshot.status; info.nvcsw_per = snapshot.nvcsw_per; info.avg_runtime = snapshot.avg_runtime; break; + case NOT_RUNNING: + /* target tasks barely running: restore the original irqs */ + recover_irq_affinity(); + break; + case FRAGMENTATION: + /* irqs may consume cpu time again, spread them over + * their NUMA node + */ + bind_irq(); + break; default: break; } -- 2.18.0.huawei.25
hulk inclusion category: feature bugzilla: https://atomgit.com/openeuler/kernel/issues/10109 ---------------------------------------- Add the actual NAPI part of the module. In LONG_RUNNING_WITH_INTERRUPTS status the handler enables threaded NAPI (dev_set_threaded) on every monitored net device and binds the NAPI threads with bind_napi_threaded(), deriving the CPU mask of each thread from the NUMA node of its queue IRQ: - none of the target task CPUs is in the node: the whole node mask is used - the node has CPUs the target tasks are not allowed to use: those CPUs are used - the target tasks may use every CPU of the node: the first dyn_threaded_corenum CPUs of the node are used, so that the network load is capped instead of creeping into the CPUs of the target application; a thread already bound to such a subset keeps its current mask With dyn_threaded_bindirq set, the queue IRQ is bound along with its NAPI thread, round-robin over the same mask. The CPUs used by the NAPI threads are recorded in napi_cpumask, to evaluate their load in the next patch. In NOT_RUNNING and FRAGMENTATION status the handler switches NAPI back to softirq mode, as the target tasks currently do not suffer from softirq activity. Co-developed-by: Yue Haibing <yuehaibing@huawei.com> Signed-off-by: Yue Haibing <yuehaibing@huawei.com> Assisted-by: opencode:glm-5.3 Signed-off-by: Cai Xinchen <caixinchen1@huawei.com> --- net/dynthread/net_dynthread.c | 192 +++++++++++++++++++++++++++++++++- 1 file changed, 188 insertions(+), 4 deletions(-) diff --git a/net/dynthread/net_dynthread.c b/net/dynthread/net_dynthread.c index 719ca0d563ed..04292fba5b6e 100644 --- a/net/dynthread/net_dynthread.c +++ b/net/dynthread/net_dynthread.c @@ -25,6 +25,7 @@ #include <linux/topology.h> #include <linux/irq.h> #include <linux/netdevice.h> +#include <linux/rtnetlink.h> MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Dynamic threaded NAPI scheduler"); @@ -116,6 +117,54 @@ module_param(dyn_threaded_irq_cpucore_per, uint, 0644); MODULE_PARM_DESC(dyn_threaded_irq_cpucore_per, "percent of rx queues to keep per NUMA node, default 50"); +/* default number of cpus used by the napi threads, computed at init */ +static u32 dyn_threaded_corenum_default; + +static u32 dyn_threaded_corenum; + +static int dyn_threaded_corenum_set(const char *val, + const struct kernel_param *kp) +{ + u32 num; + int ret; + + ret = kstrtou32(val, 0, &num); + if (ret) + return ret; + /* a zero count would leave the napi threads without cpu */ + if (!num) + return -EINVAL; + + *(u32 *)kp->arg = num; + return 0; +} + +static int dyn_threaded_corenum_get(char *buffer, + const struct kernel_param *kp) +{ + return scnprintf(buffer, PAGE_SIZE, "%u\n", *(u32 *)kp->arg); +} + +static const struct kernel_param_ops dyn_threaded_corenum_ops = { + .set = dyn_threaded_corenum_set, + .get = dyn_threaded_corenum_get, +}; + +module_param_cb(dyn_threaded_corenum, &dyn_threaded_corenum_ops, + &dyn_threaded_corenum, 0644); +MODULE_PARM_DESC(dyn_threaded_corenum, + "number of cpus used by the napi threads, computed by default"); + +static u32 dyn_threaded_corenum_lower_limit = 4; +module_param(dyn_threaded_corenum_lower_limit, uint, 0644); +MODULE_PARM_DESC(dyn_threaded_corenum_lower_limit, + "lower limit of napi thread cpus, default 4"); + +static u32 dyn_threaded_corenum_upper_limit = 32; +module_param(dyn_threaded_corenum_upper_limit, uint, 0644); +MODULE_PARM_DESC(dyn_threaded_corenum_upper_limit, + "upper limit of napi thread cpus, default 32"); + /** * struct sched_netdev_queue_info - IRQ information of one RX queue * @irq: Linux IRQ number of the queue @@ -664,6 +713,103 @@ static void recover_irq_affinity(void) affinity_to_one_node = false; } +/* CPUs the napi threads of the monitored devices may run on */ +static struct cpumask napi_cpumask; + +/* bind_napi_threaded - bind the threaded NAPIs of @dev to CPUs + * + * The mask of each NAPI thread is derived from the NUMA node of its + * queue IRQ: + * + * - if none of the target task CPUs is in that node, the whole node + * mask is used + * - otherwise, if the node has CPUs the target tasks are not allowed + * to use, those CPUs are used + * - otherwise the first dyn_threaded_corenum CPUs of the node are + * used; if the thread is already bound to such a subset, its + * current mask is kept + * + * In the latter case, with dyn_threaded_bindirq set, the queue IRQ is + * bound along with its NAPI thread, round-robin over the same mask. + * The CPUs used by the threads are recorded in napi_cpumask for the + * load evaluation. + */ +static void bind_napi_threaded(struct net_device *dev) +{ + struct napi_struct *napi; + struct cpumask bind_mask, check_mask, napi_allowed_mask; + const struct cpumask *irq_mask, *node_mask; + u32 idx = 0; + + list_for_each_entry(napi, &dev->napi_list, dev_list) { + if (!napi->thread) + continue; + + cpumask_clear(&bind_mask); + + /* the node of the irq, cpu_online_mask if unknown */ + irq_mask = irq_get_effective_affinity_mask(napi->irq); + if (unlikely(!irq_mask) || cpumask_empty(irq_mask)) + node_mask = cpu_online_mask; + else + node_mask = cpu_cpu_mask(cpumask_first(irq_mask)); + + /* no target task cpu in this node: use the whole node */ + cpumask_and(&check_mask, node_mask, &target_cpumask); + if (cpumask_empty(&check_mask)) { + set_cpus_allowed_ptr(napi->thread, node_mask); + continue; + } + + /* preferred: the node's cpus without the target tasks' ones */ + cpumask_andnot(&napi_allowed_mask, node_mask, &target_cpumask); + if (!cpumask_empty(&napi_allowed_mask)) { + set_cpus_allowed_ptr(napi->thread, &napi_allowed_mask); + continue; + } + + /* no spare cpu in the node: fall back to the whole node, */ + cpumask_copy(&napi_allowed_mask, node_mask); + + /* already bound to a subset of the right size? keep it */ + if (cpumask_weight(napi->thread->cpus_ptr) == dyn_threaded_corenum && + cpumask_subset(napi->thread->cpus_ptr, &napi_allowed_mask)) { + cpumask_or(&napi_cpumask, &napi_cpumask, napi->thread->cpus_ptr); + continue; + } + + select_napi_cpus(dyn_threaded_corenum, &napi_allowed_mask, &bind_mask); + if (cpumask_empty(&bind_mask)) + continue; + + cpumask_or(&napi_cpumask, &napi_cpumask, &bind_mask); + set_cpus_allowed_ptr(napi->thread, &bind_mask); + if (dyn_threaded_bindirq) + bind_irq_in_cpumask(napi->irq, idx++, &bind_mask); + } +} + +static void set_all_napi_threaded(bool threaded) +{ + struct sched_netdev *sched_dev; + int id; + + /* napi_list, dev_set_threaded() and set_cpus_allowed_ptr() + * are all handled under the sleepable rtnl lock + */ + rtnl_lock(); + for (id = 0; id < sched_netdev_num; id++) { + sched_dev = get_sched_netdev(id); + if (!sched_dev) + continue; + + dev_set_threaded(sched_dev->netdev, threaded); + if (threaded) + bind_napi_threaded(sched_dev->netdev); + } + rtnl_unlock(); +} + static int dynthreaded; static struct work_struct dyn_threaded_work; @@ -696,15 +842,23 @@ static void dyn_threaded_handler(struct work_struct *work) info.avg_runtime = snapshot.avg_runtime; break; case NOT_RUNNING: - /* target tasks barely running: restore the original irqs */ + /* target tasks barely running: softirq mode is fine */ + set_all_napi_threaded(false); + /* and the original irq affinity can be restored */ recover_irq_affinity(); break; case FRAGMENTATION: - /* irqs may consume cpu time again, spread them over - * their NUMA node - */ + /* softirq mode is fine, irqs spread over their NUMA node */ + set_all_napi_threaded(false); bind_irq(); break; + case LONG_RUNNING_WITH_INTERRUPTS: + /* switch NAPI processing to threads bound away from + * the target task cpus + */ + cpumask_clear(&napi_cpumask); + set_all_napi_threaded(true); + break; default: break; } @@ -788,6 +942,33 @@ static const struct kernel_param_ops dynthreaded_ops = { module_param_cb(dynthreaded, &dynthreaded_ops, NULL, 0644); MODULE_PARM_DESC(dynthreaded, "enable the scheduler, default 0"); +/* set_default_dyn_threaded_corenum - compute the default napi thread cpu count + * + * The default is the number of RX queues of the busiest monitored device + * divided by the number of NUMA nodes, clamped between the core number + * lower and upper limits. + */ +static void set_default_dyn_threaded_corenum(void) +{ + struct sched_netdev *sched_dev; + unsigned int max_rxq = 0; + u32 num; + int id; + + for (id = 0; id < sched_netdev_num; id++) { + sched_dev = get_sched_netdev(id); + if (sched_dev) + max_rxq = max(max_rxq, sched_dev->netdev->real_num_rx_queues); + } + + num = max_rxq / num_online_nodes(); + if (num < dyn_threaded_corenum_lower_limit) + num = dyn_threaded_corenum_lower_limit; + if (num > dyn_threaded_corenum_upper_limit) + num = dyn_threaded_corenum_upper_limit; + dyn_threaded_corenum_default = num; +} + static int __init dynthread_init(void) { int ret; @@ -796,6 +977,9 @@ static int __init dynthread_init(void) if (ret) return ret; + set_default_dyn_threaded_corenum(); + if (!dyn_threaded_corenum) + dyn_threaded_corenum = dyn_threaded_corenum_default; return 0; } -- 2.18.0.huawei.25
While the target tasks stay in LONG_RUNNING_WITH_INTERRUPTS status, evaluate the load of the CPUs the NAPI threads were bound to (napi_cpumask) and adapt dyn_threaded_corenum over time: - the irq and softirq share of those CPUs reaches IRQ_LIMIT percent: grow the count by one - their user share reaches dyn_threaded_user_per_limit percent or their total load drops below dyn_threaded_cpuload_lower_limit percent: shrink the count by one - their total load reaches dyn_threaded_cpuload_upper_limit percent: grow the count by one The count stays within dyn_threaded_corenum_lower_limit and dyn_threaded_corenum_upper_limit, and it is not shrunk right over a count grown out of the previous one (and vice versa), so that both directions do not fight at the boundary. Fluctuations are damped additionally: an action right after a no-action period is deferred for one period, and after a change the evaluation is skipped for one period. The load is computed from the per-CPU kernel_cpustat of the napi cpus, summed by avg_cpustat() and normalized by cal_cpustat_per(). When the target tasks leave the threaded status again, the scaling state (baseline cpustat and last count) is reset by clear_corenum_napi(). Co-developed-by: Yue Haibing <yuehaibing@huawei.com> Signed-off-by: Yue Haibing <yuehaibing@huawei.com> Assisted-by: opencode:glm-5.3 Signed-off-by: Cai Xinchen <caixinchen1@huawei.com> --- net/dynthread/net_dynthread.c | 179 ++++++++++++++++++++++++++++++++++ 1 file changed, 179 insertions(+) diff --git a/net/dynthread/net_dynthread.c b/net/dynthread/net_dynthread.c index 04292fba5b6e..b50f09fcc281 100644 --- a/net/dynthread/net_dynthread.c +++ b/net/dynthread/net_dynthread.c @@ -26,11 +26,13 @@ #include <linux/irq.h> #include <linux/netdevice.h> #include <linux/rtnetlink.h> +#include <linux/kernel_stat.h> MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Dynamic threaded NAPI scheduler"); #define ONE_HUNDRED 100 +#define IRQ_LIMIT 15 #define SCHED_MAX_NETDEV_NUM 8 #define MAX_DEV_RXQ_NUM 256 #define DYN_THREAD_INTERVAL (dyn_threaded_internal * NSEC_PER_MSEC) @@ -165,6 +167,21 @@ module_param(dyn_threaded_corenum_upper_limit, uint, 0644); MODULE_PARM_DESC(dyn_threaded_corenum_upper_limit, "upper limit of napi thread cpus, default 32"); +static u32 dyn_threaded_user_per_limit = 20; +module_param(dyn_threaded_user_per_limit, uint, 0644); +MODULE_PARM_DESC(dyn_threaded_user_per_limit, + "user percent of the napi cpus to shrink for, default 20"); + +static u32 dyn_threaded_cpuload_lower_limit = 70; +module_param(dyn_threaded_cpuload_lower_limit, uint, 0644); +MODULE_PARM_DESC(dyn_threaded_cpuload_lower_limit, + "napi cpu load percent to shrink for, default 70"); + +static u32 dyn_threaded_cpuload_upper_limit = 90; +module_param(dyn_threaded_cpuload_upper_limit, uint, 0644); +MODULE_PARM_DESC(dyn_threaded_cpuload_upper_limit, + "napi cpu load percent to grow for, default 90"); + /** * struct sched_netdev_queue_info - IRQ information of one RX queue * @irq: Linux IRQ number of the queue @@ -810,6 +827,160 @@ static void set_all_napi_threaded(bool threaded) rtnl_unlock(); } +/* percentual load of the napi cpus before the last change */ +static struct kernel_cpustat snapshot_napi_per; + +/* raw load of the napi cpus of the previous period */ +static struct kernel_cpustat snapshot_napi_cpustat; + +/* dyn_threaded_corenum of the last change */ +static u32 last_threaded_corenum; + +/* damping state of change_dyn_threaded_corenum: true right after + * the count changed, true if the previous period took no action + */ +static bool dynamic_corenum_changed = true; +static bool dynamic_corenum_keep; + +/* avg_cpustat - compute the per-CPU average of kernel_cpustat over + * the online CPUs of @mask + * @mask: desired CPUs + * @avg: result, zeroed if @mask contains no online CPU + */ +static void avg_cpustat(const struct cpumask *mask, struct kernel_cpustat *avg) +{ + struct kernel_cpustat sum = { { 0 } }; + int cpu, count = 0; + int i; + + for_each_cpu_and(cpu, mask, cpu_online_mask) { + struct kernel_cpustat *stat = &per_cpu(kernel_cpustat, cpu); + + for (i = 0; i < NR_STATS; i++) + sum.cpustat[i] += stat->cpustat[i]; + count++; + } + + if (!count) { + memset(avg, 0, sizeof(*avg)); + return; + } + + for (i = 0; i < NR_STATS; i++) + avg->cpustat[i] = sum.cpustat[i] / count; +} + +/* cal_cpustat_per - the increase of each kernel_cpustat field between + * @start and @end, in percent of the total increase + */ +static void cal_cpustat_per(const struct kernel_cpustat *end, + const struct kernel_cpustat *start, + struct kernel_cpustat *per) +{ + struct kernel_cpustat delta; + u64 total; + int i; + + for (i = 0; i < NR_STATS; i++) { + if (end->cpustat[i] > start->cpustat[i]) + delta.cpustat[i] = end->cpustat[i] - start->cpustat[i]; + else + delta.cpustat[i] = 0; + } + + total = delta.cpustat[CPUTIME_USER] + delta.cpustat[CPUTIME_NICE] + + delta.cpustat[CPUTIME_SYSTEM] + delta.cpustat[CPUTIME_IDLE] + + delta.cpustat[CPUTIME_IOWAIT] + delta.cpustat[CPUTIME_IRQ] + + delta.cpustat[CPUTIME_SOFTIRQ] + delta.cpustat[CPUTIME_STEAL]; + + /* no accounting delta, e.g. no online cpu in the mask: everything + * is zero, avoid the division by zero + */ + if (!total) { + memset(per, 0, sizeof(*per)); + return; + } + + for (i = 0; i < NR_STATS; i++) + per->cpustat[i] = 100 * delta.cpustat[i] / total; +} + +/* change_dyn_threaded_corenum - resize dyn_threaded_corenum + * + * The load of the napi cpus is evaluated from the deltas of their + * kernel_cpustat. The core count grows when the irq and softirq share + * of the napi cpus reaches IRQ_LIMIT percent or when their total load + * reaches dyn_threaded_cpuload_upper_limit percent; it shrinks when + * their user share reaches dyn_threaded_user_per_limit percent or + * their total load drops below dyn_threaded_cpuload_lower_limit + * percent, but not right over a change grown out of the target count. + * + * Fluctuations are damped: an action right after a no-action period + * is deferred for one period, and after a change the evaluation is + * skipped once. + */ +static void change_dyn_threaded_corenum(void) +{ + struct kernel_cpustat napi_per; + struct kernel_cpustat napi_cpustat; + int action = 0; + + avg_cpustat(&napi_cpumask, &napi_cpustat); + + if (dynamic_corenum_changed) { + dynamic_corenum_changed = false; + goto out; + } + + /* percentual load of the napi cpus */ + cal_cpustat_per(&napi_cpustat, &snapshot_napi_cpustat, &napi_per); + + + if (napi_per.cpustat[CPUTIME_SOFTIRQ] + napi_per.cpustat[CPUTIME_IRQ] >= IRQ_LIMIT && + dyn_threaded_corenum < dyn_threaded_corenum_upper_limit) { + action = 1; + } else if (napi_per.cpustat[CPUTIME_USER] >= dyn_threaded_user_per_limit && + dyn_threaded_corenum > dyn_threaded_corenum_lower_limit) { + action = -1; + } else if (100 - napi_per.cpustat[CPUTIME_IDLE] < dyn_threaded_cpuload_lower_limit && + dyn_threaded_corenum > dyn_threaded_corenum_lower_limit && + (snapshot_napi_per.cpustat[CPUTIME_SOFTIRQ] + + snapshot_napi_per.cpustat[CPUTIME_IRQ] < IRQ_LIMIT || + last_threaded_corenum != dyn_threaded_corenum - 1)) { + action = -1; + } else if (100 - napi_per.cpustat[CPUTIME_IDLE] >= dyn_threaded_cpuload_upper_limit && + dyn_threaded_corenum < dyn_threaded_corenum_upper_limit && + (snapshot_napi_per.cpustat[CPUTIME_USER] < dyn_threaded_user_per_limit || + last_threaded_corenum != dyn_threaded_corenum + 1)) { + action = 1; + } + + if (action) { + if (dynamic_corenum_keep) { + dynamic_corenum_keep = false; + goto out; + } + dynamic_corenum_changed = true; + snapshot_napi_per = napi_per; + last_threaded_corenum = dyn_threaded_corenum; + dyn_threaded_corenum += action; + } else { + dynamic_corenum_keep = true; + } + +out: + snapshot_napi_cpustat = napi_cpustat; +} + +/* reset the state collected while NAPI was threaded */ +static void clear_corenum_napi(void) +{ + memset(&snapshot_napi_per, 0, sizeof(snapshot_napi_per)); + last_threaded_corenum = dyn_threaded_corenum_default; + dynamic_corenum_changed = true; + dynamic_corenum_keep = false; +} + static int dynthreaded; static struct work_struct dyn_threaded_work; @@ -834,6 +1005,7 @@ static void dyn_threaded_handler(struct work_struct *work) case NO_TASK: /* no target tasks: restore the original irq affinity */ recover_irq_affinity(); + clear_corenum_napi(); break; case DATA_LOSS: /* invalid sample, reuse the previous status and statistics */ @@ -846,13 +1018,19 @@ static void dyn_threaded_handler(struct work_struct *work) set_all_napi_threaded(false); /* and the original irq affinity can be restored */ recover_irq_affinity(); + /* the napi thread cpu count goes back to the default */ + dyn_threaded_corenum = dyn_threaded_corenum_default; + clear_corenum_napi(); break; case FRAGMENTATION: /* softirq mode is fine, irqs spread over their NUMA node */ set_all_napi_threaded(false); bind_irq(); + clear_corenum_napi(); break; case LONG_RUNNING_WITH_INTERRUPTS: + /* adapt the number of thread cpus to the measured load */ + change_dyn_threaded_corenum(); /* switch NAPI processing to threads bound away from * the target task cpus */ @@ -980,6 +1158,7 @@ static int __init dynthread_init(void) set_default_dyn_threaded_corenum(); if (!dyn_threaded_corenum) dyn_threaded_corenum = dyn_threaded_corenum_default; + last_threaded_corenum = dyn_threaded_corenum; return 0; } -- 2.18.0.huawei.25
hulk inclusion category: feature bugzilla: https://atomgit.com/openeuler/kernel/issues/10109 ---------------------------------------- Add a dyn_threaded_debug module parameter that enables debug instrumentation of the scheduler. The messages are recorded with trace_printk(), so that the periodic sampling, the status transitions and the decisions on the IRQ affinity, the NAPI thread masks and the napi thread CPU count can be followed from tracefs without flooding the console; without tracing support the messages fall back to dynamic debug. The switch is off by default. This replaces the previous pr_debug() instrumentation and adds the per-branch decisions of bind_napi_threaded(), the IRQ binding results, the napi thread CPU resize and the status transitions, matching the instrumentation that was used during the development of the module. Assisted-by: opencode:deepseek-v4.1-flash Signed-off-by: Cai Xinchen <caixinchen1@huawei.com> --- net/dynthread/net_dynthread.c | 88 +++++++++++++++++++++++++++++++++-- 1 file changed, 85 insertions(+), 3 deletions(-) diff --git a/net/dynthread/net_dynthread.c b/net/dynthread/net_dynthread.c index b50f09fcc281..43eda16818b1 100644 --- a/net/dynthread/net_dynthread.c +++ b/net/dynthread/net_dynthread.c @@ -182,6 +182,30 @@ module_param(dyn_threaded_cpuload_upper_limit, uint, 0644); MODULE_PARM_DESC(dyn_threaded_cpuload_upper_limit, "napi cpu load percent to grow for, default 90"); +static bool dyn_threaded_debug; +module_param(dyn_threaded_debug, bool, 0644); +MODULE_PARM_DESC(dyn_threaded_debug, + "enable the ftrace based debug instrumentation, default 0"); + +/* Debug instrumentation of the scheduler. It is controlled by the + * dyn_threaded_debug module parameter and recorded in the ftrace buffer, + * so that it can be read from tracefs without flooding the console. + * Without tracing support it falls back to dynamic debug. + */ +#ifdef CONFIG_TRACING +#define dyn_threaded_dbg(fmt, ...) \ + do { \ + if (dyn_threaded_debug) \ + trace_printk(fmt, ##__VA_ARGS__); \ + } while (0) +#else +#define dyn_threaded_dbg(fmt, ...) \ + do { \ + if (dyn_threaded_debug) \ + pr_debug(fmt, ##__VA_ARGS__); \ + } while (0) +#endif + /** * struct sched_netdev_queue_info - IRQ information of one RX queue * @irq: Linux IRQ number of the queue @@ -338,6 +362,9 @@ static void get_netdev_queue_info(struct sched_netdev *sched_dev) rxq_info = &sched_dev->rxq[sched_dev->rxq_num++]; rxq_info->irq = irq; rxq_info->affinity_cpu = cpu; + dyn_threaded_dbg("dev %s: irq=%d [%s] rxq_id=%d affinity_cpu=%d\n", + sched_dev->dev_name, irq, name, + sched_dev->rxq_num - 1, cpu); } } @@ -381,22 +408,27 @@ static int init_dev(const char *if_name, size_t length) strscpy(dev_name, if_name, min_t(size_t, length + 1, IFNAMSIZ)); netdev = dev_get_by_name(&init_net, dev_name); - if (!netdev) + if (!netdev) { + dyn_threaded_dbg("dev [%s] does not exist\n", dev_name); return -ENODEV; + } if (!(netdev->flags & IFF_UP)) { ret = -ENETDOWN; + dyn_threaded_dbg("dev [%s] is not up, flags=%u\n", dev_name, netdev->flags); goto err_dev_put; } if (netdev->flags & IFF_LOOPBACK) { ret = -EOPNOTSUPP; + dyn_threaded_dbg("loopback devices are not supported\n"); goto err_dev_put; } sched_dev = alloc_sched_netdev(); if (!sched_dev) { ret = -ENOMEM; + dyn_threaded_dbg("too many devices\n"); goto err_dev_put; } @@ -498,6 +530,10 @@ static int sched_task_runtime_avg_snapshot(const char *target_comm, cpumask_copy(&target_cpumask, &masks); WRITE_ONCE(target_cpucnt, cpumask_weight(&target_cpumask)); + dyn_threaded_dbg("target tasks '%s': cpumask %*pb (%u cpus), runtime %llu ns, switches %llu, threads %u\n", + infos->comm, cpumask_pr_args(&masks), + READ_ONCE(target_cpucnt), infos->total_runtime_ns, + infos->total_switches, infos->thread_cnt); return 0; } @@ -594,13 +630,19 @@ static void bind_irq_in_cpumask(int irq, int index, const struct cpumask *bind_m unsigned int len = cpumask_weight(bind_mask); int cpu; - if (!len) + if (!len) { + dyn_threaded_dbg("empty bind mask for irq %d\n", irq); return; + } cpu = cpumask_nth(index % len, bind_mask); - if (cpu >= nr_cpu_ids) + if (cpu >= nr_cpu_ids) { + dyn_threaded_dbg("no cpu for index %u of irq %d\n", index % len, irq); return; + } + dyn_threaded_dbg("bind irq %d to cpu %d of %*pb\n", + irq, cpu, cpumask_pr_args(bind_mask)); irq_set_affinity(irq, cpumask_of(cpu)); } @@ -688,17 +730,22 @@ static void set_irq_node_affinity(const struct cpumask *tasks_cpumask) if (cpumask_weight(&irq_prefer_mask) < dyn_threaded_irq_cpucore_limit) return; + dyn_threaded_dbg("irq affinity mask: %*pb\n", cpumask_pr_args(&irq_prefer_mask)); cpu = cpumask_first(&irq_prefer_mask); for (dev_id = 0; dev_id < sched_netdev_num; dev_id++) { sched_dev = get_sched_netdev(dev_id); for (rxq_id = 0; rxq_id < sched_dev->rxq_num; rxq_id++) { + dyn_threaded_dbg("set irq %d affinity to cpu %d\n", + sched_dev->rxq[rxq_id].irq, cpu); if (!irq_set_affinity(sched_dev->rxq[rxq_id].irq, cpumask_of(cpu))) { cpu = cpumask_next(cpu, &irq_prefer_mask); if (cpu >= nr_cpu_ids) cpu = cpumask_first(&irq_prefer_mask); } else { + dyn_threaded_dbg("failed to set irq %d affinity to cpu %d\n", + sched_dev->rxq[rxq_id].irq, cpu); } } } @@ -775,6 +822,8 @@ static void bind_napi_threaded(struct net_device *dev) cpumask_and(&check_mask, node_mask, &target_cpumask); if (cpumask_empty(&check_mask)) { set_cpus_allowed_ptr(napi->thread, node_mask); + dyn_threaded_dbg("%s: napi %d, no target cpu in node, use %*pb\n", + dev->name, napi->napi_id, cpumask_pr_args(node_mask)); continue; } @@ -782,6 +831,9 @@ static void bind_napi_threaded(struct net_device *dev) cpumask_andnot(&napi_allowed_mask, node_mask, &target_cpumask); if (!cpumask_empty(&napi_allowed_mask)) { set_cpus_allowed_ptr(napi->thread, &napi_allowed_mask); + dyn_threaded_dbg("%s: napi %d, use target-free cpus %*pb\n", + dev->name, napi->napi_id, + cpumask_pr_args(&napi_allowed_mask)); continue; } @@ -792,6 +844,9 @@ static void bind_napi_threaded(struct net_device *dev) if (cpumask_weight(napi->thread->cpus_ptr) == dyn_threaded_corenum && cpumask_subset(napi->thread->cpus_ptr, &napi_allowed_mask)) { cpumask_or(&napi_cpumask, &napi_cpumask, napi->thread->cpus_ptr); + dyn_threaded_dbg("%s: napi %d, keep mask %*pb\n", + dev->name, napi->napi_id, + cpumask_pr_args(napi->thread->cpus_ptr)); continue; } @@ -801,6 +856,9 @@ static void bind_napi_threaded(struct net_device *dev) cpumask_or(&napi_cpumask, &napi_cpumask, &bind_mask); set_cpus_allowed_ptr(napi->thread, &bind_mask); + dyn_threaded_dbg("%s: napi %d, use first %u cpus %*pb\n", + dev->name, napi->napi_id, dyn_threaded_corenum, + cpumask_pr_args(&bind_mask)); if (dyn_threaded_bindirq) bind_irq_in_cpumask(napi->irq, idx++, &bind_mask); } @@ -821,6 +879,8 @@ static void set_all_napi_threaded(bool threaded) continue; dev_set_threaded(sched_dev->netdev, threaded); + dyn_threaded_dbg("%s: set threaded napi %d\n", + sched_dev->dev_name, threaded); if (threaded) bind_napi_threaded(sched_dev->netdev); } @@ -935,6 +995,10 @@ static void change_dyn_threaded_corenum(void) /* percentual load of the napi cpus */ cal_cpustat_per(&napi_cpustat, &snapshot_napi_cpustat, &napi_per); + dyn_threaded_dbg("napi cpu user %llu system %llu softirq %llu irq %llu idle %llu iowait %llu\n", + napi_per.cpustat[CPUTIME_USER], napi_per.cpustat[CPUTIME_SYSTEM], + napi_per.cpustat[CPUTIME_SOFTIRQ], napi_per.cpustat[CPUTIME_IRQ], + napi_per.cpustat[CPUTIME_IDLE], napi_per.cpustat[CPUTIME_IOWAIT]); if (napi_per.cpustat[CPUTIME_SOFTIRQ] + napi_per.cpustat[CPUTIME_IRQ] >= IRQ_LIMIT && dyn_threaded_corenum < dyn_threaded_corenum_upper_limit) { @@ -964,11 +1028,15 @@ static void change_dyn_threaded_corenum(void) snapshot_napi_per = napi_per; last_threaded_corenum = dyn_threaded_corenum; dyn_threaded_corenum += action; + dyn_threaded_dbg("resize napi thread cpus %u -> %u\n", + last_threaded_corenum, dyn_threaded_corenum); } else { dynamic_corenum_keep = true; } out: + dyn_threaded_dbg("napi threads use %u cpus of %*pb\n", + dyn_threaded_corenum, cpumask_pr_args(&napi_cpumask)); snapshot_napi_cpustat = napi_cpustat; } @@ -1000,6 +1068,7 @@ static void dyn_threaded_handler(struct work_struct *work) dyn_threaded_bindirq) set_irq_node_affinity(&target_cpumask); + dyn_threaded_dbg("target task status %d\n", info.status); switch (info.status) { case NO_TASK: @@ -1038,6 +1107,7 @@ static void dyn_threaded_handler(struct work_struct *work) set_all_napi_threaded(true); break; default: + dyn_threaded_dbg("unexpected status %d\n", info.status); break; } @@ -1047,6 +1117,13 @@ static void dyn_threaded_handler(struct work_struct *work) else info.last_status = snapshot.status; + if (info.status != snapshot.status) + dyn_threaded_dbg("status %d -> %d\n", + snapshot.status, info.status); + + dyn_threaded_dbg("%s: avg_runtime %llu nvcsw_per %llu cpuload %llu status %d last %u irq_node %d\n", + __func__, info.avg_runtime, info.nvcsw_per, info.cpuload, + info.status, info.last_status, affinity_to_one_node); snapshot = info; } @@ -1075,6 +1152,7 @@ static void start_worker(void) dyn_threaded_timer.function = dyn_threaded_hrtimer_callback; hrtimer_start(&dyn_threaded_timer, interval, HRTIMER_MODE_REL); + dyn_threaded_dbg("worker started, interval %u ms\n", dyn_threaded_internal); } static void stop_worker(void) @@ -1082,6 +1160,7 @@ static void stop_worker(void) hrtimer_cancel(&dyn_threaded_timer); cancel_work_sync(&dyn_threaded_work); + dyn_threaded_dbg("worker stopped\n"); } /* enable/disable the scheduler through the dynthreaded module parameter */ @@ -1160,6 +1239,8 @@ static int __init dynthread_init(void) dyn_threaded_corenum = dyn_threaded_corenum_default; last_threaded_corenum = dyn_threaded_corenum; + dyn_threaded_dbg("module loaded\n"); + return 0; } @@ -1170,6 +1251,7 @@ static void __exit dynthread_exit(void) deinit_netdevinfo(); + dyn_threaded_dbg("module unloaded\n"); } module_init(dynthread_init); -- 2.18.0.huawei.25
participants (2)
-
Cai Xinchen -
patchwork bot