* Changed the profiling API: Instead of sending all functions that shall

be tracked to the kernel, which then counts the hits, an area is
  passed to kernel in which the hits are recorded. When the area is
  full, the debugger is notified. For some reason that part doesn't work
  yet -- the whole system freezes when waiting for a reply.
* Reorganized the profile tool code a bit. For one with respect to the
  changed API, but also to prepare tracking of image creation/deletion.


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@27640 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Ingo Weinhold
2008-09-20 00:34:03 +00:00
parent 4ae1ed90b8
commit 424f833bc9
5 changed files with 658 additions and 440 deletions
+163 -221
View File
@@ -39,11 +39,6 @@
#endif
struct function_profile_info : debug_profile_function {
int32 index;
};
static port_id sDefaultDebuggerPort = -1;
// accessed atomically
@@ -57,15 +52,6 @@ static status_t ensure_debugger_installed(team_id teamID, port_id *port = NULL);
static void get_team_debug_info(team_debug_info &teamDebugInfo);
struct ProfileFunctionComparator {
inline bool operator()(const function_profile_info& a,
const function_profile_info& b) const
{
return a.base < b.base;
}
};
static status_t
kill_interruptable_write_port(port_id port, int32 code, const void *buffer,
size_t bufferSize)
@@ -296,8 +282,8 @@ init_thread_debug_info(struct thread_debug_info *info)
info->debug_port = -1;
info->ignore_signals = 0;
info->ignore_signals_once = 0;
info->profile.functions = NULL;
info->profile.result = NULL;
info->profile.sample_area = -1;
info->profile.samples = NULL;
info->profile.installed_timer = NULL;
}
}
@@ -321,8 +307,8 @@ clear_thread_debug_info(struct thread_debug_info *info, bool dying)
info->debug_port = -1;
info->ignore_signals = 0;
info->ignore_signals_once = 0;
info->profile.functions = NULL;
info->profile.result = NULL;
info->profile.sample_area = -1;
info->profile.samples = NULL;
}
}
@@ -331,8 +317,14 @@ void
destroy_thread_debug_info(struct thread_debug_info *info)
{
if (info) {
free(info->profile.functions);
free(info->profile.result);
area_id sampleArea = info->profile.sample_area;
if (sampleArea >= 0) {
area_info areaInfo;
if (get_area_info(sampleArea, &areaInfo) == B_OK) {
unlock_memory(areaInfo.address, areaInfo.size, B_READ_DEVICE);
delete_area(sampleArea);
}
}
arch_destroy_thread_debug_info(&info->arch_info);
@@ -1002,14 +994,14 @@ user_debug_thread_exiting(struct thread* thread)
SpinLocker threadLocker(gThreadSpinlock);
thread_debug_info& threadDebugInfo = thread->debug_info;
if (threadDebugInfo.profile.functions == NULL)
if (threadDebugInfo.profile.samples == NULL)
return;
int32 functionCount = threadDebugInfo.profile.function_count;
function_profile_info* profileFunctions = threadDebugInfo.profile.functions;
debug_profiler_stopped* profileResult = threadDebugInfo.profile.result;
threadDebugInfo.profile.functions = NULL;
threadDebugInfo.profile.result = NULL;
area_id sampleArea = threadDebugInfo.profile.sample_area;
int32 sampleCount = threadDebugInfo.profile.sample_count;
int32 stackDepth = threadDebugInfo.profile.stack_depth;
threadDebugInfo.profile.sample_area = -1;
threadDebugInfo.profile.samples = NULL;
atomic_or(&threadDebugInfo.flags, B_THREAD_DEBUG_DYING);
@@ -1017,13 +1009,23 @@ user_debug_thread_exiting(struct thread* thread)
interruptsLocker.Unlock();
// notify the debugger
size_t messageSize = sizeof(debug_profiler_stopped)
+ 8 * (functionCount - 1);
debugger_write(debuggerPort, B_DEBUGGER_MESSAGE_PROFILER_STOPPED,
profileResult, messageSize, false);
debug_profiler_update message;
message.origin.thread = thread->id;
message.origin.team = thread->team->id;
message.origin.nub_port = -1; // asynchronous message
message.sample_count = sampleCount;
message.stack_depth = stackDepth;
message.stopped = true;
debugger_write(debuggerPort, B_DEBUGGER_MESSAGE_PROFILER_UPDATE,
&message, sizeof(message), false);
free(profileFunctions);
free(profileResult);
if (sampleArea >= 0) {
area_info areaInfo;
if (get_area_info(sampleArea, &areaInfo) == B_OK) {
unlock_memory(areaInfo.address, areaInfo.size, B_READ_DEVICE);
delete_area(sampleArea);
}
}
}
@@ -1145,60 +1147,49 @@ schedule_profiling_timer(struct thread* thread, bigtime_t interval)
}
static function_profile_info*
find_profiled_function(const thread_debug_info& debugInfo, addr_t address)
{
// binary search the function
function_profile_info* functions = debugInfo.profile.functions;
int32 lower = 0;
int32 upper = debugInfo.profile.function_count;
while (lower < upper) {
int32 mid = (lower + upper) / 2;
if (address >= functions[mid].base + functions[mid].size)
lower = mid + 1;
else
upper = mid;
}
if (lower == debugInfo.profile.function_count)
return NULL;
function_profile_info* function = &functions[lower];
if (address >= function->base && address < function->base + function->size)
return function;
return NULL;
}
static int32
profiling_event(timer* /*unused*/)
{
struct thread* thread = thread_get_current_thread();
thread_debug_info& debugInfo = thread->debug_info;
if (debugInfo.profile.functions != NULL) {
// Find the hit function and increment the tick counter. We
addr_t returnAddresses[B_DEBUG_STACK_TRACE_DEPTH];
int32 count = arch_debug_get_stack_trace(returnAddresses,
B_DEBUG_STACK_TRACE_DEPTH, 1, 0, false);
if (debugInfo.profile.samples != NULL) {
int32 stackDepth = debugInfo.profile.stack_depth;
addr_t* returnAddresses = debugInfo.profile.samples
+ debugInfo.profile.sample_count;
int32 count = arch_debug_get_stack_trace(returnAddresses, stackDepth, 1,
0, false);
function_profile_info* function = NULL;
for (int32 i = 0; i < count; i++) {
function = find_profiled_function(debugInfo, returnAddresses[i]);
if (function != NULL)
break;
for (int32 i = count; i < stackDepth; i++)
returnAddresses[i] = 0;
debugInfo.profile.sample_count += stackDepth;
int32 sampleCount = debugInfo.profile.sample_count;
if (debugInfo.profile.max_samples - sampleCount < stackDepth) {
// The sample buffer is full; notify the debugger.
debugInfo.profile.sample_count = 0;
RELEASE_THREAD_LOCK();
enable_interrupts();
// prepare the message
debug_profiler_update message;
message.sample_count = sampleCount;
message.stack_depth = stackDepth;
message.stopped = true;
thread_hit_debug_event(B_DEBUGGER_MESSAGE_PROFILER_UPDATE, &message,
sizeof(message), false);
disable_interrupts();
GRAB_THREAD_LOCK();
}
}
if (function != NULL)
debugInfo.profile.result->function_ticks[function->index]++;
else
debugInfo.profile.result->missed_ticks++;
debugInfo.profile.result->total_ticks++;
// reschedule timer
// reschedule timer
if (debugInfo.profile.samples != NULL)
schedule_profiling_timer(thread, debugInfo.profile.interval);
} else
else
debugInfo.profile.installed_timer = NULL;
return B_HANDLED_INTERRUPT;
@@ -1225,7 +1216,7 @@ user_debug_thread_unscheduled(struct thread* thread)
void
user_debug_thread_scheduled(struct thread* thread)
{
if (thread->debug_info.profile.functions != NULL) {
if (thread->debug_info.profile.samples != NULL) {
// install profiling timer
schedule_profiling_timer(thread,
thread->debug_info.profile.interval_left);
@@ -1537,7 +1528,7 @@ debug_nub_thread(void *)
int32 command;
debug_nub_message_data message;
ssize_t messageSize = read_port_etc(port, &command, &message,
sizeof(message), B_PEEK_PORT_MESSAGE | B_KILL_CAN_INTERRUPT, 0);
sizeof(message), B_KILL_CAN_INTERRUPT, 0);
if (messageSize < 0) {
// The port is no longer valid or we were interrupted by a kill
@@ -1561,12 +1552,10 @@ debug_nub_thread(void *)
debug_nub_get_signal_masks_reply get_signal_masks;
debug_nub_get_signal_handler_reply get_signal_handler;
debug_nub_start_profiler_reply start_profiler;
debug_profiler_stopped stop_profiler;
debug_profiler_update profiler_update;
} reply;
void* replyToSend = &reply;
int32 replySize = 0;
port_id replyPort = -1;
bool removeCommandMessage = true;
// process the command
switch (command) {
@@ -2112,128 +2101,85 @@ debug_nub_thread(void *)
// get the parameters
thread_id threadID = message.start_profiler.thread;
replyPort = message.start_profiler.reply_port;
int32 functionCount = message.start_profiler.function_count;
area_id sampleArea = message.start_profiler.sample_area;
int32 stackDepth = message.start_profiler.stack_depth;
bigtime_t interval = max_c(message.start_profiler.interval,
B_DEBUG_MIN_PROFILE_INTERVAL);
status_t result = B_OK;
TRACE(("nub thread %ld: B_DEBUG_START_PROFILER: "
"thread: %ld, %ld functions\n", nubThread->id, threadID,
functionCount));
"thread: %ld, sample area: %ld\n", nubThread->id, threadID,
sampleArea));
if (functionCount < 1
|| functionCount > B_DEBUG_MAX_PROFILE_FUNCTIONS) {
result = B_BAD_VALUE;
}
if (stackDepth < 1)
stackDepth = 1;
else if (stackDepth > B_DEBUG_STACK_TRACE_DEPTH)
stackDepth = B_DEBUG_STACK_TRACE_DEPTH;
// allocate memory for the complete message
debug_nub_start_profiler* profileMessage = NULL;
size_t size = 0;
// clone the sample area
area_info areaInfo;
if (result == B_OK)
result = get_area_info(sampleArea, &areaInfo);
area_id clonedSampleArea = -1;
void* samples = NULL;
if (result == B_OK) {
size = (addr_t)&message.start_profiler.functions[
functionCount]
- (addr_t)&message.start_profiler;
profileMessage = (debug_nub_start_profiler*)malloc(size);
if (profileMessage == NULL)
result = B_NO_MEMORY;
}
MemoryDeleter profileMessageDeleter(profileMessage);
// read the complete message from the port
if (result == B_OK) {
int32 dummy;
ssize_t bytesRead = read_port_etc(port, &dummy,
profileMessage, size, B_RELATIVE_TIMEOUT, 0);
if (bytesRead < 0) {
result = bytesRead;
} else {
removeCommandMessage = false;
if ((size_t)bytesRead != size)
result = B_BAD_VALUE;
}
}
// allocate memory for the function infos
function_profile_info* profileFunctions = NULL;
if (result == B_OK) {
profileFunctions = (function_profile_info*)malloc(
sizeof(function_profile_info) * functionCount);
if (profileFunctions == NULL)
result = B_NO_MEMORY;
}
MemoryDeleter profileFunctionsDeleter(profileFunctions);
// allocate memory for the reply
debug_profiler_stopped* profileResult = NULL;
size_t profileResultSize = 0;
if (result == B_OK) {
profileResultSize = sizeof(debug_profiler_stopped)
+ 8 * (functionCount - 1);
profileResult
= (debug_profiler_stopped*)malloc(profileResultSize);
if (profileResult == NULL)
result = B_NO_MEMORY;
}
MemoryDeleter profileResultDeleter(profileResult);
// transfer the function array from the message
if (result == B_OK) {
for (int32 i = 0; i < functionCount; i++) {
profileFunctions[i].base
= profileMessage->functions[i].base;
profileFunctions[i].size
= profileMessage->functions[i].size;
profileFunctions[i].index = i;
}
}
// sort the functions and prepare the reply
if (result == B_OK) {
std::sort(profileFunctions,
profileFunctions + functionCount,
ProfileFunctionComparator());
memset(profileResult, 0, profileResultSize);
profileResult->origin.thread = threadID;
profileResult->origin.team = nubThread->team->id;
profileResult->origin.nub_port = -1;
profileResult->interval = max_c(profileMessage->interval,
B_DEBUG_MIN_PROFILE_INTERVAL);
profileResult->function_count = functionCount;
clonedSampleArea = clone_area("profiling samples", &samples,
B_ANY_KERNEL_ADDRESS, B_READ_AREA | B_WRITE_AREA,
sampleArea);
if (clonedSampleArea >= 0) {
// we need the memory locked
result = lock_memory(samples, areaInfo.size,
B_READ_DEVICE);
if (result != B_OK) {
delete_area(clonedSampleArea);
clonedSampleArea = -1;
}
} else
result = clonedSampleArea;
}
// get the thread and set the profile info
cpu_status state = disable_interrupts();
GRAB_THREAD_LOCK();
struct thread *thread
= thread_get_thread_struct_locked(threadID);
if (thread && thread->team == nubThread->team) {
thread_debug_info &threadDebugInfo = thread->debug_info;
if (threadDebugInfo.profile.functions == NULL) {
threadDebugInfo.profile.interval
= profileResult->interval;
threadDebugInfo.profile.interval_left
= threadDebugInfo.profile.interval;
threadDebugInfo.profile.function_count = functionCount;
threadDebugInfo.profile.functions = profileFunctions;
threadDebugInfo.profile.result = profileResult;
threadDebugInfo.profile.installed_timer = NULL;
} else
result = B_BAD_VALUE;
} else
result = B_BAD_THREAD_ID;
RELEASE_THREAD_LOCK();
restore_interrupts(state);
// if all went well, keep the allocated structures
if (result == B_OK) {
profileFunctionsDeleter.Detach();
profileResultDeleter.Detach();
cpu_status state = disable_interrupts();
GRAB_THREAD_LOCK();
struct thread *thread
= thread_get_thread_struct_locked(threadID);
if (thread && thread->team == nubThread->team) {
thread_debug_info &threadDebugInfo = thread->debug_info;
if (threadDebugInfo.profile.samples == NULL) {
threadDebugInfo.profile.interval = interval;
threadDebugInfo.profile.sample_area
= clonedSampleArea;
threadDebugInfo.profile.samples = (addr_t*)samples;
threadDebugInfo.profile.max_samples
= areaInfo.size / sizeof(addr_t);
threadDebugInfo.profile.sample_count = 0;
threadDebugInfo.profile.stack_depth = stackDepth;
threadDebugInfo.profile.interval_left = interval;
threadDebugInfo.profile.installed_timer = NULL;
} else
result = B_BAD_VALUE;
} else
result = B_BAD_THREAD_ID;
RELEASE_THREAD_LOCK();
restore_interrupts(state);
}
// on error unlock and delete the sample area
if (result != B_OK) {
if (clonedSampleArea >= 0) {
unlock_memory(samples, areaInfo.size, B_READ_DEVICE);
delete_area(clonedSampleArea);
}
}
// send a reply to the debugger
// TODO: profile_event
reply.start_profiler.error = result;
reply.start_profiler.interval = interval;
sendReply = true;
replySize = sizeof(reply.start_profiler);
@@ -2250,9 +2196,10 @@ debug_nub_thread(void *)
TRACE(("nub thread %ld: B_DEBUG_STOP_PROFILER: "
"thread: %ld\n", nubThread->id, threadID));
function_profile_info* profileFunctions = NULL;
debug_profiler_stopped* profileResult = NULL;
int32 functionCount = 0;
area_id sampleArea = -1;
addr_t* samples = NULL;
int32 sampleCount = 0;
int32 stackDepth = 0;
// get the thread and detach the profile info
cpu_status state = disable_interrupts();
@@ -2262,12 +2209,13 @@ debug_nub_thread(void *)
= thread_get_thread_struct_locked(threadID);
if (thread && thread->team == nubThread->team) {
thread_debug_info &threadDebugInfo = thread->debug_info;
if (threadDebugInfo.profile.functions != NULL) {
functionCount = threadDebugInfo.profile.function_count;
profileFunctions = threadDebugInfo.profile.functions;
profileResult = threadDebugInfo.profile.result;
threadDebugInfo.profile.functions = NULL;
threadDebugInfo.profile.result = NULL;
if (threadDebugInfo.profile.samples != NULL) {
sampleArea = threadDebugInfo.profile.sample_area;
samples = threadDebugInfo.profile.samples;
sampleCount = threadDebugInfo.profile.sample_count;
stackDepth = threadDebugInfo.profile.stack_depth;
threadDebugInfo.profile.sample_area = -1;
threadDebugInfo.profile.samples = NULL;
} else
result = B_BAD_VALUE;
} else
@@ -2278,37 +2226,31 @@ debug_nub_thread(void *)
// prepare the reply
if (result == B_OK) {
replyToSend = profileResult;
replySize = sizeof(debug_profiler_stopped)
+ 8 * (functionCount - 1);
} else {
reply.stop_profiler.origin.thread = result;
reply.stop_profiler.total_ticks = 0;
reply.stop_profiler.missed_ticks = 0;
replySize = sizeof(reply.stop_profiler);
}
reply.profiler_update.origin.thread = threadID;
// TODO: profile_event
reply.profiler_update.stack_depth = stackDepth;
reply.profiler_update.sample_count = sampleCount;
reply.profiler_update.stopped = true;
} else
reply.profiler_update.origin.thread = result;
replySize = sizeof(debug_profiler_update);
sendReply = true;
free(profileFunctions);
// profileResult is the reply to be sent and will be deleted
// after sending.
if (sampleArea >= 0) {
area_info areaInfo;
if (get_area_info(sampleArea, &areaInfo) == B_OK) {
unlock_memory(samples, areaInfo.size, B_READ_DEVICE);
delete_area(sampleArea);
}
}
}
}
// We only peeked the command message -- unless the command handler did
// that already, we need to remove the message from the port.
if (removeCommandMessage) {
int32 dummy;
read_port_etc(port, &dummy, NULL, 0, B_RELATIVE_TIMEOUT, 0);
}
// send the reply, if necessary
if (sendReply) {
status_t error = kill_interruptable_write_port(replyPort, command,
replyToSend, replySize);
if (replyToSend != &reply)
free(replyToSend);
&reply, replySize);
if (error != B_OK) {
// The debugger port is either not longer existing or we got