Message ID | 20250324-netns-debugfs-v1-1-c75e9d5a6266@kernel.org (mailing list archive) |
---|---|
State | Changes Requested |
Delegated to: | Netdev Maintainers |
Headers | show |
Series | net: add a debugfs files for showing netns refcount tracking info | expand |
On Mon, Mar 24, 2025 at 04:24:47PM -0400, Jeff Layton wrote: > CONFIG_NET_NS_REFCNT_TRACKER currently has no convenient way to display > its tracking info. Add a new net_ns directory in debugfs. Have a > directory in there for every net, with refcnt and notrefcnt files that > show the currently tracked active and passive references. Hi Jeff CONFIG_NET_NS_REFCNT_TRACKER is just an instance of CONFIG_REF_TRACKER. It would be good to explain why you are doing it at the netdev level, rather than part of the generic CONFIG_REF_TRACKER level. Why would other subsystems not benefit from having their reference trackers in debugfs? Andrew
On Mon, 2025-03-24 at 21:50 +0100, Andrew Lunn wrote: > On Mon, Mar 24, 2025 at 04:24:47PM -0400, Jeff Layton wrote: > > CONFIG_NET_NS_REFCNT_TRACKER currently has no convenient way to display > > its tracking info. Add a new net_ns directory in debugfs. Have a > > directory in there for every net, with refcnt and notrefcnt files that > > show the currently tracked active and passive references. > > Hi Jeff > > CONFIG_NET_NS_REFCNT_TRACKER is just an instance of > CONFIG_REF_TRACKER. > > It would be good to explain why you are doing it at the netdev level, > rather than part of the generic CONFIG_REF_TRACKER level. Why would > other subsystems not benefit from having their reference trackers in > debugfs? > > Mostly because I just needed the NS_REFCNT_TRACKER at the time. I'm OK with making this more general, but all of those subsystems using refcount trackers would need to add the infrastructure to create directories to track them. To whit: What would the directory structure look like for the more general case?
On Mon, Mar 24, 2025 at 9:24 PM Jeff Layton <jlayton@kernel.org> wrote: > > CONFIG_NET_NS_REFCNT_TRACKER currently has no convenient way to display > its tracking info. Add a new net_ns directory in debugfs. Have a > directory in there for every net, with refcnt and notrefcnt files that > show the currently tracked active and passive references. > > Signed-off-by: Jeff Layton <jlayton@kernel.org> > --- > Recently, I had a need to track down some long-held netns references, > and discovered CONFIG_NET_NS_REFCNT_TRACKER. The main thing that seemed > to be missing from it though is a simple way to view the currently held > references on the netns. This adds files in debugfs for this. Thanks for working on this, this is a very good idea. > +#define MAX_NS_DEBUG_BUFSIZE (32 * PAGE_SIZE) > + > +static int > +ns_debug_tracker_show(struct seq_file *f, void *v) > +{ > + struct ref_tracker_dir *tracker = f->private; > + int len, bufsize = PAGE_SIZE; > + char *buf; > + > + for (;;) { > + buf = kvmalloc(bufsize, GFP_KERNEL); > + if (!buf) > + return -ENOMEM; > + > + len = ref_tracker_dir_snprint(tracker, buf, bufsize); > + if (len < bufsize) > + break; > + > + kvfree(buf); > + bufsize *= 2; > + if (bufsize > MAX_NS_DEBUG_BUFSIZE) > + return -ENOBUFS; > + } > + seq_write(f, buf, len); > + kvfree(buf); > + return 0; > +} I guess we could first change ref_tracker_dir_snprint(tracker, buf, bufsize) to ref_tracker_dir_snprint(tracker, buf, bufsize, &needed) to avoid too many tries in this loop. Most of ref_tracker_dir_snprint() runs with hard irq being disabled... diff --git a/drivers/gpu/drm/i915/intel_wakeref.c b/drivers/gpu/drm/i915/intel_wakeref.c index 87f2460473127af65a9a793c7f1934fafe41e79e..6650421b4f00c318adec72cd7c17a76832f14cce 100644 --- a/drivers/gpu/drm/i915/intel_wakeref.c +++ b/drivers/gpu/drm/i915/intel_wakeref.c @@ -208,7 +208,7 @@ void intel_ref_tracker_show(struct ref_tracker_dir *dir, if (!buf) return; - count = ref_tracker_dir_snprint(dir, buf, buf_size); + count = ref_tracker_dir_snprint(dir, buf, buf_size, NULL); if (!count) goto free; /* printk does not like big buffers, so we split it */ diff --git a/include/linux/ref_tracker.h b/include/linux/ref_tracker.h index 8eac4f3d52547ccbaf9dcd09962ce80d26fbdff8..19bd42088434b661810082350a9d5afcbff6a88a 100644 --- a/include/linux/ref_tracker.h +++ b/include/linux/ref_tracker.h @@ -46,7 +46,7 @@ void ref_tracker_dir_print_locked(struct ref_tracker_dir *dir, void ref_tracker_dir_print(struct ref_tracker_dir *dir, unsigned int display_limit); -int ref_tracker_dir_snprint(struct ref_tracker_dir *dir, char *buf, size_t size); +int ref_tracker_dir_snprint(struct ref_tracker_dir *dir, char *buf, size_t size, size_t *needed); int ref_tracker_alloc(struct ref_tracker_dir *dir, struct ref_tracker **trackerp, gfp_t gfp); @@ -77,7 +77,7 @@ static inline void ref_tracker_dir_print(struct ref_tracker_dir *dir, } static inline int ref_tracker_dir_snprint(struct ref_tracker_dir *dir, - char *buf, size_t size) + char *buf, size_t size, size_t *needed) { return 0; } diff --git a/lib/ref_tracker.c b/lib/ref_tracker.c index cf5609b1ca79361763abe5a3a98484a3ee591ff2..d8d02dab7ce67caf91ae22f9391abe2c92481c7f 100644 --- a/lib/ref_tracker.c +++ b/lib/ref_tracker.c @@ -65,6 +65,7 @@ ref_tracker_get_stats(struct ref_tracker_dir *dir, unsigned int limit) struct ostream { char *buf; int size, used; + size_t needed; }; #define pr_ostream(stream, fmt, args...) \ @@ -76,6 +77,7 @@ struct ostream { } else { \ int ret, len = _s->size - _s->used; \ ret = snprintf(_s->buf + _s->used, len, pr_fmt(fmt), ##args); \ + _s->needed += ret; \ _s->used += min(ret, len); \ } \ }) @@ -141,7 +143,7 @@ void ref_tracker_dir_print(struct ref_tracker_dir *dir, } EXPORT_SYMBOL(ref_tracker_dir_print); -int ref_tracker_dir_snprint(struct ref_tracker_dir *dir, char *buf, size_t size) +int ref_tracker_dir_snprint(struct ref_tracker_dir *dir, char *buf, size_t size, size_t *needed) { struct ostream os = { .buf = buf, .size = size }; unsigned long flags; @@ -150,6 +152,8 @@ int ref_tracker_dir_snprint(struct ref_tracker_dir *dir, char *buf, size_t size) __ref_tracker_dir_pr_ostream(dir, 16, &os); spin_unlock_irqrestore(&dir->lock, flags); + if (needed) + *needed = os.needed; return os.used; } EXPORT_SYMBOL(ref_tracker_dir_snprint); diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c index ce4b01cc7aca15ddf74f4160580871868e693fb8..61ce889ab29c2b726eab064b0ecb39838db30229 100644 --- a/net/core/net_namespace.c +++ b/net/core/net_namespace.c @@ -1529,13 +1529,14 @@ struct ns_debug_net { struct dentry *notrefcnt; }; -#define MAX_NS_DEBUG_BUFSIZE (32 * PAGE_SIZE) +#define MAX_NS_DEBUG_BUFSIZE (1 << 20) static int ns_debug_tracker_show(struct seq_file *f, void *v) { struct ref_tracker_dir *tracker = f->private; int len, bufsize = PAGE_SIZE; + size_t needed; char *buf; for (;;) { @@ -1543,12 +1544,12 @@ ns_debug_tracker_show(struct seq_file *f, void *v) if (!buf) return -ENOMEM; - len = ref_tracker_dir_snprint(tracker, buf, bufsize); + len = ref_tracker_dir_snprint(tracker, buf, bufsize, &needed); if (len < bufsize) break; kvfree(buf); - bufsize *= 2; + bufsize = round_up(needed, PAGE_SIZE); if (bufsize > MAX_NS_DEBUG_BUFSIZE) return -ENOBUFS; }
On Tue, 2025-03-25 at 06:21 +0100, Eric Dumazet wrote: > On Mon, Mar 24, 2025 at 9:24 PM Jeff Layton <jlayton@kernel.org> wrote: > > > > CONFIG_NET_NS_REFCNT_TRACKER currently has no convenient way to display > > its tracking info. Add a new net_ns directory in debugfs. Have a > > directory in there for every net, with refcnt and notrefcnt files that > > show the currently tracked active and passive references. > > > > Signed-off-by: Jeff Layton <jlayton@kernel.org> > > --- > > Recently, I had a need to track down some long-held netns references, > > and discovered CONFIG_NET_NS_REFCNT_TRACKER. The main thing that seemed > > to be missing from it though is a simple way to view the currently held > > references on the netns. This adds files in debugfs for this. > > Thanks for working on this, this is a very good idea. > Thanks. I needed it when I was tracking down refs held by NFS and RPC clients. My thinking was that we could add the netns specific dirs to debugfs with the refcnt and notrefcnt files in it for now, and could add new files to it later as needed for other netns-related things. > > > +#define MAX_NS_DEBUG_BUFSIZE (32 * PAGE_SIZE) > > + > > +static int > > +ns_debug_tracker_show(struct seq_file *f, void *v) > > +{ > > + struct ref_tracker_dir *tracker = f->private; > > + int len, bufsize = PAGE_SIZE; > > + char *buf; > > + > > + for (;;) { > > + buf = kvmalloc(bufsize, GFP_KERNEL); > > + if (!buf) > > + return -ENOMEM; > > + > > + len = ref_tracker_dir_snprint(tracker, buf, bufsize); > > + if (len < bufsize) > > + break; > > + > > + kvfree(buf); > > + bufsize *= 2; > > + if (bufsize > MAX_NS_DEBUG_BUFSIZE) > > + return -ENOBUFS; > > + } > > + seq_write(f, buf, len); > > + kvfree(buf); > > + return 0; > > +} > > I guess we could first change ref_tracker_dir_snprint(tracker, buf, bufsize) > to ref_tracker_dir_snprint(tracker, buf, bufsize, &needed) to avoid > too many tries in this loop. > > Most of ref_tracker_dir_snprint() runs with hard irq being disabled... > Ouch, yeah that sounds like a good idea. > > diff --git a/drivers/gpu/drm/i915/intel_wakeref.c > b/drivers/gpu/drm/i915/intel_wakeref.c > index 87f2460473127af65a9a793c7f1934fafe41e79e..6650421b4f00c318adec72cd7c17a76832f14cce > 100644 > --- a/drivers/gpu/drm/i915/intel_wakeref.c > +++ b/drivers/gpu/drm/i915/intel_wakeref.c > @@ -208,7 +208,7 @@ void intel_ref_tracker_show(struct ref_tracker_dir *dir, > if (!buf) > return; > > - count = ref_tracker_dir_snprint(dir, buf, buf_size); > + count = ref_tracker_dir_snprint(dir, buf, buf_size, NULL); > if (!count) > goto free; > /* printk does not like big buffers, so we split it */ > diff --git a/include/linux/ref_tracker.h b/include/linux/ref_tracker.h > index 8eac4f3d52547ccbaf9dcd09962ce80d26fbdff8..19bd42088434b661810082350a9d5afcbff6a88a > 100644 > --- a/include/linux/ref_tracker.h > +++ b/include/linux/ref_tracker.h > @@ -46,7 +46,7 @@ void ref_tracker_dir_print_locked(struct ref_tracker_dir *dir, > void ref_tracker_dir_print(struct ref_tracker_dir *dir, > unsigned int display_limit); > > -int ref_tracker_dir_snprint(struct ref_tracker_dir *dir, char *buf, > size_t size); > +int ref_tracker_dir_snprint(struct ref_tracker_dir *dir, char *buf, > size_t size, size_t *needed); > > int ref_tracker_alloc(struct ref_tracker_dir *dir, > struct ref_tracker **trackerp, gfp_t gfp); > @@ -77,7 +77,7 @@ static inline void ref_tracker_dir_print(struct > ref_tracker_dir *dir, > } > > static inline int ref_tracker_dir_snprint(struct ref_tracker_dir *dir, > - char *buf, size_t size) > + char *buf, size_t size, > size_t *needed) > { > return 0; > } > diff --git a/lib/ref_tracker.c b/lib/ref_tracker.c > index cf5609b1ca79361763abe5a3a98484a3ee591ff2..d8d02dab7ce67caf91ae22f9391abe2c92481c7f > 100644 > --- a/lib/ref_tracker.c > +++ b/lib/ref_tracker.c > @@ -65,6 +65,7 @@ ref_tracker_get_stats(struct ref_tracker_dir *dir, > unsigned int limit) > struct ostream { > char *buf; > int size, used; > + size_t needed; > }; > > #define pr_ostream(stream, fmt, args...) \ > @@ -76,6 +77,7 @@ struct ostream { > } else { \ > int ret, len = _s->size - _s->used; \ > ret = snprintf(_s->buf + _s->used, len, pr_fmt(fmt), ##args); \ > + _s->needed += ret; \ > _s->used += min(ret, len); \ > } \ > }) > @@ -141,7 +143,7 @@ void ref_tracker_dir_print(struct ref_tracker_dir *dir, > } > EXPORT_SYMBOL(ref_tracker_dir_print); > > -int ref_tracker_dir_snprint(struct ref_tracker_dir *dir, char *buf, > size_t size) > +int ref_tracker_dir_snprint(struct ref_tracker_dir *dir, char *buf, > size_t size, size_t *needed) > { > struct ostream os = { .buf = buf, .size = size }; > unsigned long flags; > @@ -150,6 +152,8 @@ int ref_tracker_dir_snprint(struct ref_tracker_dir > *dir, char *buf, size_t size) > __ref_tracker_dir_pr_ostream(dir, 16, &os); > spin_unlock_irqrestore(&dir->lock, flags); > > + if (needed) > + *needed = os.needed; > return os.used; > } > EXPORT_SYMBOL(ref_tracker_dir_snprint); > diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c > index ce4b01cc7aca15ddf74f4160580871868e693fb8..61ce889ab29c2b726eab064b0ecb39838db30229 > 100644 > --- a/net/core/net_namespace.c > +++ b/net/core/net_namespace.c > @@ -1529,13 +1529,14 @@ struct ns_debug_net { > struct dentry *notrefcnt; > }; > > -#define MAX_NS_DEBUG_BUFSIZE (32 * PAGE_SIZE) > +#define MAX_NS_DEBUG_BUFSIZE (1 << 20) > > static int > ns_debug_tracker_show(struct seq_file *f, void *v) > { > struct ref_tracker_dir *tracker = f->private; > int len, bufsize = PAGE_SIZE; > + size_t needed; > char *buf; > > for (;;) { > @@ -1543,12 +1544,12 @@ ns_debug_tracker_show(struct seq_file *f, void *v) > if (!buf) > return -ENOMEM; > > - len = ref_tracker_dir_snprint(tracker, buf, bufsize); > + len = ref_tracker_dir_snprint(tracker, buf, bufsize, &needed); > if (len < bufsize) > break; > > kvfree(buf); > - bufsize *= 2; > + bufsize = round_up(needed, PAGE_SIZE); > if (bufsize > MAX_NS_DEBUG_BUFSIZE) > return -ENOBUFS; > }
On Mon, Mar 24, 2025 at 08:37:57PM -0400, Jeff Layton wrote: > On Mon, 2025-03-24 at 21:50 +0100, Andrew Lunn wrote: > > On Mon, Mar 24, 2025 at 04:24:47PM -0400, Jeff Layton wrote: > > > CONFIG_NET_NS_REFCNT_TRACKER currently has no convenient way to display > > > its tracking info. Add a new net_ns directory in debugfs. Have a > > > directory in there for every net, with refcnt and notrefcnt files that > > > show the currently tracked active and passive references. > > > > Hi Jeff > > > > CONFIG_NET_NS_REFCNT_TRACKER is just an instance of > > CONFIG_REF_TRACKER. > > > > It would be good to explain why you are doing it at the netdev level, > > rather than part of the generic CONFIG_REF_TRACKER level. Why would > > other subsystems not benefit from having their reference trackers in > > debugfs? > > > > > > Mostly because I just needed the NS_REFCNT_TRACKER at the time. > > I'm OK with making this more general, but all of those subsystems using > refcount trackers would need to add the infrastructure to create > directories to track them. The base directory can be created by ref_tracker itself. > To whit: > > What would the directory structure look like for the more general case? ref_tracker_dir_init(struct ref_tracker_dir *dir, unsigned int quarantine_count, const char *name) and then each tracker use 'name' as the debugfs filename. You would need to check it is unique before creating it, since uniqueness was probably not a requirement before. Andrew
diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c index 4303f2a4926243e2c0ff0c0387383cd8e0658019..b7ce8c7621bdf6055fa4aaa5cbfce111ca86b047 100644 --- a/net/core/net_namespace.c +++ b/net/core/net_namespace.c @@ -1512,3 +1512,154 @@ const struct proc_ns_operations netns_operations = { .owner = netns_owner, }; #endif + +#ifdef CONFIG_DEBUG_FS +#ifdef CONFIG_NET_NS_REFCNT_TRACKER + +#include <linux/debugfs.h> + +static struct dentry *ns_debug_dir; +static unsigned int ns_debug_net_id; + +struct ns_debug_net { + struct dentry *netdir; + struct dentry *refcnt; + struct dentry *notrefcnt; +}; + +#define MAX_NS_DEBUG_BUFSIZE (32 * PAGE_SIZE) + +static int +ns_debug_tracker_show(struct seq_file *f, void *v) +{ + struct ref_tracker_dir *tracker = f->private; + int len, bufsize = PAGE_SIZE; + char *buf; + + for (;;) { + buf = kvmalloc(bufsize, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + len = ref_tracker_dir_snprint(tracker, buf, bufsize); + if (len < bufsize) + break; + + kvfree(buf); + bufsize *= 2; + if (bufsize > MAX_NS_DEBUG_BUFSIZE) + return -ENOBUFS; + } + seq_write(f, buf, len); + kvfree(buf); + return 0; +} + +static int +ns_debug_ref_open(struct inode *inode, struct file *filp) +{ + int ret; + struct net *net = inode->i_private; + + ret = single_open(filp, ns_debug_tracker_show, &net->refcnt_tracker); + if (!ret) + net_passive_inc(net); + return ret; +} + +static int +ns_debug_notref_open(struct inode *inode, struct file *filp) +{ + int ret; + struct net *net = inode->i_private; + + ret = single_open(filp, ns_debug_tracker_show, &net->notrefcnt_tracker); + if (!ret) + net_passive_inc(net); + return ret; +} + +static int +ns_debug_ref_release(struct inode *inode, struct file *filp) +{ + struct net *net = inode->i_private; + + net_passive_dec(net); + return single_release(inode, filp); +} + +static const struct file_operations ns_debug_ref_fops = { + .owner = THIS_MODULE, + .open = ns_debug_ref_open, + .read = seq_read, + .llseek = seq_lseek, + .release = ns_debug_ref_release, +}; + +static const struct file_operations ns_debug_notref_fops = { + .owner = THIS_MODULE, + .open = ns_debug_notref_open, + .read = seq_read, + .llseek = seq_lseek, + .release = ns_debug_ref_release, +}; + +static int +ns_debug_init_net(struct net *net) +{ + struct ns_debug_net *dnet = net_generic(net, ns_debug_net_id); + char name[11]; /* 10 decimal digits + NULL term */ + int len; + + len = snprintf(name, sizeof(name), "%u", net->ns.inum); + if (len >= sizeof(name)) + return -EOVERFLOW; + + dnet->netdir = debugfs_create_dir(name, ns_debug_dir); + if (IS_ERR(dnet->netdir)) + return PTR_ERR(dnet->netdir); + + dnet->refcnt = debugfs_create_file("refcnt", S_IFREG | 0400, dnet->netdir, + net, &ns_debug_ref_fops); + if (IS_ERR(dnet->refcnt)) { + debugfs_remove(dnet->netdir); + return PTR_ERR(dnet->refcnt); + } + + dnet->notrefcnt = debugfs_create_file("notrefcnt", S_IFREG | 0400, dnet->netdir, + net, &ns_debug_notref_fops); + if (IS_ERR(dnet->notrefcnt)) { + debugfs_remove_recursive(dnet->netdir); + return PTR_ERR(dnet->notrefcnt); + } + + return 0; +} + +static void +ns_debug_exit_net(struct net *net) +{ + struct ns_debug_net *dnet = net_generic(net, ns_debug_net_id); + + debugfs_remove_recursive(dnet->netdir); +} + +static struct pernet_operations ns_debug_net_ops = { + .init = ns_debug_init_net, + .exit = ns_debug_exit_net, + .id = &ns_debug_net_id, + .size = sizeof(struct ns_debug_net), +}; + +static int __init ns_debug_init(void) +{ + ns_debug_dir = debugfs_create_dir("net_ns", NULL); + if (IS_ERR(ns_debug_dir)) + return PTR_ERR(ns_debug_dir); + + register_pernet_subsys(&ns_debug_net_ops); + return 0; +} +late_initcall(ns_debug_init); +#endif /* CONFIG_NET_NS_REFCNT_TRACKER */ +#endif /* CONFIG_DEBUG_FS */
CONFIG_NET_NS_REFCNT_TRACKER currently has no convenient way to display its tracking info. Add a new net_ns directory in debugfs. Have a directory in there for every net, with refcnt and notrefcnt files that show the currently tracked active and passive references. Signed-off-by: Jeff Layton <jlayton@kernel.org> --- Recently, I had a need to track down some long-held netns references, and discovered CONFIG_NET_NS_REFCNT_TRACKER. The main thing that seemed to be missing from it though is a simple way to view the currently held references on the netns. This adds files in debugfs for this. --- net/core/net_namespace.c | 151 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) --- base-commit: 695caca9345a160ecd9645abab8e70cfe849e9ff change-id: 20250324-netns-debugfs-df213b2ab9ce Best regards,