XRootD
Loading...
Searching...
No Matches
XrdHttpMon.cc
Go to the documentation of this file.
1#include "XrdHttpMon.hh"
4
5#include <iostream>
6#include <sstream>
7#include <thread>
8
9XrdSysError eDest(0, "HttpMon");
10
11typedef std::array<std::array<XrdHttpMon::HttpInfo, XrdHttpMon::StatusCodes::sc_Count>, XrdHttpReq::ReqType::rtCount>
13
14StatsMatrix XrdHttpMon::statsInfo{};
15
16XrdXrootdGStream* XrdHttpMon::gStream = nullptr;
17XrdMonRoll* XrdHttpMon::mrollP = nullptr;
18std::chrono::seconds XrdHttpMon::flushPeriod{0};
19
20bool XrdHttpMon::hasGStream = false;
21bool XrdHttpMon::hasMonRoll = false;
22bool XrdHttpMon::isInitialized = false;
23
24RAtomic_uint64_t XrdHttpMon::verbCounters[XrdHttpReq::ReqType::rtCount] = {0};
25RAtomic_uint64_t XrdHttpMon::statusCounters[XrdHttpMon::StatusCodes::sc_Count] = {0};
26
27// Combined schema for HTTP statistics
28// This creates a JSON structure: {"httpReqStats": {...}, "httpStatusCodeStats": {...}}
29std::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
92void 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
99void* XrdHttpMon::Start(void*) {
100 while (true) {
101 std::this_thread::sleep_for(flushPeriod);
102 Report();
103 }
104}
105
106void 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
159void XrdHttpMon::RecordCount(XrdHttpReq::ReqType op, StatusCodes sc) {
160 auto& info = statsInfo[op][sc];
161 info.count++;
162}
163
164void 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
170void 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
176void 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: <>}
184std::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
221std::string XrdHttpMon::GetOperationString(XrdHttpReq::ReqType op) {
222 switch (op) {
224 return "DELETE";
226 return "HEAD";
228 return "GET";
230 return "MKCOL";
232 return "MOVE";
234 return "OPTIONS";
236 return "PROPFIND";
238 return "PUT";
240 return "COPY";
242 return "Malformed";
243 default:
244 return "UNKNOWN";
245 }
246}
247
248std::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
295XrdHttpMon::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}
static XrdSysError eDest(0,"crypto_")
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
XrdSys::RAtomic< uint64_t > RAtomic_uint64_t
static void Record(XrdHttpReq &req, int code)
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.
ReqType
These are the HTTP/DAV requests that we support.
Definition XrdHttpReq.hh:76
std::chrono::steady_clock::time_point startTime
XrdHttpMonState monState
bool Insert(const char *data, int dlen)