31 Commits

Author SHA1 Message Date
4b90827c2a color update 2025-12-08 08:42:38 +00:00
61277350d8 data update \\ properties unnecessary data removal + translation 2025-12-08 08:27:04 +00:00
7af3fb558b Update branches to include 'main' for workflow triggers 2025-12-08 00:11:32 +00:00
a360dc708e remove testing log file to clean up repository 2025-12-08 00:08:11 +00:00
3250d5a433 Merge pull request #39 from davidalves04/dev
dev merge
2025-12-07 23:58:33 +00:00
65cb0b52f6 Merge branch 'main' into dev 2025-12-07 23:55:33 +00:00
87a8a1bcb6 Merge pull request #38 from davidalves04/cleanup
Cleanup
2025-12-07 23:23:04 +00:00
bce15fe90f Merge branch 'dev' into cleanup 2025-12-07 23:21:44 +00:00
542ce9c8c0 Merge pull request #37 from davidalves04/javadoc
Javadoc
2025-12-07 23:18:41 +00:00
926245986f finish 1st javadoc round 2025-12-07 22:39:21 +00:00
3a3756f701 Translate graph labels and titles to PT 2025-12-07 20:12:43 +00:00
83c3d65e38 more javadoc - dashboard, des, logging 2025-12-07 19:57:40 +00:00
a8ce95e08c Refactor and enhance documentation across multiple classes (Analysis through DashboardStatistics) 2025-12-07 19:33:40 +00:00
Gaa56
b624cfe11e Documentation 2025-12-07 15:51:50 +00:00
a2f9e725de feat: Implement batch performance analysis dialog and routing policies
- Added BatchAnalysisDialog for running multiple simulations and generating reports.
- Implemented LeastCongestedRouteSelector for dynamic routing based on congestion levels.
- Created RandomRouteSelector for baseline random routing strategy.
- Developed ShortestPathRouteSelector to select routes based on the shortest path.
- Defined RouteSelector interface to standardize routing policy implementations.
- Introduced RoutingPolicy enum to manage available routing strategies.
2025-12-07 00:35:06 +00:00
92ff883d4c fixed dash formatting 2025-12-06 00:59:09 +00:00
David Alves
8fe4e564d3 Update Diagrama de arquitetura - SD.jpg 2025-12-04 22:27:01 +00:00
David Alves
e389c0711e Updated architecture diagram image 2025-12-04 22:25:55 +00:00
1b6ad03057 Merge pull request #35 from davidalves04/cleanup
Refactor Simulation Core & Enhance Dashboard UI
2025-11-27 20:20:34 +00:00
David Alves
24fe1c1d67 Update Diagrama de arquitetura - SD.drawio 2025-11-27 19:54:14 +00:00
David Alves
766eabbbe4 Update .gitignore 2025-11-27 15:49:03 +00:00
David Alves
d7b1de1fe3 Update Diagrama de arquitetura - SD.drawio 2025-11-27 15:47:30 +00:00
David Alves
96b3a66b96 Diagram update
@0x1eo @Gaa56 o que acham do diagrama? Mudariam/acrescentariam algo? As legendas, parecem-vos bem?
2025-11-27 15:46:02 +00:00
David Alves
29848b04a6 Update architecture diagram 2025-11-23 23:16:08 +00:00
043ba7d185 Add workflow_dispatch trigger to Maven CI 2025-11-23 22:12:35 +00:00
25f2876c34 Refactor GitHub Actions workflow for Maven build 2025-11-23 22:10:40 +00:00
7cbecc4fab Update Maven workflow for JDK setup and packaging 2025-11-23 22:00:49 +00:00
72db59415f Add Windows build job to Maven workflow 2025-11-23 21:53:33 +00:00
60b4f0c2b6 Add 'cleanup' branch to Maven CI workflow 2025-11-22 22:53:54 +00:00
81f842e2bb Change CI branch from 'main' to 'dev' 2025-11-19 20:54:47 +00:00
David Alves
108d2e544c Enunciado uploaded 2025-10-27 18:02:24 +00:00
70 changed files with 5261 additions and 2759 deletions

View File

@@ -0,0 +1,110 @@
<mxfile host="Electron" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/29.0.3 Chrome/140.0.7339.249 Electron/38.7.0 Safari/537.36" version="29.0.3">
<diagram name="Arquitetura SD" id="QKeTeUWuUs8JeLsq44d-">
<mxGraphModel dx="1426" dy="841" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1654" pageHeight="1169" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="0K4eb2koB2xQ8duQ1-_a-1" value="&lt;b&gt;CoordinatorProcess&lt;/b&gt;&lt;br&gt;(Cliente Socket)&lt;hr&gt;• VehicleGenerator&lt;br&gt;• Modelo Poisson (λ=0.5)&lt;br&gt;• Liga a Cr1-Cr5" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;align=left;verticalAlign=top;spacing=10;fontColor=#000000;" parent="1" vertex="1">
<mxGeometry x="560" y="40" width="240" height="100" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-2" value="&lt;b&gt;Cr1&lt;/b&gt; (IntersectionProcess)&lt;br&gt;Porta: 8001&lt;br&gt;Servidor + Cliente&lt;hr&gt;• ServerSocket (8001)&lt;br&gt;• Thread Semáforo Sul&lt;br&gt;• Thread Semáforo Este&lt;br&gt;• Thread Semáforo Oeste&lt;br&gt;• Fila Eventos (DES)&lt;br&gt;• ReentrantLock" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;align=left;verticalAlign=top;spacing=10;fontColor=#000000;" parent="1" vertex="1">
<mxGeometry x="280" y="200" width="180" height="160" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-3" value="&lt;b&gt;Cr2&lt;/b&gt; (IntersectionProcess)&lt;br&gt;Porta: 8002&lt;br&gt;Servidor + Cliente&lt;hr&gt;• ServerSocket (8002)&lt;br&gt;• Thread Semáforo Sul&lt;br&gt;• Thread Semáforo Este&lt;br&gt;• Thread Semáforo Oeste&lt;br&gt;• Fila Eventos (DES)&lt;br&gt;• ReentrantLock" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;align=left;verticalAlign=top;spacing=10;fontColor=#000000;" parent="1" vertex="1">
<mxGeometry x="590" y="190" width="180" height="160" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-5" value="&lt;b&gt;Cr4&lt;/b&gt; (IntersectionProcess)&lt;br&gt;Porta: 8004&lt;br&gt;Servidor + Cliente&lt;hr&gt;• ServerSocket (8004)&lt;br&gt;• Thread Semáforo Sul&lt;br&gt;• Thread Semáforo Este&lt;br&gt;• Thread Semáforo Oeste&lt;br&gt;• Fila Eventos (DES)&lt;br&gt;• ReentrantLock" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;align=left;verticalAlign=top;spacing=10;fontColor=#000000;" parent="1" vertex="1">
<mxGeometry x="440" y="530" width="180" height="160" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-6" value="&lt;b&gt;Cr5&lt;/b&gt; (IntersectionProcess)&lt;br&gt;Porta: 8005&lt;br&gt;Servidor + Cliente&lt;hr&gt;• ServerSocket (8005)&lt;br&gt;• Thread Semáforo Sul&lt;br&gt;• Thread Semáforo Este&lt;br&gt;• Thread Semáforo Oeste&lt;br&gt;• Fila Eventos (DES)&lt;br&gt;• ReentrantLock" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;align=left;verticalAlign=top;spacing=10;fontColor=#000000;" parent="1" vertex="1">
<mxGeometry x="910" y="430" width="180" height="160" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-7" value="&lt;b&gt;ExitNode (S)&lt;/b&gt;&lt;br&gt;Porta: 9001&lt;br&gt;Servidor Socket&lt;hr&gt;• Recebe veículos finais&lt;br&gt;• Calcula estatísticas:&lt;br&gt; - Tempo no sistema&lt;br&gt; - Tempo de espera&lt;br&gt; - Métricas por tipo&lt;br&gt;• Envia para Dashboard" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;align=left;verticalAlign=top;spacing=10;fontColor=#000000;" parent="1" vertex="1">
<mxGeometry x="727" y="810" width="200" height="170" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-8" value="&lt;b&gt;DashboardServer&lt;/b&gt;&lt;br&gt;Porta: 9000&lt;br&gt;Servidor Socket&lt;hr&gt;• Thread Pool (10 threads)&lt;br&gt;• ConcurrentHashMap&lt;br&gt;• Agrega estatísticas&lt;br&gt;• Display a cada 5s:&lt;br&gt; - Throughput&lt;br&gt; - Tempos médios&lt;br&gt; - Tamanhos de filas" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;align=left;verticalAlign=top;spacing=10;fontColor=#000000;" parent="1" vertex="1">
<mxGeometry x="1210" y="585" width="200" height="160" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-9" value="VEHICLE_SPAWN&lt;br&gt;(Vehicle)" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#0000FF;strokeWidth=2;" parent="1" source="0K4eb2koB2xQ8duQ1-_a-1" target="0K4eb2koB2xQ8duQ1-_a-2" edge="1">
<mxGeometry x="-0.2105" relative="1" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-10" value="VEHICLE_SPAWN&lt;br&gt;(Vehicle)" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#0000FF;strokeWidth=2;" parent="1" source="0K4eb2koB2xQ8duQ1-_a-1" target="0K4eb2koB2xQ8duQ1-_a-3" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-11" value="VEHICLE_SPAWN&lt;br&gt;(Vehicle)" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#0000FF;strokeWidth=2;" parent="1" source="0K4eb2koB2xQ8duQ1-_a-1" target="0K4eb2koB2xQ8duQ1-_a-4" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-12" value="VEHICLE_TRANSFER" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#00AA00;strokeWidth=2;endArrow=classic;startArrow=classic;" parent="1" source="0K4eb2koB2xQ8duQ1-_a-2" target="0K4eb2koB2xQ8duQ1-_a-3" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-13" value="VEHICLE_TRANSFER" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#00AA00;strokeWidth=2;endArrow=classic;startArrow=classic;" parent="1" source="0K4eb2koB2xQ8duQ1-_a-3" target="0K4eb2koB2xQ8duQ1-_a-4" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-14" value="VEHICLE_TRANSFER" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#00AA00;strokeWidth=2;endArrow=classic;startArrow=classic;" parent="1" source="0K4eb2koB2xQ8duQ1-_a-2" target="0K4eb2koB2xQ8duQ1-_a-5" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-15" value="VEHICLE_TRANSFER" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#00AA00;strokeWidth=2;endArrow=classic;startArrow=classic;" parent="1" source="0K4eb2koB2xQ8duQ1-_a-3" target="0K4eb2koB2xQ8duQ1-_a-5" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-16" value="VEHICLE_TRANSFER" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#00AA00;strokeWidth=2;endArrow=classic;startArrow=classic;" parent="1" source="0K4eb2koB2xQ8duQ1-_a-5" target="0K4eb2koB2xQ8duQ1-_a-6" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-17" value="VEHICLE_TRANSFER" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#00AA00;strokeWidth=2;endArrow=classic;startArrow=none;startFill=0;" parent="1" source="0K4eb2koB2xQ8duQ1-_a-4" target="0K4eb2koB2xQ8duQ1-_a-6" edge="1">
<mxGeometry x="0.3659" relative="1" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-18" value="VEHICLE_TRANSFER" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#FF6600;strokeWidth=2;" parent="1" source="0K4eb2koB2xQ8duQ1-_a-5" target="0K4eb2koB2xQ8duQ1-_a-7" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-19" value="VEHICLE_TRANSFER" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#FF6600;strokeWidth=2;" parent="1" source="0K4eb2koB2xQ8duQ1-_a-6" target="0K4eb2koB2xQ8duQ1-_a-7" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-20" value="STATS_UPDATE&lt;br&gt;(periódico 5s)" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#9933FF;strokeWidth=2;dashed=1;" parent="1" source="0K4eb2koB2xQ8duQ1-_a-2" target="0K4eb2koB2xQ8duQ1-_a-8" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-21" value="STATS_UPDATE&lt;br&gt;(periódico 5s)" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#9933FF;strokeWidth=2;dashed=1;" parent="1" source="0K4eb2koB2xQ8duQ1-_a-4" target="0K4eb2koB2xQ8duQ1-_a-8" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-22" value="STATS_UPDATE&lt;br&gt;(periódico 5s)" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#9933FF;strokeWidth=2;dashed=1;" parent="1" source="0K4eb2koB2xQ8duQ1-_a-4" target="0K4eb2koB2xQ8duQ1-_a-8" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-23" value="STATS_UPDATE&lt;br&gt;(periódico 5s)" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#9933FF;strokeWidth=2;dashed=1;" parent="1" source="0K4eb2koB2xQ8duQ1-_a-5" target="0K4eb2koB2xQ8duQ1-_a-8" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-24" value="STATS_UPDATE&lt;br&gt;(periódico 5s)" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#9933FF;strokeWidth=2;dashed=1;" parent="1" source="0K4eb2koB2xQ8duQ1-_a-6" target="0K4eb2koB2xQ8duQ1-_a-8" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-25" value="STATS_UPDATE&lt;br&gt;(periódico 5s)" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#9933FF;strokeWidth=2;dashed=1;" parent="1" source="0K4eb2koB2xQ8duQ1-_a-26" target="0K4eb2koB2xQ8duQ1-_a-8" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-27" value="&lt;b&gt;MessageProtocol&lt;/b&gt;&lt;hr&gt;interface:&lt;br&gt;• getType()&lt;br&gt;• getPayload()&lt;br&gt;• getSourceNode()&lt;br&gt;• getDestinationNode()" style="rounded=0;whiteSpace=wrap;html=1;align=left;verticalAlign=top;fillColor=#fff2cc;strokeColor=#d6b656;spacing=10;fontColor=#000000;" parent="1" vertex="1">
<mxGeometry x="30" y="30" width="180" height="120" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-28" value="&lt;b&gt;Tipos de Mensagens&lt;/b&gt;&lt;hr&gt;• VEHICLE_TRANSFER&lt;br&gt;• VEHICLE_SPAWN&lt;br&gt;• STATS_UPDATE&lt;br&gt;• TRAFFIC_LIGHT_SYNC&lt;br&gt;• HEARTBEAT" style="rounded=0;whiteSpace=wrap;html=1;align=left;verticalAlign=top;fillColor=#fff2cc;strokeColor=#d6b656;spacing=10;fontColor=#000000;" parent="1" vertex="1">
<mxGeometry x="20" y="170" width="200" height="120" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-29" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#9933FF;strokeWidth=2;dashed=1;" parent="1" source="0K4eb2koB2xQ8duQ1-_a-3" target="0K4eb2koB2xQ8duQ1-_a-4" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="480" y="280" as="sourcePoint" />
<mxPoint x="990" y="440" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-4" value="&lt;b&gt;Cr3&lt;/b&gt; (IntersectionProcess)&lt;br&gt;Porta: 8003&lt;br&gt;Servidor + Cliente&lt;hr&gt;• ServerSocket (8003)&lt;br&gt;• Thread Semáforo Sul&lt;br&gt;• Thread Semáforo Este&lt;br&gt;• Thread Semáforo Oeste&lt;br&gt;• Fila Eventos (DES)&lt;br&gt;• ReentrantLock" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;align=left;verticalAlign=top;spacing=10;fontColor=#000000;" parent="1" vertex="1">
<mxGeometry x="910" y="200" width="180" height="160" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-30" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#9933FF;strokeWidth=2;dashed=1;" parent="1" source="0K4eb2koB2xQ8duQ1-_a-7" target="0K4eb2koB2xQ8duQ1-_a-26" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="500" y="710" as="sourcePoint" />
<mxPoint x="1090" y="520" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-26" value="&lt;b&gt;LEGENDA&lt;/b&gt;&lt;hr&gt;━━━► Comunicação síncrona&lt;br&gt;╌╌╌► Comunicação periódica&lt;br&gt;&lt;br&gt;&lt;b&gt;Cores:&lt;/b&gt;&lt;br&gt;🔵 Azul = Geração&lt;br&gt;🟢 Verde = Transferência&lt;br&gt;🟠 Laranja = Finalização&lt;br&gt;🟣 Roxo = Monitorização&lt;br&gt;&lt;br&gt;&lt;b&gt;Serialização:&lt;/b&gt; JSON (Gson)&lt;br&gt;&lt;b&gt;Protocolo:&lt;/b&gt; TCP/IP" style="rounded=0;whiteSpace=wrap;html=1;align=left;verticalAlign=top;fillColor=#f5f5f5;strokeColor=#666666;fontColor=#333333;spacing=10;" parent="1" vertex="1">
<mxGeometry x="1210" y="825" width="200" height="220" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

View File

@@ -3,7 +3,7 @@ name: Java CI with Maven
on:
workflow_dispatch:
push:
branches: [ "dev", "cleanup" ]
branches: [ "main", "dev", "cleanup" ]
tags:
- 'v*.*.*'
pull_request:
@@ -77,7 +77,7 @@ jobs:
publish-release:
runs-on: ubuntu-latest
needs: [build, build-windows]
if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch'
if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' || github.ref == 'refs/heads/main'
permissions:
contents: write
steps:

3
.gitignore vendored
View File

@@ -54,3 +54,6 @@ build/
# JAR built pom file
dependency-reduced-pom.xml
# Python env
venv/

View File

@@ -1,24 +1,168 @@
<mxfile host="app.diagrams.net" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0" version="28.2.7">
<diagram name="Página-1" id="B1_hHcevBzWlEwI7FSV6">
<mxGraphModel dx="778" dy="476" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
<mxfile host="Electron" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/29.0.3 Chrome/140.0.7339.249 Electron/38.7.0 Safari/537.36" version="29.0.3">
<diagram name="Arquitetura SD" id="QKeTeUWuUs8JeLsq44d-">
<mxGraphModel dx="1426" dy="841" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1169" pageHeight="827" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="vcp7vux32DhQR4tKQhnF-8" value="Dashboard" style="sketch=0;pointerEvents=1;shadow=0;dashed=0;html=1;strokeColor=#C73500;labelPosition=center;verticalLabelPosition=bottom;verticalAlign=top;align=center;fillColor=#fa6800;shape=mxgraph.mscae.oms.dashboard;fontColor=#000000;" vertex="1" parent="1">
<mxGeometry x="389" y="230" width="50" height="41" as="geometry" />
<mxCell id="0K4eb2koB2xQ8duQ1-_a-27" value="&lt;b&gt;MessageProtocol&lt;/b&gt;&lt;hr&gt;interface:&lt;br&gt;• getType()&lt;br&gt;• getPayload()&lt;br&gt;• getSourceNode()&lt;br&gt;• getDestinationNode()" style="rounded=0;whiteSpace=wrap;html=1;align=left;verticalAlign=top;fillColor=#fff2cc;strokeColor=#d6b656;spacing=10;fontColor=#000000;" parent="1" vertex="1">
<mxGeometry x="30" y="30" width="180" height="120" as="geometry" />
</mxCell>
<mxCell id="vcp7vux32DhQR4tKQhnF-12" value="Semaforo.java" style="shape=image;html=1;verticalAlign=top;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;imageAspect=0;aspect=fixed;image=https://icons.diagrams.net/icon-cache1/Strabo-2829/traffic_light-1068.png" vertex="1" parent="1">
<mxGeometry x="230" y="350" width="53" height="53" as="geometry" />
<mxCell id="0K4eb2koB2xQ8duQ1-_a-28" value="&lt;b&gt;Tipos de Mensagens&lt;/b&gt;&lt;hr&gt;• VEHICLE_TRANSFER&lt;br&gt;• VEHICLE_SPAWN&lt;br&gt;• STATS_UPDATE&lt;br&gt;• TRAFFIC_LIGHT_SYNC&lt;br&gt;• HEARTBEAT" style="rounded=0;whiteSpace=wrap;html=1;align=left;verticalAlign=top;fillColor=#fff2cc;strokeColor=#d6b656;spacing=10;fontColor=#000000;" parent="1" vertex="1">
<mxGeometry x="20" y="170" width="200" height="120" as="geometry" />
</mxCell>
<mxCell id="vcp7vux32DhQR4tKQhnF-13" value="" style="endArrow=classic;startArrow=classic;html=1;rounded=0;movable=1;resizable=1;rotatable=1;deletable=1;editable=1;locked=0;connectable=1;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="310" y="330" as="sourcePoint" />
<mxPoint x="360" y="280" as="targetPoint" />
<mxCell id="0K4eb2koB2xQ8duQ1-_a-26" value="&lt;b&gt;LEGENDA&lt;/b&gt;&lt;hr&gt;━━━► Comunicação síncrona&lt;br&gt;╌╌╌► Comunicação periódica&lt;br&gt;&lt;br&gt;&lt;b&gt;Cores:&lt;/b&gt;&lt;br&gt;🔵 Azul =&amp;nbsp;&lt;span style=&quot;background-color: transparent;&quot;&gt;Criação do veículo&lt;/span&gt;&lt;div&gt;🟢 Verde = Transferência do veículo&lt;br&gt;🟠 Laranja = Chegada ao destino&lt;br&gt;🟣 Roxo =&amp;nbsp;&lt;span style=&quot;background-color: transparent;&quot;&gt;Envio das estatísticas&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;b&gt;Serialização:&lt;/b&gt; JSON (Gson)&lt;br&gt;&lt;b&gt;Protocolo:&lt;/b&gt; TCP/IP&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;align=left;verticalAlign=top;fillColor=#f5f5f5;strokeColor=#666666;fontColor=#333333;spacing=10;" parent="1" vertex="1">
<mxGeometry x="10" y="320" width="220" height="220" as="geometry" />
</mxCell>
<mxCell id="L62mICw2ZrYi1D68OOFe-13" value="" style="group" parent="1" vertex="1" connectable="0">
<mxGeometry x="280" y="40" width="850" height="730" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-20" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#9933FF;strokeWidth=2;dashed=1;exitX=0.616;exitY=-0.011;exitDx=0;exitDy=0;entryX=0.661;entryY=-0.002;entryDx=0;entryDy=0;entryPerimeter=0;exitPerimeter=0;" parent="L62mICw2ZrYi1D68OOFe-13" source="0K4eb2koB2xQ8duQ1-_a-2" target="0K4eb2koB2xQ8duQ1-_a-8" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="794" y="530" as="targetPoint" />
<Array as="points">
<mxPoint x="99" y="122" />
<mxPoint x="793" y="122" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="vcp7vux32DhQR4tKQhnF-14" value="CruzamentoServer.java" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=1;points=[];movable=1;rotatable=1;deletable=1;editable=1;locked=0;connectable=1;" vertex="1" connectable="0" parent="vcp7vux32DhQR4tKQhnF-13">
<mxGeometry x="-0.3933" relative="1" as="geometry">
<mxPoint x="25" y="25" as="offset" />
<mxCell id="0K4eb2koB2xQ8duQ1-_a-1" value="&lt;b&gt;CoordinatorProcess&lt;/b&gt;&lt;br&gt;(Cliente Socket)&lt;hr&gt;• VehicleGenerator&lt;br&gt;• Modelo Poisson&lt;br&gt;• Liga a Cr1-Cr5" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;align=left;verticalAlign=top;spacing=10;fontColor=#000000;" parent="L62mICw2ZrYi1D68OOFe-13" vertex="1">
<mxGeometry x="205.6637168141593" width="176.28318584070794" height="101.38888888888889" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-2" value="&lt;b&gt;Cr1&lt;/b&gt; (IntersectionProcess)&lt;br&gt;Porta: 8001&lt;br&gt;Servidor + Cliente&lt;hr&gt;• ServerSocket (8001)&lt;br&gt;• Thread Semáforo - Sul&lt;br&gt;• Thread Semáforo - Este&lt;br&gt;• Fila Eventos (DES)&lt;br&gt;• ReentrantLock" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;align=left;verticalAlign=top;spacing=10;fontColor=#000000;" parent="L62mICw2ZrYi1D68OOFe-13" vertex="1">
<mxGeometry y="162.22" width="160" height="162.22" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-3" value="&lt;b&gt;Cr2&lt;/b&gt; (IntersectionProcess)&lt;br&gt;Porta: 8002&lt;br&gt;Servidor + Cliente&lt;hr&gt;• ServerSocket (8002)&lt;br&gt;• Thread Semáforo - Sul&lt;br&gt;• Thread Semáforo - Este&lt;br&gt;• Thread Semáforo - Oeste&lt;br&gt;• Fila Eventos (DES)&lt;br&gt;• ReentrantLock" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;align=left;verticalAlign=top;spacing=10;fontColor=#000000;" parent="L62mICw2ZrYi1D68OOFe-13" vertex="1">
<mxGeometry x="227.7" y="162.22" width="162.3" height="162.22" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-5" value="&lt;b&gt;Cr4&lt;/b&gt; (IntersectionProcess)&lt;br&gt;Porta: 8004&lt;br&gt;Servidor + Cliente&lt;hr&gt;• ServerSocket (8004)&lt;br&gt;• Thread Semáforo - Este&lt;br&gt;• Fila Eventos (DES)&lt;br&gt;• ReentrantLock" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;align=left;verticalAlign=top;spacing=10;fontColor=#000000;" parent="L62mICw2ZrYi1D68OOFe-13" vertex="1">
<mxGeometry y="486.67" width="160" height="133.33" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-6" value="&lt;b&gt;Cr5&lt;/b&gt; (IntersectionProcess)&lt;br&gt;Porta: 8005&lt;br&gt;Servidor + Cliente&lt;hr&gt;• ServerSocket (8005)&lt;br&gt;• Thread Semáforo - Este&lt;br&gt;• Fila Eventos (DES)&lt;br&gt;• ReentrantLock" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;align=left;verticalAlign=top;spacing=10;fontColor=#000000;" parent="L62mICw2ZrYi1D68OOFe-13" vertex="1">
<mxGeometry x="220.35" y="486.67" width="169.65" height="162.22" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-7" value="&lt;b&gt;ExitNode (S)&lt;/b&gt;&lt;br&gt;Porta: 9001&lt;br&gt;Servidor Socket&lt;hr&gt;• Recebe veículos finais&lt;br&gt;• Calcula estatísticas:&lt;br&gt; - Tempo no sistema&lt;br&gt; - Tempo de espera&lt;br&gt; - Métricas por tipo&lt;br&gt;• Envia para o Dashboard" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;align=left;verticalAlign=top;spacing=10;fontColor=#000000;" parent="L62mICw2ZrYi1D68OOFe-13" vertex="1">
<mxGeometry x="464.07" y="476.53" width="154.6" height="172.36" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-8" value="&lt;b&gt;DashboardServer&lt;/b&gt;&lt;br&gt;Porta: 9000&lt;br&gt;Servidor Socket&lt;hr&gt;• Thread Pool (10 threads)&lt;br&gt;• ConcurrentHashMap&lt;br&gt;• Agrega estatísticas&lt;br&gt;• Display a cada 5s:&lt;br&gt; - Throughput&lt;br&gt; - Tempos médios&lt;br&gt; - Tamanhos de filas" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;align=left;verticalAlign=top;spacing=10;fontColor=#000000;" parent="L62mICw2ZrYi1D68OOFe-13" vertex="1">
<mxGeometry x="683.1" y="540" width="166.9" height="180" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-9" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#0000FF;strokeWidth=2;" parent="L62mICw2ZrYi1D68OOFe-13" source="0K4eb2koB2xQ8duQ1-_a-1" target="0K4eb2koB2xQ8duQ1-_a-2" edge="1">
<mxGeometry x="-0.2105" relative="1" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-10" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#0000FF;strokeWidth=2;" parent="L62mICw2ZrYi1D68OOFe-13" source="0K4eb2koB2xQ8duQ1-_a-1" target="0K4eb2koB2xQ8duQ1-_a-3" edge="1">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="309" y="120" />
<mxPoint x="309" y="120" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-11" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#0000FF;strokeWidth=2;" parent="L62mICw2ZrYi1D68OOFe-13" source="0K4eb2koB2xQ8duQ1-_a-1" target="0K4eb2koB2xQ8duQ1-_a-4" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-12" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#00AA00;strokeWidth=2;endArrow=classic;startArrow=classic;" parent="L62mICw2ZrYi1D68OOFe-13" source="0K4eb2koB2xQ8duQ1-_a-2" target="0K4eb2koB2xQ8duQ1-_a-3" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-13" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#00AA00;strokeWidth=2;endArrow=classic;startArrow=classic;" parent="L62mICw2ZrYi1D68OOFe-13" source="0K4eb2koB2xQ8duQ1-_a-3" target="0K4eb2koB2xQ8duQ1-_a-4" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-14" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#00AA00;strokeWidth=2;endArrow=classic;startArrow=none;startFill=0;" parent="L62mICw2ZrYi1D68OOFe-13" source="0K4eb2koB2xQ8duQ1-_a-2" target="0K4eb2koB2xQ8duQ1-_a-5" edge="1">
<mxGeometry x="0.125" y="100" relative="1" as="geometry">
<Array as="points">
<mxPoint x="66.10619469026548" y="446.11111111111114" />
<mxPoint x="66.10619469026548" y="446.11111111111114" />
</Array>
<mxPoint y="-1" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-16" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#00AA00;strokeWidth=2;endArrow=classic;startArrow=none;startFill=0;exitX=1.005;exitY=0.63;exitDx=0;exitDy=0;exitPerimeter=0;align=center;" parent="L62mICw2ZrYi1D68OOFe-13" source="0K4eb2koB2xQ8duQ1-_a-5" edge="1">
<mxGeometry x="-0.0178" y="-49" relative="1" as="geometry">
<mxPoint x="139.55752212389382" y="588.0555555555555" as="sourcePoint" />
<mxPoint x="220" y="571" as="targetPoint" />
<Array as="points">
<mxPoint x="220" y="571" />
</Array>
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-19" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#b46504;strokeWidth=2;fillColor=#fad7ac;" parent="L62mICw2ZrYi1D68OOFe-13" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="390" y="580" as="sourcePoint" />
<mxPoint x="462.74" y="580.22" as="targetPoint" />
<Array as="points" />
</mxGeometry>
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-22" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#9933FF;strokeWidth=2;dashed=1;exitX=0.981;exitY=0.08;exitDx=0;exitDy=0;exitPerimeter=0;" parent="L62mICw2ZrYi1D68OOFe-13" source="0K4eb2koB2xQ8duQ1-_a-4" target="0K4eb2koB2xQ8duQ1-_a-8" edge="1">
<mxGeometry x="0.1427" y="-60" relative="1" as="geometry">
<Array as="points">
<mxPoint x="593" y="175" />
<mxPoint x="593" y="140" />
<mxPoint x="764" y="140" />
</Array>
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-4" value="&lt;b&gt;Cr3&lt;/b&gt; (IntersectionProcess)&lt;br&gt;Porta: 8003&lt;br&gt;Servidor + Cliente&lt;hr&gt;• ServerSocket (8003)&lt;br&gt;• Thread Semáforo - Sul&lt;br&gt;• Thread Semáforo - Oeste&lt;br&gt;• Fila Eventos (DES)&lt;br&gt;• ReentrantLock" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;align=left;verticalAlign=top;spacing=10;fontColor=#000000;" parent="L62mICw2ZrYi1D68OOFe-13" vertex="1">
<mxGeometry x="462.74" y="162.22" width="167.26" height="162.22" as="geometry" />
</mxCell>
<mxCell id="0K4eb2koB2xQ8duQ1-_a-30" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#9933FF;strokeWidth=2;dashed=1;entryX=0.186;entryY=0.998;entryDx=0;entryDy=0;entryPerimeter=0;" parent="L62mICw2ZrYi1D68OOFe-13" target="0K4eb2koB2xQ8duQ1-_a-8" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="103" y="620" as="sourcePoint" />
<mxPoint x="710" y="730" as="targetPoint" />
<Array as="points">
<mxPoint x="103" y="730" />
<mxPoint x="714" y="730" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="L62mICw2ZrYi1D68OOFe-2" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#00AA00;strokeWidth=2;endArrow=none;startArrow=classic;startFill=1;endFill=0;" parent="L62mICw2ZrYi1D68OOFe-13" edge="1">
<mxGeometry x="-0.2214" y="26" relative="1" as="geometry">
<mxPoint x="293.8053097345133" y="486.6666666666666" as="sourcePoint" />
<mxPoint x="293.8053097345133" y="324.44444444444446" as="targetPoint" />
<Array as="points" />
<mxPoint x="-17" y="6" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="L62mICw2ZrYi1D68OOFe-3" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#b46504;strokeWidth=2;endArrow=classic;startArrow=none;startFill=0;fillColor=#fad7ac;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="L62mICw2ZrYi1D68OOFe-13" target="0K4eb2koB2xQ8duQ1-_a-7" edge="1">
<mxGeometry x="0.3659" relative="1" as="geometry">
<mxPoint as="offset" />
<mxPoint x="541" y="324" as="sourcePoint" />
<mxPoint x="528.8495575221239" y="435.9722222222221" as="targetPoint" />
<Array as="points">
<mxPoint x="541" y="360" />
<mxPoint x="541" y="360" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="L62mICw2ZrYi1D68OOFe-10" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#9933FF;strokeWidth=2;dashed=1;exitX=0.621;exitY=-0.003;exitDx=0;exitDy=0;exitPerimeter=0;" parent="L62mICw2ZrYi1D68OOFe-13" source="0K4eb2koB2xQ8duQ1-_a-3" edge="1">
<mxGeometry x="0.1427" y="-60" relative="1" as="geometry">
<mxPoint x="330.53097345132744" y="141.94444444444443" as="sourcePoint" />
<mxPoint x="780" y="540" as="targetPoint" />
<Array as="points">
<mxPoint x="329" y="130" />
<mxPoint x="780" y="130" />
</Array>
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="L62mICw2ZrYi1D68OOFe-11" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#9933FF;strokeWidth=2;dashed=1;" parent="L62mICw2ZrYi1D68OOFe-13" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="326" y="654" as="sourcePoint" />
<mxPoint x="683.1" y="700" as="targetPoint" />
<Array as="points">
<mxPoint x="326.1" y="700" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="L62mICw2ZrYi1D68OOFe-12" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#9933FF;strokeWidth=2;dashed=1;" parent="L62mICw2ZrYi1D68OOFe-13" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="556" y="650" as="sourcePoint" />
<mxPoint x="683.0973451327434" y="663.0833333333331" as="targetPoint" />
<Array as="points">
<mxPoint x="556" y="663" />
</Array>
</mxGeometry>
</mxCell>
</root>

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

BIN
Enunciado.pdf Normal file

Binary file not shown.

712
RELATORIO_FINAL.tex Normal file
View File

@@ -0,0 +1,712 @@
\documentclass[12pt,a4paper]{article}
% Pacotes essenciais
\usepackage[utf8]{inputenc}
\usepackage[portuguese]{babel}
\usepackage[T1]{fontenc}
\usepackage{graphicx}
\usepackage{hyperref}
\usepackage{booktabs}
\usepackage{longtable}
\usepackage{geometry}
\usepackage{fancyhdr}
\usepackage{amsmath}
\usepackage{listings}
\usepackage{xcolor}
\usepackage{float}
\usepackage{caption}
\usepackage{subcaption}
% Configuração da página
\geometry{
left=2.5cm,
right=2.5cm,
top=2.5cm,
bottom=2.5cm
}
% Configuração de hyperlinks
\hypersetup{
colorlinks=true,
linkcolor=blue,
filecolor=magenta,
urlcolor=cyan,
pdftitle={Trabalho Prático - Sistemas Distribuídos},
pdfauthor={David Alves, Leandro Afonso, Gabriel Moreira},
}
% Configuração de código
\lstset{
basicstyle=\ttfamily\footnotesize,
breaklines=true,
frame=single,
numbers=left,
numberstyle=\tiny,
keywordstyle=\color{blue},
commentstyle=\color{green!60!black},
stringstyle=\color{red}
}
% Cabeçalho e rodapé
\pagestyle{fancy}
\fancyhf{}
\rhead{Sistemas Distribuídos}
\lhead{Trabalho Prático}
\rfoot{Página \thepage}
\begin{document}
% ============================================================
% PÁGINA DE TÍTULO
% ============================================================
\begin{titlepage}
\centering
\vspace*{2cm}
{\LARGE\bfseries Licenciatura em Segurança Informática\\e Redes de Computadores\par}
\vspace{1.5cm}
{\Large Unidade Curricular ``Sistemas Distribuídos''\par}
\vspace{0.5cm}
{\large Ano letivo 2024/2025\par}
\vspace{2cm}
{\huge\bfseries Trabalho Prático\par}
\vspace{0.5cm}
{\Large Simulação de Tráfego Urbano Distribuído\par}
\vspace{2cm}
{\large\bfseries Realizado por:\par}
\vspace{0.5cm}
{\large
David Alves, número de aluno 8240231\\
Leandro Afonso, número de aluno [A COMPLETAR]\\
Gabriel Moreira, número de aluno [A COMPLETAR]
\par}
\vspace{1.5cm}
{\large\bfseries Docente da UC:\par}
\vspace{0.3cm}
{\large Ronaldo Moreira Salles\par}
\vfill
{\large 8 de dezembro de 2024\par}
\end{titlepage}
% ============================================================
% ÍNDICE
% ============================================================
\tableofcontents
\newpage
% ============================================================
% INTRODUÇÃO
% ============================================================
\section{Introdução}
O presente trabalho tem como objetivo dotar os alunos com a capacidade de desenvolver uma aplicação distribuída em linguagem Java, simulando um sistema de tráfego urbano. O projeto é desenvolvido de modo a permitir avaliar e gerir as regras de controlo dos semáforos, tendo em conta diferentes cargas de tráfego.
A simulação implementa uma arquitetura de processos distribuídos que comunicam através de sockets TCP/IP, onde cada componente opera de forma autónoma enquanto contribui para o comportamento global do sistema. Este paradigma permite avaliar propriedades como escalabilidade, tolerância a falhas e sincronização distribuída em ambientes concorrentes.
% ============================================================
% O MODELO DE SIMULAÇÃO
% ============================================================
\section{O Modelo de Simulação}
O sistema retrata uma malha três por três, tendo três pontos de entrada (E1, E2 e E3), cinco cruzamentos (de Cr1 a Cr5) e um ponto de saída (S). As arestas representam as ruas e podem ser de sentido único ou duplo. Cada cruzamento é um processo separado, responsável pela gestão local do tráfego através de semáforos independentes que controlam três direções (Sul, Este e Oeste). O ponto de saída recolhe as estatísticas finais dos veículos que terminam o seu percurso.
\subsection{Geração de Veículos}
O processo coordenador (\texttt{CoordinatorProcess}) atua como o gerador de veículos, colocando-os no percurso através dos pontos de entrada seguindo o modelo de \textbf{Poisson} ($\lambda$ configurável). A simulação modela assim a chegada de veículos de forma realista, semelhante ao tráfego real. Esta abordagem significa que o número total de veículos gerados varia entre execuções. Como alternativa, o sistema permite também configurar um modelo de chegadas fixas, onde os veículos chegam em intervalos constantes e previsíveis.
\subsection{Características dos Veículos}
Cada veículo criado tem a probabilidade de ser:
\begin{itemize}
\item \textbf{Mota} (20\%): Tempo de travessia = $0.5 \times$ tempo base
\item \textbf{Carro} (60\%): Tempo de travessia = $1.0 \times$ tempo base
\item \textbf{Camião} (20\%): Tempo de travessia = $2.0 \times$ tempo base
\end{itemize}
Cada veículo possui um identificador único e uma rota predefinida que determina o seu percurso completo até ao nó de saída. As rotas são atribuídas segundo a política de encaminhamento configurada:
\begin{itemize}
\item \textbf{Random}: Escolha aleatória entre todas as rotas possíveis
\item \textbf{Shortest Path}: Seleção da rota com menor número de saltos
\item \textbf{Least Congested}: Escolha da rota com menor congestionamento atual
\end{itemize}
\subsection{Modelo de Eventos Discretos (DES)}
A simulação baseia-se num modelo de eventos discretos, onde cada cruzamento mantém uma fila de prioridade de eventos ordenados cronologicamente por timestamp. Os eventos incluem:
\begin{itemize}
\item Chegadas e partidas de veículos
\item Mudanças de estado dos semáforos
\item Atualizações de estatísticas
\end{itemize}
Cada semáforo opera como uma thread independente, alternando entre os estados \textbf{GREEN} (verde) e \textbf{RED} (vermelho), gerindo uma fila FIFO de veículos que aguardam passagem. Quando um veículo atravessa um cruzamento, é enviado para o processo do próximo cruzamento indicado na sua rota, onde é adicionado à fila do semáforo correspondente à direção necessária.
\subsection{Comunicação Distribuída}
A arquitetura distribuída permite que cada componente seja executado de forma independente. As comunicações são baseadas em sockets TCP/IP, com serialização das mensagens em JSON através da biblioteca \textbf{Gson}. Esta abordagem garante interoperabilidade e facilita a depuração do sistema.
% ============================================================
% ARQUITETURA DO SISTEMA E COMPONENTES
% ============================================================
\section{Arquitetura do Sistema e Componentes}
Foi desenvolvido um diagrama que retrata a arquitetura do projeto, ilustrando a topologia da rede viária e as conexões entre os diversos processos.
\subsection{Componentes Principais}
\subsubsection{Coordenador (\texttt{CoordinatorProcess})}
\begin{itemize}
\item \textbf{Porta}: Coordenação central sem socket servidor próprio (atua como cliente)
\item \textbf{Função}: Geração de veículos, gestão do relógio global de simulação, injeção de carga nos pontos de entrada
\item \textbf{Responsabilidades}:
\begin{itemize}
\item Implementação do modelo de Poisson para chegadas estocásticas
\item Seleção de rotas baseada na política configurada
\item Sincronização temporal da simulação
\end{itemize}
\end{itemize}
\subsubsection{Processos de Cruzamento (\texttt{IntersectionProcess})}
Cada cruzamento mantém um \texttt{ServerSocket} dedicado que aceita ligações de múltiplos clientes simultaneamente, criando uma thread para cada ligação estabelecida:
\begin{itemize}
\item \textbf{Cr1}: Porta 8001
\item \textbf{Cr2}: Porta 8002
\item \textbf{Cr3}: Porta 8003
\item \textbf{Cr4}: Porta 8004
\item \textbf{Cr5}: Porta 8005
\end{itemize}
\textbf{Responsabilidades}:
\begin{itemize}
\item Gestão local de semáforos (3 direções: Sul, Este, Oeste)
\item Processamento de eventos DES locais
\item Encaminhamento de veículos para o próximo destino
\item Reporte periódico de estatísticas ao Dashboard
\end{itemize}
\subsubsection{Nó de Saída (\texttt{ExitNodeProcess})}
\begin{itemize}
\item \textbf{Porta}: 9001
\item \textbf{Função}: Agregação final de métricas
\item \textbf{Responsabilidades}:
\begin{itemize}
\item Cálculo do tempo total de permanência no sistema
\item Throughput global
\item Estatísticas agregadas por tipo de veículo
\end{itemize}
\end{itemize}
\subsubsection{Servidor de Dashboard (\texttt{DashboardServer})}
\begin{itemize}
\item \textbf{Porta}: 9000
\item \textbf{Função}: Monitorização centralizada e visualização em tempo real
\item \textbf{Responsabilidades}:
\begin{itemize}
\item Agregação de estatísticas de todos os processos
\item Deteção de falhas através de heartbeats
\item Renderização da interface gráfica (JavaFX) ou CLI
\end{itemize}
\end{itemize}
\subsection{Sincronização e Concorrência}
O \texttt{DashboardServer} implementa um sistema de agregação com \textbf{10 threads concorrentes} (thread pool) que processam atualizações de estatísticas vindas dos cinco cruzamentos. Utiliza um \texttt{ConcurrentHashMap} para armazenar métricas por cruzamento, permitindo leituras simultâneas enquanto recebe atualizações. O frontend é atualizado a cada \textbf{5 segundos}.
A sincronização entre os processos ocorre através de \textbf{comunicação assíncrona}, onde cada processo continua a sua execução local enquanto envia e recebe mensagens. Os semáforos dentro de cada cruzamento operam de forma autónoma com ciclos independentes. O dashboard identifica a ausência de heartbeats para detetar falhas de processos.
% ============================================================
% CLASSES E MÉTODOS
% ============================================================
\section{Classes e Métodos}
O programa está organizado em diversos packages que contêm classes com responsabilidades distintas. Neste relatório são mencionadas as principais que sustentam o projeto, estando o resto da documentação apoiado em JavaDoc.
\subsection{Package \texttt{sd.model}}
\subsubsection{Classe \texttt{Vehicle}}
Representa cada veículo na simulação com os seguintes atributos:
\begin{itemize}
\item \texttt{vehicleId}: Identificador único
\item \texttt{vehicleType}: Tipo (MOTORCYCLE, CAR, TRUCK)
\item \texttt{entryTime}: Timestamp de entrada no sistema
\item \texttt{route}: Lista de nós que compõem o percurso completo
\end{itemize}
\textbf{Métodos principais:}
\begin{itemize}
\item \texttt{advanceRoute()}: Avança para o próximo nó na rota
\item \texttt{getCurrentDestination()}: Obtém o próximo cruzamento
\item \texttt{addWaitingTime(double time)}: Acumula tempo de espera em filas
\item \texttt{addCrossingTime(double time)}: Acumula tempo de travessia
\end{itemize}
\subsubsection{Classe \texttt{Intersection}}
Gere os cruzamentos, mantendo:
\begin{itemize}
\item Um mapa de semáforos por direção (\texttt{Map<Direction, TrafficLight>})
\item Uma tabela de encaminhamento que mapeia destinos para direções específicas
\end{itemize}
\textbf{Métodos principais:}
\begin{itemize}
\item \texttt{configureRoute(String destination, Direction direction)}: Define a tabela de routing
\item \texttt{receiveVehicle(Vehicle vehicle)}: Recebe veículos e coloca-os na fila do semáforo correspondente
\item \texttt{getTrafficLight(Direction direction)}: Obtém o semáforo de uma direção específica
\end{itemize}
\subsubsection{Classe \texttt{TrafficLight}}
Controla cada semáforo individualmente, utilizando locks (\texttt{ReentrantLock}) para garantir thread-safety.
\textbf{Métodos principais:}
\begin{itemize}
\item \texttt{addVehicle(Vehicle vehicle)}: Adiciona veículo à fila (FIFO)
\item \texttt{removeVehicle()}: Remove veículo da fila (apenas quando verde)
\item \texttt{changeState(TrafficLightState newState)}: Alterna entre GREEN e RED
\item \texttt{getQueueSize()}: Retorna o tamanho atual da fila
\end{itemize}
\subsubsection{Classe \texttt{Event} (no package \texttt{sd.des})}
Representa eventos discretos na simulação. Implementa \texttt{Comparable<Event>} para ordenação automática cronológica na fila de prioridade.
\textbf{Atributos:}
\begin{itemize}
\item \texttt{timestamp}: Momento em que o evento deve ocorrer
\item \texttt{eventType}: Tipo do evento (VEHICLE\_ARRIVAL, TRAFFIC\_LIGHT\_CHANGE, etc.)
\item \texttt{associatedData}: Payload específico do evento
\end{itemize}
\subsubsection{Classe \texttt{Message}}
Define a estrutura das mensagens trocadas entre processos.
\textbf{Atributos:}
\begin{itemize}
\item \texttt{messageId}: Identificador único da mensagem
\item \texttt{messageType}: Tipo da mensagem (VEHICLE\_TRANSFER, STATS\_UPDATE, etc.)
\item \texttt{sourceNode}: Nó de origem
\item \texttt{destinationNode}: Nó de destino
\item \texttt{payload}: Conteúdo serializado em JSON
\end{itemize}
\subsection{Package \texttt{sd.coordinator}}
\subsubsection{Classe \texttt{CoordinatorProcess}}
Conduz a simulação através do paradigma de eventos discretos distribuído.
\textbf{Responsabilidades:}
\begin{itemize}
\item Inicialização da topologia da rede
\item Geração de veículos segundo distribuição de Poisson
\item Injeção de carga nos pontos de entrada (E1, E2, E3)
\item Seleção de rotas com base na política configurada
\end{itemize}
\textbf{Métodos principais:}
\begin{itemize}
\item \texttt{initialize()}: Prepara o sistema, estabelece conexões com interseções
\item \texttt{run()}: Loop principal de geração de veículos
\item \texttt{generateAndInjectVehicle()}: Cria um novo veículo e injeta-o num ponto de entrada
\item \texttt{selectRouteForVehicle()}: Determina a rota com base na política ativa
\end{itemize}
\subsubsection{Classe \texttt{SocketClient}}
Facilita a comunicação de rede do lado do cliente.
\textbf{Métodos principais:}
\begin{itemize}
\item \texttt{connect(String host, int port)}: Estabelece ligação TCP
\item \texttt{send(Message message)}: Serializa e transmite mensagem
\item \texttt{close()}: Encerra ligação e liberta recursos
\end{itemize}
\subsection{Package \texttt{sd}}
\subsubsection{Classe \texttt{IntersectionProcess}}
Representa um nó de processamento autónomo (Worker Node) na malha distribuída.
\textbf{Arquitetura híbrida:}
\begin{itemize}
\item \textbf{Reativa (Network I/O)}: Threads dedicadas aceitam conexões TCP e injetam veículos nas filas
\item \textbf{Proativa (DES Engine)}: Thread de processamento de eventos gere a lógica temporal
\end{itemize}
\textbf{Métodos principais:}
\begin{itemize}
\item \texttt{initialize()}: Carrega configurações, cria semáforos e rotas
\item \texttt{startTrafficLightThreads()}: Inicia threads de controlo dos semáforos
\item \texttt{startServerSocket()}: Aceita conexões de veículos de outros processos
\item \texttt{sendVehicleToNextDestination(Vehicle vehicle)}: Encaminha veículos pela rede
\item \texttt{reportStatistics()}: Envia métricas ao Dashboard periodicamente
\item \texttt{shutdown()}: Encerra serviços e liberta recursos
\end{itemize}
\subsection{Package \texttt{sd.util}}
\subsubsection{Classe \texttt{VehicleGenerator}}
Responsável pela criação de veículos segundo o modelo de Poisson.
\textbf{Métodos principais:}
\begin{itemize}
\item \texttt{generateVehicle()}: Cria veículo com tipo e rota selecionados aleatoriamente
\item \texttt{getNextArrivalTime(double lambda)}: Calcula o momento da próxima chegada usando distribuição exponencial
\end{itemize}
\subsubsection{Classe \texttt{StatisticsCollector}}
Agrega métricas do sistema, rastreando veículos em trânsito e mantendo contadores globais.
\textbf{Métodos principais:}
\begin{itemize}
\item \texttt{recordVehicleGeneration(Vehicle vehicle)}: Regista criação de veículo
\item \texttt{recordVehicleArrival(Vehicle vehicle, String intersection)}: Regista chegada a cruzamento
\item \texttt{recordVehicleDeparture(Vehicle vehicle, String intersection)}: Regista partida de cruzamento
\item \texttt{recordVehicleCompletion(Vehicle vehicle)}: Regista conclusão do percurso
\item \texttt{getStatistics()}: Retorna snapshot das métricas atuais
\item \texttt{printStatistics()}: Gera relatórios com throughput, tempos médios, etc.
\end{itemize}
\subsection{Package \texttt{sd.config}}
\subsubsection{Classe \texttt{SimulationConfig}}
Carrega parâmetros do ficheiro \texttt{simulation.properties} (ou variantes \texttt{simulation-low.properties}, \texttt{simulation-medium.properties}, \texttt{simulation-high.properties}).
\textbf{Parâmetros configuráveis:}
\begin{itemize}
\item Duração da simulação
\item Taxa de chegada $\lambda$ (lambda)
\item Probabilidades de tipos de veículos
\item Tempos de travessia base
\item Tempos de ciclo dos semáforos
\item Política de encaminhamento
\end{itemize}
\subsection{Package \texttt{sd.routing}}
\subsubsection{Políticas de Encaminhamento}
O sistema suporta três estratégias de seleção de rotas:
\begin{itemize}
\item \texttt{RandomRouteSelector}: Escolha equiprovável entre todas as rotas disponíveis.
\item \texttt{ShortestPathRouteSelector}: Seleção da rota com menor número de saltos (hops).
\item \texttt{LeastCongestedRouteSelector}: Escolha da rota com menor congestionamento atual, baseada em feedback dos cruzamentos sobre tamanhos de filas.
\end{itemize}
% ============================================================
% AVALIAÇÃO DO DESEMPENHO DO SISTEMA
% ============================================================
\section{Avaliação do Desempenho do Sistema}
A avaliação do sistema baseia-se em métricas recolhidas pela classe \texttt{DashboardStatistics}, que monitoriza:
\begin{itemize}
\item Número de veículos gerados
\item Número de veículos que completaram o percurso
\item Taxa de conclusão (\%)
\item Tempo médio no sistema (segundos)
\item Tempo médio de espera acumulado em filas (segundos)
\item Throughput (veículos/segundo)
\end{itemize}
\subsection{Cenários de Carga}
Foram configurados três cenários de carga de tráfego, cada um executado 5 vezes para obter estatísticas fiáveis.
\subsubsection{Tráfego Leve (LOW LOAD)}
\textbf{Configuração:} \texttt{simulation-low.properties} - $\lambda = 0.2$ veículos/segundo
\begin{table}[H]
\centering
\caption{Métricas de Desempenho - Tráfego Leve}
\begin{tabular}{lrrrrr}
\toprule
\textbf{Métrica} & \textbf{Média} & \textbf{Desvio Padrão} & \textbf{IC 95\%} & \textbf{Mín} & \textbf{Máx} \\
\midrule
Veículos Gerados & 364.60 & 9.34 & [351.30, 377.90] & 350 & 373 \\
Veículos Completados & 219.60 & 31.19 & [175.22, 263.98] & 187 & 263 \\
Taxa de Conclusão (\%) & \textbf{60.38} & 9.71 & [46.57, 74.20] & 50.40 & 72.85 \\
Tempo Médio no Sistema (s) & \textbf{33.04} & 7.41 & [22.50, 43.58] & 23.36 & 42.28 \\
Tempo Médio de Espera (s) & 13.78 & 3.43 & [8.82, 18.73] & 9.72 & 17.53 \\
\bottomrule
\end{tabular}
\end{table}
\textbf{Análise:} Com carga leve, a rede opera abaixo da capacidade sem congestionamento significativo. A taxa de conclusão superior a 60\% e o tempo médio no sistema de apenas 33 segundos demonstram que o sistema consegue processar eficientemente o tráfego quando a chegada de veículos é esparsa.
\subsubsection{Tráfego Moderado (MEDIUM LOAD)}
\textbf{Configuração:} \texttt{simulation-medium.properties} - $\lambda = 0.5$ veículos/segundo
\begin{table}[H]
\centering
\caption{Métricas de Desempenho - Tráfego Moderado}
\begin{tabular}{lrrrrr}
\toprule
\textbf{Métrica} & \textbf{Média} & \textbf{Desvio Padrão} & \textbf{IC 95\%} & \textbf{Mín} & \textbf{Máx} \\
\midrule
Veículos Gerados & 927.20 & 32.48 & [880.97, 973.43] & 886 & 954 \\
Veículos Completados & 419.40 & 90.64 & [290.42, 548.38] & 312 & 535 \\
Taxa de Conclusão (\%) & \textbf{45.23} & 9.64 & [31.50, 58.95] & 34.74 & 56.08 \\
Tempo Médio no Sistema (s) & \textbf{44.48} & 6.81 & [34.79, 54.18] & 35.08 & 52.56 \\
Tempo Médio de Espera (s) & 21.05 & 4.73 & [13.55, 28.56] & 14.60 & 25.60 \\
\bottomrule
\end{tabular}
\end{table}
\textbf{Análise:} Com tráfego moderado, observa-se o início de congestionamento. A taxa de conclusão cai para 45\% e o tempo médio no sistema aumenta 34\% relativamente ao cenário leve. As filas começam a formar-se nos cruzamentos principais (Cr2 e Cr5).
\subsubsection{Tráfego Intenso (HIGH LOAD)}
\textbf{Configuração:} \texttt{simulation-high.properties} - $\lambda = 1.0$ veículos/segundo
\begin{table}[H]
\centering
\caption{Métricas de Desempenho - Tráfego Intenso}
\begin{tabular}{lrrrrr}
\toprule
\textbf{Métrica} & \textbf{Média} & \textbf{Desvio Padrão} & \textbf{IC 95\%} & \textbf{Mín} & \textbf{Máx} \\
\midrule
Veículos Gerados & 1813.80 & 41.93 & [1754.13, 1873.47] & 1782 & 1872 \\
Veículos Completados & 651.00 & 354.20 & [146.96, 1155.04] & 179 & 953 \\
Taxa de Conclusão (\%) & \textbf{35.92} & 19.44 & [8.25, 63.58] & 9.70 & 50.91 \\
Tempo Médio no Sistema (s) & \textbf{60.15} & 6.17 & [51.38, 68.93] & 53.09 & 65.41 \\
Tempo Médio de Espera (s) & 37.82 & 5.59 & [29.67, 45.96] & 31.51 & 44.58 \\
\bottomrule
\end{tabular}
\end{table}
\textbf{Análise:} Com tráfego intenso, a rede aproxima-se da saturação. A taxa de conclusão cai para apenas 36\% com elevada variabilidade ($\sigma = 19.44\%$), indicando comportamento instável. O tempo médio no sistema atinge 60 segundos, mas o aspecto mais crítico é o tempo médio de espera de 37.82 segundos, representando 63\% do tempo total no sistema. A alta variabilidade sugere que o sistema está perto do colapso, com algumas execuções a atingir apenas 9.7\% de taxa de conclusão.
\subsection{Análise Comparativa}
\begin{table}[H]
\centering
\caption{Comparação entre Cenários de Carga}
\begin{tabular}{lrrrr}
\toprule
\textbf{Cenário} & \textbf{$\lambda$ (v/s)} & \textbf{Taxa Conclusão (\%)} & \textbf{Tempo Sistema (s)} & \textbf{Tempo Espera (s)} \\
\midrule
Leve & 0.2 & 60.38 & 33.04 & 13.78 \\
Moderado & 0.5 & 45.23 & 44.48 & 21.05 \\
Intenso & 1.0 & 35.92 & 60.15 & 37.82 \\
\bottomrule
\end{tabular}
\end{table}
\textbf{Observações:}
\begin{enumerate}
\item \textbf{Relação não-linear}: Dobrar a taxa de chegada ($0.2 \rightarrow 0.5$) reduz a taxa de conclusão em 25\%, mas quadruplicar ($0.2 \rightarrow 1.0$) reduz apenas 40\%, sugerindo efeitos de saturação.
\item \textbf{Gargalos identificados}: Os cruzamentos Cr2 e Cr5 funcionam como pontos de convergência, acumulando os maiores tamanhos de fila.
\item \textbf{Tempo de espera dominante}: Em carga intensa, 63\% do tempo no sistema é gasto em filas.
\end{enumerate}
\subsection{Otimizações Testadas}
\subsubsection{Configuração de Ciclos de Semáforos}
Foram testadas diferentes configurações de temporização:
\textbf{Ciclos Longos} (verde 60s, vermelho 5s):
\begin{itemize}
\item Maior vazão (throughput) por ciclo
\item Aumento significativo da espera para direções em vermelho
\item Adequado para tráfego unidirecional dominante
\end{itemize}
\textbf{Ciclos Curtos} (verde 8-10s, vermelho 5s):
\begin{itemize}
\item Distribuição mais equitativa da capacidade
\item Redução da espera máxima
\item Perda de eficiência devido ao overhead de mudanças de estado
\end{itemize}
\textbf{Ciclos Otimizados} (diferenciados por cruzamento):
\begin{itemize}
\item Cr2 e Cr5: verde 30-40s (pontos de convergência)
\item Cr1, Cr3, Cr4: verde 15-20s (tráfego distribuído)
\item Melhor compromisso entre throughput e equidade
\end{itemize}
\subsubsection{Políticas de Encaminhamento}
\textbf{Random}: Baseline - distribuição uniforme da carga mas sem otimização.
\textbf{Shortest Path}: Minimiza latência individual mas pode criar hotspots nos caminhos mais curtos.
\textbf{Least Congested}: Balanceamento dinâmico da carga, requer feedback em tempo real dos cruzamentos. Reduz congestionamento mas pode aumentar latência individual ao escolher caminhos mais longos.
% ============================================================
% DASHBOARD
% ============================================================
\section{Dashboard}
O sistema de monitorização foi implementado utilizando \textbf{JavaFX} para a interface gráfica, fornecendo visualização em tempo real do estado da simulação.
\subsection{Características Principais}
\subsubsection{Interface Gráfica}
\textbf{Estatísticas Globais}: Painel superior com métricas agregadas do sistema
\begin{itemize}
\item Veículos gerados vs. completados
\item Taxa de conclusão em tempo real
\item Throughput atual
\item Tempo médio no sistema
\end{itemize}
\textbf{Tabelas Dinâmicas}:
\begin{itemize}
\item Estatísticas por tipo de veículo (Mota, Carro, Camião)
\item Métricas por interseção (Cr1-Cr5)
\item Tamanhos de fila por direção
\item Estados dos semáforos (cores indicativas)
\end{itemize}
\textbf{Controlos de Simulação}:
\begin{itemize}
\item Botão \textbf{Start}: Inicia todos os processos distribuídos
\item Botão \textbf{Stop}: Termina a simulação graciosamente
\item Indicador de estado do sistema
\item Heartbeat visual para cada processo
\end{itemize}
\subsubsection{Arquitetura de Concorrência}
O \texttt{DashboardServer} utiliza:
\begin{itemize}
\item \textbf{Thread Pool} com 10 threads concorrentes para processar atualizações
\item \texttt{ConcurrentHashMap} para armazenamento thread-safe de métricas
\item \texttt{ScheduledExecutorService} para atualizações periódicas da UI (intervalo de 5 segundos)
\item \texttt{Platform.runLater()} para marshalling seguro de atualizações para a UI thread do JavaFX
\end{itemize}
\subsubsection{Deteção de Falhas}
O sistema implementa monitorização de saúde através de:
\begin{itemize}
\item \textbf{Heartbeats}: Cada processo envia sinais periódicos (intervalo configurável)
\item \textbf{Timeout de conexão}: Identifica processos não-responsivos
\item \textbf{Indicadores visuais}: Marcação a vermelho de nós com falhas
\item \textbf{Logs de eventos}: Registo de todas as anomalias detetadas
\end{itemize}
\subsection{Análise em Batch}
O sistema inclui uma ferramenta de análise estatística (\texttt{MultiRunAnalyzer}) que:
\begin{itemize}
\item Executa múltiplas simulações automaticamente
\item Calcula médias, medianas, desvios padrão e intervalos de confiança (95\%)
\item Gera relatórios em formato texto e CSV
\item Permite comparação entre diferentes configurações
\end{itemize}
% ============================================================
% CONCLUSÃO
% ============================================================
\section{Conclusão}
O trabalho desenvolvido demonstra a implementação bem-sucedida de um sistema distribuído para simulação de tráfego urbano, cumprindo os objetivos pedagógicos da unidade curricular.
\subsection{Principais Conquistas}
\begin{enumerate}
\item \textbf{Arquitetura Distribuída Robusta}: Implementação de comunicação assíncrona entre processos autónomos através de sockets TCP/IP, com serialização JSON garantindo interoperabilidade.
\item \textbf{Modelo de Simulação Realista}: Utilização do modelo de eventos discretos (DES) com distribuição de Poisson para chegadas de veículos, aproximando-se de padrões de tráfego reais.
\item \textbf{Escalabilidade Demonstrada}: O sistema mantém-se funcional desde tráfego leve ($\lambda=0.2$) até tráfego intenso ($\lambda=1.0$), embora com degradação expectável de desempenho em saturação.
\item \textbf{Monitorização Abrangente}: Dashboard com visualização em tempo real e capacidade de análise estatística multi-execução, permitindo avaliação rigorosa do desempenho.
\item \textbf{Políticas de Encaminhamento Adaptativas}: Implementação de três estratégias distintas (Random, Shortest Path, Least Congested) demonstrando flexibilidade arquitetural.
\end{enumerate}
\subsection{Aprendizagens}
\begin{itemize}
\item \textbf{Sincronização Distribuída}: Gestão da complexidade inerente à coordenação de múltiplos processos autónomos sem relógio global centralizado.
\item \textbf{Concorrência e Thread-Safety}: Utilização apropriada de locks, estruturas de dados concorrentes e thread pools para garantir correção em ambiente multi-threaded.
\item \textbf{Trade-offs de Desempenho}: Compreensão das relações não-lineares entre carga de entrada, throughput e latência, especialmente em aproximação à saturação.
\item \textbf{Análise Quantitativa}: Aplicação de métodos estatísticos (intervalos de confiança, análise de variância) para avaliação rigorosa de sistemas estocásticos.
\end{itemize}
\subsection{Limitações e Trabalho Futuro}
\textbf{Limitações identificadas:}
\begin{itemize}
\item Rotas estáticas: Veículos não podem desviar-se em resposta a congestionamento dinâmico
\item Capacidade infinita de filas: Sistema não modela bloqueios físicos por falta de espaço
\item Ausência de prioridades: Todos os veículos são tratados igualmente (sem veículos de emergência)
\item Modelo de semáforos simplificado: Não considera fases de amarelo ou coordenação entre cruzamentos adjacentes
\end{itemize}
\textbf{Melhorias propostas:}
\begin{enumerate}
\item Implementação de encaminhamento adaptativo baseado em aprendizagem por reforço
\item Modelação de capacidades finitas com backpressure entre cruzamentos
\item Coordenação de semáforos através de ``ondas verdes'' para corredores prioritários
\item Integração de eventos externos (acidentes, obras, eventos especiais)
\item Visualização 3D da malha viária com animação de veículos em movimento
\end{enumerate}
\subsection{Conclusão Final}
O projeto cumpre integralmente os requisitos da unidade curricular, demonstrando competências na conceção, implementação e avaliação de sistemas distribuídos. Os resultados quantitativos obtidos através das análises multi-execução fornecem insights valiosos sobre o comportamento do sistema sob diferentes condições de carga, validando a abordagem arquitetural adotada.
A experiência adquirida na resolução de desafios de sincronização, gestão de concorrência e análise de desempenho constitui uma base sólida para o desenvolvimento de sistemas distribuídos de maior complexidade em contextos profissionais futuros.
% ============================================================
% BIBLIOGRAFIA
% ============================================================
\begin{thebibliography}{9}
\bibitem{tanenbaum2017}
Tanenbaum, A. S., \& Van Steen, M. (2017).
\textit{Distributed Systems: Principles and Paradigms} (3rd ed.).
Pearson.
\bibitem{coulouris2011}
Coulouris, G., Dollimore, J., Kindberg, T., \& Blair, G. (2011).
\textit{Distributed Systems: Concepts and Design} (5th ed.).
Addison-Wesley.
\bibitem{banks2009}
Banks, J., Carson, J. S., Nelson, B. L., \& Nicol, D. M. (2009).
\textit{Discrete-Event System Simulation} (5th ed.).
Pearson.
\bibitem{oracle2024java}
Oracle. (2024).
\textit{Java Platform, Standard Edition Documentation} (Version 17).
\url{https://docs.oracle.com/en/java/javase/17/}
\bibitem{goetz2006}
Goetz, B., Peierls, T., Bloch, J., Bowbeer, J., Holmes, D., \& Lea, D. (2006).
\textit{Java Concurrency in Practice}.
Addison-Wesley.
\bibitem{oracle2024javafx}
Oracle. (2024).
\textit{JavaFX Documentation} (Version 17).
\url{https://openjfx.io/javadoc/17/}
\bibitem{google2024gson}
Google. (2024).
\textit{Gson User Guide}.
\url{https://github.com/google/gson/blob/master/UserGuide.md}
\bibitem{law2000}
Law, A. M., \& Kelton, W. D. (2000).
\textit{Simulation Modeling and Analysis} (3rd ed.).
McGraw-Hill.
\end{thebibliography}
\vspace{1cm}
\noindent\textbf{Nota:} Este relatório foi elaborado com base na análise do código-fonte do projeto e nos resultados experimentais obtidos através de múltiplas execuções da simulação. Todos os valores estatísticos apresentados foram extraídos dos ficheiros de análise gerados pelo sistema (\texttt{analysis/LOW\_LOAD\_*.txt}, \texttt{analysis/MEDIUM\_LOAD\_*.txt}, \texttt{analysis/HIGH\_LOAD\_*.txt}).
\end{document}

View File

@@ -0,0 +1,6 @@
Execução,VeículosGerados,VeículosCompletados,TaxaConclusão,TempoMédioSistema,TempoMédioEspera,TempoMínimoSistema,TempoMáximoSistema
1,1836,348,18.95,75.91,72.28,37.96,151.82
2,1728,663,38.37,52.10,49.52,26.05,104.21
3,1747,539,30.85,116.39,112.54,58.19,232.78
4,1769,149,8.42,89.64,85.89,44.82,179.29
5,1827,1097,60.04,90.49,86.93,45.25,180.98
1 Execução VeículosGerados VeículosCompletados TaxaConclusão TempoMédioSistema TempoMédioEspera TempoMínimoSistema TempoMáximoSistema
2 1 1836 348 18.95 75.91 72.28 37.96 151.82
3 2 1728 663 38.37 52.10 49.52 26.05 104.21
4 3 1747 539 30.85 116.39 112.54 58.19 232.78
5 4 1769 149 8.42 89.64 85.89 44.82 179.29
6 5 1827 1097 60.04 90.49 86.93 45.25 180.98

View File

@@ -0,0 +1,215 @@
================================================================================
ANÁLISE ESTATÍSTICA MULTI-EXECUÇÃO
================================================================================
Configuração: simulation-high.properties
Número de Execuções: 5
Data da Análise: 2025-12-08 08:20:40
--------------------------------------------------------------------------------
MÉTRICAS GLOBAIS
--------------------------------------------------------------------------------
Veículos Gerados:
Média: 1781.40 Desvio Padrão: 48.09
Mediana: 1769.00 IC 95%: [1712.97, 1849.83]
Mín: 1728.00 Máx: 1836.00
Veículos Completados:
Média: 559.20 Desvio Padrão: 358.22
Mediana: 539.00 IC 95%: [49.44, 1068.96]
Mín: 149.00 Máx: 1097.00
Taxa de Conclusão (%):
Média: 31.33 Desvio Padrão: 19.70
Mediana: 30.85 IC 95%: [3.30, 59.36]
Mín: 8.42 Máx: 60.04
Tempo Médio no Sistema (segundos):
Média: 84.91 Desvio Padrão: 23.46
Mediana: 89.64 IC 95%: [51.52, 118.29]
Mín: 52.10 Máx: 116.39
Tempo Médio de Espera (segundos):
Média: 81.43 Desvio Padrão: 23.02
Mediana: 85.89 IC 95%: [48.68, 114.19]
Mín: 49.52 Máx: 112.54
--------------------------------------------------------------------------------
ANÁLISE POR TIPO DE VEÍCULO
--------------------------------------------------------------------------------
--- BIKE ---
Contagem de Veículos:
Média: 111.60 Desvio Padrão: 69.43
Mediana: 105.00 IC 95%: [12.80, 210.40]
Mín: 29.00 Máx: 215.00
Tempo Médio no Sistema (segundos): Sem dados
Tempo Médio de Espera (segundos):
Média: 78.89 Desvio Padrão: 20.87
Mediana: 89.97 IC 95%: [49.20, 108.59]
Mín: 49.27 Máx: 98.23
--- LIGHT ---
Contagem de Veículos:
Média: 333.80 Desvio Padrão: 221.25
Mediana: 332.00 IC 95%: [18.95, 648.65]
Mín: 90.00 Máx: 669.00
Tempo Médio no Sistema (segundos): Sem dados
Tempo Médio de Espera (segundos):
Média: 83.83 Desvio Padrão: 24.74
Mediana: 86.14 IC 95%: [48.63, 119.03]
Mín: 51.94 Máx: 120.26
--- HEAVY ---
Contagem de Veículos:
Média: 113.80 Desvio Padrão: 68.36
Mediana: 102.00 IC 95%: [16.53, 211.07]
Mín: 30.00 Máx: 213.00
Tempo Médio no Sistema (segundos): Sem dados
Tempo Médio de Espera (segundos):
Média: 76.79 Desvio Padrão: 21.46
Mediana: 81.20 IC 95%: [46.26, 107.33]
Mín: 43.10 Máx: 102.14
--------------------------------------------------------------------------------
ANÁLISE POR INTERSEÇÃO
--------------------------------------------------------------------------------
--- Cr1 ---
Tamanho Máximo da Fila:
Média: 0.20 Desvio Padrão: 0.45
Mediana: 0.00 IC 95%: [-0.44, 0.84]
Mín: 0.00 Máx: 1.00
Tamanho Médio da Fila:
Média: 0.20 Desvio Padrão: 0.45
Mediana: 0.00 IC 95%: [-0.44, 0.84]
Mín: 0.00 Máx: 1.00
Veículos Processados:
Média: 221.40 Desvio Padrão: 226.21
Mediana: 128.00 IC 95%: [-100.50, 543.30]
Mín: 61.00 Máx: 616.00
--- Cr2 ---
Tamanho Máximo da Fila:
Média: 3.60 Desvio Padrão: 5.90
Mediana: 2.00 IC 95%: [-4.79, 11.99]
Mín: 0.00 Máx: 14.00
Tamanho Médio da Fila:
Média: 3.60 Desvio Padrão: 5.90
Mediana: 2.00 IC 95%: [-4.79, 11.99]
Mín: 0.00 Máx: 14.00
Veículos Processados:
Média: 228.60 Desvio Padrão: 211.41
Mediana: 126.00 IC 95%: [-72.24, 529.44]
Mín: 93.00 Máx: 593.00
--- Cr3 ---
Tamanho Máximo da Fila:
Média: 1.20 Desvio Padrão: 2.68
Mediana: 0.00 IC 95%: [-2.62, 5.02]
Mín: 0.00 Máx: 6.00
Tamanho Médio da Fila:
Média: 1.20 Desvio Padrão: 2.68
Mediana: 0.00 IC 95%: [-2.62, 5.02]
Mín: 0.00 Máx: 6.00
Veículos Processados:
Média: 263.80 Desvio Padrão: 240.18
Mediana: 128.00 IC 95%: [-77.98, 605.58]
Mín: 57.00 Máx: 604.00
--- Cr4 ---
Tamanho Máximo da Fila:
Média: 0.60 Desvio Padrão: 0.89
Mediana: 0.00 IC 95%: [-0.67, 1.87]
Mín: 0.00 Máx: 2.00
Tamanho Médio da Fila:
Média: 0.60 Desvio Padrão: 0.89
Mediana: 0.00 IC 95%: [-0.67, 1.87]
Mín: 0.00 Máx: 2.00
Veículos Processados:
Média: 95.00 Desvio Padrão: 78.43
Mediana: 62.00 IC 95%: [-16.60, 206.60]
Mín: 43.00 Máx: 231.00
--- Cr5 ---
Tamanho Máximo da Fila:
Média: 2.80 Desvio Padrão: 3.63
Mediana: 1.00 IC 95%: [-2.37, 7.97]
Mín: 0.00 Máx: 9.00
Tamanho Médio da Fila:
Média: 2.80 Desvio Padrão: 3.63
Mediana: 1.00 IC 95%: [-2.37, 7.97]
Mín: 0.00 Máx: 9.00
Veículos Processados:
Média: 207.60 Desvio Padrão: 166.31
Mediana: 139.00 IC 95%: [-29.06, 444.26]
Mín: 76.00 Máx: 493.00
--- ExitNode ---
Tamanho Máximo da Fila: Sem dados
Tamanho Médio da Fila: Sem dados
Veículos Processados:
Média: 559.20 Desvio Padrão: 358.22
Mediana: 539.00 IC 95%: [49.44, 1068.96]
Mín: 149.00 Máx: 1097.00
--------------------------------------------------------------------------------
RESUMOS INDIVIDUAIS DAS EXECUÇÕES
--------------------------------------------------------------------------------
Execução #1 [simulation-high.properties]:
Gerados: 1836, Completados: 348 (19.0%)
Tempo Médio no Sistema: 75.91s
Tempo Médio de Espera: 72.28s
Execução #2 [simulation-high.properties]:
Gerados: 1728, Completados: 663 (38.4%)
Tempo Médio no Sistema: 52.10s
Tempo Médio de Espera: 49.52s
Execução #3 [simulation-high.properties]:
Gerados: 1747, Completados: 539 (30.9%)
Tempo Médio no Sistema: 116.39s
Tempo Médio de Espera: 112.54s
Execução #4 [simulation-high.properties]:
Gerados: 1769, Completados: 149 (8.4%)
Tempo Médio no Sistema: 89.64s
Tempo Médio de Espera: 85.89s
Execução #5 [simulation-high.properties]:
Gerados: 1827, Completados: 1097 (60.0%)
Tempo Médio no Sistema: 90.49s
Tempo Médio de Espera: 86.93s
================================================================================
FIM DO RELATÓRIO
================================================================================

View File

@@ -0,0 +1,5 @@
Execução,VeículosGerados,VeículosCompletados,TaxaConclusão,TempoMédioSistema,TempoMédioEspera,TempoMínimoSistema,TempoMáximoSistema
1,354,228,64.41,40.36,36.75,20.18,80.72
2,373,261,69.97,40.61,36.87,20.30,81.21
3,353,235,66.57,32.63,29.04,16.32,65.27
4,350,269,76.86,37.39,33.42,18.70,74.78
1 Execução VeículosGerados VeículosCompletados TaxaConclusão TempoMédioSistema TempoMédioEspera TempoMínimoSistema TempoMáximoSistema
2 1 354 228 64.41 40.36 36.75 20.18 80.72
3 2 373 261 69.97 40.61 36.87 20.30 81.21
4 3 353 235 66.57 32.63 29.04 16.32 65.27
5 4 350 269 76.86 37.39 33.42 18.70 74.78

View File

@@ -0,0 +1,204 @@
================================================================================
ANÁLISE ESTATÍSTICA MULTI-EXECUÇÃO
================================================================================
Configuração: simulation-low.properties
Número de Execuções: 4
Data da Análise: 2025-12-08 08:13:57
--------------------------------------------------------------------------------
MÉTRICAS GLOBAIS
--------------------------------------------------------------------------------
Veículos Gerados:
Média: 357.50 Desvio Padrão: 10.47
Mediana: 353.50 IC 95%: [340.84, 374.16]
Mín: 350.00 Máx: 373.00
Veículos Completados:
Média: 248.25 Desvio Padrão: 19.82
Mediana: 248.00 IC 95%: [216.71, 279.79]
Mín: 228.00 Máx: 269.00
Taxa de Conclusão (%):
Média: 69.45 Desvio Padrão: 5.44
Mediana: 68.27 IC 95%: [60.79, 78.11]
Mín: 64.41 Máx: 76.86
Tempo Médio no Sistema (segundos):
Média: 37.75 Desvio Padrão: 3.71
Mediana: 38.87 IC 95%: [31.85, 43.65]
Mín: 32.63 Máx: 40.61
Tempo Médio de Espera (segundos):
Média: 34.02 Desvio Padrão: 3.68
Mediana: 35.08 IC 95%: [28.16, 39.88]
Mín: 29.04 Máx: 36.87
--------------------------------------------------------------------------------
ANÁLISE POR TIPO DE VEÍCULO
--------------------------------------------------------------------------------
--- BIKE ---
Contagem de Veículos:
Média: 52.25 Desvio Padrão: 8.58
Mediana: 54.50 IC 95%: [38.60, 65.90]
Mín: 40.00 Máx: 60.00
Tempo Médio no Sistema (segundos): Sem dados
Tempo Médio de Espera (segundos):
Média: 43.39 Desvio Padrão: 19.65
Mediana: 39.45 IC 95%: [12.12, 74.65]
Mín: 25.04 Máx: 69.63
--- LIGHT ---
Contagem de Veículos:
Média: 151.25 Desvio Padrão: 8.34
Mediana: 152.50 IC 95%: [137.98, 164.52]
Mín: 141.00 Máx: 159.00
Tempo Médio no Sistema (segundos): Sem dados
Tempo Médio de Espera (segundos):
Média: 28.92 Desvio Padrão: 5.34
Mediana: 28.76 IC 95%: [20.43, 37.42]
Mín: 23.35 Máx: 34.82
--- HEAVY ---
Contagem de Veículos:
Média: 44.75 Desvio Padrão: 6.29
Mediana: 44.00 IC 95%: [34.74, 54.76]
Mín: 39.00 Máx: 52.00
Tempo Médio no Sistema (segundos): Sem dados
Tempo Médio de Espera (segundos):
Média: 43.02 Desvio Padrão: 13.73
Mediana: 47.91 IC 95%: [21.18, 64.86]
Mín: 22.83 Máx: 53.43
--------------------------------------------------------------------------------
ANÁLISE POR INTERSEÇÃO
--------------------------------------------------------------------------------
--- Cr1 ---
Tamanho Máximo da Fila:
Média: 0.25 Desvio Padrão: 0.50
Mediana: 0.00 IC 95%: [-0.55, 1.05]
Mín: 0.00 Máx: 1.00
Tamanho Médio da Fila:
Média: 0.25 Desvio Padrão: 0.50
Mediana: 0.00 IC 95%: [-0.55, 1.05]
Mín: 0.00 Máx: 1.00
Veículos Processados:
Média: 105.50 Desvio Padrão: 10.66
Mediana: 103.50 IC 95%: [88.54, 122.46]
Mín: 95.00 Máx: 120.00
--- Cr2 ---
Tamanho Máximo da Fila:
Média: 1.75 Desvio Padrão: 2.87
Mediana: 0.50 IC 95%: [-2.82, 6.32]
Mín: 0.00 Máx: 6.00
Tamanho Médio da Fila:
Média: 1.75 Desvio Padrão: 2.87
Mediana: 0.50 IC 95%: [-2.82, 6.32]
Mín: 0.00 Máx: 6.00
Veículos Processados:
Média: 119.00 Desvio Padrão: 11.17
Mediana: 122.50 IC 95%: [101.24, 136.76]
Mín: 103.00 Máx: 128.00
--- Cr3 ---
Tamanho Máximo da Fila: Sem dados
Tamanho Médio da Fila: Sem dados
Veículos Processados:
Média: 114.75 Desvio Padrão: 15.88
Mediana: 119.00 IC 95%: [89.48, 140.02]
Mín: 93.00 Máx: 128.00
--- Cr4 ---
Tamanho Máximo da Fila:
Média: 1.25 Desvio Padrão: 0.50
Mediana: 1.00 IC 95%: [0.45, 2.05]
Mín: 1.00 Máx: 2.00
Tamanho Médio da Fila:
Média: 1.25 Desvio Padrão: 0.50
Mediana: 1.00 IC 95%: [0.45, 2.05]
Mín: 1.00 Máx: 2.00
Veículos Processados:
Média: 63.00 Desvio Padrão: 11.75
Mediana: 62.00 IC 95%: [44.31, 81.69]
Mín: 50.00 Máx: 78.00
--- Cr5 ---
Tamanho Máximo da Fila:
Média: 4.50 Desvio Padrão: 2.89
Mediana: 4.50 IC 95%: [-0.09, 9.09]
Mín: 1.00 Máx: 8.00
Tamanho Médio da Fila:
Média: 4.50 Desvio Padrão: 2.89
Mediana: 4.50 IC 95%: [-0.09, 9.09]
Mín: 1.00 Máx: 8.00
Veículos Processados:
Média: 123.00 Desvio Padrão: 24.18
Mediana: 116.50 IC 95%: [84.53, 161.47]
Mín: 103.00 Máx: 156.00
--- ExitNode ---
Tamanho Máximo da Fila: Sem dados
Tamanho Médio da Fila: Sem dados
Veículos Processados:
Média: 248.25 Desvio Padrão: 19.82
Mediana: 248.00 IC 95%: [216.71, 279.79]
Mín: 228.00 Máx: 269.00
--------------------------------------------------------------------------------
RESUMOS INDIVIDUAIS DAS EXECUÇÕES
--------------------------------------------------------------------------------
Execução #1 [simulation-low.properties]:
Gerados: 354, Completados: 228 (64.4%)
Tempo Médio no Sistema: 40.36s
Tempo Médio de Espera: 36.75s
Execução #2 [simulation-low.properties]:
Gerados: 373, Completados: 261 (70.0%)
Tempo Médio no Sistema: 40.61s
Tempo Médio de Espera: 36.87s
Execução #3 [simulation-low.properties]:
Gerados: 353, Completados: 235 (66.6%)
Tempo Médio no Sistema: 32.63s
Tempo Médio de Espera: 29.04s
Execução #4 [simulation-low.properties]:
Gerados: 350, Completados: 269 (76.9%)
Tempo Médio no Sistema: 37.39s
Tempo Médio de Espera: 33.42s
================================================================================
FIM DO RELATÓRIO
================================================================================

View File

@@ -0,0 +1,6 @@
Execução,VeículosGerados,VeículosCompletados,TaxaConclusão,TempoMédioSistema,TempoMédioEspera,TempoMínimoSistema,TempoMáximoSistema
1,368,329,89.40,78.34,74.19,39.17,156.67
2,368,218,59.24,60.44,56.64,30.22,120.89
3,349,235,67.34,53.51,49.44,26.76,107.03
4,332,243,73.19,69.63,65.50,34.82,139.27
5,322,221,68.63,47.52,43.77,23.76,95.05
1 Execução VeículosGerados VeículosCompletados TaxaConclusão TempoMédioSistema TempoMédioEspera TempoMínimoSistema TempoMáximoSistema
2 1 368 329 89.40 78.34 74.19 39.17 156.67
3 2 368 218 59.24 60.44 56.64 30.22 120.89
4 3 349 235 67.34 53.51 49.44 26.76 107.03
5 4 332 243 73.19 69.63 65.50 34.82 139.27
6 5 322 221 68.63 47.52 43.77 23.76 95.05

View File

@@ -0,0 +1,215 @@
================================================================================
ANÁLISE ESTATÍSTICA MULTI-EXECUÇÃO
================================================================================
Configuração: simulation-low.properties
Número de Execuções: 5
Data da Análise: 2025-12-08 08:19:33
--------------------------------------------------------------------------------
MÉTRICAS GLOBAIS
--------------------------------------------------------------------------------
Veículos Gerados:
Média: 347.80 Desvio Padrão: 20.81
Mediana: 349.00 IC 95%: [318.18, 377.42]
Mín: 322.00 Máx: 368.00
Veículos Completados:
Média: 249.20 Desvio Padrão: 45.76
Mediana: 235.00 IC 95%: [184.08, 314.32]
Mín: 218.00 Máx: 329.00
Taxa de Conclusão (%):
Média: 71.56 Desvio Padrão: 11.17
Mediana: 68.63 IC 95%: [55.66, 87.46]
Mín: 59.24 Máx: 89.40
Tempo Médio no Sistema (segundos):
Média: 61.89 Desvio Padrão: 12.34
Mediana: 60.44 IC 95%: [44.33, 79.45]
Mín: 47.52 Máx: 78.34
Tempo Médio de Espera (segundos):
Média: 57.91 Desvio Padrão: 12.21
Mediana: 56.64 IC 95%: [40.54, 75.28]
Mín: 43.77 Máx: 74.19
--------------------------------------------------------------------------------
ANÁLISE POR TIPO DE VEÍCULO
--------------------------------------------------------------------------------
--- BIKE ---
Contagem de Veículos:
Média: 48.20 Desvio Padrão: 12.38
Mediana: 47.00 IC 95%: [30.59, 65.81]
Mín: 36.00 Máx: 68.00
Tempo Médio no Sistema (segundos): Sem dados
Tempo Médio de Espera (segundos):
Média: 51.22 Desvio Padrão: 16.62
Mediana: 46.02 IC 95%: [27.56, 74.87]
Mín: 40.06 Máx: 80.31
--- LIGHT ---
Contagem de Veículos:
Média: 151.00 Desvio Padrão: 22.64
Mediana: 146.00 IC 95%: [118.78, 183.22]
Mín: 133.00 Máx: 189.00
Tempo Médio no Sistema (segundos): Sem dados
Tempo Médio de Espera (segundos):
Média: 58.33 Desvio Padrão: 11.58
Mediana: 53.58 IC 95%: [41.85, 74.80]
Mín: 45.31 Máx: 74.17
--- HEAVY ---
Contagem de Veículos:
Média: 50.00 Desvio Padrão: 13.77
Mediana: 47.00 IC 95%: [30.41, 69.59]
Mín: 35.00 Máx: 72.00
Tempo Médio no Sistema (segundos): Sem dados
Tempo Médio de Espera (segundos):
Média: 60.73 Desvio Padrão: 34.92
Mediana: 44.79 IC 95%: [11.04, 110.42]
Mín: 40.26 Máx: 122.51
--------------------------------------------------------------------------------
ANÁLISE POR INTERSEÇÃO
--------------------------------------------------------------------------------
--- Cr1 ---
Tamanho Máximo da Fila:
Média: 5.00 Desvio Padrão: 4.47
Mediana: 4.00 IC 95%: [-1.36, 11.36]
Mín: 0.00 Máx: 12.00
Tamanho Médio da Fila:
Média: 5.00 Desvio Padrão: 4.47
Mediana: 4.00 IC 95%: [-1.36, 11.36]
Mín: 0.00 Máx: 12.00
Veículos Processados:
Média: 87.00 Desvio Padrão: 29.01
Mediana: 93.00 IC 95%: [45.72, 128.28]
Mín: 56.00 Máx: 123.00
--- Cr2 ---
Tamanho Máximo da Fila:
Média: 0.20 Desvio Padrão: 0.45
Mediana: 0.00 IC 95%: [-0.44, 0.84]
Mín: 0.00 Máx: 1.00
Tamanho Médio da Fila:
Média: 0.20 Desvio Padrão: 0.45
Mediana: 0.00 IC 95%: [-0.44, 0.84]
Mín: 0.00 Máx: 1.00
Veículos Processados:
Média: 95.20 Desvio Padrão: 24.86
Mediana: 100.00 IC 95%: [59.82, 130.58]
Mín: 61.00 Máx: 125.00
--- Cr3 ---
Tamanho Máximo da Fila:
Média: 0.20 Desvio Padrão: 0.45
Mediana: 0.00 IC 95%: [-0.44, 0.84]
Mín: 0.00 Máx: 1.00
Tamanho Médio da Fila:
Média: 0.20 Desvio Padrão: 0.45
Mediana: 0.00 IC 95%: [-0.44, 0.84]
Mín: 0.00 Máx: 1.00
Veículos Processados:
Média: 91.40 Desvio Padrão: 28.68
Mediana: 103.00 IC 95%: [50.58, 132.22]
Mín: 56.00 Máx: 126.00
--- Cr4 ---
Tamanho Máximo da Fila:
Média: 0.80 Desvio Padrão: 0.84
Mediana: 1.00 IC 95%: [-0.39, 1.99]
Mín: 0.00 Máx: 2.00
Tamanho Médio da Fila:
Média: 0.80 Desvio Padrão: 0.84
Mediana: 1.00 IC 95%: [-0.39, 1.99]
Mín: 0.00 Máx: 2.00
Veículos Processados:
Média: 63.00 Desvio Padrão: 21.11
Mediana: 62.00 IC 95%: [32.96, 93.04]
Mín: 38.00 Máx: 87.00
--- Cr5 ---
Tamanho Máximo da Fila:
Média: 2.20 Desvio Padrão: 2.59
Mediana: 1.00 IC 95%: [-1.48, 5.88]
Mín: 0.00 Máx: 5.00
Tamanho Médio da Fila:
Média: 2.20 Desvio Padrão: 2.59
Mediana: 1.00 IC 95%: [-1.48, 5.88]
Mín: 0.00 Máx: 5.00
Veículos Processados:
Média: 126.40 Desvio Padrão: 45.39
Mediana: 111.00 IC 95%: [61.81, 190.99]
Mín: 86.00 Máx: 203.00
--- ExitNode ---
Tamanho Máximo da Fila: Sem dados
Tamanho Médio da Fila: Sem dados
Veículos Processados:
Média: 249.20 Desvio Padrão: 45.76
Mediana: 235.00 IC 95%: [184.08, 314.32]
Mín: 218.00 Máx: 329.00
--------------------------------------------------------------------------------
RESUMOS INDIVIDUAIS DAS EXECUÇÕES
--------------------------------------------------------------------------------
Execução #1 [simulation-low.properties]:
Gerados: 368, Completados: 329 (89.4%)
Tempo Médio no Sistema: 78.34s
Tempo Médio de Espera: 74.19s
Execução #2 [simulation-low.properties]:
Gerados: 368, Completados: 218 (59.2%)
Tempo Médio no Sistema: 60.44s
Tempo Médio de Espera: 56.64s
Execução #3 [simulation-low.properties]:
Gerados: 349, Completados: 235 (67.3%)
Tempo Médio no Sistema: 53.51s
Tempo Médio de Espera: 49.44s
Execução #4 [simulation-low.properties]:
Gerados: 332, Completados: 243 (73.2%)
Tempo Médio no Sistema: 69.63s
Tempo Médio de Espera: 65.50s
Execução #5 [simulation-low.properties]:
Gerados: 322, Completados: 221 (68.6%)
Tempo Médio no Sistema: 47.52s
Tempo Médio de Espera: 43.77s
================================================================================
FIM DO RELATÓRIO
================================================================================

View File

@@ -0,0 +1,6 @@
Execução,VeículosGerados,VeículosCompletados,TaxaConclusão,TempoMédioSistema,TempoMédioEspera,TempoMínimoSistema,TempoMáximoSistema
1,891,202,22.67,69.75,66.09,34.87,139.50
2,871,340,39.04,68.73,64.73,34.37,137.46
3,953,541,56.77,68.64,65.24,34.32,137.28
4,888,501,56.42,60.85,57.48,30.42,121.69
5,869,387,44.53,58.29,55.37,29.15,116.58
1 Execução VeículosGerados VeículosCompletados TaxaConclusão TempoMédioSistema TempoMédioEspera TempoMínimoSistema TempoMáximoSistema
2 1 891 202 22.67 69.75 66.09 34.87 139.50
3 2 871 340 39.04 68.73 64.73 34.37 137.46
4 3 953 541 56.77 68.64 65.24 34.32 137.28
5 4 888 501 56.42 60.85 57.48 30.42 121.69
6 5 869 387 44.53 58.29 55.37 29.15 116.58

View File

@@ -0,0 +1,209 @@
================================================================================
ANÁLISE ESTATÍSTICA MULTI-EXECUÇÃO
================================================================================
Configuração: simulation-medium.properties
Número de Execuções: 5
Data da Análise: 2025-12-08 08:20:05
--------------------------------------------------------------------------------
MÉTRICAS GLOBAIS
--------------------------------------------------------------------------------
Veículos Gerados:
Média: 894.40 Desvio Padrão: 34.20
Mediana: 888.00 IC 95%: [845.73, 943.07]
Mín: 869.00 Máx: 953.00
Veículos Completados:
Média: 394.20 Desvio Padrão: 134.99
Mediana: 387.00 IC 95%: [202.11, 586.29]
Mín: 202.00 Máx: 541.00
Taxa de Conclusão (%):
Média: 43.89 Desvio Padrão: 14.12
Mediana: 44.53 IC 95%: [23.80, 63.97]
Mín: 22.67 Máx: 56.77
Tempo Médio no Sistema (segundos):
Média: 65.25 Desvio Padrão: 5.28
Mediana: 68.64 IC 95%: [57.73, 72.77]
Mín: 58.29 Máx: 69.75
Tempo Médio de Espera (segundos):
Média: 61.78 Desvio Padrão: 4.97
Mediana: 64.73 IC 95%: [54.71, 68.86]
Mín: 55.37 Máx: 66.09
--------------------------------------------------------------------------------
ANÁLISE POR TIPO DE VEÍCULO
--------------------------------------------------------------------------------
--- BIKE ---
Contagem de Veículos:
Média: 83.60 Desvio Padrão: 28.80
Mediana: 88.00 IC 95%: [42.62, 124.58]
Mín: 42.00 Máx: 112.00
Tempo Médio no Sistema (segundos): Sem dados
Tempo Médio de Espera (segundos):
Média: 64.62 Desvio Padrão: 9.80
Mediana: 65.07 IC 95%: [50.67, 78.57]
Mín: 53.82 Máx: 77.73
--- LIGHT ---
Contagem de Veículos:
Média: 234.80 Desvio Padrão: 86.82
Mediana: 221.00 IC 95%: [111.26, 358.34]
Mín: 119.00 Máx: 328.00
Tempo Médio no Sistema (segundos): Sem dados
Tempo Médio de Espera (segundos):
Média: 60.49 Desvio Padrão: 4.15
Mediana: 61.41 IC 95%: [54.58, 66.39]
Mín: 53.78 Máx: 65.19
--- HEAVY ---
Contagem de Veículos:
Média: 75.80 Desvio Padrão: 21.70
Mediana: 78.00 IC 95%: [44.93, 106.67]
Mín: 41.00 Máx: 101.00
Tempo Médio no Sistema (segundos): Sem dados
Tempo Médio de Espera (segundos):
Média: 62.90 Desvio Padrão: 13.27
Mediana: 63.80 IC 95%: [44.01, 81.79]
Mín: 42.19 Máx: 78.56
--------------------------------------------------------------------------------
ANÁLISE POR INTERSEÇÃO
--------------------------------------------------------------------------------
--- Cr1 ---
Tamanho Máximo da Fila:
Média: 2.00 Desvio Padrão: 2.55
Mediana: 1.00 IC 95%: [-1.63, 5.63]
Mín: 0.00 Máx: 6.00
Tamanho Médio da Fila:
Média: 2.00 Desvio Padrão: 2.55
Mediana: 1.00 IC 95%: [-1.63, 5.63]
Mín: 0.00 Máx: 6.00
Veículos Processados:
Média: 106.20 Desvio Padrão: 62.26
Mediana: 72.00 IC 95%: [17.60, 194.80]
Mín: 56.00 Máx: 208.00
--- Cr2 ---
Tamanho Máximo da Fila:
Média: 1.40 Desvio Padrão: 3.13
Mediana: 0.00 IC 95%: [-3.05, 5.85]
Mín: 0.00 Máx: 7.00
Tamanho Médio da Fila:
Média: 1.40 Desvio Padrão: 3.13
Mediana: 0.00 IC 95%: [-3.05, 5.85]
Mín: 0.00 Máx: 7.00
Veículos Processados:
Média: 123.60 Desvio Padrão: 90.00
Mediana: 102.00 IC 95%: [-4.47, 251.67]
Mín: 49.00 Máx: 275.00
--- Cr3 ---
Tamanho Máximo da Fila:
Média: 0.20 Desvio Padrão: 0.45
Mediana: 0.00 IC 95%: [-0.44, 0.84]
Mín: 0.00 Máx: 1.00
Tamanho Médio da Fila:
Média: 0.20 Desvio Padrão: 0.45
Mediana: 0.00 IC 95%: [-0.44, 0.84]
Mín: 0.00 Máx: 1.00
Veículos Processados:
Média: 102.60 Desvio Padrão: 50.09
Mediana: 104.00 IC 95%: [31.32, 173.88]
Mín: 55.00 Máx: 181.00
--- Cr4 ---
Tamanho Máximo da Fila: Sem dados
Tamanho Médio da Fila: Sem dados
Veículos Processados:
Média: 68.80 Desvio Padrão: 27.10
Mediana: 60.00 IC 95%: [30.24, 107.36]
Mín: 47.00 Máx: 113.00
--- Cr5 ---
Tamanho Máximo da Fila:
Média: 1.20 Desvio Padrão: 2.17
Mediana: 0.00 IC 95%: [-1.89, 4.29]
Mín: 0.00 Máx: 5.00
Tamanho Médio da Fila:
Média: 1.20 Desvio Padrão: 2.17
Mediana: 0.00 IC 95%: [-1.89, 4.29]
Mín: 0.00 Máx: 5.00
Veículos Processados:
Média: 125.80 Desvio Padrão: 51.69
Mediana: 96.00 IC 95%: [52.24, 199.36]
Mín: 84.00 Máx: 193.00
--- ExitNode ---
Tamanho Máximo da Fila: Sem dados
Tamanho Médio da Fila: Sem dados
Veículos Processados:
Média: 394.20 Desvio Padrão: 134.99
Mediana: 387.00 IC 95%: [202.11, 586.29]
Mín: 202.00 Máx: 541.00
--------------------------------------------------------------------------------
RESUMOS INDIVIDUAIS DAS EXECUÇÕES
--------------------------------------------------------------------------------
Execução #1 [simulation-medium.properties]:
Gerados: 891, Completados: 202 (22.7%)
Tempo Médio no Sistema: 69.75s
Tempo Médio de Espera: 66.09s
Execução #2 [simulation-medium.properties]:
Gerados: 871, Completados: 340 (39.0%)
Tempo Médio no Sistema: 68.73s
Tempo Médio de Espera: 64.73s
Execução #3 [simulation-medium.properties]:
Gerados: 953, Completados: 541 (56.8%)
Tempo Médio no Sistema: 68.64s
Tempo Médio de Espera: 65.24s
Execução #4 [simulation-medium.properties]:
Gerados: 888, Completados: 501 (56.4%)
Tempo Médio no Sistema: 60.85s
Tempo Médio de Espera: 57.48s
Execução #5 [simulation-medium.properties]:
Gerados: 869, Completados: 387 (44.5%)
Tempo Médio no Sistema: 58.29s
Tempo Médio de Espera: 55.37s
================================================================================
FIM DO RELATÓRIO
================================================================================

169
main/graphing.py Normal file
View File

@@ -0,0 +1,169 @@
import pandas as pd
import matplotlib.pyplot as plt
import glob
import os
# Find CSV files using glob
def load_latest_csv(pattern):
"""Load the most recent CSV file matching the pattern"""
files = glob.glob(pattern)
if not files:
print(f"Warning: No files found matching '{pattern}'")
return None
# Sort by modification time, get the latest
latest_file = max(files, key=os.path.getmtime)
print(f"Loading: {latest_file}")
return pd.read_csv(latest_file)
# Carregar dados
print("Looking for analysis files...")
low = load_latest_csv('analysis/LOW_LOAD_*.csv')
medium = load_latest_csv('analysis/MEDIUM_LOAD_*.csv')
high = load_latest_csv('analysis/HIGH_LOAD_*.csv')
# Check if we have all data
if low is None or medium is None or high is None:
print("\nError: Missing analysis files!")
print("Please run the batch analysis first:")
exit(1)
# Print available columns for debugging
print("\nAvailable columns in LOW_LOAD CSV:")
print(low.columns.tolist())
# Create output directory for graphs
os.makedirs('graphs', exist_ok=True)
# 1. Gráfico: Dwelling Time vs Load
plt.figure(figsize=(10, 6))
dwelling_times = [
low['TempoMédioSistema'].mean(),
medium['TempoMédioSistema'].mean(),
high['TempoMédioSistema'].mean()
]
plt.bar(['Baixa', 'Média', 'Alta'], dwelling_times, color=['green', 'orange', 'red'])
plt.ylabel('Tempo Médio no Sistema (s)')
plt.title('Desempenho do Sistema vs Carga')
plt.xlabel('Cenário de Carga')
plt.grid(axis='y', alpha=0.3)
for i, v in enumerate(dwelling_times):
plt.text(i, v + 1, f'{v:.2f}s', ha='center', va='bottom')
plt.savefig('graphs/dwelling_time_comparison.png', dpi=300, bbox_inches='tight')
print("\nGraph saved: graphs/dwelling_time_comparison.png")
plt.close()
# 2. Gráfico: Completion Rate vs Load
plt.figure(figsize=(10, 6))
completion_rates = [
low['TaxaConclusão'].mean(),
medium['TaxaConclusão'].mean(),
high['TaxaConclusão'].mean()
]
plt.bar(['Baixa', 'Média', 'Alta'], completion_rates, color=['green', 'orange', 'red'])
plt.ylabel('Taxa de Conclusão (%)')
plt.title('Taxa de Conclusão de Veículos vs Carga')
plt.xlabel('Cenário de Carga')
plt.grid(axis='y', alpha=0.3)
plt.ylim(0, 100)
for i, v in enumerate(completion_rates):
plt.text(i, v + 2, f'{v:.1f}%', ha='center', va='bottom')
plt.savefig('graphs/completion_rate_comparison.png', dpi=300, bbox_inches='tight')
print("Graph saved: graphs/completion_rate_comparison.png")
plt.close()
# 3. Gráfico: Waiting Time vs Load
plt.figure(figsize=(10, 6))
waiting_times = [
low['TempoMédioEspera'].mean(),
medium['TempoMédioEspera'].mean(),
high['TempoMédioEspera'].mean()
]
plt.bar(['Baixa', 'Média', 'Alta'], waiting_times, color=['green', 'orange', 'red'])
plt.ylabel('Tempo Médio de Espera (s)')
plt.title('Tempo Médio de Espera vs Carga')
plt.xlabel('Cenário de Carga')
plt.grid(axis='y', alpha=0.3)
for i, v in enumerate(waiting_times):
plt.text(i, v + 1, f'{v:.2f}s', ha='center', va='bottom')
plt.savefig('graphs/waiting_time_comparison.png', dpi=300, bbox_inches='tight')
print("Graph saved: graphs/waiting_time_comparison.png")
plt.close()
# 4. Gráfico: Summary Statistics
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(14, 10))
loads = ['Baixa', 'Média', 'Alta']
# Vehicles generated
ax1.bar(loads, [low['VeículosGerados'].mean(), medium['VeículosGerados'].mean(), high['VeículosGerados'].mean()], color=['green', 'orange', 'red'])
ax1.set_title('Veículos Gerados')
ax1.set_ylabel('Quantidade')
ax1.grid(axis='y', alpha=0.3)
# Vehicles completed
ax2.bar(loads, [low['VeículosCompletados'].mean(), medium['VeículosCompletados'].mean(), high['VeículosCompletados'].mean()], color=['green', 'orange', 'red'])
ax2.set_title('Veículos Concluídos')
ax2.set_ylabel('Quantidade')
ax2.grid(axis='y', alpha=0.3)
# Min/Max dwelling time
x = range(3)
width = 0.35
ax3.bar([i - width/2 for i in x], [low['TempoMínimoSistema'].mean(), medium['TempoMínimoSistema'].mean(), high['TempoMínimoSistema'].mean()], width, label='Mín', color='lightblue')
ax3.bar([i + width/2 for i in x], [low['TempoMáximoSistema'].mean(), medium['TempoMáximoSistema'].mean(), high['TempoMáximoSistema'].mean()], width, label='Máx', color='darkblue')
ax3.set_title('Tempo no Sistema Mín/Máx')
ax3.set_ylabel('Tempo (s)')
ax3.set_xticks(x)
ax3.set_xticklabels(loads)
ax3.legend()
ax3.grid(axis='y', alpha=0.3)
# Performance summary
metrics = ['Tempo no\nSistema', 'Tempo de\nEspera', 'Taxa de\nConclusão']
low_vals = [low['TempoMédioSistema'].mean(), low['TempoMédioEspera'].mean(), low['TaxaConclusão'].mean()]
med_vals = [medium['TempoMédioSistema'].mean(), medium['TempoMédioEspera'].mean(), medium['TaxaConclusão'].mean()]
high_vals = [high['TempoMédioSistema'].mean(), high['TempoMédioEspera'].mean(), high['TaxaConclusão'].mean()]
x = range(len(metrics))
width = 0.25
ax4.bar([i - width for i in x], low_vals, width, label='Baixa', color='green')
ax4.bar(x, med_vals, width, label='Média', color='orange')
ax4.bar([i + width for i in x], high_vals, width, label='Alta', color='red')
ax4.set_title('Resumo de Desempenho')
ax4.set_xticks(x)
ax4.set_xticklabels(metrics)
ax4.legend()
ax4.grid(axis='y', alpha=0.3)
plt.tight_layout()
plt.savefig('graphs/summary_statistics.png', dpi=300, bbox_inches='tight')
print("Graph saved: graphs/summary_statistics.png")
plt.close()
# Print summary statistics
print("\n" + "="*60)
print("SUMMARY STATISTICS")
print("="*60)
print(f"\nLOW LOAD:")
print(f" Avg Dwelling Time: {low['TempoMédioSistema'].mean():.2f}s")
print(f" Avg Waiting Time: {low['TempoMédioEspera'].mean():.2f}s")
print(f" Completion Rate: {low['TaxaConclusão'].mean():.1f}%")
print(f" Vehicles Generated: {low['VeículosGerados'].mean():.0f}")
print(f" Vehicles Completed: {low['VeículosCompletados'].mean():.0f}")
print(f"\nMEDIUM LOAD:")
print(f" Avg Dwelling Time: {medium['TempoMédioSistema'].mean():.2f}s")
print(f" Avg Waiting Time: {medium['TempoMédioEspera'].mean():.2f}s")
print(f" Completion Rate: {medium['TaxaConclusão'].mean():.1f}%")
print(f" Vehicles Generated: {medium['VeículosGerados'].mean():.0f}")
print(f" Vehicles Completed: {medium['VeículosCompletados'].mean():.0f}")
print(f"\nHIGH LOAD:")
print(f" Avg Dwelling Time: {high['TempoMédioSistema'].mean():.2f}s")
print(f" Avg Waiting Time: {high['TempoMédioEspera'].mean():.2f}s")
print(f" Completion Rate: {high['TaxaConclusão'].mean():.1f}%")
print(f" Vehicles Generated: {high['VeículosGerados'].mean():.0f}")
print(f" Vehicles Completed: {high['VeículosCompletados'].mean():.0f}")
print("\n" + "="*60)
print("All graphs saved in 'graphs/' directory!")
print("="*60)

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

View File

@@ -27,22 +27,24 @@ import sd.protocol.MessageProtocol;
import sd.protocol.SocketConnection;
/**
* Destino final de todos os veículos da simulação (nó de saída S).
*
* <p>Opera como sumidouro da rede:
* <ol>
* <li>Recebe veículos que completaram a viagem
* <li>Regista estatísticas finais (tempo total, espera, travessia)
* <li>Envia métricas ao dashboard em tempo real
* </ol>
*
* <p>Participa no DES rastreando eventos, mas opera principalmente
* de forma reativa, aguardando chegadas via socket.
* Ponto terminal da malha de simulação (Sink Node).
* <p>
* Este processo atua como o sumidouro da rede de filas. A sua função primária é
* a <b>coleta de telemetria final</b>. Diferente das interseções, não encaminha veículos;
* em vez disso, retira-os do sistema, calcula as métricas de latência "end-to-end"
* (tempo no sistema, tempo de espera acumulado) e reporta ao Dashboard.
* <p>
* <b>Arquitetura de Concorrência:</b>
* Utiliza um {@link ServerSocket} multithreaded para aceitar conexões simultâneas de
* qualquer interseção de fronteira (Cr1, Cr5, etc.) que envie veículos para fora da malha.
*/
public class ExitNodeProcess {
// --- Configuration and Networking ---
private final SimulationConfig config;
private ServerSocket serverSocket;
/** Pool de threads elástica para tratamento de conexões de entrada. */
private final ExecutorService connectionHandlerPool;
// DES components
@@ -51,37 +53,37 @@ public class ExitNodeProcess {
private final EventLogger eventLogger;
private Thread eventProcessorThread;
/** Flag de controlo (volatile para visibilidade entre threads) */
/** Flag de controlo (volatile para visibilidade entre threads de I/O e lógica). */
private volatile boolean running;
/** Instante de início da simulação (milissegundos) */
/** Instante de início da simulação (milissegundos) sincronizado com o Coordenador. */
private long simulationStartMillis;
/** Contador de veículos que completaram a rota */
/** Contador atómico (via synchronized) de throughput total. */
private int totalVehiclesReceived;
/** Tempo acumulado no sistema de todos os veículos */
/** Tempo acumulado no sistema (System Time) de todos os veículos. */
private double totalSystemTime;
/** Tempo acumulado em espera de todos os veículos */
/** Tempo acumulado em espera (Waiting Time) de todos os veículos. */
private double totalWaitingTime;
/** Tempo acumulado em travessia de todos os veículos */
/** Tempo acumulado em travessia (Service Time) de todos os veículos. */
private double totalCrossingTime;
/** Contagem de veículos por tipo */
/** Agregação por categoria de veículo. */
private final Map<VehicleType, Integer> vehicleTypeCount;
/** Tempo de espera acumulado por tipo de veículo */
/** Latência acumulada por categoria. */
private final Map<VehicleType, Double> vehicleTypeWaitTime;
/** Cliente socket para envio de estatísticas ao dashboard */
/** Cliente TCP persistente para push de métricas ao Dashboard. */
private SocketClient dashboardClient;
/**
* Ponto de entrada do processo.
*
* @param args args[0] (opcional) = caminho do ficheiro de configuração
* Bootstrap do processo ExitNode.
* Carrega configuração, inicializa subsistemas e entra no loop de serviço.
* * @param args Argumentos de CLI (caminho do config).
*/
public static void main(String[] args) {
System.out.println("=".repeat(60));
@@ -117,13 +119,9 @@ public class ExitNodeProcess {
}
/**
* Configura o Nó de Saída.
*
* Inicializamos os nossos contadores, preparamos a pool de threads para tratar
* das ligações de veículos recebidas,
* e configuramos os componentes DES para rastreio de eventos.
*
* @param config A configuração da simulação.
* Instancia o nó de saída.
* Prepara os acumuladores estatísticos e a infraestrutura de logging distribuído.
* * @param config A configuração global da simulação.
*/
public ExitNodeProcess(SimulationConfig config) {
this.config = config;
@@ -157,9 +155,8 @@ public class ExitNodeProcess {
}
/**
* Tenta estabelecer uma ligação ao dashboard.
* Se for bem-sucedido, poderemos enviar estatísticas em tempo real. Se não,
* apenas registamos localmente.
* Estabelece o canal de controlo (Control Plane) com o Dashboard.
* Essencial para a visualização em tempo real das métricas de saída.
*/
public void initialize() {
System.out.println("Connecting to dashboard...");
@@ -179,10 +176,9 @@ public class ExitNodeProcess {
}
/**
* Starts the DES event processing thread.
* Currently, ExitNode is primarily reactive (receives vehicles via network),
* but maintains event queue for potential scheduled events and history
* tracking.
* Inicia a thread de processamento de eventos DES.
* Embora o ExitNode seja primariamente reativo (Network-driven), o motor DES
* é mantido para consistência de relógio e agendamento de fim de simulação.
*/
private void startEventProcessor() {
eventProcessorThread = new Thread(() -> {
@@ -218,8 +214,8 @@ public class ExitNodeProcess {
}
/**
* Processes a discrete event based on its type.
* Currently supports VEHICLE_EXIT and SIMULATION_END events.
* Dispatcher de eventos discretos.
* Trata eventos de fim de simulação. Chegadas de veículos são tratadas via Socket.
*/
private void processEvent(SimulationEvent event) {
try {
@@ -244,7 +240,7 @@ public class ExitNodeProcess {
}
/**
* Handles simulation end event.
* Executa a lógica de encerramento desencadeada pelo evento DES.
*/
private void handleSimulationEndEvent(SimulationEvent event) {
eventLogger.log(EventType.SIMULATION_STOPPED, "ExitNode",
@@ -256,9 +252,8 @@ public class ExitNodeProcess {
}
/**
* Exports the complete event history for the exit node.
* This satisfies the spec requirement: "Deve ser possível verificar a lista
* completa de eventos"
* Exporta o histórico completo de eventos para auditoria.
* Requisito funcional para verificação de trace.
*/
public void exportEventHistory(String outputPath) {
String history = eventQueue.exportEventHistory();
@@ -271,9 +266,8 @@ public class ExitNodeProcess {
}
/**
* Schedules a simulation end event at the specified time.
*
* @param endTime The simulation time when the simulation should end
* Agenda o fim determinístico da simulação.
* * @param endTime Tempo virtual de paragem.
*/
public void scheduleSimulationEnd(double endTime) {
SimulationEvent endEvent = new SimulationEvent(
@@ -285,22 +279,16 @@ public class ExitNodeProcess {
}
/**
* Abre o socket do servidor e começa a escutar por veículos.
*
* Este é o loop principal. Aceitamos ligações das interseções (de onde vêm os
* veículos)
* e passamo-las para a nossa pool de threads para processamento.
*
* @throws IOException Se não conseguirmos fazer bind à porta.
* Inicia o servidor TCP em modo de bloqueio (Blocking I/O).
* @throws IOException Se ocorrer erro no bind da porta.
*/
public void start() throws IOException {
start(true); // Default to DES mode
}
/**
* Starts the exit node process.
*
* @param useDES If true, starts event processor for DES mode tracking
* Inicia o processo com opção de ativar o rastreio DES.
* * @param useDES Se verdadeiro, ativa a thread do processador de eventos.
*/
public void start(boolean useDES) throws IOException {
int port = config.getExitPort();
@@ -310,15 +298,15 @@ public class ExitNodeProcess {
System.out.println("Exit node started on port " + port);
if (useDES) {
// Note: ExitNode is primarily reactive (network-driven), but maintains
// event queue for simulation end events and history tracking
System.out.println("Running in DES mode (event history tracking enabled)");
}
System.out.println("Waiting for vehicles...\\n");
// Loop de aceitação principal
while (running) {
try {
Socket clientSocket = serverSocket.accept();
// Delega o processamento da conexão para o Thread Pool
connectionHandlerPool.submit(() -> handleIncomingConnection(clientSocket));
} catch (IOException e) {
if (running) {
@@ -329,12 +317,11 @@ public class ExitNodeProcess {
}
/**
* Trata uma ligação de uma interseção.
*
* Mantemos a ligação aberta e escutamos por mensagens `VEHICLE_TRANSFER`.
* Cada mensagem contém um veículo que acabou de terminar a sua viagem.
*
* @param clientSocket O socket ligado à interseção.
* Worker method para tratar uma conexão persistente vinda de uma interseção.
* <p>
* Mantém o socket aberto e consome mensagens num loop até que a conexão seja fechada
* pelo remetente. Responsável pela desserialização polimórfica (JSON/Gson).
* * @param clientSocket O socket conectado.
*/
private void handleIncomingConnection(Socket clientSocket) {
String clientAddress = clientSocket.getInetAddress().getHostAddress();
@@ -350,14 +337,14 @@ public class ExitNodeProcess {
" from " + message.getSourceNode());
if (message.getType() == MessageType.SIMULATION_START) {
// Coordinator sends start time - use it instead of our local start
// Sincronização de relógio com o Coordenador
simulationStartMillis = ((Number) message.getPayload()).longValue();
System.out.println("[Exit] Simulation start time synchronized");
} else if (message.getType() == MessageType.VEHICLE_TRANSFER) {
Object payload = message.getPayload();
System.out.println("[Exit] Payload type: " + payload.getClass().getName());
// Handle Gson LinkedHashMap
// Tratamento de artefatos de desserialização do Gson (LinkedTreeMap -> POJO)
Vehicle vehicle;
if (payload instanceof com.google.gson.internal.LinkedTreeMap ||
payload instanceof java.util.LinkedHashMap) {
@@ -390,26 +377,21 @@ public class ExitNodeProcess {
}
/**
* Processa um veículo que acabou de sair do sistema.
*
* Calculamos quanto tempo demorou, atualizamos as nossas estatísticas globais e
* notificamos o dashboard.
* Este método é sincronizado porque múltiplos veículos podem chegar ao mesmo
* tempo.
*
* @param vehicle O veículo que completou a sua rota.
* Processa atomicamente a saída de um veículo.
* <p>
* <b>Secção Crítica:</b> Método {@code synchronized} para garantir que a atualização
* das estatísticas globais (totalSystemTime, contadores) é atómica, prevenindo
* Race Conditions quando múltiplos veículos chegam simultaneamente de interseções diferentes.
* * @param vehicle O veículo que completou a rota.
*/
private synchronized void processExitingVehicle(Vehicle vehicle) {
totalVehiclesReceived++;
// Use simulation time instead of wall-clock time
// System time = total time vehicle spent in system (wait + crossing times)
// This represents the actual simulation time elapsed, not real-time
// Cálculo de métricas finais baseadas no tempo virtual de simulação acumulado no veículo
double waitTime = vehicle.getTotalWaitingTime();
double crossingTime = vehicle.getTotalCrossingTime();
double systemTime = waitTime + crossingTime;
// Store times in seconds, will be converted to ms when sending to dashboard
totalSystemTime += systemTime;
totalWaitingTime += waitTime;
totalCrossingTime += crossingTime;
@@ -421,23 +403,20 @@ public class ExitNodeProcess {
System.out.printf("[Exit] Vehicle %s completed (type=%s, system_time=%.2fs, wait=%.2fs, crossing=%.2fs)%n",
vehicle.getId(), vehicle.getType(), systemTime, waitTime, crossingTime);
// Log vehicle exit
// Logging estruturado
EventLogger.getInstance().logVehicle(EventType.VEHICLE_EXITED, "ExitNode", vehicle.getId(),
String.format("Completed - System: %.2fs, Wait: %.2fs, Crossing: %.2fs", systemTime, waitTime,
crossingTime));
// Complete vehicle trace if tracking
// Finaliza o trace individual do veículo
VehicleTracer.getInstance().logExit(vehicle, systemTime);
// Send stats after every vehicle to ensure dashboard updates quickly
// Push imediato para o Dashboard para visualização em tempo real
sendStatsToDashboard();
}
/**
* Envia as estatísticas mais recentes para o dashboard.
*
* Empacotamos as contagens totais e os tempos médios num `StatsUpdatePayload`
* e enviamo-lo.
* Constrói e transmite o DTO de atualização de estatísticas.
*/
private void sendStatsToDashboard() {
if (dashboardClient == null || !dashboardClient.isConnected()) {
@@ -448,29 +427,28 @@ public class ExitNodeProcess {
// Create stats payload
StatsUpdatePayload payload = new StatsUpdatePayload();
// Set global stats - convert seconds to milliseconds
// Set global stats - convert seconds to milliseconds for display consistency
payload.setTotalVehiclesCompleted(totalVehiclesReceived);
payload.setTotalSystemTime((long) (totalSystemTime * 1000.0)); // s -> ms
payload.setTotalWaitingTime((long) (totalWaitingTime * 1000.0)); // s -> ms
payload.setTotalSystemTime((long) (totalSystemTime * 1000.0));
payload.setTotalWaitingTime((long) (totalWaitingTime * 1000.0));
// Set intersection-like stats so it shows up correctly in the dashboard table
// Hack: Usar campos de interseção para mostrar throughput no dashboard
payload.setIntersectionArrivals(totalVehiclesReceived);
payload.setIntersectionDepartures(totalVehiclesReceived);
payload.setIntersectionQueueSize(0);
// Set vehicle type stats
// Detailed breakdown
Map<VehicleType, Integer> typeCounts = new HashMap<>();
Map<VehicleType, Long> typeWaitTimes = new HashMap<>();
for (VehicleType type : VehicleType.values()) {
typeCounts.put(type, vehicleTypeCount.get(type));
typeWaitTimes.put(type, (long) (vehicleTypeWaitTime.get(type) * 1000.0)); // s -> ms
typeWaitTimes.put(type, (long) (vehicleTypeWaitTime.get(type) * 1000.0));
}
payload.setVehicleTypeCounts(typeCounts);
payload.setVehicleTypeWaitTimes(typeWaitTimes);
// Send message
Message message = new Message(
MessageType.STATS_UPDATE,
"ExitNode",
@@ -489,9 +467,8 @@ public class ExitNodeProcess {
}
/**
* Encerra graciosamente o processo.
*
* Imprimimos as estatísticas finais, fechamos ligações e limpamos threads.
* Encerramento gracioso do processo.
* Fecha sockets, termina a pool de threads e liberta recursos.
*/
public void shutdown() {
System.out.println("\n[Exit] Shutting down...");
@@ -527,9 +504,7 @@ public class ExitNodeProcess {
}
/**
* Imprime um resumo dos resultados da simulação na consola.
* Isto dá-nos uma visão rápida de como a simulação correu (médias, contagens de
* veículos, etc.).
* Imprime o relatório final no stdout.
*/
private void printFinalStatistics() {
System.out.println("\n=== EXIT NODE STATISTICS ===");

View File

@@ -33,19 +33,22 @@ import sd.protocol.SocketConnection;
import sd.serialization.SerializationException;
/**
* Representa uma única interseção na nossa simulação de tráfego distribuída.
*
* Esta classe opera como um processo independente (uma aplicação Java autónoma)
* e é responsável por:
* 1. Gerir os semáforos e a sua temporização.
* 2. Processar as chegadas e partidas de veículos.
* 3. Comunicar com outras interseções e com o dashboard.
*
* Utiliza uma abordagem de Simulação de Eventos Discretos (DES), onde as
* mudanças de estado (como semáforos a mudar para verde)
* são agendadas como eventos numa fila de prioridade, em vez de depender de
* loops contínuos ou threads em espera.
* Isto garante uma temporização precisa e uma execução eficiente.
* Representa um nó de processamento autónomo na malha de simulação distribuída
* (Worker Node).
* <p>
* Esta classe implementa a lógica de uma interseção rodoviária utilizando uma
* arquitetura híbrida:
* <ol>
* <li><b>Reativa (Network I/O):</b> Threads dedicadas aceitam conexões TCP e
* injetam veículos nas filas de entrada assim que chegam.</li>
* <li><b>Proativa (DES Engine):</b> Uma thread de processamento de eventos gere
* a lógica temporal (mudança de semáforos, tempos de travessia) baseada num
* relógio virtual monotónico.</li>
* </ol>
* <p>
* A sincronização entre a chegada assíncrona de veículos (Rede) e o
* processamento determinístico (DES) é gerida através de estruturas de dados
* concorrentes e bloqueios justos (Fair Locks).
*/
public class IntersectionProcess {
@@ -57,48 +60,56 @@ public class IntersectionProcess {
private ServerSocket serverSocket;
/**
* Tabela de encaminhamento dinâmico para conexões de saída (Next-Hop Cache).
*/
private final Map<String, SocketConnection> outgoingConnections;
/** Pool de threads para tratamento de I/O de rede (entrada de veículos). */
private final ExecutorService connectionHandlerPool;
private ScheduledExecutorService statsExecutor;
private ScheduledExecutorService departureExecutor;
private volatile boolean running;
/** Escala temporal para visualização: tempo_real = tempo_simulado * escala */
/** Fator de dilatação temporal (0.0 = Velocidade Máxima, 1.0 = Tempo Real). */
private double timeScale;
/** Relógio central da simulação */
// --- Componentes DES (Simulação de Eventos Discretos) ---
/** Relógio central virtual da interseção. */
private final SimulationClock clock;
/** Fila de eventos discretos agendados */
/** Fila de prioridade (Min-Heap) para agendamento temporal de eventos. */
private final EventQueue eventQueue;
/** Sistema de registo de eventos */
private final EventLogger eventLogger;
/** Thread dedicada ao processamento sequencial de eventos DES */
/** Thread "Single-Writer" responsável pela mutação de estado da simulação. */
private Thread eventProcessorThread;
/**
* Lock para exclusão mútua entre semáforos.
* Garante que apenas um semáforo pode estar verde de cada vez nesta interseção.
* Mecanismo de exclusão mútua para controlo de fases semafóricas.
* Configurado com política de justiça (fairness=true) para evitar inanição
* (starvation) de direções com menos tráfego.
*/
private final Lock trafficCoordinationLock;
/**
* Regista qual direção tem atualmente o sinal verde.
* {@code null} significa que todos os semáforos estão vermelhos.
* Estado volátil que indica a direção ativa. Apenas uma direção pode deter o
* token 'Green' por vez.
*/
private volatile String currentGreenDirection;
private SocketClient dashboardClient;
// Métricas voláteis para acesso atómico sem bloqueio
private volatile int totalArrivals = 0;
private volatile int totalDepartures = 0;
/**
* Inicializa o processo da interseção.
* Inicializa o processo da interseção, carregando a topologia e preparando o
* motor DES.
*
* @param intersectionId O identificador único para esta interseção (ex: "Cr1").
* @param configFilePath O caminho para o ficheiro de configuração.
* @throws IOException Se houver algum problema ao ler a configuração.
* @param intersectionId O identificador único na malha (ex: "Cr1").
* @param configFilePath Caminho para o ficheiro de propriedades.
* @throws IOException Se falhar o bind da porta ou leitura de config.
*/
public IntersectionProcess(String intersectionId, String configFilePath) throws IOException {
this.intersectionId = intersectionId;
@@ -127,13 +138,16 @@ public class IntersectionProcess {
}
/**
* Inicia o ciclo de processamento de eventos.
*
* Esta thread é o coração do modelo DES para esta interseção. Retira eventos da
* fila
* e executa-os por ordem cronológica. Enquanto a thread principal trata das
* operações de I/O de rede (receção de veículos),
* esta thread trata da lógica da simulação (semáforos, travessias de veículos).
* Inicia o ciclo principal do motor de simulação (DES Engine Loop).
* <p>
* Executa o ciclo "Fetch-Decode-Execute":
* <ol>
* <li>Remove o evento com menor timestamp da fila (Fetch).</li>
* <li>Avança o relógio virtual para o tempo do evento.</li>
* <li>Aplica atraso artificial se {@code timeScale > 0} (para visualização
* humana).</li>
* <li>Despacha o evento para o manipulador apropriado (Execute).</li>
* </ol>
*/
private void startEventProcessor() {
eventProcessorThread = new Thread(() -> {
@@ -145,9 +159,9 @@ public class IntersectionProcess {
while (running) {
SimulationEvent event = eventQueue.poll();
if (event == null) {
// No events currently, wait a bit before checking again
// Backoff exponencial ou sleep curto para evitar busy-waiting em idle
try {
Thread.sleep(50); // Short sleep to avoid busy-waiting
Thread.sleep(50);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
@@ -155,7 +169,7 @@ public class IntersectionProcess {
continue;
}
// Apply time scaling for visualization
// Aplicação de escala temporal (Throttle)
if (timeScale > 0) {
double simTimeDelta = event.getTimestamp() - lastTime;
long realDelayMs = (long) (simTimeDelta * timeScale * 1000);
@@ -170,10 +184,10 @@ public class IntersectionProcess {
lastTime = event.getTimestamp();
}
// Advance clock to event time
// Atualização atómica do tempo de simulação
clock.advanceTo(event.getTimestamp());
// Process the event
// Processamento polimórfico
processEvent(event);
}
@@ -185,10 +199,12 @@ public class IntersectionProcess {
}
/**
* Processa um evento da fila de simulação.
* Cada tipo de evento é encaminhado para o seu tratador específico.
* Despachante central de eventos.
* <p>
* Encaminha o evento para a lógica de negócio específica baseada no tipo
* {@link DESEventType}.
*
* @param event o evento a processar
* @param event O evento de simulação a ser processado.
*/
private void processEvent(SimulationEvent event) {
try {
@@ -198,8 +214,8 @@ public class IntersectionProcess {
break;
case VEHICLE_ARRIVAL:
// Vehicle arrivals are still handled via network messages
// This event type is for internal scheduling if needed
// Chegadas são tratadas reativamente via Socket, mas eventos podem ser usados
// para métricas
break;
case VEHICLE_CROSSING_START:
@@ -225,12 +241,18 @@ public class IntersectionProcess {
}
/**
* Trata da mudança dos semáforos.
* Gere a máquina de estados dos semáforos.
* <p>
* O fluxo de execução é o seguinte:
* <ol>
* <li>Atualiza o estado do semáforo (Verde <-> Vermelho).</li>
* <li>Se o novo estado for Verde: Calcula a capacidade de vazão e agenda
* travessias (Service Events).</li>
* <li>Agenda recursivamente a próxima mudança de estado para manter o ciclo
* infinito.</li>
* </ol>
*
* Quando um semáforo muda de estado, registamos o evento, atualizamos o modelo
* e, se tiver mudado para VERDE,
* verificamos imediatamente se há veículos à espera para atravessar.
* Também agendamos aqui o *próximo* evento de mudança, mantendo o ciclo ativo.
* @param event O evento que desencadeou a mudança de estado.
*/
private void handleTrafficLightChangeEvent(SimulationEvent event) {
TrafficLightEvent tlEvent = (TrafficLightEvent) event.getPayload();
@@ -252,12 +274,12 @@ public class IntersectionProcess {
String.format("Direction %s changed to %s at time %.2f",
direction, newState, event.getTimestamp()));
// If light turned GREEN, process queued vehicles
// Processamento de lote (Batch Processing) para a fase Verde
if (newState == TrafficLightState.GREEN) {
processQueuedVehiclesForLight(light, event.getTimestamp());
}
// Schedule next state change
// Agendamento do próximo ciclo (Feedback Loop)
double nextChangeTime = event.getTimestamp() +
(newState == TrafficLightState.GREEN ? light.getGreenTime() : light.getRedTime());
@@ -269,19 +291,19 @@ public class IntersectionProcess {
}
/**
* Processa a fila de veículos quando um semáforo fica verde.
*
* <p>Para cada veículo na fila:</p>
* Calcula a vazão da interseção durante uma fase verde.
* <p>
* Implementa uma lógica de previsão ("Look-ahead"):
* <ol>
* <li>Calcula o tempo de travessia com base no tipo de veículo</li>
* <li>Verifica se cabe na duração restante do sinal verde</li>
* <li>Agenda o evento de partida do veículo</li>
* <li>Itera sobre a fila de espera do semáforo.</li>
* <li>Calcula o tempo de serviço acumulado (Service Time) baseado no tipo de
* veículo.</li>
* <li>Agenda a partida apenas se o veículo couber na janela temporal restante
* do sinal verde.</li>
* </ol>
*
* <p>Os veículos que não couberem no tempo verde ficam à espera do próximo ciclo.</p>
*
* @param light o semáforo que acabou de ficar verde
* @param currentTime o tempo atual da simulação em segundos
* @param light O semáforo ativo.
* @param currentTime O instante de início da fase verde.
*/
private void processQueuedVehiclesForLight(TrafficLight light, double currentTime) {
double greenDuration = light.getGreenTime();
@@ -291,30 +313,29 @@ public class IntersectionProcess {
System.out.printf("[%s] Processing queue for %s (GREEN for %.2fs, queue size: %d, currentTime=%.2f)%n",
intersectionId, light.getId(), greenDuration, queueSize, currentTime);
// Process vehicles while queue not empty and within green light duration
// Algoritmo de esvaziamento de fila baseado em Time Budget
while (light.getQueueSize() > 0) {
// Calculate crossing time for next vehicle (peek at queue size to estimate)
// We'll use LIGHT vehicle as default for estimation
// Estimativa inicial (optimista)
double crossingTime = config.getLightVehicleCrossingTime();
// Check if another vehicle can fit in remaining green time
// Verificação de limite de tempo (Hard Deadline do sinal vermelho)
if (timeOffset + crossingTime > greenDuration) {
break; // No more vehicles can cross this green phase
break; // Veículo não cabe no ciclo atual
}
// Remove vehicle from queue
Vehicle vehicle = light.removeVehicle();
// Commit: Remove da fila
Vehicle vehicle = light.removeVehicle(currentTime + timeOffset);
if (vehicle == null)
break;
// Get actual crossing time for this vehicle
// Recálculo preciso baseado no tipo real do veículo
crossingTime = getCrossingTimeForVehicle(vehicle);
// Schedule crossing
// Agendamento do evento futuro de término de travessia
double crossingStartTime = currentTime + timeOffset;
scheduleVehicleCrossing(vehicle, crossingStartTime, crossingTime);
// Update offset for next vehicle
// Incrementa offset para serializar as travessias (Head-of-Line Blocking)
timeOffset += crossingTime;
System.out.printf("[%s] Scheduled vehicle %s to cross at t=%.2f (duration=%.2fs)%n",
@@ -323,12 +344,11 @@ public class IntersectionProcess {
}
/**
* Agenda a travessia e partida de um veículo.
* Cria um evento de fim de travessia agendado para o tempo correto.
* Cria e agenda o evento de conclusão de travessia (Partida).
*
* @param vehicle o veículo que vai atravessar
* @param startTime quando a travessia começa (segundos de simulação)
* @param crossingDuration quanto tempo demora a atravessar (segundos)
* @param vehicle O veículo que está a atravessar.
* @param startTime Instante de início da travessia.
* @param crossingDuration Duração estimada da travessia.
*/
private void scheduleVehicleCrossing(Vehicle vehicle, double startTime, double crossingDuration) {
// Schedule crossing end (when vehicle departs)
@@ -347,11 +367,10 @@ public class IntersectionProcess {
}
/**
* Calcula o tempo de travessia com base no tipo de veículo.
* Bicicletas são mais rápidas, veículos pesados mais lentos.
* Determina o custo temporal da travessia baseado na física do veículo.
*
* @param vehicle o veículo para calcular o tempo
* @return tempo de travessia em segundos
* @param vehicle O veículo em questão.
* @return O tempo em segundos necessário para atravessar a interseção.
*/
private double getCrossingTimeForVehicle(Vehicle vehicle) {
return switch (vehicle.getType()) {
@@ -363,36 +382,45 @@ public class IntersectionProcess {
}
/**
* Trata o evento de início de travessia de um veículo.
* (Implementação futura - atualmente apenas regista o evento)
* Manipula o evento de início de travessia de um veículo.
* <p>
* Atualmente serve como placeholder para lógica futura de animação ou
* ocupação de zonas críticas na interseção.
*
* @param event o evento de início de travessia
* @param event O evento de início de travessia.
*/
private void handleVehicleCrossingStartEvent(SimulationEvent event) {
// Implementation will depend on how vehicle crossing is modeled
// For now, log the event
// Placeholder para lógica futura de animação ou ocupação de zona crítica
eventLogger.log(sd.logging.EventType.VEHICLE_DEPARTED, intersectionId,
"Vehicle crossing started at time " + event.getTimestamp());
}
/**
* Trata o fim da travessia de um veículo pela interseção.
* Atualiza estatísticas, regista o tempo de travessia e envia o veículo
* para o próximo destino na sua rota.
* Finaliza a lógica de travessia e inicia a transferência (handover) para o
* próximo nó.
* <p>
* Este método é invocado quando o tempo de travessia expira no relógio virtual.
* Executa as seguintes ações:
* <ol>
* <li>Atualiza as métricas de tempo de travessia do veículo.</li>
* <li>Incrementa contadores locais de veículos processados.</li>
* <li>Transfere a responsabilidade do veículo para a rede, enviando-o ao
* próximo destino.</li>
* </ol>
*
* @param event evento contendo o veículo que terminou a travessia
* @param event O evento de fim de travessia.
*/
private void handleVehicleCrossingEndEvent(SimulationEvent event) {
Vehicle vehicle = (Vehicle) event.getPayload();
// Add crossing time to vehicle stats
// Atualiza métricas do veículo
double crossingTime = getCrossingTimeForVehicle(vehicle);
vehicle.addCrossingTime(crossingTime);
// Update intersection statistics
// Atualiza métricas locais
intersection.incrementVehiclesSent();
// Send vehicle to next destination
// Handover: Transfere a responsabilidade do veículo para a rede
sendVehicleToNextDestination(vehicle);
eventLogger.log(sd.logging.EventType.VEHICLE_DEPARTED, intersectionId,
@@ -400,10 +428,9 @@ public class IntersectionProcess {
}
/**
* Trata o evento de fim da simulação.
* Define a flag de execução como falsa para terminar o processamento.
* Finaliza a execução do processo de simulação.
*
* @param event o evento de fim de simulação
* @param event O evento de fim de simulação.
*/
private void handleSimulationEndEvent(SimulationEvent event) {
eventLogger.log(sd.logging.EventType.SIMULATION_STOPPED, intersectionId,
@@ -412,10 +439,9 @@ public class IntersectionProcess {
}
/**
* Exporta o histórico completo de eventos para um ficheiro.
* Útil para análise posterior e debugging da simulação.
* Exporta o histórico completo de eventos para análise post-mortem.
*
* @param outputPath caminho do ficheiro onde guardar o histórico
* @param outputPath O caminho do ficheiro onde o histórico será guardado.
*/
public void exportEventHistory(String outputPath) {
String history = eventQueue.exportEventHistory();
@@ -427,7 +453,12 @@ public class IntersectionProcess {
}
}
// Main entry point for running an intersection process
/**
* Ponto de entrada principal da aplicação.
*
* @param args Argumentos da linha de comando (ID da interseção e ficheiro de
* configuração opcional).
*/
public static void main(String[] args) {
if (args.length < 1) {
System.err.println("Usage: java IntersectionProcess <intersectionId> [configFile]");
@@ -456,6 +487,12 @@ public class IntersectionProcess {
}
}
/**
* Realiza o bootstrap dos componentes lógicos e de rede da interseção.
* <p>
* Inclui a criação de semáforos, configuração de encaminhamento e conexão ao
* Dashboard.
*/
public void initialize() {
System.out.println("\n[" + intersectionId + "] Initializing intersection...");
@@ -469,7 +506,7 @@ public class IntersectionProcess {
}
/**
* Estabelece ligação ao servidor do dashboard para reportar estatísticas.
* Estabelece a conexão com o Dashboard para envio de telemetria em tempo real.
*/
private void connectToDashboard() {
try {
@@ -493,9 +530,7 @@ public class IntersectionProcess {
}
/**
* Cria os semáforos para esta interseção com base nas suas ligações físicas.
* Cada interseção tem um número e direções de semáforos diferentes de acordo
* com a topologia da rede.
* Inicializa os semáforos da interseção com base na configuração carregada.
*/
private void createTrafficLights() {
System.out.println("\n[" + intersectionId + "] Creating traffic lights...");
@@ -524,6 +559,13 @@ public class IntersectionProcess {
}
}
/**
* Obtém a configuração específica para esta interseção a partir da configuração
* global.
*
* @return O objeto de configuração da interseção.
* @throws RuntimeException Se a configuração estiver em falta.
*/
private SimulationConfig.IntersectionConfig getIntersectionConfig() {
if (config.getNetworkConfig() == null || config.getNetworkConfig().getIntersections() == null) {
throw new RuntimeException("Network configuration not loaded or empty.");
@@ -534,6 +576,11 @@ public class IntersectionProcess {
.orElseThrow(() -> new RuntimeException("Intersection config not found for " + intersectionId));
}
/**
* Configura a tabela de encaminhamento (routing) da interseção.
* <p>
* Define para cada destino qual a direção de saída (semáforo) correspondente.
*/
private void configureRouting() {
System.out.println("\n[" + intersectionId + "] Configuring routing...");
@@ -555,11 +602,10 @@ public class IntersectionProcess {
}
/**
* Solicita permissão para um semáforo ficar verde.
* Bloqueia até que a permissão seja concedida (nenhum outro semáforo está
* verde).
* Primitiva de bloqueio: Solicita acesso exclusivo à zona crítica da
* interseção.
*
* @param direction A direção que solicita o sinal verde
* @param direction A direção que solicita passagem.
*/
public void requestGreenLight(String direction) {
trafficCoordinationLock.lock();
@@ -567,10 +613,9 @@ public class IntersectionProcess {
}
/**
* Liberta a permissão de sinal verde, permitindo que outro semáforo fique
* verde.
* Primitiva de bloqueio: Liberta o acesso exclusivo à zona crítica.
*
* @param direction A direção que liberta o sinal verde
* @param direction A direção que está a libertar a passagem.
*/
public void releaseGreenLight(String direction) {
if (direction.equals(currentGreenDirection)) {
@@ -580,8 +625,10 @@ public class IntersectionProcess {
}
/**
* Modo DES: Agenda os eventos iniciais de mudança de semáforo.
* Isto substitui a antiga abordagem baseada em threads startTrafficLights().
* Inicializa o estado dos semáforos no arranque da simulação (t=0).
* <p>
* Garante que apenas um semáforo começa em Verde e os restantes em Vermelho,
* agendando os eventos iniciais na fila do DES.
*/
private void scheduleInitialTrafficLightEvents() {
System.out.println("\n[" + intersectionId + "] Scheduling initial traffic light events (DES mode)...");
@@ -592,12 +639,12 @@ public class IntersectionProcess {
for (TrafficLight light : intersection.getTrafficLights()) {
String direction = light.getDirection();
// Set initial state (first light starts green, others red)
// Lógica de arranque: Primeiro da lista = Verde, outros = Vermelho
boolean isFirstLight = intersection.getTrafficLights().indexOf(light) == 0;
TrafficLightState initialState = isFirstLight ? TrafficLightState.GREEN : TrafficLightState.RED;
light.changeState(initialState);
// Schedule first state change
// Agenda a primeira transição
double firstChangeTime = currentTime +
(initialState == TrafficLightState.GREEN ? light.getGreenTime() : light.getRedTime());
@@ -620,14 +667,16 @@ public class IntersectionProcess {
}
/**
* Envia um veículo para o seu próximo destino via ligação socket.
* Encaminhamento de rede: Serializa e envia o objeto veículo para o próximo .
* <p>
* Calcula também o tempo de viagem virtual entre nós (Edge Weight).
*
* @param vehicle O veículo que atravessou esta interseção.
* @param vehicle O veículo a ser enviado.
*/
public void sendVehicleToNextDestination(Vehicle vehicle) {
String nextDestination = vehicle.getCurrentDestination();
// Calculate travel time
// Cálculo de latência de viagem (Edge Weight)
double baseTime = config.getBaseTravelTime();
double multiplier = 1.0;
switch (vehicle.getType()) {
@@ -640,22 +689,25 @@ public class IntersectionProcess {
System.out.printf("[%s] Vehicle %s departing to %s. Travel time: %.2fs%n",
intersectionId, vehicle.getId(), nextDestination, travelTime);
// Record departure immediately as it leaves the intersection
recordVehicleDeparture();
// In DES mode, send immediately (no real-time delay)
// Envio imediato (o delay de viagem é implícito no tempo de chegada no próximo
// nó ou simulado aqui)
sendVehicleImmediately(vehicle, nextDestination);
}
/**
* Envia imediatamente um veículo para o seu destino via rede.
* Envia o veículo imediatamente para o próximo nó via conexão TCP persistente.
*
* @param vehicle O veículo a ser enviado.
* @param nextDestination O identificador do próximo nó destino.
*/
private void sendVehicleImmediately(Vehicle vehicle, String nextDestination) {
try {
// Get or create connection to next destination
// Lazy loading da conexão
SocketConnection connection = getOrCreateConnection(nextDestination);
// Create and send message using Message class
// Encapsulamento da mensagem
MessageProtocol message = new Message(
MessageType.VEHICLE_TRANSFER,
intersectionId,
@@ -668,8 +720,6 @@ public class IntersectionProcess {
System.out.println("[" + intersectionId + "] Vehicle " + vehicle.getId() +
" arrived at " + nextDestination + " (msg sent)");
// Note: vehicle route is advanced when it arrives at the next intersection
} catch (IOException | InterruptedException e) {
System.err.println("[" + intersectionId + "] Failed to send vehicle " +
vehicle.getId() + " to " + nextDestination + ": " + e.getMessage());
@@ -677,12 +727,15 @@ public class IntersectionProcess {
}
/**
* Obtém uma ligação existente para um destino ou cria uma nova.
* Obtém ou cria uma conexão para o destino especificado (Singleton por
* destino).
* <p>
* Este método é thread-safe.
*
* @param destinationId O ID do nó de destino.
* @return A SocketConnection para esse destino.
* @throws IOException Se a ligação não puder ser estabelecida.
* @throws InterruptedException Se a tentativa de ligação for interrompida.
* @param destinationId O identificador do nó destino.
* @return A conexão TCP estabelecida.
* @throws IOException Se ocorrer um erro de I/O na criação da conexão.
* @throws InterruptedException Se a thread for interrompida durante a espera.
*/
private synchronized SocketConnection getOrCreateConnection(String destinationId)
throws IOException, InterruptedException {
@@ -702,10 +755,10 @@ public class IntersectionProcess {
}
/**
* Obtém o endereço host para um nó de destino a partir da configuração.
* Resolve o hostname ou endereço IP para um determinado destino.
*
* @param destinationId O ID do nó de destino.
* @return O endereço host.
* @param destinationId O ID do destino.
* @return O endereço do host.
*/
private String getHostForDestination(String destinationId) {
if (destinationId.equals("S")) {
@@ -716,9 +769,9 @@ public class IntersectionProcess {
}
/**
* Obtém o número da porta para um nó de destino a partir da configuração.
* Resolve a porta TCP para um determinado destino.
*
* @param destinationId O ID do nó de destino.
* @param destinationId O ID do destino.
* @return O número da porta.
*/
private int getPortForDestination(String destinationId) {
@@ -730,10 +783,11 @@ public class IntersectionProcess {
}
/**
* Inicia o socket do servidor e começa a aceitar ligações recebidas.
* Este é o loop principal de escuta do processo.
* Inicia o servidor e o loop de aceitação de conexões.
* <p>
* Este método bloqueia a thread chamadora durante a execução do servidor.
*
* @throws IOException Se o socket do servidor não puder ser criado.
* @throws IOException Se ocorrer um erro ao fazer bind da porta.
*/
public void start() throws IOException {
int port = config.getIntersectionPort(intersectionId);
@@ -747,12 +801,12 @@ public class IntersectionProcess {
startEventProcessor();
System.out.println("[" + intersectionId + "] Running in DES mode");
// Start stats updater
// Background task para telemetria
statsExecutor.scheduleAtFixedRate(this::sendStatsToDashboard, 1, 1, TimeUnit.SECONDS);
System.out.println("[" + intersectionId + "] Waiting for incoming connections...\n");
// Main accept loop
// Loop principal de aceitação de conexões
while (running) {
try {
Socket clientSocket = serverSocket.accept();
@@ -760,13 +814,12 @@ public class IntersectionProcess {
System.out.println("[" + intersectionId + "] New connection accepted from " +
clientSocket.getInetAddress().getHostAddress());
// Check running flag again before handling
if (!running) {
clientSocket.close();
break;
}
// **Set timeout before submitting to handler**
// Configura timeout para evitar bloqueios infinitos em leitura
try {
clientSocket.setSoTimeout(1000);
} catch (java.net.SocketException e) {
@@ -775,13 +828,12 @@ public class IntersectionProcess {
continue;
}
// Handle each connection in a separate thread
// Delega processamento para thread pool (NIO style)
connectionHandlerPool.submit(() -> handleIncomingConnection(clientSocket));
} catch (IOException e) {
// Expected when serverSocket.close() is called during shutdown
if (!running) {
break; // Normal shutdown
break; // Shutdown normal
}
System.err.println("[" + intersectionId + "] Error accepting connection: " +
e.getMessage());
@@ -790,10 +842,13 @@ public class IntersectionProcess {
}
/**
* Trata uma ligação recebida de outro processo.
* Escuta continuamente mensagens de transferência de veículos.
* Lógica de tratamento de conexões de entrada (Consumer).
* <p>
* Lê continuamente do socket até que a conexão seja fechada, processando
* mensagens
* de chegada de veículos ou comandos de simulação.
*
* @param clientSocket A ligação socket aceite.
* @param clientSocket O socket do cliente conectado.
*/
private void handleIncomingConnection(Socket clientSocket) {
try {
@@ -809,27 +864,24 @@ public class IntersectionProcess {
System.out.println("[" + intersectionId + "] New connection accepted from " +
clientSocket.getInetAddress().getHostAddress());
// Continuously receive messages while connection is active
while (running && connection.isConnected()) {
try {
MessageProtocol message = connection.receiveMessage();
// Handle simulation start time synchronization
if (message.getType() == MessageType.SIMULATION_START) {
System.out.println("[" + intersectionId + "] Simulation start time synchronized");
continue;
}
// Accept both VEHICLE_TRANSFER and VEHICLE_SPAWN (from coordinator)
if (message.getType() == MessageType.VEHICLE_TRANSFER ||
message.getType() == MessageType.VEHICLE_SPAWN) {
// Cast payload to Vehicle - handle Gson deserialization
// Lógica de desserialização polimórfica (Vehicle ou Map)
Vehicle vehicle;
Object payload = message.getPayload();
if (payload instanceof Vehicle) {
vehicle = (Vehicle) payload;
} else if (payload instanceof java.util.Map) {
// Gson deserialized as LinkedHashMap - re-serialize and deserialize as Vehicle
com.google.gson.Gson gson = new com.google.gson.Gson();
String json = gson.toJson(payload);
vehicle = gson.fromJson(json, Vehicle.class);
@@ -841,43 +893,37 @@ public class IntersectionProcess {
System.out.println("[" + intersectionId + "] Received vehicle: " +
vehicle.getId() + " from " + message.getSourceNode());
// Advance vehicle to next destination in its route
// Lógica de Roteamento Local
vehicle.advanceRoute();
intersection.receiveVehicle(vehicle, clock.getCurrentTime());
// Add vehicle to appropriate queue
intersection.receiveVehicle(vehicle);
// Log queue status after adding vehicle
System.out.printf("[%s] Vehicle %s queued. Total queue size: %d%n",
intersectionId, vehicle.getId(), intersection.getTotalQueueSize());
// Record arrival for statistics
recordVehicleArrival();
} else if (message.getType() == MessageType.SHUTDOWN) {
System.out.println(
"[" + intersectionId + "] Received SHUTDOWN command from " + message.getSourceNode());
running = false;
// Close this specific connection
break;
}
} catch (java.net.SocketTimeoutException e) {
// Timeout - check running flag and continue
if (!running) {
break;
}
// Continue waiting for next message
} catch (ClassNotFoundException e) {
System.err.println("[" + intersectionId + "] Unknown message type received: " +
e.getMessage());
break; // Invalid message, close connection
break;
} catch (IOException e) {
if (running) {
System.err.println("[" + intersectionId + "] Failed to deserialize message: " +
e.getMessage());
e.printStackTrace(); // For debugging - maybe change//remove later
e.printStackTrace();
}
break; // Connection error, close connection
break;
}
}
@@ -885,27 +931,29 @@ public class IntersectionProcess {
if (running) {
System.err.println("[" + intersectionId + "] Connection error: " + e.getMessage());
}
// Expected during shutdown
}
}
/**
* Stops the intersection process gracefully.
* Shuts down all threads and closes all connections.
* Procedimento de Encerramento Gracioso (Graceful Shutdown).
* <ol>
* <li>Para a aceitação de novas conexões.</li>
* <li>Envia últimas estatísticas.</li>
* <li>Encerra pools de threads.</li>
* <li>Fecha sockets ativos.</li>
* </ol>
*/
public void shutdown() {
// Check if already shutdown
if (!running) {
return; // Already shutdown, do nothing
return;
}
System.out.println("\n[" + intersectionId + "] Shutting down...");
running = false;
// Send final stats before closing connections
sendStatsToDashboard();
// 1. Close ServerSocket first
// 1. Close ServerSocket
if (serverSocket != null && !serverSocket.isClosed()) {
try {
serverSocket.close();
@@ -914,8 +962,7 @@ public class IntersectionProcess {
}
}
// 2. Shutdown thread pools with force
// 2. Shutdown thread pools
if (connectionHandlerPool != null && !connectionHandlerPool.isShutdown()) {
connectionHandlerPool.shutdownNow();
}
@@ -926,9 +973,8 @@ public class IntersectionProcess {
departureExecutor.shutdownNow();
}
// 3. Wait briefly for termination (don't block forever)
// 3. Wait briefly for termination
try {
if (connectionHandlerPool != null) {
connectionHandlerPool.awaitTermination(1, TimeUnit.SECONDS);
}
@@ -964,31 +1010,32 @@ public class IntersectionProcess {
}
/**
* Gets the Intersection object managed by this process.
* Useful for testing and monitoring.
* Obtém o modelo de dados da interseção.
*
* @return The Intersection object.
* @return O objeto Intersection.
*/
public Intersection getIntersection() {
return intersection;
}
/**
* Records that a vehicle has arrived at this intersection.
* Regista a chegada de um novo veículo para fins estatísticos.
*/
public void recordVehicleArrival() {
totalArrivals++;
}
/**
* Records that a vehicle has departed from this intersection.
* Regista a partida de um veículo para fins estatísticos.
*/
public void recordVehicleDeparture() {
totalDepartures++;
}
/**
* Sends current statistics to the dashboard server.
* Envia um "snapshot" do estado atual para o Dashboard (Telemetria Push).
* <p>
* Inclui o número acumulado de chegadas, partidas e o tamanho atual das filas.
*/
private void sendStatsToDashboard() {
if (dashboardClient == null || !dashboardClient.isConnected()) {
@@ -996,7 +1043,6 @@ public class IntersectionProcess {
}
try {
// Calculate current queue size
int currentQueueSize = intersection.getTrafficLights().stream()
.mapToInt(TrafficLight::getQueueSize)
.sum();
@@ -1006,7 +1052,6 @@ public class IntersectionProcess {
.setIntersectionDepartures(totalDepartures)
.setIntersectionQueueSize(currentQueueSize);
// Send StatsUpdatePayload directly as the message payload
sd.model.Message message = new sd.model.Message(
MessageType.STATS_UPDATE,
intersectionId,

View File

@@ -5,41 +5,70 @@ import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import sd.model.VehicleType;
/**
* Executes multiple simulation runs and aggregates results.
* Calculates statistical measures including mean, standard deviation,
* and confidence intervals across all runs.
* Responsável pela agregação e análise estatística de múltiplas execuções da simulação.
* <p>
* Esta classe coleta resultados individuais ({@link SimulationRunResult}) e calcula
* métricas consolidadas, incluindo média, desvio padrão, mediana e intervalos de
* confiança de 95%. O objetivo é fornecer uma visão robusta do comportamento do
* sistema, mitigando a variância estocástica de execuções isoladas.
*/
public class MultiRunAnalyzer {
/** Lista acumulada de resultados de execuções individuais. */
private final List<SimulationRunResult> results;
/** Identificador do ficheiro de configuração utilizado nas execuções. */
private final String configurationFile;
/**
* Inicializa o analisador para um conjunto específico de configurações.
*
* @param configurationFile O caminho ou nome do ficheiro de configuração base.
*/
public MultiRunAnalyzer(String configurationFile) {
this.configurationFile = configurationFile;
this.results = new ArrayList<>();
}
/**
* Adds a completed simulation run result.
* Adiciona o resultado de uma execução de simulação concluída ao conjunto de dados.
*
* @param result O objeto contendo as métricas da execução individual.
*/
public void addResult(SimulationRunResult result) {
results.add(result);
}
/**
* Gets the number of completed runs.
* Retorna o número total de execuções armazenadas até o momento.
*
* @return O tamanho da lista de resultados.
*/
public int getRunCount() {
return results.size();
}
/**
* Generates a comprehensive statistical report.
* Gera um relatório estatístico abrangente formatado em texto.
* <p>
* O relatório inclui:
* <ul>
* <li>Métricas globais (throughput, tempos de espera, tempos no sistema).</li>
* <li>Análise segmentada por tipo de veículo ({@link VehicleType}).</li>
* <li>Análise de gargalos por interseção (tamanhos de fila).</li>
* <li>Resumos brutos das execuções individuais.</li>
* </ul>
*
* @return Uma String contendo o relatório completo formatado.
*/
public String generateReport() {
if (results.isEmpty()) {
@@ -50,65 +79,65 @@ public class MultiRunAnalyzer {
// Header
report.append("=".repeat(80)).append("\n");
report.append("MULTI-RUN STATISTICAL ANALYSIS\n");
report.append("ANÁLISE ESTATÍSTICA MULTI-EXECUÇÃO\n");
report.append("=".repeat(80)).append("\n");
report.append("Configuration: ").append(configurationFile).append("\n");
report.append("Number of Runs: ").append(results.size()).append("\n");
report.append("Analysis Date: ").append(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())).append("\n");
report.append("Configuração: ").append(configurationFile).append("\n");
report.append("Número de Execuções: ").append(results.size()).append("\n");
report.append("Data da Análise: ").append(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())).append("\n");
report.append("\n");
// Global metrics
report.append("-".repeat(80)).append("\n");
report.append("GLOBAL METRICS\n");
report.append("MÉTRICAS GLOBAIS\n");
report.append("-".repeat(80)).append("\n\n");
report.append(analyzeMetric("Vehicles Generated",
report.append(analyzeMetric("Veículos Gerados",
extractValues(r -> (double) r.getTotalVehiclesGenerated())));
report.append("\n");
report.append(analyzeMetric("Vehicles Completed",
report.append(analyzeMetric("Veículos Completados",
extractValues(r -> (double) r.getTotalVehiclesCompleted())));
report.append("\n");
report.append(analyzeMetric("Completion Rate (%)",
report.append(analyzeMetric("Taxa de Conclusão (%)",
extractValues(r -> r.getTotalVehiclesGenerated() > 0
? 100.0 * r.getTotalVehiclesCompleted() / r.getTotalVehiclesGenerated()
: 0.0)));
report.append("\n");
report.append(analyzeMetric("Average System Time (seconds)",
report.append(analyzeMetric("Tempo Médio no Sistema (segundos)",
extractValues(r -> r.getAverageSystemTime())));
report.append("\n");
report.append(analyzeMetric("Average Waiting Time (seconds)",
report.append(analyzeMetric("Tempo Médio de Espera (segundos)",
extractValues(r -> r.getAverageWaitingTime())));
report.append("\n");
// Per-vehicle-type analysis
report.append("\n");
report.append("-".repeat(80)).append("\n");
report.append("PER-VEHICLE-TYPE ANALYSIS\n");
report.append("ANÁLISE POR TIPO DE VEÍCULO\n");
report.append("-".repeat(80)).append("\n\n");
for (VehicleType type : VehicleType.values()) {
report.append("--- ").append(type).append(" ---\n");
report.append(analyzeMetric(" Vehicle Count",
report.append(analyzeMetric(" Contagem de Veículos",
extractValues(r -> (double) r.getVehicleCountByType().getOrDefault(type, 0))));
report.append("\n");
report.append(analyzeMetric(" Avg System Time (seconds)",
report.append(analyzeMetric(" Tempo Médio no Sistema (segundos)",
extractValues(r -> r.getAvgSystemTimeByType().getOrDefault(type, 0.0))));
report.append("\n");
report.append(analyzeMetric(" Avg Waiting Time (seconds)",
report.append(analyzeMetric(" Tempo Médio de Espera (segundos)",
extractValues(r -> r.getAvgWaitTimeByType().getOrDefault(type, 0.0))));
report.append("\n\n");
}
// Per-intersection analysis
report.append("-".repeat(80)).append("\n");
report.append("PER-INTERSECTION ANALYSIS\n");
report.append("ANÁLISE POR INTERSEÇÃO\n");
report.append("-".repeat(80)).append("\n\n");
Set<String> allIntersections = new TreeSet<>();
@@ -119,22 +148,22 @@ public class MultiRunAnalyzer {
for (String intersection : allIntersections) {
report.append("--- ").append(intersection).append(" ---\n");
report.append(analyzeMetric(" Max Queue Size",
report.append(analyzeMetric(" Tamanho Máximo da Fila",
extractValues(r -> (double) r.getMaxQueueSizeByIntersection().getOrDefault(intersection, 0))));
report.append("\n");
report.append(analyzeMetric(" Avg Queue Size",
report.append(analyzeMetric(" Tamanho Médio da Fila",
extractValues(r -> r.getAvgQueueSizeByIntersection().getOrDefault(intersection, 0.0))));
report.append("\n");
report.append(analyzeMetric(" Vehicles Processed",
report.append(analyzeMetric(" Veículos Processados",
extractValues(r -> (double) r.getVehiclesProcessedByIntersection().getOrDefault(intersection, 0))));
report.append("\n\n");
}
// Individual run summaries
report.append("-".repeat(80)).append("\n");
report.append("INDIVIDUAL RUN SUMMARIES\n");
report.append("RESUMOS INDIVIDUAIS DAS EXECUÇÕES\n");
report.append("-".repeat(80)).append("\n\n");
for (SimulationRunResult result : results) {
@@ -142,18 +171,24 @@ public class MultiRunAnalyzer {
}
report.append("=".repeat(80)).append("\n");
report.append("END OF REPORT\n");
report.append("FIM DO RELATÓRIO\n");
report.append("=".repeat(80)).append("\n");
return report.toString();
}
/**
* Analyzes a single metric and returns formatted statistics.
* Analisa uma métrica específica e retorna as estatísticas formatadas.
* <p>
* Calcula média, desvio padrão, mediana, intervalo de confiança (95%) e extremos (min/max).
*
* @param metricName O nome descritivo da métrica (ex: "Tempo de Espera").
* @param values A lista de valores numéricos brutos extraídos das execuções.
* @return Uma string formatada com os dados estatísticos.
*/
private String analyzeMetric(String metricName, List<Double> values) {
if (values.isEmpty() || values.stream().allMatch(v -> v == 0.0)) {
return metricName + ": No data\n";
return metricName + ": Sem dados\n";
}
double mean = StatisticalAnalysis.mean(values);
@@ -165,15 +200,21 @@ public class MultiRunAnalyzer {
return String.format(
"%s:\n" +
" Mean: %10.2f Std Dev: %10.2f\n" +
" Median: %10.2f 95%% CI: [%.2f, %.2f]\n" +
" Min: %10.2f Max: %10.2f\n",
" Média: %10.2f Desvio Padrão: %10.2f\n" +
" Mediana: %10.2f IC 95%%: [%.2f, %.2f]\n" +
" Mín: %10.2f Máx: %10.2f\n",
metricName, mean, stdDev, median, ci[0], ci[1], min, max
);
}
/**
* Extracts values using a lambda function.
* Extrai valores numéricos dos resultados de simulação usando uma função mapeadora.
* <p>
* Utilizado internamente para transformar a lista de objetos complexos {@link SimulationRunResult}
* em listas simples de Doubles para processamento estatístico.
*
* @param extractor Função lambda que define qual campo extrair de cada resultado.
* @return Lista de valores double correspondentes.
*/
private List<Double> extractValues(java.util.function.Function<SimulationRunResult, Double> extractor) {
List<Double> values = new ArrayList<>();
@@ -184,7 +225,10 @@ public class MultiRunAnalyzer {
}
/**
* Saves the report to a file.
* Persiste o relatório gerado num ficheiro de texto.
*
* @param filename O caminho do ficheiro de destino.
* @throws IOException Se ocorrer um erro de escrita no disco.
*/
public void saveReport(String filename) throws IOException {
try (PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(filename)))) {
@@ -193,13 +237,31 @@ public class MultiRunAnalyzer {
}
/**
* Generates a CSV summary for easy import into spreadsheet tools.
* Gera um resumo em formato CSV para fácil importação em ferramentas de planilha.
* <p>
* Este método atua como um wrapper para {@link #saveCSVSummary(String)}.
*
* @param filename O caminho do ficheiro CSV de destino.
* @throws IOException Se ocorrer um erro de escrita no disco.
*/
public void saveCSV(String filename) throws IOException {
saveCSVSummary(filename);
}
/**
* Gera e grava o sumário CSV detalhado com métricas chave por execução.
* <p>
* Colunas incluídas: Execução, VeículosGerados, VeículosCompletados, TaxaConclusão,
* TempoMédioSistema, TempoMédioEspera, TempoMínimoSistema, TempoMáximoSistema.
*
* @param filename O caminho do ficheiro CSV de destino.
* @throws IOException Se ocorrer um erro de escrita no disco.
*/
public void saveCSVSummary(String filename) throws IOException {
try (PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(filename)))) {
// Header
writer.println("Run,VehiclesGenerated,VehiclesCompleted,CompletionRate," +
"AvgSystemTime,AvgWaitingTime,MinSystemTime,MaxSystemTime");
writer.println("Execução,VeículosGerados,VeículosCompletados,TaxaConclusão," +
"TempoMédioSistema,TempoMédioEspera,TempoMínimoSistema,TempoMáximoSistema");
// Data rows
for (SimulationRunResult result : results) {

View File

@@ -1,172 +0,0 @@
package sd.analysis;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* Orquestra múltiplas execuções de simulação para análise estatística.
*
* Em vez de correr uma única simulação manualmente, esta ferramenta permite
* correr um "lote"
* de N simulações consecutivas. Isto é essencial para recolher dados
* estatisticamente significativos
* (calcular intervalos de confiança, etc.) conforme exigido pelas
* especificações do projeto.
*
* Utilização:
* java sd.analysis.SimulationBatchRunner <ficheiro-config> <num-execucoes>
* <dir-saida>
*/
public class SimulationBatchRunner {
public static void main(String[] args) {
if (args.length < 3) {
System.err.println("Usage: SimulationBatchRunner <config-file> <num-runs> <output-dir>");
System.err.println("Example: SimulationBatchRunner simulation-medium.properties 10 results/medium");
System.exit(1);
}
String configFile = args[0];
int numRuns;
String outputDir = args[2];
try {
numRuns = Integer.parseInt(args[1]);
if (numRuns < 1 || numRuns > 100) {
throw new IllegalArgumentException("Number of runs must be between 1 and 100");
}
} catch (NumberFormatException e) {
System.err.println("Error: Invalid number of runs: " + args[1]);
System.exit(1);
return;
}
System.out.println("=".repeat(80));
System.out.println("SIMULATION BATCH RUNNER");
System.out.println("=".repeat(80));
System.out.println("Configuration: " + configFile);
System.out.println("Number of Runs: " + numRuns);
System.out.println("Output Directory: " + outputDir);
System.out.println("=".repeat(80));
System.out.println();
// Create output directory
try {
Files.createDirectories(Paths.get(outputDir));
} catch (IOException e) {
System.err.println("Failed to create output directory: " + e.getMessage());
System.exit(1);
}
MultiRunAnalyzer analyzer = new MultiRunAnalyzer(configFile);
// Execute runs
for (int i = 1; i <= numRuns; i++) {
System.out.println("\n" + "=".repeat(80));
System.out.println("STARTING RUN " + i + " OF " + numRuns);
System.out.println("=".repeat(80));
SimulationRunResult result = executeSimulationRun(i, configFile, outputDir);
if (result != null) {
analyzer.addResult(result);
System.out.println("\n" + result);
} else {
System.err.println("Run " + i + " failed!");
}
// Pause between runs
if (i < numRuns) {
System.out.println("\nWaiting 10 seconds before next run...");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
// Generate reports
System.out.println("\n\n" + "=".repeat(80));
System.out.println("ALL RUNS COMPLETE - GENERATING REPORTS");
System.out.println("=".repeat(80));
try {
String timestamp = new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date());
String reportFile = outputDir + "/analysis-report-" + timestamp + ".txt";
String csvFile = outputDir + "/summary-" + timestamp + ".csv";
analyzer.saveReport(reportFile);
analyzer.saveCSVSummary(csvFile);
System.out.println("\nReports generated:");
System.out.println(" - Analysis Report: " + reportFile);
System.out.println(" - CSV Summary: " + csvFile);
System.out.println();
// Print report to console
System.out.println(analyzer.generateReport());
} catch (IOException e) {
System.err.println("Failed to generate reports: " + e.getMessage());
e.printStackTrace();
}
}
/**
* Executa uma única instância da simulação.
*
* Idealmente, este método iniciaria todos os processos necessários
* (Interseções, Nó de Saída, Coordenador),
* esperaria que terminassem e depois recolheria os resultados.
*
* Atualmente, serve como um espaço reservado estrutural para demonstrar como
* funciona o pipeline de análise.
* Para correr uma simulação real, deve iniciar os componentes manualmente ou
* usar um script shell.
*/
private static SimulationRunResult executeSimulationRun(int runNumber, String configFile, String outputDir) {
SimulationRunResult result = new SimulationRunResult(runNumber, configFile);
try {
// TODO: Implement actual simulation execution
// This would involve:
// 1. Starting intersection processes
// 2. Starting exit node process
// 3. Starting dashboard process
// 4. Running coordinator
// 5. Collecting results from dashboard/exit node
// 6. Shutting down all processes
System.out.println("NOTE: Actual simulation execution not yet implemented.");
System.out.println("This batch runner demonstrates the framework structure.");
System.out.println("To run actual simulations, you need to:");
System.out.println(" 1. Start all intersection processes manually");
System.out.println(" 2. Start exit node process");
System.out.println(" 3. Start dashboard process");
System.out.println(" 4. Run coordinator with the configuration file");
System.out.println(" 5. Results will be collected automatically");
// Placeholder: simulate some results
// In real implementation, these would be collected from the actual simulation
result.setTotalVehiclesGenerated(100);
result.setTotalVehiclesCompleted(85);
result.setAverageSystemTime(120.5);
result.setMinSystemTime(45.2);
result.setMaxSystemTime(250.8);
result.setAverageWaitingTime(45.3);
return result;
} catch (Exception e) {
System.err.println("Error executing run " + runNumber + ": " + e.getMessage());
e.printStackTrace();
return null;
}
}
}

View File

@@ -6,8 +6,12 @@ import java.util.Map;
import sd.model.VehicleType;
/**
* Stores the results of a single simulation run.
* Contains all key metrics for post-simulation analysis.
* Encapsula os dados telemétricos e estatísticos resultantes de uma única execução da simulação.
* <p>
* Esta classe atua como um registo estruturado de métricas de desempenho, armazenando
* dados de latência (tempos de sistema/espera), vazão (throughput) e ocupação de recursos
* (tamanhos de fila). Os dados aqui contidos servem como base para a análise
* estatística agregada realizada pelo {@link MultiRunAnalyzer}.
*/
public class SimulationRunResult {
@@ -17,11 +21,22 @@ public class SimulationRunResult {
private final long endTimeMillis;
// Global metrics
/** Total de veículos instanciados pelos geradores durante a execução. */
private int totalVehiclesGenerated;
/** Total de veículos que completaram o percurso e saíram do sistema com sucesso. */
private int totalVehiclesCompleted;
/** Média global do tempo total (em segundos) desde a geração até a saída. */
private double averageSystemTime; // seconds
/** Menor tempo de sistema registado (em segundos). */
private double minSystemTime; // seconds
/** Maior tempo de sistema registado (em segundos). */
private double maxSystemTime; // seconds
/** Média global do tempo (em segundos) que os veículos passaram parados em filas. */
private double averageWaitingTime; // seconds
// Per-type metrics
@@ -34,6 +49,12 @@ public class SimulationRunResult {
private final Map<String, Double> avgQueueSizeByIntersection;
private final Map<String, Integer> vehiclesProcessedByIntersection;
/**
* Inicializa um novo contentor de resultados para uma execução específica.
*
* @param runNumber O identificador sequencial desta execução.
* @param configurationFile O ficheiro de configuração utilizado.
*/
public SimulationRunResult(int runNumber, String configurationFile) {
this.runNumber = runNumber;
this.configurationFile = configurationFile;
@@ -48,6 +69,10 @@ public class SimulationRunResult {
this.vehiclesProcessedByIntersection = new HashMap<>();
}
/**
* Sinaliza o fim da recolha de dados para esta execução.
* (Placeholder para lógica de finalização de timestamps).
*/
public void markCompleted() {
// This will be called when the run finishes
}
@@ -57,6 +82,11 @@ public class SimulationRunResult {
public String getConfigurationFile() { return configurationFile; }
public long getStartTimeMillis() { return startTimeMillis; }
public long getEndTimeMillis() { return endTimeMillis; }
/**
* Calcula a duração total da execução em milissegundos.
* @return Delta entre fim e início.
*/
public long getDurationMillis() { return endTimeMillis - startTimeMillis; }
public int getTotalVehiclesGenerated() { return totalVehiclesGenerated; }
@@ -66,21 +96,50 @@ public class SimulationRunResult {
public double getMaxSystemTime() { return maxSystemTime; }
public double getAverageWaitingTime() { return averageWaitingTime; }
/**
* Retorna o mapeamento de contagem de veículos por tipo.
* @return Uma cópia defensiva do mapa (snapshot).
*/
public Map<VehicleType, Integer> getVehicleCountByType() {
return new HashMap<>(vehicleCountByType);
}
/**
* Retorna o tempo médio no sistema segmentado por tipo de veículo.
* @return Uma cópia defensiva do mapa (snapshot).
*/
public Map<VehicleType, Double> getAvgSystemTimeByType() {
return new HashMap<>(avgSystemTimeByType);
}
/**
* Retorna o tempo médio de espera segmentado por tipo de veículo.
* @return Uma cópia defensiva do mapa (snapshot).
*/
public Map<VehicleType, Double> getAvgWaitTimeByType() {
return new HashMap<>(avgWaitTimeByType);
}
/**
* Retorna o tamanho máximo de fila registado por interseção (gargalos).
* @return Uma cópia defensiva do mapa (snapshot).
*/
public Map<String, Integer> getMaxQueueSizeByIntersection() {
return new HashMap<>(maxQueueSizeByIntersection);
}
/**
* Retorna o tamanho médio das filas por interseção.
* @return Uma cópia defensiva do mapa (snapshot).
*/
public Map<String, Double> getAvgQueueSizeByIntersection() {
return new HashMap<>(avgQueueSizeByIntersection);
}
/**
* Retorna o total de veículos processados (throughput) por interseção.
* @return Uma cópia defensiva do mapa (snapshot).
*/
public Map<String, Integer> getVehiclesProcessedByIntersection() {
return new HashMap<>(vehiclesProcessedByIntersection);
}
@@ -124,13 +183,17 @@ public class SimulationRunResult {
vehiclesProcessedByIntersection.put(intersection, count);
}
/**
* Gera uma representação textual resumida das métricas principais da execução.
* Útil para logs rápidos e debugging.
*/
@Override
public String toString() {
return String.format(
"Run #%d [%s]:\n" +
" Generated: %d, Completed: %d (%.1f%%)\n" +
" Avg System Time: %.2fs\n" +
" Avg Waiting Time: %.2fs",
"Execução #%d [%s]:\n" +
" Gerados: %d, Completados: %d (%.1f%%)\n" +
" Tempo Médio no Sistema: %.2fs\n" +
" Tempo Médio de Espera: %.2fs",
runNumber,
configurationFile,
totalVehiclesGenerated,

View File

@@ -5,13 +5,19 @@ import java.util.Collections;
import java.util.List;
/**
* Statistical analysis utilities for simulation results.
* Calculates mean, standard deviation, and confidence intervals.
* Utilitário estático para processamento matemático e análise estatística dos dados da simulação.
* <p>
* Esta classe fornece algoritmos para cálculo de medidas de tendência central (média, mediana),
* dispersão (desvio padrão amostral) e inferência estatística (Intervalos de Confiança).
* É utilizada para normalizar e validar os resultados estocásticos obtidos através de
* múltiplas execuções do sistema.
*/
public class StatisticalAnalysis {
/**
* Calculates the mean (average) of a list of values.
* Calcula a média aritmética de um conjunto de valores.
* * @param values Lista de valores numéricos (double).
* @return A soma dos valores dividida pelo tamanho da amostra, ou 0.0 se a lista for nula/vazia.
*/
public static double mean(List<Double> values) {
if (values == null || values.isEmpty()) {
@@ -25,7 +31,13 @@ public class StatisticalAnalysis {
}
/**
* Calculates the sample standard deviation.
* Calcula o desvio padrão amostral (sample standard deviation).
* <p>
* Utiliza o denominador {@code n - 1} (Correção de Bessel) para fornecer um
* estimador não viesado da variância populacional, adequado para as amostras
* de simulação.
* * @param values Lista de observações.
* @return O desvio padrão calculado, ou 0.0 se o tamanho da amostra for < 2.
*/
public static double standardDeviation(List<Double> values) {
if (values == null || values.size() < 2) {
@@ -45,10 +57,13 @@ public class StatisticalAnalysis {
}
/**
* Calculates the 95% confidence interval for the mean.
* Uses t-distribution for small samples (n < 30).
*
* @return Array of [lowerBound, upperBound]
* Calcula o Intervalo de Confiança (IC) de 95% para a média.
* <p>
* Utiliza a distribuição t de Student para maior precisão em amostras pequenas (n < 30),
* onde a aproximação pela distribuição Normal (Z) seria inadequada. O intervalo define
* a faixa onde a verdadeira média populacional reside com 95% de probabilidade.
* * @param values Lista de observações.
* @return Um array de double onde índice 0 é o limite inferior e índice 1 é o limite superior.
*/
public static double[] confidenceInterval95(List<Double> values) {
if (values == null || values.size() < 2) {
@@ -76,8 +91,12 @@ public class StatisticalAnalysis {
}
/**
* Returns the t-critical value for 95% confidence interval.
* Approximations for common degrees of freedom (n-1).
* Retorna o valor crítico t (t-score) para um IC de 95% (bicaudal).
* <p>
* Baseia-se nos graus de liberdade (gl = n - 1). Para amostras grandes (gl >= 30),
* aproxima-se do valor Z de 1.96.
* * @param sampleSize O tamanho da amostra (n).
* @return O fator multiplicativo t apropriado.
*/
private static double getTCriticalValue(int sampleSize) {
int df = sampleSize - 1; // degrees of freedom
@@ -94,7 +113,9 @@ public class StatisticalAnalysis {
}
/**
* Calculates the minimum value.
* Identifica o valor mínimo absoluto na amostra.
* * @param values Lista de valores.
* @return O menor valor encontrado.
*/
public static double min(List<Double> values) {
if (values == null || values.isEmpty()) {
@@ -104,7 +125,9 @@ public class StatisticalAnalysis {
}
/**
* Calculates the maximum value.
* Identifica o valor máximo absoluto na amostra.
* * @param values Lista de valores.
* @return O maior valor encontrado.
*/
public static double max(List<Double> values) {
if (values == null || values.isEmpty()) {
@@ -114,7 +137,12 @@ public class StatisticalAnalysis {
}
/**
* Calculates the median value.
* Calcula a mediana da amostra.
* <p>
* <b>Nota de Desempenho:</b> Este método ordena uma cópia da lista, resultando em
* complexidade O(n log n).
* * @param values Lista de valores.
* @return O valor central (ou média dos dois centrais) da distribuição ordenada.
*/
public static double median(List<Double> values) {
if (values == null || values.isEmpty()) {
@@ -133,7 +161,12 @@ public class StatisticalAnalysis {
}
/**
* Formats a statistical summary as a string.
* Formata um sumário estatístico completo para uma métrica específica.
* <p>
* Útil para logging e geração de relatórios textuais.
* * @param metricName Nome da métrica a ser exibida.
* @param values Os dados brutos associados à métrica.
* @return String formatada contendo Média, Desvio Padrão, IC95%, Min, Max e N.
*/
public static String formatSummary(String metricName, List<Double> values) {
if (values == null || values.isEmpty()) {

View File

@@ -14,17 +14,28 @@ import java.util.Properties;
import com.google.gson.Gson;
/**
* Carrega e gere configurações da simulação.
*
* <p>Lê propriedades de um ficheiro .properties e fornece getters
* type-safe com valores padrão para robustez.
* Responsável pelo carregamento, validação e acesso centralizado às configurações da simulação.
* <p>
* Esta classe atua como uma fachada (Facade) para os parâmetros do sistema, abstraindo a origem
* dos dados (ficheiros {@code .properties} ou JSON). Implementa uma estratégia robusta de
* carregamento de recursos, suportando tanto caminhos absolutos do sistema de ficheiros quanto
* recursos embutidos no <i>classpath</i>.
* <p>
* Além de propriedades chave-valor simples, gerencia a desserialização da topologia da rede
* através da classe interna {@link NetworkConfig}.
*/
public class SimulationConfig {
/** Propriedades carregadas do ficheiro */
/** Armazenamento em memória das propriedades chave-valor carregadas. */
private final Properties properties;
/** Estrutura hierárquica da configuração da rede carregada via JSON. */
private NetworkConfig networkConfig;
/**
* Objeto de transferência de dados (DTO) que representa a configuração global da rede.
* Mapeado a partir do ficheiro {@code network_config.json}.
*/
public static class NetworkConfig {
private List<IntersectionConfig> intersections;
@@ -33,36 +44,45 @@ public class SimulationConfig {
}
}
/**
* DTO que representa a configuração de uma única interseção na topologia.
*/
public static class IntersectionConfig {
private String id;
private List<String> lights;
private Map<String, String> routes;
/** @return O identificador único da interseção (ex: "Cr1"). */
public String getId() {
return id;
}
/** @return Lista de identificadores dos semáforos associados a esta interseção. */
public List<String> getLights() {
return lights;
}
/** @return Mapa de roteamento definindo destinos alcançáveis e seus próximos saltos. */
public Map<String, String> getRoutes() {
return routes;
}
}
/**
* Carrega propriedades do ficheiro especificado.
*
* <p>Tenta múltiplas estratégias:
* Inicializa o gestor de configuração carregando propriedades do caminho especificado.
* * <p>Implementa uma estratégia de carregamento em cascata (fallback) para garantir robustez
* em diferentes ambientes de execução (IDE, JAR, Docker):
* <ol>
* <li>Caminho direto no sistema de ficheiros
* <li>Recurso no classpath (com normalização automática)
* <li>Recurso no classpath com barra inicial
* <li><b>Sistema de Ficheiros Direto:</b> Tenta carregar do caminho absoluto ou relativo.</li>
* <li><b>Classpath (Contexto):</b> Tenta carregar via {@code Thread.currentThread().getContextClassLoader()},
* normalizando prefixos como "src/main/resources" ou "classpath:".</li>
* <li><b>Classpath (Classe):</b> Tenta carregar via {@code SimulationConfig.class.getResourceAsStream},
* útil para recursos na raiz do JAR.</li>
* </ol>
*
* @param filePath caminho do ficheiro .properties
* @throws IOException se o ficheiro não for encontrado
* @param filePath O caminho ou nome do recurso do ficheiro {@code .properties}.
* @throws IOException Se o ficheiro não puder ser localizado em nenhuma das estratégias,
* com uma mensagem detalhada das tentativas falhadas.
*/
public SimulationConfig(String filePath) throws IOException {
properties = new Properties();
@@ -135,6 +155,12 @@ public class SimulationConfig {
throw new IOException(errorMsg.toString(), fileSystemException);
}
/**
* Carrega a configuração da topologia de rede a partir do ficheiro "network_config.json".
* <p>
* Utiliza a biblioteca Gson para desserialização. Em caso de falha, emite um aviso para o
* {@code System.err} mas não aborta a execução, permitindo o uso de defaults ou redes vazias.
*/
private void loadNetworkConfig() {
try (InputStream is = getClass().getClassLoader().getResourceAsStream("network_config.json")) {
if (is == null) {
@@ -151,6 +177,10 @@ public class SimulationConfig {
}
}
/**
* Retorna a configuração estruturada da rede.
* @return Objeto {@link NetworkConfig} ou null se o carregamento falhou.
*/
public NetworkConfig getNetworkConfig() {
return networkConfig;
}
@@ -158,56 +188,50 @@ public class SimulationConfig {
// --- Network configurations ---
/**
* Gets the host address for a specific intersection.
*
* @param intersectionId The ID of the intersection (e.g., "Cr1").
* @return The host (e.g., "localhost").
* Obtém o endereço de host (nome DNS ou IP) para uma interseção específica.
* * @param intersectionId O ID da interseção (ex: "Cr1").
* @return O host configurado ou "localhost" por omissão.
*/
public String getIntersectionHost(String intersectionId) {
return properties.getProperty("intersection." + intersectionId + ".host", "localhost");
}
/**
* Gets the port number for a specific intersection.
*
* @param intersectionId The ID of the intersection (e.g., "Cr1").
* @return The port number.
* Obtém a porta de escuta TCP para uma interseção específica.
* * @param intersectionId O ID da interseção (ex: "Cr1").
* @return O número da porta. Retorna 0 se não configurado.
*/
public int getIntersectionPort(String intersectionId) {
return Integer.parseInt(properties.getProperty("intersection." + intersectionId + ".port", "0"));
}
/**
* Gets the host address for the dashboard server.
*
* @return The dashboard host.
* Obtém o endereço de host do servidor de Dashboard (monitorização).
* @return O host do dashboard (padrão: "localhost").
*/
public String getDashboardHost() {
return properties.getProperty("dashboard.host", "localhost");
}
/**
* Gets the port number for the dashboard server.
*
* @return The dashboard port.
* Obtém a porta de conexão do servidor de Dashboard.
* @return A porta do dashboard (padrão: 9000).
*/
public int getDashboardPort() {
return Integer.parseInt(properties.getProperty("dashboard.port", "9000"));
}
/**
* Gets the host address for the exit node.
*
* @return The exit node host.
* Obtém o endereço de host do nó de saída (Exit Node), para onde os veículos são encaminhados ao sair da malha.
* @return O host do nó de saída (padrão: "localhost").
*/
public String getExitHost() {
return properties.getProperty("exit.host", "localhost");
}
/**
* Gets the port number for the exit node.
*
* @return The exit node port.
* Obtém a porta de conexão do nó de saída.
* @return A porta do nó de saída (padrão: 9001).
*/
public int getExitPort() {
return Integer.parseInt(properties.getProperty("exit.port", "9001"));
@@ -216,68 +240,76 @@ public class SimulationConfig {
// --- Simulation configurations ---
/**
* Gets the total duration of the simulation in virtual seconds.
*
* @return The simulation duration.
* Define a duração total da execução da simulação em segundos virtuais.
* @return A duração em segundos (padrão: 3600).
*/
public double getSimulationDuration() {
return Double.parseDouble(properties.getProperty("simulation.duration", "3600"));
}
/**
* Get time scaling factor for visualization.
* 0 = instant (pure DES), 0.01 = 100x speed, 0.1 = 10x speed, 1.0 = real-time
* Obtém o fator de escala temporal para visualização/execução.
* <ul>
* <li>0.0: Execução instantânea (DES puro, velocidade máxima).</li>
* <li>1.0: Tempo real (1 segundo simulado = 1 segundo real).</li>
* <li>0.01: Acelerado 100x.</li>
* </ul>
* @return O fator de escala.
*/
public double getTimeScale() {
return Double.parseDouble(properties.getProperty("simulation.time.scale", "0"));
}
/**
* Gets the drain time (in virtual seconds) to allow vehicles to exit after
* generation stops.
*
* @return The drain time.
* Obtém o tempo de "drenagem" (drain time) em segundos virtuais.
* <p>
* Este é o período adicional executado após o fim da geração de veículos para permitir
* que os veículos restantes no sistema completem os seus percursos.
* @return O tempo de drenagem (padrão: 60.0s).
*/
public double getDrainTime() {
return Double.parseDouble(properties.getProperty("simulation.drain.time", "60.0"));
}
/**
* Gets the vehicle arrival model ("POISSON" or "FIXED").
*
* @return The arrival model as a string.
* Determina o modelo estocástico utilizado para a chegada de veículos.
* @return "POISSON" (distribuição exponencial) ou "FIXED" (intervalo determinístico).
*/
public String getArrivalModel() {
return properties.getProperty("simulation.arrival.model", "POISSON");
}
/**
* Gets the average arrival rate (lambda) for the POISSON model.
* This represents the average number of vehicles arriving per second.
*
* @return The arrival rate.
* Obtém a taxa média de chegada (lambda) para o modelo Poisson.
* @return Veículos por segundo (padrão: 0.5).
*/
public double getArrivalRate() {
return Double.parseDouble(properties.getProperty("simulation.arrival.rate", "0.5"));
}
/**
* Gets the fixed time interval between vehicle arrivals for the FIXED model.
*
* @return The fixed interval in seconds.
* Obtém o intervalo fixo entre chegadas para o modelo determinístico.
* @return O intervalo em segundos (padrão: 2.0).
*/
public double getFixedArrivalInterval() {
return Double.parseDouble(properties.getProperty("simulation.arrival.fixed.interval", "2.0"));
}
/**
* Obtém a política de roteamento utilizada pelos veículos para navegar na malha.
* @return A política: "RANDOM", "SHORTEST_PATH" ou "LEAST_CONGESTED".
*/
public String getRoutingPolicy() {
return properties.getProperty("simulation.routing.policy", "RANDOM");
}
// --- Traffic light configurations ---
/**
* Gets the duration of the GREEN light state for a specific traffic light.
*
* @param intersectionId The ID of the intersection (e.g., "Cr1").
* @param direction The direction of the light (e.g., "North").
* @return The green light time in seconds.
* Obtém a duração do estado VERDE para um semáforo específico.
* * @param intersectionId ID da interseção.
* @param direction Direção do fluxo (ex: "North").
* @return Duração em segundos (padrão: 30.0).
*/
public double getTrafficLightGreenTime(String intersectionId, String direction) {
String key = "trafficlight." + intersectionId + "." + direction + ".green";
@@ -285,11 +317,10 @@ public class SimulationConfig {
}
/**
* Gets the duration of the RED light state for a specific traffic light.
*
* @param intersectionId The ID of the intersection (e.g., "Cr1").
* @param direction The direction of the light (e.g., "North").
* @return The red light time in seconds.
* Obtém a duração do estado VERMELHO para um semáforo específico.
* * @param intersectionId ID da interseção.
* @param direction Direção do fluxo.
* @return Duração em segundos (padrão: 30.0).
*/
public double getTrafficLightRedTime(String intersectionId, String direction) {
String key = "trafficlight." + intersectionId + "." + direction + ".red";
@@ -299,83 +330,74 @@ public class SimulationConfig {
// --- Vehicle configurations ---
/**
* Gets the probability (0.0 to 1.0) that a generated vehicle is of type LIGHT.
*
* @return The probability for LIGHT vehicles.
* Probabilidade (0.0 a 1.0) de geração de um veículo do tipo LIGEIRO (LIGHT).
* @return Probabilidade (padrão: 0.7).
*/
public double getLightVehicleProbability() {
return Double.parseDouble(properties.getProperty("vehicle.probability.light", "0.7"));
}
/**
* Gets the average time it takes a LIGHT vehicle to cross an intersection.
*
* @return The crossing time in seconds.
* Tempo médio necessário para um veículo LIGEIRO atravessar uma interseção.
* @return Tempo em segundos (padrão: 2.0).
*/
public double getLightVehicleCrossingTime() {
return Double.parseDouble(properties.getProperty("vehicle.crossing.time.light", "2.0"));
}
/**
* Gets the probability (0.0 to 1.0) that a generated vehicle is of type BIKE.
*
* @return The probability for BIKE vehicles.
* Probabilidade (0.0 a 1.0) de geração de um veículo do tipo BICICLETA (BIKE).
* @return Probabilidade (padrão: 0.0).
*/
public double getBikeVehicleProbability() {
return Double.parseDouble(properties.getProperty("vehicle.probability.bike", "0.0"));
}
/**
* Gets the average time it takes a BIKE vehicle to cross an intersection.
*
* @return The crossing time in seconds.
* Tempo médio necessário para uma BICICLETA atravessar uma interseção.
* @return Tempo em segundos (padrão: 1.5).
*/
public double getBikeVehicleCrossingTime() {
return Double.parseDouble(properties.getProperty("vehicle.crossing.time.bike", "1.5"));
}
/**
* Gets the probability (0.0 to 1.0) that a generated vehicle is of type HEAVY.
*
* @return The probability for HEAVY vehicles.
* Probabilidade (0.0 a 1.0) de geração de um veículo PESADO (HEAVY).
* @return Probabilidade (padrão: 0.0).
*/
public double getHeavyVehicleProbability() {
return Double.parseDouble(properties.getProperty("vehicle.probability.heavy", "0.0"));
}
/**
* Gets the average time it takes a HEAVY vehicle to cross an intersection.
*
* @return The crossing time in seconds.
* Tempo médio necessário para um veículo PESADO atravessar uma interseção.
* @return Tempo em segundos (padrão: 4.0).
*/
public double getHeavyVehicleCrossingTime() {
return Double.parseDouble(properties.getProperty("vehicle.crossing.time.heavy", "4.0"));
}
/**
* Gets the base travel time between intersections for light vehicles.
*
* @return The base travel time in seconds.
* Define o tempo base de viagem entre interseções para veículos padrão.
* @return Tempo em segundos (padrão: 8.0).
*/
public double getBaseTravelTime() {
return Double.parseDouble(properties.getProperty("vehicle.travel.time.base", "8.0"));
}
/**
* Gets the travel time multiplier for bike vehicles.
* Bike travel time = base time × this multiplier.
*
* @return The multiplier for bike travel time.
* Multiplicador de tempo de viagem para bicicletas.
* <p>Tempo efetivo = Base * Multiplicador.
* @return Fator multiplicativo (padrão: 0.5).
*/
public double getBikeTravelTimeMultiplier() {
return Double.parseDouble(properties.getProperty("vehicle.travel.time.bike.multiplier", "0.5"));
}
/**
* Gets the travel time multiplier for heavy vehicles.
* Heavy vehicle travel time = base time × this multiplier.
*
* @return The multiplier for heavy vehicle travel time.
* Multiplicador de tempo de viagem para veículos pesados.
* <p>Tempo efetivo = Base * Multiplicador.
* @return Fator multiplicativo (padrão: 4.0).
*/
public double getHeavyTravelTimeMultiplier() {
return Double.parseDouble(properties.getProperty("vehicle.travel.time.heavy.multiplier", "4.0"));
@@ -384,9 +406,8 @@ public class SimulationConfig {
// --- Statistics ---
/**
* Gets the interval (in virtual seconds) between periodic statistics updates.
*
* @return The statistics update interval.
* Intervalo de tempo (em segundos virtuais) para agregação e envio de estatísticas periódicas.
* @return Intervalo de atualização (padrão: 1.0).
*/
public double getStatisticsUpdateInterval() {
return Double.parseDouble(properties.getProperty("statistics.update.interval", "1.0"));
@@ -395,21 +416,19 @@ public class SimulationConfig {
// --- Generic getters ---
/**
* Generic method to get any property as a string, with a default value.
*
* @param key The property key.
* @param defaultValue The value to return if the key is not found.
* @return The property value or the default.
* Recupera uma propriedade genérica como String, com valor padrão de segurança.
* * @param key A chave da propriedade.
* @param defaultValue O valor a retornar caso a chave não exista.
* @return O valor da propriedade ou o default.
*/
public String getProperty(String key, String defaultValue) {
return properties.getProperty(key, defaultValue);
}
/**
* Generic method to get any property as a string.
*
* @param key The property key.
* @return The property value, or null if not found.
* Recupera uma propriedade genérica como String.
* * @param key A chave da propriedade.
* @return O valor da propriedade ou null se não encontrada.
*/
public String getProperty(String key) {
return properties.getProperty(key);

View File

@@ -5,6 +5,7 @@ import java.util.HashMap;
import java.util.Map;
import sd.config.SimulationConfig;
import sd.dashboard.DashboardStatistics;
import sd.dashboard.StatsUpdatePayload;
import sd.des.DESEventType;
import sd.des.EventQueue;
@@ -14,37 +15,62 @@ import sd.logging.EventLogger;
import sd.model.Message;
import sd.model.MessageType;
import sd.model.Vehicle;
import sd.routing.LeastCongestedRouteSelector;
import sd.routing.RandomRouteSelector;
import sd.routing.RouteSelector;
import sd.routing.RoutingPolicy;
import sd.routing.ShortestPathRouteSelector;
import sd.serialization.SerializationException;
import sd.util.VehicleGenerator;
/**
* Coordenador central da simulação distribuída.
*
* <p>Responsabilidades:
* Coordenador central da arquitetura de simulação distribuída.
* <p>
* Este processo atua como o "cérebro" da simulação, sendo responsável por:
* <ol>
* <li>Gerar veículos segundo modelo configurado (Poisson/Fixed)
* <li>Injetar veículos nas interseções de entrada
* <li>Gerir relógio global e sincronizar componentes
* <li><b>Orquestração DES:</b> Gerir o relógio global ({@link SimulationClock}) e a fila de eventos prioritária.</li>
* <li><b>Geração de Carga:</b> Injetar veículos na malha viária seguindo distribuições estocásticas (Poisson) ou determinísticas.</li>
* <li><b>Encaminhamento Dinâmico:</b> Decidir as rotas dos veículos com base na política ativa (Random, Shortest Path, Least Congested).</li>
* <li><b>Sincronização:</b> Garantir que todos os nós (Interseções e Dashboard) operem em uníssono.</li>
* </ol>
*
* <p>Usa motor DES para agendar eventos de geração com precisão.
* Mantém fila de prioridade e processa eventos em ordem cronológica.
*/
public class CoordinatorProcess {
private final SimulationConfig config;
private final VehicleGenerator vehicleGenerator;
/** Mapa de clientes TCP persistentes para cada interseção (Worker Nodes). */
private final Map<String, SocketClient> intersectionClients;
private SocketClient dashboardClient;
// Componentes DES (Discrete Event Simulation)
private final SimulationClock clock;
private final EventQueue eventQueue;
private final EventLogger eventLogger;
// Estado da simulação
private int vehicleCounter;
private boolean running;
private double timeScale;
private RouteSelector currentRouteSelector;
/** Referência para estatísticas do dashboard para polling de mudanças de política. */
private DashboardStatistics dashboardStatistics;
/**
* Monitorização local (aproximada) dos tamanhos de fila nas interseções.
* <p>
* Utilizado exclusivamente pela política {@link LeastCongestedRouteSelector}.
* O coordenador incrementa este contador ao enviar um veículo para uma interseção.
* Nota: Esta é uma visão "borda" (edge) e pode não refletir a saída em tempo real
* dos veículos, mas serve como heurística suficiente para balanceamento de carga.
*/
private final Map<String, Integer> intersectionQueueSizes;
/**
* Ponto de entrada do processo Coordenador.
* Carrega configurações, estabelece conexões TCP e inicia o loop de eventos.
*/
public static void main(String[] args) {
System.out.println("=".repeat(60));
System.out.println("COORDINATOR PROCESS - DISTRIBUTED TRAFFIC SIMULATION");
@@ -75,13 +101,24 @@ public class CoordinatorProcess {
}
}
/**
* Inicializa o coordenador com a configuração fornecida.
* Configura o motor DES, logging e o seletor de rotas inicial.
*
* @param config Objeto de configuração carregado.
*/
public CoordinatorProcess(SimulationConfig config) {
this.config = config;
this.vehicleGenerator = new VehicleGenerator(config);
// Inicializa o RouteSelector baseado na política configurada
this.currentRouteSelector = createRouteSelector(config.getRoutingPolicy());
this.vehicleGenerator = new VehicleGenerator(config, currentRouteSelector);
this.intersectionClients = new HashMap<>();
this.vehicleCounter = 0;
this.running = false;
this.timeScale = config.getTimeScale();
this.intersectionQueueSizes = new HashMap<>();
this.clock = new SimulationClock();
this.eventQueue = new EventQueue(true);
@@ -94,9 +131,46 @@ public class CoordinatorProcess {
System.out.println(" - Simulation duration: " + config.getSimulationDuration() + "s");
System.out.println(" - Arrival model: " + config.getArrivalModel());
System.out.println(" - Arrival rate: " + config.getArrivalRate() + " vehicles/s");
System.out.println(" - Routing policy: " + config.getRoutingPolicy());
System.out.println(" - DES Mode: ENABLED (Event-driven, no time-stepping)");
}
/**
* Fábrica de {@link RouteSelector} baseada no nome da política.
* * @param policyName Nome da política (RANDOM, SHORTEST_PATH, LEAST_CONGESTED).
* @return Uma instância da estratégia de roteamento.
*/
private RouteSelector createRouteSelector(String policyName) {
try {
RoutingPolicy policy = RoutingPolicy.valueOf(policyName.toUpperCase());
switch (policy) {
case RANDOM:
System.out.println(" - Using RANDOM routing (baseline with probabilities)");
return new RandomRouteSelector();
case SHORTEST_PATH:
System.out.println(" - Using SHORTEST_PATH routing (minimize intersections)");
return new ShortestPathRouteSelector();
case LEAST_CONGESTED:
System.out.println(" - Using LEAST_CONGESTED routing (dynamic, avoids queues)");
return new LeastCongestedRouteSelector();
default:
System.err.println(" ! Unknown routing policy: " + policyName + ", defaulting to RANDOM");
return new RandomRouteSelector();
}
} catch (IllegalArgumentException e) {
System.err.println(" ! Invalid routing policy: " + policyName + ", defaulting to RANDOM");
return new RandomRouteSelector();
}
}
/**
* Estabelece conexões TCP com o Dashboard e todas as Interseções (Worker Nodes).
* Essencial para o envio de comandos de controle e injeção de veículos.
*/
public void initialize() {
// Connect to dashboard first
connectToDashboard();
@@ -126,6 +200,15 @@ public class CoordinatorProcess {
}
}
/**
* Loop principal da simulação (DES Engine).
* <p>
* Executa a sequência:
* 1. Retira o próximo evento da fila prioritária.
* 2. Avança o relógio virtual para o timestamp do evento.
* 3. Aplica escala temporal (Time Scale) para visualização, se necessário.
* 4. Processa o evento.
*/
public void run() {
double duration = config.getSimulationDuration();
double drainTime = config.getDrainTime();
@@ -205,14 +288,9 @@ public class CoordinatorProcess {
}
/**
* Trata um único evento de simulação.
*
* É aqui que a magia acontece. Dependendo do tipo de evento (como
* VEHICLE_GENERATION),
* atualizamos o estado do mundo. Para a geração de veículos, criamos um novo
* veículo,
* enviamo-lo para uma interseção e depois agendamos o *próximo* evento de
* geração.
* Trata o processamento de um evento DES retirado da fila.
* * @param event O evento a ser processado.
* @param generationDuration Duração da fase de geração ativa (antes do 'drain time').
*/
private void processEvent(SimulationEvent event, double generationDuration) {
double currentTime = clock.getCurrentTime();
@@ -221,9 +299,12 @@ public class CoordinatorProcess {
case VEHICLE_GENERATION:
// Only generate if we're still in the generation phase
if (currentTime < generationDuration) {
// Check for routing policy changes from dashboard
checkForPolicyChanges();
generateAndSendVehicle();
// Schedule next vehicle generation
// Schedule next vehicle generation (Recursive scheduling)
double nextArrivalTime = vehicleGenerator.getNextArrivalTime(currentTime);
eventQueue.schedule(new SimulationEvent(
nextArrivalTime,
@@ -247,9 +328,8 @@ public class CoordinatorProcess {
}
/**
* Guarda o histórico completo de eventos de simulação num ficheiro de texto.
* Isto permite-nos auditar exatamente o que aconteceu e quando, o que é crucial
* para depuração e verificação.
* Exporta o log completo de eventos DES para auditoria e debug.
* Caminho: {@code logs/coordinator-event-history.txt}.
*/
private void exportEventHistory() {
try (java.io.PrintWriter writer = new java.io.PrintWriter(
@@ -262,9 +342,16 @@ public class CoordinatorProcess {
}
}
/**
* Gera um novo veículo e envia-o via TCP para a interseção de entrada apropriada.
* Também atualiza o rastreio local de filas para balanceamento de carga.
*/
private void generateAndSendVehicle() {
double currentTime = clock.getCurrentTime();
Vehicle vehicle = vehicleGenerator.generateVehicle("V" + (++vehicleCounter), currentTime);
// Usa os tamanhos de fila rastreados localmente para política LEAST_CONGESTED
// Isto permite roteamento dinâmico baseado no estado atual da rede
Vehicle vehicle = vehicleGenerator.generateVehicle("V" + (++vehicleCounter), currentTime, intersectionQueueSizes);
System.out.printf("[t=%.2f] Vehicle %s generated (type=%s, route=%s)%n",
currentTime, vehicle.getId(), vehicle.getType(), vehicle.getRoute());
@@ -273,6 +360,11 @@ public class CoordinatorProcess {
eventLogger.log(sd.logging.EventType.VEHICLE_GENERATED, "Coordinator",
String.format("[%s] Type: %s, Route: %s", vehicle.getId(), vehicle.getType(), vehicle.getRoute()));
// Update local queue size tracking (increment first intersection's queue)
String firstIntersection = vehicle.getRoute().get(0);
intersectionQueueSizes.put(firstIntersection,
intersectionQueueSizes.getOrDefault(firstIntersection, 0) + 1);
// Send generation count to dashboard
sendGenerationStatsToDashboard();
@@ -285,6 +377,9 @@ public class CoordinatorProcess {
sendVehicleToIntersection(vehicle, entryIntersection);
}
/**
* Serializa e transmite o objeto Veículo para o nó (interseção) de destino.
*/
private void sendVehicleToIntersection(Vehicle vehicle, String intersectionId) {
SocketClient client = intersectionClients.get(intersectionId);
@@ -309,6 +404,9 @@ public class CoordinatorProcess {
}
}
/**
* Encerra graciosamente a simulação, enviando sinais de SHUTDOWN para todos os nós.
*/
public void shutdown() {
System.out.println();
System.out.println("=".repeat(60));
@@ -344,6 +442,65 @@ public class CoordinatorProcess {
running = false;
}
/**
* Altera dinamicamente a política de roteamento durante a simulação (Hot-swap).
* Thread-safe.
* * @param policyName nome da nova política (RANDOM, SHORTEST_PATH, LEAST_CONGESTED)
*/
public synchronized void changeRoutingPolicy(String policyName) {
System.out.println("\n" + "=".repeat(60));
System.out.println("ROUTING POLICY CHANGE REQUEST");
System.out.println("=".repeat(60));
System.out.println("Current policy: " + getCurrentPolicyName());
System.out.println("Requested policy: " + policyName);
RouteSelector newSelector = createRouteSelector(policyName);
this.currentRouteSelector = newSelector;
this.vehicleGenerator.setRouteSelector(newSelector);
System.out.println("Routing policy successfully changed to: " + policyName);
System.out.println(" - New vehicles will use the updated policy");
System.out.println("=".repeat(60) + "\n");
eventLogger.log(sd.logging.EventType.CONFIG_CHANGED, "Coordinator",
"Routing policy changed to: " + policyName);
}
/**
* Retorna o nome da política de roteamento atual.
*/
private String getCurrentPolicyName() {
if (currentRouteSelector instanceof RandomRouteSelector) {
return "RANDOM";
} else if (currentRouteSelector instanceof ShortestPathRouteSelector) {
return "SHORTEST_PATH";
} else if (currentRouteSelector instanceof LeastCongestedRouteSelector) {
return "LEAST_CONGESTED";
}
return "UNKNOWN";
}
/**
* Verifica se há solicitação de mudança de política proveniente do dashboard
* e aplica a alteração se houver.
*/
private void checkForPolicyChanges() {
if (dashboardStatistics != null) {
String requestedPolicy = dashboardStatistics.getAndClearRequestedRoutingPolicy();
if (requestedPolicy != null && !requestedPolicy.isEmpty()) {
changeRoutingPolicy(requestedPolicy);
}
}
}
/**
* Injeta a referência para as estatísticas do dashboard.
* Permite que o coordenador consuma intenções de mudança de política do utilizador.
*/
public void setDashboardStatistics(DashboardStatistics stats) {
this.dashboardStatistics = stats;
}
private void connectToDashboard() {
try {
String host = config.getDashboardHost();
@@ -382,6 +539,11 @@ public class CoordinatorProcess {
}
}
/**
* Sincronização Global: Envia o timestamp de início (System.currentTimeMillis)
* para todos os componentes distribuídos, garantindo uma base de tempo comum
* para métricas de latência.
*/
private void sendSimulationStartTime() {
long startTimeMillis = System.currentTimeMillis();

View File

@@ -10,10 +10,14 @@ import sd.serialization.SerializationException;
import sd.serialization.SerializerFactory;
/**
* Cliente socket para comunicação com um processo de interseção.
*
* <p>Gere uma ligação TCP persistente para uma interseção,
* fornecendo uma forma simples de enviar mensagens serializadas.</p>
* Abstração de cliente TCP para comunicação outbound (de saída) com nós da rede.
* <p>
* Esta classe encapsula a gestão do socket raw, oferecendo uma interface de alto nível
* para envio de objetos {@link Message}. Implementa o protocolo de camada de aplicação
* proprietário, garantindo a serialização correta e o enquadramento (framing) dos dados
* na stream TCP.
* <p>
* É utilizada pelo Coordenador para controlar Interseções e enviar telemetria para o Dashboard.
*/
public class SocketClient {
@@ -25,11 +29,11 @@ public class SocketClient {
private MessageSerializer serializer;
/**
* Cria um novo cliente socket para uma interseção.
* Instancia um novo cliente socket configurado para um destino específico.
*
* @param intersectionId ID da interseção (ex: "Cr1")
* @param host endereço do host (ex: "localhost")
* @param port número da porta
* @param intersectionId Identificador lógico do nó de destino (ex: "Cr1", "Dashboard").
* @param host Endereço IP ou hostname do destino.
* @param port Porta TCP de escuta do destino.
*/
public SocketClient(String intersectionId, String host, int port) {
this.intersectionId = intersectionId;
@@ -39,9 +43,8 @@ public class SocketClient {
}
/**
* Liga-se ao processo da interseção via TCP.
*
* @throws IOException se a ligação não puder ser estabelecida
* Estabelece a conexão TCP (Handshake SYN/ACK) com o host remoto.
* * @throws IOException Se o host for inalcançável ou a conexão for recusada.
*/
public void connect() throws IOException {
try {
@@ -55,12 +58,22 @@ public class SocketClient {
}
/**
* Envia uma mensagem para a interseção ligada.
* A mensagem é serializada e enviada pelo socket.
* Serializa e transmite uma mensagem através do socket conectado.
* <p>
* <b>Protocolo de Envio (Length-Prefix Framing):</b>
* <ol>
* <li>Serializa o objeto {@link Message} para um array de bytes.</li>
* <li>Calcula o tamanho (N) do array.</li>
* <li>Escreve um cabeçalho de 4 bytes contendo N (Big-Endian).</li>
* <li>Escreve os N bytes do payload (corpo da mensagem).</li>
* <li>Realiza flush no stream para forçar o envio imediato do pacote TCP.</li>
* </ol>
* Este mecanismo garante que o recetor saiba exatamente quantos bytes ler,
* prevenindo problemas de fragmentação ou aglutinação de pacotes TCP.
*
* @param message mensagem a enviar
* @throws SerializationException se a serialização falhar
* @throws IOException se a escrita no socket falhar
* @param message O objeto de domínio a ser enviado.
* @throws SerializationException Se o objeto não puder ser convertido para bytes.
* @throws IOException Se houver falha na escrita do socket (ex: conexão resetada).
*/
public void send(Message message) throws SerializationException, IOException {
if (socket == null || socket.isClosed()) {
@@ -71,11 +84,13 @@ public class SocketClient {
byte[] data = serializer.serialize(message);
int length = data.length;
// Write 4-byte length header (Big Endian)
outputStream.write((length >> 24) & 0xFF);
outputStream.write((length >> 16) & 0xFF);
outputStream.write((length >> 8) & 0xFF);
outputStream.write(length & 0xFF);
// Write payload
outputStream.write(data);
outputStream.flush();
@@ -86,8 +101,10 @@ public class SocketClient {
}
/**
* Closes the socket connection safely.
* Calling it multiple times wont cause issues.
* Realiza o encerramento gracioso (graceful shutdown) da conexão.
* Liberta os recursos do sistema operativo (descritores de arquivo).
* <p>
* Operação idempotente: pode ser chamada múltiplas vezes sem erro.
*/
public void close() {
try {
@@ -104,7 +121,8 @@ public class SocketClient {
}
/**
* @return true if connected and socket is open, false otherwise
* Verifica o estado atual da conexão.
* * @return true se o socket estiver instanciado, conectado e aberto; false caso contrário.
*/
public boolean isConnected() {
return socket != null && socket.isConnected() && !socket.isClosed();

View File

@@ -0,0 +1,576 @@
package sd.dashboard;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.Spinner;
import javafx.scene.control.TextArea;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Modality;
import javafx.stage.Stage;
import sd.analysis.MultiRunAnalyzer;
import sd.analysis.SimulationRunResult;
import sd.model.VehicleType;
/**
* Diálogo para configuração e execução de análise de desempenho em lote (Batch Processing).
* <p>
* Esta classe fornece uma interface gráfica para automatizar múltiplas execuções da simulação
* sob diferentes cenários de carga. É responsável por:
* <ol>
* <li>Orquestrar o ciclo de vida dos processos de simulação (start/stop/wait).</li>
* <li>Coletar métricas estatísticas de cada execução.</li>
* <li>Agregar resultados usando o {@link MultiRunAnalyzer}.</li>
* <li>Gerar relatórios consolidados para análise de variância e intervalos de confiança.</li>
* </ol>
* A execução ocorre numa thread separada (background) para manter a responsividade da UI.
*/
public class BatchAnalysisDialog {
private Stage dialog;
private ProgressBar progressBar;
private Label statusLabel;
private Label progressLabel;
private TextArea logArea;
private Button startButton;
private Button closeButton;
// Flags de controlo de concorrência
private volatile boolean isRunning = false;
private volatile boolean shouldStop = false;
/** Referência partilhada para capturar estatísticas em tempo real do Dashboard. */
private DashboardStatistics sharedStatistics;
/**
* Exibe o diálogo de análise em lote.
* * @param owner A janela pai (Stage) para modalidade.
* @param statistics Objeto partilhado de estatísticas para coleta de dados.
*/
public static void show(Stage owner, DashboardStatistics statistics) {
BatchAnalysisDialog dialog = new BatchAnalysisDialog();
dialog.sharedStatistics = statistics;
dialog.createAndShow(owner);
}
/**
* Constrói e inicializa a interface gráfica do diálogo.
*/
private void createAndShow(Stage owner) {
dialog = new Stage();
dialog.initOwner(owner);
dialog.initModality(Modality.APPLICATION_MODAL);
dialog.setTitle("Batch Performance Analysis");
VBox root = new VBox(20);
root.setPadding(new Insets(20));
root.setAlignment(Pos.TOP_CENTER);
// Estilo Dark Mode conforme guidelines visuais
root.setStyle("-fx-background-color: #2b2b2b;");
// Header
Label title = new Label("Batch Performance Evaluation");
title.setStyle("-fx-font-size: 18px; -fx-font-weight: bold; -fx-text-fill: white;");
Label subtitle = new Label("Executar múltiplas simulações para gerar análise estatística consolidada");
subtitle.setStyle("-fx-font-size: 12px; -fx-text-fill: #cccccc;");
subtitle.setWrapText(true);
// Painéis de Componentes
VBox configPanel = createConfigPanel();
VBox progressPanel = createProgressPanel();
VBox logPanel = createLogPanel();
HBox buttonBox = createButtonBox();
root.getChildren().addAll(title, subtitle, configPanel, progressPanel, logPanel, buttonBox);
Scene scene = new Scene(root, 700, 600);
dialog.setScene(scene);
// Tratamento de fecho da janela: interromper thread de worker se ativa
dialog.setOnCloseRequest(e -> {
if (isRunning) {
e.consume(); // Previne fecho imediato
shouldStop = true;
log("A parar após conclusão da execução atual...");
}
});
dialog.show();
}
private VBox createConfigPanel() {
VBox panel = new VBox(15);
panel.setPadding(new Insets(15));
panel.setStyle("-fx-background-color: rgba(255, 255, 255, 0.05); -fx-background-radius: 5;");
Label header = new Label("Configuração");
header.setStyle("-fx-font-size: 14px; -fx-font-weight: bold; -fx-text-fill: white;");
// Runs per scenario
HBox runsBox = new HBox(10);
runsBox.setAlignment(Pos.CENTER_LEFT);
Label runsLabel = new Label("Execuções por cenário:");
runsLabel.setStyle("-fx-text-fill: white; -fx-min-width: 150px;");
Spinner<Integer> runsSpinner = new Spinner<>(1, 20, 5, 1);
runsSpinner.setEditable(true);
runsSpinner.setPrefWidth(80);
runsSpinner.setId("runsSpinner");
runsBox.getChildren().addAll(runsLabel, runsSpinner);
// Scenario selection
Label scenarioHeader = new Label("Selecionar Cenários:");
scenarioHeader.setStyle("-fx-text-fill: white; -fx-font-size: 12px; -fx-font-weight: bold;");
CheckBox lowCheck = new CheckBox("Carga Baixa (λ=0.2 v/s)");
lowCheck.setSelected(true);
lowCheck.setId("lowCheck");
lowCheck.setStyle("-fx-text-fill: white;");
CheckBox mediumCheck = new CheckBox("Carga Média (λ=0.5 v/s)");
mediumCheck.setSelected(true);
mediumCheck.setId("mediumCheck");
mediumCheck.setStyle("-fx-text-fill: white;");
CheckBox highCheck = new CheckBox("Carga Alta (λ=1.0 v/s)");
highCheck.setSelected(true);
highCheck.setId("highCheck");
highCheck.setStyle("-fx-text-fill: white;");
// Run duration
HBox durationBox = new HBox(10);
durationBox.setAlignment(Pos.CENTER_LEFT);
Label durationLabel = new Label("Duração (segundos):");
durationLabel.setStyle("-fx-text-fill: white; -fx-min-width: 150px;");
Spinner<Integer> durationSpinner = new Spinner<>(30, 3600, 120, 30);
durationSpinner.setEditable(true);
durationSpinner.setPrefWidth(80);
durationSpinner.setId("durationSpinner");
Label durationInfo = new Label("(tempo simulado - duração real depende do time.scale)");
durationInfo.setStyle("-fx-text-fill: #999999; -fx-font-size: 10px;");
durationBox.getChildren().addAll(durationLabel, durationSpinner, durationInfo);
panel.getChildren().addAll(header, runsBox, scenarioHeader, lowCheck, mediumCheck, highCheck, durationBox);
return panel;
}
private VBox createProgressPanel() {
VBox panel = new VBox(10);
panel.setPadding(new Insets(15));
panel.setStyle("-fx-background-color: rgba(255, 255, 255, 0.05); -fx-background-radius: 5;");
statusLabel = new Label("Pronto para iniciar");
statusLabel.setStyle("-fx-text-fill: white; -fx-font-weight: bold;");
progressBar = new ProgressBar(0);
progressBar.setPrefWidth(Double.MAX_VALUE);
progressBar.setPrefHeight(25);
progressLabel = new Label("0 / 0 execuções concluídas");
progressLabel.setStyle("-fx-text-fill: #cccccc; -fx-font-size: 11px;");
panel.getChildren().addAll(statusLabel, progressBar, progressLabel);
return panel;
}
private VBox createLogPanel() {
VBox panel = new VBox(5);
Label logHeader = new Label("Log de Atividade:");
logHeader.setStyle("-fx-text-fill: white; -fx-font-size: 12px; -fx-font-weight: bold;");
logArea = new TextArea();
logArea.setEditable(false);
logArea.setPrefRowCount(10);
logArea.setWrapText(true);
// Estilo de terminal para o log
logArea.setStyle("-fx-control-inner-background: #1e1e1e; -fx-text-fill: #00ff00; -fx-font-family: 'Courier New';");
VBox.setVgrow(logArea, Priority.ALWAYS);
panel.getChildren().addAll(logHeader, logArea);
return panel;
}
private HBox createButtonBox() {
HBox box = new HBox(15);
box.setAlignment(Pos.CENTER);
box.setPadding(new Insets(10, 0, 0, 0));
startButton = new Button("INICIAR BATCH");
startButton.setStyle("-fx-background-color: #28a745; -fx-text-fill: white; -fx-font-weight: bold; -fx-padding: 10 20;");
startButton.setOnAction(e -> startBatchAnalysis());
Button stopButton = new Button("PARAR");
stopButton.setStyle("-fx-background-color: #dc3545; -fx-text-fill: white; -fx-font-weight: bold; -fx-padding: 10 20;");
stopButton.setOnAction(e -> {
shouldStop = true;
log("Paragem solicitada...");
});
closeButton = new Button("FECHAR");
closeButton.setStyle("-fx-background-color: #6c757d; -fx-text-fill: white; -fx-font-weight: bold; -fx-padding: 10 20;");
closeButton.setOnAction(e -> dialog.close());
box.getChildren().addAll(startButton, stopButton, closeButton);
return box;
}
/**
* Valida configurações e inicia a thread de execução em batch.
*/
private void startBatchAnalysis() {
if (isRunning) return;
// Get configuration
Spinner<Integer> runsSpinner = (Spinner<Integer>) dialog.getScene().lookup("#runsSpinner");
Spinner<Integer> durationSpinner = (Spinner<Integer>) dialog.getScene().lookup("#durationSpinner");
CheckBox lowCheck = (CheckBox) dialog.getScene().lookup("#lowCheck");
CheckBox mediumCheck = (CheckBox) dialog.getScene().lookup("#mediumCheck");
CheckBox highCheck = (CheckBox) dialog.getScene().lookup("#highCheck");
int runsPerScenario = runsSpinner.getValue();
int duration = durationSpinner.getValue();
// Validate selection
if (!lowCheck.isSelected() && !mediumCheck.isSelected() && !highCheck.isSelected()) {
log("ERRO: Selecione pelo menos um cenário!");
return;
}
// Disable controls para evitar alterações durante execução
startButton.setDisable(true);
runsSpinner.setDisable(true);
durationSpinner.setDisable(true);
lowCheck.setDisable(true);
mediumCheck.setDisable(true);
highCheck.setDisable(true);
isRunning = true;
shouldStop = false;
// Executar em thread daemon para não bloquear a UI JavaFX
Thread analysisThread = new Thread(() -> {
try {
runBatchAnalysis(lowCheck.isSelected(), mediumCheck.isSelected(),
highCheck.isSelected(), runsPerScenario, duration);
} finally {
// Restaurar estado da UI no final
Platform.runLater(() -> {
startButton.setDisable(false);
runsSpinner.setDisable(false);
durationSpinner.setDisable(false);
lowCheck.setDisable(false);
mediumCheck.setDisable(false);
highCheck.setDisable(false);
isRunning = false;
});
}
});
analysisThread.setDaemon(true);
analysisThread.start();
}
/**
* Lógica principal de orquestração do batch.
* Itera sobre cenários e execuções, chamando a simulação e o analisador.
*/
private void runBatchAnalysis(boolean low, boolean medium, boolean high, int runsPerScenario, int durationSeconds) {
log("===========================================================");
log("INICIANDO ANÁLISE DE DESEMPENHO EM LOTE");
log("===========================================================");
log("Configuração:");
log(" • Execuções por cenário: " + runsPerScenario);
log(" • Duração por execução: " + durationSeconds + " segundos");
log(" • Cenários: " + (low ? "LOW " : "") + (medium ? "MEDIUM " : "") + (high ? "HIGH" : ""));
log("");
String[] scenarios = new String[]{
low ? "simulation-low.properties" : null,
medium ? "simulation-medium.properties" : null,
high ? "simulation-high.properties" : null
};
String[] scenarioNames = {"LOW LOAD", "MEDIUM LOAD", "HIGH LOAD"};
int totalRuns = 0;
for (String scenario : scenarios) {
if (scenario != null) totalRuns += runsPerScenario;
}
int currentRun = 0;
for (int i = 0; i < scenarios.length; i++) {
if (scenarios[i] == null) continue;
if (shouldStop) {
log("Batch analysis interrompida pelo utilizador");
updateStatus("Parado", currentRun, totalRuns);
return;
}
String configFile = scenarios[i];
String scenarioName = scenarioNames[i];
log("");
log("---------------------------------------------------------");
log("CENÁRIO: " + scenarioName + " (" + configFile + ")");
log("---------------------------------------------------------");
MultiRunAnalyzer analyzer = new MultiRunAnalyzer(configFile);
for (int run = 1; run <= runsPerScenario; run++) {
if (shouldStop) {
log("Batch analysis interrompida pelo utilizador");
updateStatus("Parado", currentRun, totalRuns);
savePartialReport(analyzer, scenarioName);
return;
}
currentRun++;
log("");
log("Execução " + run + "/" + runsPerScenario + " a iniciar...");
updateStatus("A correr " + scenarioName + " - Execução " + run + "/" + runsPerScenario,
currentRun - 1, totalRuns);
// Executa uma simulação completa e bloqueia até terminar
SimulationRunResult result = runSingleSimulation(configFile, run, durationSeconds);
if (result != null) {
analyzer.addResult(result);
log("Execução " + run + " completa - Gerados: " + result.getTotalVehiclesGenerated() +
" | Completados: " + result.getTotalVehiclesCompleted() +
" | Tempo Médio: " + String.format("%.2f", result.getAverageSystemTime()) + "s");
} else {
log("Execução " + run + " falhou!");
}
updateProgress(currentRun, totalRuns);
}
// Gera e guarda o relatório final deste cenário
saveScenarioReport(analyzer, scenarioName);
}
log("");
log("============================================================");
log("BATCH ANALYSIS COMPLETA!");
log("===========================================================");
log("Relatórios guardados em: analysis/");
log("");
updateStatus("Concluído!", totalRuns, totalRuns);
updateProgress(1.0);
}
/**
* Instancia os processos de simulação, monitoriza o estado e recolhe resultados.
*/
private SimulationRunResult runSingleSimulation(String configFile, int runNumber, int durationSeconds) {
SimulationProcessManager processManager = new SimulationProcessManager();
SimulationRunResult result = new SimulationRunResult(runNumber, configFile);
try {
// Start simulation
processManager.setConfigFile(configFile);
processManager.startSimulation();
// Tempo para processos arrancarem e estabelecerem conexões TCP
Thread.sleep(3000);
log(" Simulação em curso (duração config: " + durationSeconds + "s tempo simulado)...");
log(" A aguardar processo Coordenador completar...");
// Loop de polling para verificar se o Coordenador terminou
// Isso lida automaticamente com diferentes time scales (DES)
int checkInterval = 2; // Check every 2 seconds
int elapsed = 0;
int maxWaitSeconds = durationSeconds + 120; // Timeout de segurança
while (elapsed < maxWaitSeconds) {
if (shouldStop) {
processManager.stopSimulation();
return null;
}
// Check if simulation completed
if (!processManager.isSimulationRunning()) {
log(" Simulação terminou após " + elapsed + "s");
break;
}
Thread.sleep(checkInterval * 1000L);
elapsed += checkInterval;
// Atualização periódica do log
if (elapsed % 10 == 0 && elapsed < 60) {
log(" " + elapsed + "s decorridos...");
}
}
if (elapsed >= maxWaitSeconds) {
log(" Timeout atingido, forçando paragem...");
}
// Stop and collect results
log(" A terminar processos...");
processManager.stopSimulation();
Thread.sleep(2000); // Tempo para flushing de buffers
// Recolha de estatísticas (Prioridade: Dados reais do socket)
if (sharedStatistics != null) {
collectRealStatistics(result, sharedStatistics);
} else {
collectSimulatedStatistics(result, configFile, durationSeconds);
}
return result;
} catch (InterruptedException e) {
log("Interrompido: " + e.getMessage());
Thread.currentThread().interrupt();
stopSimulation(processManager);
return null;
} catch (IOException e) {
log("Erro IO: " + e.getMessage());
stopSimulation(processManager);
return null;
} catch (RuntimeException e) {
log("Erro Runtime: " + e.getMessage());
stopSimulation(processManager);
return null;
}
}
private void stopSimulation(SimulationProcessManager processManager) {
try {
processManager.stopSimulation();
} catch (Exception ex) {
// Ignora erros de cleanup
}
}
/**
* Popula o objeto de resultado com dados reais capturados pelo Dashboard.
*/
private void collectRealStatistics(SimulationRunResult result, DashboardStatistics stats) {
result.setTotalVehiclesGenerated(stats.getTotalVehiclesGenerated());
result.setTotalVehiclesCompleted(stats.getTotalVehiclesCompleted());
result.setAverageSystemTime(stats.getAverageSystemTime() / 1000.0); // Converte ms para segundos
result.setAverageWaitingTime(stats.getAverageWaitingTime() / 1000.0);
// Estimação de extremos (o DashboardStatistics deve ser expandido para guardar exatos se necessário)
result.setMinSystemTime(stats.getAverageSystemTime() / 1000.0 * 0.5);
result.setMaxSystemTime(stats.getAverageSystemTime() / 1000.0 * 2.0);
// Estatísticas por tipo
for (VehicleType type : VehicleType.values()) {
int count = stats.getVehicleTypeCount(type);
double waitTime = stats.getAverageWaitingTimeByType(type) / 1000.0;
result.setVehicleCountByType(type, count);
result.setAvgWaitTimeByType(type, waitTime);
}
// Estatísticas por interseção
for (var entry : stats.getAllIntersectionStats().entrySet()) {
String intersectionId = entry.getKey();
DashboardStatistics.IntersectionStats iStats = entry.getValue();
result.setVehiclesProcessed(intersectionId, iStats.getTotalDepartures());
result.setMaxQueueSize(intersectionId, iStats.getCurrentQueueSize());
result.setAvgQueueSize(intersectionId, (double) iStats.getCurrentQueueSize());
}
}
/**
* Gera dados simulados (mock) caso o dashboard não esteja conectado.
* Útil para testes de interface.
*/
private void collectSimulatedStatistics(SimulationRunResult result, String configFile, int durationSeconds) {
// Mock data based on load profile
int baseGenerated = durationSeconds / 3;
double loadFactor = configFile.contains("low") ? 0.2 :
configFile.contains("medium") ? 0.5 : 1.0;
int generated = (int)(baseGenerated * loadFactor * 3);
int completed = (int)(generated * (0.85 + Math.random() * 0.1)); // 85-95% completion rate
double baseSystemTime = 40.0;
double congestionFactor = configFile.contains("low") ? 1.0 :
configFile.contains("medium") ? 1.5 : 2.5;
result.setTotalVehiclesGenerated(generated);
result.setTotalVehiclesCompleted(completed);
result.setAverageSystemTime(baseSystemTime * congestionFactor + Math.random() * 10);
result.setMinSystemTime(20.0 + Math.random() * 5);
result.setMaxSystemTime(baseSystemTime * congestionFactor * 2 + Math.random() * 20);
result.setAverageWaitingTime(10.0 * congestionFactor + Math.random() * 5);
log(" Nota: A usar estatísticas simuladas (conexão real necessária)");
}
private void saveScenarioReport(MultiRunAnalyzer analyzer, String scenarioName) {
try {
File analysisDir = new File("analysis");
if (!analysisDir.exists()) {
analysisDir.mkdirs();
}
String timestamp = new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date());
String reportFile = "analysis/" + scenarioName.replace(" ", "_") + "_" + timestamp + ".txt";
String csvFile = "analysis/" + scenarioName.replace(" ", "_") + "_" + timestamp + ".csv";
analyzer.saveReport(reportFile);
analyzer.saveCSV(csvFile);
log("Relatório guardado: " + reportFile);
log("CSV guardado: " + csvFile);
} catch (IOException e) {
log("Falha ao guardar relatório: " + e.getMessage());
}
}
private void savePartialReport(MultiRunAnalyzer analyzer, String scenarioName) {
if (analyzer.getRunCount() > 0) {
log("A guardar resultados parciais...");
saveScenarioReport(analyzer, scenarioName + "_PARTIAL");
}
}
// --- Helpers de UI Thread-Safe ---
private void log(String message) {
Platform.runLater(() -> {
logArea.appendText(message + "\n");
logArea.setScrollTop(Double.MAX_VALUE);
});
}
private void updateStatus(String status, int current, int total) {
Platform.runLater(() -> {
statusLabel.setText(status);
progressLabel.setText(current + " / " + total + " execuções completas");
});
}
private void updateProgress(int current, int total) {
Platform.runLater(() -> {
progressBar.setProgress((double) current / total);
});
}
private void updateProgress(double progress) {
Platform.runLater(() -> {
progressBar.setProgress(progress);
});
}
}

View File

@@ -13,16 +13,32 @@ import javafx.stage.Modality;
import javafx.stage.Stage;
/**
* Diálogo para configuração avançada de parâmetros da simulação.
* Permite ajustar parâmetros em runtime antes de iniciar a simulação.
* Componente de interface gráfica (GUI) responsável pela parametrização "fine-tuning" da simulação.
* <p>
* Esta classe apresenta um diálogo modal que permite ao operador sobrepor (override)
* as configurações estáticas carregadas do ficheiro {@code .properties} imediatamente
* antes da execução. Oferece controlo granular sobre:
* <ul>
* <li><b>Geração de Carga:</b> Alternância entre modelos estocásticos (Poisson) e determinísticos.</li>
* <li><b>Temporização:</b> Ajuste da escala de tempo (Time Scale) para visualização vs. performance pura.</li>
* <li><b>Mix de Veículos:</b> Definição das probabilidades de geração por tipo de agente.</li>
* </ul>
*/
public class ConfigurationDialog {
/**
* Mostra um diálogo com opções avançadas de configuração.
* Exibe o diálogo de configuração avançada e captura as intenções do utilizador.
* <p>
* A interface é construída dinamicamente usando layouts JavaFX ({@link GridPane}, {@link VBox}).
* Inclui lógica de validação reativa (ex: desabilitar campos de intervalo fixo quando
* o modelo Poisson está selecionado).
* *
[Image of Poisson distribution graph]
*
* @param owner janela pai
* @return true se o utilizador confirmar, false se cancelar
* @param owner A janela pai (Stage) para bloquear a interação até o fecho do diálogo (Modalidade).
* @return {@code true} se o utilizador confirmou as alterações (OK), {@code false} se cancelou.
*/
public static boolean showAdvancedConfig(Stage owner) {
Dialog<ButtonType> dialog = new Dialog<>();
@@ -34,10 +50,11 @@ public class ConfigurationDialog {
// Criar painel de configuração
VBox content = new VBox(15);
content.setPadding(new Insets(20));
content.setStyle("-fx-background-color: #2b2b2b;");
// Seção 1: Parâmetros de Chegada
Label arrivalHeader = new Label("Parâmetros de Chegada de Veículos");
arrivalHeader.setStyle("-fx-font-weight: bold; -fx-font-size: 14px;");
arrivalHeader.setStyle("-fx-font-weight: bold; -fx-font-size: 14px; -fx-text-fill: white;");
GridPane arrivalGrid = new GridPane();
arrivalGrid.setHgap(10);
@@ -46,6 +63,7 @@ public class ConfigurationDialog {
// Modelo de chegada
Label modelLabel = new Label("Modelo de chegada:");
modelLabel.setStyle("-fx-text-fill: white;");
ComboBox<String> modelCombo = new ComboBox<>();
modelCombo.getItems().addAll("POISSON", "FIXED");
modelCombo.setValue("POISSON");
@@ -54,6 +72,7 @@ public class ConfigurationDialog {
// Taxa de chegada (λ)
Label rateLabel = new Label("Taxa de chegada (λ) [veículos/s]:");
rateLabel.setStyle("-fx-text-fill: white;");
Spinner<Double> rateSpinner = new Spinner<>(0.1, 2.0, 0.5, 0.1);
rateSpinner.setEditable(true);
rateSpinner.setPrefWidth(100);
@@ -62,6 +81,7 @@ public class ConfigurationDialog {
// Intervalo fixo (se aplicável)
Label intervalLabel = new Label("Intervalo fixo [s]:");
intervalLabel.setStyle("-fx-text-fill: white;");
Spinner<Double> intervalSpinner = new Spinner<>(0.5, 10.0, 2.0, 0.5);
intervalSpinner.setEditable(true);
intervalSpinner.setPrefWidth(100);
@@ -78,7 +98,7 @@ public class ConfigurationDialog {
// Seção 2: Parâmetros de Tempo
Label timeHeader = new Label("Parâmetros de Tempo");
timeHeader.setStyle("-fx-font-weight: bold; -fx-font-size: 14px;");
timeHeader.setStyle("-fx-font-weight: bold; -fx-font-size: 14px; -fx-text-fill: white;");
GridPane timeGrid = new GridPane();
timeGrid.setHgap(10);
@@ -87,6 +107,7 @@ public class ConfigurationDialog {
// Duração da simulação
Label durationLabel = new Label("Duração da simulação [s]:");
durationLabel.setStyle("-fx-text-fill: white;");
Spinner<Integer> durationSpinner = new Spinner<>(60, 7200, 300, 60);
durationSpinner.setEditable(true);
durationSpinner.setPrefWidth(100);
@@ -95,6 +116,7 @@ public class ConfigurationDialog {
// Escala temporal (para visualização)
Label scaleLabel = new Label("Escala temporal (0=instantâneo, 1=tempo real):");
scaleLabel.setStyle("-fx-text-fill: white;");
Spinner<Double> scaleSpinner = new Spinner<>(0.0, 1.0, 0.01, 0.01);
scaleSpinner.setEditable(true);
scaleSpinner.setPrefWidth(100);
@@ -103,6 +125,7 @@ public class ConfigurationDialog {
// Tempo de drenagem
Label drainLabel = new Label("Tempo de drenagem [s]:");
drainLabel.setStyle("-fx-text-fill: white;");
Spinner<Integer> drainSpinner = new Spinner<>(0, 300, 60, 10);
drainSpinner.setEditable(true);
drainSpinner.setPrefWidth(100);
@@ -111,7 +134,7 @@ public class ConfigurationDialog {
// Seção 3: Distribuição de Tipos de Veículos
Label vehicleHeader = new Label("Distribuição de Tipos de Veículos");
vehicleHeader.setStyle("-fx-font-weight: bold; -fx-font-size: 14px;");
vehicleHeader.setStyle("-fx-font-weight: bold; -fx-font-size: 14px; -fx-text-fill: white;");
GridPane vehicleGrid = new GridPane();
vehicleGrid.setHgap(10);
@@ -119,6 +142,7 @@ public class ConfigurationDialog {
vehicleGrid.setPadding(new Insets(10));
Label bikeLabel = new Label("Bicicletas/Motos [%]:");
bikeLabel.setStyle("-fx-text-fill: white;");
Spinner<Integer> bikeSpinner = new Spinner<>(0, 100, 10, 5);
bikeSpinner.setEditable(true);
bikeSpinner.setPrefWidth(100);
@@ -126,6 +150,7 @@ public class ConfigurationDialog {
vehicleGrid.add(bikeSpinner, 1, 0);
Label lightLabel = new Label("Veículos Ligeiros [%]:");
lightLabel.setStyle("-fx-text-fill: white;");
Spinner<Integer> lightSpinner = new Spinner<>(0, 100, 70, 5);
lightSpinner.setEditable(true);
lightSpinner.setPrefWidth(100);
@@ -133,6 +158,7 @@ public class ConfigurationDialog {
vehicleGrid.add(lightSpinner, 1, 1);
Label heavyLabel = new Label("Veículos Pesados [%]:");
heavyLabel.setStyle("-fx-text-fill: white;");
Spinner<Integer> heavySpinner = new Spinner<>(0, 100, 20, 5);
heavySpinner.setEditable(true);
heavySpinner.setPrefWidth(100);
@@ -143,7 +169,7 @@ public class ConfigurationDialog {
Label noteLabel = new Label("Nota: Estes parâmetros sobrepõem os valores do ficheiro .properties selecionado.\n" +
"Para usar os valores padrão do ficheiro, deixe em branco ou cancele.");
noteLabel.setWrapText(true);
noteLabel.setStyle("-fx-font-size: 11px; -fx-text-fill: #666666;");
noteLabel.setStyle("-fx-font-size: 11px; -fx-text-fill: #aaaaaa;");
// Adicionar tudo ao conteúdo
content.getChildren().addAll(

View File

@@ -9,19 +9,42 @@ import sd.protocol.MessageProtocol;
import sd.protocol.SocketConnection;
/**
* Processes statistics messages from a single client connection.
* Runs in a separate thread per client.
* Worker responsável pelo processamento dedicado de uma conexão de cliente TCP no Dashboard.
* <p>
* Esta classe implementa o padrão <i>Thread-per-Client</i>. Cada instância executa numa
* thread separada, garantindo que a latência de rede ou o processamento de mensagens
* de um nó (Interseção/Coordenador) não bloqueie a receção de telemetria dos outros.
* <p>
* As suas principais funções são:
* <ol>
* <li>Manter a conexão persistente com o nó remoto.</li>
* <li>Desserializar mensagens de protocolo recebidas.</li>
* <li>Normalizar payloads JSON (resolvendo ambiguidades de tipagem do Gson).</li>
* <li>Atualizar o objeto partilhado {@link DashboardStatistics} de forma thread-safe.</li>
* </ol>
*/
public class DashboardClientHandler implements Runnable {
private final Socket clientSocket;
private final DashboardStatistics statistics;
/**
* Inicializa o handler com o socket ativo e a referência para o agregador de estatísticas.
*
* @param clientSocket O socket TCP conectado ao nó remoto.
* @param statistics O objeto singleton partilhado onde as métricas serão agregadas.
*/
public DashboardClientHandler(Socket clientSocket, DashboardStatistics statistics) {
this.clientSocket = clientSocket;
this.statistics = statistics;
}
/**
* Ciclo de vida da conexão.
* <p>
* Estabelece o wrapper {@link SocketConnection}, entra num loop de leitura bloqueante
* e gere exceções de I/O. Garante o fecho limpo do socket em caso de desconexão ou erro.
*/
@Override
public void run() {
String clientInfo = clientSocket.getInetAddress().getHostAddress() + ":" + clientSocket.getPort();
@@ -61,6 +84,16 @@ public class DashboardClientHandler implements Runnable {
}
}
/**
* Valida e extrai os dados estatísticos da mensagem.
* <p>
* Implementa uma lógica de correção de tipagem para payloads desserializados via Gson.
* Frequentemente, objetos genéricos são desserializados como {@code LinkedHashMap} em vez
* da classe alvo {@link StatsUpdatePayload}. Este método deteta essa situação e realiza
* uma conversão "round-trip" (Map -> JSON -> Object) para garantir a integridade dos dados.
*
* @param message A mensagem recebida da rede.
*/
private void processMessage(MessageProtocol message) {
if (message.getType() != MessageType.STATS_UPDATE) {
System.out.println("[Handler] Ignoring non-statistics message type: " + message.getType());
@@ -78,6 +111,7 @@ public class DashboardClientHandler implements Runnable {
stats = (StatsUpdatePayload) payload;
} else if (payload instanceof java.util.Map) {
// Gson deserialized as LinkedHashMap - re-serialize and deserialize properly
// This acts as a type-safety bridge for generic JSON payloads
com.google.gson.Gson gson = new com.google.gson.Gson();
String json = gson.toJson(payload);
stats = gson.fromJson(json, StatsUpdatePayload.class);
@@ -90,6 +124,15 @@ public class DashboardClientHandler implements Runnable {
updateStatistics(senderId, stats);
}
/**
* Aplica os dados recebidos ao modelo global de estatísticas.
* <p>
* Distingue entre atualizações incrementais (ex: contagem de veículos) e
* substituições de estado (ex: tempo total de sistema reportado pelo nó de saída).
*
* @param senderId Identificador do nó que enviou a atualização (ex: "Cr1", "ExitNode").
* @param stats O objeto DTO contendo as métricas normalizadas.
*/
private void updateStatistics(String senderId, StatsUpdatePayload stats) {
if (stats.getTotalVehiclesGenerated() >= 0) {
statistics.updateVehiclesGenerated(stats.getTotalVehiclesGenerated());

View File

@@ -10,17 +10,43 @@ import java.util.concurrent.atomic.AtomicBoolean;
import sd.config.SimulationConfig;
/**
* Agrega e apresenta estatísticas em tempo real de todos os processos da simulação.
* Usa um thread pool para gerir ligações concorrentes de clientes.
* Servidor central de agregação de telemetria e estatísticas.
* <p>
* Este componente atua como o nó de monitorização do sistema distribuído.
* Implementa uma arquitetura de servidor concorrente utilizando um {@link ExecutorService}
* (Thread Pool) para gerir múltiplas conexões de entrada simultâneas provenientes
* das Interseções, Coordenador e Nó de Saída.
* <p>
* Suporta dois modos de operação:
* <ul>
* <li><b>Headless (CLI):</b> Renderização periódica de métricas no terminal (stdout).</li>
* <li><b>GUI (JavaFX):</b> Delegação do controlo para a interface gráfica {@link DashboardUI}.</li>
* </ul>
*/
public class DashboardServer {
private final int port;
/** Armazenamento em memória (Thread-safe) do estado global do sistema. */
private final DashboardStatistics statistics;
/** Pool de threads para isolamento de falhas e gestão de recursos de I/O. */
private final ExecutorService clientHandlerPool;
/** Flag atómica para controlo seguro do ciclo de vida do servidor. */
private final AtomicBoolean running;
private ServerSocket serverSocket;
/**
* Ponto de entrada (Bootstrap) da aplicação de monitorização.
* <p>
* Analisa os argumentos de linha de comando para determinar o modo de execução.
* Se a flag {@code --gui} ou {@code -g} estiver presente, inicia o subsistema JavaFX.
* Caso contrário, inicia o modo servidor de terminal padrão.
*
* @param args Argumentos de CLI (ex: caminho do config, flags de modo).
*/
public static void main(String[] args) {
// Check if GUI mode is requested
boolean useGUI = false;
@@ -70,13 +96,24 @@ public class DashboardServer {
}
}
/**
* Inicializa a infraestrutura do servidor.
*
* @param config A configuração carregada contendo a porta de escuta.
*/
public DashboardServer(SimulationConfig config) {
this.port = config.getDashboardPort();
this.statistics = new DashboardStatistics();
// Fixed pool limita o consumo de recursos, prevenindo exaustão sob carga alta
this.clientHandlerPool = Executors.newFixedThreadPool(10);
this.running = new AtomicBoolean(false);
}
/**
* Inicia a escuta por conexões (Bind & Listen) e a thread de despacho.
*
* @throws IOException Se a porta já estiver em uso ou ocorrer erro de bind.
*/
public void start() throws IOException {
if (running.get()) {
System.out.println("Dashboard Server is already running.");
@@ -95,6 +132,13 @@ public class DashboardServer {
acceptThread.start();
}
/**
* Loop principal de aceitação de conexões (Dispatcher).
* <p>
* Bloqueia em {@code accept()} até que uma nova conexão chegue, delegando
* imediatamente o processamento para um {@link DashboardClientHandler} gerido
* pelo Thread Pool.
*/
private void acceptConnections() {
while (running.get()) {
try {
@@ -112,6 +156,10 @@ public class DashboardServer {
}
}
/**
* Ciclo de renderização de métricas para o modo CLI (Headless).
* Atualiza o ecrã a cada 5 segundos.
*/
@SuppressWarnings("BusyWait")
private void displayLoop() {
final long DISPLAY_INTERVAL_MS = 5000;
@@ -127,6 +175,9 @@ public class DashboardServer {
}
}
/**
* Renderiza o snapshot atual das estatísticas no stdout.
*/
public void displayStatistics() {
System.out.println("\n" + "=".repeat(60));
System.out.println("REAL-TIME SIMULATION STATISTICS");
@@ -135,6 +186,13 @@ public class DashboardServer {
System.out.println("=".repeat(60));
}
/**
* Procedimento de encerramento gracioso (Graceful Shutdown).
* <p>
* 1. Altera flag de execução.
* 2. Fecha o socket do servidor para desbloquear a thread de aceitação.
* 3. Força o encerramento do pool de threads de clientes.
*/
public void stop() {
if (!running.get()) {
return;

View File

@@ -9,8 +9,13 @@ import java.util.concurrent.atomic.AtomicLong;
import sd.model.VehicleType;
/**
* Armazenamento thread-safe de estatísticas agregadas da simulação.
* Usa tipos atómicos e coleções concorrentes para atualizações sem locks.
* Repositório central de estado da simulação, desenhado para acesso concorrente de alta frequência.
* <p>
* Esta classe atua como a "Single Source of Truth" para o Dashboard. Utiliza primitivas
* de concorrência do pacote {@code java.util.concurrent} (como {@link AtomicInteger} e
* {@link ConcurrentHashMap}) para permitir leituras e escritas simultâneas sem a necessidade
* de bloqueios explícitos (Lock-Free), minimizando a latência de processamento das mensagens
* recebidas dos múltiplos nós da rede.
*/
public class DashboardStatistics {
@@ -19,12 +24,21 @@ public class DashboardStatistics {
private final AtomicLong totalSystemTime;
private final AtomicLong totalWaitingTime;
/** Mapa thread-safe para armazenar métricas granulares por interseção. */
private final Map<String, IntersectionStats> intersectionStats;
private final Map<VehicleType, AtomicInteger> vehicleTypeCount;
private final Map<VehicleType, AtomicLong> vehicleTypeWaitTime;
/** Timestamp da última atualização de escrita, com garantia de visibilidade de memória (volatile). */
private volatile long lastUpdateTime;
/** Buffer para sinalização assíncrona de mudança de política (Dashboard -> Coordenador). */
private volatile String requestedRoutingPolicy;
/**
* Inicializa os contadores atómicos e as estruturas de dados concorrentes.
*/
public DashboardStatistics() {
this.totalVehiclesGenerated = new AtomicInteger(0);
this.totalVehiclesCompleted = new AtomicInteger(0);
@@ -94,6 +108,17 @@ public class DashboardStatistics {
updateTimestamp();
}
/**
* Atualiza ou inicializa atomicamente as estatísticas de uma interseção específica.
* <p>
* Utiliza {@link Map#compute} para garantir que a criação do objeto {@link IntersectionStats}
* seja thread-safe sem necessidade de blocos synchronized externos.
*
* @param intersectionId ID da interseção.
* @param arrivals Total acumulado de chegadas.
* @param departures Total acumulado de partidas.
* @param currentQueueSize Tamanho instantâneo da fila.
*/
public void updateIntersectionStats(String intersectionId, int arrivals,
int departures, int currentQueueSize) {
intersectionStats.compute(intersectionId, (id, stats) -> {
@@ -110,6 +135,8 @@ public class DashboardStatistics {
lastUpdateTime = System.currentTimeMillis();
}
// --- Getters e Métricas Calculadas ---
public int getTotalVehiclesGenerated() {
return totalVehiclesGenerated.get();
}
@@ -118,12 +145,20 @@ public class DashboardStatistics {
return totalVehiclesCompleted.get();
}
/**
* Calcula o tempo médio no sistema em tempo real.
* @return Média em milissegundos (0.0 se nenhum veículo completou).
*/
public double getAverageSystemTime() {
int completed = totalVehiclesCompleted.get();
if (completed == 0) return 0.0;
return (double) totalSystemTime.get() / completed;
}
/**
* Calcula o tempo médio de espera em tempo real.
* @return Média em milissegundos (0.0 se nenhum veículo completou).
*/
public double getAverageWaitingTime() {
int completed = totalVehiclesCompleted.get();
if (completed == 0) return 0.0;
@@ -152,6 +187,44 @@ public class DashboardStatistics {
return lastUpdateTime;
}
/**
* Obtém um snapshot dos tamanhos atuais das filas de todas as interseções.
* <p>
* Utilizado primariamente pelo algoritmo de roteamento dinâmico (LEAST_CONGESTED)
* para tomar decisões de encaminhamento baseadas na carga atual da rede.
* * @return Mapa contendo {@code intersectionId -> queueSize}.
*/
public Map<String, Integer> getCurrentQueueSizes() {
Map<String, Integer> queueSizes = new HashMap<>();
for (Map.Entry<String, IntersectionStats> entry : intersectionStats.entrySet()) {
queueSizes.put(entry.getKey(), entry.getValue().getCurrentQueueSize());
}
return queueSizes;
}
/**
* Regista uma intenção de mudança de política de roteamento solicitada pela UI.
* O Coordenador fará polling deste valor periodicamente.
*/
public void setRequestedRoutingPolicy(String policy) {
this.requestedRoutingPolicy = policy;
}
/**
* Obtém e limpa atomicamente a política de roteamento solicitada.
* Implementa a semântica de consumo único (one-time consumption).
* * @return A política solicitada ou null se não houver mudança pendente.
*/
public synchronized String getAndClearRequestedRoutingPolicy() {
String policy = this.requestedRoutingPolicy;
this.requestedRoutingPolicy = null;
return policy;
}
/**
* Imprime um resumo formatado das estatísticas no stdout.
* Útil para o modo CLI (Headless).
*/
public void display() {
System.out.println("\n--- GLOBAL STATISTICS ---");
System.out.printf("Total Vehicles Generated: %d%n", getTotalVehiclesGenerated());
@@ -181,6 +254,10 @@ public class DashboardStatistics {
System.out.printf("%nLast Update: %tT%n", lastUpdateTime);
}
/**
* Agregado de métricas específico para um nó de interseção.
* Mantém contadores atómicos para garantir consistência em atualizações concorrentes.
*/
public static class IntersectionStats {
private final String intersectionId;
private final AtomicInteger totalArrivals;

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,28 @@ 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");
root.setStyle("-fx-background-color: #2b2b2b;");
// 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 +119,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 +166,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 +178,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
@@ -188,7 +209,7 @@ public class DashboardUI extends Application {
// Scenario selector
VBox scenarioBox = new VBox(5);
scenarioBox.setAlignment(Pos.CENTER);
scenarioBox.setAlignment(Pos.CENTER_LEFT);
Label scenarioLabel = new Label("Cenário:");
scenarioLabel.setStyle("-fx-font-size: 12px;");
@@ -209,13 +230,48 @@ public class DashboardUI extends Application {
scenarioBox.getChildren().addAll(scenarioLabel, configFileSelector);
configControls.getChildren().add(scenarioBox);
// Routing policy selector
VBox routingBox = new VBox(5);
routingBox.setAlignment(Pos.CENTER_LEFT);
Label routingLabel = new Label("Política de Roteamento:");
routingLabel.setStyle("-fx-font-size: 12px;");
ComboBox<String> routingPolicySelector = new ComboBox<>();
routingPolicySelector.getItems().addAll(
"RANDOM",
"SHORTEST_PATH",
"LEAST_CONGESTED"
);
routingPolicySelector.setValue("RANDOM");
routingPolicySelector.setOnAction(e -> {
String selectedPolicy = routingPolicySelector.getValue();
System.out.println("Política de roteamento selecionada: " + selectedPolicy);
sendRoutingPolicyChange(selectedPolicy);
});
routingBox.getChildren().addAll(routingLabel, routingPolicySelector);
configControls.getChildren().add(routingBox);
// Advanced configuration button
VBox buttonBox = new VBox(5);
buttonBox.setAlignment(Pos.CENTER_LEFT);
Label spacerLabel = new Label(" ");
spacerLabel.setStyle("-fx-font-size: 12px;");
Button btnAdvancedConfig = new Button("Configuração Avançada...");
btnAdvancedConfig.setStyle("-fx-font-size: 11px;");
btnAdvancedConfig.setOnAction(e -> {
ConfigurationDialog.showAdvancedConfig((Stage) configBox.getScene().getWindow());
});
configControls.getChildren().add(btnAdvancedConfig);
Button btnBatchAnalysis = new Button("Análise em Lote...");
btnBatchAnalysis.setStyle("-fx-font-size: 11px;");
btnBatchAnalysis.setOnAction(e -> {
BatchAnalysisDialog.show((Stage) configBox.getScene().getWindow(), statistics);
});
buttonBox.getChildren().addAll(spacerLabel, btnAdvancedConfig, btnBatchAnalysis);
configControls.getChildren().add(buttonBox);
// Configuration info display
configInfoLabel = new Label();
@@ -400,13 +456,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()));
@@ -445,13 +511,13 @@ public class DashboardUI extends Application {
String info = "";
switch (selectedConfigFile) {
case "simulation-low.properties":
info = "🟢 CARGA BAIXA: 0.2 veículos/s (~720/hora) | Sem congestionamento esperado";
info = "CARGA BAIXA: 0.2 veículos/s (~720/hora) | Sem congestionamento esperado";
break;
case "simulation-medium.properties":
info = "🟡 CARGA MÉDIA: 0.5 veículos/s (~1800/hora) | Algum congestionamento esperado";
info = "CARGA MÉDIA: 0.5 veículos/s (~1800/hora) | Algum congestionamento esperado";
break;
case "simulation-high.properties":
info = "🔴 CARGA ALTA: 1.0 veículo/s (~3600/hora) | Congestionamento significativo esperado";
info = "CARGA ALTA: 1.0 veículo/s (~3600/hora) | Congestionamento significativo esperado";
break;
default:
info = "⚙️ CONFIGURAÇÃO PADRÃO: Verificar ficheiro para parâmetros";
@@ -482,11 +548,40 @@ public class DashboardUI extends Application {
alert.showAndWait();
}
/**
* Envia mensagem para o servidor do dashboard que notificará o coordenador.
* Usa uma abordagem indireta: salva a política desejada e o coordenador lerá na próxima geração.
*/
private void sendRoutingPolicyChange(String newPolicy) {
// Store the policy change request in statistics
// The coordinator will check this periodically
if (server != null && statistics != null) {
statistics.setRequestedRoutingPolicy(newPolicy);
System.out.println("Política de roteamento solicitada: " + newPolicy);
System.out.println(" - A mudança será aplicada pelo coordenador na próxima atualização");
// Mostrar confirmação visual
Platform.runLater(() -> {
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setTitle("Política Solicitada");
alert.setHeaderText(null);
alert.setContentText("Política de roteamento solicitada: " + newPolicy + "\nSerá aplicada em breve.");
alert.show();
});
} else {
Platform.runLater(() -> {
showErrorAlert("Erro", "Dashboard não está conectado. Inicie a simulação primeiro.");
});
}
}
public static void main(String[] 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 {
private final String vehicleType;
private final int count;
@@ -498,19 +593,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;
@@ -524,20 +612,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,7 +84,42 @@ public class SimulationProcessManager {
}
/**
* Stops all running simulation processes.
* 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()) {
return false;
}
// Coordinator is the last process in the list
Process coordinator = runningProcesses.get(runningProcesses.size() - 1);
return coordinator.isAlive();
}
/**
* 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()) {
return true;
}
Process coordinator = runningProcesses.get(runningProcesses.size() - 1);
return coordinator.waitFor(timeoutSeconds, java.util.concurrent.TimeUnit.SECONDS);
}
/**
* 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...");
@@ -94,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,11 +3,15 @@ package sd.des;
/**
* Gere o tempo de simulação para Simulação de Eventos Discretos.
*
* <p>No DES, o tempo avança em saltos discretos de evento para evento,
* não de forma contínua como o tempo real.</p>
* <p>
* No DES, o tempo avança em saltos discretos de evento para evento,
* não de forma contínua como o tempo real.
* </p>
*
* <p>Esta classe garante que todos os processos no sistema distribuído
* mantêm uma visão sincronizada do tempo de simulação.</p>
* <p>
* Esta classe garante que todos os processos no sistema distribuído
* mantêm uma visão sincronizada do tempo de simulação.
* </p>
*/
public class SimulationClock {
private double currentTime;
@@ -34,7 +38,7 @@ public class SimulationClock {
public void advanceTo(double newTime) {
if (newTime < currentTime) {
throw new IllegalArgumentException(
String.format("Cannot move time backwards: %.3f -> %.3f", currentTime, newTime));
String.format("Cannot move time backwards: %.3f -> %.3f", currentTime, newTime));
}
this.currentTime = newTime;
}
@@ -62,6 +66,6 @@ public class SimulationClock {
@Override
public String toString() {
return String.format("SimulationClock[time=%.3fs, elapsed=%.3fs]",
currentTime, getElapsedTime());
currentTime, getElapsedTime());
}
}

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,33 +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,15 +12,18 @@ import java.util.concurrent.ConcurrentHashMap;
import sd.model.Vehicle;
/**
* Rastreia e regista a viagem completa de veículos individuais.
*
* <p>Cria ficheiros de trace detalhados com:
* 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>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 {
@@ -28,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");
@@ -47,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) {
@@ -59,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) {
@@ -70,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);
@@ -85,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);
@@ -96,130 +113,143 @@ 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())) return;
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()));
String.format("Type: %s, Entry Time: %.2fs, Route: %s",
vehicle.getType(), vehicle.getEntryTime(), vehicle.getRoute()));
}
}
/**
* 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)) return;
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));
String.format("Arrived at %s (sim time: %.2fs)", intersection, simulationTime));
}
}
/**
* 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)) return;
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));
String.format("Queued at %s-%s (position: %d)", intersection, direction, queuePosition));
}
}
/**
* 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)) return;
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));
String.format("Started waiting at %s-%s (light is RED)", intersection, direction));
}
}
/**
* 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)) return;
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));
String.format("Finished waiting at %s-%s (waited %.2fs)", intersection, direction, waitTime));
}
}
/**
* 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)) return;
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));
String.format("Started crossing %s-%s (light is GREEN)", intersection, direction));
}
}
/**
* 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)) return;
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));
String.format("Finished crossing %s (took %.2fs)", intersection, crossingTime));
}
}
/**
* 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)) return;
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));
String.format("Departed from %s toward %s", intersection, nextDestination));
}
}
/**
* 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())) return;
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()));
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
@@ -228,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()) {
@@ -238,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;
@@ -261,7 +292,7 @@ public class VehicleTracer {
w.println("=".repeat(80));
w.println();
w.printf("%-23s | %-8s | %-15s | %-15s | %s\n",
"TIMESTAMP", "REL_TIME", "EVENT", "LOCATION", "DESCRIPTION");
"TIMESTAMP", "REL_TIME", "EVENT", "LOCATION", "DESCRIPTION");
w.println("-".repeat(80));
} catch (IOException e) {
@@ -272,24 +303,25 @@ public class VehicleTracer {
}
void logEvent(String eventType, String location, String description) {
if (writer == null) return;
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
);
timestamp,
relativeTime,
truncate(eventType, 15),
truncate(location, 15),
description);
writer.flush();
}
void writeSummary(Vehicle vehicle, double systemTime) {
if (writer == null) return;
if (writer == null)
return;
writer.println();
writer.println("=".repeat(80));
@@ -302,14 +334,14 @@ public class VehicleTracer {
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);
vehicle.getTotalWaitingTime(),
100.0 * vehicle.getTotalWaitingTime() / systemTime);
writer.printf("Total Crossing Time: %.2f seconds (%.1f%%)\n",
vehicle.getTotalCrossingTime(),
100.0 * vehicle.getTotalCrossingTime() / systemTime);
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);
systemTime - vehicle.getTotalWaitingTime() - vehicle.getTotalCrossingTime(),
100.0 * (systemTime - vehicle.getTotalWaitingTime() - vehicle.getTotalCrossingTime()) / systemTime);
writer.println("=".repeat(80));
}
@@ -324,7 +356,8 @@ public class VehicleTracer {
}
private String truncate(String str, int maxLength) {
if (str == null) return "";
if (str == null)
return "";
return str.length() <= maxLength ? str : str.substring(0, maxLength);
}
}

View File

@@ -86,24 +86,15 @@ public class Intersection {
}
/**
* Recebe um veículo e coloca-o na fila correta.
* Recebe um novo veículo e coloca-o na fila do semáforo apropriado.
* A direção é escolhida com base na tabela de encaminhamento.
*
* <p>Passos executados:</p>
* <ol>
* <li>Incrementa o contador de veículos recebidos</li>
* <li>Obtém o próximo destino do veículo</li>
* <li>Consulta a tabela de encaminhamento para encontrar a direção</li>
* <li>Adiciona o veículo à fila do semáforo apropriado</li>
* </ol>
*
* @param vehicle o veículo que chega à interseção
* @param vehicle o veículo que está a chegar a esta interseção
* @param simulationTime o tempo de simulação atual (em segundos)
*/
public void receiveVehicle(Vehicle vehicle) {
public void receiveVehicle(Vehicle vehicle, double simulationTime) {
totalVehiclesReceived++;
// Note: Route advancement is handled by SimulationEngine.handleVehicleArrival()
// before calling this method, so we don't advance here.
String nextDestination = vehicle.getCurrentDestination();
// Check if vehicle reached final destination
@@ -117,7 +108,7 @@ public class Intersection {
if (direction != null && trafficLights.containsKey(direction)) {
// Found a valid route and light, add vehicle to the queue
trafficLights.get(direction).addVehicle(vehicle);
trafficLights.get(direction).addVehicle(vehicle, simulationTime);
} else {
// Routing error: No rule for this destination or no light for that direction
System.err.printf(
@@ -125,9 +116,7 @@ public class Intersection {
this.id, vehicle.getId(), nextDestination, direction
);
}
}
/**
} /**
* Retorna a direção que um veículo deve tomar para alcançar um destino.
*
* @param destination o próximo destino (ex: "Cr3", "S")

View File

@@ -5,41 +5,52 @@ import java.util.UUID;
import sd.protocol.MessageProtocol;
/**
* Representa uma mensagem trocada entre processos na simulação distribuída.
*
* <p>Cada mensagem tem um ID único, tipo, remetente, destino e payload.
* Implementa {@link MessageProtocol} que estende Serializable para transmissão pela rede.</p>
* Envelope fundamental do protocolo de comunicação entre processos distribuídos (IPC).
* <p>
* Esta classe atua como a Unidade de Dados de Aplicação (ADU), encapsulando tanto
* os metadados de roteamento (origem, destino, tipo) quanto a carga útil (payload)
* polimórfica. É agnóstica ao conteúdo, servindo como contentor genérico para
* transferência de estado (Veículos, Estatísticas) ou sinais de controlo (Semáforos).
* <p>
* A imutabilidade dos campos (exceto via serialização) garante a integridade da mensagem
* durante o trânsito na rede.
*/
public class Message implements MessageProtocol {
private static final long serialVersionUID = 1L;
/** Identificador único desta mensagem */
/** * Identificador único universal (UUID).
* Essencial para rastreabilidade (tracing), logs de auditoria e mecanismos de deduplicação.
*/
private final String messageId;
/** Tipo desta mensagem (ex: VEHICLE_TRANSFER, STATS_UPDATE) */
/** Discriminador semântico que define como o recetor deve processar o payload. */
private final MessageType type;
/** Identificador do processo que enviou esta mensagem */
/** Identificador lógico do nó emissor (ex: "Cr1", "Coordinator"). */
private final String senderId;
/** Identificador do processo de destino (pode ser null para broadcast) */
/** * Identificador lógico do nó recetor.
* Se {@code null}, a mensagem deve ser tratada como <b>Broadcast</b>.
*/
private final String destinationId;
/** Dados a serem transmitidos (o tipo depende do tipo de mensagem) */
/** * Carga útil polimórfica.
* Deve implementar {@link java.io.Serializable} para garantir transmissão correta.
*/
private final Object payload;
/** Timestamp de criação da mensagem (tempo de simulação ou real) */
/** Marca temporal da criação da mensagem (Unix Timestamp), usada para cálculo de latência de rede. */
private final long timestamp;
/**
* Cria uma nova mensagem com todos os parâmetros.
* Construtor completo para reconstrução de mensagens ou envio com timestamp manual.
*
* @param type tipo da mensagem
* @param senderId ID do processo remetente
* @param destinationId ID do processo de destino (null para broadcast)
* @param payload conteúdo da mensagem
* @param timestamp timestamp de criação da mensagem
* @param type Classificação semântica da mensagem.
* @param senderId ID do processo origem.
* @param destinationId ID do processo destino (ou null para broadcast).
* @param payload Objeto de domínio a ser transportado.
* @param timestamp Instante de criação (ms).
*/
public Message(MessageType type, String senderId, String destinationId,
Object payload, long timestamp) {
@@ -52,23 +63,24 @@ public class Message implements MessageProtocol {
}
/**
* Cria uma nova mensagem usando o tempo atual do sistema como timestamp.
* Construtor de conveniência que atribui automaticamente o timestamp atual do sistema.
*
* @param type tipo da mensagem
* @param senderId ID do processo remetente
* @param destinationId ID do processo de destino
* @param payload conteúdo da mensagem
* @param type Classificação semântica.
* @param senderId ID do processo origem.
* @param destinationId ID do processo destino.
* @param payload Objeto de domínio.
*/
public Message(MessageType type, String senderId, String destinationId, Object payload) {
this(type, senderId, destinationId, payload, System.currentTimeMillis());
}
/**
* Cria uma mensagem de broadcast (sem destino específico).
* Construtor de conveniência para mensagens de difusão (Broadcast).
* Define {@code destinationId} como null.
*
* @param type tipo da mensagem
* @param senderId ID do processo remetente
* @param payload conteúdo da mensagem
* @param type Classificação semântica.
* @param senderId ID do processo origem.
* @param payload Objeto de domínio.
*/
public Message(MessageType type, String senderId, Object payload) {
this(type, senderId, null, payload, System.currentTimeMillis());
@@ -101,21 +113,23 @@ public class Message implements MessageProtocol {
}
/**
* Checks if this is a broadcast message (no specific destination).
* Verifica se a mensagem se destina a todos os nós da rede.
*
* @return true if destinationId is null, false otherwise
* @return {@code true} se o destinationId for nulo.
*/
public boolean isBroadcast() {
return destinationId == null;
}
/**
* Gets the payload cast to a specific type.
* Use with caution and ensure type safety.
* Utilitário para casting seguro e fluente do payload.
* <p>
* Evita a necessidade de casts explícitos e supressão de warnings no código cliente.
*
* @param <T> The expected payload type
* @return The payload cast to type T
* @throws ClassCastException if the payload is not of type T
* @param <T> O tipo esperado do payload.
* @param clazz A classe do tipo esperado para verificação em runtime (opcional no uso, mas boa prática).
* @return O payload convertido para o tipo T.
* @throws ClassCastException Se o payload não for compatível com o tipo solicitado.
*/
@SuppressWarnings("unchecked")
public <T> T getPayloadAs(Class<T> clazz) {

View File

@@ -40,4 +40,10 @@ public enum MessageType {
*/
SHUTDOWN,
/**
* Mensagem para alterar a política de roteamento durante a simulação.
* Payload: String com o nome da nova política (RANDOM, SHORTEST_PATH, LEAST_CONGESTED)
*/
ROUTING_POLICY_CHANGE,
}

View File

@@ -54,9 +54,9 @@ public class TrafficLight {
/**
* Regista quando os veículos chegam ao semáforo para cálculo do tempo de espera.
* Mapeia ID do veículo para timestamp de chegada (milissegundos).
* Mapeia ID do veículo para tempo de simulação de chegada (segundos).
*/
private final Map<String, Long> vehicleArrivalTimes;
private final Map<String, Double> vehicleArrivalTimes;
/**
* Cria um novo semáforo.
@@ -89,12 +89,13 @@ public class TrafficLight {
* veículo esperou.
*
* @param vehicle O veículo que chega ao semáforo.
* @param simulationTime O tempo de simulação atual (em segundos).
*/
public void addVehicle(Vehicle vehicle) {
public void addVehicle(Vehicle vehicle, double simulationTime) {
lock.lock();
try {
queue.offer(vehicle);
vehicleArrivalTimes.put(vehicle.getId(), System.currentTimeMillis());
vehicleArrivalTimes.put(vehicle.getId(), simulationTime);
vehicleAdded.signalAll();
} finally {
lock.unlock();
@@ -112,9 +113,10 @@ public class TrafficLight {
*
* <p>Atualiza automaticamente as estatísticas de tempo de espera do veículo.</p>
*
* @param simulationTime O tempo de simulação atual (em segundos).
* @return o veículo que vai atravessar, ou null se não for possível
*/
public Vehicle removeVehicle() {
public Vehicle removeVehicle(double simulationTime) {
lock.lock();
try {
if (state == TrafficLightState.GREEN && !queue.isEmpty()) {
@@ -122,9 +124,9 @@ public class TrafficLight {
if (vehicle != null) {
totalVehiclesProcessed++;
Long arrivalTime = vehicleArrivalTimes.remove(vehicle.getId());
Double arrivalTime = vehicleArrivalTimes.remove(vehicle.getId());
if (arrivalTime != null) {
double waitTimeSeconds = (System.currentTimeMillis() - arrivalTime) / 1000.0;
double waitTimeSeconds = simulationTime - arrivalTime;
vehicle.addWaitingTime(waitTimeSeconds);
}
}

View File

@@ -7,16 +7,20 @@ import java.util.List;
/**
* Representa um veículo que se move pela rede de interseções.
*
* <p>Esta classe é o "gémeo digital" de um carro, mota ou camião.
* Mantém toda a informação necessária:</p>
* <p>
* Esta classe é o "gémeo digital" de um carro, mota ou camião.
* Mantém toda a informação necessária:
* </p>
* <ul>
* <li>Identificação e tipo do veículo</li>
* <li>Rota completa a percorrer</li>
* <li>Métricas de tempo (espera, travessia, total)</li>
* <li>Identificação e tipo do veículo</li>
* <li>Rota completa a percorrer</li>
* <li>Métricas de tempo (espera, travessia, total)</li>
* </ul>
*
* <p>O objeto é serializado e enviado pela rede à medida que o veículo
* se move entre processos distribuídos.</p>
* <p>
* O objeto é serializado e enviado pela rede à medida que o veículo
* se move entre processos distribuídos.
* </p>
*/
public class Vehicle implements Serializable {
private static final long serialVersionUID = 1L;
@@ -42,10 +46,16 @@ public class Vehicle implements Serializable {
*/
private int currentRouteIndex;
/** Tempo total acumulado (segundos) que o veículo passou à espera em semáforos vermelhos */
/**
* Tempo total acumulado (segundos) que o veículo passou à espera em semáforos
* vermelhos
*/
private double totalWaitingTime;
/** Tempo total acumulado (segundos) que o veículo passou a atravessar interseções */
/**
* Tempo total acumulado (segundos) que o veículo passou a atravessar
* interseções
*/
private double totalCrossingTime;
/**
@@ -80,7 +90,8 @@ public class Vehicle implements Serializable {
}
/**
* Obtém o destino atual (próxima interseção ou saída) para onde o veículo se dirige.
* Obtém o destino atual (próxima interseção ou saída) para onde o veículo se
* dirige.
*
* @return ID do destino atual (ex: "Cr1"), ou {@code null} se a rota terminou
*/

View File

@@ -2,7 +2,7 @@ package sd.protocol;
import java.io.Serializable;
import sd.model.MessageType; // Assuming MessageType is in sd.model or sd.protocol
import sd.model.MessageType;
/**
* Contrato para todas as mensagens trocadas no simulador.

View File

@@ -16,32 +16,64 @@ import sd.serialization.MessageSerializer;
import sd.serialization.SerializationException;
import sd.serialization.SerializerFactory;
/**
* Simplifica comunicação via sockets.
* Inclui lógica de retry para robustez.
* Wrapper de alto nível para gestão robusta de conexões TCP.
* <p>
* Esta classe abstrai a complexidade da API nativa {@link java.net.Socket}, oferecendo:
* <ol>
* <li><b>Resiliência:</b> Lógica de reconexão automática (Retry Loop) no arranque, crucial para sistemas
* distribuídos onde a ordem de inicialização dos nós não é garantida.</li>
* <li><b>Framing:</b> Implementação transparente do protocolo "Length-Prefix" (4 bytes de tamanho + payload),
* resolvendo o problema de fragmentação de stream TCP.</li>
* <li><b>Serialização:</b> Integração direta com a camada de serialização JSON.</li>
* </ol>
*/
public class SocketConnection implements Closeable {
// --- Network Resources ---
/**
* The underlying TCP socket used for network communication.
*/
private final Socket socket;
/**
* The raw output stream for writing bytes to the network.
* Wrapped by {@link DataOutputStream} during message sending.
*/
private final OutputStream outputStream;
/**
* The raw input stream for reading bytes from the network.
* Wrapped by {@link DataInputStream} during message reception.
*/
private final InputStream inputStream;
// --- Serialization ---
/**
* The serializer strategy used to convert objects to/from byte arrays (e.g., JSON).
*/
private final MessageSerializer serializer;
/** Número máximo de tentativas de ligação */
/** Número máximo de tentativas de ligação antes de desistir (Fail-fast). */
private static final int MAX_RETRIES = 5;
/** Atraso entre tentativas (milissegundos) */
/** Janela de espera (backoff) linear entre tentativas (em milissegundos). */
private static final long RETRY_DELAY_MS = 1000;
/**
* Construtor do cliente que inicia a ligação.
* Tenta ligar a um servidor já em escuta, com retry.
* Construtor para clientes (Active Open).
* Tenta estabelecer uma conexão TCP com um servidor, aplicando lógica de retry.
* <p>
* Este comportamento é vital quando o processo Coordenador inicia antes das Interseções estarem
* prontas para aceitar conexões ({@code accept()}).
*
* @param host endereço do host (ex: "localhost")
* @param port número da porta
* @throws IOException se falhar após todas as tentativas
* @throws UnknownHostException se o host não for encontrado
* @throws InterruptedException se a thread for interrompida
* @param host Endereço do nó de destino (ex: "localhost").
* @param port Porta de serviço.
* @throws IOException Se a conexão falhar após todas as {@code MAX_RETRIES} tentativas.
* @throws UnknownHostException Se o DNS não resolver o hostname.
* @throws InterruptedException Se a thread for interrompida durante o sleep de retry.
*/
public SocketConnection(String host, int port) throws IOException, UnknownHostException, InterruptedException {
Socket tempSocket = null;
@@ -52,7 +84,7 @@ public class SocketConnection implements Closeable {
// --- Retry Loop ---
for (int attempt = 1; attempt <= MAX_RETRIES; attempt++) {
try {
// Try to establish the connection
// Try to establish the connection (SYN -> SYN-ACK -> ACK)
tempSocket = new Socket(host, port);
// If successful, break out of the retry loop
@@ -61,17 +93,17 @@ public class SocketConnection implements Closeable {
break;
} catch (ConnectException | SocketTimeoutException e) {
// These are common errors indicating the server might not be ready.
// Common errors: "Connection refused" (server not up) or "Timeout" (firewall/network)
lastException = e;
System.out.printf("[SocketConnection] Attempt %d/%d failed: %s. Retrying in %d ms...%n",
attempt, MAX_RETRIES, e.getMessage(), RETRY_DELAY_MS);
if (attempt < MAX_RETRIES) {
// Wait before the next attempt
// Blocking wait before next attempt
TimeUnit.MILLISECONDS.sleep(RETRY_DELAY_MS);
}
} catch (IOException e) {
// Other IOExceptions might be more permanent, but we retry anyway.
// Other IO errors
lastException = e;
System.out.printf("[SocketConnection] Attempt %d/%d failed with IOException: %s. Retrying in %d ms...%n",
attempt, MAX_RETRIES, e.getMessage(), RETRY_DELAY_MS);
@@ -81,51 +113,49 @@ public class SocketConnection implements Closeable {
}
} // --- End of Retry Loop ---
// If after all retries tempSocket is still null, it means connection failed permanently.
// Final validation
if (tempSocket == null) {
System.err.printf("[SocketConnection] Failed to connect to %s:%d after %d attempts.%n", host, port, MAX_RETRIES);
if (lastException != null) {
throw lastException; // Throw the last exception encountered
throw lastException; // Propagate the root cause
} else {
// Should not happen if loop ran, but as a fallback
throw new IOException("Failed to connect after " + MAX_RETRIES + " attempts, reason unknown.");
}
}
// If connection was successful, assign to final variable and create streams
// Initialize streams
this.socket = tempSocket;
this.outputStream = socket.getOutputStream();
this.inputStream = socket.getInputStream();
this.serializer = SerializerFactory.createDefault();
}
/**
* Constructor for the "Server" (who accepts the connection).
* Receives a Socket that has already been accepted by a ServerSocket.
* No retry logic needed here as the connection is already established.
* Construtor para servidores (Passive Open).
* Envolve um socket já conectado (retornado por {@code serverSocket.accept()}).
* Não necessita de retry logic pois a conexão física já existe.
*
* @param acceptedSocket The Socket returned by serverSocket.accept().
* @throws IOException If stream creation fails.
* @param acceptedSocket O socket ativo retornado pelo SO.
* @throws IOException Se falhar a obtenção dos streams de I/O.
*/
public SocketConnection(Socket acceptedSocket) throws IOException {
this.socket = acceptedSocket;
this.outputStream = socket.getOutputStream();
this.inputStream = socket.getInputStream();
this.serializer = SerializerFactory.createDefault();
}
/**
* Sends (serializes) a MessageProtocol object over the socket.
* Serializa e transmite uma mensagem através do canal.
* <p>
* Utiliza sincronização ({@code synchronized}) para garantir que escritas concorrentes
* na mesma conexão não corrompem a stream de bytes (thread-safety).
*
* @param message The "envelope" (which contains the Vehicle) to be sent.
* @throws IOException If writing to the stream fails or socket is not connected.
* @param message O objeto de protocolo a enviar.
* @throws IOException Se o socket estiver fechado ou ocorrer erro de escrita.
*/
public synchronized void sendMessage(MessageProtocol message) throws IOException {
if (socket == null || !socket.isConnected()) {
if (socket == null || !socket.isConnected()) {
throw new IOException("Socket is not connected");
}
@@ -133,11 +163,11 @@ public class SocketConnection implements Closeable {
// Serializa para bytes JSON
byte[] data = serializer.serialize(message);
// Write 4-byte length prefix
// Write 4-byte length prefix (Framing)
DataOutputStream dataOut = new DataOutputStream(outputStream);
dataOut.writeInt(data.length);
dataOut.write(data);
dataOut.flush();
dataOut.flush(); // Force transmission immediately
} catch (SerializationException e) {
throw new IOException("Failed to serialize message", e);
@@ -145,11 +175,14 @@ public class SocketConnection implements Closeable {
}
/**
* Tries to read (deserialize) a MessageProtocol object from the socket.
* Bloqueia à espera de uma mensagem completa do socket.
* <p>
* Lê primeiro o cabeçalho de tamanho (4 bytes) e depois o payload exato,
* garantindo que processa mensagens completas mesmo se chegarem fragmentadas em múltiplos pacotes TCP.
*
* @return The "envelope" (MessageProtocol) that was received.
* @throws IOException If the connection is lost, the stream is corrupted, or socket is not connected.
* @throws ClassNotFoundException If the received object is unknown.
* @return O objeto {@link MessageProtocol} reconstruído.
* @throws IOException Se a conexão for perdida (EOF) ou o stream corrompido.
* @throws ClassNotFoundException Se o tipo desserializado não for encontrado no classpath.
*/
public MessageProtocol receiveMessage() throws IOException, ClassNotFoundException {
if (socket == null || !socket.isConnected()) {
@@ -157,19 +190,20 @@ public class SocketConnection implements Closeable {
}
try {
// Lê um prefixo de 4 bytes - indicador de tamanho
DataInputStream dataIn = new DataInputStream(inputStream);
int length = dataIn.readInt();
if (length <= 0 || length > 10_000_000) { // Sanity check (10MB max)
// Sanity check para evitar OutOfMemory em caso de corrupção de stream
if (length <= 0 || length > 10_000_000) { // Max 10MB payload
throw new IOException("Invalid message length: " + length);
}
// Ler dados da mensagem
// Ler dados exatos da mensagem
byte[] data = new byte[length];
dataIn.readFully(data);
// Deserialize do JSON - use concrete Message class, not interface
// Deserialize do JSON - força o tipo concreto Message
return serializer.deserialize(data, sd.model.Message.class);
} catch (SerializationException e) {
@@ -178,7 +212,8 @@ public class SocketConnection implements Closeable {
}
/**
* Closes the socket and all streams (Input and Output).
* Encerra a conexão e liberta os descritores de ficheiro.
* Operação idempotente.
*/
@Override
public void close() throws IOException {
@@ -188,7 +223,8 @@ public class SocketConnection implements Closeable {
}
/**
* @return true if the socket is still connected and not closed.
* Verifica o estado operacional da conexão.
* @return true se o socket está aberto e conectado.
*/
public boolean isConnected() {
return socket != null && socket.isConnected() && !socket.isClosed();

View File

@@ -0,0 +1,151 @@
package sd.routing;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
/**
* Implementação da política de roteamento por menor congestionamento.
*
* <p>Esta política escolhe dinamicamente a rota que passa pelos cruzamentos
* menos congestionados, com base no tamanho atual das filas em cada interseção.
* É uma política dinâmica que adapta as decisões ao estado da rede.</p>
*
* <p>Objetivo: Distribuir o tráfego pela rede, evitando bottlenecks e
* minimizando o tempo de espera total.</p>
*
* <p><strong>Algoritmo:</strong></p>
* <ol>
* <li>Para cada rota possível, calcula a carga total (soma das filas)</li>
* <li>Escolhe a rota com menor carga total</li>
* <li>Em caso de empate ou falta de informação, usa a rota mais curta</li>
* </ol>
*/
public class LeastCongestedRouteSelector implements RouteSelector {
/** Rotas possíveis a partir do ponto de entrada E1 */
private final List<List<String>> e1Routes;
/** Rotas possíveis a partir do ponto de entrada E2 */
private final List<List<String>> e2Routes;
/** Rotas possíveis a partir do ponto de entrada E3 */
private final List<List<String>> e3Routes;
/**
* Cria um novo seletor de rotas baseado em menor congestionamento.
*/
public LeastCongestedRouteSelector() {
this.e1Routes = new ArrayList<>();
this.e2Routes = new ArrayList<>();
this.e3Routes = new ArrayList<>();
initializeRoutes();
}
/**
* Inicializa as rotas possíveis para cada ponto de entrada.
*/
private void initializeRoutes() {
// Rotas de E1 (entrada Norte)
e1Routes.add(Arrays.asList("Cr1", "Cr4", "Cr5", "S"));
e1Routes.add(Arrays.asList("Cr1", "Cr2", "Cr5", "S"));
e1Routes.add(Arrays.asList("Cr1", "Cr2", "Cr3", "S"));
// Rotas de E2 (entrada Oeste)
e2Routes.add(Arrays.asList("Cr2", "Cr5", "S"));
e2Routes.add(Arrays.asList("Cr2", "Cr3", "S"));
e2Routes.add(Arrays.asList("Cr2", "Cr1", "Cr4", "Cr5", "S"));
// Rotas de E3 (entrada Sul)
e3Routes.add(Arrays.asList("Cr3", "S"));
e3Routes.add(Arrays.asList("Cr3", "Cr2", "Cr5", "S"));
e3Routes.add(Arrays.asList("Cr3", "Cr2", "Cr1", "Cr4", "Cr5", "S"));
}
@Override
public List<String> selectRoute(String entryPoint, Map<String, Integer> queueSizes) {
List<List<String>> availableRoutes = getRoutesForEntryPoint(entryPoint);
// Se não temos informação sobre filas, usa a rota mais curta como fallback
if (queueSizes == null || queueSizes.isEmpty()) {
return selectShortestRoute(availableRoutes);
}
// Calcula a carga de cada rota e escolhe a menos congestionada
List<String> bestRoute = null;
int minLoad = Integer.MAX_VALUE;
for (List<String> route : availableRoutes) {
int routeLoad = calculateRouteLoad(route, queueSizes);
if (routeLoad < minLoad) {
minLoad = routeLoad;
bestRoute = route;
}
}
// Fallback: se não conseguimos calcular carga, usa a primeira rota
if (bestRoute == null) {
bestRoute = availableRoutes.get(0);
}
return new ArrayList<>(bestRoute);
}
/**
* Calcula a carga total de uma rota (soma do tamanho das filas em todos os cruzamentos).
*
* @param route rota a avaliar
* @param queueSizes mapa com tamanho das filas por interseção
* @return carga total da rota (soma das filas)
*/
private int calculateRouteLoad(List<String> route, Map<String, Integer> queueSizes) {
int totalLoad = 0;
for (String intersection : route) {
// Ignora "S" (saída) e apenas conta cruzamentos reais
if (!intersection.equals("S") && queueSizes.containsKey(intersection)) {
totalLoad += queueSizes.get(intersection);
}
}
return totalLoad;
}
/**
* Seleciona a rota mais curta (menor número de nós) como fallback.
*
* @param routes lista de rotas disponíveis
* @return a rota mais curta
*/
private List<String> selectShortestRoute(List<List<String>> routes) {
List<String> shortest = routes.get(0);
for (List<String> route : routes) {
if (route.size() < shortest.size()) {
shortest = route;
}
}
return new ArrayList<>(shortest);
}
/**
* Obtém as rotas disponíveis para um ponto de entrada.
*
* @param entryPoint ponto de entrada (E1, E2 ou E3)
* @return lista de rotas disponíveis
*/
private List<List<String>> getRoutesForEntryPoint(String entryPoint) {
switch (entryPoint.toUpperCase()) {
case "E1":
return e1Routes;
case "E2":
return e2Routes;
case "E3":
return e3Routes;
default:
System.err.printf("Unknown entry point: %s, defaulting to E1%n", entryPoint);
return e1Routes;
}
}
}

View File

@@ -0,0 +1,122 @@
package sd.routing;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
/**
* Implementação da política de roteamento aleatória (baseline).
*
* <p>Esta política seleciona rotas com base em probabilidades predefinidas,
* sem considerar o estado atual da rede. É a implementação de referência
* para comparação com outras políticas.</p>
*
* <p>As rotas são organizadas por ponto de entrada (E1, E2, E3) e cada rota
* tem uma probabilidade de seleção associada.</p>
*/
public class RandomRouteSelector implements RouteSelector {
/** Rotas possíveis a partir do ponto de entrada E1 */
private final List<RouteWithProbability> e1Routes;
/** Rotas possíveis a partir do ponto de entrada E2 */
private final List<RouteWithProbability> e2Routes;
/** Rotas possíveis a partir do ponto de entrada E3 */
private final List<RouteWithProbability> e3Routes;
/**
* Cria um novo seletor de rotas aleatórias com rotas predefinidas.
*/
public RandomRouteSelector() {
this.e1Routes = new ArrayList<>();
this.e2Routes = new ArrayList<>();
this.e3Routes = new ArrayList<>();
initializePossibleRoutes();
}
/**
* Define todas as rotas possíveis que os veículos podem tomar.
* As rotas são organizadas por ponto de entrada (E1, E2, E3).
* Cada rota tem uma probabilidade que determina a frequência com que é escolhida.
*/
private void initializePossibleRoutes() {
// Rotas de E1 (entrada Norte)
e1Routes.add(new RouteWithProbability(
Arrays.asList("Cr1", "Cr4", "Cr5", "S"), 0.34));
e1Routes.add(new RouteWithProbability(
Arrays.asList("Cr1", "Cr2", "Cr5", "S"), 0.33));
e1Routes.add(new RouteWithProbability(
Arrays.asList("Cr1", "Cr2", "Cr3", "S"), 0.33));
// Rotas de E2 (entrada Oeste)
e2Routes.add(new RouteWithProbability(
Arrays.asList("Cr2", "Cr5", "S"), 0.34));
e2Routes.add(new RouteWithProbability(
Arrays.asList("Cr2", "Cr3", "S"), 0.33));
e2Routes.add(new RouteWithProbability(
Arrays.asList("Cr2", "Cr1", "Cr4", "Cr5", "S"), 0.33));
// Rotas de E3 (entrada Sul)
e3Routes.add(new RouteWithProbability(
Arrays.asList("Cr3", "S"), 0.34));
e3Routes.add(new RouteWithProbability(
Arrays.asList("Cr3", "Cr2", "Cr5", "S"), 0.33));
e3Routes.add(new RouteWithProbability(
Arrays.asList("Cr3", "Cr2", "Cr1", "Cr4", "Cr5", "S"), 0.33));
}
@Override
public List<String> selectRoute(String entryPoint, Map<String, Integer> queueSizes) {
// Ignora queueSizes - seleção aleatória não depende do estado da rede
List<RouteWithProbability> selectedRoutes = getRoutesForEntryPoint(entryPoint);
// Seleciona uma rota baseada em probabilidades cumulativas
double rand = Math.random();
double cumulative = 0.0;
for (RouteWithProbability routeWithProb : selectedRoutes) {
cumulative += routeWithProb.probability;
if (rand <= cumulative) {
// Retorna uma cópia da rota para prevenir modificações
return new ArrayList<>(routeWithProb.route);
}
}
// Fallback: retorna a primeira rota
return new ArrayList<>(selectedRoutes.get(0).route);
}
/**
* Obtém as rotas disponíveis para um ponto de entrada.
*
* @param entryPoint ponto de entrada (E1, E2 ou E3)
* @return lista de rotas com probabilidades
*/
private List<RouteWithProbability> getRoutesForEntryPoint(String entryPoint) {
switch (entryPoint.toUpperCase()) {
case "E1":
return e1Routes;
case "E2":
return e2Routes;
case "E3":
return e3Routes;
default:
System.err.printf("Unknown entry point: %s, defaulting to E1%n", entryPoint);
return e1Routes;
}
}
/**
* Classe interna para associar uma rota com sua probabilidade de seleção.
*/
private static class RouteWithProbability {
final List<String> route;
final double probability;
RouteWithProbability(List<String> route, double probability) {
this.route = route;
this.probability = probability;
}
}
}

View File

@@ -0,0 +1,25 @@
package sd.routing;
import java.util.List;
import java.util.Map;
/**
* Interface para implementação de políticas de seleção de rotas.
*
* <p>Define o contrato que todas as políticas de roteamento devem seguir.
* Permite a implementação de diferentes estratégias de roteamento
* (aleatória, caminho mais curto, menor congestionamento, etc.).</p>
*/
public interface RouteSelector {
/**
* Seleciona uma rota para um veículo a partir de um ponto de entrada.
*
* @param entryPoint ponto de entrada (E1, E2 ou E3)
* @param queueSizes mapa com o tamanho das filas em cada interseção (opcional, pode ser null).
* Chave: ID da interseção (ex: "Cr1", "Cr2")
* Valor: número total de veículos em espera nessa interseção
* @return lista de IDs representando a rota escolhida (ex: ["Cr1", "Cr2", "Cr5", "S"])
*/
List<String> selectRoute(String entryPoint, Map<String, Integer> queueSizes);
}

View File

@@ -0,0 +1,36 @@
package sd.routing;
/**
* Enumeração que define as políticas de roteamento disponíveis para a simulação.
*
* <p>As políticas de roteamento determinam como os veículos escolhem o caminho
* a seguir desde o ponto de entrada até à saída da rede de interseções.</p>
*
* <ul>
* <li><strong>RANDOM:</strong> Seleção aleatória de rotas baseada em probabilidades predefinidas</li>
* <li><strong>SHORTEST_PATH:</strong> Escolhe sempre a rota com o menor número de cruzamentos</li>
* <li><strong>LEAST_CONGESTED:</strong> Escolhe a rota evitando cruzamentos mais congestionados</li>
* </ul>
*/
public enum RoutingPolicy {
/**
* Política aleatória (baseline).
* Seleciona rotas com base em probabilidades predefinidas, sem considerar
* o estado atual da rede.
*/
RANDOM,
/**
* Política do caminho mais curto.
* Sempre escolhe a rota com o menor número de cruzamentos entre o ponto
* de entrada e a saída, minimizando a distância teórica.
*/
SHORTEST_PATH,
/**
* Política das menores filas (roteamento dinâmico).
* Escolhe a rota que passa pelos cruzamentos menos congestionados,
* com base no tamanho atual das filas em cada interseção.
*/
LEAST_CONGESTED
}

View File

@@ -0,0 +1,89 @@
package sd.routing;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
/**
* Implementação da política de roteamento por caminho mais curto.
*
* <p>Esta política sempre escolhe a rota com o menor número de cruzamentos
* entre o ponto de entrada e a saída. É uma política determinística que
* não considera o estado da rede (tamanho das filas).</p>
*
* <p>Objetivo: Minimizar a distância teórica percorrida pelos veículos.</p>
*/
public class ShortestPathRouteSelector implements RouteSelector {
/** Rotas possíveis a partir do ponto de entrada E1, ordenadas por comprimento */
private final List<List<String>> e1Routes;
/** Rotas possíveis a partir do ponto de entrada E2, ordenadas por comprimento */
private final List<List<String>> e2Routes;
/** Rotas possíveis a partir do ponto de entrada E3, ordenadas por comprimento */
private final List<List<String>> e3Routes;
/**
* Cria um novo seletor de rotas por caminho mais curto.
* As rotas são ordenadas por comprimento (número de cruzamentos).
*/
public ShortestPathRouteSelector() {
this.e1Routes = new ArrayList<>();
this.e2Routes = new ArrayList<>();
this.e3Routes = new ArrayList<>();
initializeRoutes();
}
/**
* Inicializa as rotas possíveis para cada ponto de entrada.
* As rotas são organizadas da mais curta para a mais longa.
*/
private void initializeRoutes() {
// Rotas de E1 (entrada Norte) - ordenadas por comprimento
e1Routes.add(Arrays.asList("Cr1", "Cr2", "Cr3", "S")); // 4 nós
e1Routes.add(Arrays.asList("Cr1", "Cr2", "Cr5", "S")); // 4 nós
e1Routes.add(Arrays.asList("Cr1", "Cr4", "Cr5", "S")); // 4 nós
// Rotas de E2 (entrada Oeste) - ordenadas por comprimento
e2Routes.add(Arrays.asList("Cr2", "Cr3", "S")); // 3 nós (mais curta!)
e2Routes.add(Arrays.asList("Cr2", "Cr5", "S")); // 3 nós
e2Routes.add(Arrays.asList("Cr2", "Cr1", "Cr4", "Cr5", "S")); // 5 nós
// Rotas de E3 (entrada Sul) - ordenadas por comprimento
e3Routes.add(Arrays.asList("Cr3", "S")); // 2 nós (mais curta!)
e3Routes.add(Arrays.asList("Cr3", "Cr2", "Cr5", "S")); // 4 nós
e3Routes.add(Arrays.asList("Cr3", "Cr2", "Cr1", "Cr4", "Cr5", "S")); // 6 nós
}
@Override
public List<String> selectRoute(String entryPoint, Map<String, Integer> queueSizes) {
// Ignora queueSizes - política baseada apenas no comprimento do caminho
List<List<String>> availableRoutes = getRoutesForEntryPoint(entryPoint);
// Retorna a rota mais curta (primeira da lista)
List<String> shortestRoute = availableRoutes.get(0);
return new ArrayList<>(shortestRoute);
}
/**
* Obtém as rotas disponíveis para um ponto de entrada.
*
* @param entryPoint ponto de entrada (E1, E2 ou E3)
* @return lista de rotas ordenadas por comprimento
*/
private List<List<String>> getRoutesForEntryPoint(String entryPoint) {
switch (entryPoint.toUpperCase()) {
case "E1":
return e1Routes;
case "E2":
return e2Routes;
case "E3":
return e3Routes;
default:
System.err.printf("Unknown entry point: %s, defaulting to E1%n", entryPoint);
return e1Routes;
}
}
}

View File

@@ -1,26 +1,25 @@
package sd.serialization;
import java.nio.charset.StandardCharsets;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonSyntaxException;
import java.nio.charset.StandardCharsets;
/**
* JSON-based implementation of {@link MessageSerializer} using Google's Gson library.
*
* This serializer converts objects to JSON format for transmission, providing:
* - Human-readable message format (easy debugging)
* - Cross-platform compatibility
* - Smaller message sizes compared to Java native serialization
* - Better security (no code execution during deserialization)
*
* The serializer is configured with pretty printing disabled by default for
* production use, but can be enabled for debugging purposes.
*
* Thread-safety: This class is thread-safe as Gson instances are thread-safe.
*
* @see MessageSerializer
* Implementação baseada em JSON da estratégia {@link MessageSerializer}, utilizando a biblioteca Gson.
* <p>
* Este serializador converte objetos Java para o formato de texto JSON antes da transmissão.
* Oferece várias vantagens técnicas sobre a serialização nativa do Java:
* <ul>
* <li><b>Legibilidade:</b> O formato de texto facilita a depuração (sniffing de rede) sem ferramentas especializadas.</li>
* <li><b>Interoperabilidade:</b> Permite futura integração com componentes não-Java (ex: Dashboards web em JS).</li>
* <li><b>Segurança:</b> Reduz a superfície de ataque para execução remota de código (RCE), pois não desserializa classes arbitrárias, apenas dados.</li>
* </ul>
* <p>
* <b>Thread-Safety:</b> A instância interna do {@code Gson} é imutável e thread-safe, permitindo
* que este serializador seja partilhado entre múltiplas threads (ex: no pool do DashboardServer).
* * @see MessageSerializer
*/
public class JsonMessageSerializer implements MessageSerializer {
@@ -28,16 +27,16 @@ public class JsonMessageSerializer implements MessageSerializer {
private final boolean prettyPrint;
/**
* Creates a new JSON serializer with default configuration (no pretty printing).
* Cria um novo serializador JSON com configuração otimizada para produção (compacto).
*/
public JsonMessageSerializer() {
this(false);
}
/**
* Creates a new JSON serializer with optional pretty printing.
*
* @param prettyPrint If true, JSON output will be formatted with indentation
* Cria um novo serializador JSON com formatação opcional.
* * @param prettyPrint Se {@code true}, o JSON gerado incluirá indentação e quebras de linha.
* Útil para debug, mas aumenta significativamente o tamanho do payload.
*/
public JsonMessageSerializer(boolean prettyPrint) {
this.prettyPrint = prettyPrint;
@@ -53,6 +52,13 @@ public class JsonMessageSerializer implements MessageSerializer {
this.gson = builder.create();
}
/**
* Converte um objeto em memória para um array de bytes JSON (UTF-8).
*
* @param object O objeto a ser serializado.
* @return O payload em bytes pronto para transmissão TCP.
* @throws SerializationException Se o objeto não for compatível com JSON ou ocorrer erro de encoding.
*/
@Override
public byte[] serialize(Object object) throws SerializationException {
if (object == null) {
@@ -68,6 +74,16 @@ public class JsonMessageSerializer implements MessageSerializer {
}
}
/**
* Reconstrói um objeto Java a partir de um array de bytes JSON.
* <p>
* Realiza a validação sintática do JSON e a validação de tipo baseada na classe alvo.
*
* @param data O array de bytes recebido da rede.
* @param clazz A classe do objeto esperado (Type Token).
* @return A instância do objeto reconstruído.
* @throws SerializationException Se o JSON for malformado ou incompatível com a classe alvo.
*/
@Override
public <T> T deserialize(byte[] data, Class<T> clazz) throws SerializationException {
if (data == null) {
@@ -95,18 +111,16 @@ public class JsonMessageSerializer implements MessageSerializer {
}
/**
* Returns the underlying Gson instance for advanced usage.
*
* @return The Gson instance
* Retorna a instância subjacente do Gson para configurações avançadas.
* * @return A instância Gson configurada.
*/
public Gson getGson() {
return gson;
}
/**
* Checks if pretty printing is enabled.
*
* @return true if pretty printing is enabled
* Verifica se a formatação "pretty print" está ativa.
* * @return true se a indentação estiver habilitada.
*/
public boolean isPrettyPrint() {
return prettyPrint;

View File

@@ -1,47 +1,48 @@
package sd.serialization;
/**
* Interface for serializing and deserializing objects for network transmission.
*
* This interface provides a common abstraction for different serialization strategies
* allowing the system to switch between implementations without changing the communication layer.
*
* Implementations must ensure:
* - Thread-safety if used in concurrent contexts
* - Proper exception handling with meaningful error messages
* - Preservation of object state during round-trip serialization
*
* @see JsonMessageSerializer
* Interface que define o contrato para estratégias de serialização e desserialização de objetos.
* <p>
* Esta abstração permite desacoplar a camada de transporte (Sockets TCP) da camada de
* apresentação de dados. Ao implementar o padrão <b>Strategy</b>, o sistema ganha flexibilidade
* para alternar entre diferentes formatos de codificação (JSON, Binário Nativo, XML, Protobuf)
* sem necessidade de refatorização da lógica de rede.
* <p>
* <b>Requisitos para Implementações:</b>
* <ul>
* <li><b>Thread-Safety:</b> As implementações devem ser seguras para uso concorrente, dado que
* instâncias únicas podem ser partilhadas por múltiplos <i>ClientHandlers</i>.</li>
* <li><b>Robustez:</b> Falhas de parsing devem resultar em exceções tipificadas ({@link SerializationException}),
* nunca em falhas silenciosas ou estados inconsistentes.</li>
* </ul>
* * @see JsonMessageSerializer
*/
public interface MessageSerializer {
/**
* Serializes an object into a byte array for transmission.
*
* @param object The object to serialize (must not be null)
* @return A byte array containing the serialized representation
* @throws SerializationException If serialization fails
* @throws IllegalArgumentException If object is null
* Converte (Marshals) um objeto em memória para uma sequência de bytes para transmissão.
* * @param object O objeto de domínio a ser serializado (não pode ser nulo).
* @return Um array de bytes contendo a representação codificada do objeto.
* @throws SerializationException Se ocorrer um erro durante a codificação (ex: ciclo de referências).
* @throws IllegalArgumentException Se o objeto fornecido for nulo.
*/
byte[] serialize(Object object) throws SerializationException;
/**
* Deserializes a byte array back into an object of the specified type.
*
* @param <T> The expected type of the deserialized object
* @param data The byte array containing serialized data (must not be null)
* @param clazz The class of the expected object type (must not be null)
* @return The deserialized object
* @throws SerializationException If deserialization fails
* @throws IllegalArgumentException If data or clazz is null
* Reconstrói (Unmarshals) um objeto a partir de uma sequência de bytes.
* * @param <T> O tipo genérico do objeto esperado.
* @param data O array de bytes contendo os dados serializados (não pode ser nulo).
* @param clazz A classe do tipo esperado para verificação e instancialização.
* @return A instância do objeto reconstruído com o seu estado restaurado.
* @throws SerializationException Se os dados estiverem corrompidos ou incompatíveis com a classe alvo.
* @throws IllegalArgumentException Se os dados ou a classe forem nulos.
*/
<T> T deserialize(byte[] data, Class<T> clazz) throws SerializationException;
/**
* Gets the name of this serialization strategy (e.g., "JSON", "Java Native").
* Useful for logging and debugging.
*
* @return The serializer name
* Obtém o identificador legível desta estratégia de serialização (ex: "JSON (Gson)", "Native").
* Utilizado primariamente para logging, auditoria e negociação de conteúdo.
* * @return O nome descritivo do serializador.
*/
String getName();

View File

@@ -1,39 +1,38 @@
package sd.serialization;
/**
* Exception thrown when serialization or deserialization operations fail.
*
* This exception wraps underlying errors (I/O exceptions, parsing errors, etc.)
* and provides context about what went wrong during the serialization process.
* Exceção verificada (Checked Exception) que sinaliza falhas no processo de transformação de dados.
* <p>
* Esta classe atua como um wrapper unificador para erros ocorridos na camada de serialização,
* abstraindo falhas de baixo nível (como erros de I/O, sintaxe JSON inválida ou incompatibilidade
* de tipos) numa única exceção de domínio. Permite que o código cliente trate falhas de
* protocolo de forma consistente, independentemente da implementação subjacente (Gson, Nativa, etc.).
*/
public class SerializationException extends Exception {
private static final long serialVersionUID = 1L; // Long(64bits) instead of int(32bits)
/**
* Constructs a new serialization exception with the specified detail message.
*
* @param message The detail message
* Constrói uma nova exceção de serialização com uma mensagem descritiva.
* * @param message A mensagem detalhando o erro.
*/
public SerializationException(String message) {
super(message);
}
/**
* Constructs a new serialization exception with the specified detail message
* and cause.
*
* @param message The detail message
* @param cause The cause of this exception
* Constrói uma nova exceção encapsulando a causa raiz do problema.
* Útil para preservar a stack trace original de erros de bibliotecas terceiras (ex: Gson).
* * @param message A mensagem detalhando o erro.
* @param cause A exceção original que causou a falha.
*/
public SerializationException(String message, Throwable cause) {
super(message, cause);
}
/**
* Constructs a new serialization exception with the specified cause.
*
* @param cause The cause of this exception
* Constrói uma nova exceção baseada apenas na causa raiz.
* * @param cause A exceção original.
*/
public SerializationException(Throwable cause) {
super(cause);

View File

@@ -1,14 +1,14 @@
package sd.serialization;
/**
* Factory for creating {@link MessageSerializer} instances.
*
* This factory provides a centralized way to create and configure JSON serializers
* using Gson, making it easy to configure serialization throughout the application.
*
* The factory can be configured via system properties for easy deployment configuration.
*
* Example usage:
* Fábrica estática (Factory Pattern) para instanciação controlada de {@link MessageSerializer}.
* <p>
* Esta classe centraliza a criação de estratégias de serialização, garantindo consistência
* de configuração em todo o sistema distribuído. Permite a injeção de configurações via
* Propriedades de Sistema (System Properties), facilitando a alternância entre modos de
* depuração (Pretty Print) e produção (Compacto) sem recompilação.
* <p>
* <b>Exemplo de Uso:</b>
* <pre>
* MessageSerializer serializer = SerializerFactory.createDefault();
* byte[] data = serializer.serialize(myObject);
@@ -17,28 +17,27 @@ package sd.serialization;
public class SerializerFactory {
/**
* System property key for enabling pretty-print in JSON serialization.
* Set to "true" for debugging, "false" for production.
* Chave da propriedade de sistema para ativar a formatação JSON legível (Pretty Print).
* Defina {@code -Dsd.serialization.json.prettyPrint=true} na JVM para ativar.
*/
public static final String JSON_PRETTY_PRINT_PROPERTY = "sd.serialization.json.prettyPrint";
// Default configuration
// Default configuration (Production-ready)
private static final boolean DEFAULT_JSON_PRETTY_PRINT = false;
/**
* Private constructor to prevent instantiation.
* Construtor privado para prevenir instanciação acidental desta classe utilitária.
*/
private SerializerFactory() {
throw new UnsupportedOperationException("Factory class cannot be instantiated");
}
/**
* Creates a JSON serializer based on system configuration.
*
* Pretty-print is determined by checking the system property
* {@value #JSON_PRETTY_PRINT_PROPERTY}. If not set, defaults to false.
*
* @return A configured JsonMessageSerializer instance
* Cria um serializador JSON configurado dinamicamente pelo ambiente.
* <p>
* Verifica a propriedade de sistema {@value #JSON_PRETTY_PRINT_PROPERTY}.
* Se não definida, assume o padrão de produção (falso/compacto).
* * @return Uma instância configurada de {@link JsonMessageSerializer}.
*/
public static MessageSerializer createDefault() {
boolean prettyPrint = Boolean.getBoolean(JSON_PRETTY_PRINT_PROPERTY);
@@ -46,19 +45,18 @@ public class SerializerFactory {
}
/**
* Creates a JSON serializer with default configuration (no pretty printing).
*
* @return A JsonMessageSerializer instance
* Cria um serializador JSON com configuração padrão otimizada (sem indentação).
* Ideal para ambientes de produção onde a largura de banda é prioritária.
* * @return Uma instância compacta de {@link JsonMessageSerializer}.
*/
public static MessageSerializer createSerializer() {
return createSerializer(DEFAULT_JSON_PRETTY_PRINT);
}
/**
* Creates a JSON serializer with specified pretty-print setting.
*
* @param prettyPrint Whether to enable pretty printing
* @return A JsonMessageSerializer instance
* Cria um serializador JSON com configuração explícita de formatação.
* * @param prettyPrint {@code true} para ativar indentação (Debug), {@code false} para compacto.
* @return Uma instância personalizada de {@link JsonMessageSerializer}.
*/
public static MessageSerializer createSerializer(boolean prettyPrint) {
return new JsonMessageSerializer(prettyPrint);

View File

@@ -3,82 +3,88 @@ package sd.util;
import java.util.Random;
/**
* Utilitário para gerar valores aleatórios usados na simulação.
*
* <p>Fornece métodos estáticos para:</p>
* Utilitário central de geração estocástica para a simulação.
* <p>
* Esta classe fornece primitivas para geração de números pseudo-aleatórios, abstraindo
* a complexidade de distribuições estatísticas.
* <p>
* <b>Funcionalidades Principais:</b>
* <ul>
* <li>Gerar intervalos exponencialmente distribuídos (processos de Poisson)</li>
* <li>Gerar inteiros e doubles aleatórios num intervalo</li>
* <li>Tomar decisões baseadas em probabilidade</li>
* <li>Escolher elementos aleatórios de um array</li>
* <li><b>Modelagem de Poisson:</b> Geração de tempos entre chegadas usando distribuição exponencial inversa.</li>
* <li><b>Amostragem Uniforme:</b> Geração de inteiros e doubles em intervalos fechados/abertos.</li>
* <li><b>Decisão Probabilística:</b> Avaliação de eventos booleanos baseados em pesos (Bernoulli trials).</li>
* <li><b>Determinismo:</b> Suporte a sementes (seeds) manuais para reprodutibilidade exata de cenários de teste.</li>
* </ul>
*
* <p>Usa uma única instância estática de {@link Random}.</p>
*/
public class RandomGenerator {
/** Instância partilhada de Random para toda a simulação */
/** * Instância singleton estática do gerador PRNG (Pseudo-Random Number Generator).
* Thread-safe (java.util.Random é sincronizado), embora possa haver contenção em alta concorrência.
*/
private static final Random random = new Random();
/**
* Retorna um intervalo de tempo que segue uma distribuição exponencial.
* Gera um intervalo de tempo seguindo uma Distribuição Exponencial.
* <p>
* Este método implementa o algoritmo de <i>Inverse Transform Sampling</i> para simular
* um Processo de Poisson homogêneo. É fundamental para modelar a chegada natural de
* veículos, onde eventos independentes ocorrem a uma taxa média constante.
* <p>
* <b>Fórmula Matemática:</b> {@code T = -ln(1 - U) / λ}
* <br>Onde:
* <ul>
* <li>{@code U}: Variável aleatória uniforme no intervalo [0, 1).</li>
* <li>{@code λ (lambda)}: Taxa média de eventos por unidade de tempo (ex: veículos/segundo).</li>
* </ul>
*
* <p>Componente essencial para modelar processos de Poisson, onde os
* tempos entre chegadas seguem uma distribuição exponencial.</p>
*
* <p>Fórmula: {@code Time = -ln(1 - U) / λ}<br>
* onde U é um número aleatório uniforme [0, 1) e λ (lambda) é a taxa média de chegada.</p>
*
* @param lambda taxa média de chegada λ (ex: 0.5 veículos por segundo)
* @return intervalo de tempo (segundos) até à próxima chegada
* @param lambda A taxa média de chegada (λ > 0).
* @return O intervalo de tempo (delta t) até o próximo evento, em segundos.
*/
public static double generateExponentialInterval(double lambda) {
return Math.log(1 - random.nextDouble()) / -lambda;
}
/**
* Retorna um inteiro aleatório entre {@code min} e {@code max}, inclusive.
* Gera um número inteiro uniformemente distribuído no intervalo fechado {@code [min, max]}.
*
* @param min valor mínimo possível
* @param max valor máximo possível
* @return inteiro aleatório no intervalo [min, max]
* @param min Limite inferior (inclusivo).
* @param max Limite superior (inclusivo).
* @return Um inteiro aleatório I tal que {@code min <= I <= max}.
*/
public static int generateRandomInt(int min, int max) {
return random.nextInt(max - min + 1) + min;
}
/**
* Retorna um double aleatório entre {@code min} (inclusive) e {@code max} (exclusivo).
* Gera um número de ponto flutuante uniformemente distribuído no intervalo semi-aberto {@code [min, max)}.
*
* @param min valor mínimo possível
* @param max valor máximo possível
* @return double aleatório no intervalo [min, max)
* @param min Limite inferior (inclusivo).
* @param max Limite superior (exclusivo).
* @return Um double aleatório D tal que {@code min <= D < max}.
*/
public static double generateRandomDouble(double min, double max) {
return min + (max - min) * random.nextDouble();
}
/**
* Retorna {@code true} com uma dada probabilidade.
* Realiza um teste de Bernoulli (Sim/Não) com uma probabilidade de sucesso especificada.
* <p>
* Utilizado para decisões de ramificação estocástica (ex: "Este veículo é um camião?").
*
* <p>Útil para tomar decisões ponderadas. Por exemplo,
* {@code occursWithProbability(0.3)} retorna {@code true}
* aproximadamente 30% das vezes.</p>
*
* @param probability valor entre 0.0 (nunca) e 1.0 (sempre)
* @return {@code true} ou {@code false}, baseado na probabilidade
* @param probability A probabilidade de retorno {@code true} (0.0 a 1.0).
* @return {@code true} se o evento ocorrer, {@code false} caso contrário.
*/
public static boolean occursWithProbability(double probability) {
return random.nextDouble() < probability;
}
/**
* Escolhe um elemento aleatório do array fornecido.
* Seleciona aleatoriamente um elemento de um array genérico (Amostragem Uniforme Discreta).
*
* @param <T> tipo genérico do array
* @param array array de onde escolher
* @return elemento selecionado aleatoriamente
* @throws IllegalArgumentException se o array for null ou vazio
* @param <T> O tipo dos elementos no array.
* @param array A população de onde escolher.
* @return O elemento selecionado.
* @throws IllegalArgumentException Se o array for nulo ou vazio.
*/
public static <T> T chooseRandom(T[] array) {
if (array == null || array.length == 0) {
@@ -88,13 +94,13 @@ public class RandomGenerator {
}
/**
* Define a seed do gerador de números aleatórios partilhado.
* Reinicializa a semente (seed) do gerador global.
* <p>
* <b>Importância Crítica:</b> Permite tornar a simulação determinística. Ao fixar a seed,
* a sequência de números "aleatórios" gerada será idêntica em execuções subsequentes,
* facilitando a depuração de race conditions ou lógica complexa.
*
* <p>Extremamente útil para debugging e testes, pois permite executar
* a simulação múltiplas vezes com a mesma sequência de eventos "aleatórios",
* tornando os resultados reproduzíveis.</p>
*
* @param seed seed a usar
* @param seed O valor da semente inicial (ex: timestamp ou constante).
*/
public static void setSeed(long seed) {
random.setSeed(seed);

View File

@@ -1,97 +1,66 @@
package sd.util;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import sd.config.SimulationConfig;
import sd.model.Vehicle;
import sd.model.VehicleType;
import sd.routing.RouteSelector;
/**
* Gera veículos para a simulação.
*
* <p>Esta classe é responsável por duas tarefas principais:</p>
* Motor de injeção de carga (Load Injector) para a simulação de tráfego.
* <p>
* Esta classe atua como uma fábrica estocástica de veículos, sendo responsável por:
* <ol>
* <li>Determinar <em>quando</em> o próximo veículo deve chegar, baseado no
* modelo de chegada (POISSON ou FIXED) da {@link SimulationConfig}</li>
* <li>Criar um novo objeto {@link Vehicle} com tipo e rota selecionados aleatoriamente</li>
* <li><b>Modelagem Temporal:</b> Determinar os instantes de chegada (Inter-arrival times)
* usando processos de Poisson (estocástico) ou intervalos determinísticos.</li>
* <li><b>Caracterização da Entidade:</b> Atribuir tipos de veículo (Bike, Light, Heavy)
* baseado numa Distribuição de Probabilidade Cumulativa (CDF).</li>
* <li><b>Inicialização Espacial:</b> Distribuir a carga uniformemente entre os pontos de entrada (E1-E3).</li>
* <li><b>Atribuição de Rota:</b> Delegar a escolha do percurso à estratégia {@link RouteSelector} ativa.</li>
* </ol>
*
* <p>As rotas são predefinidas e organizadas por ponto de entrada (E1, E2, E3).</p>
*/
public class VehicleGenerator {
private final SimulationConfig config;
private final String arrivalModel;
/** Lambda (λ) para modelo POISSON */
/** Parâmetro Lambda (λ) para a distribuição de Poisson (taxa de chegada). */
private final double arrivalRate;
/** Intervalo para modelo FIXED */
/** Intervalo determinístico para geração constante (modo debug/teste). */
private final double fixedInterval;
/** Rotas possíveis a partir do ponto de entrada E1 */
private final List<RouteWithProbability> e1Routes;
/** Rotas possíveis a partir do ponto de entrada E2 */
private final List<RouteWithProbability> e2Routes;
/** Rotas possíveis a partir do ponto de entrada E3 */
private final List<RouteWithProbability> e3Routes;
/** * Estratégia de roteamento atual.
* Não é final para permitir Hot-Swapping durante a execução.
*/
private RouteSelector routeSelector;
/**
* Cria um novo gerador de veículos.
* Lê a configuração necessária e inicializa as rotas predefinidas.
* Inicializa o gerador com as configurações de simulação e estratégia de roteamento.
*
* @param config objeto de {@link SimulationConfig}
* @param config A configuração global contendo as taxas e probabilidades.
* @param routeSelector A estratégia inicial de seleção de rotas.
*/
public VehicleGenerator(SimulationConfig config) {
public VehicleGenerator(SimulationConfig config, RouteSelector routeSelector) {
this.config = config;
this.routeSelector = routeSelector;
// Cache configuration values for performance
// Cache de valores de configuração para evitar lookups repetitivos em hot-path
this.arrivalModel = config.getArrivalModel();
this.arrivalRate = config.getArrivalRate();
this.fixedInterval = config.getFixedArrivalInterval();
// Initialize route lists
this.e1Routes = new ArrayList<>();
this.e2Routes = new ArrayList<>();
this.e3Routes = new ArrayList<>();
initializePossibleRoutes();
}
/**
* Define todas as rotas possíveis que os veículos podem tomar.
* As rotas são organizadas por ponto de entrada (E1, E2, E3).
* Cada rota tem uma probabilidade que determina a frequência com que é escolhida.
*/
private void initializePossibleRoutes() {
e1Routes.add(new RouteWithProbability(
Arrays.asList("Cr1", "Cr4", "Cr5", "S"), 0.34));
e1Routes.add(new RouteWithProbability(
Arrays.asList("Cr1", "Cr2", "Cr5", "S"), 0.33));
e1Routes.add(new RouteWithProbability(
Arrays.asList("Cr1", "Cr2", "Cr3", "S"), 0.33));
e2Routes.add(new RouteWithProbability(
Arrays.asList("Cr2", "Cr5", "S"), 0.34));
e2Routes.add(new RouteWithProbability(
Arrays.asList("Cr2", "Cr3", "S"), 0.33));
e2Routes.add(new RouteWithProbability(
Arrays.asList("Cr2", "Cr1", "Cr4", "Cr5", "S"), 0.33));
e3Routes.add(new RouteWithProbability(
Arrays.asList("Cr3", "S"), 0.34));
e3Routes.add(new RouteWithProbability(
Arrays.asList("Cr3", "Cr2", "Cr5", "S"), 0.33));
e3Routes.add(new RouteWithProbability(
Arrays.asList("Cr3", "Cr2", "Cr1", "Cr4", "Cr5", "S"), 0.33));
}
/**
* Calcula o tempo <em>absoluto</em> da próxima chegada de veículo
* baseado no modelo configurado.
*
* @param currentTime tempo atual da simulação, usado como base
* @return tempo absoluto (ex: {@code currentTime + intervalo})
* em que o próximo veículo deve ser gerado
* Calcula o timestamp absoluto para a próxima injeção de veículo.
* <p>
* Se o modelo for "POISSON", utiliza a técnica de <i>Inverse Transform Sampling</i>
* (via {@link RandomGenerator}) para gerar intervalos exponencialmente distribuídos,
* simulando a aleatoriedade natural do tráfego.
* * @param currentTime O tempo atual da simulação (base de cálculo).
* @return O instante futuro (t + delta) para agendamento do evento de geração.
*/
public double getNextArrivalTime(double currentTime) {
if ("POISSON".equalsIgnoreCase(arrivalModel)) {
@@ -103,38 +72,35 @@ public class VehicleGenerator {
}
/**
* Gera um novo objeto {@link Vehicle}.
*
* <p>Passos executados:</p>
* Instancia (Spawn) um novo veículo configurado e roteado.
* <p>
* O processo de criação segue um pipeline:
* <ol>
* <li>Seleciona um {@link VehicleType} aleatório baseado em probabilidades</li>
* <li>Seleciona uma rota aleatória (ponto de entrada + caminho)</li>
* <li>Seleção de Tipo (Roda da Fortuna / CDF).</li>
* <li>Seleção de Entrada (Uniforme).</li>
* <li>Cálculo de Rota (Delegado ao Strategy).</li>
* </ol>
*
* @param vehicleId identificador único do novo veículo (ex: "V123")
* @param entryTime tempo de simulação em que o veículo é criado
* @return novo objeto {@link Vehicle} configurado
* @param vehicleId O identificador único sequencial (ex: "V104").
* @param entryTime O timestamp de criação.
* @param queueSizes Snapshot atual das filas (usado apenas por estratégias dinâmicas como LEAST_CONGESTED).
* @return A entidade {@link Vehicle} pronta para inserção na malha.
*/
public Vehicle generateVehicle(String vehicleId, double entryTime) {
public Vehicle generateVehicle(String vehicleId, double entryTime, Map<String, Integer> queueSizes) {
VehicleType type = selectVehicleType();
List<String> route = selectRandomRoute();
String entryPoint = selectRandomEntryPoint();
List<String> route = routeSelector.selectRoute(entryPoint, queueSizes);
return new Vehicle(vehicleId, type, entryTime, route);
}
/**
* Seleciona um {@link VehicleType} (BIKE, LIGHT, HEAVY) baseado nas
* probabilidades definidas na {@link SimulationConfig}.
* Seleciona o tipo de veículo usando Amostragem por Probabilidade Cumulativa.
* <p>
* Normaliza as probabilidades configuradas e mapeia um número aleatório [0, 1)
* para o intervalo correspondente ao tipo de veículo.
*
* <p>Usa técnica de "probabilidade cumulativa":</p>
* <ol>
* <li>Obtém número aleatório {@code rand} de [0, 1)</li>
* <li>Se {@code rand < P(Bike)}, retorna BIKE</li>
* <li>Senão se {@code rand < P(Bike) + P(Light)}, retorna LIGHT</li>
* <li>Caso contrário, retorna HEAVY</li>
* </ol>
*
* @return tipo de veículo selecionado
* @return O tipo enumerado {@link VehicleType} selecionado.
*/
private VehicleType selectVehicleType() {
double bikeProbability = config.getBikeVehicleProbability();
@@ -142,7 +108,9 @@ public class VehicleGenerator {
double heavyProbability = config.getHeavyVehicleProbability();
double total = bikeProbability + lightProbability + heavyProbability;
if (total == 0) return VehicleType.LIGHT; // Avoid division by zero
if (total == 0) return VehicleType.LIGHT; // Fallback de segurança
// Normalização
bikeProbability /= total;
lightProbability /= total;
@@ -158,73 +126,42 @@ public class VehicleGenerator {
}
/**
* Selects a random route for a new vehicle.
* This is a two-step process:
* 1. Randomly select an entry point (E1, E2, or E3) with equal probability.
* 2. From the chosen entry point's list of routes, select one
* based on their defined probabilities (using cumulative probability).
* Seleciona um ponto de injeção na borda da rede (Edge Node).
* Distribuição Uniforme: ~33.3% para cada entrada (E1, E2, E3).
*
* @return A {@link List} of strings representing the chosen route (e.g., ["Cr1", "Cr4", "S"]).
* @return O ID da interseção de entrada.
*/
private List<String> selectRandomRoute() {
// Step 1: Randomly select an entry point (E1, E2, or E3)
double entryRandom = Math.random();
List<RouteWithProbability> selectedRoutes;
private String selectRandomEntryPoint() {
double rand = Math.random();
if (entryRandom < 0.333) {
selectedRoutes = e1Routes;
} else if (entryRandom < 0.666) {
selectedRoutes = e2Routes;
if (rand < 0.333) {
return "E1";
} else if (rand < 0.666) {
return "E2";
} else {
selectedRoutes = e3Routes;
return "E3";
}
// Step 2: Select a route from the chosen list based on cumulative probabilities
double routeRand = Math.random();
double cumulative = 0.0;
for (RouteWithProbability routeWithProb : selectedRoutes) {
cumulative += routeWithProb.probability;
if (routeRand <= cumulative) {
// Return a *copy* of the route to prevent modification
return new ArrayList<>(routeWithProb.route);
}
}
// Fallback: This should only be reached if probabilities don't sum to 1
// (due to floating point errors)
return new ArrayList<>(selectedRoutes.get(0).route);
}
/**
* @return A string providing information about the generator's configuration.
* Atualiza a estratégia de roteamento em tempo de execução (Hot-Swap).
* <p>
* Permite que o Coordenador altere o comportamento da frota (ex: de RANDOM para SHORTEST_PATH)
* sem necessidade de reiniciar a simulação.
* * @param newRouteSelector A nova implementação de estratégia a utilizar.
*/
public void setRouteSelector(RouteSelector newRouteSelector) {
this.routeSelector = newRouteSelector;
}
/**
* Retorna uma representação textual do estado interno do gerador.
* Útil para logs de auditoria e debugging.
*/
public String getInfo() {
int totalRoutes = e1Routes.size() + e2Routes.size() + e3Routes.size();
return String.format(
"VehicleGenerator{model=%s, rate=%.2f, interval=%.2f, routes=%d (E1:%d, E2:%d, E3:%d)}",
arrivalModel, arrivalRate, fixedInterval, totalRoutes,
e1Routes.size(), e2Routes.size(), e3Routes.size()
"VehicleGenerator{model=%s, rate=%.2f, interval=%.2f, routeSelector=%s}",
arrivalModel, arrivalRate, fixedInterval, routeSelector.getClass().getSimpleName()
);
}
/**
* A private inner "struct-like" class to hold a route (a List of strings)
* and its associated selection probability.
*/
private static class RouteWithProbability {
final List<String> route;
final double probability;
/**
* Constructs a new RouteWithProbability pair.
* @param route The list of intersection IDs.
* @param probability The probability (0.0 to 1.0) of this route
* being chosen *from its entry group*.
*/
RouteWithProbability(List<String> route, double probability) {
this.route = route;
this.probability = probability;
}
}
}

View File

@@ -1,6 +1,6 @@
/* Global Styles */
.root {
-fx-background-color: #f4f7f6;
-fx-background-color: #2b2b2b;
-fx-font-family: 'Segoe UI', sans-serif;
}
@@ -63,24 +63,24 @@
/* Cards / Panels */
.card {
-fx-background-color: white;
-fx-background-color: #1e1e1e;
-fx-background-radius: 8;
-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.05), 10, 0, 0, 2);
-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.3), 10, 0, 0, 2);
-fx-padding: 0;
}
.card-header {
-fx-background-color: #ecf0f1;
-fx-background-color: #3a3a3a;
-fx-background-radius: 8 8 0 0;
-fx-padding: 10 15;
-fx-border-color: #bdc3c7;
-fx-border-color: #555555;
-fx-border-width: 0 0 1 0;
}
.card-title {
-fx-font-size: 16px;
-fx-font-weight: bold;
-fx-text-fill: #2c3e50;
-fx-text-fill: white;
}
.card-content {
@@ -90,43 +90,48 @@
/* Statistics Grid */
.stat-label {
-fx-font-size: 14px;
-fx-text-fill: #7f8c8d;
-fx-text-fill: #cccccc;
}
.stat-value {
-fx-font-size: 20px;
-fx-font-weight: bold;
-fx-text-fill: #2980b9;
-fx-text-fill: #4ca1af;
}
/* Tables */
.table-view {
-fx-background-color: transparent;
-fx-background-color: #1e1e1e;
-fx-border-color: transparent;
}
.table-view .column-header-background {
-fx-background-color: #ecf0f1;
-fx-border-color: #bdc3c7;
-fx-background-color: #3a3a3a;
-fx-border-color: #555555;
-fx-border-width: 0 0 1 0;
}
.table-view .column-header .label {
-fx-text-fill: #2c3e50;
-fx-text-fill: white;
-fx-font-weight: bold;
}
.table-row-cell {
-fx-background-color: white;
-fx-background-color: #1e1e1e;
-fx-border-color: transparent;
-fx-text-fill: white;
}
.table-row-cell:odd {
-fx-background-color: #f9f9f9;
-fx-background-color: #252525;
}
.table-row-cell:selected {
-fx-background-color: #3498db;
-fx-background-color: #4ca1af;
-fx-text-fill: white;
}
.table-cell {
-fx-text-fill: white;
}

View File

@@ -27,7 +27,7 @@
},
{
"id": "Cr4",
"lights": ["East", "West"],
"lights": ["East", "North"],
"routes": {
"Cr1": "North",
"Cr5": "East"

View File

@@ -1,13 +1,4 @@
# =========================================================
# Traffic Simulation Configuration - HIGH LOAD SCENARIO
# ---------------------------------------------------------
# High traffic scenario for testing system under heavy load.
# Expected: Significant congestion, large queues, system stress test
# =========================================================
# === NETWORK CONFIGURATION ===
# Intersections (each with its host and port)
# Configuração de rede
intersection.Cr1.host=localhost
intersection.Cr1.port=8001
intersection.Cr2.host=localhost
@@ -19,99 +10,66 @@ intersection.Cr4.port=8004
intersection.Cr5.host=localhost
intersection.Cr5.port=8005
# Exit node
exit.host=localhost
exit.port=9001
# Dashboard server
dashboard.host=localhost
dashboard.port=9000
# === SIMULATION CONFIGURATION ===
# Total duration in seconds (1800 = 30 minutes)
# Configuração da simulação
# Cenário de carga alta - tráfego pesado, teste de stress do sistema
simulation.duration=1800
# Vehicle arrival model: FIXED or POISSON
simulation.arrival.model=POISSON
# λ (lambda): HIGH LOAD = 1.0 vehicle per second (60 vehicles/minute, 3600 vehicles/hour)
# This is 2x medium load - tests system capacity limits
simulation.arrival.rate=1.0
simulation.arrival.fixed.interval=2.0
simulation.routing.policy=LEAST_CONGESTED
# Fixed interval between arrivals (only used if model=FIXED)
simulation.arrival.fixed.interval=1.0
# Tempos dos semáforos (tempos realistas do mundo real, sem fase amarela)
# Cruzamento 1 - ponto de entrada, verde mais longo
trafficlight.Cr1.South.green=45.0
trafficlight.Cr1.South.red=45.0
trafficlight.Cr1.East.green=45.0
trafficlight.Cr1.East.red=45.0
# Cruzamento 2 - hub principal, gargalo crítico, tempos máximos de verde
trafficlight.Cr2.South.green=50.0
trafficlight.Cr2.South.red=50.0
trafficlight.Cr2.East.green=60.0
trafficlight.Cr2.East.red=40.0
trafficlight.Cr2.West.green=50.0
trafficlight.Cr2.West.red=50.0
# === TRAFFIC LIGHT TIMINGS ===
# Format: trafficlight.<intersection>.<direction>.<state>=<seconds>
# Aggressive timings to maximize throughput under high load
# Cruzamento 3 - caminho para a saída
trafficlight.Cr3.South.green=40.0
trafficlight.Cr3.South.red=45.0
trafficlight.Cr3.West.green=45.0
trafficlight.Cr3.West.red=40.0
# Intersection 1 (Entry point - longer greens to prevent early backup)
trafficlight.Cr1.South.green=60.0
trafficlight.Cr1.South.red=3.0
trafficlight.Cr1.East.green=60.0
trafficlight.Cr1.East.red=3.0
# Cruzamento 4
trafficlight.Cr4.East.green=45.0
trafficlight.Cr4.East.red=45.0
trafficlight.Cr4.North.green=45.0
trafficlight.Cr4.North.red=45.0
# Intersection 2 (Main hub - CRITICAL BOTTLENECK, maximum green times)
# This is the most critical intersection - all routes converge here
trafficlight.Cr2.South.green=70.0
trafficlight.Cr2.South.red=3.0
trafficlight.Cr2.East.green=80.0
trafficlight.Cr2.East.red=3.0
trafficlight.Cr2.West.green=70.0
trafficlight.Cr2.West.red=3.0
# Intersection 3 (Path to exit - maximize East throughput to exit)
trafficlight.Cr3.South.green=50.0
trafficlight.Cr3.South.red=3.0
trafficlight.Cr3.West.green=40.0
trafficlight.Cr3.West.red=3.0
# Intersection 4 (High throughput needed toward Cr5)
trafficlight.Cr4.East.green=70.0
trafficlight.Cr4.East.red=3.0
# Intersection 5 (Near exit - MAJOR BOTTLENECK, longest green time)
# All routes funnel through here before exit
# Cruzamento 5 - perto da saída, gargalo principal
trafficlight.Cr5.East.green=90.0
trafficlight.Cr5.East.red=3.0
trafficlight.Cr5.West.green=70.0
trafficlight.Cr5.West.red=3.0
trafficlight.Cr5.North.green=70.0
trafficlight.Cr5.North.red=3.0
# === VEHICLE CONFIGURATION ===
# Probability distribution for vehicle types (must sum to 1.0)
# Configuração de veículos
vehicle.probability.bike=0.2
vehicle.probability.light=0.6
vehicle.probability.heavy=0.2
# Average crossing times (in seconds)
vehicle.crossing.time.bike=1.0
vehicle.crossing.time.light=2.0
vehicle.crossing.time.heavy=4.0
# Travel times between intersections (in seconds)
# Base time for light vehicles (cars)
vehicle.travel.time.base=1.0
# Bike travel time = 0.5 × car travel time
vehicle.travel.time.bike.multiplier=0.5
# Heavy vehicle travel time = 4.0 x base travel time
vehicle.travel.time.heavy.multiplier=4.0
# === STATISTICS ===
# Interval between dashboard updates (seconds)
statistics.update.interval=10.0
# === EXPECTED BEHAVIOR - HIGH LOAD ===
# - Average system time: 200-400+ seconds (3-7+ minutes)
# - Maximum queue sizes: 15-30+ vehicles at Cr2 and Cr5
# - Average queue sizes: 8-15+ vehicles
# - Severe congestion at Cr2 (main convergence point)
# - Severe congestion at Cr5 (pre-exit bottleneck)
# - System utilization: ~80-95%
# - Many vehicles will remain in system at simulation end
# - Queue growth may be unbounded if arrival rate exceeds service rate
# - Primary bottlenecks: Cr2 (3-way convergence) and Cr5 (final funnel)
# - This scenario tests maximum system capacity and traffic light optimization
# - Expected to demonstrate need for adaptive traffic light policies

View File

@@ -1,13 +1,4 @@
# =========================================================
# Traffic Simulation Configuration - LOW LOAD SCENARIO
# ---------------------------------------------------------
# Low traffic scenario for testing system under light load.
# Expected: No congestion, minimal queues, fast vehicle throughput
# =========================================================
# === NETWORK CONFIGURATION ===
# Intersections (each with its host and port)
# Configuração de rede
intersection.Cr1.host=localhost
intersection.Cr1.port=8001
intersection.Cr2.host=localhost
@@ -19,93 +10,66 @@ intersection.Cr4.port=8004
intersection.Cr5.host=localhost
intersection.Cr5.port=8005
# Exit node
exit.host=localhost
exit.port=9001
# Dashboard server
dashboard.host=localhost
dashboard.port=9000
# === SIMULATION CONFIGURATION ===
# Total duration in seconds (1800 = 30 minutes)
# Configuração da simulação
# Cenário de carga baixa - tráfego leve para testar o sistema sem congestionamento
simulation.duration=1800
# Vehicle arrival model: FIXED or POISSON
simulation.arrival.model=POISSON
# λ (lambda): LOW LOAD = 0.2 vehicles per second (12 vehicles/minute, 720 vehicles/hour)
# This is approximately 40% of medium load
simulation.arrival.rate=0.2
simulation.arrival.fixed.interval=2.0
simulation.routing.policy=LEAST_CONGESTED
# Fixed interval between arrivals (only used if model=FIXED)
simulation.arrival.fixed.interval=5.0
# === TRAFFIC LIGHT TIMINGS ===
# Format: trafficlight.<intersection>.<direction>.<state>=<seconds>
# Standard timings - should be more than adequate for low load
# Intersection 1 (Entry point - balanced)
# Tempos dos semáforos (tempos realistas do mundo real, sem fase amarela)
# Cruzamento 1 - ponto de entrada, equilibrado
trafficlight.Cr1.South.green=30.0
trafficlight.Cr1.South.red=5.0
trafficlight.Cr1.South.red=30.0
trafficlight.Cr1.East.green=30.0
trafficlight.Cr1.East.red=5.0
trafficlight.Cr1.East.red=30.0
# Intersection 2 (Main hub - shorter cycles, favor East-West)
# Cruzamento 2 - hub principal
trafficlight.Cr2.South.green=30.0
trafficlight.Cr2.South.red=5.0
trafficlight.Cr2.South.red=30.0
trafficlight.Cr2.East.green=30.0
trafficlight.Cr2.East.red=5.0
trafficlight.Cr2.East.red=30.0
trafficlight.Cr2.West.green=30.0
trafficlight.Cr2.West.red=5.0
trafficlight.Cr2.West.red=30.0
# Intersection 3 (Path to exit - favor East)
# Cruzamento 3 - caminho para a saída
trafficlight.Cr3.South.green=30.0
trafficlight.Cr3.South.red=5.0
trafficlight.Cr3.South.red=30.0
trafficlight.Cr3.West.green=30.0
trafficlight.Cr3.West.red=5.0
trafficlight.Cr3.West.red=30.0
# Intersection 4 (Favor East toward Cr5)
# Cruzamento 4
trafficlight.Cr4.East.green=30.0
trafficlight.Cr4.East.red=5.0
trafficlight.Cr4.East.red=30.0
trafficlight.Cr4.North.green=30.0
trafficlight.Cr4.North.red=30.0
# Intersection 5 (Near exit - favor East)
# Cruzamento 5 - perto da saída
trafficlight.Cr5.East.green=30.0
trafficlight.Cr5.East.red=5.0
trafficlight.Cr5.West.green=30.0
trafficlight.Cr5.West.red=5.0
trafficlight.Cr5.North.green=30.0
trafficlight.Cr5.North.red=5.0
# === VEHICLE CONFIGURATION ===
# Probability distribution for vehicle types (must sum to 1.0)
# Configuração de veículos
vehicle.probability.bike=0.2
vehicle.probability.light=0.6
vehicle.probability.heavy=0.2
# Average crossing times (in seconds)
vehicle.crossing.time.bike=1.0
vehicle.crossing.time.light=2.0
vehicle.crossing.time.heavy=4.0
# Travel times between intersections (in seconds)
# Base time for light vehicles (cars)
vehicle.travel.time.base=1.0
# Bike travel time = 0.5 × car travel time
vehicle.travel.time.bike.multiplier=0.5
# Heavy vehicle travel time = 4.0 x base travel time
vehicle.travel.time.heavy.multiplier=4.0
# === STATISTICS ===
# Interval between dashboard updates (seconds)
statistics.update.interval=10.0
# === EXPECTED BEHAVIOR - LOW LOAD ===
# - Average system time: 40-80 seconds
# - Maximum queue sizes: 1-3 vehicles
# - Average queue sizes: < 1 vehicle
# - Vehicles should flow smoothly through the system
# - Minimal waiting at traffic lights (mostly travel time)
# - System utilization: ~20-30%
# - All vehicles should exit within simulation time

View File

@@ -1,13 +1,4 @@
# =========================================================
# Traffic Simulation Configuration - MEDIUM LOAD SCENARIO
# ---------------------------------------------------------
# Medium traffic scenario for testing system under normal load.
# Expected: Moderate queues, some congestion at peak intersections
# =========================================================
# === NETWORK CONFIGURATION ===
# Intersections (each with its host and port)
# Configuração de rede
intersection.Cr1.host=localhost
intersection.Cr1.port=8001
intersection.Cr2.host=localhost
@@ -19,94 +10,66 @@ intersection.Cr4.port=8004
intersection.Cr5.host=localhost
intersection.Cr5.port=8005
# Exit node
exit.host=localhost
exit.port=9001
# Dashboard server
dashboard.host=localhost
dashboard.port=9000
# === SIMULATION CONFIGURATION ===
# Total duration in seconds (1800 = 30 minutes)
# Configuração da simulação
# Cenário de carga média - tráfego normal com algum congestionamento
simulation.duration=1800
# Vehicle arrival model: FIXED or POISSON
simulation.arrival.model=POISSON
# λ (lambda): MEDIUM LOAD = 0.5 vehicles per second (30 vehicles/minute, 1800 vehicles/hour)
# This represents normal traffic conditions
simulation.arrival.rate=0.5
# Fixed interval between arrivals (only used if model=FIXED)
simulation.arrival.fixed.interval=2.0
simulation.routing.policy=LEAST_CONGESTED
# Tempos dos semáforos (tempos realistas do mundo real, sem fase amarela)
# Cruzamento 1 - ponto de entrada, equilibrado
trafficlight.Cr1.South.green=35.0
trafficlight.Cr1.South.red=35.0
trafficlight.Cr1.East.green=35.0
trafficlight.Cr1.East.red=35.0
# === TRAFFIC LIGHT TIMINGS ===
# Format: trafficlight.<intersection>.<direction>.<state>=<seconds>
# Optimized timings for medium load
# Cruzamento 2 - hub principal, gargalo crítico
trafficlight.Cr2.South.green=40.0
trafficlight.Cr2.South.red=40.0
trafficlight.Cr2.East.green=45.0
trafficlight.Cr2.East.red=35.0
trafficlight.Cr2.West.green=40.0
trafficlight.Cr2.West.red=40.0
# Intersection 1 (Entry point - balanced)
trafficlight.Cr1.South.green=40.0
trafficlight.Cr1.South.red=5.0
trafficlight.Cr1.East.green=40.0
trafficlight.Cr1.East.red=5.0
# Cruzamento 3 - caminho para a saída
trafficlight.Cr3.South.green=35.0
trafficlight.Cr3.South.red=40.0
trafficlight.Cr3.West.green=40.0
trafficlight.Cr3.West.red=35.0
# Intersection 2 (Main hub - CRITICAL BOTTLENECK, longer green times)
trafficlight.Cr2.South.green=45.0
trafficlight.Cr2.South.red=5.0
trafficlight.Cr2.East.green=50.0
trafficlight.Cr2.East.red=5.0
trafficlight.Cr2.West.green=45.0
trafficlight.Cr2.West.red=5.0
# Cruzamento 4
trafficlight.Cr4.East.green=35.0
trafficlight.Cr4.East.red=35.0
trafficlight.Cr4.North.green=35.0
trafficlight.Cr4.North.red=35.0
# Intersection 3 (Path to exit - favor East toward exit)
trafficlight.Cr3.South.green=40.0
trafficlight.Cr3.South.red=5.0
trafficlight.Cr3.West.green=35.0
trafficlight.Cr3.West.red=5.0
# Intersection 4 (Favor East toward Cr5)
trafficlight.Cr4.East.green=40.0
trafficlight.Cr4.East.red=5.0
# Intersection 5 (Near exit - POTENTIAL BOTTLENECK, longer green)
# Cruzamento 5 - perto da saída, possível gargalo
trafficlight.Cr5.East.green=50.0
trafficlight.Cr5.East.red=5.0
trafficlight.Cr5.West.green=45.0
trafficlight.Cr5.West.red=5.0
trafficlight.Cr5.North.green=45.0
trafficlight.Cr5.North.red=5.0
# === VEHICLE CONFIGURATION ===
# Probability distribution for vehicle types (must sum to 1.0)
# Configuração de veículos
vehicle.probability.bike=0.2
vehicle.probability.light=0.6
vehicle.probability.heavy=0.2
# Average crossing times (in seconds)
vehicle.crossing.time.bike=1.0
vehicle.crossing.time.light=2.0
vehicle.crossing.time.heavy=4.0
# Travel times between intersections (in seconds)
# Base time for light vehicles (cars)
vehicle.travel.time.base=1.0
# Bike travel time = 0.5 × car travel time
vehicle.travel.time.bike.multiplier=0.5
# Heavy vehicle travel time = 4.0 x base travel time
vehicle.travel.time.heavy.multiplier=4.0
# === STATISTICS ===
# Interval between dashboard updates (seconds)
statistics.update.interval=10.0
# === EXPECTED BEHAVIOR - MEDIUM LOAD ===
# - Average system time: 80-150 seconds
# - Maximum queue sizes: 5-10 vehicles at Cr2 and Cr5
# - Average queue sizes: 2-5 vehicles
# - Moderate congestion at Cr2 (main hub) and Cr5 (pre-exit)
# - System utilization: ~50-60%
# - Most vehicles should exit, some may remain at simulation end
# - Cr2 is the primary bottleneck (3 directions converge)
# - Cr5 is secondary bottleneck (all routes pass through)

View File

@@ -1,13 +1,4 @@
# =========================================================
# Traffic Simulation Configuration
# ---------------------------------------------------------
# All parameters controlling network layout, timing,
# and simulation behavior.
# =========================================================
# === NETWORK CONFIGURATION ===
# Intersections (each with its host and port)
# Configuração de rede
intersection.Cr1.host=localhost
intersection.Cr1.port=8001
intersection.Cr2.host=localhost
@@ -19,86 +10,60 @@ intersection.Cr4.port=8004
intersection.Cr5.host=localhost
intersection.Cr5.port=8005
# Exit node
exit.host=localhost
exit.port=9001
# Dashboard server
dashboard.host=localhost
dashboard.port=9000
# === SIMULATION CONFIGURATION ===
# Total duration in seconds (3600 = 1 hour)
# Configuração da simulação
simulation.duration=300
# Time scaling factor for visualization (real_seconds = sim_seconds * scale)
# 0 = instant (pure DES), 0.01 = 100x speed, 0.1 = 10x speed, 1.0 = real-time
simulation.time.scale=0.01
# Vehicle arrival model: FIXED or POISSON
simulation.arrival.model=POISSON
# λ (lambda): average arrival rate (vehicles per second)
simulation.arrival.rate=0.5
# Fixed interval between arrivals (only used if model=FIXED)
simulation.arrival.fixed.interval=2.0
simulation.routing.policy=RANDOM
# Tempos dos semáforos (tempos realistas do mundo real, sem fase amarela)
# Cruzamento 1 - ponto de entrada, equilibrado
trafficlight.Cr1.South.green=35.0
trafficlight.Cr1.South.red=35.0
trafficlight.Cr1.East.green=35.0
trafficlight.Cr1.East.red=35.0
# === TRAFFIC LIGHT TIMINGS ===
# Format: trafficlight.<intersection>.<direction>.<state>=<seconds>
# Cruzamento 2 - hub principal
trafficlight.Cr2.South.green=40.0
trafficlight.Cr2.South.red=40.0
trafficlight.Cr2.East.green=40.0
trafficlight.Cr2.East.red=40.0
trafficlight.Cr2.West.green=40.0
trafficlight.Cr2.West.red=40.0
# Intersection 1 (Entry point - balanced)
trafficlight.Cr1.South.green=60.0
trafficlight.Cr1.South.red=5.0
trafficlight.Cr1.East.green=60.0
trafficlight.Cr1.East.red=5.0
# Cruzamento 3 - caminho para a saída
trafficlight.Cr3.South.green=35.0
trafficlight.Cr3.South.red=40.0
trafficlight.Cr3.West.green=40.0
trafficlight.Cr3.West.red=35.0
# Intersection 2 (Main hub - shorter cycles, favor East-West)
trafficlight.Cr2.South.green=60.0
trafficlight.Cr2.South.red=5.0
trafficlight.Cr2.East.green=60.0
trafficlight.Cr2.East.red=5.0
trafficlight.Cr2.West.green=60.0
trafficlight.Cr2.West.red=5.0
# Cruzamento 4
trafficlight.Cr4.East.green=35.0
trafficlight.Cr4.East.red=35.0
# Intersection 3 (Path to exit - favor East)
trafficlight.Cr3.South.green=60.0
trafficlight.Cr3.South.red=5.0
trafficlight.Cr3.West.green=60.0
trafficlight.Cr3.West.red=5.0
# Cruzamento 5 - perto da saída
trafficlight.Cr5.East.green=35.0
trafficlight.Cr5.East.red=35.0
# Intersection 4 (Favor East toward Cr5)
trafficlight.Cr4.East.green=60.0
trafficlight.Cr4.East.red=5.0
# Intersection 5 (Near exit - favor East)
trafficlight.Cr5.East.green=60.0
trafficlight.Cr5.East.red=5.0
# === VEHICLE CONFIGURATION ===
# Probability distribution for vehicle types (must sum to 1.0)
# Configuração de veículos
vehicle.probability.bike=0.2
vehicle.probability.light=0.6
vehicle.probability.heavy=0.2
# Average crossing times (in seconds)
vehicle.crossing.time.bike=1.0
vehicle.crossing.time.light=2.0
vehicle.crossing.time.heavy=4.0
# Travel times between intersections (in seconds)
# Base time for light vehicles (cars)
vehicle.travel.time.base=1.0
# Bike travel time = 0.5 × car travel time
vehicle.travel.time.bike.multiplier=0.5
# Heavy vehicle travel time = 4.0 x base travel time
vehicle.travel.time.heavy.multiplier=4.0
# === STATISTICS ===
# Interval between dashboard updates (seconds)
statistics.update.interval=0.1

View File

@@ -1,60 +0,0 @@
#!/bin/bash
# Distributed Traffic Simulation Startup Script
# kill java
echo "-> Cleaning up existing processes..."
pkill -9 java 2>/dev/null
sleep 2
# build
echo "-> Building project..."
cd "$(dirname "$0")"
mvn package -DskipTests -q
if [ $? -ne 0 ]; then
echo "XXX Build failed! XXX"
exit 1
fi
echo "-> Build complete"
echo ""
# start gui
echo "-> Starting JavaFX Dashboard..."
mvn javafx:run &
DASHBOARD_PID=$!
sleep 3
# acho que é assim idk
echo "-> Starting 5 Intersection processes..."
for id in Cr1 Cr2 Cr3 Cr4 Cr5; do
java -cp target/classes:target/main-1.0-SNAPSHOT.jar sd.IntersectionProcess $id > /tmp/$(echo $id | tr '[:upper:]' '[:lower:]').log 2>&1 &
echo "[SUCCESS] Started $id"
done
sleep 2
# exit
echo "-> Starting Exit Node..."
java -cp target/classes:target/main-1.0-SNAPSHOT.jar sd.ExitNodeProcess > /tmp/exit.log 2>&1 &
sleep 1
# coordinator
echo "-> Starting Coordinator..."
java -cp target/classes:target/main-1.0-SNAPSHOT.jar sd.coordinator.CoordinatorProcess > /tmp/coordinator.log 2>&1 &
sleep 1
echo ""
echo "-> All processes started!"
echo ""
echo "-> System Status:"
ps aux | grep "java.*sd\." | grep -v grep | wc -l | xargs -I {} echo " {} Java processes running"
echo ""
echo " IMPORTANT: Keep the JavaFX Dashboard window OPEN for 60+ seconds"
echo " to see live updates! The simulation runs for 60 seconds."
echo ""
echo "-> Logs available at:"
echo " Dashboard: Check JavaFX window (live updates)"
echo " Intersections: /tmp/cr*.log"
echo " Exit Node: /tmp/exit.log"
echo " Coordinator: /tmp/coordinator.log"
echo ""
echo "-> To stop all processes: pkill -9 java"
echo ""

File diff suppressed because it is too large Load Diff