Skip to main content

Tracer

The Tracer system provides lightweight TCP-based diagnostics and logging for Atlas Engine. It includes tools for performance profiling, resource tracking, and debug logging.
The Tracer API is currently in alpha and may change in future releases.

Logger

Simple logger that emits formatted messages via the Tracer protocol.
class Logger {
public:
    static Logger &getInstance();
    
    void log(const std::string &message, const std::string &file, int line);
    void warning(const std::string &message, const std::string &file, int line);
    void error(const std::string &message, const std::string &file, int line);
    void setConsoleFilter(bool showLogs, bool showWarnings, bool showErrors);
};

Convenience Macros

// Log informational message with automatic file/line tracking
atlas_log("Application started");

// Log warning message
atlas_warning("Texture quality reduced due to memory constraints");

// Log error message
atlas_error("Failed to load shader: vertex.glsl");

Usage Example

void initializeRenderer() {
    atlas_log("Initializing renderer...");
    
    if (!loadShaders()) {
        atlas_error("Failed to load required shaders");
        return;
    }
    
    if (gpuMemory < requiredMemory) {
        atlas_warning("Low GPU memory detected");
    }
    
    atlas_log("Renderer initialized successfully");
}

// Configure logging output
Logger::getInstance().setConsoleFilter(
    true,   // Show logs
    true,   // Show warnings
    true    // Show errors
);

TracerServices

Singleton container that manages tracer connectivity.
class TracerServices {
public:
    static TracerServices &getInstance();
    
    std::shared_ptr<NetworkPipe> tracerPipe;
    
    bool isOk() const;  // Returns true when tracer is connected
    void startTracing(int port);  // Connect to tracer on specified port
};

Usage Example

#define TRACER_PORT 5123

int main() {
    // Start tracer connection
    TracerServices::getInstance().startTracing(TRACER_PORT);
    
    if (TracerServices::getInstance().isOk()) {
        atlas_log("Tracer connected successfully");
    } else {
        atlas_warning("Running without tracer");
    }
    
    // Your application code...
}

DebugTimer

RAII helper that measures scope duration and reports it on destruction.
class DebugTimer {
public:
    DebugTimer(const std::string &name);
    ~DebugTimer();
    
    uint64_t stop();  // Manually stop and return duration in microseconds
};

Usage Example

void renderScene() {
    // Timer automatically starts
    DebugTimer timer("SceneRender");
    
    renderSkybox();
    renderGeometry();
    renderLighting();
    renderPostEffects();
    
    // Timer automatically stops and reports when leaving scope
}

void complexOperation() {
    DebugTimer timer("ComplexOperation");
    
    // Do work...
    
    if (shouldEarlyExit) {
        uint64_t elapsed = timer.stop();  // Manual stop
        atlas_log("Operation cancelled after " + std::to_string(elapsed) + "us");
        return;
    }
    
    // More work...
}

// Nested timing
void renderFrame() {
    DebugTimer frameTimer("FullFrame");
    
    {
        DebugTimer shadowTimer("ShadowPass");
        renderShadows();
    }
    
    {
        DebugTimer geometryTimer("GeometryPass");
        renderGeometry();
    }
    
    {
        DebugTimer lightingTimer("LightingPass");
        renderLighting();
    }
}

Performance Telemetry

DrawCallInfo

Telemetry for individual draw calls.
enum class DrawCallType {
    Draw = 1,
    Indexed = 2,
    Patch = 3
};

struct DrawCallInfo {
    std::string callerObject;
    DrawCallType type;
    unsigned int frameNumber;
    
    void send();  // Send to tracer
};

FrameDrawInfo

Per-frame aggregate draw call statistics.
struct FrameDrawInfo {
    unsigned int frameNumber;
    unsigned int drawCallCount;
    float frameTimeMs;
    float fps;
    
    void send();
};

Usage Example

class Renderer {
    unsigned int currentFrame = 0;
    unsigned int drawCallCount = 0;
    
public:
    void drawObject(const CoreObject &obj) {
        // Perform draw call
        glDrawElements(...);
        
        // Report to tracer
        DrawCallInfo info;
        info.callerObject = obj.getName();
        info.type = DrawCallType::Indexed;
        info.frameNumber = currentFrame;
        info.send();
        
        drawCallCount++;
    }
    
    void endFrame(float deltaTime) {
        // Send frame statistics
        FrameDrawInfo frameInfo;
        frameInfo.frameNumber = currentFrame;
        frameInfo.drawCallCount = drawCallCount;
        frameInfo.frameTimeMs = deltaTime * 1000.0f;
        frameInfo.fps = 1.0f / deltaTime;
        frameInfo.send();
        
        currentFrame++;
        drawCallCount = 0;
    }
};

Resource Tracking

ResourceEventInfo

Resource lifecycle event telemetry.
enum class DebugResourceType {
    Texture = 1,
    Buffer = 2,
    Shader = 3,
    Mesh = 4
};

enum class DebugResourceOperation {
    Created = 1,
    Loaded = 2,
    Unloaded = 3
};

struct ResourceEventInfo {
    std::string callerObject;
    DebugResourceType resourceType;
    DebugResourceOperation operation;
    unsigned int frameNumber;
    float sizeMb;
    
    void send();
};

FrameResourcesInfo

Per-frame aggregate resource statistics.
struct FrameResourcesInfo {
    unsigned int frameNumber;
    int resourcesCreated;
    int resourcesLoaded;
    int resourcesUnloaded;
    float totalMemoryMb;
    
    void send();
};

ResourceTracker

Singleton accumulator for resource usage.
class ResourceTracker {
public:
    static ResourceTracker &getInstance();
    
    int createdResources = 0;
    int loadedResources = 0;
    int unloadedResources = 0;
    float totalMemoryMb = 0.0f;
};

Usage Example

class TextureManager {
public:
    Texture loadTexture(const std::string &path) {
        Texture tex = loadFromDisk(path);
        
        // Report resource event
        ResourceEventInfo event;
        event.callerObject = "TextureManager";
        event.resourceType = DebugResourceType::Texture;
        event.operation = DebugResourceOperation::Loaded;
        event.frameNumber = getCurrentFrame();
        event.sizeMb = tex.getSizeInBytes() / (1024.0f * 1024.0f);
        event.send();
        
        // Update tracker
        auto &tracker = ResourceTracker::getInstance();
        tracker.loadedResources++;
        tracker.totalMemoryMb += event.sizeMb;
        
        return tex;
    }
    
    void unloadTexture(const Texture &tex) {
        float sizeMb = tex.getSizeInBytes() / (1024.0f * 1024.0f);
        
        // Report unload event
        ResourceEventInfo event;
        event.callerObject = "TextureManager";
        event.resourceType = DebugResourceType::Texture;
        event.operation = DebugResourceOperation::Unloaded;
        event.frameNumber = getCurrentFrame();
        event.sizeMb = sizeMb;
        event.send();
        
        // Update tracker
        auto &tracker = ResourceTracker::getInstance();
        tracker.unloadedResources++;
        tracker.totalMemoryMb -= sizeMb;
        
        releaseTexture(tex);
    }
};

Memory Telemetry

AllocationPacket

Single allocation event.
enum class DebugMemoryDomain {
    GPU = 1,
    CPU = 2
};

enum class DebugResourceKind {
    VertexBuffer = 1,
    IndexBuffer = 2,
    UniformBuffer = 3,
    StorageBuffer = 4,
    Texture2d = 5,
    Texture3d = 6,
    TextureCube = 7,
    RenderTarget = 8,
    DepthStencil = 9,
    Sampler = 10,
    PipelineCache = 11,
    AccelerationStructure = 12,
    Other = 13
};

struct AllocationPacket {
    std::string description;
    std::string owner;
    DebugMemoryDomain domain;
    DebugResourceKind kind;
    float sizeMb;
    unsigned int frameNumber;
    
    void send();
};

FrameMemoryPacket

Per-frame memory statistics.
struct FrameMemoryPacket {
    unsigned int frameNumber;
    float totalAllocatedMb;
    float totalGPUMb;
    float totalCPUMb;
    int allocationCount;
    int deallocationCount;
    
    void send();
};

Usage Example

class MemoryAllocator {
public:
    void* allocateVertexBuffer(size_t size, const std::string &owner) {
        void* buffer = gpuAlloc(size);
        
        // Report allocation
        AllocationPacket packet;
        packet.description = "Vertex Buffer";
        packet.owner = owner;
        packet.domain = DebugMemoryDomain::GPU;
        packet.kind = DebugResourceKind::VertexBuffer;
        packet.sizeMb = size / (1024.0f * 1024.0f);
        packet.frameNumber = getCurrentFrame();
        packet.send();
        
        return buffer;
    }
};

Timing Events

TimingEventPacket

Timed event with subsystem classification.
enum class TimingEventSubsystem {
    Rendering = 1,
    Physics = 2,
    AI = 3,
    Scripting = 4,
    Animation = 5,
    Audio = 6,
    Networking = 7,
    Io = 8,
    Scene = 9,
    Other = 10
};

struct TimingEventPacket {
    std::string name;
    TimingEventSubsystem subsystem;
    float durationMs;
    unsigned int frameNumber;
    
    void send();
};

FrameTimingPacket

Comprehensive frame timing data.
struct FrameTimingPacket {
    unsigned int frameNumber;
    float cpuFrameTimeMs;
    float gpuFrameTimeMs;
    float mainThreadTimeMs;
    float workerThreadTimeMs;
    float memoryMb;
    float cpuUsagePercent;
    float gpuUsagePercent;
    
    void send();
};

Complete Profiling Example

class GameEngine {
    unsigned int frameNumber = 0;
    
public:
    void initialize() {
        // Start tracer
        TracerServices::getInstance().startTracing(TRACER_PORT);
        atlas_log("Engine initialized");
    }
    
    void runFrame() {
        DebugTimer frameTimer("Frame");
        auto frameStart = std::chrono::high_resolution_clock::now();
        
        // Update physics
        {
            DebugTimer physicsTimer("Physics");
            auto start = std::chrono::high_resolution_clock::now();
            updatePhysics();
            auto end = std::chrono::high_resolution_clock::now();
            
            TimingEventPacket event;
            event.name = "PhysicsUpdate";
            event.subsystem = TimingEventSubsystem::Physics;
            event.durationMs = std::chrono::duration<float, std::milli>(end - start).count();
            event.frameNumber = frameNumber;
            event.send();
        }
        
        // Render scene
        {
            DebugTimer renderTimer("Rendering");
            renderScene();
        }
        
        // Calculate frame stats
        auto frameEnd = std::chrono::high_resolution_clock::now();
        float frameTimeMs = std::chrono::duration<float, std::milli>(frameEnd - frameStart).count();
        
        // Send frame timing
        FrameTimingPacket timing;
        timing.frameNumber = frameNumber;
        timing.cpuFrameTimeMs = frameTimeMs;
        timing.gpuFrameTimeMs = getGPUFrameTime();
        timing.mainThreadTimeMs = frameTimeMs;
        timing.memoryMb = getCurrentMemoryUsage();
        timing.cpuUsagePercent = getCPUUsage();
        timing.gpuUsagePercent = getGPUUsage();
        timing.send();
        
        frameNumber++;
    }
};

Best Practices

  1. Connect tracer early: Call startTracing() during initialization
  2. Use RAII timers: Prefer DebugTimer over manual timing
  3. Be specific with names: Use descriptive names for timers and events
  4. Track memory carefully: Report both allocations and deallocations
  5. Use appropriate subsystems: Classify timing events correctly
  6. Send frame summaries: Aggregate per-frame statistics for better analysis
  7. Don’t spam logs: Log important events, not every frame update
  8. Check tracer status: Use isOk() to verify connection before heavy logging

Build docs developers (and LLMs) love