A service started by systemd without User= runs as root with no HOME, so the driver keeps no JIT cache and every cuFFT plan is compiled again (50-130 ms each, under cuFFT's process-wide lock) - seconds of MR in the broker. Before anything touches CUDA, the broker and rugnux now point CUDA_CACHE_PATH at /var/tmp/jfjoch-cuda-cache-<uid> when the default cache is missing or unusable and the site has set no CUDA_CACHE_PATH. The directory is used only if it is a real directory of this user's, mode 0700; otherwise the start-up warning stays as before. Measured without HOME: a cuFFT plan 51 ms -> 0.5 ms. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PXc1zD1yxHqaR1DGdCTHL4
158 lines
5.5 KiB
C++
158 lines
5.5 KiB
C++
// SPDX-FileCopyrightText: 2024 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
// Using OpenAPI licensed with Apache License 2.0
|
|
|
|
#include <csignal>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <vector>
|
|
|
|
#include <nlohmann/json.hpp>
|
|
|
|
#include "../common/CudaJitCache.h"
|
|
#include "../common/GitInfo.h"
|
|
#include "../common/Logger.h"
|
|
#include "../common/print_license.h"
|
|
#include "../detector_control/DectrisSimplonClient.h"
|
|
#include "../image_analysis/indexing/IndexerThreadPool.h"
|
|
#include "../writer/HDF5Objects.h"
|
|
|
|
#include "JFJochBrokerHttp.h"
|
|
#include "JFJochBrokerParser.h"
|
|
|
|
static httplib::Server *httpServer = nullptr;
|
|
|
|
static void sigHandler [[noreturn]] (int sig) {
|
|
switch (sig) {
|
|
case SIGINT:
|
|
case SIGQUIT:
|
|
case SIGTERM:
|
|
case SIGHUP:
|
|
default:
|
|
if (httpServer)
|
|
httpServer->stop();
|
|
break;
|
|
}
|
|
std::exit(0);
|
|
}
|
|
|
|
static void setUpUnixSignals(std::vector<int> quitSignals) {
|
|
sigset_t blocking_mask;
|
|
sigemptyset(&blocking_mask);
|
|
for (auto sig : quitSignals)
|
|
sigaddset(&blocking_mask, sig);
|
|
|
|
struct sigaction sa;
|
|
sa.sa_handler = sigHandler;
|
|
sa.sa_mask = blocking_mask;
|
|
sa.sa_flags = 0;
|
|
|
|
for (auto sig : quitSignals)
|
|
sigaction(sig, &sa, nullptr);
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
UsePrivateCudaJitCacheIfNeeded();
|
|
RegisterHDF5Filter();
|
|
print_license("jfjoch_broker");
|
|
|
|
if ((argc == 1) || (argc > 3)) {
|
|
std::cout << "Usage ./jfjoch_broker <JSON config> {<TCP http port>}" << std::endl;
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
uint16_t http_port = 5232;
|
|
if (argc >= 3) http_port = static_cast<uint16_t>(std::atoi(argv[2]));
|
|
|
|
Logger logger("jfjoch_broker");
|
|
logger.Info("jfjoch_broker {} (git {}), built with {}", jfjoch_version(), jfjoch_git_sha1().substr(0, 6),
|
|
jfjoch_build_info(true));
|
|
// cuFFT's initialisation, and card 0's context, ahead of the first collection; the plan it times shows
|
|
// whether the CUDA JIT cache works - without one, every plan the analysis makes is compiled again.
|
|
LogCudaJitCache(logger, WarmUpCuFFT());
|
|
|
|
org::openapitools::server::model::Jfjoch_settings settings;
|
|
std::ifstream file(argv[1]);
|
|
try {
|
|
nlohmann::json input = nlohmann::json::parse(file);
|
|
settings = input;
|
|
settings.validate();
|
|
logger.Info("JSON configuration file read properly");
|
|
} catch (const std::exception &e) {
|
|
logger.Error("Error reading JSON configuration file: " + std::string(e.what()));
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
if (settings.numaPolicyIsSet())
|
|
logger.Warning("NUMA policy is deprecated and ignored - processed are split over all the CPUs/GPUs in the system");
|
|
const bool verbose = settings.verboseIsSet() && settings.isVerbose();
|
|
logger.Verbose(verbose);
|
|
Logger::ServiceLevel("JFJochBroker", verbose ? spdlog::level::debug : spdlog::level::info);
|
|
// What rugnux logs as it processes is its result, and that reaches the user through the live status,
|
|
// the end message and the written files: the journal keeps its errors only, and all of it when verbose.
|
|
for (const auto &service: {"Rugnux", "ScaleOnTheFly"})
|
|
Logger::ServiceLevel(service, verbose ? spdlog::level::debug : spdlog::level::err);
|
|
|
|
std::unique_ptr<ImagePusher> image_pusher = ParseImagePusher(settings);
|
|
if (image_pusher)
|
|
logger.Info(image_pusher->PrintSetup());
|
|
|
|
std::vector<DetectorSetup> det_setup;
|
|
for (const auto &d: settings.getDetector())
|
|
det_setup.push_back(ParseDetectorSetup(d));
|
|
|
|
if (det_setup.empty()) {
|
|
logger.Error("At least one detector need to be defined in the config file");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
DiffractionExperiment experiment(det_setup[0]);
|
|
ParseFacilityConfiguration(settings, experiment);
|
|
|
|
SpotFindingSettings spot_finding_settings = ParseSpotFindingSettings(settings);
|
|
|
|
AcquisitionDeviceGroup aq_devices;
|
|
ParseAcquisitionDeviceGroup(settings, aq_devices);
|
|
experiment.DataStreams(aq_devices.size());
|
|
|
|
int32_t send_buffer_size_MiB = settings.getImageBufferMiB();
|
|
std::unique_ptr<JFJochReceiverService> receiver =
|
|
std::make_unique<JFJochReceiverService>(aq_devices, logger, *image_pusher, send_buffer_size_MiB);
|
|
ParseReceiverSettings(settings, *receiver);
|
|
|
|
JFJochBrokerHttp broker(experiment, spot_finding_settings);
|
|
broker.FrontendDirectory(settings.getFrontendDirectory());
|
|
|
|
for (const auto &d: det_setup)
|
|
broker.AddDetectorSetup(d);
|
|
|
|
if (receiver)
|
|
broker.Services().Receiver(receiver.get());
|
|
ParseRugnuxSettings(settings, broker.Services());
|
|
|
|
httplib::Server server;
|
|
httpServer = &server;
|
|
// Set here rather than through httplib's compile-time default: a reader following a collection
|
|
// holds one thread for the whole run (/live/events), beside the web frontend's polling.
|
|
server.new_task_queue = [] { return new httplib::ThreadPool(16, 64); };
|
|
|
|
server.Get("/", [](const httplib::Request &req, httplib::Response &res) {
|
|
res.status = 302; // Found (temporary redirect)
|
|
res.set_header("Location", "/frontend");
|
|
});
|
|
server.set_mount_point("/frontend", settings.getFrontendDirectory());
|
|
|
|
signal(SIGPIPE, SIG_IGN);
|
|
std::vector<int> sigs{SIGQUIT, SIGINT, SIGTERM, SIGHUP};
|
|
setUpUnixSignals(sigs);
|
|
|
|
broker.attach(server);
|
|
|
|
if (!server.listen("0.0.0.0", http_port)) {
|
|
logger.Error("Failed to start HTTP server");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
return EXIT_SUCCESS;
|
|
}
|