more javadoc - dashboard, des, logging

This commit is contained in:
2025-12-07 19:57:40 +00:00
parent a8ce95e08c
commit 83c3d65e38
9 changed files with 370 additions and 139 deletions

View File

@@ -30,8 +30,22 @@ import sd.config.SimulationConfig;
import sd.model.VehicleType; import sd.model.VehicleType;
/** /**
* JavaFX-based Dashboard UI for displaying real-time simulation statistics. * Interface Gráfica (GUI) baseada em JavaFX para visualização de telemetria em tempo real.
* Provides a graphical interface with auto-updating statistics panels. * <p>
* Esta classe atua como a camada de apresentação (View) do sistema. Implementa o padrão
* <i>Observer</i> (via polling) para refletir o estado do modelo {@link DashboardStatistics}
* nos componentes visuais.
* <p>
* <b>Aspetos Técnicos Relevantes:</b>
* <ul>
* <li><b>Concorrência de UI:</b> Utiliza um {@link ScheduledExecutorService} para buscar dados
* em background e {@link Platform#runLater(Runnable)} para injetar atualizações na
* <i>JavaFX Application Thread</i>, evitando exceções de "Not on FX application thread".</li>
* <li><b>Data Binding:</b> Utiliza {@link TableView} com classes internas (DTOs) para
* renderização tabular eficiente de tipos de veículos e interseções.</li>
* <li><b>Controlo de Processos:</b> Integra com {@link SimulationProcessManager} para orquestrar
* o ciclo de vida (spawn/kill) dos processos externos da simulação.</li>
* </ul>
*/ */
public class DashboardUI extends Application { public class DashboardUI extends Application {
@@ -52,7 +66,7 @@ public class DashboardUI extends Application {
// Intersection Table // Intersection Table
private TableView<IntersectionRow> intersectionTable; private TableView<IntersectionRow> intersectionTable;
// Update scheduler // Update scheduler (Background Thread)
private ScheduledExecutorService updateScheduler; private ScheduledExecutorService updateScheduler;
// Configuration controls // Configuration controls
@@ -60,6 +74,10 @@ public class DashboardUI extends Application {
private String selectedConfigFile = "simulation.properties"; private String selectedConfigFile = "simulation.properties";
private Label configInfoLabel; 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 @Override
public void start(Stage primaryStage) { public void start(Stage primaryStage) {
try { try {
@@ -72,29 +90,27 @@ public class DashboardUI extends Application {
server = new DashboardServer(config); server = new DashboardServer(config);
statistics = server.getStatistics(); statistics = server.getStatistics();
// Start the dashboard server // Start the dashboard server (Backend listening port)
server.start(); server.start();
// Build UI // Build UI Layout
BorderPane root = new BorderPane(); BorderPane root = new BorderPane();
root.getStyleClass().add("root"); root.getStyleClass().add("root");
// Header // Header (Top)
VBox header = createHeader(); VBox header = createHeader();
root.setTop(header); root.setTop(header);
// Main content // Main content (Center)
VBox mainContent = createMainContent(); VBox mainContent = createMainContent();
root.setCenter(mainContent); root.setCenter(mainContent);
// Footer // Footer (Bottom)
HBox footer = createFooter(); HBox footer = createFooter();
root.setBottom(footer); root.setBottom(footer);
// Create scene // Create scene & apply CSS
Scene scene = new Scene(root, 1200, 850); Scene scene = new Scene(root, 1200, 850);
// Load CSS
String cssUrl = getClass().getResource("/dashboard.css").toExternalForm(); String cssUrl = getClass().getResource("/dashboard.css").toExternalForm();
scene.getStylesheets().add(cssUrl); scene.getStylesheets().add(cssUrl);
@@ -102,10 +118,10 @@ public class DashboardUI extends Application {
primaryStage.setScene(scene); primaryStage.setScene(scene);
primaryStage.show(); primaryStage.show();
// Start periodic updates // Start periodic updates loop
startPeriodicUpdates(); startPeriodicUpdates();
// Handle window close // Handle window close (Graceful shutdown)
primaryStage.setOnCloseRequest(event -> { primaryStage.setOnCloseRequest(event -> {
shutdown(); shutdown();
}); });
@@ -149,6 +165,8 @@ public class DashboardUI extends Application {
// Passar o ficheiro de configuração selecionado // Passar o ficheiro de configuração selecionado
processManager.setConfigFile(selectedConfigFile); processManager.setConfigFile(selectedConfigFile);
processManager.startSimulation(); processManager.startSimulation();
// Toggle UI state
btnStart.setDisable(true); btnStart.setDisable(true);
btnStop.setDisable(false); btnStop.setDisable(false);
configFileSelector.setDisable(true); // Bloquear mudanças durante simulação configFileSelector.setDisable(true); // Bloquear mudanças durante simulação
@@ -159,6 +177,8 @@ public class DashboardUI extends Application {
btnStop.setOnAction(e -> { btnStop.setOnAction(e -> {
processManager.stopSimulation(); processManager.stopSimulation();
// Toggle UI state
btnStart.setDisable(false); btnStart.setDisable(false);
btnStop.setDisable(true); btnStop.setDisable(true);
configFileSelector.setDisable(false); // Desbloquear para nova simulação configFileSelector.setDisable(false); // Desbloquear para nova simulação
@@ -435,13 +455,23 @@ public class DashboardUI extends Application {
grid.add(container, colGroup, row); grid.add(container, colGroup, row);
} }
/**
* Inicia o ciclo de polling em background.
* Atualiza a UI a cada 100ms.
*/
private void startPeriodicUpdates() { private void startPeriodicUpdates() {
updateScheduler = Executors.newSingleThreadScheduledExecutor(); updateScheduler = Executors.newSingleThreadScheduledExecutor();
updateScheduler.scheduleAtFixedRate(() -> { updateScheduler.scheduleAtFixedRate(() -> {
// Crucial: Encapsular atualização de UI em Platform.runLater
// para garantir execução na JavaFX Application Thread
Platform.runLater(this::updateUI); Platform.runLater(this::updateUI);
}, 0, 100, TimeUnit.MILLISECONDS); }, 0, 100, TimeUnit.MILLISECONDS);
} }
/**
* Sincroniza o estado atual do objeto Statistics com os controlos JavaFX.
* Chamado periodicamente pela thread de UI.
*/
private void updateUI() { private void updateUI() {
// Update global statistics // Update global statistics
lblVehiclesGenerated.setText(String.valueOf(statistics.getTotalVehiclesGenerated())); lblVehiclesGenerated.setText(String.valueOf(statistics.getTotalVehiclesGenerated()));
@@ -548,7 +578,9 @@ public class DashboardUI extends Application {
launch(args); 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 { public static class VehicleTypeRow {
private final String vehicleType; private final String vehicleType;
private final int count; private final int count;
@@ -560,19 +592,12 @@ public class DashboardUI extends Application {
this.avgWaitTime = avgWaitTime; this.avgWaitTime = avgWaitTime;
} }
public String getVehicleType() { public String getVehicleType() { return vehicleType; }
return vehicleType; public int getCount() { return count; }
} public String getAvgWaitTime() { return avgWaitTime; }
public int getCount() {
return count;
}
public String getAvgWaitTime() {
return avgWaitTime;
}
} }
/** DTO para linhas da tabela de Interseções. */
public static class IntersectionRow { public static class IntersectionRow {
private final String intersectionId; private final String intersectionId;
private final int arrivals; private final int arrivals;
@@ -586,20 +611,9 @@ public class DashboardUI extends Application {
this.queueSize = queueSize; this.queueSize = queueSize;
} }
public String getIntersectionId() { public String getIntersectionId() { return intersectionId; }
return intersectionId; public int getArrivals() { return arrivals; }
} public int getDepartures() { return departures; }
public int getQueueSize() { return queueSize; }
public int getArrivals() {
return arrivals;
}
public int getDepartures() {
return departures;
}
public int getQueueSize() {
return queueSize;
}
} }
} }

View File

@@ -6,9 +6,17 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
* Gere o ciclo de vida dos processos de simulação (Intersections, Exit Node, * Orquestrador de processos para o ambiente de simulação distribuída.
* Coordinator). * <p>
* Permite iniciar e parar a simulação distribuída dentro da aplicação Java. * Esta classe atua como um supervisor (Process Manager), responsável pelo <i>bootstrapping</i>
* e <i>teardown</i> das múltiplas Java Virtual Machines (JVMs) que compõem o sistema.
* <p>
* Funcionalidades principais:
* <ul>
* <li><b>Isolamento:</b> Cada nó (Interseção, Coordinator, ExitNode) corre no seu próprio processo OS.</li>
* <li><b>Ordem de Arranque:</b> Garante que os servidores (Interseções) estão online antes dos clientes (Coordenador).</li>
* <li><b>Gestão de Logs:</b> Redireciona stdout/stderr de cada processo filho para ficheiros temporários para facilitar o debug.</li>
* </ul>
*/ */
public class SimulationProcessManager { public class SimulationProcessManager {
@@ -16,6 +24,10 @@ public class SimulationProcessManager {
private final String classpath; private final String classpath;
private String configFile; 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() { public SimulationProcessManager() {
this.runningProcesses = new ArrayList<>(); this.runningProcesses = new ArrayList<>();
this.classpath = System.getProperty("java.class.path"); this.classpath = System.getProperty("java.class.path");
@@ -23,9 +35,9 @@ public class SimulationProcessManager {
} }
/** /**
* Define o ficheiro de configuração a usar. * 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 (ex: "simulation-low.properties") * * @param configFile Nome do ficheiro de propriedades (ex: "simulation-low.properties").
*/ */
public void setConfigFile(String configFile) { public void setConfigFile(String configFile) {
this.configFile = "src/main/resources/" + 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. * Executa o procedimento de arranque (Bootstrap) da simulação distribuída.
* * <p>
* @throws IOException se um processo falhar ao iniciar * A ordem de inicialização é crítica para evitar <i>Race Conditions</i> na conexão TCP:
* <ol>
* <li><b>Workers (Interseções):</b> Iniciam os ServerSockets.</li>
* <li><b>Sink (Exit Node):</b> Prepara-se para receber métricas finais.</li>
* <li><b>Delay de Estabilização:</b> Pausa de 1s para garantir que os sockets estão em LISTENING.</li>
* <li><b>Source (Coordinator):</b> Inicia a geração de carga e conecta-se aos nós.</li>
* </ol>
* * @throws IOException Se falhar o fork de algum processo.
*/ */
public void startSimulation() throws IOException { public void startSimulation() throws IOException {
if (!runningProcesses.isEmpty()) { if (!runningProcesses.isEmpty()) {
@@ -65,8 +84,11 @@ public class SimulationProcessManager {
} }
/** /**
* Checks if the coordinator process (last process started) is still running. * Verifica o estado de "liveness" da simulação monitorizando o processo Coordenador.
* When the coordinator finishes, the simulation is complete. * <p>
* 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() { public boolean isSimulationRunning() {
if (runningProcesses.isEmpty()) { if (runningProcesses.isEmpty()) {
@@ -78,8 +100,10 @@ public class SimulationProcessManager {
} }
/** /**
* Waits for the simulation to complete naturally. * Bloqueia a thread atual até que a simulação termine naturalmente ou ocorra timeout.
* Returns true if completed, false if 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 { public boolean waitForCompletion(long timeoutSeconds) throws InterruptedException {
if (runningProcesses.isEmpty()) { 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.
* <p>
* 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() { public void stopSimulation() {
System.out.println("Stopping simulation processes..."); 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 { private void startProcess(String className, String arg) throws IOException {
String javaBin = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java"; String javaBin = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java";

View File

@@ -4,7 +4,14 @@ import sd.model.MessageType;
import sd.protocol.MessageProtocol; import sd.protocol.MessageProtocol;
/** /**
* Message wrapper for sending statistics to the dashboard. * Implementação concreta do protocolo de mensagens destinada ao transporte de telemetria.
* <p>
* 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.
* <p>
* 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 { public class StatsMessage implements MessageProtocol {
@@ -14,27 +21,49 @@ public class StatsMessage implements MessageProtocol {
private final String destinationNode; private final String destinationNode;
private final StatsUpdatePayload payload; 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) { public StatsMessage(String sourceNode, StatsUpdatePayload payload) {
this.sourceNode = sourceNode; this.sourceNode = sourceNode;
this.destinationNode = "DashboardServer"; this.destinationNode = "DashboardServer"; // Destino implícito e fixo
this.payload = payload; this.payload = payload;
} }
/**
* Retorna o tipo da mensagem, que identifica semanticamente o conteúdo para o recetor.
* @return Sempre {@link MessageType#STATS_UPDATE}.
*/
@Override @Override
public MessageType getType() { public MessageType getType() {
return MessageType.STATS_UPDATE; return MessageType.STATS_UPDATE;
} }
/**
* Obtém a carga útil da mensagem.
* @return O objeto {@link StatsUpdatePayload} associado.
*/
@Override @Override
public Object getPayload() { public Object getPayload() {
return payload; return payload;
} }
/**
* Identifica a origem da mensagem.
* @return O ID do nó remetente.
*/
@Override @Override
public String getSourceNode() { public String getSourceNode() {
return sourceNode; return sourceNode;
} }
/**
* Identifica o destino da mensagem.
* @return Sempre "DashboardServer".
*/
@Override @Override
public String getDestinationNode() { public String getDestinationNode() {
return destinationNode; return destinationNode;

View File

@@ -7,25 +7,60 @@ import java.util.Map;
import sd.model.VehicleType; import sd.model.VehicleType;
/** /**
* DTO para atualizações de estatísticas ao dashboard. * Objeto de Transferência de Dados (DTO) otimizado para transporte de telemetria.
* Campos com valor -1 não são atualizados nesta mensagem. * <p>
* 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 <b>atualizações parciais</b>
* (Sparse Updates):
* <ul>
* <li>Campos globais inicializados com {@code -1} indicam "sem alteração" (no-op). O Dashboard
* deve ignorar estes campos e manter o valor acumulado anterior.</li>
* <li>Campos de interseção ({@code arrivals}, {@code departures}) representam deltas ou snapshots
* específicos do nó remetente.</li>
* </ul>
* Implementa {@link Serializable} para transmissão direta via Java Sockets.
*
[Image of data transfer object pattern]
*/ */
public class StatsUpdatePayload implements Serializable { public class StatsUpdatePayload implements Serializable {
private static final long serialVersionUID = 1L; 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; private int totalVehiclesGenerated = -1;
/** Total completado. Valor -1 indica que este campo deve ser ignorado. */
private int totalVehiclesCompleted = -1; private int totalVehiclesCompleted = -1;
/** Tempo total de sistema acumulado (ms). Valor -1 indica que deve ser ignorado. */
private long totalSystemTime = -1; private long totalSystemTime = -1;
/** Tempo total de espera acumulado (ms). Valor -1 indica que deve ser ignorado. */
private long totalWaitingTime = -1; 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; private int intersectionArrivals = 0;
/** Número de veículos que saíram da interseção desde o último reporte. */
private int intersectionDepartures = 0; private int intersectionDepartures = 0;
/** Snapshot do tamanho atual da fila na interseção. */
private int intersectionQueueSize = 0; private int intersectionQueueSize = 0;
// Detailed Breakdowns
/** Contagem acumulada por tipo de veículo. */
private Map<VehicleType, Integer> vehicleTypeCounts; private Map<VehicleType, Integer> vehicleTypeCounts;
/** Tempos de espera acumulados por tipo de veículo. */
private Map<VehicleType, Long> vehicleTypeWaitTimes; private Map<VehicleType, Long> vehicleTypeWaitTimes;
/**
* Inicializa o payload com os mapas vazios e contadores globais a -1 (estado neutro).
*/
public StatsUpdatePayload() { public StatsUpdatePayload() {
this.vehicleTypeCounts = new HashMap<>(); this.vehicleTypeCounts = new HashMap<>();
this.vehicleTypeWaitTimes = new HashMap<>(); this.vehicleTypeWaitTimes = new HashMap<>();
@@ -67,6 +102,8 @@ public class StatsUpdatePayload implements Serializable {
return vehicleTypeWaitTimes; return vehicleTypeWaitTimes;
} }
// Setters implementam Fluent Interface para construção encadeada
public StatsUpdatePayload setTotalVehiclesGenerated(int totalVehiclesGenerated) { public StatsUpdatePayload setTotalVehiclesGenerated(int totalVehiclesGenerated) {
this.totalVehiclesGenerated = totalVehiclesGenerated; this.totalVehiclesGenerated = totalVehiclesGenerated;
return this; return this;

View File

@@ -3,31 +3,46 @@ package sd.des;
import java.io.Serializable; import java.io.Serializable;
/** /**
* Evento discreto da simulação. * Representa um evento atómico e imutável no contexto da Simulação de Eventos Discretos (DES).
* * <p>
* <p>Unidade fundamental de execução num sistema 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.
* <p>
* Características principais:
* <ul> * <ul>
* <li>timestamp - quando ocorre * <li><b>Ordenação Temporal:</b> Implementa {@link Comparable} para ser armazenado numa Fila de
* <li>type - o que acontece * Eventos Futuros (FEL - Future Event List), garantindo execução cronológica.</li>
* <li>payload - dados associados * <li><b>Distribuído:</b> Implementa {@link Serializable} para permitir que eventos gerados num nó
* <li>location - qual processo o trata * (ex: Coordenador) sejam transmitidos e executados noutro (ex: Interseção).</li>
* <li><b>Polimórfico:</b> Transporta um {@code payload} genérico, permitindo associar qualquer
* entidade (Veículo, Sinal, etc.) ao evento.</li>
* </ul> * </ul>
*/ */
public class SimulationEvent implements Comparable<SimulationEvent>, Serializable { public class SimulationEvent implements Comparable<SimulationEvent>, Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/** O instante virtual exato em que o evento deve ser processado. */
private final double timestamp; private final double timestamp;
/** A categoria do evento (ex: VEHICLE_ARRIVAL, LIGHT_CHANGE). */
private final DESEventType type; private final DESEventType type;
/** Dados contextuais associados (ex: o objeto Vehicle que chegou). */
private final Object payload; private final Object payload;
private final String location; // Process ID (e.g., "Cr1", "Coordinator", "Exit")
/** /**
* Cria um novo evento de simulação. * 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 do evento (tempo de simulação em segundos) * @param timestamp Instante de execução (segundos virtuais).
* @param type tipo de evento * @param type Tipo enumerado do evento.
* @param payload dados associados (ex: objeto Vehicle) * @param payload Objeto de dados associado (pode ser null).
* @param location processo que trata o evento * @param location ID do processo alvo para execução distribuída.
*/ */
public SimulationEvent(double timestamp, DESEventType type, Object payload, String location) { public SimulationEvent(double timestamp, DESEventType type, Object payload, String location) {
this.timestamp = timestamp; this.timestamp = timestamp;
@@ -36,7 +51,14 @@ public class SimulationEvent implements Comparable<SimulationEvent>, Serializabl
this.location = location; 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) { public SimulationEvent(double timestamp, DESEventType type, Object payload) {
this(timestamp, type, payload, null); this(timestamp, type, payload, null);
} }
@@ -58,8 +80,18 @@ public class SimulationEvent implements Comparable<SimulationEvent>, Serializabl
} }
/** /**
* Ordena eventos por timestamp (mais cedo primeiro). * Define a ordem natural de processamento na Fila de Prioridade.
* Em caso de empate, ordena por tipo para determinismo. * <p>
* <b>Lógica de Ordenação:</b>
* <ol>
* <li><b>Primária (Tempo):</b> Eventos com menor timestamp ocorrem primeiro.</li>
* <li><b>Secundária (Determinismo):</b> 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).</li>
* </ol>
*
* @param other O outro evento a comparar.
* @return Inteiro negativo, zero ou positivo conforme a ordem.
*/ */
@Override @Override
public int compareTo(SimulationEvent other) { public int compareTo(SimulationEvent other) {
@@ -67,7 +99,7 @@ public class SimulationEvent implements Comparable<SimulationEvent>, Serializabl
if (timeComparison != 0) { if (timeComparison != 0) {
return timeComparison; 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()); return this.type.name().compareTo(other.type.name());
} }

View File

@@ -3,28 +3,47 @@ package sd.des;
import sd.model.TrafficLight; import sd.model.TrafficLight;
/** /**
* Payload for traffic light change events. * Encapsula o contexto de dados para eventos de mudança de estado de semáforos.
* Contains the traffic light and its direction. * <p>
* Este objeto atua como o <i>payload</i> 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 { public class TrafficLightEvent {
private final TrafficLight light; private final TrafficLight light;
private final String direction; private final String direction;
private final String intersectionId; 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) { public TrafficLightEvent(TrafficLight light, String direction, String intersectionId) {
this.light = light; this.light = light;
this.direction = direction; this.direction = direction;
this.intersectionId = intersectionId; this.intersectionId = intersectionId;
} }
/**
* @return A referência direta para o objeto de domínio do semáforo.
*/
public TrafficLight getLight() { public TrafficLight getLight() {
return light; return light;
} }
/**
* @return A direção do fluxo controlado por este semáforo.
*/
public String getDirection() { public String getDirection() {
return direction; return direction;
} }
/**
* @return O ID da interseção pai.
*/
public String getIntersectionId() { public String getIntersectionId() {
return intersectionId; return intersectionId;
} }

View File

@@ -11,10 +11,19 @@ import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
/** /**
* Sistema de registo centralizado de eventos para a simulação distribuída. * Motor de logging assíncrono e thread-safe para a simulação distribuída.
* * <p>
* <p>Regista todos os eventos da simulação num ficheiro com timestamps e categorização. * Implementa o padrão <i>Singleton</i> para garantir um ponto centralizado de registo.
* Thread-safe e não-bloqueante para impacto mínimo na performance.</p> * Utiliza o padrão <i>Producer-Consumer</i> 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).
* <p>
* <b>Garantias:</b>
* <ul>
* <li>Non-blocking writes (para a thread chamadora, na maioria dos casos).</li>
* <li>Ordering cronológico aproximado (FIFO na fila).</li>
* <li>Graceful Shutdown (flush de logs pendentes ao terminar).</li>
* </ul>
*/ */
public class EventLogger { public class EventLogger {
@@ -22,20 +31,33 @@ public class EventLogger {
private static final Object instanceLock = new Object(); private static final Object instanceLock = new Object();
private final PrintWriter writer; private final PrintWriter writer;
/** Buffer de memória para absorver picos de eventos (Burst traffic). */
private final BlockingQueue<LogEntry> logQueue; private final BlockingQueue<LogEntry> logQueue;
/** Thread dedicada (Consumer) para escrita em ficheiro. */
private final Thread writerThread; private final Thread writerThread;
private final AtomicBoolean running; private final AtomicBoolean running;
private final SimpleDateFormat timestampFormat; private final SimpleDateFormat timestampFormat;
private final long simulationStartMillis; 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 { 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.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.running = new AtomicBoolean(true);
this.timestampFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); this.timestampFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
this.simulationStartMillis = System.currentTimeMillis(); this.simulationStartMillis = System.currentTimeMillis();
// Header inicial do log
writer.println("=".repeat(80)); writer.println("=".repeat(80));
writer.println("SIMULATION EVENT LOG"); writer.println("SIMULATION EVENT LOG");
writer.println("Started: " + timestampFormat.format(new Date())); writer.println("Started: " + timestampFormat.format(new Date()));
@@ -47,11 +69,16 @@ public class EventLogger {
writer.flush(); writer.flush();
this.writerThread = new Thread(this::processLogQueue, "EventLogger-Writer"); 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(); 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() { public static EventLogger getInstance() {
if (instance == null) { if (instance == null) {
synchronized (instanceLock) { 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 { public static void initialize(String logFilePath) throws IOException {
synchronized (instanceLock) { 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) { public void log(EventType eventType, String component, String description) {
if (!running.get()) return; if (!running.get()) return;
@@ -96,7 +130,7 @@ public class EventLogger {
description 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)) { if (!logQueue.offer(entry)) {
// Queue full - this shouldn't happen with 10k buffer, but handle gracefully // Queue full - this shouldn't happen with 10k buffer, but handle gracefully
System.err.println("EventLogger queue full - dropping event: " + eventType); 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) { public void logVehicle(EventType eventType, String component, String vehicleId, String description) {
log(eventType, component, "[" + vehicleId + "] " + 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) { public void logError(String component, String description, Exception e) {
String fullDescription = description + (e != null ? ": " + e.getMessage() : ""); 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() { private void processLogQueue() {
while (running.get() || !logQueue.isEmpty()) { while (running.get() || !logQueue.isEmpty()) {
try { try {
// Poll com timeout para permitir verificar a flag 'running' periodicamente
LogEntry entry = logQueue.poll(100, java.util.concurrent.TimeUnit.MILLISECONDS); LogEntry entry = logQueue.poll(100, java.util.concurrent.TimeUnit.MILLISECONDS);
if (entry != null) { if (entry != null) {
writeEntry(entry); 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()) { while (!logQueue.isEmpty()) {
LogEntry entry = logQueue.poll(); LogEntry entry = logQueue.poll();
if (entry != null) { 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) { private void writeEntry(LogEntry entry) {
String timestamp = timestampFormat.format(new Date(entry.timestampMillis)); String timestamp = timestampFormat.format(new Date(entry.timestampMillis));
@@ -158,7 +194,7 @@ public class EventLogger {
entry.description 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) { if (logQueue.size() < 10) {
writer.flush(); 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() { public void shutdown() {
if (!running.compareAndSet(true, false)) { if (!running.compareAndSet(true, false)) {
return; // Already shut down return; // Já encerrado
} }
try { try {
// Wait for writer thread to finish // Wait for writer thread to finish flushing
writerThread.join(5000); // Wait up to 5 seconds writerThread.join(5000); // Wait up to 5 seconds
// Write footer // 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 { private static class LogEntry {
final long timestampMillis; final long timestampMillis;

View File

@@ -1,34 +1,46 @@
package sd.logging; package sd.logging;
/** /**
* Tipos de eventos que podem ocorrer na simulação. * Taxonomia oficial de eventos para o subsistema de logging centralizado.
* Usados para categorizar e filtrar logs. * <p>
* Este enumerado padroniza a categorização de todas as ocorrências na simulação, permitindo:
* <ul>
* <li>Filtragem granular de logs (ex: ver apenas erros ou apenas tráfego de rede).</li>
* <li>Análise estatística post-mortem (parsear logs para calcular latências).</li>
* <li>Correlação de eventos distribuídos (seguir o rastro de um veículo através de vários nós).</li>
* </ul>
*/ */
public enum EventType { public enum EventType {
// --- Ciclo de Vida do Veículo ---
VEHICLE_GENERATED("Vehicle Generated"), VEHICLE_GENERATED("Vehicle Generated"),
VEHICLE_ARRIVED("Vehicle Arrived"), VEHICLE_ARRIVED("Vehicle Arrived"),
VEHICLE_QUEUED("Vehicle Queued"), VEHICLE_QUEUED("Vehicle Queued"),
VEHICLE_DEPARTED("Vehicle Departed"), VEHICLE_DEPARTED("Vehicle Departed"),
VEHICLE_EXITED("Vehicle Exited"), VEHICLE_EXITED("Vehicle Exited"),
// --- Controlo de Semáforos e Exclusão Mútua ---
LIGHT_CHANGED_GREEN("Light Changed to Green"), LIGHT_CHANGED_GREEN("Light Changed to Green"),
LIGHT_CHANGED_RED("Light Changed to Red"), LIGHT_CHANGED_RED("Light Changed to Red"),
LIGHT_REQUEST_GREEN("Light Requested Green"), LIGHT_REQUEST_GREEN("Light Requested Green"),
LIGHT_RELEASE_GREEN("Light Released Green"), LIGHT_RELEASE_GREEN("Light Released Green"),
// --- Ciclo de Vida da Simulação/Processos ---
SIMULATION_STARTED("Simulation Started"), SIMULATION_STARTED("Simulation Started"),
SIMULATION_STOPPED("Simulation Stopped"), SIMULATION_STOPPED("Simulation Stopped"),
PROCESS_STARTED("Process Started"), PROCESS_STARTED("Process Started"),
PROCESS_STOPPED("Process Stopped"), PROCESS_STOPPED("Process Stopped"),
// --- Configuração e Telemetria ---
STATS_UPDATE("Statistics Update"), STATS_UPDATE("Statistics Update"),
CONFIG_CHANGED("Configuration Changed"), CONFIG_CHANGED("Configuration Changed"),
// --- Camada de Rede (TCP/Sockets) ---
CONNECTION_ESTABLISHED("Connection Established"), CONNECTION_ESTABLISHED("Connection Established"),
CONNECTION_LOST("Connection Lost"), CONNECTION_LOST("Connection Lost"),
MESSAGE_SENT("Message Sent"), MESSAGE_SENT("Message Sent"),
MESSAGE_RECEIVED("Message Received"), MESSAGE_RECEIVED("Message Received"),
// --- Tratamento de Exceções ---
ERROR("Error"); ERROR("Error");
private final String displayName; private final String displayName;

View File

@@ -12,16 +12,18 @@ import java.util.concurrent.ConcurrentHashMap;
import sd.model.Vehicle; 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.
*
* <p> * <p>
* Cria ficheiros de trace detalhados com: * Diferente do {@link EventLogger} (que regista eventos globais do sistema), esta classe foca-se
* na perspetiva do <b>agente</b>. 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).
* <p>
* <b>Funcionalidades:</b>
* <ul> * <ul>
* <li>Timestamps de todos os eventos * <li>Análise forense de percursos individuais.</li>
* <li>Localizões (interseções) * <li>Validão de tempos de espera e travessia por nó.</li>
* <li>Tempos de espera em cada semáforo * <li>Cálculo de eficiência de rota (tempo em movimento vs. tempo parado).</li>
* <li>Tempos de travessia
* <li>Tempo total no sistema
* </ul> * </ul>
*/ */
public class VehicleTracer { public class VehicleTracer {
@@ -29,12 +31,18 @@ public class VehicleTracer {
private static VehicleTracer instance; private static VehicleTracer instance;
private static final Object instanceLock = new Object(); private static final Object instanceLock = new Object();
/** Mapa thread-safe de sessões de trace ativas (VehicleID -> TraceHandler). */
private final Map<String, VehicleTrace> trackedVehicles; private final Map<String, VehicleTrace> trackedVehicles;
private final SimpleDateFormat timestampFormat; private final SimpleDateFormat timestampFormat;
private final long simulationStartMillis; private final long simulationStartMillis;
private final String traceDirectory; 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) { private VehicleTracer(String traceDirectory) {
this.trackedVehicles = new ConcurrentHashMap<>(); this.trackedVehicles = new ConcurrentHashMap<>();
this.timestampFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); 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() { public static VehicleTracer getInstance() {
if (instance == null) { if (instance == null) {
synchronized (instanceLock) { synchronized (instanceLock) {
@@ -60,7 +71,10 @@ public class VehicleTracer {
return instance; 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) { public static void initialize(String traceDirectory) {
synchronized (instanceLock) { synchronized (instanceLock) {
if (instance != null) { if (instance != null) {
@@ -71,12 +85,14 @@ public class VehicleTracer {
} }
/** /**
* Começa a rastrear um veículo específico. * Inicia a sessão de rastreio para um veículo específico.
* Cria ficheiro de trace para este veículo. * 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) { public void startTracking(String vehicleId) {
if (trackedVehicles.containsKey(vehicleId)) { if (trackedVehicles.containsKey(vehicleId)) {
return; // Already tracking return; // Já está a ser rastreado
} }
VehicleTrace trace = new VehicleTrace(vehicleId, traceDirectory); 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) { public void stopTracking(String vehicleId) {
VehicleTrace trace = trackedVehicles.remove(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) { public boolean isTracking(String vehicleId) {
return trackedVehicles.containsKey(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) { public void logGenerated(Vehicle vehicle) {
if (!isTracking(vehicle.getId())) 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) { public void logArrival(String vehicleId, String intersection, double simulationTime) {
if (!isTracking(vehicleId)) 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) { public void logQueued(String vehicleId, String intersection, String direction, int queuePosition) {
if (!isTracking(vehicleId)) 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) { public void logWaitingStart(String vehicleId, String intersection, String direction) {
if (!isTracking(vehicleId)) 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) { public void logWaitingEnd(String vehicleId, String intersection, String direction, double waitTime) {
if (!isTracking(vehicleId)) 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) { public void logCrossingStart(String vehicleId, String intersection, String direction) {
if (!isTracking(vehicleId)) 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) { public void logCrossingEnd(String vehicleId, String intersection, double crossingTime) {
if (!isTracking(vehicleId)) 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) { public void logDeparture(String vehicleId, String intersection, String nextDestination) {
if (!isTracking(vehicleId)) 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).
* <p>
* Este método também desencadeia a escrita do <b>Sumário de Viagem</b> no final do log
* e fecha o ficheiro automaticamente.
*/ */
public void logExit(Vehicle vehicle, double systemTime) { public void logExit(Vehicle vehicle, double systemTime) {
if (!isTracking(vehicle.getId())) if (!isTracking(vehicle.getId()))
@@ -229,7 +249,7 @@ public class VehicleTracer {
String.format("Exited system - Total time: %.2fs, Waiting: %.2fs, Crossing: %.2fs", String.format("Exited system - Total time: %.2fs, Waiting: %.2fs, Crossing: %.2fs",
systemTime, vehicle.getTotalWaitingTime(), vehicle.getTotalCrossingTime())); systemTime, vehicle.getTotalWaitingTime(), vehicle.getTotalCrossingTime()));
// Write summary // Escreve estatísticas sumarizadas
trace.writeSummary(vehicle, systemTime); trace.writeSummary(vehicle, systemTime);
// Stop tracking and close file // 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() { public void shutdown() {
for (VehicleTrace trace : trackedVehicles.values()) { 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 class VehicleTrace {
private final String vehicleId; private final String vehicleId;