| 93 | |
| 94 | |
| 95 | def _qs(source: str) -> dict[str, str]: |
| 96 | return { |
| 97 | # 1. Top user agents. |
| 98 | "top_user_agents": f""" |
| 99 | SELECT |
| 100 | JSONExtractString(raw, 'message_json', 'request', 'headers', 'User-Agent', 1) AS ua, |
| 101 | count() AS count, |
| 102 | uniqExact(JSONExtractString(raw, 'message_json', 'request', 'client_ip')) AS distinct_ips, |
| 103 | round(countIf(JSONExtractInt(raw, 'message_json', 'status') BETWEEN 400 AND 499) / count(), 3) AS p_4xx, |
| 104 | round(countIf(JSONExtractInt(raw, 'message_json', 'status') BETWEEN 500 AND 599) / count(), 3) AS p_5xx |
| 105 | FROM {source} |
| 106 | GROUP BY ua |
| 107 | ORDER BY count DESC |
| 108 | LIMIT 50 |
| 109 | FORMAT JSONEachRow |
| 110 | """, |
| 111 | # 2. Top client IPs. |
| 112 | "top_client_ips": f""" |
| 113 | SELECT |
| 114 | JSONExtractString(raw, 'message_json', 'request', 'client_ip') AS ip, |
| 115 | any(JSONExtractString(raw, 'message_json', 'request', 'headers', 'Cf-Ipcountry', 1)) AS country, |
| 116 | count() AS count, |
| 117 | uniqExact(JSONExtractString(raw, 'message_json', 'request', 'headers', 'User-Agent', 1)) AS distinct_uas, |
| 118 | round(countIf(JSONExtractInt(raw, 'message_json', 'status') BETWEEN 400 AND 499) / count(), 3) AS p_4xx, |
| 119 | topK(1)(JSONExtractString(raw, 'message_json', 'request', 'headers', 'User-Agent', 1))[1] AS top_ua_for_ip |
| 120 | FROM {source} |
| 121 | GROUP BY ip |
| 122 | ORDER BY count DESC |
| 123 | LIMIT 50 |
| 124 | FORMAT JSONEachRow |
| 125 | """, |
| 126 | # 3. Top /24 buckets. |
| 127 | "top_slash24": f""" |
| 128 | SELECT |
| 129 | IPv4NumToString(toUInt32(intDiv(IPv4StringToNum(JSONExtractString(raw, 'message_json', 'request', 'client_ip')), 256) * 256)) AS slash24, |
| 130 | count() AS count, |
| 131 | uniqExact(JSONExtractString(raw, 'message_json', 'request', 'client_ip')) AS distinct_ips, |
| 132 | uniqExact(JSONExtractString(raw, 'message_json', 'request', 'headers', 'User-Agent', 1)) AS distinct_uas |
| 133 | FROM {source} |
| 134 | WHERE match(JSONExtractString(raw, 'message_json', 'request', 'client_ip'), '^[0-9]+\\\\.[0-9]+\\\\.[0-9]+\\\\.[0-9]+$') |
| 135 | GROUP BY slash24 |
| 136 | ORDER BY count DESC |
| 137 | LIMIT 50 |
| 138 | FORMAT JSONEachRow |
| 139 | """, |
| 140 | # 4. Top /explain cmd shapes (first token + arg-count signature). |
| 141 | "top_cmd_shapes": f""" |
| 142 | SELECT |
| 143 | shape, |
| 144 | count() AS count, |
| 145 | uniqExact(JSONExtractString(raw, 'message_json', 'request', 'headers', 'User-Agent', 1)) AS distinct_uas, |
| 146 | uniqExact(JSONExtractString(raw, 'message_json', 'request', 'client_ip')) AS distinct_ips |
| 147 | FROM ( |
| 148 | SELECT |
| 149 | raw, |
| 150 | decodeURLFormComponent(extractURLParameter(JSONExtractString(raw, 'message_json', 'request', 'uri'), 'cmd')) AS cmd_decoded, |
| 151 | concat( |
| 152 | splitByChar(' ', cmd_decoded)[1], |