boinc/client/app_stats_mac.cpp

714 lines
24 KiB
C++
Raw Blame History

// 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 <http://www.gnu.org/licenses/>.
// 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<6E>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 <cerrno>
#include <sys/types.h>
#include <mach/shared_memory_server.h>
#include <mach/mach.h>
#include <mach/mach_error.h>
#include <sys/sysctl.h>
#include "procinfo.h"
using std::vector;
static int get_boinc_proc_info(int my_pid, int boinc_pid);
static int build_proc_list (vector<PROCINFO>& pi, int boinc_pid);
static void output_child_totals(PROCINFO& pinfo);
static boolean_t appstats_task_update(task_t a_task, vector<PROCINFO>& piv);
static void find_all_descendants(vector<PROCINFO>& piv, int pid, int rlvl);
static void add_child_totals(PROCINFO& pi, vector<PROCINFO>& piv, int pid, int rlvl);
//static void add_others(PROCINFO&, std::vector<PROCINFO>&);
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<PROCINFO> piv;
PROCINFO child_total;
unsigned int i;
retval = build_proc_list(piv, boinc_pid);
if (retval)
return retval;
for (i=0; i<piv.size(); i++) {
PROCINFO& p = piv[i];
if (p.parentid == boinc_pid) {
if (p.id == my_pid)
continue;
child_total = p;
p.is_boinc_app = true;
#ifdef _DEBUG
printf("\n\nSumming info for process %d and its children:\n", child_total.id);
print_procinfo(child_total);
#endif
// look for child processes
add_child_totals(child_total, piv, p.id, 0);
#ifdef _DEBUG
printf("Totals for process %d and its children:\n", child_total.id);
#endif
output_child_totals(child_total);
}
}
memset(&child_total, 0, sizeof(child_total));
#if 0
#ifdef _DEBUG
printf("\n\nSumming info for all other processes\n");
#endif
add_others(child_total, piv);
#endif
output_child_totals(child_total); // zero pid signals end of data
return 0;
}
static void output_child_totals(PROCINFO& pinfo) {
printf("%d %d %.0lf %.0lf %lu %lf %lf\n",
pinfo.id, pinfo.parentid, pinfo.working_set_size, pinfo.swap_size,
pinfo.page_fault_count, pinfo.user_time, pinfo.kernel_time);
// fflush(stdout);
}
static int build_proc_list (vector<PROCINFO>& 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<PROCINFO>& 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; i<piv.size(); i++) {
pinfo = &piv[i];
if (pinfo->id == 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<PROCINFO>& piv, int pid, int rlvl) {
unsigned int i;
if (rlvl > MAX_DESCENDANT_LEVEL) {
return;
}
for (i=0; i<piv.size(); i++) {
PROCINFO& p = piv[i];
if (p.parentid == pid) {
p.is_boinc_app = true;
// look for child process of this one
find_all_descendants(piv, p.id, rlvl+1); // recursion - woo hoo!
}
}
}
// Scan the process table adding in CPU time and mem usage. Loop
// thru entire table as the entries aren't in order. Recurse at
// most 4 times to get additional child processes
//
static void add_child_totals(PROCINFO& pi, vector<PROCINFO>& piv, int pid, int rlvl) {
unsigned int i;
if (rlvl > (MAX_DESCENDANT_LEVEL - 1)) {
return;
}
for (i=0; i<piv.size(); i++) {
PROCINFO& p = piv[i];
if (p.parentid == pid) {
pi.kernel_time += p.kernel_time;
pi.user_time += p.user_time;
pi.swap_size += p.swap_size;
pi.working_set_size += p.working_set_size;
pi.page_fault_count += p.page_fault_count;
p.is_boinc_app = true;
#ifdef _DEBUG
print_procinfo(p);
#endif
// look for child process of this one
add_child_totals(pi, piv, p.id, rlvl+1); // recursion - woo hoo!
}
}
}
#if 0
static void add_others(PROCINFO& pi, vector<PROCINFO>& piv) {
unsigned int i;
memset(&pi, 0, sizeof(pi));
for (i=0; i<piv.size(); i++) {
PROCINFO& p = piv[i];
if (!p.is_boinc_app) {
pi.kernel_time += p.kernel_time;
pi.user_time += p.user_time;
pi.swap_size += p.swap_size;
pi.working_set_size += p.working_set_size;
pi.page_fault_count += p.page_fault_count;
p.is_boinc_app = true;
#ifdef _DEBUG
print_procinfo(p);
#endif
}
}
}
#endif
static void sig_pipe(int signo)
{
exit(1);
}
#ifdef _DEBUG
static void print_procinfo(PROCINFO& pinfo) {
unsigned long long rsize, vsize;
rsize = (unsigned long long)pinfo.working_set_size;
vsize = (unsigned long long)pinfo.swap_size;
printf("pid=%d, ppid=%d, rm=%llu=", pinfo.id, pinfo.parentid, rsize);
vm_size_render(rsize);
printf("=, vm=%llu=", vsize);
vm_size_render(vsize);
printf(", pageins=%lu, usertime=%lf, systime=%lf\n", pinfo.page_fault_count, pinfo.user_time, pinfo.kernel_time);
}
/*
* Render a memory size in units of B, K, M, or G, depending on the value.
*
* a_size is ULL, since there are places where VM sizes are capable of
* overflowing 32 bits, particularly when VM stats are multiplied by the
* pagesize.
*/
static void vm_size_render(unsigned long long a_size)
{
if (a_size < 1024) {
/* 1023B. */
printf("%4lluB", a_size);
} else if (a_size < (1024ULL * 1024ULL)) {
/* K. */
if (a_size < 10ULL * 1024ULL) {
/* 9.99K */
printf("%1.2fK",
((double)a_size) / 1024);
} else if (a_size < 100ULL * 1024ULL) {
/* 99.9K */
printf("%2.1fK",
((double)a_size) / 1024);
} else {
/* 1023K */
printf("%4lluK",
a_size / 1024ULL);
}
} else if (a_size < (1024ULL * 1024ULL * 1024ULL)) {
/* M. */
if (a_size < 10ULL * 1024ULL * 1024ULL) {
/* 9.99M */
printf("%1.2fM",
((double)a_size) / (1024 * 1024));
} else if (a_size < 100ULL * 1024ULL * 1024ULL) {
/* 99.9M */
printf("%2.1fM",
((double)a_size) / (1024 * 1024));
} else {
/* 1023M */
printf("%4lluM",
a_size / (1024ULL * 1024ULL));
}
} else if (a_size < (1024ULL * 1024ULL * 1024ULL * 1024ULL)) {
/* G. */
if (a_size < 10ULL * 1024ULL * 1024ULL * 1024ULL) {
/* 9.99G. */
printf("%1.2fG",
((double)a_size) / (1024 * 1024 * 1024));
} else if (a_size < 100ULL * 1024ULL * 1024ULL * 1024ULL) {
/* 99.9G. */
printf("%2.1fG",
((double)a_size) / (1024 * 1024 * 1024));
} else {
/* 1023G */
printf("%4lluG",
a_size / (1024ULL * 1024ULL * 1024ULL));
}
} else if (a_size < (1024ULL * 1024ULL * 1024ULL * 1024ULL)) {
/* T. */
if (a_size < 10ULL * 1024ULL * 1024ULL * 1024ULL * 1024ULL) {
/* 9.99T. */
printf("%1.2fT",
((double)a_size) /
(1024ULL * 1024ULL * 1024ULL * 1024ULL));
} else if (a_size < (100ULL * 1024ULL * 1024ULL * 1024ULL
* 1024ULL)) {
/* 99.9T. */
printf("%2.1fT",
((double)a_size) /
(1024ULL * 1024ULL * 1024ULL * 1024ULL));
} else {
/* 1023T */
printf("%4lluT",
a_size /
(1024ULL * 1024ULL * 1024ULL * 1024ULL));
}
} else {
/* P. */
if (a_size < (10ULL * 1024ULL * 1024ULL * 1024ULL * 1024ULL
* 1024ULL)) {
/* 9.99P. */
printf("%1.2fP",
((double)a_size) /
(1024ULL * 1024ULL * 1024ULL * 1024ULL
* 1024ULL));
} else if (a_size < (100ULL * 1024ULL * 1024ULL * 1024ULL
* 1024ULL)) {
/* 99.9P. */
printf("%2.1fP",
((double)a_size) /
(1024ULL * 1024ULL * 1024ULL * 1024ULL
* 1024ULL));
} else {
/* 1023P */
printf("%4lluP",
a_size /
(1024ULL * 1024ULL * 1024ULL * 1024ULL
* 1024ULL));
}
}
}
#endif // _DEBUG