XRootD
XrdHttpMon.cc
Go to the documentation of this file.
1 #include "XrdHttpMon.hh"
2 #include "XrdSys/XrdSysError.hh"
4 
5 #include <iostream>
6 #include <sstream>
7 #include <thread>
8 
9 XrdSysError eDest(0, "HttpMon");
10 
11 typedef std::array<std::array<XrdHttpMon::HttpInfo, XrdHttpMon::StatusCodes::sc_Count>, XrdHttpReq::ReqType::rtCount>
13 
14 StatsMatrix XrdHttpMon::statsInfo{};
15 
16 XrdXrootdGStream* XrdHttpMon::gStream = nullptr;
17 XrdMonRoll* XrdHttpMon::mrollP = nullptr;
18 std::chrono::seconds XrdHttpMon::flushPeriod{0};
19 
20 bool XrdHttpMon::hasGStream = false;
21 bool XrdHttpMon::hasMonRoll = false;
22 bool XrdHttpMon::isInitialized = false;
23 
24 RAtomic_uint64_t XrdHttpMon::verbCounters[XrdHttpReq::ReqType::rtCount] = {0};
25 RAtomic_uint64_t XrdHttpMon::statusCounters[XrdHttpMon::StatusCodes::sc_Count] = {0};
26 
27 // Combined schema for HTTP statistics
28 // This creates a JSON structure: {"httpReqStats": {...}, "httpStatusCodeStats": {...}}
29 std::vector<XrdMonRoll::Item> XrdHttpMon::statsSchema = {
30 // NOTE: Keep this mapping aligned to the XrdHttpReq enum
32  XrdMonRoll::Item("Unknown", verbCounters[0]),
33  XrdMonRoll::Item("Malformed", verbCounters[1]),
34  XrdMonRoll::Item("GET", verbCounters[2]),
35  XrdMonRoll::Item("HEAD", verbCounters[3]),
36  XrdMonRoll::Item("PUT", verbCounters[4]),
37  XrdMonRoll::Item("OPTIONS", verbCounters[5]),
38  XrdMonRoll::Item("PATCH", verbCounters[6]),
39  XrdMonRoll::Item("DELETE", verbCounters[7]),
40  XrdMonRoll::Item("PROPFIND", verbCounters[8]),
41  XrdMonRoll::Item("MKCOL", verbCounters[9]),
42  XrdMonRoll::Item("MOVE", verbCounters[10]),
43  XrdMonRoll::Item("POST", verbCounters[11]),
44  XrdMonRoll::Item("COPY", verbCounters[12]),
46 
47 // NOTE: Keep this mapping strictly aligned with StatusCodes enum XrdHttpMon::StatusCode
48 // The order and number of entries MUST match.
50  XrdMonRoll::Item("100", statusCounters[sc_100]),
51  XrdMonRoll::Item("200", statusCounters[sc_200]),
52  XrdMonRoll::Item("201", statusCounters[sc_201]),
53  XrdMonRoll::Item("202", statusCounters[sc_202]),
54  XrdMonRoll::Item("206", statusCounters[sc_206]),
55  XrdMonRoll::Item("207", statusCounters[sc_207]),
56  XrdMonRoll::Item("302", statusCounters[sc_302]),
57  XrdMonRoll::Item("307", statusCounters[sc_307]),
58  XrdMonRoll::Item("400", statusCounters[sc_400]),
59  XrdMonRoll::Item("401", statusCounters[sc_401]),
60  XrdMonRoll::Item("403", statusCounters[sc_403]),
61  XrdMonRoll::Item("404", statusCounters[sc_404]),
62  XrdMonRoll::Item("405", statusCounters[sc_405]),
63  XrdMonRoll::Item("409", statusCounters[sc_409]),
64  XrdMonRoll::Item("416", statusCounters[sc_416]),
65  XrdMonRoll::Item("423", statusCounters[sc_423]),
66  XrdMonRoll::Item("500", statusCounters[sc_500]),
67  XrdMonRoll::Item("502", statusCounters[sc_502]),
68  XrdMonRoll::Item("504", statusCounters[sc_504]),
69  XrdMonRoll::Item("507", statusCounters[sc_507]),
70  XrdMonRoll::Item("OTHERS", statusCounters[sc_UNKNOWN]),
72 };
73 
75  eDest.logger(logP);
76  XrdHttpMon::gStream = gStream;
77  XrdHttpMon::mrollP = mrollP;
78 
79  if (gStream != nullptr){
80  hasGStream = true;
81  flushPeriod = std::chrono::seconds(gStream->GetAutoFlush());
82  }
83 
84  if (mrollP != nullptr) {
85  hasMonRoll = true;
86  mrollP->Register(XrdMonRoll::AddOn, "http_plugin", statsSchema);
87  }
88 
89  isInitialized = true;
90 }
91 
92 void XrdHttpMon::Report() {
93  std::string json = GetMonitoringJson();
94  if (!gStream->Insert(json.c_str(), json.size() + 1)) {
95  eDest.Emsg("HttpMon", "Gstream Buffer Rejected");
96  }
97 }
98 
99 void* XrdHttpMon::Start(void*) {
100  while (true) {
101  std::this_thread::sleep_for(flushPeriod);
102  Report();
103  }
104 }
105 
106 void XrdHttpMon::Record(XrdHttpReq &req, int code)
107 {
108  // Early return if monitoring is not enabled.
109  if (!isInitialized) return;
110 
111  // Only record once we have a "final" (>= 200) response code. 100-Continue is interim.
112  if (code < 200) return;
113 
114  std::chrono::steady_clock::duration duration{};
115  if (hasGStream) {
116  const auto now = std::chrono::steady_clock::now();
117  duration = now - req.startTime;
118  }
119 
120  StatusCodes statusCode = ToStatusCode(code);
121  XrdHttpMonState st = req.monState;
122 
123  if (req.request >= XrdHttpReq::ReqType::rtCount || req.request < 0) {
124  eDest.Emsg("Record", "ERROR: Record called with invalid request type");
125  return;
126  }
127 
128  switch (st) {
130  RecordGStreamCount(req.request, statusCode);
131  RecordMonRollVerb(req.request);
133  return;
134 
136  RecordGStreamSuccess(req.request, statusCode, duration);
137  RecordMonRollStatus(statusCode);
139  return;
140 
142  RecordGStreamErrNet(req.request, statusCode, duration);
143  RecordMonRollStatus(statusCode);
145  return;
146 
148  RecordGStreamErrProt(req.request, statusCode, duration);
149  RecordMonRollStatus(statusCode);
151  return;
152 
154  eDest.Emsg("Record", "ERROR: Record called after state was set to DONE");
155  return;
156  }
157 }
158 
159 void XrdHttpMon::RecordCount(XrdHttpReq::ReqType op, StatusCodes sc) {
160  auto& info = statsInfo[op][sc];
161  info.count++;
162 }
163 
164 void XrdHttpMon::RecordSuccess(XrdHttpReq::ReqType op, StatusCodes sc, std::chrono::steady_clock::duration duration) {
165  auto& info = statsInfo[op][sc];
166  info.success++;
167  info.duration_us += std::chrono::duration_cast<std::chrono::microseconds>(duration).count();
168 }
169 
170 void XrdHttpMon::RecordErrProt(XrdHttpReq::ReqType op, StatusCodes sc, std::chrono::steady_clock::duration duration) {
171  auto& info = statsInfo[op][sc];
172  info.error_xrootd++;
173  info.duration_us += std::chrono::duration_cast<std::chrono::microseconds>(duration).count();
174 }
175 
176 void XrdHttpMon::RecordErrNet(XrdHttpReq::ReqType op, StatusCodes sc, std::chrono::steady_clock::duration duration) {
177  auto& info = statsInfo[op][sc];
178  info.error_network++;
179  info.duration_us += std::chrono::duration_cast<std::chrono::microseconds>(duration).count();
180 }
181 
182 // This creates a json with the following format:
183 // http_GET_200 = {count: <>, bytes: <>, duration: <>}
184 std::string XrdHttpMon::GetMonitoringJson() {
185  std::ostringstream oss;
186  oss << "{";
187 
188  bool first = true;
189  for (size_t op = 0; op < XrdHttpReq::ReqType::rtCount; ++op) {
190  std::string opName = GetOperationString(static_cast<XrdHttpReq::ReqType>(op));
191  for (size_t sc = 0; sc < StatusCodes::sc_Count; ++sc) {
192  auto& info = statsInfo[op][sc];
193 
194  uint64_t total_count = info.count;
195  if (total_count == 0) continue;
196 
197  uint64_t error_count_network = info.error_network;
198  uint64_t error_count_xrootd = info.error_xrootd;
199  uint64_t success_count = info.success;
200  double duration_us = std::chrono::duration<double>(std::chrono::microseconds(static_cast<uint64_t>(info.duration_us))).count();
201 
202  std::string key = "HTTP_" + opName + "_" + GetStatusCodeString(static_cast<StatusCodes>(sc));
203 
204  if (!first) oss << ",";
205  first = false;
206 
207  oss << "\"" << key << "\":{";
208  oss << "\"count\":" << total_count << ",";
209  oss << "\"errors_network\":" << error_count_network << ",";
210  oss << "\"errors_xrootd\":" << error_count_xrootd << ",";
211  oss << "\"success\":" << success_count << ",";
212  oss << "\"duration_seconds\":" << duration_us;
213  oss << "}";
214  }
215  }
216 
217  oss << "}";
218  return oss.str();
219 }
220 
221 std::string XrdHttpMon::GetOperationString(XrdHttpReq::ReqType op) {
222  switch (op) {
223  case XrdHttpReq::ReqType::rtDELETE:
224  return "DELETE";
225  case XrdHttpReq::ReqType::rtHEAD:
226  return "HEAD";
227  case XrdHttpReq::ReqType::rtGET:
228  return "GET";
229  case XrdHttpReq::ReqType::rtMKCOL:
230  return "MKCOL";
231  case XrdHttpReq::ReqType::rtMOVE:
232  return "MOVE";
233  case XrdHttpReq::ReqType::rtOPTIONS:
234  return "OPTIONS";
235  case XrdHttpReq::ReqType::rtPROPFIND:
236  return "PROPFIND";
237  case XrdHttpReq::ReqType::rtPUT:
238  return "PUT";
239  case XrdHttpReq::ReqType::rtCOPY:
240  return "COPY";
241  case XrdHttpReq::ReqType::rtMalformed:
242  return "Malformed";
243  default:
244  return "UNKNOWN";
245  }
246 }
247 
248 std::string XrdHttpMon::GetStatusCodeString(StatusCodes sc) {
249  switch (sc) {
250  case sc_100:
251  return "100";
252  case sc_200:
253  return "200";
254  case sc_201:
255  return "201";
256  case sc_202:
257  return "202";
258  case sc_206:
259  return "206";
260  case sc_207:
261  return "207";
262  case sc_302:
263  return "302";
264  case sc_307:
265  return "307";
266  case sc_400:
267  return "400";
268  case sc_401:
269  return "401";
270  case sc_403:
271  return "403";
272  case sc_404:
273  return "404";
274  case sc_405:
275  return "405";
276  case sc_409:
277  return "409";
278  case sc_416:
279  return "416";
280  case sc_423:
281  return "423";
282  case sc_500:
283  return "500";
284  case sc_502:
285  return "502";
286  case sc_504:
287  return "504";
288  case sc_507:
289  return "507";
290  default:
291  return "UNKNOWN";
292  }
293 }
294 
295 XrdHttpMon::StatusCodes XrdHttpMon::ToStatusCode(int code) {
296  switch (code) {
297  case 100:
298  return sc_100;
299  case 200:
300  return sc_200;
301  case 201:
302  return sc_201;
303  case 202:
304  return sc_202;
305  case 206:
306  return sc_206;
307  case 207:
308  return sc_207;
309  case 302:
310  return sc_302;
311  case 307:
312  return sc_307;
313  case 400:
314  return sc_400;
315  case 401:
316  return sc_401;
317  case 403:
318  return sc_403;
319  case 404:
320  return sc_404;
321  case 405:
322  return sc_405;
323  case 409:
324  return sc_409;
325  case 416:
326  return sc_416;
327  case 423:
328  return sc_423;
329  case 500:
330  return sc_500;
331  case 502:
332  return sc_502;
333  case 504:
334  return sc_504;
335  case 507:
336  return sc_507;
337  default:
338  return sc_UNKNOWN;
339  }
340 }
XrdHttpMonState
XrdSysError eDest(0, "HttpMon")
std::array< std::array< XrdHttpMon::HttpInfo, XrdHttpMon::StatusCodes::sc_Count >, XrdHttpReq::ReqType::rtCount > StatsMatrix
Definition: XrdHttpMon.cc:12
nlohmann::json json
static void Record(XrdHttpReq &req, int code)
Definition: XrdHttpMon.cc:106
static void Initialize(XrdSysLogger *logP, XrdXrootdGStream *gStream, XrdMonRoll *mrollP)
Definition: XrdHttpMon.cc:74
static void * Start(void *)
Definition: XrdHttpMon.cc:99
ReqType request
The request we got.
Definition: XrdHttpReq.hh:268
ReqType
These are the HTTP/DAV requests that we support.
Definition: XrdHttpReq.hh:76
std::chrono::steady_clock::time_point startTime
Definition: XrdHttpReq.hh:368
XrdHttpMonState monState
Definition: XrdHttpReq.hh:377
bool Register(rollType setType, const char *setName, std::vector< Item > &iVec)
Definition: XrdMonRoll.cc:53
int Emsg(const char *esfx, int ecode, const char *text1, const char *text2=0)
Definition: XrdSysError.cc:116
XrdSysLogger * logger(XrdSysLogger *lp=0)
Definition: XrdSysError.hh:175
bool Insert(const char *data, int dlen)