Home | History | Annotate | Line # | Download | only in pa
      1 #include "test/jemalloc_test.h"
      2 
      3 /* Additional includes for PA functionality */
      4 #include "jemalloc/internal/pa.h"
      5 #include "jemalloc/internal/tsd.h"
      6 #include "jemalloc/internal/sz.h"
      7 #include "jemalloc/internal/base.h"
      8 #include "jemalloc/internal/ehooks.h"
      9 #include "jemalloc/internal/nstime.h"
     10 #include "jemalloc/internal/hpa.h"
     11 #include "jemalloc/internal/sec.h"
     12 #include "jemalloc/internal/emap.h"
     13 #include "jemalloc/internal/psset.h"
     14 
     15 /*
     16  * PA Microbenchmark (Simplified Version)
     17  *
     18  * This tool reads allocation traces and simulates PA behavior
     19  * for testing and understanding the allocation patterns.
     20  *
     21  * Features:
     22  * 1. Reads CSV input file with format: shard_ind,operation,size_or_alloc_index,is_frequent
     23  * 2. Simulates allocations/deallocations tracking
     24  * 3. Provides basic statistics analysis
     25  * 4. Validates the framework setup
     26  */
     27 
     28 #define MAX_LINE_LENGTH 1024
     29 #define MAX_ALLOCATIONS 10000000
     30 #define MAX_ARENAS 128
     31 
     32 typedef enum { PA_ALLOC = 0, PA_DALLOC = 1 } pa_op_t;
     33 
     34 typedef struct {
     35 	int      shard_ind;
     36 	pa_op_t  operation;
     37 	size_t   size_or_alloc_index;
     38 	uint64_t nsecs;
     39 	int      is_frequent;
     40 } pa_event_t;
     41 
     42 typedef struct {
     43 	edata_t *edata;
     44 	size_t   size;
     45 	int      shard_ind;
     46 	bool     active;
     47 } allocation_record_t;
     48 
     49 /* Structure to group per-shard tracking statistics */
     50 typedef struct {
     51 	uint64_t alloc_count;     /* Number of allocations */
     52 	uint64_t dealloc_count;   /* Number of deallocations */
     53 	uint64_t bytes_allocated; /* Current bytes allocated */
     54 } shard_stats_t;
     55 
     56 /* Structure to group per-shard PA infrastructure */
     57 typedef struct {
     58 	base_t          *base;        /* Base allocator */
     59 	emap_t           emap;        /* Extent map */
     60 	pa_shard_t       pa_shard;    /* PA shard */
     61 	pa_shard_stats_t shard_stats; /* PA shard statistics */
     62 	malloc_mutex_t   stats_mtx;   /* Statistics mutex */
     63 } shard_infrastructure_t;
     64 
     65 static FILE                *g_stats_output = NULL; /* Output file for stats */
     66 static size_t               g_alloc_counter = 0; /* Global allocation counter */
     67 static allocation_record_t *g_alloc_records =
     68     NULL;                     /* Global allocation tracking */
     69 static bool g_use_sec = true; /* Global flag for SEC vs HPA-only */
     70 
     71 /* Refactored arrays using structures */
     72 static shard_stats_t *g_shard_stats = NULL; /* Per-shard tracking statistics */
     73 static shard_infrastructure_t *g_shard_infra =
     74     NULL;                         /* Per-shard PA infrastructure */
     75 static pa_central_t g_pa_central; /* Global PA central */
     76 
     77 /* Override for curtime */
     78 static hpa_hooks_t hpa_hooks_override;
     79 static nstime_t    cur_time_clock;
     80 
     81 void
     82 curtime(nstime_t *r_time, bool first_reading) {
     83 	if (first_reading) {
     84 		nstime_init_zero(r_time);
     85 	}
     86 	*r_time = cur_time_clock;
     87 }
     88 
     89 static void
     90 set_clock(uint64_t nsecs) {
     91 	nstime_init(&cur_time_clock, nsecs);
     92 }
     93 
     94 static void
     95 init_hpa_hooks() {
     96 	hpa_hooks_override = hpa_hooks_default;
     97 	hpa_hooks_override.curtime = curtime;
     98 }
     99 
    100 static void cleanup_pa_infrastructure(int num_shards);
    101 
    102 static bool
    103 initialize_pa_infrastructure(int num_shards) {
    104 	/*
    105 	 * Note when we call malloc, it resolves to je_malloc, while internal
    106 	 * functions like base_new resolve to jet_malloc.  This is because this
    107 	 * file is compiled with -DJEMALLOC_JET as a test.  This allows us to
    108 	 * completely isolate the PA infrastructure benchmark from the rest of
    109 	 * the jemalloc usage.
    110 	*/
    111 	void *dummy_jet = jet_malloc(16);
    112 	if (dummy_jet == NULL) {
    113 		fprintf(stderr, "Failed to initialize JET jemalloc\n");
    114 		return 1;
    115 	}
    116 
    117 	/* Force JET system to be fully initialized */
    118 	if (jet_mallctl("epoch", NULL, NULL, NULL, 0) != 0) {
    119 		fprintf(stderr, "Failed to initialize JET system fully\n");
    120 		jet_free(dummy_jet);
    121 		return 1;
    122 	}
    123 	jet_free(dummy_jet);
    124 
    125 	/* Allocate shard tracking statistics */
    126 	g_shard_stats = calloc(num_shards, sizeof(shard_stats_t));
    127 	if (g_shard_stats == NULL) {
    128 		printf("DEBUG: Failed to allocate shard stats\n");
    129 		return true;
    130 	}
    131 
    132 	/* Allocate shard infrastructure */
    133 	g_shard_infra = calloc(num_shards, sizeof(shard_infrastructure_t));
    134 	if (g_shard_infra == NULL) {
    135 		printf("DEBUG: Failed to allocate shard infrastructure\n");
    136 		free(g_shard_stats);
    137 		return true;
    138 	}
    139 
    140 	/* Initialize one base allocator for PA central */
    141 	base_t *central_base = base_new(tsd_tsdn(tsd_fetch()), 0 /* ind */,
    142 	    (extent_hooks_t *)&ehooks_default_extent_hooks,
    143 	    /* metadata_use_hooks */ true);
    144 	if (central_base == NULL) {
    145 		printf("DEBUG: Failed to create central_base\n");
    146 		free(g_shard_stats);
    147 		free(g_shard_infra);
    148 		return true;
    149 	}
    150 
    151 	/* Initialize PA central with HPA enabled */
    152 	init_hpa_hooks();
    153 	if (pa_central_init(&g_pa_central, central_base, true /* hpa */,
    154 	        &hpa_hooks_override)) {
    155 		printf("DEBUG: Failed to initialize PA central\n");
    156 		base_delete(tsd_tsdn(tsd_fetch()), central_base);
    157 		free(g_shard_stats);
    158 		free(g_shard_infra);
    159 		return true;
    160 	}
    161 
    162 	for (int i = 0; i < num_shards; i++) {
    163 		/* Create a separate base allocator for each shard */
    164 		g_shard_infra[i].base = base_new(tsd_tsdn(tsd_fetch()),
    165 		    i /* ind */, (extent_hooks_t *)&ehooks_default_extent_hooks,
    166 		    /* metadata_use_hooks */ true);
    167 		if (g_shard_infra[i].base == NULL) {
    168 			printf("DEBUG: Failed to create base %d\n", i);
    169 			/* Clean up partially initialized shards */
    170 			cleanup_pa_infrastructure(num_shards);
    171 			return true;
    172 		}
    173 
    174 		/* Initialize emap for this shard */
    175 		if (emap_init(&g_shard_infra[i].emap, g_shard_infra[i].base,
    176 		        /* zeroed */ false)) {
    177 			printf("DEBUG: Failed to initialize emap %d\n", i);
    178 			/* Clean up partially initialized shards */
    179 			cleanup_pa_infrastructure(num_shards);
    180 			return true;
    181 		}
    182 
    183 		/* Initialize stats mutex */
    184 		if (malloc_mutex_init(&g_shard_infra[i].stats_mtx,
    185 		        "pa_shard_stats", WITNESS_RANK_OMIT,
    186 		        malloc_mutex_rank_exclusive)) {
    187 			printf(
    188 			    "DEBUG: Failed to initialize stats mutex %d\n", i);
    189 			/* Clean up partially initialized shards */
    190 			cleanup_pa_infrastructure(num_shards);
    191 			return true;
    192 		}
    193 
    194 		/* Initialize PA shard */
    195 		nstime_t cur_time;
    196 		nstime_init_zero(&cur_time);
    197 
    198 		if (pa_shard_init(tsd_tsdn(tsd_fetch()),
    199 		        &g_shard_infra[i].pa_shard, &g_pa_central,
    200 		        &g_shard_infra[i].emap /* emap */,
    201 		        g_shard_infra[i].base, i /* ind */,
    202 		        &g_shard_infra[i].shard_stats /* stats */,
    203 		        &g_shard_infra[i].stats_mtx /* stats_mtx */,
    204 		        &cur_time /* cur_time */,
    205 		        SIZE_MAX /* oversize_threshold */,
    206 		        -1 /* dirty_decay_ms */, -1 /* muzzy_decay_ms */)) {
    207 			printf("DEBUG: Failed to initialize PA shard %d\n", i);
    208 			/* Clean up partially initialized shards */
    209 			cleanup_pa_infrastructure(num_shards);
    210 			return true;
    211 		}
    212 
    213 		/* Enable HPA for this shard with proper configuration */
    214 		hpa_shard_opts_t hpa_opts = HPA_SHARD_OPTS_DEFAULT;
    215 		hpa_opts.deferral_allowed =
    216 		    false; /* No background threads in microbench */
    217 
    218 		sec_opts_t sec_opts = SEC_OPTS_DEFAULT;
    219 		if (!g_use_sec) {
    220 			/* Disable SEC by setting nshards to 0 */
    221 			sec_opts.nshards = 0;
    222 		}
    223 
    224 		if (pa_shard_enable_hpa(tsd_tsdn(tsd_fetch()),
    225 		        &g_shard_infra[i].pa_shard, &hpa_opts, &sec_opts)) {
    226 			fprintf(
    227 			    stderr, "Failed to enable HPA on shard %d\n", i);
    228 			/* Clean up partially initialized shards */
    229 			cleanup_pa_infrastructure(num_shards);
    230 			return true;
    231 		}
    232 	}
    233 
    234 	printf("PA infrastructure configured: HPA=enabled, SEC=%s\n",
    235 	    g_use_sec ? "enabled" : "disabled");
    236 
    237 	return false;
    238 }
    239 
    240 static void
    241 cleanup_pa_infrastructure(int num_shards) {
    242 	if (g_shard_infra != NULL) {
    243 		for (int i = 0; i < num_shards; i++) {
    244 			pa_shard_destroy(
    245 			    tsd_tsdn(tsd_fetch()), &g_shard_infra[i].pa_shard);
    246 			if (g_shard_infra[i].base != NULL) {
    247 				base_delete(tsd_tsdn(tsd_fetch()),
    248 				    g_shard_infra[i].base);
    249 			}
    250 		}
    251 		free(g_shard_infra);
    252 		g_shard_infra = NULL;
    253 	}
    254 
    255 	if (g_shard_stats != NULL) {
    256 		free(g_shard_stats);
    257 		g_shard_stats = NULL;
    258 	}
    259 }
    260 
    261 static bool
    262 parse_csv_line(const char *line, pa_event_t *event) {
    263 	/* Expected format: shard_ind,operation,size_or_alloc_index,is_frequent */
    264 	int operation;
    265 	int fields = sscanf(line, "%d,%d,%zu,%lu,%d", &event->shard_ind,
    266 	    &operation, &event->size_or_alloc_index, &event->nsecs,
    267 	    &event->is_frequent);
    268 
    269 	if (fields < 4) { /* is_frequent is optional */
    270 		return false;
    271 	}
    272 
    273 	if (fields == 4) {
    274 		event->is_frequent = 0; /* Default value */
    275 	}
    276 
    277 	if (operation == 0) {
    278 		event->operation = PA_ALLOC;
    279 	} else if (operation == 1) {
    280 		event->operation = PA_DALLOC;
    281 	} else {
    282 		return false;
    283 	}
    284 
    285 	return true;
    286 }
    287 
    288 static size_t
    289 load_trace_file(const char *filename, pa_event_t **events, int *max_shard_id) {
    290 	FILE *file = fopen(filename, "r");
    291 	if (!file) {
    292 		fprintf(stderr, "Failed to open trace file: %s\n", filename);
    293 		return 0;
    294 	}
    295 
    296 	*events = malloc(MAX_ALLOCATIONS * sizeof(pa_event_t));
    297 	if (!*events) {
    298 		fclose(file);
    299 		return 0;
    300 	}
    301 
    302 	char   line[MAX_LINE_LENGTH];
    303 	size_t count = 0;
    304 	*max_shard_id = 0;
    305 
    306 	/* Skip header line */
    307 	if (fgets(line, sizeof(line), file) == NULL) {
    308 		fclose(file);
    309 		free(*events);
    310 		return 0;
    311 	}
    312 
    313 	while (fgets(line, sizeof(line), file) && count < MAX_ALLOCATIONS) {
    314 		if (parse_csv_line(line, &(*events)[count])) {
    315 			if ((*events)[count].shard_ind > *max_shard_id) {
    316 				*max_shard_id = (*events)[count].shard_ind;
    317 			}
    318 			count++;
    319 		}
    320 	}
    321 
    322 	fclose(file);
    323 	printf("Loaded %zu events from %s\n", count, filename);
    324 	printf("Maximum shard ID found: %d\n", *max_shard_id);
    325 	return count;
    326 }
    327 
    328 static void
    329 collect_hpa_stats(int shard_id, hpa_shard_stats_t *hpa_stats_out) {
    330 	/* Get tsdn for statistics collection */
    331 	tsdn_t *tsdn = tsd_tsdn(tsd_fetch());
    332 
    333 	/* Clear the output structure */
    334 	memset(hpa_stats_out, 0, sizeof(hpa_shard_stats_t));
    335 
    336 	/* Check if this shard has HPA enabled */
    337 	if (!g_shard_infra[shard_id].pa_shard.ever_used_hpa) {
    338 		return;
    339 	}
    340 
    341 	/* Merge HPA statistics from the shard */
    342 	hpa_shard_stats_merge(
    343 	    tsdn, &g_shard_infra[shard_id].pa_shard.hpa_shard, hpa_stats_out);
    344 }
    345 
    346 static void
    347 print_shard_stats(int shard_id, size_t operation_count) {
    348 	if (!g_stats_output) {
    349 		return;
    350 	}
    351 
    352 	/* Collect HPA statistics */
    353 	hpa_shard_stats_t hpa_stats;
    354 	collect_hpa_stats(shard_id, &hpa_stats);
    355 	psset_stats_t *psset_stats = &hpa_stats.psset_stats;
    356 
    357 	/* Total pageslabs */
    358 	size_t total_pageslabs = psset_stats->merged.npageslabs;
    359 
    360 	/* Full pageslabs breakdown by hugification */
    361 	size_t full_pageslabs_non_huge =
    362 	    psset_stats->full_slabs[0].npageslabs; /* [0] = non-hugified */
    363 	size_t full_pageslabs_huge =
    364 	    psset_stats->full_slabs[1].npageslabs; /* [1] = hugified */
    365 	size_t full_pageslabs_total = full_pageslabs_non_huge
    366 	    + full_pageslabs_huge;
    367 
    368 	/* Empty pageslabs breakdown by hugification */
    369 	size_t empty_pageslabs_non_huge =
    370 	    psset_stats->empty_slabs[0].npageslabs; /* [0] = non-hugified */
    371 	size_t empty_pageslabs_huge =
    372 	    psset_stats->empty_slabs[1].npageslabs; /* [1] = hugified */
    373 	size_t empty_pageslabs_total = empty_pageslabs_non_huge
    374 	    + empty_pageslabs_huge;
    375 
    376 	/* Hugified pageslabs (full + empty + partial) */
    377 	size_t hugified_pageslabs = full_pageslabs_huge + empty_pageslabs_huge;
    378 	/* Add hugified partial slabs */
    379 	for (int i = 0; i < PSSET_NPSIZES; i++) {
    380 		hugified_pageslabs +=
    381 		    psset_stats->nonfull_slabs[i][1].npageslabs;
    382 	}
    383 
    384 	/* Dirty bytes */
    385 	size_t   dirty_bytes = psset_stats->merged.ndirty * PAGE;
    386 	uint64_t npurge_passes = hpa_stats.nonderived_stats.npurge_passes;
    387 	uint64_t npurges = hpa_stats.nonderived_stats.npurges;
    388 
    389 	assert(g_use_sec
    390 	    || psset_stats->merged.nactive * PAGE
    391 	        == g_shard_stats[shard_id].bytes_allocated);
    392 	/* Output enhanced stats with detailed breakdown */
    393 	fprintf(g_stats_output,
    394 	    "%zu,%d,%lu,%lu,%lu,%zu,%zu,%zu,%zu,%zu,%zu,%zu,%zu,%zu,%lu,%lu,%lu"
    395 	    ",%lu,%lu\n",
    396 	    operation_count, shard_id, g_shard_stats[shard_id].alloc_count,
    397 	    g_shard_stats[shard_id].dealloc_count,
    398 	    g_shard_stats[shard_id].bytes_allocated, total_pageslabs,
    399 	    full_pageslabs_total, empty_pageslabs_total, hugified_pageslabs,
    400 	    full_pageslabs_non_huge, full_pageslabs_huge,
    401 	    empty_pageslabs_non_huge, empty_pageslabs_huge, dirty_bytes,
    402 	    hpa_stats.nonderived_stats.nhugifies,
    403 	    hpa_stats.nonderived_stats.nhugify_failures,
    404 	    hpa_stats.nonderived_stats.ndehugifies, npurge_passes, npurges);
    405 	fflush(g_stats_output);
    406 }
    407 
    408 static void
    409 simulate_trace(
    410     int num_shards, pa_event_t *events, size_t count, size_t stats_interval) {
    411 	uint64_t total_allocs = 0, total_deallocs = 0;
    412 	uint64_t total_allocated_bytes = 0;
    413 
    414 	printf("Starting simulation with %zu events across %d shards...\n",
    415 	    count, num_shards);
    416 
    417 	for (size_t i = 0; i < count; i++) {
    418 		pa_event_t *event = &events[i];
    419 
    420 		/* Validate shard index */
    421 		if (event->shard_ind >= num_shards) {
    422 			fprintf(stderr,
    423 			    "Warning: Invalid shard index %d (max %d)\n",
    424 			    event->shard_ind, num_shards - 1);
    425 			continue;
    426 		}
    427 
    428 		set_clock(event->nsecs);
    429 		switch (event->operation) {
    430 		case PA_ALLOC: {
    431 			size_t size = event->size_or_alloc_index;
    432 
    433 			/* Get tsdn and calculate parameters for PA */
    434 			tsdn_t *tsdn = tsd_tsdn(tsd_fetch());
    435 			szind_t szind = sz_size2index(size);
    436 			bool    slab =
    437 			    event
    438 			        ->is_frequent; /* Use frequent_reuse for slab */
    439 			bool deferred_work_generated = false;
    440 
    441 			/* Allocate using PA allocator */
    442 			edata_t *edata = pa_alloc(tsdn,
    443 			    &g_shard_infra[event->shard_ind].pa_shard, size,
    444 			    PAGE /* alignment */, slab, szind, false /* zero */,
    445 			    false /* guarded */, &deferred_work_generated);
    446 
    447 			if (edata != NULL) {
    448 				/* Store allocation record */
    449 				g_alloc_records[g_alloc_counter].edata = edata;
    450 				g_alloc_records[g_alloc_counter].size = size;
    451 				g_alloc_records[g_alloc_counter].shard_ind =
    452 				    event->shard_ind;
    453 				g_alloc_records[g_alloc_counter].active = true;
    454 				g_alloc_counter++;
    455 
    456 				/* Update shard-specific stats */
    457 				g_shard_stats[event->shard_ind].alloc_count++;
    458 				g_shard_stats[event->shard_ind]
    459 				    .bytes_allocated += size;
    460 
    461 				total_allocs++;
    462 				total_allocated_bytes += size;
    463 			}
    464 			break;
    465 		}
    466 		case PA_DALLOC: {
    467 			size_t alloc_index = event->size_or_alloc_index;
    468 			if (alloc_index < g_alloc_counter
    469 			    && g_alloc_records[alloc_index].active
    470 			    && g_alloc_records[alloc_index].shard_ind
    471 			        == event->shard_ind) {
    472 				/* Get tsdn for PA */
    473 				tsdn_t *tsdn = tsd_tsdn(tsd_fetch());
    474 				bool    deferred_work_generated = false;
    475 
    476 				/* Deallocate using PA allocator */
    477 				pa_dalloc(tsdn,
    478 				    &g_shard_infra[event->shard_ind].pa_shard,
    479 				    g_alloc_records[alloc_index].edata,
    480 				    &deferred_work_generated);
    481 
    482 				/* Update shard-specific stats */
    483 				g_shard_stats[event->shard_ind].dealloc_count++;
    484 				g_shard_stats[event->shard_ind]
    485 				    .bytes_allocated -=
    486 				    g_alloc_records[alloc_index].size;
    487 
    488 				g_alloc_records[alloc_index].active = false;
    489 				total_deallocs++;
    490 			}
    491 			break;
    492 		}
    493 		}
    494 
    495 		/* Periodic stats output and progress reporting */
    496 		if (stats_interval > 0 && (i + 1) % stats_interval == 0) {
    497 			/* Print stats for all shards */
    498 			for (int j = 0; j < num_shards; j++) {
    499 				print_shard_stats(j, i + 1);
    500 			}
    501 		}
    502 	}
    503 
    504 	printf("\nSimulation completed:\n");
    505 	printf("  Total allocations: %lu\n", total_allocs);
    506 	printf("  Total deallocations: %lu\n", total_deallocs);
    507 	printf("  Total allocated: %lu bytes\n", total_allocated_bytes);
    508 	printf("  Active allocations: %lu\n", g_alloc_counter - total_deallocs);
    509 
    510 	/* Print final stats for all shards */
    511 	printf("\nFinal shard statistics:\n");
    512 	for (int i = 0; i < num_shards; i++) {
    513 		printf(
    514 		    "  Shard %d: Allocs=%lu, Deallocs=%lu, Active Bytes=%lu\n",
    515 		    i, g_shard_stats[i].alloc_count,
    516 		    g_shard_stats[i].dealloc_count,
    517 		    g_shard_stats[i].bytes_allocated);
    518 
    519 		/* Final stats to file */
    520 		print_shard_stats(i, count);
    521 	}
    522 }
    523 
    524 static void
    525 cleanup_remaining_allocations(int num_shards) {
    526 	size_t cleaned_up = 0;
    527 
    528 	printf("Cleaning up remaining allocations...\n");
    529 
    530 	for (size_t i = 0; i < g_alloc_counter; i++) {
    531 		if (g_alloc_records[i].active) {
    532 			int shard_ind = g_alloc_records[i].shard_ind;
    533 			if (shard_ind < num_shards) {
    534 				tsdn_t *tsdn = tsd_tsdn(tsd_fetch());
    535 				bool    deferred_work_generated = false;
    536 
    537 				pa_dalloc(tsdn,
    538 				    &g_shard_infra[shard_ind].pa_shard,
    539 				    g_alloc_records[i].edata,
    540 				    &deferred_work_generated);
    541 
    542 				g_alloc_records[i].active = false;
    543 				cleaned_up++;
    544 			}
    545 		}
    546 	}
    547 
    548 	printf("Cleaned up %zu remaining allocations\n", cleaned_up);
    549 }
    550 
    551 static void
    552 print_usage(const char *program) {
    553 	printf("Usage: %s [options] <trace_file.csv>\n", program);
    554 	printf("Options:\n");
    555 	printf("  -h, --help           Show this help message\n");
    556 	printf(
    557 	    "  -o, --output FILE    Output file for statistics (default: stdout)\n");
    558 	printf("  -s, --sec            Use SEC (default)\n");
    559 	printf("  -p, --hpa-only       Use HPA only (no SEC)\n");
    560 	printf(
    561 	    "  -i, --interval N     Stats print interval (default: 100000, 0=disable)\n");
    562 	printf(
    563 	    "\nTrace file format: shard_ind,operation,size_or_alloc_index,is_frequent\n");
    564 	printf("  - operation: 0=alloc, 1=dealloc\n");
    565 	printf("  - is_frequent: optional column\n");
    566 }
    567 
    568 int
    569 main(int argc, char *argv[]) {
    570 	const char *trace_file = NULL;
    571 	const char *stats_output_file = NULL;
    572 	size_t      stats_interval = 100000; /* Default stats print interval */
    573 	/* Parse command line arguments */
    574 	for (int i = 1; i < argc; i++) {
    575 		if (strcmp(argv[i], "-h") == 0
    576 		    || strcmp(argv[i], "--help") == 0) {
    577 			print_usage(argv[0]);
    578 			return 0;
    579 		} else if (strcmp(argv[i], "-o") == 0
    580 		    || strcmp(argv[i], "--output") == 0) {
    581 			if (i + 1 >= argc) {
    582 				fprintf(stderr,
    583 				    "Error: %s requires an argument\n",
    584 				    argv[i]);
    585 				return 1;
    586 			}
    587 			stats_output_file = argv[++i];
    588 		} else if (strcmp(argv[i], "-s") == 0
    589 		    || strcmp(argv[i], "--sec") == 0) {
    590 			g_use_sec = true;
    591 		} else if (strcmp(argv[i], "-p") == 0
    592 		    || strcmp(argv[i], "--hpa-only") == 0) {
    593 			g_use_sec = false;
    594 		} else if (strcmp(argv[i], "-i") == 0
    595 		    || strcmp(argv[i], "--interval") == 0) {
    596 			if (i + 1 >= argc) {
    597 				fprintf(stderr,
    598 				    "Error: %s requires an argument\n",
    599 				    argv[i]);
    600 				return 1;
    601 			}
    602 			stats_interval = (size_t)atol(argv[++i]);
    603 		} else if (argv[i][0] != '-') {
    604 			trace_file = argv[i];
    605 		} else {
    606 			fprintf(stderr, "Unknown option: %s\n", argv[i]);
    607 			print_usage(argv[0]);
    608 			return 1;
    609 		}
    610 	}
    611 
    612 	if (!trace_file) {
    613 		fprintf(stderr, "Error: No trace file specified\n");
    614 		print_usage(argv[0]);
    615 		return 1;
    616 	}
    617 
    618 	printf("Trace file: %s\n", trace_file);
    619 	printf("Mode: %s\n", g_use_sec ? "PA with SEC" : "HPA only");
    620 
    621 	/* Open stats output file */
    622 	if (stats_output_file) {
    623 		g_stats_output = fopen(stats_output_file, "w");
    624 		if (!g_stats_output) {
    625 			fprintf(stderr,
    626 			    "Failed to open stats output file: %s\n",
    627 			    stats_output_file);
    628 			return 1;
    629 		}
    630 		printf("Stats output: %s\n", stats_output_file);
    631 
    632 		/* Write CSV header */
    633 		fprintf(g_stats_output,
    634 		    "operation_count,shard_id,alloc_count,dealloc_count,active_bytes,"
    635 		    "total_pageslabs,full_pageslabs_total,empty_pageslabs_total,hugified_pageslabs,"
    636 		    "full_pageslabs_non_huge,full_pageslabs_huge,"
    637 		    "empty_pageslabs_non_huge,empty_pageslabs_huge,"
    638 		    "dirty_bytes,nhugifies,nhugify_failures,ndehugifies,"
    639 		    "npurge_passes,npurges\n");
    640 	}
    641 
    642 	/* Load trace data and determine max number of arenas */
    643 	pa_event_t *events;
    644 	int         max_shard_id;
    645 	size_t      event_count = load_trace_file(
    646             trace_file, &events, &max_shard_id);
    647 	if (event_count == 0) {
    648 		if (g_stats_output)
    649 			fclose(g_stats_output);
    650 		return 1;
    651 	}
    652 
    653 	int num_shards = max_shard_id + 1; /* shard IDs are 0-based */
    654 	if (num_shards > MAX_ARENAS) {
    655 		fprintf(stderr, "Error: Too many arenas required (%d > %d)\n",
    656 		    num_shards, MAX_ARENAS);
    657 		free(events);
    658 		if (g_stats_output)
    659 			fclose(g_stats_output);
    660 		return 1;
    661 	}
    662 
    663 	/* Allocate allocation tracking array */
    664 	g_alloc_records = malloc(event_count * sizeof(allocation_record_t));
    665 
    666 	if (!g_alloc_records) {
    667 		fprintf(
    668 		    stderr, "Failed to allocate allocation tracking array\n");
    669 		free(events);
    670 		if (g_stats_output) {
    671 			fclose(g_stats_output);
    672 		}
    673 		return 1;
    674 	}
    675 
    676 	/* Initialize PA infrastructure */
    677 	if (initialize_pa_infrastructure(num_shards)) {
    678 		fprintf(stderr, "Failed to initialize PA infrastructure\n");
    679 		free(events);
    680 		free(g_alloc_records);
    681 		if (g_stats_output) {
    682 			fclose(g_stats_output);
    683 		}
    684 		return 1;
    685 	}
    686 
    687 	/* Run simulation */
    688 	simulate_trace(num_shards, events, event_count, stats_interval);
    689 
    690 	/* Clean up remaining allocations */
    691 	cleanup_remaining_allocations(num_shards);
    692 
    693 	/* Cleanup PA infrastructure */
    694 	cleanup_pa_infrastructure(num_shards);
    695 
    696 	/* Cleanup */
    697 	free(g_alloc_records);
    698 	free(events);
    699 
    700 	if (g_stats_output) {
    701 		fclose(g_stats_output);
    702 		printf("Statistics written to: %s\n", stats_output_file);
    703 	}
    704 
    705 	return 0;
    706 }
    707