// 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