turbolane-cli 2.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- turbolane_cli-2.0.0/PKG-INFO +206 -0
- turbolane_cli-2.0.0/README.md +190 -0
- turbolane_cli-2.0.0/setup.cfg +4 -0
- turbolane_cli-2.0.0/setup.py +27 -0
- turbolane_cli-2.0.0/turbolane/__init__.py +10 -0
- turbolane_cli-2.0.0/turbolane/engine.py +218 -0
- turbolane_cli-2.0.0/turbolane/policies/__init__.py +4 -0
- turbolane_cli-2.0.0/turbolane/policies/federated.py +528 -0
- turbolane_cli-2.0.0/turbolane/rl/__init__.py +5 -0
- turbolane_cli-2.0.0/turbolane/rl/agent.py +532 -0
- turbolane_cli-2.0.0/turbolane/rl/storage.py +177 -0
- turbolane_cli-2.0.0/turbolane_cli.egg-info/PKG-INFO +206 -0
- turbolane_cli-2.0.0/turbolane_cli.egg-info/SOURCES.txt +22 -0
- turbolane_cli-2.0.0/turbolane_cli.egg-info/dependency_links.txt +1 -0
- turbolane_cli-2.0.0/turbolane_cli.egg-info/entry_points.txt +2 -0
- turbolane_cli-2.0.0/turbolane_cli.egg-info/top_level.txt +2 -0
- turbolane_cli-2.0.0/turbolane_server/__init__.py +19 -0
- turbolane_cli-2.0.0/turbolane_server/adapter.py +224 -0
- turbolane_cli-2.0.0/turbolane_server/cli.py +309 -0
- turbolane_cli-2.0.0/turbolane_server/metrics.py +193 -0
- turbolane_cli-2.0.0/turbolane_server/protocol.py +177 -0
- turbolane_cli-2.0.0/turbolane_server/sender.py +179 -0
- turbolane_cli-2.0.0/turbolane_server/server.py +522 -0
- turbolane_cli-2.0.0/turbolane_server/transfer.py +796 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: turbolane-cli
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: RL-optimized parallel TCP file transfer CLI (TurboLane)
|
|
5
|
+
Classifier: Development Status :: 3 - Alpha
|
|
6
|
+
Classifier: Intended Audience :: Science/Research
|
|
7
|
+
Classifier: Topic :: System :: Networking
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Dynamic: classifier
|
|
12
|
+
Dynamic: description
|
|
13
|
+
Dynamic: description-content-type
|
|
14
|
+
Dynamic: requires-python
|
|
15
|
+
Dynamic: summary
|
|
16
|
+
|
|
17
|
+
# TurboLane — Phase 2: CLI File Transfer Server
|
|
18
|
+
|
|
19
|
+
RL-optimized parallel TCP file transfer system. Phase 2 builds a production-grade
|
|
20
|
+
CLI application on top of the Phase 1 TurboLane engine.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Folder Structure
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
.
|
|
28
|
+
├── turbolane/ # Phase 1 — RL engine (UNCHANGED)
|
|
29
|
+
│ ├── __init__.py
|
|
30
|
+
│ ├── engine.py # TurboLaneEngine — only public import
|
|
31
|
+
│ ├── policies/
|
|
32
|
+
│ │ └── federated.py # FederatedPolicy (DCI / Q-learning)
|
|
33
|
+
│ └── rl/
|
|
34
|
+
│ ├── agent.py # RLAgent (Q-table, Bellman updates)
|
|
35
|
+
│ └── storage.py # QTableStorage (atomic JSON persistence)
|
|
36
|
+
│
|
|
37
|
+
├── turbolane_server/ # Phase 2 — CLI application layer
|
|
38
|
+
│ ├── __init__.py
|
|
39
|
+
│ ├── protocol.py # Binary wire protocol (struct + CRC32)
|
|
40
|
+
│ ├── metrics.py # In-app RTT / throughput / loss metrics
|
|
41
|
+
│ ├── adapter.py # TurboLaneAdapter — engine ↔ server bridge
|
|
42
|
+
│ ├── transfer.py # TransferSession + StreamWorker (sender side)
|
|
43
|
+
│ ├── server.py # TurboLaneServer + FileAssembler (receiver)
|
|
44
|
+
│ ├── sender.py # TurboLaneSender — orchestrator
|
|
45
|
+
│ └── cli.py # argparse CLI: start / send / status
|
|
46
|
+
│
|
|
47
|
+
├── models/
|
|
48
|
+
│ └── dci/ # Q-table persistence directory
|
|
49
|
+
├── setup.py
|
|
50
|
+
└── README.md
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Architecture
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
SENDER SIDE RECEIVER SIDE
|
|
59
|
+
─────────────────────────────── ────────────────────────────
|
|
60
|
+
turbolane-server send turbolane-server start
|
|
61
|
+
│ │
|
|
62
|
+
TurboLaneSender TurboLaneServer
|
|
63
|
+
│ │
|
|
64
|
+
┌─────────────────┐ ┌────────────────────┐
|
|
65
|
+
│ TurboLaneAdapter│ │ accept loop │
|
|
66
|
+
│ (5s RL loop) │ │ (one thread/conn)│
|
|
67
|
+
│ │ └────────────────────┘
|
|
68
|
+
│ TurboLaneEngine │ │
|
|
69
|
+
│ (embedded DCI) │ ┌────────────────────┐
|
|
70
|
+
└────────┬────────┘ │ StreamHandler │
|
|
71
|
+
│ adjust_streams(n) │ HELLO/CHUNK/PING │
|
|
72
|
+
▼ └────────────────────┘
|
|
73
|
+
TransferSession │
|
|
74
|
+
┌─────────────────────────────┐ ┌────────────────────┐
|
|
75
|
+
│ ChunkQueue (thread-safe) │ │ FileAssembler │
|
|
76
|
+
│ StreamWorker × N │ │ (sparse write, │
|
|
77
|
+
│ (one thread per stream) │ │ out-of-order OK) │
|
|
78
|
+
└─────────────────────────────┘ └────────────────────┘
|
|
79
|
+
│ N×TCP connections
|
|
80
|
+
└──────────────────────────────────────────────┘
|
|
81
|
+
|
|
82
|
+
MetricsCollector (shared)
|
|
83
|
+
├── per-stream StreamMetrics
|
|
84
|
+
├── RTT: in-app PING/PONG round-trip timing
|
|
85
|
+
├── Throughput: bytes_sent / elapsed per snapshot
|
|
86
|
+
└── Loss: chunk retransmit rate proxy
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Key design rules
|
|
90
|
+
- **TurboLane engine is completely decoupled** — only `adapter.py` imports from `turbolane.*`
|
|
91
|
+
- **No networking in the engine** — sockets live only in `transfer.py` and `server.py`
|
|
92
|
+
- **Single transfer lock** — server rejects new connections with `BUSY` during active transfer
|
|
93
|
+
- **RTT without root** — measured via application-layer PING/PONG timing per stream
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Wire Protocol
|
|
98
|
+
|
|
99
|
+
Binary struct header (34 bytes, big-endian) + payload:
|
|
100
|
+
|
|
101
|
+
| Field | Bytes | Description |
|
|
102
|
+
|--------------|-------|--------------------------------------|
|
|
103
|
+
| magic | 4 | `0x544C414E` ("TLAN") |
|
|
104
|
+
| msg_type | 1 | MessageType enum |
|
|
105
|
+
| stream_id | 1 | Parallel stream index (0-255) |
|
|
106
|
+
| chunk_idx | 4 | Chunk index within file |
|
|
107
|
+
| total_chunks | 4 | Total chunks in transfer |
|
|
108
|
+
| seq | 4 | Sequence number |
|
|
109
|
+
| file_offset | 8 | Byte offset in source file |
|
|
110
|
+
| data_len | 4 | Payload length (0 for control) |
|
|
111
|
+
| checksum | 4 | CRC32 of payload |
|
|
112
|
+
| **payload** | N | Raw file bytes / JSON metadata |
|
|
113
|
+
|
|
114
|
+
Message types: `HELLO`, `HELLO_ACK`, `CHUNK`, `CHUNK_ACK`, `PING`, `PONG`,
|
|
115
|
+
`TRANSFER_DONE`, `COMPLETE`, `ERROR`, `BUSY`, `STATUS_REQ`, `STATUS_RESP`
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Installation
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
# From the project root (where setup.py lives)
|
|
123
|
+
pip install -e .
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Usage
|
|
129
|
+
|
|
130
|
+
### 1. Start the receiver server
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
turbolane-server start --port 9000 --output-dir ./received
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Options:
|
|
137
|
+
```
|
|
138
|
+
--host HOST Bind interface (default: 0.0.0.0)
|
|
139
|
+
--port PORT TCP port (default: 9000)
|
|
140
|
+
--output-dir DIR Where to save received files (default: ./received)
|
|
141
|
+
--verbose / -v Debug logging
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### 2. Send a file
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
turbolane-server send /data/large_dataset.tar \
|
|
148
|
+
--host 192.168.1.50 --port 9000 \
|
|
149
|
+
--streams 6 \
|
|
150
|
+
--min-streams 1 --max-streams 32 \
|
|
151
|
+
--model-dir models/dci \
|
|
152
|
+
--interval 5.0
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Options:
|
|
156
|
+
```
|
|
157
|
+
FILE File to send (required positional)
|
|
158
|
+
--host HOST Receiver hostname/IP (required)
|
|
159
|
+
--port PORT Receiver port (default: 9000)
|
|
160
|
+
--streams N Initial parallel TCP streams (default: 4)
|
|
161
|
+
--min-streams N Minimum streams TurboLane may use (default: 1)
|
|
162
|
+
--max-streams N Maximum streams TurboLane may use (default: 32)
|
|
163
|
+
--model-dir DIR Q-table persistence directory (default: models/dci)
|
|
164
|
+
--interval SECS RL decision interval in seconds (default: 5.0)
|
|
165
|
+
--timeout SECS Max wait for completion (default: unlimited)
|
|
166
|
+
--verbose / -v Debug logging
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### 3. Query server status
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
turbolane-server status --host 192.168.1.50 --port 9000
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## How TurboLane integrates (5-second loop)
|
|
178
|
+
|
|
179
|
+
```
|
|
180
|
+
Every 5 seconds (TurboLaneAdapter._tick):
|
|
181
|
+
1. MetricsCollector.snapshot()
|
|
182
|
+
→ throughput_mbps (sum of per-stream byte rates)
|
|
183
|
+
→ rtt_ms (mean of PING/PONG RTT samples)
|
|
184
|
+
→ loss_pct (chunk retransmit rate proxy)
|
|
185
|
+
|
|
186
|
+
2. engine.learn(throughput, rtt, loss)
|
|
187
|
+
→ Q-table Bellman update from previous decision's outcome
|
|
188
|
+
|
|
189
|
+
3. engine.decide(throughput, rtt, loss)
|
|
190
|
+
→ Q-learning ε-greedy action → new stream count
|
|
191
|
+
|
|
192
|
+
4. session.adjust_streams(new_count)
|
|
193
|
+
→ spawn / stop StreamWorker threads to match recommendation
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Future upgrades (designed-in hooks)
|
|
199
|
+
|
|
200
|
+
| Capability | Where to add |
|
|
201
|
+
|----------------------|---------------------------|
|
|
202
|
+
| PPO algorithm | `turbolane/rl/` only |
|
|
203
|
+
| Multi-session | `server.py` busy logic |
|
|
204
|
+
| Shared policy learning | `adapter.py` FederatedPolicy |
|
|
205
|
+
| Resume / checkpointing | `ChunkQueue` + `FileAssembler` |
|
|
206
|
+
| TLS encryption | `StreamWorker` + `StreamHandler` |
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# TurboLane — Phase 2: CLI File Transfer Server
|
|
2
|
+
|
|
3
|
+
RL-optimized parallel TCP file transfer system. Phase 2 builds a production-grade
|
|
4
|
+
CLI application on top of the Phase 1 TurboLane engine.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Folder Structure
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
.
|
|
12
|
+
├── turbolane/ # Phase 1 — RL engine (UNCHANGED)
|
|
13
|
+
│ ├── __init__.py
|
|
14
|
+
│ ├── engine.py # TurboLaneEngine — only public import
|
|
15
|
+
│ ├── policies/
|
|
16
|
+
│ │ └── federated.py # FederatedPolicy (DCI / Q-learning)
|
|
17
|
+
│ └── rl/
|
|
18
|
+
│ ├── agent.py # RLAgent (Q-table, Bellman updates)
|
|
19
|
+
│ └── storage.py # QTableStorage (atomic JSON persistence)
|
|
20
|
+
│
|
|
21
|
+
├── turbolane_server/ # Phase 2 — CLI application layer
|
|
22
|
+
│ ├── __init__.py
|
|
23
|
+
│ ├── protocol.py # Binary wire protocol (struct + CRC32)
|
|
24
|
+
│ ├── metrics.py # In-app RTT / throughput / loss metrics
|
|
25
|
+
│ ├── adapter.py # TurboLaneAdapter — engine ↔ server bridge
|
|
26
|
+
│ ├── transfer.py # TransferSession + StreamWorker (sender side)
|
|
27
|
+
│ ├── server.py # TurboLaneServer + FileAssembler (receiver)
|
|
28
|
+
│ ├── sender.py # TurboLaneSender — orchestrator
|
|
29
|
+
│ └── cli.py # argparse CLI: start / send / status
|
|
30
|
+
│
|
|
31
|
+
├── models/
|
|
32
|
+
│ └── dci/ # Q-table persistence directory
|
|
33
|
+
├── setup.py
|
|
34
|
+
└── README.md
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Architecture
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
SENDER SIDE RECEIVER SIDE
|
|
43
|
+
─────────────────────────────── ────────────────────────────
|
|
44
|
+
turbolane-server send turbolane-server start
|
|
45
|
+
│ │
|
|
46
|
+
TurboLaneSender TurboLaneServer
|
|
47
|
+
│ │
|
|
48
|
+
┌─────────────────┐ ┌────────────────────┐
|
|
49
|
+
│ TurboLaneAdapter│ │ accept loop │
|
|
50
|
+
│ (5s RL loop) │ │ (one thread/conn)│
|
|
51
|
+
│ │ └────────────────────┘
|
|
52
|
+
│ TurboLaneEngine │ │
|
|
53
|
+
│ (embedded DCI) │ ┌────────────────────┐
|
|
54
|
+
└────────┬────────┘ │ StreamHandler │
|
|
55
|
+
│ adjust_streams(n) │ HELLO/CHUNK/PING │
|
|
56
|
+
▼ └────────────────────┘
|
|
57
|
+
TransferSession │
|
|
58
|
+
┌─────────────────────────────┐ ┌────────────────────┐
|
|
59
|
+
│ ChunkQueue (thread-safe) │ │ FileAssembler │
|
|
60
|
+
│ StreamWorker × N │ │ (sparse write, │
|
|
61
|
+
│ (one thread per stream) │ │ out-of-order OK) │
|
|
62
|
+
└─────────────────────────────┘ └────────────────────┘
|
|
63
|
+
│ N×TCP connections
|
|
64
|
+
└──────────────────────────────────────────────┘
|
|
65
|
+
|
|
66
|
+
MetricsCollector (shared)
|
|
67
|
+
├── per-stream StreamMetrics
|
|
68
|
+
├── RTT: in-app PING/PONG round-trip timing
|
|
69
|
+
├── Throughput: bytes_sent / elapsed per snapshot
|
|
70
|
+
└── Loss: chunk retransmit rate proxy
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Key design rules
|
|
74
|
+
- **TurboLane engine is completely decoupled** — only `adapter.py` imports from `turbolane.*`
|
|
75
|
+
- **No networking in the engine** — sockets live only in `transfer.py` and `server.py`
|
|
76
|
+
- **Single transfer lock** — server rejects new connections with `BUSY` during active transfer
|
|
77
|
+
- **RTT without root** — measured via application-layer PING/PONG timing per stream
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Wire Protocol
|
|
82
|
+
|
|
83
|
+
Binary struct header (34 bytes, big-endian) + payload:
|
|
84
|
+
|
|
85
|
+
| Field | Bytes | Description |
|
|
86
|
+
|--------------|-------|--------------------------------------|
|
|
87
|
+
| magic | 4 | `0x544C414E` ("TLAN") |
|
|
88
|
+
| msg_type | 1 | MessageType enum |
|
|
89
|
+
| stream_id | 1 | Parallel stream index (0-255) |
|
|
90
|
+
| chunk_idx | 4 | Chunk index within file |
|
|
91
|
+
| total_chunks | 4 | Total chunks in transfer |
|
|
92
|
+
| seq | 4 | Sequence number |
|
|
93
|
+
| file_offset | 8 | Byte offset in source file |
|
|
94
|
+
| data_len | 4 | Payload length (0 for control) |
|
|
95
|
+
| checksum | 4 | CRC32 of payload |
|
|
96
|
+
| **payload** | N | Raw file bytes / JSON metadata |
|
|
97
|
+
|
|
98
|
+
Message types: `HELLO`, `HELLO_ACK`, `CHUNK`, `CHUNK_ACK`, `PING`, `PONG`,
|
|
99
|
+
`TRANSFER_DONE`, `COMPLETE`, `ERROR`, `BUSY`, `STATUS_REQ`, `STATUS_RESP`
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Installation
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
# From the project root (where setup.py lives)
|
|
107
|
+
pip install -e .
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Usage
|
|
113
|
+
|
|
114
|
+
### 1. Start the receiver server
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
turbolane-server start --port 9000 --output-dir ./received
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Options:
|
|
121
|
+
```
|
|
122
|
+
--host HOST Bind interface (default: 0.0.0.0)
|
|
123
|
+
--port PORT TCP port (default: 9000)
|
|
124
|
+
--output-dir DIR Where to save received files (default: ./received)
|
|
125
|
+
--verbose / -v Debug logging
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### 2. Send a file
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
turbolane-server send /data/large_dataset.tar \
|
|
132
|
+
--host 192.168.1.50 --port 9000 \
|
|
133
|
+
--streams 6 \
|
|
134
|
+
--min-streams 1 --max-streams 32 \
|
|
135
|
+
--model-dir models/dci \
|
|
136
|
+
--interval 5.0
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Options:
|
|
140
|
+
```
|
|
141
|
+
FILE File to send (required positional)
|
|
142
|
+
--host HOST Receiver hostname/IP (required)
|
|
143
|
+
--port PORT Receiver port (default: 9000)
|
|
144
|
+
--streams N Initial parallel TCP streams (default: 4)
|
|
145
|
+
--min-streams N Minimum streams TurboLane may use (default: 1)
|
|
146
|
+
--max-streams N Maximum streams TurboLane may use (default: 32)
|
|
147
|
+
--model-dir DIR Q-table persistence directory (default: models/dci)
|
|
148
|
+
--interval SECS RL decision interval in seconds (default: 5.0)
|
|
149
|
+
--timeout SECS Max wait for completion (default: unlimited)
|
|
150
|
+
--verbose / -v Debug logging
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### 3. Query server status
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
turbolane-server status --host 192.168.1.50 --port 9000
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## How TurboLane integrates (5-second loop)
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
Every 5 seconds (TurboLaneAdapter._tick):
|
|
165
|
+
1. MetricsCollector.snapshot()
|
|
166
|
+
→ throughput_mbps (sum of per-stream byte rates)
|
|
167
|
+
→ rtt_ms (mean of PING/PONG RTT samples)
|
|
168
|
+
→ loss_pct (chunk retransmit rate proxy)
|
|
169
|
+
|
|
170
|
+
2. engine.learn(throughput, rtt, loss)
|
|
171
|
+
→ Q-table Bellman update from previous decision's outcome
|
|
172
|
+
|
|
173
|
+
3. engine.decide(throughput, rtt, loss)
|
|
174
|
+
→ Q-learning ε-greedy action → new stream count
|
|
175
|
+
|
|
176
|
+
4. session.adjust_streams(new_count)
|
|
177
|
+
→ spawn / stop StreamWorker threads to match recommendation
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Future upgrades (designed-in hooks)
|
|
183
|
+
|
|
184
|
+
| Capability | Where to add |
|
|
185
|
+
|----------------------|---------------------------|
|
|
186
|
+
| PPO algorithm | `turbolane/rl/` only |
|
|
187
|
+
| Multi-session | `server.py` busy logic |
|
|
188
|
+
| Shared policy learning | `adapter.py` FederatedPolicy |
|
|
189
|
+
| Resume / checkpointing | `ChunkQueue` + `FileAssembler` |
|
|
190
|
+
| TLS encryption | `StreamWorker` + `StreamHandler` |
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from setuptools import setup, find_packages
|
|
3
|
+
|
|
4
|
+
this_dir = Path(__file__).parent
|
|
5
|
+
readme = (this_dir / "README.md").read_text(encoding="utf-8")
|
|
6
|
+
|
|
7
|
+
setup(
|
|
8
|
+
name="turbolane-cli",
|
|
9
|
+
version="2.0.0",
|
|
10
|
+
description="RL-optimized parallel TCP file transfer CLI (TurboLane)",
|
|
11
|
+
long_description=readme,
|
|
12
|
+
long_description_content_type="text/markdown",
|
|
13
|
+
packages=find_packages(exclude=["tests*"]),
|
|
14
|
+
python_requires=">=3.10",
|
|
15
|
+
install_requires=[],
|
|
16
|
+
entry_points={
|
|
17
|
+
"console_scripts": [
|
|
18
|
+
"turbolane-server=turbolane_server.cli:main",
|
|
19
|
+
],
|
|
20
|
+
},
|
|
21
|
+
classifiers=[
|
|
22
|
+
"Development Status :: 3 - Alpha",
|
|
23
|
+
"Intended Audience :: Science/Research",
|
|
24
|
+
"Topic :: System :: Networking",
|
|
25
|
+
"Programming Language :: Python :: 3.10",
|
|
26
|
+
],
|
|
27
|
+
)
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"""
|
|
2
|
+
turbolane/engine.py
|
|
3
|
+
|
|
4
|
+
TurboLaneEngine — the single public entry point for the TurboLane SDK.
|
|
5
|
+
|
|
6
|
+
This is the ONLY class that application code (or the adapter) should import.
|
|
7
|
+
All policy selection, agent wiring, and mode routing happens here.
|
|
8
|
+
|
|
9
|
+
Supported modes:
|
|
10
|
+
'dci' → FederatedPolicy (data center / private network) ← current use
|
|
11
|
+
'client' → EdgePolicy (public internet / edge) ← future
|
|
12
|
+
|
|
13
|
+
Supported algorithms:
|
|
14
|
+
'qlearning' → RLAgent ← current use
|
|
15
|
+
'ppo' → PPOAgent ← future (drop-in via FederatedPolicy)
|
|
16
|
+
|
|
17
|
+
Usage (DCI + Q-learning):
|
|
18
|
+
from turbolane.engine import TurboLaneEngine
|
|
19
|
+
|
|
20
|
+
engine = TurboLaneEngine(mode='dci', algorithm='qlearning')
|
|
21
|
+
streams = engine.decide(throughput_mbps, rtt_ms, loss_pct)
|
|
22
|
+
engine.learn(throughput_mbps, rtt_ms, loss_pct)
|
|
23
|
+
engine.save()
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
import logging
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
# Supported modes and their policy classes
|
|
31
|
+
_MODE_POLICY_MAP = {
|
|
32
|
+
"dci": "turbolane.policies.federated.FederatedPolicy",
|
|
33
|
+
"client": "turbolane.policies.edge.EdgePolicy", # future
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
_VALID_ALGORITHMS = {"qlearning", "ppo"}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class TurboLaneEngine:
|
|
40
|
+
"""
|
|
41
|
+
Unified TurboLane control-plane engine.
|
|
42
|
+
|
|
43
|
+
Public interface (identical regardless of mode or algorithm):
|
|
44
|
+
decide(throughput_mbps, rtt_ms, loss_pct) → int
|
|
45
|
+
learn(throughput_mbps, rtt_ms, loss_pct)
|
|
46
|
+
save() → bool
|
|
47
|
+
get_stats() → dict
|
|
48
|
+
reset()
|
|
49
|
+
|
|
50
|
+
Convenience properties:
|
|
51
|
+
.current_connections → int
|
|
52
|
+
.mode → str
|
|
53
|
+
.algorithm → str
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
def __init__(
|
|
57
|
+
self,
|
|
58
|
+
mode: str = "dci",
|
|
59
|
+
algorithm: str = "qlearning",
|
|
60
|
+
**policy_kwargs,
|
|
61
|
+
):
|
|
62
|
+
"""
|
|
63
|
+
Initialize TurboLane engine.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
mode: 'dci' or 'client'
|
|
67
|
+
algorithm: 'qlearning' or 'ppo'
|
|
68
|
+
**policy_kwargs: Passed directly to the policy constructor.
|
|
69
|
+
See FederatedPolicy.__init__ for valid keys.
|
|
70
|
+
|
|
71
|
+
Example:
|
|
72
|
+
TurboLaneEngine(
|
|
73
|
+
mode='dci',
|
|
74
|
+
algorithm='qlearning',
|
|
75
|
+
model_dir='models/dci',
|
|
76
|
+
min_connections=1,
|
|
77
|
+
max_connections=32,
|
|
78
|
+
default_connections=4,
|
|
79
|
+
monitoring_interval=5.0,
|
|
80
|
+
)
|
|
81
|
+
"""
|
|
82
|
+
mode = mode.lower()
|
|
83
|
+
algorithm = algorithm.lower().replace("-", "").replace("_", "")
|
|
84
|
+
|
|
85
|
+
if mode not in _MODE_POLICY_MAP:
|
|
86
|
+
raise ValueError(
|
|
87
|
+
f"Unknown mode '{mode}'. Valid modes: {list(_MODE_POLICY_MAP)}"
|
|
88
|
+
)
|
|
89
|
+
if algorithm not in _VALID_ALGORITHMS:
|
|
90
|
+
raise ValueError(
|
|
91
|
+
f"Unknown algorithm '{algorithm}'. Valid: {list(_VALID_ALGORITHMS)}"
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
self.mode = mode
|
|
95
|
+
self.algorithm = algorithm
|
|
96
|
+
|
|
97
|
+
# Instantiate the appropriate policy
|
|
98
|
+
self._policy = self._build_policy(mode, algorithm, policy_kwargs)
|
|
99
|
+
|
|
100
|
+
logger.info(
|
|
101
|
+
"TurboLaneEngine ready: mode=%s algorithm=%s",
|
|
102
|
+
self.mode, self.algorithm,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# -----------------------------------------------------------------------
|
|
106
|
+
# Core interface — these are the ONLY methods the adapter calls
|
|
107
|
+
# -----------------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
def decide(
|
|
110
|
+
self,
|
|
111
|
+
throughput_mbps: float,
|
|
112
|
+
rtt_ms: float,
|
|
113
|
+
loss_pct: float,
|
|
114
|
+
) -> int:
|
|
115
|
+
"""
|
|
116
|
+
Make a stream count recommendation based on current network metrics.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
throughput_mbps: Observed throughput in Mbps
|
|
120
|
+
rtt_ms: Observed round-trip time in milliseconds
|
|
121
|
+
loss_pct: Observed packet loss in percent (0–100)
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
Recommended number of parallel TCP streams (int)
|
|
125
|
+
"""
|
|
126
|
+
return self._policy.decide(throughput_mbps, rtt_ms, loss_pct)
|
|
127
|
+
|
|
128
|
+
def learn(
|
|
129
|
+
self,
|
|
130
|
+
throughput_mbps: float,
|
|
131
|
+
rtt_ms: float,
|
|
132
|
+
loss_pct: float,
|
|
133
|
+
) -> None:
|
|
134
|
+
"""
|
|
135
|
+
Update the policy from the outcome of the previous decision.
|
|
136
|
+
|
|
137
|
+
Call this once per monitoring cycle, AFTER decide(), with
|
|
138
|
+
the metrics observed after the previous action took effect.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
throughput_mbps: Current throughput in Mbps
|
|
142
|
+
rtt_ms: Current RTT in milliseconds
|
|
143
|
+
loss_pct: Current packet loss in percent (0–100)
|
|
144
|
+
"""
|
|
145
|
+
self._policy.learn(throughput_mbps, rtt_ms, loss_pct)
|
|
146
|
+
|
|
147
|
+
def save(self) -> bool:
|
|
148
|
+
"""
|
|
149
|
+
Persist the policy to disk.
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
True on success, False on failure.
|
|
153
|
+
"""
|
|
154
|
+
return self._policy.save()
|
|
155
|
+
|
|
156
|
+
def get_stats(self) -> dict:
|
|
157
|
+
"""Return a stats dict for logging, monitoring, and CLI display."""
|
|
158
|
+
stats = self._policy.get_stats()
|
|
159
|
+
stats["engine_mode"] = self.mode
|
|
160
|
+
stats["engine_algorithm"] = self.algorithm
|
|
161
|
+
return stats
|
|
162
|
+
|
|
163
|
+
def sync_current_connections(self, observed_connections: int) -> int:
|
|
164
|
+
"""
|
|
165
|
+
Synchronize the policy with the real applied stream count observed by
|
|
166
|
+
the transfer layer.
|
|
167
|
+
"""
|
|
168
|
+
sync_fn = getattr(self._policy, "sync_current_connections", None)
|
|
169
|
+
if sync_fn is None:
|
|
170
|
+
return self.current_connections
|
|
171
|
+
return sync_fn(observed_connections)
|
|
172
|
+
|
|
173
|
+
def reset(self) -> None:
|
|
174
|
+
"""Clear the policy's learned state. Does not delete files on disk."""
|
|
175
|
+
self._policy.reset()
|
|
176
|
+
|
|
177
|
+
# -----------------------------------------------------------------------
|
|
178
|
+
# Convenience properties
|
|
179
|
+
# -----------------------------------------------------------------------
|
|
180
|
+
|
|
181
|
+
@property
|
|
182
|
+
def current_connections(self) -> int:
|
|
183
|
+
"""Current recommended stream count."""
|
|
184
|
+
return self._policy.current_connections
|
|
185
|
+
|
|
186
|
+
# -----------------------------------------------------------------------
|
|
187
|
+
# Internal factory
|
|
188
|
+
# -----------------------------------------------------------------------
|
|
189
|
+
|
|
190
|
+
def _build_policy(self, mode: str, algorithm: str, kwargs: dict):
|
|
191
|
+
"""
|
|
192
|
+
Instantiate the correct policy for the given mode.
|
|
193
|
+
|
|
194
|
+
DCI mode: always uses FederatedPolicy, passes algorithm as a hint
|
|
195
|
+
for future PPO support. Currently FederatedPolicy only supports
|
|
196
|
+
Q-learning; PPO support will be added to FederatedPolicy directly.
|
|
197
|
+
"""
|
|
198
|
+
if mode == "dci":
|
|
199
|
+
from turbolane.policies.federated import FederatedPolicy
|
|
200
|
+
return FederatedPolicy(**kwargs)
|
|
201
|
+
|
|
202
|
+
elif mode == "client":
|
|
203
|
+
# Future: EdgePolicy
|
|
204
|
+
raise NotImplementedError(
|
|
205
|
+
"Client/Edge mode is not yet implemented. "
|
|
206
|
+
"Use mode='dci' for data center transfers."
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
else:
|
|
210
|
+
raise ValueError(f"Unknown mode: {mode}")
|
|
211
|
+
|
|
212
|
+
def __repr__(self) -> str:
|
|
213
|
+
return (
|
|
214
|
+
f"TurboLaneEngine("
|
|
215
|
+
f"mode={self.mode!r}, "
|
|
216
|
+
f"algorithm={self.algorithm!r}, "
|
|
217
|
+
f"connections={self.current_connections})"
|
|
218
|
+
)
|