diff --git a/README.md b/README.md index eae17a8..18541eb 100644 --- a/README.md +++ b/README.md @@ -1,713 +1,203 @@ -# Process Scheduler Simulator — Especificação Técnica Completa +# 🖥️ Process Scheduler Simulator -> **Versão:** 1.0 -> **Data:** 2026-03-19 -> **Autor:** Lucas Rayan Guerra -> **Stack alvo:** React + Vite (Single Page Application, browser-only, sem backend) +> Uma ferramenta educacional interativa para visualizar e comparar algoritmos clássicos de escalonamento de processos de Sistemas Operacionais. + +[![React](https://img.shields.io/badge/React-18-61DAFB?logo=react&logoColor=white)](https://react.dev/) +[![TypeScript](https://img.shields.io/badge/TypeScript-5-3178C6?logo=typescript&logoColor=white)](https://www.typescriptlang.org/) +[![Vite](https://img.shields.io/badge/Vite-6-646CFF?logo=vite&logoColor=white)](https://vitejs.dev/) +[![Tailwind CSS](https://img.shields.io/badge/Tailwind-4-06B6D4?logo=tailwindcss&logoColor=white)](https://tailwindcss.com/) +[![Vitest](https://img.shields.io/badge/Tested_with-Vitest-6E9F18?logo=vitest&logoColor=white)](https://vitest.dev/) +[![License: ISC](https://img.shields.io/badge/License-ISC-yellow.svg)](https://opensource.org/licenses/ISC) --- -## 1. Visão Geral +## ✨ O que é? -O **Process Scheduler Simulator** é uma ferramenta educacional interativa que roda inteiramente no navegador. Ele permite ao usuário criar processos, escolher um algoritmo de escalonamento, executar a simulação passo a passo ou de forma automática, visualizar o diagrama de Gantt em tempo real e analisar métricas detalhadas por processo e globais. +O **Process Scheduler Simulator (PSS)** roda completamente no navegador — sem servidor, sem instalação de dependências de sistema. Você define os processos, escolhe o algoritmo, e assiste à simulação acontecer em tempo real através do diagrama de Gantt. -### 1.1 Objetivos - -| # | Objetivo | -|---|----------| -| O1 | Simular fielmente os algoritmos clássicos de escalonamento de SO | -| O2 | Permitir execução passo a passo para fins didáticos | -| O3 | Exibir diagrama de Gantt animado com estados de CPU e I/O | -| O4 | Calcular e exibir métricas completas por processo e agregadas | -| O5 | Ser completamente autocontido — sem servidor, sem dependências externas de dados | - -### 1.2 Público-Alvo - -- Estudantes de Sistemas Operacionais (graduação/pós) -- Professores que queiram usar em sala de aula -- Profissionais revisando fundamentos de SO +Ideal para: +- 📚 Estudantes de Sistemas Operacionais querendo visualizar o que antes era só teoria +- 👩‍🏫 Professores buscando uma ferramenta interativa para sala de aula +- 🔍 Profissionais revisitando fundamentos de SO --- -## 2. Requisitos Funcionais +## 🚀 Quick Start -### RF-01 — Gerenciamento de Processos +### Pré-requisitos -| ID | Descrição | -|----|-----------| -| RF-01.1 | O usuário pode **criar** um processo com os atributos listados na seção 3.1 | -| RF-01.2 | O usuário pode **editar** um processo antes de iniciar a simulação | -| RF-01.3 | O usuário pode **remover** um processo individual ou **limpar** todos | -| RF-01.4 | O usuário pode **gerar processos aleatórios** com parâmetros configuráveis (quantidade, faixas de valores) | -| RF-01.5 | A lista de processos exibe uma prévia das propriedades e a classificação CPU/I/O Bound estimada | -| RF-01.6 | Mínimo de 1 processo, máximo de 16 processos por simulação | +- [Node.js](https://nodejs.org/) 20+ +- npm 10+ -### RF-02 — Configuração do Escalonador +### Instalação e execução local -| ID | Descrição | -|----|-----------| -| RF-02.1 | O usuário seleciona **um** algoritmo de escalonamento por simulação | -| RF-02.2 | Parâmetros específicos de algoritmo são exibidos dinamicamente (ex.: quantum para RR) | -| RF-02.3 | O usuário configura o **tempo de context switch** (0–10 unidades de tempo, padrão 0) | -| RF-02.4 | O usuário configura se o escalonador é **preemptivo ou cooperativo** (onde aplicável) | +```bash +# Clone o repositório +git clone https://github.com/lucasrguerra/pss.git +cd pss -### RF-03 — Simulação +# Instale as dependências +npm install -| ID | Descrição | -|----|-----------| -| RF-03.1 | **Play** inicia/retoma a simulação com velocidade configurável | -| RF-03.2 | **Pause** suspende a execução sem perder o estado | -| RF-03.3 | **Step** avança exatamente 1 unidade de tempo (tick) | -| RF-03.4 | **Reset** retorna ao estado inicial sem perder a configuração de processos | -| RF-03.5 | **Velocidade** ajustável: 0.25×, 0.5×, 1×, 2×, 4× (padrão 1×) | -| RF-03.6 | A simulação termina quando todos os processos atingem estado **Terminated** | -| RF-03.7 | O tick atual é exibido em destaque no painel de controle | - -### RF-04 — Diagrama de Gantt - -| ID | Descrição | -|----|-----------| -| RF-04.1 | Cada processo ocupa uma linha horizontal no diagrama | -| RF-04.2 | Cada tick é representado por uma célula colorida conforme o estado | -| RF-04.3 | O tick atual é destacado com uma linha vertical animada | -| RF-04.4 | Legenda de cores dos estados exibida abaixo do diagrama | -| RF-04.5 | O diagrama faz scroll horizontal automático para acompanhar o tick atual | -| RF-04.6 | Hovering sobre uma célula exibe tooltip: processo, tick, estado, detalhes | - -### RF-05 — Métricas - -| ID | Descrição | -|----|-----------| -| RF-05.1 | Métricas são calculadas por processo ao final da simulação | -| RF-05.2 | Métricas globais (médias, totais) são exibidas em painel separado | -| RF-05.3 | Um gráfico de barras comparativo exibe o turnaround e waiting time por processo | -| RF-05.4 | A classificação CPU Bound / I/O Bound é determinada automaticamente (ver seção 5) | -| RF-05.5 | As métricas podem ser exportadas como CSV | - -### RF-06 — Persistência Local - -| ID | Descrição | -|----|-----------| -| RF-06.1 | O estado atual (processos + configuração) é salvo automaticamente no `localStorage` | -| RF-06.2 | O usuário pode exportar/importar configurações como JSON | -| RF-06.3 | Cenários pré-definidos (presets) estão disponíveis para carregamento rápido | - ---- - -## 3. Modelo de Dados - -### 3.1 Processo (`Process`) - -```typescript -interface BurstSegment { - type: "cpu" | "io"; - duration: number; // unidades de tempo -} - -interface Process { - id: string; // UUID gerado na criação - name: string; // "P1", "P2" ou nome personalizado - arrivalTime: number; // tick de chegada na fila ready (≥ 0) - priority: number; // 1 (maior prioridade) a 10 (menor); padrão 5 - color: string; // cor HEX para o diagrama - bursts: BurstSegment[]; // sequência alternada CPU→I/O→CPU→... - // deve começar e terminar com CPU -} +# Inicie o servidor de desenvolvimento +npm run dev ``` -**Exemplo de processo:** +Acesse [http://localhost:5173](http://localhost:5173) no seu navegador. -```json -{ - "id": "p1", - "name": "P1", - "arrivalTime": 0, - "priority": 3, - "color": "#4FC3F7", - "bursts": [ - { "type": "cpu", "duration": 4 }, - { "type": "io", "duration": 3 }, - { "type": "cpu", "duration": 2 } - ] -} +### Build de produção + +```bash +npm run build +npm run preview ``` -> **Regra:** `bursts` deve ter estrutura CPU → (IO → CPU)\* — sempre começa com CPU. Mínimo: 1 burst CPU. +### Com Docker -### 3.2 Configuração do Escalonador (`SchedulerConfig`) - -```typescript -interface SchedulerConfig { - algorithm: SchedulingAlgorithm; - quantum: number; // apenas para RR (padrão: 2) - contextSwitchTime: number; // overhead entre processos (padrão: 0) - isPreemptive: boolean; // aplicável a SJF e Priority - agingEnabled: boolean; // evitar starvation em Priority - agingInterval: number; // ticks para incremento de prioridade -} -``` - -### 3.3 Estado de Execução do Processo (`ProcessState`) - -```typescript -type StateLabel = - | "New" // antes do arrivalTime - | "Ready" // na fila, aguardando CPU - | "Running" // executando burst CPU - | "Waiting" // executando burst I/O - | "Terminated"; // concluído - -interface ProcessRuntime { - processId: string; - state: StateLabel; - remainingBurst: number; // ticks restantes no burst atual - burstIndex: number; // índice atual em Process.bursts - arrivalTick: number; // tick em que entrou na fila Ready pela 1ª vez - startTick: number | null; // tick da 1ª vez em Running - finishTick: number | null; // tick em que terminou - waitingTime: number; // acumulado em Ready - cpuTime: number; // acumulado em Running - ioTime: number; // acumulado em Waiting - responseTime: number | null; -} -``` - -### 3.4 Tick de Simulação (`SimTick`) - -```typescript -interface SimTick { - tick: number; - cpuProcess: string | null; // PID executando na CPU - ioProcesses: string[]; // PIDs em I/O - readyQueue: string[]; // ordem da fila ready - states: Record; - contextSwitching: boolean; // overhead ativo -} +```bash +docker compose up ``` --- -## 4. Algoritmos de Escalonamento +## 🎮 Como usar -### 4.1 Lista de Algoritmos Suportados +1. **Crie seus processos** — No painel esquerdo, adicione processos definindo nome, tempo de chegada, prioridade e sequência de bursts (CPU e I/O). Ou use o gerador aleatório para começar rápido. +2. **Configure o escalonador** — Escolha o algoritmo e ajuste os parâmetros (quantum para RR, aging para Priority, etc.). +3. **Execute a simulação** — Use os controles de Play/Pause/Step para rodar a simulação na velocidade que quiser (0.25× até 4×). +4. **Analise os resultados** — O diagrama de Gantt mostra em tempo real o estado de cada processo. Ao final, o painel de métricas exibe turnaround, waiting time, throughput e mais. -| Código | Nome | Preemptivo? | Parâmetro extra | -|--------|------|-------------|-----------------| -| `FCFS` | First Come First Served | Não | — | -| `SJF_NP` | Shortest Job First (não-preemptivo) | Não | — | -| `SJF_P` | Shortest Remaining Time First (preemptivo) | Sim | — | -| `RR` | Round Robin | Sim | Quantum | -| `PRIORITY_NP` | Priority Scheduling (não-preemptivo) | Não | — | -| `PRIORITY_P` | Priority Scheduling (preemptivo) | Sim | — | -| `MULTILEVEL` | Multilevel Queue | Sim | Nº de filas, quantum por fila | -| `HRRN` | Highest Response Ratio Next | Não | — | - -### 4.2 Regras de Desempate - -Para todos os algoritmos, quando dois processos têm o mesmo critério de seleção, o desempate é feito por ordem de chegada (`arrivalTime`) e depois por `id` lexicográfico. - -### 4.3 Especificação de Comportamento por Algoritmo - -#### FCFS -- A fila ready é ordenada por `arrivalTime` ascendente. -- O processo na cabeça da fila ocupa a CPU até terminar seu burst CPU atual. -- Ao término do burst CPU, se houver burst I/O, transita para Waiting. -- Ao terminar o I/O, retorna para a fila Ready (cauda). - -#### SJF Não-Preemptivo -- Na seleção, escolhe o processo com **menor burst CPU atual** (`remainingBurst`). -- Não interrompe o processo em execução. - -#### SRTF (SJF Preemptivo) -- A cada tick, verifica se algum processo na fila Ready tem `remainingBurst` menor que o processo atual. -- Se sim, realiza preempção (com context switch se configurado). - -#### Round Robin -- Usa fila FIFO com quantum `q`. -- Ao expirar o quantum, o processo retorna ao fim da fila Ready se ainda tiver burst CPU restante. -- I/O bursts não consomem quantum. - -#### Priority (Não-Preemptivo / Preemptivo) -- Menor valor numérico = maior prioridade. -- Com aging habilitado: a cada `agingInterval` ticks em Ready, `priority--` (até mínimo 1). - -#### HRRN -- `ratio = (waitingTime + burstTime) / burstTime` -- A cada seleção, escolhe o processo com maior ratio. -- Não-preemptivo. +> 💡 **Dica:** Carregue um dos **presets** disponíveis para ver fenômenos clássicos como o efeito de comboio do FCFS ou starvation com Priority Scheduling. --- -## 5. Definição de CPU Bound vs. I/O Bound +## ⚙️ Algoritmos Suportados -A classificação é feita **por processo**, baseada na proporção de tempo total gasto: +| Algoritmo | Preemptivo? | Parâmetros | +|-----------|:-----------:|------------| +| **FCFS** — First Come, First Served | ❌ | — | +| **SJF** — Shortest Job First | ❌ | — | +| **SRTF** — Shortest Remaining Time First | ✅ | — | +| **Round Robin (RR)** | ✅ | Quantum | +| **Priority NP** — Prioridade Não-Preemptivo | ❌ | Aging (opcional) | +| **Priority P** — Prioridade Preemptivo | ✅ | Aging (opcional) | +| **HRRN** — Highest Response Ratio Next | ❌ | — | +| **Multilevel Queue** | ✅ | Quantum por fila | -``` -cpuRatio = cpuTime / (cpuTime + ioTime) -ioRatio = ioTime / (cpuTime + ioTime) - -if cpuRatio >= 0.65 → "CPU Bound" -if ioRatio >= 0.65 → "I/O Bound" -else → "Balanced" -``` - -A classificação aparece: -- Na lista de processos (badge de preview, calculado sobre os bursts declarados) -- Na tabela de métricas (calculado sobre os tempos reais simulados) +> Consulte a [documentação dos algoritmos](./docs/algorithms.md) para detalhes sobre como cada um funciona. --- -## 6. Métricas +## 📊 Métricas calculadas -### 6.1 Métricas por Processo +Para cada processo simulado, o PSS calcula automaticamente: -| Métrica | Fórmula | Descrição | -|---------|---------|-----------| -| **Arrival Time** | `arrivalTime` | Tick de chegada na fila | -| **First CPU Tick** | `startTick` | Primeiro tick em Running | -| **Finish Time** | `finishTick` | Tick de término | -| **Response Time** | `startTick − arrivalTime` | Tempo até 1ª vez na CPU | -| **Turnaround Time** | `finishTick − arrivalTime` | Tempo total de vida | -| **Waiting Time** | `turnaround − cpuTime − ioTime` | Tempo na fila Ready | -| **CPU Time** | Σ Running ticks | Total de CPU consumida | -| **I/O Time** | Σ Waiting ticks | Total de I/O | -| **CPU Utilization** | `cpuTime / turnaround × 100` | % do turnaround em CPU | -| **Bound Type** | ver seção 5 | CPU Bound / I/O Bound / Balanced | +- **Response Time** — tempo até o primeiro uso de CPU +- **Turnaround Time** — tempo total desde a chegada até o término +- **Waiting Time** — tempo total na fila Ready +- **CPU Time / I/O Time** — tempo efetivo em cada tipo de burst +- **CPU Utilization** — percentual do turnaround em CPU +- **Bound Type** — classificação automática em CPU Bound, I/O Bound ou Balanced -### 6.2 Métricas Globais - -| Métrica | Fórmula | -|---------|---------| -| **Avg Response Time** | Média dos response times | -| **Avg Turnaround Time** | Média dos turnarounds | -| **Avg Waiting Time** | Média dos waiting times | -| **CPU Throughput** | `processos concluídos / tempo total` | -| **CPU Utilization (global)** | `Σ Running ticks / tempo total × 100` | -| **Total Simulation Time** | Tick final | +E globalmente: médias de response/turnaround/waiting, throughput e utilização global de CPU. --- -## 7. Arquitetura da Aplicação - -### 7.1 Stack Tecnológica - -``` -Runtime: React 18 + Vite 5 -Linguagem: TypeScript 5 -Estilos: Tailwind CSS 3 + CSS Modules (para componentes específicos) -Gráficos: Recharts (gráfico de barras de métricas) -Estado: Zustand (store global) -Persistência: localStorage (auto-save) + JSON import/export -Testes: Vitest + @testing-library/react -``` - -### 7.2 Estrutura de Pastas +## 🗂️ Estrutura do Projeto ``` src/ ├── core/ # Lógica pura — sem React -│ ├── algorithms/ +│ ├── algorithms/ # Implementação de cada algoritmo +│ │ ├── base.ts # Classe abstrata BaseScheduler │ │ ├── fcfs.ts │ │ ├── sjf.ts │ │ ├── rr.ts │ │ ├── priority.ts +│ │ ├── priority_rr.ts │ │ ├── hrrn.ts │ │ ├── multilevel.ts -│ │ └── index.ts # dispatch por algoritmo -│ ├── engine.ts # SimulationEngine — step(), runAll() -│ ├── metrics.ts # cálculo de métricas -│ ├── types.ts # todas as interfaces -│ └── presets.ts # cenários pré-definidos +│ │ └── index.ts # Dispatch por algoritmo +│ ├── engine.ts # SimulationEngine — orquestra os ticks +│ ├── metrics.ts # Cálculo de métricas por processo e globais +│ ├── types.ts # Todas as interfaces TypeScript +│ └── presets.ts # Cenários pré-definidos │ -├── store/ -│ ├── processStore.ts # CRUD de processos -│ ├── simulationStore.ts # estado da simulação, ticks -│ └── uiStore.ts # estado da UI (painéis abertos, velocidade) +├── store/ # Estado global com Zustand +├── components/ # Componentes React +│ ├── ProcessPanel/ # Criação e edição de processos +│ ├── SchedulerPanel/ # Seleção de algoritmo e parâmetros +│ ├── ControlBar/ # Play, Pause, Step, Reset, velocidade +│ ├── GanttChart/ # Diagrama de Gantt interativo +│ ├── MetricsPanel/ # Tabela, gráficos e métricas globais +│ └── shared/ # Botões, badges, tooltips, modais │ -├── components/ -│ ├── ProcessPanel/ # criação/edição de processos -│ │ ├── ProcessList.tsx -│ │ ├── ProcessForm.tsx -│ │ └── ProcessCard.tsx -│ ├── SchedulerPanel/ # seleção de algoritmo e parâmetros -│ │ ├── AlgorithmSelector.tsx -│ │ └── SchedulerConfig.tsx -│ ├── ControlBar/ # play, pause, step, reset, velocidade -│ │ └── ControlBar.tsx -│ ├── GanttChart/ # diagrama de Gantt -│ │ ├── GanttChart.tsx -│ │ ├── GanttRow.tsx -│ │ ├── GanttCell.tsx -│ │ └── GanttLegend.tsx -│ ├── MetricsPanel/ # tabela + gráficos -│ │ ├── MetricsTable.tsx -│ │ ├── MetricsChart.tsx -│ │ └── GlobalMetrics.tsx -│ └── shared/ # botões, badges, tooltips, modais -│ -├── hooks/ -│ ├── useSimulation.ts # loop de simulação com requestAnimationFrame -│ ├── useLocalStorage.ts -│ └── useExport.ts -│ -├── App.tsx -└── main.tsx -``` - -### 7.3 Fluxo de Dados - -``` -[ProcessForm] ──create/edit──► [processStore] - │ -[AlgorithmSelector] ──config──► [simulationStore] - │ - [SimulationEngine] - ┌──────┴──────┐ - step() runAll() - └──────┬──────┘ - SimTick[] - │ - ┌─────────────────┼────────────────┐ - [GanttChart] [ControlBar] [MetricsPanel] +└── hooks/ # Hooks personalizados (loop de animação, export, etc.) ``` --- -## 8. Motor de Simulação (`SimulationEngine`) +## 📦 Presets disponíveis -### 8.1 Interface Pública +Carregue cenários clássicos com um clique para explorar fenômenos de SO: -```typescript -class SimulationEngine { - constructor(processes: Process[], config: SchedulerConfig); - - // Avança 1 tick; retorna o snapshot daquele tick - step(): SimTick; - - // Executa todos os ticks até o fim; retorna array completo - runAll(): SimTick[]; - - // Verdadeiro se todos os processos estão Terminated - get isFinished(): boolean; - - // Tick atual - get currentTick(): number; - - // Estado atual de todos os processos - get runtimeStates(): ProcessRuntime[]; -} -``` - -### 8.2 Loop Principal (pseudocódigo) - -``` -function step(): - tick++ - - // 1. Verificar chegadas de novos processos - for each process where arrivalTime == tick: - transitionTo(process, "Ready") - addToReadyQueue(process) - - // 2. Aging (se habilitado) - if agingEnabled and tick % agingInterval == 0: - for each process in readyQueue: - process.priority = max(1, process.priority - 1) - - // 3. Verificar fim de I/O - for each process in ioQueue: - process.remainingBurst-- - if process.remainingBurst == 0: - advanceBurst(process) - if hasMoreBursts(process): - transitionTo(process, "Ready") - addToReadyQueue(process) - else: - transitionTo(process, "Terminated") - process.finishTick = tick - - // 4. Verificar fim do burst CPU atual - if cpuProcess != null: - cpuProcess.remainingBurst-- - cpuProcess.cpuTime++ - if cpuProcess.remainingBurst == 0: - advanceBurst(cpuProcess) - if hasIoBurst(cpuProcess): - transitionTo(cpuProcess, "Waiting") - addToIoQueue(cpuProcess) - cpuProcess = null - else if hasMoreCpuBurst(cpuProcess): - // volta pra ready (ex: após I/O futuro) - else: - transitionTo(cpuProcess, "Terminated") - cpuProcess = null - - // 5. Verificar preempção (algoritmos preemptivos) - if isPreemptive and cpuProcess != null: - candidate = algorithm.selectFromReady(readyQueue) - if shouldPreempt(candidate, cpuProcess): - preempt(cpuProcess) - cpuProcess = candidate - - // 6. Selecionar próximo processo (se CPU livre) - if cpuProcess == null and readyQueue.length > 0: - cpuProcess = algorithm.selectFromReady(readyQueue) - if cpuProcess.startTick == null: - cpuProcess.startTick = tick - transitionTo(cpuProcess, "Running") - - // 7. Acumular waiting time - for each process in readyQueue: - process.waitingTime++ - - return snapshot() -``` +| Preset | O que demonstra | +|--------|-----------------| +| `classic_fcfs` | Execução básica com FCFS, sem I/O | +| `convoy_effect` | Efeito de comboio: um processo longo bloqueando vários curtos | +| `rr_demo` | Revezamento justo com Round Robin (quantum=3) | +| `starvation` | Processos de baixa prioridade nunca recebem CPU | +| `aging_fix` | O mesmo cenário de starvation, resolvido com aging | +| `io_heavy` | Mix de processos CPU Bound vs I/O Bound | +| `multilevel_demo` | Múltiplas filas com prioridades diferentes | --- -## 9. Interface do Usuário +## 🧪 Testes -### 9.1 Layout Geral (Desktop) +```bash +# Roda todos os testes unitários +npm test -``` -┌─────────────────────────────────────────────────────────────────────┐ -│ HEADER: título, botão de preset, import/export │ -├──────────────┬──────────────────────────────────────────────────────┤ -│ │ │ -│ ESQUERDA │ CENTRO/DIREITA │ -│ (320px) │ │ -│ │ ┌─────────────────────────────────────────────────┐ │ -│ [Processos] │ │ BARRA DE CONTROLE (play/pause/step/reset/vel.) │ │ -│ + lista │ └─────────────────────────────────────────────────┘ │ -│ + formulário│ │ -│ │ ┌─────────────────────────────────────────────────┐ │ -│ ────────── │ │ GANTT CHART (scroll horizontal) │ │ -│ │ │ Tick: 00000000000001111111111122222222222233... │ │ -│ [Algoritmo] │ │ P1: ██████░░░░░░██████░░░░░█████ │ │ -│ + parâmetros│ │ P2: ░░░░░░██████░░░░░░████░░░░░ │ │ -│ │ └─────────────────────────────────────────────────┘ │ -│ │ │ -│ │ ┌─────────────────────────────────────────────────┐ │ -│ │ │ MÉTRICAS (tabela por processo + gráficos) │ │ -│ │ └─────────────────────────────────────────────────┘ │ -└──────────────┴──────────────────────────────────────────────────────┘ +# Modo watch (re-executa ao salvar) +npm run test:watch + +# Com relatório de cobertura +npm run test:coverage ``` -### 9.2 Cores de Estado no Gantt - -| Estado | Cor | Token CSS | -|--------|-----|-----------| -| New | Cinza escuro | `--state-new` | -| Ready | Amarelo âmbar | `--state-ready` | -| Running | Verde brilhante | `--state-running` | -| Waiting (I/O) | Azul ciano | `--state-waiting` | -| Terminated | Cinza claro | `--state-terminated` | -| Context Switch | Vermelho/laranja | `--state-ctx-switch` | - -### 9.3 Formulário de Processo - -Campos: -- **Nome** (text, max 8 chars) -- **Arrival Time** (number, ≥ 0) -- **Priority** (1–10) -- **Cor** (color picker) -- **Bursts** — editor visual de bursts com botões +/− por segmento - - Tipo: `CPU` | `I/O` - - Duração (number, ≥ 1) - - Validação: deve iniciar com CPU, alternar CPU/IO - -### 9.4 Painel de Algoritmo - -Exibe: -- Dropdown de seleção de algoritmo -- Campos condicionais: - - RR → `quantum` - - Priority → `preemptive toggle`, `aging toggle + interval` - - Multilevel → configuração de filas -- Context switch time (sempre) -- Tooltip explicativo por algoritmo - -### 9.5 Barra de Controle - -``` -[⏮ Reset] [⏭ Step] [▶ Play / ⏸ Pause] [Tick: 0042] [Velocidade: 1×▾] -``` - -### 9.6 Painel de Métricas (após simulação) - -**Tab 1 — Por Processo:** - -Tabela com colunas: -`Processo | Arrival | Start | Finish | Response | Turnaround | Waiting | CPU Time | I/O Time | CPU% | Bound` - -**Tab 2 — Globais:** - -Cards com: -- Avg Response Time -- Avg Turnaround Time -- Avg Waiting Time -- CPU Throughput -- CPU Utilization % -- Total Simulation Time - -**Tab 3 — Gráfico:** - -Gráfico de barras agrupadas por processo: -- Barras: Waiting Time, CPU Time, I/O Time (empilhadas para turnaround) +Os testes cobrem a engine de simulação, todos os algoritmos de escalonamento, e o cálculo de métricas. --- -## 10. Cenários Pré-definidos (Presets) +## 🛠️ Stack Tecnológica -| Nome | Descrição | Algoritmo sugerido | -|------|-----------|-------------------| -| `classic_fcfs` | 4 processos simples, sem I/O | FCFS | -| `convoy_effect` | Demonstra o efeito de comboio do FCFS | FCFS vs SJF | -| `rr_demo` | 5 processos com quantum 3 | Round Robin | -| `starvation` | Prioridades que causam starvation | Priority NP | -| `aging_fix` | Mesmo cenário, com aging ativo | Priority P + Aging | -| `io_heavy` | Processos I/O Bound vs CPU Bound misturados | SRTF | -| `multilevel_demo` | Demonstra MLQ | Multilevel | +| Camada | Tecnologia | +|--------|-----------| +| Framework UI | React 18 | +| Linguagem | TypeScript 5 | +| Build tool | Vite 6 | +| Estilos | Tailwind CSS 4 | +| Estado global | Zustand 5 | +| Gráficos | Recharts | +| Ícones | Lucide React | +| Testes | Vitest + Testing Library | --- -## 11. Requisitos Não-Funcionais +## 📄 Documentação adicional -| RNF | Requisito | -|-----|-----------| -| RNF-01 | Desempenho: simulações com até 16 processos e 1000 ticks sem travamento | -| RNF-02 | Responsividade: layout funcional em telas ≥ 768px | -| RNF-03 | Acessibilidade: ARIA labels nos controles principais, contraste WCAG AA | -| RNF-04 | Offline-first: 100% funcional sem internet após build | -| RNF-05 | Build size < 500 KB gzipped | -| RNF-06 | Suporte a Chrome 120+, Firefox 120+, Safari 17+ | +- [📐 Algoritmos de Escalonamento](./docs/algorithms.md) — descrição detalhada de cada algoritmo, regras de desempate e exemplos +- [📏 Métricas](./docs/metrics.md) — fórmulas e definições de todas as métricas calculadas --- -## 12. Plano de Implementação (Fases) +## 🤝 Contribuindo -### Fase 1 — Core Engine (sem UI) — *~2 dias* -- [ ] Definir todos os tipos em `core/types.ts` -- [ ] Implementar `SimulationEngine` com suporte a FCFS -- [ ] Testes unitários: FCFS, estados de transição, métricas -- [ ] Implementar SJF NP e RR com testes -- [ ] Implementar Priority NP/P com aging e testes -- [ ] Implementar SRTF e HRRN com testes - -### Fase 2 — Store & State Management — *~1 dia* -- [ ] Configurar Zustand stores -- [ ] Hook `useSimulation` com loop de animação -- [ ] Integração com localStorage - -### Fase 3 — UI Base — *~2 dias* -- [ ] Layout geral (sidebar + área principal) -- [ ] `ProcessForm` e `ProcessList` -- [ ] `AlgorithmSelector` e `SchedulerConfig` -- [ ] `ControlBar` - -### Fase 4 — Gantt Chart — *~2 dias* -- [ ] Renderização de células por tick e processo -- [ ] Auto-scroll horizontal -- [ ] Tooltip por célula -- [ ] Legenda de estados - -### Fase 5 — Métricas e Gráficos — *~1 dia* -- [ ] Tabela de métricas por processo -- [ ] Cards de métricas globais -- [ ] Gráfico de barras com Recharts -- [ ] Export CSV - -### Fase 6 — Polimento e Presets — *~1 dia* -- [ ] Presets prontos para carregar -- [ ] Gerador de processos aleatórios -- [ ] Import/export JSON -- [ ] Responsividade mobile (≥768px) -- [ ] Refinamento visual - -**Total estimado: ~9 dias de trabalho focado** +Contribuições são bem-vindas! Sinta-se livre para abrir uma [issue](https://github.com/lucasrguerra/pss/issues) ou enviar um Pull Request. --- -## 13. Casos de Teste Funcionais +## 📜 Licença -### CT-01: FCFS Básico - -**Entrada:** -``` -P1: arrival=0, bursts=[CPU:4] -P2: arrival=1, bursts=[CPU:3] -P3: arrival=2, bursts=[CPU:1] -``` - -**Esperado:** -``` -Gantt: P1(0-4), P2(4-7), P3(7-8) -P1: response=0, turnaround=4, waiting=0 -P2: response=3, turnaround=6, waiting=3 -P3: response=5, turnaround=6, waiting=5 -``` - -### CT-02: Round Robin (quantum=2) - -**Entrada:** -``` -P1: arrival=0, bursts=[CPU:5] -P2: arrival=0, bursts=[CPU:3] -P3: arrival=0, bursts=[CPU:1] -quantum=2 -``` - -**Esperado:** -``` -Gantt: P1(0-2), P2(2-4), P3(4-5), P1(5-7), P2(7-8), P1(8-9) -P1: response=0, turnaround=9, waiting=4 -P2: response=2, turnaround=8, waiting=5 -P3: response=4, turnaround=5, waiting=4 -``` - -### CT-03: Classificação Bound - -**Entrada:** -``` -P1: bursts=[CPU:8, IO:1, CPU:1] → cpuRatio = 9/10 = 90% → CPU Bound -P2: bursts=[CPU:1, IO:8, CPU:1] → cpuRatio = 2/10 = 20% → I/O Bound -P3: bursts=[CPU:4, IO:2, CPU:4] → cpuRatio = 8/10 = 80% → CPU Bound -``` - -### CT-04: Preempção SRTF - -**Entrada:** -``` -P1: arrival=0, bursts=[CPU:8] -P2: arrival=1, bursts=[CPU:4] -quantum=∞ (SRTF) -``` - -**Esperado:** -``` -P1 executa tick 0-1, P2 preempta no tick 1 (remaining=3<7), P2 termina tick 5, -P1 retoma e termina tick 12 -``` - ---- - -## 14. Glossário - -| Termo | Definição | -|-------|-----------| -| **Tick** | Unidade atômica de tempo na simulação (equivale a 1 unidade de tempo de CPU) | -| **Burst** | Segmento contínuo de execução (CPU ou I/O) | -| **Quantum** | Fatia máxima de CPU no Round Robin | -| **Context Switch** | Overhead ao trocar o processo em execução na CPU | -| **Starvation** | Processo que nunca recebe CPU por processos de maior prioridade sempre chegando | -| **Aging** | Mecanismo que aumenta gradualmente a prioridade de processos esperando há muito tempo | -| **Turnaround Time** | Tempo total desde a chegada até o término do processo | -| **Response Time** | Tempo entre a chegada e o primeiro uso de CPU | -| **Waiting Time** | Tempo total em que o processo ficou na fila Ready | -| **CPU Bound** | Processo que passa a maior parte do tempo usando CPU | -| **I/O Bound** | Processo que passa a maior parte do tempo aguardando I/O | -| **FCFS** | First Come, First Served — ordem de chegada | -| **SJF** | Shortest Job First — menor burst next | -| **SRTF** | Shortest Remaining Time First — SJF preemptivo | -| **RR** | Round Robin — revezamento com quantum | -| **HRRN** | Highest Response Ratio Next — balanceia tempo de espera e burst | +Distribuído sob a [Licença ISC](./LICENSE). Desenvolvido por [Lucas Rayan Guerra](https://github.com/lucasrguerra). diff --git a/dist.tar.gz b/dist.tar.gz index db8f039..f6b9c30 100644 Binary files a/dist.tar.gz and b/dist.tar.gz differ diff --git a/docs/algorithms.md b/docs/algorithms.md new file mode 100644 index 0000000..cac6954 --- /dev/null +++ b/docs/algorithms.md @@ -0,0 +1,162 @@ +# 📐 Algoritmos de Escalonamento + +Este documento descreve em detalhes os algoritmos de escalonamento implementados no PSS. Para cada algoritmo são apresentados o princípio de funcionamento, parâmetros, regras de desempate e exemplos didáticos. + +--- + +## Regras Gerais + +- **Unidade de tempo:** o simulador opera em *ticks* discretos. Cada tick representa 1 unidade de tempo de CPU. +- **Bursts:** cada processo possui uma sequência de bursts alternados: `CPU → I/O → CPU → ...`, sempre começando com CPU. +- **Desempate:** quando dois processos estão empatados no critério de seleção do algoritmo, a prioridade vai para o que chegou primeiro (`arrivalTime`). Em segundo desempate, usa-se o `id` em ordem lexicográfica. +- **Context Switch:** se configurado (≥ 1 tick), um overhead é inserido toda vez que a CPU troca de processo. + +--- + +## FCFS — First Come, First Served + +**Tipo:** Não-preemptivo + +### Como funciona + +Talvez o algoritmo mais intuitivo: o processo que chegou primeiro é atendido primeiro. A fila Ready é ordenada por `arrivalTime`. Quando um processo ocupa a CPU, ele permanece lá até completar seu burst de CPU atual — sem interrupções. + +### Exemplo + +``` +P1: chegada=0, CPU: 4 ticks +P2: chegada=1, CPU: 3 ticks +P3: chegada=2, CPU: 1 tick + +Gantt: | P1 P1 P1 P1 | P2 P2 P2 | P3 | +Tick: 0 1 2 3 4 5 6 7 +``` + +| Processo | Turnaround | Waiting | +|----------|:----------:|:-------:| +| P1 | 4 | 0 | +| P2 | 6 | 3 | +| P3 | 6 | 5 | + +### Observação: Efeito de Comboio + +Se um processo longo chega antes de vários processos curtos, estes ficam esperando por muito tempo — mesmo que a CPU esteja "disponível" para eles. Este fenômeno é chamado de **Convoy Effect** e é um dos principais problemas do FCFS. Experimente o preset `convoy_effect` para visualizá-lo. + +--- + +## SJF — Shortest Job First (Não-Preemptivo) + +**Tipo:** Não-preemptivo + +### Como funciona + +Ao selecionar o próximo processo, o SJF escolhe aquele com o **menor burst de CPU restante** na fila Ready. Uma vez na CPU, o processo não é interrompido até completar seu burst. + +O SJF é ótimo para minimizar o tempo médio de espera, mas tem um problema prático: em sistemas reais, a duração dos bursts futuros é desconhecida. No simulador, você define os bursts explicitamente, então o SJF pode ser aplicado perfeitamente. + +### Parâmetros + +Nenhum parâmetro adicional. + +--- + +## SRTF — Shortest Remaining Time First (SJF Preemptivo) + +**Tipo:** Preemptivo + +### Como funciona + +Versão preemptiva do SJF. A cada tick, o escalonador verifica se algum processo na fila Ready possui tempo de burst restante **menor** que o processo atualmente em execução. Se sim, ocorre uma preempção: o processo atual é devolvido à fila Ready e o novo candidato assume a CPU. + +### Exemplo + +``` +P1: chegada=0, CPU: 8 ticks +P2: chegada=1, CPU: 4 ticks + +Tick 0: P1 começa (remaining=8) +Tick 1: P2 chega (remaining=4 < 7) → preempção! +Tick 5: P2 termina → P1 retoma (remaining=7) +Tick 12: P1 termina +``` + +--- + +## Round Robin (RR) + +**Tipo:** Preemptivo +**Parâmetro:** `quantum` (fatia de tempo, padrão: 2) + +### Como funciona + +A fila Ready é tratada como uma fila circular. Cada processo recebe no máximo `quantum` ticks de CPU consecutivos. Se ao final do quantum o processo ainda tiver burst restante, ele retorna ao **fim** da fila Ready e aguarda sua vez. + +I/O bursts **não** consomem quantum — o processo sai voluntariamente da CPU quando inicia I/O. + +### Tradeoff + +Quanto menor o quantum, mais justo o revezamento, mas maior o overhead de context switches. Quanto maior, o RR se aproxima do FCFS. + +--- + +## Priority Scheduling (Não-Preemptivo e Preemptivo) + +**Tipo:** Não-preemptivo ou Preemptivo (selecione no painel) +**Parâmetros:** Aging (opcional) + intervalo de aging + +### Como funciona + +Cada processo tem uma prioridade de `1` (maior) a `10` (menor). O escalonador sempre seleciona o processo de maior prioridade na fila Ready. + +- **Não-preemptivo:** uma vez na CPU, o processo termina seu burst sem ser interrompido. +- **Preemptivo:** se um processo de prioridade maior chegar, o atual é preemptado imediatamente. + +### Starvation e Aging + +O problema clássico do Priority Scheduling é o **starvation**: processos de baixa prioridade podem *nunca* receber a CPU se processos de alta prioridade chegam continuamente. + +A solução é o **aging**: a cada `agingInterval` ticks que um processo permanece na fila Ready, sua prioridade é incrementada em 1 (ou seja, o valor numérico diminui), até o mínimo de `1`. Com o tempo, mesmo processos de baixa prioridade chegam à cabeça da fila. + +Experimente os presets `starvation` e `aging_fix` para ver esta diferença. + +--- + +## HRRN — Highest Response Ratio Next + +**Tipo:** Não-preemptivo + +### Como funciona + +O HRRN tenta ser mais justo que o SJF levando em conta o tempo de espera do processo. A cada seleção, calcula-se o **Response Ratio** de cada processo na fila: + +``` +ratio = (waitingTime + burstTime) / burstTime +``` + +O processo com o **maior ratio** é selecionado. Isso faz com que processos que esperam há muito tempo tenham sua prioridade aumentada naturalmente, evitando starvation. + +| Situação | Efeito no ratio | +|----------|----------------| +| Processo com burst curto | ratio alto desde o início | +| Processo esperando há muito tempo | ratio cresce com o tempo | +| Processo com burst longo + pouco wait | ratio baixo | + +--- + +## Multilevel Queue + +**Tipo:** Preemptivo +**Parâmetros:** número de filas, quantum por fila + +### Como funciona + +Os processos são distribuídos em múltiplas filas com diferentes prioridades. Cada fila tem seu próprio quantum de Round Robin. Filas de maior prioridade sempre são servidas primeiro — uma fila de menor prioridade só recebe CPU se todas as filas acima dela estiverem vazias. + +Isso permite tratar diferentes classes de processos de forma distinta: por exemplo, processos interativos em filas de alta prioridade (quantum pequeno) e processos batch em filas de baixa prioridade (quantum maior). + +--- + +## Referências + +- Silberschatz, A., Galvin, P. B., & Gagne, G. — *Operating System Concepts*, 10ª edição +- Tanenbaum, A. S. — *Modern Operating Systems*, 4ª edição diff --git a/docs/metrics.md b/docs/metrics.md new file mode 100644 index 0000000..5e90959 --- /dev/null +++ b/docs/metrics.md @@ -0,0 +1,114 @@ +# 📏 Métricas + +O PSS calcula automaticamente um conjunto de métricas ao final (ou durante) a simulação. Este documento explica cada métrica, sua fórmula, o que ela representa e como lê-la nos gráficos. + +--- + +## Métricas por Processo + +Estas métricas são calculadas individualmente para cada processo simulado. + +| Métrica | Fórmula | O que significa | +|---------|---------|----------------| +| **Arrival Time** | `arrivalTime` | Tick em que o processo entrou na fila Ready pela primeira vez | +| **First CPU Tick** | `startTick` | Tick em que o processo usou a CPU pela primeira vez | +| **Finish Time** | `finishTick` | Tick em que o processo encerrou sua execução | +| **Response Time** | `startTick − arrivalTime` | Quanto tempo o processo esperou antes de receber a CPU pela primeira vez | +| **Turnaround Time** | `finishTick − arrivalTime` | Tempo total de vida do processo (chegada → término) | +| **Waiting Time** | `turnaround − cpuTime − ioTime` | Tempo total na fila Ready (sem fazer nada útil) | +| **CPU Time** | Σ ticks em estado Running | Total de CPU efetivamente consumido | +| **I/O Time** | Σ ticks em estado Waiting | Total de tempo aguardando dispositivos de I/O | +| **CPU Utilization** | `cpuTime / turnaround × 100` | Percentual do turnaround em que o processo estava de fato usando CPU | +| **Bound Type** | ver abaixo | Classificação do processo como CPU Bound, I/O Bound ou Balanced | + +### Visualização na tabela + +Na aba **Por Processo**, cada linha exibe mini barras de progresso horizontais inline ao lado dos valores numéricos. As barras são proporcionais ao **maior valor da mesma métrica entre todos os processos**, permitindo comparação visual imediata: + +| Coluna | Cor da barra | +|--------|-------------| +| Response Time | Âmbar | +| Turnaround Time | Azul | +| Waiting Time | Laranja | +| CPU Time | Esmeralda | +| I/O Time | Ciano | +| CPU Utilization | Gradiente violeta → azul | + +--- + +## Métricas Globais + +Calculadas sobre o conjunto completo de processos da simulação. + +| Métrica | Fórmula | +|---------|---------| +| **Avg Response Time** | Média dos response times de todos os processos | +| **Avg Turnaround Time** | Média dos turnaround times | +| **Avg Waiting Time** | Média dos waiting times | +| **CPU Throughput** | `processos concluídos / tempo total de simulação` | +| **CPU Utilization (global)** | `Σ ticks em Running / tempo total × 100` | +| **Total Simulation Time** | Tick do último evento de término | + +Na aba **Global**, cada card de tempo (Response, Turnaround, Waiting) exibe uma barra proporcional ao **tempo total de simulação**, indicando "quanto desse tempo total o processo médio passa nesse estado". A barra de CPU Utilization usa um gradiente âmbar → verde para indicar saúde da utilização. + +--- + +## Classificação CPU Bound vs. I/O Bound + +Ao final da simulação, cada processo é classificado automaticamente com base na proporção de tempo gasto em CPU e I/O: + +``` +cpuRatio = cpuTime / (cpuTime + ioTime) +ioRatio = ioTime / (cpuTime + ioTime) + +cpuRatio ≥ 0.65 → CPU Bound +ioRatio ≥ 0.65 → I/O Bound +caso contrário → Balanced +``` + +- **CPU Bound:** o processo passa a maior parte do tempo usando o processador. +- **I/O Bound:** o processo passa a maior parte do tempo esperando dispositivos de entrada/saída. +- **Balanced:** o processo usa CPU e I/O de forma equilibrada. + +> **Na lista de processos:** estimativa calculada sobre os bursts declarados (antes da simulação). +> **Na tabela de métricas:** classificação calculada sobre os tempos reais registrados. + +--- + +## Gráficos (aba "Gráfico") + +### Perfil Comparativo — Radar Chart + +Exibido quando há **2 ou mais processos**. Cada eixo representa uma dimensão de tempo, normalizada de 0–100% em relação ao maior valor dessa dimensão entre todos os processos: + +| Eixo | Significado | +|------|------------| +| **Turnaround** | Tempo total de vida relativo | +| **Response** | Tempo até a primeira CPU relativo | +| **Waiting** | Tempo na fila Ready relativo | +| **CPU Time** | Uso de CPU relativo | +| **I/O Time** | Uso de I/O relativo | + +Cada processo é desenhado como um polígono colorido (a cor configurada no processo). Polígonos mais "amplos" indicam processos com tempos maiores nas dimensões correspondentes. O radar é ideal para identificar **diferenças de perfil** entre processos — por exemplo, um processo CPU Bound terá o eixo CPU Time muito maior que o eixo I/O Time. + +### Breakdown por Processo — Stacked Bar Chart + +Sempre exibido. Cada barra representa um processo, com os segmentos empilhados: + +- **Âmbar** — Waiting Time +- **Verde** — CPU Time +- **Ciano** — I/O Time + +A altura total de cada barra equivale ao **Turnaround Time** do processo. + +--- + +## Exemplo + +Considere três processos com os seguintes bursts: + +| Processo | Bursts | cpuTime | ioTime | cpuRatio | Classificação | +|----------|--------|:-------:|:------:|:--------:|:-------------:| +| P1 | CPU:8, IO:1, CPU:1 | 9 | 1 | 90% | CPU Bound | +| P2 | CPU:1, IO:8, CPU:1 | 2 | 8 | 20% | I/O Bound | +| P3 | CPU:4, IO:2, CPU:4 | 8 | 2 | 80% | CPU Bound | diff --git a/src/components/MetricsPanel/GlobalMetrics.tsx b/src/components/MetricsPanel/GlobalMetrics.tsx index 7d39215..1f2899c 100644 --- a/src/components/MetricsPanel/GlobalMetrics.tsx +++ b/src/components/MetricsPanel/GlobalMetrics.tsx @@ -1,3 +1,11 @@ +import { + Clock, + Timer, + Hourglass, + Cpu, + Zap, + Activity, +} from 'lucide-react'; import type { GlobalMetrics as GlobalMetricsType } from '@core/types'; interface GlobalMetricsProps { @@ -8,14 +16,36 @@ interface MetricCardProps { label: string; value: string; sub?: string; - accent?: string; + accentColor: string; // hex or CSS color for the bar fill + accentClass: string; // tailwind text class for the value + icon: React.ReactNode; + barPct?: number; // 0–100; undefined = no bar } -const MetricCard = ({ label, value, sub, accent = 'text-blue-300' }: MetricCardProps) => ( -
- {label} - {value} +const MetricCard = ({ + label, + value, + sub, + accentColor, + accentClass, + icon, + barPct, +}: MetricCardProps) => ( +
+
+ {label} + {icon} +
+ {value} {sub && {sub}} + {barPct !== undefined && ( +
+
+
+ )}
); @@ -30,48 +60,74 @@ const GlobalMetrics = ({ metrics }: GlobalMetricsProps) => { } = metrics; const utilizationPct = Math.min(100, cpuUtilization); + // Bars for time metrics are proportional to the total simulation time + const toPct = (v: number) => + totalSimulationTime > 0 ? Math.min(100, (v / totalSimulationTime) * 100) : 0; return ( -
+
} + barPct={toPct(avgResponseTime)} /> } + barPct={toPct(avgTurnaroundTime)} /> } + barPct={toPct(avgWaitingTime)} /> } /> } /> - {/* CPU Utilization card with progress bar */} -
- CPU Utilization - {utilizationPct.toFixed(1)}% -
+ + {/* CPU Utilization — gradient bar */} +
+
+ CPU Utilization + +
+ + {utilizationPct.toFixed(1)}% + + do tempo total em CPU +
diff --git a/src/components/MetricsPanel/MetricsChart.tsx b/src/components/MetricsPanel/MetricsChart.tsx index 27bf118..39f2570 100644 --- a/src/components/MetricsPanel/MetricsChart.tsx +++ b/src/components/MetricsPanel/MetricsChart.tsx @@ -7,6 +7,10 @@ import { Legend, ResponsiveContainer, CartesianGrid, + RadarChart, + PolarGrid, + PolarAngleAxis, + Radar, } from 'recharts'; import type { ProcessMetrics, Process } from '@core/types'; @@ -15,6 +19,15 @@ interface MetricsChartProps { processMap: Record; } +const TOOLTIP_STYLE = { + backgroundColor: '#1e293b', + border: '1px solid #334155', + borderRadius: '8px', + fontSize: '11px', + color: '#e2e8f0', + boxShadow: '0 4px 12px rgba(0,0,0,0.4)', +}; + const MetricsChart = ({ metrics, processMap }: MetricsChartProps) => { if (metrics.length === 0) { return ( @@ -24,57 +37,131 @@ const MetricsChart = ({ metrics, processMap }: MetricsChartProps) => { ); } - const data = metrics.map((m) => ({ - name: processMap[m.processId]?.name ?? m.processId, - 'Waiting Time': m.waitingTime, - 'CPU Time': m.cpuTime, - 'I/O Time': m.ioTime, + const getName = (id: string) => processMap[id]?.name ?? id; + const getColor = (id: string) => processMap[id]?.color ?? '#94a3b8'; + + // ── Stacked bar data ────────────────────────────────────────── + const barData = metrics.map((m) => ({ + name: getName(m.processId), + 'Waiting': m.waitingTime, + 'CPU': m.cpuTime, + 'I/O': m.ioTime, + _color: getColor(m.processId), })); + // ── Radar chart data (normalized 0-100 per dimension) ───────── + // Each "subject" is a process, each series is a dimension. + // We invert "Response" and "Waiting" so higher = better. + const maxT = Math.max(...metrics.map(m => m.turnaroundTime), 1); + const maxR = Math.max(...metrics.map(m => m.responseTime), 1); + const maxW = Math.max(...metrics.map(m => m.waitingTime), 1); + const maxCpu = Math.max(...metrics.map(m => m.cpuTime), 1); + const maxIo = Math.max(...metrics.map(m => m.ioTime), 1); + + const radarDimensions = [ + 'Turnaround', 'Response', 'Waiting', 'CPU Time', 'I/O Time', + ] as const; + + // Radar: rows = dimensions, column per process → Recharts RadarChart needs + // one row per "subject" (dimension), with one field per process. + const radarData = radarDimensions.map(dim => { + const row: Record = { dimension: dim }; + metrics.forEach(m => { + const key = getName(m.processId); + switch (dim) { + case 'Turnaround': row[key] = (m.turnaroundTime / maxT) * 100; break; + case 'Response': row[key] = (m.responseTime / maxR) * 100; break; + case 'Waiting': row[key] = (m.waitingTime / maxW) * 100; break; + case 'CPU Time': row[key] = (m.cpuTime / maxCpu)* 100; break; + case 'I/O Time': row[key] = (m.ioTime / maxIo) * 100; break; + } + }); + return row; + }); + + const showRadar = metrics.length >= 2; + return ( -
- - - - - - - - - - - - -

- Barras empilhadas = Turnaround Time (Waiting + CPU + I/O) -

+
+ {/* ── Radar chart ───────────────────────────────────────── */} + {showRadar && ( +
+

+ Perfil Comparativo (normalizado) +

+ + + + + {metrics.map((m) => ( + + ))} + [typeof value === 'number' ? `${value.toFixed(1)}%` : '-', '']} + /> + + + +
+ )} + + {/* ── Stacked bar chart ─────────────────────────────────── */} +
+

+ Breakdown por Processo (ticks) +

+ + + + + + + + + + + + +

+ Waiting + CPU + I/O = Turnaround +

+
); }; diff --git a/src/components/MetricsPanel/MetricsPanel.tsx b/src/components/MetricsPanel/MetricsPanel.tsx index 8a6c7fb..940a826 100644 --- a/src/components/MetricsPanel/MetricsPanel.tsx +++ b/src/components/MetricsPanel/MetricsPanel.tsx @@ -46,7 +46,7 @@ const MetricsPanel = () => { const canExportCSV = status === 'finished' && processMetrics.length > 0; return ( -
+
{/* Panel header with tabs + export button */}
{TAB_CONFIG.map(tab => ( diff --git a/src/components/MetricsPanel/MetricsTable.tsx b/src/components/MetricsPanel/MetricsTable.tsx index 2d9ee13..58cc1d0 100644 --- a/src/components/MetricsPanel/MetricsTable.tsx +++ b/src/components/MetricsPanel/MetricsTable.tsx @@ -12,6 +12,26 @@ function boundVariant(type: ProcessMetrics['boundType']) { return 'balanced'; } +// Inline mini bar rendered behind the numeric value +interface MiniBarProps { + value: number; + max: number; + color: string; // tailwind bg class +} + +const MiniBar = ({ value, max, color }: MiniBarProps) => { + const pct = max > 0 ? (value / max) * 100 : 0; + return ( +
+
+ {value} +
+ ); +}; + const MetricsTable = ({ metrics, processMap }: MetricsTableProps) => { if (metrics.length === 0) { return ( @@ -21,34 +41,42 @@ const MetricsTable = ({ metrics, processMap }: MetricsTableProps) => { ); } + // Pre-compute maximums for proportional bars + const maxResponse = Math.max(...metrics.map(m => m.responseTime), 1); + const maxTurnaround = Math.max(...metrics.map(m => m.turnaroundTime), 1); + const maxWaiting = Math.max(...metrics.map(m => m.waitingTime), 1); + const maxCpu = Math.max(...metrics.map(m => m.cpuTime), 1); + const maxIo = Math.max(...metrics.map(m => m.ioTime), 1); + return (
- +
- + - - - - - + + + + + {metrics.map((m, i) => { - const proc = processMap[m.processId]; - const name = proc?.name ?? m.processId; + const proc = processMap[m.processId]; + const name = proc?.name ?? m.processId; const color = proc?.color ?? '#94a3b8'; return ( + {/* Process name */} - - - - - - - - - + + {/* Plain timestamps */} + + + + + {/* Cells with inline mini-bars */} + + + + + + + {/* CPU utilization with a tiny arc/bar */} + + + {/* Bound badge */} diff --git a/src/core/__tests__/engine.test.ts b/src/core/__tests__/engine.test.ts index b551b97..f35625a 100644 --- a/src/core/__tests__/engine.test.ts +++ b/src/core/__tests__/engine.test.ts @@ -193,10 +193,11 @@ describe("CT-04: SRTF Preemption", () => { }); // ============================================================ -// General engine behaviour +// SimulationEngine — ciclo de vida +// Tests: isFinished, step() guard, runAll vs step equivalence // ============================================================ -describe("SimulationEngine general", () => { +describe("SimulationEngine — ciclo de vida", () => { it("isFinished returns false mid-run, true after runAll", () => { const engine = new SimulationEngine( [p("p1", 0, [{ type: "cpu", duration: 3 }])], @@ -232,22 +233,14 @@ describe("SimulationEngine general", () => { expect(fromRunAll).toEqual(fromStep); }); +}); - it("process with I/O: transitions through Waiting state", () => { - const engine = new SimulationEngine( - [p("p1", 0, [{ type: "cpu", duration: 2 }, { type: "io", duration: 2 }, { type: "cpu", duration: 1 }])], - cfg(), - ); - const ticks = engine.runAll(); - const states = ticks.map((t) => t.states["p1"]); - // Running for 2, Waiting for 2, Running for 1 - expect(states[0]).toBe("Running"); - expect(states[1]).toBe("Running"); - expect(states[2]).toBe("Waiting"); - expect(states[3]).toBe("Waiting"); - expect(states[4]).toBe("Running"); - }); +// ============================================================ +// SimulationEngine — chegada de processos +// Tests: late arrivals, idle ticks while waiting for process +// ============================================================ +describe("SimulationEngine — chegada de processos", () => { it("process arriving after simulation starts is correctly scheduled", () => { const engine = new SimulationEngine( [ @@ -261,7 +254,14 @@ describe("SimulationEngine general", () => { expect(ticks[4]?.cpuProcess).toBeNull(); // tick 4: idle expect(ticks[5]?.cpuProcess).toBe("p2"); }); +}); +// ============================================================ +// SimulationEngine — troca de contexto +// Tests: idle ticks during switch, ctxSwitchForProcess across overhead +// ============================================================ + +describe("SimulationEngine — troca de contexto", () => { it("context switch inserts idle ticks between processes", () => { const engine = new SimulationEngine( [ @@ -298,3 +298,25 @@ describe("SimulationEngine general", () => { expect(ticks[4]?.ctxSwitchForProcess).toBeNull(); }); }); + +// ============================================================ +// SimulationEngine — I/O +// Tests: Waiting state, IO burst transitions +// ============================================================ + +describe("SimulationEngine — I/O", () => { + it("process with I/O: transitions through Waiting state", () => { + const engine = new SimulationEngine( + [p("p1", 0, [{ type: "cpu", duration: 2 }, { type: "io", duration: 2 }, { type: "cpu", duration: 1 }])], + cfg(), + ); + const ticks = engine.runAll(); + const states = ticks.map((t) => t.states["p1"]); + // Running for 2, Waiting for 2, Running for 1 + expect(states[0]).toBe("Running"); + expect(states[1]).toBe("Running"); + expect(states[2]).toBe("Waiting"); + expect(states[3]).toBe("Waiting"); + expect(states[4]).toBe("Running"); + }); +}); diff --git a/src/core/__tests__/fcfs.test.ts b/src/core/__tests__/fcfs.test.ts index 7799993..3a93203 100644 --- a/src/core/__tests__/fcfs.test.ts +++ b/src/core/__tests__/fcfs.test.ts @@ -1,7 +1,9 @@ import { describe, it, expect } from "vitest"; -import { fcfs } from "../algorithms/fcfs"; +import { FCFSAlgorithm } from "../algorithms/fcfs"; import type { ProcessRuntime } from "../types"; +const fcfs = new FCFSAlgorithm(); + function makeRt(id: string, arrivalTick: number): ProcessRuntime { return { processId: id, state: "Ready", remainingBurst: 4, burstIndex: 0, @@ -11,37 +13,33 @@ function makeRt(id: string, arrivalTick: number): ProcessRuntime { }; } -const config = { algorithm: "FCFS" as const, quantum: 2, contextSwitchTime: 0, isPreemptive: false, agingEnabled: false, agingInterval: 5 }; - describe("FCFS select()", () => { it("returns null for empty queue", () => { - expect(fcfs.select([], config)).toBeNull(); + expect(fcfs.select([])).toBeNull(); }); it("returns single process", () => { const rt = makeRt("p1", 0); - expect(fcfs.select([rt], config)).toBe(rt); + expect(fcfs.select([rt])).toBe(rt); }); it("returns earliest-arriving process", () => { const r1 = makeRt("p1", 5); const r2 = makeRt("p2", 2); const r3 = makeRt("p3", 8); - expect(fcfs.select([r1, r2, r3], config)?.processId).toBe("p2"); + expect(fcfs.select([r1, r2, r3])?.processId).toBe("p2"); }); it("tiebreaks by processId lexicographic", () => { const r1 = makeRt("p2", 0); const r2 = makeRt("p1", 0); - expect(fcfs.select([r1, r2], config)?.processId).toBe("p1"); + expect(fcfs.select([r1, r2])?.processId).toBe("p1"); }); }); describe("FCFS shouldPreempt()", () => { it("always returns false", () => { - const a = makeRt("p1", 0); - const b = makeRt("p2", 0); - expect(fcfs.shouldPreempt(a, b, config)).toBe(false); + expect(fcfs.shouldPreempt()).toBe(false); }); }); @@ -49,6 +47,14 @@ describe("FCFS isQuantumExpired()", () => { it("always returns false", () => { const rt = makeRt("p1", 0); rt.quantumRemaining = 0; - expect(fcfs.isQuantumExpired(rt, config)).toBe(false); + expect(fcfs.isQuantumExpired()).toBe(false); + }); +}); + +describe("FCFSAlgorithm — metadados", () => { + it("expõe propriedades educacionais corretas", () => { + expect(fcfs.name).toContain("First Come"); + expect(fcfs.isPreemptiveCapable).toBe(false); + expect(fcfs.usesQuantum).toBe(false); }); }); diff --git a/src/core/__tests__/hrrn.test.ts b/src/core/__tests__/hrrn.test.ts index bcf221b..822a4a1 100644 --- a/src/core/__tests__/hrrn.test.ts +++ b/src/core/__tests__/hrrn.test.ts @@ -1,8 +1,10 @@ import { describe, it, expect } from "vitest"; -import { hrrn } from "../algorithms/hrrn"; +import { HRRNAlgorithm } from "../algorithms/hrrn"; import { SimulationEngine } from "../engine"; import type { Process, ProcessRuntime, SchedulerConfig } from "../types"; +const hrrn = new HRRNAlgorithm(); + function makeRt(id: string, remainingBurst: number, waitingTime: number, arrivalTick = 0): ProcessRuntime { return { processId: id, state: "Ready", remainingBurst, burstIndex: 0, @@ -24,7 +26,7 @@ const p = (id: string, arrival: number, cpuDuration: number): Process => ({ describe("HRRN select()", () => { it("returns null for empty queue", () => { - expect(hrrn.select([], config)).toBeNull(); + expect(hrrn.select([])).toBeNull(); }); it("picks process with highest response ratio", () => { @@ -35,20 +37,18 @@ describe("HRRN select()", () => { const r1 = makeRt("p1", 8, 0); const r2 = makeRt("p2", 3, 6); const r3 = makeRt("p3", 5, 2); - expect(hrrn.select([r1, r2, r3], config)?.processId).toBe("p2"); + expect(hrrn.select([r1, r2, r3])?.processId).toBe("p2"); }); it("tiebreaks equal ratio by arrival then id", () => { // Both ratio = (4 + 4) / 4 = 2.0 const r1 = makeRt("p2", 4, 4, 1); // arrives at 1 const r2 = makeRt("p1", 4, 4, 2); // arrives at 2 - expect(hrrn.select([r1, r2], config)?.processId).toBe("p2"); + expect(hrrn.select([r1, r2])?.processId).toBe("p2"); }); it("shouldPreempt always false", () => { - const a = makeRt("p1", 4, 0); - const b = makeRt("p2", 4, 10); - expect(hrrn.shouldPreempt(a, b, config)).toBe(false); + expect(hrrn.shouldPreempt()).toBe(false); }); }); @@ -81,3 +81,11 @@ describe("HRRN integration", () => { expect(ticks[3]?.cpuProcess).toBe("p3"); }); }); + +describe("HRRNAlgorithm — metadados", () => { + it("expõe propriedades educacionais corretas", () => { + expect(hrrn.name).toContain("HRRN"); + expect(hrrn.isPreemptiveCapable).toBe(false); + expect(hrrn.usesQuantum).toBe(false); + }); +}); diff --git a/src/core/__tests__/multilevel.test.ts b/src/core/__tests__/multilevel.test.ts index 8ec1d41..ea03c09 100644 --- a/src/core/__tests__/multilevel.test.ts +++ b/src/core/__tests__/multilevel.test.ts @@ -1,8 +1,10 @@ import { describe, it, expect } from "vitest"; -import { multilevel } from "../algorithms/multilevel"; +import { MultilevelQueueAlgorithm } from "../algorithms/multilevel"; import { SimulationEngine } from "../engine"; import type { MlqQueueDef, Process, ProcessRuntime, SchedulerConfig } from "../types"; +const multilevel = new MultilevelQueueAlgorithm(); + // ---------------------------------------------------------------- // Helpers // ---------------------------------------------------------------- @@ -232,3 +234,11 @@ describe("MULTILEVEL integration — strict queue ordering", () => { expect(rts.find((r) => r.processId === "p3")!.cpuTime).toBe(3); }); }); + +describe("MultilevelQueueAlgorithm — metadados", () => { + it("expõe propriedades educacionais corretas", () => { + expect(multilevel.name).toContain("Multilevel"); + expect(multilevel.isPreemptiveCapable).toBe(true); + expect(multilevel.usesQuantum).toBe(true); + }); +}); diff --git a/src/core/__tests__/priority.test.ts b/src/core/__tests__/priority.test.ts index 62bd8d7..dd95d83 100644 --- a/src/core/__tests__/priority.test.ts +++ b/src/core/__tests__/priority.test.ts @@ -1,8 +1,11 @@ import { describe, it, expect } from "vitest"; -import { priorityNp, priorityP } from "../algorithms/priority"; +import { PriorityNonPreemptiveAlgorithm, PriorityPreemptiveAlgorithm } from "../algorithms/priority"; import { SimulationEngine } from "../engine"; import type { Process, ProcessRuntime, SchedulerConfig } from "../types"; +const priorityNp = new PriorityNonPreemptiveAlgorithm(); +const priorityP = new PriorityPreemptiveAlgorithm(); + function makeRt(id: string, priority: number, arrivalTick = 0): ProcessRuntime { return { processId: id, state: "Ready", remainingBurst: 4, burstIndex: 0, @@ -16,10 +19,6 @@ const cfgNp: SchedulerConfig = { algorithm: "PRIORITY_NP", quantum: 2, contextSwitchTime: 0, isPreemptive: false, agingEnabled: false, agingInterval: 5, }; -const cfgP: SchedulerConfig = { - algorithm: "PRIORITY_P", quantum: 2, contextSwitchTime: 0, - isPreemptive: true, agingEnabled: false, agingInterval: 5, -}; const p = (id: string, arrival: number, priority: number, cpuDuration: number): Process => ({ id, name: id, arrivalTime: arrival, priority, color: "#fff", @@ -31,19 +30,17 @@ describe("PRIORITY_NP select()", () => { const r1 = makeRt("p1", 5); const r2 = makeRt("p2", 2); const r3 = makeRt("p3", 8); - expect(priorityNp.select([r1, r2, r3], cfgNp)?.processId).toBe("p2"); + expect(priorityNp.select([r1, r2, r3])?.processId).toBe("p2"); }); it("tiebreaks by arrival then id", () => { const r1 = makeRt("p2", 3, 2); const r2 = makeRt("p1", 3, 5); // p1 arrives later - expect(priorityNp.select([r1, r2], cfgNp)?.processId).toBe("p2"); + expect(priorityNp.select([r1, r2])?.processId).toBe("p2"); }); it("shouldPreempt always false", () => { - const a = makeRt("p1", 5); - const b = makeRt("p2", 1); - expect(priorityNp.shouldPreempt(a, b, cfgNp)).toBe(false); + expect(priorityNp.shouldPreempt()).toBe(false); }); }); @@ -51,19 +48,19 @@ describe("PRIORITY_P shouldPreempt()", () => { it("preempts when candidate has higher priority (lower number)", () => { const current = makeRt("p1", 5); const candidate = makeRt("p2", 2); - expect(priorityP.shouldPreempt(current, candidate, cfgP)).toBe(true); + expect(priorityP.shouldPreempt(current, candidate)).toBe(true); }); it("does NOT preempt on equal priority", () => { const current = makeRt("p1", 3); const candidate = makeRt("p2", 3); - expect(priorityP.shouldPreempt(current, candidate, cfgP)).toBe(false); + expect(priorityP.shouldPreempt(current, candidate)).toBe(false); }); it("does NOT preempt when current has higher priority", () => { const current = makeRt("p1", 2); const candidate = makeRt("p2", 5); - expect(priorityP.shouldPreempt(current, candidate, cfgP)).toBe(false); + expect(priorityP.shouldPreempt(current, candidate)).toBe(false); }); }); @@ -116,3 +113,14 @@ describe("Priority P + aging", () => { expect(rt.currentPriority).toBeGreaterThanOrEqual(1); }); }); + +describe("Priority — metadados", () => { + it("PriorityNonPreemptiveAlgorithm: isPreemptiveCapable=false", () => { + expect(priorityNp.isPreemptiveCapable).toBe(false); + expect(priorityNp.usesQuantum).toBe(false); + }); + it("PriorityPreemptiveAlgorithm: isPreemptiveCapable=true", () => { + expect(priorityP.isPreemptiveCapable).toBe(true); + expect(priorityP.usesQuantum).toBe(false); + }); +}); diff --git a/src/core/__tests__/priority_rr.test.ts b/src/core/__tests__/priority_rr.test.ts index 3f4f2fb..55c1d84 100644 --- a/src/core/__tests__/priority_rr.test.ts +++ b/src/core/__tests__/priority_rr.test.ts @@ -1,8 +1,10 @@ import { describe, it, expect } from "vitest"; -import { priorityRr } from "../algorithms/priority_rr"; +import { PriorityRoundRobinAlgorithm } from "../algorithms/priority_rr"; import { SimulationEngine } from "../engine"; import type { Process, ProcessRuntime, SchedulerConfig } from "../types"; +const priorityRr = new PriorityRoundRobinAlgorithm(); + // ---------------------------------------------------------------- // Helpers // ---------------------------------------------------------------- @@ -58,22 +60,22 @@ const p = ( describe("PRIORITY_RR select()", () => { it("returns null for empty queue", () => { - expect(priorityRr.select([], cfg())).toBeNull(); + expect(priorityRr.select([])).toBeNull(); }); it("picks the process with the lowest priority number (highest priority)", () => { const high = makeRt("high", 1); const low = makeRt("low", 5); - expect(priorityRr.select([low, high], cfg())?.processId).toBe("high"); - expect(priorityRr.select([high, low], cfg())?.processId).toBe("high"); + expect(priorityRr.select([low, high])?.processId).toBe("high"); + expect(priorityRr.select([high, low])?.processId).toBe("high"); }); it("picks the FIRST in insertion order when priorities are equal (FIFO = RR)", () => { const a = makeRt("a", 3); const b = makeRt("b", 3); const c = makeRt("c", 3); - expect(priorityRr.select([a, b, c], cfg())?.processId).toBe("a"); - expect(priorityRr.select([b, c, a], cfg())?.processId).toBe("b"); + expect(priorityRr.select([a, b, c])?.processId).toBe("a"); + expect(priorityRr.select([b, c, a])?.processId).toBe("b"); }); it("ignores lower-priority processes when a higher-priority one is present", () => { @@ -81,7 +83,7 @@ describe("PRIORITY_RR select()", () => { const normal1 = makeRt("n1", 5); const normal2 = makeRt("n2", 5); expect( - priorityRr.select([normal1, normal2, critical], cfg())?.processId, + priorityRr.select([normal1, normal2, critical])?.processId, ).toBe("critical"); }); }); @@ -94,19 +96,19 @@ describe("PRIORITY_RR shouldPreempt()", () => { it("preempts when candidate has strictly higher priority", () => { const running = makeRt("current", 5); const higher = makeRt("higher", 1); - expect(priorityRr.shouldPreempt(running, higher, cfg())).toBe(true); + expect(priorityRr.shouldPreempt(running, higher)).toBe(true); }); it("does NOT preempt for same priority (quantum handles rotation)", () => { const a = makeRt("a", 3); const b = makeRt("b", 3); - expect(priorityRr.shouldPreempt(a, b, cfg())).toBe(false); + expect(priorityRr.shouldPreempt(a, b)).toBe(false); }); it("does NOT preempt when candidate has lower priority", () => { const running = makeRt("current", 1); const lower = makeRt("lower", 5); - expect(priorityRr.shouldPreempt(running, lower, cfg())).toBe(false); + expect(priorityRr.shouldPreempt(running, lower)).toBe(false); }); }); @@ -116,13 +118,13 @@ describe("PRIORITY_RR shouldPreempt()", () => { describe("PRIORITY_RR isQuantumExpired()", () => { it("returns true when quantumRemaining <= 0", () => { - expect(priorityRr.isQuantumExpired(makeRt("p", 5, 0), cfg())).toBe(true); - expect(priorityRr.isQuantumExpired(makeRt("p", 5, -1), cfg())).toBe(true); + expect(priorityRr.isQuantumExpired(makeRt("p", 5, 0))).toBe(true); + expect(priorityRr.isQuantumExpired(makeRt("p", 5, -1))).toBe(true); }); it("returns false when quantum is not exhausted", () => { - expect(priorityRr.isQuantumExpired(makeRt("p", 5, 1), cfg())).toBe(false); - expect(priorityRr.isQuantumExpired(makeRt("p", 5, 2), cfg())).toBe(false); + expect(priorityRr.isQuantumExpired(makeRt("p", 5, 1))).toBe(false); + expect(priorityRr.isQuantumExpired(makeRt("p", 5, 2))).toBe(false); }); }); @@ -246,3 +248,11 @@ describe("PRIORITY_RR integration — combined preemption + RR", () => { expect(rts.find((r) => r.processId === "p3")!.cpuTime).toBe(2); }); }); + +describe("PriorityRoundRobinAlgorithm — metadados", () => { + it("expõe propriedades educacionais corretas", () => { + expect(priorityRr.name).toContain("Priority Round Robin"); + expect(priorityRr.isPreemptiveCapable).toBe(true); + expect(priorityRr.usesQuantum).toBe(true); + }); +}); diff --git a/src/core/__tests__/rr.test.ts b/src/core/__tests__/rr.test.ts index 77e0091..7d38750 100644 --- a/src/core/__tests__/rr.test.ts +++ b/src/core/__tests__/rr.test.ts @@ -1,8 +1,10 @@ import { describe, it, expect } from "vitest"; -import { rr } from "../algorithms/rr"; +import { RoundRobinAlgorithm } from "../algorithms/rr"; import { SimulationEngine } from "../engine"; import type { Process, ProcessRuntime, SchedulerConfig } from "../types"; +const rr = new RoundRobinAlgorithm(); + function makeRt(id: string, quantumRemaining = 2): ProcessRuntime { return { processId: id, state: "Running", remainingBurst: 4, burstIndex: 0, @@ -26,32 +28,30 @@ describe("RR select()", () => { it("returns front of queue (FIFO)", () => { const r1: ProcessRuntime = { ...makeRt("p1"), state: "Ready" }; const r2: ProcessRuntime = { ...makeRt("p2"), state: "Ready" }; - expect(rr.select([r1, r2], config)?.processId).toBe("p1"); - expect(rr.select([r2, r1], config)?.processId).toBe("p2"); + expect(rr.select([r1, r2])?.processId).toBe("p1"); + expect(rr.select([r2, r1])?.processId).toBe("p2"); }); it("returns null for empty queue", () => { - expect(rr.select([], config)).toBeNull(); + expect(rr.select([])).toBeNull(); }); }); describe("RR isQuantumExpired()", () => { it("returns true when quantumRemaining <= 0", () => { - expect(rr.isQuantumExpired(makeRt("p1", 0), config)).toBe(true); - expect(rr.isQuantumExpired(makeRt("p1", -1), config)).toBe(true); + expect(rr.isQuantumExpired(makeRt("p1", 0))).toBe(true); + expect(rr.isQuantumExpired(makeRt("p1", -1))).toBe(true); }); it("returns false when quantum not yet exhausted", () => { - expect(rr.isQuantumExpired(makeRt("p1", 1), config)).toBe(false); - expect(rr.isQuantumExpired(makeRt("p1", 2), config)).toBe(false); + expect(rr.isQuantumExpired(makeRt("p1", 1))).toBe(false); + expect(rr.isQuantumExpired(makeRt("p1", 2))).toBe(false); }); }); describe("RR shouldPreempt()", () => { it("always returns false (preemption via quantum expiry only)", () => { - const a = makeRt("p1"); - const b = makeRt("p2"); - expect(rr.shouldPreempt(a, b, config)).toBe(false); + expect(rr.shouldPreempt()).toBe(false); }); }); @@ -114,3 +114,11 @@ describe("CT-02: Round Robin integration", () => { void ticks; // used for debugging if needed }); }); + +describe("RoundRobinAlgorithm — metadados", () => { + it("expõe propriedades educacionais corretas", () => { + expect(rr.name).toContain("Round Robin"); + expect(rr.isPreemptiveCapable).toBe(false); + expect(rr.usesQuantum).toBe(true); + }); +}); diff --git a/src/core/__tests__/sjf.test.ts b/src/core/__tests__/sjf.test.ts index 012f1f9..ebb2173 100644 --- a/src/core/__tests__/sjf.test.ts +++ b/src/core/__tests__/sjf.test.ts @@ -1,8 +1,13 @@ import { describe, it, expect } from "vitest"; -import { sjfNp, srtf } from "../algorithms/sjf"; +import { SJFNonPreemptiveAlgorithm, SRTFAlgorithm } from "../algorithms/sjf"; import { SimulationEngine } from "../engine"; import type { Process, ProcessRuntime, SchedulerConfig } from "../types"; +const sjfNp = new SJFNonPreemptiveAlgorithm(); +const srtf = new SRTFAlgorithm(); + + + function makeRt(id: string, remainingBurst: number, arrivalTick = 0): ProcessRuntime { return { processId: id, state: "Ready", remainingBurst, burstIndex: 0, @@ -23,27 +28,25 @@ const p = (id: string, arrival: number, bursts: { type: "cpu" | "io"; duration: describe("SJF_NP select()", () => { it("returns null for empty queue", () => { - expect(sjfNp.select([], config)).toBeNull(); + expect(sjfNp.select([])).toBeNull(); }); it("picks shortest remaining burst", () => { const r1 = makeRt("p1", 8); const r2 = makeRt("p2", 3); const r3 = makeRt("p3", 5); - expect(sjfNp.select([r1, r2, r3], config)?.processId).toBe("p2"); + expect(sjfNp.select([r1, r2, r3])?.processId).toBe("p2"); }); it("tiebreaks equal burst by arrival then id", () => { const r1 = makeRt("p2", 4, 1); const r2 = makeRt("p1", 4, 2); // p2 arrives earlier - expect(sjfNp.select([r1, r2], config)?.processId).toBe("p2"); + expect(sjfNp.select([r1, r2])?.processId).toBe("p2"); }); it("shouldPreempt always false", () => { - const a = makeRt("p1", 4); - const b = makeRt("p2", 2); - expect(sjfNp.shouldPreempt(a, b, config)).toBe(false); + expect(sjfNp.shouldPreempt()).toBe(false); }); }); @@ -51,19 +54,19 @@ describe("SRTF shouldPreempt()", () => { it("preempts when candidate has strictly smaller burst", () => { const current = makeRt("p1", 7); const candidate = makeRt("p2", 4); - expect(srtf.shouldPreempt(current, candidate, config)).toBe(true); + expect(srtf.shouldPreempt(current, candidate)).toBe(true); }); it("does NOT preempt on equal remaining burst", () => { const current = makeRt("p1", 4); const candidate = makeRt("p2", 4); - expect(srtf.shouldPreempt(current, candidate, config)).toBe(false); + expect(srtf.shouldPreempt(current, candidate)).toBe(false); }); it("does NOT preempt when current has smaller burst", () => { const current = makeRt("p1", 3); const candidate = makeRt("p2", 6); - expect(srtf.shouldPreempt(current, candidate, config)).toBe(false); + expect(srtf.shouldPreempt(current, candidate)).toBe(false); }); }); @@ -92,3 +95,16 @@ describe("CT-04: SRTF Preemption (integration)", () => { expect(rt1.finishTick).toBe(12); }); }); + +describe("SJF/SRTF — metadados", () => { + it("SJFNonPreemptiveAlgorithm: isPreemptiveCapable=false, usesQuantum=false", () => { + expect(sjfNp.isPreemptiveCapable).toBe(false); + expect(sjfNp.usesQuantum).toBe(false); + expect(sjfNp.name).toContain("Shortest Job First"); + }); + it("SRTFAlgorithm: isPreemptiveCapable=true, usesQuantum=false", () => { + expect(srtf.isPreemptiveCapable).toBe(true); + expect(srtf.usesQuantum).toBe(false); + expect(srtf.name).toContain("SRTF"); + }); +}); diff --git a/src/core/algorithms/base.ts b/src/core/algorithms/base.ts new file mode 100644 index 0000000..4064cbe --- /dev/null +++ b/src/core/algorithms/base.ts @@ -0,0 +1,69 @@ +import type { ProcessRuntime, SchedulerConfig } from "../types"; +import { tiebreak as _tiebreak } from "./utils"; + +/** + * BaseAlgorithm — Classe abstrata para algoritmos de escalonamento. + * + * Cada algoritmo concreto deve implementar: + * - Propriedades educacionais (name, description, isPreemptiveCapable, usesQuantum) + * - Os três métodos do contrato de escalonamento (select, shouldPreempt, isQuantumExpired) + * + * O método estático `tiebreak` é disponibilizado a todas as subclasses como + * critério de desempate padrão: chegada mais cedo → ID lexicográfico menor. + */ +export abstract class BaseAlgorithm { + /** Nome completo do algoritmo para exibição. */ + abstract readonly name: string; + + /** Breve descrição educacional do comportamento do algoritmo. */ + abstract readonly description: string; + + /** Indica se o algoritmo suporta preempção por candidato mais prioritário. */ + abstract readonly isPreemptiveCapable: boolean; + + /** Indica se o algoritmo usa fatia de tempo (quantum). */ + abstract readonly usesQuantum: boolean; + + // ─── Contrato de escalonamento ─────────────────────────────────────────── + + /** + * Seleciona o próximo processo a executar a partir da fila de prontos. + * Não deve modificar o array — o engine remove o processo selecionado. + * Retorna null se a fila estiver vazia. + */ + abstract select( + readyQueue: readonly ProcessRuntime[], + config: SchedulerConfig, + ): ProcessRuntime | null; + + /** + * Para algoritmos preemptivos: indica se o processo em execução deve ser + * interrompido em favor do candidato selecionado. + * Chamado apenas quando há um processo rodando e um candidato disponível. + */ + abstract shouldPreempt( + current: ProcessRuntime, + candidate: ProcessRuntime, + config: SchedulerConfig, + ): boolean; + + /** + * Para Round Robin: indica se o quantum do processo expirou. + * Todos os outros algoritmos devem retornar false. + */ + abstract isQuantumExpired( + runtime: ProcessRuntime, + config: SchedulerConfig, + ): boolean; + + // ─── Helper compartilhado ──────────────────────────────────────────────── + + /** + * Critério de desempate padrão: chegada mais cedo (arrivalTick ASC), + * depois ID do processo em ordem lexicográfica. + * Disponível a todas as subclasses como método estático herdado. + */ + public static tiebreak(a: ProcessRuntime, b: ProcessRuntime): number { + return _tiebreak(a, b); + } +} diff --git a/src/core/algorithms/fcfs.ts b/src/core/algorithms/fcfs.ts index a88e0f7..e2f26a7 100644 --- a/src/core/algorithms/fcfs.ts +++ b/src/core/algorithms/fcfs.ts @@ -1,21 +1,32 @@ -import type { SchedulerAlgorithm } from "./index"; -import { tiebreak } from "./utils"; +import type { ProcessRuntime } from "../types"; +import { BaseAlgorithm } from "./base"; /** - * FCFS — First Come, First Served - * Non-preemptive. Selects the process with earliest arrival; tiebreak by id. + * FCFS — First Come, First Served (Primeiro a Chegar, Primeiro a Ser Servido) + * + * Algoritmo não-preemptivo mais simples: processa os jobs na ordem exata + * de chegada. Fácil de implementar, mas pode causar o "efeito comboio" + * (convoy effect), onde processos curtos ficam presos atrás de um longo. */ -export const fcfs: SchedulerAlgorithm = { - select(readyQueue) { +export class FCFSAlgorithm extends BaseAlgorithm { + readonly name = "First Come, First Served (FCFS)"; + readonly description = + "Não-preemptivo. Despacha processos na ordem de chegada. " + + "Simples, mas pode causar o efeito comboio: processos curtos " + + "aguardam atrás de um processo longo."; + readonly isPreemptiveCapable = false; + readonly usesQuantum = false; + + select(readyQueue: readonly ProcessRuntime[]): ProcessRuntime | null { if (readyQueue.length === 0) return null; - return [...readyQueue].sort(tiebreak)[0] ?? null; - }, + return [...readyQueue].sort(FCFSAlgorithm.tiebreak)[0] ?? null; + } - shouldPreempt() { + shouldPreempt(): boolean { return false; - }, + } - isQuantumExpired() { + isQuantumExpired(): boolean { return false; - }, -}; + } +} diff --git a/src/core/algorithms/hrrn.ts b/src/core/algorithms/hrrn.ts index 1a3f30b..267322b 100644 --- a/src/core/algorithms/hrrn.ts +++ b/src/core/algorithms/hrrn.ts @@ -1,33 +1,52 @@ import type { ProcessRuntime } from "../types"; -import type { SchedulerAlgorithm } from "./index"; -import { tiebreak } from "./utils"; - -function hrrnRatio(rt: ProcessRuntime): number { - // ratio = (waitingTime + burstTime) / burstTime - // remainingBurst > 0 is guaranteed for processes in the ready queue - if (rt.remainingBurst <= 0) return Infinity; - return (rt.waitingTime + rt.remainingBurst) / rt.remainingBurst; -} +import { BaseAlgorithm } from "./base"; /** * HRRN — Highest Response Ratio Next - * Non-preemptive. Balances waiting time and burst length to prevent starvation. + * + * Algoritmo não-preemptivo que equilibra o tempo de burst e o tempo de espera + * para evitar inanição. A cada decisão de escalonamento, calcula a razão de + * resposta de cada processo na fila e despacha o de maior razão. + * + * Fórmula: ratio = (waitingTime + remainingBurst) / remainingBurst + * + * - Processos longos que esperam muito aumentam sua razão, evitando inanição. + * - Processos curtos naturalmente têm razão alta (denominador pequeno). + * - Oferece um balanço entre SJF (favorece curtos) e FCFS (favorece chegada). */ -export const hrrn: SchedulerAlgorithm = { - select(readyQueue) { +export class HRRNAlgorithm extends BaseAlgorithm { + readonly name = "Highest Response Ratio Next (HRRN)"; + readonly description = + "Não-preemptivo. Despacha o processo com maior razão de resposta: " + + "(espera + burst) / burst. Equilibra duração e tempo de espera, " + + "prevenindo inanição sem precisar de aging explícito."; + readonly isPreemptiveCapable = false; + readonly usesQuantum = false; + + /** + * Calcula a razão de resposta de um processo. + * Retorna Infinity se remainingBurst <= 0 (não deve ocorrer na fila de prontos, + * mas garante ordenação segura em casos de borda). + */ + private static hrrnRatio(rt: ProcessRuntime): number { + if (rt.remainingBurst <= 0) return Infinity; + return (rt.waitingTime + rt.remainingBurst) / rt.remainingBurst; + } + + select(readyQueue: readonly ProcessRuntime[]): ProcessRuntime | null { if (readyQueue.length === 0) return null; return [...readyQueue].sort((a, b) => { - const ratioDiff = hrrnRatio(b) - hrrnRatio(a); // descending + const ratioDiff = HRRNAlgorithm.hrrnRatio(b) - HRRNAlgorithm.hrrnRatio(a); // descendente if (ratioDiff !== 0) return ratioDiff; - return tiebreak(a, b); + return HRRNAlgorithm.tiebreak(a, b); })[0] ?? null; - }, + } - shouldPreempt() { + shouldPreempt(): boolean { return false; - }, + } - isQuantumExpired() { + isQuantumExpired(): boolean { return false; - }, -}; + } +} diff --git a/src/core/algorithms/index.ts b/src/core/algorithms/index.ts index ab7b801..eaa6b2e 100644 --- a/src/core/algorithms/index.ts +++ b/src/core/algorithms/index.ts @@ -1,61 +1,31 @@ -import type { ProcessRuntime, SchedulerConfig, SchedulingAlgorithm } from "../types"; -import { fcfs } from "./fcfs"; -import { sjfNp, srtf } from "./sjf"; -import { rr } from "./rr"; -import { priorityNp, priorityP } from "./priority"; -import { priorityRr } from "./priority_rr"; -import { hrrn } from "./hrrn"; -import { multilevel } from "./multilevel"; +import type { SchedulingAlgorithm } from "../types"; +import { BaseAlgorithm } from "./base"; +import { FCFSAlgorithm } from "./fcfs"; +import { SJFNonPreemptiveAlgorithm, SRTFAlgorithm } from "./sjf"; +import { RoundRobinAlgorithm } from "./rr"; +import { PriorityNonPreemptiveAlgorithm, PriorityPreemptiveAlgorithm } from "./priority"; +import { PriorityRoundRobinAlgorithm } from "./priority_rr"; +import { HRRNAlgorithm } from "./hrrn"; +import { MultilevelQueueAlgorithm } from "./multilevel"; + +export type { BaseAlgorithm }; // ============================================================ -// SchedulerAlgorithm interface -// All implementations must be pure (no mutation of the queue). +// Tabela de despacho — o engine busca o algoritmo pelo nome // ============================================================ +// +// As instâncias são criadas uma única vez no carregamento do módulo. +// Isso é seguro pois todas as classes são stateless: todo estado mutável +// da simulação vive em ProcessRuntime, gerenciado pelo SimulationEngine. -export interface SchedulerAlgorithm { - /** - * Select the best process to dispatch from the ready queue. - * Returns null if the queue is empty. - * Must NOT mutate the array; the engine handles removal. - */ - select( - readyQueue: readonly ProcessRuntime[], - config: SchedulerConfig, - ): ProcessRuntime | null; - - /** - * For preemptive algorithms: should the currently-running process - * be replaced by `candidate`? - * Called only when cpuProcess != null and a candidate exists. - */ - shouldPreempt( - current: ProcessRuntime, - candidate: ProcessRuntime, - config: SchedulerConfig, - ): boolean; - - /** - * RR only: has the process exhausted its quantum? - * For all other algorithms this must return false. - */ - isQuantumExpired( - runtime: ProcessRuntime, - config: SchedulerConfig, - ): boolean; -} - -// ============================================================ -// Dispatch table — engine looks up algorithm by name -// ============================================================ - -export const algorithms: Record = { - FCFS: fcfs, - SJF_NP: sjfNp, - SJF_P: srtf, - RR: rr, - PRIORITY_NP: priorityNp, - PRIORITY_P: priorityP, - PRIORITY_RR: priorityRr, - HRRN: hrrn, - MULTILEVEL: multilevel, +export const algorithms: Record = { + FCFS: new FCFSAlgorithm(), + SJF_NP: new SJFNonPreemptiveAlgorithm(), + SJF_P: new SRTFAlgorithm(), + RR: new RoundRobinAlgorithm(), + PRIORITY_NP: new PriorityNonPreemptiveAlgorithm(), + PRIORITY_P: new PriorityPreemptiveAlgorithm(), + PRIORITY_RR: new PriorityRoundRobinAlgorithm(), + HRRN: new HRRNAlgorithm(), + MULTILEVEL: new MultilevelQueueAlgorithm(), }; diff --git a/src/core/algorithms/multilevel.ts b/src/core/algorithms/multilevel.ts index 218994d..78c2898 100644 --- a/src/core/algorithms/multilevel.ts +++ b/src/core/algorithms/multilevel.ts @@ -1,109 +1,132 @@ -import type { MlqQueueDef, ProcessRuntime } from "../types"; -import type { SchedulerAlgorithm } from "./index"; -import { tiebreak } from "./utils"; +import type { MlqQueueDef, ProcessRuntime, SchedulerConfig } from "../types"; +import { BaseAlgorithm } from "./base"; /** - * MULTILEVEL — Multilevel Queue Scheduling + * MLQ — Multilevel Queue (Fila Multinível) * - * Processes are permanently assigned to a queue based on their currentPriority. - * The scheduler always dispatches from the highest-priority non-empty queue. - * Each queue has its own scheduling algorithm (FCFS, RR, or PRIORITY_NP). + * Processos são atribuídos permanentemente a filas com base em sua prioridade. + * Cada fila tem seu próprio algoritmo interno (FCFS, RR ou PRIORITY_NP). * - * Inter-queue behaviour: - * - A process from a higher-priority queue always preempts a process - * from a lower-priority queue. - * - No process from a lower-priority queue runs while a higher-priority - * queue has processes. + * Comportamento entre filas: + * - O escalonador sempre despacha da fila de maior prioridade não-vazia (índice 0). + * - Um processo de fila superior sempre preempta um processo de fila inferior. + * - Nenhum processo de fila inferior executa enquanto houver processos em fila superior. * - * Intra-queue behaviour: - * - FCFS: dispatch in arrival order; no preemption within the queue. - * - RR: round-robin using the queue's own quantum; no cross-queue switching. - * - PRIORITY_NP: dispatch the highest currentPriority process; no preemption. + * Comportamento dentro de cada fila: + * - FCFS: despacha na ordem de chegada; sem preempção interna. + * - RR: rodízio usando o quantum da fila; sem preempção interna. + * - PRIORITY_NP: despacha o de maior prioridade; sem preempção interna. * - * Queue assignment is derived from config.mlqQueues. Each queue definition - * specifies [priorityMin, priorityMax] (both inclusive). If a process's - * currentPriority falls outside all queues, it is assigned to the last queue. + * A configuração das filas vem de `config.mlqQueues`; se ausente, usa as + * filas padrão definidas em `DEFAULT_QUEUES`. */ +export class MultilevelQueueAlgorithm extends BaseAlgorithm { + readonly name = "Multilevel Queue (MLQ)"; + readonly description = + "Filas de prioridade fixas, cada uma com seu algoritmo interno (FCFS, RR, PRIORITY_NP). " + + "A fila de maior prioridade sempre executa primeiro. Processos são atribuídos " + + "permanentemente a uma fila conforme sua prioridade."; + readonly isPreemptiveCapable = true; + readonly usesQuantum = true; // verdadeiro porque a fila RR usa quantum -/** Returns the index of the queue that owns this priority value (0 = highest). */ -function getQueueIndex(priority: number, queues: readonly MlqQueueDef[]): number { - for (let i = 0; i < queues.length; i++) { - const q = queues[i]!; - if (priority >= q.priorityMin && priority <= q.priorityMax) return i; - } - // Fallback: assign to last queue if priority is out of all defined ranges. - return queues.length - 1; -} + /** Configuração padrão: 3 filas cobrindo prioridades 1–10. */ + private static readonly DEFAULT_QUEUES: MlqQueueDef[] = [ + { priorityMin: 1, priorityMax: 3, algorithm: "RR", quantum: 2 }, + { priorityMin: 4, priorityMax: 7, algorithm: "PRIORITY_NP", quantum: 2 }, + { priorityMin: 8, priorityMax: 10, algorithm: "FCFS", quantum: 2 }, + ]; -/** Select the best candidate from a sub-queue using the queue's algorithm. */ -function selectWithinQueue( - candidates: readonly ProcessRuntime[], - queueDef: MlqQueueDef, -): ProcessRuntime | null { - if (candidates.length === 0) return null; - - if (queueDef.algorithm === "RR") { - // RR: dispatch in insertion order (engine tail-appends on preemption). - return candidates[0]!; - } - - if (queueDef.algorithm === "FCFS") { - // FCFS: dispatch earliest-arriving process (by arrivalTick, then processId). - return candidates.reduce((best, rt) => - tiebreak(rt, best) < 0 ? rt : best, - ); - } - - // PRIORITY_NP: dispatch the process with the lowest currentPriority number. - let best = candidates[0]!; - for (let i = 1; i < candidates.length; i++) { - const rt = candidates[i]!; - if ( - rt.currentPriority < best.currentPriority || - (rt.currentPriority === best.currentPriority && tiebreak(rt, best) < 0) - ) { - best = rt; + /** + * Retorna o índice da fila à qual pertence o valor de prioridade informado. + * Se a prioridade não se enquadrar em nenhuma fila definida, usa a última. + */ + private static getQueueIndex( + priority: number, + queues: readonly MlqQueueDef[], + ): number { + for (let i = 0; i < queues.length; i++) { + const q = queues[i]!; + if (priority >= q.priorityMin && priority <= q.priorityMax) return i; } + return queues.length - 1; } - return best; -} -const DEFAULT_QUEUES: MlqQueueDef[] = [ - { priorityMin: 1, priorityMax: 3, algorithm: "RR", quantum: 2 }, - { priorityMin: 4, priorityMax: 7, algorithm: "PRIORITY_NP", quantum: 2 }, - { priorityMin: 8, priorityMax: 10, algorithm: "FCFS", quantum: 2 }, -]; + /** + * Seleciona o melhor candidato dentro de uma sub-fila usando o algoritmo + * definido para ela (RR, FCFS ou PRIORITY_NP). + */ + private static selectWithinQueue( + candidates: readonly ProcessRuntime[], + queueDef: MlqQueueDef, + ): ProcessRuntime | null { + if (candidates.length === 0) return null; -export const multilevel: SchedulerAlgorithm = { - select(readyQueue, config): ProcessRuntime | null { - const queues = config.mlqQueues ?? DEFAULT_QUEUES; + if (queueDef.algorithm === "RR") { + // RR: despacha na ordem de inserção (engine mantém FIFO). + return candidates[0]!; + } + + if (queueDef.algorithm === "FCFS") { + // FCFS: despacha o de chegada mais antiga (arrivalTick, depois processId). + return candidates.reduce((best, rt) => + BaseAlgorithm.tiebreak(rt, best) < 0 ? rt : best, + ); + } + + // PRIORITY_NP: despacha o de maior prioridade (menor número). + let best = candidates[0]!; + for (let i = 1; i < candidates.length; i++) { + const rt = candidates[i]!; + if ( + rt.currentPriority < best.currentPriority || + (rt.currentPriority === best.currentPriority && + BaseAlgorithm.tiebreak(rt, best) < 0) + ) { + best = rt; + } + } + return best; + } + + select( + readyQueue: readonly ProcessRuntime[], + config: SchedulerConfig, + ): ProcessRuntime | null { + const queues = config.mlqQueues ?? MultilevelQueueAlgorithm.DEFAULT_QUEUES; if (queues.length === 0) return null; - // Iterate from highest-priority queue (index 0) to lowest. + // Itera da fila de maior prioridade (índice 0) para a de menor. for (let qi = 0; qi < queues.length; qi++) { const queueDef = queues[qi]!; const candidates = readyQueue.filter( - (rt) => getQueueIndex(rt.currentPriority, queues) === qi, + (rt) => MultilevelQueueAlgorithm.getQueueIndex(rt.currentPriority, queues) === qi, ); - const selected = selectWithinQueue(candidates, queueDef); + const selected = MultilevelQueueAlgorithm.selectWithinQueue(candidates, queueDef); if (selected !== null) return selected; } return null; - }, + } - shouldPreempt(current, candidate, config): boolean { - const queues = config.mlqQueues ?? DEFAULT_QUEUES; - const currentQueueIdx = getQueueIndex(current.currentPriority, queues); - const candidateQueueIdx = getQueueIndex(candidate.currentPriority, queues); - // Preempt only if the candidate belongs to a strictly higher-priority queue. + shouldPreempt( + current: ProcessRuntime, + candidate: ProcessRuntime, + config: SchedulerConfig, + ): boolean { + const queues = config.mlqQueues ?? MultilevelQueueAlgorithm.DEFAULT_QUEUES; + const currentQueueIdx = MultilevelQueueAlgorithm.getQueueIndex(current.currentPriority, queues); + const candidateQueueIdx = MultilevelQueueAlgorithm.getQueueIndex(candidate.currentPriority, queues); + // Preempta apenas se o candidato pertence a uma fila de prioridade estritamente maior. return candidateQueueIdx < currentQueueIdx; - }, + } - isQuantumExpired(runtime, config): boolean { - const queues = config.mlqQueues ?? DEFAULT_QUEUES; - const queueIdx = getQueueIndex(runtime.currentPriority, queues); + isQuantumExpired( + runtime: ProcessRuntime, + config: SchedulerConfig, + ): boolean { + const queues = config.mlqQueues ?? MultilevelQueueAlgorithm.DEFAULT_QUEUES; + const queueIdx = MultilevelQueueAlgorithm.getQueueIndex(runtime.currentPriority, queues); const queueDef = queues[queueIdx]; if (!queueDef) return false; return queueDef.algorithm === "RR" && runtime.quantumRemaining <= 0; - }, -}; + } +} diff --git a/src/core/algorithms/priority.ts b/src/core/algorithms/priority.ts index a844b5d..d0bb236 100644 --- a/src/core/algorithms/priority.ts +++ b/src/core/algorithms/priority.ts @@ -1,49 +1,79 @@ import type { ProcessRuntime } from "../types"; -import type { SchedulerAlgorithm } from "./index"; -import { tiebreak } from "./utils"; +import { BaseAlgorithm } from "./base"; +/** + * Comparador interno compartilhado pelas duas variantes de Prioridade. + * Menor número = maior prioridade. Usa tiebreak padrão em caso de empate. + */ function prioritySort(a: ProcessRuntime, b: ProcessRuntime): number { - // Lower number = higher priority if (a.currentPriority !== b.currentPriority) return a.currentPriority - b.currentPriority; - return tiebreak(a, b); + return BaseAlgorithm.tiebreak(a, b); } -/** - * Priority Non-Preemptive - * Selects process with lowest priority number; does not interrupt running process. - */ -export const priorityNp: SchedulerAlgorithm = { - select(readyQueue) { - if (readyQueue.length === 0) return null; - return [...readyQueue].sort(prioritySort)[0] ?? null; - }, - - shouldPreempt() { - return false; - }, - - isQuantumExpired() { - return false; - }, -}; +// ───────────────────────────────────────────────────────────────────────────── /** - * Priority Preemptive - * Preempts running process if a higher-priority (lower number) process arrives. - * Aging is applied by the engine — currentPriority decrements over time. + * Priority Não-Preemptivo + * + * Seleciona o processo com maior prioridade (menor número). Uma vez que + * um processo ocupa a CPU, ele roda até terminar — novos processos de alta + * prioridade precisam esperar. Pode causar inanição (starvation) de processos + * de baixa prioridade. O mecanismo de aging, quando ativado, incrementa + * gradualmente a prioridade de processos em espera. */ -export const priorityP: SchedulerAlgorithm = { - select(readyQueue) { +export class PriorityNonPreemptiveAlgorithm extends BaseAlgorithm { + readonly name = "Escalonamento por Prioridade — Não-Preemptivo"; + readonly description = + "Despacha o processo de maior prioridade (menor número). Não-preemptivo: " + + "o processo atual não é interrompido por novos de alta prioridade. " + + "Pode causar inanição; use aging para mitigar."; + readonly isPreemptiveCapable = false; + readonly usesQuantum = false; + + select(readyQueue: readonly ProcessRuntime[]): ProcessRuntime | null { if (readyQueue.length === 0) return null; return [...readyQueue].sort(prioritySort)[0] ?? null; - }, + } - shouldPreempt(current, candidate) { + shouldPreempt(): boolean { + return false; + } + + isQuantumExpired(): boolean { + return false; + } +} + +// ───────────────────────────────────────────────────────────────────────────── + +/** + * Priority Preemptivo + * + * Variante preemptiva: o processo em execução é interrompido imediatamente + * ao chegar um processo com prioridade estritamente maior (número menor). + * Prioridade igual não causa preempção (evita context switches desnecessários). + * O aging, quando ativado no engine, decrementa `currentPriority` a cada + * intervalo, prevenindo inanição de processos de baixa prioridade. + */ +export class PriorityPreemptiveAlgorithm extends BaseAlgorithm { + readonly name = "Escalonamento por Prioridade — Preemptivo"; + readonly description = + "Preempta o processo em execução quando um de maior prioridade fica pronto. " + + "Prioridade igual não gera preempção. Suporta aging para evitar inanição."; + readonly isPreemptiveCapable = true; + readonly usesQuantum = false; + + select(readyQueue: readonly ProcessRuntime[]): ProcessRuntime | null { + if (readyQueue.length === 0) return null; + return [...readyQueue].sort(prioritySort)[0] ?? null; + } + + shouldPreempt(current: ProcessRuntime, candidate: ProcessRuntime): boolean { return candidate.currentPriority < current.currentPriority; - }, + } - isQuantumExpired() { + isQuantumExpired(): boolean { return false; - }, -}; + } +} diff --git a/src/core/algorithms/priority_rr.ts b/src/core/algorithms/priority_rr.ts index 82b42a7..7eabe04 100644 --- a/src/core/algorithms/priority_rr.ts +++ b/src/core/algorithms/priority_rr.ts @@ -1,49 +1,59 @@ -import type { SchedulerAlgorithm } from "./index"; +import type { ProcessRuntime } from "../types"; +import { BaseAlgorithm } from "./base"; /** * Priority Round Robin (PRIORITY_RR) * - * Mimics real-OS scheduling (Windows priority bands, POSIX SCHED_RR): + * Combina escalonamento por prioridade com rodízio dentro de cada banda: * - * - The ready queue is conceptually divided into priority bands. - * - The scheduler always dispatches from the highest-priority band - * (lowest numeric value), leaving lower-priority processes waiting. - * - Within the same priority band, processes share the CPU via round-robin - * with the configured quantum — fairness among equals. - * - A running process is preempted immediately when a STRICTLY higher-priority - * process becomes ready (arrival or I/O completion). - * - Quantum expiry only rotates within the same priority band; it does not - * allow a lower-priority process to run while a same-priority one is waiting. - * - Compatible with the aging mechanism: as low-priority processes age their - * currentPriority decreases, eventually entering a higher-priority band. + * - A fila de prontos é dividida conceitualmente em bandas de prioridade. + * - O escalonador sempre despacha da banda de maior prioridade (menor número), + * mantendo processos de menor prioridade em espera. + * - Dentro de uma mesma banda, os processos compartilham a CPU via round-robin + * com o quantum configurado — garantindo justiça entre iguais. + * - Preempção ocorre imediatamente ao chegar um processo de prioridade + * estritamente maior; prioridade igual é tratada pelo quantum. + * - Compatível com aging: ao diminuir `currentPriority`, processos de baixa + * prioridade eventualmente sobem de banda, prevenindo inanição. + * + * Simula o comportamento de SOs reais como Windows (priority bands) + * e POSIX SCHED_RR. */ -export const priorityRr: SchedulerAlgorithm = { - select(readyQueue) { +export class PriorityRoundRobinAlgorithm extends BaseAlgorithm { + readonly name = "Priority Round Robin (PRR)"; + readonly description = + "Combina prioridade com round-robin dentro de cada banda. A banda mais " + + "prioritária sempre executa primeiro; dentro da mesma banda, os processos " + + "se revezam pelo quantum. Simula o escalonamento de SOs reais (Windows, POSIX)."; + readonly isPreemptiveCapable = true; + readonly usesQuantum = true; + + select(readyQueue: readonly ProcessRuntime[]): ProcessRuntime | null { if (readyQueue.length === 0) return null; - // Find the highest priority level (smallest numeric value) present. + // Localiza o nível de prioridade mais alto (menor valor numérico) presente. let bestPriority = Infinity; for (const rt of readyQueue) { if (rt.currentPriority < bestPriority) bestPriority = rt.currentPriority; } - // Within that band, return the first process (FIFO insertion order). - // The engine maintains insertion order, so this gives round-robin rotation - // after quantum expiry: the expired process goes to the tail, the next - // same-priority process is now at the head. + // Dentro da banda, retorna o primeiro processo (ordem de inserção = FIFO). + // O engine insere processos expirados no final da fila, garantindo a rotação + // round-robin: o processo que esgotou o quantum vai para o fim, + // o próximo da mesma prioridade assume. return readyQueue.find(rt => rt.currentPriority === bestPriority) ?? null; - }, + } - // Preempt ONLY for strictly higher priority. - // Same-priority candidates are handled by quantum expiry — no unnecessary - // context switches within a priority band. - shouldPreempt(current, candidate) { + // Preempta APENAS por prioridade estritamente maior. + // Processos de mesma prioridade são tratados pelo mecanismo de quantum — + // evita trocas de contexto desnecessárias dentro da mesma banda. + shouldPreempt(current: ProcessRuntime, candidate: ProcessRuntime): boolean { return candidate.currentPriority < current.currentPriority; - }, + } - // Quantum expiry drives round-robin rotation within a priority band, - // exactly like plain Round Robin. - isQuantumExpired(runtime) { + // A expiração do quantum conduz a rotação round-robin dentro da banda, + // exatamente como no Round Robin puro. + isQuantumExpired(runtime: ProcessRuntime): boolean { return runtime.quantumRemaining <= 0; - }, -}; + } +} diff --git a/src/core/algorithms/rr.ts b/src/core/algorithms/rr.ts index c1b4ac6..dc5f9f2 100644 --- a/src/core/algorithms/rr.ts +++ b/src/core/algorithms/rr.ts @@ -1,22 +1,36 @@ -import type { SchedulerAlgorithm } from "./index"; +import type { ProcessRuntime } from "../types"; +import { BaseAlgorithm } from "./base"; /** * RR — Round Robin - * FIFO queue with a configurable quantum. - * select() returns the front of the queue (insertion-order maintained by engine). - * Preemption is driven by quantum expiry, not by a better candidate. + * + * Mantém a fila de prontos como FIFO e concede a cada processo uma fatia + * de tempo (quantum). Ao esgotar o quantum, o processo vai para o fim da + * fila e o próximo assume a CPU. Garante justiça e baixo tempo de resposta, + * ao custo de maior tempo de turnaround para processos curtos. + * + * A rotação é garantida pelo engine: ao expirar o quantum, o processo é + * reinserido no final da fila — select() simplesmente retorna o primeiro. */ -export const rr: SchedulerAlgorithm = { - select(readyQueue) { +export class RoundRobinAlgorithm extends BaseAlgorithm { + readonly name = "Round Robin (RR)"; + readonly description = + "Fila FIFO com quantum configurável. Cada processo recebe uma fatia " + + "de tempo igual. Garante justiça e baixo tempo de resposta, mas pode " + + "aumentar o turnaround de processos longos."; + readonly isPreemptiveCapable = false; // preempção é por expiração de quantum, não por candidato + readonly usesQuantum = true; + + select(readyQueue: readonly ProcessRuntime[]): ProcessRuntime | null { return readyQueue[0] ?? null; - }, + } - // RR does not preempt based on candidate priority; quantum expiry handles it. - shouldPreempt() { + // RR não preempta por candidato mais prioritário; a troca ocorre via quantum. + shouldPreempt(): boolean { return false; - }, + } - isQuantumExpired(runtime) { + isQuantumExpired(runtime: ProcessRuntime): boolean { return runtime.quantumRemaining <= 0; - }, -}; + } +} diff --git a/src/core/algorithms/sjf.ts b/src/core/algorithms/sjf.ts index 6578bba..6b26b3e 100644 --- a/src/core/algorithms/sjf.ts +++ b/src/core/algorithms/sjf.ts @@ -1,48 +1,79 @@ import type { ProcessRuntime } from "../types"; -import type { SchedulerAlgorithm } from "./index"; -import { tiebreak } from "./utils"; +import { BaseAlgorithm } from "./base"; +/** + * Comparador interno compartilhado pelas duas variantes do SJF. + * Ordena por menor burst restante; usa tiebreak padrão em caso de empate. + */ function sjfSort(a: ProcessRuntime, b: ProcessRuntime): number { if (a.remainingBurst !== b.remainingBurst) return a.remainingBurst - b.remainingBurst; - return tiebreak(a, b); + return BaseAlgorithm.tiebreak(a, b); } -/** - * SJF Non-Preemptive — Shortest Job First - * Selects the process with the smallest remaining burst; no preemption. - */ -export const sjfNp: SchedulerAlgorithm = { - select(readyQueue) { - if (readyQueue.length === 0) return null; - return [...readyQueue].sort(sjfSort)[0] ?? null; - }, - - shouldPreempt() { - return false; - }, - - isQuantumExpired() { - return false; - }, -}; +// ───────────────────────────────────────────────────────────────────────────── /** - * SRTF — Shortest Remaining Time First (SJF Preemptive) - * At each tick, preempts if a ready process has strictly smaller remainingBurst. - * Equal remaining burst → no preemption (avoids unnecessary context switches). + * SJF Não-Preemptivo — Shortest Job First + * + * Seleciona o processo com o menor burst de CPU restante. Uma vez que um + * processo ocupa a CPU, ele roda até terminar (não pode ser interrompido). + * Ótimo em tempo médio de espera, mas pode causar inanição (starvation) + * de processos longos se processos curtos chegam continuamente. */ -export const srtf: SchedulerAlgorithm = { - select(readyQueue) { +export class SJFNonPreemptiveAlgorithm extends BaseAlgorithm { + readonly name = "Shortest Job First — Não-Preemptivo (SJF)"; + readonly description = + "Seleciona o processo com menor burst restante. Não-preemptivo: " + + "o processo rodando não pode ser interrompido. Minimiza o tempo " + + "médio de espera, mas pode causar inanição de processos longos."; + readonly isPreemptiveCapable = false; + readonly usesQuantum = false; + + select(readyQueue: readonly ProcessRuntime[]): ProcessRuntime | null { if (readyQueue.length === 0) return null; return [...readyQueue].sort(sjfSort)[0] ?? null; - }, + } - shouldPreempt(current, candidate) { + shouldPreempt(): boolean { + return false; + } + + isQuantumExpired(): boolean { + return false; + } +} + +// ───────────────────────────────────────────────────────────────────────────── + +/** + * SRTF — Shortest Remaining Time First (SJF Preemptivo) + * + * Variante preemptiva do SJF. A cada tick, se um processo recém-chegado + * tem burst restante estritamente menor que o processo em execução, ocorre + * preempção. Burst igual não gera troca (evita context switches desnecessários). + * Produz o menor tempo médio de espera possível entre todos os algoritmos, + * porém com maior overhead de trocas de contexto. + */ +export class SRTFAlgorithm extends BaseAlgorithm { + readonly name = "Shortest Remaining Time First (SRTF)"; + readonly description = + "Variante preemptiva do SJF. Preempta o processo rodando sempre que " + + "um processo mais curto fica disponível. Otimiza o tempo médio de " + + "espera, mas gera mais trocas de contexto."; + readonly isPreemptiveCapable = true; + readonly usesQuantum = false; + + select(readyQueue: readonly ProcessRuntime[]): ProcessRuntime | null { + if (readyQueue.length === 0) return null; + return [...readyQueue].sort(sjfSort)[0] ?? null; + } + + shouldPreempt(current: ProcessRuntime, candidate: ProcessRuntime): boolean { return candidate.remainingBurst < current.remainingBurst; - }, + } - isQuantumExpired() { + isQuantumExpired(): boolean { return false; - }, -}; + } +} diff --git a/src/core/engine.ts b/src/core/engine.ts index 7e0659d..dd38ac3 100644 --- a/src/core/engine.ts +++ b/src/core/engine.ts @@ -6,24 +6,52 @@ import type { StateLabel, } from "./types"; import { algorithms } from "./algorithms/index"; -import type { SchedulerAlgorithm } from "./algorithms/index"; - -// ============================================================ -// SimulationEngine -// ============================================================ +import type { BaseAlgorithm } from "./algorithms/index"; +/** + * SimulationEngine — Núcleo de execução da simulação de escalonamento. + * + * Responsável por avançar o simulador tick a tick, aplicando o algoritmo + * de escalonamento configurado e mantendo o estado de todos os processos. + * + * Ciclo de vida de um único tick (método `step()`): + * 1. Chegada — processos cujo `arrivalTime` é igual ao tick atual + * transitam New → Ready e entram na fila de prontos. + * 2. Preempção — o algoritmo decide se o processo em CPU deve ceder + * lugar a um candidato mais prioritário. + * 3. Despacho — seleciona e despacha o próximo processo (com overhead + * de troca de contexto, se configurado). + * 4. Snapshot — captura o estado de todos os processos neste tick. + * 5. Passagem do tempo — decrementa bursts, acumula tempos de CPU/IO/espera. + * 6. Aging — decrementa prioridade dos processos em espera prolongada. + * 7. Conclusões — trata término de bursts de CPU e IO; avança para o + * próximo burst ou termina o processo. + * + * Uso típico: + * ```ts + * const engine = new SimulationEngine(processes, config); + * const ticks = engine.runAll(); // executa até todos os processos terminarem + * ``` + * + * Invariante: após `runAll()`, `isFinished` é `true` e todos os runtimes + * têm `state === "Terminated"`. + */ export class SimulationEngine { + // ─── Campos privados ────────────────────────────────────────────────────── + private readonly _processes: Process[]; private readonly _config: SchedulerConfig; - private readonly _algorithm: SchedulerAlgorithm; + private readonly _algorithm: BaseAlgorithm; - private _tick: number; // starts at -1; incremented at top of step() + private _tick: number; // começa em -1; incrementado no início de step() private _runtimes: Map; private _cpuProcess: ProcessRuntime | null; - private _readyQueue: ProcessRuntime[]; // ordered array (FIFO for RR) + private _readyQueue: ProcessRuntime[]; // array ordenado (FIFO para RR) private _ioQueue: ProcessRuntime[]; private _contextSwitchRemaining: number; - private _prevCpuProcessId: string | null; // track context switch detection + private _prevCpuProcessId: string | null; // para detectar troca de contexto + + // ─── Construtor ─────────────────────────────────────────────────────────── constructor(processes: Process[], config: SchedulerConfig) { if (processes.length === 0) throw new Error("At least one process required"); @@ -36,20 +64,75 @@ export class SimulationEngine { this._ioQueue = []; this._contextSwitchRemaining = 0; this._prevCpuProcessId = null; - this._runtimes = new Map(processes.map((p) => [p.id, initRuntime(p, config)])); + this._runtimes = new Map( + processes.map((p) => [p.id, SimulationEngine._initRuntime(p, config)]), + ); } - // ---------------------------------------------------------- - // Public API - // ---------------------------------------------------------- + // ─── API pública ────────────────────────────────────────────────────────── - /** Advance one tick; return snapshot of that tick. */ + /** + * Avança a simulação por um tick e retorna o snapshot do estado neste tick. + * + * Lança um erro se a simulação já tiver terminado (`isFinished === true`). + * O número do tick é incrementado no início; o primeiro tick válido é 0. + */ step(): SimTick { if (this.isFinished) throw new Error("Simulation is already finished"); this._tick++; - // 1. Arrivals + this._handleArrivals(); // 1. New → Ready para quem chega agora + this._checkPreemption(); // 2. Preempta CPU se candidato melhor chegou + this._dispatchNext(); // 3. Despacha próximo (ou inicia context switch) + const snapshot = this._snapshot(); // 4. Captura estado DURANTE este tick + this._advanceTime(); // 5. Decrementa bursts, acumula métricas + this._applyAging(); // 6. Aging de prioridade (se habilitado) + this._handleCpuCompletion(); // 7a. Finaliza burst/quantum de CPU + this._handleIoCompletions(); // 7b. Finaliza bursts de I/O + + return snapshot; + } + + /** Executa até o fim e retorna todos os snapshots de tick. */ + runAll(): SimTick[] { + const results: SimTick[] = []; + while (!this.isFinished) { + results.push(this.step()); + if (results.length > 10_000) { + throw new Error("Simulation exceeded 10,000 ticks — check for infinite loop"); + } + } + return results; + } + + /** Verdadeiro quando todos os processos estão no estado "Terminated". */ + get isFinished(): boolean { + for (const rt of this._runtimes.values()) { + if (rt.state !== "Terminated") return false; + } + return true; + } + + /** Número do tick atual (−1 antes do primeiro `step()`). */ + get currentTick(): number { + return this._tick; + } + + /** Array com o estado de runtime de todos os processos. */ + get runtimeStates(): ProcessRuntime[] { + return Array.from(this._runtimes.values()); + } + + // ─── Fases do ciclo de simulação ────────────────────────────────────────── + + /** + * Fase 1 — Chegada de processos. + * + * Processos cujo `arrivalTime` é igual ao tick atual transitam do estado + * "New" para "Ready" e são inseridos na fila de prontos. + */ + private _handleArrivals(): void { for (const p of this._processes) { const rt = this._runtimes.get(p.id)!; if (p.arrivalTime === this._tick && rt.state === "New") { @@ -57,55 +140,79 @@ export class SimulationEngine { this._readyQueue.push(rt); } } + } - // 2. Preemption & Quantum Check (from previous tick's state) - if (this._cpuProcess !== null && this._readyQueue.length > 0) { - const candidate = this._algorithm.select(this._readyQueue, this._config); - if ( - candidate !== null && - this._algorithm.shouldPreempt(this._cpuProcess, candidate, this._config) - ) { - const cpu = this._cpuProcess; - this._transition(cpu, "Ready"); - this._readyQueue.push(cpu); - this._cpuProcess = null; - } + /** + * Fase 2 — Verificação de preempção. + * + * Consulta o algoritmo: se houver um candidato na fila de prontos que deva + * preemptar o processo em CPU, o processo atual retorna à fila de prontos + * e `_cpuProcess` é zerado. Chamado após a chegada para que processos + * recém-chegados possam imediatamente preemptar. + */ + private _checkPreemption(): void { + if (this._cpuProcess === null || this._readyQueue.length === 0) return; + const candidate = this._algorithm.select(this._readyQueue, this._config); + if ( + candidate !== null && + this._algorithm.shouldPreempt(this._cpuProcess, candidate, this._config) + ) { + const cpu = this._cpuProcess; + this._transition(cpu, "Ready"); + this._readyQueue.push(cpu); + this._cpuProcess = null; } + } - // 3. Dispatch / Context Switch - if (this._contextSwitchRemaining > 0) { - // Doing context switch this tick - // We don't dispatch. + /** + * Fase 3 — Despacho / troca de contexto. + * + * Se um context switch estiver em andamento, nenhum processo é despachado + * neste tick. Caso contrário, e se a CPU estiver ociosa com processos + * prontos, o algoritmo seleciona o próximo processo: + * + * - Se houver overhead de troca de contexto configurado e o processo + * selecionado for diferente do último, o processo volta ao início da + * fila e o contador de context-switch é iniciado. + * - Caso contrário, o processo é despachado imediatamente para a CPU. + */ + private _dispatchNext(): void { + if (this._contextSwitchRemaining > 0) return; + if (this._cpuProcess !== null || this._readyQueue.length === 0) return; + + const next = this._algorithm.select(this._readyQueue, this._config); + if (next === null) return; + + const idx = this._readyQueue.indexOf(next); + if (idx !== -1) this._readyQueue.splice(idx, 1); + + if ( + this._config.contextSwitchTime > 0 && + this._prevCpuProcessId !== null && + this._prevCpuProcessId !== next.processId + ) { + this._readyQueue.unshift(next); + this._contextSwitchRemaining = this._config.contextSwitchTime; + this._prevCpuProcessId = next.processId; } else { - if (this._cpuProcess === null && this._readyQueue.length > 0) { - const next = this._algorithm.select(this._readyQueue, this._config); - if (next !== null) { - const idx = this._readyQueue.indexOf(next); - if (idx !== -1) this._readyQueue.splice(idx, 1); - - if ( - this._config.contextSwitchTime > 0 && - this._prevCpuProcessId !== null && - this._prevCpuProcessId !== next.processId - ) { - this._readyQueue.unshift(next); - this._contextSwitchRemaining = this._config.contextSwitchTime; - this._prevCpuProcessId = next.processId; - } else { - if (next.startTick === null) next.startTick = this._tick; - next.quantumRemaining = this._config.quantum; - this._transition(next, "Running"); - this._prevCpuProcessId = next.processId; - this._cpuProcess = next; - } - } - } + if (next.startTick === null) next.startTick = this._tick; + next.quantumRemaining = this._config.quantum; + this._transition(next, "Running"); + this._prevCpuProcessId = next.processId; + this._cpuProcess = next; } + } - // 4. Take Snapshot representing what happens DURING this tick - const snapshot = this._snapshot(); - - // 5. Passage of Time (Simulate execution for 1 tick) + /** + * Fase 5 — Passagem do tempo. + * + * Simula a execução de 1 tick: + * - Decrementa o contador de context-switch restante (se ativo). + * - Decrementa o burst restante do processo em CPU e acumula cpuTime/quantum. + * - Decrementa o burst restante de cada processo em I/O e acumula ioTime. + * - Incrementa o waitingTime de todos os processos na fila de prontos. + */ + private _advanceTime(): void { if (this._contextSwitchRemaining > 0) { this._contextSwitchRemaining--; } @@ -125,44 +232,80 @@ export class SimulationEngine { for (const rt of this._readyQueue) { rt.waitingTime++; } + } - // 6. Aging (Priority) + /** + * Fase 6 — Aging (envelhecimento de prioridade). + * + * Quando o aging está habilitado, a cada `agingInterval` ticks o valor de + * `currentPriority` de todos os processos na fila de prontos é decrementado + * em 1 (até o mínimo de 1). Isso previne inanição em algoritmos de prioridade. + * + * Não é executado no tick 0 para evitar decremento prematuro. + */ + private _applyAging(): void { if ( - this._config.agingEnabled && - this._tick > 0 && - this._tick % this._config.agingInterval === 0 + !this._config.agingEnabled || + this._tick === 0 || + this._tick % this._config.agingInterval !== 0 ) { - for (const rt of this._readyQueue) { - rt.currentPriority = Math.max(1, rt.currentPriority - 1); - } + return; } + for (const rt of this._readyQueue) { + rt.currentPriority = Math.max(1, rt.currentPriority - 1); + } + } - // 7. Process completions / transition to next state for NEXT tick - if (this._cpuProcess !== null) { - const cpu = this._cpuProcess; - if (cpu.remainingBurst === 0) { - this._advanceBurst(cpu); - const process = this._processes.find((p) => p.id === cpu.processId)!; - const nextBurst = process.bursts[cpu.burstIndex]; - if (nextBurst !== undefined && nextBurst.type === "io") { - this._transition(cpu, "Waiting"); - this._ioQueue.push(cpu); - } else if (nextBurst !== undefined && nextBurst.type === "cpu") { - this._transition(cpu, "Ready"); - this._readyQueue.push(cpu); - } else { - this._transition(cpu, "Terminated"); - cpu.finishTick = this._tick + 1; - } - this._cpuProcess = null; - } else if (this._algorithm.isQuantumExpired(cpu, this._config)) { + /** + * Fase 7a — Conclusão de burst de CPU. + * + * Após a passagem do tempo, verifica se o processo em CPU terminou seu burst + * ou esgotou o quantum: + * + * - Burst zerado: avança para o próximo burst do processo. + * · Se o próximo for I/O → transita para "Waiting" e entra na ioQueue. + * · Se o próximo for CPU → retorna à fila de prontos ("Ready"). + * · Se não houver próximo → termina o processo ("Terminated"). + * + * - Quantum expirado (sem término de burst): retorna à fila de prontos. + */ + private _handleCpuCompletion(): void { + if (this._cpuProcess === null) return; + + const cpu = this._cpuProcess; + + if (cpu.remainingBurst === 0) { + this._advanceBurst(cpu); + const process = this._processes.find((p) => p.id === cpu.processId)!; + const nextBurst = process.bursts[cpu.burstIndex]; + if (nextBurst !== undefined && nextBurst.type === "io") { + this._transition(cpu, "Waiting"); + this._ioQueue.push(cpu); + } else if (nextBurst !== undefined && nextBurst.type === "cpu") { this._transition(cpu, "Ready"); this._readyQueue.push(cpu); - this._cpuProcess = null; + } else { + this._transition(cpu, "Terminated"); + cpu.finishTick = this._tick + 1; } + this._cpuProcess = null; + } else if (this._algorithm.isQuantumExpired(cpu, this._config)) { + this._transition(cpu, "Ready"); + this._readyQueue.push(cpu); + this._cpuProcess = null; } + } - // IO completions + /** + * Fase 7b — Conclusão de bursts de I/O. + * + * Percorre a ioQueue e, para cada processo cujo burst de I/O zerou: + * - Se houver próximo burst → transita para "Ready" e insere na readyQueue. + * - Caso contrário → termina o processo ("Terminated"). + * + * Processos ainda com I/O pendente permanecem na ioQueue. + */ + private _handleIoCompletions(): void { const nextIoQueue: ProcessRuntime[] = []; for (const rt of this._ioQueue) { if (rt.remainingBurst === 0) { @@ -180,45 +323,22 @@ export class SimulationEngine { } } this._ioQueue = nextIoQueue; - - return snapshot; } - /** Run to completion; return all tick snapshots. */ - runAll(): SimTick[] { - const results: SimTick[] = []; - while (!this.isFinished) { - results.push(this.step()); - if (results.length > 10_000) { - throw new Error("Simulation exceeded 10,000 ticks — check for infinite loop"); - } - } - return results; - } - - get isFinished(): boolean { - for (const rt of this._runtimes.values()) { - if (rt.state !== "Terminated") return false; - } - return true; - } - - get currentTick(): number { - return this._tick; - } - - get runtimeStates(): ProcessRuntime[] { - return Array.from(this._runtimes.values()); - } - - // ---------------------------------------------------------- - // Private helpers - // ---------------------------------------------------------- + // ─── Helpers internos ───────────────────────────────────────────────────── + /** + * Aplica uma transição de estado a um runtime, atualizando `state`. + * Centraliza todas as transições para facilitar rastreamento e depuração. + */ private _transition(rt: ProcessRuntime, state: StateLabel): void { rt.state = state; } + /** + * Avança o índice de burst de um runtime e atualiza `remainingBurst` + * com a duração do próximo burst. Se não houver próximo burst, `remainingBurst` é 0. + */ private _advanceBurst(rt: ProcessRuntime): void { rt.burstIndex++; const process = this._processes.find((p) => p.id === rt.processId)!; @@ -226,6 +346,11 @@ export class SimulationEngine { rt.remainingBurst = nextBurst !== undefined ? nextBurst.duration : 0; } + /** + * Captura o estado instantâneo de todos os processos no tick atual. + * Se um context switch estiver em andamento, `cpuProcess` é null e + * `ctxSwitchForProcess` identifica o processo prestes a executar. + */ private _snapshot(): SimTick { const states: Record = {}; for (const [id, rt] of this._runtimes) { @@ -244,30 +369,32 @@ export class SimulationEngine { : null, }; } -} -// ============================================================ -// Helper: initialise a ProcessRuntime from a Process -// ============================================================ + // ─── Inicialização ──────────────────────────────────────────────────────── -function initRuntime(p: Process, config: SchedulerConfig): ProcessRuntime { - const firstBurst = p.bursts[0]; - if (firstBurst === undefined || firstBurst.type !== "cpu") { - throw new Error(`Process ${p.id}: first burst must be a CPU burst`); + /** + * Inicializa o estado de runtime de um processo a partir de sua definição. + * O primeiro burst deve ser do tipo "cpu"; caso contrário, um erro é lançado. + */ + private static _initRuntime(p: Process, config: SchedulerConfig): ProcessRuntime { + const firstBurst = p.bursts[0]; + if (firstBurst === undefined || firstBurst.type !== "cpu") { + throw new Error(`Process ${p.id}: first burst must be a CPU burst`); + } + return { + processId: p.id, + state: "New", + remainingBurst: firstBurst.duration, + burstIndex: 0, + arrivalTick: p.arrivalTime, + startTick: null, + finishTick: null, + waitingTime: 0, + cpuTime: 0, + ioTime: 0, + responseTime: null, + quantumRemaining: config.quantum, + currentPriority: p.priority, + }; } - return { - processId: p.id, - state: "New", - remainingBurst: firstBurst.duration, - burstIndex: 0, - arrivalTick: p.arrivalTime, - startTick: null, - finishTick: null, - waitingTime: 0, - cpuTime: 0, - ioTime: 0, - responseTime: null, - quantumRemaining: config.quantum, - currentPriority: p.priority, - }; }
Processo Arrival Start FinishResponseTurnaroundWaitingCPU TimeI/O TimeResponseTurnaroundWaitingCPUI/O CPU% Bound
{ style={{ backgroundColor: color }} /> {proc?.pid !== undefined && ( - + #{proc.pid} )} {name}
{m.arrivalTime}{m.startTick}{m.finishTick}{m.responseTime}{m.turnaroundTime}{m.waitingTime}{m.cpuTime}{m.ioTime}{m.cpuUtilization.toFixed(1)}%{m.arrivalTime}{m.startTick}{m.finishTick} + + + + + + + + + + +
+
+
+
+ + {m.cpuUtilization.toFixed(0)}% + +
+
{m.boundType}