/* $Copyright:	$
 * Copyright (c) 1984, 1985, 1986, 1987, 1988, 1989, 1990 
 * Sequent Computer Systems, Inc.   All rights reserved.
 *  
 * This software is furnished under a license and may be used
 * only in accordance with the terms of that license and with the
 * inclusion of the above copyright notice.   This software may not
 * be provided or otherwise made available to, or used by, any
 * other person.  No title to or ownership of the software is
 * hereby transferred.
 */

#ident "$Header: stats_disk.c 1.8 90/11/02 $"

/*
 * stats_disk.c
 *	Disk performance statistics
 */

/* $Log:	stats_disk.c,v $
 */

#include "sys/types.h"
#include "sys/devsw.h"
#include "sys/sysmacros.h"
#include "sys/signal.h"
#include "sys/param.h"
#include "sys/mutex.h"
#include "sys/file.h"
#include "sys/user.h"
#include "sys/buf.h"
#include "sys/systm.h"
#include "sys/stats_disk.h"
#include "sys/vm.h"

extern bool_t calloc_ok;
extern unchar ds_keep_cylinfo;
extern unsigned ds_arraysize;
extern unsigned ds_blocks_per_bucket;

/*
 * Disk statistics list management
 */

struct disk_stats_list *ds_list;
int ds_ndisks;				/* total number of disks monitored */


/*
 * Allocate a disk statistics structure
 * Add a new chunk of disk_stats structures to the list if necessary
 */

struct disk_stats *
ds_alloc_stats(name, size, unit, flags)
	char *name;		/* base device name */
	ulong size;		/* "size of device" in cylinders or blocks */
	int unit;		/* device unit number */
	ushort flags;		/* eg: true cylinders or buckets of blocks */
{
	register struct disk_stats *ds;
	register struct disk_stats_list *dl;
	struct disk_stats_list *prev = NULL;
	caddr_t ds_space;
	unsigned len;
	int nbytes;

	/*
	 * Walk down the chain looking for a block of stats with
	 * a free entry
	 */
	dl = ds_list;
	while (dl != NULL && dl->dl_used >= dl->dl_num) {
		prev = dl;
		dl = dl->dl_next;
	}

	/*
	 * dl == NULL implies a new chunk of disk_stats is needed so attempt
	 * to allocate.  Ask for space for both the list structure and the
	 * array of disk_stats structures at the same time to reduce
	 * fragmentation.
	 */
	if (dl == NULL) {
		nbytes = sizeof(struct disk_stats_list) +
				(ds_arraysize) * sizeof(struct disk_stats);
		if (calloc_ok) {
			ds_space = calloc(nbytes);
		} else {
			ds_space = wmemall(nbytes, (bool_t) 1);
		}

		/*
		 * allocation failed, return
		 */
		if (ds_space == NULL)
			return((struct disk_stats *)NULL);
		/*
		 * Initialize the list
		 */
		dl = (struct disk_stats_list *) ds_space;
		ds_space += sizeof(struct disk_stats_list);
		dl->dl_stats = (struct disk_stats *)ds_space;

		dl->dl_num = ds_arraysize;
		dl->dl_used = 0;
		dl->dl_next = NULL;

		/*
		 * prev will only be equal to NULL if ds_list is also NULL
		 * so the check of prev only is sufficient
		 */
		if (prev == NULL) {
			ds_list = dl;
		} else {
			prev->dl_next = dl;
		}
	}

	/*
	 * Found a block of stats with a free entry, so allocate one from it
	 * and increment the number of entries used.
	 */
	ds = &dl->dl_stats[dl->dl_used++];

	/*
	 * Initialize the structure
	 */
	bzero(ds, sizeof(struct disk_stats));
	len = strlen(name);
	bcopy(name, ds->ds_name, len);
	if (unit > 9) {
		ds->ds_name[len++] = unit/10 + '0';
		unit %= 10;
	}
	ds->ds_name[len++] = unit + '0';
	ds->ds_name[len] = '\0';

	/*
	 * Allocate space for the cylinder and seek distance histograms.
	 * Attempt to allocate them at the same time to reduce fragmentation.
	 */
	ds->ds_cylhist = NULL;
	ds->ds_seekhist = NULL;

	/*
	 * ds_cyls was zeroed by the bzero() above.  This is important
	 * as ds_cyls == 0 => no cylinder/seek info is kept.
	 * ds_cyls is set below if the cylinder/seek info is kept.
	 */
	if (ds_keep_cylinfo && size != 0) {
		/*
		 * If faking cylinder access data with blocks of
		 * buckets compute the number of fake "cylinders" (buckets)
		 * Record fact that we're faking it in the stats structure.
		 */
		if (flags & DS_BLK_HIST) {
			size /= ds_blocks_per_bucket;
			ds->ds_flags |= DS_BLK_HIST;
		}

		nbytes = size * sizeof(ulong);
		nbytes += sizeof(ulong) * DS_SEEK_HISTSZ(size);

		if (calloc_ok) {
			ds_space = calloc(nbytes);
		} else {
			ds_space = wmemall(nbytes, (bool_t) 1);
		}
		/*
		 * If allocation succeeded, set the pointers
		 */
		if (ds_space != NULL) {
			bzero(ds_space, (unsigned) nbytes);
			ds->ds_cyls = size;
			ds->ds_cylhist = (ulong *) ds_space;
			ds_space += size * sizeof(ulong);
			ds->ds_seekhist = (ulong *) ds_space;
		}
	}

	ds_ndisks++;
	return(ds);
}


/*
 * ds_access_stats() updates the cylinder access and seek distance
 * histograms
 */

void
ds_access_stats(dsp, cyl)
	register struct disk_stats *dsp;
	ulong cyl;
{
	register int sdist;

	if (dsp == NULL || dsp->ds_cyls == 0)
		return;

	/*
	 * If faking cylinder access data with buckets of blocks,
	 * cyl is really a block number, so convert it to a bucket.
	 */
	if (dsp->ds_flags & DS_BLK_HIST) {
		cyl /= ds_blocks_per_bucket;
	}


	/*
	 * Determine the seek distance and update the histogram
	 */
	sdist = dsp->ds_cyl - cyl;
	if (sdist < 0)
		sdist = -sdist;
	if (sdist / DS_SEEK_SMALL < DS_SEEK_N_SMALL) {
		dsp->ds_seekhist[sdist / DS_SEEK_SMALL]++;
	} else {
		dsp->ds_seekhist[DS_SEEK_N_SMALL +
			(sdist - (DS_SEEK_SMALL * DS_SEEK_N_SMALL)) /
			DS_SEEK_LARGE]++;
	}

	/*
	 * update the cylinder access histogram and remember what cylinder
	 * was accessed
	 */
	dsp->ds_cyl = cyl;
	dsp->ds_cylhist[cyl]++;
}


/*
 * Calculate elapsed time from microsecond clock.
 * Handles one wraparound
 */
#define et(t1,t0) ((t1)-(t0))

void
ds_completion_stats(dsp, bp)
	register struct disk_stats *dsp;
	register struct buf *bp;
{
	register ulong t;

	if (dsp == NULL)
		return;

	dsp->ds_qlen--;		/* one less request in the queue */

	/*
	 * ds_start is time (in microseconds) that this request finished
	 * (and next one started if one was started).
	 *
	 * The difference between this and the time the request was queued
	 * to the driver is the response time.  Add the response time to the
	 * cumulative response time and update the histogram.
	 */
	t = et(dsp->ds_start, bp->b_queued);
	addltoq(t, dsp->ds_resp);

	/*
	 * Convert t to milliseconds and update the histogram
	 */
	t /= 1000;
	if (t < DS_RT_SML_UPPER) {
		dsp->ds_resphist[t / DS_RT_SMALL]++;
	} else if (t < DS_RT_LRG_UPPER) {
		dsp->ds_resphist[DS_RT_N_SMALL +
				(t - DS_RT_SML_UPPER) / DS_RT_LARGE]++;
	} else {
		dsp->ds_resphist[DS_RT_N_SMALL + DS_RT_N_LARGE]++;
	}

	/*
	 * The difference between ds_start and ds_savedstart is the service
	 * time for this request.  Add this to the cumulative active (busy)
	 * time for the drive.
	 */
	t = et(dsp->ds_start,dsp->ds_savedstart);
	addltoq(t, dsp->ds_active);

	/*
	 * Update information on the type of request and data transfered
	 */
	if (bp->b_flags & B_IOCTL) {
		dsp->ds_misc++;
		dsp->ds_miscblks += (bp->b_bcount - bp->b_resid) >> DEV_BSHIFT;
	} else {
		if (bp->b_flags & B_READ) {
			dsp->ds_reads[bp->b_iotype]++;
			dsp->ds_rdblks[bp->b_iotype] +=
			    (bp->b_bcount - bp->b_resid) >> DEV_BSHIFT;
		} else {
			dsp->ds_writes[bp->b_iotype]++;
			dsp->ds_wtblks[bp->b_iotype] +=
			    (bp->b_bcount - bp->b_resid) >> DEV_BSHIFT;
		}
	}
}
