// This file is part of BOINC.
// http://boinc.berkeley.edu
// Copyright (C) 2008 University of California
//
// BOINC is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License
// as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
//
// BOINC is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with BOINC. If not, see .
// This file is adapted from code originally supplied by Apple Computer, Inc.
// The Berkeley Open Infrastructure for Network Computing project has modified
// the original code and made additions as of September 22, 2006. The original
// Apple Public Source License statement appears below:
/*
* Copyright (c) 2002-2004 Apple Computer, Inc. All rights reserved.
*
* @APPLE_LICENSE_HEADER_START@
*
* The contents of this file constitute Original Code as defined in and
* are subject to the Apple Public Source License Version 1.1 (the
* "License"). You may not use this file except in compliance with the
* License. Please obtain a copy of the License at
* http://www.apple.com/publicsource and read it before using this file.
*
* This Original Code and all software distributed under the License are
* distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the
* License for the specific language governing rights and limitations
* under the License.
*
* @APPLE_LICENSE_HEADER_END@
*/
// app_stats_mac.C
//
// #define _DEBUG 1
// Put a safety limit on recursion
#define MAX_DESCENDANT_LEVEL 4
// Totals for non_BOINC processes are not useful because most OSs don't
// move idle processes out of RAM, so physical memory is always full
#define GET_NON_BOINC_INFO 0
// We don't need swap space info because
// http://developer.apple.com/documentation/Performance/Conceptual/ManagingMemory/Articles/AboutMemory.html says:
// Unlike most UNIX-based operating systems, Mac OS X does not use a
// preallocated swap partition for virtual memory. Instead, it uses all
// of the available space on the machineÕs boot partition.
// However, the associated overhead is not significant if we are examining
// only BOINC descendant processes.
#define GET_SWAP_SIZE 1
// The overhead for getting CPU times is not significant if we are
// examining only BOINC descendant processes.
#define GET_CPU_TIMES 1
#include
#include
#include
#include
#include
#include
#include "procinfo.h"
using std::vector;
static int get_boinc_proc_info(int my_pid, int boinc_pid);
static int build_proc_list (vector& pi, int boinc_pid);
static void output_child_totals(PROCINFO& pinfo);
static boolean_t appstats_task_update(task_t a_task, vector& piv);
static void find_all_descendants(vector& piv, int pid, int rlvl);
static void add_child_totals(PROCINFO& pi, vector& piv, int pid, int rlvl);
//static void add_others(PROCINFO&, std::vector&);
static void sig_pipe(int signo);
#ifdef _DEBUG
static void print_procinfo(PROCINFO& pinfo);
static void vm_size_render(unsigned long long a_size);
#endif
// BOINC helper application to get info about each of the BOINC Client's
// child processes (including all its descendants) and also totals for
// all other processes.
// On the Mac, much of this information is accessible only by the super-user,
// so this helper application must be run setuid root.
int main(int argc, char** argv) {
int boinc_pid, my_pid;
int retval;
char buf[256];
if (geteuid() != 0) // This must be run setuid root
return EACCES;
my_pid = getpid();
boinc_pid = getppid(); // Assumes we were called by BOINC client
if (argc == 2)
boinc_pid = atoi(argv[1]); // Pass in any desired valid pid for testing
if (signal(SIGPIPE, sig_pipe) == SIG_ERR) {
fprintf(stderr, "signal error");
return 0;
}
setbuf(stdin, 0);
setbuf(stdout, 0);
while (1) {
if (fgets(buf, sizeof(buf), stdin) == NULL)
return 0;
if (feof(stdin))
return 0;
retval = get_boinc_proc_info(my_pid, boinc_pid);
}
return 0;
}
static int get_boinc_proc_info(int my_pid, int boinc_pid) {
int retval;
vector piv;
PROCINFO child_total;
unsigned int i;
retval = build_proc_list(piv, boinc_pid);
if (retval)
return retval;
for (i=0; i& pi, int boinc_pid) {
boolean_t retval = FALSE;
kern_return_t error;
mach_port_t appstats_port;
processor_set_t *psets, pset;
task_t *tasks;
unsigned i, j, pcnt, tcnt;
PROCINFO pinfo;
int pid, mib[4];
struct kinfo_proc kinfo;
size_t kinfosize;
appstats_port = mach_host_self();
// First, get a list of all tasks / processes
error = host_processor_sets(appstats_port, &psets, &pcnt);
if (error != KERN_SUCCESS) {
fprintf(stderr,
"Error in host_processor_sets(): %s",
mach_error_string(error));
retval = TRUE;
goto RETURN;
}
for (i = 0; i < pcnt; i++) {
if (retval)
break;
error = host_processor_set_priv(appstats_port, psets[i], &pset);
if (error != KERN_SUCCESS) {
fprintf(stderr,
"Error in host_processor_set_priv(): %s",
mach_error_string(error));
retval = TRUE;
break;
}
error = processor_set_tasks(pset, &tasks, &tcnt);
if (error != KERN_SUCCESS) {
fprintf(stderr,
"Error in processor_set_tasks(): %s",
mach_error_string(error));
retval = TRUE;
break;
}
for (j = 0; j < tcnt; j++) {
if (retval)
break;
memset(&pinfo, 0, sizeof(PROCINFO));
/* Get pid for this task. */
error = pid_for_task(tasks[j], &pid);
if (error != KERN_SUCCESS) {
/* Not a process, or the process is gone. */
continue;
}
// Get parent pid for each process
/* Get kinfo structure for this task. */
kinfosize = sizeof(struct kinfo_proc);
mib[0] = CTL_KERN;
mib[1] = KERN_PROC;
mib[2] = KERN_PROC_PID;
mib[3] = pid;
if (sysctl(mib, 4, &kinfo, &kinfosize, NULL, 0) == -1) {
fprintf(stderr,
"%s(): Error in sysctl(): %s", __FUNCTION__,
strerror(errno));
retval = TRUE;
break;
}
if (kinfo.kp_proc.p_stat == 0) {
/* Zombie process. */
continue;
}
pinfo.id = pid;
pinfo.parentid = kinfo.kp_eproc.e_ppid;
pi.push_back(pinfo);
}
}
#if ! GET_NON_BOINC_INFO
// Next, find all BOINC's decendants and mark them for further study
if (! retval)
find_all_descendants(pi, boinc_pid, 0);
#endif
// Now get the process information for each descendant
for (i = 0; i < pcnt; i++) {
for (j = 0; j < tcnt; j++) {
if (! retval)
if (appstats_task_update(tasks[j], pi)) {
retval = TRUE;
goto RETURN;
}
/* Delete task port if it isn't our own. */
if (tasks[j] != mach_task_self()) {
mach_port_deallocate(mach_task_self(),
tasks[j]);
}
}
error = vm_deallocate((vm_map_t)mach_task_self(),
(vm_address_t)tasks, tcnt * sizeof(task_t));
if (error != KERN_SUCCESS) {
if (!retval)
fprintf(stderr,
"Error in vm_deallocate(): %s",
mach_error_string(error));
retval = TRUE;
goto RETURN;
}
if ((error = mach_port_deallocate(mach_task_self(),
pset)) != KERN_SUCCESS
|| (error = mach_port_deallocate(mach_task_self(),
psets[i])) != KERN_SUCCESS) {
if (!retval)
fprintf(stderr,
"Error in mach_port_deallocate(): %s",
mach_error_string(error));
retval = TRUE;
goto RETURN;
}
}
error = vm_deallocate((vm_map_t)mach_task_self(),
(vm_address_t)psets, pcnt * sizeof(processor_set_t));
if (error != KERN_SUCCESS) {
if (!retval)
fprintf(stderr,
"Error in vm_deallocate(): %s",
mach_error_string(error));
retval = TRUE;
goto RETURN;
}
RETURN:
return retval;
}
/* Update statistics for task a_task. */
static boolean_t appstats_task_update(task_t a_task, vector& piv)
{
boolean_t retval;
kern_return_t error;
mach_msg_type_number_t count;
task_basic_info_data_t ti;
vm_address_t address;
mach_port_t object_name;
vm_region_top_info_data_t info;
vm_size_t size;
thread_array_t thread_table;
unsigned int table_size;
thread_basic_info_t thi;
thread_basic_info_data_t thi_data;
unsigned i;
task_events_info_data_t events;
vm_size_t vsize, rsize;
PROCINFO *pinfo;
int pid;
/* Get pid for this task. */
error = pid_for_task(a_task, &pid);
if (error != KERN_SUCCESS) {
/* Not a process, or the process is gone. */
retval = FALSE;
goto GONE;
}
for (i=0; iid == pid)
break;
}
if (pinfo->id != pid) {
fprintf(stderr, "pid %d missing from list\n", pid);
retval = FALSE;
goto RETURN;
}
#if ! GET_NON_BOINC_INFO
if (!pinfo->is_boinc_app) {
retval = FALSE;
goto RETURN;
}
#endif
/*
* Get task_info, which is used for memory usage and CPU usage
* statistics.
*/
count = TASK_BASIC_INFO_COUNT;
error = task_info(a_task, TASK_BASIC_INFO, (task_info_t)&ti, &count);
if (error != KERN_SUCCESS) {
retval = FALSE;
goto GONE;
}
/*
* Get memory usage statistics.
*/
/*
* Set rsize and vsize; they require no calculation. (Well, actually,
* we adjust vsize if traversing memory objects to not include the
* globally shared text and data regions).
*/
rsize = ti.resident_size;
#if GET_SWAP_SIZE
vsize = ti.virtual_size;
/*
* Iterate through the VM regions of the process and determine
* the amount of memory of various types it has mapped.
*/
for (address = 0; ; address += size) {
/* Get memory region. */
count = VM_REGION_TOP_INFO_COUNT;
if (vm_region(a_task, &address, &size,
VM_REGION_TOP_INFO, (vm_region_info_t)&info, &count,
&object_name) != KERN_SUCCESS) {
/* No more memory regions. */
break;
}
if (address >= GLOBAL_SHARED_TEXT_SEGMENT
&& address < (GLOBAL_SHARED_DATA_SEGMENT
+ SHARED_DATA_REGION_SIZE)) {
/* This region is private shared. */
/*
* Check if this process has the globally shared
* text and data regions mapped in. If so, adjust
* virtual memory size and exit loop.
*/
if (info.share_mode == SM_EMPTY) {
vm_region_basic_info_data_64_t b_info;
count = VM_REGION_BASIC_INFO_COUNT_64;
if (vm_region_64(a_task, &address,
&size, VM_REGION_BASIC_INFO,
(vm_region_info_t)&b_info, &count,
&object_name) != KERN_SUCCESS) {
break;
}
if (b_info.reserved) {
vsize -= (SHARED_TEXT_REGION_SIZE + SHARED_DATA_REGION_SIZE);
break;
}
}
}
}
#else
vsize = 0;
#endif // GET_SWAP_SIZE
pinfo->working_set_size = rsize;
pinfo->swap_size = vsize;
/*
* Get CPU usage statistics.
*/
pinfo->user_time = (double)ti.user_time.seconds + (((double)ti.user_time.microseconds)/1000000.);
pinfo->kernel_time = (double)ti.system_time.seconds + (((double)ti.system_time.microseconds)/1000000.);
/* Get number of threads. */
error = task_threads(a_task, &thread_table, &table_size);
if (error != KERN_SUCCESS) {
retval = FALSE;
goto RETURN;
}
#if GET_CPU_TIMES
/* Iterate through threads and collect usage stats. */
thi = &thi_data;
for (i = 0; i < table_size; i++) {
count = THREAD_BASIC_INFO_COUNT;
if (thread_info(thread_table[i], THREAD_BASIC_INFO,
(thread_info_t)thi, &count) == KERN_SUCCESS) {
if ((thi->flags & TH_FLAGS_IDLE) == 0) {
pinfo->user_time += (double)thi->user_time.seconds + (((double)thi->user_time.microseconds)/1000000.);
pinfo->kernel_time += (double)thi->system_time.seconds + (((double)thi->system_time.microseconds)/1000000.);
}
}
if (a_task != mach_task_self()) {
if ((error = mach_port_deallocate(mach_task_self(),
thread_table[i])) != KERN_SUCCESS) {
fprintf(stderr,
"Error in mach_port_deallocate(): %s",
mach_error_string(error));
retval = TRUE;
goto RETURN;
}
}
}
if ((error = vm_deallocate(mach_task_self(), (vm_offset_t)thread_table,
table_size * sizeof(thread_array_t)) != KERN_SUCCESS)) {
fprintf(stderr,
"Error in vm_deallocate(): %s",
mach_error_string(error));
retval = TRUE;
goto RETURN;
}
#endif GET_CPU_TIMES
/*
* Get event counters.
*/
count = TASK_EVENTS_INFO_COUNT;
if (task_info(a_task, TASK_EVENTS_INFO,
(task_info_t)&events, &count) != KERN_SUCCESS) {
/* Error. */
retval = FALSE;
goto RETURN;
} else {
pinfo->page_fault_count = events.pageins;
}
retval = FALSE;
RETURN:
GONE:
return retval;
}
// Scan the process table marking all the decendants of the parent
// process. Loop thru entire table as the entries aren't in order.
// Recurse at most 5 times to get additional child processes.
//
static void find_all_descendants(vector& piv, int pid, int rlvl) {
unsigned int i;
if (rlvl > MAX_DESCENDANT_LEVEL) {
return;
}
for (i=0; i& piv, int pid, int rlvl) {
unsigned int i;
if (rlvl > (MAX_DESCENDANT_LEVEL - 1)) {
return;
}
for (i=0; i& piv) {
unsigned int i;
memset(&pi, 0, sizeof(pi));
for (i=0; i