From 5578f58bdc65d8a2d49b34be21f979bd1fcb0093 Mon Sep 17 00:00:00 2001 From: Boris Protopopov Date: Tue, 30 Jun 2015 17:47:15 -0400 Subject: [PATCH] Add a script to display SPL slab cache statistics Useful when looking for the info on ZFS/SPL related memory consumption. Signed-off-by: Boris Protopopov Signed-off-by: Brian Behlendorf Closes #460 --- cmd/Makefile.am | 12 +-- cmd/splat/Makefile.am | 11 +++ cmd/{ => splat}/splat.c | 0 cmd/{ => splat}/splat.h | 0 cmd/splslab/Makefile.am | 2 + cmd/splslab/splslab.py | 202 ++++++++++++++++++++++++++++++++++++++++ configure.ac | 2 + rpm/generic/spl.spec.in | 1 + 8 files changed, 219 insertions(+), 11 deletions(-) create mode 100644 cmd/splat/Makefile.am rename cmd/{ => splat}/splat.c (100%) rename cmd/{ => splat}/splat.h (100%) create mode 100644 cmd/splslab/Makefile.am create mode 100755 cmd/splslab/splslab.py diff --git a/cmd/Makefile.am b/cmd/Makefile.am index 01afdcf..63a3c76 100644 --- a/cmd/Makefile.am +++ b/cmd/Makefile.am @@ -1,11 +1 @@ -include $(top_srcdir)/config/Rules.am - -DEFAULT_INCLUDES += \ - -I$(top_srcdir)/lib - -sbin_PROGRAMS = splat - -splat_SOURCES = splat.c -splat_LDFLAGS = $(top_builddir)/lib/libcommon.la - -EXTRA_DIST = splat.h +SUBDIRS = splat splslab diff --git a/cmd/splat/Makefile.am b/cmd/splat/Makefile.am new file mode 100644 index 0000000..01afdcf --- /dev/null +++ b/cmd/splat/Makefile.am @@ -0,0 +1,11 @@ +include $(top_srcdir)/config/Rules.am + +DEFAULT_INCLUDES += \ + -I$(top_srcdir)/lib + +sbin_PROGRAMS = splat + +splat_SOURCES = splat.c +splat_LDFLAGS = $(top_builddir)/lib/libcommon.la + +EXTRA_DIST = splat.h diff --git a/cmd/splat.c b/cmd/splat/splat.c similarity index 100% rename from cmd/splat.c rename to cmd/splat/splat.c diff --git a/cmd/splat.h b/cmd/splat/splat.h similarity index 100% rename from cmd/splat.h rename to cmd/splat/splat.h diff --git a/cmd/splslab/Makefile.am b/cmd/splslab/Makefile.am new file mode 100644 index 0000000..b18d52d --- /dev/null +++ b/cmd/splslab/Makefile.am @@ -0,0 +1,2 @@ +bin_SCRIPTS = splslab.py +EXTRA_DIST = $(bin_SCRIPTS) diff --git a/cmd/splslab/splslab.py b/cmd/splslab/splslab.py new file mode 100755 index 0000000..160fb27 --- /dev/null +++ b/cmd/splslab/splslab.py @@ -0,0 +1,202 @@ +#!/usr/bin/python + +import sys +import time +import getopt +import re +import signal +from collections import defaultdict + +class Stat: + # flag definitions based on the kmem.h + NOTOUCH = 1 + NODEBUG = 2 + KMEM = 32 + VMEM = 64 + SLAB = 128 + OFFSLAB = 256 + NOEMERGENCY = 512 + DEADLOCKED = 16384 + GROWING = 32768 + REAPING = 65536 + DESTROY = 131072 + + fdefs = { + NOTOUCH : "NTCH", + NODEBUG : "NDBG", + KMEM : "KMEM", + VMEM : "VMEM", + SLAB : "SLAB", + OFFSLAB : "OFSL", + NOEMERGENCY : "NEMG", + DEADLOCKED : "DDLK", + GROWING : "GROW", + REAPING : "REAP", + DESTROY : "DSTR" + } + + def __init__(self, name, flags, size, alloc, slabsize, objsize): + self._name = name + self._flags = self.f2str(flags) + self._size = size + self._alloc = alloc + self._slabsize = slabsize + self._objsize = objsize + + def f2str(self, flags): + fstring = '' + for k in Stat.fdefs.keys(): + if flags & k: + fstring = fstring + Stat.fdefs[k] + '|' + + fstring = fstring[:-1] + return fstring + +class CumulativeStat: + def __init__(self, skey="a"): + self._size = 0 + self._alloc = 0 + self._pct = 0 + self._skey = skey + self._regexp = \ + re.compile('(\w+)\s+(\w+)\s+(\w+)\s+(\w+)\s+(\w+)\s+(\w+)\s+'); + self._stats = defaultdict(list) + + # Add another stat to the dictionary and re-calculate the totals + def add(self, s): + key = 0 + if self._skey == "a": + key = s._alloc + else: + key = s._size + self._stats[key].append(s) + self._size = self._size + s._size + self._alloc = self._alloc + s._alloc + if self._size: + self._pct = self._alloc * 100 / self._size + else: + self._pct = 0 + + # Parse the slab info in the procfs + # Calculate cumulative stats + def slab_update(self): + k = [line.strip() for line in open('/proc/spl/kmem/slab')] + + if not k: + sys.stderr.write("No SPL slab stats found\n") + sys.exit(1) + + del k[0:2] + + for s in k: + if not s: + continue + m = self._regexp.match(s) + if m: + self.add(Stat(m.group(1), int(m.group(2),16), int(m.group(3)), + int(m.group(4)), int(m.group(5)), int(m.group(6)))) + else: + sys.stderr.write("Error: unexpected input format\n" % s) + exit(-1) + + def show_header(self): + sys.stdout.write("\n%25s %20s %15s %15s %15s %15s\n\n" % \ + ("cache name", "flags", "size", "alloc", "slabsize", "objsize")) + + # Show up to the number of 'rows' of output sorted in descending order + # by the key specified earlier; if rows == 0, all rows are shown + def show(self, rows): + self.show_header() + i = 1 + done = False + for k in reversed(sorted(self._stats.keys())): + for s in self._stats[k]: + sys.stdout.write("%25s %20s %15d %15d %15d %15d\n" % \ + (s._name, s._flags, s._size, s._alloc, \ + s._slabsize, s._objsize)) + i = i + 1 + if rows != 0 and i > rows: + done = True + break + if done: + break + sys.stdout.write("%25s %36d %15d (%d%%)\n\n" % \ + ("Totals:", self._size, self._alloc, self._pct)) + +def usage(): + cmd = "Usage: splslab.py [-n|--num-rows] number [-s|--sort-by] " + \ + "[interval] [count]"; + sys.stderr.write("%s\n" % cmd) + sys.stderr.write("\t-h : print help\n") + sys.stderr.write("\t-n : --num-rows N : limit output to N top " + + "largest slabs (default: all)\n") + sys.stderr.write("\t-s : --sort-by key : sort output in descending " + + "order by total size (s)\n\t\tor allocated size (a) " + + "(default: a)\n") + sys.stderr.write("\tinterval : repeat every interval seconds\n") + sys.stderr.write("\tcount : output statistics count times and exit\n") + + +def main(): + + rows = 0 + count = 0 + skey = "a" + interval = 1 + + signal.signal(signal.SIGINT, signal.SIG_DFL) + + try: + opts, args = getopt.getopt( + sys.argv[1:], + "n:s:h", + [ + "num-rows", + "sort-by", + "help" + ] + ) + except getopt.error as e: + sys.stderr.write("Error: %s\n" % e.msg) + usage() + exit(-1) + + i = 1 + for opt, arg in opts: + if opt in ('-n', '--num-rows'): + rows = int(arg) + i = i + 2 + elif opt in ('-s', '--sort-by'): + if arg != "s" and arg != "a": + sys.stderr.write("Error: invalid sorting key \"%s\"\n" % arg) + usage() + exit(-1) + skey = arg + i = i + 2 + elif opt in ('-h', '--help'): + usage() + exit(0) + else: + break + + args = sys.argv[i:] + + interval = int(args[0]) if len(args) else interval + count = int(args[1]) if len(args) > 1 else count + + i = 0 + while True: + cs = CumulativeStat(skey) + cs.slab_update() + cs.show(rows) + + i = i + 1 + if count and i >= count: + break + + time.sleep(interval) + + return 0 + +if __name__ == '__main__': + main() diff --git a/configure.ac b/configure.ac index efeb243..70735ce 100644 --- a/configure.ac +++ b/configure.ac @@ -54,6 +54,8 @@ AC_CONFIG_FILES([ man/man5/Makefile lib/Makefile cmd/Makefile + cmd/splat/Makefile + cmd/splslab/Makefile module/Makefile module/spl/Makefile module/splat/Makefile diff --git a/rpm/generic/spl.spec.in b/rpm/generic/spl.spec.in index 48eafe6..43bbd98 100644 --- a/rpm/generic/spl.spec.in +++ b/rpm/generic/spl.spec.in @@ -33,6 +33,7 @@ make install DESTDIR=%{?buildroot} %files %doc AUTHORS COPYING DISCLAIMER +%{_bindir}/* %{_sbindir}/* %{_mandir}/man1/* %{_mandir}/man5/* -- 2.39.5