160 lines
4.3 KiB
C
160 lines
4.3 KiB
C
/* SPDX-License-Identifier: GPL-2.0 */
|
|
/*
|
|
* bch2_time_stats - collect statistics on events that have a duration, with nicely
|
|
* formatted textual output on demand
|
|
*
|
|
* - percpu buffering of event collection: cheap enough to shotgun
|
|
* everywhere without worrying about overhead
|
|
*
|
|
* tracks:
|
|
* - number of events
|
|
* - maximum event duration ever seen
|
|
* - sum of all event durations
|
|
* - average event duration, standard and weighted
|
|
* - standard deviation of event durations, standard and weighted
|
|
* and analagous statistics for the frequency of events
|
|
*
|
|
* We provide both mean and weighted mean (exponentially weighted), and standard
|
|
* deviation and weighted standard deviation, to give an efficient-to-compute
|
|
* view of current behaviour versus. average behaviour - "did this event source
|
|
* just become wonky, or is this typical?".
|
|
*
|
|
* Particularly useful for tracking down latency issues.
|
|
*/
|
|
#ifndef _BCACHEFS_TIME_STATS_H
|
|
#define _BCACHEFS_TIME_STATS_H
|
|
|
|
#include <linux/sched/clock.h>
|
|
#include <linux/spinlock_types.h>
|
|
#include <linux/string.h>
|
|
|
|
#include "mean_and_variance.h"
|
|
|
|
struct time_unit {
|
|
const char *name;
|
|
u64 nsecs;
|
|
};
|
|
|
|
/*
|
|
* given a nanosecond value, pick the preferred time units for printing:
|
|
*/
|
|
const struct time_unit *bch2_pick_time_units(u64 ns);
|
|
|
|
/*
|
|
* quantiles - do not use:
|
|
*
|
|
* Only enabled if bch2_time_stats->quantiles_enabled has been manually set - don't
|
|
* use in new code.
|
|
*/
|
|
|
|
#define NR_QUANTILES 15
|
|
#define QUANTILE_IDX(i) inorder_to_eytzinger0(i, NR_QUANTILES)
|
|
#define QUANTILE_FIRST eytzinger0_first(NR_QUANTILES)
|
|
#define QUANTILE_LAST eytzinger0_last(NR_QUANTILES)
|
|
|
|
struct quantiles {
|
|
struct quantile_entry {
|
|
u64 m;
|
|
u64 step;
|
|
} entries[NR_QUANTILES];
|
|
};
|
|
|
|
struct time_stat_buffer {
|
|
unsigned nr;
|
|
struct time_stat_buffer_entry {
|
|
u64 start;
|
|
u64 end;
|
|
} entries[31];
|
|
};
|
|
|
|
struct bch2_time_stats {
|
|
spinlock_t lock;
|
|
bool have_quantiles;
|
|
/* all fields are in nanoseconds */
|
|
u64 min_duration;
|
|
u64 max_duration;
|
|
u64 total_duration;
|
|
u64 max_freq;
|
|
u64 min_freq;
|
|
u64 last_event;
|
|
u64 last_event_start;
|
|
|
|
struct mean_and_variance duration_stats;
|
|
struct mean_and_variance freq_stats;
|
|
|
|
/* default weight for weighted mean and variance calculations */
|
|
#define TIME_STATS_MV_WEIGHT 8
|
|
|
|
struct mean_and_variance_weighted duration_stats_weighted;
|
|
struct mean_and_variance_weighted freq_stats_weighted;
|
|
struct time_stat_buffer __percpu *buffer;
|
|
};
|
|
|
|
struct bch2_time_stats_quantiles {
|
|
struct bch2_time_stats stats;
|
|
struct quantiles quantiles;
|
|
};
|
|
|
|
static inline struct quantiles *time_stats_to_quantiles(struct bch2_time_stats *stats)
|
|
{
|
|
return stats->have_quantiles
|
|
? &container_of(stats, struct bch2_time_stats_quantiles, stats)->quantiles
|
|
: NULL;
|
|
}
|
|
|
|
void __bch2_time_stats_clear_buffer(struct bch2_time_stats *, struct time_stat_buffer *);
|
|
void __bch2_time_stats_update(struct bch2_time_stats *stats, u64, u64);
|
|
|
|
/**
|
|
* time_stats_update - collect a new event being tracked
|
|
*
|
|
* @stats - bch2_time_stats to update
|
|
* @start - start time of event, recorded with local_clock()
|
|
*
|
|
* The end duration of the event will be the current time
|
|
*/
|
|
static inline void bch2_time_stats_update(struct bch2_time_stats *stats, u64 start)
|
|
{
|
|
__bch2_time_stats_update(stats, start, local_clock());
|
|
}
|
|
|
|
/**
|
|
* track_event_change - track state change events
|
|
*
|
|
* @stats - bch2_time_stats to update
|
|
* @v - new state, true or false
|
|
*
|
|
* Use this when tracking time stats for state changes, i.e. resource X becoming
|
|
* blocked/unblocked.
|
|
*/
|
|
static inline bool track_event_change(struct bch2_time_stats *stats, bool v)
|
|
{
|
|
if (v != !!stats->last_event_start) {
|
|
if (!v) {
|
|
bch2_time_stats_update(stats, stats->last_event_start);
|
|
stats->last_event_start = 0;
|
|
} else {
|
|
stats->last_event_start = local_clock() ?: 1;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void bch2_time_stats_exit(struct bch2_time_stats *);
|
|
void bch2_time_stats_init(struct bch2_time_stats *);
|
|
|
|
static inline void bch2_time_stats_quantiles_exit(struct bch2_time_stats_quantiles *statq)
|
|
{
|
|
bch2_time_stats_exit(&statq->stats);
|
|
}
|
|
static inline void bch2_time_stats_quantiles_init(struct bch2_time_stats_quantiles *statq)
|
|
{
|
|
bch2_time_stats_init(&statq->stats);
|
|
statq->stats.have_quantiles = true;
|
|
memset(&statq->quantiles, 0, sizeof(statq->quantiles));
|
|
}
|
|
|
|
#endif /* _BCACHEFS_TIME_STATS_H */
|