Hello,
This is a summary of the original question on 4/11/95...
----------------------------------------------------
We are looking for a way to gather I/O statistics at the disk and
filesystem level. We are particularly interested in the read/write
transfer and byte counts. Filesystem information would be more helpful
than at the disk level. This information is useful to determine if
RAID is appropriate and compensate for AdvFS's affinity to CPU 0
in the SMP environment.
For example, on our DECsystem 5900/240 running Ultrix 4.3a we could
use the crash utility and invoke the 'cam -transfer' command to get...
SCSI/CAM data transfer information:
Controller #0 Targid #0 Lun #0:
read count: 0x6898
read bytes: 0xb2e9200
write count: 0x3176
write bytes: 0x627a800
How can we do this under OSF/1?
We have several Alpha systems running OSF/1 3.2a.
Under OSF/1, I have a C program that uses the table() function and dkinfo
structure to ascertain the following for rz0, for example. Notice it
does not provide separate read and write stats. I am curious why
some of the fields are zero.
Max. # of disks providing stats (di_ndrive) = 256
Bit mask of disks currently busy (di_busy) = 0
Amount of time requested disk is busy (di_time) = 0
Number of seeks for requested disk (di_seek) = 0
Number of data transfer operations (di_xfer) = 3361189
Number of words transferred (di_wds) = 741826192
Words transferred per millisecond (di_wpms) = 0
Disk unit (di_unit) = 0
Disk name (di_name) = rz
Average # of outstanding requests (di_avque) = 0
Average time (ms) in wait queue (di_avwait) = 0
Average time (ms) for transfer completion (di_avserv)= 0
----------------------------------------------------
DEC's Alan Rollow responded to my request with code he hacked to dump
read/write byte/count information for SCSI/CAM devices. I used his
code to produce similar information for SWXCR RAID devices. The output
looks like this...
#./re
Disk Read Count Wrt Count Read Byte Wrt Byte
Ctrl: 0 Unit: 0 LUN: 0 317814 2123024 1257392640 609935360
Ctrl: 0 Unit: 1 LUN: 1 455534 737038 3072221696 85925888
Ctrl: 0 Unit: 2 LUN: 2 625349 1377532 304538112 593387520
Ctrl: 0 Unit: 3 LUN: 3 462974 748474 3014869504 183074816
Ctrl: 0 Unit: 4 LUN: 4 516951 1711281 4248832512 1767399424
Ctrl: 0 Unit: 5 LUN: 5 488778 687916 3730096640 23928832
#./pdrv
Disk Read Count Wrt Count Read Byte Wrt Byte
Bus: 0 Target: 0 LUN: 0 214120 1636446 1984642560 1962516480
Bus: 0 Target: 1 LUN: 0 477669 867033 3215303168 1591730176
Bus: 0 Target: 2 LUN: 0 476755 623645 3186254336 3692584960
Bus: 0 Target: 6 LUN: 0 66 0 325632 0
Bus: 1 Target: 5 LUN: 0 0 624566 0 4013596672
Also, DEC's Janet Aman, CSC, sent me some code to dump misc. disk
information -- example of the is above. I have included the code
below for re.c (SWXCR RAID info), pdrv.c (SCSI/CAM info), dkinfo.c
(misc disk info). NOTE: the count and byte information is stored in
4 byte (int) variables. That means it is possible for the counter to
roll over. Wouldn't it be nice if OSF/1 used 8 byte counters? ;-)
--------------------------------- begin re.c --------------------------------
/*
* Authors: Alan Rollow, StorageWorks Software, Digital Equipment Corp.
* Richard Jackson, Systems Engineering, George Mason University
* re.c is heavily based on Dr. Rollow's pdrv.c (SCSI/CAM) device
* code.
* File: %M%
* Date: %G%
* Version: %I%
*
* %M% -
*
* NOTE:
* to compile: cc -o re re.c
*/
#ifndef lint
static char SccsId[] = "%W% %G%" ;
#endif
#include <sys/types.h>
#include <sys/file.h>
#include <sys/param.h>
#include <sys/sysinfo.h>
#include <sys/dk.h>
#include <sys/time.h>
#include <kern/lock.h>
#include <sys/disklabel.h>
#include <io/common/iotypes.h>
#include <io/common/pt.h>
#include <io/common/devdriver.h>
#include <io/dec/eisa/xcr_port.h>
#include <io/dec/eisa/xcr_common.h>
#include <io/dec/eisa/re.h>
#include <stdlib.h>
#include <fcntl.h>
#include <nlist.h>
#include <errno.h>
#include <stdio.h>
int kmem ;
/*
* Read an address that is the given kernel address.
*/
void *
get_kaddr(off_t where)
{
void *pointer ;
if( lseek(kmem, where, SEEK_SET) == -1 ) {
fprintf(stderr, "Can't lseek in /dev/kmem: %s.\n",
strerror(errno));
return NULL ;
}
if( read(kmem, (char *)&pointer, sizeof(pointer)) == -1 ) {
fprintf(stderr, "Can't read from /dev/kmem: %s.\n",
strerror(errno));
return NULL ;
}
return pointer ;
}
unsigned int
get_kint(off_t where)
{
unsigned int word ;
if( lseek(kmem, where, SEEK_SET) == -1 ) {
fprintf(stderr, "Can't lseek in /dev/kmem: %s.\n",
strerror(errno));
exit(errno) ;
}
if( read(kmem, (char *)&word, sizeof(word)) == -1 ) {
fprintf(stderr, "Can't read from /dev/kmem: %s.\n",
strerror(errno));
exit(errno) ;
}
return word ;
}
/*
* Seek to "where" in the kernel and read "size" bytes
* into the space at "addr".
*/
readk(struct re_device *where, struct re_device *addr, int size)
{
if( lseek(kmem, (off_t)where, SEEK_SET) == -1 ) {
fprintf(stderr, "Can't lseek in /dev/kmem: %s.\n",
strerror(errno));
return -1 ;
}
if( read(kmem, addr, size) == -1 ) {
fprintf(stderr, "Can't read from /dev/kmem: %s.\n",
strerror(errno));
return -1 ;
}
return size ;
}
/**/
/*
* The address of the peripheral device structure and the
* number of CAM busses.
*/
struct nlist namelist[] = {
#define NM_RE_UNIT (0)
{ "re_unit_table" },
#define NM_MAXUNITS (1)
{ "nRE_MAXUNITS" },
{ NULL }
} ;
main()
{
int n_units, i ;
struct re_table_elem *table, *p, *lastp ;
/*
* Get the addresses for the namelist.
*/
if( nlist("/vmunix", namelist) == -1 ) {
fprintf(stderr, "Can't fill in nlist: %s.\n", strerror(errno)) ;
exit(errno) ;
}
/*
* Open kernel memory.
*/
if((kmem = open("/dev/kmem", O_RDONLY)) == -1 ) {
fprintf(stderr, "Can't open /dev/kmem: %s.\n",
strerror(errno)) ;
exit(errno) ;
}
/*
* Get the number of SCSI busses.
*/
n_units = get_kint(namelist[NM_MAXUNITS].n_value) ;
/*
* The table.
*/
table = (struct re_table_elem *)namelist[NM_RE_UNIT].n_value ;
lastp = table + n_units ;
/*
* Examine each entry in the table, passing the
* address of the re_device structure in the
* element.
*/
printf("Disk Read Count Wrt Count Read Byte Wrt Byte\n");
for(p = table; p < lastp; p++)
examine(&p->el_device) ;
if( close(kmem) == -1 )
fprintf(stderr, "Can't close /dev/kmem: %s.\n",
strerror(errno)) ;
return 0 ;
}
/*
* Given the kernel address of a re_device address, read
* later address. If it is NULL return. Then try to read
* the re_device structure at the address. If that fails,
* return.
*
* Finally, having a copy of the structure, print some of
* the contents.
*
* Please note that from the declaration of re_device in
* /usr/include/io/dec/eisa/re.h that the byte counts are both
* 32 bit integers. Also note that a steady data rate of
* 1 MB/sec (1,048,576 bytes/sec) that this counter can
* overflow every 4,096 seconds; a little over an hour.
*
* The table(2) counters are least up in the couple of weeks
* range. As I vaguely recall they count 64 byte words, if
* the counter there is a 32 bit signed integer it can count
* 1 MB/sec for 131,072 seconds (36 days). But, I looked at
* di_wds and it is a long. That's a long time.
*/
examine(struct re_device *p)
{
struct re_device *re, space ;
if((re = (struct re_device *)get_kaddr((off_t)p)) == NULL )
return ;
if( readk(re, &space, sizeof(struct re_device)) == -1 )
return ;
printf("Ctrl: %2d Unit: %2d LUN: %2d %10u %10u %10u %10u\n",
space.re_ctrl, space.re_unit, space.re_log_unit,
space.re_read_count, space.re_write_count,
space.re_read_bytes, space.re_write_bytes) ;
}
--------------------------------- end re.c --------------------------------
--------------------------------- begin pdrv.c --------------------------------
/*
* Author: Alan Rollow, StorageWorks Software, Digital Equipment Corp.
* File: %M%
* Date: %G%
* Version: %I%
*
* %M% -
*
* NOTE:
* to compile: cc -o pdrv pdrv.c
*/
#ifndef lint
static char SccsId[] = "%W% %G%" ;
#endif
#include <sys/types.h>
#include <sys/file.h>
#include <sys/param.h>
#include <sys/sysinfo.h>
#include <sys/dk.h>
#include <sys/time.h>
#include <kern/lock.h>
#include <io/common/iotypes.h>
#include <io/cam/cam.h>
#include <io/cam/dec_cam.h>
#include <io/cam/uagt.h>
#include <io/cam/scsi_all.h>
#include <io/cam/dme.h>
#include <io/cam/sim_common.h>
#include <io/cam/sim_cirq.h>
#include <io/cam/sim_config.h>
#include <io/cam/sim_target.h>
#include <io/cam/sim_xpt.h>
#include <io/cam/sim.h>
#include <io/cam/pdrv.h>
#include <stdlib.h>
#include <fcntl.h>
#include <nlist.h>
#include <errno.h>
#include <stdio.h>
int kmem ;
/*
* Read an address that is the given kernel address.
*/
void *
get_kaddr(off_t where)
{
void *pointer ;
if( lseek(kmem, where, SEEK_SET) == -1 ) {
fprintf(stderr, "Can't lseek in /dev/kmem: %s.\n",
strerror(errno));
return NULL ;
}
if( read(kmem, (char *)&pointer, sizeof(pointer)) == -1 ) {
fprintf(stderr, "Can't read from /dev/kmem: %s.\n",
strerror(errno));
return NULL ;
}
return pointer ;
}
unsigned int
get_kint(off_t where)
{
unsigned int word ;
if( lseek(kmem, where, SEEK_SET) == -1 ) {
fprintf(stderr, "Can't lseek in /dev/kmem: %s.\n",
strerror(errno));
exit(errno) ;
}
if( read(kmem, (char *)&word, sizeof(word)) == -1 ) {
fprintf(stderr, "Can't read from /dev/kmem: %s.\n",
strerror(errno));
exit(errno) ;
}
return word ;
}
/*
* Seek to "where" in the kernel and read "size" bytes
* into the space at "addr".
*/
readk(struct pdrv_device *where, struct pdrv_device *addr, int size)
{
if( lseek(kmem, (off_t)where, SEEK_SET) == -1 ) {
fprintf(stderr, "Can't lseek in /dev/kmem: %s.\n",
strerror(errno));
return -1 ;
}
if( read(kmem, addr, size) == -1 ) {
fprintf(stderr, "Can't read from /dev/kmem: %s.\n",
strerror(errno));
return -1 ;
}
return size ;
}
/**/
/*
* The address of the peripheral device structure and the
* number of CAM busses.
*/
struct nlist namelist[] = {
#define NM_PDRV_UNIT (0)
{ "pdrv_unit_table" },
#define NM_CAM (1)
{ "nCAMBUS" },
{ NULL }
} ;
main()
{
int n_pdrv = NDPS * NLPT, n_cam, i ;
struct pdrv_unit_elem *table, *p, *lastp ;
/*
* Get the addresses for the namelist.
*/
if( nlist("/vmunix", namelist) == -1 ) {
fprintf(stderr, "Can't fill in nlist: %s.\n", strerror(errno)) ;
exit(errno) ;
}
/*
* Open kernel memory.
*/
if((kmem = open("/dev/kmem", O_RDONLY)) == -1 ) {
fprintf(stderr, "Can't open /dev/kmem: %s.\n",
strerror(errno)) ;
exit(errno) ;
}
/*
* Get the number of SCSI busses.
*/
n_cam = get_kint(namelist[NM_CAM].n_value) ;
/*
* The number of pdrv_unit_elem structures.
*/
n_pdrv *= n_cam ;
/*
* The table.
*/
table = (struct pdrv_unit_elem *)namelist[NM_PDRV_UNIT].n_value ;
lastp = table + n_pdrv ;
/*
* Examine each entry in the table, passing the
* address of the pdrv_device structure in the
* element.
*/
printf("Disk Read Count Wrt Count Read Byte Wrt Byte\n");
for(p = table; p < lastp; p++)
examine(&p->pu_device) ;
if( close(kmem) == -1 )
fprintf(stderr, "Can't close /dev/kmem: %s.\n",
strerror(errno)) ;
return 0 ;
}
/*
* Given the kernel address of a pdrv_device address, read
* later address. If it is NULL return. Then try to read
* the pdrv_device structure at the address. If that fails,
* return.
*
* Finally, having a copy of the structure, print some of
* the contents.
*
* Please note that from the declaration of pdrv_device in
* /usr/include/io/cam/pdrv.h that the byte counts are both
* 32 bit integers. Also note that a steady data rate of
* 1 MB/sec (1,048,576 bytes/sec) that this counter can
* overflow every 4,096 seconds; a little over an hour.
*
* The table(2) counters are least up in the couple of weeks
* range. As I vaguely recall they count 64 byte words, if
* the counter there is a 32 bit signed integer it can count
* 1 MB/sec for 131,072 seconds (36 days). But, I looked at
* di_wds and it is a long. That's a long time.
*/
examine(struct pdrv_device *p)
{
struct pdrv_device *pdrv, space ;
if((pdrv = (struct pdrv_device *)get_kaddr((off_t)p)) == NULL )
return ;
if( readk(pdrv, &space, sizeof(struct pdrv_device)) == -1 )
return ;
printf("Bus: %2d Target: %2d LUN: %2d %10u %10u %10u %10u\n",
space.pd_bus, space.pd_target, space.pd_lun,
space.pd_read_count, space.pd_write_count,
space.pd_read_bytes, space.pd_write_bytes) ;
}
--------------------------------- end pdrv.c --------------------------------
--------------------------------- begin dkinfo.c -----------------------------
/*
* Richard Jackson, George Mason University
* NOTE:
* to compile: cc -o dkinfo dkinfo.c
*/
#include <sys/table.h>
void prt_dkinfo(struct tbl_dkinfo *dkinfo)
{
printf("Max. # of disks providing stats (di_ndrive) = %d\n",
dkinfo->di_ndrive);
printf("Bit mask of disks currently busy (di_busy) = %d\n",
dkinfo->di_busy);
printf("Amount of time requested disk is busy (di_time) = %ld\n",
dkinfo->di_time);
printf("Number of seeks for requested disk (di_seek) = %ld\n",
dkinfo->di_seek);
printf("Number of data transfer operations (di_xfer) = %ld\n",
dkinfo->di_xfer);
printf("Number of words transferred (di_wds) = %ld\n",
dkinfo->di_wds);
printf("Words transferred per millisecond (di_wpms) = %ld\n",
dkinfo->di_wpms);
printf("Disk unit (di_unit) = %d\n",
dkinfo->di_unit);
printf("Disk name (di_name) = %s\n",
dkinfo->di_name);
printf("Average # of outstanding requests (di_avque) = %ld\n",
dkinfo->di_avque);
printf("Average time (ms) in wait queue (di_avwait) = %ld\n",
dkinfo->di_avwait);
printf("Average time (ms) for transfer completion (di_avserv)= %ld\n",
dkinfo->di_avserv);
} /* prt_dkinfo */
main()
{
int ndrives, i;
struct tbl_dkinfo dkinfo;
table(TBL_DKINFO, 0, &dkinfo, 1, sizeof(dkinfo));
ndrives = dkinfo.di_ndrive;
prt_dkinfo(&dkinfo);
for (i = 0; i < ndrives; i++)
{
table(TBL_DKINFO, i, &dkinfo, 1, sizeof(dkinfo));
if (strncmp(dkinfo.di_name, "re", 2) != 0 &&
strncmp(dkinfo.di_name, "rz", 2) != 0)
continue;
prt_dkinfo(&dkinfo);
}
} /* main() */
--------------------------------- end dkinfo.c --------------------------------
--
Regards,
Richard Jackson George Mason University
UNIX Systems Engineer UCIS / ISO
Computer Systems Engineering
Received on Thu Apr 27 1995 - 14:19:05 NZST