zrcadlo https://github.com/atscan/atscan
pds latency tabs
This commit is contained in:
rodič
904dd83fe0
revize
f4281adf29
117
backend/api.js
117
backend/api.js
|
@ -90,6 +90,14 @@ function findDIDFed(item) {
|
|||
return ff ? ff.id : null;
|
||||
}
|
||||
|
||||
function pdsUrlFromHost(host) {
|
||||
let https = true;
|
||||
if (host.startsWith("localhost:")) {
|
||||
https = false;
|
||||
}
|
||||
return `http${https ? "s" : ""}://${host}`;
|
||||
}
|
||||
|
||||
function getPDSStatus(item) {
|
||||
if (!item.inspect) {
|
||||
return "unknown";
|
||||
|
@ -155,28 +163,39 @@ router
|
|||
perf(ctx);
|
||||
})
|
||||
.get("/pds/:host", async (ctx) => {
|
||||
let host = ctx.params.host;
|
||||
let https = true;
|
||||
if (host.startsWith("localhost:")) {
|
||||
https = false;
|
||||
}
|
||||
const q = { url: `http${https ? "s" : ""}://${host}` };
|
||||
const item = await ats.db.pds.findOne(q);
|
||||
const url = pdsUrlFromHost(ctx.params.host);
|
||||
const item = await ats.db.pds.findOne({ url });
|
||||
if (!item) {
|
||||
return ctx.response.code = 404;
|
||||
}
|
||||
Object.assign(item, prepareObject("pds", item));
|
||||
|
||||
ctx.response.body = item;
|
||||
perf(ctx);
|
||||
})
|
||||
.get("/pds/:host/latency", async (ctx) => {
|
||||
const url = pdsUrlFromHost(ctx.params.host);
|
||||
const item = await ats.db.pds.findOne({ url });
|
||||
if (!item) {
|
||||
return ctx.response.code = 404;
|
||||
}
|
||||
const host = item.url.replace(/^https?:\/\//, "");
|
||||
const allowedRanges = ["24h", "7d", "30d"];
|
||||
const rangesAggregation = ["3m", "15m", "1h"];
|
||||
const userRange = ctx.request.url.searchParams.get("range");
|
||||
const range = userRange && allowedRanges.includes(userRange)
|
||||
? userRange
|
||||
: "24h";
|
||||
const aggregation = rangesAggregation[allowedRanges.indexOf(range)];
|
||||
const query = `
|
||||
from(bucket: "ats-stats")
|
||||
|> range(start: -1d)
|
||||
|> range(start: -${range})
|
||||
|> filter(fn: (r) => r["_measurement"] == "pds_response_time")
|
||||
|> filter(fn: (r) => r["pds"] == "${item.host}")
|
||||
|> aggregateWindow(every: 3m, fn: mean, createEmpty: true)`;
|
||||
|> filter(fn: (r) => r["pds"] == "${host}")
|
||||
|> aggregateWindow(every: ${aggregation}, fn: mean, createEmpty: true)`;
|
||||
|
||||
item.responseTimesDay = await ats.influxQuery.collectRows(query);
|
||||
|
||||
ctx.response.body = item;
|
||||
const data = await ats.influxQuery.collectRows(query);
|
||||
ctx.response.body = { range, aggregation, data };
|
||||
perf(ctx);
|
||||
})
|
||||
.get("/dids", async (ctx) => {
|
||||
|
@ -309,34 +328,52 @@ router
|
|||
perf(ctx);
|
||||
})
|
||||
.get("/_metrics", async (ctx) => {
|
||||
const metrics = {
|
||||
pds_count: [await ats.db.pds.count()],
|
||||
did_count: [await ats.db.did.count()],
|
||||
};
|
||||
for (const queueName of Object.keys(ats.queues)) {
|
||||
const queue = ats.queues[queueName];
|
||||
const getMetric = async (name) =>
|
||||
(await queue.getMetrics(name)).meta.count;
|
||||
const metrics = {};
|
||||
|
||||
metrics[`queue_metric_completed{name="${queueName}"}`] = [
|
||||
await getMetric("completed"),
|
||||
];
|
||||
metrics[`queue_metric_failed{name="${queueName}"}`] = [
|
||||
await getMetric("failed"),
|
||||
];
|
||||
metrics[`queue_active{name="${queueName}"}`] = [
|
||||
await queue.getActiveCount(),
|
||||
];
|
||||
metrics[`queue_waiting{name="${queueName}"}`] = [
|
||||
await queue.getWaitingCount(),
|
||||
];
|
||||
metrics[`queue_waiting_children{name="${queueName}"}`] = [
|
||||
await queue.getWaitingChildrenCount(),
|
||||
];
|
||||
metrics[`queue_prioritized{name="${queueName}"}`] = [
|
||||
await queue.getPrioritizedCount(),
|
||||
];
|
||||
}
|
||||
await Promise.all([
|
||||
(async () => {
|
||||
metrics.did_count = [await ats.db.did.count()];
|
||||
})(),
|
||||
(async () => {
|
||||
metrics.pds_count = [await ats.db.pds.count()];
|
||||
})(),
|
||||
(async () => {
|
||||
const statuses = { online: 0, offline: 0, degraded: 0, unknown: 0 };
|
||||
for (const pds of await ats.db.pds.find().toArray()) {
|
||||
const stn = getPDSStatus(pds);
|
||||
statuses[stn]++;
|
||||
}
|
||||
for (const st of Object.keys(statuses)) {
|
||||
metrics[`pds_count{status="${st}"}`] = [statuses[st]];
|
||||
}
|
||||
})(),
|
||||
(async () => {
|
||||
for (const queueName of Object.keys(ats.queues)) {
|
||||
const queue = ats.queues[queueName];
|
||||
const getMetric = async (name) =>
|
||||
(await queue.getMetrics(name)).meta.count;
|
||||
|
||||
metrics[`queue_metric_completed{name="${queueName}"}`] = [
|
||||
await getMetric("completed"),
|
||||
];
|
||||
metrics[`queue_metric_failed{name="${queueName}"}`] = [
|
||||
await getMetric("failed"),
|
||||
];
|
||||
metrics[`queue_active{name="${queueName}"}`] = [
|
||||
await queue.getActiveCount(),
|
||||
];
|
||||
metrics[`queue_waiting{name="${queueName}"}`] = [
|
||||
await queue.getWaitingCount(),
|
||||
];
|
||||
metrics[`queue_waiting_children{name="${queueName}"}`] = [
|
||||
await queue.getWaitingChildrenCount(),
|
||||
];
|
||||
metrics[`queue_prioritized{name="${queueName}"}`] = [
|
||||
await queue.getPrioritizedCount(),
|
||||
];
|
||||
}
|
||||
})(),
|
||||
]);
|
||||
ctx.response.body = Object.keys(metrics).map((m) => {
|
||||
const [data, help, type] = metrics[m];
|
||||
return `${m} ${data}`;
|
||||
|
|
|
@ -38,11 +38,13 @@ export class ATScan {
|
|||
};
|
||||
console.log(`Connected to MongoDB: ${this.env.MONGODB_URL}`);
|
||||
if (this.enableNats) {
|
||||
this.nats = await NATSConnect({
|
||||
servers: this.env.NATS_SERVERS,
|
||||
});
|
||||
this.JSONCodec = JSONCodec();
|
||||
console.log(`Connected to NATS: ${this.env.NATS_SERVERS}`);
|
||||
await (async () => {
|
||||
this.nats = await NATSConnect({
|
||||
servers: this.env.NATS_SERVERS,
|
||||
});
|
||||
this.JSONCodec = JSONCodec();
|
||||
console.log(`Connected to NATS: ${this.env.NATS_SERVERS}`);
|
||||
})();
|
||||
}
|
||||
if (this.enableQueues) {
|
||||
this.queues = await makeQueues(this);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "atscan-fe",
|
||||
"version": "0.7.2-alpha",
|
||||
"version": "0.7.3-alpha",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
|
|
|
@ -5,9 +5,16 @@
|
|||
import SourceSection from '$lib/components/SourceSection.svelte';
|
||||
import BasicPage from '$lib/components/BasicPage.svelte';
|
||||
import Chart from '$lib/components/Chart.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import { request } from '$lib/api';
|
||||
import { TabGroup, Tab, TabAnchor } from '@skeletonlabs/skeleton';
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export let data;
|
||||
|
||||
const chartResponseTimesTab = writable(0);
|
||||
let chartResponseTimes = null;
|
||||
|
||||
const item = data.item;
|
||||
const status = getPDSStatus(item);
|
||||
|
||||
|
@ -114,47 +121,69 @@
|
|||
}
|
||||
};
|
||||
|
||||
$: chartResponseTimes = {
|
||||
animationDuration: 250,
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
//formatter: '{b}: {c} ms'
|
||||
},
|
||||
legend: {
|
||||
data: Object.keys(crawlers).map((c) => `${crawlers[c].region} (${crawlers[c].location})`)
|
||||
},
|
||||
grid: {
|
||||
left: '2%',
|
||||
right: '2%',
|
||||
bottom: '3%',
|
||||
containLabel: true
|
||||
},
|
||||
toolbox: {
|
||||
feature: {
|
||||
saveAsImage: {}
|
||||
}
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: item.responseTimesDay?.filter((r) => r.table === 0).map((r) => r._time) || []
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
axisLabel: {
|
||||
formatter: '{value} ms'
|
||||
}
|
||||
},
|
||||
series: Object.keys(crawlers).map((crawler) => {
|
||||
const crawlerOptions = crawlers[crawler];
|
||||
return {
|
||||
name: `${crawlerOptions.region} (${crawlerOptions.location})`,
|
||||
type: 'line',
|
||||
//stack: 'ms',
|
||||
data: item.responseTimesDay?.filter((r) => r.crawler === crawler).map((r) => r._value) || []
|
||||
};
|
||||
})
|
||||
};
|
||||
function renderLatencyChart(chartData) {
|
||||
return {
|
||||
animationDuration: 250,
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
//formatter: '{b}: {c} ms'
|
||||
},
|
||||
legend: {
|
||||
data: Object.keys(crawlers).map((c) => `${crawlers[c].region} (${crawlers[c].location})`)
|
||||
},
|
||||
grid: {
|
||||
left: '2%',
|
||||
right: '2%',
|
||||
bottom: '3%',
|
||||
containLabel: true
|
||||
},
|
||||
toolbox: {
|
||||
feature: {
|
||||
saveAsImage: {}
|
||||
}
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: chartData.filter((r) => r.table === 0).map((r) => r._time) || []
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
axisLabel: {
|
||||
formatter: '{value} ms'
|
||||
}
|
||||
},
|
||||
series: Object.keys(crawlers).map((crawler) => {
|
||||
const crawlerOptions = crawlers[crawler];
|
||||
return {
|
||||
name: `${crawlerOptions.region} (${crawlerOptions.location})`,
|
||||
type: 'line',
|
||||
//stack: 'ms',
|
||||
data: chartData.filter((r) => r.crawler === crawler).map((r) => r._value) || []
|
||||
};
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
const latencyConfig = ['24h', '7d', '30d'];
|
||||
|
||||
chartResponseTimesTab.subscribe(async (num) => {
|
||||
chartResponseTimes = null;
|
||||
const latencyData = await request(
|
||||
fetch,
|
||||
`/pds/${item.host}/latency?range=${latencyConfig[num]}`
|
||||
);
|
||||
if (latencyData) {
|
||||
chartResponseTimes = renderLatencyChart(latencyData.data);
|
||||
}
|
||||
});
|
||||
|
||||
onMount(async () => {
|
||||
const latencyData = await request(fetch, `/pds/${item.host}/latency`);
|
||||
if (latencyData) {
|
||||
chartResponseTimes = renderLatencyChart(latencyData.data);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<BasicPage {data} title={item.host} {breadcrumb} noHeader="true">
|
||||
|
@ -187,11 +216,20 @@
|
|||
</div>
|
||||
|
||||
<h2 class="h2">Response times</h2>
|
||||
{#if chartResponseTimes}
|
||||
<div class="w-full h-64">
|
||||
<Chart options={chartResponseTimes} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<TabGroup>
|
||||
<Tab bind:group={$chartResponseTimesTab} name="tab1" value={0}>Last 24h</Tab>
|
||||
<Tab bind:group={$chartResponseTimesTab} name="tab2" value={1}>Last 7d</Tab>
|
||||
<Tab bind:group={$chartResponseTimesTab} name="tab3" value={2}>Last 30d</Tab>
|
||||
<!-- Tab Panels --->
|
||||
<svelte:fragment slot="panel">
|
||||
<div class="w-full h-64">
|
||||
{#if chartResponseTimes}
|
||||
<Chart options={chartResponseTimes} />
|
||||
{/if}
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</TabGroup>
|
||||
|
||||
<div class="table-container">
|
||||
<!-- Native Table Element -->
|
||||
|
@ -201,7 +239,7 @@
|
|||
<th class="text-sm">Region</th>
|
||||
<th class="text-sm">Location</th>
|
||||
<th class="text-sm">Status</th>
|
||||
<th class="text-sm">Response time</th>
|
||||
<th class="text-sm">Latency</th>
|
||||
<th class="text-sm">Last check</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
|
Načítá se…
Odkázat v novém úkolu