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