Skip site navigation (1)Skip section navigation (2)
Date:      Fri, 30 Sep 2016 17:49:05 +0000 (UTC)
From:      Warner Losh <imp@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org
Subject:   svn commit: r306514 - head/sys/cam
Message-ID:  <201609301749.u8UHn5Ok059229@repo.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: imp
Date: Fri Sep 30 17:49:04 2016
New Revision: 306514
URL: https://svnweb.freebsd.org/changeset/base/306514

Log:
  Compute two new metrics. Disk load, the average number of transactions
  we have queued up normaliazed to the queue size. Also compute buckets
  of latency to help compute, in userland, estimates of Median, P90, P95
  and P99 values.
  
  Sponsored by: Netflix, Inc

Modified:
  head/sys/cam/cam_iosched.c

Modified: head/sys/cam/cam_iosched.c
==============================================================================
--- head/sys/cam/cam_iosched.c	Fri Sep 30 17:30:39 2016	(r306513)
+++ head/sys/cam/cam_iosched.c	Fri Sep 30 17:49:04 2016	(r306514)
@@ -42,12 +42,14 @@ __FBSDID("$FreeBSD$");
 #include <sys/lock.h>
 #include <sys/malloc.h>
 #include <sys/mutex.h>
+#include <sys/sbuf.h>
 #include <sys/sysctl.h>
 
 #include <cam/cam.h>
 #include <cam/cam_ccb.h>
 #include <cam/cam_periph.h>
 #include <cam/cam_xpt_periph.h>
+#include <cam/cam_xpt_internal.h>
 #include <cam/cam_iosched.h>
 
 #include <ddb/ddb.h>
@@ -73,14 +75,31 @@ SYSCTL_INT(_kern_cam, OID_AUTO, do_dynam
     &do_dynamic_iosched, 1,
     "Enable Dynamic I/O scheduler optimizations.");
 
+/*
+ * For an EMA, with an alpha of alpha, we know
+ * 	alpha = 2 / (N + 1)
+ * or
+ * 	N = 1 + (2 / alpha)
+ * where N is the number of samples that 86% of the current
+ * EMA is derived from.
+ *
+ * So we invent[*] alpha_bits:
+ *	alpha_bits = -log_2(alpha)
+ *	alpha = 2^-alpha_bits
+ * So
+ *	N = 1 + 2^(alpha_bits + 1)
+ *
+ * The default 9 gives a 1025 lookback for 86% of the data.
+ * For a brief intro: https://en.wikipedia.org/wiki/Moving_average
+ *
+ * [*] Steal from the load average code and many other places.
+ */
 static int alpha_bits = 9;
 TUNABLE_INT("kern.cam.iosched_alpha_bits", &alpha_bits);
 SYSCTL_INT(_kern_cam, OID_AUTO, iosched_alpha_bits, CTLFLAG_RW,
     &alpha_bits, 1,
     "Bits in EMA's alpha.");
 
-
-
 struct iop_stats;
 struct cam_iosched_softc;
 
@@ -208,11 +227,17 @@ struct iop_stats 
 	/*
 	 * Statistics on different bits of the process.
 	 */
-		/* Exp Moving Average, alpha = 1 / (1 << alpha_bits) */
+		/* Exp Moving Average, see alpha_bits for more details */
 	sbintime_t      ema;
 	sbintime_t      emss;		/* Exp Moving sum of the squares */
 	sbintime_t      sd;		/* Last computed sd */
 
+	uint32_t	state_flags;
+#define IOP_RATE_LIMITED		1u
+
+#define LAT_BUCKETS 12			/* < 1ms < 2ms ... 512ms < 1024ms > 1024ms */
+	uint64_t	latencies[LAT_BUCKETS];
+
 	struct cam_iosched_softc *softc;
 };
 
@@ -258,6 +283,7 @@ struct cam_iosched_softc
 	int		read_bias;		/* Read bias setting */
 	int		current_read_bias;	/* Current read bias state */
 	int		total_ticks;
+	int		load;			/* EMA of 'load average' of disk / 2^16 */
 
 	struct bio_queue_head write_queue;
 	struct iop_stats read_stats, write_stats, trim_stats;
@@ -509,6 +535,7 @@ cam_iosched_ticker(void *arg)
 {
 	struct cam_iosched_softc *isc = arg;
 	sbintime_t now, delta;
+	int pending;
 
 	callout_reset(&isc->ticker, hz / isc->quanta - 1, cam_iosched_ticker, isc);
 
@@ -525,6 +552,36 @@ cam_iosched_ticker(void *arg)
 
 	cam_iosched_schedule(isc, isc->periph);
 
+	/*
+	 * isc->load is an EMA of the pending I/Os at each tick. The number of
+	 * pending I/Os is the sum of the I/Os queued to the hardware, and those
+	 * in the software queue that could be queued to the hardware if there
+	 * were slots.
+	 *
+	 * ios_stats.pending is a count of requests in the SIM right now for
+	 * each of these types of I/O. So the total pending count is the sum of
+	 * these I/Os and the sum of the queued I/Os still in the software queue
+	 * for those operations that aren't being rate limited at the moment.
+	 *
+	 * The reason for the rate limiting bit is because those I/Os
+	 * aren't part of the software queued load (since we could
+	 * give them to hardware, but choose not to).
+	 *
+	 * Note: due to a bug in counting pending TRIM in the device, we
+	 * don't include them in this count. We count each BIO_DELETE in
+	 * the pending count, but the periph drivers collapse them down
+	 * into one TRIM command. That one trim command gets the completion
+	 * so the counts get off.
+	 */
+	pending = isc->read_stats.pending + isc->write_stats.pending /* + isc->trim_stats.pending */;
+	pending += !!(isc->read_stats.state_flags & IOP_RATE_LIMITED) * isc->read_stats.queued +
+	    !!(isc->write_stats.state_flags & IOP_RATE_LIMITED) * isc->write_stats.queued /* +
+	    !!(isc->trim_stats.state_flags & IOP_RATE_LIMITED) * isc->trim_stats.queued */ ;
+	pending <<= 16;
+	pending /= isc->periph->path->device->ccbq.total_openings;
+
+	isc->load = (pending + (isc->load << 13) - isc->load) >> 13; /* see above: 13 -> 16139 / 200/s = ~81s ~1 minute */
+
 	isc->total_ticks++;
 }
 
@@ -610,7 +667,7 @@ cam_iosched_cl_maybe_steer(struct contro
 		if (isc->write_stats.current > isc->write_stats.max)
 			isc->write_stats.current = isc->write_stats.max;
 		if (old != isc->write_stats.current && 	iosched_debug)
-			printf("Steering write from %d kBps to %d kBps due to latency of %jdms\n",
+			printf("Steering write from %d kBps to %d kBps due to latency of %jdus\n",
 			    old, isc->write_stats.current,
 			    (uintmax_t)((uint64_t)1000000 * (uint32_t)lat) >> 32);
 		break;
@@ -714,7 +771,7 @@ cam_iosched_limiter_sysctl(SYSCTL_HANDLE
 	char buf[16];
 	struct iop_stats *ios;
 	struct cam_iosched_softc *isc;
-	int value, i, error, cantick;
+	int value, i, error;
 	const char *p;
 	
 	ios = arg1;
@@ -742,21 +799,9 @@ cam_iosched_limiter_sysctl(SYSCTL_HANDLE
 			cam_periph_unlock(isc->periph);
 			return error;
 		}
-		cantick = !!limsw[isc->read_stats.limiter].l_tick +
-		    !!limsw[isc->write_stats.limiter].l_tick +
-		    !!limsw[isc->trim_stats.limiter].l_tick +
-		    1;	/* Control loop requires it */
-		if (isc->flags & CAM_IOSCHED_FLAG_CALLOUT_ACTIVE) {
-			if (cantick == 0) {
-				callout_stop(&isc->ticker);
-				isc->flags &= ~CAM_IOSCHED_FLAG_CALLOUT_ACTIVE;
-			}
-		} else {
-			if (cantick != 0) {
-				callout_reset(&isc->ticker, hz / isc->quanta - 1, cam_iosched_ticker, isc);
-				isc->flags |= CAM_IOSCHED_FLAG_CALLOUT_ACTIVE;
-			}
-		}
+		/* Note: disk load averate requires ticker to be always running */
+		callout_reset(&isc->ticker, hz / isc->quanta - 1, cam_iosched_ticker, isc);
+		isc->flags |= CAM_IOSCHED_FLAG_CALLOUT_ACTIVE;
 
 		cam_periph_unlock(isc->periph);
 		return 0;
@@ -821,6 +866,25 @@ cam_iosched_sbintime_sysctl(SYSCTL_HANDL
 	return 0;
 }
 
+static int
+cam_iosched_sysctl_latencies(SYSCTL_HANDLER_ARGS)
+{
+	int i, error;
+	struct sbuf sb;
+	uint64_t *latencies;
+
+	latencies = arg1;
+	sbuf_new_for_sysctl(&sb, NULL, LAT_BUCKETS * 16, req);
+
+	for (i = 0; i < LAT_BUCKETS - 1; i++)
+		sbuf_printf(&sb, "%jd,", (intmax_t)latencies[i]);
+	sbuf_printf(&sb, "%jd", (intmax_t)latencies[LAT_BUCKETS - 1]);
+	error = sbuf_finish(&sb);
+	sbuf_delete(&sb);
+
+	return (error);
+}
+
 static void
 cam_iosched_iop_stats_sysctl_init(struct cam_iosched_softc *isc, struct iop_stats *ios, char *name)
 {
@@ -884,6 +948,11 @@ cam_iosched_iop_stats_sysctl_init(struct
 	    &ios->current, 0,
 	    "current resource");
 
+	SYSCTL_ADD_PROC(ctx, n,
+	    OID_AUTO, "latencies", CTLTYPE_STRING | CTLFLAG_RD,
+	    &ios->latencies, 0,
+	    cam_iosched_sysctl_latencies, "A",
+	    "Array of power of 2 latency from 1ms to 1.024s");
 }
 
 static void
@@ -1051,6 +1120,11 @@ void cam_iosched_sysctl_init(struct cam_
 	    OID_AUTO, "total_ticks", CTLFLAG_RD,
 	    &isc->total_ticks, 0,
 	    "Total number of ticks we've done");
+
+	SYSCTL_ADD_INT(ctx, n,
+	    OID_AUTO, "load", CTLFLAG_RD,
+	    &isc->load, 0,
+	    "scaled load average / 100");
 #endif
 }
 
@@ -1100,6 +1174,7 @@ cam_iosched_get_write(struct cam_iosched
 		if (iosched_debug)
 			printf("Reads present and current_read_bias is %d queued writes %d queued reads %d\n", isc->current_read_bias, isc->write_stats.queued, isc->read_stats.queued);
 		isc->current_read_bias--;
+		/* We're not limiting writes, per se, just doing reads first */
 		return NULL;
 	}
 
@@ -1109,6 +1184,7 @@ cam_iosched_get_write(struct cam_iosched
 	if (cam_iosched_limiter_iop(&isc->write_stats, bp) != 0) {
 		if (iosched_debug)
 			printf("Can't write because limiter says no.\n");
+		isc->write_stats.state_flags |= IOP_RATE_LIMITED;
 		return NULL;
 	}
 
@@ -1125,6 +1201,7 @@ cam_iosched_get_write(struct cam_iosched
 	}
 	if (iosched_debug > 9)
 		printf("HWQ : %p %#x\n", bp, bp->bio_cmd);
+	isc->write_stats.state_flags &= ~IOP_RATE_LIMITED;
 	return bp;
 }
 #endif
@@ -1224,14 +1301,17 @@ cam_iosched_next_bio(struct cam_iosched_
 
 #ifdef CAM_IOSCHED_DYNAMIC
 	/*
-	 * For the netflix scheduler, bio_queue is only for reads, so enforce
+	 * For the dynamic scheduler, bio_queue is only for reads, so enforce
 	 * the limits here. Enforce only for reads.
 	 */
 	if (do_dynamic_iosched) {
 		if (bp->bio_cmd == BIO_READ &&
-		    cam_iosched_limiter_iop(&isc->read_stats, bp) != 0)
+		    cam_iosched_limiter_iop(&isc->read_stats, bp) != 0) {
+			isc->read_stats.state_flags |= IOP_RATE_LIMITED;
 			return NULL;
+		}
 	}
+	isc->read_stats.state_flags &= ~IOP_RATE_LIMITED;
 #endif
 	bioq_remove(&isc->bio_queue, bp);
 #ifdef CAM_IOSCHED_DYNAMIC
@@ -1478,13 +1558,41 @@ mul(uint64_t a, uint64_t b)
 	return ((ah * bh) << 32) + al * bh + ah * bl + ((al * bl) >> 32);
 }
 
+static sbintime_t latencies[] = {
+	SBT_1MS <<  0,
+	SBT_1MS <<  1,
+	SBT_1MS <<  2,
+	SBT_1MS <<  3,
+	SBT_1MS <<  4,
+	SBT_1MS <<  5,
+	SBT_1MS <<  6,
+	SBT_1MS <<  7,
+	SBT_1MS <<  8,
+	SBT_1MS <<  9,
+	SBT_1MS << 10
+};
+
 static void
 cam_iosched_update(struct iop_stats *iop, sbintime_t sim_latency)
 {
 	sbintime_t y, yy;
 	uint64_t var;
+	int i;
+
+	/*
+	 * Keep counts for latency. We do it by power of two buckets.
+	 * This helps us spot outlier behavior obscured by averages.
+	 */
+	for (i = 0; i < LAT_BUCKETS - 1; i++) {
+		if (sim_latency < latencies[i]) {
+			iop->latencies[i]++;
+			break;
+		}
+	}
+	if (i == LAT_BUCKETS - 1)
+		iop->latencies[i]++; 	 /* Put all > 1024ms values into the last bucket. */
 
-	/* 
+	/*
 	 * Classic expoentially decaying average with a tiny alpha
 	 * (2 ^ -alpha_bits). For more info see the NIST statistical
 	 * handbook.



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201609301749.u8UHn5Ok059229>