linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 1/2] kmemleak: dump all objects for slab usage analysis
@ 2018-08-28 10:39 Vincent Whitchurch
  2018-08-28 10:39 ` [PATCH 2/2] scripts: add kmemleak2pprof.py " Vincent Whitchurch
  0 siblings, 1 reply; 5+ messages in thread
From: Vincent Whitchurch @ 2018-08-28 10:39 UTC (permalink / raw)
  To: catalin.marinas, akpm; +Cc: linux-kernel, linux-mm, Vincent Whitchurch

In order to be able to analyse the kernel's slab usage, we'd need a list
of allocated objects and their allocation stacks.  Kmemleak already
maintains such a list internally, so we expose it via debugfs file.

This file can be post-processed in userspace and converted to a suitable
format for slab usage analysis.

Signed-off-by: Vincent Whitchurch <vincent.whitchurch@axis.com>
---
 mm/kmemleak.c | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 53 insertions(+)

diff --git a/mm/kmemleak.c b/mm/kmemleak.c
index 17dd883198ae..7bef05c690d6 100644
--- a/mm/kmemleak.c
+++ b/mm/kmemleak.c
@@ -1759,6 +1759,34 @@ static int kmemleak_seq_show(struct seq_file *seq, void *v)
 	return 0;
 }
 
+static void kmemleak_print_object(struct seq_file *seq,
+				  struct kmemleak_object *object)
+{
+	int i;
+
+	seq_printf(seq, "object 0x%08lx (size %zu):\n",
+		   object->pointer, object->size);
+	seq_printf(seq, "  comm \"%s\", pid %d, jiffies %lu\n",
+		   object->comm, object->pid, object->jiffies);
+
+	for (i = 0; i < object->trace_len; i++) {
+		void *ptr = (void *)object->trace[i];
+
+		seq_printf(seq, "    [<%p>] %pS\n", ptr, ptr);
+	}
+}
+
+static int kmemleak_all_seq_show(struct seq_file *seq, void *v)
+{
+	struct kmemleak_object *object = v;
+	unsigned long flags;
+
+	spin_lock_irqsave(&object->lock, flags);
+	kmemleak_print_object(seq, object);
+	spin_unlock_irqrestore(&object->lock, flags);
+	return 0;
+}
+
 static const struct seq_operations kmemleak_seq_ops = {
 	.start = kmemleak_seq_start,
 	.next  = kmemleak_seq_next,
@@ -1766,11 +1794,23 @@ static const struct seq_operations kmemleak_seq_ops = {
 	.show  = kmemleak_seq_show,
 };
 
+static const struct seq_operations kmemleak_all_seq_ops = {
+	.start = kmemleak_seq_start,
+	.next  = kmemleak_seq_next,
+	.stop  = kmemleak_seq_stop,
+	.show  = kmemleak_all_seq_show,
+};
+
 static int kmemleak_open(struct inode *inode, struct file *file)
 {
 	return seq_open(file, &kmemleak_seq_ops);
 }
 
+static int kmemleak_all_open(struct inode *inode, struct file *file)
+{
+	return seq_open(file, &kmemleak_all_seq_ops);
+}
+
 static int dump_str_object_info(const char *str)
 {
 	unsigned long flags;
@@ -1911,6 +1951,14 @@ static const struct file_operations kmemleak_fops = {
 	.release	= seq_release,
 };
 
+static const struct file_operations kmemleak_all_fops = {
+	.owner		= THIS_MODULE,
+	.open		= kmemleak_all_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= seq_release,
+};
+
 static void __kmemleak_do_cleanup(void)
 {
 	struct kmemleak_object *object;
@@ -2102,6 +2150,11 @@ static int __init kmemleak_late_init(void)
 	if (!dentry)
 		pr_warn("Failed to create the debugfs kmemleak file\n");
 
+	dentry = debugfs_create_file("kmemleak_all", 0400, NULL, NULL,
+				     &kmemleak_all_fops);
+	if (!dentry)
+		pr_warn("Failed to create the debugfs kmemleak_all file\n");
+
 	if (kmemleak_error) {
 		/*
 		 * Some error occurred and kmemleak was disabled. There is a
-- 
2.11.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH 2/2] scripts: add kmemleak2pprof.py for slab usage analysis
  2018-08-28 10:39 [PATCH 1/2] kmemleak: dump all objects for slab usage analysis Vincent Whitchurch
@ 2018-08-28 10:39 ` Vincent Whitchurch
  2018-08-28 23:28   ` Andrew Morton
  0 siblings, 1 reply; 5+ messages in thread
From: Vincent Whitchurch @ 2018-08-28 10:39 UTC (permalink / raw)
  To: catalin.marinas, akpm; +Cc: linux-kernel, linux-mm, Vincent Whitchurch

Add a script which converts /sys/kernel/debug/kmemleak_all to the pprof
format, which can be used for analysing memory usage.  See
https://github.com/google/pprof.

 $ ./kmemleak2pprof.py kmemleak_all
 $ pprof -text -ignore free_area_init_node -compact_labels -nodecount 10 prof
 Showing nodes accounting for 4.85MB, 34.05% of 14.23MB total
 Dropped 3989 nodes (cum <= 0.07MB)
 Showing top 10 nodes out of 190
       flat  flat%   sum%        cum   cum%
     1.39MB  9.78%  9.78%     1.61MB 11.29%  new_inode_pseudo+0x8/0x4c
     0.75MB  5.27% 15.04%     0.75MB  5.27%  alloc_large_system_hash+0x19c/0x2b8
     0.73MB  5.12% 20.17%     0.86MB  6.07%  kernfs_new_node+0x30/0x50
     0.66MB  4.62% 24.79%     0.66MB  4.62%  __vmalloc_node.constprop.9+0x48/0x50
     0.61MB  4.28% 29.06%     0.61MB  4.28%  d_alloc+0x10/0x78
     0.22MB  1.52% 30.58%     0.22MB  1.52%  alloc_inode+0x1c/0xa4
     0.18MB  1.28% 31.86%     0.20MB  1.42%  _do_fork+0xb0/0x41c
     0.13MB  0.88% 32.74%     0.13MB  0.88%  early_trace_init+0x16c/0x374
     0.09MB  0.66% 33.40%     0.17MB  1.17%  inet_init+0x128/0x24c
     0.09MB  0.65% 34.05%     0.09MB  0.65%  __kernfs_new_node+0x34/0x1a8

Signed-off-by: Vincent Whitchurch <vincent.whitchurch@axis.com>
---
 scripts/kmemleak2pprof.py | 164 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 164 insertions(+)
 create mode 100755 scripts/kmemleak2pprof.py

diff --git a/scripts/kmemleak2pprof.py b/scripts/kmemleak2pprof.py
new file mode 100755
index 000000000000..1295d3ca9a9d
--- /dev/null
+++ b/scripts/kmemleak2pprof.py
@@ -0,0 +1,164 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (C) 2018 Axis Communications AB
+#
+# Converts /sys/kernel/debug/kmemleak_all to the pprof format, see
+# https://github.com/google/pprof.
+#
+# profile_pb2.py can be generated with the following commands.  protoc is
+# packaged as protobuf-compiler in Debian:
+#
+#  wget https://raw.githubusercontent.com/google/pprof/master/proto/profile.proto
+#  protoc -I. --python_out=. profile.proto
+
+import argparse
+
+from collections import defaultdict
+
+import profile_pb2
+
+
+# object 0xee0243b0 (size 464):
+#   comm "swapper/0", pid 0, jiffies 4294937296
+#     [<80220673>] alloc_inode+0x13/0x60
+#     [<80221cc5>] new_inode_pseudo+0xd/0x38
+#     [<802568a3>] proc_setup_thread_self+0x37/0xc4
+#     [<8020e8c1>] mount_ns+0x55/0x94
+#     [<8024f2e1>] proc_mount+0x45/0x48
+#     [<8020ee9b>] mount_fs+0x1f/0x104
+#     [<80224785>] vfs_kern_mount.part.3+0x35/0xbc
+#     [<80224833>] kern_mount_data+0x17/0x2c
+#     [<8024f44b>] pid_ns_prepare_proc+0x13/0x24
+#     [<8012ed0d>] alloc_pid+0x309/0x338
+#     [<80118e2b>] copy_process.part.5+0xa2b/0x1308
+#     [<80119807>] _do_fork+0x77/0x2f0
+#     [<80119abf>] kernel_thread+0x23/0x28
+#     [<8053517f>] rest_init+0x27/0xb4
+#     [<80900afb>] start_kernel+0x369/0x372
+#     [<0000807b>] 0x807b
+class KmemleakAll(object):
+    def __init__(self):
+        pass
+
+    def analyze(self, f):
+        allocs = defaultdict(int)
+        stack = []
+        size = 0
+
+        while True:
+            line = f.readline()
+            if not line:
+                break
+
+            line = line.strip()
+
+            if line.startswith('['):
+                # (null) is in the address part so later parsing steps fail.
+                # Don't bother fixing it up since it's clearly bogus.
+                if '(null)' in line:
+                    continue
+
+                stack.append(line)
+                continue
+            elif line.startswith('comm'):
+                continue
+
+            if size:
+                allocs[(tuple(stack), size)] += 1
+                size = 0
+
+            stack = []
+            size = int(line.split('(size ')[1].strip('):'))
+
+        return sorted(allocs.items(), key=lambda x: x[0][1] * x[1], reverse=True)
+
+
+class ProfileWriter(object):
+    def __init__(self, allocs):
+        self.profile = profile_pb2.Profile()
+        self.strings = ['']
+        self.allocs = allocs
+        self.locations = {}
+        self.functions = {}
+
+    def stridx(self, s):
+        try:
+            idx = self.strings.index(s)
+        except ValueError:
+            idx = len(self.strings)
+            self.strings.append(s)
+
+        return idx
+
+    def get_function_id(self, funcname, filename):
+        try:
+            return self.functions[(funcname, filename)].id
+        except KeyError:
+            pass
+
+        function = self.profile.function.add()
+        function.id = len(self.functions) + 1
+        function.name = self.stridx(funcname)
+        function.filename = self.stridx(filename)
+
+        self.functions[(funcname, filename)] = function
+
+        return function.id
+
+    def get_location_id(self, addr):
+        if addr.startswith('['):
+            _, func = addr.split(' ', maxsplit=1)
+
+        try:
+            return self.locations[addr].id
+        except KeyError:
+            pass
+
+        location = self.profile.location.add()
+        location.id = len(self.locations) + 1
+
+        # We don't have access to the file or line information.
+        locline = location.line.add()
+        locline.function_id = self.get_function_id(func, 'dummy.c')
+
+        self.locations[addr] = location
+
+        return location.id
+
+    def write(self, fn):
+        valuetype = self.profile.sample_type.add()
+        valuetype.type = self.stridx('slab')
+        valuetype.unit = self.stridx('bytes')
+
+        for i, alloc in enumerate(self.allocs):
+            stacksize, count = alloc
+            stack, size = stacksize
+
+            for instance in range(count):
+                sample = self.profile.sample.add()
+                sample.value.append(size)
+
+                for addr in stack:
+                    sample.location_id.append(self.get_location_id(addr))
+
+        self.profile.string_table.extend(self.strings)
+
+        with open(fn, 'wb') as f:
+            f.write(self.profile.SerializeToString())
+
+
+def main():
+    parser = argparse.ArgumentParser()
+    parser.add_argument('--output', default='prof')
+    parser.add_argument('data')
+    args = parser.parse_args()
+
+    with open(args.data) as f:
+        allocs = KmemleakAll().analyze(f)
+
+    ProfileWriter(allocs).write(args.output)
+
+
+if __name__ == '__main__':
+    main()
-- 
2.11.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [PATCH 2/2] scripts: add kmemleak2pprof.py for slab usage analysis
  2018-08-28 10:39 ` [PATCH 2/2] scripts: add kmemleak2pprof.py " Vincent Whitchurch
@ 2018-08-28 23:28   ` Andrew Morton
  2018-08-30  7:29     ` Vincent Whitchurch
  0 siblings, 1 reply; 5+ messages in thread
From: Andrew Morton @ 2018-08-28 23:28 UTC (permalink / raw)
  To: Vincent Whitchurch
  Cc: catalin.marinas, linux-kernel, linux-mm, Vincent Whitchurch

On Tue, 28 Aug 2018 12:39:14 +0200 Vincent Whitchurch <vincent.whitchurch@axis.com> wrote:

> Add a script which converts /sys/kernel/debug/kmemleak_all to the pprof
> format, which can be used for analysing memory usage.  See
> https://github.com/google/pprof.

Why is this better than /proc/slabinfo?

>  $ ./kmemleak2pprof.py kmemleak_all
>  $ pprof -text -ignore free_area_init_node -compact_labels -nodecount 10 prof

Are we missing an argument here?  s/prof/kmemleak_all/?

>  Showing nodes accounting for 4.85MB, 34.05% of 14.23MB total
>  Dropped 3989 nodes (cum <= 0.07MB)
>  Showing top 10 nodes out of 190
>        flat  flat%   sum%        cum   cum%
>      1.39MB  9.78%  9.78%     1.61MB 11.29%  new_inode_pseudo+0x8/0x4c
>      0.75MB  5.27% 15.04%     0.75MB  5.27%  alloc_large_system_hash+0x19c/0x2b8
>      0.73MB  5.12% 20.17%     0.86MB  6.07%  kernfs_new_node+0x30/0x50
>      0.66MB  4.62% 24.79%     0.66MB  4.62%  __vmalloc_node.constprop.9+0x48/0x50
>      0.61MB  4.28% 29.06%     0.61MB  4.28%  d_alloc+0x10/0x78
>      0.22MB  1.52% 30.58%     0.22MB  1.52%  alloc_inode+0x1c/0xa4
>      0.18MB  1.28% 31.86%     0.20MB  1.42%  _do_fork+0xb0/0x41c
>      0.13MB  0.88% 32.74%     0.13MB  0.88%  early_trace_init+0x16c/0x374
>      0.09MB  0.66% 33.40%     0.17MB  1.17%  inet_init+0x128/0x24c
>      0.09MB  0.65% 34.05%     0.09MB  0.65%  __kernfs_new_node+0x34/0x1a8

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH 2/2] scripts: add kmemleak2pprof.py for slab usage analysis
  2018-08-28 23:28   ` Andrew Morton
@ 2018-08-30  7:29     ` Vincent Whitchurch
  2018-08-31  0:21       ` Andrew Morton
  0 siblings, 1 reply; 5+ messages in thread
From: Vincent Whitchurch @ 2018-08-30  7:29 UTC (permalink / raw)
  To: Andrew Morton; +Cc: catalin.marinas, linux-kernel, linux-mm

On Tue, Aug 28, 2018 at 04:28:04PM -0700, Andrew Morton wrote:
> On Tue, 28 Aug 2018 12:39:14 +0200 Vincent Whitchurch <vincent.whitchurch@axis.com> wrote:
> 
> > Add a script which converts /sys/kernel/debug/kmemleak_all to the pprof
> > format, which can be used for analysing memory usage.  See
> > https://github.com/google/pprof.
> 
> Why is this better than /proc/slabinfo?

slabinfo just tells you how much memory is being used in a particular
slab, it doesn't give you a breakdown of who allocated all that memory.
slabinfo can't also tell you how much memory a particular subsystem is
using.

For example, here we can see that tracer_init_tracefs() and its callers
are using ~12% of the total tracked memory:

 $ pprof -top -compact_labels -cum prof 
 Showing nodes accounting for 13418.95kB, 92.07% of 14575.28kB total
 Dropped 4069 nodes (cum <= 72.88kB)
       flat  flat%   sum%        cum   cum%
       ...
          0     0% 56.71%  1832.15kB 12.57%  tracer_init_tracefs+0x74/0x1cc

 
And that tracefs' dentrys use 500 KiB and its inodes use 1+ MiB:
 	
 $ pprof -text -compact_labels -focus tracer_init_tracefs -nodecount 2 prof
 Main binary filename not available.
 Showing nodes accounting for 1794.85kB, 12.31% of 14575.28kB total
 Dropped 1912 nodes (cum <= 72.88kB)
 Showing top 2 nodes out of 32
       flat  flat%   sum%        cum   cum%
  1294.56kB  8.88%  8.88%  1294.56kB  8.88%  new_inode_pseudo+0x8/0x4c
   500.29kB  3.43% 12.31%   500.29kB  3.43%  d_alloc+0x10/0x78
   ...

> 
> >  $ ./kmemleak2pprof.py kmemleak_all
> >  $ pprof -text -ignore free_area_init_node -compact_labels -nodecount 10 prof
> 
> Are we missing an argument here?  s/prof/kmemleak_all/?

No, the default output filename of this script is called "prof".

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH 2/2] scripts: add kmemleak2pprof.py for slab usage analysis
  2018-08-30  7:29     ` Vincent Whitchurch
@ 2018-08-31  0:21       ` Andrew Morton
  0 siblings, 0 replies; 5+ messages in thread
From: Andrew Morton @ 2018-08-31  0:21 UTC (permalink / raw)
  To: Vincent Whitchurch; +Cc: catalin.marinas, linux-kernel, linux-mm

On Thu, 30 Aug 2018 09:29:40 +0200 Vincent Whitchurch <vincent.whitchurch@axis.com> wrote:

> On Tue, Aug 28, 2018 at 04:28:04PM -0700, Andrew Morton wrote:
> > On Tue, 28 Aug 2018 12:39:14 +0200 Vincent Whitchurch <vincent.whitchurch@axis.com> wrote:
> > 
> > > Add a script which converts /sys/kernel/debug/kmemleak_all to the pprof
> > > format, which can be used for analysing memory usage.  See
> > > https://github.com/google/pprof.
> > 
> > Why is this better than /proc/slabinfo?
> 
> slabinfo just tells you how much memory is being used in a particular
> slab, it doesn't give you a breakdown of who allocated all that memory.
> slabinfo can't also tell you how much memory a particular subsystem is
> using.
> 
> For example, here we can see that tracer_init_tracefs() and its callers
> are using ~12% of the total tracked memory:
> 
>  $ pprof -top -compact_labels -cum prof 
>  Showing nodes accounting for 13418.95kB, 92.07% of 14575.28kB total
>  Dropped 4069 nodes (cum <= 72.88kB)
>        flat  flat%   sum%        cum   cum%
>        ...
>           0     0% 56.71%  1832.15kB 12.57%  tracer_init_tracefs+0x74/0x1cc
> 
>  
> And that tracefs' dentrys use 500 KiB and its inodes use 1+ MiB:
>  	
>  $ pprof -text -compact_labels -focus tracer_init_tracefs -nodecount 2 prof
>  Main binary filename not available.
>  Showing nodes accounting for 1794.85kB, 12.31% of 14575.28kB total
>  Dropped 1912 nodes (cum <= 72.88kB)
>  Showing top 2 nodes out of 32
>        flat  flat%   sum%        cum   cum%
>   1294.56kB  8.88%  8.88%  1294.56kB  8.88%  new_inode_pseudo+0x8/0x4c
>    500.29kB  3.43% 12.31%   500.29kB  3.43%  d_alloc+0x10/0x78
>    ...

OK, thanks.  Please include this info in future changelogs?

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2018-08-31  0:21 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-08-28 10:39 [PATCH 1/2] kmemleak: dump all objects for slab usage analysis Vincent Whitchurch
2018-08-28 10:39 ` [PATCH 2/2] scripts: add kmemleak2pprof.py " Vincent Whitchurch
2018-08-28 23:28   ` Andrew Morton
2018-08-30  7:29     ` Vincent Whitchurch
2018-08-31  0:21       ` Andrew Morton

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).