public inbox for linux-bcache@vger.kernel.org
 help / color / mirror / Atom feed
From: Andrew Thrift <andrew-3e6jenk95VYpDvLZ8AWkcaVXKuFTiq87@public.gmane.org>
To: "Darrick J. Wong"
	<darrick.wong-QHcLZuEGTsvQT0dZR+AlfA@public.gmane.org>,
	linux-bcache-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
Subject: Re: [RFC] bcache-status
Date: Fri, 16 Aug 2013 17:09:00 +1200	[thread overview]
Message-ID: <520DB3EC.70001@networklabs.co.nz> (raw)
In-Reply-To: <20130815233508.GB6949-yuuUpGxbzT9UbpRmUfBrXUB+6BGkLq7r@public.gmane.org>

On 8/16/2013 11:35 AM, Darrick J. Wong wrote:
> Hi all,
>
> I wrote a script to dump out various stats about a bcache.  It would be nice
> eventually to put it into bcache-tools so that users don't have to go digging
> through sysfs, but for now I'm really just wondering, does this program
> interpret the sysfs files correctly?
>
> I'm particularly anxious about 'cache used' since it's just reading out of
> /sys/fs/bcache/*/cache*/priority_stats and multiplying by cache size...
>
> ...also I wonder what the negative dirty data count means?
>
> $ bcache-status -s
> --- bcache ---
> Device                      /dev/bcache0 (253:0)
> UUID                        c4157b48-5cdc-4554-8ce6-520dafdbac55
> Block Size                  4.00KiB
> Bucket Size                 512.00KiB
> Congested?                  False
> Read Congestion             2.0ms
> Write Congestion            20.0ms
> Total Cache Size            205.66GiB
> Total Cache Used            26.74GiB	(12%)
> Total Cache Unused          178.92GiB	(87%)
> Dirty Data                  0B	(0%)
> Evictable Cache             205.66GiB	(100%)
> Replacement Policy          [lru] fifo random
> Cache Mode                  writethrough [writeback] writearound none
> Total Hits                  263809	(56%)
> Total Misses                199136
> Total Bypass Hits           9079	(100%)
> Total Bypass Misses         0
> Total Bypassed              2.70GiB
> --- Backing Device ---
>    Device                    /dev/sdb1 (8:17)
>    Size                      698.64GiB
>    Cache Mode                writethrough [writeback] writearound none
>    Readahead                 0
>    Sequential Cutoff         4.00MiB
>    Merge sequential?         True
>    State                     dirty
>    Writeback?                True
>    Dirty Data                -2.10MiB
>    Total Hits                263809	(56%)
>    Total Misses              199136
>    Total Bypass Hits         9079	(100%)
>    Total Bypass Misses       0
>    Total Bypassed            2.60GiB
> --- Cache Device ---
>    Device                    /dev/sda4 (8:4)
>    Size                      205.66GiB
>    Block Size                4.00KiB
>    Bucket Size               512.00KiB
>    Replacement Policy        [lru] fifo random
>    Discard?                  False
>    I/O Errors                0
>    Metadata Written          1.10GiB
>    Data Written              25.30GiB
>    Buckets                   421190
>    Cache Used                26.74GiB	(12%)
>    Cache Unused              178.92GiB	(87%)
>
> --D
>
> #!/usr/bin/env python3
> # Dumb script to dump (some) of bcache status
> # Copyright 2013 Darrick J. Wong. All rights reserved.
> # This program is licensed under GPLv2.
>
> import os
> import sys
>
> MAX_KEY_LENGTH = 28
>
> def file_to_lines(fname):
> 	try:
> 		with open(fname, "r") as fd:
> 			return fd.readlines()
> 	except:
> 		return []
>
> def file_to_line(fname):
> 	ret = file_to_lines(fname)
> 	if len(ret) > 0:
> 		return ret[0].strip()
> 	return ''
>
> def str_to_bool(x):
> 	if x == '1':
> 		return True
> 	return False
>
> def format_sectors(x):
> 	'''Pretty print a sector count.'''
> 	sectors = int(x)
> 	asectors = abs(sectors)
>
> 	if asectors == 0:
> 		return '0B'
> 	elif asectors < 2048:
> 		return '%.2fKiB' % (sectors / 2)
> 	elif asectors < 2097152:
> 		return '%.2fMiB' % (sectors / 2048)
> 	elif asectors < 2147483648:
> 		return '%.2fGiB' % (sectors / 2097152)
> 	else:
> 		return '%.2fTiB' % (sectors / 2147483648)
>
> def interpret_sectors(x):
> 	'''Interpret a pretty-printed disk size.'''
> 	factors = {
> 		'k': 1 << 10,
> 		'M': 1 << 20,
> 		'G': 1 << 30,
> 		'T': 1 << 40,
> 		'P': 1 << 50,
> 		'E': 1 << 60,
> 		'Z': 1 << 70,
> 		'Y': 1 << 80,
> 	}
>
> 	factor = 1
> 	if x[-1] in factors:
> 		factor = factors[x[-1]]
> 		x = x[:-1]
> 	return int(float(x) * factor / 512)
>
> def pretty_size(x):
> 	return format_sectors(interpret_sectors(x))
>
> def dump_bdev(bdev_path):
> 	'''Dump a backing device stats.'''
> 	global MAX_KEY_LENGTH, devnum_map
> 	attrs = [
> 		('../dev',		'Device',		lambda x: '%s (%s)' % (devnum_map.get(x, '?'), x)),
> 		('../size',		'Size',			format_sectors),
> 		('cache_mode',		'Cache Mode',		None),
> 		('readahead',		'Readahead',		None),
> 		('sequential_cutoff',	'Sequential Cutoff',	pretty_size),
> 		('sequential_merge',	'Merge sequential?',	str_to_bool),
> 		('state',		'State',		None),
> 		('writeback_running',	'Writeback?',		str_to_bool),
> 		('dirty_data',		'Dirty Data',		pretty_size),
> 	]
>
> 	print('--- Backing Device ---')
> 	for (sysfs_name, display_name, conversion_func) in attrs:
> 		val = file_to_line('%s/%s' % (bdev_path, sysfs_name))
> 		if conversion_func is not None:
> 			val = conversion_func(val)
> 		if display_name is None:
> 			display_name = sysfs_name
> 		print('  %-*s%s' % (MAX_KEY_LENGTH - 2, display_name, val))
>
> def dump_cachedev(cachedev_path):
> 	'''Dump a cachding device stats.'''
> 	def fmt_cachesize(val):
> 		return '%s\t(%d%%)' % (format_sectors(val), float(val) / cache_size * 100)
>
> 	global MAX_KEY_LENGTH, devnum_map
> 	attrs = [
> 		('../dev',			'Device',		lambda x: '%s (%s)' % (devnum_map.get(x, '?'), x)),
> 		('../size',			'Size',			format_sectors),
> 		('block_size',			'Block Size',		pretty_size),
> 		('bucket_size',			'Bucket Size',		pretty_size),
> 		('cache_replacement_policy',	'Replacement Policy',	None),
> 		('discard',			'Discard?',		str_to_bool),
> 		('io_errors',			'I/O Errors',		None),
> 		('metadata_written',		'Metadata Written',	pretty_size),
> 		('written',			'Data Written',		pretty_size),
> 		('nbuckets',			'Buckets',		None),
> 		(None,				'Cache Used',		lambda x: fmt_cachesize(used_sectors)),
> 		(None,				'Cache Unused',		lambda x: fmt_cachesize(unused_sectors)),
> 	]
>
> 	stats = get_cache_priority_stats(cachedev_path)
> 	cache_size = int(file_to_line('%s/../size' % cachedev_path))
> 	unused_sectors = float(stats['Unused'][:-1]) * cache_size / 100
> 	used_sectors = cache_size - unused_sectors
>
> 	print('--- Cache Device ---')
> 	for (sysfs_name, display_name, conversion_func) in attrs:
> 		if sysfs_name is not None:
> 			val = file_to_line('%s/%s' % (cachedev_path, sysfs_name))
> 		if conversion_func is not None:
> 			val = conversion_func(val)
> 		if display_name is None:
> 			display_name = sysfs_name
> 		print('  %-*s%s' % (MAX_KEY_LENGTH - 2, display_name, val))
>
> def hits_to_str(hits_str, misses_str):
> 	'''Render a hits/misses ratio as a string.'''
> 	hits = int(hits_str)
> 	misses = int(misses_str)
>
> 	ret = '%d' % hits
> 	if hits + misses != 0:
> 		ret = '%s\t(%.d%%)' % (ret, 100 * hits / (hits + misses))
> 	return ret
>
> def dump_stats(sysfs_path, indent_str, stats):
> 	'''Dump stats on a bcache device.'''
> 	stat_types = [
> 		('five_minute',	'Last 5min'),
> 		('hour',	'Last Hour'),
> 		('day',		'Last Day'),
> 		('total',	'Total'),
> 	]
> 	attrs = ['bypassed', 'cache_bypass_hits', 'cache_bypass_misses', 'cache_hits', 'cache_misses']
> 	display = [
> 		('Hits',		lambda: hits_to_str(stat_data['cache_hits'], stat_data['cache_misses'])),
> 		('Misses',		lambda: stat_data['cache_misses']),
> 		('Bypass Hits',		lambda: hits_to_str(stat_data['cache_bypass_hits'], stat_data['cache_bypass_misses'])),
> 		('Bypass Misses',	lambda: stat_data['cache_bypass_misses']),
> 		('Bypassed',		lambda: pretty_size(stat_data['bypassed'])),
> 	]
>
> 	for (sysfs_name, stat_display_name) in stat_types:
> 		if len(stats) > 0 and sysfs_name not in stats:
> 			continue
> 		stat_data = {}
> 		for attr in attrs:
> 			val = file_to_line('%s/stats_%s/%s' % (sysfs_path, sysfs_name, attr))
> 			stat_data[attr] = val
> 		for (display_name, str_func) in display:
> 			d = '%s%s %s' % (indent_str, stat_display_name, display_name)
> 			print('%-*s%s' % (MAX_KEY_LENGTH, d, str_func()))
>
> def get_cache_priority_stats(cache):
> 	'''Retrieve priority stats from a cache.'''
> 	attrs = {}
>
> 	for line in file_to_lines('%s/priority_stats' % cache):
> 		x = line.split()
> 		key = x[0]
> 		value = x[1]
> 		attrs[key[:-1]] = value
> 	return attrs
>
> def dump_bcache(bcache_sysfs_path, stats, print_subdevices, device):
> 	'''Dump bcache stats'''
> 	global devnum_map
> 	def fmt_cachesize(val):
> 		return '%s\t(%d%%)' % (format_sectors(val), 100.0 * val / cache_sectors)
>
> 	attrs = [
> 		(None,					'Device',		lambda x: '%s (%s)' % (devnum_map.get(device, '?'), device)),
> 		(None,					'UUID',			lambda x: os.path.basename(bcache_sysfs_path)),
> 		('block_size',				'Block Size',		pretty_size),
> 		('bucket_size',				'Bucket Size',		pretty_size),
> 		('congested', 				'Congested?',		str_to_bool),
> 		('congested_read_threshold_us',		'Read Congestion',	lambda x: '%.1fms' % (int(x) / 1000)),
> 		('congested_write_threshold_us',	'Write Congestion',	lambda x: '%.1fms' % (int(x) / 1000)),
> 		(None,					'Total Cache Size',	lambda x: format_sectors(cache_sectors)),
> 		(None,					'Total Cache Used',	lambda x: fmt_cachesize(cache_used_sectors)),
> 		(None,					'Total Cache Unused',	lambda x: fmt_cachesize(cache_unused_sectors)),
> 		('dirty_data',				'Dirty Data',		lambda x: fmt_cachesize(interpret_sectors(x))),
> 		('cache_available_percent',		'Evictable Cache',	lambda x: '%s\t(%s%%)' % (format_sectors(float(x) * cache_sectors / 100), x)),
> 		(None,					'Replacement Policy',	lambda x: replacement_policies.pop() if len(replacement_policies) == 1 else '(Unknown)'),
> 		(None,					'Cache Mode',		lambda x: cache_modes.pop() if len(cache_modes) == 1 else '(Unknown)'),
> 	]
>
> 	# Calculate aggregate data
> 	cache_sectors = 0
> 	cache_unused_sectors = 0
> 	cache_modes = set()
> 	replacement_policies = set()
> 	for obj in os.listdir(bcache_sysfs_path):
> 		if not os.path.isdir('%s/%s' % (bcache_sysfs_path, obj)):
> 			continue
> 		if obj.startswith('cache'):
> 			cache_size = int(file_to_line('%s/%s/../size' % (bcache_sysfs_path, obj)))
> 			cache_sectors += cache_size
> 			cstats = get_cache_priority_stats('%s/%s' % (bcache_sysfs_path, obj))
> 			unused_size = float(cstats['Unused'][:-1]) * cache_size / 100
> 			cache_unused_sectors += unused_size
> 			replacement_policies.add(file_to_line('%s/%s/cache_replacement_policy' % (bcache_sysfs_path, obj)))
> 		elif obj.startswith('bdev'):
> 			cache_modes.add(file_to_line('%s/%s/cache_mode' % (bcache_sysfs_path, obj)))
> 	cache_used_sectors = cache_sectors - cache_unused_sectors
>
> 	# Dump basic stats
> 	print("--- bcache ---")
> 	for (sysfs_name, display_name, conversion_func) in attrs:
> 		if sysfs_name is not None:
> 			val = file_to_line('%s/%s' % (bcache_sysfs_path, sysfs_name))
> 		else:
> 			val = None
> 		if conversion_func is not None:
> 			val = conversion_func(val)
> 		if display_name is None:
> 			display_name = sysfs_name
> 		print('%-*s%s' % (MAX_KEY_LENGTH, display_name, val))
> 	dump_stats(bcache_sysfs_path, '', stats)
>
> 	# Dump sub-device stats
> 	if not print_subdevices:
> 		return
> 	for obj in os.listdir(bcache_sysfs_path):
> 		if not os.path.isdir('%s/%s' % (bcache_sysfs_path, obj)):
> 			continue
> 		if obj.startswith('bdev'):
> 			dump_bdev('%s/%s' % (bcache_sysfs_path, obj))
> 			dump_stats('%s/%s' % (bcache_sysfs_path, obj), '  ', stats)
> 		elif obj.startswith('cache'):
> 			dump_cachedev('%s/%s' % (bcache_sysfs_path, obj))
>
> def map_uuid_to_device():
> 	'''Map bcache UUIDs to device files.'''
> 	global SYSFS_BLOCK_PATH
> 	ret = {}
>
> 	for bdev in os.listdir(SYSFS_BLOCK_PATH):
> 		link = '%s%s/bcache/cache' % (SYSFS_BLOCK_PATH, bdev)
> 		if not os.path.islink(link):
> 			continue
> 		basename = os.path.basename(os.readlink(link))
> 		ret[basename] = file_to_line('%s%s/dev' % (SYSFS_BLOCK_PATH, bdev))
> 	return ret
>
> def map_devnum_to_device():
> 	'''Map device numbers to device files.'''
> 	global DEV_BLOCK_PATH
> 	ret = {}
>
> 	for bdev in os.listdir(DEV_BLOCK_PATH):
> 		ret[bdev] = os.path.realpath('%s%s' % (DEV_BLOCK_PATH, bdev))
>
> 	return ret
>
> def print_help():
> 	print('Usage: %s [OPTIONS]' % sys.argv[0])
> 	print('Options:')
> 	print(' -f	Print the last five minutes of stats.')
> 	print(' -d	Print the last hour of stats.')
> 	print(' -h	Print the last day of stats.')
> 	print(' -t	Print total stats.')
> 	print(' -a	Print all stats.')
> 	print(' -r	Reset stats after printing them.')
> 	print(' -s	Print subdevice status.')
> 	print(' -g	Invoke GC before printing status.')
> 	print('By default, print only the total stats.')
>
> def main():
> 	'''Main function'''
> 	global SYSFS_BCACHE_PATH
> 	global uuid_map, devnum_map
> 	stats = set()
> 	reset_stats = False
> 	print_subdevices = False
> 	run_gc = False
>
> 	for arg in sys.argv[1:]:
> 		if arg == '--help':
> 			print_help()
> 			return 0
> 		elif arg == '-f':
> 			stats.add('five_minute')
> 		elif arg == '-h':
> 			stats.add('hour')
> 		elif arg == '-d':
> 			stats.add('day')
> 		elif arg == '-t':
> 			stats.add('total')
> 		elif arg == '-a':
> 			stats.add('five_minute')
> 			stats.add('hour')
> 			stats.add('day')
> 			stats.add('total')
> 		elif arg == '-r':
> 			reset_stats = True
> 		elif arg == '-s':
> 			print_subdevices = True
> 		elif arg == '-g':
> 			run_gc = True
> 		else:
> 			print_help()
> 			return 0
> 	if len(stats) == 0:
> 		stats.add('total')
>
> 	uuid_map = map_uuid_to_device()
> 	devnum_map = map_devnum_to_device()
> 	for cache in os.listdir(SYSFS_BCACHE_PATH):
> 		if not os.path.isdir('%s%s' % (SYSFS_BCACHE_PATH, cache)):
> 			continue
>
> 		if run_gc:
> 			with open('%s%s/internal/trigger_gc' % (SYSFS_BCACHE_PATH, cache), 'w') as fd:
> 				fd.write('1\n')
>
> 		dump_bcache('%s%s' % (SYSFS_BCACHE_PATH, cache), stats, print_subdevices, uuid_map.get(cache, '?'))
>
> 		if reset_stats:
> 			with open('%s%s/clear_stats' % (SYSFS_BCACHE_PATH, cache), 'w') as fd:
> 				fd.write('1\n')
>
> SYSFS_BCACHE_PATH = '/sys/fs/bcache/'
> SYSFS_BLOCK_PATH = '/sys/block/'
> DEV_BLOCK_PATH = '/dev/block/'
> if __name__ == '__main__':
> 	main()
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-bcache" in
> the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
Hi Darrick,


A very useful script indeed.

We made the following changes:

-    Removed "Device" from under cache-set header
-    Added "bcache Device" to Backing Device section
-    Changed "Device" to "Physical Device" in Backing Device section
-    Removed "Cache Mode" from cache-set header as it set per backing device

We will make some more changes in the coming weeks to implement a brief 
display mode as well as a verbose display mode.

Patch inline:

--- /usr/local/bin/bcache-status        2013-08-16 11:51:55.281095640 +1200
+++ bcache-status       2013-08-16 17:00:28.884020997 +1200
@@ -64,11 +64,16 @@
  def pretty_size(x):
          return format_sectors(interpret_sectors(x))

+def device_path(x):
+       x="/dev/block/%s" % x
+       return os.path.abspath(os.path.join(os.path.dirname(x), 
os.readlink(x)))
+
  def dump_bdev(bdev_path):
          '''Dump a backing device stats.'''
          global MAX_KEY_LENGTH, devnum_map
          attrs = [
-                ('../dev',              'Device', lambda x: '%s (%s)' % 
(devnum_map.get(x, '?'), x)),
+                ('dev/dev',             'bcache Device', device_path),
+               ('../dev',              'Physical Device', lambda x: '%s 
(%s)' % (devnum_map.get(x, '?'), x)),
                  ('../size',             'Size', format_sectors),
                  ('cache_mode',          'Cache Mode', None),
                  ('readahead',           'Readahead', None),
@@ -180,7 +185,7 @@
                  return '%s\t(%d%%)' % (format_sectors(val), 100.0 * 
val / cache_sectors)

          attrs = [
-                (None, 'Device',               lambda x: '%s (%s)' % 
(devnum_map.get(device, '?'), device)),
+                #(None, 'Device',               lambda x: '%s (%s)' % 
(devnum_map.get(device, '?'), device)),
                  (None, 'UUID',                 lambda x: 
os.path.basename(bcache_sysfs_path)),
                  ('block_size',                          'Block 
Size',           pretty_size),
                  ('bucket_size',                         'Bucket 
Size',          pretty_size),
@@ -268,8 +273,8 @@
          print('Usage: %s [OPTIONS]' % sys.argv[0])
          print('Options:')
          print(' -f      Print the last five minutes of stats.')
-        print(' -d      Print the last hour of stats.')
-        print(' -h      Print the last day of stats.')
+        print(' -h      Print the last hour of stats.')
+        print(' -d      Print the last day of stats.')
          print(' -t      Print total stats.')
          print(' -a      Print all stats.')
          print(' -r      Reset stats after printing them.')


Regards,







Andrew

  parent reply	other threads:[~2013-08-16  5:09 UTC|newest]

Thread overview: 14+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2013-08-15 23:35 [RFC] bcache-status Darrick J. Wong
     [not found] ` <20130815233508.GB6949-yuuUpGxbzT9UbpRmUfBrXUB+6BGkLq7r@public.gmane.org>
2013-08-16  5:09   ` Andrew Thrift [this message]
     [not found]     ` <520DB3EC.70001-3e6jenk95VYpDvLZ8AWkcaVXKuFTiq87@public.gmane.org>
2013-08-16 21:35       ` Darrick J. Wong
2013-08-19 11:48   ` Damien Churchill
  -- strict thread matches above, loose matches on Subject: below --
2013-08-25 19:07 bcache-tools package for Fedora Rolf Fokkens
     [not found] ` <521A55D4.20908-6w2rdlBuEQTpMFipWq+H6g@public.gmane.org>
2013-08-25 20:39   ` [RFC] bcache-status Rolf Fokkens
     [not found]     ` <521A6B92.60001-6w2rdlBuEQTpMFipWq+H6g@public.gmane.org>
2013-08-26 16:30       ` Darrick J. Wong
     [not found]         ` <20130826163055.GB4780-yuuUpGxbzT9UbpRmUfBrXUB+6BGkLq7r@public.gmane.org>
2013-08-26 20:36           ` Rolf Fokkens
     [not found]             ` <521BBC3B.9010003-6w2rdlBuEQTpMFipWq+H6g@public.gmane.org>
2013-08-29  0:46               ` Darrick J. Wong
     [not found]                 ` <20130829004646.GA3099-yuuUpGxbzT9UbpRmUfBrXUB+6BGkLq7r@public.gmane.org>
2013-09-03 20:46                   ` Rolf Fokkens
     [not found]                     ` <52264AAD.3050401-6w2rdlBuEQTpMFipWq+H6g@public.gmane.org>
2013-09-04 20:26                       ` Darrick J. Wong
     [not found]                         ` <20130904202600.GA4554-yuuUpGxbzT9UbpRmUfBrXUB+6BGkLq7r@public.gmane.org>
2013-09-04 20:52                           ` Rolf Fokkens
     [not found]                             ` <52279DA5.7050505-6w2rdlBuEQTpMFipWq+H6g@public.gmane.org>
2013-09-05  6:14                               ` Stefan Priebe - Profihost AG
2013-09-09 22:45                               ` Darrick J. Wong
2013-08-26 23:01           ` Rolf Fokkens

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=520DB3EC.70001@networklabs.co.nz \
    --to=andrew-3e6jenk95vypdvlz8awkcavxkuftiq87@public.gmane.org \
    --cc=darrick.wong-QHcLZuEGTsvQT0dZR+AlfA@public.gmane.org \
    --cc=linux-bcache-u79uwXL29TY76Z2rM5mHXA@public.gmane.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox