Cpuschedulingalgorithms
R
import copy
import threading
import time
import tkinter as tk
from collections.abc import Generator
from tkinter import messagebox, ttk
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
# ===================== Scheduler Engine ===================== #
class SchedulerEngine:
def __init__(self, processes: list[dict], algorithm: str, quantum: int = 2) -> None:
"""
Initializes the scheduler engine.
>>> processes = [{"pid": "P1", "arrival": 0, "burst": 4}]
>>> engine = SchedulerEngine(processes, "FCFS")
>>> isinstance(engine, SchedulerEngine)
True
"""
self.original = copy.deepcopy(processes)
self.processes = [p.copy() for p in processes]
self.algorithm = algorithm
self.quantum = quantum
for process in self.processes:
process["remaining"] = process["burst"]
self.timeline: list[tuple[int, str]] = [] # [(time, pid)]
self.stats: list[tuple] = []
def simulate(self) -> Generator[tuple[int, str | None, list[str]]]:
"""
Runs the selected CPU scheduling algorithm.
>>> processes = [{"pid": "P1", "arrival": 0, "burst": 2}]
>>> engine = SchedulerEngine(processes, "FCFS")
>>> list(engine.simulate())[:2]
[(0, 'P1', []), (1, 'P1', [])]
"""
algo = self.algorithm.lower()
if algo == "fcfs":
yield from self._simulate_fcfs()
elif algo == "sjf (non-preemptive)":
yield from self._simulate_sjf_np()
elif algo == "sjf (preemptive)":
yield from self._simulate_sjf_p()
elif algo == "priority (non-preemptive)":
yield from self._simulate_priority_np()
elif algo == "priority (preemptive)":
yield from self._simulate_priority_p()
elif algo == "round robin":
yield from self._simulate_rr()
self._calculate_stats()
# first come first serve
def _simulate_fcfs(self) -> Generator[tuple[int, str, list[str]]]:
"""
Simulates First Come First Serve scheduling.
>>> processes = [{"pid": "P1", "arrival": 0, "burst": 2}]
>>> engine = SchedulerEngine(processes, "FCFS")
>>> next(engine._simulate_fcfs())
(0, 'P1', [])
"""
t = 0
processes = sorted(self.processes, key=lambda process: process["arrival"])
for process in processes:
t = max(t, process["arrival"])
for _ in range(process["burst"]):
self.timeline.append((t, process["pid"]))
yield (t, process["pid"], [])
t += 1
process["completion"] = t
# shortest job first non preemptive
def _simulate_sjf_np(self) -> Generator[tuple[int, str | None, list[str]]]:
"""
Simulates Shortest Job First (Non-Preemptive).
>>> processes = [{"pid": "P1", "arrival": 0, "burst": 2}]
>>> engine = SchedulerEngine(processes, "SJF (Non-Preemptive)")
>>> isinstance(engine._simulate_sjf_np(), Generator)
True
"""
t = 0
processes = sorted(self.processes, key=lambda process: process["arrival"])
done = 0
while done < len(processes):
ready = [
process
for process in processes
if process["arrival"] <= t and "completion" not in process
]
if not ready:
t += 1
yield (t, None, [])
continue
process = min(ready, key=lambda x: x["burst"])
for _ in range(process["burst"]):
self.timeline.append((t, process["pid"]))
yield (t, process["pid"], [])
t += 1
process["completion"] = t
done += 1
# shortest job first preemptive
def _simulate_sjf_p(self) -> Generator[tuple[int, str | None, list[str]]]:
"""Simulates SJF Preemptive scheduling."""
t = 0
processes = sorted(self.processes, key=lambda process: process["arrival"])
done = 0
while done < len(processes):
ready = [
process
for process in processes
if process["arrival"] <= t and process["remaining"] > 0
]
if not ready:
t += 1
yield (t, None, [])
continue
process = min(ready, key=lambda x: x["remaining"])
self.timeline.append((t, process["pid"]))
yield (t, process["pid"], [])
process["remaining"] -= 1
if process["remaining"] == 0:
process["completion"] = t + 1
done += 1
t += 1
# priority non preemptive
def _simulate_priority_np(self) -> Generator[tuple[int, str | None, list[str]]]:
"""Simulates Priority (Non-Preemptive) scheduling."""
t = 0
done = 0
while done < len(self.processes):
ready = [
process
for process in self.processes
if process["arrival"] <= t and "completion" not in process
]
if not ready:
t += 1
yield (t, None, [])
continue
process = min(ready, key=lambda x: x["priority"])
for _ in range(process["burst"]):
self.timeline.append((t, process["pid"]))
yield (t, process["pid"], [])
t += 1
process["completion"] = t
done += 1
# priority preemptive
def _simulate_priority_p(self) -> Generator[tuple[int, str | None, list[str]]]:
"""Simulates Priority (Preemptive) scheduling."""
t = 0
done = 0
while done < len(self.processes):
ready = [
process
for process in self.processes
if process["arrival"] <= t and process["remaining"] > 0
]
if not ready:
t += 1
yield (t, None, [])
continue
process = min(ready, key=lambda x: x["priority"])
self.timeline.append((t, process["pid"]))
yield (t, process["pid"], [])
process["remaining"] -= 1
if process["remaining"] == 0:
process["completion"] = t + 1
done += 1
t += 1
# round robin
def _simulate_rr(self) -> Generator[tuple[int, str | None, list[str]]]:
"""Simulates Round Robin scheduling."""
t = 0
q: list[dict] = []
processes = sorted(self.processes, key=lambda process: process["arrival"])
i = 0
done = 0
while done < len(processes):
while i < len(processes) and processes[i]["arrival"] <= t:
q.append(processes[i])
i += 1
if not q:
t += 1
yield (t, None, [])
continue
process = q.pop(0)
burst = min(self.quantum, process["remaining"])
for _ in range(burst):
self.timeline.append((t, process["pid"]))
yield (t, process["pid"], [x["pid"] for x in q])
t += 1
process["remaining"] -= 1
while i < len(processes) and processes[i]["arrival"] <= t:
q.append(processes[i])
i += 1
if process["remaining"] > 0:
q.append(process)
else:
process["completion"] = t
done += 1
def _calculate_stats(self) -> None:
"""Calculates turnaround, waiting, and response times."""
for process in self.processes:
pid = process["pid"]
arrival = process["arrival"]
burst = process["burst"]
completion = process["completion"]
first_exec = next((t for t, pid2 in self.timeline if pid2 == pid), arrival)
tat = completion - arrival
wt = tat - burst
rt = first_exec - arrival
self.stats.append((pid, arrival, burst, completion, tat, wt, rt))
# ===================== Interface ===================== #
class CPUSchedulerGUI:
def __init__(self, root: tk.Tk) -> None:
"""Initializes the GUI window."""
self.root = root
self.root.title("CPU Scheduling Visualizer")
self.root.geometry("1000x700")
self.processes: list[dict] = []
self.setup_ui()
def setup_ui(self) -> None:
"""Sets up GUI widgets."""
top_frame = ttk.Frame(self.root)
top_frame.pack(pady=10)
self.tree = ttk.Treeview(
top_frame,
columns=("pid", "arrival", "burst", "priority"),
show="headings",
)
for col in self.tree["columns"]:
self.tree.heading(col, text=col.capitalize())
self.tree.pack(side="left")
form = ttk.Frame(top_frame)
form.pack(side="left", padx=10)
ttk.Label(form, text="PID").grid(row=0, column=0)
ttk.Label(form, text="Arrival").grid(row=1, column=0)
ttk.Label(form, text="Burst").grid(row=2, column=0)
ttk.Label(form, text="Priority").grid(row=3, column=0)
self.pid_e = ttk.Entry(form)
self.arrival_e = ttk.Entry(form)
self.burst_e = ttk.Entry(form)
self.priority_e = ttk.Entry(form)
self.pid_e.grid(row=0, column=1)
self.arrival_e.grid(row=1, column=1)
self.burst_e.grid(row=2, column=1)
self.priority_e.grid(row=3, column=1)
ttk.Button(
form,
text="Add",
command=self.add_process,
).grid(row=4, column=0, pady=5)
ttk.Button(
form,
text="Delete",
command=self.delete_process,
).grid(row=4, column=1)
algo_frame = ttk.Frame(self.root)
algo_frame.pack(pady=10)
ttk.Label(algo_frame, text="Algorithm:").pack(side="left")
self.algo_cb = ttk.Combobox(
algo_frame,
values=[
"FCFS",
"SJF (Non-Preemptive)",
"SJF (Preemptive)",
"Priority (Non-Preemptive)",
"Priority (Preemptive)",
"Round Robin",
],
)
self.algo_cb.current(0)
self.algo_cb.pack(side="left", padx=5)
ttk.Label(algo_frame, text="Quantum:").pack(side="left")
self.quantum_e = ttk.Entry(algo_frame, width=5)
self.quantum_e.insert(0, "2")
self.quantum_e.pack(side="left")
ttk.Button(
algo_frame,
text="Run",
command=self.run_scheduling,
).pack(side="left", padx=10)
self.ready_label = ttk.Label(self.root, text="Ready Queue:")
self.ready_list = tk.Listbox(self.root, height=3)
self.ready_label.pack_forget()
self.ready_list.pack_forget()
self.figure, self.ax = plt.subplots(figsize=(8, 3))
self.canvas = FigureCanvasTkAgg(self.figure, master=self.root)
self.canvas.get_tk_widget().pack()
self.result_box = ttk.Treeview(
self.root,
columns=("pid", "arrival", "burst", "completion", "tat", "wt", "rt"),
show="headings",
height=6,
)
for col in self.result_box["columns"]:
self.result_box.heading(col, text=col.upper())
self.result_box.pack(pady=10)
self.avg_label = ttk.Label(self.root, text="", font=("Arial", 11, "bold"))
self.avg_label.pack()
def add_process(self) -> None:
"""Adds a new process entry to the table."""
try:
pid = self.pid_e.get()
arrival = int(self.arrival_e.get())
burst = int(self.burst_e.get())
priority = int(self.priority_e.get() or 0)
self.processes.append(
{"pid": pid, "arrival": arrival, "burst": burst, "priority": priority}
)
self.tree.insert("", "end", values=(pid, arrival, burst, priority))
except ValueError:
messagebox.showerror("Error", "Invalid input")
def delete_process(self) -> None:
"""Deletes a selected process."""
if sel := self.tree.selection():
values = self.tree.item(sel[0])["values"]
if not values:
return
pid = values[0]
self.processes = [p for p in self.processes if p["pid"] != pid]
self.tree.delete(sel[0])
def run_scheduling(self) -> None:
"""Runs the selected scheduling algorithm."""
algo = self.algo_cb.get()
quantum = int(self.quantum_e.get() or 2)
if algo.lower() == "round robin":
self.ready_label.pack()
self.ready_list.pack()
else:
self.ready_label.pack_forget()
self.ready_list.pack_forget()
self.engine = SchedulerEngine(self.processes, algo, quantum)
threading.Thread(target=self.animate, daemon=True).start()
def animate(self) -> None:
"""Animates the scheduling visualization."""
self.ax.clear()
x: int = 0
colors: dict[str, str] = {}
current_pid: str | None = None
for step in self.engine.simulate():
_, pid, rq = step
if pid:
if pid != current_pid:
self.ax.axvline(x, color="black", linewidth=0.8)
current_pid = pid
colors.setdefault(pid, plt.cm.tab20(len(colors) % 20))
self.ax.barh(0, 1, left=x, color=colors[pid])
self.ax.text(
x + 0.5,
0,
pid,
ha="center",
va="center",
color="white",
fontsize=9,
)
x += 1
self.ax.set_xticks(range(x + 1))
self.ax.set_yticks([])
self.ax.set_xlabel("Time")
self.canvas.draw()
if rq:
self.ready_list.delete(0, tk.END)
for pid_r in rq:
self.ready_list.insert(tk.END, pid_r)
time.sleep(0.3)
self.show_results()
def show_results(self) -> None:
"""Displays scheduling results."""
for item in self.result_box.get_children():
self.result_box.delete(item)
total_wt = total_tat = total_rt = 0
for row in self.engine.stats:
self.result_box.insert("", "end", values=row)
total_wt += row[5]
total_tat += row[4]
total_rt += row[6]
n = len(self.engine.stats) or 1
self.avg_label.config(
text=(
f"AVG WT = {total_wt / n:.2f} | "
f"AVG TAT = {total_tat / n:.2f} | "
f"AVG RT = {total_rt / n:.2f}"
)
)
if __name__ == "__main__":
root = tk.Tk()
CPUSchedulerGUI(root)
root.mainloop()