Profiler Usage Guide

This guide covers the Screeps profiler functionality, including how to use it, the memory retention policy, and external archival.

Overview

The profiler is a CPU performance monitoring tool that tracks the execution time of decorated functions. It stores data in Memory.profiler and provides console commands for analysis.

Enabling the Profiler

The profiler is enabled at build time via the PROFILER_ENABLED environment variable:

1
2
3
4
5
# Build with profiler enabled (default)
yarn build

# Build without profiler
yarn build:no-profiler

The profiler auto-starts on the first tick when enabled. You can verify its status via the Screeps console:

1
Profiler.status()  // Returns "Profiler is running" or "Profiler is stopped"

Console Commands

Access the profiler through the Profiler global in the Screeps console:

Command Description
Profiler.start() Start collecting profiler data
Profiler.stop() Stop collecting data
Profiler.status() Check if profiler is running
Profiler.output() Print CPU usage summary to console
Profiler.clear() Clear all collected data

Memory Retention Policy

To prevent unbounded memory growth, the profiler implements a retention policy that limits the number of tracked functions.

Configuration

1
2
3
// packages/bot/src/main.ts
const MAX_PROFILER_ENTRIES = 500; // Max function entries to retain
const PROFILER_RETENTION_INTERVAL = 100; // Run policy every 100 ticks

How It Works

  1. Periodic Check: Every 100 ticks, the retention policy checks Memory.profiler.data
  2. Entry Count: If entries exceed MAX_PROFILER_ENTRIES (500), pruning occurs
  3. Priority Retention: Entries are sorted by total CPU time (descending)
  4. Pruning: The least significant entries (lowest CPU time) are removed
  5. Logging: Pruning events are logged: "Profiler retention: pruned N entries, kept 500"

Why 500 Entries?

  • Typical bot has 100-300 profiled functions
  • 500 entries provide ample room for all significant functions
  • Prevents memory bloat during extended operation (24+ hours)
  • Minimizes serialization overhead per tick

External Archival

The monitoring workflow archives profiler data every 30 minutes to preserve historical data while keeping Memory lean.

Archive Script

1
2
3
4
5
# Manual archival
yarn tsx packages/utilities/scripts/archive-profiler-data.ts

# Skip clearing (archive only)
SKIP_PROFILER_CLEAR=true yarn tsx packages/utilities/scripts/archive-profiler-data.ts

Archive Location

Archives are stored in reports/profiler/:

1
2
3
4
5
6
reports/profiler/
├── latest.json # Most recent snapshot (for health checks)
├── archive-index.json # Index of all archives
├── archive-2024-01-15T12-30-00.json
├── archive-2024-01-15T13-00-00.json
└── ...

Archive Contents

Each archive contains:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"fetchedAt": "2024-01-15T12:30:00.000Z",
"source": "console-archive",
"isEnabled": true,
"hasData": true,
"profilerMemory": {
"data": { ... },
"start": 12345678,
"total": 5000
},
"summary": {
"totalTicks": 5000,
"totalFunctions": 250,
"averageCpuPerTick": 8.5,
"topCpuConsumers": [ ... ]
}
}

Monitoring Integration

The screeps-monitoring.yml workflow includes profiler management:

  1. Ensure Running: ensure-profiler-running.ts - Verify profiler is active
  2. Fetch Data: fetch-profiler-console.ts - Get current profiler data
  3. Archive Data: archive-profiler-data.ts - Save and clear profiler data
  4. Health Check: check-profiler-health.ts - Validate profiler status

Health Check

The profiler health check (check-profiler-health.ts) reports:

  • healthy: Profiler running, has data, data is fresh (<1 hour)
  • warning: Profiler stopped, no data, or stale data
  • error: Report missing, parse failure, or fetch error

Troubleshooting

Profiler Not Running

1
2
// In console
Profiler.start()

Or wait for the next deployment - auto-start is enabled by default.

Profiler Data Returns Undefined

If Memory.profiler.data returns undefined despite profiler being enabled:

  1. Check build configuration: Ensure PROFILER_ENABLED is set correctly

    1
    2
    3
    4
    5
    # Build with profiler enabled (default)
    yarn build

    # Verify in console
    Profiler.status() // Should return "Profiler is running"
  2. Verify Memory.profiler structure: The profiler should auto-initialize Memory

    1
    2
    3
    // In console - check if structure exists
    JSON.stringify(Memory.profiler)
    // Expected: {"data":{},"total":0,"start":<tick>}
  3. Check for type mismatch: The build system uses string "true" or "false" for
    __PROFILER_ENABLED__. If runtime comparisons fail, verify the build output:

    1
    2
    3
    # Check built output for correct conditionals
    grep -A2 "ensureProfilerRunning" dist/main.js
    # Should show `if (false)` when profiler is enabled
  4. Related issue: #1499 - Fixed a type mismatch where __PROFILER_ENABLED__ was being interpreted as a boolean instead of a string, causing profiler data export to fail.

High Memory Usage

The retention policy should prevent this. If you see excessive Memory.profiler size:

  1. Check if retention policy is running (look for log messages)
  2. Manually clear: Profiler.clear() followed by Profiler.start()
  3. Wait for next monitoring workflow archival

Missing Historical Data

Check reports/profiler/archive-index.json for archive history. Archives are uploaded as workflow artifacts with 30-day retention.

Best Practices

  1. Let it run: Leave profiler enabled for continuous monitoring
  2. Check periodically: Use Profiler.output() to review CPU hotspots
  3. Archive before analysis: Archive data before major performance optimization
  4. Don’t over-decorate: Profile only significant functions to reduce overhead
  • #1490 - Profiler memory retention policy implementation
  • #1499 - Profiler data export returns undefined - fixed type mismatch for __PROFILER_ENABLED__