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 */
31 #include <mach/mach.h>
32 #include <mach/mach_error.h>
33 #include <sys/param.h>
35 #include <CoreFoundation/CoreFoundation.h>
36 #include <IOKit/IOKitLib.h>
37 #include <IOKit/storage/IOBlockStorageDriver.h>
38 #include <IOKit/storage/IOMedia.h>
39 #include <IOKit/IOBSD.h>
41 #include <sys/socket.h>
43 #include <net/if_var.h>
50 FILE *data_fp
= (FILE *)0; /* raw data output file pointer */
53 #define REVISION_HISTORY_DATE 20030718
55 struct record_hdr restart_record
= { SAR_RESTART
, REVISION_HISTORY_DATE
, 0, 0 };
56 struct record_hdr timestamp_record
= { SAR_TIMESTAMP
, 1, 0, 0 };
57 struct record_hdr vmstat_record
= {SAR_VMSTAT
, 1, 1, 0 };
58 struct record_hdr cpu_record
= {SAR_CPU
, 1, 1, 0 };
59 struct record_hdr drivestats_record
= {SAR_DRIVESTATS
, 1, 0, 0 };
60 struct record_hdr drivepath_record
= {SAR_DRIVEPATH
, 1, 1, 0 };
61 struct record_hdr netstats_record
= {SAR_NETSTATS
, 1, 0, 0};
63 /* Compile for verbose output */
65 int t_interval
= 0; /* in seconds */
66 int n_samples
= 1; /* number of sample loops */
67 char *ofile
= NULL
; /* output file */
68 int ofd
; /* output file descriptor */
69 static mach_port_t myHost
;
70 static mach_port_t masterPort
;
72 /* internal table of drive path mappings */
73 struct drivepath
*dp_table
= NULL
;
75 /* number of entries in the dp_table */
78 /* internal table of network interface statistics */
79 struct netstats
*ns_table
= NULL
;
86 /* Forward fuction declarations */
87 static void exit_usage();
88 static void open_datafile(char *);
89 static void write_record_hdr(struct record_hdr
*);
90 static void write_record_data(char *, int);
91 static void get_all_stats();
92 static void get_vmstat_sample();
93 static void get_drivestat_sample();
94 static int get_ndrives();
95 static int record_device(io_registry_entry_t
, struct drivestats
*, int ndrives
);
96 static int check_device_path (char *name
, char *path
, int ndrives
);
97 static void get_netstat_sample(int pppflag
);
109 * Stop being root ASAP.
113 fprintf(stderr
, "sadc: must be setuid root or root");
120 setvbuf(stdout
, (char *)NULL
, _IONBF
, 0);
122 while ((ch
=getopt(argc
, argv
, "m:")) != EOF
) {
125 /* Only the PPP mode matters on this collector side */
126 /* The reporter side deals with the DEV or EDEV modes */
127 if (!strncmp(optarg
, "PPP", 3))
128 network_mode
|= NET_PPP_MODE
;
139 if (isdigit(*argv
[optind
]))
141 /* we expect to have both an interval and a sample count */
143 t_interval
= strtol(argv
[optind
], &p
, 0);
144 if (errno
|| (*p
!='\0') || t_interval
<= 0)
150 if ((argc
< 2) || (!isdigit(*argv
[optind
]))) {
155 n_samples
= strtol(argv
[optind
], &p
, 0);
156 if (errno
|| (*p
!= '\0') || n_samples
<= 0)
164 /* we have an output file */
165 ofile
= argv
[optind
];
170 /* all we have is an output file */
171 ofile
= argv
[optind
];
176 /* open the output file */
177 (void)open_datafile(ofile
);
180 * Get the Mach private port.
182 myHost
= mach_host_self();
185 * Get the I/O Kit communication handle.
187 IOMasterPort(bootstrap_port
, &masterPort
);
190 restart_record
.rec_timestamp
= time((time_t *)0);
191 write_record_hdr(&restart_record
);
192 get_all_stats(); /* this is the initial stat collection */
197 /* this init sample is not counted */
198 timestamp_record
.rec_data
= time((time_t *)0); /* returns time in
202 tm
= gmtime(&(timestamp_record
.rec_data
));
203 fprintf(stderr
, "timestamp=%ld\n", timestamp_record
.rec_data
);
204 fprintf(stderr
, "GMTIME offset from UTC in seconds = %ld\n", tm
->tm_gmtoff
);
205 fprintf(stderr
, "GMTIME secnds=%d, min=%d, hour=%d\n", tm
->tm_sec
, tm
->tm_min
, tm
->tm_hour
);
206 fprintf(stderr
, "asctime = %s\n", asctime(tm
));
208 tm
=localtime(&(timestamp_record
.rec_data
));
209 fprintf(stderr
, "LOCTIME offset from UTC in seconds = %ld\n",tm
->tm_gmtoff
);
210 fprintf(stderr
, "LOCTIME secnds=%d, min=%d, hour=%d\n", tm
->tm_sec
, tm
->tm_min
, tm
->tm_hour
);
211 fprintf(stderr
, "asctime = %s\n", asctime(tm
));
214 write_record_hdr(×tamp_record
);
221 timestamp_record
.rec_timestamp
= time((time_t *)0); /* returns time in
223 write_record_hdr(×tamp_record
);
233 fprintf(stderr
, "/usr/lib/sa/sadc [-m {PPP}] [t n] [ofile]\n");
238 open_datafile(char *path
)
246 data_fp
= fopen(path
, "w+");
250 /* failed to open path */
251 fprintf(stderr
, "sadc: failed to open data file [%s]\n", path
?path
:"stdout");
257 write_record_hdr(hdr
)
258 struct record_hdr
*hdr
;
262 if (fwrite(hdr
, sizeof(struct record_hdr
), 1, data_fp
) != 1)
264 fprintf(stderr
, "sadc: write_record_hdr failed, errno=%d\n", errno
);
273 write_record_data(data
, size
)
279 if (fwrite(data
, size
, 1, data_fp
) != 1)
281 fprintf(stderr
, "sadc: write_record_data failed, errno=%d\n", errno
);
293 struct vm_statistics stat
;
295 mach_msg_type_number_t count
;
297 count
= HOST_VM_INFO_COUNT
;
298 error
= host_statistics(myHost
, HOST_VM_INFO
, (host_info_t
)&stat
, &count
);
299 if (error
!= KERN_SUCCESS
) {
300 fprintf(stderr
, "sadc: Error in vm host_statistics(): %s\n",
301 mach_error_string(error
));
305 vmstat_record
.rec_count
= 1;
306 vmstat_record
.rec_size
= sizeof(vm_statistics_data_t
);
307 write_record_hdr(&vmstat_record
);
308 write_record_data((char *)&stat
, sizeof(vm_statistics_data_t
));
314 host_cpu_load_info_data_t cpuload
;
316 mach_msg_type_number_t count
;
318 count
= HOST_CPU_LOAD_INFO_COUNT
;
319 error
= host_statistics(myHost
, HOST_CPU_LOAD_INFO
,(host_info_t
)&cpuload
, &count
);
320 if (error
!= KERN_SUCCESS
) {
321 fprintf(stderr
, "sadc: Error in cpu host_statistics(): %s",
322 mach_error_string(error
));
326 cpu_record
.rec_count
= 1;
327 cpu_record
.rec_size
= sizeof(host_cpu_load_info_data_t
);
328 write_record_hdr(&cpu_record
);
329 write_record_data((char *)&cpuload
, sizeof(host_cpu_load_info_data_t
));
333 get_drivestat_sample()
335 io_registry_entry_t drive
;
336 io_iterator_t drivelist
;
337 CFMutableDictionaryRef match
;
342 struct drivestats
*dbuf
;
343 kern_return_t status
;
346 if ((ndrives
= get_ndrives()) <= 0)
349 /* allocate space to collect stats for all the drives */
350 bufsize
= ndrives
* sizeof(struct drivestats
);
351 buf
= (char *) malloc (bufsize
);
352 dbuf
= (struct drivestats
*)buf
;
354 bzero((char *)buf
, bufsize
);
359 * Get an iterator for IOMedia objects.
361 match
= IOServiceMatching("IOMedia");
363 /* Get whole disk info */
364 CFDictionaryAddValue(match
, CFSTR(kIOMediaWholeKey
), kCFBooleanTrue
);
366 status
= IOServiceGetMatchingServices(masterPort
, match
, &drivelist
);
367 if (status
!= KERN_SUCCESS
)
371 * Scan all of the IOMedia objects, and for each
372 * object that has a parent IOBlockStorageDriver,
373 * record the statistics
375 * XXX What about RAID devices?
379 while ((drive
= IOIteratorNext(drivelist
)))
383 if (record_device(drive
, &dbuf
[i
], ndrives
))
391 IOObjectRelease(drive
);
394 IOObjectRelease(drive
);
396 IOObjectRelease(drivelist
);
400 drivestats_record
.rec_count
= i
;
401 drivestats_record
.rec_size
= sizeof (struct drivestats
);
402 write_record_hdr(&drivestats_record
);
403 write_record_data((char *)buf
, (i
* sizeof(struct drivestats
)));
413 * Determine whether an IORegistryEntry refers to a valid
414 * I/O device, and if so, record it.
415 * Return zero: no device recorded
416 * Return non-zero: device stats recorded
419 record_device(io_registry_entry_t drive
, struct drivestats
* drivestat
, int ndrives
)
421 io_registry_entry_t parent
;
422 CFDictionaryRef properties
, statistics
;
426 kern_return_t status
;
430 char BSDName
[MAXDRIVENAME
+ 1];
432 status
= IORegistryEntryGetParentEntry(drive
, kIOServicePlane
, &parent
);
433 if (status
!= KERN_SUCCESS
)
435 /* device has no parent */
439 if (IOObjectConformsTo(parent
, "IOBlockStorageDriver"))
442 * Get a unique device path identifier.
443 * Devices available at boot have an Open Firmware Device Tree path.
444 * The OF path is short and concise and should be first choice.
445 * Devices that show up after boot, are guaranteed to have
446 * a Service Plane, hardware unique path.
449 bzero(path
, sizeof(io_string_t
));
450 if (IORegistryEntryGetPath(drive
, kIODeviceTreePlane
, path
) != KERN_SUCCESS
)
452 if(IORegistryEntryGetPath(drive
, kIOServicePlane
, path
) != KERN_SUCCESS
)
453 /* device has no unique path identifier */
458 /* get drive properties */
459 status
= IORegistryEntryCreateCFProperties(drive
,
460 (CFMutableDictionaryRef
*)&properties
,
463 if (status
!= KERN_SUCCESS
)
465 /* device has no properties */
469 bzero(BSDName
, MAXDRIVENAME
+1);
470 /* get name from properties */
471 name
= (CFStringRef
)CFDictionaryGetValue(properties
,
472 CFSTR(kIOBSDNameKey
));
474 CFStringGetCString(name
, BSDName
,
475 MAXDRIVENAME
, kCFStringEncodingUTF8
);
479 /* get blocksize from properties */
480 number
= (CFNumberRef
)CFDictionaryGetValue(properties
,
481 CFSTR(kIOMediaPreferredBlockSizeKey
));
483 CFNumberGetValue(number
,
484 kCFNumberSInt64Type
, &value
);
485 drivestat
->blocksize
= value
;
488 CFRelease(properties
);
493 /* we should have a name and blocksize at a minimum */
500 drive_id
= check_device_path (BSDName
, path
, ndrives
);
507 drivestat
->drivepath_id
= drive_id
;
510 /* get parent drive properties */
511 status
= IORegistryEntryCreateCFProperties(parent
,
512 (CFMutableDictionaryRef
*)&properties
,
515 if (status
!= KERN_SUCCESS
)
517 /* device has no properties */
521 /* Obtain the statistics from the parent drive properties. */
524 = (CFDictionaryRef
)CFDictionaryGetValue(properties
,
525 CFSTR(kIOBlockStorageDriverStatisticsKey
));
529 /* Get number of reads. */
531 (CFNumberRef
)CFDictionaryGetValue(statistics
,
532 CFSTR(kIOBlockStorageDriverStatisticsReadsKey
));
534 CFNumberGetValue(number
,
535 kCFNumberSInt64Type
, &value
);
536 drivestat
->Reads
= value
;
539 /* Get bytes read. */
541 (CFNumberRef
)CFDictionaryGetValue(statistics
,
542 CFSTR(kIOBlockStorageDriverStatisticsBytesReadKey
));
544 CFNumberGetValue(number
, kCFNumberSInt64Type
, &value
);
545 drivestat
->BytesRead
= value
;
548 /* Get number of writes. */
550 (CFNumberRef
)CFDictionaryGetValue(statistics
,
551 CFSTR(kIOBlockStorageDriverStatisticsWritesKey
));
553 CFNumberGetValue(number
, kCFNumberSInt64Type
, &value
);
554 drivestat
->Writes
= value
;
557 /* Get bytes written. */
559 (CFNumberRef
)CFDictionaryGetValue(statistics
,
560 CFSTR(kIOBlockStorageDriverStatisticsBytesWrittenKey
));
562 CFNumberGetValue(number
, kCFNumberSInt64Type
, &value
);
563 drivestat
->BytesWritten
= value
;
566 /* Get LatentReadTime. */
568 (CFNumberRef
)CFDictionaryGetValue(statistics
,
569 CFSTR(kIOBlockStorageDriverStatisticsLatentReadTimeKey
));
571 CFNumberGetValue(number
, kCFNumberSInt64Type
, &value
);
572 drivestat
->LatentReadTime
= value
;
575 /* Get LatentWriteTime. */
577 (CFNumberRef
)CFDictionaryGetValue(statistics
,
578 CFSTR(kIOBlockStorageDriverStatisticsLatentWriteTimeKey
));
580 CFNumberGetValue(number
, kCFNumberSInt64Type
, &value
);
581 drivestat
->LatentWriteTime
= value
;
584 /* Get ReadErrors. */
586 (CFNumberRef
)CFDictionaryGetValue(statistics
,
587 CFSTR(kIOBlockStorageDriverStatisticsReadErrorsKey
));
589 CFNumberGetValue(number
, kCFNumberSInt64Type
, &value
);
590 drivestat
->ReadErrors
= value
;
593 /* Get WriteErrors. */
595 (CFNumberRef
)CFDictionaryGetValue(statistics
,
596 CFSTR(kIOBlockStorageDriverStatisticsWriteErrorsKey
));
598 CFNumberGetValue(number
, kCFNumberSInt64Type
, &value
);
599 drivestat
->WriteErrors
= value
;
602 /* Get ReadRetries. */
604 (CFNumberRef
)CFDictionaryGetValue(statistics
,
605 CFSTR(kIOBlockStorageDriverStatisticsReadRetriesKey
));
607 CFNumberGetValue(number
, kCFNumberSInt64Type
, &value
);
608 drivestat
->ReadRetries
= value
;
611 /* Get WriteRetries. */
613 (CFNumberRef
)CFDictionaryGetValue(statistics
,
614 CFSTR(kIOBlockStorageDriverStatisticsWriteRetriesKey
));
616 CFNumberGetValue(number
, kCFNumberSInt64Type
, &value
);
617 drivestat
->WriteRetries
= value
;
620 /* Get TotalReadTime. */
622 (CFNumberRef
)CFDictionaryGetValue(statistics
,
623 CFSTR(kIOBlockStorageDriverStatisticsTotalReadTimeKey
));
625 CFNumberGetValue(number
, kCFNumberSInt64Type
, &value
);
626 drivestat
->TotalReadTime
= value
;
629 /* Get WriteRetries. */
631 (CFNumberRef
)CFDictionaryGetValue(statistics
,
632 CFSTR(kIOBlockStorageDriverStatisticsTotalWriteTimeKey
));
634 CFNumberGetValue(number
, kCFNumberSInt64Type
, &value
);
635 drivestat
->TotalWriteTime
= value
;
638 CFRelease(properties
);
639 } /* end if statistics != 0 */
642 IOObjectRelease(parent
);
648 * find IOMedia objects
649 * This routine always gives me a lower count on the number
650 * of disks. I don't know which one to use.
655 io_iterator_t drivelist
;
656 io_registry_entry_t drive
;
657 io_registry_entry_t parent
;
658 CFMutableDictionaryRef match
;
660 kern_return_t status
;
663 * Get an iterator for IOMedia objects.
665 match
= IOServiceMatching("IOMedia");
666 CFDictionaryAddValue(match
, CFSTR(kIOMediaWholeKey
), kCFBooleanTrue
);
667 status
= IOServiceGetMatchingServices(masterPort
, match
, &drivelist
);
668 if (status
!= KERN_SUCCESS
)
672 * Scan all of the IOMedia objects, and count each
673 * object that has a parent IOBlockStorageDriver
675 * XXX What about RAID devices?
679 while ((drive
= IOIteratorNext(drivelist
)))
681 /* get drive's parent */
682 status
= IORegistryEntryGetParentEntry(drive
,
683 kIOServicePlane
, &parent
);
684 if (status
!= KERN_SUCCESS
)
686 IOObjectRelease(drive
);
690 if (IOObjectConformsTo(parent
, "IOBlockStorageDriver"))
695 IOObjectRelease(parent
);
696 IOObjectRelease(drive
);
699 IOObjectRelease(drivelist
);
706 * When getting the stats, do it in the order
707 * of their type. The types that have the most
708 * data come first in the list if possible.
709 * This makes the sar reporter tool more efficient,
710 * because in some cases, it will allocate a buffer
711 * and keep reusing it as long as the sample data fits.
712 * When a sample data doesn't fit, it reallocates the buffer
713 * to a bigger size etc.
719 get_drivestat_sample();
720 get_netstat_sample(network_mode
);
727 * An internal table maps the BSDName to a unique ioregistry path.
728 * The table's index is then used as a unique compressed path, and
729 * helps track disks that come and go during the sampling intervals.
730 * This routine finds an entry that maps both the BSDName and the
731 * IOKit registry path. If no mapping is discovered, a new entry
732 * is created. An entry is never removed, this maintaining the
733 * unique index throughout the data collection.
734 * Success returns the map index. Failure returns -1.
737 check_device_path (char *name
, char *path
, int ndrives
)
743 if (dp_table
== NULL
)
745 /* First setup of internal drivepath table */
746 dp_table
= (struct drivepath
*)malloc (ndrives
* sizeof(struct drivepath
));
747 if (dp_table
== NULL
)
751 bzero(dp_table
, (ndrives
* sizeof(struct drivepath
)));
753 drivepath_record
.rec_size
= sizeof(struct drivepath
);
757 for (i
=0; i
< dp_count
; i
++)
759 if (dp_table
[i
].state
== DPSTATE_UNINITIALIZED
)
761 /* This is a new drive entry that should be recorded */
765 else if (!strcmp (dp_table
[i
].ioreg_path
, path
))
767 /* Found a matching hardware path */
768 if (!strcmp(dp_table
[i
].BSDName
, name
))
770 /* The BSDName matches the entry in the table
771 * so there is no need to record this data.
777 /* The BSDName is different ... implies a change,
778 * like the drive was removed and now is back
780 bzero((char *)dp_table
[i
].BSDName
, MAXDRIVENAME
+1);
781 dp_table
[i
].drivepath_id
= i
;
782 dp_table
[i
].state
= DPSTATE_CHANGED
;
783 strcpy(dp_table
[i
].BSDName
, name
);
784 write_record_hdr(&drivepath_record
);
785 write_record_data((char *)&dp_table
[i
], sizeof(struct drivepath
));
792 * If we reach this point, then we've run out of
793 * table entries. Double the size of the table.
796 dp_table
= (struct drivepath
*)realloc(dp_table
, n
* sizeof(struct drivepath
));
797 bzero(&dp_table
[dp_count
], dp_count
* sizeof(struct drivepath
));
801 /* This is a new drive entry that should be recorded */
803 dp_table
[index
].drivepath_id
= index
;
804 dp_table
[index
].state
= DPSTATE_NEW
;
805 strcpy(dp_table
[index
].BSDName
, name
);
806 strcpy(dp_table
[index
].ioreg_path
, path
);
807 write_record_hdr(&drivepath_record
);
808 write_record_data((char *)&dp_table
[index
], sizeof(struct drivepath
));
815 * Thus far, only the networking stats take an optional flag
816 * to modify the collection of data. The number of ppp
817 * interfaces can be very high, causing the raw data file to
818 * grow very large. We want this option to include ppp
819 * statistics to be off by default. When we see the -m PPP
820 * mode passed in, ppp collection will be turned on.
823 get_netstat_sample(int mode
)
828 char tname
[MAX_TNAME_SIZE
+ 1];
829 char name
[MAX_TNAME_UNIT_SIZE
+ 1];
830 struct ifaddrs
*ifa_list
, *ifa
;
834 * Set the starting table size to 100 entries
835 * That should be big enough for most cases,
836 * even with a lot of ppp connections.
839 ns_table
= (struct netstats
*) malloc(ns_count
* sizeof (struct netstats
));
840 if (ns_table
== NULL
)
842 fprintf(stderr
, "sadc: malloc netstat table failed\n");
846 bzero(ns_table
, ns_count
* sizeof(struct netstats
));
847 if (getifaddrs(&ifa_list
) == -1)
850 for (ifa
= ifa_list
; ifa
; ifa
= ifa
->ifa_next
)
852 struct if_data
*if_data
= (struct if_data
*)ifa
->ifa_data
;
854 if (AF_LINK
!= ifa
->ifa_addr
->sa_family
)
856 if (ifa
->ifa_data
== 0)
858 tname
[MAX_TNAME_SIZE
] = '\0';
859 if (!(network_mode
& NET_PPP_MODE
))
862 * If the flag is set, include PPP connections.
863 * By default this collection is turned off
865 if(!strncmp(ifa
->ifa_name
, "ppp", 3))
868 snprintf(name
, MAX_TNAME_UNIT_SIZE
, "%s", ifa
->ifa_name
);
869 name
[MAX_TNAME_UNIT_SIZE
] = '\0';
871 if (ns_index
== ns_count
)
873 /* the stat table needs to grow */
875 ns_table
= (struct netstats
*)realloc(ns_table
, n
* sizeof(struct netstats
));
876 bzero(&ns_table
[ns_count
], ns_count
* sizeof(struct netstats
));
881 * As a means of helping to identify when interface unit numbers
882 * are reused, a generation counter may eventually be implemented.
883 * This will be especially helpful with ppp-x connections.
884 * In anticipation, we will reserve a space for it, but always
885 * set it to zero for now.
887 ns_table
[ns_index
].gen_counter
= 0;
889 strncpy(ns_table
[ns_index
].tname_unit
, name
, MAX_TNAME_UNIT_SIZE
);
890 ns_table
[ns_index
].tname_unit
[MAX_TNAME_UNIT_SIZE
] = '\0';
891 ns_table
[ns_index
].net_ipackets
= if_data
->ifi_ipackets
;
892 ns_table
[ns_index
].net_ierrors
= if_data
->ifi_ierrors
;
893 ns_table
[ns_index
].net_opackets
= if_data
->ifi_opackets
;
894 ns_table
[ns_index
].net_oerrors
= if_data
->ifi_oerrors
;
895 ns_table
[ns_index
].net_collisions
= if_data
->ifi_collisions
;
896 ns_table
[ns_index
].net_ibytes
= if_data
->ifi_ibytes
;
897 ns_table
[ns_index
].net_obytes
= if_data
->ifi_obytes
;
898 ns_table
[ns_index
].net_imcasts
= if_data
->ifi_imcasts
;
899 ns_table
[ns_index
].net_omcasts
= if_data
->ifi_omcasts
;
900 ns_table
[ns_index
].net_drops
= if_data
->ifi_iqdrops
;
904 netstats_record
.rec_count
= ns_index
;
905 netstats_record
.rec_size
= sizeof(struct netstats
);
906 write_record_hdr(&netstats_record
);
907 write_record_data((char *)ns_table
, (ns_index
* sizeof(struct netstats
)));