telemetryService = new SimpleTelemetryService($randomGenerator); $this->metricsCollector = new ComponentMetricsCollector(); $this->memoryMonitor = new MemoryMonitor(); $this->profiler = new LiveComponentProfiler( $this->telemetryService, $this->metricsCollector, $this->memoryMonitor ); }); it('starts profiling session', function () { $session = $this->profiler->startSession('UserCard'); expect($session)->toBeInstanceOf(ProfileSession::class); expect($session->componentId)->toBe('UserCard'); expect($session->sessionId)->toBeInstanceOf(ProfileSessionId::class); expect($session->startTime)->toBeInstanceOf(Timestamp::class); expect($session->startMemory)->toBeInstanceOf(Byte::class); expect($session->operation)->toBeInstanceOf(\App\Framework\Telemetry\OperationHandle::class); }); it('profiles resolve phase', function () { $session = $this->profiler->startSession('UserCard'); $result = $this->profiler->profileResolve($session, function () { return ['resolved' => true]; }); expect($result)->toBe(['resolved' => true]); expect($session->getPhases())->toHaveCount(1); $resolvePhase = $session->getPhase('resolve'); expect($resolvePhase)->toBeInstanceOf(ProfilePhase::class); expect($resolvePhase->name)->toBe('resolve'); expect($resolvePhase->isSuccessful())->toBeTrue(); }); it('profiles render phase with cache flag', function () { $session = $this->profiler->startSession('UserCard'); $html = $this->profiler->profileRender($session, function () { return '
User Card
'; }, cached: true); expect($html)->toBe('
User Card
'); expect($session->getPhases())->toHaveCount(1); $renderPhase = $session->getPhase('render'); expect($renderPhase)->toBeInstanceOf(ProfilePhase::class); expect($renderPhase->attributes['cached'])->toBeTrue(); expect($renderPhase->isSuccessful())->toBeTrue(); }); it('profiles action execution', function () { $session = $this->profiler->startSession('UserCard'); $result = $this->profiler->profileAction($session, 'submit', function () { return ['success' => true]; }); expect($result)->toBe(['success' => true]); expect($session->getPhases())->toHaveCount(1); $actionPhase = $session->getPhase('action.submit'); expect($actionPhase)->toBeInstanceOf(ProfilePhase::class); expect($actionPhase->name)->toBe('action.submit'); expect($actionPhase->isSuccessful())->toBeTrue(); }); it('handles action errors', function () { $session = $this->profiler->startSession('UserCard'); expect(fn() => $this->profiler->profileAction($session, 'submit', function () { throw new \RuntimeException('Action failed'); }))->toThrow(\RuntimeException::class); $actionPhase = $session->getPhase('action.submit'); expect($actionPhase)->toBeInstanceOf(ProfilePhase::class); expect($actionPhase->isSuccessful())->toBeFalse(); expect($actionPhase->getError())->toBe('Action failed'); }); it('profiles cache operations', function () { $session = $this->profiler->startSession('UserCard'); $result = $this->profiler->profileCache($session, 'get', function () { return ['cached_data' => true]; }); expect($result)->toBe(['cached_data' => true]); expect($session->getPhases())->toHaveCount(1); $cachePhase = $session->getPhase('cache.get'); expect($cachePhase)->toBeInstanceOf(ProfilePhase::class); expect($cachePhase->name)->toBe('cache.get'); expect($cachePhase->attributes['hit'])->toBeTrue(); }); it('takes memory snapshots', function () { $session = $this->profiler->startSession('UserCard'); $snapshot = $this->profiler->takeMemorySnapshot($session, 'after_render'); expect($snapshot)->toBeInstanceOf(MemorySnapshot::class); expect($snapshot->label)->toBe('after_render'); expect($snapshot->timestamp)->toBeInstanceOf(Timestamp::class); expect($session->getMemorySnapshots())->toHaveCount(1); }); it('ends session and returns result', function () { $session = $this->profiler->startSession('UserCard'); // Profile some phases $this->profiler->profileResolve($session, fn() => ['resolved' => true]); $this->profiler->profileRender($session, fn() => '
Test
'); $result = $this->profiler->endSession($session); expect($result)->toBeInstanceOf(ProfileResult::class); expect($result->componentId)->toBe('UserCard'); expect($result->sessionId)->toBeInstanceOf(ProfileSessionId::class); expect($result->totalDuration)->toBeInstanceOf(Duration::class); expect($result->totalMemory)->toBeInstanceOf(Byte::class); expect($result->phases)->toHaveCount(2); }); it('provides timeline access', function () { $timeline = $this->profiler->getTimeline(); expect($timeline)->toBeInstanceOf(ProfileTimeline::class); }); }); describe('ProfileSessionId', function () { it('generates unique session IDs', function () { $id1 = ProfileSessionId::generate('UserCard'); $id2 = ProfileSessionId::generate('UserCard'); expect($id1->toString())->not->toBe($id2->toString()); expect($id1->toString())->toStartWith('UserCard_'); }); it('converts to string', function () { $id = ProfileSessionId::generate('UserCard'); expect($id->toString())->toBeString(); expect(strlen($id->toString()))->toBeGreaterThan(10); }); }); describe('ProfilePhase', function () { it('creates from raw values', function () { $phase = ProfilePhase::create( name: 'render', durationMs: 15.5, memoryBytes: 2048, attributes: ['cached' => true] ); expect($phase)->toBeInstanceOf(ProfilePhase::class); expect($phase->name)->toBe('render'); expect($phase->duration)->toBeInstanceOf(Duration::class); expect($phase->memoryDelta)->toBeInstanceOf(Byte::class); expect($phase->getDurationMs())->toBeFloat(); expect($phase->getMemoryBytes())->toBe(2048); expect($phase->attributes)->toBe(['cached' => true]); }); it('gets memory in megabytes', function () { $phase = ProfilePhase::create( name: 'render', durationMs: 10.0, memoryBytes: 1048576 // 1 MB ); expect($phase->getMemoryMB())->toBe(1.0); }); it('checks if phase was successful', function () { $success = ProfilePhase::create( name: 'render', durationMs: 10.0, memoryBytes: 1024, attributes: ['success' => true] ); $failure = ProfilePhase::create( name: 'render', durationMs: 10.0, memoryBytes: 1024, attributes: ['success' => false, 'error' => 'Render failed'] ); expect($success->isSuccessful())->toBeTrue(); expect($failure->isSuccessful())->toBeFalse(); expect($failure->getError())->toBe('Render failed'); }); it('converts to array', function () { $phase = ProfilePhase::create( name: 'render', durationMs: 15.5, memoryBytes: 2048 ); $array = $phase->toArray(); expect($array)->toHaveKeys([ 'name', 'duration_ms', 'memory_bytes', 'memory_mb', 'attributes', ]); }); }); describe('MemorySnapshot', function () { it('creates from MemoryMonitor', function () { $monitor = new MemoryMonitor(); $timestamp = Timestamp::now(); $snapshot = MemorySnapshot::fromMonitor('checkpoint', $monitor, $timestamp); expect($snapshot)->toBeInstanceOf(MemorySnapshot::class); expect($snapshot->label)->toBe('checkpoint'); expect($snapshot->currentUsage)->toBeInstanceOf(Byte::class); expect($snapshot->peakUsage)->toBeInstanceOf(Byte::class); expect($snapshot->timestamp)->toBe($timestamp); }); it('takes snapshot now', function () { $snapshot = MemorySnapshot::now('test'); expect($snapshot)->toBeInstanceOf(MemorySnapshot::class); expect($snapshot->label)->toBe('test'); }); it('gets memory in MB', function () { $snapshot = new MemorySnapshot( label: 'test', currentUsage: Byte::fromBytes(1048576), // 1 MB peakUsage: Byte::fromBytes(2097152), // 2 MB allocatedObjects: 100, timestamp: Timestamp::now() ); expect($snapshot->getCurrentMemoryMB())->toBe(1.0); expect($snapshot->getPeakMemoryMB())->toBe(2.0); }); it('calculates memory delta', function () { $snapshot1 = new MemorySnapshot( label: 'before', currentUsage: Byte::fromBytes(1048576), peakUsage: Byte::fromBytes(1048576), allocatedObjects: 100, timestamp: Timestamp::now() ); $snapshot2 = new MemorySnapshot( label: 'after', currentUsage: Byte::fromBytes(2097152), peakUsage: Byte::fromBytes(2097152), allocatedObjects: 100, timestamp: Timestamp::now() ); $delta = $snapshot2->deltaFrom($snapshot1); expect($delta->toBytes())->toBe(1048576); // 1 MB increase }); it('checks if memory increased', function () { $snapshot1 = new MemorySnapshot( label: 'before', currentUsage: Byte::fromBytes(1048576), peakUsage: Byte::fromBytes(1048576), allocatedObjects: 100, timestamp: Timestamp::now() ); $snapshot2 = new MemorySnapshot( label: 'after', currentUsage: Byte::fromBytes(2097152), peakUsage: Byte::fromBytes(2097152), allocatedObjects: 100, timestamp: Timestamp::now() ); expect($snapshot2->memoryIncreasedFrom($snapshot1))->toBeTrue(); expect($snapshot1->memoryIncreasedFrom($snapshot2))->toBeFalse(); }); it('converts to array', function () { $snapshot = MemorySnapshot::now('test'); $array = $snapshot->toArray(); expect($array)->toHaveKeys([ 'label', 'current_usage_bytes', 'current_usage_mb', 'peak_usage_bytes', 'peak_usage_mb', 'allocated_objects', 'timestamp', ]); }); });