refactor: Convert scheduling algorithms to class-based BaseAlgorithm structure, update engine and tests, and add new algorithm and metrics documentation.
This commit is contained in:
1 parent
9c552e53fa
commit
571d8e51d2
26 files changed
+1465
-1103
No files matched your search
@@ -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.
|
||||
|
||||
[](https://react.dev/)
|
||||
[](https://www.typescriptlang.org/)
|
||||
[](https://vitejs.dev/)
|
||||
[](https://tailwindcss.com/)
|
||||
[](https://vitest.dev/)
|
||||
[](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<string, StateLabel>;
|
||||
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).
|
||||
BIN
Binary file not shown.
@@ -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
|
||||
+114
@@ -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 |
|
||||
@@ -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) => (
|
||||
<div className="bg-slate-800 border border-slate-700 rounded-lg px-4 py-3 flex flex-col gap-1">
|
||||
const MetricCard = ({
|
||||
label,
|
||||
value,
|
||||
sub,
|
||||
accentColor,
|
||||
accentClass,
|
||||
icon,
|
||||
barPct,
|
||||
}: MetricCardProps) => (
|
||||
<div className="bg-slate-800/70 border border-slate-700/80 rounded-xl px-4 py-3 flex flex-col gap-1.5 hover:border-slate-600 transition-colors">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-[10px] text-slate-400 uppercase tracking-wider font-medium">{label}</span>
|
||||
<span className={`text-xl font-bold font-mono ${accent}`}>{value}</span>
|
||||
<span className="text-slate-500">{icon}</span>
|
||||
</div>
|
||||
<span className={`text-2xl font-bold font-mono leading-none ${accentClass}`}>{value}</span>
|
||||
{sub && <span className="text-[10px] text-slate-500">{sub}</span>}
|
||||
{barPct !== undefined && (
|
||||
<div className="mt-1 h-1.5 w-full bg-slate-700 rounded-full overflow-hidden">
|
||||
<div
|
||||
className="h-full rounded-full transition-all duration-500"
|
||||
style={{ width: `${Math.min(100, barPct)}%`, backgroundColor: accentColor }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -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 (
|
||||
<div className="p-4 space-y-4">
|
||||
<div className="p-4">
|
||||
<div className="grid grid-cols-2 gap-3 sm:grid-cols-3">
|
||||
<MetricCard
|
||||
label="Avg Response Time"
|
||||
value={avgResponseTime.toFixed(2)}
|
||||
sub="unidades de tempo"
|
||||
accent="text-amber-300"
|
||||
accentColor="#f59e0b"
|
||||
accentClass="text-amber-300"
|
||||
icon={<Clock size={13} />}
|
||||
barPct={toPct(avgResponseTime)}
|
||||
/>
|
||||
<MetricCard
|
||||
label="Avg Turnaround Time"
|
||||
value={avgTurnaroundTime.toFixed(2)}
|
||||
sub="unidades de tempo"
|
||||
accent="text-blue-300"
|
||||
accentColor="#60a5fa"
|
||||
accentClass="text-blue-300"
|
||||
icon={<Timer size={13} />}
|
||||
barPct={toPct(avgTurnaroundTime)}
|
||||
/>
|
||||
<MetricCard
|
||||
label="Avg Waiting Time"
|
||||
value={avgWaitingTime.toFixed(2)}
|
||||
sub="unidades de tempo"
|
||||
accent="text-orange-300"
|
||||
accentColor="#fb923c"
|
||||
accentClass="text-orange-300"
|
||||
icon={<Hourglass size={13} />}
|
||||
barPct={toPct(avgWaitingTime)}
|
||||
/>
|
||||
<MetricCard
|
||||
label="CPU Throughput"
|
||||
value={cpuThroughput.toFixed(3)}
|
||||
sub="processos / u.t."
|
||||
accent="text-emerald-300"
|
||||
accentColor="#34d399"
|
||||
accentClass="text-emerald-300"
|
||||
icon={<Zap size={13} />}
|
||||
/>
|
||||
<MetricCard
|
||||
label="Total Simulation Time"
|
||||
value={String(totalSimulationTime)}
|
||||
sub="ticks"
|
||||
accent="text-slate-200"
|
||||
accentColor="#94a3b8"
|
||||
accentClass="text-slate-200"
|
||||
icon={<Activity size={13} />}
|
||||
/>
|
||||
{/* CPU Utilization card with progress bar */}
|
||||
<div className="bg-slate-800 border border-slate-700 rounded-lg px-4 py-3 flex flex-col gap-1">
|
||||
|
||||
{/* CPU Utilization — gradient bar */}
|
||||
<div className="bg-slate-800/70 border border-slate-700/80 rounded-xl px-4 py-3 flex flex-col gap-1.5 hover:border-slate-600 transition-colors">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-[10px] text-slate-400 uppercase tracking-wider font-medium">CPU Utilization</span>
|
||||
<span className="text-xl font-bold font-mono text-violet-300">{utilizationPct.toFixed(1)}%</span>
|
||||
<div className="mt-1 h-1.5 w-full bg-slate-700 rounded-full overflow-hidden">
|
||||
<span className="text-slate-500"><Cpu size={13} /></span>
|
||||
</div>
|
||||
<span className="text-2xl font-bold font-mono leading-none text-violet-300">
|
||||
{utilizationPct.toFixed(1)}%
|
||||
</span>
|
||||
<span className="text-[10px] text-slate-500">do tempo total em CPU</span>
|
||||
<div className="mt-1 h-2 w-full bg-slate-700 rounded-full overflow-hidden">
|
||||
<div
|
||||
className="h-full bg-violet-500 rounded-full transition-all duration-500"
|
||||
style={{ width: `${utilizationPct}%` }}
|
||||
className="h-full rounded-full transition-all duration-500"
|
||||
style={{
|
||||
width: `${utilizationPct}%`,
|
||||
background: `linear-gradient(90deg, #f59e0b ${0}%, #34d399 ${100}%)`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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<string, Process>;
|
||||
}
|
||||
|
||||
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,58 +37,132 @@ 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<string, string | number> = { 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 (
|
||||
<div className="p-4">
|
||||
<div className={`p-3 flex gap-3 ${showRadar ? 'flex-row' : 'flex-col'}`}>
|
||||
{/* ── Radar chart ───────────────────────────────────────── */}
|
||||
{showRadar && (
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-[10px] text-slate-400 uppercase tracking-wider mb-1 font-medium text-center">
|
||||
Perfil Comparativo (normalizado)
|
||||
</p>
|
||||
<ResponsiveContainer width="100%" height={220}>
|
||||
<BarChart data={data} margin={{ top: 4, right: 16, left: 0, bottom: 4 }}>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="#334155" vertical={false} />
|
||||
<RadarChart data={radarData} margin={{ top: 8, right: 20, bottom: 8, left: 20 }}>
|
||||
<PolarGrid stroke="#334155" />
|
||||
<PolarAngleAxis
|
||||
dataKey="dimension"
|
||||
tick={{ fill: '#94a3b8', fontSize: 10 }}
|
||||
/>
|
||||
{metrics.map((m) => (
|
||||
<Radar
|
||||
key={m.processId}
|
||||
name={getName(m.processId)}
|
||||
dataKey={getName(m.processId)}
|
||||
stroke={getColor(m.processId)}
|
||||
fill={getColor(m.processId)}
|
||||
fillOpacity={0.15}
|
||||
strokeWidth={2}
|
||||
dot={{ r: 3, fill: getColor(m.processId) }}
|
||||
/>
|
||||
))}
|
||||
<Tooltip
|
||||
contentStyle={TOOLTIP_STYLE}
|
||||
formatter={(value) => [typeof value === 'number' ? `${value.toFixed(1)}%` : '-', '']}
|
||||
/>
|
||||
<Legend
|
||||
wrapperStyle={{ fontSize: '10px', paddingTop: '4px', color: '#94a3b8' }}
|
||||
/>
|
||||
</RadarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ── Stacked bar chart ─────────────────────────────────── */}
|
||||
<div className={showRadar ? 'flex-1 min-w-0' : 'w-full'}>
|
||||
<p className="text-[10px] text-slate-400 uppercase tracking-wider mb-1 font-medium text-center">
|
||||
Breakdown por Processo (ticks)
|
||||
</p>
|
||||
<ResponsiveContainer width="100%" height={220}>
|
||||
<BarChart data={barData} margin={{ top: 8, right: 12, left: -8, bottom: 4 }}>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="#1e293b" vertical={false} />
|
||||
<XAxis
|
||||
dataKey="name"
|
||||
tick={{ fill: '#94a3b8', fontSize: 11 }}
|
||||
axisLine={{ stroke: '#475569' }}
|
||||
axisLine={{ stroke: '#334155' }}
|
||||
tickLine={false}
|
||||
/>
|
||||
<YAxis
|
||||
tick={{ fill: '#94a3b8', fontSize: 11 }}
|
||||
tick={{ fill: '#94a3b8', fontSize: 10 }}
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
label={{
|
||||
value: 'ticks',
|
||||
angle: -90,
|
||||
position: 'insideLeft',
|
||||
offset: 10,
|
||||
style: { fill: '#64748b', fontSize: 10 },
|
||||
offset: 14,
|
||||
style: { fill: '#475569', fontSize: 9 },
|
||||
}}
|
||||
/>
|
||||
<Tooltip
|
||||
contentStyle={{
|
||||
backgroundColor: '#1e293b',
|
||||
border: '1px solid #334155',
|
||||
borderRadius: '6px',
|
||||
fontSize: '12px',
|
||||
color: '#e2e8f0',
|
||||
}}
|
||||
cursor={{ fill: 'rgba(255,255,255,0.05)' }}
|
||||
contentStyle={TOOLTIP_STYLE}
|
||||
cursor={{ fill: 'rgba(255,255,255,0.04)' }}
|
||||
/>
|
||||
<Legend
|
||||
wrapperStyle={{ fontSize: '11px', paddingTop: '8px', color: '#94a3b8' }}
|
||||
wrapperStyle={{ fontSize: '10px', paddingTop: '4px', color: '#94a3b8' }}
|
||||
/>
|
||||
<Bar dataKey="Waiting Time" stackId="a" fill="#f59e0b" radius={[0, 0, 0, 0]} />
|
||||
<Bar dataKey="CPU Time" stackId="a" fill="#22c55e" radius={[0, 0, 0, 0]} />
|
||||
<Bar dataKey="I/O Time" stackId="a" fill="#06b6d4" radius={[4, 4, 0, 0]} />
|
||||
<Bar dataKey="Waiting" stackId="a" fill="#f59e0b" radius={[0, 0, 0, 0]} />
|
||||
<Bar dataKey="CPU" stackId="a" fill="#22c55e" radius={[0, 0, 0, 0]} />
|
||||
<Bar dataKey="I/O" stackId="a" fill="#06b6d4" radius={[4, 4, 0, 0]} />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
<p className="text-center text-[10px] text-slate-500 mt-1">
|
||||
Barras empilhadas = Turnaround Time (Waiting + CPU + I/O)
|
||||
<p className="text-center text-[9px] text-slate-600 mt-0.5">
|
||||
Waiting + CPU + I/O = Turnaround
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ const MetricsPanel = () => {
|
||||
const canExportCSV = status === 'finished' && processMetrics.length > 0;
|
||||
|
||||
return (
|
||||
<div className="border-t border-slate-700 bg-slate-950 flex flex-col shrink-0" style={{ height: '280px' }}>
|
||||
<div className="border-t border-slate-700 bg-slate-950 flex flex-col shrink-0" style={{ height: '340px' }}>
|
||||
{/* Panel header with tabs + export button */}
|
||||
<div className="flex items-center px-3 border-b border-slate-700 shrink-0 h-10 gap-1">
|
||||
{TAB_CONFIG.map(tab => (
|
||||
|
||||
@@ -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 (
|
||||
<div className="relative flex items-center justify-end gap-1.5 min-w-[52px]">
|
||||
<div
|
||||
className="absolute inset-y-0 right-0 rounded-sm opacity-20 transition-all duration-300"
|
||||
style={{ width: `${pct}%`, backgroundColor: color }}
|
||||
/>
|
||||
<span className="relative z-10 font-mono text-xs">{value}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const MetricsTable = ({ metrics, processMap }: MetricsTableProps) => {
|
||||
if (metrics.length === 0) {
|
||||
return (
|
||||
@@ -21,20 +41,27 @@ 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 (
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-xs text-slate-300 border-collapse min-w-175">
|
||||
<table className="w-full text-xs text-slate-300 border-collapse min-w-[780px]">
|
||||
<thead>
|
||||
<tr className="text-[10px] text-slate-400 uppercase tracking-wider border-b border-slate-700">
|
||||
<tr className="text-[10px] text-slate-400 uppercase tracking-wider border-b border-slate-700 sticky top-0 bg-slate-950 z-10">
|
||||
<th className="text-left px-3 py-2 font-medium">Processo</th>
|
||||
<th className="text-right px-3 py-2 font-medium">Arrival</th>
|
||||
<th className="text-right px-3 py-2 font-medium">Start</th>
|
||||
<th className="text-right px-3 py-2 font-medium">Finish</th>
|
||||
<th className="text-right px-3 py-2 font-medium">Response</th>
|
||||
<th className="text-right px-3 py-2 font-medium">Turnaround</th>
|
||||
<th className="text-right px-3 py-2 font-medium">Waiting</th>
|
||||
<th className="text-right px-3 py-2 font-medium">CPU Time</th>
|
||||
<th className="text-right px-3 py-2 font-medium">I/O Time</th>
|
||||
<th className="text-right px-3 py-2 font-medium text-amber-400">Response</th>
|
||||
<th className="text-right px-3 py-2 font-medium text-blue-400">Turnaround</th>
|
||||
<th className="text-right px-3 py-2 font-medium text-orange-400">Waiting</th>
|
||||
<th className="text-right px-3 py-2 font-medium text-emerald-400">CPU</th>
|
||||
<th className="text-right px-3 py-2 font-medium text-cyan-400">I/O</th>
|
||||
<th className="text-right px-3 py-2 font-medium">CPU%</th>
|
||||
<th className="text-center px-3 py-2 font-medium">Bound</th>
|
||||
</tr>
|
||||
@@ -47,8 +74,9 @@ const MetricsTable = ({ metrics, processMap }: MetricsTableProps) => {
|
||||
return (
|
||||
<tr
|
||||
key={m.processId}
|
||||
className={`border-b border-slate-800 hover:bg-slate-800/60 transition-colors ${i % 2 === 0 ? '' : 'bg-slate-900/30'}`}
|
||||
className={`border-b border-slate-800/80 hover:bg-slate-800/50 transition-colors ${i % 2 === 0 ? '' : 'bg-slate-900/20'}`}
|
||||
>
|
||||
{/* Process name */}
|
||||
<td className="px-3 py-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<span
|
||||
@@ -56,22 +84,55 @@ const MetricsTable = ({ metrics, processMap }: MetricsTableProps) => {
|
||||
style={{ backgroundColor: color }}
|
||||
/>
|
||||
{proc?.pid !== undefined && (
|
||||
<span className="text-[11px] font-mono font-semibold text-slate-400 bg-slate-700 px-1.5 py-0.5 rounded shrink-0">
|
||||
<span className="text-[10px] font-mono font-semibold text-slate-400 bg-slate-700 px-1.5 py-0.5 rounded shrink-0">
|
||||
#{proc.pid}
|
||||
</span>
|
||||
)}
|
||||
<span className="font-medium text-slate-200">{name}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="text-right px-3 py-2 font-mono">{m.arrivalTime}</td>
|
||||
<td className="text-right px-3 py-2 font-mono">{m.startTick}</td>
|
||||
<td className="text-right px-3 py-2 font-mono">{m.finishTick}</td>
|
||||
<td className="text-right px-3 py-2 font-mono text-amber-300">{m.responseTime}</td>
|
||||
<td className="text-right px-3 py-2 font-mono text-blue-300">{m.turnaroundTime}</td>
|
||||
<td className="text-right px-3 py-2 font-mono text-orange-300">{m.waitingTime}</td>
|
||||
<td className="text-right px-3 py-2 font-mono text-emerald-300">{m.cpuTime}</td>
|
||||
<td className="text-right px-3 py-2 font-mono text-cyan-300">{m.ioTime}</td>
|
||||
<td className="text-right px-3 py-2 font-mono">{m.cpuUtilization.toFixed(1)}%</td>
|
||||
|
||||
{/* Plain timestamps */}
|
||||
<td className="text-right px-3 py-2 font-mono text-slate-400">{m.arrivalTime}</td>
|
||||
<td className="text-right px-3 py-2 font-mono text-slate-400">{m.startTick}</td>
|
||||
<td className="text-right px-3 py-2 font-mono text-slate-400">{m.finishTick}</td>
|
||||
|
||||
{/* Cells with inline mini-bars */}
|
||||
<td className="px-3 py-2 text-amber-300">
|
||||
<MiniBar value={m.responseTime} max={maxResponse} color="#f59e0b" />
|
||||
</td>
|
||||
<td className="px-3 py-2 text-blue-300">
|
||||
<MiniBar value={m.turnaroundTime} max={maxTurnaround} color="#60a5fa" />
|
||||
</td>
|
||||
<td className="px-3 py-2 text-orange-300">
|
||||
<MiniBar value={m.waitingTime} max={maxWaiting} color="#fb923c" />
|
||||
</td>
|
||||
<td className="px-3 py-2 text-emerald-300">
|
||||
<MiniBar value={m.cpuTime} max={maxCpu} color="#34d399" />
|
||||
</td>
|
||||
<td className="px-3 py-2 text-cyan-300">
|
||||
<MiniBar value={m.ioTime} max={maxIo} color="#22d3ee" />
|
||||
</td>
|
||||
|
||||
{/* CPU utilization with a tiny arc/bar */}
|
||||
<td className="px-3 py-2">
|
||||
<div className="flex items-center justify-end gap-1.5">
|
||||
<div className="w-10 h-1.5 bg-slate-700 rounded-full overflow-hidden">
|
||||
<div
|
||||
className="h-full rounded-full transition-all duration-300"
|
||||
style={{
|
||||
width: `${Math.min(100, m.cpuUtilization)}%`,
|
||||
background: `linear-gradient(90deg, #a78bfa, #60a5fa)`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<span className="font-mono text-slate-300 w-9 text-right">
|
||||
{m.cpuUtilization.toFixed(0)}%
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
{/* Bound badge */}
|
||||
<td className="text-center px-3 py-2">
|
||||
<Badge variant={boundVariant(m.boundType)}>{m.boundType}</Badge>
|
||||
</td>
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
+24
-13
@@ -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;
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
+39
-20
@@ -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;
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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<SchedulingAlgorithm, SchedulerAlgorithm> = {
|
||||
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<SchedulingAlgorithm, BaseAlgorithm> = {
|
||||
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(),
|
||||
};
|
||||
@@ -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 {
|
||||
/** 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 },
|
||||
];
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
// Fallback: assign to last queue if priority is out of all defined ranges.
|
||||
return queues.length - 1;
|
||||
}
|
||||
}
|
||||
|
||||
/** Select the best candidate from a sub-queue using the queue's algorithm. */
|
||||
function selectWithinQueue(
|
||||
/**
|
||||
* 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 {
|
||||
): ProcessRuntime | null {
|
||||
if (candidates.length === 0) return null;
|
||||
|
||||
if (queueDef.algorithm === "RR") {
|
||||
// RR: dispatch in insertion order (engine tail-appends on preemption).
|
||||
// RR: despacha na ordem de inserção (engine mantém FIFO).
|
||||
return candidates[0]!;
|
||||
}
|
||||
|
||||
if (queueDef.algorithm === "FCFS") {
|
||||
// FCFS: dispatch earliest-arriving process (by arrivalTick, then processId).
|
||||
// FCFS: despacha o de chegada mais antiga (arrivalTick, depois processId).
|
||||
return candidates.reduce((best, rt) =>
|
||||
tiebreak(rt, best) < 0 ? rt : best,
|
||||
BaseAlgorithm.tiebreak(rt, best) < 0 ? rt : best,
|
||||
);
|
||||
}
|
||||
|
||||
// PRIORITY_NP: dispatch the process with the lowest currentPriority number.
|
||||
// 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 && tiebreak(rt, best) < 0)
|
||||
(rt.currentPriority === best.currentPriority &&
|
||||
BaseAlgorithm.tiebreak(rt, best) < 0)
|
||||
) {
|
||||
best = rt;
|
||||
}
|
||||
}
|
||||
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 },
|
||||
];
|
||||
|
||||
export const multilevel: SchedulerAlgorithm = {
|
||||
select(readyQueue, config): ProcessRuntime | null {
|
||||
const queues = config.mlqQueues ?? DEFAULT_QUEUES;
|
||||
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;
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
+27
-13
@@ -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;
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
+63
-32
@@ -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;
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
+201
-74
@@ -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<string, ProcessRuntime>;
|
||||
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,9 +140,18 @@ export class SimulationEngine {
|
||||
this._readyQueue.push(rt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Preemption & Quantum Check (from previous tick's state)
|
||||
if (this._cpuProcess !== null && this._readyQueue.length > 0) {
|
||||
/**
|
||||
* 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 &&
|
||||
@@ -72,14 +164,25 @@ export class SimulationEngine {
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Dispatch / Context Switch
|
||||
if (this._contextSwitchRemaining > 0) {
|
||||
// Doing context switch this tick
|
||||
// We don't dispatch.
|
||||
} else {
|
||||
if (this._cpuProcess === null && this._readyQueue.length > 0) {
|
||||
/**
|
||||
* 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) {
|
||||
if (next === null) return;
|
||||
|
||||
const idx = this._readyQueue.indexOf(next);
|
||||
if (idx !== -1) this._readyQueue.splice(idx, 1);
|
||||
|
||||
@@ -99,13 +202,17 @@ export class SimulationEngine {
|
||||
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,21 +232,48 @@ 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
|
||||
) {
|
||||
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) {
|
||||
/**
|
||||
* 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)!;
|
||||
@@ -162,7 +296,16 @@ export class SimulationEngine {
|
||||
}
|
||||
}
|
||||
|
||||
// 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<string, StateLabel> = {};
|
||||
for (const [id, rt] of this._runtimes) {
|
||||
@@ -244,13 +369,14 @@ export class SimulationEngine {
|
||||
: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Helper: initialise a ProcessRuntime from a Process
|
||||
// ============================================================
|
||||
// ─── Inicialização ────────────────────────────────────────────────────────
|
||||
|
||||
function initRuntime(p: Process, config: SchedulerConfig): ProcessRuntime {
|
||||
/**
|
||||
* 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`);
|
||||
@@ -270,4 +396,5 @@ function initRuntime(p: Process, config: SchedulerConfig): ProcessRuntime {
|
||||
quantumRemaining: config.quantum,
|
||||
currentPriority: p.priority,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user