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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox