Compare commits

...

5 Commits

Author SHA1 Message Date
Patrick Lipka dcee7c054f Add stack table and summary 2025-08-08 16:18:32 +02:00
Patrick Lipka ff290e1fad add printing of region table 2025-08-08 16:14:36 +02:00
Patrick Lipka 1437339a1c add sorting logic 2025-08-08 16:10:14 +02:00
Patrick Lipka bfa35f7d62 add stats comparison 2025-08-08 15:54:28 +02:00
Patrick Lipka 2a1b8f9617 add implementations for rvprof_add* 2025-08-08 15:48:06 +02:00
2 changed files with 263 additions and 0 deletions

View File

@ -114,3 +114,88 @@ IMPLEMENT_DYNAMIC_ARRAY(function_stats_t, function_stats, INITIAL_FUNCTIONS, cle
IMPLEMENT_DYNAMIC_ARRAY(stack_info_t, stack_info, INITIAL_STACKS, cleanup_stack_info)
// SPECIALIZED ARRAY OPERATIONS
// maybe also move to macros to remove reduncancies later
int rvprof_memory_add_stack_id_to_function(int func_id, int stack_id) {
if (func_id < 0 || func_id >= g_rvprof.functions.size || stack_id < 0) {
return RVPROF_ERROR_INVALID_STATE;
}
function_stats_t* stats = &g_rvprof.functions.data[func_id];
for (int i = 0; i < stats->num_stack_ids; i++) {
if (stats->stack_ids[i] == stack_id) {
return RVPROF_SUCCESS; // already exists
}
}
// ensure capacity for new stack ID
if (stats->num_stack_ids >= stats->max_stack_ids) {
int new_size = stats->max_stack_ids == 0 ? 4 : stats->max_stack_ids * 2;
size_t old_size = stats->max_stack_ids * sizeof(int);
size_t new_size_bytes = new_size * sizeof(int);
int* new_stack_ids = rvprof_realloc(stats->stack_ids, old_size, new_size_bytes);
if (!new_stack_ids) {
return RVPROF_ERROR_MEMORY;
}
stats->stack_ids = new_stack_ids;
stats->max_stack_ids = new_size;
}
// add new stack ID
stats->stack_ids[stats->num_stack_ids] = stack_id;
stats->num_stack_ids++;
return RVPROF_SUCCESS;
}
int rvprof_memory_add_caller_to_function(int func_id, const char* caller) {
if (func_id < 0 || func_id >= g_rvprof.functions.size || !caller) {
return RVPROF_ERROR_INVALID_STATE;
}
function_stats_t* stats = &g_rvprof.functions.data[func_id];
for (int i = 0; i < stats->num_callers; i++) {
if (strcmp(stats->callers[i], caller) == 0) {
return RVPROF_SUCCESS; // already exists
}
}
// ensure capacity for new caller
if (stats->num_callers >= stats->max_callers) {
int new_size = stats->max_callers == 0 ? 4 : stats->max_callers * 2;
size_t old_size = stats->max_callers * sizeof(char*);
size_t new_size_bytes = new_size * sizeof(char*);
char** new_callers = rvprof_realloc(stats->callers, old_size, new_size_bytes);
if (!new_callers) {
return RVPROF_ERROR_MEMORY;
}
stats->callers = new_callers;
stats->max_callers = new_size;
}
char* caller_copy = rvprof_malloc(strlen(caller) + 1);
if (!caller_copy) {
return RVPROF_ERROR_MEMORY;
}
strcpy(caller_copy, caller);
stats->callers[stats->num_callers] = caller_copy;
stats->num_callers++;
// update the caller field based on number of callers
if (stats->num_callers == 1) {
// only one caller: use it
strncpy(stats->caller, caller, MAX_NAME_LEN - 1);
stats->caller[MAX_NAME_LEN - 1] = '\0';
} else {
// multiple callers: use "various"
strncpy(stats->caller, "various", MAX_NAME_LEN - 1);
stats->caller[MAX_NAME_LEN - 1] = '\0';
}
return RVPROF_SUCCESS;
}

178
src/rvprof_output.c Normal file
View File

@ -0,0 +1,178 @@
#include "rvprof_internal.h"
// comparison used to sort regions by exclusive time
static int compare_functions(const void* fn_a, const void* fn_b){
const function_stats_t* stats_a = (const function_stats_t*)fn_a;
const function_stats_t* stats_b = (const function_stats_t*)fn_b;
if (stats_a->total_exclusive_time > stats_b->total_exclusive_time){
return -1;
} else if (stats_a->total_exclusive_time < stats_b->total_exclusive_time){
return 1;
} else{
return 0;
}
}
// actual report file generation
rvprof_error_t rvprof_output_generate_report(void){
if(!g_rvprof.output_file || g_rvprof.functions.size == 0){
return RVPROF_ERROR_INVALID_STATE;
}
// safety measure: calculate total program time if not set or too small
uint64_t total_time_report = g_rvprof.total_program_time;
if (total_time_report == 0){
for(int i=0; i<g_rvprof.functions.size; i++){
function_stats_t* stats = &g_rvprof.functions.data[i];
// top-level (main)
if (strcmp(stats->caller, "---")==0){
if (stats->total_inclusive_time > total_time_report){
total_time_report = stats->total_inclusive_time;
}
}
}
}
// if still zero, sum all exclusive times
if (total_time_report == 0){
for (int i=0; i<g_rvprof.functions.size; i++){
total_time_report += g_rvprof.functions.data[i].total_exclusive_time;
}
}
// calculate elapsed time fractions
for (int i=0; i<g_rvprof.functions.size; i++){
function_stats_t* stats = &g_rvprof.functions.data[i];
if(total_time_report > 0){
stats->exclusive_percent = (double)stats->total_exclusive_time * 100.0 / (double)total_time_report;
stats->inclusive_percent = (double)stats->total_inclusive_time * 100.0 / (double)total_time_report;
} else{
stats->exclusive_percent = 0.0;
stats->inclusive_percent = 0.0;
}
}
// use stdlib's quicksort to sort functions by elapsed time
qsort(g_rvprof.functions.data, g_rvprof.functions.size, sizeof(function_stats_t), compare_functions);
// write region table
fprintf(g_rvprof.output_file, "RVProf Runtime Profile\n");
fprintf(g_rvprof.output_file, "+-------+-----------+-----------+-----------+-----------+---------------+---------------+---------------------------------------------+---------------------------------------------+------+\n");
fprintf(g_rvprof.output_file, "| Calls | t_excl[s] | t_excl[%%] | t_incl[s] | t_incl[%%] | excl_cycles | incl_cycles | Function | Caller | STID |\n");
fprintf(g_rvprof.output_file, "+-------+-----------+-----------+-----------+-----------+---------------+---------------+---------------------------------------------+---------------------------------------------+------+\n");
// add region data
for (int i = 0; i < g_rvprof.functions.size; i++) {
function_stats_t* stats = &g_rvprof.functions.data[i];
double excl_sec = rvprof_timing_to_seconds(stats->total_exclusive_time);
double incl_sec = rvprof_timing_to_seconds(stats->total_inclusive_time);
// use first stack ID for display
int display_stid = (stats->num_stack_ids > 0) ? stats->stack_ids[0] : 0;
// truncate function name if too long
char truncated_function[44];
if (strlen(stats->name) > 43) {
strncpy(truncated_function, stats->name, 40);
strcpy(truncated_function + 40, "...");
} else {
strcpy(truncated_function, stats->name);
}
// truncate caller name if too long
char truncated_caller[44];
if (strlen(stats->caller) > 43) {
strncpy(truncated_caller, stats->caller, 40);
strcpy(truncated_caller + 40, "...");
} else {
strcpy(truncated_caller, stats->caller);
}
fprintf(g_rvprof.output_file, "|%6lu |%10.3f |%10.1f |%10.3f |%10.1f |%14llu |%14llu | %-43s | %-43s |%5d |\n",
stats->call_count,
excl_sec,
stats->exclusive_percent,
incl_sec,
stats->inclusive_percent,
(unsigned long long)stats->total_exclusive_cycles,
(unsigned long long)stats->total_inclusive_cycles,
truncated_function,
truncated_caller,
display_stid);
}
fprintf(g_rvprof.output_file, "+-------+-----------+-----------+-----------+-----------+---------------+---------------+---------------------------------------------+---------------------------------------------+------+\n");
fprintf(g_rvprof.output_file, "\n");
// write global call stacks
fprintf(g_rvprof.output_file, "Global call stacks:\n");
fprintf(g_rvprof.output_file, "--------------------------------------------------------------------\n");
fprintf(g_rvprof.output_file, " STID Call stack \n");
fprintf(g_rvprof.output_file, "--------------------------------------------------------------------\n");
for (int i = 0; i < g_rvprof.stacks.size; i++) {
fprintf(g_rvprof.output_file, " STID%-3d %s\n",
g_rvprof.stacks.data[i].stid,
g_rvprof.stacks.data[i].stack_path);
}
fprintf(g_rvprof.output_file, "--------------------------------------------------------------------\n");
fprintf(g_rvprof.output_file, "\n");
// summary
double total_sec = rvprof_timing_to_seconds(total_time_report);
fprintf(g_rvprof.output_file, "Summary:\n");
fprintf(g_rvprof.output_file, " Total execution time: %.3f seconds\n", total_sec);
if (g_rvprof.cycles_avaiable && total_time_report > 0) {
fprintf(g_rvprof.output_file, " Total cycles: %llu cycles (informational)\n",
(unsigned long long)g_rvprof.total_program_cycles);
} else {
fprintf(g_rvprof.output_file, " Total cycles: N/A (cycle counter unavailable)\n");
}
fprintf(g_rvprof.output_file, " Timer resolution: nanosecond timer\n");
fprintf(g_rvprof.output_file, " Number of functions: %d\n", g_rvprof.functions.size);
fprintf(g_rvprof.output_file, " Number of stacks: %d\n", g_rvprof.stacks.size);
fprintf(g_rvprof.output_file, " Function hooks: %s\n",
g_rvprof.config.enable_hooks ? "enabled" : "disabled");
fprintf(g_rvprof.output_file, " Symbol table: %d symbols loaded (ELF parsing)\n",
g_rvprof.symbols.size);
fprintf(stderr, "RVProf: Report generation - symbols.size=%d, symbols_loaded=%d\n",
g_rvprof.symbols.size, g_rvprof.symbols_loaded);
fprintf(g_rvprof.output_file, " Region merging: %s\n",
g_rvprof.config.merge_regions ? "enabled" : "disabled");
fprintf(g_rvprof.output_file, " Memory footprint: %.1f KB\n",
(double)g_rvprof.total_memory_allocated / 1024.0);
// write 3nvironment variable info
fprintf(g_rvprof.output_file, "\nEnvironment Variables:\n");
char* disable_hooks = getenv("RVPROF_DISABLE_HOOKS");
char* disable_merge = getenv("RVPROF_DISABLE_MERGE");
char* env_output = getenv("RVPROF_OUTPUT");
fprintf(g_rvprof.output_file, " RVPROF_DISABLE_HOOKS: %s\n",
disable_hooks ? disable_hooks : "(unset)");
fprintf(g_rvprof.output_file, " RVPROF_DISABLE_MERGE: %s\n",
disable_merge ? disable_merge : "(unset)");
fprintf(g_rvprof.output_file, " RVPROF_OUTPUT: %s\n",
env_output ? env_output : "(unset)");
// print success message to stderr
const char* output_filename = "unknown";
if (g_rvprof.config.output_filename) {
output_filename = g_rvprof.config.output_filename;
} else {
output_filename = rvprof_utils_get_program_name();
if (output_filename) {
// generate the full filename like the utility function does
static char temp_filename[256];
snprintf(temp_filename, sizeof(temp_filename), "%s_rvprof.log", output_filename);
output_filename = temp_filename;
} else {
output_filename = "rvprof_output.log";
}
}
fprintf(stderr, "rvprof: Saved profiling information: %s\n", output_filename);
return RVPROF_SUCCESS;
}