| 75 | /* ── extractHTTPInfo ─────────────────────────────────────────────── */ |
| 76 | |
| 77 | bool cbm_extract_http_info(const cbm_trace_span_t *span, const char *service_name, |
| 78 | cbm_http_span_info_t *out) { |
| 79 | if (!span || !out) { |
| 80 | return false; |
| 81 | } |
| 82 | memset(out, 0, sizeof(*out)); |
| 83 | out->service_name = service_name ? service_name : ""; |
| 84 | out->span_kind = span->kind; |
| 85 | |
| 86 | bool has_http = false; |
| 87 | static char url_buf[CBM_SZ_1K]; |
| 88 | |
| 89 | for (int i = 0; i < span->attr_count; i++) { |
| 90 | const char *key = span->attributes[i].key; |
| 91 | const char *val = span->attributes[i].string_value; |
| 92 | if (!key || !val) { |
| 93 | continue; |
| 94 | } |
| 95 | |
| 96 | if (strcmp(key, "http.method") == 0 || strcmp(key, "http.request.method") == 0) { |
| 97 | out->method = val; |
| 98 | has_http = true; |
| 99 | } else if (strcmp(key, "http.route") == 0 || strcmp(key, "http.target") == 0 || |
| 100 | strcmp(key, "url.path") == 0) { |
| 101 | out->path = val; |
| 102 | has_http = true; |
| 103 | } else if (strcmp(key, "http.status_code") == 0) { |
| 104 | out->status_code = val; |
| 105 | } else if (strcmp(key, "url.full") == 0) { |
| 106 | const char *path = cbm_extract_path_from_url(val, url_buf, sizeof(url_buf)); |
| 107 | if (path[0] != '\0') { |
| 108 | out->path = path; |
| 109 | has_http = true; |
| 110 | } |
| 111 | } |
| 112 | } |
| 113 | |
| 114 | if (!has_http || !out->path || out->path[0] == '\0') { |
| 115 | return false; |
| 116 | } |
| 117 | |
| 118 | out->duration_ns = cbm_parse_duration(span->start_time, span->end_time); |
| 119 | return true; |
| 120 | } |
| 121 | |
| 122 | /* ── calculateP99 ────────────────────────────────────────────────── */ |
| 123 |
no test coverage detected