/* * Copyright (c) 2011 Apple Computer, Inc. All rights reserved. * * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. The rights granted to you under the License * may not be used to create, or enable the creation or redistribution of, * unlawful or unlicensed copies of an Apple operating system, or to * circumvent, violate, or enable the circumvention or violation of, any * terms of an Apple operating system software license agreement. * * Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this file. * * The 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, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ */ /* * Called from a trigger. Actually takes the data from the different * modules and puts them in a buffer */ #include #include // #include #include #include /* panic */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define ACTION_MAX 32 /* XXX: callback handler from chudxnu */ /* FIXME: hook this up to something */ //void (*kperf_thread_ast_handler)(thread_t); /* the list of different actions to take */ struct action { unsigned sample; }; /* the list of actions */ static unsigned actionc = 0; static struct action *actionv = NULL; /* Do the real work! */ /* this can be called in any context ... right? */ static kern_return_t kperf_sample_internal( struct kperf_sample *sbuf, struct kperf_context *context, unsigned sample_what, boolean_t pend_user ) { boolean_t enabled; int did_ucallstack = 0, did_tinfo_extra = 0; /* not much point continuing here, but what to do ? return * Shutdown? cut a tracepoint and continue? */ if( sample_what == 0 ) return SAMPLE_CONTINUE; int is_kernel = (context->cur_pid == 0); /* an event occurred. Sample everything and dump it in a * buffer. */ /* collect data from samplers */ if( sample_what & SAMPLER_TINFO ) { kperf_threadinfo_sample( &sbuf->threadinfo, context ); /* XXX FIXME This drops events when the thread is idle. * This should be configurable. */ if (sbuf->threadinfo.runmode & 0x40) return SAMPLE_CONTINUE; } if( sample_what & SAMPLER_KSTACK ) kperf_kcallstack_sample( &sbuf->kcallstack, context ); /* sensitive ones */ if ( !is_kernel ) { if( pend_user ) { if( sample_what & SAMPLER_USTACK ) did_ucallstack = kperf_ucallstack_pend( context ); if( sample_what & SAMPLER_TINFOEX ) did_tinfo_extra = kperf_threadinfo_extra_pend( context ); } else { if( sample_what & SAMPLER_USTACK ) kperf_ucallstack_sample( &sbuf->ucallstack, context ); if( sample_what & SAMPLER_TINFOEX ) kperf_threadinfo_extra_sample( &sbuf->tinfo_ex, context ); } } /* stash the data into the buffer * interrupts off to ensure we don't get split */ enabled = ml_set_interrupts_enabled(FALSE); if ( pend_user ) BUF_DATA1( PERF_GEN_EVENT | DBG_FUNC_START, sample_what ); /* dump threadinfo */ if( sample_what & SAMPLER_TINFO ) kperf_threadinfo_log( &sbuf->threadinfo ); /* dump kcallstack */ if( sample_what & SAMPLER_KSTACK ) kperf_kcallstack_log( &sbuf->kcallstack ); /* dump user stuff */ if ( !is_kernel ) { if ( pend_user ) { if ( did_ucallstack ) BUF_INFO1( PERF_CS_UPEND, 0 ); if ( did_tinfo_extra ) BUF_INFO1( PERF_TI_XPEND, 0 ); } else { if( sample_what & SAMPLER_USTACK ) kperf_ucallstack_log( &sbuf->ucallstack ); if( sample_what & SAMPLER_TINFOEX ) kperf_threadinfo_extra_log( &sbuf->tinfo_ex ); } } if ( pend_user ) BUF_DATA1( PERF_GEN_EVENT | DBG_FUNC_END, sample_what ); /* intrs back on */ ml_set_interrupts_enabled(enabled); return SAMPLE_CONTINUE; } /* Translate actionid into sample bits and take a sample */ kern_return_t kperf_sample( struct kperf_sample *sbuf, struct kperf_context *context, unsigned actionid, boolean_t pend_user ) { unsigned sample_what = 0; /* check samppling is on, or panic */ if( kperf_sampling_status() == KPERF_SAMPLING_OFF ) panic("trigger fired while sampling off"); else if( kperf_sampling_status() == KPERF_SAMPLING_SHUTDOWN ) return SAMPLE_SHUTDOWN; /* work out what to sample, if anything */ if( actionid >= actionc ) return SAMPLE_SHUTDOWN; sample_what = actionv[actionid].sample; return kperf_sample_internal( sbuf, context, sample_what, pend_user ); } /* ast callback on a thread */ void kperf_thread_ast_handler( thread_t thread ) { int r; uint32_t t_chud; unsigned sample_what = 0; /* we know we're on a thread, so let's do stuff */ task_t task = NULL; /* Don't sample if we are shutting down or off */ if( kperf_sampling_status() != KPERF_SAMPLING_ON ) return; BUF_INFO1(PERF_AST_HNDLR | DBG_FUNC_START, thread); /* FIXME: probably want a faster allocator here... :P */ struct kperf_sample *sbuf = kalloc( sizeof(*sbuf) ); if( sbuf == NULL ) { /* FIXME: error code */ BUF_INFO1( PERF_AST_ERROR, 0 ); goto error; } /* make a context, take a sample */ struct kperf_context ctx; ctx.cur_thread = thread; ctx.cur_pid = -1; task = chudxnu_task_for_thread(thread); if(task) ctx.cur_pid = chudxnu_pid_for_task(task); /* decode the chud bits so we know what to sample */ t_chud = kperf_get_thread_bits(thread); if (t_chud & T_AST_NAME) sample_what |= SAMPLER_TINFOEX; if (t_chud & T_AST_CALLSTACK) sample_what |= SAMPLER_USTACK; /* do the sample, just of the user stuff */ r = kperf_sample_internal( sbuf, &ctx, sample_what, FALSE ); /* free it again */ kfree( sbuf, sizeof(*sbuf) ); error: BUF_INFO1(PERF_AST_HNDLR | DBG_FUNC_END, r); } /* register AST bits */ int kperf_ast_pend( thread_t cur_thread, uint32_t check_bits, uint32_t set_bits ) { /* pend on the thread */ uint32_t t_chud, set_done = 0; /* can only pend on the current thread */ if( cur_thread != chudxnu_current_thread() ) panic("pending to non-current thread"); /* get our current bits */ t_chud = kperf_get_thread_bits(cur_thread); /* see if it's already been done or pended */ if( !(t_chud & check_bits ) ) { /* set the bit on the thread */ t_chud |= set_bits; kperf_set_thread_bits(cur_thread, t_chud); /* set the actual AST */ kperf_set_thread_ast( cur_thread ); set_done = 1; } return set_done; // BUF_INFO3( dbg_code, (uintptr_t)cur_thread, t_chud, set_done ); } unsigned kperf_action_get_count(void) { return actionc; } int kperf_action_set_samplers( unsigned actionid, uint32_t samplers ) { if( actionid >= actionc ) return EINVAL; actionv[actionid].sample = samplers; return 0; } int kperf_action_get_samplers( unsigned actionid, uint32_t *samplers_out ) { if( actionid >= actionc ) return EINVAL; *samplers_out = actionv[actionid].sample; return 0; } int kperf_action_set_count(unsigned count) { struct action *new_actionv = NULL, *old_actionv = NULL; unsigned old_count; /* easy no-op */ if( count == actionc ) return 0; /* TODO: allow shrinking? */ if( count < actionc ) return EINVAL; /* cap it for good measure */ if( count > ACTION_MAX ) return EINVAL; /* creating the action arror for the first time. create a few * more things, too. */ if( actionc == 0 ) { int r; r = kperf_init(); if( r != 0 ) return r; } /* create a new array */ new_actionv = kalloc( count * sizeof(*new_actionv) ); if( new_actionv == NULL ) return ENOMEM; old_actionv = actionv; old_count = actionc; if( old_actionv != NULL ) bcopy( actionv, new_actionv, actionc * sizeof(*actionv) ); bzero( &new_actionv[actionc], (count - old_count) * sizeof(*actionv) ); actionv = new_actionv; actionc = count; if( old_actionv != NULL ) kfree( old_actionv, old_count * sizeof(*actionv) ); printf( "kperf: done the alloc\n" ); return 0; }