File 0016-bcache-tools-add-bcache-status.patch of Package bcache-tools.17817

From 10824170800268e91508e2edc6ed745f40370f0a Mon Sep 17 00:00:00 2001
From: Coly Li <colyli@suse.de>
Date: Wed, 2 Sep 2020 19:27:08 +0800
Subject: [PATCH 16/17] bcache-tools: add bcache-status
Git-commit: 10824170800268e91508e2edc6ed745f40370f0a
Patch-mainline: bcache-tools-1.1
References: jsc#SLE-9807

People request to include bcache-status into bcache-tools package. This
patch picks bcache-status script from github page of the orginal author
Darrick J. Wong,
  https://github.com/djwong/bcache-tools/blob/master/bcache-status

Thanks to Darrick for writing the great bcache-status, and I will keep
this script being updated from Darrick's repo time to time.

Signed-off-by: Coly Li <colyli@suse.de>
Cc: Darrick J. Wong <darrick.wong@oracle.com>
---
 bcache-status | 352 ++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 352 insertions(+)
 create mode 100755 bcache-status

diff --git a/bcache-status b/bcache-status
new file mode 100755
index 0000000..ac5a22f
--- /dev/null
+++ b/bcache-status
@@ -0,0 +1,352 @@
+#!/usr/bin/env python
+#
+# Dumb script to dump (some) of bcache status
+# Copyright 2014 Darrick J. Wong. All rights reserved.
+#
+# This file is part of Bcache.  Bcache is free software: you can
+# redistribute it and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation, version 2.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc., 51
+# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+import os
+import sys
+import argparse
+
+MAX_KEY_LENGTH		= 28
+DEV_BLOCK_PATH		= '/dev/block/'
+SYSFS_BCACHE_PATH	= '/sys/fs/bcache/'
+SYSFS_BLOCK_PATH	= '/sys/block/'
+
+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 ret:
+		return ret[0].strip()
+	return ''
+
+def str_to_bool(x):
+	return x == '1'
+
+def format_sectors(x):
+	'''Pretty print a sector count.'''
+	sectors = float(x)
+	asectors = abs(sectors)
+
+	if asectors < 2:
+		return '%d B' % (sectors * 512)
+	elif asectors < 2048:
+		return '%.2f KiB' % (sectors / 2)
+	elif asectors < 2097152:
+		return '%.1f MiB' % (sectors / 2048)
+	elif asectors < 2147483648:
+		return '%.0f GiB' % (sectors / 2097152)
+	else:
+		return '%.0f TiB' % (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 device_path(x):
+	if not os.path.isdir(DEV_BLOCK_PATH):
+		return '?'
+	x = '%s/%s' % (DEV_BLOCK_PATH, x)
+	return os.path.abspath(os.path.join(os.path.dirname(x), os.readlink(x)))
+
+def str_device_path(x):
+	return '%s (%s)' % (device_path(x), x)
+
+def dump_bdev(bdev_path):
+	'''Dump a backing device stats.'''
+	global MAX_KEY_LENGTH
+	attrs = [
+		('../dev',		'Device File',		str_device_path),
+		('dev/dev',		'bcache Device File',	str_device_path),
+		('../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),
+		('writeback_rate',	'Writeback Rate',	lambda x: '%s/s' % x),
+		('writeback_percent',	'Dirty Target',		lambda x: '%s%%' % x),
+	]
+
+	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(%.0f%%)' % (format_sectors(val), float(val) / cache_size * 100)
+
+	global MAX_KEY_LENGTH
+	attrs = [
+		('../dev',			'Device File',		str_device_path),
+		('../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'''
+	def fmt_cachesize(val):
+		return '%s\t(%.0f%%)' % (format_sectors(val), 100.0 * val / cache_sectors)
+
+	attrs = [
+		(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))),		# disappeared in 3.13?
+		('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 '(Various)'),
+		(None,					'Cache Mode',		lambda x: cache_modes.pop() if len(cache_modes) == 1 else '(Various)'),
+	]
+
+	# 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 = {}
+
+	if not os.path.isdir(SYSFS_BLOCK_PATH):
+		return 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 main():
+	'''Main function'''
+	global SYSFS_BCACHE_PATH
+	global uuid_map
+	stats = set()
+	reset_stats = False
+	print_subdevices = False
+	run_gc = False
+
+	parser = argparse.ArgumentParser(add_help=False)
+	parser.add_argument('--help',			help='Show this help message and exit',		action='store_true')
+	parser.add_argument('-f', '--five-minute',	help='Print the last five minutes of stats.',	action='store_true')
+	parser.add_argument('-h', '--hour',		help='Print the last hour of stats.',		action='store_true')
+	parser.add_argument('-d', '--day',		help='Print the last day of stats.',		action='store_true')
+	parser.add_argument('-t', '--total',		help='Print total stats.',			action='store_true')
+	parser.add_argument('-a', '--all',		help='Print all stats.',			action='store_true')
+	parser.add_argument('-r', '--reset-stats',	help='Reset stats after printing them.',	action='store_true')
+	parser.add_argument('-s', '--sub-status',	help='Print subdevice status.',			action='store_true')
+	parser.add_argument('-g', '--gc',		help='Invoke GC before printing status.',	action='store_true')
+	args = parser.parse_args()
+
+	if args.help:
+		parser.print_help()
+		return 0
+
+	if args.five_minute:
+		stats.add('five_minute')
+	if args.hour:
+		stats.add('hour')
+	if args.day:
+		stats.add('day')
+	if args.total:
+		stats.add('total')
+	if args.all:
+		stats.add('five_minute')
+		stats.add('hour')
+		stats.add('day')
+		stats.add('total')
+	if args.reset_stats:
+		reset_stats = True
+	if args.sub_status:
+		print_subdevices = True
+	if args.gc:
+		run_gc = True
+
+	if not stats:
+		stats.add('total')
+
+	uuid_map = map_uuid_to_device()
+	if not os.path.isdir(SYSFS_BCACHE_PATH):
+		print('bcache is not loaded.')
+		return
+	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')
+
+if __name__ == '__main__':
+	main()
-- 
2.26.2
openSUSE Build Service is sponsored by