/* System dependent file for NCR System V Release 4 MP/RAS */ /* $Id: s-ncr.c,v 1.3 2004/05/29 18:21:29 jdd Exp $ */ #include "s.h" #include #include #include #include #include #include #include #include #include #include #include #include #include extern int diskflag, cpuflag; extern char *kernelSymbols; extern void draw_bar(int bar_num, int *states, int num_states); extern void display_bars(int); struct cpu_names { int cpu_type; char cpu_type_name[8]; }; const struct cpu_names cpu_model_names[] = { { CPU_386, "i386" }, { CPU_486, "i486" }, #ifdef CPU_586 { CPU_586, "Pentium" }, #elif defined CPU_Pentium { CPU_Pentium, "Pentium" }, #endif }; /* The following defines are used to index the variable kernel_nl[] */ #define KERNEL_UTSNAME 0 #define KERNEL_PERCPUP 1 #define KERNEL_CPU_INFOS 2 #define KERNEL_NCPU 3 #define KERNEL_SCSI_STAT 4 #define KERNEL_LBOLT 5 /* kernel_nl[] -- list of symbol addresses needed. */ static struct nlist kernel_nl[] = { { "utsname", 0 }, { "percpup", 0 }, { "cpu_infos", 0 }, { "ncpu", 0 }, { "SCSI_Stat_Ptr", 0 }, { "lbolt", 0 }, { 0, 0 } }; /* kernel_symbols[] -- list of places to look for the running kernel's symbols */ char *kernel_symbols[] = { 0, /* used by -kernel option */ "/stand/unix.diag", "/stand/unix", "/stand/unix.old", 0 }; enum stype { CPU_STATE, DISK_STATE, }; enum stype state = CPU_STATE; int kmem; /* file descriptor for /dev/kmem */ char *kernel_name; /* name of kernel (/stand/unix) */ struct utsname nodename; /* nodename.nodename */ int kmem_is_mapped; int kmem_is_reading; int kmem_can_map = 1; /* Fake kernel memory mapping information */ typedef struct kmem_map { struct kmem_map *km_next; void *km_addr; size_t km_size; char km_buf[4]; } kmem_map_t; kmem_map_t *kmem_list; /* linked list of fake memory mappings */ /* CPU Information */ int kncpu; /* maximum number of cpu's on system */ int ncpus; /* number of cpu's which exist */ percpu_t **kpercpup; /* array of percpu_t pointers */ percpu_t **xpercpup; /* percpu_t pointers when mmapped */ percpu_t **mypercpup = 0; /* Pointers to each cpu's percpu area */ time_t *lastcpu; /* last snapshot of each cpu's times */ cpu_info_t *kcpu_infos; /* State of each cpu */ /* SCSI Disk Information */ int ndisks; long kscsi_stat_ptr_addr; long kaddr; typedef struct diskinfo { dev_t di_dev; SCSI_Device_t *di_kscsi; StatStructure_t di_stats; } diskinfo_t; long *lboltp; long last_lbolt, new_lbolt; diskinfo_t *kscsi_stat; SCSI_Device_t *lscsi_stat; SCSI_Device_t tscsi_stat; /* The size of the array which holds the cpu execution information. */ #if defined( CPU_Pentium ) #define MPSYSINFO c_sysinfo #else #define MPSYSINFO c_mpinfo #endif #define CPU_STATE_SZ sizeof(mypercpup[0]->MPSYSINFO.cpu) /* Computes the time spent in the specified state. */ #define delta(state) \ ((int) mypercpup[cpu]->MPSYSINFO.cpu[state] - cputime[state]) /* Save the state times for a specific cpu */ #define savecpuinfo(cpu) \ memcpy(cputime, mypercpup[cpu]->MPSYSINFO.cpu, CPU_STATE_SZ); /* The size of the local state time array */ #define NSTATES CPU_STATE_SZ #define CPUSTATES (CPU_STATE_SZ / sizeof(mypercpup[0]->MPSYSINFO.cpu[0]))-1 /* Display an error message based upon errno and return 0 to caller */ #define ERR_EXIT(msg) \ { \ perror(msg); \ return 0; \ } /* Read a specified number of bytes of kernel memory and report any error conditions which might arise. */ #define seekreadok(desc, seekpos, buf, sz, msg) \ if (-1 == lseek(desc, seekpos, SEEK_SET)) \ ERR_EXIT("lseeking " msg); \ \ if ((sz) != read(desc, buf, (sz))) \ ERR_EXIT("reading " msg); #define seekreaderr(desc, seekpos, buf, sz, msg) \ ( -1 == lseek(desc, seekpos, SEEK_SET) || \ (sz) != read(desc, buf, (sz))) /* fakethis() -- provide faked out memory mappings if unable to mmap kernel. */ void * fakethis(void *loc, size_t sz) { kmem_map_t *mapping = (kmem_map_t *)malloc( sizeof(kmem_map_t) + sz ); if (!mapping) return 0; mapping->km_addr = loc; mapping->km_size = sz; mapping->km_next = kmem_list; seekreadok(kmem, mapping->km_addr, mapping->km_buf, mapping->km_size, "faking mmap"); if (!kmem_list && kmem_can_map) fprintf(stderr, "Unable to mmap kernel addresses, using lseek/read\n"); kmem_list = mapping; kmem_is_reading++; return (void *) mapping->km_buf; } /* refreshfakes() -- Refresh fake mmap snapshots. */ int refreshfakes(void) { kmem_map_t *mapping; int errs = 0; for (mapping = kmem_list; mapping; mapping = mapping->km_next) if (seekreaderr(kmem, mapping->km_addr, mapping->km_buf, mapping->km_size, "refreshing fake mmap's")) errs++; return errs; } /* Given an address (and size in bytes) in kernel virtual memory, this function uses the mmap function to provide a mapping for those addresses in the current process. The result is either a pointer to the data in this processes virtual space or (void *)-1 if unable to provide the mapping. */ void * mapthis(int desc, void *locp, unsigned long sz) { unsigned long pgoffs = (unsigned long) locp & POFFMASK; unsigned long pgstrt = (unsigned long) locp & PG_ADDR; unsigned long pgend = ((unsigned long) locp + sz) & PG_ADDR; unsigned long mapsz = pgend - pgstrt + NBPP; char *map = (char *)-1; if (kmem_can_map) map = mmap(0, mapsz, PROT_READ, MAP_SHARED, desc, pgstrt); if ((char *)-1 == map) { if (kmem_is_mapped) fprintf(stderr, "Mixing mapping and lseek/read kernel memory?\n"); map = fakethis(locp, sz); return map ? map : (void *)-1; } kmem_is_mapped++; return (void *) (map + pgoffs); } /* get_kernel_symbols -- Find the file containing the symbol definitions for the currently running kernel. The kernel must be running out of /stand (on an svr4 system) and the symbol value for utsname must match the name returned by the uname() system call. This function is called given a list of possible files from /stand which may be running and returns the name of the first file which appears to be correct. */ char * get_kernel_symbols(int kmemfd, char **knamelist) { char **cpp; int nf, errs; int nsz = strlen(nodename.nodename) + 1; struct utsname nname; if (1 >= nsz) fprintf(stderr, "xcpustate: Empty node name\n"); if (kernelSymbols) *knamelist = kernelSymbols; if (!*knamelist) knamelist++; /* skip over initial NULL pointer */ for (cpp = knamelist; *cpp; cpp++) { for (nf = 0; kernel_nl[nf].n_name; nf++) kernel_nl[nf].n_value = 0; nf = nlist(*cpp, kernel_nl); if (-1 == nf || 0 == kernel_nl[KERNEL_UTSNAME].n_value) continue; if (!seekreaderr(kmemfd, kernel_nl[KERNEL_UTSNAME].n_value, &nname.nodename, nsz, "utsname") && 0 == strncmp(nodename.nodename, nname.nodename, nsz)) { for (errs = nf = 0; kernel_nl[nf].n_name; nf ++) if (0 == kernel_nl[nf].n_value) { fprintf(stderr, "kernel symbol `%s' not found\n", kernel_nl[nf].n_name); errs++; } if (errs) return 0; return *cpp; } } fprintf(stderr, "xcpustate: No kernel symbol file matches running kernel\n"); return 0; } /* init_cpu_init() -- Initialize for CPU display */ int init_cpu_info(void) { int nf; seekreadok(kmem, kernel_nl[KERNEL_NCPU].n_value, &kncpu, sizeof(kncpu), "/dev/kmem [ncpu]"); if (kncpu > 64 || kncpu < 0) { fprintf(stderr, "Unlikely value for ncpu found.\n"); return 0; } kcpu_infos = mapthis(kmem, (void *)kernel_nl[KERNEL_CPU_INFOS].n_value, kncpu*sizeof(cpu_info_t)); if (-1 == (int)kcpu_infos) ERR_EXIT("mapping cpu_infos[]"); kpercpup = mapthis(kmem, (void *)kernel_nl[KERNEL_PERCPUP].n_value, kncpu * sizeof(percpu_t *)); if (-1 == (int)kpercpup) ERR_EXIT("mapping percpup[]"); mypercpup = (percpu_t **) malloc( sizeof(percpu_t *) * kncpu ); if (!mypercpup) ERR_EXIT("allocating memory"); xpercpup = (percpu_t **) malloc( kncpu * sizeof(percpu_t *)); if (!kpercpup) ERR_EXIT("allocating memory"); memset(xpercpup, 0, kncpu * sizeof(percpu_t *)); for (ncpus = nf = 0; nf < kncpu; nf++) if (kcpu_infos[nf].cpu_flag & CPU_EXISTS) { if (kpercpup[nf]) { xpercpup[ncpus] = kpercpup[nf]; mypercpup[ncpus] = mapthis(kmem, xpercpup[ncpus], sizeof(*(kpercpup[nf]))); if (-1 == mypercpup[ncpus]) ERR_EXIT("mapping percpu area"); } ncpus++; } nf = ncpus * CPU_STATE_SZ; lastcpu = (time_t *) malloc(nf); memset(lastcpu, 0, nf); return ncpus; } /* scsitype() -- return string naming scsi device type. */ char * scsitype(int t) { switch (t) { case SCSI_DAD_TYPE: return "disk"; case SCSI_SAD_TYPE: return "tape"; case SCSI_PRT_TYPE: return "printer"; case SCSI_PROC_TYPE: return "proc"; case SCSI_WORM_TYPE: return "worm"; case SCSI_CD_ROM_TYPE: return "cdrom"; case SCSI_SCAN_TYPE: return "scanner"; case SCSI_OPTICAL_TYPE: return "optical"; case SCSI_CHANGER_TYPE: return "changer"; case SCSI_COMM_TYPE: return "comm"; } return ""; } /* diskname() -- Return string naming disk device. */ char * diskname(Partition_Identifier_t *s) { static char name[52]; if (0 == s->IO_Bus) { if (0 == s->SCSI_Controller) sprintf(name, "c%xt%xd%xs%x", s->SCSI_Bus, s->PUN, s->LUN, s->Partition); else sprintf(name, "c%x%xt%xd%xs%x", s->SCSI_Controller, s->SCSI_Bus, s->PUN, s->LUN, s->Partition); } else sprintf(name, "c%x%x%xt%xd%xs%x", s->IO_Bus, s->SCSI_Controller, s->SCSI_Bus, s->PUN, s->LUN, s->Partition); return name; } int init_disk_info(void) { long kaddr; int cdisk; int head = 0, nprocs = 0, ncdroms = 0, ntapes = 0; seekreadok(kmem, kernel_nl[KERNEL_SCSI_STAT].n_value, &kscsi_stat_ptr_addr, sizeof(kscsi_stat_ptr_addr), "/dev/kmem [SCSI_Stat_Ptr]"); kaddr = kscsi_stat_ptr_addr; ndisks = 0; do { seekreadok(kmem,kaddr,&tscsi_stat,sizeof(tscsi_stat),"/dev/kmem [SCSI]"); switch (tscsi_stat.inquiry_data.Periph_Device_Type) { case SCSI_DAD_TYPE: ndisks++; break; case SCSI_SAD_TYPE: ntapes++; break; case SCSI_CD_ROM_TYPE: ncdroms++; break; case SCSI_PROC_TYPE: nprocs++; break; } kaddr = (long) tscsi_stat.Statistics.ForwardPointer; } while (kscsi_stat_ptr_addr != kaddr); fprintf(stderr, "SCSI: %d disks, %d tapes, %d cdroms, %d controllers\n", ndisks, ntapes, ncdroms, nprocs); kscsi_stat = (diskinfo_t *) malloc((1+ndisks) * sizeof(diskinfo_t)); if (!kscsi_stat) ERR_EXIT("Allocate SCSI Disk memory"); #if 0 lscsi_stat = (SCSI_Device_t *) malloc((1+ndisks) * sizeof(SCSI_Device_t)); if (!lscsi_stat) ERR_EXIT("Allocate SCSI Statistics array"); memset(lscsi_stat, 0, ndisks * sizeof(SCSI_Device_t)); #endif kaddr = kscsi_stat_ptr_addr; cdisk = 0; lboltp = (long *) mapthis(kmem, (void *)kernel_nl[KERNEL_LBOLT].n_value, sizeof(long)); if (-1 == (long) lboltp) { perror("Can't map lbolt"); return 0; } last_lbolt = *lboltp; do { lscsi_stat = (SCSI_Device_t *) mapthis(kmem, (void *)kaddr, sizeof(tscsi_stat)); if (-1 == (long) lscsi_stat) { perror("Can't map SCSI information"); return 0; } kscsi_stat[cdisk].di_kscsi = (SCSI_Device_t *) lscsi_stat; kscsi_stat[cdisk].di_dev = lscsi_stat->dev; memcpy(&kscsi_stat[cdisk].di_stats, &lscsi_stat->Statistics, sizeof(lscsi_stat->Statistics)); if (!head++) fprintf(stderr," # Bus Ctrlr SBus PUN LUN Part dev_num Name\n"); fprintf(stderr, "%3d %3d %5d %4d %3d %3d %4d %08x %s (%s)\n", cdisk, lscsi_stat->SCSI_device.IO_Bus, lscsi_stat->SCSI_device.SCSI_Controller, lscsi_stat->SCSI_device.SCSI_Bus, lscsi_stat->SCSI_device.PUN, lscsi_stat->SCSI_device.LUN, lscsi_stat->SCSI_device.Partition, lscsi_stat->dev, diskname(&lscsi_stat->SCSI_device), scsitype(lscsi_stat->inquiry_data.Periph_Device_Type)); kaddr = (long) lscsi_stat->Statistics.ForwardPointer; if (SCSI_DAD_TYPE == lscsi_stat->inquiry_data.Periph_Device_Type) cdisk++; } while (kscsi_stat_ptr_addr != kaddr); if (cdisk != ndisks) ERR_EXIT("Error counting disks"); return ndisks; } /* Called by -version */ void version() { rintf("NCR SVR4 MP/RAS\n"); } /* Called at the beginning to inquire how many bars are needed */ int num_bars(void) { int nf; int errs; int sz; char *pgs = 0; kmem = open("/dev/kmem", O_RDONLY); if (-1 == kmem) ERR_EXIT("opening /dev/kmem"); if (-1 == uname(&nodename)) ERR_EXIT("uname"); kernel_name = get_kernel_symbols(kmem, kernel_symbols); if (!kernel_name) return 0; if (diskflag) { if (cpuflag) fprintf(stderr, "Only disk or cpu state can be monitored\n"); state = DISK_STATE; return init_disk_info(); } return init_cpu_info(); } /* * Indicates how many levels each bar has. For most machines, each bar will * have the same stuff. But one can, for instance, display memory use on one * bar, processor levels on others, etc. */ void bar_items(nbars, items) int nbars; int items[]; /* nbars items in this */ { int i; int nlevels = (DISK_STATE == state) ? 2 : CPUSTATES; for (i = 0; i < nbars; ) items[i++] = nlevels; } /* Called after num_bars to ask for bar names */ char ** label_cpu_bars(nbars) { char **names; int hsz = strlen(nodename.nodename); /***** ' ' '#' %d ' ' 'i486' ' ' *****/ int rowsz = hsz + 1 + 1 + 3 + 1 + 7 + 2; int sz = nbars * rowsz; int cpu, i; char *cp; shorten(nodename.nodename); names = (char **)malloc(sz + sizeof(char *) * nbars); cp = (char *)names + sizeof(char *) * nbars; for (cpu = i = 0; i < kncpu; i++) if (kcpu_infos[i].cpu_flag & CPU_EXISTS) { names[cpu++] = cp; if (1 == ncpus) sprintf(cp, "%s", nodename.nodename); else if (0 == i) sprintf(cp, "%s #%d", nodename.nodename, i); else sprintf(cp, "%*s #%d", hsz, "", i); cp += rowsz; } return names; } char ** label_disk_bars(nbars) { char **names; char *cp; int i; int hsz = sizeof("cxxxtxdxsx"); names = (char **) malloc( hsz * nbars + nbars * sizeof(char *) ); cp = (char *) names + sizeof(char *) * nbars; for (i = 0; i < nbars; i++) { names[i] = cp; lscsi_stat = kscsi_stat[i].di_kscsi; strcpy(cp, diskname(&lscsi_stat->SCSI_device)); cp += hsz; } return names; } char ** label_bars(nbars) { switch (state) { case CPU_STATE: return label_cpu_bars(nbars); case DISK_STATE: return label_disk_bars(nbars); } return NULL; } /* Called after the bars are created to do machine dependent inits */ void init_bars(nbars) int nbars; { (void) display_bars(nbars); } /* This function is called every interval to compute and display the bars. It should call draw_bar() with the bar number, the array of integer values to display in the bar, and the number of values in the array. */ void display_cpu_bars(nbars) { int states[NSTATES]; int nstates; int cpu, i; time_t *cputime = lastcpu; for (i = cpu = 0; i < kncpu && cpu < nbars; i++) { if (0 == (kcpu_infos[i].cpu_flag & CPU_EXISTS)) continue; nstates = 0; if (kcpu_infos[i].cpu_flag & CPU_RUNNING && kpercpup[i]) { if (kpercpup[cpu] != xpercpup[i]) { /* mapping changed or became available */ xpercpup[cpu] = kpercpup[i]; mypercpup[ i] = mapthis(kmem, xpercpup[cpu], sizeof(*(xpercpup[cpu]))); } states[nstates++] = delta(CPU_IDLE); states[nstates++] = delta(CPU_USER); states[nstates++] = delta(CPU_KERNEL); states[nstates++] = delta(CPU_WAIT) + delta(CPU_SXBRK); #if 0 states[nstates++] = delta(CPU_SXBRK); #endif } else { states[nstates++] = 100; /* CPU_IDLE */ states[nstates++] = 0; /* CPU_USER */ states[nstates++] = 0; /* CPU_KERNEL */ states[nstates++] = 0; /* CPU_WAIT */ #if 0 states[nstates++] = 0; /* CPU_SXBRK */ #endif } savecpuinfo(cpu); draw_bar(cpu, states, nstates); cpu++; cputime += CPU_STATE_SZ / sizeof(time_t); } } void display_disk_bars(nbars) { int states[2]; int i; time_t t; static time_t maxt = 0; static int npr = 0; time_t dlt; struct iotime *new, *old; new_lbolt = *lboltp; dlt = new_lbolt - last_lbolt; last_lbolt = new_lbolt; for (i = 0; i < ndisks; i++) { lscsi_stat = kscsi_stat[i].di_kscsi; if (kscsi_stat[i].di_dev != lscsi_stat->dev) { /* device changed */ states[0] = 100; /* idle */ states[1] = 0; /* busy */ } else { new = &lscsi_stat->Statistics.SCSI_Stats; old = &kscsi_stat[i].di_stats.SCSI_Stats; t = new->io_act - old->io_act; states[1] = t; if (states[1] > dlt) states[0] = 0; else states[0] = dlt - states[1]; old->io_act = new->io_act; old->io_resp = new->io_resp; } draw_bar(i, states, 2); } } void display_bars(nbars) { if (kmem_list) refreshfakes(); switch (state) { case CPU_STATE: display_cpu_bars(nbars); case DISK_STATE: display_disk_bars(nbars); } return; }