mirror of
https://github.com/torvalds/linux.git
synced 2026-09-24 06:24:02 +02:00
s390/dasd: Derive adaptive ESE fulltrack heuristic from ft_bias
Turn the middle of the ft_bias range (1..99) into an adaptive heuristic that switches between fulltrack write (ft1) and plain write ft0 depending on how sparse the device still is. A sparse device benefits from fulltrack writes (it avoids the format/retry cycle); once enough tracks are formatted the per-write overhead of ft1 outweighs that. An state machine measures the NRF rate in short ft0 probe windows and flips back to ft1 when it is high (FT1_ACTIVE -> PROBING -> FT0_STABLE, with a backing-off reprobe interval). The four parameters are derived from ft_bias by linear interpolation, anchored so ft_bias == 50 derives the following values: ese_heu_start_interval - 2000 - IOs in ft1, before first ft0-Probe starts ese_heu_probe_window - 100 - IOs in probe window ese_heu_nrf_high - 10 ‰ (= 1 %) - TRACK_FORMAT rate that leads to ft1 ese_heu_max_interval - 500000 - Backoff-Cap: max. IOs between two probes Higher is more eager to use ft1, and 0/100 skips the heuristic. The NRF counter is bumped in dasd_eckd_ese_format() for both the classic NRF sense and the HPF INV_TRACK_FORMAT equivalent. The state machine resets to ft1 on check_characteristics, full format, and release-space. A read-only ese_heuristic_state sysfs attribute exposes the current mode. Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com> Signed-off-by: Stefan Haberland <sth@linux.ibm.com> Link: https://patch.msgid.link/20260805111612.1285190-15-sth@linux.ibm.com Signed-off-by: Jens Axboe <axboe@kernel.dk>
This commit is contained in:
parent
42849375e9
commit
4e304b2e56
|
|
@ -1659,8 +1659,14 @@ static ssize_t full_track_bias_store(struct device *dev,
|
|||
if (IS_ERR(device))
|
||||
return -ENODEV;
|
||||
|
||||
/*
|
||||
* ft_bias is the tuning target; fulltrack is a best-effort mode hint
|
||||
* that the per-IO heuristic also updates locklessly. A racing writer can
|
||||
* at most leave a transient mismatch that self-corrects on the next IO,
|
||||
* never corruption, so the update is left unlocked.
|
||||
*/
|
||||
device->ft_bias = val;
|
||||
device->fulltrack = val ? 1 : 0;
|
||||
dasd_ft_bias_apply(device);
|
||||
|
||||
dasd_put_device(device);
|
||||
return count;
|
||||
|
|
@ -1668,6 +1674,44 @@ static ssize_t full_track_bias_store(struct device *dev,
|
|||
|
||||
static DEVICE_ATTR_RW(full_track_bias);
|
||||
|
||||
static const char * const dasd_ese_heu_state_names[] = {
|
||||
[DASD_ESE_HEU_FT1_ACTIVE] = "fulltrack active",
|
||||
[DASD_ESE_HEU_PROBING] = "probing",
|
||||
[DASD_ESE_HEU_FT0_STABLE] = "fulltrack inactive",
|
||||
};
|
||||
|
||||
/* read-only: current full-track mode / adaptive FSM state, for observability */
|
||||
static ssize_t
|
||||
ese_heuristic_state_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct dasd_device *device;
|
||||
unsigned int state;
|
||||
int len;
|
||||
|
||||
device = dasd_device_from_cdev(to_ccwdev(dev));
|
||||
if (IS_ERR(device))
|
||||
return -ENODEV;
|
||||
if (device->ft_bias == 0) {
|
||||
len = sysfs_emit(buf, "fulltrack deactivated\n");
|
||||
} else if (device->ft_bias >= DASD_FT_BIAS_MAX) {
|
||||
len = sysfs_emit(buf, "fulltrack permanent active\n");
|
||||
} else if (!dasd_ese_adaptive(device)) {
|
||||
/* adaptive range but not ESE: the heuristic does not run */
|
||||
len = sysfs_emit(buf, "fulltrack deactivated\n");
|
||||
} else {
|
||||
state = device->ese_probe_state;
|
||||
if (state < ARRAY_SIZE(dasd_ese_heu_state_names))
|
||||
len = sysfs_emit(buf, "%s\n", dasd_ese_heu_state_names[state]);
|
||||
else
|
||||
len = sysfs_emit(buf, "unknown\n");
|
||||
}
|
||||
dasd_put_device(device);
|
||||
return len;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR_RO(ese_heuristic_state);
|
||||
|
||||
static ssize_t
|
||||
dasd_retries_show(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
|
|
@ -2464,6 +2508,7 @@ static struct attribute * dasd_attrs[] = {
|
|||
&dev_attr_failfast.attr,
|
||||
&dev_attr_expires.attr,
|
||||
&dev_attr_full_track_bias.attr,
|
||||
&dev_attr_ese_heuristic_state.attr,
|
||||
&dev_attr_retries.attr,
|
||||
&dev_attr_timeout.attr,
|
||||
&dev_attr_reservation_policy.attr,
|
||||
|
|
|
|||
|
|
@ -2160,11 +2160,6 @@ dasd_eckd_check_characteristics(struct dasd_device *device)
|
|||
device->path_interval = DASD_ECKD_PATH_INTERVAL;
|
||||
device->aq_timeouts = DASD_RETRIES_MAX;
|
||||
|
||||
/* default ESE fulltrack write aggressiveness from the module parameter */
|
||||
device->ft_bias = min_t(unsigned int, full_track_bias, DASD_FT_BIAS_MAX);
|
||||
/* only the "always" endpoint forces fulltrack unconditionally here */
|
||||
device->fulltrack = (device->ft_bias >= DASD_FT_BIAS_MAX) ? 1 : 0;
|
||||
|
||||
if (private->conf.gneq) {
|
||||
value = 1;
|
||||
for (i = 0; i < private->conf.gneq->timeout.value; i++)
|
||||
|
|
@ -2219,6 +2214,13 @@ dasd_eckd_check_characteristics(struct dasd_device *device)
|
|||
/* Read Volume Information */
|
||||
dasd_eckd_read_vol_info(device);
|
||||
|
||||
/*
|
||||
* is_ese() now reflects the hardware ESE state, so derive the default
|
||||
* fulltrack write bias from the module parameter.
|
||||
*/
|
||||
device->ft_bias = min_t(unsigned int, full_track_bias, DASD_FT_BIAS_MAX);
|
||||
dasd_ft_bias_apply(device);
|
||||
|
||||
/* Read Extent Pool Information */
|
||||
dasd_eckd_read_ext_pool_info(device);
|
||||
|
||||
|
|
@ -3171,6 +3173,13 @@ static int dasd_eckd_format_process_data(struct dasd_device *base,
|
|||
static int dasd_eckd_format_device(struct dasd_device *base,
|
||||
struct format_data_t *fdata, int enable_pav)
|
||||
{
|
||||
/*
|
||||
* A full format (start_unit == 0) returns the device to a fully sparse
|
||||
* state, so restart the heuristic from ft1 without an offline cycle.
|
||||
*/
|
||||
if (fdata->start_unit == 0)
|
||||
dasd_ft_bias_apply(base);
|
||||
|
||||
return dasd_eckd_format_process_data(base, fdata, enable_pav, 0, NULL,
|
||||
0, NULL);
|
||||
}
|
||||
|
|
@ -3230,6 +3239,69 @@ static void clear_format_track(struct dasd_format_entry *format,
|
|||
spin_unlock_irqrestore(&block->format_lock, flags);
|
||||
}
|
||||
|
||||
/*
|
||||
* Adaptive ft_bias heuristic, called once per IO from dasd_eckd_build_cp().
|
||||
* Probes the device formatting state by briefly switching to ft0 and measuring
|
||||
* the NRF rate; parameters are derived from ft_bias.
|
||||
*/
|
||||
static void dasd_ese_heuristic_tick(struct dasd_device *basedev)
|
||||
{
|
||||
int ios, nrf, rate;
|
||||
|
||||
if (atomic_inc_return(&basedev->ese_io_cnt) < (int)basedev->ese_probe_interval)
|
||||
return;
|
||||
|
||||
/*
|
||||
* One wins the race to evaluate, the rest see ios == 0 after the
|
||||
* xchg and return early, preventing redundant state transitions.
|
||||
*/
|
||||
ios = atomic_xchg(&basedev->ese_io_cnt, 0);
|
||||
if (ios <= 0)
|
||||
return;
|
||||
|
||||
switch (basedev->ese_probe_state) {
|
||||
case DASD_ESE_HEU_FT1_ACTIVE:
|
||||
/* Start ft0 probe window, reset NRF counter for clean measurement */
|
||||
basedev->fulltrack = 0;
|
||||
basedev->ese_probe_state = DASD_ESE_HEU_PROBING;
|
||||
basedev->ese_probe_interval = basedev->ese_heu_probe_window;
|
||||
atomic_set(&basedev->ese_nrf_window, 0);
|
||||
break;
|
||||
|
||||
case DASD_ESE_HEU_PROBING:
|
||||
case DASD_ESE_HEU_FT0_STABLE:
|
||||
nrf = atomic_xchg(&basedev->ese_nrf_window, 0);
|
||||
rate = (int)((u64)nrf * 1000 / ios);
|
||||
if (rate > (int)basedev->ese_heu_nrf_high) {
|
||||
/* NRF rate high: device still sparse, ft1 is better */
|
||||
basedev->fulltrack = 1;
|
||||
basedev->ese_probe_state = DASD_ESE_HEU_FT1_ACTIVE;
|
||||
basedev->ese_probe_interval = basedev->ese_heu_start_interval;
|
||||
} else if (basedev->ese_probe_state == DASD_ESE_HEU_PROBING) {
|
||||
/*
|
||||
* NRF rate low: device mostly formatted, ft0 is faster.
|
||||
* Re-probe frequently at first, then back off below.
|
||||
*/
|
||||
basedev->fulltrack = 0;
|
||||
basedev->ese_probe_state = DASD_ESE_HEU_FT0_STABLE;
|
||||
basedev->ese_probe_interval = basedev->ese_heu_probe_window;
|
||||
} else {
|
||||
/*
|
||||
* Still stable in ft0: re-assert plain-write mode so a
|
||||
* fulltrack value left behind by a racing sysfs write
|
||||
* self-corrects, and back off the re-probe interval
|
||||
* (double it, capped at max_interval) so a long-lived
|
||||
* formatted device is not probed more often than needed.
|
||||
*/
|
||||
basedev->fulltrack = 0;
|
||||
basedev->ese_probe_interval =
|
||||
min(basedev->ese_probe_interval * 2,
|
||||
basedev->ese_heu_max_interval);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void dasd_eckd_ese_format(struct dasd_device *startdev, struct dasd_ccw_req *cqr,
|
||||
struct irb *irb)
|
||||
{
|
||||
|
|
@ -3254,6 +3326,8 @@ static void dasd_eckd_ese_format(struct dasd_device *startdev, struct dasd_ccw_r
|
|||
block = cqr->block;
|
||||
base = block->base;
|
||||
private = base->private;
|
||||
if (dasd_ese_adaptive(base))
|
||||
atomic_inc(&base->ese_nrf_window);
|
||||
blksize = block->bp_block;
|
||||
recs_per_trk = recs_per_track(&private->rdc_data, 0, blksize);
|
||||
|
||||
|
|
@ -4017,6 +4091,14 @@ static int dasd_eckd_release_space_full(struct dasd_device *device)
|
|||
|
||||
rc = dasd_sleep_on_interruptible(cqr);
|
||||
|
||||
if (!rc) {
|
||||
/*
|
||||
* Releasing all space (RAS) wipes every track and the device is fully
|
||||
* sparse again, so restart the heuristic from ft1.
|
||||
*/
|
||||
dasd_ft_bias_apply(device);
|
||||
}
|
||||
|
||||
dasd_sfree_request(cqr, cqr->memdev);
|
||||
|
||||
return rc;
|
||||
|
|
@ -5185,6 +5267,11 @@ static struct dasd_ccw_req *dasd_eckd_build_cp(struct dasd_device *startdev,
|
|||
struct dasd_ccw_req *cqr;
|
||||
|
||||
basedev = block->base;
|
||||
if (dasd_ese_adaptive(basedev))
|
||||
dasd_ese_heuristic_tick(basedev);
|
||||
else
|
||||
/* re-assert the endpoint mode: a stale heuristic write cannot stick */
|
||||
basedev->fulltrack = (basedev->ft_bias >= DASD_FT_BIAS_MAX) ? 1 : 0;
|
||||
private = basedev->private;
|
||||
|
||||
/* Calculate number of blocks/records per track. */
|
||||
|
|
|
|||
|
|
@ -633,6 +633,15 @@ struct dasd_device {
|
|||
/* ESE fulltrack write control (see full_track_bias sysfs attribute) */
|
||||
unsigned int ft_bias; /* aggressiveness 0..100: 0=off, 100=always */
|
||||
unsigned int fulltrack; /* internal: use WRITE_FULL_TRACK for aligned writes */
|
||||
/* adaptive heuristic (active for ft_bias 1..99), derived from ft_bias */
|
||||
unsigned int ese_probe_state; /* heuristic FSM state */
|
||||
unsigned int ese_probe_interval; /* IOs between evaluations */
|
||||
atomic_t ese_io_cnt; /* IO counter for current window */
|
||||
atomic_t ese_nrf_window; /* NRF/INV_TRACK_FORMAT events in window */
|
||||
unsigned int ese_heu_start_interval; /* IOs before first probe */
|
||||
unsigned int ese_heu_probe_window; /* IOs in probe window */
|
||||
unsigned int ese_heu_max_interval; /* max IOs between probes (backoff cap) */
|
||||
unsigned int ese_heu_nrf_high; /* NRF per-mille threshold → activate ft1 */
|
||||
};
|
||||
|
||||
struct dasd_block {
|
||||
|
|
@ -704,6 +713,25 @@ struct dasd_queue {
|
|||
#define DASD_FT_BIAS_MAX 100
|
||||
#define DASD_FT_BIAS_DEFAULT 50
|
||||
|
||||
/* ESE fulltrack heuristic FSM states (adaptive range, ft_bias 1..99) */
|
||||
#define DASD_ESE_HEU_FT1_ACTIVE 0 /* fulltrack write active */
|
||||
#define DASD_ESE_HEU_PROBING 1 /* ft0 probe window, measuring NRF rate */
|
||||
#define DASD_ESE_HEU_FT0_STABLE 2 /* device formatted, ft0 active */
|
||||
|
||||
/*
|
||||
* Heuristic parameters are derived from ft_bias by linear interpolation,
|
||||
* anchored so that ft_bias == 50 reproduces the previously shipped defaults
|
||||
* and ft_bias == 100 is the most aggressive end of the range.
|
||||
* probe_window is constant.
|
||||
*/
|
||||
#define DASD_ESE_HEU_PROBE_WINDOW 100
|
||||
#define DASD_ESE_HEU_NRF_HIGH_A50 10 /* NRF per-mille threshold */
|
||||
#define DASD_ESE_HEU_NRF_HIGH_A100 1
|
||||
#define DASD_ESE_HEU_START_A50 2000 /* IOs before first probe */
|
||||
#define DASD_ESE_HEU_START_A100 500
|
||||
#define DASD_ESE_HEU_MAX_A50 500000 /* backoff cap */
|
||||
#define DASD_ESE_HEU_MAX_A100 20000
|
||||
|
||||
/* per device flags */
|
||||
#define DASD_FLAG_OFFLINE 3 /* device is in offline processing */
|
||||
#define DASD_FLAG_EER_SNSS 4 /* A SNSS is required */
|
||||
|
|
@ -866,6 +894,63 @@ static inline bool dasd_req_conflict(struct dasd_ccw_req *cqr1,
|
|||
cqr2->end_trk < cqr1->format->start_trk);
|
||||
}
|
||||
|
||||
/*
|
||||
* true when device is ese device and ft_bias selects the adaptive
|
||||
* heuristic (neither hard endpoint)
|
||||
*/
|
||||
static inline bool dasd_ese_adaptive(struct dasd_device *device)
|
||||
{
|
||||
return device->discipline &&
|
||||
device->discipline->is_ese &&
|
||||
device->discipline->is_ese(device) &&
|
||||
device->ft_bias > 0 &&
|
||||
device->ft_bias < DASD_FT_BIAS_MAX;
|
||||
}
|
||||
|
||||
/*
|
||||
* Linear interpolation of a heuristic parameter between its value at aggr==50
|
||||
* (v50) and its value at aggr==100 (v100).
|
||||
*/
|
||||
static inline unsigned int dasd_ese_lerp(unsigned int v50, unsigned int v100,
|
||||
unsigned int aggr)
|
||||
{
|
||||
return (unsigned int)((int)v50 +
|
||||
((int)v100 - (int)v50) * ((int)aggr - 50) / 50);
|
||||
}
|
||||
|
||||
/*
|
||||
* Apply the ft_bias knob. For the hard endpoints just pin the mode; for the
|
||||
* adaptive range derive the heuristic parameters from ft_bias and (re)start
|
||||
* the FSM in ft1 so a freshly sparse device avoids the NRF penalty right away.
|
||||
*/
|
||||
static inline void dasd_ft_bias_apply(struct dasd_device *device)
|
||||
{
|
||||
unsigned int a = device->ft_bias;
|
||||
|
||||
if (!dasd_ese_adaptive(device)) {
|
||||
device->fulltrack = (a >= DASD_FT_BIAS_MAX) ? 1 : 0;
|
||||
device->ese_probe_state = DASD_ESE_HEU_FT1_ACTIVE;
|
||||
return;
|
||||
}
|
||||
|
||||
device->ese_heu_nrf_high =
|
||||
dasd_ese_lerp(DASD_ESE_HEU_NRF_HIGH_A50,
|
||||
DASD_ESE_HEU_NRF_HIGH_A100, a);
|
||||
device->ese_heu_start_interval =
|
||||
dasd_ese_lerp(DASD_ESE_HEU_START_A50,
|
||||
DASD_ESE_HEU_START_A100, a);
|
||||
device->ese_heu_max_interval =
|
||||
dasd_ese_lerp(DASD_ESE_HEU_MAX_A50,
|
||||
DASD_ESE_HEU_MAX_A100, a);
|
||||
device->ese_heu_probe_window = DASD_ESE_HEU_PROBE_WINDOW;
|
||||
|
||||
device->ese_probe_state = DASD_ESE_HEU_FT1_ACTIVE;
|
||||
device->ese_probe_interval = device->ese_heu_start_interval;
|
||||
device->fulltrack = 1;
|
||||
atomic_set(&device->ese_io_cnt, 0);
|
||||
atomic_set(&device->ese_nrf_window, 0);
|
||||
}
|
||||
|
||||
/* externals in dasd.c */
|
||||
#define DASD_PROFILE_OFF 0
|
||||
#define DASD_PROFILE_ON 1
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user