diff --git a/dist.tar.gz b/dist.tar.gz index a3835f3..db8f039 100644 Binary files a/dist.tar.gz and b/dist.tar.gz differ diff --git a/src/components/MetricsPanel/MetricsTable.tsx b/src/components/MetricsPanel/MetricsTable.tsx index 7602d84..2d9ee13 100644 --- a/src/components/MetricsPanel/MetricsTable.tsx +++ b/src/components/MetricsPanel/MetricsTable.tsx @@ -23,7 +23,7 @@ const MetricsTable = ({ metrics, processMap }: MetricsTableProps) => { return (
- +
@@ -55,6 +55,11 @@ const MetricsTable = ({ metrics, processMap }: MetricsTableProps) => { className="inline-block w-2.5 h-2.5 rounded-full shrink-0" style={{ backgroundColor: color }} /> + {proc?.pid !== undefined && ( + + #{proc.pid} + + )} {name} diff --git a/src/components/ProcessPanel/ProcessCard.tsx b/src/components/ProcessPanel/ProcessCard.tsx index 85a3bc9..92e40ab 100644 --- a/src/components/ProcessPanel/ProcessCard.tsx +++ b/src/components/ProcessPanel/ProcessCard.tsx @@ -24,7 +24,7 @@ const ProcessCard = ({ process, onEdit, onRemove }: ProcessCardProps) => { > {/* Color indicator */} )} + + {/* MLQ Queue Configuration */} + {algo === 'MULTILEVEL' && ( + setConfig({ mlqQueues: queues })} + /> + )} ); }; diff --git a/src/components/__tests__/SchedulerConfig.test.tsx b/src/components/__tests__/SchedulerConfig.test.tsx index 3c6a324..5f278ee 100644 --- a/src/components/__tests__/SchedulerConfig.test.tsx +++ b/src/components/__tests__/SchedulerConfig.test.tsx @@ -65,10 +65,10 @@ describe('SchedulerConfig', () => { expect(screen.getByLabelText('Quantum do Round Robin')).toBeInTheDocument(); }); - it('shows Quantum field when algorithm is MULTILEVEL', () => { + it('shows MLQ queue config panel when algorithm is MULTILEVEL', () => { setAlgorithm('MULTILEVEL'); render(); - expect(screen.getByTestId('quantum-field')).toBeInTheDocument(); + expect(screen.getByTestId('mlq-queues-config')).toBeInTheDocument(); }); it('does NOT show Quantum field for SJF_NP', () => { diff --git a/src/core/__tests__/multilevel.test.ts b/src/core/__tests__/multilevel.test.ts new file mode 100644 index 0000000..8ec1d41 --- /dev/null +++ b/src/core/__tests__/multilevel.test.ts @@ -0,0 +1,234 @@ +import { describe, it, expect } from "vitest"; +import { multilevel } from "../algorithms/multilevel"; +import { SimulationEngine } from "../engine"; +import type { MlqQueueDef, Process, ProcessRuntime, SchedulerConfig } from "../types"; + +// ---------------------------------------------------------------- +// Helpers +// ---------------------------------------------------------------- + +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 }, +]; + +function makeRt( + id: string, + priority: number, + arrivalTick = 0, + quantumRemaining = 2, +): ProcessRuntime { + return { + processId: id, + state: "Ready", + remainingBurst: 4, + burstIndex: 0, + arrivalTick, + startTick: null, + finishTick: null, + waitingTime: 0, + cpuTime: 0, + ioTime: 0, + responseTime: null, + quantumRemaining, + currentPriority: priority, + }; +} + +const cfg = (overrides: Partial = {}): SchedulerConfig => ({ + algorithm: "MULTILEVEL", + quantum: 2, + contextSwitchTime: 0, + isPreemptive: false, + agingEnabled: false, + agingInterval: 5, + mlqQueues: DEFAULT_QUEUES, + ...overrides, +}); + +const p = ( + id: string, + arrival: number, + cpuDuration: number, + priority = 5, +): Process => ({ + id, + name: id, + arrivalTime: arrival, + priority, + color: "#fff", + bursts: [{ type: "cpu", duration: cpuDuration }], +}); + +// ---------------------------------------------------------------- +// Unit: select() +// ---------------------------------------------------------------- + +describe("MULTILEVEL select()", () => { + it("returns null for empty queue", () => { + expect(multilevel.select([], cfg())).toBeNull(); + }); + + it("selects from queue 0 (highest priority) when it has processes", () => { + const q0 = makeRt("hi", 1); // priority 1 → queue 0 (1–3) + const q1 = makeRt("mid", 5); // priority 5 → queue 1 (4–7) + const q2 = makeRt("lo", 9); // priority 9 → queue 2 (8–10) + expect(multilevel.select([q1, q2, q0], cfg())?.processId).toBe("hi"); + }); + + it("falls through to queue 1 when queue 0 is empty", () => { + const q1a = makeRt("m1", 4, 0); + const q1b = makeRt("m2", 7, 1); + const q2 = makeRt("lo", 9, 0); + // queue 0 empty — queue 1 processes present + expect(multilevel.select([q1a, q1b, q2], cfg())?.processId).toBe("m1"); + }); + + it("falls through to queue 2 (FCFS) when queues 0 and 1 are empty", () => { + const a = makeRt("a", 10, 5); // priority 10 → queue 2 + const b = makeRt("b", 8, 0); // priority 8 → queue 2, earlier arrival + expect(multilevel.select([a, b], cfg())?.processId).toBe("b"); + }); + + it("queue 1 (PRIORITY_NP) selects by lowest priority number", () => { + const low = makeRt("lo", 7, 0); // priority 7 + const high = makeRt("hi", 4, 0); // priority 4 (highest in band) + const mid = makeRt("mid", 6, 0); // priority 6 + expect(multilevel.select([low, mid, high], cfg())?.processId).toBe("hi"); + }); + + it("queue 0 (RR) returns first in insertion order", () => { + const a = makeRt("a", 1, 0); + const b = makeRt("b", 2, 1); + const c = makeRt("c", 3, 2); + expect(multilevel.select([b, a, c], cfg())?.processId).toBe("b"); + expect(multilevel.select([c, b, a], cfg())?.processId).toBe("c"); + }); +}); + +// ---------------------------------------------------------------- +// Unit: shouldPreempt() +// ---------------------------------------------------------------- + +describe("MULTILEVEL shouldPreempt()", () => { + it("preempts when candidate is in a strictly higher-priority queue", () => { + const running = makeRt("lo", 9); // queue 2 + const candidate = makeRt("hi", 1); // queue 0 + expect(multilevel.shouldPreempt(running, candidate, cfg())).toBe(true); + }); + + it("preempts across adjacent queues", () => { + const running = makeRt("mid", 5); // queue 1 + const candidate = makeRt("hi", 2); // queue 0 + expect(multilevel.shouldPreempt(running, candidate, cfg())).toBe(true); + }); + + it("does NOT preempt for same queue", () => { + const a = makeRt("a", 1); // queue 0 + const b = makeRt("b", 2); // queue 0 + expect(multilevel.shouldPreempt(a, b, cfg())).toBe(false); + }); + + it("does NOT preempt when candidate is in a lower-priority queue", () => { + const running = makeRt("hi", 1); // queue 0 + const candidate = makeRt("lo", 9); // queue 2 + expect(multilevel.shouldPreempt(running, candidate, cfg())).toBe(false); + }); +}); + +// ---------------------------------------------------------------- +// Unit: isQuantumExpired() +// ---------------------------------------------------------------- + +describe("MULTILEVEL isQuantumExpired()", () => { + it("returns true for a RR queue process with quantumRemaining <= 0", () => { + const rt = makeRt("hi", 1, 0, 0); // queue 0 → RR + expect(multilevel.isQuantumExpired(rt, cfg())).toBe(true); + }); + + it("returns false for a RR queue process with quantum remaining", () => { + const rt = makeRt("hi", 2, 0, 1); // queue 0 → RR, still has quantum + expect(multilevel.isQuantumExpired(rt, cfg())).toBe(false); + }); + + it("returns false for a FCFS queue process even if quantumRemaining is 0", () => { + const rt = makeRt("lo", 9, 0, 0); // queue 2 → FCFS + expect(multilevel.isQuantumExpired(rt, cfg())).toBe(false); + }); + + it("returns false for a PRIORITY_NP queue process", () => { + const rt = makeRt("mid", 5, 0, 0); // queue 1 → PRIORITY_NP + expect(multilevel.isQuantumExpired(rt, cfg())).toBe(false); + }); +}); + +// ---------------------------------------------------------------- +// Integration +// ---------------------------------------------------------------- + +describe("MULTILEVEL integration — strict queue ordering", () => { + it("queue 0 processes run before queue 1 and queue 2 (CT-MLQ-01)", () => { + // P1: priority=1 (queue 0), CPU:3 + // P2: priority=5 (queue 1), CPU:3 + // P3: priority=9 (queue 2), CPU:3 + // All arrive at 0. Queue 0 runs first, then queue 1, then queue 2. + const processes = [p("p2", 0, 3, 5), p("p3", 0, 3, 9), p("p1", 0, 3, 1)]; + const engine = new SimulationEngine(processes, cfg()); + const ticks = engine.runAll(); + const cpu = ticks.map((t) => t.cpuProcess); + + expect(cpu.slice(0, 3).every((id) => id === "p1")).toBe(true); + expect(cpu.slice(3, 6).every((id) => id === "p2")).toBe(true); + expect(cpu.slice(6, 9).every((id) => id === "p3")).toBe(true); + expect(cpu).toHaveLength(9); + }); + + it("queue 0 RR rotates with quantum=2 (CT-MLQ-02)", () => { + // P1 and P2 both in queue 0 (priority 1), CPU:4 each, quantum=2 + const processes = [p("p1", 0, 4, 1), p("p2", 0, 4, 1)]; + const engine = new SimulationEngine(processes, cfg()); + const ticks = engine.runAll(); + const cpu = ticks.map((t) => t.cpuProcess); + + expect(cpu[0]).toBe("p1"); + expect(cpu[1]).toBe("p1"); + expect(cpu[2]).toBe("p2"); + expect(cpu[3]).toBe("p2"); + expect(cpu[4]).toBe("p1"); + expect(cpu[5]).toBe("p1"); + expect(cpu[6]).toBe("p2"); + expect(cpu[7]).toBe("p2"); + expect(cpu).toHaveLength(8); + }); + + it("high-priority queue process preempts lower-queue running process (CT-MLQ-03)", () => { + // P1: priority=9 (queue 2), arrives at 0, CPU:6 + // P2: priority=1 (queue 0), arrives at 2, CPU:3 + // P1 runs ticks 0–1, P2 preempts at tick 2 + const processes = [p("p1", 0, 6, 9), p("p2", 2, 3, 1)]; + const engine = new SimulationEngine(processes, cfg()); + const ticks = engine.runAll(); + const cpu = ticks.map((t) => t.cpuProcess); + + expect(cpu[0]).toBe("p1"); + expect(cpu[1]).toBe("p1"); + expect(cpu[2]).toBe("p2"); + expect(cpu[3]).toBe("p2"); + expect(cpu[4]).toBe("p2"); + // P1 resumes after P2 terminates + expect(cpu[5]).toBe("p1"); + expect(cpu).toHaveLength(9); + }); + + it("all processes terminate correctly", () => { + const processes = [p("p1", 0, 3, 1), p("p2", 0, 3, 5), p("p3", 0, 3, 9)]; + const engine = new SimulationEngine(processes, cfg()); + engine.runAll(); + const rts = engine.runtimeStates; + expect(rts.every((rt) => rt.state === "Terminated")).toBe(true); + expect(rts.find((r) => r.processId === "p1")!.cpuTime).toBe(3); + expect(rts.find((r) => r.processId === "p2")!.cpuTime).toBe(3); + expect(rts.find((r) => r.processId === "p3")!.cpuTime).toBe(3); + }); +}); diff --git a/src/core/algorithms/multilevel.ts b/src/core/algorithms/multilevel.ts index a23acc7..218994d 100644 --- a/src/core/algorithms/multilevel.ts +++ b/src/core/algorithms/multilevel.ts @@ -1,19 +1,109 @@ +import type { MlqQueueDef, ProcessRuntime } from "../types"; import type { SchedulerAlgorithm } from "./index"; +import { tiebreak } from "./utils"; /** - * MULTILEVEL — Multilevel Queue (stub) - * Not yet implemented. Deferred to a later phase. + * MULTILEVEL — Multilevel Queue Scheduling + * + * 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). + * + * 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. + * + * 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. + * + * 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. */ + +/** Returns the index of the queue that owns this priority value (0 = highest). */ +function getQueueIndex(priority: number, queues: readonly MlqQueueDef[]): number { + for (let i = 0; i < queues.length; i++) { + const q = queues[i]!; + if (priority >= q.priorityMin && priority <= q.priorityMax) return i; + } + // Fallback: assign to last queue if priority is out of all defined ranges. + return queues.length - 1; +} + +/** Select the best candidate from a sub-queue using the queue's algorithm. */ +function selectWithinQueue( + candidates: readonly ProcessRuntime[], + queueDef: MlqQueueDef, +): ProcessRuntime | null { + if (candidates.length === 0) return null; + + if (queueDef.algorithm === "RR") { + // RR: dispatch in insertion order (engine tail-appends on preemption). + return candidates[0]!; + } + + if (queueDef.algorithm === "FCFS") { + // FCFS: dispatch earliest-arriving process (by arrivalTick, then processId). + return candidates.reduce((best, rt) => + tiebreak(rt, best) < 0 ? rt : best, + ); + } + + // PRIORITY_NP: dispatch the process with the lowest currentPriority number. + let best = candidates[0]!; + for (let i = 1; i < candidates.length; i++) { + const rt = candidates[i]!; + if ( + rt.currentPriority < best.currentPriority || + (rt.currentPriority === best.currentPriority && tiebreak(rt, best) < 0) + ) { + best = rt; + } + } + 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() { - throw new Error("MULTILEVEL scheduling is not yet implemented"); + select(readyQueue, config): ProcessRuntime | null { + const queues = config.mlqQueues ?? DEFAULT_QUEUES; + if (queues.length === 0) return null; + + // Iterate from highest-priority queue (index 0) to lowest. + for (let qi = 0; qi < queues.length; qi++) { + const queueDef = queues[qi]!; + const candidates = readyQueue.filter( + (rt) => getQueueIndex(rt.currentPriority, queues) === qi, + ); + const selected = selectWithinQueue(candidates, queueDef); + if (selected !== null) return selected; + } + return null; }, - shouldPreempt() { - throw new Error("MULTILEVEL scheduling is not yet implemented"); + 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. + return candidateQueueIdx < currentQueueIdx; }, - isQuantumExpired() { - throw new Error("MULTILEVEL scheduling is not yet implemented"); + isQuantumExpired(runtime, config): boolean { + const queues = config.mlqQueues ?? DEFAULT_QUEUES; + const queueIdx = getQueueIndex(runtime.currentPriority, queues); + const queueDef = queues[queueIdx]; + if (!queueDef) return false; + return queueDef.algorithm === "RR" && runtime.quantumRemaining <= 0; }, }; diff --git a/src/core/presets.ts b/src/core/presets.ts index 1677c55..7e23d61 100644 --- a/src/core/presets.ts +++ b/src/core/presets.ts @@ -1,4 +1,4 @@ -import type { Process, SchedulerConfig } from "./types"; +import type { MlqQueueDef, Process, SchedulerConfig } from "./types"; export interface Preset { name: string; @@ -7,6 +7,12 @@ export interface Preset { config: SchedulerConfig; } +const DEFAULT_MLQ_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 }, +]; + const defaultConfig = ( overrides: Partial = {}, ): SchedulerConfig => ({ @@ -16,6 +22,7 @@ const defaultConfig = ( isPreemptive: false, agingEnabled: false, agingInterval: 5, + mlqQueues: DEFAULT_MLQ_QUEUES, ...overrides, }); @@ -24,10 +31,10 @@ export const presets: Preset[] = [ name: "classic_fcfs", description: "4 simple processes, no I/O — demonstrates FCFS order", processes: [ - { id: "p1", name: "P1", arrivalTime: 0, priority: 5, color: "#4FC3F7", bursts: [{ type: "cpu", duration: 5 }] }, - { id: "p2", name: "P2", arrivalTime: 1, priority: 5, color: "#81C784", bursts: [{ type: "cpu", duration: 3 }] }, - { id: "p3", name: "P3", arrivalTime: 2, priority: 5, color: "#FFB74D", bursts: [{ type: "cpu", duration: 1 }] }, - { id: "p4", name: "P4", arrivalTime: 3, priority: 5, color: "#F06292", bursts: [{ type: "cpu", duration: 7 }] }, + { pid: 1, id: "p1", name: "P1", arrivalTime: 0, priority: 5, color: "#4FC3F7", bursts: [{ type: "cpu", duration: 5 }] }, + { pid: 2, id: "p2", name: "P2", arrivalTime: 1, priority: 5, color: "#81C784", bursts: [{ type: "cpu", duration: 3 }] }, + { pid: 3, id: "p3", name: "P3", arrivalTime: 2, priority: 5, color: "#FFB74D", bursts: [{ type: "cpu", duration: 1 }] }, + { pid: 4, id: "p4", name: "P4", arrivalTime: 3, priority: 5, color: "#F06292", bursts: [{ type: "cpu", duration: 7 }] }, ], config: defaultConfig({ algorithm: "FCFS" }), }, @@ -35,11 +42,11 @@ export const presets: Preset[] = [ name: "rr_demo", description: "5 processes with quantum 3 — Round Robin rotation", processes: [ - { id: "p1", name: "P1", arrivalTime: 0, priority: 5, color: "#4FC3F7", bursts: [{ type: "cpu", duration: 6 }] }, - { id: "p2", name: "P2", arrivalTime: 0, priority: 5, color: "#81C784", bursts: [{ type: "cpu", duration: 4 }] }, - { id: "p3", name: "P3", arrivalTime: 1, priority: 5, color: "#FFB74D", bursts: [{ type: "cpu", duration: 5 }] }, - { id: "p4", name: "P4", arrivalTime: 2, priority: 5, color: "#F06292", bursts: [{ type: "cpu", duration: 2 }] }, - { id: "p5", name: "P5", arrivalTime: 3, priority: 5, color: "#CE93D8", bursts: [{ type: "cpu", duration: 3 }] }, + { pid: 1, id: "p1", name: "P1", arrivalTime: 0, priority: 5, color: "#4FC3F7", bursts: [{ type: "cpu", duration: 6 }] }, + { pid: 2, id: "p2", name: "P2", arrivalTime: 0, priority: 5, color: "#81C784", bursts: [{ type: "cpu", duration: 4 }] }, + { pid: 3, id: "p3", name: "P3", arrivalTime: 1, priority: 5, color: "#FFB74D", bursts: [{ type: "cpu", duration: 5 }] }, + { pid: 4, id: "p4", name: "P4", arrivalTime: 2, priority: 5, color: "#F06292", bursts: [{ type: "cpu", duration: 2 }] }, + { pid: 5, id: "p5", name: "P5", arrivalTime: 3, priority: 5, color: "#CE93D8", bursts: [{ type: "cpu", duration: 3 }] }, ], config: defaultConfig({ algorithm: "RR", quantum: 3 }), }, @@ -47,11 +54,11 @@ export const presets: Preset[] = [ name: "starvation", description: "High-priority processes starve P5 — demonstrates Priority NP starvation", processes: [ - { id: "p1", name: "P1", arrivalTime: 0, priority: 1, color: "#4FC3F7", bursts: [{ type: "cpu", duration: 3 }] }, - { id: "p2", name: "P2", arrivalTime: 1, priority: 1, color: "#81C784", bursts: [{ type: "cpu", duration: 3 }] }, - { id: "p3", name: "P3", arrivalTime: 2, priority: 1, color: "#FFB74D", bursts: [{ type: "cpu", duration: 3 }] }, - { id: "p4", name: "P4", arrivalTime: 3, priority: 1, color: "#F06292", bursts: [{ type: "cpu", duration: 3 }] }, - { id: "p5", name: "P5", arrivalTime: 0, priority: 10, color: "#CE93D8", bursts: [{ type: "cpu", duration: 5 }] }, + { pid: 1, id: "p1", name: "P1", arrivalTime: 0, priority: 1, color: "#4FC3F7", bursts: [{ type: "cpu", duration: 3 }] }, + { pid: 2, id: "p2", name: "P2", arrivalTime: 1, priority: 1, color: "#81C784", bursts: [{ type: "cpu", duration: 3 }] }, + { pid: 3, id: "p3", name: "P3", arrivalTime: 2, priority: 1, color: "#FFB74D", bursts: [{ type: "cpu", duration: 3 }] }, + { pid: 4, id: "p4", name: "P4", arrivalTime: 3, priority: 1, color: "#F06292", bursts: [{ type: "cpu", duration: 3 }] }, + { pid: 5, id: "p5", name: "P5", arrivalTime: 0, priority: 10, color: "#CE93D8", bursts: [{ type: "cpu", duration: 5 }] }, ], config: defaultConfig({ algorithm: "PRIORITY_NP" }), }, @@ -59,11 +66,11 @@ export const presets: Preset[] = [ name: "aging_fix", description: "Same starvation scenario fixed with aging", processes: [ - { id: "p1", name: "P1", arrivalTime: 0, priority: 1, color: "#4FC3F7", bursts: [{ type: "cpu", duration: 3 }] }, - { id: "p2", name: "P2", arrivalTime: 1, priority: 1, color: "#81C784", bursts: [{ type: "cpu", duration: 3 }] }, - { id: "p3", name: "P3", arrivalTime: 2, priority: 1, color: "#FFB74D", bursts: [{ type: "cpu", duration: 3 }] }, - { id: "p4", name: "P4", arrivalTime: 3, priority: 1, color: "#F06292", bursts: [{ type: "cpu", duration: 3 }] }, - { id: "p5", name: "P5", arrivalTime: 0, priority: 10, color: "#CE93D8", bursts: [{ type: "cpu", duration: 5 }] }, + { pid: 1, id: "p1", name: "P1", arrivalTime: 0, priority: 1, color: "#4FC3F7", bursts: [{ type: "cpu", duration: 3 }] }, + { pid: 2, id: "p2", name: "P2", arrivalTime: 1, priority: 1, color: "#81C784", bursts: [{ type: "cpu", duration: 3 }] }, + { pid: 3, id: "p3", name: "P3", arrivalTime: 2, priority: 1, color: "#FFB74D", bursts: [{ type: "cpu", duration: 3 }] }, + { pid: 4, id: "p4", name: "P4", arrivalTime: 3, priority: 1, color: "#F06292", bursts: [{ type: "cpu", duration: 3 }] }, + { pid: 5, id: "p5", name: "P5", arrivalTime: 0, priority: 10, color: "#CE93D8", bursts: [{ type: "cpu", duration: 5 }] }, ], config: defaultConfig({ algorithm: "PRIORITY_P", isPreemptive: true, agingEnabled: true, agingInterval: 3 }), }, @@ -71,9 +78,9 @@ export const presets: Preset[] = [ name: "priority_rr_demo", description: "Priority RR: P3 (priority 1) preempts mid-run; P1 & P2 (priority 2) share CPU via RR", processes: [ - { id: "p1", name: "P1", arrivalTime: 0, priority: 2, color: "#4FC3F7", bursts: [{ type: "cpu", duration: 4 }] }, - { id: "p2", name: "P2", arrivalTime: 0, priority: 2, color: "#81C784", bursts: [{ type: "cpu", duration: 4 }] }, - { id: "p3", name: "P3", arrivalTime: 4, priority: 1, color: "#F06292", bursts: [{ type: "cpu", duration: 2 }] }, + { pid: 1, id: "p1", name: "P1", arrivalTime: 0, priority: 2, color: "#4FC3F7", bursts: [{ type: "cpu", duration: 4 }] }, + { pid: 2, id: "p2", name: "P2", arrivalTime: 0, priority: 2, color: "#81C784", bursts: [{ type: "cpu", duration: 4 }] }, + { pid: 3, id: "p3", name: "P3", arrivalTime: 4, priority: 1, color: "#F06292", bursts: [{ type: "cpu", duration: 2 }] }, ], config: defaultConfig({ algorithm: "PRIORITY_RR", quantum: 2 }), }, @@ -81,9 +88,9 @@ export const presets: Preset[] = [ name: "io_heavy", description: "Mixed CPU-bound and I/O-bound processes", processes: [ - { id: "p1", name: "P1", arrivalTime: 0, priority: 5, color: "#4FC3F7", bursts: [{ type: "cpu", duration: 6 }, { type: "io", duration: 1 }, { type: "cpu", duration: 2 }] }, - { id: "p2", name: "P2", arrivalTime: 0, priority: 5, color: "#81C784", bursts: [{ type: "cpu", duration: 1 }, { type: "io", duration: 8 }, { type: "cpu", duration: 1 }] }, - { id: "p3", name: "P3", arrivalTime: 2, priority: 5, color: "#FFB74D", bursts: [{ type: "cpu", duration: 4 }, { type: "io", duration: 2 }, { type: "cpu", duration: 4 }] }, + { pid: 1, id: "p1", name: "P1", arrivalTime: 0, priority: 5, color: "#4FC3F7", bursts: [{ type: "cpu", duration: 6 }, { type: "io", duration: 1 }, { type: "cpu", duration: 2 }] }, + { pid: 2, id: "p2", name: "P2", arrivalTime: 0, priority: 5, color: "#81C784", bursts: [{ type: "cpu", duration: 1 }, { type: "io", duration: 8 }, { type: "cpu", duration: 1 }] }, + { pid: 3, id: "p3", name: "P3", arrivalTime: 2, priority: 5, color: "#FFB74D", bursts: [{ type: "cpu", duration: 4 }, { type: "io", duration: 2 }, { type: "cpu", duration: 4 }] }, ], config: defaultConfig({ algorithm: "SJF_P", isPreemptive: true }), }, @@ -91,10 +98,10 @@ export const presets: Preset[] = [ name: "convoy_effect", description: "One long process blocks shorter ones — FCFS convoy vs SJF comparison", processes: [ - { id: "p1", name: "P1", arrivalTime: 0, priority: 5, color: "#EF5350", bursts: [{ type: "cpu", duration: 12 }] }, - { id: "p2", name: "P2", arrivalTime: 1, priority: 5, color: "#81C784", bursts: [{ type: "cpu", duration: 2 }] }, - { id: "p3", name: "P3", arrivalTime: 2, priority: 5, color: "#FFB74D", bursts: [{ type: "cpu", duration: 1 }] }, - { id: "p4", name: "P4", arrivalTime: 3, priority: 5, color: "#CE93D8", bursts: [{ type: "cpu", duration: 3 }] }, + { pid: 1, id: "p1", name: "P1", arrivalTime: 0, priority: 5, color: "#EF5350", bursts: [{ type: "cpu", duration: 12 }] }, + { pid: 2, id: "p2", name: "P2", arrivalTime: 1, priority: 5, color: "#81C784", bursts: [{ type: "cpu", duration: 2 }] }, + { pid: 3, id: "p3", name: "P3", arrivalTime: 2, priority: 5, color: "#FFB74D", bursts: [{ type: "cpu", duration: 1 }] }, + { pid: 4, id: "p4", name: "P4", arrivalTime: 3, priority: 5, color: "#CE93D8", bursts: [{ type: "cpu", duration: 3 }] }, ], config: defaultConfig({ algorithm: "FCFS" }), }, @@ -102,22 +109,22 @@ export const presets: Preset[] = [ name: "context_switch_demo", description: "3 processos FCFS com 2 ticks de overhead — veja as células vermelhas de Context Switch", processes: [ - { id: "p1", name: "P1", arrivalTime: 0, priority: 5, color: "#4FC3F7", bursts: [{ type: "cpu", duration: 3 }] }, - { id: "p2", name: "P2", arrivalTime: 0, priority: 5, color: "#81C784", bursts: [{ type: "cpu", duration: 3 }] }, - { id: "p3", name: "P3", arrivalTime: 0, priority: 5, color: "#FFB74D", bursts: [{ type: "cpu", duration: 3 }] }, + { pid: 1, id: "p1", name: "P1", arrivalTime: 0, priority: 5, color: "#4FC3F7", bursts: [{ type: "cpu", duration: 3 }] }, + { pid: 2, id: "p2", name: "P2", arrivalTime: 0, priority: 5, color: "#81C784", bursts: [{ type: "cpu", duration: 3 }] }, + { pid: 3, id: "p3", name: "P3", arrivalTime: 0, priority: 5, color: "#FFB74D", bursts: [{ type: "cpu", duration: 3 }] }, ], config: defaultConfig({ algorithm: "FCFS", contextSwitchTime: 2 }), }, { name: "multilevel_demo", - description: "Priority RR with 3 priority tiers — demonstrates multilevel scheduling", + description: "MLQ: 3 priority tiers — Q0 (1–3) RR, Q1 (4–7) Priority NP, Q2 (8–10) FCFS", processes: [ - { id: "p1", name: "P1", arrivalTime: 0, priority: 1, color: "#EF5350", bursts: [{ type: "cpu", duration: 4 }] }, - { id: "p2", name: "P2", arrivalTime: 0, priority: 1, color: "#FF8A65", bursts: [{ type: "cpu", duration: 3 }] }, - { id: "p3", name: "P3", arrivalTime: 2, priority: 2, color: "#81C784", bursts: [{ type: "cpu", duration: 5 }] }, - { id: "p4", name: "P4", arrivalTime: 2, priority: 2, color: "#4FC3F7", bursts: [{ type: "cpu", duration: 2 }] }, - { id: "p5", name: "P5", arrivalTime: 4, priority: 3, color: "#CE93D8", bursts: [{ type: "cpu", duration: 6 }] }, + { pid: 1, id: "p1", name: "P1", arrivalTime: 0, priority: 1, color: "#EF5350", bursts: [{ type: "cpu", duration: 4 }] }, + { pid: 2, id: "p2", name: "P2", arrivalTime: 0, priority: 1, color: "#FF8A65", bursts: [{ type: "cpu", duration: 3 }] }, + { pid: 3, id: "p3", name: "P3", arrivalTime: 2, priority: 5, color: "#81C784", bursts: [{ type: "cpu", duration: 5 }] }, + { pid: 4, id: "p4", name: "P4", arrivalTime: 2, priority: 5, color: "#4FC3F7", bursts: [{ type: "cpu", duration: 2 }] }, + { pid: 5, id: "p5", name: "P5", arrivalTime: 4, priority: 9, color: "#CE93D8", bursts: [{ type: "cpu", duration: 6 }] }, ], - config: defaultConfig({ algorithm: "PRIORITY_RR", quantum: 2 }), + config: defaultConfig({ algorithm: "MULTILEVEL", quantum: 2, mlqQueues: DEFAULT_MLQ_QUEUES }), }, ]; diff --git a/src/core/types.ts b/src/core/types.ts index cb09458..c1ef6de 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -21,6 +21,7 @@ export interface BurstSegment { } export interface Process { + pid?: number; // auto-incremented sequential integer (1, 2, 3…); set by store id: string; name: string; arrivalTime: number; // tick of arrival (≥ 0) @@ -31,6 +32,13 @@ export interface Process { // --------------- Scheduler configuration -------------------- +export interface MlqQueueDef { + priorityMin: number; // inclusive lower bound + priorityMax: number; // inclusive upper bound + algorithm: "FCFS" | "RR" | "PRIORITY_NP"; + quantum: number; // used only when algorithm === "RR" +} + export interface SchedulerConfig { algorithm: SchedulingAlgorithm; quantum: number; // for RR (default 2) @@ -38,6 +46,7 @@ export interface SchedulerConfig { isPreemptive: boolean; // applicable to SJF and Priority agingEnabled: boolean; // prevent starvation in Priority scheduling agingInterval: number; // ticks in Ready before priority-- (min 1) + mlqQueues?: MlqQueueDef[]; // queue definitions for MULTILEVEL algorithm } // --------------- Runtime state ------------------------------ diff --git a/src/store/processStore.ts b/src/store/processStore.ts index 7ece227..b489951 100644 --- a/src/store/processStore.ts +++ b/src/store/processStore.ts @@ -1,11 +1,17 @@ import { create } from "zustand"; import { persist } from "zustand/middleware"; -import type { Process, SchedulerConfig } from "@core/types"; +import type { MlqQueueDef, Process, SchedulerConfig } from "@core/types"; // ============================================================ // Defaults // ============================================================ +const DEFAULT_MLQ_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 }, +]; + const DEFAULT_CONFIG: SchedulerConfig = { algorithm: "FCFS", quantum: 2, @@ -13,6 +19,7 @@ const DEFAULT_CONFIG: SchedulerConfig = { isPreemptive: false, agingEnabled: false, agingInterval: 5, + mlqQueues: DEFAULT_MLQ_QUEUES, }; // ============================================================ @@ -22,6 +29,7 @@ const DEFAULT_CONFIG: SchedulerConfig = { interface ProcessStore { processes: Process[]; config: SchedulerConfig; + _nextPid: number; addProcess: (p: Process) => void; updateProcess: (id: string, updates: Partial) => void; @@ -36,9 +44,13 @@ export const useProcessStore = create()( (set) => ({ processes: [], config: DEFAULT_CONFIG, + _nextPid: 1, addProcess: (p) => - set((s) => ({ processes: [...s.processes, p] })), + set((s) => ({ + processes: [...s.processes, { ...p, pid: s._nextPid }], + _nextPid: s._nextPid + 1, + })), updateProcess: (id, updates) => set((s) => ({ @@ -50,12 +62,17 @@ export const useProcessStore = create()( removeProcess: (id) => set((s) => ({ processes: s.processes.filter((p) => p.id !== id) })), - clearProcesses: () => set({ processes: [] }), + clearProcesses: () => set({ processes: [], _nextPid: 1 }), setConfig: (updates) => set((s) => ({ config: { ...s.config, ...updates } })), - loadPreset: (processes, config) => set({ processes, config }), + loadPreset: (processes, config) => + set({ + processes, + config, + _nextPid: processes.reduce((max, p) => Math.max(max, p.pid ?? 0), 0) + 1, + }), }), { name: "pss:session" }, ),
Processo