| // SPDX-License-Identifier: GPL-2.0 | 
 | /* | 
 |  * bcache stats code | 
 |  * | 
 |  * Copyright 2012 Google, Inc. | 
 |  */ | 
 |  | 
 | #include "bcache.h" | 
 | #include "stats.h" | 
 | #include "btree.h" | 
 | #include "sysfs.h" | 
 |  | 
 | /* | 
 |  * We keep absolute totals of various statistics, and addionally a set of three | 
 |  * rolling averages. | 
 |  * | 
 |  * Every so often, a timer goes off and rescales the rolling averages. | 
 |  * accounting_rescale[] is how many times the timer has to go off before we | 
 |  * rescale each set of numbers; that gets us half lives of 5 minutes, one hour, | 
 |  * and one day. | 
 |  * | 
 |  * accounting_delay is how often the timer goes off - 22 times in 5 minutes, | 
 |  * and accounting_weight is what we use to rescale: | 
 |  * | 
 |  * pow(31 / 32, 22) ~= 1/2 | 
 |  * | 
 |  * So that we don't have to increment each set of numbers every time we (say) | 
 |  * get a cache hit, we increment a single atomic_t in acc->collector, and when | 
 |  * the rescale function runs it resets the atomic counter to 0 and adds its | 
 |  * old value to each of the exported numbers. | 
 |  * | 
 |  * To reduce rounding error, the numbers in struct cache_stats are all | 
 |  * stored left shifted by 16, and scaled back in the sysfs show() function. | 
 |  */ | 
 |  | 
 | static const unsigned int DAY_RESCALE		= 288; | 
 | static const unsigned int HOUR_RESCALE		= 12; | 
 | static const unsigned int FIVE_MINUTE_RESCALE	= 1; | 
 | static const unsigned int accounting_delay	= (HZ * 300) / 22; | 
 | static const unsigned int accounting_weight	= 32; | 
 |  | 
 | /* sysfs reading/writing */ | 
 |  | 
 | read_attribute(cache_hits); | 
 | read_attribute(cache_misses); | 
 | read_attribute(cache_bypass_hits); | 
 | read_attribute(cache_bypass_misses); | 
 | read_attribute(cache_hit_ratio); | 
 | read_attribute(cache_readaheads); | 
 | read_attribute(cache_miss_collisions); | 
 | read_attribute(bypassed); | 
 |  | 
 | SHOW(bch_stats) | 
 | { | 
 | 	struct cache_stats *s = | 
 | 		container_of(kobj, struct cache_stats, kobj); | 
 | #define var(stat)		(s->stat >> 16) | 
 | 	var_print(cache_hits); | 
 | 	var_print(cache_misses); | 
 | 	var_print(cache_bypass_hits); | 
 | 	var_print(cache_bypass_misses); | 
 |  | 
 | 	sysfs_print(cache_hit_ratio, | 
 | 		    DIV_SAFE(var(cache_hits) * 100, | 
 | 			     var(cache_hits) + var(cache_misses))); | 
 |  | 
 | 	var_print(cache_readaheads); | 
 | 	var_print(cache_miss_collisions); | 
 | 	sysfs_hprint(bypassed,	var(sectors_bypassed) << 9); | 
 | #undef var | 
 | 	return 0; | 
 | } | 
 |  | 
 | STORE(bch_stats) | 
 | { | 
 | 	return size; | 
 | } | 
 |  | 
 | static void bch_stats_release(struct kobject *k) | 
 | { | 
 | } | 
 |  | 
 | static struct attribute *bch_stats_files[] = { | 
 | 	&sysfs_cache_hits, | 
 | 	&sysfs_cache_misses, | 
 | 	&sysfs_cache_bypass_hits, | 
 | 	&sysfs_cache_bypass_misses, | 
 | 	&sysfs_cache_hit_ratio, | 
 | 	&sysfs_cache_readaheads, | 
 | 	&sysfs_cache_miss_collisions, | 
 | 	&sysfs_bypassed, | 
 | 	NULL | 
 | }; | 
 | static KTYPE(bch_stats); | 
 |  | 
 | int bch_cache_accounting_add_kobjs(struct cache_accounting *acc, | 
 | 				   struct kobject *parent) | 
 | { | 
 | 	int ret = kobject_add(&acc->total.kobj, parent, | 
 | 			      "stats_total"); | 
 | 	ret = ret ?: kobject_add(&acc->five_minute.kobj, parent, | 
 | 				 "stats_five_minute"); | 
 | 	ret = ret ?: kobject_add(&acc->hour.kobj, parent, | 
 | 				 "stats_hour"); | 
 | 	ret = ret ?: kobject_add(&acc->day.kobj, parent, | 
 | 				 "stats_day"); | 
 | 	return ret; | 
 | } | 
 |  | 
 | void bch_cache_accounting_clear(struct cache_accounting *acc) | 
 | { | 
 | 	memset(&acc->total.cache_hits, | 
 | 	       0, | 
 | 	       sizeof(unsigned long) * 7); | 
 | } | 
 |  | 
 | void bch_cache_accounting_destroy(struct cache_accounting *acc) | 
 | { | 
 | 	kobject_put(&acc->total.kobj); | 
 | 	kobject_put(&acc->five_minute.kobj); | 
 | 	kobject_put(&acc->hour.kobj); | 
 | 	kobject_put(&acc->day.kobj); | 
 |  | 
 | 	atomic_set(&acc->closing, 1); | 
 | 	if (del_timer_sync(&acc->timer)) | 
 | 		closure_return(&acc->cl); | 
 | } | 
 |  | 
 | /* EWMA scaling */ | 
 |  | 
 | static void scale_stat(unsigned long *stat) | 
 | { | 
 | 	*stat =  ewma_add(*stat, 0, accounting_weight, 0); | 
 | } | 
 |  | 
 | static void scale_stats(struct cache_stats *stats, unsigned long rescale_at) | 
 | { | 
 | 	if (++stats->rescale == rescale_at) { | 
 | 		stats->rescale = 0; | 
 | 		scale_stat(&stats->cache_hits); | 
 | 		scale_stat(&stats->cache_misses); | 
 | 		scale_stat(&stats->cache_bypass_hits); | 
 | 		scale_stat(&stats->cache_bypass_misses); | 
 | 		scale_stat(&stats->cache_readaheads); | 
 | 		scale_stat(&stats->cache_miss_collisions); | 
 | 		scale_stat(&stats->sectors_bypassed); | 
 | 	} | 
 | } | 
 |  | 
 | static void scale_accounting(struct timer_list *t) | 
 | { | 
 | 	struct cache_accounting *acc = from_timer(acc, t, timer); | 
 |  | 
 | #define move_stat(name) do {						\ | 
 | 	unsigned int t = atomic_xchg(&acc->collector.name, 0);		\ | 
 | 	t <<= 16;							\ | 
 | 	acc->five_minute.name += t;					\ | 
 | 	acc->hour.name += t;						\ | 
 | 	acc->day.name += t;						\ | 
 | 	acc->total.name += t;						\ | 
 | } while (0) | 
 |  | 
 | 	move_stat(cache_hits); | 
 | 	move_stat(cache_misses); | 
 | 	move_stat(cache_bypass_hits); | 
 | 	move_stat(cache_bypass_misses); | 
 | 	move_stat(cache_readaheads); | 
 | 	move_stat(cache_miss_collisions); | 
 | 	move_stat(sectors_bypassed); | 
 |  | 
 | 	scale_stats(&acc->total, 0); | 
 | 	scale_stats(&acc->day, DAY_RESCALE); | 
 | 	scale_stats(&acc->hour, HOUR_RESCALE); | 
 | 	scale_stats(&acc->five_minute, FIVE_MINUTE_RESCALE); | 
 |  | 
 | 	acc->timer.expires += accounting_delay; | 
 |  | 
 | 	if (!atomic_read(&acc->closing)) | 
 | 		add_timer(&acc->timer); | 
 | 	else | 
 | 		closure_return(&acc->cl); | 
 | } | 
 |  | 
 | static void mark_cache_stats(struct cache_stat_collector *stats, | 
 | 			     bool hit, bool bypass) | 
 | { | 
 | 	if (!bypass) | 
 | 		if (hit) | 
 | 			atomic_inc(&stats->cache_hits); | 
 | 		else | 
 | 			atomic_inc(&stats->cache_misses); | 
 | 	else | 
 | 		if (hit) | 
 | 			atomic_inc(&stats->cache_bypass_hits); | 
 | 		else | 
 | 			atomic_inc(&stats->cache_bypass_misses); | 
 | } | 
 |  | 
 | void bch_mark_cache_accounting(struct cache_set *c, struct bcache_device *d, | 
 | 			       bool hit, bool bypass) | 
 | { | 
 | 	struct cached_dev *dc = container_of(d, struct cached_dev, disk); | 
 |  | 
 | 	mark_cache_stats(&dc->accounting.collector, hit, bypass); | 
 | 	mark_cache_stats(&c->accounting.collector, hit, bypass); | 
 | } | 
 |  | 
 | void bch_mark_cache_readahead(struct cache_set *c, struct bcache_device *d) | 
 | { | 
 | 	struct cached_dev *dc = container_of(d, struct cached_dev, disk); | 
 |  | 
 | 	atomic_inc(&dc->accounting.collector.cache_readaheads); | 
 | 	atomic_inc(&c->accounting.collector.cache_readaheads); | 
 | } | 
 |  | 
 | void bch_mark_cache_miss_collision(struct cache_set *c, struct bcache_device *d) | 
 | { | 
 | 	struct cached_dev *dc = container_of(d, struct cached_dev, disk); | 
 |  | 
 | 	atomic_inc(&dc->accounting.collector.cache_miss_collisions); | 
 | 	atomic_inc(&c->accounting.collector.cache_miss_collisions); | 
 | } | 
 |  | 
 | void bch_mark_sectors_bypassed(struct cache_set *c, struct cached_dev *dc, | 
 | 			       int sectors) | 
 | { | 
 | 	atomic_add(sectors, &dc->accounting.collector.sectors_bypassed); | 
 | 	atomic_add(sectors, &c->accounting.collector.sectors_bypassed); | 
 | } | 
 |  | 
 | void bch_cache_accounting_init(struct cache_accounting *acc, | 
 | 			       struct closure *parent) | 
 | { | 
 | 	kobject_init(&acc->total.kobj,		&bch_stats_ktype); | 
 | 	kobject_init(&acc->five_minute.kobj,	&bch_stats_ktype); | 
 | 	kobject_init(&acc->hour.kobj,		&bch_stats_ktype); | 
 | 	kobject_init(&acc->day.kobj,		&bch_stats_ktype); | 
 |  | 
 | 	closure_init(&acc->cl, parent); | 
 | 	timer_setup(&acc->timer, scale_accounting, 0); | 
 | 	acc->timer.expires	= jiffies + accounting_delay; | 
 | 	add_timer(&acc->timer); | 
 | } |