From 83c3d65e38ed822430befd64db594a76a4955022 Mon Sep 17 00:00:00 2001 From: Leandro Afonso Date: Sun, 7 Dec 2025 19:57:40 +0000 Subject: [PATCH] more javadoc - dashboard, des, logging --- .../main/java/sd/dashboard/DashboardUI.java | 96 +++++++++++-------- .../dashboard/SimulationProcessManager.java | 61 ++++++++---- .../main/java/sd/dashboard/StatsMessage.java | 35 ++++++- .../java/sd/dashboard/StatsUpdatePayload.java | 43 ++++++++- .../src/main/java/sd/des/SimulationEvent.java | 70 ++++++++++---- .../main/java/sd/des/TrafficLightEvent.java | 25 ++++- .../src/main/java/sd/logging/EventLogger.java | 82 +++++++++++----- main/src/main/java/sd/logging/EventType.java | 18 +++- .../main/java/sd/logging/VehicleTracer.java | 79 +++++++++------ 9 files changed, 370 insertions(+), 139 deletions(-) diff --git a/main/src/main/java/sd/dashboard/DashboardUI.java b/main/src/main/java/sd/dashboard/DashboardUI.java index 88378d3..8757784 100644 --- a/main/src/main/java/sd/dashboard/DashboardUI.java +++ b/main/src/main/java/sd/dashboard/DashboardUI.java @@ -30,8 +30,22 @@ import sd.config.SimulationConfig; import sd.model.VehicleType; /** - * JavaFX-based Dashboard UI for displaying real-time simulation statistics. - * Provides a graphical interface with auto-updating statistics panels. + * Interface Gráfica (GUI) baseada em JavaFX para visualização de telemetria em tempo real. + *

+ * Esta classe atua como a camada de apresentação (View) do sistema. Implementa o padrão + * Observer (via polling) para refletir o estado do modelo {@link DashboardStatistics} + * nos componentes visuais. + *

+ * Aspetos Técnicos Relevantes: + *

*/ public class DashboardUI extends Application { @@ -52,7 +66,7 @@ public class DashboardUI extends Application { // Intersection Table private TableView intersectionTable; - // Update scheduler + // Update scheduler (Background Thread) private ScheduledExecutorService updateScheduler; // Configuration controls @@ -60,6 +74,10 @@ public class DashboardUI extends Application { private String selectedConfigFile = "simulation.properties"; private Label configInfoLabel; + /** + * Ponto de entrada da aplicação JavaFX. + * Configura o Stage primário, inicializa o servidor de backend e constrói a árvore de cena (Scene Graph). + */ @Override public void start(Stage primaryStage) { try { @@ -72,29 +90,27 @@ public class DashboardUI extends Application { server = new DashboardServer(config); statistics = server.getStatistics(); - // Start the dashboard server + // Start the dashboard server (Backend listening port) server.start(); - // Build UI + // Build UI Layout BorderPane root = new BorderPane(); root.getStyleClass().add("root"); - // Header + // Header (Top) VBox header = createHeader(); root.setTop(header); - // Main content + // Main content (Center) VBox mainContent = createMainContent(); root.setCenter(mainContent); - // Footer + // Footer (Bottom) HBox footer = createFooter(); root.setBottom(footer); - // Create scene + // Create scene & apply CSS Scene scene = new Scene(root, 1200, 850); - - // Load CSS String cssUrl = getClass().getResource("/dashboard.css").toExternalForm(); scene.getStylesheets().add(cssUrl); @@ -102,10 +118,10 @@ public class DashboardUI extends Application { primaryStage.setScene(scene); primaryStage.show(); - // Start periodic updates + // Start periodic updates loop startPeriodicUpdates(); - // Handle window close + // Handle window close (Graceful shutdown) primaryStage.setOnCloseRequest(event -> { shutdown(); }); @@ -149,6 +165,8 @@ public class DashboardUI extends Application { // Passar o ficheiro de configuração selecionado processManager.setConfigFile(selectedConfigFile); processManager.startSimulation(); + + // Toggle UI state btnStart.setDisable(true); btnStop.setDisable(false); configFileSelector.setDisable(true); // Bloquear mudanças durante simulação @@ -159,6 +177,8 @@ public class DashboardUI extends Application { btnStop.setOnAction(e -> { processManager.stopSimulation(); + + // Toggle UI state btnStart.setDisable(false); btnStop.setDisable(true); configFileSelector.setDisable(false); // Desbloquear para nova simulação @@ -435,13 +455,23 @@ public class DashboardUI extends Application { grid.add(container, colGroup, row); } + /** + * Inicia o ciclo de polling em background. + * Atualiza a UI a cada 100ms. + */ private void startPeriodicUpdates() { updateScheduler = Executors.newSingleThreadScheduledExecutor(); updateScheduler.scheduleAtFixedRate(() -> { + // Crucial: Encapsular atualização de UI em Platform.runLater + // para garantir execução na JavaFX Application Thread Platform.runLater(this::updateUI); }, 0, 100, TimeUnit.MILLISECONDS); } + /** + * Sincroniza o estado atual do objeto Statistics com os controlos JavaFX. + * Chamado periodicamente pela thread de UI. + */ private void updateUI() { // Update global statistics lblVehiclesGenerated.setText(String.valueOf(statistics.getTotalVehiclesGenerated())); @@ -548,7 +578,9 @@ public class DashboardUI extends Application { launch(args); } - // Inner classes for TableView data models + // --- DTOs para Data Binding nas Tabelas --- + + /** DTO para linhas da tabela de Tipos de Veículo. */ public static class VehicleTypeRow { private final String vehicleType; private final int count; @@ -560,19 +592,12 @@ public class DashboardUI extends Application { this.avgWaitTime = avgWaitTime; } - public String getVehicleType() { - return vehicleType; - } - - public int getCount() { - return count; - } - - public String getAvgWaitTime() { - return avgWaitTime; - } + public String getVehicleType() { return vehicleType; } + public int getCount() { return count; } + public String getAvgWaitTime() { return avgWaitTime; } } + /** DTO para linhas da tabela de Interseções. */ public static class IntersectionRow { private final String intersectionId; private final int arrivals; @@ -586,20 +611,9 @@ public class DashboardUI extends Application { this.queueSize = queueSize; } - public String getIntersectionId() { - return intersectionId; - } - - public int getArrivals() { - return arrivals; - } - - public int getDepartures() { - return departures; - } - - public int getQueueSize() { - return queueSize; - } + public String getIntersectionId() { return intersectionId; } + public int getArrivals() { return arrivals; } + public int getDepartures() { return departures; } + public int getQueueSize() { return queueSize; } } -} +} \ No newline at end of file diff --git a/main/src/main/java/sd/dashboard/SimulationProcessManager.java b/main/src/main/java/sd/dashboard/SimulationProcessManager.java index 56d1c33..063a355 100644 --- a/main/src/main/java/sd/dashboard/SimulationProcessManager.java +++ b/main/src/main/java/sd/dashboard/SimulationProcessManager.java @@ -6,9 +6,17 @@ import java.util.ArrayList; import java.util.List; /** - * Gere o ciclo de vida dos processos de simulação (Intersections, Exit Node, - * Coordinator). - * Permite iniciar e parar a simulação distribuída dentro da aplicação Java. + * Orquestrador de processos para o ambiente de simulação distribuída. + *

+ * Esta classe atua como um supervisor (Process Manager), responsável pelo bootstrapping + * e teardown das múltiplas Java Virtual Machines (JVMs) que compõem o sistema. + *

+ * Funcionalidades principais: + *

*/ public class SimulationProcessManager { @@ -16,6 +24,10 @@ public class SimulationProcessManager { private final String classpath; private String configFile; + /** + * Inicializa o gestor capturando o classpath da JVM atual. + * Isto garante que os processos filhos herdam as mesmas dependências e configurações de ambiente. + */ public SimulationProcessManager() { this.runningProcesses = new ArrayList<>(); this.classpath = System.getProperty("java.class.path"); @@ -23,9 +35,9 @@ public class SimulationProcessManager { } /** - * Define o ficheiro de configuração a usar. - * - * @param configFile nome do ficheiro (ex: "simulation-low.properties") + * Define o perfil de configuração a ser injetado nos processos filhos. + * Útil para alternar entre cenários (Low/Medium/High Load) dinamicamente. + * * @param configFile Nome do ficheiro de propriedades (ex: "simulation-low.properties"). */ public void setConfigFile(String configFile) { this.configFile = "src/main/resources/" + configFile; @@ -33,9 +45,16 @@ public class SimulationProcessManager { } /** - * Inicia a simulação completa: 5 Intersections, 1 Exit Node, e 1 Coordinator. - * - * @throws IOException se um processo falhar ao iniciar + * Executa o procedimento de arranque (Bootstrap) da simulação distribuída. + *

+ * A ordem de inicialização é crítica para evitar Race Conditions na conexão TCP: + *

    + *
  1. Workers (Interseções): Iniciam os ServerSockets.
  2. + *
  3. Sink (Exit Node): Prepara-se para receber métricas finais.
  4. + *
  5. Delay de Estabilização: Pausa de 1s para garantir que os sockets estão em LISTENING.
  6. + *
  7. Source (Coordinator): Inicia a geração de carga e conecta-se aos nós.
  8. + *
+ * * @throws IOException Se falhar o fork de algum processo. */ public void startSimulation() throws IOException { if (!runningProcesses.isEmpty()) { @@ -65,8 +84,11 @@ public class SimulationProcessManager { } /** - * Checks if the coordinator process (last process started) is still running. - * When the coordinator finishes, the simulation is complete. + * Verifica o estado de "liveness" da simulação monitorizando o processo Coordenador. + *

+ * Como o Coordenador gere o relógio DES e a geração de eventos, a sua terminação + * (após o drain time) sinaliza o fim efetivo da simulação. + * * @return true se o Coordenador ainda estiver ativo (alive). */ public boolean isSimulationRunning() { if (runningProcesses.isEmpty()) { @@ -78,8 +100,10 @@ public class SimulationProcessManager { } /** - * Waits for the simulation to complete naturally. - * Returns true if completed, false if timeout. + * Bloqueia a thread atual até que a simulação termine naturalmente ou ocorra timeout. + * * @param timeoutSeconds Tempo máximo de espera. + * @return true se terminou, false se o timeout expirou. + * @throws InterruptedException Se a espera for interrompida. */ public boolean waitForCompletion(long timeoutSeconds) throws InterruptedException { if (runningProcesses.isEmpty()) { @@ -91,7 +115,11 @@ public class SimulationProcessManager { } /** - * Stops all running simulation processes. + * Executa o procedimento de encerramento (Teardown) de todos os processos. + *

+ * Tenta primeiro uma paragem graciosa (`SIGTERM`), aguarda meio segundo, e + * força a paragem (`SIGKILL`) para processos persistentes, garantindo que não + * ficam processos órfãos no SO. */ public void stopSimulation() { System.out.println("Stopping simulation processes..."); @@ -120,7 +148,8 @@ public class SimulationProcessManager { } /** - * Helper para iniciar um único processo Java. + * Helper de baixo nível para construção e lançamento de processos Java. + * Configura o redirecionamento de I/O para ficheiros de log na diretoria temporária do SO. */ private void startProcess(String className, String arg) throws IOException { String javaBin = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java"; @@ -152,4 +181,4 @@ public class SimulationProcessManager { // print where the logs are actually going System.out.println("Logs redirected to: " + logFile.getAbsolutePath()); } -} +} \ No newline at end of file diff --git a/main/src/main/java/sd/dashboard/StatsMessage.java b/main/src/main/java/sd/dashboard/StatsMessage.java index 7209130..abc4730 100644 --- a/main/src/main/java/sd/dashboard/StatsMessage.java +++ b/main/src/main/java/sd/dashboard/StatsMessage.java @@ -4,7 +4,14 @@ import sd.model.MessageType; import sd.protocol.MessageProtocol; /** - * Message wrapper for sending statistics to the dashboard. + * Implementação concreta do protocolo de mensagens destinada ao transporte de telemetria. + *

+ * Esta classe atua como um envelope especializado para o envio de dados estatísticos + * (encapsulados em {@link StatsUpdatePayload}) dos nós operacionais (Interseções, Coordenador) + * para o servidor de Dashboard centralizado. + *

+ * Diferencia-se das mensagens de controlo genéricas por ter o destino fixado no + * "DashboardServer" e um tipo de mensagem imutável ({@code STATS_UPDATE}). */ public class StatsMessage implements MessageProtocol { @@ -14,27 +21,49 @@ public class StatsMessage implements MessageProtocol { private final String destinationNode; private final StatsUpdatePayload payload; + /** + * Cria uma nova mensagem de estatística. + * + * @param sourceNode O ID do nó que gerou as estatísticas (ex: "Cr1", "ExitNode"). + * @param payload O objeto DTO contendo os dados estatísticos brutos ou agregados. + */ public StatsMessage(String sourceNode, StatsUpdatePayload payload) { this.sourceNode = sourceNode; - this.destinationNode = "DashboardServer"; + this.destinationNode = "DashboardServer"; // Destino implícito e fixo this.payload = payload; } + /** + * Retorna o tipo da mensagem, que identifica semanticamente o conteúdo para o recetor. + * @return Sempre {@link MessageType#STATS_UPDATE}. + */ @Override public MessageType getType() { return MessageType.STATS_UPDATE; } + /** + * Obtém a carga útil da mensagem. + * @return O objeto {@link StatsUpdatePayload} associado. + */ @Override public Object getPayload() { return payload; } + /** + * Identifica a origem da mensagem. + * @return O ID do nó remetente. + */ @Override public String getSourceNode() { return sourceNode; } + /** + * Identifica o destino da mensagem. + * @return Sempre "DashboardServer". + */ @Override public String getDestinationNode() { return destinationNode; @@ -45,4 +74,4 @@ public class StatsMessage implements MessageProtocol { return String.format("StatsMessage[from=%s, to=%s, payload=%s]", sourceNode, destinationNode, payload); } -} +} \ No newline at end of file diff --git a/main/src/main/java/sd/dashboard/StatsUpdatePayload.java b/main/src/main/java/sd/dashboard/StatsUpdatePayload.java index e62e866..6f1915e 100644 --- a/main/src/main/java/sd/dashboard/StatsUpdatePayload.java +++ b/main/src/main/java/sd/dashboard/StatsUpdatePayload.java @@ -7,25 +7,60 @@ import java.util.Map; import sd.model.VehicleType; /** - * DTO para atualizações de estatísticas ao dashboard. - * Campos com valor -1 não são atualizados nesta mensagem. + * Objeto de Transferência de Dados (DTO) otimizado para transporte de telemetria. + *

+ * Esta classe encapsula as métricas de desempenho enviadas pelos nós da simulação (Coordenador, + * Interseções, ExitNode) para o Dashboard. Foi desenhada para suportar atualizações parciais + * (Sparse Updates): + *

+ * Implementa {@link Serializable} para transmissão direta via Java Sockets. + * + +[Image of data transfer object pattern] + */ public class StatsUpdatePayload implements Serializable { private static final long serialVersionUID = 1L; + // Global Metrics (Coordinator/ExitNode) + /** Total gerado. Valor -1 indica que este campo deve ser ignorado na atualização. */ private int totalVehiclesGenerated = -1; + + /** Total completado. Valor -1 indica que este campo deve ser ignorado. */ private int totalVehiclesCompleted = -1; + + /** Tempo total de sistema acumulado (ms). Valor -1 indica que deve ser ignorado. */ private long totalSystemTime = -1; + + /** Tempo total de espera acumulado (ms). Valor -1 indica que deve ser ignorado. */ private long totalWaitingTime = -1; + // Intersection Metrics (Worker Nodes) + /** Número de veículos que entraram na interseção desde o último reporte. */ private int intersectionArrivals = 0; + + /** Número de veículos que saíram da interseção desde o último reporte. */ private int intersectionDepartures = 0; + + /** Snapshot do tamanho atual da fila na interseção. */ private int intersectionQueueSize = 0; + // Detailed Breakdowns + /** Contagem acumulada por tipo de veículo. */ private Map vehicleTypeCounts; + + /** Tempos de espera acumulados por tipo de veículo. */ private Map vehicleTypeWaitTimes; + /** + * Inicializa o payload com os mapas vazios e contadores globais a -1 (estado neutro). + */ public StatsUpdatePayload() { this.vehicleTypeCounts = new HashMap<>(); this.vehicleTypeWaitTimes = new HashMap<>(); @@ -67,6 +102,8 @@ public class StatsUpdatePayload implements Serializable { return vehicleTypeWaitTimes; } + // Setters implementam Fluent Interface para construção encadeada + public StatsUpdatePayload setTotalVehiclesGenerated(int totalVehiclesGenerated) { this.totalVehiclesGenerated = totalVehiclesGenerated; return this; @@ -118,4 +155,4 @@ public class StatsUpdatePayload implements Serializable { totalVehiclesGenerated, totalVehiclesCompleted, intersectionArrivals, intersectionDepartures, intersectionQueueSize); } -} +} \ No newline at end of file diff --git a/main/src/main/java/sd/des/SimulationEvent.java b/main/src/main/java/sd/des/SimulationEvent.java index 7d486d9..589bc5e 100644 --- a/main/src/main/java/sd/des/SimulationEvent.java +++ b/main/src/main/java/sd/des/SimulationEvent.java @@ -3,31 +3,46 @@ package sd.des; import java.io.Serializable; /** - * Evento discreto da simulação. - * - *

Unidade fundamental de execução num sistema DES: + * Representa um evento atómico e imutável no contexto da Simulação de Eventos Discretos (DES). + *

+ * Esta classe é a unidade fundamental de processamento. Numa arquitetura DES, o estado do sistema + * não muda continuamente, mas sim em instantes discretos definidos por estes eventos. + *

+ * Características principais: *

    - *
  • timestamp - quando ocorre - *
  • type - o que acontece - *
  • payload - dados associados - *
  • location - qual processo o trata + *
  • Ordenação Temporal: Implementa {@link Comparable} para ser armazenado numa Fila de + * Eventos Futuros (FEL - Future Event List), garantindo execução cronológica.
  • + *
  • Distribuído: Implementa {@link Serializable} para permitir que eventos gerados num nó + * (ex: Coordenador) sejam transmitidos e executados noutro (ex: Interseção).
  • + *
  • Polimórfico: Transporta um {@code payload} genérico, permitindo associar qualquer + * entidade (Veículo, Sinal, etc.) ao evento.
  • *
*/ public class SimulationEvent implements Comparable, Serializable { private static final long serialVersionUID = 1L; + /** O instante virtual exato em que o evento deve ser processado. */ private final double timestamp; + + /** A categoria do evento (ex: VEHICLE_ARRIVAL, LIGHT_CHANGE). */ private final DESEventType type; + + /** Dados contextuais associados (ex: o objeto Vehicle que chegou). */ private final Object payload; - private final String location; // Process ID (e.g., "Cr1", "Coordinator", "Exit") /** - * Cria um novo evento de simulação. - * - * @param timestamp instante do evento (tempo de simulação em segundos) - * @param type tipo de evento - * @param payload dados associados (ex: objeto Vehicle) - * @param location processo que trata o evento + * O identificador do nó de destino onde o evento deve ser executado. + * (ex: "Cr1", "Coordinator", "ExitNode"). Se null, é um evento local. + */ + private final String location; + + /** + * Instancia um novo evento de simulação completo. + * + * @param timestamp Instante de execução (segundos virtuais). + * @param type Tipo enumerado do evento. + * @param payload Objeto de dados associado (pode ser null). + * @param location ID do processo alvo para execução distribuída. */ public SimulationEvent(double timestamp, DESEventType type, Object payload, String location) { this.timestamp = timestamp; @@ -36,7 +51,14 @@ public class SimulationEvent implements Comparable, Serializabl this.location = location; } - /** Cria evento sem localização (para eventos locais) */ + /** + * Construtor de conveniência para eventos locais (dentro do mesmo processo). + * Define {@code location} como null. + * + * @param timestamp Instante de execução. + * @param type Tipo do evento. + * @param payload Objeto de dados associado. + */ public SimulationEvent(double timestamp, DESEventType type, Object payload) { this(timestamp, type, payload, null); } @@ -58,8 +80,18 @@ public class SimulationEvent implements Comparable, Serializabl } /** - * Ordena eventos por timestamp (mais cedo primeiro). - * Em caso de empate, ordena por tipo para determinismo. + * Define a ordem natural de processamento na Fila de Prioridade. + *

+ * Lógica de Ordenação: + *

    + *
  1. Primária (Tempo): Eventos com menor timestamp ocorrem primeiro.
  2. + *
  3. Secundária (Determinismo): Em caso de empate temporal (simultaneidade), + * ordena alfabeticamente pelo nome do tipo. Isto garante que execuções repetidas + * da simulação produzam exatamente o mesmo resultado (determinismo estrito).
  4. + *
+ * + * @param other O outro evento a comparar. + * @return Inteiro negativo, zero ou positivo conforme a ordem. */ @Override public int compareTo(SimulationEvent other) { @@ -67,7 +99,7 @@ public class SimulationEvent implements Comparable, Serializabl if (timeComparison != 0) { return timeComparison; } - // Tie-breaker: order by event type name + // Tie-breaker: order by event type name to ensure reproducible runs return this.type.name().compareTo(other.type.name()); } @@ -95,4 +127,4 @@ public class SimulationEvent implements Comparable, Serializabl result = 31 * result + (location != null ? location.hashCode() : 0); return result; } -} +} \ No newline at end of file diff --git a/main/src/main/java/sd/des/TrafficLightEvent.java b/main/src/main/java/sd/des/TrafficLightEvent.java index 9ca1357..0b27107 100644 --- a/main/src/main/java/sd/des/TrafficLightEvent.java +++ b/main/src/main/java/sd/des/TrafficLightEvent.java @@ -3,28 +3,47 @@ package sd.des; import sd.model.TrafficLight; /** - * Payload for traffic light change events. - * Contains the traffic light and its direction. + * Encapsula o contexto de dados para eventos de mudança de estado de semáforos. + *

+ * Este objeto atua como o payload transportado por um {@link SimulationEvent} + * quando o tipo de evento é relacionado com controlo de tráfego (ex: mudança Verde -> Amarelo). + * Permite que o motor DES identifique exatamente qual instância de {@link TrafficLight} + * deve ser atualizada numa determinada interseção e direção. */ public class TrafficLightEvent { private final TrafficLight light; private final String direction; private final String intersectionId; + /** + * Cria um novo payload de evento de semáforo. + * @param light A instância do objeto semáforo a ser manipulado. + * @param direction A direção cardeal associada (ex: "North", "East"). + * @param intersectionId O identificador da interseção onde o semáforo reside. + */ public TrafficLightEvent(TrafficLight light, String direction, String intersectionId) { this.light = light; this.direction = direction; this.intersectionId = intersectionId; } + /** + * @return A referência direta para o objeto de domínio do semáforo. + */ public TrafficLight getLight() { return light; } + /** + * @return A direção do fluxo controlado por este semáforo. + */ public String getDirection() { return direction; } + /** + * @return O ID da interseção pai. + */ public String getIntersectionId() { return intersectionId; } @@ -33,4 +52,4 @@ public class TrafficLightEvent { public String toString() { return String.format("TrafficLightEvent[%s-%s]", intersectionId, direction); } -} +} \ No newline at end of file diff --git a/main/src/main/java/sd/logging/EventLogger.java b/main/src/main/java/sd/logging/EventLogger.java index f653ff9..dd08b23 100644 --- a/main/src/main/java/sd/logging/EventLogger.java +++ b/main/src/main/java/sd/logging/EventLogger.java @@ -11,10 +11,19 @@ import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicBoolean; /** - * Sistema de registo centralizado de eventos para a simulação distribuída. - * - *

Regista todos os eventos da simulação num ficheiro com timestamps e categorização. - * Thread-safe e não-bloqueante para impacto mínimo na performance.

+ * Motor de logging assíncrono e thread-safe para a simulação distribuída. + *

+ * Implementa o padrão Singleton para garantir um ponto centralizado de registo. + * Utiliza o padrão Producer-Consumer com uma {@link BlockingQueue} para desacoplar + * a geração de eventos (crítica para a performance da simulação) da persistência em disco + * (operação de I/O lenta). + *

+ * Garantias: + *

    + *
  • Non-blocking writes (para a thread chamadora, na maioria dos casos).
  • + *
  • Ordering cronológico aproximado (FIFO na fila).
  • + *
  • Graceful Shutdown (flush de logs pendentes ao terminar).
  • + *
*/ public class EventLogger { @@ -22,20 +31,33 @@ public class EventLogger { private static final Object instanceLock = new Object(); private final PrintWriter writer; + + /** Buffer de memória para absorver picos de eventos (Burst traffic). */ private final BlockingQueue logQueue; + + /** Thread dedicada (Consumer) para escrita em ficheiro. */ private final Thread writerThread; + private final AtomicBoolean running; private final SimpleDateFormat timestampFormat; private final long simulationStartMillis; - /** Construtor privado para padrão singleton */ + /** + * Inicializa o sistema de logs. + * Abre o ficheiro, escreve o cabeçalho e inicia a thread consumidora. + * + * @param logFilePath Caminho relativo ou absoluto do ficheiro de log. + * @throws IOException Se não for possível criar ou escrever no ficheiro. + */ private EventLogger(String logFilePath) throws IOException { + // Auto-flush ativado para garantir persistência, mas gerido pelo buffer do BufferedWriter this.writer = new PrintWriter(new BufferedWriter(new FileWriter(logFilePath, false)), true); - this.logQueue = new LinkedBlockingQueue<>(10000); + this.logQueue = new LinkedBlockingQueue<>(10000); // Backpressure: limita a 10k eventos pendentes this.running = new AtomicBoolean(true); this.timestampFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); this.simulationStartMillis = System.currentTimeMillis(); + // Header inicial do log writer.println("=".repeat(80)); writer.println("SIMULATION EVENT LOG"); writer.println("Started: " + timestampFormat.format(new Date())); @@ -47,11 +69,16 @@ public class EventLogger { writer.flush(); this.writerThread = new Thread(this::processLogQueue, "EventLogger-Writer"); - this.writerThread.setDaemon(true); + this.writerThread.setDaemon(true); // Permite que a JVM termine se apenas esta thread sobrar this.writerThread.start(); } - /** Obtém ou cria a instância singleton */ + /** + * Obtém a instância única do logger (Lazy Initialization). + * Se não existir, cria uma predefinida em "logs/simulation-events.log". + * + * @return A instância singleton. + */ public static EventLogger getInstance() { if (instance == null) { synchronized (instanceLock) { @@ -72,7 +99,8 @@ public class EventLogger { } /** - * Initialize with custom log file path. + * Reinicializa o logger com um ficheiro específico. + * Útil para testes ou configurações personalizadas. */ public static void initialize(String logFilePath) throws IOException { synchronized (instanceLock) { @@ -84,7 +112,13 @@ public class EventLogger { } /** - * Logs an event (non-blocking). + * Regista um evento genérico. + * Esta operação é não-bloqueante (retorna imediatamente após colocar na fila), + * exceto se a fila estiver cheia (backpressure). + * + * @param eventType Categoria do evento. + * @param component Nome do componente (ex: "Coordinator", "IntersectionProcess"). + * @param description Detalhes do evento. */ public void log(EventType eventType, String component, String description) { if (!running.get()) return; @@ -96,7 +130,7 @@ public class EventLogger { description ); - // Non-blocking offer - if queue is full, drop oldest + // Non-blocking offer - if queue is full, drop oldest or warn if (!logQueue.offer(entry)) { // Queue full - this shouldn't happen with 10k buffer, but handle gracefully System.err.println("EventLogger queue full - dropping event: " + eventType); @@ -104,14 +138,14 @@ public class EventLogger { } /** - * Logs an event with vehicle context. + * Regista um evento associado a um veículo específico (Helper method). */ public void logVehicle(EventType eventType, String component, String vehicleId, String description) { log(eventType, component, "[" + vehicleId + "] " + description); } /** - * Logs an error event. + * Regista um erro ou exceção com formatação apropriada. */ public void logError(String component, String description, Exception e) { String fullDescription = description + (e != null ? ": " + e.getMessage() : ""); @@ -119,11 +153,13 @@ public class EventLogger { } /** - * Background thread that writes log entries to file. + * Lógica da thread consumidora (Worker Thread). + * Retira eventos da fila e escreve no disco continuamente. */ private void processLogQueue() { while (running.get() || !logQueue.isEmpty()) { try { + // Poll com timeout para permitir verificar a flag 'running' periodicamente LogEntry entry = logQueue.poll(100, java.util.concurrent.TimeUnit.MILLISECONDS); if (entry != null) { writeEntry(entry); @@ -134,7 +170,7 @@ public class EventLogger { } } - // Flush remaining entries + // Flush final: garantir que eventos restantes na fila são escritos antes de morrer while (!logQueue.isEmpty()) { LogEntry entry = logQueue.poll(); if (entry != null) { @@ -144,7 +180,7 @@ public class EventLogger { } /** - * Writes a single log entry to file. + * Formata e escreve uma entrada de log no PrintWriter. */ private void writeEntry(LogEntry entry) { String timestamp = timestampFormat.format(new Date(entry.timestampMillis)); @@ -158,7 +194,7 @@ public class EventLogger { entry.description ); - // Flush periodically for real-time viewing + // Flush periódico inteligente: se a carga for baixa, garante que vemos logs em tempo real if (logQueue.size() < 10) { writer.flush(); } @@ -170,15 +206,17 @@ public class EventLogger { } /** - * Shuts down the logger and flushes all pending entries. + * Encerra o logger de forma segura. + * Desativa a aceitação de novos eventos, aguarda que a fila esvazie (flush) + * e fecha o ficheiro. */ public void shutdown() { if (!running.compareAndSet(true, false)) { - return; // Already shut down + return; // Já encerrado } try { - // Wait for writer thread to finish + // Wait for writer thread to finish flushing writerThread.join(5000); // Wait up to 5 seconds // Write footer @@ -195,7 +233,7 @@ public class EventLogger { } /** - * Internal class to represent a log entry. + * DTO interno imutável para armazenar dados do evento na fila. */ private static class LogEntry { final long timestampMillis; @@ -210,4 +248,4 @@ public class EventLogger { this.description = description; } } -} +} \ No newline at end of file diff --git a/main/src/main/java/sd/logging/EventType.java b/main/src/main/java/sd/logging/EventType.java index 634fabd..910ae94 100644 --- a/main/src/main/java/sd/logging/EventType.java +++ b/main/src/main/java/sd/logging/EventType.java @@ -1,34 +1,46 @@ package sd.logging; /** - * Tipos de eventos que podem ocorrer na simulação. - * Usados para categorizar e filtrar logs. + * Taxonomia oficial de eventos para o subsistema de logging centralizado. + *

+ * Este enumerado padroniza a categorização de todas as ocorrências na simulação, permitindo: + *

    + *
  • Filtragem granular de logs (ex: ver apenas erros ou apenas tráfego de rede).
  • + *
  • Análise estatística post-mortem (parsear logs para calcular latências).
  • + *
  • Correlação de eventos distribuídos (seguir o rastro de um veículo através de vários nós).
  • + *
*/ public enum EventType { + // --- Ciclo de Vida do Veículo --- VEHICLE_GENERATED("Vehicle Generated"), VEHICLE_ARRIVED("Vehicle Arrived"), VEHICLE_QUEUED("Vehicle Queued"), VEHICLE_DEPARTED("Vehicle Departed"), VEHICLE_EXITED("Vehicle Exited"), + // --- Controlo de Semáforos e Exclusão Mútua --- LIGHT_CHANGED_GREEN("Light Changed to Green"), LIGHT_CHANGED_RED("Light Changed to Red"), LIGHT_REQUEST_GREEN("Light Requested Green"), LIGHT_RELEASE_GREEN("Light Released Green"), + // --- Ciclo de Vida da Simulação/Processos --- SIMULATION_STARTED("Simulation Started"), SIMULATION_STOPPED("Simulation Stopped"), PROCESS_STARTED("Process Started"), PROCESS_STOPPED("Process Stopped"), + // --- Configuração e Telemetria --- STATS_UPDATE("Statistics Update"), CONFIG_CHANGED("Configuration Changed"), + // --- Camada de Rede (TCP/Sockets) --- CONNECTION_ESTABLISHED("Connection Established"), CONNECTION_LOST("Connection Lost"), MESSAGE_SENT("Message Sent"), MESSAGE_RECEIVED("Message Received"), + // --- Tratamento de Exceções --- ERROR("Error"); private final String displayName; @@ -45,4 +57,4 @@ public enum EventType { public String toString() { return displayName; } -} +} \ No newline at end of file diff --git a/main/src/main/java/sd/logging/VehicleTracer.java b/main/src/main/java/sd/logging/VehicleTracer.java index 194f319..611e6aa 100644 --- a/main/src/main/java/sd/logging/VehicleTracer.java +++ b/main/src/main/java/sd/logging/VehicleTracer.java @@ -12,16 +12,18 @@ import java.util.concurrent.ConcurrentHashMap; import sd.model.Vehicle; /** - * Rastreia e regista a viagem completa de veículos individuais. - * + * Subsistema de auditoria granular responsável pelo rastreio detalhado (Tracing) de veículos individuais. *

- * Cria ficheiros de trace detalhados com: + * Diferente do {@link EventLogger} (que regista eventos globais do sistema), esta classe foca-se + * na perspetiva do agente. Cria um ficheiro de rastro dedicado (`.trace`) para cada veículo + * monitorizado, registando cronologicamente cada interação com a infraestrutura (interseções, + * filas, semáforos). + *

+ * Funcionalidades: *

    - *
  • Timestamps de todos os eventos - *
  • Localizações (interseções) - *
  • Tempos de espera em cada semáforo - *
  • Tempos de travessia - *
  • Tempo total no sistema + *
  • Análise forense de percursos individuais.
  • + *
  • Validação de tempos de espera e travessia por nó.
  • + *
  • Cálculo de eficiência de rota (tempo em movimento vs. tempo parado).
  • *
*/ public class VehicleTracer { @@ -29,12 +31,18 @@ public class VehicleTracer { private static VehicleTracer instance; private static final Object instanceLock = new Object(); + /** Mapa thread-safe de sessões de trace ativas (VehicleID -> TraceHandler). */ private final Map trackedVehicles; + private final SimpleDateFormat timestampFormat; private final long simulationStartMillis; private final String traceDirectory; - /** Construtor privado (singleton) */ + /** + * Inicializa o tracer e prepara o diretório de saída. + * + * @param traceDirectory Caminho para armazenamento dos ficheiros .trace. + */ private VehicleTracer(String traceDirectory) { this.trackedVehicles = new ConcurrentHashMap<>(); this.timestampFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); @@ -48,7 +56,10 @@ public class VehicleTracer { } } - /** Obtém ou cria a instância singleton */ + /** + * Obtém a instância única do tracer (Singleton). + * @return A instância global. + */ public static VehicleTracer getInstance() { if (instance == null) { synchronized (instanceLock) { @@ -60,7 +71,10 @@ public class VehicleTracer { return instance; } - /** Inicializa com diretório de trace customizado */ + /** + * Reinicializa o tracer com um diretório personalizado. + * Útil para isolar logs de diferentes execuções em lote. + */ public static void initialize(String traceDirectory) { synchronized (instanceLock) { if (instance != null) { @@ -71,12 +85,14 @@ public class VehicleTracer { } /** - * Começa a rastrear um veículo específico. - * Cria ficheiro de trace para este veículo. + * Inicia a sessão de rastreio para um veículo específico. + * Cria o ficheiro {@code logs/traces/vehicle-{id}.trace} e escreve o cabeçalho. + * + * @param vehicleId O identificador único do veículo. */ public void startTracking(String vehicleId) { if (trackedVehicles.containsKey(vehicleId)) { - return; // Already tracking + return; // Já está a ser rastreado } VehicleTrace trace = new VehicleTrace(vehicleId, traceDirectory); @@ -86,7 +102,7 @@ public class VehicleTracer { } /** - * Stops tracking a vehicle and closes its trace file. + * Encerra a sessão de rastreio, fecha o descritor de ficheiro e remove da memória. */ public void stopTracking(String vehicleId) { VehicleTrace trace = trackedVehicles.remove(vehicleId); @@ -97,14 +113,14 @@ public class VehicleTracer { } /** - * Checks if a vehicle is being tracked. + * Verifica se um veículo está atualmente sob auditoria. */ public boolean isTracking(String vehicleId) { return trackedVehicles.containsKey(vehicleId); } /** - * Logs when a vehicle is generated. + * Regista o evento de instanciação do veículo pelo Coordenador. */ public void logGenerated(Vehicle vehicle) { if (!isTracking(vehicle.getId())) @@ -119,7 +135,7 @@ public class VehicleTracer { } /** - * Logs when a vehicle arrives at an intersection. + * Regista a chegada física do veículo à zona de deteção de uma interseção. */ public void logArrival(String vehicleId, String intersection, double simulationTime) { if (!isTracking(vehicleId)) @@ -133,7 +149,7 @@ public class VehicleTracer { } /** - * Logs when a vehicle is queued at a traffic light. + * Regista a entrada do veículo na estrutura de fila de um semáforo. */ public void logQueued(String vehicleId, String intersection, String direction, int queuePosition) { if (!isTracking(vehicleId)) @@ -147,7 +163,7 @@ public class VehicleTracer { } /** - * Logs when a vehicle starts waiting at a red light. + * Regista o início da espera ativa (veículo parado no Vermelho). */ public void logWaitingStart(String vehicleId, String intersection, String direction) { if (!isTracking(vehicleId)) @@ -161,7 +177,8 @@ public class VehicleTracer { } /** - * Logs when a vehicle finishes waiting (light turns green). + * Regista o fim da espera (Sinal Verde). + * @param waitTime Duração total da paragem nesta instância. */ public void logWaitingEnd(String vehicleId, String intersection, String direction, double waitTime) { if (!isTracking(vehicleId)) @@ -175,7 +192,7 @@ public class VehicleTracer { } /** - * Logs when a vehicle starts crossing an intersection. + * Regista o início da travessia da interseção (ocupação da zona crítica). */ public void logCrossingStart(String vehicleId, String intersection, String direction) { if (!isTracking(vehicleId)) @@ -189,7 +206,7 @@ public class VehicleTracer { } /** - * Logs when a vehicle finishes crossing an intersection. + * Regista a libertação da zona crítica da interseção. */ public void logCrossingEnd(String vehicleId, String intersection, double crossingTime) { if (!isTracking(vehicleId)) @@ -203,7 +220,7 @@ public class VehicleTracer { } /** - * Logs when a vehicle departs from an intersection. + * Regista a partida da interseção em direção ao próximo nó. */ public void logDeparture(String vehicleId, String intersection, String nextDestination) { if (!isTracking(vehicleId)) @@ -217,7 +234,10 @@ public class VehicleTracer { } /** - * Logs when a vehicle exits the system. + * Regista a saída do sistema (no Exit Node). + *

+ * Este método também desencadeia a escrita do Sumário de Viagem no final do log + * e fecha o ficheiro automaticamente. */ public void logExit(Vehicle vehicle, double systemTime) { if (!isTracking(vehicle.getId())) @@ -229,7 +249,7 @@ public class VehicleTracer { String.format("Exited system - Total time: %.2fs, Waiting: %.2fs, Crossing: %.2fs", systemTime, vehicle.getTotalWaitingTime(), vehicle.getTotalCrossingTime())); - // Write summary + // Escreve estatísticas sumarizadas trace.writeSummary(vehicle, systemTime); // Stop tracking and close file @@ -238,7 +258,8 @@ public class VehicleTracer { } /** - * Shuts down the tracer and closes all trace files. + * Fecha forçosamente todos os traces abertos. + * Deve ser chamado no shutdown da simulação para evitar corrupção de logs. */ public void shutdown() { for (VehicleTrace trace : trackedVehicles.values()) { @@ -248,7 +269,7 @@ public class VehicleTracer { } /** - * Internal class to handle tracing for a single vehicle. + * Classe interna auxiliar que gere o descritor de ficheiro e a formatação para um único veículo. */ private class VehicleTrace { private final String vehicleId; @@ -340,4 +361,4 @@ public class VehicleTracer { return str.length() <= maxLength ? str : str.substring(0, maxLength); } } -} +} \ No newline at end of file