2 * Portions Copyright (c) 1999-2003 Apple Computer, Inc. All Rights
5 * This file contains Original Code and/or Modifications of Original Code
6 * as defined in and that are subject to the Apple Public Source License
7 * Version 2.0 (the 'License'). You may not use this file except in
8 * compliance with the License. Please obtain a copy of the License at
9 * http://www.opensource.apple.com/apsl/ and read it before using this
12 * The Original Code and all software distributed under the License are
13 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
14 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
15 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
17 * Please see the License for the specific language governing rights and
18 * limitations under the License.
21 #define IOKIT 1 /* to get io_name_t in device_types.h */
32 #include <mach/mach.h>
33 #include <mach/mach_error.h>
34 #include <sys/param.h>
36 #include <CoreFoundation/CoreFoundation.h>
37 #include <IOKit/IOKitLib.h>
38 #include <IOKit/storage/IOBlockStorageDriver.h>
39 #include <IOKit/storage/IOMedia.h>
40 #include <IOKit/IOBSD.h>
42 #include <sys/socket.h>
44 #include <net/if_var.h>
51 FILE *data_fp
= (FILE *)0; /* raw data output file pointer */
54 #define REVISION_HISTORY_DATE 20030718
56 struct record_hdr restart_record
= { SAR_RESTART
, REVISION_HISTORY_DATE
, 0, 0 };
57 struct record_hdr timestamp_record
= { SAR_TIMESTAMP
, 1, 0, 0 };
58 struct record_hdr vmstat_record
= {SAR_VMSTAT
, 1, 1, 0 };
59 struct record_hdr cpu_record
= {SAR_CPU
, 1, 1, 0 };
60 struct record_hdr drivestats_record
= {SAR_DRIVESTATS
, 1, 0, 0 };
61 struct record_hdr drivepath_record
= {SAR_DRIVEPATH
, 1, 1, 0 };
62 struct record_hdr netstats_record
= {SAR_NETSTATS
, 1, 0, 0};
64 /* Compile for verbose output */
66 int t_interval
= 0; /* in seconds */
67 int n_samples
= 1; /* number of sample loops */
68 char *ofile
= NULL
; /* output file */
69 int ofd
; /* output file descriptor */
70 static mach_port_t myHost
;
71 static mach_port_t masterPort
;
73 /* internal table of drive path mappings */
74 struct drivepath
*dp_table
= NULL
;
76 /* number of entries in the dp_table */
79 /* internal table of network interface statistics */
80 struct netstats
*ns_table
= NULL
;
87 /* Forward fuction declarations */
88 static void exit_usage();
89 static void open_datafile(char *);
90 static void write_record_hdr(struct record_hdr
*);
91 static void write_record_data(char *, int);
92 static void get_all_stats();
93 static void get_vmstat_sample();
94 static void get_drivestat_sample();
95 static int get_ndrives();
96 static int record_device(io_registry_entry_t
, struct drivestats
*, int ndrives
);
97 static int check_device_path (char *name
, char *path
, int ndrives
);
98 static void get_netstat_sample(int pppflag
);
110 * Stop being root ASAP.
114 fprintf(stderr
, "sadc: must be setuid root or root");
121 setvbuf(stdout
, (char *)NULL
, _IONBF
, 0);
123 while ((ch
=getopt(argc
, argv
, "m:")) != EOF
) {
126 /* Only the PPP mode matters on this collector side */
127 /* The reporter side deals with the DEV or EDEV modes */
128 if (!strncmp(optarg
, "PPP", 3))
129 network_mode
|= NET_PPP_MODE
;
140 if (isdigit(*argv
[optind
]))
142 /* we expect to have both an interval and a sample count */
144 t_interval
= strtol(argv
[optind
], &p
, 0);
145 if (errno
|| (*p
!='\0') || t_interval
<= 0)
151 if ((argc
< 2) || (!isdigit(*argv
[optind
]))) {
156 n_samples
= strtol(argv
[optind
], &p
, 0);
157 if (errno
|| (*p
!= '\0') || n_samples
<= 0)
165 /* we have an output file */
166 ofile
= argv
[optind
];
171 /* all we have is an output file */
172 ofile
= argv
[optind
];
177 /* open the output file */
178 (void)open_datafile(ofile
);
181 * Get the Mach private port.
183 myHost
= mach_host_self();
186 * Get the I/O Kit communication handle.
188 IOMasterPort(bootstrap_port
, &masterPort
);
191 restart_record
.rec_timestamp
= time((time_t *)0);
192 write_record_hdr(&restart_record
);
193 get_all_stats(); /* this is the initial stat collection */
198 /* this init sample is not counted */
199 timestamp_record
.rec_data
= time((time_t *)0); /* returns time in
203 tm
= gmtime(&(timestamp_record
.rec_data
));
204 fprintf(stderr
, "timestamp=%ld\n", timestamp_record
.rec_data
);
205 fprintf(stderr
, "GMTIME offset from UTC in seconds = %ld\n", tm
->tm_gmtoff
);
206 fprintf(stderr
, "GMTIME secnds=%d, min=%d, hour=%d\n", tm
->tm_sec
, tm
->tm_min
, tm
->tm_hour
);
207 fprintf(stderr
, "asctime = %s\n", asctime(tm
));
209 tm
=localtime(&(timestamp_record
.rec_data
));
210 fprintf(stderr
, "LOCTIME offset from UTC in seconds = %ld\n",tm
->tm_gmtoff
);
211 fprintf(stderr
, "LOCTIME secnds=%d, min=%d, hour=%d\n", tm
->tm_sec
, tm
->tm_min
, tm
->tm_hour
);
212 fprintf(stderr
, "asctime = %s\n", asctime(tm
));
215 write_record_hdr(×tamp_record
);
222 timestamp_record
.rec_timestamp
= time((time_t *)0); /* returns time in
224 write_record_hdr(×tamp_record
);
234 fprintf(stderr
, "/usr/lib/sa/sadc [-m {PPP}] [t n] [ofile]\n");
239 open_datafile(char *path
)
247 data_fp
= fopen(path
, "w+");
251 /* failed to open path */
252 fprintf(stderr
, "sadc: failed to open data file [%s]\n", path
?path
:"stdout");
258 write_record_hdr(hdr
)
259 struct record_hdr
*hdr
;
263 if (fwrite(hdr
, sizeof(struct record_hdr
), 1, data_fp
) != 1)
265 fprintf(stderr
, "sadc: write_record_hdr failed, errno=%d\n", errno
);
274 write_record_data(data
, size
)
280 if (fwrite(data
, size
, 1, data_fp
) != 1)
282 fprintf(stderr
, "sadc: write_record_data failed, errno=%d\n", errno
);
294 struct vm_statistics stat
;
296 mach_msg_type_number_t count
;
298 count
= HOST_VM_INFO_COUNT
;
299 error
= host_statistics(myHost
, HOST_VM_INFO
, (host_info_t
)&stat
, &count
);
300 if (error
!= KERN_SUCCESS
) {
301 fprintf(stderr
, "sadc: Error in vm host_statistics(): %s\n",
302 mach_error_string(error
));
306 vmstat_record
.rec_count
= 1;
307 vmstat_record
.rec_size
= sizeof(vm_statistics_data_t
);
308 write_record_hdr(&vmstat_record
);
309 write_record_data((char *)&stat
, sizeof(vm_statistics_data_t
));
315 host_cpu_load_info_data_t cpuload
;
317 mach_msg_type_number_t count
;
319 count
= HOST_CPU_LOAD_INFO_COUNT
;
320 error
= host_statistics(myHost
, HOST_CPU_LOAD_INFO
,(host_info_t
)&cpuload
, &count
);
321 if (error
!= KERN_SUCCESS
) {
322 fprintf(stderr
, "sadc: Error in cpu host_statistics(): %s",
323 mach_error_string(error
));
327 cpu_record
.rec_count
= 1;
328 cpu_record
.rec_size
= sizeof(host_cpu_load_info_data_t
);
329 write_record_hdr(&cpu_record
);
330 write_record_data((char *)&cpuload
, sizeof(host_cpu_load_info_data_t
));
334 get_drivestat_sample()
336 io_registry_entry_t drive
;
337 io_iterator_t drivelist
;
338 CFMutableDictionaryRef match
;
343 struct drivestats
*dbuf
;
344 kern_return_t status
;
347 if ((ndrives
= get_ndrives()) <= 0)
350 /* allocate space to collect stats for all the drives */
351 bufsize
= ndrives
* sizeof(struct drivestats
);
352 buf
= (char *) malloc (bufsize
);
353 dbuf
= (struct drivestats
*)buf
;
355 bzero((char *)buf
, bufsize
);
360 * Get an iterator for IOMedia objects.
362 match
= IOServiceMatching("IOMedia");
364 /* Get whole disk info */
365 CFDictionaryAddValue(match
, CFSTR(kIOMediaWholeKey
), kCFBooleanTrue
);
367 status
= IOServiceGetMatchingServices(masterPort
, match
, &drivelist
);
368 if (status
!= KERN_SUCCESS
)
372 * Scan all of the IOMedia objects, and for each
373 * object that has a parent IOBlockStorageDriver,
374 * record the statistics
376 * XXX What about RAID devices?
380 while ((drive
= IOIteratorNext(drivelist
)))
384 if (record_device(drive
, &dbuf
[i
], ndrives
))
392 IOObjectRelease(drive
);
395 IOObjectRelease(drive
);
397 IOObjectRelease(drivelist
);
401 drivestats_record
.rec_count
= i
;
402 drivestats_record
.rec_size
= sizeof (struct drivestats
);
403 write_record_hdr(&drivestats_record
);
404 write_record_data((char *)buf
, (i
* sizeof(struct drivestats
)));
414 * Determine whether an IORegistryEntry refers to a valid
415 * I/O device, and if so, record it.
416 * Return zero: no device recorded
417 * Return non-zero: device stats recorded
420 record_device(io_registry_entry_t drive
, struct drivestats
* drivestat
, int ndrives
)
422 io_registry_entry_t parent
;
423 CFDictionaryRef properties
, statistics
;
427 kern_return_t status
;
431 char BSDName
[MAXDRIVENAME
+ 1];
433 status
= IORegistryEntryGetParentEntry(drive
, kIOServicePlane
, &parent
);
434 if (status
!= KERN_SUCCESS
)
436 /* device has no parent */
440 if (IOObjectConformsTo(parent
, "IOBlockStorageDriver"))
443 * Get a unique device path identifier.
444 * Devices available at boot have an Open Firmware Device Tree path.
445 * The OF path is short and concise and should be first choice.
446 * Devices that show up after boot, are guaranteed to have
447 * a Service Plane, hardware unique path.
450 bzero(path
, sizeof(io_string_t
));
451 if (IORegistryEntryGetPath(drive
, kIODeviceTreePlane
, path
) != KERN_SUCCESS
)
453 if(IORegistryEntryGetPath(drive
, kIOServicePlane
, path
) != KERN_SUCCESS
)
454 /* device has no unique path identifier */
459 /* get drive properties */
460 status
= IORegistryEntryCreateCFProperties(drive
,
461 (CFMutableDictionaryRef
*)&properties
,
464 if (status
!= KERN_SUCCESS
)
466 /* device has no properties */
470 bzero(BSDName
, MAXDRIVENAME
+1);
471 /* get name from properties */
472 name
= (CFStringRef
)CFDictionaryGetValue(properties
,
473 CFSTR(kIOBSDNameKey
));
475 CFStringGetCString(name
, BSDName
,
476 MAXDRIVENAME
, CFStringGetSystemEncoding());
480 /* get blocksize from properties */
481 number
= (CFNumberRef
)CFDictionaryGetValue(properties
,
482 CFSTR(kIOMediaPreferredBlockSizeKey
));
484 CFNumberGetValue(number
,
485 kCFNumberSInt64Type
, &value
);
486 drivestat
->blocksize
= value
;
489 CFRelease(properties
);
494 /* we should have a name and blocksize at a minimum */
501 drive_id
= check_device_path (BSDName
, path
, ndrives
);
508 drivestat
->drivepath_id
= drive_id
;
511 /* get parent drive properties */
512 status
= IORegistryEntryCreateCFProperties(parent
,
513 (CFMutableDictionaryRef
*)&properties
,
516 if (status
!= KERN_SUCCESS
)
518 /* device has no properties */
522 /* Obtain the statistics from the parent drive properties. */
525 = (CFDictionaryRef
)CFDictionaryGetValue(properties
,
526 CFSTR(kIOBlockStorageDriverStatisticsKey
));
530 /* Get number of reads. */
532 (CFNumberRef
)CFDictionaryGetValue(statistics
,
533 CFSTR(kIOBlockStorageDriverStatisticsReadsKey
));
535 CFNumberGetValue(number
,
536 kCFNumberSInt64Type
, &value
);
537 drivestat
->Reads
= value
;
540 /* Get bytes read. */
542 (CFNumberRef
)CFDictionaryGetValue(statistics
,
543 CFSTR(kIOBlockStorageDriverStatisticsBytesReadKey
));
545 CFNumberGetValue(number
, kCFNumberSInt64Type
, &value
);
546 drivestat
->BytesRead
= value
;
549 /* Get number of writes. */
551 (CFNumberRef
)CFDictionaryGetValue(statistics
,
552 CFSTR(kIOBlockStorageDriverStatisticsWritesKey
));
554 CFNumberGetValue(number
, kCFNumberSInt64Type
, &value
);
555 drivestat
->Writes
= value
;
558 /* Get bytes written. */
560 (CFNumberRef
)CFDictionaryGetValue(statistics
,
561 CFSTR(kIOBlockStorageDriverStatisticsBytesWrittenKey
));
563 CFNumberGetValue(number
, kCFNumberSInt64Type
, &value
);
564 drivestat
->BytesWritten
= value
;
567 /* Get LatentReadTime. */
569 (CFNumberRef
)CFDictionaryGetValue(statistics
,
570 CFSTR(kIOBlockStorageDriverStatisticsLatentReadTimeKey
));
572 CFNumberGetValue(number
, kCFNumberSInt64Type
, &value
);
573 drivestat
->LatentReadTime
= value
;
576 /* Get LatentWriteTime. */
578 (CFNumberRef
)CFDictionaryGetValue(statistics
,
579 CFSTR(kIOBlockStorageDriverStatisticsLatentWriteTimeKey
));
581 CFNumberGetValue(number
, kCFNumberSInt64Type
, &value
);
582 drivestat
->LatentWriteTime
= value
;
585 /* Get ReadErrors. */
587 (CFNumberRef
)CFDictionaryGetValue(statistics
,
588 CFSTR(kIOBlockStorageDriverStatisticsReadErrorsKey
));
590 CFNumberGetValue(number
, kCFNumberSInt64Type
, &value
);
591 drivestat
->ReadErrors
= value
;
594 /* Get WriteErrors. */
596 (CFNumberRef
)CFDictionaryGetValue(statistics
,
597 CFSTR(kIOBlockStorageDriverStatisticsWriteErrorsKey
));
599 CFNumberGetValue(number
, kCFNumberSInt64Type
, &value
);
600 drivestat
->WriteErrors
= value
;
603 /* Get ReadRetries. */
605 (CFNumberRef
)CFDictionaryGetValue(statistics
,
606 CFSTR(kIOBlockStorageDriverStatisticsReadRetriesKey
));
608 CFNumberGetValue(number
, kCFNumberSInt64Type
, &value
);
609 drivestat
->ReadRetries
= value
;
612 /* Get WriteRetries. */
614 (CFNumberRef
)CFDictionaryGetValue(statistics
,
615 CFSTR(kIOBlockStorageDriverStatisticsWriteRetriesKey
));
617 CFNumberGetValue(number
, kCFNumberSInt64Type
, &value
);
618 drivestat
->WriteRetries
= value
;
621 /* Get TotalReadTime. */
623 (CFNumberRef
)CFDictionaryGetValue(statistics
,
624 CFSTR(kIOBlockStorageDriverStatisticsTotalReadTimeKey
));
626 CFNumberGetValue(number
, kCFNumberSInt64Type
, &value
);
627 drivestat
->TotalReadTime
= value
;
630 /* Get WriteRetries. */
632 (CFNumberRef
)CFDictionaryGetValue(statistics
,
633 CFSTR(kIOBlockStorageDriverStatisticsTotalWriteTimeKey
));
635 CFNumberGetValue(number
, kCFNumberSInt64Type
, &value
);
636 drivestat
->TotalWriteTime
= value
;
639 CFRelease(properties
);
640 } /* end if statistics != 0 */
643 IOObjectRelease(parent
);
649 * find IOMedia objects
650 * This routine always gives me a lower count on the number
651 * of disks. I don't know which one to use.
656 io_iterator_t drivelist
;
657 io_registry_entry_t drive
;
658 io_registry_entry_t parent
;
659 CFMutableDictionaryRef match
;
661 kern_return_t status
;
664 * Get an iterator for IOMedia objects.
666 match
= IOServiceMatching("IOMedia");
667 CFDictionaryAddValue(match
, CFSTR(kIOMediaWholeKey
), kCFBooleanTrue
);
668 status
= IOServiceGetMatchingServices(masterPort
, match
, &drivelist
);
669 if (status
!= KERN_SUCCESS
)
673 * Scan all of the IOMedia objects, and count each
674 * object that has a parent IOBlockStorageDriver
676 * XXX What about RAID devices?
680 while ((drive
= IOIteratorNext(drivelist
)))
682 /* get drive's parent */
683 status
= IORegistryEntryGetParentEntry(drive
,
684 kIOServicePlane
, &parent
);
685 if (status
!= KERN_SUCCESS
)
687 IOObjectRelease(drive
);
691 if (IOObjectConformsTo(parent
, "IOBlockStorageDriver"))
696 IOObjectRelease(parent
);
697 IOObjectRelease(drive
);
700 IOObjectRelease(drivelist
);
707 * When getting the stats, do it in the order
708 * of their type. The types that have the most
709 * data come first in the list if possible.
710 * This makes the sar reporter tool more efficient,
711 * because in some cases, it will allocate a buffer
712 * and keep reusing it as long as the sample data fits.
713 * When a sample data doesn't fit, it reallocates the buffer
714 * to a bigger size etc.
720 get_drivestat_sample();
721 get_netstat_sample(network_mode
);
728 * An internal table maps the BSDName to a unique ioregistry path.
729 * The table's index is then used as a unique compressed path, and
730 * helps track disks that come and go during the sampling intervals.
731 * This routine finds an entry that maps both the BSDName and the
732 * IOKit registry path. If no mapping is discovered, a new entry
733 * is created. An entry is never removed, this maintaining the
734 * unique index throughout the data collection.
735 * Success returns the map index. Failure returns -1.
738 check_device_path (char *name
, char *path
, int ndrives
)
744 if (dp_table
== NULL
)
746 /* First setup of internal drivepath table */
747 dp_table
= (struct drivepath
*)malloc (ndrives
* sizeof(struct drivepath
));
748 if (dp_table
== NULL
)
752 bzero(dp_table
, (ndrives
* sizeof(struct drivepath
)));
754 drivepath_record
.rec_size
= sizeof(struct drivepath
);
758 for (i
=0; i
< dp_count
; i
++)
760 if (dp_table
[i
].state
== DPSTATE_UNINITIALIZED
)
762 /* This is a new drive entry that should be recorded */
766 else if (!strcmp (dp_table
[i
].ioreg_path
, path
))
768 /* Found a matching hardware path */
769 if (!strcmp(dp_table
[i
].BSDName
, name
))
771 /* The BSDName matches the entry in the table
772 * so there is no need to record this data.
778 /* The BSDName is different ... implies a change,
779 * like the drive was removed and now is back
781 bzero((char *)dp_table
[i
].BSDName
, MAXDRIVENAME
+1);
782 dp_table
[i
].drivepath_id
= i
;
783 dp_table
[i
].state
= DPSTATE_CHANGED
;
784 strcpy(dp_table
[i
].BSDName
, name
);
785 write_record_hdr(&drivepath_record
);
786 write_record_data((char *)&dp_table
[i
], sizeof(struct drivepath
));
793 * If we reach this point, then we've run out of
794 * table entries. Double the size of the table.
797 dp_table
= (struct drivepath
*)realloc(dp_table
, n
* sizeof(struct drivepath
));
798 bzero(&dp_table
[dp_count
], dp_count
* sizeof(struct drivepath
));
802 /* This is a new drive entry that should be recorded */
804 dp_table
[index
].drivepath_id
= index
;
805 dp_table
[index
].state
= DPSTATE_NEW
;
806 strcpy(dp_table
[index
].BSDName
, name
);
807 strcpy(dp_table
[index
].ioreg_path
, path
);
808 write_record_hdr(&drivepath_record
);
809 write_record_data((char *)&dp_table
[index
], sizeof(struct drivepath
));
816 * Thus far, only the networking stats take an optional flag
817 * to modify the collection of data. The number of ppp
818 * interfaces can be very high, causing the raw data file to
819 * grow very large. We want this option to include ppp
820 * statistics to be off by default. When we see the -m PPP
821 * mode passed in, ppp collection will be turned on.
824 get_netstat_sample(int mode
)
829 char tname
[MAX_TNAME_SIZE
+ 1];
830 char name
[MAX_TNAME_UNIT_SIZE
+ 1];
831 struct ifaddrs
*ifa_list
, *ifa
;
835 * Set the starting table size to 100 entries
836 * That should be big enough for most cases,
837 * even with a lot of ppp connections.
840 ns_table
= (struct netstats
*) malloc(ns_count
* sizeof (struct netstats
));
841 if (ns_table
== NULL
)
843 fprintf(stderr
, "sadc: malloc netstat table failed\n");
847 bzero(ns_table
, ns_count
* sizeof(struct netstats
));
848 if (getifaddrs(&ifa_list
) == -1)
851 for (ifa
= ifa_list
; ifa
; ifa
= ifa
->ifa_next
)
853 struct if_data
*if_data
= (struct if_data
*)ifa
->ifa_data
;
855 if (AF_LINK
!= ifa
->ifa_addr
->sa_family
)
857 if (ifa
->ifa_data
== 0)
859 tname
[MAX_TNAME_SIZE
] = '\0';
860 if (!(network_mode
& NET_PPP_MODE
))
863 * If the flag is set, include PPP connections.
864 * By default this collection is turned off
866 if(!strncmp(ifa
->ifa_name
, "ppp", 3))
869 snprintf(name
, MAX_TNAME_UNIT_SIZE
, "%s", ifa
->ifa_name
);
870 name
[MAX_TNAME_UNIT_SIZE
] = '\0';
872 if (ns_index
== ns_count
)
874 /* the stat table needs to grow */
876 ns_table
= (struct netstats
*)realloc(ns_table
, n
* sizeof(struct netstats
));
877 bzero(&ns_table
[ns_count
], ns_count
* sizeof(struct netstats
));
882 * As a means of helping to identify when interface unit numbers
883 * are reused, a generation counter may eventually be implemented.
884 * This will be especially helpful with ppp-x connections.
885 * In anticipation, we will reserve a space for it, but always
886 * set it to zero for now.
888 ns_table
[ns_index
].gen_counter
= 0;
890 strncpy(ns_table
[ns_index
].tname_unit
, name
, MAX_TNAME_UNIT_SIZE
);
891 ns_table
[ns_index
].tname_unit
[MAX_TNAME_UNIT_SIZE
] = '\0';
892 ns_table
[ns_index
].net_ipackets
= if_data
->ifi_ipackets
;
893 ns_table
[ns_index
].net_ierrors
= if_data
->ifi_ierrors
;
894 ns_table
[ns_index
].net_opackets
= if_data
->ifi_opackets
;
895 ns_table
[ns_index
].net_oerrors
= if_data
->ifi_oerrors
;
896 ns_table
[ns_index
].net_collisions
= if_data
->ifi_collisions
;
897 ns_table
[ns_index
].net_ibytes
= if_data
->ifi_ibytes
;
898 ns_table
[ns_index
].net_obytes
= if_data
->ifi_obytes
;
899 ns_table
[ns_index
].net_imcasts
= if_data
->ifi_imcasts
;
900 ns_table
[ns_index
].net_omcasts
= if_data
->ifi_omcasts
;
901 ns_table
[ns_index
].net_drops
= if_data
->ifi_iqdrops
;
905 netstats_record
.rec_count
= ns_index
;
906 netstats_record
.rec_size
= sizeof(struct netstats
);
907 write_record_hdr(&netstats_record
);
908 write_record_data((char *)ns_table
, (ns_index
* sizeof(struct netstats
)));