# QueueMaster - Guia de Desenvolvimento

## Índice

1. [Setup de Desenvolvimento](#setup-de-desenvolvimento)
2. [Estrutura de Código](#estrutura-de-código)
3. [Adicionar Novos Supervisors](#adicionar-novos-supervisors)
4. [Modificar Comportamento de Workers](#modificar-comportamento-de-workers)
5. [Adicionar Métricas Customizadas](#adicionar-métricas-customizadas)
6. [Como Testar Mudanças](#como-testar-mudanças)
7. [Boas Práticas de Código](#boas-práticas-de-código)
8. [Debugging Avançado](#debugging-avançado)

---

## Setup de Desenvolvimento

### Ambiente Recomendado

```bash
# OS
Ubuntu 22.04+ ou similar

# PHP
PHP 8.3 com extensões:
- pcntl (obrigatório para sinais)
- redis
- amqp (RabbitMQ)

# Infraestrutura
- Redis 7+
- RabbitMQ 3.12+
- Docker (opcional)
```

### Instalação

```bash
# 1. Clonar repositório
cd /home/paulo/www/guisoft/ogdevel

# 2. Instalar dependências
composer install

# 3. Verificar extensões
php -m | grep -E "pcntl|redis|amqp"

# 4. Configurar Redis
cp .env.example .env
# Editar REDIS_HOST, REDIS_PORT

# 5. Configurar RabbitMQ
# Editar RABBITMQ_HOST, RABBITMQ_PORT, RABBITMQ_USER, RABBITMQ_PASSWORD

# 6. Testar
php og queue-master --help
```

### IDE Setup (VS Code / PHPStorm)

**Extensions recomendadas**:
- PHP Intelephense (autocomplete)
- PHP Debug (Xdebug)
- GitLens

**Configuração Xdebug** (`php.ini`):

```ini
[xdebug]
zend_extension=xdebug.so
xdebug.mode=debug
xdebug.start_with_request=yes
xdebug.client_host=127.0.0.1
xdebug.client_port=9003
```

---

## Estrutura de Código

### Arquivos Principais

```
Modules/QueueMaster/
├── Console/
│   ├── QueueMasterCommand.php         # Entry point do Master
│   └── QueueMasterWorkCommand.php     # Entry point do Worker
│
├── MasterSupervisor/
│   ├── Master/
│   │   ├── MasterSupervisor.php       # Lógica principal do Master
│   │   └── ProvisioningPlan.php       # Cria supervisors
│   │
│   └── Supervisor/
│       ├── QueueSupervisor.php        # Lógica do Supervisor
│       ├── ProcessPool.php            # Gerencia workers
│       ├── SupervisorOptions.php      # Options do supervisor
│       └── WorkerProcess.php          # Wrapper de processo
│
├── Contracts/
│   ├── MasterSupervisorRepositoryInterface.php
│   ├── SupervisorRepositoryInterface.php
│   ├── WorkerRepositoryInterface.php
│   ├── MasterCommandQueueInterface.php
│   ├── SupervisorCommandQueueInterface.php
│   └── JobTrackerInterface.php
│
├── Services/
│   ├── RedisWorkerRegistry.php        # Registry de workers no Redis
│   ├── QueueMasterIntegration.php     # Integração com tracking
│   ├── TenantSwitchMonitor.php        # Monitora tenant switches
│   ├── MetricsCollector.php           # Coleta métricas RabbitMQ
│   └── JobTracker.php                 # Rastreia jobs
│
├── Controllers/
│   └── Api/
│       └── DashboardControllerAPI.php # API do dashboard
│
├── Views/
│   └── dashboard/
│       └── dashboard.blade.php        # Dashboard web
│
├── Enums/
│   └── PosixSignal.php                # Enum de sinais
│
├── Events/
│   ├── MasterSupervisorLooped.php
│   ├── SupervisorLooped.php
│   └── SupervisorParentDied.php
│
└── Traits/
    └── ListensForSignals.php          # Trait para escutar sinais

Modules/Common/
├── Config/
│   └── queue-master.php               # Configuração
│
└── Queue/
    ├── Worker.php                     # Worker base
    ├── Job.php                        # Job base
    └── QueueManager.php               # Queue manager
```

### Fluxo de Execução

```
php og queue-master
  ↓
QueueMasterCommand::handle()
  ↓
MasterSupervisor::monitor()
  ↓
  loop {
    processPendingSignals()
    processPendingCommands()
    monitorSupervisors()
    cleanupOrphanedWorkers()
    persist()
  }
```

### Principais Classes

#### MasterSupervisor

**Responsabilidades**:
- Coordenar supervisors
- Responder a sinais
- Limpar workers órfãos
- Persistir estado no Redis

**Métodos importantes**:

```php
public function monitor(): void
// Loop principal

public function terminate(): never
// Shutdown gracioso

public function restart(): void
// Restart de supervisors

protected function cleanupOrphanedWorkers(): void
// Limpeza de órfãos
```

#### QueueSupervisor

**Responsabilidades**:
- Gerenciar ProcessPool
- Escalar workers
- Verificar se parent (Master) está vivo

**Métodos importantes**:

```php
public function monitor(): void
// Loop de monitoramento

protected function ensureParentIsRunning(): void
// Verifica se Master está vivo

public function terminate(int $exitCode = 0): never
// Shutdown

public function restart(): void
// Restart de workers
```

#### ProcessPool

**Responsabilidades**:
- Criar/destruir workers
- Scaling up/down
- Pruning de processos mortos

**Métodos importantes**:

```php
public function scale(int $processes): void
// Escalar para N workers

protected function start(): void
// Criar novo worker

public function markForTermination(WorkerProcess $process): void
// Marcar worker para término

protected function pruneTerminatingProcesses(): void
// Remover workers mortos
```

---

## Adicionar Novos Supervisors

### Cenário

Você quer adicionar uma nova fila chamada `notifications` com configuração específica.

### Passo 1: Configuração

```php
// Modules/Common/Config/queue-master.php

'environments' => [
    'production' => [
        // ... outras filas ...

        'notifications' => [
            'connection'    => 'rabbitmq',
            'queue'         => 'notifications',
            'balance'       => 'auto',
            'maxProcesses'  => envVar('NOTIFICATIONS_WORKERS', 3),
            'minProcesses'  => 1,
            'maxTime'       => 3600,
            'workerMaxTime' => 300,
            'maxJobs'       => 0,
            'memory'        => 256,
            'timeout'       => 30,      // Notificações rápidas
            'tries'         => 5,       // Retry agressivo
            'sleep'         => 2,
            'backoff'       => 5,
            'vhost'         => envVar('RABBITMQ_VHOST', '/'),
        ],
    ],
],
```

### Passo 2: Testar

```bash
# 1. Adicionar variável de ambiente
export NOTIFICATIONS_WORKERS=3

# 2. Restart QueueMaster
kill -SIGTERM <master_pid>
php og queue-master --environment=production

# 3. Verificar se supervisor foi criado
ps aux | grep "queue:work.*notifications"

# Esperado: 3 workers
```

### Passo 3: Criar Jobs para a Fila

```php
<?php

namespace App\Jobs;

use Og\Modules\Common\Queue\Job;

class SendPushNotificationJob extends Job
{
    private int $userId;
    private string $message;

    public function __construct(int $userId, string $message)
    {
        $this->userId = $userId;
        $this->message = $message;
    }

    public function handle(): void
    {
        $user = User::find($this->userId);

        // Enviar notificação push
        PushService::send($user->device_token, $this->message);
    }

    public function queue(): string
    {
        return 'notifications';  // Direciona para fila correta
    }

    public function maxTries(): int
    {
        return 5;  // Retry agressivo
    }

    public function timeout(): int
    {
        return 30;  // 30 segundos
    }
}
```

---

## Modificar Comportamento de Workers

### Cenário

Você quer adicionar logging adicional quando worker recicla.

### Modificar QueueMasterWorkCommand

**Arquivo**: `Modules/QueueMaster/Console/QueueMasterWorkCommand.php`

```php
protected function registerMaxTimeCheck(Worker $worker, int $maxTime): void
{
    if ($maxTime <= 0) {
        return;
    }

    $startedAt = microtime(true);

    $worker->afterProcess(function () use ($worker, $startedAt, $maxTime) {
        $elapsed = microtime(true) - $startedAt;

        if ($elapsed >= $maxTime) {
            // ADICIONAR: Log customizado
            loggerBatch('warning', 'Worker reciclando por max_time', [
                'pid' => getmypid(),
                'elapsed' => round($elapsed, 2),
                'max_time' => $maxTime,
                'jobs_processed' => $worker->getJobsProcessed(),  // Se disponível
                'memory_usage_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),
            ]);

            $worker->stop('max_time_reached');
        }
    });
}
```

### Adicionar Hook Customizado

**Criar novo hook** no Worker:

```php
// Modules/Common/Queue/Worker.php

private array $beforeRecycleCallbacks = [];

public function beforeRecycle(callable $callback): self
{
    $this->beforeRecycleCallbacks[] = $callback;
    return $this;
}

private function executeBeforeRecycleCallbacks(): void
{
    foreach ($this->beforeRecycleCallbacks as $callback) {
        $callback($this);
    }
}

// Chamar antes de stop()
public function stop(?string $reason = null): void
{
    $this->executeBeforeRecycleCallbacks();

    // ... resto do código ...
}
```

**Usar no QueueMasterWorkCommand**:

```php
$worker->beforeRecycle(function (Worker $worker) {
    // Custom logic antes de reciclar
    $this->sendMetrics([
        'worker_recycled' => 1,
        'reason' => $worker->getStopReason(),
    ]);
});
```

---

## Adicionar Métricas Customizadas

### Cenário

Você quer rastrear quantos jobs de cada tipo são processados.

### Passo 1: Criar Serviço de Métricas

```php
<?php

namespace Og\Modules\QueueMaster\Services;

use Illuminate\Support\Facades\Redis;

class CustomMetricsCollector
{
    private string $redisPrefix = 'queuemaster:custom-metrics:';

    public function recordJobProcessed(string $jobClass, float $duration): void
    {
        $key = $this->redisPrefix . 'jobs:' . $jobClass;

        Redis::hincrby($key, 'count', 1);
        Redis::hincrbyfloat($key, 'total_duration', $duration);
        Redis::expire($key, 86400); // 24h TTL
    }

    public function getJobStats(string $jobClass): array
    {
        $key = $this->redisPrefix . 'jobs:' . $jobClass;

        $count = (int) Redis::hget($key, 'count');
        $totalDuration = (float) Redis::hget($key, 'total_duration');

        return [
            'count' => $count,
            'total_duration' => $totalDuration,
            'avg_duration' => $count > 0 ? $totalDuration / $count : 0,
        ];
    }

    public function getAllJobStats(): array
    {
        $keys = Redis::keys($this->redisPrefix . 'jobs:*');
        $stats = [];

        foreach ($keys as $key) {
            $jobClass = str_replace($this->redisPrefix . 'jobs:', '', $key);
            $stats[$jobClass] = $this->getJobStats($jobClass);
        }

        return $stats;
    }
}
```

### Passo 2: Integrar no Worker

```php
// Modules/Common/Queue/Worker.php

protected function processJob(JobInterface $job, string $jobId, ...): void
{
    $startTime = microtime(true);

    try {
        // ... processar job ...

        $duration = microtime(true) - $startTime;

        // ADICIONAR: Registrar métrica customizada
        app(CustomMetricsCollector::class)->recordJobProcessed(
            get_class($job),
            $duration
        );

    } catch (\Throwable $e) {
        // ...
    }
}
```

### Passo 3: Endpoint da API

```php
// Modules/QueueMaster/Controllers/Api/DashboardControllerAPI.php

public function customMetrics(): Response
{
    try {
        $collector = app(CustomMetricsCollector::class);
        $stats = $collector->getAllJobStats();

        return response()->json([
            'success' => true,
            'data' => [
                'job_stats' => $stats,
                'timestamp' => time(),
            ]
        ]);
    } catch (\Exception $e) {
        return response()->json([
            'success' => false,
            'message' => 'Failed to fetch custom metrics: ' . $e->getMessage()
        ], Response::HTTP_INTERNAL_SERVER_ERROR);
    }
}
```

**Rota**:

```php
Route::get('/api/queue-master/custom-metrics', [DashboardControllerAPI::class, 'customMetrics']);
```

---

## Como Testar Mudanças

### Testes Unitários

**Criar teste** para nova funcionalidade:

```php
<?php

namespace Tests\Unit\QueueMaster;

use PHPUnit\Framework\TestCase;
use Og\Modules\QueueMaster\Services\CustomMetricsCollector;

class CustomMetricsCollectorTest extends TestCase
{
    private CustomMetricsCollector $collector;

    protected function setUp(): void
    {
        parent::setUp();
        $this->collector = new CustomMetricsCollector();
    }

    public function testRecordJobProcessed(): void
    {
        $jobClass = 'App\\Jobs\\TestJob';
        $duration = 2.5;

        $this->collector->recordJobProcessed($jobClass, $duration);

        $stats = $this->collector->getJobStats($jobClass);

        $this->assertEquals(1, $stats['count']);
        $this->assertEquals(2.5, $stats['total_duration']);
        $this->assertEquals(2.5, $stats['avg_duration']);
    }

    public function testMultipleJobsAverage(): void
    {
        $jobClass = 'App\\Jobs\\TestJob';

        $this->collector->recordJobProcessed($jobClass, 2.0);
        $this->collector->recordJobProcessed($jobClass, 4.0);

        $stats = $this->collector->getJobStats($jobClass);

        $this->assertEquals(2, $stats['count']);
        $this->assertEquals(6.0, $stats['total_duration']);
        $this->assertEquals(3.0, $stats['avg_duration']);
    }
}
```

**Executar**:

```bash
./vendor/bin/phpunit tests/Unit/QueueMaster/CustomMetricsCollectorTest.php
```

#### Cobertura com Pest + Xdebug

O Dockerfile já instala `php8.3-xdebug` e o `docker/php/local.ini` mantém o Xdebug desativado por padrão (`xdebug.mode=off`) para não degradar o FPM.  
Quando precisar de cobertura, habilite apenas na execução do Pest definindo `XDEBUG_MODE=coverage` (ou `coverage,develop` se quiser relatórios adicionais).

```bash
# Executa dentro do container app
docker compose exec app bash -lc \
  'XDEBUG_MODE=coverage vendor/bin/pest --coverage --min=80 --coverage-html storage/coverage'
```

- `XDEBUG_MODE=coverage` liga o Xdebug somente para esse processo CLI.
- Ajuste `--min` conforme a meta do módulo e use `--coverage-html` para gerar relatórios navegáveis em `storage/coverage/index.html`.
- Para rodar testes específicos: `XDEBUG_MODE=coverage vendor/bin/pest tests/Unit/QueueMaster/RedisWorkerRegistryTest.php --coverage`.

Caso precise depurar interativamente, use `XDEBUG_MODE=debug,develop` e configure o IDE (porta 9003).

### Testes de Integração

**Criar teste** que inicializa QueueMaster:

```php
<?php

namespace Tests\Integration\QueueMaster;

use PHPUnit\Framework\TestCase;

class QueueMasterIntegrationTest extends TestCase
{
    public function testMasterStartsSuccessfully(): void
    {
        // Iniciar Master em processo separado
        $process = proc_open(
            'php og queue-master --environment=development',
            [
                0 => ['pipe', 'r'],
                1 => ['pipe', 'w'],
                2 => ['pipe', 'w'],
            ],
            $pipes
        );

        // Aguardar 5s
        sleep(5);

        // Verificar se Master está rodando
        $status = proc_get_status($process);
        $this->assertTrue($status['running'], 'Master should be running');

        // Terminar Master
        proc_terminate($process);
        proc_close($process);
    }
}
```

### Testes Manuais

#### Script de Teste

```bash
#!/bin/bash

echo "=== Testing QueueMaster Changes ==="

# 1. Limpar ambiente
echo "1. Cleanup..."
ps aux | grep -E "queue-master|queue:work" | grep -v grep | awk '{print $2}' | xargs -r kill -9
redis-cli FLUSHDB

# 2. Iniciar Master
echo "2. Starting Master..."
php og queue-master --environment=development > /tmp/queuemaster.log 2>&1 &
MASTER_PID=$!

# 3. Aguardar inicialização
echo "3. Waiting for initialization..."
sleep 5

# 4. Verificar workers
WORKER_COUNT=$(ps aux | grep "queue:work" | grep -v grep | wc -l)
echo "4. Workers active: $WORKER_COUNT"

if [ "$WORKER_COUNT" -eq 0 ]; then
    echo "ERROR: No workers started!"
    cat /tmp/queuemaster.log
    kill $MASTER_PID
    exit 1
fi

# 5. Enviar job de teste
echo "5. Dispatching test job..."
php og queue:dispatch "App\Jobs\TestJob" --queue=default

# 6. Aguardar processamento
sleep 5

# 7. Verificar se job foi processado
echo "6. Checking if job was processed..."
# Verificar logs ou Redis

# 8. Cleanup
echo "7. Cleanup..."
kill -SIGTERM $MASTER_PID
sleep 5

echo "=== Test Complete ==="
```

---

## Boas Práticas de Código

### 1. Namespaces e Estrutura

```php
// Seguir PSR-4
namespace Og\Modules\QueueMaster\Services;

// Imports organizados
use Illuminate\Support\Facades\Redis;
use Og\Modules\QueueMaster\Contracts\MetricsInterface;
```

### 2. Type Hints

```php
// Sempre usar type hints
public function processJob(JobInterface $job, int $timeout): void
{
    // ...
}

// Propriedades tipadas
private int $maxProcesses;
private ?string $vhost = null;
```

### 3. Logs Estruturados

```php
// Usar loggerBatch com contexto
loggerBatch('info', 'Worker starting', [
    'pid' => getmypid(),
    'queue' => $queue,
    'vhost' => $vhost,
    'max_time' => $maxTime,
]);

// Evitar:
error_log("Worker starting on $queue");
```

### 4. Exception Handling

```php
try {
    $this->doSomething();
} catch (\Throwable $e) {
    // Log completo
    loggerBatch('error', 'Operation failed', [
        'operation' => 'doSomething',
        'exception' => get_class($e),
        'message' => $e->getMessage(),
        'trace' => $e->getTraceAsString(),
    ]);

    // Re-throw ou handle
    throw $e;
}
```

### 5. Comentários

```php
// Comentários apenas quando necessário
// Código deve ser autoexplicativo

// BOM:
public function scaleUp(int $processes): void
{
    $difference = $processes - count($this->processes);

    for ($i = 0; $i < $difference; $i++) {
        $this->start();
    }
}

// RUIM:
public function scaleUp(int $processes): void
{
    // Calcula diferença
    $difference = $processes - count($this->processes);

    // Loop para criar workers
    for ($i = 0; $i < $difference; $i++) {
        // Inicia worker
        $this->start();
    }
}
```

### 6. Avoid God Objects

```php
// Separar responsabilidades

// BOM:
class MasterSupervisor
{
    private ProcessManager $processManager;
    private SignalHandler $signalHandler;
    private OrphanCleaner $orphanCleaner;

    // Delegar para classes especializadas
}

// RUIM:
class MasterSupervisor
{
    // 50 métodos fazendo tudo
}
```

---

## Debugging Avançado

### Xdebug com QueueMaster

**Configuração**:

```ini
[xdebug]
xdebug.mode=debug,develop
xdebug.start_with_request=trigger
xdebug.idekey=VSCODE
```

**Uso**:

```bash
# Iniciar com Xdebug
XDEBUG_SESSION=1 php og queue-master

# Breakpoint será atingido no IDE
```

### Strace (System Calls)

```bash
# Rastrear syscalls do Master
strace -p <master_pid> -f -e trace=process

# Ver forks
strace -p <master_pid> -f -e trace=fork,clone

# Ver sinais
strace -p <master_pid> -f -e trace=signal
```

### GDB (Stack Trace)

```bash
# Attach ao processo
gdb -p <worker_pid>

# Ver threads
(gdb) info threads

# Backtrace
(gdb) thread apply all bt

# Detach
(gdb) detach
(gdb) quit
```

### Memory Profiling

```bash
# Instalar Valgrind
sudo apt install valgrind

# Profile Master
valgrind --tool=massif php og queue-master

# Analisar
ms_print massif.out.<pid>
```

### Performance Profiling

**Instalar Xhprof**:

```bash
sudo apt install php-xhprof
```

**Habilitar**:

```php
// No início do QueueMasterCommand
xhprof_enable(XHPROF_FLAGS_CPU + XHPROF_FLAGS_MEMORY);

// No final
$data = xhprof_disable();
file_put_contents('/tmp/xhprof.json', json_encode($data));
```

---

## Próximos Documentos

- **[ROADMAP.md](ROADMAP.md)** - Funcionalidades planejadas
- **[ARCHITECTURE.md](ARCHITECTURE.md)** - Arquitetura detalhada
- **[TROUBLESHOOTING.md](TROUBLESHOOTING.md)** - Como debugar problemas
