mirror of
https://github.com/davidalves04/Trabalho-Pratico-SD.git
synced 2025-12-08 12:33:31 +00:00
364 lines
13 KiB
Java
364 lines
13 KiB
Java
package sd.logging;
|
|
|
|
import java.io.BufferedWriter;
|
|
import java.io.FileWriter;
|
|
import java.io.IOException;
|
|
import java.io.PrintWriter;
|
|
import java.text.SimpleDateFormat;
|
|
import java.util.Date;
|
|
import java.util.Map;
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
|
|
import sd.model.Vehicle;
|
|
|
|
/**
|
|
* Subsistema de auditoria granular responsável pelo rastreio detalhado (Tracing) de veículos individuais.
|
|
* <p>
|
|
* 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>Análise forense de percursos individuais.</li>
|
|
* <li>Validaçã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 {
|
|
|
|
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;
|
|
|
|
/**
|
|
* 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");
|
|
this.simulationStartMillis = System.currentTimeMillis();
|
|
this.traceDirectory = traceDirectory;
|
|
|
|
try {
|
|
java.nio.file.Files.createDirectories(java.nio.file.Paths.get(traceDirectory));
|
|
} catch (IOException e) {
|
|
System.err.println("Failed to create trace directory: " + e.getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Obtém a instância única do tracer (Singleton).
|
|
* @return A instância global.
|
|
*/
|
|
public static VehicleTracer getInstance() {
|
|
if (instance == null) {
|
|
synchronized (instanceLock) {
|
|
if (instance == null) {
|
|
instance = new VehicleTracer("logs/traces");
|
|
}
|
|
}
|
|
}
|
|
return instance;
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
instance.shutdown();
|
|
}
|
|
instance = new VehicleTracer(traceDirectory);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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; // Já está a ser rastreado
|
|
}
|
|
|
|
VehicleTrace trace = new VehicleTrace(vehicleId, traceDirectory);
|
|
trackedVehicles.put(vehicleId, trace);
|
|
|
|
trace.logEvent("TRACKING_STARTED", "", "Started tracking vehicle " + vehicleId);
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
if (trace != null) {
|
|
trace.logEvent("TRACKING_STOPPED", "", "Stopped tracking vehicle " + vehicleId);
|
|
trace.close();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Verifica se um veículo está atualmente sob auditoria.
|
|
*/
|
|
public boolean isTracking(String vehicleId) {
|
|
return trackedVehicles.containsKey(vehicleId);
|
|
}
|
|
|
|
/**
|
|
* Regista o evento de instanciação do veículo pelo Coordenador.
|
|
*/
|
|
public void logGenerated(Vehicle vehicle) {
|
|
if (!isTracking(vehicle.getId()))
|
|
return;
|
|
|
|
VehicleTrace trace = trackedVehicles.get(vehicle.getId());
|
|
if (trace != null) {
|
|
trace.logEvent("GENERATED", "Coordinator",
|
|
String.format("Type: %s, Entry Time: %.2fs, Route: %s",
|
|
vehicle.getType(), vehicle.getEntryTime(), vehicle.getRoute()));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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))
|
|
return;
|
|
|
|
VehicleTrace trace = trackedVehicles.get(vehicleId);
|
|
if (trace != null) {
|
|
trace.logEvent("ARRIVED", intersection,
|
|
String.format("Arrived at %s (sim time: %.2fs)", intersection, simulationTime));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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))
|
|
return;
|
|
|
|
VehicleTrace trace = trackedVehicles.get(vehicleId);
|
|
if (trace != null) {
|
|
trace.logEvent("QUEUED", intersection,
|
|
String.format("Queued at %s-%s (position: %d)", intersection, direction, queuePosition));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Regista o início da espera ativa (veículo parado no Vermelho).
|
|
*/
|
|
public void logWaitingStart(String vehicleId, String intersection, String direction) {
|
|
if (!isTracking(vehicleId))
|
|
return;
|
|
|
|
VehicleTrace trace = trackedVehicles.get(vehicleId);
|
|
if (trace != null) {
|
|
trace.logEvent("WAITING_START", intersection,
|
|
String.format("Started waiting at %s-%s (light is RED)", intersection, direction));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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))
|
|
return;
|
|
|
|
VehicleTrace trace = trackedVehicles.get(vehicleId);
|
|
if (trace != null) {
|
|
trace.logEvent("WAITING_END", intersection,
|
|
String.format("Finished waiting at %s-%s (waited %.2fs)", intersection, direction, waitTime));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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))
|
|
return;
|
|
|
|
VehicleTrace trace = trackedVehicles.get(vehicleId);
|
|
if (trace != null) {
|
|
trace.logEvent("CROSSING_START", intersection,
|
|
String.format("Started crossing %s-%s (light is GREEN)", intersection, direction));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Regista a libertação da zona crítica da interseção.
|
|
*/
|
|
public void logCrossingEnd(String vehicleId, String intersection, double crossingTime) {
|
|
if (!isTracking(vehicleId))
|
|
return;
|
|
|
|
VehicleTrace trace = trackedVehicles.get(vehicleId);
|
|
if (trace != null) {
|
|
trace.logEvent("CROSSING_END", intersection,
|
|
String.format("Finished crossing %s (took %.2fs)", intersection, crossingTime));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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))
|
|
return;
|
|
|
|
VehicleTrace trace = trackedVehicles.get(vehicleId);
|
|
if (trace != null) {
|
|
trace.logEvent("DEPARTED", intersection,
|
|
String.format("Departed from %s toward %s", intersection, nextDestination));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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()))
|
|
return;
|
|
|
|
VehicleTrace trace = trackedVehicles.get(vehicle.getId());
|
|
if (trace != null) {
|
|
trace.logEvent("EXITED", "Exit Node",
|
|
String.format("Exited system - Total time: %.2fs, Waiting: %.2fs, Crossing: %.2fs",
|
|
systemTime, vehicle.getTotalWaitingTime(), vehicle.getTotalCrossingTime()));
|
|
|
|
// Escreve estatísticas sumarizadas
|
|
trace.writeSummary(vehicle, systemTime);
|
|
|
|
// Stop tracking and close file
|
|
stopTracking(vehicle.getId());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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()) {
|
|
trace.close();
|
|
}
|
|
trackedVehicles.clear();
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
private final PrintWriter writer;
|
|
private final long traceStartMillis;
|
|
|
|
VehicleTrace(String vehicleId, String directory) {
|
|
this.vehicleId = vehicleId;
|
|
this.traceStartMillis = System.currentTimeMillis();
|
|
|
|
PrintWriter w = null;
|
|
try {
|
|
String filename = String.format("%s/vehicle-%s.trace", directory, vehicleId);
|
|
w = new PrintWriter(new BufferedWriter(new FileWriter(filename, false)), true);
|
|
|
|
// Write header
|
|
w.println("=".repeat(80));
|
|
w.println("VEHICLE TRACE: " + vehicleId);
|
|
w.println("Trace Started: " + timestampFormat.format(new Date()));
|
|
w.println("=".repeat(80));
|
|
w.println();
|
|
w.printf("%-23s | %-8s | %-15s | %-15s | %s\n",
|
|
"TIMESTAMP", "REL_TIME", "EVENT", "LOCATION", "DESCRIPTION");
|
|
w.println("-".repeat(80));
|
|
|
|
} catch (IOException e) {
|
|
System.err.println("Failed to create trace file for " + vehicleId + ": " + e.getMessage());
|
|
}
|
|
|
|
this.writer = w;
|
|
}
|
|
|
|
void logEvent(String eventType, String location, String description) {
|
|
if (writer == null)
|
|
return;
|
|
|
|
long now = System.currentTimeMillis();
|
|
String timestamp = timestampFormat.format(new Date(now));
|
|
double relativeTime = (now - traceStartMillis) / 1000.0;
|
|
|
|
writer.printf("%-23s | %8.3fs | %-15s | %-15s | %s\n",
|
|
timestamp,
|
|
relativeTime,
|
|
truncate(eventType, 15),
|
|
truncate(location, 15),
|
|
description);
|
|
writer.flush();
|
|
}
|
|
|
|
void writeSummary(Vehicle vehicle, double systemTime) {
|
|
if (writer == null)
|
|
return;
|
|
|
|
writer.println();
|
|
writer.println("=".repeat(80));
|
|
writer.println("JOURNEY SUMMARY");
|
|
writer.println("=".repeat(80));
|
|
writer.println("Vehicle ID: " + vehicle.getId());
|
|
writer.println("Vehicle Type: " + vehicle.getType());
|
|
writer.println("Route: " + vehicle.getRoute());
|
|
writer.println();
|
|
writer.printf("Entry Time: %.2f seconds\n", vehicle.getEntryTime());
|
|
writer.printf("Total System Time: %.2f seconds\n", systemTime);
|
|
writer.printf("Total Waiting Time: %.2f seconds (%.1f%%)\n",
|
|
vehicle.getTotalWaitingTime(),
|
|
100.0 * vehicle.getTotalWaitingTime() / systemTime);
|
|
writer.printf("Total Crossing Time: %.2f seconds (%.1f%%)\n",
|
|
vehicle.getTotalCrossingTime(),
|
|
100.0 * vehicle.getTotalCrossingTime() / systemTime);
|
|
writer.printf("Travel Time: %.2f seconds (%.1f%%)\n",
|
|
systemTime - vehicle.getTotalWaitingTime() - vehicle.getTotalCrossingTime(),
|
|
100.0 * (systemTime - vehicle.getTotalWaitingTime() - vehicle.getTotalCrossingTime()) / systemTime);
|
|
writer.println("=".repeat(80));
|
|
}
|
|
|
|
void close() {
|
|
if (writer != null) {
|
|
writer.println();
|
|
writer.println("-".repeat(80));
|
|
writer.println("END OF TRACE");
|
|
writer.println("=".repeat(80));
|
|
writer.close();
|
|
}
|
|
}
|
|
|
|
private String truncate(String str, int maxLength) {
|
|
if (str == null)
|
|
return "";
|
|
return str.length() <= maxLength ? str : str.substring(0, maxLength);
|
|
}
|
|
}
|
|
} |