Page Menu
Home
ClusterLabs Projects
Search
Configure Global Search
Log In
Files
F1841977
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
33 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/askant/INSTALL b/askant/INSTALL
new file mode 100644
index 000000000..ce26b8255
--- /dev/null
+++ b/askant/INSTALL
@@ -0,0 +1,42 @@
+
+ How To Install Askant
+ ---------------------
+
+o Build Dependencies
+ - Python
+ - libgfs2 (part of the Red Hat cluster suite as a static lib)
+
+o Dependencies
+ - Python
+ - blktrace and blkparse with appropriate kernel config options enabled and
+ debugfs mounted
+
+o Intro
+ Askant uses Python's distutils for its standard installation procedure so the
+ setup.py script provides installation routines. Note that it doesn't provide
+ uninstallation routines so the best way to install it is to obtain a package
+ for your particular distribution and install that.
+
+o Installation
+ To install askant to the default location on your system, run as root:
+
+ ./setup.py install
+
+ If you wish to install it to a different root directory (e.g. /var/mychroot)
+ or prefix directory (e.g. /usr/local instead of /usr) see:
+
+ ./setup.py install --help
+
+ To find out everything else setup.py can do:
+
+ ./setup.py --help-commands
+
+o Use Without Installing
+ To use askant without installing it (e.g. if you want to test patches
+ quickly) it is necessary to carry out the build stage to compile the required
+ plugins. Do:
+
+ ./setup.py build
+ cd build/lib*
+ python askant/askant.py
+
diff --git a/askant/PLUGINAPI b/askant/PLUGINAPI
new file mode 100644
index 000000000..4c2ecebc7
--- /dev/null
+++ b/askant/PLUGINAPI
@@ -0,0 +1,65 @@
+
+ Writing File System Plugins for Askant
+ --------------------------------------
+
+0. Contents
+
+ 1. Intro
+ 2. API
+ 3. Reference
+
+
+1. Intro
+
+In order to gather file system data with which to enhance the data provided by
+blktrace, there must be specific code written for each file system type we wish
+to support. This is where file system plugins come along. The plugins must
+basically traverse an on-disk file system structure on a block device and report
+back their findings through a callback handed to them by askant. They must also
+expose a function which stops their traversal loop in case askant wishes to stop
+their execution prematurely.
+
+
+2. API
+
+A file system plugin must be written as a Python module. This means that C, C++
+and Python are suitable languages to write them in. They must expose a number of
+Python functions to askant:
+
+parsefs(dev)
+
+ This function is called when askant wants the plugin to parse the file
+ system on the device specified by the device, dev.
+
+get_block_size()
+
+ This function is called to ascertain the file system block size which is
+ required in order to map the disk sector numbers provided by blktrace to
+ file system block numbers. It takes no arguments and should return a Python
+ integer value.
+
+set_report_hook(func)
+
+ This function is called to pass in a report function which the plugin should
+ call to report the details of a file system block. func is a function which
+ accepts four arguments in this order:
+
+ Block number: a Python long integer
+ Block type: a Python string
+ Block number of parent: a Python long integer
+ Block file name: a Python string (should be "" for non-inode blocks)
+
+handle_sigint()
+
+ This function should make the plugin stop its parsing loop. This allows it
+ to be interrupted if askant catches a signal, for example. It takes no
+ arguments and its return value is not used.
+
+
+3. Reference
+
+o See fsplugins/gfs2/gfs2module.c for an example of writing a Python fs plugin
+ module in C.
+
+o http://www.python.org/doc/ext/intro.html shows how to extend Python with C or
+ C++.
diff --git a/askant/README b/askant/README
new file mode 100644
index 000000000..74e32193f
--- /dev/null
+++ b/askant/README
@@ -0,0 +1,74 @@
+
+ Askant - A Block I/O Analysis Tool
+ ----------------------------------
+
+0. Contents
+
+ 1. Intro
+ 2. Commands and their options
+ 3. Examples
+
+
+1. Intro
+
+Askant allows you to gather file system block I/O data for performance analysis.
+It uses blktrace to trace block operations and adds file system specific data by
+parsing fs block information straight from the storage device. To do this it
+invokes functionality from file system parser plugins which traverse the on-disk
+structures and report details of the blocks they encounter through callbacks.
+
+Usage: askant <command> [options...]
+
+o See 'askant --commands' for a full list of commands.
+
+
+2. Commands and their options
+
+ dumpfs
+
+ Dumps the block data provided by an fs plugin from the given file system.
+
+ -d The block device which contains the file system under scrutiny
+ -t The type of the file system, i.e. the name of the fs plugin to load
+ -o Output file (optional). Stdout is used if '-' or omitted.
+ (Note that the output file should be on a different device to the one
+ specified with -d.)
+
+ unlinks
+
+ Does a dumpfs and runs blktrace until interrupted. It then blkparses the
+ blktrace data and adds the block data. This is suitable for tracing I/O
+ operations during an unlink test run.
+
+ -d The block device which contains the file system under scrutiny
+ -t The type of the file system, i.e. the name of the fs plugin to load
+ -g The path to the debugfs mount point, defaults to /sys/kernel/debug
+ -D The directory in which to store the block dumps and blktrace files.
+
+ creates
+
+ Runs blktrace until interrupted. It then does a dumpfs and blkparses the
+ blktrace data and adds the block data. This is suitable for tracing I/O
+ operations during a file creation test run.
+
+ -d The block device which contains the file system under scrutiny
+ -t The type of the file system, i.e. the name of the fs plugin to load
+ -g The path to the debugfs mount point, defaults to /sys/kernel/debug
+ -D The directory in which to store the block dumps and blktrace files.
+
+3. Examples
+
+o Parse the file system and dump information about each block to stdout e.g.:
+
+ # askant dumpfs -t gfs2 -d /dev/sda3
+
+o Trace 'unlinks' by dumping block information before the blktrace and matching
+ block data to provide a more detailed blkparse output e.g.:
+
+ # askant unlinks -t gfs2 -d /dev/sda3 -D /tmp/dir -g /mnt/debugfs
+
+o Trace 'creates' by dumping block information after the blktrace and matching
+ block data to provide a more detailed blkparse output e.g.:
+
+ # askant creates -t gfs2 -d /dev/sda3 -D /tmp/dir -g /mnt/debugfs
+
diff --git a/askant/askant/__init__.py b/askant/askant/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/askant/askant/about.py b/askant/askant/about.py
new file mode 100644
index 000000000..3d40a746e
--- /dev/null
+++ b/askant/askant/about.py
@@ -0,0 +1,5 @@
+"""
+Version information for askant.
+"""
+
+version = "0.0.1"
diff --git a/askant/askant/askant.py b/askant/askant/askant.py
new file mode 100644
index 000000000..a4376b6c5
--- /dev/null
+++ b/askant/askant/askant.py
@@ -0,0 +1,24 @@
+"""
+The main entry point for askant.
+"""
+
+import sys
+import commands
+
+def main():
+
+ """Run askant"""
+
+ cmds = commands.Commands()
+ cmds.register_command(commands.DumpFSCommand())
+ cmds.register_command(commands.UnlinksCommand())
+ cmds.register_command(commands.CreatesCommand())
+
+ try:
+ cmds.process_argv(sys.argv)
+ except commands.NoFSPluginException, p:
+ print >>sys.stderr, "Plugin not found: %s" %p
+
+
+if __name__ == '__main__':
+ main()
diff --git a/askant/askant/blktrace.py b/askant/askant/blktrace.py
new file mode 100644
index 000000000..fdf02194b
--- /dev/null
+++ b/askant/askant/blktrace.py
@@ -0,0 +1,93 @@
+"""
+Wrapper classes for running blktrace and blkparse and using their output
+"""
+
+import os
+import sys
+import time
+import signal
+from subprocess import Popen
+from subprocess import PIPE
+from sysfs import Sysfs
+
+class BlktraceException(Exception):
+
+ def __init__(self, val, msg):
+ Exception.__init__(self)
+ self.val = val
+ self.msg = msg
+
+ def __str__(self):
+ return self.msg
+
+class Blktrace:
+
+ def __init__(self, dev, debugfs='/sys/kernel/debug'):
+ self.dev = dev
+ self.debugfs = debugfs
+ self.sysfs = Sysfs(dev)
+ self.btpid = -1
+
+ def handle_sigint(self, sig, frame):
+ if self.btpid >= 0:
+ os.kill(self.btpid, signal.SIGTERM)
+
+ def trace(self, tracefile):
+
+ if not self.dev:
+ raise Exception("No device specified.")
+
+ btargs = ['blktrace',
+ '-d', self.dev,
+ '-r', self.debugfs,
+ '-o', tracefile]
+
+ blktrace = Popen(btargs, bufsize=1, stdout=PIPE,
+ stderr=open('/dev/null','w'))
+ self.btpid = blktrace.pid
+ btres = None
+ while btres is None:
+ time.sleep(1)
+ btres = blktrace.poll()
+
+ self.btpid = -1
+ if btres:
+ raise BlktraceException(btres,
+ 'blktrace exited with code ' + str(btres))
+
+
+
+ def parse(self, tracefile, getblk):
+
+ if not self.dev:
+ raise Exception("No device specified.")
+
+ offset = self.sysfs.get_partition_start_sector()
+
+ bpargs = ['blkparse', '-i', tracefile]
+ blkparse = Popen(bpargs, bufsize=1, stdout=PIPE)
+
+ bpres = None
+ while bpres is None:
+ output = blkparse.stdout.readline().strip()
+ if output:
+ chunks = output.split()
+ try:
+ # chunks[7] is the sector number
+ blk = list(getblk(int(chunks[7])))
+ print "%s %s %s" %\
+ (' '.join(blk[0:3]),\
+ output.strip(),
+ blk[3].strip())
+ except KeyError:
+ pass
+ except ValueError:
+ pass
+
+ bpres = blkparse.poll()
+
+ if bpres:
+ raise BlktraceException(bpres,
+ 'blkparse exited with code ' + str(bpres))
+
+
diff --git a/askant/askant/commands.py b/askant/askant/commands.py
new file mode 100644
index 000000000..ad4639f67
--- /dev/null
+++ b/askant/askant/commands.py
@@ -0,0 +1,333 @@
+"""Command line parsing and processing for askant"""
+
+import fs
+import os
+import sys
+import time
+import about
+import signal
+import tempfile
+from sysfs import Sysfs, SysfsException
+from blktrace import Blktrace, BlktraceException
+from optparse import OptionParser, make_option
+
+class NoFSPluginException(Exception):
+
+ """A generic Exception class for when file system plugins are missing"""
+
+ pass # Nothing special about this exception
+
+class Commands:
+
+ """Provides a convenient interface to the askant commands"""
+
+ def __init__(self):
+
+ self.commands = {}
+ self.parser = OptionParser(usage="%prog COMMAND [options]",
+ version=about.version)
+
+
+ def register_command(self, command):
+ self.commands[str(command)] = command
+
+ def __lookup_command(self, command):
+ try:
+ return self.commands[command]
+ except KeyError, k:
+ return DefaultCommand()
+
+ def __parse_command(self, command, argv):
+ self.parser.prog = "%s %s" %\
+ (self.parser.get_prog_name(), str(command))
+ self.parser.set_usage(command.get_usage())
+ optlst = command.get_options()
+ for o in optlst:
+ self.parser.add_option(o)
+ self.parser.set_description(command.get_help())
+
+ if str(command) != "":
+ argv = argv[1:]
+
+ return self.parser.parse_args(argv)
+
+ def process_argv(self, argv):
+ try:
+ command = self.__lookup_command(argv[1])
+ except IndexError, i:
+ # This exits
+ self.parser.error("No command specified. Try --help")
+
+ options, args = self.__parse_command(command, argv[1:])
+
+ command.do_command(options, args, self)
+
+class Command:
+ def __init__(self):
+ self.options = []
+
+class DefaultCommand(Command):
+ def __str__(self):
+ return ""
+
+ def get_options(self):
+ self.options.append(make_option("-c","--commands",
+ action="store_true",
+ help="Lists available commands"))
+ return self.options
+
+ def get_usage(self):
+ return "%prog COMMAND [options] [args]"
+
+ def get_help(self):
+ return "Askant is a tool for tracing I/O activity in Linux "\
+ "and adding extra file system context by "\
+ "parsing file system data directly."
+
+ def do_command(self, options, args, base):
+ if options.commands:
+ base.parser.print_usage()
+ print "Available commands:"
+ keys = base.commands.keys()
+ keys.sort()
+ for k in keys:
+ print " %10s %s" %\
+ (k, base.commands[k].get_help())
+ print ""
+ print "For command-specific help, use "\
+ "%sCOMMAND --help" % base.parser.get_prog_name()
+ else:
+ base.parser.error("Command not found. Try --commands")
+
+
+class FSCommandTemplate(Command):
+ def __init__(self):
+ self.fsmod = None
+ self.block_table = {}
+ self.block_func = self.__report_hook
+ self.sector_size = None
+ self.offset = None
+ self.options = [
+ make_option("-d","--device", metavar="DEVICE",
+ help="The device to analyse."),
+ ]
+
+ def load_fs_plugin(self, fsname):
+ try:
+ self.fsmod = __import__("fs." + fsname,
+ globals(), locals(), ["fs"])
+ except ImportError, i:
+ raise NoFSPluginException(fsname)
+
+ def parse_fs(self, dev, hook=None):
+ if hook is None:
+ hook = self.block_func
+
+ sfs = Sysfs(dev)
+ offset = sfs.get_partition_start_sector()
+ sector_size = sfs.get_dev_sector_size()
+
+ self.fsmod.set_report_hook(hook)
+
+ try:
+ sfs = Sysfs(dev)
+ self.offset = sfs.get_partition_start_sector()
+ self.sector_size = sfs.get_dev_sector_size()
+ signal.signal(signal.SIGINT, self.fsmod.handle_sigint)
+ self.blk_size = self.fsmod.get_block_size(dev)
+ self.fsmod.parsefs(dev)
+ signal.signal(signal.SIGINT, signal.SIG_DFL)
+ except IOError, i:
+ print >>sys.stderr, str(i)
+ sys.exit(1)
+
+ def set_outfile(self, outfile):
+ self.outfile = outfile
+
+ def trace(self, options, dump_before):
+
+ sfs = Sysfs(options.device)
+ self.offset = sfs.get_partition_start_sector()
+ self.sector_size = sfs.get_dev_sector_size()
+
+ tmstamp = time.strftime('%Y%m%d%H%M%S')
+ self.block_func = self.__report_hook
+ try:
+ self.outfile = open(os.path.join(options.outdir, "blocks-%s" %
+ tmstamp), 'w')
+ except IOError, e:
+ print >>sys.stderr, str(e)
+ sys.exit(1)
+
+ if dump_before:
+ print >>sys.stderr, "Gathering block data..."
+ self.parse_fs(options.device)
+ self.outfile.close()
+
+ if options.debugfs:
+ bt = Blktrace(options.device, options.debugfs)
+ else:
+ bt = Blktrace(options.device)
+
+ os.chdir(options.outdir) # Blktrace doesn't use absolute paths
+ print >>sys.stderr, "Tracing. Hit Ctrl-C to end..."
+ try:
+ signal.signal(signal.SIGINT, bt.handle_sigint)
+ bt.trace(tmstamp)
+ signal.signal(signal.SIGINT, signal.SIG_DFL)
+ except BlktraceException, b:
+ signal.signal(signal.SIGINT, signal.SIG_DFL)
+ print >>sys.stderr, str(b)
+ sys.exit(1)
+
+ if not dump_before:
+ print >>sys.stderr, "Gathering block data..."
+ self.parse_fs(options.device)
+ self.outfile.close()
+
+ print >>sys.stderr, "Matching blocks..."
+ blockdb = open(self.outfile.name, 'r')
+ blocks = {}
+ for l in blockdb.readlines():
+ s = l.split('\t')
+ blocks[int(s[0])] = tuple(s[1:])
+
+ bt.parse(os.path.join(options.outdir, tmstamp), blocks.__getitem__)
+ blockdb.close()
+
+ def __report_hook(self, blk, type, parent, fn):
+ if not fn:
+ fn = ""
+ try:
+ self.outfile.write("%d\t%d\t%s\t%d\t\"%s\"\n" % (
+ ((blk * self.blk_size)/self.sector_size) + self.offset,
+ blk, type, parent, fn))
+ except Exception, e:
+ print str(e)
+
+ def __dict_hook(self, blk, type, parent, fn):
+ self.block_table[
+ ((blk * self.blk_size)/self.sector_size)+self.offset] =\
+ (blk, type, parent, fn)
+
+
+class DumpFSCommand(FSCommandTemplate):
+ def __str__(self):
+ return "dumpfs"
+
+ def get_options(self):
+ self.options.append(make_option("-t","--type", metavar="FSTYPE",
+ help="The type of file system on the device."))
+ self.options.append(make_option("-o","--output", metavar="FILE",
+ default="-",
+ help="File to write output to or '-' for stdout "
+ "(default)"))
+ return self.options
+
+ def get_usage(self):
+ return "%prog [options]"
+
+ def get_help(self):
+ return "Dump fs block information in TSV format."
+
+ def do_command(self, options, args, base):
+
+ if not options.device:
+ base.parser.error("No device specified. Use -d.")
+
+ if not options.type:
+ base.parser.error("No file system type specified. "
+ "Use -t.")
+ try:
+ if options.output != "-":
+ self.set_outfile(open(options.output, 'w'))
+ else:
+ self.set_outfile(sys.stdout)
+ except IOError, e:
+ print >>sys.stderr,\
+ "Unable to open file for writing: %s" %\
+ options.output
+ return
+ except KeyError:
+ pass
+
+ self.load_fs_plugin(options.type)
+ try:
+ self.parse_fs(options.device)
+ except SysfsException, s:
+ base.parser.error(s)
+
+class UnlinksCommand(FSCommandTemplate):
+ def __str__(self):
+ return "unlinks"
+
+ def get_options(self):
+ self.options.append(make_option("-t","--type", metavar="FSTYPE",
+ help="The type of file system on the device."))
+ self.options.append(make_option("-g","--debugfs", metavar="PATH",
+ default="/sys/kernel/debug",
+ help="The path to the debugfs mountpoint. Default "
+ "/sys/kernel/debug"))
+ self.options.append(make_option("-D","--outdir", metavar="DIR",
+ help="Directory to write output to. Required."))
+ return self.options
+
+ def get_usage(self):
+ return "%prog [options]"
+
+ def get_help(self):
+ return "Trace block I/O activity during unlink tests."
+
+ def do_command(self, options, args, base):
+ if not options.device:
+ base.parser.error("No device specified. Use -d.")
+
+ self.device = options.device
+
+ if not options.type:
+ base.parser.error("No file system type specified. "
+ "Use -t.")
+ if not options.outdir:
+ base.parser.error("No output directory specified. "
+ "Use -D.")
+
+ self.load_fs_plugin(options.type)
+ self.trace(options, dump_before=True)
+
+class CreatesCommand(FSCommandTemplate):
+ def __str__(self):
+ return "creates"
+
+ def get_options(self):
+ self.options.append(make_option("-t","--type", metavar="FSTYPE",
+ help="The type of file system on the device."))
+ self.options.append(make_option("-g","--debugfs", metavar="PATH",
+ default="/sys/kernel/debug",
+ help="The path to the debugfs mountpoint. Default "
+ "/sys/kernel/debug"))
+ self.options.append(make_option("-D","--outdir", metavar="DIR",
+ help="Directory to write output to. Required."))
+ return self.options
+
+ def get_usage(self):
+ return "%prog [options]"
+
+ def get_help(self):
+ return "Trace block I/O activity during create tests."
+
+ def do_command(self, options, args, base):
+ if not options.device:
+ base.parser.error("No device specified. Use -d.")
+
+ self.device = options.device
+
+ if not options.type:
+ base.parser.error("No file system type specified. "
+ "Use -t.")
+ if not options.outdir:
+ base.parser.error("No output directory specified. "
+ "Use -D.")
+
+ self.load_fs_plugin(options.type)
+ self.trace(options, dump_before=False)
+
diff --git a/askant/askant/fs/__init__.py b/askant/askant/fs/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/askant/askant/sysfs.py b/askant/askant/sysfs.py
new file mode 100644
index 000000000..12b7a3bc0
--- /dev/null
+++ b/askant/askant/sysfs.py
@@ -0,0 +1,86 @@
+"""
+Provides access to sysfs data pertaining to storage partitions.
+"""
+
+import os
+import os.path
+
+class SysfsException(Exception):
+ """
+ An exception which is raised when things go wrong with Sysfs.
+ """
+ pass # No functionality to add over Exception class
+
+class Sysfs:
+ """
+ Provides access to sysfs data pertaining to storage partitions.
+ """
+ def __init__(self, partition, sysfs_path='/sys'):
+ """
+ Instantiate a Sysfs object. partition should be a path to a
+ disc partition device e.g. /dev/sda3.
+ """
+ self.partition = partition
+ self.device = self.__partition2parent()
+ self.sysfs_path = sysfs_path
+ line = self.__firstline(os.path.join(
+ sysfs_path,'block',
+ os.path.split(self.device)[1],
+ os.path.split(self.partition)[1],
+ 'start'))
+ self.partition_start = int(line)
+ line = self.__firstline(os.path.join(
+ sysfs_path,'block',
+ os.path.split(self.device)[1],
+ 'queue',
+ 'hw_sector_size'))
+ self.dev_sector_size = int(line)
+ line = self.__firstline(os.path.join(
+ sysfs_path,'block',
+ os.path.split(self.device)[1],
+ os.path.split(self.partition)[1],
+ 'size'))
+ self.partition_size = int(line)
+
+ def __firstline(self, fname):
+ """
+ Read the first line of a file
+ """
+ try:
+ fobj = open(fname, 'r')
+ except IOError:
+ raise SysfsException(
+ 'Could not open file "%s" for reading. Please '
+ 'check your kernel is 2.6.25 or later and '
+ 'sysfs is mounted.' % fname)
+ line = fobj.readline()
+ fobj.close()
+ return line
+
+ def __partition2parent(self):
+ """
+ Return the name of a partition device's parent device
+ e.g. /dev/sda3 -> /dev/sda
+ """
+ # This may need thinking about a bit more -
+ # nothing in life can be this simple.
+ return self.partition.rstrip('0123456789')
+
+
+ def get_partition_start_sector(self):
+ """
+ Look up the start sector of the partition.
+ """
+ return self.partition_start
+
+ def get_dev_sector_size(self):
+ """
+ Look up the size of the device's sectors.
+ """
+ return self.dev_sector_size
+
+ def get_partition_size(self):
+ """
+ Look up the size of the partition.
+ """
+ return self.partition_size
diff --git a/askant/fsplugins/__init__.py b/askant/fsplugins/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/askant/fsplugins/gfs2/gfs2.c b/askant/fsplugins/gfs2/gfs2.c
new file mode 100644
index 000000000..bac9c3509
--- /dev/null
+++ b/askant/fsplugins/gfs2/gfs2.c
@@ -0,0 +1,405 @@
+/**
+ * main.c - Functions for parsing the on-disk structure of a GFS2 fs.
+ * Written by Andrew Price
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <string.h>
+#include <fcntl.h>
+#include <libgfs2.h>
+#include <errno.h>
+
+void (*report_func)(long int blk, char *type, long int parent, char *fn);
+
+/* Define how the block information is reported */
+#define report(b,p,t,n) \
+ ((*report_func)((long int)b, t, (long int)p,n))
+/* (printf("%lu\t%s\t%lu\t%s\n", (long int)b, t, (long int)p, f)) */
+#define report_data(b,p) report(b,p,"D","")
+#define report_leaf(b,p) report(b,p,"L","")
+#define report_indir(b,p) report(b,p,"i","")
+#define report_inode(b,p,n) report(b,p,"I",n)
+
+struct blk_extended {
+ uint64_t parent_blk;
+ char *fname;
+ char *blk;
+};
+
+/* FIXME: A static length block stack is most likely a stupid idea */
+struct blkstack {
+ int top;
+ struct blk_extended blocks[1024];
+};
+
+struct blkstack blk_stack;
+struct gfs2_sb sb;
+uint32_t blk_size;
+off_t max_seek;
+int fd;
+int flag_stop;
+
+/* prog_name and print_it() are needed to satisfy externs in libgfs2 */
+char *prog_name = "askant";
+
+void print_it(const char *label, const char *fmt, const char *fmt2, ...)
+{
+ va_list args;
+
+ va_start(args, fmt2);
+ printf("%s = ", label);
+ vprintf(fmt, args);
+ printf("\n");
+ va_end(args);
+}
+
+/**
+ * Push a block onto the stack
+ */
+static void push_blk(char *blk, uint64_t parent, char *fn)
+{
+ struct blk_extended eblk;
+
+ eblk.blk = blk;
+ eblk.parent_blk = parent;
+ eblk.fname = fn;
+
+ blk_stack.top++;
+ blk_stack.blocks[blk_stack.top] = eblk;
+}
+
+/**
+ * Initialise the block stack
+ */
+static int blk_stack_init(void)
+{
+ blk_stack.top = -1;
+ return 1;
+}
+
+/**
+ * Pop a block from the stack
+ */
+static struct blk_extended pop_blk(void)
+{
+ return blk_stack.blocks[blk_stack.top--];
+}
+
+/**
+ * Read the GFS2 superblock into the global sb variable.
+ * Returns 1 on error, 0 on success.
+ */
+static int read_gfs2_sb(void)
+{
+ off_t offsetsb;
+ off_t offsetres;
+ unsigned char buffer[GFS2_BASIC_BLOCK];
+ ssize_t readsz;
+
+ offsetsb = GFS2_SB_ADDR * GFS2_BASIC_BLOCK;
+ offsetres = lseek(fd, offsetsb, SEEK_SET);
+
+ if (offsetres != offsetsb) {
+ fprintf(stderr, "Could not seek to sb location on device.\n");
+ return 0;
+ }
+
+ readsz = read(fd, buffer, GFS2_BASIC_BLOCK);
+ if (readsz != GFS2_BASIC_BLOCK) {
+ fprintf(stderr, "Could not read superblock.\n");
+ return 0;
+ }
+
+ gfs2_sb_in(&sb, (char *)buffer);
+ if (check_sb(&sb)) {
+ fprintf(stderr, "Not a GFS2 filesystem.\n");
+ return 0;
+ }
+
+ return 1;
+}
+
+/**
+ * Read a block.
+ * blk_offset must be a block number.
+ * The returned pointer must be free'd.
+ */
+static char *read_gfs2_blk(off_t blk_offset)
+{
+ off_t offset;
+ off_t offsetres;
+ ssize_t readsz;
+ char *buffer;
+
+ buffer = (char *)malloc(blk_size);
+ if (!buffer) {
+ fprintf(stderr, "Could not allocate memory for block.\n");
+ return NULL;
+ }
+
+ offset = blk_offset * blk_size;
+
+ offsetres = lseek(fd, offset, SEEK_SET);
+ if (offsetres != offset) {
+ fprintf(stderr,
+ "Could not seek to block location: %lu error: %s\n",
+ (long int)blk_offset, strerror(errno));
+ return NULL;
+ }
+
+ readsz = read(fd, buffer, blk_size);
+ if (readsz != blk_size) {
+ fprintf(stderr, "Could not read block: %lu\n",
+ (long int)blk_offset);
+ return NULL;
+ }
+
+ return buffer;
+}
+
+/**
+ * Look at indirect pointers from a starting point in a block.
+ */
+static void do_indirect(char *start, uint16_t height, uint64_t parent)
+{
+ uint64_t ptr;
+ unsigned int i;
+ char *blk;
+
+ for (i = 0; i < blk_size; i += sizeof(uint64_t)) {
+ ptr = be64_to_cpu(*(uint64_t *)(start + i));
+ if (ptr > 0 && ptr < (max_seek / blk_size)) {
+ if (height == 1) {
+ report_data(ptr, parent);
+ } else if (height > 1) {
+ blk = read_gfs2_blk(ptr);
+ if (blk) {
+ report_indir(ptr, parent);
+ do_indirect(blk, height - 1, ptr);
+ free(blk);
+ }
+ }
+ } else {
+ break;
+ }
+ }
+}
+
+/**
+ * Parse count number of dirents from a starting point in a block.
+ */
+static void do_dirents(char *dirents, char *end, uint64_t parent, uint64_t gparent)
+{
+ struct gfs2_dirent dirent;
+ char *di_blk;
+ char *fname;
+
+ while (dirents < end) {
+ gfs2_dirent_in(&dirent, dirents);
+ if (dirent.de_inum.no_addr &&
+ dirent.de_inum.no_addr != parent &&
+ dirent.de_inum.no_addr != gparent &&
+ dirent.de_name_len > 0 &&
+ dirent.de_name_len <= GFS2_FNAMESIZE) {
+
+ fname = (char *)malloc(dirent.de_name_len + 1);
+ if (!fname) {
+ break;
+ }
+
+ memcpy(fname, dirents + sizeof(struct gfs2_dirent),
+ dirent.de_name_len);
+ fname[dirent.de_name_len] = '\0';
+
+ di_blk = read_gfs2_blk(dirent.de_inum.no_addr);
+ if (di_blk) {
+ push_blk(di_blk, parent, fname);
+ }
+ }
+ dirents += dirent.de_rec_len;
+ }
+}
+
+/**
+ * Examine the dirents in a leaf block.
+ * If the leaf is chained, do the chained leaves too.
+ */
+static void do_leaf(char *blk, uint64_t parent, uint64_t gparent)
+{
+ struct gfs2_leaf leaf;
+
+ while (blk) {
+ gfs2_leaf_in(&leaf, blk);
+ do_dirents(blk + sizeof(struct gfs2_leaf), blk + blk_size,
+ parent, gparent);
+ free(blk);
+ if (!leaf.lf_next) {
+ break;
+ }
+
+ blk = read_gfs2_blk(leaf.lf_next);
+ }
+}
+
+/**
+ * Parse leaf pointer data and examine the
+ * dirents in the destination leaf blocks.
+ */
+static void do_leaves(char *start, uint64_t parent, uint64_t gparent)
+{
+ uint64_t ptr;
+ uint64_t prev;
+ unsigned int i;
+ char *blk;
+
+ prev = 0;
+ for (i = 0; i < blk_size - sizeof(struct gfs2_dinode);
+ i += sizeof(uint64_t)) {
+ ptr = be64_to_cpu(*(uint64_t *)(start + i));
+
+ if (ptr >= (max_seek / blk_size)) {
+ break;
+ }
+
+ if (ptr && ptr != prev) {
+ blk = read_gfs2_blk(ptr);
+ if (blk) {
+ report_leaf(ptr, parent);
+ do_leaf(blk, parent, gparent);
+ }
+ prev = ptr;
+ }
+ }
+}
+
+/**
+ * Parse inode data from a block
+ */
+static void do_inode_blk(char *blk, uint64_t parent, char *fname)
+{
+ struct gfs2_dinode di;
+ char *data;
+
+ gfs2_dinode_in(&di, blk);
+ report_inode(di.di_num.no_addr, parent, fname);
+
+ data = (char *)((struct gfs2_dinode *)blk + 1);
+
+ if (di.di_height > 0) {
+ /* Indirect pointers */
+ do_indirect(data, di.di_height, di.di_num.no_addr);
+ } else if (S_ISDIR(di.di_mode) && !(di.di_flags & GFS2_DIF_EXHASH)) {
+ /* Stuffed directory */
+ do_dirents(data, blk + blk_size, di.di_num.no_addr, parent);
+ } else if (S_ISDIR(di.di_mode) &&
+ (di.di_flags & GFS2_DIF_EXHASH) &&
+ !(di.di_height)) {
+ /* Directory, has hashtable, height == 0 */
+ do_leaves(data, di.di_num.no_addr, parent);
+ }
+
+ /* free previously stacked block */
+ free(fname);
+ free(blk);
+}
+
+/**
+ * Get the root dir block and parse the fs
+ * using a stack to keep track of the unvisited
+ * inode blocks.
+ */
+static void parse_fs(void)
+{
+ struct gfs2_inum *root_dir_inum;
+ struct gfs2_inum *master_dir_inum;
+ struct blk_extended blk;
+ char *root_blk;
+ char *master_blk;
+
+ flag_stop = 0;
+
+ root_dir_inum = &(sb.sb_root_dir);
+ master_dir_inum = &(sb.sb_master_dir);
+
+ root_blk = read_gfs2_blk(root_dir_inum->no_addr);
+ master_blk = read_gfs2_blk(master_dir_inum->no_addr);
+ if (!root_blk || !master_blk) {
+ return;
+ }
+
+ push_blk(root_blk, root_dir_inum->no_addr, NULL);
+ while (blk_stack.top >= 0 && !flag_stop) {
+ blk = pop_blk();
+ do_inode_blk(blk.blk, blk.parent_blk, blk.fname);
+ }
+
+ push_blk(master_blk, master_dir_inum->no_addr, NULL);
+ while (blk_stack.top >= 0 && !flag_stop) {
+ blk = pop_blk();
+ /* TODO: Examine each block's magic number instead of assuming
+ * they're inodes. Omitted for now due to time constraints and
+ * the number of GFS2_METATYPE_*s which need catering for.
+ */
+ do_inode_blk(blk.blk, blk.parent_blk, blk.fname);
+ }
+}
+
+/**
+ * Raise a flag to stop the parse loop cleanly
+ */
+void gfs2_stop(void)
+{
+ flag_stop = 1;
+}
+
+/**
+ * Parse a gfs2 file system on a given device
+ */
+int gfs2_parse(char *dev, void (*func)(long int b, char *t, long int p, char *f))
+{
+ report_func = func;
+
+ if (!blk_stack_init()) {
+ return 0;
+ }
+
+ if ((fd = open(dev, O_RDONLY)) < 0) {
+ return 0;
+ }
+
+ if (!read_gfs2_sb()) {
+ close(fd);
+ return 0;
+ }
+
+ blk_size = sb.sb_bsize;
+ max_seek = lseek(fd, 0, SEEK_END);
+
+ parse_fs();
+
+ close(fd);
+
+ return 1;
+}
+
+/**
+ * Return the block size of the gfs2 file system on a given device.
+ */
+uint32_t gfs2_block_size(char *dev)
+{
+ if ((fd = open(dev, O_RDONLY)) < 0) {
+ return 0;
+ }
+
+ if (!read_gfs2_sb()) {
+ close(fd);
+ return 0;
+ }
+ close(fd);
+ return sb.sb_bsize;
+}
diff --git a/askant/fsplugins/gfs2/gfs2.h b/askant/fsplugins/gfs2/gfs2.h
new file mode 100644
index 000000000..6c72eb087
--- /dev/null
+++ b/askant/fsplugins/gfs2/gfs2.h
@@ -0,0 +1,3 @@
+uint32_t gfs2_block_size(char *dev);
+int gfs2_parse(char *dev, void (*func)(long int b, char *t, long int p, char *f));
+void gfs2_stop(void);
diff --git a/askant/fsplugins/gfs2/gfs2module.c b/askant/fsplugins/gfs2/gfs2module.c
new file mode 100644
index 000000000..c6a0584e1
--- /dev/null
+++ b/askant/fsplugins/gfs2/gfs2module.c
@@ -0,0 +1,104 @@
+#include <Python.h>
+
+#include "gfs2.h"
+
+static PyObject *report_func = NULL;
+
+void report_func_wrapper(long int blk, char *type, long int parent, char *fn)
+{
+ PyObject *arglist;
+ PyObject *result;
+
+ arglist = Py_BuildValue("lsls", blk, type, parent, fn);
+ if (!arglist) {
+ return;
+ }
+ result = PyEval_CallObject(report_func, arglist);
+ Py_DECREF(arglist);
+ if (!result) {
+ return;
+ }
+ Py_DECREF(result);
+}
+
+static PyObject *gfs2_set_report_hook(PyObject *self, PyObject *args)
+{
+ PyObject *result = NULL;
+ PyObject *temp;
+
+ if (PyArg_ParseTuple(args, "O:set_callback", &temp)) {
+ if (!PyCallable_Check(temp)) {
+ PyErr_SetString(PyExc_TypeError, "parameter must be callable");
+ return NULL;
+ }
+ Py_XINCREF(temp);
+ Py_XDECREF(report_func);
+ report_func = temp;
+
+ Py_INCREF(Py_None);
+ result = Py_None;
+ }
+ return result;
+}
+
+static PyObject *gfs2_parsefs(PyObject *self, PyObject *args)
+{
+ char *dev;
+
+ if (!PyArg_ParseTuple(args, "s:parsefs", &dev)) {
+ return NULL;
+ }
+
+ if (!gfs2_parse(dev, &report_func_wrapper)) {
+ return PyErr_SetFromErrno(PyExc_IOError);
+ }
+ Py_INCREF(Py_None);
+
+ return Py_None;
+}
+
+static PyObject *gfs2_get_block_size(PyObject *self, PyObject *args)
+{
+ char *dev;
+ uint32_t blksize;
+
+ if (!PyArg_ParseTuple(args, "s:get_block_size", &dev)) {
+ return NULL;
+ }
+
+ blksize = gfs2_block_size(dev);
+ if (!blksize) {
+ return PyErr_SetFromErrno(PyExc_IOError);
+ }
+
+ PyObject *size = Py_BuildValue("I", blksize);
+ if (!size) {
+ return NULL;
+ }
+ Py_INCREF(size);
+ return size;
+}
+
+static PyObject *gfs2_handle_sigint(PyObject *signum, PyObject *frame)
+{
+ gfs2_stop();
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyMethodDef GFS2Methods[] = {
+ {"set_report_hook", gfs2_set_report_hook, METH_VARARGS,
+ "Specify a hook function through which to report blocks."},
+ {"parsefs", gfs2_parsefs, METH_VARARGS,
+ "Parses the given block device as a GFS2 file system."},
+ {"get_block_size", gfs2_get_block_size, METH_VARARGS,
+ "Returns the file system block size."},
+ {"handle_sigint", gfs2_handle_sigint, METH_VARARGS,
+ "Handles signal SIGINT."},
+ {NULL, NULL, 0, NULL}
+};
+
+PyMODINIT_FUNC initgfs2(void)
+{
+ (void)Py_InitModule("gfs2", GFS2Methods);
+}
diff --git a/askant/scripts/askant b/askant/scripts/askant
new file mode 100755
index 000000000..3375b0c96
--- /dev/null
+++ b/askant/scripts/askant
@@ -0,0 +1,6 @@
+#! /usr/bin/env python
+
+from askant import askant
+
+if __name__ == '__main__':
+ askant.main()
diff --git a/askant/setup.py b/askant/setup.py
new file mode 100755
index 000000000..78daff24c
--- /dev/null
+++ b/askant/setup.py
@@ -0,0 +1,18 @@
+#!/usr/bin/env python
+
+from distutils.core import setup, Extension
+from askant import about
+
+setup(name="askant",
+ version=about.version,
+ description="File system performance analysis tool",
+ author="Andrew Price",
+ author_email="andy@andrewprice.me.uk",
+ url="http://andrewprice.me.uk/projects/askant",
+ packages = ['askant','askant.fs'],
+ ext_modules = [Extension("askant.fs.gfs2",
+ sources = ["fsplugins/gfs2/gfs2module.c","fsplugins/gfs2/gfs2.c"],
+ libraries = ["gfs2"],
+ include_dirs=['../gfs2/libgfs2', '../gfs2/include', '../make'],
+ library_dirs=['../gfs2/libgfs2'])],
+ scripts = ['scripts/askant'])
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Nov 23, 11:04 AM (1 d, 13 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1018599
Default Alt Text
(33 KB)
Attached To
Mode
rR Resource Agents
Attached
Detach File
Event Timeline
Log In to Comment