Loading learning content...
Understanding fragmentation conceptually is one thing; measuring it in a running system is another. Without measurement, you can't answer critical questions: Is fragmentation causing our out-of-memory errors despite apparent free space? Should we trigger compaction? Is our allocator underperforming?
Fragmentation measurement transforms abstract concepts into actionable metrics. It enables:
This page explores the practical tools and techniques for quantifying both internal and external fragmentation, from simple ratios to sophisticated distribution analysis. You'll learn how production systems monitor fragmentation and how to build similar capabilities into your own systems.
By the end of this page, you will understand multiple metrics for both internal and external fragmentation, how to instrument allocators for measurement, visualization techniques for fragmentation data, real-world monitoring in Linux and other systems, and how to set thresholds and automate responses.
Fragmentation manifests differently and requires different metrics for internal versus external fragmentation. Let's establish a comprehensive taxonomy of measurement approaches.
| Metric | Formula | Measures | Range | Ideal |
|---|---|---|---|---|
| Internal Fragmentation Ratio | (Allocated - Used) / Allocated | Waste within allocations | 0 to 1 | 0 (no waste) |
| External Fragmentation Ratio | 1 - (LargestHole / TotalFree) | Scatter of free memory | 0 to 1 | 0 (one hole) |
| Memory Utilization | Used / Total | Effective memory use | 0 to 1 | High (varies) |
| Fragmentation Index | 1 - (Allocatable / TotalFree) | Usable free memory | 0 to 1 | 0 |
| Hole Count | Count of free regions | Number of discontinuities | 1 to n | 1 |
| Hole Size Variance | σ² of hole sizes | Hole size uniformity | 0 to ∞ | Low |
| 50% Rule Deviation | (Holes/Blocks) - 0.5 | Deviation from expected | -0.5 to +∞ | 0 |
Internal Fragmentation Metrics:
1. Per-Allocation IF = Allocated_Size - Used_Size
2. Absolute IF = Σ(Allocated[i] - Used[i]) for all allocations
3. IF Ratio = Absolute_IF / Total_Allocated
4. IF Percentage = IF_Ratio × 100%
External Fragmentation Metrics:
1. Basic EF Ratio = 1 - (Largest_Hole / Total_Free)
- 0 = all free memory contiguous
- 1 = free memory completely scattered
2. Fragmentation Index = 1 - (Max_Allocatable / Total_Free)
- Accounts for alignment/overhead that reduce max allocation
3. Free Memory Entropy = -Σ(pi × log(pi))
- Where pi = Size_of_Hole_i / Total_Free
- Higher entropy = more fragmentation
4. Hole Distribution Statistics:
- Mean hole size = Total_Free / Hole_Count
- Median hole size (robust to outliers)
- Standard deviation (uniformity measure)
- Min/Max hole sizes
No single metric captures all aspects of fragmentation. Use the EF Ratio for quick health checks, hole count for simple monitoring, distribution statistics for deep analysis, and the 50% rule deviation for allocator evaluation. Combine multiple metrics for comprehensive assessment.
To measure fragmentation, allocators must track additional state beyond what's needed for basic allocation. Here's how to instrument an allocator for comprehensive fragmentation measurement.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
#include <stdio.h>#include <stdlib.h>#include <stdint.h>#include <stdbool.h>#include <string.h>#include <math.h> // Block header structure (embedded in each block)typedef struct BlockHeader { size_t size; // Size of data portion size_t allocated_size; // Actual allocated size (includes padding) bool is_free; struct BlockHeader* next; struct BlockHeader* prev;} BlockHeader; // Fragmentation statisticstypedef struct { // Internal fragmentation size_t total_requested; // Sum of user-requested sizes size_t total_allocated; // Sum of actual allocated sizes size_t allocation_count; // Number of active allocations // External fragmentation size_t total_free; // Total free memory size_t largest_hole; // Largest contiguous free block size_t smallest_hole; // Smallest free block size_t hole_count; // Number of free blocks double hole_size_sum_sq; // For variance calculation // Derived metrics (computed on demand) double internal_frag_ratio; double external_frag_ratio; double utilization; double hole_size_mean; double hole_size_variance;} FragmentationStats; typedef struct { BlockHeader* head; size_t total_memory; size_t header_size; FragmentationStats stats;} InstrumentedAllocator; #define ALIGNMENT 8#define ALIGN(size) (((size) + (ALIGNMENT-1)) & ~(ALIGNMENT-1)) // Initialize allocator with given memory sizeInstrumentedAllocator* create_allocator(size_t total_size) { InstrumentedAllocator* alloc = malloc(sizeof(InstrumentedAllocator)); alloc->total_memory = total_size; alloc->header_size = ALIGN(sizeof(BlockHeader)); // Create initial free block alloc->head = malloc(sizeof(BlockHeader) + total_size); alloc->head->size = total_size; alloc->head->allocated_size = total_size; alloc->head->is_free = true; alloc->head->next = NULL; alloc->head->prev = NULL; // Initialize stats memset(&alloc->stats, 0, sizeof(FragmentationStats)); alloc->stats.total_free = total_size; alloc->stats.largest_hole = total_size; alloc->stats.smallest_hole = total_size; alloc->stats.hole_count = 1; return alloc;} // Update statistics when allocatingvoid update_stats_on_alloc(InstrumentedAllocator* alloc, size_t requested, size_t allocated, BlockHeader* block, BlockHeader* remainder) { FragmentationStats* s = &alloc->stats; // Internal fragmentation tracking s->total_requested += requested; s->total_allocated += allocated; s->allocation_count++; // External fragmentation: update free memory tracking s->total_free -= allocated; s->hole_count--; // This hole is now allocated if (remainder) { // Splitting created a new hole s->hole_count++; } // Must rescan for largest/smallest hole (simplified) // In production, maintain a sorted structure} // Update statistics when freeingvoid update_stats_on_free(InstrumentedAllocator* alloc, size_t requested, size_t allocated, bool coalesced_prev, bool coalesced_next) { FragmentationStats* s = &alloc->stats; // Internal fragmentation tracking s->total_requested -= requested; s->total_allocated -= allocated; s->allocation_count--; // External fragmentation s->total_free += allocated; // Hole count changes depend on coalescing if (coalesced_prev && coalesced_next) { s->hole_count--; // Two holes merged into one } else if (!coalesced_prev && !coalesced_next) { s->hole_count++; // New hole created } // If exactly one coalesced, hole count unchanged} // Compute derived fragmentation metricsvoid compute_derived_metrics(InstrumentedAllocator* alloc) { FragmentationStats* s = &alloc->stats; // Internal fragmentation ratio if (s->total_allocated > 0) { s->internal_frag_ratio = (double)(s->total_allocated - s->total_requested) / s->total_allocated; } else { s->internal_frag_ratio = 0; } // External fragmentation ratio if (s->total_free > 0) { s->external_frag_ratio = 1.0 - ((double)s->largest_hole / s->total_free); } else { s->external_frag_ratio = 0; } // Utilization s->utilization = (double)s->total_requested / alloc->total_memory; // Hole statistics (require scanning free list) if (s->hole_count > 0) { s->hole_size_mean = (double)s->total_free / s->hole_count; }} // Full scan to recalculate hole statisticsvoid scan_hole_statistics(InstrumentedAllocator* alloc) { FragmentationStats* s = &alloc->stats; s->total_free = 0; s->largest_hole = 0; s->smallest_hole = SIZE_MAX; s->hole_count = 0; s->hole_size_sum_sq = 0; BlockHeader* block = alloc->head; while (block) { if (block->is_free) { s->total_free += block->size; s->hole_count++; if (block->size > s->largest_hole) s->largest_hole = block->size; if (block->size < s->smallest_hole) s->smallest_hole = block->size; } block = block->next; } // Compute variance (requires second pass or running sum) if (s->hole_count > 0) { double mean = (double)s->total_free / s->hole_count; s->hole_size_variance = 0; block = alloc->head; while (block) { if (block->is_free) { double diff = block->size - mean; s->hole_size_variance += diff * diff; } block = block->next; } s->hole_size_variance /= s->hole_count; } if (s->smallest_hole == SIZE_MAX) s->smallest_hole = 0; compute_derived_metrics(alloc);} // Print comprehensive fragmentation reportvoid print_fragmentation_report(InstrumentedAllocator* alloc) { scan_hole_statistics(alloc); FragmentationStats* s = &alloc->stats; printf("╔══════════════════════════════════════════════════════╗"); printf("║ FRAGMENTATION ANALYSIS REPORT ║"); printf("╠══════════════════════════════════════════════════════╣"); printf("║ MEMORY OVERVIEW ║"); printf("║ Total Memory: %10zu bytes ║", alloc->total_memory); printf("║ Allocated: %10zu bytes ║", s->total_allocated); printf("║ Free: %10zu bytes ║", s->total_free); printf("║ Utilization: %6.2f%% ║", s->utilization * 100); printf("╠══════════════════════════════════════════════════════╣"); printf("║ INTERNAL FRAGMENTATION ║"); printf("║ Requested: %10zu bytes ║", s->total_requested); printf("║ Actually Used: %10zu bytes ║", s->total_allocated); printf("║ Wasted (internal): %10zu bytes ║", s->total_allocated - s->total_requested); printf("║ IF Ratio: %6.2f%% ║", s->internal_frag_ratio * 100); printf("╠══════════════════════════════════════════════════════╣"); printf("║ EXTERNAL FRAGMENTATION ║"); printf("║ Number of Holes: %10zu ║", s->hole_count); printf("║ Largest Hole: %10zu bytes ║", s->largest_hole); printf("║ Smallest Hole: %10zu bytes ║", s->smallest_hole); printf("║ Mean Hole Size: %10.1f bytes ║", s->hole_size_mean); printf("║ Hole Size Std Dev: %10.1f bytes ║", sqrt(s->hole_size_variance)); printf("║ EF Ratio: %6.2f%% ║", s->external_frag_ratio * 100); printf("╠══════════════════════════════════════════════════════╣"); printf("║ HEALTH ASSESSMENT ║"); // Severity assessment const char* severity; if (s->external_frag_ratio < 0.2) severity = "HEALTHY"; else if (s->external_frag_ratio < 0.4) severity = "MILD"; else if (s->external_frag_ratio < 0.6) severity = "MODERATE"; else if (s->external_frag_ratio < 0.8) severity = "SEVERE"; else severity = "CRITICAL"; printf("║ Severity: %10s ║", severity); // 50% rule analysis if (s->allocation_count > 0) { double expected_holes = s->allocation_count / 2.0; double deviation = (s->hole_count - expected_holes) / expected_holes; printf("║ 50%% Rule Deviation: %+6.1f%% ║", deviation * 100); } printf("╚══════════════════════════════════════════════════════╝");}Comprehensive statistics tracking adds overhead. In production, use sampling (measure every Nth operation) or periodic snapshots rather than continuous tracking. Critical metrics (total_free, largest_hole) can be maintained incrementally; others (variance) may require periodic rescans.
Visualizing fragmentation helps understand patterns that raw numbers obscure. Several visualization approaches prove useful:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
#include <stdio.h>#include <stdlib.h> typedef struct { int start; int size; int is_allocated;} Block; // ASCII memory map visualizationvoid visualize_memory_map(Block* blocks, int count, int total_mem) { int scale = 80; // Characters width printf("╔"); for (int i = 0; i < scale; i++) printf("═"); printf("╗║"); for (int b = 0; b < count; b++) { int start_pos = (blocks[b].start * scale) / total_mem; int end_pos = ((blocks[b].start + blocks[b].size) * scale) / total_mem; int width = end_pos - start_pos; if (width < 1) width = 1; char fill = blocks[b].is_allocated ? '█' : '░'; for (int i = 0; i < width; i++) { printf("%c", fill); } } printf("║╚"); for (int i = 0; i < scale; i++) printf("═"); printf("╝"); printf("Legend: █ = Allocated, ░ = Free");} // Hole size histogramvoid visualize_hole_histogram(int* hole_sizes, int count, int max_size) { int buckets = 10; int bucket_size = (max_size + buckets - 1) / buckets; int* bucket_counts = calloc(buckets, sizeof(int)); for (int i = 0; i < count; i++) { int bucket = hole_sizes[i] / bucket_size; if (bucket >= buckets) bucket = buckets - 1; bucket_counts[bucket]++; } // Find max count for scaling int max_count = 0; for (int i = 0; i < buckets; i++) { if (bucket_counts[i] > max_count) max_count = bucket_counts[i]; } printf("Hole Size Distribution:"); printf("────────────────────────────────"); int bar_width = 30; for (int i = 0; i < buckets; i++) { int bar_len = (bucket_counts[i] * bar_width) / (max_count > 0 ? max_count : 1); printf("%5d-%5d │", i * bucket_size, (i+1) * bucket_size - 1); for (int j = 0; j < bar_len; j++) printf("▓"); printf(" %d", bucket_counts[i]); } free(bucket_counts);} // Time series data structuretypedef struct { double* values; int count; int capacity;} TimeSeries; void add_sample(TimeSeries* ts, double value) { if (ts->count >= ts->capacity) { ts->capacity *= 2; ts->values = realloc(ts->values, ts->capacity * sizeof(double)); } ts->values[ts->count++] = value;} // ASCII Sparkline visualizationvoid visualize_sparkline(double* values, int count, const char* label) { if (count == 0) return; double min_val = values[0], max_val = values[0]; for (int i = 1; i < count; i++) { if (values[i] < min_val) min_val = values[i]; if (values[i] > max_val) max_val = values[i]; } const char* blocks = "▁▂▃▄▅▆▇█"; int levels = 8; double range = max_val - min_val; if (range == 0) range = 1; printf("%12s: ", label); // Show last 50 values at most int start = count > 50 ? count - 50 : 0; for (int i = start; i < count; i++) { int level = (int)((values[i] - min_val) / range * (levels - 1)); if (level >= levels) level = levels - 1; printf("%c", blocks[level * 3]); // UTF-8 block chars } printf(" (%.2f-%.2f)", min_val, max_val);} // Generate sample fragmentation report with visualizationsvoid demo_visualization() { printf("═══════════════════════════════════════════════════════════════"); printf(" FRAGMENTATION VISUALIZATION DEMO"); printf("═══════════════════════════════════════════════════════════════"); // Sample blocks Block blocks[] = { {0, 100, 1}, {100, 50, 0}, {150, 200, 1}, {350, 30, 0}, {380, 150, 1}, {530, 80, 0}, {610, 90, 1}, {700, 40, 0}, {740, 120, 1}, {860, 60, 0}, {920, 80, 1} }; int block_count = sizeof(blocks) / sizeof(blocks[0]); printf("Memory Layout (1000 units total):"); visualize_memory_map(blocks, block_count, 1000); // Sample hole sizes int hole_sizes[] = {50, 30, 80, 40, 60, 15, 45, 25, 70, 35, 20, 55, 10, 65}; int hole_count = sizeof(hole_sizes) / sizeof(hole_sizes[0]); visualize_hole_histogram(hole_sizes, hole_count, 100); // Sample time series double ef_history[] = {0.12, 0.15, 0.18, 0.22, 0.19, 0.25, 0.31, 0.28, 0.35, 0.42, 0.38, 0.45, 0.52, 0.48, 0.55, 0.51}; printf("Fragmentation Over Time:"); visualize_sparkline(ef_history, 16, "EF Ratio");} int main() { demo_visualization(); return 0;}Interpreting Visualizations:
Memory Map:
╔════════════════════════════════════════════╗
║████░░██████████░░░████████░████░░░██████░░█║
╚════════════════════════════════════════════╝
Hole Size Histogram:
0- 99 │▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ 45 ← Many small holes (bad)
100- 199 │▓▓▓▓▓ 12
200- 299 │▓▓ 5
300- 399 │▓ 2
400- 499 │ 0
500+ │ 0 ← No large holes (bad)
Left-skewed = many small holes = fragmented
Time Series:
EF Ratio: ▁▂▂▃▃▄▅▅▆▆▇▇█ (trending up = worsening)
Rising trend indicates accumulating fragmentation.
In production, integrate fragmentation metrics into monitoring dashboards (Grafana, Datadog, etc.). Set alerts on EF ratio > 0.6, sudden hole count increases, or memory utilization drops despite stable allocation. Visualization aids troubleshooting and capacity planning.
Linux provides several interfaces for monitoring memory fragmentation. Understanding these tools is essential for system administration and performance tuning.
/proc/buddyinfo - Buddy Allocator Status:
Linux uses the buddy system for physical page allocation. /proc/buddyinfo shows free page counts at each order (power-of-2 block size).
$ cat /proc/buddyinfo
Node 0, zone DMA 1 1 0 0 2 1 1 0 1 1 3
Node 0, zone DMA32 463 341 254 169 101 54 29 12 6 3 326
Node 0, zone Normal 12145 8321 5234 2156 892 432 198 87 35 12 156
Interpretation:
Fragmentation Indicator:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152
#!/bin/bash# Parse buddyinfo and calculate fragmentation metrics echo "=== Linux Memory Fragmentation Analysis ==="echo # Read buddyinfowhile read line; do # Parse zone information if [[ $line =~ ^Node ]]; then zone=$(echo $line | awk '{print $4}') # Extract order counts (columns 5 onwards) counts=($(echo $line | awk '{for(i=5;i<=NF;i++) print $i}')) # Calculate total free pages total=0 weighted_sum=0 for i in "${!counts[@]}"; do pages=$((counts[i] * (1 << i))) # Pages in this order total=$((total + pages)) weighted_sum=$((weighted_sum + pages * (1 << i))) # Weighted by size done # Calculate fragmentation index # High orders available vs total high_order_pages=0 for i in 7 8 9 10; do if [ $i -lt ${#counts[@]} ]; then high_order_pages=$((high_order_pages + counts[i] * (1 << i))) fi done if [ $total -gt 0 ]; then frag_pct=$((100 - (high_order_pages * 100 / total))) else frag_pct=0 fi echo "Zone: $zone" echo " Total free pages: $total ($(($total * 4 / 1024)) MB)" echo " High-order pages (order 7+): $high_order_pages" echo " Fragmentation estimate: $frag_pct%" echo fidone < /proc/buddyinfo # Additional info from pagetypeinfo if availableif [ -f /proc/pagetypeinfo ]; then echo "=== Page Type Distribution ===" head -20 /proc/pagetypeinfofi/proc/pagetypeinfo - Detailed Page Allocation:
Provides more detailed breakdown by page mobility type:
Fragmentation Commands:
# Trigger memory compaction (Linux 2.6.35+)
echo 1 > /proc/sys/vm/compact_memory
# Check compaction statistics
cat /proc/vmstat | grep compact
compact_stall 1523 # Times a process stalled waiting for compaction
compact_success 892 # Successful compactions
compact_fail 631 # Failed compactions
# Check memory zones fragmentation index
cat /sys/kernel/debug/extfrag/extfrag_index
extfrag_index: Per-zone fragmentation index from 0 (no fragmentation) to 1 (completely fragmented).
If /proc/buddyinfo shows zeros at orders 9-10 while orders 0-2 have thousands of entries, the system is severely fragmented. Large allocations (huge pages, DMA buffers) will fail. Consider: reducing memory pressure, triggering compaction, or rebooting in extreme cases.
User-space memory allocators like glibc's malloc, jemalloc, and tcmalloc provide their own fragmentation metrics and analysis tools.
| Allocator | Analysis Tool | Key Metrics | How to Enable |
|---|---|---|---|
| glibc malloc | malloc_stats(), malloc_info() | Arena stats, free chunks | Call functions or use MALLOC_DEBUG |
| jemalloc | malloc_stats_print(), jeprof | Active/mapped memory, fragmentation | MALLOC_CONF=stats_print:true |
| tcmalloc | MallocExtension::GetStats() | Page heap stats, free lists | Link with tcmalloc, call API |
| mimalloc | mi_stats_print() | Heap statistics, fragmentation | Call function or use MI_VERBOSE |
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
#include <stdio.h>#include <stdlib.h>#include <malloc.h> // glibc malloc analysis functionsvoid analyze_glibc_malloc() { printf("=== glibc malloc Statistics === "); // malloc_stats() prints to stderr printf("Calling malloc_stats():"); malloc_stats(); printf(""); // malloc_info() provides XML output // malloc_info(0, stdout); // mallinfo() provides structured data (deprecated but simple) struct mallinfo mi = mallinfo(); printf("mallinfo() results:"); printf(" arena: %10d bytes (total heap from system)", mi.arena); printf(" ordblks: %10d (number of free chunks)", mi.ordblks); printf(" smblks: %10d (small free blocks)", mi.smblks); printf(" hblks: %10d (number of mmapped regions)", mi.hblks); printf(" hblkhd: %10d bytes (mmapped bytes)", mi.hblkhd); printf(" uordblks: %10d bytes (allocated bytes)", mi.uordblks); printf(" fordblks: %10d bytes (free bytes)", mi.fordblks); printf(" keepcost: %10d bytes (top-most releasable space)", mi.keepcost); // Calculate fragmentation metrics if (mi.arena > 0) { double utilization = (double)mi.uordblks / mi.arena * 100; double internal_waste = (double)(mi.arena - mi.uordblks - mi.fordblks) / mi.arena * 100; printf("Derived Metrics:"); printf(" Heap utilization: %.1f%%", utilization); printf(" Internal overhead: %.1f%%", internal_waste); printf(" Free chunk ratio: %.1f%%", (double)mi.fordblks / mi.arena * 100); }} // Demonstrate with a workloadint main() { printf("=== Memory Allocator Fragmentation Analysis ==="); // Initial state printf("--- Initial State ---"); analyze_glibc_malloc(); // Allocate many small blocks #define NUM_ALLOCS 10000 void* ptrs[NUM_ALLOCS]; printf("--- After %d allocations (100-500 bytes each) ---", NUM_ALLOCS); for (int i = 0; i < NUM_ALLOCS; i++) { ptrs[i] = malloc(100 + rand() % 400); } analyze_glibc_malloc(); // Free every other block (create fragmentation) printf("--- After freeing every other block ---"); for (int i = 0; i < NUM_ALLOCS; i += 2) { free(ptrs[i]); ptrs[i] = NULL; } analyze_glibc_malloc(); // Attempt large allocation printf("--- Attempting large allocation (1MB) ---"); void* big = malloc(1024 * 1024); if (big) { printf("Large allocation succeeded (via mmap likely)"); free(big); } else { printf("Large allocation FAILED"); } analyze_glibc_malloc(); // Cleanup for (int i = 0; i < NUM_ALLOCS; i++) { if (ptrs[i]) free(ptrs[i]); } return 0;}jemalloc Statistics Example:
# Enable stats for jemalloc-based program
MALLOC_CONF="stats_print:true" ./myprogram
# Output includes:
___ Begin jemalloc statistics ___
Version: 5.2.1
Config: ...
Arenas: 4
allocated: 125,324,800 # Bytes in use
active: 138,412,032 # Bytes in active pages
mapped: 167,772,160 # Bytes mapped from OS
metadata: 3,456,000 # Allocator overhead
fragmentation: 9.5% # Built-in fragmentation metric
Key jemalloc Fragmentation Metrics:
Different allocators have vastly different fragmentation characteristics. glibc malloc uses arenas and bins; jemalloc uses size classes and runs; tcmalloc uses thread caches and spans. Choosing the right allocator for your workload can reduce fragmentation by 30-50%.
In production systems, manual fragmentation checks are insufficient. Automated monitoring with alerts enables proactive management.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
#!/usr/bin/env python3"""Memory Fragmentation Monitor for Production Systems Periodically samples fragmentation metrics and:- Logs to file/monitoring system- Sends alerts when thresholds exceeded- Triggers compaction when appropriate""" import subprocessimport timeimport jsonfrom datetime import datetimefrom dataclasses import dataclassfrom typing import List, Optional @dataclassclass FragmentationMetrics: timestamp: str zone: str total_free_pages: int high_order_free: int # Order 7+ pages fragmentation_index: float largest_contiguous_kb: int @dataclassclass AlertThresholds: frag_index_warn: float = 0.6 frag_index_critical: float = 0.8 high_order_min: int = 100 # Minimum high-order pages class FragmentationMonitor: def __init__(self, log_file: str = "/var/log/fragmentation.json"): self.log_file = log_file self.thresholds = AlertThresholds() self.history: List[FragmentationMetrics] = [] def parse_buddyinfo(self) -> List[FragmentationMetrics]: """Parse /proc/buddyinfo and return metrics per zone.""" metrics = [] try: with open('/proc/buddyinfo', 'r') as f: for line in f: if not line.startswith('Node'): continue parts = line.split() zone = parts[3].rstrip(',') counts = [int(x) for x in parts[4:]] # Calculate metrics total_pages = sum(c * (2**i) for i, c in enumerate(counts)) high_order = sum(c * (2**i) for i, c in enumerate(counts) if i >= 7) largest = 0 for i in range(len(counts)-1, -1, -1): if counts[i] > 0: largest = (2**i) * 4 # KB break # Fragmentation index (0 = no frag, 1 = max frag) frag_idx = 1.0 - (high_order / max(total_pages, 1)) metrics.append(FragmentationMetrics( timestamp=datetime.now().isoformat(), zone=zone, total_free_pages=total_pages, high_order_free=high_order, fragmentation_index=round(frag_idx, 3), largest_contiguous_kb=largest )) except Exception as e: print(f"Error parsing buddyinfo: {e}") return metrics def check_alerts(self, metrics: FragmentationMetrics) -> Optional[str]: """Check if metrics exceed thresholds.""" if metrics.fragmentation_index >= self.thresholds.frag_index_critical: return f"CRITICAL: Zone {metrics.zone} fragmentation at {metrics.fragmentation_index:.1%}" elif metrics.fragmentation_index >= self.thresholds.frag_index_warn: return f"WARNING: Zone {metrics.zone} fragmentation at {metrics.fragmentation_index:.1%}" elif metrics.high_order_free < self.thresholds.high_order_min: return f"WARNING: Zone {metrics.zone} low high-order pages: {metrics.high_order_free}" return None def trigger_compaction(self) -> bool: """Request memory compaction from kernel.""" try: with open('/proc/sys/vm/compact_memory', 'w') as f: f.write('1') return True except PermissionError: print("Permission denied for compaction (need root)") return False except Exception as e: print(f"Compaction failed: {e}") return False def log_metrics(self, metrics: List[FragmentationMetrics]): """Append metrics to log file.""" try: with open(self.log_file, 'a') as f: for m in metrics: f.write(json.dumps(m.__dict__) + '') except Exception as e: print(f"Logging failed: {e}") def run(self, interval_seconds: int = 60): """Main monitoring loop.""" print(f"Starting fragmentation monitor (interval: {interval_seconds}s)") print(f"Alert thresholds: warn={self.thresholds.frag_index_warn:.0%}, " f"critical={self.thresholds.frag_index_critical:.0%}") while True: metrics = self.parse_buddyinfo() self.log_metrics(metrics) for m in metrics: alert = self.check_alerts(m) if alert: print(f"[{m.timestamp}] {alert}") # Auto-trigger compaction on critical if 'CRITICAL' in alert: print("Triggering memory compaction...") if self.trigger_compaction(): print("Compaction triggered successfully") else: print(f"[{m.timestamp}] Zone {m.zone}: " f"frag={m.fragmentation_index:.1%}, " f"largest={m.largest_contiguous_kb}KB") time.sleep(interval_seconds) if __name__ == '__main__': monitor = FragmentationMonitor() monitor.run(interval_seconds=30)Export fragmentation metrics to Prometheus (node_exporter custom metrics), CloudWatch, Datadog, or similar. Create dashboards showing fragmentation alongside application performance. Set up anomaly detection for unusual fragmentation patterns.
When evaluating allocation strategies or comparing allocators, systematic fragmentation benchmarking provides objective data.
Benchmark Design Principles:
Representative Workloads: Use allocation patterns that match your actual application (size distribution, allocation rate, lifetime distribution).
Controlled Variables: Change one thing at a time (allocator, policy, configuration).
Run to Equilibrium: Ensure the system reaches steady state before measuring (typically after ~10,000 allocations).
Multiple Runs: Collect statistics across multiple runs to account for randomness.
Measure What Matters: Focus on metrics relevant to your constraints (if large allocations are critical, track largest_hole; if utilization matters, track fragmentation ratio).
Standard Benchmark Workloads:
| Workload | Description | Purpose |
|---|---|---|
| Uniform | All allocations same size | Baseline (minimal fragmentation expected) |
| Bimodal | 80% small, 20% large | Common real-world pattern |
| Power-law | Sizes follow power distribution | Realistic web/database workloads |
| Random LIFO | Random sizes, LIFO free | Best-case coalescing |
| Random FIFO | Random sizes, FIFO free | Queue workloads |
| Adversarial | Designed to maximize fragmentation | Worst-case analysis |
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <time.h> #define MAX_ALLOCS 50000 typedef struct { void* ptr; size_t size; int active;} Allocation; typedef struct { const char* name; double avg_fragmentation; double max_fragmentation; size_t peak_memory; int failed_allocs; double steady_state_frag;} BenchmarkResult; // Simulated fragmentation measurement (in real code, query allocator)double measure_fragmentation() { struct mallinfo mi = mallinfo(); if (mi.arena == 0) return 0; // Approximate: unused arena space / total arena return (double)(mi.arena - mi.uordblks) / mi.arena;} BenchmarkResult run_benchmark(const char* name, size_t (*size_gen)(void), int (*free_selector)(Allocation*, int)) { printf("Running benchmark: %s", name); Allocation allocs[MAX_ALLOCS] = {0}; int alloc_count = 0; BenchmarkResult result = {0}; result.name = name; double frag_sum = 0; int frag_samples = 0; // Warm-up: fill to ~50% target occupancy for (int i = 0; i < MAX_ALLOCS / 2; i++) { size_t size = size_gen(); allocs[i].ptr = malloc(size); if (allocs[i].ptr) { allocs[i].size = size; allocs[i].active = 1; alloc_count++; } else { result.failed_allocs++; } } // Steady-state: allocate and free at balanced rate for (int iter = 0; iter < 100000; iter++) { // Randomly allocate or free if (rand() % 2 == 0 && alloc_count < MAX_ALLOCS) { // Allocate for (int i = 0; i < MAX_ALLOCS; i++) { if (!allocs[i].active) { size_t size = size_gen(); allocs[i].ptr = malloc(size); if (allocs[i].ptr) { allocs[i].size = size; allocs[i].active = 1; alloc_count++; } else { result.failed_allocs++; } break; } } } else if (alloc_count > 0) { // Free based on selector int idx = free_selector(allocs, MAX_ALLOCS); if (idx >= 0 && allocs[idx].active) { free(allocs[idx].ptr); allocs[idx].active = 0; allocs[idx].ptr = NULL; alloc_count--; } } // Sample fragmentation periodically if (iter % 1000 == 0 && iter >= 10000) { double frag = measure_fragmentation(); frag_sum += frag; frag_samples++; if (frag > result.max_fragmentation) { result.max_fragmentation = frag; } } } result.avg_fragmentation = frag_samples > 0 ? frag_sum / frag_samples : 0; result.steady_state_frag = measure_fragmentation(); // Cleanup for (int i = 0; i < MAX_ALLOCS; i++) { if (allocs[i].active) { free(allocs[i].ptr); } } return result;} // Size generatorssize_t uniform_size() { return 256; }size_t bimodal_size() { return (rand() % 100 < 80) ? (32 + rand() % 96) : (1024 + rand() % 3072); }size_t powerlaw_size() { double u = (double)rand() / RAND_MAX; return (size_t)(16 * pow(1.0 / u, 0.5)); // Rough power-law} // Free selectors int random_free(Allocation* a, int n) { int active[MAX_ALLOCS]; int count = 0; for (int i = 0; i < n; i++) { if (a[i].active) active[count++] = i; } return count > 0 ? active[rand() % count] : -1;} int oldest_free(Allocation* a, int n) { for (int i = 0; i < n; i++) { if (a[i].active) return i; } return -1;} int newest_free(Allocation* a, int n) { for (int i = n-1; i >= 0; i--) { if (a[i].active) return i; } return -1;} void print_result(BenchmarkResult* r) { printf(" %-25s: avg=%.1f%%, max=%.1f%%, steady=%.1f%%, failed=%d", r->name, r->avg_fragmentation * 100, r->max_fragmentation * 100, r->steady_state_frag * 100, r->failed_allocs);} int main() { srand(time(NULL)); printf("=== Fragmentation Benchmark Suite === "); BenchmarkResult results[] = { run_benchmark("Uniform/Random", uniform_size, random_free), run_benchmark("Uniform/FIFO", uniform_size, oldest_free), run_benchmark("Uniform/LIFO", uniform_size, newest_free), run_benchmark("Bimodal/Random", bimodal_size, random_free), run_benchmark("Bimodal/FIFO", bimodal_size, oldest_free), run_benchmark("PowerLaw/Random", powerlaw_size, random_free), }; printf("=== Results ==="); for (int i = 0; i < sizeof(results)/sizeof(results[0]); i++) { print_result(&results[i]); } return 0;}Expect uniform workloads to show minimal fragmentation (often <10%). Bimodal workloads typically reach 20-40% fragmentation. Power-law distributions (realistic) may show 30-50% fragmentation. LIFO freeing patterns result in lower fragmentation than random or FIFO due to better coalescing.
We've explored comprehensive techniques for measuring, monitoring, and analyzing memory fragmentation. Let's consolidate the key insights:
/proc/buddyinfo, /proc/pagetypeinfo, and compaction statistics provide kernel-level fragmentation data. User allocators (jemalloc, tcmalloc) expose their own metrics.What's Next:
With measurement techniques in hand, we now examine how fragmentation impacts system performance. The final page of this module connects fragmentation metrics to observable performance degradation, memory stalls, allocation failures, and ultimately, user-visible system behavior.
You now possess practical skills for measuring and monitoring memory fragmentation. These techniques transform fragmentation from an abstract concept into actionable operational data, enabling informed decisions about compaction, allocator selection, and capacity planning.