2 * Copyright (c) 2003 Apple Computer, Inc. All rights reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * "Portions Copyright (c) 1999 Apple Computer, Inc. All Rights
7 * Reserved. This file contains Original Code and/or Modifications of
8 * Original Code as defined in and that are subject to the Apple Public
9 * Source License Version 1.0 (the 'License'). You may not use this file
10 * except in compliance with the License. Please obtain a copy of the
11 * License at http://www.apple.com/publicsource and read it before using
14 * The Original Code and all software distributed under the License are
15 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
16 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
17 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the
19 * License for the specific language governing rights and limitations
22 * @APPLE_LICENSE_HEADER_END@
25 #define IOKIT 1 /* to get io_name_t in device_types.h */
36 #include <mach/mach.h>
37 #include <mach/mach_error.h>
38 #include <sys/param.h>
40 #include <CoreFoundation/CoreFoundation.h>
41 #include <IOKit/IOKitLib.h>
42 #include <IOKit/storage/IOBlockStorageDriver.h>
43 #include <IOKit/storage/IOMedia.h>
44 #include <IOKit/IOBSD.h>
46 #include <sys/socket.h>
48 #include <net/if_var.h>
54 FILE *data_fp
= (FILE *)0; /* raw data output file pointer */
57 #define REVISION_HISTORY_DATE 20030718
59 struct record_hdr restart_record
= { SAR_RESTART
, REVISION_HISTORY_DATE
, 0, 0 };
60 struct record_hdr timestamp_record
= { SAR_TIMESTAMP
, 1, 0, 0 };
61 struct record_hdr vmstat_record
= {SAR_VMSTAT
, 1, 1, 0 };
62 struct record_hdr cpu_record
= {SAR_CPU
, 1, 1, 0 };
63 struct record_hdr drivestats_record
= {SAR_DRIVESTATS
, 1, 0, 0 };
64 struct record_hdr drivepath_record
= {SAR_DRIVEPATH
, 1, 1, 0 };
65 struct record_hdr netstats_record
= {SAR_NETSTATS
, 1, 0, 0};
67 /* Compile for verbose output */
69 int t_interval
= 0; /* in seconds */
70 int n_samples
= 1; /* number of sample loops */
71 char *ofile
= NULL
; /* output file */
72 int ofd
; /* output file descriptor */
73 static mach_port_t myHost
;
74 static mach_port_t masterPort
;
76 /* internal table of drive path mappings */
77 struct drivepath
*dp_table
= NULL
;
79 /* number of entries in the dp_table */
82 /* internal table of network interface statistics */
83 struct netstats
*ns_table
= NULL
;
87 static struct nlist nlist_net
[2];
88 int kvm_init_failed
= 0;
94 /* Forward fuction declarations */
95 static void exit_usage();
96 static void open_datafile(char *);
97 static void write_record_hdr(struct record_hdr
*);
98 static void write_record_data(char *, int);
99 static void get_all_stats();
100 static void get_vmstat_sample();
101 static void get_drivestat_sample();
102 static int get_ndrives();
103 static int record_device(io_registry_entry_t
, struct drivestats
*, int ndrives
);
104 static int check_device_path (char *name
, char *path
, int ndrives
);
105 static void get_netstat_sample(int pppflag
);
106 static int kvm_init();
107 static int kread(u_long addr
, void *buf
, size_t nbytes
);
119 * Stop being root ASAP.
123 fprintf(stderr
, "sadc: must be setuid root or root");
130 setvbuf(stdout
, (char *)NULL
, _IONBF
, 0);
132 while ((ch
=getopt(argc
, argv
, "m:")) != EOF
) {
135 /* Only the PPP mode matters on this collector side */
136 /* The reporter side deals with the DEV or EDEV modes */
137 if (!strncmp(optarg
, "PPP", 3))
138 network_mode
|= NET_PPP_MODE
;
149 if (isdigit(*argv
[optind
]))
151 /* we expect to have both an interval and a sample count */
153 t_interval
= strtol(argv
[optind
], &p
, 0);
154 if (errno
|| (*p
!='\0') || t_interval
<= 0)
160 if ((argc
< 2) || (!isdigit(*argv
[optind
]))) {
165 n_samples
= strtol(argv
[optind
], &p
, 0);
166 if (errno
|| (*p
!= '\0') || n_samples
<= 0)
174 /* we have an output file */
175 ofile
= argv
[optind
];
180 /* all we have is an output file */
181 ofile
= argv
[optind
];
186 /* open the output file */
187 (void)open_datafile(ofile
);
190 * Get the Mach private port.
192 myHost
= mach_host_self();
195 * Get the I/O Kit communication handle.
197 IOMasterPort(bootstrap_port
, &masterPort
);
200 restart_record
.rec_timestamp
= time((time_t *)0);
201 write_record_hdr(&restart_record
);
202 get_all_stats(); /* this is the initial stat collection */
207 /* this init sample is not counted */
208 timestamp_record
.rec_data
= time((time_t *)0); /* returns time in
212 tm
= gmtime(&(timestamp_record
.rec_data
));
213 fprintf(stderr
, "timestamp=%ld\n", timestamp_record
.rec_data
);
214 fprintf(stderr
, "GMTIME offset from UTC in seconds = %ld\n", tm
->tm_gmtoff
);
215 fprintf(stderr
, "GMTIME secnds=%d, min=%d, hour=%d\n", tm
->tm_sec
, tm
->tm_min
, tm
->tm_hour
);
216 fprintf(stderr
, "asctime = %s\n", asctime(tm
));
218 tm
=localtime(&(timestamp_record
.rec_data
));
219 fprintf(stderr
, "LOCTIME offset from UTC in seconds = %ld\n",tm
->tm_gmtoff
);
220 fprintf(stderr
, "LOCTIME secnds=%d, min=%d, hour=%d\n", tm
->tm_sec
, tm
->tm_min
, tm
->tm_hour
);
221 fprintf(stderr
, "asctime = %s\n", asctime(tm
));
224 write_record_hdr(×tamp_record
);
231 timestamp_record
.rec_timestamp
= time((time_t *)0); /* returns time in
233 write_record_hdr(×tamp_record
);
243 fprintf(stderr
, "/usr/lib/sa/sadc [-m {PPP}] [t n] [ofile]\n");
248 open_datafile(char *path
)
256 data_fp
= fopen(path
, "w+");
260 /* failed to open path */
261 fprintf(stderr
, "sadc: failed to open data file [%s]\n", path
?path
:"stdout");
267 write_record_hdr(hdr
)
268 struct record_hdr
*hdr
;
272 if (fwrite(hdr
, sizeof(struct record_hdr
), 1, data_fp
) != 1)
274 fprintf(stderr
, "sadc: write_record_hdr failed, errno=%d\n", errno
);
283 write_record_data(data
, size
)
289 if (fwrite(data
, size
, 1, data_fp
) != 1)
291 fprintf(stderr
, "sadc: write_record_data failed, errno=%d\n", errno
);
303 struct vm_statistics stat
;
305 mach_msg_type_number_t count
;
307 count
= HOST_VM_INFO_COUNT
;
308 error
= host_statistics(myHost
, HOST_VM_INFO
, (host_info_t
)&stat
, &count
);
309 if (error
!= KERN_SUCCESS
) {
310 fprintf(stderr
, "sadc: Error in vm host_statistics(): %s\n",
311 mach_error_string(error
));
315 vmstat_record
.rec_count
= 1;
316 vmstat_record
.rec_size
= sizeof(vm_statistics_data_t
);
317 write_record_hdr(&vmstat_record
);
318 write_record_data((char *)&stat
, sizeof(vm_statistics_data_t
));
324 host_cpu_load_info_data_t cpuload
;
326 mach_msg_type_number_t count
;
328 count
= HOST_CPU_LOAD_INFO_COUNT
;
329 error
= host_statistics(myHost
, HOST_CPU_LOAD_INFO
,(host_info_t
)&cpuload
, &count
);
330 if (error
!= KERN_SUCCESS
) {
331 fprintf(stderr
, "sadc: Error in cpu host_statistics(): %s",
332 mach_error_string(error
));
336 cpu_record
.rec_count
= 1;
337 cpu_record
.rec_size
= sizeof(host_cpu_load_info_data_t
);
338 write_record_hdr(&cpu_record
);
339 write_record_data((char *)&cpuload
, sizeof(host_cpu_load_info_data_t
));
343 get_drivestat_sample()
345 io_registry_entry_t drive
;
346 io_iterator_t drivelist
;
347 CFMutableDictionaryRef match
;
352 struct drivestats
*dbuf
;
353 kern_return_t status
;
356 if ((ndrives
= get_ndrives()) <= 0)
359 /* allocate space to collect stats for all the drives */
360 bufsize
= ndrives
* sizeof(struct drivestats
);
361 buf
= (char *) malloc (bufsize
);
362 dbuf
= (struct drivestats
*)buf
;
364 bzero((char *)buf
, bufsize
);
369 * Get an iterator for IOMedia objects.
371 match
= IOServiceMatching("IOMedia");
373 /* Get whole disk info */
374 CFDictionaryAddValue(match
, CFSTR(kIOMediaWholeKey
), kCFBooleanTrue
);
376 status
= IOServiceGetMatchingServices(masterPort
, match
, &drivelist
);
377 if (status
!= KERN_SUCCESS
)
381 * Scan all of the IOMedia objects, and for each
382 * object that has a parent IOBlockStorageDriver,
383 * record the statistics
385 * XXX What about RAID devices?
389 while ((drive
= IOIteratorNext(drivelist
)))
393 if (record_device(drive
, &dbuf
[i
], ndrives
))
401 IOObjectRelease(drive
);
404 IOObjectRelease(drive
);
406 IOObjectRelease(drivelist
);
410 drivestats_record
.rec_count
= i
;
411 drivestats_record
.rec_size
= sizeof (struct drivestats
);
412 write_record_hdr(&drivestats_record
);
413 write_record_data((char *)buf
, (i
* sizeof(struct drivestats
)));
423 * Determine whether an IORegistryEntry refers to a valid
424 * I/O device, and if so, record it.
425 * Return zero: no device recorded
426 * Return non-zero: device stats recorded
429 record_device(io_registry_entry_t drive
, struct drivestats
* drivestat
, int ndrives
)
431 io_registry_entry_t parent
;
432 CFDictionaryRef properties
, statistics
;
436 kern_return_t status
;
440 char BSDName
[MAXDRIVENAME
+ 1];
442 status
= IORegistryEntryGetParentEntry(drive
, kIOServicePlane
, &parent
);
443 if (status
!= KERN_SUCCESS
)
445 /* device has no parent */
449 if (IOObjectConformsTo(parent
, "IOBlockStorageDriver"))
452 * Get a unique device path identifier.
453 * Devices available at boot have an Open Firmware Device Tree path.
454 * The OF path is short and concise and should be first choice.
455 * Devices that show up after boot, are guaranteed to have
456 * a Service Plane, hardware unique path.
459 bzero(path
, sizeof(io_string_t
));
460 if (IORegistryEntryGetPath(drive
, kIODeviceTreePlane
, path
) != KERN_SUCCESS
)
462 if(IORegistryEntryGetPath(drive
, kIOServicePlane
, path
) != KERN_SUCCESS
)
463 /* device has no unique path identifier */
468 /* get drive properties */
469 status
= IORegistryEntryCreateCFProperties(drive
,
470 (CFMutableDictionaryRef
*)&properties
,
473 if (status
!= KERN_SUCCESS
)
475 /* device has no properties */
479 bzero(BSDName
, MAXDRIVENAME
+1);
480 /* get name from properties */
481 name
= (CFStringRef
)CFDictionaryGetValue(properties
,
482 CFSTR(kIOBSDNameKey
));
484 CFStringGetCString(name
, BSDName
,
485 MAXDRIVENAME
, CFStringGetSystemEncoding());
489 /* get blocksize from properties */
490 number
= (CFNumberRef
)CFDictionaryGetValue(properties
,
491 CFSTR(kIOMediaPreferredBlockSizeKey
));
493 CFNumberGetValue(number
,
494 kCFNumberSInt64Type
, &value
);
495 drivestat
->blocksize
= value
;
498 CFRelease(properties
);
503 /* we should have a name and blocksize at a minimum */
510 drive_id
= check_device_path (BSDName
, path
, ndrives
);
517 drivestat
->drivepath_id
= drive_id
;
520 /* get parent drive properties */
521 status
= IORegistryEntryCreateCFProperties(parent
,
522 (CFMutableDictionaryRef
*)&properties
,
525 if (status
!= KERN_SUCCESS
)
527 /* device has no properties */
531 /* Obtain the statistics from the parent drive properties. */
534 = (CFDictionaryRef
)CFDictionaryGetValue(properties
,
535 CFSTR(kIOBlockStorageDriverStatisticsKey
));
539 /* Get number of reads. */
541 (CFNumberRef
)CFDictionaryGetValue(statistics
,
542 CFSTR(kIOBlockStorageDriverStatisticsReadsKey
));
544 CFNumberGetValue(number
,
545 kCFNumberSInt64Type
, &value
);
546 drivestat
->Reads
= value
;
549 /* Get bytes read. */
551 (CFNumberRef
)CFDictionaryGetValue(statistics
,
552 CFSTR(kIOBlockStorageDriverStatisticsBytesReadKey
));
554 CFNumberGetValue(number
, kCFNumberSInt64Type
, &value
);
555 drivestat
->BytesRead
= value
;
558 /* Get number of writes. */
560 (CFNumberRef
)CFDictionaryGetValue(statistics
,
561 CFSTR(kIOBlockStorageDriverStatisticsWritesKey
));
563 CFNumberGetValue(number
, kCFNumberSInt64Type
, &value
);
564 drivestat
->Writes
= value
;
567 /* Get bytes written. */
569 (CFNumberRef
)CFDictionaryGetValue(statistics
,
570 CFSTR(kIOBlockStorageDriverStatisticsBytesWrittenKey
));
572 CFNumberGetValue(number
, kCFNumberSInt64Type
, &value
);
573 drivestat
->BytesWritten
= value
;
576 /* Get LatentReadTime. */
578 (CFNumberRef
)CFDictionaryGetValue(statistics
,
579 CFSTR(kIOBlockStorageDriverStatisticsLatentReadTimeKey
));
581 CFNumberGetValue(number
, kCFNumberSInt64Type
, &value
);
582 drivestat
->LatentReadTime
= value
;
585 /* Get LatentWriteTime. */
587 (CFNumberRef
)CFDictionaryGetValue(statistics
,
588 CFSTR(kIOBlockStorageDriverStatisticsLatentWriteTimeKey
));
590 CFNumberGetValue(number
, kCFNumberSInt64Type
, &value
);
591 drivestat
->LatentWriteTime
= value
;
594 /* Get ReadErrors. */
596 (CFNumberRef
)CFDictionaryGetValue(statistics
,
597 CFSTR(kIOBlockStorageDriverStatisticsReadErrorsKey
));
599 CFNumberGetValue(number
, kCFNumberSInt64Type
, &value
);
600 drivestat
->ReadErrors
= value
;
603 /* Get WriteErrors. */
605 (CFNumberRef
)CFDictionaryGetValue(statistics
,
606 CFSTR(kIOBlockStorageDriverStatisticsWriteErrorsKey
));
608 CFNumberGetValue(number
, kCFNumberSInt64Type
, &value
);
609 drivestat
->WriteErrors
= value
;
612 /* Get ReadRetries. */
614 (CFNumberRef
)CFDictionaryGetValue(statistics
,
615 CFSTR(kIOBlockStorageDriverStatisticsReadRetriesKey
));
617 CFNumberGetValue(number
, kCFNumberSInt64Type
, &value
);
618 drivestat
->ReadRetries
= value
;
621 /* Get WriteRetries. */
623 (CFNumberRef
)CFDictionaryGetValue(statistics
,
624 CFSTR(kIOBlockStorageDriverStatisticsWriteRetriesKey
));
626 CFNumberGetValue(number
, kCFNumberSInt64Type
, &value
);
627 drivestat
->WriteRetries
= value
;
630 /* Get TotalReadTime. */
632 (CFNumberRef
)CFDictionaryGetValue(statistics
,
633 CFSTR(kIOBlockStorageDriverStatisticsTotalReadTimeKey
));
635 CFNumberGetValue(number
, kCFNumberSInt64Type
, &value
);
636 drivestat
->TotalReadTime
= value
;
639 /* Get WriteRetries. */
641 (CFNumberRef
)CFDictionaryGetValue(statistics
,
642 CFSTR(kIOBlockStorageDriverStatisticsTotalWriteTimeKey
));
644 CFNumberGetValue(number
, kCFNumberSInt64Type
, &value
);
645 drivestat
->TotalWriteTime
= value
;
648 CFRelease(properties
);
649 } /* end if statistics != 0 */
652 IOObjectRelease(parent
);
658 * find IOMedia objects
659 * This routine always gives me a lower count on the number
660 * of disks. I don't know which one to use.
665 io_iterator_t drivelist
;
666 io_registry_entry_t drive
;
667 io_registry_entry_t parent
;
668 CFMutableDictionaryRef match
;
670 kern_return_t status
;
673 * Get an iterator for IOMedia objects.
675 match
= IOServiceMatching("IOMedia");
676 CFDictionaryAddValue(match
, CFSTR(kIOMediaWholeKey
), kCFBooleanTrue
);
677 status
= IOServiceGetMatchingServices(masterPort
, match
, &drivelist
);
678 if (status
!= KERN_SUCCESS
)
682 * Scan all of the IOMedia objects, and count each
683 * object that has a parent IOBlockStorageDriver
685 * XXX What about RAID devices?
689 while ((drive
= IOIteratorNext(drivelist
)))
691 /* get drive's parent */
692 status
= IORegistryEntryGetParentEntry(drive
,
693 kIOServicePlane
, &parent
);
694 if (status
!= KERN_SUCCESS
)
696 IOObjectRelease(drive
);
700 if (IOObjectConformsTo(parent
, "IOBlockStorageDriver"))
705 IOObjectRelease(parent
);
706 IOObjectRelease(drive
);
709 IOObjectRelease(drivelist
);
716 * When getting the stats, do it in the order
717 * of their type. The types that have the most
718 * data come first in the list if possible.
719 * This makes the sar reporter tool more efficient,
720 * because in some cases, it will allocate a buffer
721 * and keep reusing it as long as the sample data fits.
722 * When a sample data doesn't fit, it reallocates the buffer
723 * to a bigger size etc.
729 get_drivestat_sample();
730 get_netstat_sample(network_mode
);
737 * An internal table maps the BSDName to a unique ioregistry path.
738 * The table's index is then used as a unique compressed path, and
739 * helps track disks that come and go during the sampling intervals.
740 * This routine finds an entry that maps both the BSDName and the
741 * IOKit registry path. If no mapping is discovered, a new entry
742 * is created. An entry is never removed, this maintaining the
743 * unique index throughout the data collection.
744 * Success returns the map index. Failure returns -1.
747 check_device_path (char *name
, char *path
, int ndrives
)
753 if (dp_table
== NULL
)
755 /* First setup of internal drivepath table */
756 dp_table
= (struct drivepath
*)malloc (ndrives
* sizeof(struct drivepath
));
757 if (dp_table
== NULL
)
761 bzero(dp_table
, (ndrives
* sizeof(struct drivepath
)));
763 drivepath_record
.rec_size
= sizeof(struct drivepath
);
767 for (i
=0; i
< dp_count
; i
++)
769 if (dp_table
[i
].state
== DPSTATE_UNINITIALIZED
)
771 /* This is a new drive entry that should be recorded */
775 else if (!strcmp (dp_table
[i
].ioreg_path
, path
))
777 /* Found a matching hardware path */
778 if (!strcmp(dp_table
[i
].BSDName
, name
))
780 /* The BSDName matches the entry in the table
781 * so there is no need to record this data.
787 /* The BSDName is different ... implies a change,
788 * like the drive was removed and now is back
790 bzero((char *)dp_table
[i
].BSDName
, MAXDRIVENAME
+1);
791 dp_table
[i
].drivepath_id
= i
;
792 dp_table
[i
].state
= DPSTATE_CHANGED
;
793 strcpy(dp_table
[i
].BSDName
, name
);
794 write_record_hdr(&drivepath_record
);
795 write_record_data((char *)&dp_table
[i
], sizeof(struct drivepath
));
802 * If we reach this point, then we've run out of
803 * table entries. Double the size of the table.
806 dp_table
= (struct drivepath
*)realloc(dp_table
, n
* sizeof(struct drivepath
));
807 bzero(&dp_table
[dp_count
], dp_count
* sizeof(struct drivepath
));
811 /* This is a new drive entry that should be recorded */
813 dp_table
[index
].drivepath_id
= index
;
814 dp_table
[index
].state
= DPSTATE_NEW
;
815 strcpy(dp_table
[index
].BSDName
, name
);
816 strcpy(dp_table
[index
].ioreg_path
, path
);
817 write_record_hdr(&drivepath_record
);
818 write_record_data((char *)&dp_table
[index
], sizeof(struct drivepath
));
824 * success - returns 1
825 * failure - returns 0
831 char errbuf
[_POSIX2_LINE_MAX
];
835 * Initialize the kvm descriptor and get the location of _ifnet in
836 * preparation for gathering network statistics.
838 * We become root again momentarily so that we have permission to
843 fprintf(stderr
, "sar: root privleges denied\n");
847 kvmd
= kvm_openfiles(NULL
, NULL
, NULL
, O_RDONLY
, errbuf
);
851 fprintf(stderr
, "sar: error in kvm_openfiles(): %s", errbuf
);
855 nlist_net
[0].n_name
= "_ifnet";
856 nlist_net
[1].n_name
= NULL
;
857 if (kvm_nlist(kvmd
, nlist_net
) < 0) {
858 fprintf(stderr
,"sar: error in kvm_nlist(): %s", kvm_geterr(kvmd
));
862 if (nlist_net
[0].n_type
== N_UNDF
) {
863 fprintf(stderr
, "sadc: No nlist for _ifnet");
871 /* Read data from kernel memory. */
873 kread(u_long addr
, void *buf
, size_t nbytes
)
877 if (kvm_read(kvmd
, addr
, buf
, nbytes
) != (ssize_t
)nbytes
) {
878 fprintf(stderr
, "sadc: error in kvm_read(): %s\n", kvm_geterr(kvmd
));
887 * Thus far, only the networking stats take an optional flag
888 * to modify the collection of data. The number of ppp
889 * interfaces can be very high, causing the raw data file to
890 * grow very large. We want this option to include ppp
891 * statistics to be off by default. When we see the -m PPP
892 * mode passed in, ppp collection will be turned on.
895 get_netstat_sample(int mode
)
901 struct ifnethead ifnethead
;
903 char tname
[MAX_TNAME_SIZE
+ 1];
904 char name
[MAX_TNAME_UNIT_SIZE
+ 1];
906 if (ns_table
== NULL
)
908 /* this is our first sample -- do some init */
910 /* if kvm_init fails, we don't retry */
911 if (kvm_init_failed
|| !kvm_init())
918 * Set the starting table size to 100 entries
919 * That should be big enough for most cases,
920 * even with a lot of ppp connections.
923 ns_table
= (struct netstats
*) malloc(ns_count
* sizeof (struct netstats
));
924 if (ns_table
== NULL
)
926 fprintf(stderr
, "sadc: malloc netstat table failed\n");
931 bzero(ns_table
, ns_count
* sizeof(struct netstats
));
932 if (nlist_net
[0].n_value
!= 0
933 && kread(nlist_net
[0].n_value
, &ifnethead
, sizeof(ifnethead
)) == 0)
935 for (ns_index
= 0, off
= (u_long
)ifnethead
.tqh_first
;
937 off
= (u_long
)ifnet
.if_link
.tqe_next
)
939 if (kread(off
, &ifnet
, sizeof(ifnet
)))
943 if (kread((u_long
)ifnet
.if_name
, tname
, sizeof(tname
)))
947 tname
[MAX_TNAME_SIZE
] = '\0';
948 if (!(network_mode
& NET_PPP_MODE
))
951 * If the flag is set, include PPP connections.
952 * By default this collection is turned off
954 if(!strncmp(tname
, "ppp", 3))
957 snprintf(name
, MAX_TNAME_UNIT_SIZE
, "%s%d", tname
, ifnet
.if_unit
);
958 name
[MAX_TNAME_UNIT_SIZE
] = '\0';
960 if (ns_index
== ns_count
)
962 /* the stat table needs to grow */
964 ns_table
= (struct netstats
*)realloc(ns_table
, n
* sizeof(struct netstats
));
965 bzero(&ns_table
[ns_count
], ns_count
* sizeof(struct netstats
));
971 * As a means of helping to identify when interface unit numbers
972 * are reused, a generation counter may eventually be implemented.
973 * This will be especially helpful with ppp-x connections.
974 * In anticipation, we will reserve a space for it, but always
975 * set it to zero for now.
977 ns_table
[ns_index
].gen_counter
= 0;
979 strncpy(ns_table
[ns_index
].tname_unit
, name
, MAX_TNAME_UNIT_SIZE
);
980 ns_table
[ns_index
].tname_unit
[MAX_TNAME_UNIT_SIZE
] = '\0';
981 ns_table
[ns_index
].net_ipackets
= ifnet
.if_ipackets
;
982 ns_table
[ns_index
].net_ierrors
= ifnet
.if_ierrors
;
983 ns_table
[ns_index
].net_opackets
= ifnet
.if_opackets
;
984 ns_table
[ns_index
].net_oerrors
= ifnet
.if_oerrors
;
985 ns_table
[ns_index
].net_collisions
= ifnet
.if_collisions
;
986 ns_table
[ns_index
].net_ibytes
= ifnet
.if_ibytes
;
987 ns_table
[ns_index
].net_obytes
= ifnet
.if_obytes
;
988 ns_table
[ns_index
].net_imcasts
= ifnet
.if_imcasts
;
989 ns_table
[ns_index
].net_omcasts
= ifnet
.if_omcasts
;
990 ns_table
[ns_index
].net_drops
= ifnet
.if_snd
.ifq_drops
;
994 netstats_record
.rec_count
= ns_index
;
995 netstats_record
.rec_size
= sizeof(struct netstats
);
996 write_record_hdr(&netstats_record
);
997 write_record_data((char *)ns_table
, (ns_index
* sizeof(struct netstats
)));