mirror of
https://github.com/davidalves04/Trabalho-Pratico-SD.git
synced 2025-12-08 12:33:31 +00:00
more javadoc - dashboard, des, logging
This commit is contained in:
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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";
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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>Localizações (interseções)
|
* <li>Validaçã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;
|
||||||
|
|||||||
Reference in New Issue
Block a user