Skip to content

Commit 2cfed3c

Browse files
committed
gui: compress ws messages
1 parent 1f2ad62 commit 2cfed3c

26 files changed

+88181
-454
lines changed

book/api/websocket.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,35 @@ file.
3838
The API is split into various topics which will be streamed to any and
3939
all connected clients.
4040

41+
## Compression
42+
If desired, the server can optionally compress messages larger than 200
43+
bytes. In order to enable this feature, the client must specify the
44+
`compress-zstd` subprotocol in the opening websocket handshake.
45+
46+
::: code-group
47+
48+
```js [client.js]
49+
client = new WebSocket("ws://localhost:80/websocket", protocols=['compress-zstd']);
50+
client.binaryType = "arraybuffer";
51+
```
52+
53+
```
54+
ws.onmessage = function onmessage(ev: MessageEvent<unknown>) {
55+
if (typeof ev.data === 'string') {
56+
... parse string
57+
} else if (ev.data instanceof ArrayBuffer) {
58+
... decompress then parse
59+
}
60+
};
61+
```
62+
63+
:::
64+
65+
In order to distinguish between compressed and non-compressed messages,
66+
the server will send compressed messages as a binary websocket frame
67+
(i.e. opcode=0x2) and regular messages as a text websocket frame (i.e.
68+
opcode=0x1).
69+
4170
## Keeping Up
4271
The server does not drop information, slow down, or stop publishing the
4372
stream of information if the client cannot keep up. A client that is

src/app/fdctl/config/default.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1715,3 +1715,13 @@ dynamic_port_range = "8900-9000"
17151715
# - 'ip neigh get <NEXTHOP> dev <INTERFACE>' returns REACHABLE
17161716
# If these requirements are not met, no packets will be sent out.
17171717
fake_dst_ip = "" # e.g. "192.0.2.64"
1718+
1719+
# Experimental options for the gui tile. These should not be
1720+
# changed on a production validator.
1721+
[development.gui]
1722+
# Enables backend support for websocket compression. Websocket
1723+
# messages typically consist of a single websocket text frame
1724+
# with a header and a json payload. Enabling this option will
1725+
# make the backend compress large json payloads with ZSTD stream
1726+
# compression, sending the compressed payload in a binary frame.
1727+
websocket_compression = false

src/app/fdctl/topology.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,7 @@ fd_topo_initialize( config_t * config ) {
497497
tile->gui.max_http_request_length = config->tiles.gui.max_http_request_length;
498498
tile->gui.send_buffer_size_mb = config->tiles.gui.send_buffer_size_mb;
499499
tile->gui.schedule_strategy = config->tiles.pack.schedule_strategy_enum;
500+
tile->gui.websocket_compression = config->development.gui.websocket_compression;
500501
} else if( FD_UNLIKELY( !strcmp( tile->name, "plugin" ) ) ) {
501502

502503
} else {

src/app/firedancer/config/default.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1477,3 +1477,13 @@ user = ""
14771477
# - 'ip neigh get <NEXTHOP> dev <INTERFACE>' returns REACHABLE
14781478
# If these requirements are not met, no packets will be sent out.
14791479
fake_dst_ip = "" # e.g. "192.0.2.64"
1480+
1481+
# Experimental options for the gui tile. These should not be
1482+
# changed on a production validator.
1483+
[development.gui]
1484+
# Enables backend support for websocket compression. Websocket
1485+
# messages typically consist of a single websocket text frame
1486+
# with a header and a json payload. Enabling this option will
1487+
# make the backend compress large json payloads with ZSTD stream
1488+
# compression, sending the compressed payload in a binary frame.
1489+
websocket_compression = false

src/app/shared/fd_config.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,10 @@ struct fd_config {
316316
struct {
317317
char affinity[ AFFINITY_SZ ];
318318
} snapshot_load;
319+
320+
struct {
321+
int websocket_compression;
322+
} gui;
319323
} development;
320324

321325
struct {

src/app/shared/fd_config_parse.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,8 @@ fd_config_extract_pod( uchar * pod,
301301
CFG_POP ( cstr, development.pktgen.affinity );
302302
CFG_POP ( cstr, development.pktgen.fake_dst_ip );
303303

304+
CFG_POP ( bool, development.gui.websocket_compression );
305+
304306
if( FD_UNLIKELY( config->is_firedancer ) ) {
305307
if( FD_UNLIKELY( !fd_config_extract_podf( pod, &config->firedancer ) ) ) return NULL;
306308
fd_config_check_configf( config, &config->firedancer );

src/disco/gui/bandwidth.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,28 @@
33
import time
44
import json
55
from collections import defaultdict
6+
import zstandard as zstd
67

78
async def measure_bandwidth(uri):
8-
async with websockets.connect(uri, max_size=1_000_000_000) as websocket:
9+
dcmp = zstd.ZstdDecompressor()
10+
async with websockets.connect(uri, max_size=1_000_000_000, subprotocols=['compress-zstd']) as websocket:
911
start_time = time.time()
1012
total_bytes_by_group = defaultdict(int)
1113
overall_total_bytes = 0
1214

1315
while True:
1416
frame = await websocket.recv()
15-
data = json.loads(frame)
17+
if(isinstance(frame, bytes)):
18+
frame_sz = len(frame)
19+
data = json.loads(dcmp.stream_reader(frame).readall())
20+
else:
21+
frame_sz = len(frame.encode('utf-8'))
22+
data = json.loads(frame)
1623
topic = data.get("topic")
1724
key = data.get("key")
1825
group = (topic, key)
19-
total_bytes_by_group[group] += len(frame)
20-
overall_total_bytes += len(frame)
26+
total_bytes_by_group[group] += frame_sz
27+
overall_total_bytes += frame_sz
2128
elapsed_time = time.time() - start_time
2229

2330
if elapsed_time >= 1.0:
@@ -26,17 +33,17 @@ async def measure_bandwidth(uri):
2633
bandwidth_mbps = (total_bytes * 8) / (elapsed_time * 1_000_000)
2734
if bandwidth_mbps >= 0.001:
2835
bandwidths.append((group, bandwidth_mbps))
29-
36+
3037
# Sort by bandwidth in descending order
3138
bandwidths.sort(key=lambda x: x[1], reverse=True)
32-
39+
3340
for group, bandwidth_mbps in bandwidths:
34-
print(f"Incoming bandwidth for {group}: {bandwidth_mbps:.2f} Mbps")
35-
41+
print(f"Incoming bandwidth for {str(group):50}: {bandwidth_mbps:6.2f} Mbps")
42+
3643
# Calculate and print the overall total bandwidth
3744
overall_bandwidth_mbps = (overall_total_bytes * 8) / (elapsed_time * 1_000_000)
3845
print(f"Total incoming bandwidth: {overall_bandwidth_mbps:.2f} Mbps")
39-
46+
4047
start_time = time.time()
4148
total_bytes_by_group.clear()
4249
overall_total_bytes = 0

src/disco/gui/dist/LICENSE_DEPENDENCIES

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2690,6 +2690,45 @@ THE SOFTWARE.
26902690

26912691
---
26922692

2693+
Name: @oneidentity/zstd-js
2694+
Version: 1.0.3
2695+
License: SEE LICENSE IN LICENSE
2696+
Private: false
2697+
Description: Browser side compression library from the official Zstandard library.
2698+
Repository: git+https://github.com/OneIdentity/zstd-js.git
2699+
Homepage: https://github.com/OneIdentity/zstd-js
2700+
Author: Zsombor Szende - @szendezsombor
2701+
Contributors:
2702+
Csaba Tamás - @tamascsaba
2703+
Gergely Szabó - @szaboge
2704+
László Zana - @zanalaci
2705+
License Copyright:
2706+
===
2707+
2708+
MIT License
2709+
2710+
Copyright (c) 2022 One Identity
2711+
2712+
Permission is hereby granted, free of charge, to any person obtaining a copy
2713+
of this software and associated documentation files (the "Software"), to deal
2714+
in the Software without restriction, including without limitation the rights
2715+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
2716+
copies of the Software, and to permit persons to whom the Software is
2717+
furnished to do so, subject to the following conditions:
2718+
2719+
The above copyright notice and this permission notice shall be included in all
2720+
copies or substantial portions of the Software.
2721+
2722+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
2723+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
2724+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
2725+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
2726+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2727+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2728+
SOFTWARE.
2729+
2730+
---
2731+
26932732
Name: @ag-grid-community/core
26942733
Version: 32.3.4
26952734
License: MIT

src/disco/gui/dist/assets/index-BMcp749y.css

Lines changed: 0 additions & 1 deletion
This file was deleted.

src/disco/gui/dist/assets/index-BOM3Hkl7.css

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)