/* * Copyright (c) 2000 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. * * 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. 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_LICENSE_HEADER_END@ */ /* * @OSF_COPYRIGHT@ */ /* * Mach Operating System * Copyright (c) 1991,1990,1989 Carnegie Mellon University * All Rights Reserved. * * Permission to use, copy, modify and distribute this software and its * documentation is hereby granted, provided that both the copyright * notice and this permission notice appear in all copies of the * software, derivative works or modified versions, and any portions * thereof, and that both notices appear in supporting documentation. * * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. * * Carnegie Mellon requests users of this software to return to * * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU * School of Computer Science * Carnegie Mellon University * Pittsburgh PA 15213-3890 * * any improvements or extensions that they make and grant Carnegie Mellon * the rights to redistribute these changes. */ /* */ #include <mach_prof.h> #include <mach/task_server.h> #include <mach/thread_act_server.h> #if MACH_PROF #include <cpus.h> #include <kern/thread.h> #include <kern/thread_swap.h> #include <kern/queue.h> #include <kern/profile.h> #include <kern/sched_prim.h> #include <kern/spl.h> #include <kern/misc_protos.h> #include <ipc/ipc_space.h> #include <machine/machparam.h> #include <mach/prof.h> thread_t profile_thread_id = THREAD_NULL; int profile_sample_count = 0; /* Provided for looking at from kdb. */ extern kern_return_t task_suspend(task_t task); /* ack */ /* Forwards */ prof_data_t pbuf_alloc(void); void pbuf_free( prof_data_t pbuf); void profile_thread(void); void send_last_sample_buf( prof_data_t pbuf); /* ***************************************************************************** * profile_thread is the profile/trace kernel support thread. It is started * by a server/user request through task_sample, or thread_sample. The profile * thread dequeues messages and sends them to the receive_prof thread, in the * server, via the send_samples and send_notices mig interface functions. If * there are no messages in the queue profile thread blocks until wakened by * profile (called in from mach_clock), or last_sample (called by thread/task_ * sample). */ void profile_thread(void) { spl_t s; buffer_t buf_entry; queue_entry_t prof_queue_entry; prof_data_t pbuf; kern_return_t kr; int j; thread_swappable(current_act(), FALSE); /* Initialise the queue header for the prof_queue */ mpqueue_init(&prof_queue); while (TRUE) { /* Dequeue the first buffer. */ s = splsched(); mpdequeue_head(&prof_queue, &prof_queue_entry); splx(s); if ((buf_entry = (buffer_t) prof_queue_entry) == NULLPBUF) { assert_wait((event_t) profile_thread, THREAD_UNINT); thread_block(THREAD_CONTINUE_NULL); if (current_thread()->wait_result != THREAD_AWAKENED) break; } else { int dropped; pbuf = buf_entry->p_prof; kr = send_samples(pbuf->prof_port, (void *)buf_entry->p_zone, (mach_msg_type_number_t)buf_entry->p_index); profile_sample_count += buf_entry->p_index; if (kr != KERN_SUCCESS) printf("send_samples(%x, %x, %d) error %x\n", pbuf->prof_port, buf_entry->p_zone, buf_entry->p_index, kr); dropped = buf_entry->p_dropped; if (dropped > 0) { printf("kernel: profile dropped %d sample%s\n", dropped, dropped == 1 ? "" : "s"); buf_entry->p_dropped = 0; } /* Indicate you've finished the dirty job */ buf_entry->p_full = FALSE; if (buf_entry->p_wakeme) thread_wakeup((event_t) &buf_entry->p_wakeme); } } /* The profile thread has been signalled to exit. Any threads waiting for the last buffer of samples to be acknowledged should be woken up now. */ profile_thread_id = THREAD_NULL; while (1) { s = splsched(); mpdequeue_head(&prof_queue, &prof_queue_entry); splx(s); if ((buf_entry = (buffer_t) prof_queue_entry) == NULLPBUF) break; if (buf_entry->p_wakeme) thread_wakeup((event_t) &buf_entry->p_wakeme); } #if 0 /* XXXXX */ thread_halt_self(); #else panic("profile_thread(): halt_self"); #endif /* XXXXX */ } /* ***************************************************************************** * send_last_sample is the drain mechanism to allow partial profiled buffers * to be sent to the receive_prof thread in the server. ***************************************************************************** */ void send_last_sample_buf(prof_data_t pbuf) { spl_t s; buffer_t buf_entry; if (pbuf == NULLPROFDATA) return; /* Ask for the sending of the last PC buffer. * Make a request to the profile_thread by inserting * the buffer in the send queue, and wake it up. * The last buffer must be inserted at the head of the * send queue, so the profile_thread handles it immediatly. */ buf_entry = pbuf->prof_area + pbuf->prof_index; buf_entry->p_prof = pbuf; /* Watch out in case profile thread exits while we are about to queue data for it. */ s = splsched(); if (profile_thread_id == THREAD_NULL) splx(s); else { buf_entry->p_wakeme = 1; mpenqueue_tail(&prof_queue, &buf_entry->p_list); thread_wakeup((event_t) profile_thread); assert_wait((event_t) &buf_entry->p_wakeme, THREAD_ABORTSAFE); splx(s); thread_block(THREAD_CONTINUE_NULL); } } /* ***************************************************************************** * add clock tick parameters to profile/trace buffers. Called from the mach_ * clock heritz_tick function. DCI version stores thread, sp, and pc values * into the profile/trace buffers. MACH_PROF version just stores pc values. ***************************************************************************** */ void profile(natural_t pc, prof_data_t pbuf) { natural_t inout_val = pc; buffer_t buf_entry; if (pbuf == NULLPROFDATA) return; /* Inserts the PC value in the buffer of the thread */ set_pbuf_value(pbuf, &inout_val); switch((int)inout_val) { case 0: if (profile_thread_id == THREAD_NULL) { reset_pbuf_area(pbuf); } break; case 1: /* Normal case, value successfully inserted */ break; case 2 : /* * The value we have just inserted caused the * buffer to be full, and ready to be sent. * If profile_thread_id is null, the profile * thread has been killed. Since this generally * happens only when the O/S server task of which * it is a part is killed, it is not a great loss * to throw away the data. */ if (profile_thread_id == THREAD_NULL) { reset_pbuf_area(pbuf); break; } buf_entry = (buffer_t) &pbuf->prof_area[pbuf->prof_index]; buf_entry->p_prof = pbuf; mpenqueue_tail(&prof_queue, &buf_entry->p_list); /* Switch to another buffer */ reset_pbuf_area(pbuf); /* Wake up the profile thread */ if (profile_thread_id != THREAD_NULL) thread_wakeup((event_t) profile_thread); break; default: printf("profile : unexpected case\n"); } } /* ***************************************************************************** * pbuf_alloc creates a profile/trace buffer and assoc. zones for storing * profiled items. ***************************************************************************** */ prof_data_t pbuf_alloc(void) { register prof_data_t pbuf; register int i; register natural_t *zone; pbuf = (prof_data_t)kalloc(sizeof(struct prof_data)); if (!pbuf) return(NULLPROFDATA); pbuf->prof_port = MACH_PORT_NULL; for (i=0; i< NB_PROF_BUFFER; i++) { zone = (natural_t *)kalloc(SIZE_PROF_BUFFER*sizeof(natural_t)); if (!zone) { i--; while (i--) kfree((vm_offset_t)pbuf->prof_area[i].p_zone, SIZE_PROF_BUFFER*sizeof(natural_t)); kfree((vm_offset_t)pbuf, sizeof(struct prof_data)); return(NULLPROFDATA); } pbuf->prof_area[i].p_zone = zone; pbuf->prof_area[i].p_full = FALSE; } pbuf->prof_port = MACH_PORT_NULL; return(pbuf); } /* ***************************************************************************** * pbuf_free free memory allocated for storing profile/trace items. Called * when a task is no longer profiled/traced. Pbuf_free tears down the memory * alloced in pbuf_alloc. It does not check to see if the structures are valid * since it is only called by functions in this file. ***************************************************************************** */ void pbuf_free( prof_data_t pbuf) { register int i; if (pbuf->prof_port) ipc_port_release_send(pbuf->prof_port); for(i=0; i < NB_PROF_BUFFER ; i++) kfree((vm_offset_t)pbuf->prof_area[i].p_zone, SIZE_PROF_BUFFER*sizeof(natural_t)); kfree((vm_offset_t)pbuf, sizeof(struct prof_data)); } #endif /* MACH_PROF */ /* ***************************************************************************** * Thread_sample is used by MACH_PROF to profile a single thread, and is only * stub in DCI. ***************************************************************************** */ kern_return_t thread_sample( thread_act_t thr_act, ipc_port_t reply) { /* * This routine is called every time that a new thread has made * a request for the sampling service. We must keep track of the * correspondance between its identity (thread) and the port * we are going to use as a reply port to send out the samples resulting * from its execution. */ #if !MACH_PROF return KERN_FAILURE; #else prof_data_t pbuf; vm_offset_t vmpbuf; if (reply != MACH_PORT_NULL) { if (thr_act->act_profiled) /* yuck! */ return KERN_INVALID_ARGUMENT; /* Start profiling this activation, do the initialization. */ pbuf = pbuf_alloc(); if ((thr_act->profil_buffer = pbuf) == NULLPROFDATA) { printf("thread_sample: cannot allocate pbuf\n"); return KERN_RESOURCE_SHORTAGE; } else { if (!set_pbuf_nb(pbuf, NB_PROF_BUFFER-1)) { printf("mach_sample_thread: cannot set pbuf_nb\n"); return KERN_FAILURE; } reset_pbuf_area(pbuf); } pbuf->prof_port = reply; thr_act->act_profiled = TRUE; thr_act->act_profiled_own = TRUE; if (profile_thread_id == THREAD_NULL) profile_thread_id = kernel_thread(kernel_task, profile_thread); } else { if (!thr_act->act_profiled) return(KERN_INVALID_ARGUMENT); thr_act->act_profiled = FALSE; /* do not stop sampling if thread is not profiled by its own */ if (!thr_act->act_profiled_own) return KERN_SUCCESS; else thr_act->act_profiled_own = FALSE; send_last_sample_buf(thr_act->profil_buffer); pbuf_free(thr_act->profil_buffer); thr_act->profil_buffer = NULLPROFDATA; } return KERN_SUCCESS; #endif /* MACH_PROF */ } /* ***************************************************************************** * Task_sample is used to profile/trace tasks - all thread within a task using * a common profile buffer to collect items generated by the hertz_tick. For * each task profiled a profile buffer is created that associates a reply port * (used to send the data to a server thread), task (used for throttling), and * a zone area (used to store profiled/traced items). ***************************************************************************** */ kern_return_t task_sample( task_t task, ipc_port_t reply) { #if !MACH_PROF return KERN_FAILURE; #else prof_data_t pbuf=task->profil_buffer; vm_offset_t vmpbuf; boolean_t turnon = (reply != MACH_PORT_NULL); if (task == TASK_NULL) return KERN_INVALID_ARGUMENT; if (turnon) /* Do we want to profile this task? */ { pbuf = pbuf_alloc(); /* allocate a profile buffer */ task_lock(task); if (task->task_profiled) { /* if it is already profiled return so */ task_unlock(task); if (pbuf != NULLPROFDATA) pbuf_free(pbuf); return(KERN_INVALID_ARGUMENT); } if (pbuf == NULLPROFDATA) { task_unlock(task); return KERN_RESOURCE_SHORTAGE; /* can't allocate a buffer, quit */ } task->profil_buffer = pbuf; if (!set_pbuf_nb(pbuf, NB_PROF_BUFFER-1)) { pbuf_free(pbuf); task_unlock(task); return KERN_FAILURE; } reset_pbuf_area(pbuf); pbuf->prof_port = reply; /* assoc. buffer with reply port */ } else { /* We want to stop profiling/tracing */ task_lock(task); if (!task->task_profiled) { /* but this task is not being profiled */ task_unlock(task); return(KERN_INVALID_ARGUMENT); } } /* * turnon = FALSE && task_profile = TRUE || * turnon = TRUE && task_profile = FALSE */ if (turnon != task->task_profiled) { int actual, i; thread_act_t thr_act; if (turnon && profile_thread_id == THREAD_NULL) /* 1st time thru? */ profile_thread_id = /* then start profile thread. */ kernel_thread(kernel_task, profile_thread); task->task_profiled = turnon; actual = task->thr_act_count; for (i = 0, thr_act = (thread_act_t)queue_first(&task->thr_acts); i < actual; i++, thr_act = (thread_act_t)queue_next(&thr_act->thr_acts)) { if (!thr_act->act_profiled_own) { thr_act->act_profiled = turnon; if (turnon) { thr_act->profil_buffer = task->profil_buffer; thr_act->act_profiled = TRUE; } else { thr_act->act_profiled = FALSE; thr_act->profil_buffer = NULLPROFDATA; } } } if (!turnon) { /* drain buffers and clean-up */ send_last_sample_buf(task->profil_buffer); pbuf_free(task->profil_buffer); task->profil_buffer = NULLPROFDATA; } } task_unlock(task); return KERN_SUCCESS; #endif /* MACH_PROF */ }