1時間ごとの統計を追加

This commit is contained in:
yuuki 2024-06-22 19:33:50 +09:00
parent e9d51fe794
commit 1f8259f04d
5 changed files with 203 additions and 3 deletions

27
analytics_hourly.txt Normal file
View file

@ -0,0 +1,27 @@
{
viewer {
zones(filter: {
zoneTag: $zoneTag
}) {
httpRequests1hGroups(
orderBy: [datetime_ASC],
limit: $limit,
filter: {
datetime_gt: $from,
datetime_leq: $to
}
) {
dimensions {
datetime
}
sum {
bytes
cachedBytes
}
uniq {
uniques
}
}
}
}
}

32
app.py
View file

@ -34,7 +34,7 @@ def fastapi_serve(dir: str, ref: str, indexes: List[str] = ["index.html", "index
app = FastAPI() app = FastAPI()
@app.get("/cloudflare") @app.get("/api/cloudflare")
async def cloudflare(zone_id: str, x_token: Union[str, None] = Header()): async def cloudflare(zone_id: str, x_token: Union[str, None] = Header()):
query = Path("analytics_daily.txt").read_text("UTF-8") query = Path("analytics_daily.txt").read_text("UTF-8")
@ -64,6 +64,36 @@ async def cloudflare(zone_id: str, x_token: Union[str, None] = Header()):
res.headers["CDN-Cache-Control"] = f"max-age=60" res.headers["CDN-Cache-Control"] = f"max-age=60"
return res return res
@app.get("/api/cloudflare2")
async def cloudflare2(zone_id: str, x_token: Union[str, None] = Header()):
query = Path("analytics_hourly.txt").read_text("UTF-8")
now = datetime.now()
before = now - timedelta(**{ "hours": 72 })
variables = {
"zoneTag": zone_id,
"from": before.astimezone(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
"to": now.astimezone(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
"limit": 72
}
result = post(
url="https://api.cloudflare.com/client/v4/graphql",
headers={
"Authorization": f"Bearer {x_token}"
},
data=dumps({
"query": query,
"variables": variables
})
)
res = JSONResponse(result.json())
res.headers["Cache-Control"] = f"public, max-age=60, s-maxage=60"
res.headers["CDN-Cache-Control"] = f"max-age=60"
return res
@app.get("/") @app.get("/")
@app.get("/{ref:path}") @app.get("/{ref:path}")
async def home(ref: str = None): async def home(ref: str = None):

View file

@ -1,2 +1,2 @@
requests==2.32.2 requests==2.32.3
fastapi==0.111.0 fastapi==0.111.0

View file

@ -9,6 +9,11 @@
<script src="script.js"></script> <script src="script.js"></script>
</head> </head>
<body> <body>
<div id="loading-2">読み込み中…</div>
<canvas id="users-2" width="10" height="10"></canvas>
<canvas id="bytes-2" width="10" height="10"></canvas>
<canvas id="bytes2-2" width="10" height="10"></canvas>
<hr>
<div id="loading">読み込み中…</div> <div id="loading">読み込み中…</div>
<canvas id="users" width="10" height="10"></canvas> <canvas id="users" width="10" height="10"></canvas>
<canvas id="bytes" width="10" height="10"></canvas> <canvas id="bytes" width="10" height="10"></canvas>

View file

@ -28,7 +28,22 @@ const websites = [
]; ];
async function worker(item) { async function worker(item) {
const res = await fetch(`/cloudflare?zone_id=${encodeURIComponent(item.zoneId)}`, { const res = await fetch(`/api/cloudflare?zone_id=${encodeURIComponent(item.zoneId)}`, {
headers: {
"X-Token": token,
},
});
const json = await res.json();
return {
domain: item.domain,
fg: `rgb(${item.color[0]}, ${item.color[1]}, ${item.color[2]})`,
bg: `rgba(${item.color[0]}, ${item.color[1]}, ${item.color[2]}, 0.2)`,
json,
};
}
async function worker2(item) {
const res = await fetch(`/api/cloudflare2?zone_id=${encodeURIComponent(item.zoneId)}`, {
headers: { headers: {
"X-Token": token, "X-Token": token,
}, },
@ -165,4 +180,127 @@ addEventListener("load", () => {
}, },
}); });
}); });
Promise.all(websites.map((i) => worker2(i)))
.then((results) => {
document.querySelector("#loading-2").remove();
const scale = Math.max(...results.map((r) => r.json["data"]["viewer"]["zones"][0]["httpRequests1hGroups"].length));
const endedAt = Math.max(...results.map((r) => r.json["data"]["viewer"]["zones"][0]["httpRequests1hGroups"].map((g) => new Date(g["dimensions"]["datetime"]).getTime())).flat());
const range = Array.from({ length: scale }, (_, k) => new Date(endedAt - k * 60 * 60 * 1000));
const ctxUsers = document.querySelector("#users-2").getContext("2d");
new Chart(ctxUsers, {
data: {
labels: range.map((d) => d.toLocaleString()).reverse(),
datasets: results.map((r) => {
return {
type: "line",
label: r.domain,
data: range.map((d) => r.json["data"]["viewer"]["zones"][0]["httpRequests1hGroups"].find((g) => g["dimensions"]["datetime"] === d.toISOString().slice(0, 19) + "Z")?.["uniq"]?.["uniques"] ?? null).reverse(),
borderColor: r.fg,
borderWidth: 1,
fill: "origin",
backgroundColor: r.bg,
pointStyle: "star",
};
}),
},
options: {
plugins: {
title: {
display: true,
text: "過去72時間のユーザー数の推移",
},
legend: {
labels: {
usePointStyle: true,
},
},
},
scales: {
y: {
beginAtZero: true,
},
},
},
});
const ctxBytes = document.querySelector("#bytes-2").getContext("2d");
new Chart(ctxBytes, {
data: {
labels: range.map((d) => d.toLocaleString()).reverse(),
datasets: results.map((r) => {
return {
type: "line",
label: r.domain,
data: range.map((d) => r.json["data"]["viewer"]["zones"][0]["httpRequests1hGroups"].find((g) => g["dimensions"]["datetime"] === d.toISOString().slice(0, 19) + "Z")?.["sum"]?.["bytes"] / 1000 ** 3 ?? null).reverse(),
borderColor: r.fg,
borderWidth: 1,
fill: "origin",
backgroundColor: r.bg,
pointStyle: "star",
};
}),
},
options: {
plugins: {
title: {
display: true,
text: "過去72時間の送受信データ量(GB)の推移",
},
legend: {
labels: {
usePointStyle: true,
},
},
},
scales: {
y: {
beginAtZero: true,
},
},
},
});
const ctxBytes2 = document.querySelector("#bytes2-2").getContext("2d");
new Chart(ctxBytes2, {
data: {
labels: range.map((d) => d.toLocaleString()).reverse(),
datasets: results.map((r) => {
return {
type: "line",
label: r.domain,
data: range.map((d) => r.json["data"]["viewer"]["zones"][0]["httpRequests1hGroups"].find((g) => g["dimensions"]["datetime"] === d.toISOString().slice(0, 19) + "Z")?.["sum"]?.["cachedBytes"] / 1000 ** 3 ?? null).reverse(),
borderColor: r.fg,
borderWidth: 1,
fill: "origin",
backgroundColor: r.bg,
pointStyle: "star",
};
}),
},
options: {
plugins: {
title: {
display: true,
text: "過去72時間のキャッシュ済み送受信データ量(GB)の推移",
},
legend: {
labels: {
usePointStyle: true,
},
},
},
scales: {
y: {
beginAtZero: true,
},
},
},
});
});
}); });