diff --git a/.github/workflows/syntax-check.yml b/.github/workflows/syntax-check.yml index 824efae..9c53551 100644 --- a/.github/workflows/syntax-check.yml +++ b/.github/workflows/syntax-check.yml @@ -3,7 +3,7 @@ name: Python Syntax Check on: pull_request: branches: - - main # or your default branch + - main # default branch jobs: syntax-check: diff --git a/CloudKernel/ARCHITECTURE.md b/CloudKernel/ARCHITECTURE.md new file mode 100644 index 0000000..91f5287 --- /dev/null +++ b/CloudKernel/ARCHITECTURE.md @@ -0,0 +1,77 @@ +# CloudKernel Architecture + +## Purpose + +CloudKernel models a cloud-hypervisor execution cycle with Java concurrency primitives while exposing runtime behavior through a real-time Swing dashboard. + +## Layers + +- Entry: Main.java +- Configuration: config.ConfigLoader +- Concurrency Core: core.BootManager, core.ClockSynchronizer +- Runtime Entities: entities.VirtualMachine, entities.ResourceManager, entities.VMState, entities.VMPriority, entities.VMStats +- Observability: utils.GUILogger, utils.StatsCollector +- UI: ui.CloudKernelGUI and component panels +- Shutdown: shutdown.ShutdownManager + +## Concurrency Contracts + +### BootManager + +- Uses CountDownLatch(4). +- Boots disk, RAM, network stack, and CPU scheduler asynchronously. +- Exposes latch for visual countdown. + +### ClockSynchronizer + +- Uses CyclicBarrier(vmCount). +- Increments global cycle in barrier action. +- Records cycle completion in StatsCollector. + +### ResourceManager + +- Uses three fair semaphores: + - CPU permits + - Memory permits + - Network permits +- Uses timeout-based tryAcquire for deadlock-resistant resource requests. +- Tracks current holders for UI rendering. + +### VirtualMachine + +- Executes configured number of cycles. +- Transitions through VMState values. +- Requests CPU, memory, network resources sequentially. +- Synchronizes on cyclic barrier each cycle. + +## UI Composition + +CloudKernelGUI builds the dashboard from modular components: + +- VMCard +- ResourceMonitorPanel +- BarrierPanel +- LogPanel +- StatsBar +- ControlPanel +- DashboardUpdater + +DashboardUpdater centralizes UI refresh operations and dispatches thread-crossing updates via SwingUtilities.invokeLater. + +## Data And Events + +- GUILogger writes every event to terminal and broadcasts to GUI listeners. +- CloudKernelGUI parses selected log patterns to drive visual state changes. +- StatsCollector aggregates per-VM and system-wide counters for the stats bar and summary output. + +## Runtime Flow + +1. Main launches CloudKernelGUI on the Swing event dispatch thread. +2. GUI initializes managers and registers listeners. +3. User starts simulation through ControlPanel. +4. BootManager performs subsystem initialization. +5. VirtualMachine threads execute cycles with resource access and barrier sync. +6. DashboardUpdater continuously refreshes resources, VM cards, barrier state, and stats. +7. ShutdownManager prints graceful summary on termination. + + \ No newline at end of file diff --git a/CloudKernel/README.md b/CloudKernel/README.md index 8ffa132..b93b93b 100644 --- a/CloudKernel/README.md +++ b/CloudKernel/README.md @@ -1,181 +1,129 @@ -# CloudKernel ☁️⚙️ +# CloudKernel -## Overview +CloudKernel is a Java concurrency simulator with a professional Swing dashboard that visualizes hypervisor-like VM scheduling, shared resource contention, and synchronization. -**CloudKernel** is a small Java-based simulation created for our **Operating Systems Lab**. -The purpose of this project is to show how a hypervisor-like system can manage multiple **Virtual Machines (VMs)** while coordinating shared resources. +## 📋 Project Information -Instead of building a real operating system, this project focuses on demonstrating **important OS concepts** like synchronization, resource sharing, and concurrent execution using Java threads. - -The program simulates a system where several virtual machines start after the system boots, run tasks together, and share limited network resources. - ---- - -## 🎓 Academic Information - -**Course:** Operating Systems Lab -**Semester:** 4th Semester - -**Submitted to:** -Mam Amara Nadeem - -**Submitted by:** - -- **Moavia Amir** (2k24_BSAI_72) -- **Ali Raza** (2k24_BSAI_44) -- **Muhammad Arslan Nasir** (2k24_BSAI_26) - -**Submission Date:** -March 03, 2026 - ---- - -## 🎯 Project Goals - -This project was designed to help understand how operating systems manage: - -- System boot coordination -- Thread synchronization -- Limited resource sharing -- Parallel execution of processes - -All these ideas are implemented using **Java concurrency utilities**. - ---- - -## ⚙️ Key Concepts Used - -### 1. System Boot Coordination - -Before any virtual machine starts running, the system must finish its boot process. - -We simulate this using **CountDownLatch**. -It ensures that resources like **Disk and RAM** are ready before the virtual machines begin execution. +| Field | Details | +|-------|---------| +| **Subject** | Operating Systems | +| **Semester** | 4th Semester — BSAI 2k24 | +| **Institute** | NFC Institute of Engineering & Technology, Multan | +| **Department** | Artificial Intelligence | +| **Submitted To** | Mam Amara Nadeem — [ammara.visiting@nfciet.edu.pk](mailto:ammara.visiting@nfciet.edu.pk) | +| **Submission Date** | March 03, 2026 | --- -### 2. VM Cycle Synchronization +## 👥 Team Members + +| Name | Roll Number | Email | +|------|-------------|-------| +| Muawiya Amir | 2k24_BSAI_72 | [2k24bsai72@undergrad.nfciet.edu.pk](mailto:2k24bsai72@undergrad.nfciet.edu.pk) | +| Ali Raza | 2k24_BSAI_44 | [2k24bsai44@undergrad.nfciet.edu.pk](mailto:2k24bsai44@undergrad.nfciet.edu.pk) | +| Muhammad Arslan Nasir | 2k24_BSAI_26 | [2k24bsai26@undergrad.nfciet.edu.pk](mailto:2k24bsai26@undergrad.nfciet.edu.pk) | +## Highlights + +- Dark-theme dashboard: Cloud hypervisor monitor layout. +- Boot orchestration with CountDownLatch. +- VM cycle synchronization with CyclicBarrier. +- Shared CPU, memory, and network resources with fair semaphores and timeout handling. +- Color-coded live logs streamed to terminal and GUI simultaneously. +- Live stats for cycles, operations, contentions, timeouts, and uptime. +- Configurable behavior through config.properties. + + +## Final Package Structure + +```text +CloudKernel/ + src/ + Main.java + config/ + ConfigLoader.java + core/ + BootManager.java + ClockSynchronizer.java + entities/ + ResourceManager.java + VirtualMachine.java + VMPriority.java + VMState.java + VMStats.java + shutdown/ + ShutdownManager.java + ui/ + BarrierPanel.java + CloudKernelGUI.java + ControlPanel.java + DashboardUpdater.java + LogPanel.java + ResourceMonitorPanel.java + StatsBar.java + VMCard.java + utils/ + GUILogger.java + StatsCollector.java + config.properties + ARCHITECTURE.md + doc/ + PROJECT_PROPOSAL.md + PROJECT_Report.md + Project_presentation.ppt -Each virtual machine performs its work in cycles. -To keep them synchronized, we use **CyclicBarrier**. - -This means all VMs must finish a cycle before the next one begins. - ---- -### 3. Limited Network Access - -In real systems, hardware resources are limited. -In this simulation, only **two VMs can use the network at the same time**. - -This is managed using a **Semaphore**, which controls access to the shared network ports. - ---- - -## 🧩 Project Structure -``` -CloudKernel -│ -├── src -│ │ -│ ├── Main.java -│ │ -│ ├── core -│ │ ├── BootManager.java -│ │ ├── ClockSynchronizer.java -│ │ └── NetworkPortManager.java -│ │ -│ ├── entities -│ │ └── VirtualMachine.java -│ │ -│ └── utils -│ └── Logger.java -│ -├── doc -│ └── proposal -│ -└── README.md ``` ---- -## 🏗 System Workflow +## GUI Overview -The program runs in the following order: +Main window sections: -``` -System Boot -│ -▼ -BootManager initializes resources -│ -▼ -Virtual Machines start (Threads) -│ -▼ -VMs execute cycles together -│ -▼ -Network access controlled by Semaphore -│ -▼ -Logs printed to terminal -``` +- Header: title, digital clock, online indicator. +- Boot panel: resource chips and latch countdown. +- VM dashboard: one card per VM with state, priority, progress, and resource indicators. +- Left sidebar: semaphore slot view for CPU, memory, and network. +- Barrier panel: arrival dots and cycle display. +- Right sidebar: color-coded live event log. +- Bottom bars: statistics and controls. ---- +## Core Concurrency Model -## ▶️ How to Run the Project +- Boot phase: CountDownLatch initialized to four boot tasks. +- Runtime phase: each VM executes for configured cycles. +- Resource phase: each VM requests CPU, memory, and network permits with timeout. +- Synchronization phase: all VMs rendezvous at a CyclicBarrier before the next cycle. -### 1. Compile the project +## Configuration -```bash -javac -d out -sourcepath src src/Main.java -``` -### 2. Run the program -```bash -java -cp out Main -``` -## 🖥 Example Output +Edit config.properties before running: -When the program runs, you may see output like: -``` -[BOOT] Disk initialized -[BOOT] RAM initialized -[BOOT] System ready - -[VM-1] Starting execution -[VM-2] Starting execution -[VM-3] Starting execution +- vm.count +- cycle.count +- semaphore.cpu.permits +- semaphore.memory.permits +- semaphore.network.permits +- task.duration.min +- task.duration.max +- timeout.duration +- gui.enabled +- gui.theme +- gui.font +- logging.level +- stats.enabled -[VM-1] Requesting network access -[VM-2] Requesting network access +## Build And Run -[VM-1] Using network port -[VM-2] Using network port +From CloudKernel root: -[VM-3] Waiting for network port +```powershell +javac -encoding UTF-8 -d bin (Get-ChildItem -Recurse src -Filter *.java | ForEach-Object { $_.FullName }) +java -cp "bin;." Main ``` -The Logger class keeps the output organized so it is easier to read. - -## 🧠 What We Learned - -While building this project, we understood how operating systems handle: - -+ **Thread** synchronization - -+ **Shared** resource management - -+ **Parallel** execution - -+ **Process** coordination - -These concepts are important for understanding how real operating systems and cloud platforms work. - ---- -## 📌 Conclusion +## Notes -CloudKernel is a simple educational simulation that demonstrates how a hypervisor-like system can coordinate virtual machines and manage shared resources. +- All UI updates triggered by worker threads are dispatched through SwingUtilities.invokeLater. +- Main.java contains only the GUI entry point. +- Legacy duplicate docs and unused legacy classes were removed to keep one canonical implementation path. -Although it is a simplified model, it provides a clear understanding of synchronization and concurrency in operating systems. ---- \ No newline at end of file diff --git a/CloudKernel/bin/Main.class b/CloudKernel/bin/Main.class new file mode 100644 index 0000000..93019a2 Binary files /dev/null and b/CloudKernel/bin/Main.class differ diff --git a/CloudKernel/bin/config/ConfigLoader.class b/CloudKernel/bin/config/ConfigLoader.class new file mode 100644 index 0000000..054488e Binary files /dev/null and b/CloudKernel/bin/config/ConfigLoader.class differ diff --git a/CloudKernel/bin/core/BootManager.class b/CloudKernel/bin/core/BootManager.class new file mode 100644 index 0000000..691bd88 Binary files /dev/null and b/CloudKernel/bin/core/BootManager.class differ diff --git a/CloudKernel/bin/core/ClockSynchronizer.class b/CloudKernel/bin/core/ClockSynchronizer.class new file mode 100644 index 0000000..2069d39 Binary files /dev/null and b/CloudKernel/bin/core/ClockSynchronizer.class differ diff --git a/CloudKernel/bin/entities/ResourceManager.class b/CloudKernel/bin/entities/ResourceManager.class new file mode 100644 index 0000000..0e89778 Binary files /dev/null and b/CloudKernel/bin/entities/ResourceManager.class differ diff --git a/CloudKernel/bin/entities/VMPriority.class b/CloudKernel/bin/entities/VMPriority.class new file mode 100644 index 0000000..f0ae0bf Binary files /dev/null and b/CloudKernel/bin/entities/VMPriority.class differ diff --git a/CloudKernel/bin/entities/VMState.class b/CloudKernel/bin/entities/VMState.class new file mode 100644 index 0000000..93dc68e Binary files /dev/null and b/CloudKernel/bin/entities/VMState.class differ diff --git a/CloudKernel/bin/entities/VMStats.class b/CloudKernel/bin/entities/VMStats.class new file mode 100644 index 0000000..d97b51b Binary files /dev/null and b/CloudKernel/bin/entities/VMStats.class differ diff --git a/CloudKernel/bin/entities/VirtualMachine.class b/CloudKernel/bin/entities/VirtualMachine.class new file mode 100644 index 0000000..3e11420 Binary files /dev/null and b/CloudKernel/bin/entities/VirtualMachine.class differ diff --git a/CloudKernel/bin/shutdown/ShutdownManager.class b/CloudKernel/bin/shutdown/ShutdownManager.class new file mode 100644 index 0000000..8c3d233 Binary files /dev/null and b/CloudKernel/bin/shutdown/ShutdownManager.class differ diff --git a/CloudKernel/bin/ui/BarrierPanel.class b/CloudKernel/bin/ui/BarrierPanel.class new file mode 100644 index 0000000..036f4ff Binary files /dev/null and b/CloudKernel/bin/ui/BarrierPanel.class differ diff --git a/CloudKernel/bin/ui/CloudKernelGUI.class b/CloudKernel/bin/ui/CloudKernelGUI.class new file mode 100644 index 0000000..9cfa3a4 Binary files /dev/null and b/CloudKernel/bin/ui/CloudKernelGUI.class differ diff --git a/CloudKernel/bin/ui/ControlPanel$1.class b/CloudKernel/bin/ui/ControlPanel$1.class new file mode 100644 index 0000000..0c9a3fb Binary files /dev/null and b/CloudKernel/bin/ui/ControlPanel$1.class differ diff --git a/CloudKernel/bin/ui/ControlPanel.class b/CloudKernel/bin/ui/ControlPanel.class new file mode 100644 index 0000000..3d85f4c Binary files /dev/null and b/CloudKernel/bin/ui/ControlPanel.class differ diff --git a/CloudKernel/bin/ui/DashboardUpdater.class b/CloudKernel/bin/ui/DashboardUpdater.class new file mode 100644 index 0000000..2bce142 Binary files /dev/null and b/CloudKernel/bin/ui/DashboardUpdater.class differ diff --git a/CloudKernel/bin/ui/LogPanel.class b/CloudKernel/bin/ui/LogPanel.class new file mode 100644 index 0000000..32d37e4 Binary files /dev/null and b/CloudKernel/bin/ui/LogPanel.class differ diff --git a/CloudKernel/bin/ui/ResourceMonitorPanel.class b/CloudKernel/bin/ui/ResourceMonitorPanel.class new file mode 100644 index 0000000..5a9d663 Binary files /dev/null and b/CloudKernel/bin/ui/ResourceMonitorPanel.class differ diff --git a/CloudKernel/bin/ui/StatsBar.class b/CloudKernel/bin/ui/StatsBar.class new file mode 100644 index 0000000..2f2787d Binary files /dev/null and b/CloudKernel/bin/ui/StatsBar.class differ diff --git a/CloudKernel/bin/ui/VMCard$RoundedBorder.class b/CloudKernel/bin/ui/VMCard$RoundedBorder.class new file mode 100644 index 0000000..d230354 Binary files /dev/null and b/CloudKernel/bin/ui/VMCard$RoundedBorder.class differ diff --git a/CloudKernel/bin/ui/VMCard.class b/CloudKernel/bin/ui/VMCard.class new file mode 100644 index 0000000..96d9c0f Binary files /dev/null and b/CloudKernel/bin/ui/VMCard.class differ diff --git a/CloudKernel/bin/utils/GUILogger$LogEntry.class b/CloudKernel/bin/utils/GUILogger$LogEntry.class new file mode 100644 index 0000000..489e000 Binary files /dev/null and b/CloudKernel/bin/utils/GUILogger$LogEntry.class differ diff --git a/CloudKernel/bin/utils/GUILogger$LogListener.class b/CloudKernel/bin/utils/GUILogger$LogListener.class new file mode 100644 index 0000000..ce64a66 Binary files /dev/null and b/CloudKernel/bin/utils/GUILogger$LogListener.class differ diff --git a/CloudKernel/bin/utils/GUILogger.class b/CloudKernel/bin/utils/GUILogger.class new file mode 100644 index 0000000..31fc239 Binary files /dev/null and b/CloudKernel/bin/utils/GUILogger.class differ diff --git a/CloudKernel/bin/utils/StatsCollector.class b/CloudKernel/bin/utils/StatsCollector.class new file mode 100644 index 0000000..9060bfa Binary files /dev/null and b/CloudKernel/bin/utils/StatsCollector.class differ diff --git a/CloudKernel/config.properties b/CloudKernel/config.properties new file mode 100644 index 0000000..e165178 --- /dev/null +++ b/CloudKernel/config.properties @@ -0,0 +1,38 @@ +# Number of virtual machines to create for each simulation run. +vm.count=5 + +# Number of execution cycles each virtual machine performs. +cycle.count=4 + +# Number of concurrent CPU permits available to VMs. +semaphore.cpu.permits=3 + +# Number of concurrent memory permits available to VMs. +semaphore.memory.permits=2 + +# Number of concurrent network permits available to VMs. +semaphore.network.permits=2 + +# Minimum simulated workload duration in milliseconds. +task.duration.min=500 + +# Maximum simulated workload duration in milliseconds. +task.duration.max=1500 + +# Maximum wait duration in milliseconds for acquiring a resource permit. +timeout.duration=2000 + +# Enables or disables GUI startup mode. +gui.enabled=true + +# Preferred GUI theme identifier used by the dashboard. +gui.theme=dark + +# Preferred GUI font family hint. +gui.font=JetBrains Mono + +# Global logging level: VERBOSE, NORMAL, or QUIET. +logging.level=NORMAL + +# Enables or disables runtime statistics collection. +stats.enabled=true diff --git a/CloudKernel/doc/CloudKernel.pptx b/CloudKernel/doc/CloudKernel.pptx new file mode 100644 index 0000000..9804a71 Binary files /dev/null and b/CloudKernel/doc/CloudKernel.pptx differ diff --git a/CloudKernel/doc/CloudKernel_ProjectReport.pdf b/CloudKernel/doc/CloudKernel_ProjectReport.pdf deleted file mode 100644 index 8b8891e..0000000 Binary files a/CloudKernel/doc/CloudKernel_ProjectReport.pdf and /dev/null differ diff --git a/CloudKernel/doc/CloudKernel_QA.pdf b/CloudKernel/doc/CloudKernel_QA.pdf new file mode 100644 index 0000000..00f71b1 Binary files /dev/null and b/CloudKernel/doc/CloudKernel_QA.pdf differ diff --git a/CloudKernel/doc/CloudKernel_Report.pdf b/CloudKernel/doc/CloudKernel_Report.pdf new file mode 100644 index 0000000..ba95e6d Binary files /dev/null and b/CloudKernel/doc/CloudKernel_Report.pdf differ diff --git a/CloudKernel/doc/PROJECT_PROPOSAL.md b/CloudKernel/doc/PROJECT_PROPOSAL.md new file mode 100644 index 0000000..24b2e41 --- /dev/null +++ b/CloudKernel/doc/PROJECT_PROPOSAL.md @@ -0,0 +1,68 @@ +# CloudKernel Final Project Proposal Update + +## Project Title + +CloudKernel: Multi-Threaded Hypervisor Monitor + +## Final Delivered Scope + +The final project delivers a Java-based operating-system lab simulation with: + +- A full Swing dashboard for live monitoring. +- Multi-VM concurrent execution. +- Shared resource arbitration through semaphores. +- Global cycle synchronization through a cyclic barrier. +- Boot orchestration through a countdown latch. +- Dual-channel logging and live statistics. + +## Objectives Achieved + +- Demonstrate practical use of CountDownLatch, Semaphore, and CyclicBarrier. +- Visualize VM lifecycle transitions in real time. +- Measure system-level and VM-level behavior using statistics. +- Provide configurable runtime parameters without code changes. + +## Final Technical Stack + +- Language: Java +- GUI: Swing +- Concurrency: java.util.concurrent +- Build: javac +- Runtime: java + +## Final Package Design + +- config: configuration loading +- core: synchronization and boot coordinators +- entities: VM and resource domain logic +- ui: modular dashboard components +- utils: logging and stats +- shutdown: graceful shutdown behavior + +## Configuration-Driven Behavior + +The simulator uses config.properties for: + +- VM count and cycle count +- CPU, memory, and network permit capacities +- Task duration range and timeout duration +- GUI and logging toggles + +## Deliverables + +- Fully integrated source code under src. +- Updated README and architecture documentation. +- Cleaned project with obsolete duplicate files removed. +- Consistent Javadoc comments and naming conventions. +- Successful compile validation with current structure. + +## Future Extensions + +- Export runtime metrics to CSV or JSON. +- Add configurable VM scheduling policies. +- Add persistence and replay mode for event timelines. + + + + + \ No newline at end of file diff --git a/CloudKernel/img/banner.png b/CloudKernel/img/banner.png new file mode 100644 index 0000000..28de80d Binary files /dev/null and b/CloudKernel/img/banner.png differ diff --git a/CloudKernel/img/screen.png b/CloudKernel/img/screen.png new file mode 100644 index 0000000..254eb20 Binary files /dev/null and b/CloudKernel/img/screen.png differ diff --git a/CloudKernel/out/Main.class b/CloudKernel/out/Main.class deleted file mode 100644 index 745078b..0000000 Binary files a/CloudKernel/out/Main.class and /dev/null differ diff --git a/CloudKernel/out/core/BootManager.class b/CloudKernel/out/core/BootManager.class deleted file mode 100644 index 4c9ffa3..0000000 Binary files a/CloudKernel/out/core/BootManager.class and /dev/null differ diff --git a/CloudKernel/out/core/ClockSynchronizer.class b/CloudKernel/out/core/ClockSynchronizer.class deleted file mode 100644 index 65ce430..0000000 Binary files a/CloudKernel/out/core/ClockSynchronizer.class and /dev/null differ diff --git a/CloudKernel/out/core/NetworkPortManager.class b/CloudKernel/out/core/NetworkPortManager.class deleted file mode 100644 index cf004f0..0000000 Binary files a/CloudKernel/out/core/NetworkPortManager.class and /dev/null differ diff --git a/CloudKernel/out/entities/VirtualMachine.class b/CloudKernel/out/entities/VirtualMachine.class deleted file mode 100644 index 3a8e891..0000000 Binary files a/CloudKernel/out/entities/VirtualMachine.class and /dev/null differ diff --git a/CloudKernel/out/utils/Logger.class b/CloudKernel/out/utils/Logger.class deleted file mode 100644 index 9ba1646..0000000 Binary files a/CloudKernel/out/utils/Logger.class and /dev/null differ diff --git a/CloudKernel/src/Main.java b/CloudKernel/src/Main.java index 44ceb3a..6faed7e 100644 --- a/CloudKernel/src/Main.java +++ b/CloudKernel/src/Main.java @@ -1,58 +1,18 @@ -import core.BootManager; -import core.ClockSynchronizer; -import core.NetworkPortManager; -import entities.VirtualMachine; -import utils.Logger; +import ui.CloudKernelGUI; -// Entry point for the CloudKernel simulation. -public class Main { - - private static final int NUM_VMS = 3; - private static final int NUM_CYCLES = 2; - - public static void main(String[] args) throws InterruptedException { - // Phase 1: boot - Logger.section("PHASE 1: SYSTEM BOOT [CountDownLatch]"); - Logger.log("HYPERVISOR", "CloudKernel v1.0 starting...", Logger.BOLD + Logger.GREEN); - - BootManager bootManager = new BootManager(); - bootManager.initDisk(); - bootManager.initRAM(); - bootManager.awaitBootCompletion(); - - Thread.sleep(500); +import javax.swing.SwingUtilities; - // Phase 2: VM execution - Logger.section("PHASE 2: VM EXECUTION [CyclicBarrier + Semaphore]"); - int[] cycleNum = { 0 }; - ClockSynchronizer clock = new ClockSynchronizer(NUM_VMS, cycleNum); - NetworkPortManager networkManager = new NetworkPortManager(); - - Logger.log("HYPERVISOR", - "Launching " + NUM_VMS + " VMs for " + NUM_CYCLES + " cycles each...", - Logger.CYAN); - - Thread[] vmThreads = new Thread[NUM_VMS]; - for (int i = 1; i <= NUM_VMS; i++) { - int workDuration = 600 + (i * 200); - - VirtualMachine vm = new VirtualMachine( - "VM-" + i, NUM_CYCLES, clock, networkManager, workDuration); - vmThreads[i - 1] = new Thread(vm, "VM-" + i); - vmThreads[i - 1].start(); - } - - for (Thread t : vmThreads) { - t.join(); - } +/** + * Application entry point for CloudKernel. + */ +public class Main { - // Phase 3: shutdown - Logger.section("PHASE 3: SYSTEM SHUTDOWN"); - Logger.log("HYPERVISOR", "All VMs have completed execution.", Logger.GREEN); - Logger.log("HYPERVISOR", "Releasing all system resources...", Logger.YELLOW); - Thread.sleep(300); - Logger.log("HYPERVISOR", "CloudKernel has shut down cleanly. Goodbye. [OK]", Logger.BOLD + Logger.GREEN); - Logger.separator(); - System.out.println(); + /** + * Starts the CloudKernel GUI on the Swing event dispatch thread. + * + * @param args command-line arguments (unused) + */ + public static void main(String[] args) { + SwingUtilities.invokeLater(() -> new CloudKernelGUI().setVisible(true)); } } \ No newline at end of file diff --git a/CloudKernel/src/config/ConfigLoader.java b/CloudKernel/src/config/ConfigLoader.java new file mode 100644 index 0000000..5da04fe --- /dev/null +++ b/CloudKernel/src/config/ConfigLoader.java @@ -0,0 +1,190 @@ +package config; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +/** + * Loads simulator configuration from config.properties. + * Falls back to sane defaults when file loading fails. + */ +public class ConfigLoader { + + private static final String CONFIG_FILE = "config.properties"; + private final Properties properties; + + /** + * Creates a loader and immediately reads configuration values. + */ + public ConfigLoader() { + properties = new Properties(); + loadConfig(); + } + + /** + * Loads configuration from file or uses defaults. + */ + private void loadConfig() { + try (InputStream input = new FileInputStream(CONFIG_FILE)) { + properties.load(input); + System.out.println("[CONFIG] Loaded configuration from: " + CONFIG_FILE); + } catch (FileNotFoundException e) { + System.out.println("[CONFIG] config.properties not found. Using defaults."); + loadDefaults(); + } catch (IOException e) { + System.out.println("[CONFIG] Error reading config file: " + e.getMessage()); + loadDefaults(); + } + } + + /** + * Applies default values for all recognized properties. + */ + private void loadDefaults() { + properties.setProperty("vm.count", "5"); + properties.setProperty("cycle.count", "4"); + properties.setProperty("semaphore.cpu.permits", "3"); + properties.setProperty("semaphore.memory.permits", "2"); + properties.setProperty("semaphore.network.permits", "2"); + properties.setProperty("task.duration.min", "500"); + properties.setProperty("task.duration.max", "1500"); + properties.setProperty("timeout.duration", "2000"); + properties.setProperty("gui.enabled", "true"); + properties.setProperty("logging.level", "NORMAL"); + properties.setProperty("stats.enabled", "true"); + } + + /** + * Reads an integer property. + * + * @param key property name + * @param defaultValue fallback value + * @return parsed integer or fallback + */ + public int getInt(String key, int defaultValue) { + try { + return Integer.parseInt(properties.getProperty(key, String.valueOf(defaultValue))); + } catch (NumberFormatException e) { + System.out.println("[CONFIG] Invalid integer for key: " + key + ", using default: " + defaultValue); + return defaultValue; + } + } + + /** + * Reads a long property. + * + * @param key property name + * @param defaultValue fallback value + * @return parsed long or fallback + */ + public long getLong(String key, long defaultValue) { + try { + return Long.parseLong(properties.getProperty(key, String.valueOf(defaultValue))); + } catch (NumberFormatException e) { + System.out.println("[CONFIG] Invalid long for key: " + key + ", using default: " + defaultValue); + return defaultValue; + } + } + + /** + * Reads a string property. + * + * @param key property name + * @param defaultValue fallback value + * @return property value or fallback + */ + public String getString(String key, String defaultValue) { + return properties.getProperty(key, defaultValue); + } + + /** + * Reads a boolean property. + * + * @param key property name + * @param defaultValue fallback value + * @return parsed boolean or fallback + */ + public boolean getBoolean(String key, boolean defaultValue) { + String value = properties.getProperty(key, String.valueOf(defaultValue)); + return Boolean.parseBoolean(value); + } + + /** + * @return configured VM count + */ + public int getVMCount() { + return getInt("vm.count", 5); + } + + /** + * @return configured cycle count per VM + */ + public int getCycleCount() { + return getInt("cycle.count", 4); + } + + /** + * @return configured CPU permits + */ + public int getCPUPermits() { + return getInt("semaphore.cpu.permits", 3); + } + + /** + * @return configured memory permits + */ + public int getMemoryPermits() { + return getInt("semaphore.memory.permits", 2); + } + + /** + * @return configured network permits + */ + public int getNetworkPermits() { + return getInt("semaphore.network.permits", 2); + } + + /** + * @return minimum task duration in milliseconds + */ + public int getTaskDurationMin() { + return getInt("task.duration.min", 500); + } + + /** + * @return maximum task duration in milliseconds + */ + public int getTaskDurationMax() { + return getInt("task.duration.max", 1500); + } + + /** + * @return resource acquisition timeout in milliseconds + */ + public long getTimeoutDuration() { + return getLong("timeout.duration", 2000); + } + + /** + * @return true if GUI mode is enabled + */ + public boolean isGUIEnabled() { + return getBoolean("gui.enabled", true); + } + + /** + * @return configured logging level label + */ + public String getLoggingLevel() { + return getString("logging.level", "NORMAL"); + } + + /** + * @return true if stats collection is enabled + */ + public boolean isStatsEnabled() { + return getBoolean("stats.enabled", true); + } +} diff --git a/CloudKernel/src/core/BootManager.java b/CloudKernel/src/core/BootManager.java index 7a7697a..aa04354 100644 --- a/CloudKernel/src/core/BootManager.java +++ b/CloudKernel/src/core/BootManager.java @@ -1,34 +1,51 @@ package core; -import utils.Logger; +import utils.GUILogger; import java.util.concurrent.CountDownLatch; -// Handles system boot readiness using CountDownLatch. +/** + * Coordinates CloudKernel boot initialization using a countdown latch. + */ public class BootManager { - private final CountDownLatch bootLatch = new CountDownLatch(2); + private final CountDownLatch bootLatch = new CountDownLatch(4); + private final GUILogger logger; + /** + * Creates a boot manager. + * + * @param logger logger used for boot event reporting + */ + public BootManager(GUILogger logger) { + this.logger = logger; + } + + /** + * Starts disk subsystem initialization on its own thread. + */ public void initDisk() { new Thread(() -> { try { - Logger.log("BOOT", "Disk subsystem starting...", Logger.YELLOW); + logger.log("SYSTEM", "Disk subsystem starting...", "BOOT"); Thread.sleep(1500); - Logger.log("BOOT", "Disk subsystem initialized. [OK]", Logger.GREEN); + logger.log("SYSTEM", "Disk subsystem initialized. [OK]", "BOOT"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { - // Count down in finally to avoid deadlock on errors. bootLatch.countDown(); } }, "Disk-Init-Thread").start(); } + /** + * Starts RAM subsystem initialization on its own thread. + */ public void initRAM() { new Thread(() -> { try { - Logger.log("BOOT", "RAM subsystem starting...", Logger.YELLOW); + logger.log("SYSTEM", "RAM subsystem starting...", "BOOT"); Thread.sleep(1000); - Logger.log("BOOT", "RAM subsystem initialized. [OK]", Logger.GREEN); + logger.log("SYSTEM", "RAM subsystem initialized. [OK]", "BOOT"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { @@ -37,9 +54,57 @@ public void initRAM() { }, "RAM-Init-Thread").start(); } + /** + * Starts network stack initialization on its own thread. + */ + public void initNetworkStack() { + new Thread(() -> { + try { + logger.log("SYSTEM", "Network Stack initializing...", "BOOT"); + Thread.sleep(1200); + logger.log("SYSTEM", "Network Stack initialized. [OK]", "BOOT"); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } finally { + bootLatch.countDown(); + } + }, "Network-Init-Thread").start(); + } + + /** + * Starts CPU scheduler initialization on its own thread. + */ + public void initCPUScheduler() { + new Thread(() -> { + try { + logger.log("SYSTEM", "CPU Scheduler initializing...", "BOOT"); + Thread.sleep(800); + logger.log("SYSTEM", "CPU Scheduler initialized. [OK]", "BOOT"); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } finally { + bootLatch.countDown(); + } + }, "CPU-Init-Thread").start(); + } + + /** + * Blocks until all boot tasks complete. + * + * @throws InterruptedException if interrupted while waiting + */ public void awaitBootCompletion() throws InterruptedException { - Logger.log("BOOT", "Hypervisor waiting for subsystems...", Logger.CYAN); + logger.boot("Hypervisor waiting for all subsystems..."); bootLatch.await(); - Logger.log("BOOT", "All subsystems ready. CloudKernel is ONLINE. [OK]", Logger.GREEN); + logger.boot("All subsystems ready. CloudKernel is ONLINE."); + } + + /** + * Returns the latch used by the boot phase. + * + * @return boot latch + */ + public CountDownLatch getBootLatch() { + return bootLatch; } } \ No newline at end of file diff --git a/CloudKernel/src/core/ClockSynchronizer.java b/CloudKernel/src/core/ClockSynchronizer.java index cfbf217..5fd89f3 100644 --- a/CloudKernel/src/core/ClockSynchronizer.java +++ b/CloudKernel/src/core/ClockSynchronizer.java @@ -1,28 +1,64 @@ package core; -import utils.Logger; +import utils.GUILogger; +import utils.StatsCollector; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; -// Keeps all VM threads synchronized at each cycle. +/** + * Synchronizes VM cycle boundaries through a cyclic barrier. + */ public class ClockSynchronizer { private final CyclicBarrier barrier; + private final GUILogger logger; + private final StatsCollector stats; + private int cycleCount = 0; + + /** + * Creates a clock synchronizer. + * + * @param vmCount number of participating VM threads + * @param logger logger instance + * @param stats statistics collector + */ + public ClockSynchronizer(int vmCount, GUILogger logger, StatsCollector stats) { + this.logger = logger; + this.stats = stats; - public ClockSynchronizer(int vmCount, int[] cycleNum) { Runnable clockTick = () -> { - cycleNum[0]++; - Logger.log("CLOCK", - "=== Global Clock Tick #" + cycleNum[0] + - " - All VMs synchronized. Next cycle begins. ===", - Logger.BOLD + Logger.CYAN); + cycleCount++; + GUILogger.cycleComplete(cycleCount); + logger.boot("Global Clock Tick #" + cycleCount + " - All VMs synchronized."); + stats.recordCycleCompletion(); }; this.barrier = new CyclicBarrier(vmCount, clockTick); } + /** + * Waits for all VMs to arrive at the barrier and synchronizes the cycle. + * + * @param vmName VM identifier for log messages + * @throws InterruptedException if interrupted while waiting + * @throws BrokenBarrierException if the barrier is broken + */ public void sync(String vmName) throws InterruptedException, BrokenBarrierException { - Logger.log(vmName, "Work unit done. Waiting at clock barrier...", Logger.YELLOW); + logger.log(vmName, "Work unit done. Waiting at clock barrier...", "BARRIER"); barrier.await(); } + + /** + * @return underlying cyclic barrier + */ + public CyclicBarrier getBarrier() { + return barrier; + } + + /** + * @return completed cycle count + */ + public int getCycleCount() { + return cycleCount; + } } \ No newline at end of file diff --git a/CloudKernel/src/core/NetworkPortManager.java b/CloudKernel/src/core/NetworkPortManager.java deleted file mode 100644 index 9cb5c4a..0000000 --- a/CloudKernel/src/core/NetworkPortManager.java +++ /dev/null @@ -1,31 +0,0 @@ -package core; - -import utils.Logger; -import java.util.concurrent.Semaphore; - -// Manages limited network ports using a fair semaphore. -public class NetworkPortManager { - - private static final int TOTAL_PORTS = 2; - private final Semaphore networkPorts = new Semaphore(TOTAL_PORTS, true); - - public void acquirePort(String vmName) throws InterruptedException { - Logger.log(vmName, - "Requesting Network Port... (available: " + networkPorts.availablePermits() + "/" + TOTAL_PORTS + ")", - Logger.YELLOW); - - networkPorts.acquire(); - int inUse = TOTAL_PORTS - networkPorts.availablePermits(); - - Logger.log(vmName, - "Network Port GRANTED. (in use: " + inUse + "/" + TOTAL_PORTS + ") Transmitting data...", - Logger.GREEN); - } - - public void releasePort(String vmName) { - networkPorts.release(); - Logger.log(vmName, - "Network Port RELEASED. (available: " + networkPorts.availablePermits() + "/" + TOTAL_PORTS + ")", - Logger.CYAN); - } -} \ No newline at end of file diff --git a/CloudKernel/src/entities/ResourceManager.java b/CloudKernel/src/entities/ResourceManager.java new file mode 100644 index 0000000..66f73c5 --- /dev/null +++ b/CloudKernel/src/entities/ResourceManager.java @@ -0,0 +1,194 @@ +package entities; + +import utils.GUILogger; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Manages shared CPU, memory, and network resources using fair semaphores. + */ +public class ResourceManager { + private final Semaphore cpuSemaphore; + private final Semaphore memorySemaphore; + private final Semaphore networkSemaphore; + private final int cpuPermits; + private final int memoryPermits; + private final int networkPermits; + + private final Map cpuHolders = new ConcurrentHashMap<>(); + private final Map memoryHolders = new ConcurrentHashMap<>(); + private final Map networkHolders = new ConcurrentHashMap<>(); + + private final GUILogger logger; + private final long timeout; + + /** + * Creates a resource manager. + * + * @param cpuPermits total CPU permits + * @param memoryPermits total memory permits + * @param networkPermits total network permits + * @param timeout resource acquisition timeout in milliseconds + * @param logger logger for resource events + */ + public ResourceManager(int cpuPermits, int memoryPermits, int networkPermits, long timeout, GUILogger logger) { + this.cpuSemaphore = new Semaphore(cpuPermits, true); + this.memorySemaphore = new Semaphore(memoryPermits, true); + this.networkSemaphore = new Semaphore(networkPermits, true); + this.cpuPermits = cpuPermits; + this.memoryPermits = memoryPermits; + this.networkPermits = networkPermits; + this.timeout = timeout; + this.logger = logger; + } + + /** + * Attempts to acquire a CPU permit. + * + * @param vmName VM requesting the permit + * @return true if permit acquired, false on timeout + * @throws InterruptedException if interrupted while waiting + */ + public boolean acquireCPU(String vmName) throws InterruptedException { + if (cpuSemaphore.tryAcquire(timeout, TimeUnit.MILLISECONDS)) { + String coreId = "CPU-" + (cpuPermits - cpuSemaphore.availablePermits()); + cpuHolders.put(coreId, vmName); + logger.log(vmName, + "acquired CPU core. (available: " + cpuSemaphore.availablePermits() + "/" + cpuPermits + ")", + "CPU"); + return true; + } else { + logger.log(vmName, "[TIMEOUT] Could not acquire CPU core within " + timeout + "ms", "TIMEOUT"); + return false; + } + } + + /** + * Releases a CPU permit for the specified VM. + * + * @param vmName VM releasing the permit + */ + public void releaseCPU(String vmName) { + cpuSemaphore.release(); + cpuHolders.values().remove(vmName); + logger.log(vmName, + "released CPU core. (available: " + cpuSemaphore.availablePermits() + "/" + cpuPermits + ")", + "CPU"); + } + + /** + * Attempts to acquire a memory permit. + * + * @param vmName VM requesting the permit + * @return true if permit acquired, false on timeout + * @throws InterruptedException if interrupted while waiting + */ + public boolean acquireMemory(String vmName) throws InterruptedException { + if (memorySemaphore.tryAcquire(timeout, TimeUnit.MILLISECONDS)) { + String memId = "MEM-" + (memoryPermits - memorySemaphore.availablePermits()); + memoryHolders.put(memId, vmName); + logger.log(vmName, + "acquired Memory block. (available: " + memorySemaphore.availablePermits() + "/" + + memoryPermits + ")", + "MEMORY"); + return true; + } else { + logger.log(vmName, "[TIMEOUT] Could not acquire Memory block within " + timeout + "ms", "TIMEOUT"); + return false; + } + } + + /** + * Releases a memory permit for the specified VM. + * + * @param vmName VM releasing the permit + */ + public void releaseMemory(String vmName) { + memorySemaphore.release(); + memoryHolders.values().remove(vmName); + logger.log(vmName, + "released Memory block. (available: " + memorySemaphore.availablePermits() + "/" + memoryPermits + + ")", + "MEMORY"); + } + + /** + * Attempts to acquire a network permit. + * + * @param vmName VM requesting the permit + * @return true if permit acquired, false on timeout + * @throws InterruptedException if interrupted while waiting + */ + public boolean acquireNetwork(String vmName) throws InterruptedException { + if (networkSemaphore.tryAcquire(timeout, TimeUnit.MILLISECONDS)) { + String portId = "PORT-" + (networkPermits - networkSemaphore.availablePermits()); + networkHolders.put(portId, vmName); + logger.log(vmName, + "acquired Network port. (available: " + networkSemaphore.availablePermits() + "/" + + networkPermits + ")", + "NETWORK"); + return true; + } else { + logger.log(vmName, "[TIMEOUT] Could not acquire Network port within " + timeout + "ms", "TIMEOUT"); + return false; + } + } + + /** + * Releases a network permit for the specified VM. + * + * @param vmName VM releasing the permit + */ + public void releaseNetwork(String vmName) { + networkSemaphore.release(); + networkHolders.values().remove(vmName); + logger.log(vmName, + "released Network port. (available: " + networkSemaphore.availablePermits() + "/" + networkPermits + + ")", + "NETWORK"); + } + + /** + * @return available CPU permits + */ + public int getCPUAvailable() { + return cpuSemaphore.availablePermits(); + } + + /** + * @return available memory permits + */ + public int getMemoryAvailable() { + return memorySemaphore.availablePermits(); + } + + /** + * @return available network permits + */ + public int getNetworkAvailable() { + return networkSemaphore.availablePermits(); + } + + /** + * @return map of CPU slot holders + */ + public Map getCPUHolders() { + return cpuHolders; + } + + /** + * @return map of memory slot holders + */ + public Map getMemoryHolders() { + return memoryHolders; + } + + /** + * @return map of network slot holders + */ + public Map getNetworkHolders() { + return networkHolders; + } +} diff --git a/CloudKernel/src/entities/VMPriority.java b/CloudKernel/src/entities/VMPriority.java new file mode 100644 index 0000000..8f87f18 --- /dev/null +++ b/CloudKernel/src/entities/VMPriority.java @@ -0,0 +1,56 @@ +package entities; + +/** + * Priority levels assigned to virtual machines. + */ +public enum VMPriority { + LOW(1, "LOW", "\u001B[36m"), // Cyan + MEDIUM(2, "MEDIUM", "\u001B[33m"), // Yellow + HIGH(3, "HIGH", "\u001B[31m"); // Red + + private final int value; + private final String label; + private final String color; + + VMPriority(int value, String label, String color) { + this.value = value; + this.label = label; + this.color = color; + } + + /** + * @return numeric priority value + */ + public int getValue() { + return value; + } + + /** + * @return display label + */ + public String getLabel() { + return label; + } + + /** + * @return ANSI color code for console output + */ + public String getColor() { + return color; + } + + /** + * Returns a random priority. + * + * @return randomly selected priority + */ + public static VMPriority getRandomPriority() { + int rand = (int) (Math.random() * 3); + return values()[rand]; + } + + @Override + public String toString() { + return color + label + "\u001B[0m"; + } +} diff --git a/CloudKernel/src/entities/VMState.java b/CloudKernel/src/entities/VMState.java new file mode 100644 index 0000000..0149d74 --- /dev/null +++ b/CloudKernel/src/entities/VMState.java @@ -0,0 +1,43 @@ +package entities; + +/** + * Defines VM lifecycle states used by the simulator and dashboard. + */ +public enum VMState { + BOOTING("BOOTING", "\u001B[33m"), // Yellow + READY("READY", "\u001B[32m"), // Green + RUNNING("RUNNING", "\u001B[36m"), // Cyan + REQUESTING_RESOURCE("REQUESTING", "\u001B[33m"), // Yellow + USING_RESOURCE("USING", "\u001B[32m"), // Green + RELEASING("RELEASING", "\u001B[34m"), // Blue + BARRIER_WAIT("BARRIER WAIT", "\u001B[35m"), // Purple + TIMEOUT("TIMEOUT", "\u001B[31m"), // Red + SHUTDOWN("SHUTDOWN", "\u001B[37m"); // White + + private final String label; + private final String color; + + VMState(String label, String color) { + this.label = label; + this.color = color; + } + + /** + * @return display label for the state + */ + public String getLabel() { + return label; + } + + /** + * @return ANSI color code used for console rendering + */ + public String getColor() { + return color; + } + + @Override + public String toString() { + return color + label + "\u001B[0m"; + } +} diff --git a/CloudKernel/src/entities/VMStats.java b/CloudKernel/src/entities/VMStats.java new file mode 100644 index 0000000..79124fe --- /dev/null +++ b/CloudKernel/src/entities/VMStats.java @@ -0,0 +1,102 @@ +package entities; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Stores per-VM runtime statistics. + */ +public class VMStats { + private final String vmName; + private final AtomicInteger tasksCompleted = new AtomicInteger(0); + private final AtomicInteger networkUses = new AtomicInteger(0); + private final AtomicInteger cpuUses = new AtomicInteger(0); + private final AtomicInteger memoryUses = new AtomicInteger(0); + private final AtomicInteger timeouts = new AtomicInteger(0); + private final AtomicLong totalWaitTime = new AtomicLong(0); + private final AtomicInteger waitCount = new AtomicInteger(0); + + public VMStats(String vmName) { + this.vmName = vmName; + } + + /** Records one completed task unit. */ + public void recordTaskCompleted() { + tasksCompleted.incrementAndGet(); + } + + /** Records one network acquisition. */ + public void recordNetworkUse() { + networkUses.incrementAndGet(); + } + + /** Records one CPU acquisition. */ + public void recordCPUUse() { + cpuUses.incrementAndGet(); + } + + /** Records one memory acquisition. */ + public void recordMemoryUse() { + memoryUses.incrementAndGet(); + } + + /** Records one timeout event. */ + public void recordTimeout() { + timeouts.incrementAndGet(); + } + + /** + * Adds a wait duration sample. + * + * @param waitTimeMs wait duration in milliseconds + */ + public void recordWaitTime(long waitTimeMs) { + totalWaitTime.addAndGet(waitTimeMs); + waitCount.incrementAndGet(); + } + + /** @return total completed tasks */ + public int getTasksCompleted() { + return tasksCompleted.get(); + } + + /** @return total network acquisitions */ + public int getNetworkUses() { + return networkUses.get(); + } + + /** @return total CPU acquisitions */ + public int getCPUUses() { + return cpuUses.get(); + } + + /** @return total memory acquisitions */ + public int getMemoryUses() { + return memoryUses.get(); + } + + /** @return total timeout count */ + public int getTimeouts() { + return timeouts.get(); + } + + /** @return average wait time in milliseconds */ + public long getAverageWaitTime() { + if (waitCount.get() == 0) + return 0; + return totalWaitTime.get() / waitCount.get(); + } + + /** @return total resource uses across CPU, memory, and network */ + public int getTotalResourceUses() { + return networkUses.get() + cpuUses.get() + memoryUses.get(); + } + + @Override + public String toString() { + return String.format( + "%s | Tasks: %d | Network: %d | CPU: %d | Memory: %d | Timeouts: %d | Avg Wait: %dms", + vmName, tasksCompleted.get(), networkUses.get(), cpuUses.get(), + memoryUses.get(), timeouts.get(), getAverageWaitTime()); + } +} diff --git a/CloudKernel/src/entities/VirtualMachine.java b/CloudKernel/src/entities/VirtualMachine.java index eb0bfd8..5580b57 100644 --- a/CloudKernel/src/entities/VirtualMachine.java +++ b/CloudKernel/src/entities/VirtualMachine.java @@ -1,51 +1,182 @@ package entities; import core.ClockSynchronizer; -import core.NetworkPortManager; -import utils.Logger; +import utils.GUILogger; +import utils.StatsCollector; + import java.util.concurrent.BrokenBarrierException; -// Represents one VM thread in the simulation. +/** + * Represents a virtual machine worker in the simulation. + */ public class VirtualMachine implements Runnable { private final String name; + private final int vmId; private final int cycles; private final ClockSynchronizer clock; - private final NetworkPortManager networkManager; + private final ResourceManager resourceManager; private final int workDuration; + private final GUILogger logger; + private final VMPriority priority; + private final VMStats stats; + private final StatsCollector statsCollector; + + private VMState currentState; - public VirtualMachine(String name, int cycles, ClockSynchronizer clock, - NetworkPortManager networkManager, int workDuration) { + /** + * Creates a virtual machine runnable. + * + * @param name VM name + * @param vmId VM numeric identifier + * @param cycles number of cycles to execute + * @param clock cycle synchronizer + * @param resourceManager shared resource manager + * @param workDuration simulated workload duration in milliseconds + * @param logger logger for runtime events + * @param priority assigned VM priority + * @param stats per-VM statistics collector + * @param statsCollector global statistics collector + */ + public VirtualMachine(String name, int vmId, int cycles, ClockSynchronizer clock, + ResourceManager resourceManager, int workDuration, GUILogger logger, + VMPriority priority, VMStats stats, StatsCollector statsCollector) { this.name = name; + this.vmId = vmId; this.cycles = cycles; this.clock = clock; - this.networkManager = networkManager; + this.resourceManager = resourceManager; this.workDuration = workDuration; + this.logger = logger; + this.priority = priority; + this.stats = stats; + this.statsCollector = statsCollector; + this.currentState = VMState.BOOTING; } + /** + * Executes the VM lifecycle across all configured cycles. + */ @Override public void run() { - Logger.log(name, "Virtual Machine is ONLINE.", Logger.GREEN); - try { + setState(VMState.READY); + logger.log(name, "Virtual Machine is ONLINE. Priority: " + priority, "BOOT"); + for (int i = 1; i <= cycles; i++) { - Logger.log(name, "Cycle " + i + " - executing workload...", Logger.CYAN); + logger.log(name, "Starting Cycle " + i + " of " + cycles, "INFO"); + + setState(VMState.RUNNING); + logger.log(name, "Executing workload for " + workDuration + "ms...", "INFO"); Thread.sleep(workDuration); + stats.recordTaskCompleted(); - networkManager.acquirePort(name); - Thread.sleep(500); - networkManager.releasePort(name); + // Request and use CPU + setState(VMState.REQUESTING_RESOURCE); + long cpuWaitStart = System.currentTimeMillis(); + if (resourceManager.acquireCPU(name)) { + setState(VMState.USING_RESOURCE); + Thread.sleep(300); + resourceManager.releaseCPU(name); + setState(VMState.RELEASING); + stats.recordCPUUse(); + stats.recordWaitTime(System.currentTimeMillis() - cpuWaitStart); + } else { + stats.recordTimeout(); + statsCollector.recordTimeout(); + } + // Request and use Memory + setState(VMState.REQUESTING_RESOURCE); + long memWaitStart = System.currentTimeMillis(); + if (resourceManager.acquireMemory(name)) { + setState(VMState.USING_RESOURCE); + Thread.sleep(250); + resourceManager.releaseMemory(name); + setState(VMState.RELEASING); + stats.recordMemoryUse(); + stats.recordWaitTime(System.currentTimeMillis() - memWaitStart); + } else { + stats.recordTimeout(); + statsCollector.recordTimeout(); + } + + // Request and use Network + setState(VMState.REQUESTING_RESOURCE); + long netWaitStart = System.currentTimeMillis(); + if (resourceManager.acquireNetwork(name)) { + setState(VMState.USING_RESOURCE); + Thread.sleep(500); + resourceManager.releaseNetwork(name); + setState(VMState.RELEASING); + stats.recordNetworkUse(); + stats.recordWaitTime(System.currentTimeMillis() - netWaitStart); + } else { + stats.recordTimeout(); + statsCollector.recordTimeout(); + } + + // Barrier synchronization + setState(VMState.BARRIER_WAIT); + logger.log(name, "All resources released. Waiting at barrier...", "BARRIER"); clock.sync(name); + setState(VMState.READY); } - Logger.log(name, "All cycles complete. Shutting down gracefully. [OK]", Logger.GREEN); + setState(VMState.SHUTDOWN); + logger.log(name, "All cycles complete. Shutting down gracefully. [OK]", "BOOT"); } catch (InterruptedException e) { - Logger.log(name, "Interrupted during execution!", Logger.RED); + logger.log(name, "Interrupted during execution!", "ERROR"); + setState(VMState.SHUTDOWN); Thread.currentThread().interrupt(); } catch (BrokenBarrierException e) { - Logger.log(name, "Clock barrier broken - system error!", Logger.RED); + logger.log(name, "Clock barrier broken - system error!", "ERROR"); + setState(VMState.SHUTDOWN); } } + + /** + * Updates the current VM state. + * + * @param newState next state + */ + private void setState(VMState newState) { + this.currentState = newState; + } + + /** + * @return current VM state + */ + public VMState getCurrentState() { + return currentState; + } + + /** + * @return assigned VM priority + */ + public VMPriority getPriority() { + return priority; + } + + /** + * @return per-VM statistics object + */ + public VMStats getStats() { + return stats; + } + + /** + * @return VM display name + */ + public String getName() { + return name; + } + + /** + * @return numeric VM identifier + */ + public int getVMId() { + return vmId; + } } \ No newline at end of file diff --git a/CloudKernel/src/shutdown/ShutdownManager.java b/CloudKernel/src/shutdown/ShutdownManager.java new file mode 100644 index 0000000..214a47f --- /dev/null +++ b/CloudKernel/src/shutdown/ShutdownManager.java @@ -0,0 +1,38 @@ +package shutdown; + +import utils.GUILogger; +import utils.StatsCollector; + +/** + * Registers a JVM shutdown hook to print a graceful summary. + */ +public class ShutdownManager { + private static StatsCollector statsCollector; + + /** + * Creates and registers the shutdown hook. + * + * @param stats stats collector used for summary output + */ + public ShutdownManager(StatsCollector stats) { + ShutdownManager.statsCollector = stats; + setupShutdownHook(); + } + + /** Registers the shutdown hook with the runtime. */ + private void setupShutdownHook() { + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + System.out.println(); + GUILogger.section("INTERRUPTED - GRACEFUL SHUTDOWN"); + System.out.println(GUILogger.YELLOW + "Received shutdown signal..." + GUILogger.RESET); + + if (statsCollector != null) { + statsCollector.printSummary(); + } + + System.out.println(); + System.out.println(GUILogger.GREEN + "CloudKernel shut down gracefully." + GUILogger.RESET); + System.out.println(); + })); + } +} diff --git a/CloudKernel/src/ui/BarrierPanel.java b/CloudKernel/src/ui/BarrierPanel.java new file mode 100644 index 0000000..9ef4b4d --- /dev/null +++ b/CloudKernel/src/ui/BarrierPanel.java @@ -0,0 +1,118 @@ +package ui; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import java.awt.*; + +/** + * Visualizes CyclicBarrier arrivals and cycle transitions. + */ +public class BarrierPanel extends JPanel { + private static final Color BG_PANEL = new Color(16, 24, 38); + private static final Color TEXT_PRIMARY = new Color(200, 216, 240); + private static final Color ACCENT_PURPLE = new Color(180, 80, 255); + private static final Color ACCENT_GREEN = new Color(0, 255, 136); + private static final Color ACCENT_CYAN = new Color(0, 212, 255); + + private final JLabel cycleLabel; + private final JLabel arrivedLabel; + private final JLabel[] dots; + private final Timer flashTimer; + + /** + * Creates the barrier panel. + * + * @param vmCount number of VM participants + */ + public BarrierPanel(int vmCount) { + setLayout(new BorderLayout(0, 8)); + setBackground(BG_PANEL); + setBorder(new EmptyBorder(8, 10, 8, 10)); + + JLabel title = new JLabel("CYCLIC BARRIER - Global Clock Sync"); + title.setForeground(ACCENT_PURPLE); + title.setFont(CloudKernelGUI.uiFont(Font.BOLD, 12)); + add(title, BorderLayout.NORTH); + + JPanel center = new JPanel(new BorderLayout()); + center.setOpaque(false); + + JPanel dotsRow = new JPanel(new FlowLayout(FlowLayout.LEFT, 10, 0)); + dotsRow.setOpaque(false); + dots = new JLabel[vmCount]; + for (int i = 0; i < vmCount; i++) { + JLabel dot = new JLabel("\u25cf VM-" + (i + 1)); + dot.setFont(CloudKernelGUI.getSymbolFont(Font.BOLD, 11)); + dot.setForeground(new Color(92, 99, 116)); + dots[i] = dot; + dotsRow.add(dot); + } + + center.add(dotsRow, BorderLayout.CENTER); + + JPanel infoRow = new JPanel(new FlowLayout(FlowLayout.LEFT, 18, 0)); + infoRow.setOpaque(false); + cycleLabel = new JLabel("CYCLE: 0"); + cycleLabel.setForeground(TEXT_PRIMARY); + cycleLabel.setFont(CloudKernelGUI.uiFont(Font.BOLD, 11)); + arrivedLabel = new JLabel("Arrived: 0 / " + vmCount); + arrivedLabel.setForeground(ACCENT_CYAN); + arrivedLabel.setFont(CloudKernelGUI.uiFont(Font.PLAIN, 11)); + infoRow.add(cycleLabel); + infoRow.add(arrivedLabel); + + center.add(infoRow, BorderLayout.SOUTH); + add(center, BorderLayout.CENTER); + + flashTimer = new Timer(260, e -> resetAfterFlash()); + flashTimer.setRepeats(false); + } + + /** + * Marks one VM as arrived. + * + * @param vmId VM id + * @param arrivedCount arrived count + * @param vmCount total VM count + */ + public void markArrived(int vmId, int arrivedCount, int vmCount) { + if (vmId >= 1 && vmId <= dots.length) { + dots[vmId - 1].setForeground(ACCENT_PURPLE); + } + arrivedLabel.setText("Arrived: " + arrivedCount + " / " + vmCount); + } + + /** + * Updates displayed cycle number. + * + * @param cycle cycle number + */ + public void setCycle(int cycle) { + cycleLabel.setText("CYCLE: " + cycle); + } + + /** + * Flashes all dots green and schedules state reset. + * + * @param vmCount total VM count + */ + public void flashAllAndReset(int vmCount) { + for (JLabel dot : dots) { + dot.setForeground(ACCENT_GREEN); + } + arrivedLabel.setText("Arrived: " + vmCount + " / " + vmCount); + flashTimer.restart(); + } + + /** Resets dot state after flash animation. */ + private void resetAfterFlash() { + for (JLabel dot : dots) { + dot.setForeground(new Color(92, 99, 116)); + } + String text = arrivedLabel.getText(); + if (text.contains("/")) { + String[] parts = text.split("/"); + arrivedLabel.setText("Arrived: 0 / " + parts[1].trim()); + } + } +} diff --git a/CloudKernel/src/ui/CloudKernelGUI.java b/CloudKernel/src/ui/CloudKernelGUI.java new file mode 100644 index 0000000..0ac3784 --- /dev/null +++ b/CloudKernel/src/ui/CloudKernelGUI.java @@ -0,0 +1,622 @@ +package ui; + +import config.ConfigLoader; +import core.BootManager; +import core.ClockSynchronizer; +import entities.ResourceManager; +import entities.VMPriority; +import entities.VMState; +import entities.VMStats; +import entities.VirtualMachine; +import shutdown.ShutdownManager; +import utils.GUILogger; +import utils.StatsCollector; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import javax.swing.border.MatteBorder; +import java.awt.*; +import java.util.Arrays; +import java.util.HashSet; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Main Swing dashboard window for CloudKernel runtime monitoring. + */ +public class CloudKernelGUI extends JFrame { + private static final Color BG_DARK = new Color(10, 14, 26); + private static final Color BG_HEADER = new Color(12, 19, 32); + private static final Color TEXT_PRIMARY = new Color(200, 216, 240); + private static final Color ACCENT_CYAN = new Color(0, 212, 255); + private static final Color ACCENT_GREEN = new Color(0, 255, 136); + + private static final Pattern VM_NAME_PATTERN = Pattern.compile("VM-(\\d+)"); + private static final Pattern CYCLE_PATTERN = Pattern.compile("Cycle (\\d+)"); + + private final ConfigLoader config; + private final StatsCollector statsCollector; + private final GUILogger logger; + private final BootManager bootManager; + private final ClockSynchronizer clockSynchronizer; + private final ResourceManager resourceManager; + + private final int numVMs; + private final int numCycles; + + private final AtomicBoolean running = new AtomicBoolean(false); + private final AtomicBoolean paused = new AtomicBoolean(false); + + private final JLabel clockLabel = new JLabel("00:00:00"); + private final JLabel onlineLabel = new JLabel("\u25cf SYSTEM OFFLINE"); + private final JPanel[] bootChips = new JPanel[4]; + private final JLabel latchLabel = new JLabel("LATCH: 4"); + + private final Map vmCards = new LinkedHashMap<>(); + private final Set barrierArrivals = ConcurrentHashMap.newKeySet(); + + private ResourceMonitorPanel resourceMonitorPanel; + private BarrierPanel barrierPanel; + private LogPanel logPanel; + private StatsBar statsBar; + private ControlPanel controlPanel; + private DashboardUpdater dashboardUpdater; + + private Timer clockTimer; + private Timer onlinePulseTimer; + private Timer refreshTimer; + private Timer bootLatchTimer; + + /** + * Creates and wires the complete dashboard UI and simulation observers. + */ + public CloudKernelGUI() { + this.config = new ConfigLoader(); + this.statsCollector = new StatsCollector(); + this.logger = new GUILogger(); + this.numVMs = config.getVMCount(); + this.numCycles = config.getCycleCount(); + + this.bootManager = new BootManager(logger); + this.clockSynchronizer = new ClockSynchronizer(numVMs, logger, statsCollector); + this.resourceManager = new ResourceManager( + config.getCPUPermits(), + config.getMemoryPermits(), + config.getNetworkPermits(), + config.getTimeoutDuration(), + logger); + + new ShutdownManager(statsCollector); + + setupFrame(); + setupLayout(); + setupTimers(); + registerLoggerListener(); + } + + /** Configures top-level frame properties. */ + private void setupFrame() { + setTitle("CloudKernel — Cloud Hypervisor Monitor"); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setLayout(new BorderLayout()); + setSize(1280, 800); + setResizable(false); + setLocationRelativeTo(null); + getContentPane().setBackground(BG_DARK); + } + + /** Builds and attaches all visual dashboard sections. */ + private void setupLayout() { + JPanel topContainer = new JPanel(); + topContainer.setLayout(new BoxLayout(topContainer, BoxLayout.Y_AXIS)); + topContainer.setBackground(BG_DARK); + topContainer.add(buildHeader()); + topContainer.add(buildBootPanel()); + add(topContainer, BorderLayout.NORTH); + + resourceMonitorPanel = new ResourceMonitorPanel(config.getCPUPermits(), config.getMemoryPermits(), + config.getNetworkPermits()); + logPanel = new LogPanel(); + statsBar = new StatsBar(); + barrierPanel = new BarrierPanel(numVMs); + + JPanel vmDashboard = new JPanel(new GridLayout(1, numVMs, 8, 0)); + vmDashboard.setBackground(BG_DARK); + vmDashboard.setBorder(new EmptyBorder(10, 10, 8, 10)); + for (int i = 1; i <= numVMs; i++) { + VMCard card = new VMCard("VM-" + i); + vmCards.put(card.getVmName(), card); + vmDashboard.add(card); + } + + dashboardUpdater = new DashboardUpdater(vmCards, resourceMonitorPanel, barrierPanel, statsBar); + + JPanel centerColumn = new JPanel(new BorderLayout(0, 8)); + centerColumn.setBackground(BG_DARK); + centerColumn.add(vmDashboard, BorderLayout.CENTER); + centerColumn.add(barrierPanel, BorderLayout.SOUTH); + + controlPanel = new ControlPanel( + this::startSimulation, + this::togglePause, + this::resetDashboard, + speed -> GUILogger + .boot("Simulation speed set to " + String.format("%.1fx", speed) + " (visual only).")); + + JPanel bottom = new JPanel(new BorderLayout()); + bottom.setBackground(BG_DARK); + bottom.add(statsBar, BorderLayout.CENTER); + bottom.add(controlPanel, BorderLayout.SOUTH); + + add(resourceMonitorPanel, BorderLayout.WEST); + add(centerColumn, BorderLayout.CENTER); + add(logPanel, BorderLayout.EAST); + add(bottom, BorderLayout.SOUTH); + + resourceMonitorPanel.setPreferredSize(new Dimension(310, 0)); + logPanel.setPreferredSize(new Dimension(370, 0)); + } + + /** + * Creates the top header bar. + * + * @return header panel + */ + private JPanel buildHeader() { + JPanel header = new JPanel(new BorderLayout()); + header.setBackground(BG_HEADER); + header.setPreferredSize(new Dimension(1280, 52)); + header.setBorder(new MatteBorder(0, 0, 1, 0, new Color(30, 45, 69))); + + JLabel title = new JLabel("\u2601 CLOUDKERNEL HYPERVISOR"); + title.setFont(getSymbolFont(Font.BOLD, 18)); + title.setForeground(ACCENT_CYAN); + title.setBorder(new EmptyBorder(0, 12, 0, 0)); + header.add(title, BorderLayout.WEST); + + JPanel right = new JPanel(new FlowLayout(FlowLayout.RIGHT, 16, 14)); + right.setOpaque(false); + + clockLabel.setFont(uiFont(Font.PLAIN, 14)); + clockLabel.setForeground(TEXT_PRIMARY); + + onlineLabel.setFont(getSymbolFont(Font.BOLD, 13)); + onlineLabel.setForeground(new Color(90, 98, 112)); + + right.add(clockLabel); + right.add(onlineLabel); + header.add(right, BorderLayout.EAST); + + return header; + } + + /** + * Creates the boot visualization panel. + * + * @return boot panel + */ + private JPanel buildBootPanel() { + JPanel panel = new JPanel(new BorderLayout(10, 8)); + panel.setBackground(BG_DARK); + panel.setBorder(new EmptyBorder(10, 12, 10, 12)); + + JLabel title = new JLabel("BOOT MANAGER - CountDownLatch"); + title.setForeground(ACCENT_CYAN); + title.setFont(uiFont(Font.BOLD, 13)); + panel.add(title, BorderLayout.NORTH); + + JPanel chipRow = new JPanel(new GridLayout(1, 4, 8, 0)); + chipRow.setOpaque(false); + + bootChips[0] = bootChip("Disk"); + bootChips[1] = bootChip("RAM"); + bootChips[2] = bootChip("Network Stack"); + bootChips[3] = bootChip("CPU Scheduler"); + + for (JPanel chip : bootChips) { + chipRow.add(chip); + } + + panel.add(chipRow, BorderLayout.CENTER); + + latchLabel.setForeground(new Color(130, 150, 180)); + latchLabel.setFont(uiFont(Font.BOLD, 12)); + panel.add(latchLabel, BorderLayout.SOUTH); + + return panel; + } + + /** + * Creates one boot subsystem chip. + * + * @param name chip label + * @return chip panel + */ + private JPanel bootChip(String name) { + JPanel chip = new JPanel(new BorderLayout()); + chip.setBackground(new Color(30, 45, 69)); + chip.setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createLineBorder(new Color(42, 63, 98), 1), + new EmptyBorder(7, 8, 7, 8))); + + JLabel label = new JLabel(name, SwingConstants.CENTER); + label.setForeground(TEXT_PRIMARY); + label.setFont(uiFont(Font.BOLD, 12)); + chip.add(label, BorderLayout.CENTER); + + return chip; + } + + /** Initializes all Swing timers used by the dashboard. */ + private void setupTimers() { + clockTimer = new Timer(1000, + e -> clockLabel.setText(LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")))); + clockTimer.start(); + + onlinePulseTimer = new Timer(500, e -> { + if (running.get()) { + Color current = onlineLabel.getForeground(); + onlineLabel.setForeground(current.equals(ACCENT_GREEN) ? new Color(80, 140, 92) : ACCENT_GREEN); + } + }); + + refreshTimer = new Timer(220, e -> refreshPanels()); + + bootLatchTimer = new Timer(150, e -> { + long count = bootManager.getBootLatch().getCount(); + if (count == 0) { + latchLabel.setText("LATCH: 0 (RELEASED)"); + bootLatchTimer.stop(); + } else { + latchLabel.setText("LATCH: " + count); + } + }); + } + + /** Registers the logger listener that feeds the live log and visual updates. */ + private void registerLoggerListener() { + GUILogger.addListener(entry -> SwingUtilities.invokeLater(() -> { + logPanel.appendEntry(entry); + processLogEvent(entry); + })); + } + + /** + * Applies event-driven UI changes from one log entry. + * + * @param entry parsed logger entry + */ + private void processLogEvent(GUILogger.LogEntry entry) { + String message = entry.message; + String category = entry.category.toUpperCase(); + String vmName = entry.vmName; + + if ("BOOT".equals(category) && "SYSTEM".equals(vmName)) { + if (message.contains("Disk subsystem initialized")) { + setBootChipReady(0); + } + if (message.contains("RAM subsystem initialized")) { + setBootChipReady(1); + } + if (message.contains("Network Stack initialized")) { + setBootChipReady(2); + } + if (message.contains("CPU Scheduler initialized")) { + setBootChipReady(3); + } + } + + if (!vmCards.containsKey(vmName)) { + if (message.contains("Global Clock Tick #")) { + int cycle = extractLastNumber(message); + barrierArrivals.clear(); + dashboardUpdater.updateBarrierCycle(cycle, numVMs); + } + return; + } + + if (message.contains("Starting Cycle")) { + Matcher cycleMatcher = CYCLE_PATTERN.matcher(message); + if (cycleMatcher.find()) { + int cycle = Integer.parseInt(cycleMatcher.group(1)); + int progress = (int) Math.round((cycle * 100.0) / Math.max(1, numCycles)); + vmCards.get(vmName).setCycleProgress(progress); + dashboardUpdater.update(vmName, VMState.RUNNING); + } + return; + } + + if (message.contains("Executing workload")) { + dashboardUpdater.update(vmName, VMState.RUNNING); + return; + } + + if (message.contains("acquired")) { + dashboardUpdater.update(vmName, VMState.USING_RESOURCE); + return; + } + + if (message.contains("released")) { + dashboardUpdater.update(vmName, VMState.REQUESTING_RESOURCE); + return; + } + + if (message.contains("[TIMEOUT]")) { + dashboardUpdater.update(vmName, VMState.TIMEOUT); + return; + } + + if ("BARRIER".equals(category)) { + dashboardUpdater.update(vmName, VMState.BARRIER_WAIT); + Integer vmId = extractVmId(vmName); + if (vmId != null) { + barrierArrivals.add(vmId); + dashboardUpdater.updateBarrierArrivals(vmId, barrierArrivals.size(), numVMs); + } + return; + } + + if (message.contains("Shutting down gracefully")) { + dashboardUpdater.update(vmName, VMState.SHUTDOWN); + } + } + + /** + * Marks one boot chip as initialized. + * + * @param index chip index + */ + private void setBootChipReady(int index) { + if (index < 0 || index >= bootChips.length) { + return; + } + bootChips[index].setBackground(new Color(0, 255, 136)); + for (Component child : bootChips[index].getComponents()) { + if (child instanceof JLabel) { + child.setForeground(new Color(5, 18, 16)); + } + } + bootChips[index].repaint(); + } + + /** Refreshes all data-driven dashboard widgets. */ + private void refreshPanels() { + dashboardUpdater.updateResourceSlots( + resourceManager.getCPUHolders(), + resourceManager.getMemoryHolders(), + resourceManager.getNetworkHolders()); + + dashboardUpdater.updateVMStats(statsCollector.getAllVMStats(), numCycles); + dashboardUpdater.updateSystemStats(statsCollector); + } + + /** Starts one simulation run if not already running. */ + private synchronized void startSimulation() { + if (running.getAndSet(true)) { + return; + } + + controlPanel.setBootEnabled(false); + controlPanel.setPauseEnabled(true); + controlPanel.setResetEnabled(true); + + onlineLabel.setText("\u25cf SYSTEM ONLINE"); + onlineLabel.setForeground(ACCENT_GREEN); + onlinePulseTimer.start(); + refreshTimer.start(); + bootLatchTimer.start(); + + Thread simulation = new Thread(this::runSimulation, "CloudKernel-Simulation"); + simulation.start(); + } + + /** Toggles pause/resume UI mode for observer controls. */ + private void togglePause() { + paused.set(!paused.get()); + if (paused.get()) { + controlPanel.setPauseText("\u25b6 RESUME"); + GUILogger.boot("Pause requested from dashboard (observer mode). VM threads continue unchanged."); + } else { + controlPanel.setPauseText("|| PAUSE"); + GUILogger.boot("Dashboard resumed."); + } + } + + /** Resets dashboard visuals when simulation is not running. */ + private synchronized void resetDashboard() { + if (running.get()) { + GUILogger.boot("Reset requested while running. Observer mode keeps worker threads unchanged."); + return; + } + + for (int i = 0; i < bootChips.length; i++) { + bootChips[i].setBackground(new Color(30, 45, 69)); + for (Component child : bootChips[i].getComponents()) { + if (child instanceof JLabel) { + child.setForeground(TEXT_PRIMARY); + } + } + } + + latchLabel.setText("LATCH: 4"); + barrierArrivals.clear(); + logPanel.clear(); + dashboardUpdater.reset(); + + for (VMCard card : vmCards.values()) { + card.setState(VMState.BOOTING); + card.setTaskCount(0); + card.setAvgWait(0); + card.setCycleProgress(0); + card.setResourceHold(false, false, false); + } + + onlineLabel.setText("\u25cf SYSTEM OFFLINE"); + onlineLabel.setForeground(new Color(90, 98, 112)); + controlPanel.setPauseText("|| PAUSE"); + controlPanel.setBootEnabled(true); + } + + /** Executes the full boot and VM simulation lifecycle. */ + private void runSimulation() { + try { + GUILogger.section("PHASE 1: SYSTEM BOOT [CountDownLatch]"); + bootManager.initDisk(); + bootManager.initRAM(); + bootManager.initNetworkStack(); + bootManager.initCPUScheduler(); + bootManager.awaitBootCompletion(); + + GUILogger.section("PHASE 2: VM EXECUTION [CyclicBarrier + Semaphore]"); + GUILogger.boot("Launching " + numVMs + " VMs for " + numCycles + " cycles each..."); + + Thread[] vmThreads = new Thread[numVMs]; + for (int i = 0; i < numVMs; i++) { + int vmId = i + 1; + String vmName = "VM-" + vmId; + VMPriority priority = VMPriority.getRandomPriority(); + VMStats vmStats = statsCollector.getOrCreateVMStats(vmName); + + VMCard card = vmCards.get(vmName); + if (card != null) { + SwingUtilities.invokeLater(() -> card.setPriority(priority)); + } + + int workDuration = 600 + (vmId * 200); + VirtualMachine vm = new VirtualMachine( + vmName, + vmId, + numCycles, + clockSynchronizer, + resourceManager, + workDuration, + logger, + priority, + vmStats, + statsCollector); + + vmThreads[i] = new Thread(vm, vmName); + vmThreads[i].start(); + } + + for (Thread vmThread : vmThreads) { + vmThread.join(); + } + + GUILogger.section("PHASE 3: SYSTEM SHUTDOWN"); + GUILogger.boot("All VMs have completed execution. CloudKernel shutting down."); + statsCollector.printSummary(); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + GUILogger.boot("Simulation interrupted."); + } finally { + SwingUtilities.invokeLater(() -> { + running.set(false); + onlinePulseTimer.stop(); + refreshTimer.stop(); + onlineLabel.setText("\u25cf SYSTEM OFFLINE"); + onlineLabel.setForeground(new Color(90, 98, 112)); + controlPanel.setPauseEnabled(false); + controlPanel.setBootEnabled(true); + }); + } + } + + /** + * Parses VM id from a VM name string. + * + * @param vmName VM name + * @return parsed id or null when missing + */ + private Integer extractVmId(String vmName) { + Matcher matcher = VM_NAME_PATTERN.matcher(vmName); + if (matcher.find()) { + return Integer.parseInt(matcher.group(1)); + } + return null; + } + + /** + * Extracts the trailing number from a message string. + * + * @param message source message + * @return trailing number or zero + */ + private int extractLastNumber(String message) { + StringBuilder number = new StringBuilder(); + for (int i = message.length() - 1; i >= 0; i--) { + char c = message.charAt(i); + if (Character.isDigit(c)) { + number.insert(0, c); + } else if (number.length() > 0) { + break; + } + } + return number.length() == 0 ? 0 : Integer.parseInt(number.toString()); + } + + /** + * Creates a UI font with project defaults. + * + * @param style font style + * @param size font size + * @return configured font + */ + public static Font uiFont(int style, int size) { + return new Font(fontFamily(), style, size); + } + + /** + * Creates a font that is likely to render Unicode symbols consistently. + * + * @param style font style + * @param size font size + * @return resolved symbol font + */ + public static Font getSymbolFont(int style, int size) { + String[] candidates = {"Segoe UI Symbol", "DejaVu Sans", "Arial Unicode MS", "Symbola", "Dialog"}; + GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); + Set available = new HashSet<>(Arrays.asList(ge.getAvailableFontFamilyNames())); + for (String name : candidates) { + if (available.contains(name)) { + return new Font(name, style, size); + } + } + return new Font("Dialog", style, size); + } + + /** + * Resolves the preferred monospace family available on the machine. + * + * @return resolved font family name + */ + public static String fontFamily() { + String[] available = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames(); + for (String name : available) { + if ("JetBrains Mono".equalsIgnoreCase(name)) { + return "JetBrains Mono"; + } + } + return "Consolas"; + } + + /** + * Formats uptime as mm:ss. + * + * @param uptimeMs uptime in milliseconds + * @return formatted uptime + */ + public static String formatUptime(long uptimeMs) { + long totalSeconds = uptimeMs / 1000; + long minutes = totalSeconds / 60; + long seconds = totalSeconds % 60; + return String.format("%02d:%02d", minutes, seconds); + } +} diff --git a/CloudKernel/src/ui/ControlPanel.java b/CloudKernel/src/ui/ControlPanel.java new file mode 100644 index 0000000..c0a8f3f --- /dev/null +++ b/CloudKernel/src/ui/ControlPanel.java @@ -0,0 +1,135 @@ +package ui; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import javax.swing.border.LineBorder; +import java.awt.*; +import java.util.function.Consumer; + +/** + * Bottom control bar for simulation actions and speed configuration. + */ +public class ControlPanel extends JPanel { + private static final Color BG_PANEL = new Color(12, 19, 32); + private static final Color ACCENT_CYAN = new Color(0, 212, 255); + private static final Color ACCENT_RED = new Color(255, 90, 90); + + private final JButton bootButton; + private final JButton pauseButton; + private final JButton resetButton; + private final JSlider speedSlider; + private final JLabel speedLabel; + + /** + * Creates the control panel. + * + * @param onBoot callback for boot button + * @param onPause callback for pause button + * @param onReset callback for reset button + * @param onSpeedChanged callback for slider changes + */ + public ControlPanel(Runnable onBoot, Runnable onPause, Runnable onReset, Consumer onSpeedChanged) { + setLayout(new FlowLayout(FlowLayout.CENTER, 14, 10)); + setBackground(BG_PANEL); + setBorder(new EmptyBorder(4, 8, 8, 8)); + + bootButton = createButton("\u25b6 BOOT SYSTEM", ACCENT_CYAN); + pauseButton = createButton("|| PAUSE", new Color(255, 191, 0)); + resetButton = createButton("\u21ba RESET", ACCENT_RED); + + bootButton.addActionListener(e -> onBoot.run()); + pauseButton.addActionListener(e -> onPause.run()); + resetButton.addActionListener(e -> onReset.run()); + + add(bootButton); + add(pauseButton); + add(resetButton); + + JLabel sliderTitle = new JLabel("SIMULATION SPEED"); + sliderTitle.setForeground(new Color(200, 216, 240)); + sliderTitle.setFont(CloudKernelGUI.uiFont(Font.BOLD, 11)); + add(sliderTitle); + + speedLabel = new JLabel("1.0x"); + speedLabel.setForeground(Color.WHITE); + speedLabel.setFont(CloudKernelGUI.uiFont(Font.BOLD, 12)); + + speedSlider = new JSlider(50, 300, 100); + speedSlider.setPreferredSize(new Dimension(170, 26)); + speedSlider.setBackground(BG_PANEL); + speedSlider.setForeground(ACCENT_CYAN); + speedSlider.addChangeListener(e -> { + double value = speedSlider.getValue() / 100.0; + speedLabel.setText(String.format("%.1fx", value)); + onSpeedChanged.accept(value); + }); + add(speedSlider); + add(speedLabel); + } + + /** + * Creates a styled dashboard button. + * + * @param text button text + * @param borderColor border color + * @return styled button + */ + private JButton createButton(String text, Color borderColor) { + JButton button = new JButton(text); + button.setFont(CloudKernelGUI.getSymbolFont(Font.BOLD, 12)); + button.setForeground(Color.WHITE); + button.setBackground(new Color(20, 29, 45)); + button.setBorder(new LineBorder(borderColor, 2)); + button.setFocusPainted(false); + button.setPreferredSize(new Dimension(160, 34)); + + button.addMouseListener(new java.awt.event.MouseAdapter() { + @Override + public void mouseEntered(java.awt.event.MouseEvent evt) { + button.setBackground(new Color(32, 42, 62)); + } + + @Override + public void mouseExited(java.awt.event.MouseEvent evt) { + button.setBackground(new Color(20, 29, 45)); + } + }); + return button; + } + + /** + * Enables or disables the boot button. + * + * @param enabled true to enable + */ + public void setBootEnabled(boolean enabled) { + bootButton.setEnabled(enabled); + } + + /** + * Updates pause button text. + * + * @param text button text + */ + public void setPauseText(String text) { + pauseButton.setText(text); + } + + /** + * Enables or disables the pause button. + * + * @param enabled true to enable + */ + public void setPauseEnabled(boolean enabled) { + pauseButton.setEnabled(enabled); + } + + /** + * Enables or disables the reset button. + * + * @param enabled true to enable + */ + public void setResetEnabled(boolean enabled) { + resetButton.setEnabled(enabled); + } +} diff --git a/CloudKernel/src/ui/DashboardUpdater.java b/CloudKernel/src/ui/DashboardUpdater.java new file mode 100644 index 0000000..6d04ed9 --- /dev/null +++ b/CloudKernel/src/ui/DashboardUpdater.java @@ -0,0 +1,172 @@ +package ui; + +import entities.VMState; +import entities.VMStats; +import utils.StatsCollector; + +import javax.swing.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * Centralized UI refresh adapter for dashboard widgets. + */ +public class DashboardUpdater { + private final Map cards; + private final ResourceMonitorPanel resourcePanel; + private final BarrierPanel barrierPanel; + private final StatsBar statsBar; + + /** + * Creates an updater bound to all dashboard sections. + * + * @param cards VM cards indexed by VM name + * @param resourcePanel resource panel + * @param barrierPanel barrier panel + * @param statsBar stats bar + */ + public DashboardUpdater(Map cards, + ResourceMonitorPanel resourcePanel, + BarrierPanel barrierPanel, + StatsBar statsBar) { + this.cards = cards; + this.resourcePanel = resourcePanel; + this.barrierPanel = barrierPanel; + this.statsBar = statsBar; + } + + /** + * Updates one VM state card. + * + * @param vmName VM name + * @param state VM state + */ + public void update(String vmName, VMState state) { + SwingUtilities.invokeLater(() -> { + VMCard card = cards.get(vmName); + if (card != null) { + card.setState(state); + } + }); + } + + /** + * Refreshes displayed resource holders and VM resource indicators. + * + * @param cpuHolders CPU holder map + * @param memoryHolders memory holder map + * @param networkHolders network holder map + */ + public void updateResourceSlots(Map cpuHolders, + Map memoryHolders, + Map networkHolders) { + SwingUtilities.invokeLater(() -> { + List cpu = sortedValues(cpuHolders); + List memory = sortedValues(memoryHolders); + List network = sortedValues(networkHolders); + + resourcePanel.updateCPU(cpu); + resourcePanel.updateMemory(memory); + resourcePanel.updateNetwork(network); + + for (VMCard card : cards.values()) { + String vm = card.getVmName(); + card.setResourceHold(cpu.contains(vm), memory.contains(vm), network.contains(vm)); + } + }); + } + + /** + * Updates barrier arrival state for one VM. + * + * @param vmId VM numeric id + * @param arrived arrived VM count + * @param total total VM count + */ + public void updateBarrierArrivals(int vmId, int arrived, int total) { + SwingUtilities.invokeLater(() -> barrierPanel.markArrived(vmId, arrived, total)); + } + + /** + * Updates cycle value and executes barrier flash animation. + * + * @param cycle cycle number + * @param total total VM count + */ + public void updateBarrierCycle(int cycle, int total) { + SwingUtilities.invokeLater(() -> { + barrierPanel.setCycle(cycle); + barrierPanel.flashAllAndReset(total); + }); + } + + /** + * Updates VM cards and aggregate operation counters. + * + * @param vmStats per-VM stats map + * @param totalCycles configured total cycles + */ + public void updateVMStats(Map vmStats, int totalCycles) { + SwingUtilities.invokeLater(() -> { + int networkOps = 0; + int cpuOps = 0; + int totalTimeouts = 0; + + for (Map.Entry entry : vmStats.entrySet()) { + VMStats stats = entry.getValue(); + VMCard card = cards.get(entry.getKey()); + if (card != null) { + card.setTaskCount(stats.getTasksCompleted()); + card.setAvgWait(stats.getAverageWaitTime()); + int progress = totalCycles == 0 ? 0 + : Math.min(100, (stats.getTasksCompleted() * 100) / Math.max(1, totalCycles * 4)); + card.setCycleProgress(progress); + } + + networkOps += stats.getNetworkUses(); + cpuOps += stats.getCPUUses(); + totalTimeouts += stats.getTimeouts(); + } + + statsBar.setValue("Network Ops", String.valueOf(networkOps)); + statsBar.setValue("CPU Ops", String.valueOf(cpuOps)); + statsBar.setValue("Timeouts", String.valueOf(totalTimeouts)); + }); + } + + /** + * Updates system-wide statistics cards. + * + * @param statsCollector collector instance + */ + public void updateSystemStats(StatsCollector statsCollector) { + SwingUtilities.invokeLater(() -> { + statsBar.setValue("Total Cycles", String.valueOf(statsCollector.getTotalCycles())); + statsBar.setValue("Contentions", String.valueOf(statsCollector.getTotalContentions())); + statsBar.setValue("Uptime", CloudKernelGUI.formatUptime(statsCollector.getUptimeMs())); + }); + } + + /** Resets stats bar values to initial state. */ + public void reset() { + SwingUtilities.invokeLater(statsBar::reset); + } + + /** + * Converts a slot holder map into a key-sorted holder list. + * + * @param map holder map + * @return sorted holder values + */ + private List sortedValues(Map map) { + List keys = new ArrayList<>(map.keySet()); + Collections.sort(keys); + List values = new ArrayList<>(); + for (String key : keys) { + values.add(map.get(key)); + } + return values; + } +} diff --git a/CloudKernel/src/ui/LogPanel.java b/CloudKernel/src/ui/LogPanel.java new file mode 100644 index 0000000..8412958 --- /dev/null +++ b/CloudKernel/src/ui/LogPanel.java @@ -0,0 +1,95 @@ +package ui; + +import utils.GUILogger; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import javax.swing.text.SimpleAttributeSet; +import javax.swing.text.StyleConstants; +import javax.swing.text.StyledDocument; +import java.awt.*; + +/** + * Styled live log panel for categorized simulation events. + */ +public class LogPanel extends JPanel { + private static final Color BG_PANEL = new Color(16, 24, 38); + private static final Color BG_LOG = new Color(11, 18, 30); + private static final Color TEXT_PRIMARY = new Color(200, 216, 240); + private static final Color ACCENT_CYAN = new Color(0, 212, 255); + + private final JTextPane textPane; + + /** Creates the log panel UI. */ + public LogPanel() { + setLayout(new BorderLayout(0, 8)); + setBackground(BG_PANEL); + setBorder(new EmptyBorder(8, 10, 8, 10)); + + JLabel title = new JLabel("LIVE LOG PANEL"); + title.setForeground(ACCENT_CYAN); + title.setFont(CloudKernelGUI.uiFont(Font.BOLD, 12)); + add(title, BorderLayout.NORTH); + + textPane = new JTextPane(); + textPane.setEditable(false); + textPane.setBackground(BG_LOG); + textPane.setForeground(TEXT_PRIMARY); + textPane.setFont(CloudKernelGUI.uiFont(Font.PLAIN, 11)); + + JScrollPane scrollPane = new JScrollPane(textPane); + add(scrollPane, BorderLayout.CENTER); + } + + /** + * Appends one entry and auto-scrolls to bottom. + * + * @param entry log entry + */ + public void appendEntry(GUILogger.LogEntry entry) { + StyledDocument doc = textPane.getStyledDocument(); + SimpleAttributeSet attrs = new SimpleAttributeSet(); + StyleConstants.setForeground(attrs, colorForCategory(entry.category)); + StyleConstants.setFontFamily(attrs, CloudKernelGUI.fontFamily()); + StyleConstants.setFontSize(attrs, 11); + + String line = String.format("[%s] [%s] %s -> %s%n", entry.timestamp, entry.category, entry.vmName, + entry.message); + try { + doc.insertString(doc.getLength(), line, attrs); + textPane.setCaretPosition(doc.getLength()); + } catch (Exception ignored) { + } + } + + /** Clears all log content. */ + public void clear() { + textPane.setText(""); + } + + /** + * Maps category to display color. + * + * @param category log category + * @return display color + */ + private Color colorForCategory(String category) { + String upper = category.toUpperCase(); + if ("BOOT".equals(upper)) { + return new Color(0, 212, 255); + } + if ("NETWORK".equals(upper) || "CPU".equals(upper) || "MEMORY".equals(upper)) { + return new Color(0, 255, 136); + } + if ("WAITING".equals(upper)) { + return new Color(255, 191, 0); + } + if ("BARRIER".equals(upper)) { + return new Color(180, 80, 255); + } + if ("TIMEOUT".equals(upper) || "ERROR".equals(upper)) { + return new Color(255, 90, 90); + } + return TEXT_PRIMARY; + } +} diff --git a/CloudKernel/src/ui/ResourceMonitorPanel.java b/CloudKernel/src/ui/ResourceMonitorPanel.java new file mode 100644 index 0000000..bc6aaf5 --- /dev/null +++ b/CloudKernel/src/ui/ResourceMonitorPanel.java @@ -0,0 +1,137 @@ +package ui; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import javax.swing.border.LineBorder; +import java.awt.*; +import java.util.ArrayList; +import java.util.List; + +/** + * Displays live semaphore slot occupancy for CPU, memory, and network + * resources. + */ +public class ResourceMonitorPanel extends JPanel { + private static final Color BG_PANEL = new Color(16, 24, 38); + private static final Color TEXT_PRIMARY = new Color(200, 216, 240); + private static final Color TEXT_MUTED = new Color(130, 150, 180); + private static final Color ACCENT_CYAN = new Color(0, 212, 255); + private static final Color ACCENT_GREEN = new Color(0, 255, 136); + + private final List cpuSlots = new ArrayList<>(); + private final List memorySlots = new ArrayList<>(); + private final List networkSlots = new ArrayList<>(); + + /** + * Creates the resource monitor panel. + * + * @param cpuPermits CPU permit count + * @param memoryPermits memory permit count + * @param networkPermits network permit count + */ + public ResourceMonitorPanel(int cpuPermits, int memoryPermits, int networkPermits) { + setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + setBackground(BG_PANEL); + setBorder(new EmptyBorder(10, 10, 10, 10)); + + JLabel title = new JLabel("RESOURCE MONITOR"); + title.setForeground(ACCENT_CYAN); + title.setFont(CloudKernelGUI.uiFont(Font.BOLD, 13)); + title.setAlignmentX(Component.LEFT_ALIGNMENT); + add(title); + add(Box.createVerticalStrut(10)); + + add(createSection("CPU Cores - Semaphore (" + cpuPermits + " permits)", cpuPermits, cpuSlots)); + add(Box.createVerticalStrut(8)); + add(createSection("Memory Blocks - Semaphore (" + memoryPermits + " permits)", memoryPermits, memorySlots)); + add(Box.createVerticalStrut(8)); + add(createSection("Network Ports - Semaphore (" + networkPermits + " permits)", networkPermits, networkSlots)); + } + + /** + * Builds one resource section row. + * + * @param titleText section title + * @param count number of slots + * @param targets backing labels list + * @return section panel + */ + private JPanel createSection(String titleText, int count, List targets) { + JPanel section = new JPanel(); + section.setLayout(new BoxLayout(section, BoxLayout.Y_AXIS)); + section.setOpaque(false); + section.setAlignmentX(Component.LEFT_ALIGNMENT); + + JLabel title = new JLabel(titleText); + title.setForeground(TEXT_PRIMARY); + title.setFont(CloudKernelGUI.uiFont(Font.PLAIN, 11)); + title.setAlignmentX(Component.LEFT_ALIGNMENT); + section.add(title); + section.add(Box.createVerticalStrut(5)); + + JPanel row = new JPanel(new GridLayout(1, count, 6, 0)); + row.setOpaque(false); + + for (int i = 0; i < count; i++) { + JLabel slot = new JLabel("FREE", SwingConstants.CENTER); + slot.setOpaque(true); + slot.setBackground(new Color(25, 35, 52)); + slot.setForeground(TEXT_MUTED); + slot.setFont(CloudKernelGUI.uiFont(Font.BOLD, 10)); + slot.setBorder(new LineBorder(new Color(42, 63, 98), 1)); + targets.add(slot); + row.add(slot); + } + + section.add(row); + return section; + } + + /** + * Updates CPU slot labels. + * + * @param holders current holder list + */ + public void updateCPU(List holders) { + updateSlots(cpuSlots, holders); + } + + /** + * Updates memory slot labels. + * + * @param holders current holder list + */ + public void updateMemory(List holders) { + updateSlots(memorySlots, holders); + } + + /** + * Updates network slot labels. + * + * @param holders current holder list + */ + public void updateNetwork(List holders) { + updateSlots(networkSlots, holders); + } + + /** + * Applies holder labels to a slot collection. + * + * @param slots visual slots + * @param holders holder names + */ + private void updateSlots(List slots, List holders) { + for (int i = 0; i < slots.size(); i++) { + JLabel slot = slots.get(i); + if (i < holders.size()) { + slot.setText(holders.get(i)); + slot.setForeground(ACCENT_GREEN); + slot.setBackground(new Color(18, 62, 46)); + } else { + slot.setText("FREE"); + slot.setForeground(TEXT_MUTED); + slot.setBackground(new Color(25, 35, 52)); + } + } + } +} diff --git a/CloudKernel/src/ui/StatsBar.java b/CloudKernel/src/ui/StatsBar.java new file mode 100644 index 0000000..b14dc76 --- /dev/null +++ b/CloudKernel/src/ui/StatsBar.java @@ -0,0 +1,81 @@ +package ui; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import javax.swing.border.LineBorder; +import java.awt.*; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Bottom statistics row that displays live aggregate metrics. + */ +public class StatsBar extends JPanel { + private static final Color BG_BAR = new Color(12, 19, 32); + private static final Color BG_CARD = new Color(16, 24, 38); + private static final Color TEXT_MUTED = new Color(130, 150, 180); + + private final Map valueLabels = new LinkedHashMap<>(); + + /** Creates the stats bar with all predefined stat cards. */ + public StatsBar() { + setLayout(new GridLayout(1, 6, 8, 0)); + setBackground(BG_BAR); + setBorder(new EmptyBorder(6, 8, 6, 8)); + setPreferredSize(new Dimension(0, 78)); + + addStat("Total Cycles"); + addStat("Network Ops"); + addStat("CPU Ops"); + addStat("Timeouts"); + addStat("Contentions"); + addStat("Uptime"); + } + + /** + * Adds one metric card. + * + * @param name metric name + */ + private void addStat(String name) { + JPanel card = new JPanel(new GridLayout(2, 1)); + card.setBackground(BG_CARD); + card.setBorder(new LineBorder(new Color(30, 45, 69), 1)); + + JLabel top = new JLabel(name, SwingConstants.CENTER); + top.setFont(CloudKernelGUI.uiFont(Font.PLAIN, 10)); + top.setForeground(TEXT_MUTED); + + JLabel value = new JLabel("0", SwingConstants.CENTER); + value.setFont(CloudKernelGUI.uiFont(Font.BOLD, 18)); + value.setForeground(Color.WHITE); + + valueLabels.put(name, value); + card.add(top); + card.add(value); + add(card); + } + + /** + * Sets one metric value. + * + * @param name metric name + * @param value formatted metric value + */ + public void setValue(String name, String value) { + JLabel label = valueLabels.get(name); + if (label != null) { + label.setText(value); + } + } + + /** Resets all metric cards to startup values. */ + public void reset() { + setValue("Total Cycles", "0"); + setValue("Network Ops", "0"); + setValue("CPU Ops", "0"); + setValue("Timeouts", "0"); + setValue("Contentions", "0"); + setValue("Uptime", "00:00"); + } +} diff --git a/CloudKernel/src/ui/VMCard.java b/CloudKernel/src/ui/VMCard.java new file mode 100644 index 0000000..c73de2c --- /dev/null +++ b/CloudKernel/src/ui/VMCard.java @@ -0,0 +1,290 @@ +package ui; + +import entities.VMPriority; +import entities.VMState; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import javax.swing.border.LineBorder; +import java.awt.*; + +/** + * Visual card representation for a single VM. + */ +public class VMCard extends JPanel { + private static final Color BG_CARD = new Color(16, 24, 38); + private static final Color TEXT_PRIMARY = new Color(200, 216, 240); + private static final Color TEXT_MUTED = new Color(130, 150, 180); + private static final Color ACCENT_CYAN = new Color(0, 212, 255); + private static final Color ACCENT_GREEN = new Color(0, 255, 136); + private static final Color ACCENT_AMBER = new Color(255, 191, 0); + private static final Color ACCENT_PURPLE = new Color(180, 80, 255); + private static final Color ACCENT_RED = new Color(255, 90, 90); + + private final String vmName; + private final JLabel nameLabel; + private final JLabel priorityLabel; + private final JLabel stateLabel; + private final JProgressBar progressBar; + private final JLabel cpuDot; + private final JLabel memoryDot; + private final JLabel networkDot; + private final JLabel tasksLabel; + private final JLabel waitLabel; + + private VMState state = VMState.BOOTING; + + /** + * Creates a VM dashboard card. + * + * @param vmName VM name label + */ + public VMCard(String vmName) { + this.vmName = vmName; + setLayout(new BorderLayout(0, 8)); + setBackground(BG_CARD); + setBorder(new RoundedBorder(12, new Color(30, 45, 69))); + setOpaque(true); + + JPanel top = new JPanel(new BorderLayout()); + top.setOpaque(false); + top.setBorder(new EmptyBorder(8, 10, 0, 10)); + + nameLabel = new JLabel(vmName); + nameLabel.setFont(CloudKernelGUI.uiFont(Font.BOLD, 14)); + nameLabel.setForeground(Color.WHITE); + top.add(nameLabel, BorderLayout.WEST); + + priorityLabel = new JLabel("LOW"); + priorityLabel.setFont(CloudKernelGUI.uiFont(Font.BOLD, 11)); + priorityLabel.setOpaque(true); + priorityLabel.setBackground(new Color(0, 90, 120)); + priorityLabel.setForeground(Color.WHITE); + priorityLabel.setBorder(new EmptyBorder(3, 8, 3, 8)); + top.add(priorityLabel, BorderLayout.EAST); + + add(top, BorderLayout.NORTH); + + JPanel center = new JPanel(); + center.setOpaque(false); + center.setLayout(new BoxLayout(center, BoxLayout.Y_AXIS)); + center.setBorder(new EmptyBorder(0, 10, 6, 10)); + + stateLabel = new JLabel(state.getLabel()); + stateLabel.setForeground(ACCENT_AMBER); + stateLabel.setFont(CloudKernelGUI.uiFont(Font.BOLD, 11)); + stateLabel.setAlignmentX(Component.LEFT_ALIGNMENT); + center.add(stateLabel); + center.add(Box.createVerticalStrut(6)); + + progressBar = new JProgressBar(0, 100); + progressBar.setValue(0); + progressBar.setStringPainted(false); + progressBar.setBackground(new Color(28, 38, 56)); + progressBar.setForeground(ACCENT_CYAN); + progressBar.setBorder(new LineBorder(new Color(42, 63, 98), 1)); + progressBar.setPreferredSize(new Dimension(190, 10)); + progressBar.setMaximumSize(new Dimension(Integer.MAX_VALUE, 10)); + progressBar.setAlignmentX(Component.LEFT_ALIGNMENT); + center.add(progressBar); + center.add(Box.createVerticalStrut(8)); + + JPanel resources = new JPanel(new FlowLayout(FlowLayout.LEFT, 8, 0)); + resources.setOpaque(false); + resources.setAlignmentX(Component.LEFT_ALIGNMENT); + + cpuDot = createDot("CPU"); + memoryDot = createDot("MEM"); + networkDot = createDot("NET"); + + resources.add(cpuDot); + resources.add(memoryDot); + resources.add(networkDot); + center.add(resources); + center.add(Box.createVerticalStrut(8)); + + tasksLabel = new JLabel("Tasks: 0"); + tasksLabel.setFont(CloudKernelGUI.uiFont(Font.PLAIN, 11)); + tasksLabel.setForeground(TEXT_MUTED); + tasksLabel.setAlignmentX(Component.LEFT_ALIGNMENT); + center.add(tasksLabel); + + waitLabel = new JLabel("Avg Wait: 0ms"); + waitLabel.setFont(CloudKernelGUI.uiFont(Font.PLAIN, 11)); + waitLabel.setForeground(TEXT_MUTED); + waitLabel.setAlignmentX(Component.LEFT_ALIGNMENT); + center.add(waitLabel); + + add(center, BorderLayout.CENTER); + } + + /** + * Creates a resource indicator label. + * + * @param text dot text + * @return configured label + */ + private JLabel createDot(String text) { + JLabel dot = new JLabel("\u25cf " + text); + dot.setForeground(new Color(75, 86, 105)); + dot.setFont(CloudKernelGUI.getSymbolFont(Font.BOLD, 10)); + return dot; + } + + /** @return VM name bound to the card */ + public String getVmName() { + return vmName; + } + + /** + * Updates displayed priority badge. + * + * @param priority VM priority + */ + public void setPriority(VMPriority priority) { + String label = priority.getLabel(); + priorityLabel.setText(label); + if (priority == VMPriority.HIGH) { + priorityLabel.setBackground(new Color(130, 30, 30)); + } else if (priority == VMPriority.MEDIUM) { + priorityLabel.setBackground(new Color(135, 92, 0)); + } else { + priorityLabel.setBackground(new Color(0, 90, 120)); + } + } + + /** + * Updates state text and border color. + * + * @param newState next VM state + */ + public void setState(VMState newState) { + this.state = newState; + stateLabel.setText(newState.getLabel()); + + Color stateColor = colorForState(newState); + stateLabel.setForeground(stateColor); + setBorder(new RoundedBorder(12, borderForState(newState))); + } + + /** + * Sets cycle progress as a percentage. + * + * @param progressPercent progress value in [0, 100] + */ + public void setCycleProgress(int progressPercent) { + progressBar.setValue(Math.max(0, Math.min(progressPercent, 100))); + } + + /** + * Sets completed task count text. + * + * @param tasks task count + */ + public void setTaskCount(int tasks) { + tasksLabel.setText("Tasks: " + tasks); + } + + /** + * Sets average wait-time text. + * + * @param waitMs average wait in milliseconds + */ + public void setAvgWait(long waitMs) { + waitLabel.setText("Avg Wait: " + waitMs + "ms"); + } + + /** + * Updates resource hold indicators. + * + * @param cpu whether CPU is currently held + * @param memory whether memory is currently held + * @param network whether network is currently held + */ + public void setResourceHold(boolean cpu, boolean memory, boolean network) { + cpuDot.setForeground(cpu ? ACCENT_GREEN : new Color(75, 86, 105)); + memoryDot.setForeground(memory ? ACCENT_GREEN : new Color(75, 86, 105)); + networkDot.setForeground(network ? ACCENT_GREEN : new Color(75, 86, 105)); + } + + /** + * Returns text color for a VM state. + * + * @param vmState VM state + * @return mapped state color + */ + private Color colorForState(VMState vmState) { + if (vmState == VMState.RUNNING) { + return ACCENT_CYAN; + } + if (vmState == VMState.USING_RESOURCE) { + return ACCENT_GREEN; + } + if (vmState == VMState.BARRIER_WAIT) { + return ACCENT_PURPLE; + } + if (vmState == VMState.TIMEOUT) { + return ACCENT_RED; + } + if (vmState == VMState.REQUESTING_RESOURCE) { + return ACCENT_AMBER; + } + return TEXT_PRIMARY; + } + + /** + * Returns border color for a VM state. + * + * @param vmState VM state + * @return mapped border color + */ + private Color borderForState(VMState vmState) { + if (vmState == VMState.RUNNING) { + return ACCENT_CYAN; + } + if (vmState == VMState.USING_RESOURCE) { + return ACCENT_GREEN; + } + if (vmState == VMState.BARRIER_WAIT) { + return ACCENT_PURPLE; + } + if (vmState == VMState.TIMEOUT) { + return ACCENT_RED; + } + if (vmState == VMState.REQUESTING_RESOURCE) { + return ACCENT_AMBER; + } + return new Color(30, 45, 69); + } + + /** Rounded border painter for card emphasis. */ + private static class RoundedBorder extends EmptyBorder { + private final int radius; + private final Color color; + + /** + * Creates a rounded border. + * + * @param radius corner radius + * @param color border color + */ + RoundedBorder(int radius, Color color) { + super(2, 2, 2, 2); + this.radius = radius; + this.color = color; + } + + /** + * Paints the rounded card border. + */ + @Override + public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) { + Graphics2D g2 = (Graphics2D) g.create(); + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g2.setColor(color); + g2.setStroke(new BasicStroke(1.5f)); + g2.drawRoundRect(x, y, width - 1, height - 1, radius, radius); + g2.dispose(); + } + } +} diff --git a/CloudKernel/src/utils/GUILogger.java b/CloudKernel/src/utils/GUILogger.java new file mode 100644 index 0000000..357ba68 --- /dev/null +++ b/CloudKernel/src/utils/GUILogger.java @@ -0,0 +1,239 @@ +package utils; + +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * Logger that writes to terminal output and GUI listeners simultaneously. + */ +public class GUILogger { + + private static final DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern("HH:mm:ss.S"); + + // ANSI Color codes + public static final String RESET = "\u001B[0m"; + public static final String BOLD = "\u001B[1m"; + public static final String RED = "\u001B[31m"; + public static final String GREEN = "\u001B[32m"; + public static final String YELLOW = "\u001B[33m"; + public static final String BLUE = "\u001B[34m"; + public static final String PURPLE = "\u001B[35m"; + public static final String CYAN = "\u001B[36m"; + public static final String WHITE = "\u001B[37m"; + + // Log level constants + public static final int VERBOSE = 0; + public static final int NORMAL = 1; + public static final int QUIET = 2; + + private static int logLevel = NORMAL; + private static final List listeners = new CopyOnWriteArrayList<>(); + + /** Creates a logger instance. */ + public GUILogger() { + } + + /** + * Sets logger verbosity. + * + * @param level one of VERBOSE, NORMAL, QUIET + */ + public static void setLogLevel(int level) { + logLevel = level; + } + + /** + * Registers a GUI log listener. + * + * @param listener listener to register + */ + public static void addListener(LogListener listener) { + listeners.add(listener); + } + + /** + * Removes a GUI log listener. + * + * @param listener listener to remove + */ + public static void removeListener(LogListener listener) { + listeners.remove(listener); + } + + /** + * Logs a categorized VM message. + * + * @param vmName VM name or source actor + * @param message event message + * @param category message category + */ + public static void log(String vmName, String message, String category) { + if (logLevel == QUIET) + return; + + String timestamp = LocalTime.now().format(TIME_FORMAT); + String color = getCategoryColor(category); + String logLine = String.format( + "%s[%s] [%-8s] %s -> %s%s", + color, timestamp, category, vmName, message, RESET); + + // Print to console + System.out.println(logLine); + + // Notify GUI listeners + notifyListeners(new LogEntry(timestamp, category, vmName, message, color)); + } + + /** + * Logs a system boot event. + * + * @param message event message + */ + public static void boot(String message) { + if (logLevel == QUIET) + return; + + String timestamp = LocalTime.now().format(TIME_FORMAT); + String logLine = String.format( + "%s[%s] [%-8s] %s%s", + CYAN, timestamp, "BOOT", message, RESET); + + System.out.println(logLine); + notifyListeners(new LogEntry(timestamp, "BOOT", "SYSTEM", message, CYAN)); + } + + /** + * Logs a cycle separator block. + * + * @param cycleNum cycle number + */ + public static void cycleSeparator(int cycleNum) { + if (logLevel == QUIET) + return; + + System.out.println(BOLD + "═══════════════════════════════════════════════════════════" + RESET); + System.out.println(BOLD + CYAN + " CYCLE #" + cycleNum + " BEGINS" + RESET); + System.out.println(BOLD + "═══════════════════════════════════════════════════════════" + RESET); + } + + /** + * Logs cycle completion. + * + * @param cycleNum cycle number + */ + public static void cycleComplete(int cycleNum) { + if (logLevel == QUIET) + return; + + System.out.println(BOLD + CYAN + " CYCLE #" + cycleNum + " COMPLETE" + RESET); + System.out.println(BOLD + "═══════════════════════════════════════════════════════════" + RESET); + } + + /** + * Returns ANSI color based on message category. + * + * @param category event category + * @return ANSI color code + */ + private static String getCategoryColor(String category) { + switch (category.toUpperCase()) { + case "BOOT": + return CYAN; + case "CPU": + case "NETWORK": + case "MEMORY": + return GREEN; + case "WAITING": + return YELLOW; + case "BARRIER": + return PURPLE; + case "TIMEOUT": + case "ERROR": + return RED; + default: + return WHITE; + } + } + + /** + * Emits a log entry to all listeners. + * + * @param entry formatted log entry + */ + private static void notifyListeners(LogEntry entry) { + for (LogListener listener : listeners) { + listener.onLogEntry(entry); + } + } + + /** + * Immutable GUI log entry payload. + */ + public static class LogEntry { + public final String timestamp; + public final String category; + public final String vmName; + public final String message; + public final String color; + + /** + * Creates an immutable log entry payload. + * + * @param timestamp event timestamp + * @param category category tag + * @param vmName source VM or component + * @param message message body + * @param color ANSI color used for console output + */ + public LogEntry(String timestamp, String category, String vmName, String message, String color) { + this.timestamp = timestamp; + this.category = category; + this.vmName = vmName; + this.message = message; + this.color = color; + } + + /** + * @return formatted display text + */ + @Override + public String toString() { + return String.format("[%s] [%s] %s -> %s", timestamp, category, vmName, message); + } + } + + /** + * Listener contract for GUI log streams. + */ + public interface LogListener { + /** + * Receives one emitted log entry. + * + * @param entry log entry + */ + void onLogEntry(LogEntry entry); + } + + /** Writes a visual separator to terminal output. */ + public static void separator() { + if (logLevel == QUIET) + return; + System.out.println(BOLD + "─".repeat(65) + RESET); + } + + /** + * Writes a section heading to terminal output. + * + * @param title heading text + */ + public static void section(String title) { + if (logLevel == QUIET) + return; + System.out.println(); + separator(); + System.out.println(BOLD + " " + title + RESET); + separator(); + } +} diff --git a/CloudKernel/src/utils/Logger.java b/CloudKernel/src/utils/Logger.java deleted file mode 100644 index 84495e3..0000000 --- a/CloudKernel/src/utils/Logger.java +++ /dev/null @@ -1,38 +0,0 @@ -package utils; - -import java.time.LocalTime; -import java.time.format.DateTimeFormatter; - -// Small utility for formatted console logging. -public class Logger { - - private static final DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern("HH:mm:ss"); - - public static final String RESET = "\u001B[0m"; - public static final String GREEN = "\u001B[32m"; - public static final String CYAN = "\u001B[36m"; - public static final String YELLOW = "\u001B[33m"; - public static final String RED = "\u001B[31m"; - public static final String BOLD = "\u001B[1m"; - - private Logger() { - // Prevent instantiation. - } - - public static void log(String tag, String message, String color) { - String timestamp = LocalTime.now().format(TIME_FORMAT); - System.out.printf("%s[%s] %-12s%s %s%n", - color, timestamp, "[" + tag + "]", RESET, message); - } - - public static void separator() { - System.out.println(BOLD + "-".repeat(65) + RESET); - } - - public static void section(String title) { - System.out.println(); - separator(); - System.out.println(BOLD + " " + title + RESET); - separator(); - } -} \ No newline at end of file diff --git a/CloudKernel/src/utils/StatsCollector.java b/CloudKernel/src/utils/StatsCollector.java new file mode 100644 index 0000000..f8c7a0f --- /dev/null +++ b/CloudKernel/src/utils/StatsCollector.java @@ -0,0 +1,139 @@ +package utils; + +import entities.VMStats; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Collects system-wide and per-VM simulation statistics. + */ +public class StatsCollector { + private final Map vmStats = new LinkedHashMap<>(); + private final AtomicInteger totalCycles = new AtomicInteger(0); + private final AtomicInteger totalContentions = new AtomicInteger(0); + private final AtomicInteger peakConcurrency = new AtomicInteger(0); + private final AtomicLong startTime = new AtomicLong(0); + private final AtomicInteger totalTimeouts = new AtomicInteger(0); + + /** Creates a new collector and captures simulation start time. */ + public StatsCollector() { + startTime.set(System.currentTimeMillis()); + } + + /** + * Returns existing stats for a VM or creates a new entry. + * + * @param vmName VM name + * @return mutable VM stats object + */ + public VMStats getOrCreateVMStats(String vmName) { + return vmStats.computeIfAbsent(vmName, k -> new VMStats(vmName)); + } + + /** Records one completed cycle event. */ + public void recordCycleCompletion() { + totalCycles.incrementAndGet(); + } + + /** Records one resource contention event. */ + public void recordContention() { + totalContentions.incrementAndGet(); + } + + /** + * Updates peak concurrency if a higher value is observed. + * + * @param concurrency current observed concurrency + */ + public void recordPeakConcurrency(int concurrency) { + int current = peakConcurrency.get(); + while (concurrency > current) { + peakConcurrency.compareAndSet(current, concurrency); + current = peakConcurrency.get(); + } + } + + /** Records one timeout event. */ + public void recordTimeout() { + totalTimeouts.incrementAndGet(); + } + + /** Prints a human-readable summary to console output. */ + public void printSummary() { + long elapsedMs = System.currentTimeMillis() - startTime.get(); + long hours = elapsedMs / 3600000; + long minutes = (elapsedMs % 3600000) / 60000; + long seconds = (elapsedMs % 60000) / 1000; + + System.out.println(); + System.out.println( + GUILogger.BOLD + "═══════════════════════════════════════════════════════════" + GUILogger.RESET); + System.out.println(GUILogger.BOLD + GUILogger.CYAN + " SIMULATION SUMMARY" + GUILogger.RESET); + System.out.println( + GUILogger.BOLD + "═══════════════════════════════════════════════════════════" + GUILogger.RESET); + + System.out.println(); + System.out.println(GUILogger.BOLD + "System Statistics:" + GUILogger.RESET); + System.out.println(String.format(" Total Cycles Completed: %d", totalCycles.get())); + System.out.println(String.format(" Total Contentions: %d", totalContentions.get())); + System.out.println(String.format(" Peak Concurrency: %d VMs", peakConcurrency.get())); + System.out.println(String.format(" Total Timeouts: %d", totalTimeouts.get())); + System.out.println(String.format(" Total Uptime: %02d:%02d:%02d", hours, minutes, seconds)); + + System.out.println(); + System.out.println(GUILogger.BOLD + "Per-VM Statistics:" + GUILogger.RESET); + System.out.println( + GUILogger.BOLD + String.format(" %-8s | Tasks | Network | CPU | Memory | Timeouts | Avg Wait", "VM") + + GUILogger.RESET); + System.out.println(GUILogger.BOLD + " " + "─".repeat(70) + GUILogger.RESET); + + for (VMStats stats : vmStats.values()) { + System.out.println(String.format( + " %-8s | %5d | %7d | %3d | %6d | %8d | %6dms", + stats.toString().split("\\|")[0], + stats.getTasksCompleted(), + stats.getNetworkUses(), + stats.getCPUUses(), + stats.getMemoryUses(), + stats.getTimeouts(), + stats.getAverageWaitTime())); + } + + System.out.println(); + System.out.println( + GUILogger.BOLD + "═══════════════════════════════════════════════════════════" + GUILogger.RESET); + } + + /** @return total completed cycles */ + public int getTotalCycles() { + return totalCycles.get(); + } + + /** @return total contention count */ + public int getTotalContentions() { + return totalContentions.get(); + } + + /** @return highest concurrent VM count observed */ + public int getPeakConcurrency() { + return peakConcurrency.get(); + } + + /** @return total timeout count */ + public int getTotalTimeouts() { + return totalTimeouts.get(); + } + + /** @return uptime in milliseconds */ + public long getUptimeMs() { + return System.currentTimeMillis() - startTime.get(); + } + + /** @return snapshot copy of per-VM stats map */ + public Map getAllVMStats() { + return new LinkedHashMap<>(vmStats); + } +}