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;
/**
* 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.
* <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 {
@@ -52,7 +66,7 @@ public class DashboardUI extends Application {
// Intersection Table
private TableView<IntersectionRow> 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; }
}
}

View File

@@ -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.
* <p>
* 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 {
@@ -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.
* <p>
* 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 {
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.
* <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() {
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.
* <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() {
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";

View File

@@ -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.
* <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 {
@@ -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;

View File

@@ -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.
* <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 {
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<VehicleType, Integer> vehicleTypeCounts;
/** Tempos de espera acumulados por tipo de veículo. */
private Map<VehicleType, Long> 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;

View File

@@ -3,31 +3,46 @@ package sd.des;
import java.io.Serializable;
/**
* Evento discreto da simulação.
*
* <p>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).
* <p>
* 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>
* <li>timestamp - quando ocorre
* <li>type - o que acontece
* <li>payload - dados associados
* <li>location - qual processo o trata
* <li><b>Ordenação Temporal:</b> Implementa {@link Comparable} para ser armazenado numa Fila de
* Eventos Futuros (FEL - Future Event List), garantindo execução cronológica.</li>
* <li><b>Distribuído:</b> Implementa {@link Serializable} para permitir que eventos gerados num nó
* (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>
*/
public class SimulationEvent implements Comparable<SimulationEvent>, 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.
* 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 type tipo de evento
* @param payload dados associados (ex: objeto Vehicle)
* @param location processo que trata o evento
* @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<SimulationEvent>, 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<SimulationEvent>, 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.
* <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
public int compareTo(SimulationEvent other) {
@@ -67,7 +99,7 @@ public class SimulationEvent implements Comparable<SimulationEvent>, 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());
}

View File

@@ -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.
* <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 {
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;
}

View File

@@ -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.
*
* <p>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.</p>
* Motor de logging assíncrono e thread-safe para a simulação distribuída.
* <p>
* Implementa o padrão <i>Singleton</i> para garantir um ponto centralizado de registo.
* 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 {
@@ -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<LogEntry> 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;

View File

@@ -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.
* <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 {
// --- 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;

View File

@@ -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.
* <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>
* <li>Timestamps de todos os eventos
* <li>Localizões (interseções)
* <li>Tempos de espera em cada semáforo
* <li>Tempos de travessia
* <li>Tempo total no sistema
* <li>Análise forense de percursos individuais.</li>
* <li>Validão de tempos de espera e travessia por nó.</li>
* <li>Cálculo de eficiência de rota (tempo em movimento vs. tempo parado).</li>
* </ul>
*/
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<String, VehicleTrace> 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).
* <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) {
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;