Compare commits
5 Commits
b61b35b812
...
dcee7c054f
Author | SHA1 | Date |
---|---|---|
|
dcee7c054f | |
|
ff290e1fad | |
|
1437339a1c | |
|
bfa35f7d62 | |
|
2a1b8f9617 |
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue