import argparse import collections import copy import datetime import json import requests parser = argparse.ArgumentParser(description="Process some integers.") parser.add_argument("--cookie", dest="cookie", required=True, help="grafana cookie") parser.add_argument( "--offset", dest="offset", required=True, help="offset of the week from this week" ) args = parser.parse_args() weekdays = [ "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday", ] def get_byday(dayname, offset): today = datetime.datetime.today() start_date = datetime.datetime(today.year, today.month, today.day, 18, 0, 0) day_num = datetime.datetime.today().weekday() day_num_target = weekdays.index(dayname) days_ago = day_num - day_num_target - int(offset) * 7 target_date = start_date - datetime.timedelta(days=days_ago) return target_date begin = get_byday("Friday", int(args.offset) - 1) end = get_byday("Friday", int(args.offset)) begin_ts = str(int(begin.timestamp()) * 1000) end_ts = str(int(end.timestamp()) * 1000) print( f""" This weekly report summarizes all TiDB Cloud HTTP API calls from API Gateway. Timeframe: {begin} ~ {end} Databoard: https://clinic.pingcap.com/grafana/d/6abxfwgVk/api-gateway-kong?orgId=1&from={begin_ts}&to={end_ts} # Some insights (this section is a quick analysis, rather than an overall summary.) # Details """ ) TEMPLATE = { "queries": [ { "editorMode": "code", "expr": 'sum(rate(kong_custom_http_status{kube_service=~".*", host=~".*", path=~".*", code=~"4(.*)|5(.*)"}[5m])) by (code) > 0', "legendFormat": "{{code}}", "range": True, "refId": "A", "datasource": {"uid": "Ak6VpZD4z", "type": "prometheus"}, "queryType": "timeSeriesQuery", "exemplar": False, "requestId": "23A", "utcOffsetSec": 28800, "interval": "", "datasourceId": 9, "intervalMs": 15000, "maxDataPoints": 393, } ], "from": begin_ts, "to": end_ts, } COOKIE = args.cookie def call_grafana(param): payload = copy.deepcopy(TEMPLATE) query = payload["queries"][0] query["expr"] = param.expr query["legendFormat"] = param.legendFormat resp = requests.post( "https://clinic.pingcap.com/grafana/api/ds/query", headers={ "content-type": "application/json", "cookie": COOKIE, }, data=json.dumps(payload), timeout=180, ) if resp.status_code != 200: print(resp.status_code) print(resp.json()) raise result = {} for frame in resp.json()["results"]["A"]["frames"]: result[frame["schema"]["name"]] = values = frame["data"]["values"][1] return result Param = collections.namedtuple("Param", ["expr", "legendFormat"]) class Overall: params = [ Param("sum(rate(kong_custom_http_status{}[5m]))", "QPS"), Param( "(sum(kong_custom_http_status{}) - sum(kong_custom_http_status{} offset $__range)) > 0", "TOTAL", ), Param( '(sum(kong_custom_http_status{code=~"4(.*)", code!="401"}) - sum(kong_custom_http_status{code=~"4(.*)", code != "401"} offset $__range)) > 0', "4xx", ), Param( '(sum(kong_custom_http_status{code=~"5(.*)"}) - sum(kong_custom_http_status{code=~"5(.*)"} offset $__range)) > 0', "5xx", ), Param( 'histogram_quantile(0.90, sum(rate(kong_custom_request_latency_ms_bucket{path!~"(.*)root/ws(.*)"}[1m])) by (le))', "P90", ), Param( 'histogram_quantile(0.95, sum(rate(kong_custom_request_latency_ms_bucket{path!~"(.*)root/ws(.*)"}[1m])) by (le))', "P95", ), Param( 'histogram_quantile(0.99, sum(rate(kong_custom_request_latency_ms_bucket{path!~"(.*)root/ws(.*)"}[1m])) by (le))', "P99", ), ] def __init__(self): self.mean_qps = None self.max_qps = None self.mean_p90 = None self.mean_p95 = None self.mean_p99 = None self.err_rate = None self.four_rate = None self.five_rate = None self.store = {} def _do_call_grafana(self): for param in self.params: r = call_grafana(param) self.store.update(r) def _calculate(self): self.mean_qps = sum(self.store["QPS"]) / len(self.store["QPS"]) self.max_qps = max(self.store["QPS"]) self.mean_p90 = sum(self.store["P90"]) / len(self.store["P90"]) self.mean_p95 = sum(self.store["P95"]) / len(self.store["P95"]) self.mean_p99 = sum(self.store["P99"]) / len(self.store["P99"]) self.err_rate = (self.store["4xx"][-1] + self.store["5xx"][-1]) / self.store[ "TOTAL" ][-1] self.four_rate = self.store["4xx"][-1] / self.store["TOTAL"][-1] self.five_rate = self.store["5xx"][-1] / self.store["TOTAL"][-1] def _format(self): # print(f"QPS: {round(self.mean_qps, 2)}, {round(self.max_qps, 2)}") # print(f"Latency: {round(self.mean_p90, 2)}, {round(self.mean_p95, 2)}, {round(self.mean_p99, 2)}") # print(f"Error: {round(self.err_rate * 100, 2)}%, {round(self.four_rate * 100, 2)}%, {round(self.five_rate * 100, 2)}%") print("**Traffic**(count),,**Latency**(ms),,,**Error* Rate**,,,") print("QPS-Mean,QPS-Max,TP90,TP95,TP99,Total,4xx,5xx") print( f"{round(self.mean_qps, 2)},{round(self.max_qps, 2)},{round(self.mean_p90, 2)},{round(self.mean_p95, 2)},{round(self.mean_p99, 2)},{round(self.err_rate * 100, 2)}%,{round(self.four_rate * 100, 2)}%,{round(self.five_rate * 100, 2)}%" ) def run(self): self._do_call_grafana() self._calculate() self._format() class ErrorRateComponent: params = [ Param( '(sum(kong_custom_http_status{kube_service=~"(.*)account(.*)|(.*)sqleditor(.*)|(.*)chat2query(.*)"}) by (code) - sum(kong_custom_http_status{kube_service=~"(.*)account(.*)|(.*)sqleditor(.*)|(.*)chat2query(.*)"} offset $__range) by (code)) > 0', "{{code}}", ), Param( '(sum(kong_custom_http_status{kube_service=~"(.*)core-portal(.*)"}) by (code) - sum(kong_custom_http_status{kube_service=~"(.*)core-portal(.*)"} offset $__range) by (code)) > 0', "{{code}}", ), Param( '(sum(kong_custom_http_status{kube_service=~"(.*)dataflow(.*)"}) by (code) - sum(kong_custom_http_status{kube_service=~"(.*)dataflow(.*)"} offset $__range) by (code)) > 0', "{{code}}", ), Param( '(sum(kong_custom_http_status{kube_service=~"(.*)metrics(.*)|(.*)internal(.*)"}) by (code) - sum(kong_custom_http_status{kube_service=~"(.*)metrics(.*)|(.*)internal(.*)"} offset $__range) by (code)) > 0', "{{code}}", ), Param( '(sum(kong_custom_http_status{kube_service=~"(.*)serverless(.*)"}) by (code) - sum(kong_custom_http_status{kube_service=~"(.*)serverless(.*)"} offset $__range) by (code)) > 0', "{{code}}", ), ] store_names = ( "CP", "CM", "DP", "AS", "CE", ) def __init__(self): self.store = {} for name in self.store_names: self.store[name] = None self.codes = set() def _do_call_grafana(self): for index, param in enumerate(self.params): r = call_grafana(param) total = 0 codes_count = {} for code, values in r.items(): item_total = values[-1] if item_total <= 0: continue self.codes.add(code) total += item_total codes_count[code] = {"count": item_total} for code in codes_count: codes_count[code]["rate"] = codes_count[code]["count"] / total self.store[self.store_names[index]] = codes_count def _format(self): print(",CP,,CM,,DP,,AS,,CE,,") print("Error Code,Count,Rate,Count,Rate,Count,Rate,Count,Rate,Count,Rate") for code in sorted(self.codes): if int(code) < 400 or int(code) == 401: continue print(f"{code},", end="") for store_name in self.store_names: if not self.store[store_name].get(code): print(",,", end="") continue print( f"{self.store[store_name][code]['count']},{round(self.store[store_name][code]['rate'] * 100, 2)}%,", end="", ) print() print("Total") def run(self): self._do_call_grafana() self._format() class ErrorPathComponent: params = [ Param( 'sum(kong_custom_http_status{kube_service=~"(.*)account(.*)|(.*)sqleditor(.*)|(.*)chat2query(.*)", code=~"4(.*)|5(.*)", code != "401"}) by (path) - sum(kong_custom_http_status{kube_service=~"(.*)account(.*)|(.*)sqleditor(.*)|(.*)chat2query(.*)", code=~"4(.*)|5(.*)", code != "401"} offset $__range) by (path) > 0', "{{path}}", ), Param( '(sum(kong_custom_http_status{kube_service=~"(.*)account(.*)|(.*)sqleditor(.*)|(.*)chat2query(.*)"}) by (path) - sum(kong_custom_http_status{kube_service=~"(.*)account(.*)|(.*)sqleditor(.*)|(.*)chat2query(.*)"} offset $__range) by (path)) > 0', "{{path}}", ), Param( 'sum(kong_custom_http_status{kube_service=~"(.*)core-portal(.*)", code=~"4(.*)|5(.*)", code != "401"}) by (path) - sum(kong_custom_http_status{kube_service=~"(.*)core-portal(.*)", code=~"4(.*)|5(.*)", code != "401"} offset $__range) by (path) > 0', "{{path}}", ), Param( '(sum(kong_custom_http_status{kube_service=~"(.*)core-portal(.*)"}) by (path) - sum(kong_custom_http_status{kube_service=~"(.*)core-portal(.*)"} offset $__range) by (path)) > 0', "{{path}}", ), Param( 'sum(kong_custom_http_status{kube_service=~"(.*)dataflow(.*)", code=~"4(.*)|5(.*)", code != "401"}) by (path) - sum(kong_custom_http_status{kube_service=~"(.*)dataflow(.*)", code=~"4(.*)|5(.*)", code != "401"} offset $__range) by (path) > 0', "{{path}}", ), Param( '(sum(kong_custom_http_status{kube_service=~"(.*)dataflow(.*)"}) by (path) - sum(kong_custom_http_status{kube_service=~"(.*)dataflow(.*)"} offset $__range) by (path)) > 0', "{{path}}", ), Param( 'sum(kong_custom_http_status{kube_service=~"(.*)metrics(.*)|(.*)internal(.*)", code=~"4(.*)|5(.*)", code != "401"}) by (path) - sum(kong_custom_http_status{kube_service=~"(.*)metrics(.*)|(.*)internal(.*)", code=~"4(.*)|5(.*)", code != "401"} offset $__range) by (path) > 0', "{{path}}", ), Param( '(sum(kong_custom_http_status{kube_service=~"(.*)metrics(.*)|(.*)internal(.*)"}) by (path) - sum(kong_custom_http_status{kube_service=~"(.*)metrics(.*)|(.*)internal(.*)"} offset $__range) by (path)) > 0', "{{path}}", ), Param( 'sum(kong_custom_http_status{kube_service=~"(.*)serverless(.*)", code=~"4(.*)|5(.*)", code != "401"}) by (path) - sum(kong_custom_http_status{kube_service=~"(.*)serverless(.*)", code=~"4(.*)|5(.*)", code != "401"} offset $__range) by (path) > 0', "{{path}}", ), Param( '(sum(kong_custom_http_status{kube_service=~"(.*)serverless(.*)"}) by (path) - sum(kong_custom_http_status{kube_service=~"(.*)serverless(.*)"} offset $__range) by (path)) > 0', "{{path}}", ), ] store_names = ( "CP", "CM", "DP", "AS", "CE", ) value_names = ("error", "all") def __init__(self): self.store = {} for name in self.store_names: self.store[name] = collections.defaultdict(dict) def _do_call_grafana(self): for index, param in enumerate(self.params): r = call_grafana(param) store_name = self.store_names[int(index / 2)] value_name = self.value_names[index % 2] for path, values in r.items(): item_total = values[-1] if item_total <= 0: continue self.store[store_name][path][value_name] = item_total def _format(self): print(",API Path,Count,Rate") for store_name in self.store_names: error_list = [] for path, item in self.store[store_name].items(): if not item.get("error"): continue item["path"] = path error_list.append(item) error_list.sort(key=lambda item: item["error"], reverse=True) i = 0 for item in error_list: if not item.get("all"): continue if i >= 5: break if i == 0: print(f"{store_name},", end="") else: print(",", end="") i += 1 print( f"{item['path']},{item['error']},{round(item['error']/item['all']*100, 2)}%" ) def run(self): self._do_call_grafana() self._format() class LatencyPathComponent: params = [ Param( 'histogram_quantile(0.99, sum(rate(kong_custom_request_latency_ms_bucket{kube_service=~"(.*)account(.*)|(.*)sqleditor(.*)|(.*)chat2query(.*)"}[1m])) by (le, path)) > 0', "{{path}}", ), Param( 'histogram_quantile(0.99, sum(rate(kong_custom_request_latency_ms_bucket{kube_service=~"(.*)core-portal(.*)",path!~"(.*)root/ws(.*)"}[1m])) by (le, path)) > 0', "{{path}}", ), Param( 'histogram_quantile(0.99, sum(rate(kong_custom_request_latency_ms_bucket{kube_service=~"(.*)dataflow(.*)"}[1m])) by (le, path)) > 0', "{{path}}", ), Param( 'histogram_quantile(0.99, sum(rate(kong_custom_request_latency_ms_bucket{kube_service=~"(.*)metrics(.*)|(.*)internal(.*)"}[1m])) by (le, path)) > 0', "{{path}}", ), Param( 'histogram_quantile(0.99, sum(rate(kong_custom_request_latency_ms_bucket{kube_service=~"(.*)serverless(.*)"}[1m])) by (le, path)) > 0', "{{path}}", ), ] store_names = ( "CP", "CM", "DP", "AS", "CE", ) def __init__(self): self.store = {} def _do_call_grafana(self): for index, param in enumerate(self.params): r = call_grafana(param) store_name = self.store_names[index] path_latency = [] for path, values in r.items(): path_latency.append( { "path": path, "MeanP99": sum(values) / len(values), "MaxP99": max(values), } ) self.store[store_name] = path_latency def _format(self): print(",API Path,MeanP99,MaxP99") for store_name in self.store_names: path_latency = self.store[store_name] path_latency.sort(key=lambda item: item["MeanP99"], reverse=True) i = 0 for item in path_latency: if i >= 5: break if i == 0: print(f"{store_name},", end="") else: print(",", end="") i += 1 print( f"{item['path']},{round(item['MeanP99'], 2)},{round(item['MaxP99'],2)}" ) def run(self): self._do_call_grafana() self._format() class LatencyComponent: params = [ Param( 'histogram_quantile(0.99, sum(rate(kong_custom_request_latency_ms_bucket{kube_service=~"(.*)account(.*)|(.*)sqleditor(.*)|(.*)chat2query(.*)"}[1m])) by (le)) > 0', "P", ), Param( 'histogram_quantile(0.99, sum(rate(kong_custom_request_latency_ms_bucket{kube_service=~"(.*)core-portal(.*)",path!~"(.*)root/ws(.*)"}[1m])) by (le)) > 0', "P", ), Param( 'histogram_quantile(0.99, sum(rate(kong_custom_request_latency_ms_bucket{kube_service=~"(.*)dataflow(.*)"}[1m])) by (le)) > 0', "P", ), Param( 'histogram_quantile(0.99, sum(rate(kong_custom_request_latency_ms_bucket{kube_service=~"(.*)metrics(.*)|(.*)internal(.*)"}[1m])) by (le)) > 0', "P", ), Param( 'histogram_quantile(0.99, sum(rate(kong_custom_request_latency_ms_bucket{kube_service=~"(.*)serverless(.*)"}[1m])) by (le)) > 0', "P", ), Param( 'histogram_quantile(0.95, sum(rate(kong_custom_request_latency_ms_bucket{kube_service=~"(.*)account(.*)|(.*)sqleditor(.*)|(.*)chat2query(.*)"}[1m])) by (le)) > 0', "P", ), Param( 'histogram_quantile(0.95, sum(rate(kong_custom_request_latency_ms_bucket{kube_service=~"(.*)core-portal(.*)",path!~"(.*)root/ws(.*)"}[1m])) by (le)) > 0', "P", ), Param( 'histogram_quantile(0.95, sum(rate(kong_custom_request_latency_ms_bucket{kube_service=~"(.*)dataflow(.*)"}[1m])) by (le)) > 0', "P", ), Param( 'histogram_quantile(0.95, sum(rate(kong_custom_request_latency_ms_bucket{kube_service=~"(.*)metrics(.*)|(.*)internal(.*)"}[1m])) by (le)) > 0', "P", ), Param( 'histogram_quantile(0.95, sum(rate(kong_custom_request_latency_ms_bucket{kube_service=~"(.*)serverless(.*)"}[1m])) by (le)) > 0', "P", ), Param( 'histogram_quantile(0.90, sum(rate(kong_custom_request_latency_ms_bucket{kube_service=~"(.*)account(.*)|(.*)sqleditor(.*)|(.*)chat2query(.*)"}[1m])) by (le)) > 0', "P", ), Param( 'histogram_quantile(0.90, sum(rate(kong_custom_request_latency_ms_bucket{kube_service=~"(.*)core-portal(.*)",path!~"(.*)root/ws(.*)"}[1m])) by (le)) > 0', "P", ), Param( 'histogram_quantile(0.90, sum(rate(kong_custom_request_latency_ms_bucket{kube_service=~"(.*)dataflow(.*)"}[1m])) by (le)) > 0', "P", ), Param( 'histogram_quantile(0.90, sum(rate(kong_custom_request_latency_ms_bucket{kube_service=~"(.*)metrics(.*)|(.*)internal(.*)"}[1m])) by (le)) > 0', "P", ), Param( 'histogram_quantile(0.90, sum(rate(kong_custom_request_latency_ms_bucket{kube_service=~"(.*)serverless(.*)"}[1m])) by (le)) > 0', "P", ), ] store_names = ( "CP", "CM", "DP", "AS", "CE", ) def __init__(self): self.store = collections.defaultdict(list) def _do_call_grafana(self): for index, param in enumerate(self.params): r = call_grafana(param) store_name = self.store_names[index % 5] self.store[store_name].append(r["P"]) def _format(self): print(",CP,,CM,,DP,,AS,,CE,") print(",Mean,Max,Mean,Max,Mean,Max,Mean,Max,Mean,Max") for i, v in enumerate(["P90", "P95", "P99"]): for j, store_name in enumerate(self.store_names): if j == 0: print(v, end="") latency = self.store[store_name][2 - i] print( f",{round(sum(latency)/len(latency),2)},{round(max(latency),2)}", end="", ) print() def run(self): self._do_call_grafana() self._format() if __name__ == "__main__": runners = [ Overall(), ErrorRateComponent(), LatencyComponent(), ErrorPathComponent(), LatencyPathComponent(), ] for runner in runners: runner.run() print("\n")