2025-04-12 17:39:38 +08:00

830 lines
22 KiB
Vue

<template>
<div class="space-y-4">
<div class="flex space-x-2">
<FormControl
class="w-40"
label="服务器"
type="select"
:options="serverOptions"
v-model="chosenServer"
/>
<FormControl
class="w-32"
label="持续时间"
type="select"
:options="
durationOptions.map((option) => ({ label: option, value: option }))
"
v-model="duration"
/>
</div>
<div class="grid grid-cols-1 gap-5 sm:grid-cols-2">
<AnalyticsCard title="运行时间" v-if="!isServerType('Application Server')">
<LineChart
type="time"
title="运行时间"
:key="databaseUptimeData"
:data="databaseUptimeData"
unit="秒"
:chartTheme="[$theme.colors.purple[500]]"
:loading="$resources.databaseUptime.loading"
:error="$resources.databaseUptime.error"
:showCard="false"
class="h-[15.55rem] p-2 pb-3"
/>
</AnalyticsCard>
<AnalyticsCard title="CPU">
<LineChart
type="time"
title="CPU"
:key="cpuData"
:data="cpuData"
unit="%"
:chartTheme="[
$theme.colors.yellow[500], // system
$theme.colors.cyan[500], // user
$theme.colors.red[500], // iowait
$theme.colors.teal[500], // irq
$theme.colors.purple[500], // softirq
$theme.colors.pink[500], // nice
$theme.colors.blue[500], // steal
$theme.colors.green[500], // idle
]"
:loading="$resources.cpu.loading"
:error="$resources.cpu.error"
:showCard="false"
class="h-[15.55rem] p-2 pb-3"
/>
</AnalyticsCard>
<AnalyticsCard title="平均负载">
<LineChart
type="time"
title="平均负载"
:key="loadAverageData"
:data="loadAverageData"
:chartTheme="[
$theme.colors.green[500],
$theme.colors.yellow[400],
$theme.colors.red[500],
]"
:loading="$resources.loadavg.loading"
:error="$resources.loadavg.error"
:showCard="false"
class="h-[15.55rem] p-2 pb-3"
/>
</AnalyticsCard>
<AnalyticsCard title="内存">
<LineChart
type="time"
title="内存"
:key="memoryData"
:data="memoryData"
unit="字节"
:chartTheme="[$theme.colors.yellow[500]]"
:loading="$resources.memory.loading"
:error="$resources.memory.error"
:showCard="false"
class="h-[15.55rem] p-2 pb-3"
/>
</AnalyticsCard>
<AnalyticsCard title="磁盘空间">
<LineChart
type="time"
title="磁盘空间"
:key="spaceData"
:data="spaceData"
unit="%"
:chartTheme="[$theme.colors.red[500], $theme.colors.yellow[400]]"
:loading="$resources.space.loading"
:error="$resources.space.error"
:showCard="false"
class="h-[15.55rem] p-2 pb-3"
/>
</AnalyticsCard>
<AnalyticsCard title="网络">
<LineChart
type="time"
title="网络"
:key="networkData"
:data="networkData"
unit="字节"
:chartTheme="[$theme.colors.blue[500]]"
:loading="$resources.network.loading"
:error="$resources.network.error"
:showCard="false"
class="h-[15.55rem] p-2 pb-3"
/>
</AnalyticsCard>
<AnalyticsCard title="磁盘 I/O">
<LineChart
type="time"
title="磁盘 I/O"
:key="iopsData"
:data="iopsData"
unit="I0ps"
:chartTheme="[$theme.colors.purple[500], $theme.colors.blue[500]]"
:loading="$resources.iops.loading"
:error="$resources.iops.error"
:showCard="false"
class="h-[15.55rem] p-2 pb-3"
/>
</AnalyticsCard>
</div>
<div
class="!mt-6 flex w-fit cursor-pointer space-x-2"
@click="toggleAdvancedAnalytics"
>
<h2 class="text-lg font-semibold">高级分析</h2>
<FeatherIcon
class="h-5 w-5 text-gray-500 hover:text-gray-700"
:name="showAdvancedAnalytics ? 'chevron-down' : 'chevron-right'"
/>
</div>
<!-- 高级分析 -->
<div
v-if="showAdvancedAnalytics"
class="grid grid-cols-1 gap-5 sm:grid-cols-2"
>
<!-- 高级图表 -->
<AnalyticsCard
v-if="isServerType('Application Server')"
class="sm:col-span-2"
title="按站点的请求频率"
>
<BarChart
title="按站点的请求频率"
:key="requestCountBySiteData"
:data="requestCountBySiteData"
unit="请求"
:chartTheme="[
this.$theme.colors.green[500],
this.$theme.colors.red[500],
this.$theme.colors.yellow[500],
this.$theme.colors.pink[500],
this.$theme.colors.purple[500],
this.$theme.colors.blue[500],
this.$theme.colors.teal[500],
this.$theme.colors.cyan[500],
this.$theme.colors.gray[500],
this.$theme.colors.orange[500],
]"
:loading="$resources.requestCountBySite.loading"
:error="$resources.requestCountBySite.error"
:showCard="false"
class="h-[15.55rem] p-2 pb-3"
/>
</AnalyticsCard>
<AnalyticsCard
v-if="isServerType('Application Server')"
class="sm:col-span-2"
title="按站点的最慢请求"
>
<BarChart
title="按站点的最慢请求"
:key="requestDurationBySiteData"
:data="requestDurationBySiteData"
unit="秒"
:chartTheme="chartColors"
:loading="$resources.requestDurationBySite.loading"
:error="$resources.requestDurationBySite.error"
:showCard="false"
class="h-[15.55rem] p-2 pb-3"
/>
</AnalyticsCard>
<AnalyticsCard title="查询" v-if="!isServerType('Application Server')">
<LineChart
type="time"
title="查询"
unit="查询"
:key="databaseCommandsCountData"
:data="databaseCommandsCountData"
:chartTheme="[
$theme.colors.green[500],
$theme.colors.red[500],
$theme.colors.yellow[500],
$theme.colors.pink[500],
$theme.colors.purple[500],
$theme.colors.blue[500],
$theme.colors.teal[500],
$theme.colors.cyan[500],
]"
:loading="$resources.databaseCommandsCount.loading"
:error="$resources.databaseCommandsCount.error"
:showCard="false"
class="h-[15.55rem] p-2 pb-3"
/>
</AnalyticsCard>
<AnalyticsCard
title="数据库连接"
v-if="!isServerType('Application Server')"
>
<LineChart
type="time"
title="数据库连接"
:key="databaseConnectionsData"
:data="databaseConnectionsData"
unit="连接"
:chartTheme="[
this.$theme.colors.yellow[500],
this.$theme.colors.green[500],
]"
:loading="$resources.databaseConnections.loading"
:error="$resources.databaseConnections.error"
:showCard="false"
class="h-[15.55rem] p-2 pb-3"
/>
</AnalyticsCard>
<AnalyticsCard
title="平均行锁定时间"
v-if="!isServerType('Application Server')"
>
<LineChart
type="time"
title="平均行锁定时间"
:key="innodbAvgRowLockTimeData"
:data="innodbAvgRowLockTimeData"
unit="秒"
:chartTheme="[$theme.colors.purple[500]]"
:loading="$resources.innodbAvgRowLockTime.loading"
:error="$resources.innodbAvgRowLockTime.error"
:showCard="false"
class="h-[15.55rem] p-2 pb-3"
/>
</AnalyticsCard>
<AnalyticsCard
title="缓冲池大小"
v-if="!isServerType('Application Server')"
>
<LineChart
type="time"
title="缓冲池大小"
:key="innodbBufferPoolSizeData"
:data="innodbBufferPoolSizeData"
unit="bytes"
:chartTheme="[$theme.colors.teal[500]]"
:loading="$resources.innodbBufferPoolSize.loading"
:error="$resources.innodbBufferPoolSize.error"
:showCard="false"
class="h-[15.55rem] p-2 pb-3"
/>
</AnalyticsCard>
<AnalyticsCard
title="总内存中的缓冲池大小"
v-if="!isServerType('Application Server')"
>
<LineChart
type="time"
title="总内存中的缓冲池大小"
:key="innodbBufferPoolSizeOfTotalRamData"
:data="innodbBufferPoolSizeOfTotalRamData"
unit="%"
:chartTheme="[$theme.colors.cyan[500]]"
:loading="$resources.innodbBufferPoolSizeOfTotalRam.loading"
:error="$resources.innodbBufferPoolSizeOfTotalRam.error"
:showCard="false"
class="h-[15.55rem] p-2 pb-3"
/>
</AnalyticsCard>
<AnalyticsCard
title="缓冲池未命中百分比"
v-if="!isServerType('Application Server')"
>
<LineChart
type="time"
title="缓冲池未命中百分比"
:key="innodbBufferPoolMissPercentageData"
:data="innodbBufferPoolMissPercentageData"
unit="%"
:chartTheme="[$theme.colors.orange[500]]"
:loading="$resources.innodbBufferPoolMissPercentage.loading"
:error="$resources.innodbBufferPoolMissPercentage.error"
:showCard="false"
class="h-[15.55rem] p-2 pb-3"
/>
</AnalyticsCard>
<AnalyticsCard
v-if="!isServerType('Application Server')"
class="sm:col-span-2"
title="频繁的慢查询"
>
<template #action>
<TabButtons
:buttons="[{ label: '非规范化' }, { label: '规范化' }]"
v-model="slowLogsFrequencyType"
/>
</template>
<BarChart
title="频繁的慢查询"
:key="slowLogsCountData"
:data="slowLogsCountData"
unit="queries"
:chartTheme="chartColors"
:loading="$resources.slowLogsCount.loading"
:error="$resources.slowLogsCount.error"
:showCard="false"
class="h-[15.55rem] p-2 pb-3"
/>
</AnalyticsCard>
<AnalyticsCard
v-if="!isServerType('Application Server')"
class="sm:col-span-2"
title="最慢的查询"
>
<template #action>
<TabButtons
:buttons="[{ label: '非规范化' }, { label: '规范化' }]"
v-model="slowLogsDurationType"
/>
</template>
<BarChart
title="最慢的查询"
:key="slowLogsDurationData"
:data="slowLogsDurationData"
unit="seconds"
:chartTheme="chartColors"
:loading="$resources.slowLogsDuration.loading"
:error="$resources.slowLogsDuration.error"
:showCard="false"
class="h-[15.55rem] p-2 pb-3"
/>
</AnalyticsCard>
</div>
</div>
</template>
<script>
import { getCachedDocumentResource } from 'jingrow-ui';
import LineChart from '@/components/charts/LineChart.vue';
import BarChart from '@/components/charts/BarChart.vue';
import AnalyticsCard from '../site/AnalyticsCard.vue';
import dayjs from '../../utils/dayjs';
export default {
props: ['serverName'],
components: {
AnalyticsCard,
BarChart,
LineChart,
},
data() {
return {
duration: '1 Hour',
showAdvancedAnalytics: false,
localTimezone: dayjs.tz.guess(),
slowLogsDurationType: 'Denormalized',
slowLogsFrequencyType: 'Denormalized',
chosenServer: this.$route.query.server ?? this.serverName,
durationOptions: ['1 小时', '6 小时', '24 小时', '7 天', '15 天'],
chartColors: [
this.$theme.colors.green[500],
this.$theme.colors.red[500],
this.$theme.colors.yellow[500],
this.$theme.colors.pink[500],
this.$theme.colors.purple[500],
this.$theme.colors.blue[500],
this.$theme.colors.teal[500],
this.$theme.colors.cyan[500],
this.$theme.colors.gray[500],
this.$theme.colors.orange[500],
],
};
},
watch: {
chosenServer() {
this.$router.push({
query: {
server: this.chosenServer,
},
});
},
},
resources: {
loadavg() {
return {
url: 'jcloud.api.server.analytics',
params: {
name: this.chosenServer,
timezone: this.localTimezone,
query: 'loadavg',
duration: this.duration,
},
auto: true,
};
},
cpu() {
return {
url: 'jcloud.api.server.analytics',
params: {
name: this.chosenServer,
timezone: this.localTimezone,
query: 'cpu',
duration: this.duration,
},
auto: true,
};
},
memory() {
return {
url: 'jcloud.api.server.analytics',
params: {
name: this.chosenServer,
timezone: this.localTimezone,
query: 'memory',
duration: this.duration,
},
auto: true,
};
},
network() {
return {
url: 'jcloud.api.server.analytics',
params: {
name: this.chosenServer,
timezone: this.localTimezone,
query: 'network',
duration: this.duration,
},
auto: true,
};
},
iops() {
return {
url: 'jcloud.api.server.analytics',
params: {
name: this.chosenServer,
timezone: this.localTimezone,
query: 'iops',
duration: this.duration,
},
auto: true,
};
},
space() {
return {
url: 'jcloud.api.server.analytics',
params: {
name: this.chosenServer,
timezone: this.localTimezone,
query: 'space',
duration: this.duration,
},
auto: true,
};
},
requestCountBySite() {
return {
url: 'jcloud.api.server.get_request_by_site',
params: {
name: this.chosenServer,
query: 'count',
timezone: this.localTimezone,
duration: this.duration,
},
auto:
this.showAdvancedAnalytics && this.isServerType('Application Server'),
};
},
requestDurationBySite() {
return {
url: 'jcloud.api.server.get_request_by_site',
params: {
name: this.chosenServer,
query: 'duration',
timezone: this.localTimezone,
duration: this.duration,
},
auto:
this.showAdvancedAnalytics && this.isServerType('Application Server'),
};
},
slowLogsCount() {
return {
url: 'jcloud.api.server.get_slow_logs_by_site',
params: {
name: this.chosenServer,
query: 'count',
timezone: this.localTimezone,
duration: this.duration,
normalize: this.slowLogsFrequencyType === 'Normalized',
},
auto:
this.showAdvancedAnalytics &&
!this.isServerType('Application Server'),
};
},
slowLogsDuration() {
return {
url: 'jcloud.api.server.get_slow_logs_by_site',
params: {
name: this.chosenServer,
query: 'duration',
timezone: this.localTimezone,
duration: this.duration,
normalize: this.slowLogsDurationType === 'Normalized',
},
auto:
this.showAdvancedAnalytics &&
!this.isServerType('Application Server'),
};
},
databaseUptime() {
return {
url: 'jcloud.api.server.analytics',
params: {
name: this.chosenServer,
timezone: this.localTimezone,
query: 'database_uptime',
duration: this.duration,
},
auto: this.isServerType('Database Server'),
};
},
databaseCommandsCount() {
return {
url: 'jcloud.api.server.analytics',
params: {
name: this.chosenServer,
timezone: this.localTimezone,
query: 'database_commands_count',
duration: this.duration,
},
auto:
this.showAdvancedAnalytics && this.isServerType('Database Server'),
};
},
databaseConnections() {
return {
url: 'jcloud.api.server.analytics',
params: {
name: this.chosenServer,
timezone: this.localTimezone,
query: 'database_connections',
duration: this.duration,
},
auto:
this.showAdvancedAnalytics && this.isServerType('Database Server'),
};
},
innodbBufferPoolSize() {
return {
url: 'jcloud.api.server.analytics',
params: {
name: this.chosenServer,
timezone: this.localTimezone,
query: 'innodb_bp_size',
duration: this.duration,
},
auto:
this.showAdvancedAnalytics && this.isServerType('Database Server'),
};
},
innodbBufferPoolSizeOfTotalRam() {
return {
url: 'jcloud.api.server.analytics',
params: {
name: this.chosenServer,
timezone: this.localTimezone,
query: 'innodb_bp_size_of_total_ram',
duration: this.duration,
},
auto:
this.showAdvancedAnalytics && this.isServerType('Database Server'),
};
},
innodbBufferPoolMissPercentage() {
return {
url: 'jcloud.api.server.analytics',
params: {
name: this.chosenServer,
timezone: this.localTimezone,
query: 'innodb_bp_miss_percent',
duration: this.duration,
},
auto:
this.showAdvancedAnalytics && this.isServerType('Database Server'),
};
},
innodbAvgRowLockTime() {
return {
url: 'jcloud.api.server.analytics',
params: {
name: this.chosenServer,
timezone: this.localTimezone,
query: 'innodb_avg_row_lock_time',
duration: this.duration,
},
auto:
this.showAdvancedAnalytics && this.isServerType('Database Server'),
};
},
},
computed: {
$server() {
return getCachedDocumentResource('Server', this.serverName);
},
serverOptions() {
return [
{
label: '应用服务器',
value: this.$server.pg.name,
},
{
label: '数据库服务器',
value: this.$server.pg.database_server,
},
{
label: '复制服务器',
value: this.$server.pg.replication_server,
},
].filter((v) => v.value);
},
loadAverageData() {
let loadavg = this.$resources.loadavg.data;
if (!loadavg) return;
loadavg.datasets.sort(
(a, b) => Number(a.name.split(' ')[2]) - Number(b.name.split(' ')[2]),
);
return this.transformMultiLineChartData(loadavg);
},
cpuData() {
let cpu = this.$resources.cpu.data;
if (!cpu) return;
const order = [
'system',
'user',
'iowait',
'irq',
'softirq',
'nice',
'steal',
'idle',
];
cpu.datasets = cpu.datasets.sort((a, b) => {
if (order.indexOf(a.name) > order.indexOf(b.name)) return 1;
});
return this.transformMultiLineChartData(cpu, 'cpu', true);
},
memoryData() {
let memory = this.$resources.memory.data;
if (!memory) return;
return this.transformSingleLineChartData(memory);
},
iopsData() {
let iops = this.$resources.iops.data;
if (!iops) return;
return this.transformMultiLineChartData(iops);
},
spaceData() {
let space = this.$resources.space.data;
if (!space) return;
return this.transformMultiLineChartData(space);
},
networkData() {
let network = this.$resources.network.data;
if (!network) return;
return this.transformSingleLineChartData(network);
},
requestCountBySiteData() {
const requests = this.$resources.requestCountBySite.data;
if (!requests) return;
return requests;
},
requestDurationBySiteData() {
const requests = this.$resources.requestDurationBySite.data;
if (!requests) return;
return requests;
},
slowLogsDurationData() {
const slowLogs = this.$resources.slowLogsDuration.data;
if (!slowLogs) return;
return slowLogs;
},
slowLogsCountData() {
const slowLogs = this.$resources.slowLogsCount.data;
if (!slowLogs) return;
return slowLogs;
},
databaseUptimeData() {
const uptime = this.$resources.databaseUptime.data;
if (!uptime) return;
return this.transformSingleLineChartData(uptime);
},
databaseCommandsCountData() {
const commandsCount = this.$resources.databaseCommandsCount.data;
if (!commandsCount) return;
return this.transformMultiLineChartData(commandsCount, null, false);
},
databaseConnectionsData() {
const connections = this.$resources.databaseConnections.data;
if (!connections) return;
return this.transformMultiLineChartData(connections, null, false);
},
innodbBufferPoolSizeData() {
let innodbBufferPoolSize = this.$resources.innodbBufferPoolSize.data;
if (!innodbBufferPoolSize) return;
return this.transformSingleLineChartData(innodbBufferPoolSize, false);
},
innodbBufferPoolSizeOfTotalRamData() {
let data = this.$resources.innodbBufferPoolSizeOfTotalRam.data;
if (!data) return;
return this.transformSingleLineChartData(data, true);
},
innodbBufferPoolMissPercentageData() {
let data = this.$resources.innodbBufferPoolMissPercentage.data;
if (!data) return;
return this.transformSingleLineChartData(data, false);
},
innodbAvgRowLockTimeData() {
let data = this.$resources.innodbAvgRowLockTime.data;
if (!data) return;
return this.transformSingleLineChartData(data, false);
},
},
methods: {
transformSingleLineChartData(data, percentage = false) {
if (!data.datasets?.length) return;
let dataset = [];
const name = data.datasets ? data.datasets[0]?.name : null;
for (let index = 0; index < data.datasets[0].values.length; index++) {
dataset.push([
+new Date(data.labels[index]),
data.datasets[0].values[index],
]);
}
return {
datasets: [{ dataset: dataset, name }],
yMax: percentage ? 100 : null,
};
},
transformMultiLineChartData(data, stack = null, percentage = false) {
if (!data.datasets?.length) return;
let total = [];
if (percentage) {
// 每个CPU值的总和往往会有一些差异
// 所以我们需要为每个时间戳计算总和
for (let i = 0; i < data.datasets[0].values.length; i++) {
for (let j = 0; j < data.datasets.length; j++) {
if (!total[i]) total[i] = 0;
total[i] += data.datasets[j].values[i];
}
}
}
const datasets = data.datasets.map(({ name, values }) => {
let dataset = [];
for (let i = 0; i < values.length; i++) {
dataset.push([
+new Date(data.labels[i]),
percentage ? (values[i] / total[i]) * 100 : values[i],
]);
}
return { name, dataset, stack };
});
return { datasets, yMax: percentage ? 100 : null };
},
isServerType(type) {
return (
this.chosenServer ===
this.serverOptions.find((s) => s.label === type)?.value
);
},
toggleAdvancedAnalytics() {
this.showAdvancedAnalytics = !this.showAdvancedAnalytics;
},
},
};
</script>