LCOV - code coverage report
Current view: top level - mu2e-pcie-utils/dtcInterfaceLib - dtcPrometheusExporter.cc (source / functions) Coverage Total Hit
Test: mu2edaq.info.cleaned Lines: 0.0 % 180 0
Test Date: 2026-09-10 01:53:42 Functions: 0.0 % 11 0

            Line data    Source code
       1              : // dtcPrometheusExporter.cc
       2              : // Prometheus metrics HTTP exporter for Mu2e DTC/CFO register monitoring.
       3              : 
       4              : #include <arpa/inet.h>
       5              : #include <dirent.h>
       6              : #include <netinet/in.h>
       7              : #include <sys/select.h>
       8              : #include <sys/socket.h>
       9              : #include <unistd.h>
      10              : 
      11              : #include <algorithm>
      12              : #include <cerrno>
      13              : #include <csignal>
      14              : #include <cctype>
      15              : #include <cstring>
      16              : #include <iostream>
      17              : #include <optional>
      18              : #include <set>
      19              : #include <sstream>
      20              : #include <string>
      21              : #include <vector>
      22              : 
      23              : #include "cfoInterfaceLib/CFO_Registers.h"
      24              : #include "dtcInterfaceLib/DTC_Registers.h"
      25              : 
      26              : namespace {
      27              : volatile sig_atomic_t g_running = 1;
      28              : 
      29              : struct ExporterConfig
      30              : {
      31              :         std::optional<int> dtcFilter;
      32              :         std::string memFileName;
      33              : };
      34              : 
      35            0 : void signalHandler(int /*sig*/) { g_running = 0; }
      36              : 
      37            0 : std::string escapeLabelValue(const std::string& in)
      38              : {
      39            0 :         std::string out;
      40            0 :         out.reserve(in.size());
      41            0 :         for (char c : in)
      42              :         {
      43            0 :                 if (c == '\\' || c == '"') out.push_back('\\');
      44            0 :                 out.push_back(c);
      45              :         }
      46            0 :         return out;
      47            0 : }
      48              : 
      49            0 : std::vector<int> discoverMu2eDeviceIndices()
      50              : {
      51            0 :         std::vector<int> ids;
      52            0 :         DIR* dir = opendir("/dev");
      53            0 :         if (!dir) return ids;
      54              : 
      55            0 :         while (auto* entry = readdir(dir))
      56              :         {
      57            0 :                 const std::string name(entry->d_name);
      58            0 :                 if (name.rfind("mu2e", 0) != 0 || name.size() <= 4) continue;
      59            0 :                 const std::string suffix = name.substr(4);
      60            0 :                 if (!std::all_of(suffix.begin(), suffix.end(), [](unsigned char ch) { return std::isdigit(ch) != 0; })) continue;
      61            0 :                 ids.push_back(std::stoi(suffix));
      62            0 :         }
      63            0 :         closedir(dir);
      64            0 :         std::sort(ids.begin(), ids.end());
      65            0 :         ids.erase(std::unique(ids.begin(), ids.end()), ids.end());
      66            0 :         return ids;
      67            0 : }
      68              : 
      69            0 : void writeGaugeSample(std::ostringstream& oss, const std::string& labels, uint32_t value)
      70              : {
      71            0 :         oss << "dtc_register_value{" << labels << "} " << value << "\n";
      72            0 : }
      73              : 
      74            0 : void appendRegisterMetrics(std::ostringstream& oss, int deviceIndex, const std::string& deviceType,
      75              :                                                    const std::vector<std::function<DTCLib::RegisterFormatter()>>& functions,
      76              :                                                    std::set<uint16_t>& seenAddresses)
      77              : {
      78            0 :         for (const auto& fn : functions)
      79              :         {
      80              :                 try
      81              :                 {
      82            0 :                         auto reg = fn();
      83            0 :                         if (!seenAddresses.insert(reg.address).second) continue;
      84              : 
      85            0 :                         std::ostringstream addr;
      86            0 :                         addr << "0x" << std::hex << reg.address;
      87            0 :                         const std::string regName = reg.description.empty() ? addr.str() : reg.description;
      88              :                         const std::string labels =
      89            0 :                                 "dtc=\"" + std::to_string(deviceIndex) + "\",device_type=\"" + deviceType +
      90            0 :                                 "\",address=\"" + addr.str() + "\",register=\"" + escapeLabelValue(regName) + "\"";
      91            0 :                         writeGaugeSample(oss, labels, reg.value);
      92            0 :                 }
      93            0 :                 catch (...)
      94            0 :                 {}
      95              :         }
      96            0 : }
      97              : 
      98            0 : std::string collectMetrics(const ExporterConfig& config)
      99              : {
     100            0 :         std::ostringstream oss;
     101            0 :         oss << "# HELP dtc_register_value Raw register value for Mu2e DTC/CFO devices\n";
     102            0 :         oss << "# TYPE dtc_register_value gauge\n";
     103              : 
     104            0 :         std::vector<int> deviceIds;
     105            0 :         if (config.dtcFilter.has_value())
     106              :         {
     107            0 :                 deviceIds.push_back(*config.dtcFilter);
     108              :         }
     109              :         else
     110              :         {
     111            0 :                 deviceIds = discoverMu2eDeviceIndices();
     112              :         }
     113              : 
     114            0 :         for (int deviceIndex : deviceIds)
     115              :         {
     116              :                 try
     117              :                 {
     118              :                         {
     119            0 :                                 DTCLib::DTC_Registers regs(DTCLib::DTC_SimMode_Disabled, deviceIndex, config.memFileName, 0x1, "", true);
     120            0 :                                 if (!regs.isCFODesignFlavour())
     121              :                                 {
     122            0 :                                         std::set<uint16_t> seenAddresses;
     123            0 :                                         appendRegisterMetrics(oss, deviceIndex, "dtc", regs.getFormattedSimpleDumpFunctions(), seenAddresses);
     124            0 :                                         appendRegisterMetrics(oss, deviceIndex, "dtc", regs.getFormattedDumpFunctions(), seenAddresses);
     125            0 :                                         appendRegisterMetrics(oss, deviceIndex, "dtc", regs.formattedPerformanceCounterFunctions_, seenAddresses);
     126            0 :                                         appendRegisterMetrics(oss, deviceIndex, "dtc", regs.formattedSERDESErrorFunctions_, seenAddresses);
     127            0 :                                         appendRegisterMetrics(oss, deviceIndex, "dtc", regs.formattedPacketCounterFunctions_, seenAddresses);
     128            0 :                                         continue;
     129            0 :                                 }
     130            0 :                         }
     131              : 
     132            0 :                         CFOLib::CFO_Registers cfo(DTCLib::DTC_SimMode_Disabled, deviceIndex, "", true);
     133            0 :                         std::set<uint16_t> seenAddresses;
     134            0 :                         appendRegisterMetrics(oss, deviceIndex, "cfo", cfo.getFormattedSimpleDumpFunctions(), seenAddresses);
     135            0 :                         appendRegisterMetrics(oss, deviceIndex, "cfo", cfo.getFormattedDumpFunctions(), seenAddresses);
     136            0 :                         appendRegisterMetrics(oss, deviceIndex, "cfo", cfo.formattedCounterFunctions_, seenAddresses);
     137            0 :                 }
     138            0 :                 catch (...)
     139            0 :                 {}
     140              :         }
     141              : 
     142            0 :         return oss.str();
     143            0 : }
     144              : 
     145            0 : void writeAll(int fd, const char* buf, size_t len)
     146              : {
     147            0 :         while (len > 0)
     148              :         {
     149            0 :                 const ssize_t n = send(fd, buf, len, MSG_NOSIGNAL);
     150            0 :                 if (n < 0)
     151              :                 {
     152            0 :                         if (errno == EINTR) continue;
     153            0 :                         break;
     154              :                 }
     155            0 :                 if (n == 0) break;
     156            0 :                 buf += n;
     157            0 :                 len -= static_cast<size_t>(n);
     158              :         }
     159            0 : }
     160              : 
     161            0 : void handleRequest(int clientFd, const ExporterConfig& config)
     162              : {
     163            0 :         char buf[4096] = {};
     164            0 :         ssize_t nread = -1;
     165              :         do
     166              :         {
     167            0 :                 nread = read(clientFd, buf, sizeof(buf) - 1);
     168            0 :         } while (nread < 0 && errno == EINTR);
     169            0 :         if (nread <= 0)
     170              :         {
     171            0 :                 close(clientFd);
     172            0 :                 return;
     173              :         }
     174              : 
     175            0 :         const std::string request(buf);
     176            0 :         const auto firstLineEnd = request.find("\r\n");
     177            0 :         if (firstLineEnd == std::string::npos)
     178              :         {
     179            0 :                 close(clientFd);
     180            0 :                 return;
     181              :         }
     182            0 :         const std::string requestLine = request.substr(0, firstLineEnd);
     183            0 :         const bool isMetrics = requestLine == "GET /metrics HTTP/1.1" ||
     184            0 :                                                    requestLine == "GET /metrics HTTP/1.0" ||
     185            0 :                                                    requestLine == "GET /metrics";
     186              : 
     187            0 :         std::string body;
     188            0 :         std::string statusLine;
     189            0 :         std::string contentType;
     190              : 
     191            0 :         if (isMetrics)
     192              :         {
     193            0 :                 body = collectMetrics(config);
     194            0 :                 statusLine = "HTTP/1.1 200 OK\r\n";
     195            0 :                 contentType = "text/plain; version=0.0.4; charset=utf-8";
     196              :         }
     197              :         else
     198              :         {
     199            0 :                 body = "Not Found. Use /metrics\n";
     200            0 :                 statusLine = "HTTP/1.1 404 Not Found\r\n";
     201            0 :                 contentType = "text/plain; charset=utf-8";
     202              :         }
     203              : 
     204            0 :         const std::string response = statusLine +
     205            0 :                                                                  "Content-Type: " + contentType +
     206              :                                                                  "\r\n"
     207            0 :                                                                  "Content-Length: " +
     208            0 :                                                                  std::to_string(body.size()) +
     209              :                                                                  "\r\n"
     210              :                                                                  "Connection: close\r\n\r\n" +
     211            0 :                                                                  body;
     212              : 
     213            0 :         writeAll(clientFd, response.c_str(), response.size());
     214            0 :         close(clientFd);
     215            0 : }
     216              : }  // namespace
     217              : 
     218            0 : void printHelpMsg()
     219              : {
     220              :         std::cout << "Usage: dtcPrometheusExporter [options]\n"
     221              :                           << "Options:\n"
     222              :                           << "    -h: This message.\n"
     223              :                           << "    -d: Restrict to one DTC/CFO instance (default: scrape all /dev/mu2e* devices)\n"
     224              :                           << "    -m: Use <file> as the emulated DTC memory area (default: mu2esim.bin)\n"
     225            0 :                           << "    -p: TCP port to listen on (default: 9100)\n";
     226            0 :         exit(0);
     227              : }
     228              : 
     229            0 : int main(int argc, char* argv[])
     230              : {
     231            0 :         ExporterConfig config;
     232            0 :         uint16_t port = 9100;
     233            0 :         config.memFileName = "mu2esim.bin";
     234              : 
     235            0 :         for (int optind = 1; optind < argc; ++optind)
     236              :         {
     237            0 :                 if (argv[optind][0] == '-')
     238              :                 {
     239            0 :                         switch (argv[optind][1])
     240              :                         {
     241            0 :                                 case 'h':
     242            0 :                                         printHelpMsg();
     243            0 :                                         break;
     244            0 :                                 case 'd':
     245            0 :                                         config.dtcFilter = DTCLib::Utilities::getOptionValue(&optind, &argv);
     246            0 :                                         break;
     247            0 :                                 case 'm':
     248            0 :                                         config.memFileName = DTCLib::Utilities::getOptionString(&optind, &argv);
     249            0 :                                         break;
     250            0 :                                 case 'p':
     251            0 :                                         port = static_cast<uint16_t>(DTCLib::Utilities::getOptionValue(&optind, &argv));
     252            0 :                                         break;
     253            0 :                                 default:
     254            0 :                                         std::cerr << "Unknown option: " << argv[optind] << "\n";
     255            0 :                                         printHelpMsg();
     256            0 :                                         break;
     257              :                         }
     258              :                 }
     259              :         }
     260              : 
     261            0 :         signal(SIGINT, signalHandler);
     262            0 :         signal(SIGTERM, signalHandler);
     263              : 
     264            0 :         const int serverFd = socket(AF_INET, SOCK_STREAM, 0);
     265            0 :         if (serverFd < 0)
     266              :         {
     267            0 :                 std::cerr << "Failed to create socket: " << strerror(errno) << "\n";
     268            0 :                 return 1;
     269              :         }
     270              : 
     271            0 :         const int opt = 1;
     272            0 :         if (setsockopt(serverFd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0)
     273              :         {
     274            0 :                 std::cerr << "Warning: setsockopt SO_REUSEADDR failed: " << strerror(errno) << "\n";
     275              :         }
     276              : 
     277            0 :         sockaddr_in addr{};
     278            0 :         addr.sin_family = AF_INET;
     279            0 :         addr.sin_addr.s_addr = INADDR_ANY;
     280            0 :         addr.sin_port = htons(port);
     281              : 
     282            0 :         if (bind(serverFd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) < 0)
     283              :         {
     284            0 :                 std::cerr << "Failed to bind to port " << port << ": " << strerror(errno) << "\n";
     285            0 :                 close(serverFd);
     286            0 :                 return 1;
     287              :         }
     288              : 
     289            0 :         if (listen(serverFd, 10) < 0)
     290              :         {
     291            0 :                 std::cerr << "Failed to listen: " << strerror(errno) << "\n";
     292            0 :                 close(serverFd);
     293            0 :                 return 1;
     294              :         }
     295              : 
     296            0 :         std::cout << "DTC/CFO Prometheus exporter listening on :" << port << "/metrics";
     297            0 :         if (config.dtcFilter.has_value()) std::cout << " (device " << *config.dtcFilter << ")";
     298            0 :         std::cout << "\n";
     299              : 
     300            0 :         while (g_running)
     301              :         {
     302              :                 fd_set fds;
     303            0 :                 struct timeval tv = {1, 0};
     304            0 :                 FD_ZERO(&fds);
     305            0 :                 FD_SET(serverFd, &fds);
     306              : 
     307            0 :                 if (select(serverFd + 1, &fds, nullptr, nullptr, &tv) <= 0)
     308              :                 {
     309            0 :                         continue;
     310              :                 }
     311              : 
     312            0 :                 const int clientFd = accept(serverFd, nullptr, nullptr);
     313            0 :                 if (clientFd >= 0)
     314              :                 {
     315            0 :                         handleRequest(clientFd, config);
     316              :                 }
     317              :         }
     318              : 
     319            0 :         close(serverFd);
     320            0 :         std::cout << "DTC/CFO Prometheus exporter stopped.\n";
     321            0 :         return 0;
     322            0 : }
        

Generated by: LCOV version 2.0-1