Skip to content

Commit 08f562a

Browse files
committed
DPL: allow connecting to the GUI of devices
1 parent 2bcf8d5 commit 08f562a

4 files changed

Lines changed: 84 additions & 25 deletions

File tree

Framework/Core/src/DPLWebSocket.cxx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,8 @@ void remoteGuiCallback(uv_timer_s* ctx)
236236
renderer->handler->write(outputs);
237237
free(frame);
238238

239+
renderer->guiConnected = true;
240+
239241
if (frameLatency / 1000000 > 15) {
240242
uint64_t frameEnd = uv_hrtime();
241243
*(renderer->gui->frameCost) = (frameEnd - frameStart) / 1000000.f;
@@ -290,7 +292,7 @@ void WSDPLHandler::endHeaders()
290292
}
291293
}
292294
} else {
293-
if (getenv("DPL_DRIVER_REMOTE_GUI")) {
295+
if ((mServerContext->isDriver && getenv("DPL_DRIVER_REMOTE_GUI")) || ((mServerContext->isDriver == false) && getenv("DPL_DEVICE_REMOTE_GUI"))) {
294296
LOG(info) << "Connection not bound to a PID";
295297
GuiRenderer* renderer = new GuiRenderer;
296298
renderer->gui = mServerContext->gui;
@@ -301,9 +303,11 @@ void WSDPLHandler::endHeaders()
301303
mHandler = std::make_unique<GUIWebSocketHandler>(*mServerContext, renderer);
302304
mHandler->headers(mHeaders);
303305
mServerContext->gui->renderers.insert(renderer);
306+
304307
LOGP(info, "RemoteGUI connected, {} running", mServerContext->gui->renderers.size());
305308
} else {
306-
LOG(warning) << "Connection not bound to a PID however DPL_DRIVER_REMOTE_GUI is not set. Skipping.";
309+
LOGP(warning, "Connection not bound to a PID however {} is not set. Skipping.",
310+
mServerContext->isDriver ? "DPL_DRIVER_REMOTE_GUI" : "DPL_DEVICE_REMOTE_GUI");
307311
throw WSError{418, "Remote GUI not enabled"};
308312
}
309313
}

Framework/Core/src/DriverServerContext.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ struct DriverServerContext {
3939
std::vector<ServiceMetricHandling>* metricProcessingCallbacks = nullptr;
4040
DriverInfo* driver = nullptr;
4141
GuiCallbackContext* gui = nullptr;
42+
/// Whether or not this server is associated to
43+
/// the DPL driver or one of the devices.
44+
/// FIXME: we should probably rename this completely and simply call it "DPLServerContext"
45+
/// or something like that.
46+
bool isDriver = false;
4247
};
4348
} // namespace o2::framework
4449

Framework/Core/src/GuiCallbackContext.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "Framework/DeviceState.h"
1717

1818
#include <functional>
19+
#include <uv.h>
1920

2021
namespace o2::framework
2122
{
@@ -27,6 +28,7 @@ struct GuiRenderer {
2728
uv_timer_t drawTimer;
2829
WSDPLHandler* handler = nullptr;
2930
GuiCallbackContext* gui = nullptr;
31+
bool guiConnected = false;
3032
};
3133

3234
struct GuiCallbackContext {

Framework/Core/src/runDataProcessing.cxx

Lines changed: 71 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1133,6 +1133,7 @@ int doChild(int argc, char** argv, ServiceRegistry& serviceRegistry,
11331133
};
11341134

11351135
runner.AddHook<fair::mq::hooks::InstantiateDevice>(afterConfigParsingCallback);
1136+
11361137
auto result = runner.Run();
11371138
serviceRegistry.preExitCallbacks();
11381139
return result;
@@ -1242,29 +1243,30 @@ int runStateMachine(DataProcessorSpecs const& workflow,
12421243
decltype(debugGUI->getGUIDebugger(infos, runningWorkflow.devices, dataProcessorInfos, metricsInfos, driverInfo, controls, driverControl)) debugGUICallback;
12431244

12441245
// An empty frameworkId means this is the driver, so we initialise the GUI
1245-
if ((driverInfo.batch == false || getenv("DPL_DRIVER_REMOTE_GUI") != nullptr) && frameworkId.empty()) {
1246-
auto initDebugGUI = []() -> DebugGUI* {
1247-
uv_lib_t supportLib;
1248-
int result = 0;
1246+
auto initDebugGUI = []() -> DebugGUI* {
1247+
uv_lib_t supportLib;
1248+
int result = 0;
12491249
#ifdef __APPLE__
1250-
result = uv_dlopen("libO2FrameworkGUISupport.dylib", &supportLib);
1250+
result = uv_dlopen("libO2FrameworkGUISupport.dylib", &supportLib);
12511251
#else
1252-
result = uv_dlopen("libO2FrameworkGUISupport.so", &supportLib);
1252+
result = uv_dlopen("libO2FrameworkGUISupport.so", &supportLib);
12531253
#endif
1254-
if (result == -1) {
1255-
LOG(error) << uv_dlerror(&supportLib);
1256-
return nullptr;
1257-
}
1258-
DPLPluginHandle* (*dpl_plugin_callback)(DPLPluginHandle*);
1254+
if (result == -1) {
1255+
LOG(error) << uv_dlerror(&supportLib);
1256+
return nullptr;
1257+
}
1258+
DPLPluginHandle* (*dpl_plugin_callback)(DPLPluginHandle*);
12591259

1260-
result = uv_dlsym(&supportLib, "dpl_plugin_callback", (void**)&dpl_plugin_callback);
1261-
if (result == -1) {
1262-
LOG(error) << uv_dlerror(&supportLib);
1263-
return nullptr;
1264-
}
1265-
DPLPluginHandle* pluginInstance = dpl_plugin_callback(nullptr);
1266-
return PluginManager::getByName<DebugGUI>(pluginInstance, "ImGUIDebugGUI");
1267-
};
1260+
result = uv_dlsym(&supportLib, "dpl_plugin_callback", (void**)&dpl_plugin_callback);
1261+
if (result == -1) {
1262+
LOG(error) << uv_dlerror(&supportLib);
1263+
return nullptr;
1264+
}
1265+
DPLPluginHandle* pluginInstance = dpl_plugin_callback(nullptr);
1266+
return PluginManager::getByName<DebugGUI>(pluginInstance, "ImGUIDebugGUI");
1267+
};
1268+
1269+
if ((driverInfo.batch == false || getenv("DPL_DRIVER_REMOTE_GUI") != nullptr) && frameworkId.empty()) {
12681270
debugGUI = initDebugGUI();
12691271
if (debugGUI) {
12701272
if (driverInfo.batch == false) {
@@ -1273,6 +1275,17 @@ int runStateMachine(DataProcessorSpecs const& workflow,
12731275
window = debugGUI->initGUI(nullptr);
12741276
}
12751277
}
1278+
} else if (getenv("DPL_DEVICE_REMOTE_GUI") && !frameworkId.empty()) {
1279+
debugGUI = initDebugGUI();
1280+
// We never run the GUI on desktop for devices. All
1281+
// you can do is to connect to the remote version.
1282+
// this is done to avoid having a proliferation of
1283+
// GUIs popping up when the variable is set globally.
1284+
// FIXME: maybe this is not what we want, but it should
1285+
// be ok for now.
1286+
if (debugGUI) {
1287+
window = debugGUI->initGUI(nullptr);
1288+
}
12761289
}
12771290
if (driverInfo.batch == false && window == nullptr && frameworkId.empty()) {
12781291
LOG(warn) << "Could not create GUI. Switching to batch mode. Do you have GLFW on your system?";
@@ -1317,7 +1330,6 @@ int runStateMachine(DataProcessorSpecs const& workflow,
13171330
guiContext.frameLatency = &driverInfo.frameLatency;
13181331
guiContext.frameCost = &driverInfo.frameCost;
13191332
guiContext.guiQuitRequested = &guiQuitRequested;
1320-
auto inputProcessingLast = guiContext.frameLast;
13211333

13221334
// This is to make sure we can process metrics, commands, configuration
13231335
// changes coming from websocket (or even via any standard uv_stream_t, I guess).
@@ -1331,11 +1343,27 @@ int runStateMachine(DataProcessorSpecs const& workflow,
13311343
serverContext.driver = &driverInfo;
13321344
serverContext.metricProcessingCallbacks = &metricProcessingCallbacks;
13331345
serverContext.gui = &guiContext;
1346+
serverContext.isDriver = frameworkId.empty();
13341347

13351348
uv_tcp_t serverHandle;
13361349
serverHandle.data = &serverContext;
13371350
uv_tcp_init(loop, &serverHandle);
1351+
13381352
driverInfo.port = 8080 + (getpid() % 30000);
1353+
1354+
if (getenv("DPL_REMOTE_GUI_PORT")) {
1355+
try {
1356+
driverInfo.port = stoi(std::string(getenv("DPL_REMOTE_GUI_PORT")));
1357+
} catch (std::invalid_argument) {
1358+
LOG(error) << "DPL_REMOTE_GUI_PORT not a valid integer";
1359+
} catch (std::out_of_range) {
1360+
LOG(error) << "DPL_REMOTE_GUI_PORT out of range (integer)";
1361+
}
1362+
if (driverInfo.port < 1024 || driverInfo.port > 65535) {
1363+
LOG(error) << "DPL_REMOTE_GUI_PORT out of range (1024-65535)";
1364+
}
1365+
}
1366+
13391367
int result = 0;
13401368
struct sockaddr_in* serverAddr = nullptr;
13411369

@@ -1346,9 +1374,28 @@ int runStateMachine(DataProcessorSpecs const& workflow,
13461374
// the future to inspect them via some web based interface.
13471375
if (frameworkId.empty()) {
13481376
do {
1349-
if (serverAddr) {
1350-
free(serverAddr);
1377+
free(serverAddr);
1378+
if (driverInfo.port > 64000) {
1379+
throw runtime_error_f("Unable to find a free port for the driver. Last attempt returned %d", result);
13511380
}
1381+
serverAddr = (sockaddr_in*)malloc(sizeof(sockaddr_in));
1382+
uv_ip4_addr("0.0.0.0", driverInfo.port, serverAddr);
1383+
auto bindResult = uv_tcp_bind(&serverHandle, (const struct sockaddr*)serverAddr, 0);
1384+
if (bindResult != 0) {
1385+
driverInfo.port++;
1386+
usleep(1000);
1387+
continue;
1388+
}
1389+
result = uv_listen((uv_stream_t*)&serverHandle, 100, ws_connect_callback);
1390+
if (result != 0) {
1391+
driverInfo.port++;
1392+
usleep(1000);
1393+
continue;
1394+
}
1395+
} while (result != 0);
1396+
} else if (getenv("DPL_DEVICE_REMOTE_GUI") && !frameworkId.empty()) {
1397+
do {
1398+
free(serverAddr);
13521399
if (driverInfo.port > 64000) {
13531400
throw runtime_error_f("Unable to find a free port for the driver. Last attempt returned %d", result);
13541401
}
@@ -1366,6 +1413,7 @@ int runStateMachine(DataProcessorSpecs const& workflow,
13661413
usleep(1000);
13671414
continue;
13681415
}
1416+
LOG(info) << "Device GUI port: " << driverInfo.port << " " << frameworkId;
13691417
} while (result != 0);
13701418
}
13711419

@@ -2719,7 +2767,7 @@ int doMain(int argc, char** argv, o2::framework::WorkflowSpec const& workflow,
27192767
// values are not filled into the vector, even if specifying `-b true`
27202768
// need to find out why the boost program options example is not working
27212769
// in our case. Might depend on the parser options
2722-
//auto value = varmap["batch"].as<std::vector<std::string>>();
2770+
// auto value = varmap["batch"].as<std::vector<std::string>>();
27232771
return true;
27242772
};
27252773
DriverInfo driverInfo{

0 commit comments

Comments
 (0)