| /* Low-level statistical profiling support function. Mach/Hurd version. |
| Copyright (C) 1995-2014 Free Software Foundation, Inc. |
| This file is part of the GNU C Library. |
| |
| The GNU C Library 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 2.1 of the License, or (at your option) any later version. |
| |
| The GNU C Library 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 the GNU C Library; if not, see |
| <http://www.gnu.org/licenses/>. */ |
| |
| #include <sys/types.h> |
| #include <unistd.h> |
| #include <errno.h> |
| #include <hurd.h> |
| #include <mach/mach4.h> |
| #include <mach/pc_sample.h> |
| #include <cthreads.h> |
| #include <assert.h> |
| #include <libc-internal.h> |
| |
| |
| #define MAX_PC_SAMPLES 512 /* XXX ought to be exported in kernel hdr */ |
| |
| static thread_t profile_thread = MACH_PORT_NULL; |
| static u_short *samples; |
| static size_t maxsamples; |
| static size_t pc_offset; |
| static size_t sample_scale; |
| static sampled_pc_seqno_t seqno; |
| static spin_lock_t lock = SPIN_LOCK_INITIALIZER; |
| static mach_msg_timeout_t collector_timeout; /* ms between collections. */ |
| static int profile_tick; |
| |
| /* Reply port used by profiler thread */ |
| static mach_port_t profil_reply_port; |
| |
| /* Forwards */ |
| static kern_return_t profil_task_get_sampled_pcs (mach_port_t, |
| sampled_pc_seqno_t *, |
| sampled_pc_array_t, |
| mach_msg_type_number_t *); |
| static void fetch_samples (void); |
| static void profile_waiter (void); |
| |
| /* Enable statistical profiling, writing samples of the PC into at most |
| SIZE bytes of SAMPLE_BUFFER; every processor clock tick while profiling |
| is enabled, the system examines the user PC and increments |
| SAMPLE_BUFFER[((PC - OFFSET) / 2) * SCALE / 65536]. If SCALE is zero, |
| disable profiling. Returns zero on success, -1 on error. */ |
| |
| static error_t |
| update_waiter (u_short *sample_buffer, size_t size, size_t offset, u_int scale) |
| { |
| error_t err; |
| |
| if (profile_thread == MACH_PORT_NULL) |
| { |
| /* Set up the profiling collector thread. */ |
| err = __thread_create (__mach_task_self (), &profile_thread); |
| if (! err) |
| err = __mach_setup_thread (__mach_task_self (), profile_thread, |
| &profile_waiter, NULL, NULL); |
| } |
| else |
| err = 0; |
| |
| if (! err) |
| { |
| err = __task_enable_pc_sampling (__mach_task_self (), &profile_tick, |
| SAMPLED_PC_PERIODIC); |
| if (!err && sample_scale == 0) |
| /* Profiling was not turned on, so the collector thread was |
| suspended. Resume it. */ |
| err = __thread_resume (profile_thread); |
| if (! err) |
| { |
| samples = sample_buffer; |
| maxsamples = size / sizeof *sample_buffer; |
| pc_offset = offset; |
| sample_scale = scale; |
| /* Calculate a good period for the collector thread. From TICK |
| and the kernel buffer size we get the length of time it takes |
| to fill the buffer; translate that to milliseconds for |
| mach_msg, and chop it in half for general lag factor. */ |
| collector_timeout = MAX_PC_SAMPLES * profile_tick / 1000 / 2; |
| } |
| } |
| |
| return err; |
| } |
| |
| int |
| __profile_frequency (void) |
| { |
| return profile_tick; |
| } |
| libc_hidden_def (__profile_frequency) |
| |
| int |
| __profil (u_short *sample_buffer, size_t size, size_t offset, u_int scale) |
| { |
| error_t err; |
| |
| __spin_lock (&lock); |
| |
| if (scale == 0) |
| { |
| /* Disable profiling. */ |
| int count; |
| |
| if (profile_thread != MACH_PORT_NULL) |
| __thread_suspend (profile_thread); |
| |
| /* Fetch the last set of samples */ |
| if (sample_scale) |
| fetch_samples (); |
| |
| err = __task_disable_pc_sampling (__mach_task_self (), &count); |
| sample_scale = 0; |
| seqno = 0; |
| } |
| else |
| err = update_waiter (sample_buffer, size, offset, scale); |
| |
| __spin_unlock (&lock); |
| |
| return err ? __hurd_fail (err) : 0; |
| } |
| weak_alias (__profil, profil) |
| |
| /* Fetch PC samples. This function must be very careful not to depend |
| on Hurd threadvar variables. We arrange that by using a special |
| stub arranged for at the end of this file. */ |
| static void |
| fetch_samples (void) |
| { |
| sampled_pc_t pc_samples[MAX_PC_SAMPLES]; |
| mach_msg_type_number_t nsamples, i; |
| error_t err; |
| |
| nsamples = MAX_PC_SAMPLES; |
| |
| err = profil_task_get_sampled_pcs (__mach_task_self (), &seqno, |
| pc_samples, &nsamples); |
| if (err) |
| { |
| static error_t special_profil_failure; |
| static volatile int a, b, c; |
| |
| special_profil_failure = err; |
| a = 1; |
| b = 0; |
| while (1) |
| c = a / b; |
| } |
| |
| for (i = 0; i < nsamples; ++i) |
| { |
| /* Do arithmetic in long long to avoid overflow problems. */ |
| long long pc_difference = pc_samples[i].pc - pc_offset; |
| size_t idx = ((pc_difference / 2) * sample_scale) / 65536; |
| if (idx < maxsamples) |
| ++samples[idx]; |
| } |
| } |
| |
| |
| /* This function must be very careful not to depend on Hurd threadvar |
| variables. We arrange that by using special stubs arranged for at the |
| end of this file. */ |
| static void |
| profile_waiter (void) |
| { |
| mach_msg_header_t msg; |
| mach_port_t timeout_reply_port; |
| |
| profil_reply_port = __mach_reply_port (); |
| timeout_reply_port = __mach_reply_port (); |
| |
| while (1) |
| { |
| __spin_lock (&lock); |
| |
| fetch_samples (); |
| |
| __spin_unlock (&lock); |
| |
| __mach_msg (&msg, MACH_RCV_MSG|MACH_RCV_TIMEOUT, 0, sizeof msg, |
| timeout_reply_port, collector_timeout, MACH_PORT_NULL); |
| } |
| } |
| |
| /* Fork interaction */ |
| |
| /* Before fork, lock the interlock so that we are in a clean state. */ |
| static void |
| fork_profil_prepare (void) |
| { |
| __spin_lock (&lock); |
| } |
| text_set_element (_hurd_fork_prepare_hook, fork_profil_prepare); |
| |
| /* In the parent, unlock the interlock once fork is complete. */ |
| static void |
| fork_profil_parent (void) |
| { |
| __spin_unlock (&lock); |
| } |
| text_set_element (_hurd_fork_parent_hook, fork_profil_parent); |
| |
| /* In the child, unlock the interlock, and start a profiling thread up |
| if necessary. */ |
| static void |
| fork_profil_child (void) |
| { |
| u_short *sb; |
| size_t n, o, ss; |
| error_t err; |
| |
| __spin_unlock (&lock); |
| |
| if (profile_thread != MACH_PORT_NULL) |
| { |
| __mach_port_deallocate (__mach_task_self (), profile_thread); |
| profile_thread = MACH_PORT_NULL; |
| } |
| |
| sb = samples; |
| samples = NULL; |
| n = maxsamples; |
| maxsamples = 0; |
| o = pc_offset; |
| pc_offset = 0; |
| ss = sample_scale; |
| sample_scale = 0; |
| |
| if (ss != 0) |
| { |
| err = update_waiter (sb, n * sizeof *sb, o, ss); |
| assert_perror (err); |
| } |
| } |
| text_set_element (_hurd_fork_child_hook, fork_profil_child); |
| |
| |
| |
| |
| /* Special RPC stubs for profile_waiter are made by including the normal |
| source code, with special CPP state to prevent it from doing the |
| usual thing. */ |
| |
| /* Include these first; then our #define's will take full effect, not |
| being overridden. */ |
| #include <mach/mig_support.h> |
| |
| /* This need not do anything; it is always associated with errors, which |
| are fatal in profile_waiter anyhow. */ |
| #define __mig_put_reply_port(foo) |
| |
| /* Use our static variable instead of the usual threadvar mechanism for |
| this. */ |
| #define __mig_get_reply_port() profil_reply_port |
| |
| /* Make the functions show up as static */ |
| #define mig_external static |
| |
| /* Turn off the attempt to generate ld aliasing records. */ |
| #undef weak_alias |
| #define weak_alias(a,b) |
| |
| /* And change their names to avoid confusing disasters. */ |
| #define __vm_deallocate_rpc profil_vm_deallocate |
| #define __task_get_sampled_pcs profil_task_get_sampled_pcs |
| |
| /* And include the source code */ |
| #include <../mach/RPC_task_get_sampled_pcs.c> |