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
next prev 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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.