Add Cloudflare traffic helper scripts and commercialization notes.

Track reusable CF analytics utilities and the commercial prospects document while keeping local probe/sample artifacts untracked.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
voson
2026-07-29 12:44:31 +08:00
co-authored by Cursor
parent 3ec8007077
commit 96a9312194
4 changed files with 922 additions and 0 deletions
+217
View File
@@ -0,0 +1,217 @@
"""Pull traffic for dota2.refining.dev via Cloudflare GraphQL Analytics API.
Credentials from env (keyzoo inject). Read-only; prints a short summary.
"""
from __future__ import annotations
import json
import os
import urllib.error
import urllib.request
from datetime import date, timedelta
HOST = "dota2.refining.dev"
ZONE_NAME = "refining.dev"
DAYS = 14
def headers() -> dict[str, str]:
email = os.environ.get("CLOUDFLARE_EMAIL") or os.environ.get(
"KEYZOO_ASSET_META_USERNAME"
)
key = os.environ.get("CLOUDFLARE_API_KEY") or os.environ.get(
"KEYZOO_ASSET_SECRET_GLOBAL_API_KEY"
)
if not email or not key:
raise SystemExit("missing credentials in env")
return {
"X-Auth-Email": email,
"X-Auth-Key": key,
"Content-Type": "application/json",
}
def api(path: str) -> dict:
req = urllib.request.Request(
"https://api.cloudflare.com/client/v4" + path,
method="GET",
headers=headers(),
)
with urllib.request.urlopen(req) as r:
return json.loads(r.read().decode())
def gql(query: str, variables: dict) -> dict:
req = urllib.request.Request(
"https://api.cloudflare.com/client/v4/graphql",
data=json.dumps({"query": query, "variables": variables}).encode(),
method="POST",
headers=headers(),
)
try:
with urllib.request.urlopen(req) as r:
return json.loads(r.read().decode())
except urllib.error.HTTPError as e:
body = e.read().decode()
raise SystemExit(f"HTTP {e.code}: {body[:2000]}") from e
def fmt_bytes(n: int | float | None) -> str:
if n is None:
return "-"
n = float(n)
for unit in ("B", "KB", "MB", "GB", "TB"):
if abs(n) < 1024:
return f"{n:.1f} {unit}"
n /= 1024
return f"{n:.1f} PB"
def main() -> None:
zd = api(f"/zones?name={ZONE_NAME}")
zone = (zd.get("result") or [None])[0]
if not zone:
raise SystemExit(f"zone not found: {zd.get('errors')}")
zid = zone["id"]
end = date.today()
start = end - timedelta(days=DAYS)
# Free plan: httpRequests1dGroups works for multi-day zone totals.
# Adaptive (host/path filter) is limited to a 1-day window.
zone_q = """
query($zoneTag: string!, $start: Date!, $end: Date!) {
viewer {
zones(filter: {zoneTag: $zoneTag}) {
httpRequests1dGroups(
limit: 20
filter: {date_geq: $start, date_leq: $end}
orderBy: [date_ASC]
) {
dimensions { date }
sum { requests bytes cachedRequests threats }
uniq { uniques }
}
}
}
}
"""
data = gql(
zone_q,
{"zoneTag": zid, "start": start.isoformat(), "end": end.isoformat()},
)
if data.get("errors"):
raise SystemExit(json.dumps(data["errors"], indent=2)[:4000])
rows = data["data"]["viewer"]["zones"][0]["httpRequests1dGroups"]
print(f"\n{ZONE_NAME} zone (all hosts) {start} .. {end}")
print(f"{'date':<12} {'requests':>10} {'uniques':>10} {'bytes':>12} {'threats':>8}")
tot_r = tot_u = tot_b = tot_t = 0
for row in rows:
d = row["dimensions"]["date"]
s = row["sum"]
u = row["uniq"]["uniques"]
print(
f"{d:<12} {s['requests']:>10} {u:>10} "
f"{fmt_bytes(s['bytes']):>12} {s['threats']:>8}"
)
tot_r += s["requests"]
tot_u += u
tot_b += s["bytes"]
tot_t += s["threats"]
print(
f"{'TOTAL':<12} {tot_r:>10} {tot_u:>10} "
f"{fmt_bytes(tot_b):>12} {tot_t:>8}"
)
# Host-scoped detail: one day at a time (Free quota).
host_q = """
query($zoneTag: string!, $day: Date!, $host: string!) {
viewer {
zones(filter: {zoneTag: $zoneTag}) {
total: httpRequestsAdaptiveGroups(
limit: 1
filter: {date: $day, clientRequestHTTPHost: $host}
) {
count
sum { edgeResponseBytes visits }
}
byPath: httpRequestsAdaptiveGroups(
limit: 15
filter: {date: $day, clientRequestHTTPHost: $host}
orderBy: [count_DESC]
) {
dimensions { clientRequestPath }
count
sum { visits }
}
byCountry: httpRequestsAdaptiveGroups(
limit: 8
filter: {date: $day, clientRequestHTTPHost: $host}
orderBy: [count_DESC]
) {
dimensions { clientCountryName }
count
sum { visits }
}
byStatus: httpRequestsAdaptiveGroups(
limit: 8
filter: {date: $day, clientRequestHTTPHost: $host}
orderBy: [count_DESC]
) {
dimensions { edgeResponseStatus }
count
}
}
}
}
"""
print(f"\n{HOST} host detail (last {min(DAYS, 7)} days, Free=1d/query)")
print(f"{'date':<12} {'requests':>10} {'visits':>10} {'bytes':>12}")
host_days = []
for i in range(min(DAYS, 7), -1, -1):
day = end - timedelta(days=i)
hd = gql(
host_q,
{"zoneTag": zid, "day": day.isoformat(), "host": HOST},
)
if hd.get("errors"):
print(f"{day.isoformat():<12} error: {hd['errors'][0].get('message')}")
continue
z = hd["data"]["viewer"]["zones"][0]
tot = (z["total"] or [{}])[0]
c = tot.get("count") or 0
v = (tot.get("sum") or {}).get("visits") or 0
b = (tot.get("sum") or {}).get("edgeResponseBytes") or 0
print(f"{day.isoformat():<12} {c:>10} {v:>10} {fmt_bytes(b):>12}")
host_days.append((day, z, c, v, b))
# Detail for the busiest recent day.
pick = max(host_days, key=lambda x: x[2], default=None)
if pick and pick[2] > 0:
day, z, _, _, _ = pick
print(f"\nDetail for busiest day {day.isoformat()} ({HOST})")
print("Top paths:")
for row in z["byPath"][:15]:
path = row["dimensions"].get("clientRequestPath") or "/"
v = (row.get("sum") or {}).get("visits") or 0
print(f" {row['count']:>8} req {v:>6} visits {path}")
print("Top countries:")
for row in z["byCountry"]:
name = row["dimensions"].get("clientCountryName") or "?"
v = (row.get("sum") or {}).get("visits") or 0
print(f" {row['count']:>8} req {v:>6} visits {name}")
print("Status codes:")
for row in z["byStatus"]:
st = row["dimensions"].get("edgeResponseStatus")
print(f" {st}: {row['count']}")
print(
"\nNote: edge requests/visits via Cloudflare proxy (not RUM pageviews). "
"Includes assets/bots. Zone totals mix all refining.dev hosts; "
"host table is dota2-only."
)
if __name__ == "__main__":
main()