33#include <unordered_map>
42std::string jsonEscape(std::string_view
s)
45 out.reserve(
s.size() + 4);
46 for (
unsigned char c :
s) {
66 snprintf(
buf,
sizeof(
buf),
"\\u%04x",
c);
69 out +=
static_cast<char>(
c);
79 case LogParsingHelpers::LogLevel::Debug:
81 case LogParsingHelpers::LogLevel::Info:
83 case LogParsingHelpers::LogLevel::Important:
85 case LogParsingHelpers::LogLevel::Warning:
87 case LogParsingHelpers::LogLevel::Alarm:
89 case LogParsingHelpers::LogLevel::Error:
91 case LogParsingHelpers::LogLevel::Critical:
93 case LogParsingHelpers::LogLevel::Fatal:
106 return "EndOfStreaming";
114void appendMetricValue(std::string& out, DeviceMetricsInfo
const& info,
size_t mi)
116 auto const& metric =
info.metrics[mi];
117 if (metric.pos == 0) {
122 switch (metric.type) {
128 snprintf(
buf,
sizeof(
buf),
"%g",
static_cast<double>(
info.floatMetrics[metric.storeIdx][last]));
143std::string_view extractStringField(std::string_view
json, std::string_view
key)
150 if (
pos == std::string_view::npos) {
153 pos += needle.size();
163 if (
end == std::string_view::npos) {
171std::string_view extractArrayField(std::string_view
json, std::string_view
key)
178 if (
pos == std::string_view::npos) {
181 pos += needle.size();
208void forEachStringInArray(std::string_view arr, F&& callback)
212 while (
pos < arr.size()) {
213 auto q = arr.find(
'"',
pos);
214 if (q == std::string_view::npos) {
217 auto end = arr.find(
'"', q + 1);
218 if (
end == std::string_view::npos) {
221 callback(arr.substr(q + 1,
end - q - 1));
229 : mContext{context}, mHandler{handler}
236 handlers.erase(std::remove(handlers.begin(), handlers.end(),
this), handlers.end());
247 auto cmd = extractStringField(
msg,
"cmd");
251 auto deviceName = extractStringField(
msg,
"device");
253 if (cmd ==
"list_metrics") {
254 handleListMetrics(deviceName);
255 }
else if (cmd ==
"subscribe") {
256 handleSubscribe(deviceName, extractArrayField(
msg,
"metrics"));
257 }
else if (cmd ==
"unsubscribe") {
258 handleUnsubscribe(deviceName, extractArrayField(
msg,
"metrics"));
259 }
else if (cmd ==
"subscribe_logs") {
260 handleSubscribeLogs(deviceName);
261 }
else if (cmd ==
"unsubscribe_logs") {
262 handleUnsubscribeLogs(deviceName);
263 }
else if (cmd ==
"start_devices") {
264 handleStartDevices();
265 }
else if (cmd ==
"enable_signpost") {
266 handleEnableSignpost(deviceName, extractArrayField(
msg,
"streams"));
267 }
else if (cmd ==
"disable_signpost") {
268 handleDisableSignpost(deviceName, extractArrayField(
msg,
"streams"));
269 }
else if (cmd ==
"smaps") {
270 handleSmaps(deviceName);
274void StatusWebSocketHandler::sendText(std::string
const&
json)
276 std::vector<uv_buf_t> outputs;
278 mHandler->
write(outputs);
283 auto const& specs = *mContext.
specs;
284 auto const& infos = *mContext.
infos;
287 mSubscribedMetrics.resize(specs.size());
288 mLastLogSeq.resize(infos.size());
289 for (
size_t di = 0;
di < infos.size(); ++
di) {
290 mLastLogSeq[
di] = infos[
di].logSeq;
294 out.reserve(512 + specs.size() * 128);
295 out += R
"({"type":"snapshot","devices":[)";
296 for (
size_t di = 0;
di < specs.size(); ++
di) {
300 auto const& info = infos[
di];
301 out += R
"({"name":")";
302 out += jsonEscape(specs[di].name);
303 out += R"(","pid":)";
305 out += R"(,"active":)";
306 out += info.active ? "true" :
"false";
307 out += R
"(,"streamingState":")";
308 out += streamingStateName(info.streamingState);
309 out += R"(","deviceState":")";
310 out += jsonEscape(info.deviceState);
319 auto const& specs = *mContext.
specs;
322 if (deviceIndex >= specs.size() || deviceIndex >=
metrics.size()) {
327 if (mSubscribedMetrics.size() <= deviceIndex) {
328 mSubscribedMetrics.resize(deviceIndex + 1);
331 auto const& subscribed = mSubscribedMetrics[deviceIndex];
332 if (subscribed.empty()) {
336 auto const& info =
metrics[deviceIndex];
337 std::string metricsJson;
340 for (
size_t mi = 0; mi < info.metrics.size(); ++mi) {
341 if (!info.changed[mi]) {
344 auto const& metric = info.metrics[mi];
350 auto const&
label = info.metricLabels[mi];
351 std::string_view labelSV{
label.label,
label.size};
352 if (subscribed.find(std::string(labelSV)) == subscribed.end()) {
360 metricsJson += jsonEscape(labelSV);
361 metricsJson +=
"\":";
362 appendMetricValue(metricsJson, info, mi);
372 out += R
"({"type":"update","device":)";
374 out += R"(,"name":")";
375 out += jsonEscape(specs[deviceIndex].name);
376 out += R"(","metrics":)";
382void StatusWebSocketHandler::handleListMetrics(std::string_view deviceName)
384 size_t di = findDeviceIndex(deviceName);
385 if (
di == SIZE_MAX) {
391 out += R
"({"type":"metrics_list","device":")";
392 out += jsonEscape(deviceName);
393 out += R"(","metrics":[)";
397 for (
size_t mi = 0; mi < info.metrics.size(); ++mi) {
398 auto const& metric = info.metrics[mi];
408 auto const&
label =
info.metricLabels[mi];
418void StatusWebSocketHandler::handleSubscribe(std::string_view deviceName, std::string_view metricsArr)
420 size_t di = findDeviceIndex(deviceName);
421 if (
di == SIZE_MAX || metricsArr.empty()) {
424 if (mSubscribedMetrics.size() <=
di) {
425 mSubscribedMetrics.resize(
di + 1);
427 forEachStringInArray(metricsArr, [&](std::string_view
name) {
428 mSubscribedMetrics[
di].emplace(
name);
432void StatusWebSocketHandler::handleUnsubscribe(std::string_view deviceName, std::string_view metricsArr)
434 size_t di = findDeviceIndex(deviceName);
435 if (
di == SIZE_MAX || metricsArr.empty() ||
di >= mSubscribedMetrics.size()) {
438 forEachStringInArray(metricsArr, [&](std::string_view
name) {
439 mSubscribedMetrics[
di].erase(std::string(
name));
443size_t StatusWebSocketHandler::findDeviceIndex(std::string_view
name)
const
445 auto const& specs = *mContext.
specs;
446 for (
size_t di = 0;
di < specs.size(); ++
di) {
454void StatusWebSocketHandler::handleSmaps(std::string_view deviceName)
456 size_t di = findDeviceIndex(deviceName);
457 if (
di == SIZE_MAX) {
461 std::string out = R
"({"type":"smaps","device":")";
462 out += jsonEscape(deviceName);
463 out += R"(","pid":)";
468 out += R
"(,"error":"cannot read /proc/)" + std::to_string(pid) + R"(/smaps"})";
472 constexpr std::array<std::string_view, 7>
fields{
"Rss:",
"Pss:",
"Shared_Clean:",
"Shared_Dirty:",
"Private_Clean:",
"Private_Dirty:",
"Swap:"};
476 while (std::getline(smaps, line)) {
477 std::istringstream tokens(line);
483 if (
first.back() !=
':') {
485 tokens >> perms >>
offset >>
dev >> inode;
486 std::getline(tokens >> std::ws,
name);
492 if (current && field !=
fields.end() && tokens >> kb) {
493 (*current)[field -
fields.begin()] += kb;
496 out += R
"(,"fields":["rss","pss","shared_clean","shared_dirty","private_clean","private_dirty","swap"],"mappings":{)";
499 out +=
first ?
"\"" :
",\"";
501 out += jsonEscape(
name);
503 for (
size_t fi = 0; fi < kbs.size(); ++fi) {
512void StatusWebSocketHandler::handleStartDevices()
514 for (
auto const& info : *mContext.infos) {
516 kill(
info.pid, SIGCONT);
521void StatusWebSocketHandler::handleEnableSignpost(std::string_view deviceName, std::string_view streamsArr)
523 if (streamsArr.empty()) {
526 if (deviceName.empty()) {
528 forEachStringInArray(streamsArr, [](std::string_view streamName) {
529 std::string
target(streamName);
532 if (
static_cast<std::string*
>(context)->
compare(
name) == 0) {
540 size_t di = findDeviceIndex(deviceName);
544 auto* controller = (*mContext.
controls)[
di].controller;
545 forEachStringInArray(streamsArr, [controller](std::string_view
name) {
546 std::string cmd =
"/signpost:enable ";
548 controller->write(cmd.c_str(), cmd.size());
553void StatusWebSocketHandler::handleDisableSignpost(std::string_view deviceName, std::string_view streamsArr)
555 if (streamsArr.empty()) {
558 if (deviceName.empty()) {
559 forEachStringInArray(streamsArr, [](std::string_view streamName) {
560 std::string
target(streamName);
563 if (
static_cast<std::string*
>(context)->
compare(
name) == 0) {
571 size_t di = findDeviceIndex(deviceName);
575 auto* controller = (*mContext.
controls)[
di].controller;
576 forEachStringInArray(streamsArr, [controller](std::string_view
name) {
577 std::string cmd =
"/signpost:disable ";
579 controller->write(cmd.c_str(), cmd.size());
584void StatusWebSocketHandler::handleSubscribeLogs(std::string_view deviceName)
586 size_t di = findDeviceIndex(deviceName);
587 if (
di == SIZE_MAX) {
590 if (mLastLogSeq.size() <=
di) {
591 mLastLogSeq.resize(
di + 1, 0);
594 mLastLogSeq[
di] = (*mContext.
infos)[
di].logSeq;
595 mLogSubscriptions.insert(
di);
598void StatusWebSocketHandler::handleUnsubscribeLogs(std::string_view deviceName)
600 size_t di = findDeviceIndex(deviceName);
601 if (
di == SIZE_MAX) {
604 mLogSubscriptions.erase(
di);
609 if (mLogSubscriptions.find(deviceIndex) == mLogSubscriptions.end()) {
612 auto const& infos = *mContext.
infos;
613 auto const& specs = *mContext.
specs;
614 if (deviceIndex >= infos.size() || deviceIndex >= specs.size()) {
617 if (mLastLogSeq.size() <= deviceIndex) {
618 mLastLogSeq.resize(deviceIndex + 1, 0);
621 auto const& info = infos[deviceIndex];
622 size_t newLines = info.logSeq - mLastLogSeq[deviceIndex];
627 if (newLines > info.history.size()) {
628 newLines = info.history.size();
631 size_t histSize = info.history.size();
633 size_t startPos = (info.historyPos + histSize - newLines) % histSize;
635 std::string_view devName = specs[deviceIndex].name;
636 for (
size_t i = 0;
i < newLines; ++
i) {
637 size_t pos = (startPos +
i) % histSize;
639 out += R
"({"type":"log","device":")";
640 out += jsonEscape(devName);
641 out += R"(","level":")";
642 out += logLevelName(info.historyLevel[pos]);
643 out += R"(","line":")";
644 out += jsonEscape(info.history[pos]);
648 mLastLogSeq[deviceIndex] = info.logSeq;
std::vector< std::shared_ptr< arrow::Field > > fields
Timeframe settings Average distance in cm of events merged into Randomize distance around average distance of merged events Simulate correct bunch interactions instead of placing only the average number of events A value[n] sets TFlen for[n] collisions in Number of bunches per trainsort Number of bunch trains Instantaneous interaction rate Do not simulate border Total number of collisions to be placed in the interior of all time Do not select random but walk over array of events in stride steps h Event generator settings h configStandalone Use CPU for GPU Force usage of the specified GPU device no CPU fallback Continue processing after an error s
Timeframe settings Average distance in cm of events merged into Randomize distance around average distance of merged events Simulate correct bunch interactions instead of placing only the average number of events A value[n] sets TFlen for[n] collisions in Number of bunches per trainsort Number of bunch trains Instantaneous interaction rate Do not simulate border Total number of collisions to be placed in the interior of all time Do not select random but walk over array of events in stride steps h Event generator settings h configStandalone Use CPU for GPU Force usage of the specified GPU device no CPU fallback Continue processing after an error First event to Number of events to Number of iterations to Number of iterations to Number of initial iterations excluded from const char Directory with events to Events directory is and not inside events Show standalone event Run event generator maximum time bin of continuous for triggered for automatic continuous for automatic continuous triggered uint64_t
o2_log_handle_t * o2_walk_logs(bool(*callback)(char const *name, void *log, void *context), void *context=nullptr)
void _o2_log_set_stacktrace(_o2_log_t *log, int stacktrace)
GLuint const GLchar * name
GLsizei GLenum const void GLuint GLsizei GLfloat * metrics
GLuint GLsizei const GLchar * label
GLint GLint GLsizei GLsizei GLsizei depth
GLenum GLuint GLenum GLsizei const GLchar * buf
GLsizeiptr const void GLenum usage
Defining ITS Vertex explicitly as messageable.
constexpr size_t metricStorageSize()
void encode_websocket_frames(std::vector< uv_buf_t > &outputs, char const *src, size_t size, WebSocketOpCode opcode, uint32_t mask)
@ EndOfStreaming
End of streaming requested, but not notified.
@ Streaming
Data is being processed.
@ Idle
End of streaming notified.
int32_t const char int32_t line
std::string to_string(gsl::span< T, Size > span)
std::vector< DeviceSpec > * specs
std::vector< DeviceMetricsInfo > * metrics
std::vector< StatusWebSocketHandler * > statusHandlers
std::vector< DeviceInfo > * infos
std::vector< DeviceControl > * controls
LogLevel
Possible log levels for device log entries.
StatusWebSocketHandler(DriverServerContext &context, WSDPLHandler *handler)
void sendSnapshot()
Send a minimal JSON snapshot (device list + basic state, no metrics/logs).
void sendNewLogs(size_t deviceIndex)
void sendUpdate(size_t deviceIndex)
~StatusWebSocketHandler() override
void frame(char const *data, size_t s) override
Handles incoming commands from the MCP client.
void headers(std::map< std::string, std::string > const &headers) override
Sends the minimal snapshot on handshake completion.
void write(char const *, size_t)
Helper to write a message to the associated client.
void compare(std::string_view s1, std::string_view s2)
uint64_t const void const *restrict const msg