/* $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_tape.c 1.5 90/11/02 $"

/*
 * stats_tape.c
 *	Tape performance statistics
 */

/* $Log:	stats_tape.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_tape.h"
#include "sys/vm.h"

extern bool_t calloc_ok;
extern unsigned tp_arraysize;

/*
 * Tape statistics list management
 */

struct tape_stats_list *tp_list;
int tp_ntapes;				/* total number of tapes monitored */


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

struct tape_stats *
tp_alloc_stats(name, unit)
	char *name;
	int unit;
{
	register struct tape_stats *tp;
	register struct tape_stats_list *tl;
	struct tape_stats_list *prev = NULL;
	caddr_t tp_space;
	unsigned len;
	int nbytes;

	/*
	 * Walk down the chain looking for a block of stats with
	 * a free entry
	 */
	tl = tp_list;
	while (tl != NULL && tl->tl_used >= tl->tl_num) {
		prev = tl;
		tl = tl->tl_next;
	}

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

		/*
		 * allocation failed, return
		 */
		if (tp_space == NULL)
			return((struct tape_stats *)NULL);
		/*
		 * Initialize the list
		 */
		tl = (struct tape_stats_list *) tp_space;
		tp_space += sizeof(struct tape_stats_list);
		tl->tl_stats = (struct tape_stats *)tp_space;

		tl->tl_num = tp_arraysize;
		tl->tl_used = 0;
		tl->tl_next = NULL;

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

	/*
	 * Found a block of stats with a free entry, so allocate one from it
	 * and increment the number of entries used.
	 */
	tp = &tl->tl_stats[tl->tl_used++];

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

	tp_ntapes++;
	return(tp);
}


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

void
tp_completion_stats(tsp, nblks, qtime, rw)
	register struct tape_stats *tsp;
	int nblks;
	ulong qtime;
	int rw;
{
	register ulong t;

	if (tsp == NULL)
		return;

	tsp->tp_qlen--;		/* one less request in the queue */

	/*
	 * tp_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(tsp->tp_start, qtime);
	addltoq(t, tsp->tp_resp);

	/*
	 * The difference between tp_start and tp_savedstart is the service
	 * time for this request.  Add this to the cumulative active (busy)
	 * time for the drive.
	 */
	t = et(tsp->tp_start,tsp->tp_savedstart);
	addltoq(t, tsp->tp_active);

	/*
	 * Update information on the type of request and data transfered
	 */
	if (rw & B_READ) {
		tsp->tp_reads++;
		tsp->tp_rdblks += nblks;
	} else {
		tsp->tp_writes++;
		tsp->tp_wtblks += nblks;
	}
}
