Files
mercury/runtime/mercury_report_stats.c
Julien Fischer f5e71b1e90 Fix copyright notices in recently modified files.
compiler/*.m:
library/*.m:
mdbcomp/*.m:
runtime/*.[ch]:
    As above.

    Fix spelling in some spots.
2024-02-20 15:09:17 +11:00

607 lines
19 KiB
C

// vim: ts=4 sw=4 expandtab ft=c
// Copyright (C) 2021, 2024 The Mercury team.
// This file is distributed under the terms specified in COPYING.LIB.
// This file contains code for implementing the predicates in the
// Mercury standard library that report statistics about the program's
// execution.
////////////////////////////////////////////////////////////////////////////
#include <stdlib.h>
#include <errno.h>
#include "mercury_prof_mem.h"
#include "mercury_heap_profile.h"
#include "mercury_wrapper.h" // for MR_user_time_at_last_stat
#include "mercury_report_stats.h" // for MR_user_time_at_last_stat
#ifdef MR_MPROF_PROFILE_MEMORY
#define MEMORY_PROFILE_SIZE 10 // Profile the top 10 entries.
#define MAX_REPORT_LINES 10 // Display the top 10 entries.
// local types
typedef struct MR_memprof_float_counter
{
double cells_at_period_end;
double words_at_period_end;
double cells_since_period_start;
double words_since_period_start;
} MR_memprof_float_counter;
typedef struct MR_memprof_report_entry
{
const char *name;
MR_memprof_float_counter counter;
} MR_memprof_report_entry;
// static variables
static MR_memprof_float_counter MR_overall_memprof_counter;
// local function declarations
static void MR_update_memprof_counter(MR_memprof_counter *counter,
MR_memprof_float_counter *float_counter);
static int MR_insert_into_memprof_table(const MR_memprof_report_entry
*new_entry, MR_memprof_report_entry *table,
int table_size, int next_slot);
static int MR_memory_profile_top_table(MR_memprof_record *node,
MR_memprof_report_entry *table,
int size, int next_slot);
static int MR_memory_profile_fill_table(MR_memprof_record *node,
MR_memprof_report_entry *table, int next_slot);
static int MR_memory_profile_report(FILE *fp, int *line_number,
const MR_memprof_report_entry *,
int num_entries, MR_bool complete);
static int MR_memory_profile_compare_final(const void *i1,
const void *i2);
#endif // MR_MPROF_PROFILE_MEMORY
int
MR_report_standard_stats(FILE *fp, int *line_number)
{
int user_time_at_prev_stat;
int real_time_at_prev_stat;
#if !defined(MR_HIGHLEVEL_CODE) || !defined(MR_CONSERVATIVE_GC)
MercuryEngine *eng;
#endif
#ifdef MR_MPROF_PROFILE_MEMORY
int num_table_entries;
MR_memprof_report_entry table[MEMORY_PROFILE_SIZE];
#endif
int result;
// Print timing and stack usage information.
user_time_at_prev_stat = MR_user_time_at_last_stat;
MR_user_time_at_last_stat = MR_get_user_cpu_milliseconds();
real_time_at_prev_stat = MR_real_time_at_last_stat;
MR_real_time_at_last_stat = MR_get_real_milliseconds();
#if !defined(MR_HIGHLEVEL_CODE) || !defined(MR_CONSERVATIVE_GC)
eng = MR_get_engine();
#endif
result = fprintf(fp, "[User time: +%.3fs, %.3fs,",
(MR_user_time_at_last_stat - user_time_at_prev_stat) / 1000.0,
(MR_user_time_at_last_stat - MR_user_time_at_start) / 1000.0
);
if (result < 0) {
return errno;
}
result = fprintf(fp, " Real time: +%.3fs, %.3fs,",
(MR_real_time_at_last_stat - real_time_at_prev_stat) / 1000.0,
(MR_real_time_at_last_stat - MR_real_time_at_start) / 1000.0
);
if (result < 0) {
return errno;
}
#ifndef MR_HIGHLEVEL_CODE
result = fprintf(fp, " D Stack: %.3fk, ND Stack: %.3fk,",
((char *) MR_sp - (char *)
eng->MR_eng_context.MR_ctxt_detstack_zone->MR_zone_min) / 1024.0,
((char *) MR_maxfr - (char *)
eng->MR_eng_context.MR_ctxt_nondetstack_zone->MR_zone_min) / 1024.0
);
if (result < 0) {
return errno;
}
#endif
#ifdef MR_BOEHM_GC
{
char local_var;
struct GC_stack_base base;
if (GC_SUCCESS == GC_get_stack_base(&base)) {
result = fprintf(fp, " C Stack: %.3fk,",
labs(&local_var - (char *) base.mem_base) / 1024.0
);
if (result < 0) {
return errno;
}
} else {
result = fprintf(fp, " Cannot locate C stack base.");
if (result < 0) {
return errno;
}
}
}
#endif
#ifdef MR_USE_TRAIL
#ifdef MR_THREAD_SAFE
result = fprintf(fp, ", Trail: %.3fk,",
((char *) MR_trail_ptr -
(char *) MR_CONTEXT(MR_ctxt_trail_zone)->MR_zone_min) / 1024.0
);
if (result < 0) {
return errno;
}
#else
result = fprintf(fp, " Trail: %.3fk,",
((char *) MR_trail_ptr -
(char *) MR_trail_zone->MR_zone_min) / 1024.0
);
if (result < 0) {
return errno;
}
#endif // !MR_THREAD_SAFE
#endif // !MR_USE_TRAIL
// Print heap usage information.
#ifdef MR_CONSERVATIVE_GC
#ifdef MR_BOEHM_GC
result = fprintf(fp, "\n#GCs: %lu, ",
(unsigned long) GC_get_gc_no());
if (result < 0) {
return errno;
}
{
unsigned long total_gc_time;
total_gc_time = GC_get_full_gc_total_time();
if (total_gc_time != 0) {
// Convert from unsigned long milliseconds to float seconds.
result = fprintf(fp, "total GC time: %.2fs, ",
(float) total_gc_time / (float) 1000);
if (result < 0) {
return errno;
}
}
}
result = fprintf(fp, "Heap used since last GC: %.3fk, Total used: %.3fk",
GC_get_bytes_since_gc() / 1024.0,
GC_get_heap_size() / 1024.0
);
if (result < 0) {
return errno;
}
(*line_number)++;
#endif
#else // !MR_CONSERVATIVE_GC
result = fprintf(fp, "\nHeap: %.3fk",
((char *) MR_hp - (char *) eng->MR_eng_heap_zone->MR_zone_min) / 1024.0
);
if (result < 0) {
return errno;
}
(*line_number)++;
#endif // !MR_CONSERVATIVE_GC
#ifdef MR_MPROF_PROFILE_MEMORY
// Update the overall counter (this needs to be done first,
// so that the percentages come out right).
MR_update_memprof_counter(&MR_memprof_overall, &MR_overall_memprof_counter);
// Print out the per-procedure memory profile (top N entries).
num_table_entries = MR_memory_profile_top_table(MR_memprof_procs.root,
table, MEMORY_PROFILE_SIZE, 0);
if (fprintf(fp, "\nMemory profile by procedure\n") < 0) {
return errno;
}
*line_number += 2;
result = MR_memory_profile_report(fp, line_number, table,
num_table_entries, MR_FALSE);
if (result != 0) {
return result;
}
// Print out the per-type memory profile (top N entries).
num_table_entries = MR_memory_profile_top_table(MR_memprof_types.root,
table, MEMORY_PROFILE_SIZE, 0);
result = fprintf(fp, "\nMemory profile by type\n");
if (result < 0) {
return errno;
}
*line_number += 2;
result = MR_memory_profile_report(fp, line_number, table,
num_table_entries, MR_FALSE);
if (result != 0) {
return result;
}
// Print out the overall memory usage.
result = fprintf(fp, "Overall memory usage:"
"+%8.8g %8.8g cells, +%8.8g %8.8g words\n",
MR_overall_memprof_counter.cells_since_period_start,
MR_overall_memprof_counter.cells_at_period_end,
MR_overall_memprof_counter.words_since_period_start,
MR_overall_memprof_counter.words_at_period_end
);
if (result < 0) {
return errno;
}
(*line_number)++;
#endif // MR_MPROF_PROFILE_MEMORY
result = fprintf(fp, "]\n");
if (result < 0) {
return errno;
}
(*line_number)++;
return 0;
}
int
MR_full_memory_stats_available(void)
{
#ifndef MR_MPROF_PROFILE_MEMORY
return 0;
#else
return 1;
#endif
}
int
MR_report_full_memory_stats(FILE *fp, int *line_number)
{
#ifndef MR_MPROF_PROFILE_MEMORY
if (fprintf(fp, "\nMemory profiling is not enabled.\n") < 0) {
return errno;
}
*line_number += 2;
#else
int num_table_entries;
int table_size;
MR_memprof_report_entry *table;
int result;
// Update the overall counter (this needs to be done first,
// so that the percentages come out right).
MR_update_memprof_counter(&MR_memprof_overall, &MR_overall_memprof_counter);
// Allocate space for the table,
if (MR_memprof_procs.num_entries > MR_memprof_types.num_entries) {
table_size = MR_memprof_procs.num_entries;
} else {
table_size = MR_memprof_types.num_entries;
}
table = MR_GC_NEW_ARRAY(MR_memprof_report_entry, table_size);
// Print the by-procedure memory profile.
num_table_entries = MR_memory_profile_fill_table(MR_memprof_procs.root,
table, 0);
qsort(table, MR_memprof_procs.num_entries, sizeof(MR_memprof_report_entry),
MR_memory_profile_compare_final);
result = fprintf(fp, "\nMemory profile by procedure\n");
if (result < 0) {
return errno;
}
*line_number += 2;
result = fprintf(fp, "%14s %14s %s\n",
"Cells", "Words", "Procedure label");
if (result < 0) {
return errno;
}
(*line_number)++;
result = MR_memory_profile_report(fp, line_number, table,
num_table_entries, MR_TRUE);
if (result != 0) {
return result;
}
// Print the by-type memory profile.
num_table_entries = MR_memory_profile_fill_table(MR_memprof_types.root,
table, 0);
qsort(table, MR_memprof_types.num_entries, sizeof(MR_memprof_report_entry),
MR_memory_profile_compare_final);
result = fprintf(fp, "\nMemory profile by type\n");
if (result < 0) {
return errno;
}
*line_number += 2;
result = fprintf(fp, "%14s %14s %s\n",
"Cells", "Words", "Procedure label");
if (result < 0) {
return errno;
}
(*line_number)++;
result = MR_memory_profile_report(fp, line_number, table,
num_table_entries, MR_TRUE);
if (result != 0) {
return result;
}
// Deallocate space for the table.
MR_GC_free(table);
// Print the overall memory usage.
result = fprintf(fp, "\nOverall memory usage: %8.8g cells, %8.8g words\n",
MR_overall_memprof_counter.cells_at_period_end,
MR_overall_memprof_counter.words_at_period_end
);
if (result < 0) {
return errno;
}
*line_number += 2;
#endif // MR_MPROF_PROFILE_MEMORY
return 0;
}
#ifdef MR_MPROF_PROFILE_MEMORY
// MR_update_memprof_counter(counter, float_counter):
//
// Copy the data for a period from `counter' into `float_counter'
// (changing the format slightly as we go), and update `counter'
// to reflect the start of a new period.
static void
MR_update_memprof_counter(MR_memprof_counter *counter,
MR_memprof_float_counter *float_counter)
{
MR_add_two_dwords(counter->cells_at_period_start,
counter->cells_since_period_start);
MR_add_two_dwords(counter->words_at_period_start,
counter->words_since_period_start);
MR_convert_dword_to_double(counter->cells_since_period_start,
float_counter->cells_since_period_start);
MR_convert_dword_to_double(counter->words_since_period_start,
float_counter->words_since_period_start);
// Since the 'at start' numbers have already been incremented,
// they now refer to the start of the *next* period.
MR_convert_dword_to_double(counter->cells_at_period_start,
float_counter->cells_at_period_end);
MR_convert_dword_to_double(counter->words_at_period_start,
float_counter->words_at_period_end);
MR_zero_dword(counter->cells_since_period_start);
MR_zero_dword(counter->words_since_period_start);
}
// Insert an entry into the table of the top `table_size' entries.
// Entries are ranked according to their words_since_period_start.
// (This is an arbitrary choice; we might equally well choose
// to order them by cells_since_period_start. I prefer words (zs).)
// Entries that are not in the top `table_size' are discarded.
static int
MR_insert_into_memprof_table(const MR_memprof_report_entry *new_entry,
MR_memprof_report_entry *table, int table_size, int next_slot)
{
int slot;
// Ignore entries whose counts are zero (allowing for rounding).
if (new_entry->counter.words_since_period_start < 1.0) {
return next_slot;
}
// Find the slot where this entry should be inserted.
// Start at the end and work backwards until we find
// the start of the table or until we find a table
// entry which ranks higher that the new entry.
slot = next_slot;
while (slot > 0 && table[slot - 1].counter.words_since_period_start
< new_entry->counter.words_since_period_start)
{
slot--;
}
// If this entry fits in the table, then shuffle the displaced entries
// to the right, insert the new entry in the table, and increment next_slot
// (unless it is already at the end of the table).
if (slot < table_size) {
#if 0
// The following code is disabled because it causes gcc (2.7.2) internal
// errors (``fixed or forbidden register spilled'') on x86 machines when
// using gcc global register variables.
int i;
for (i = table_size - 1; i > slot; i--) {
table[i] = table[i - 1];
}
table[slot] = *new_entry;
#else
memmove(&table[slot + 1], &table[slot],
(table_size - slot - 1) * sizeof(*table));
MR_memcpy(&table[slot], new_entry, sizeof(*table));
#endif
if (next_slot < table_size) {
next_slot++;
}
}
return next_slot;
}
// MR_memory_profile_top_table(node, table, table_size, next_slot):
//
// Insert the entries for `node' and its children into `table', which is
// big enough to hold the top `table_size' entries. `next_slot' specifies
// the number of entries currently in the table. Returns the new value
// of `next_slot'.
static int
MR_memory_profile_top_table(MR_memprof_record *node,
MR_memprof_report_entry *table, int table_size, int next_slot)
{
MR_memprof_report_entry new_entry;
if (node != NULL) {
next_slot = MR_memory_profile_top_table(node->left,
table, table_size, next_slot);
if (node->type_name != NULL) {
new_entry.name = node->type_name;
} else {
new_entry.name = MR_lookup_entry_or_internal(node->proc);
}
MR_update_memprof_counter(&node->counter, &new_entry.counter);
next_slot = MR_insert_into_memprof_table(&new_entry,
table, table_size, next_slot);
next_slot = MR_memory_profile_top_table(node->right,
table, table_size, next_slot);
}
return next_slot;
}
// MR_memory_profile_fill_table(node, table, next_slot):
// Insert the entries for `node' and its children into `table', which the
// caller guarantees is big enough to hold them all. `next_slot' specifies
// the number of entries currently in the table. Returns the new value
// of `next_slot'.
static int
MR_memory_profile_fill_table(MR_memprof_record *node,
MR_memprof_report_entry *table, int next_slot)
{
if (node != NULL) {
next_slot = MR_memory_profile_fill_table(node->left,
table, next_slot);
if (node->type_name != NULL) {
table[next_slot].name = node->type_name;
} else {
table[next_slot].name = MR_lookup_entry_or_internal(node->proc);
}
MR_update_memprof_counter(&node->counter, &table[next_slot].counter);
next_slot++;
next_slot = MR_memory_profile_fill_table(node->right,
table, next_slot);
}
return next_slot;
}
// MR_memory_profile_report(fp, line_number, table, num_entries, complete):
//
// Print out a profiling report for the specified table.
// Returns zero on success, or errno if an error occurs.
static int
MR_memory_profile_report(FILE *fp, int *line_number,
const MR_memprof_report_entry *table, int num_entries, MR_bool complete)
{
int i;
const char *name;
int result;
if (complete) {
if (MR_overall_memprof_counter.cells_at_period_end < 1.0
|| MR_overall_memprof_counter.words_at_period_end < 1.0)
{
result = fprintf(fp, "no allocations to report\n");
if (result < 0) {
return errno;
}
(*line_number)++;
return 0;
}
} else {
if (MR_overall_memprof_counter.cells_since_period_start < 1.0
|| MR_overall_memprof_counter.words_since_period_start < 1.0)
{
result = fprintf(fp, "no allocations to report\n");
if (result < 0) {
return errno;
}
(*line_number)++;
return 0;
}
}
if (num_entries > MAX_REPORT_LINES && !complete) {
num_entries = MAX_REPORT_LINES;
}
for (i = 0; i < num_entries; i++) {
if (complete) {
result = fprintf(fp, "%8.8g/%4.1f%% %8.8g/%4.1f%% %s\n",
table[i].counter.cells_at_period_end,
100 * table[i].counter.cells_at_period_end /
MR_overall_memprof_counter.cells_at_period_end,
table[i].counter.words_at_period_end,
100 * table[i].counter.words_at_period_end /
MR_overall_memprof_counter.words_at_period_end,
table[i].name
);
if (result < 0) {
return errno;
}
(*line_number)++;
} else {
result = fprintf(fp, "%8.8g/%4.1f%% %8.8g/%4.1f%% %s\n",
table[i].counter.cells_since_period_start,
100 * table[i].counter.cells_since_period_start /
MR_overall_memprof_counter.cells_since_period_start,
table[i].counter.words_since_period_start,
100 * table[i].counter.words_since_period_start /
MR_overall_memprof_counter.words_since_period_start,
table[i].name
);
if (result < 0) {
return errno;
}
(*line_number)++;
}
}
return 0;
}
// Comparison routine used for qsort().
// Compares two MR_memprof_report_entry structures.
static int
MR_memory_profile_compare_final(const void *i1, const void *i2)
{
const MR_memprof_report_entry *e1 = (const MR_memprof_report_entry *) i1;
const MR_memprof_report_entry *e2 = (const MR_memprof_report_entry *) i2;
if (e1->counter.words_at_period_end < e2->counter.words_at_period_end)
{
return 1;
} else if
(e1->counter.words_at_period_end > e2->counter.words_at_period_end)
{
return -1;
} else {
return strcmp(e1->name, e2->name);
}
}
#endif // MR_MPROF_PROFILE_MEMORY