perf: 抓包功能优化

This commit is contained in:
TsMask
2024-08-20 15:49:03 +08:00
parent 03352f3aa8
commit 999ccf64ad
4 changed files with 285 additions and 142 deletions

View File

@@ -18,10 +18,21 @@ export function dumpStop(data: Record<string, string>) {
}); });
} }
// 网元抓包PACP 下载
export function dumpDownload(data: Record<string, string>) {
return request({
url: '/trace/tcpdump/download',
method: 'get',
params: data,
responseType: 'blob',
timeout: 60_000,
});
}
// UPF标准版内部抓包 // UPF标准版内部抓包
export function traceUPF(data: Record<string, string>) { export function traceUPF(data: Record<string, string>) {
return request({ return request({
url: '/trace/tcpdump/traceUPF', url: '/trace/tcpdump/upf',
method: 'post', method: 'post',
data: data, data: data,
}); });

View File

@@ -1086,18 +1086,20 @@ export default {
pcap: { pcap: {
capArgPlease: 'Please enter tcpdump -i any support parameter', capArgPlease: 'Please enter tcpdump -i any support parameter',
cmd: 'Command', cmd: 'Command',
execCmd: "Generic tcpdump packet capture command", execCmd: "Common Command Options",
execCmdsSctp: "Generic tcpdump filter sctp and port commands", execCmd2: "Filter Protocol Port Command",
execUPFCmdA: 'Suitable for anomalous packet capture of other NE', execCmd3: "File Split By Time, Number Of Retained File (-G 10 -W 7)",
execUPFCmdB: 'Suitable for UPF anomaly packet capture analysis', execUPFCmdA: 'Standard Edition - UPF with other NE anomalous packet capture analysis',
execUPFCmdB: 'Standard Edition - UPF anomalies requiring packet capture analysis',
batchOper: 'Batch Operations',
batchStartText: 'Batch Start',
batchStopText: 'Batch Stop',
batchDownText: 'Batch Download',
textStart: "Start", textStart: "Start",
textStartBatch: "Batch Start",
textStop: "Stop", textStop: "Stop",
textStopBatch: "Batch Stop",
textLog: "Log", textLog: "Log",
textLogMsg: "Log Info", textLogMsg: "Log Info",
textDown: "Download", textDown: "Download",
textDownBatch: "Batch Download",
downTip: "Are you sure you want to download the {title} capture data file?", downTip: "Are you sure you want to download the {title} capture data file?",
downOk: "{title} file download complete", downOk: "{title} file download complete",
downErr: "{title} file download exception", downErr: "{title} file download exception",

View File

@@ -1086,18 +1086,20 @@ export default {
pcap: { pcap: {
capArgPlease: '请输入tcpdump -i any支持参数', capArgPlease: '请输入tcpdump -i any支持参数',
cmd: '命令', cmd: '命令',
execCmd: "通用tcpdump抓包命令", execCmd: "通用命令选项",
execCmdsSctp: "过滤sctp和port命令", execCmd2: "过滤协议端口命令",
execUPFCmdA: '适合其他网元异常UPF配合抓包的情况', execCmd3: "按时间分割的文件,保留的文件数量 (-G 10 -W 7)",
execUPFCmdB: '适合UPF异常需要抓包分析的情况', execUPFCmdA: '标准版-UPF配合其他网元异常抓包分析',
execUPFCmdB: '标准版-UPF异常需要抓包分析',
batchOper: '批量操作',
batchStartText: '批量开始',
batchStopText: '批量停止',
batchDownText: '批量下载',
textStart: "开始", textStart: "开始",
textStartBatch: "批量开始",
textStop: "停止", textStop: "停止",
textStopBatch: "批量停止",
textLog: "日志", textLog: "日志",
textLogMsg: "日志信息", textLogMsg: "日志信息",
textDown: "下载", textDown: "下载",
textDownBatch: "批量下载",
downTip: "确认要下载 {title} 抓包数据文件吗?", downTip: "确认要下载 {title} 抓包数据文件吗?",
downOk: "{title} 文件下载完成", downOk: "{title} 文件下载完成",
downErr: "{title} 文件下载异常", downErr: "{title} 文件下载异常",

View File

@@ -3,7 +3,12 @@ import { onMounted, reactive } from 'vue';
import { message, Modal } from 'ant-design-vue/lib'; import { message, Modal } from 'ant-design-vue/lib';
import { ColumnsType } from 'ant-design-vue/lib/table'; import { ColumnsType } from 'ant-design-vue/lib/table';
import { PageContainer } from 'antdv-pro-layout'; import { PageContainer } from 'antdv-pro-layout';
import { dumpStart, dumpStop, traceUPF } from '@/api/traceManage/pcap'; import {
dumpStart,
dumpStop,
dumpDownload,
traceUPF,
} from '@/api/traceManage/pcap';
import { listAllNeInfo } from '@/api/ne/neInfo'; import { listAllNeInfo } from '@/api/ne/neInfo';
import { getNeFile } from '@/api/tool/neFile'; import { getNeFile } from '@/api/tool/neFile';
import saveAs from 'file-saver'; import saveAs from 'file-saver';
@@ -14,11 +19,50 @@ const { t } = useI18n();
/**对话框对象信息状态类型 */ /**对话框对象信息状态类型 */
type ModalStateType = { type ModalStateType = {
/**表单数据 */ /**表单数据 */
from: Record<string, any>; from: Record<
string,
{
loading: boolean;
/**网元名 */
title: string;
/**命令 */
cmdStart: string;
/**upf标准版需要停止命令一般空字符 */
cmdStop: string;
/**任务编号 */
taskCode: string;
/**任务日志,upf标准版为空字符串 */
logMsg: string;
/**提交表单参数 */
data: {
neType: string;
neId: string;
cmd?: string;
};
}
>;
/**tcpdump命令组 */ /**tcpdump命令组 */
cmdOptions: Record<string, any>[]; cmdOptions: {
/**命令名称 */
label: string;
/**命令选中值 */
value: string;
/**开始命令 */
start: string;
/**停止命令 */
stop: string;
}[];
/**UPF命令组 */ /**UPF命令组 */
cmdOptionsUPF: Record<string, any>[]; cmdOptionsUPF: {
/**命令名称 */
label: string;
/**命令选中值 */
value: string;
/**开始命令 */
start: string;
/**停止命令 */
stop: string;
}[];
/**详情框是否显示 */ /**详情框是否显示 */
visibleByView: boolean; visibleByView: boolean;
/**详情框内容 */ /**详情框内容 */
@@ -31,29 +75,35 @@ let modalState: ModalStateType = reactive({
cmdOptions: [ cmdOptions: [
{ {
label: t('views.traceManage.pcap.execCmd'), label: t('views.traceManage.pcap.execCmd'),
start: '-n -s 0 -v -w',
stop: '',
value: 'any', value: 'any',
start: '-n -v -s 0',
stop: '',
}, },
{ {
label: t('views.traceManage.pcap.execCmdsSctp'), label: t('views.traceManage.pcap.execCmd2'),
value: 'any2',
start: 'sctp or tcp port 3030 or 8088', start: 'sctp or tcp port 3030 or 8088',
stop: '', stop: '',
value: 'any2', },
{
label: t('views.traceManage.pcap.execCmd3'),
value: 'any3',
start: '-n -s 0 -v -G 10 -W 7',
stop: '',
}, },
], ],
cmdOptionsUPF: [ cmdOptionsUPF: [
{ {
label: t('views.traceManage.pcap.execUPFCmdA'), label: t('views.traceManage.pcap.execUPFCmdA'),
value: 'pcap trace',
start: 'pcap trace rx tx max 100000 intfc any', start: 'pcap trace rx tx max 100000 intfc any',
stop: 'pcap trace rx tx off', stop: 'pcap trace rx tx off',
value: 'pcap trace',
}, },
{ {
label: t('views.traceManage.pcap.execUPFCmdB'), label: t('views.traceManage.pcap.execUPFCmdB'),
value: 'pcap dispatch',
start: 'pcap dispatch trace on max 100000', start: 'pcap dispatch trace on max 100000',
stop: 'pcap dispatch trace off', stop: 'pcap dispatch trace off',
value: 'pcap dispatch',
}, },
], ],
visibleByView: false, visibleByView: false,
@@ -80,25 +130,25 @@ let tableState: TabeStateType = reactive({
/**表格字段列 */ /**表格字段列 */
let tableColumns: ColumnsType = [ let tableColumns: ColumnsType = [
{ {
title: t('views.configManage.neManage.neType'), title: t('views.ne.common.neType'),
dataIndex: 'neType', dataIndex: 'neType',
align: 'left', align: 'left',
width: 100, width: 100,
}, },
{ {
title: t('views.configManage.neManage.neId'), title: t('views.ne.common.neId'),
dataIndex: 'neId', dataIndex: 'neId',
align: 'left', align: 'left',
width: 100, width: 100,
}, },
{ {
title: t('views.configManage.neManage.neName'), title: t('views.ne.common.neName'),
dataIndex: 'neName', dataIndex: 'neName',
align: 'left', align: 'left',
width: 100, width: 100,
}, },
{ {
title: t('views.configManage.neManage.ip'), title: t('views.ne.common.ipAddr'),
dataIndex: 'ip', dataIndex: 'ip',
align: 'left', align: 'left',
width: 150, width: 150,
@@ -108,13 +158,12 @@ let tableColumns: ColumnsType = [
key: 'cmd', key: 'cmd',
dataIndex: 'serverState', dataIndex: 'serverState',
align: 'left', align: 'left',
width: 300, width: 350,
}, },
{ {
title: t('common.operate'), title: t('common.operate'),
key: 'id', key: 'id',
align: 'left', align: 'left',
fixed: 'right',
}, },
]; ];
@@ -137,20 +186,22 @@ function fnGetList() {
) { ) {
tableState.data = res.data; tableState.data = res.data;
// 初始网元参数表单 // 初始网元参数表单
const { start, stop } = modalState.cmdOptions[0]; if (tableState.data.length > 0) {
for (const item of res.data) { const { start, stop } = modalState.cmdOptions[0];
modalState.from[item.id] = { for (const item of res.data) {
loading: false, modalState.from[item.id] = {
title: item.neName, // 网元名 loading: false,
cmdStart: start, title: item.neName,
cmdStop: stop, // upf需要停止命令 cmdStart: start,
out: '', cmdStop: stop,
log: '', taskCode: '',
data: { logMsg: '',
neType: item.neType, data: {
neId: item.neId, neType: item.neType,
}, neId: item.neId,
}; },
};
}
} }
} else { } else {
message.warning({ message.warning({
@@ -166,6 +217,9 @@ function fnGetList() {
function fnSelectCmd(id: any, option: any) { function fnSelectCmd(id: any, option: any) {
modalState.from[id].cmdStart = option.start; modalState.from[id].cmdStart = option.start;
modalState.from[id].cmdStop = option.stop; modalState.from[id].cmdStop = option.stop;
// 重置任务
modalState.from[id].taskCode = '';
modalState.from[id].logMsg = '';
} }
/** /**
@@ -203,8 +257,9 @@ function fnRecordStart(row?: Record<string, any>) {
if (res.status === 'fulfilled') { if (res.status === 'fulfilled') {
const resV = res.value; const resV = res.value;
if (resV.code === RESULT_CODE_SUCCESS) { if (resV.code === RESULT_CODE_SUCCESS) {
fromArr[idx].out = resV.data.out; if (!fromArr[idx].cmdStop) {
fromArr[idx].log = resV.data.log; fromArr[idx].taskCode = resV.data;
}
fromArr[idx].loading = true; fromArr[idx].loading = true;
message.success({ message.success({
content: t('views.traceManage.pcap.startOk', { title }), content: t('views.traceManage.pcap.startOk', { title }),
@@ -250,24 +305,51 @@ function fnRecordStop(row?: Record<string, any>) {
title: t('common.tipTitle'), title: t('common.tipTitle'),
content: t('views.traceManage.pcap.stopTip', { title: row.neName }), content: t('views.traceManage.pcap.stopTip', { title: row.neName }),
onOk() { onOk() {
const hide = message.loading(t('common.loading'), 0);
const fromArr = neIDs.map(id => modalState.from[id]); const fromArr = neIDs.map(id => modalState.from[id]);
const reqArr = fromArr.map(from => { const reqArr: any = [];
for (const from of fromArr) {
if (from.data.neType === 'UPF' && from.cmdStart.startsWith('pcap')) { if (from.data.neType === 'UPF' && from.cmdStart.startsWith('pcap')) {
return traceUPF(Object.assign({ cmd: from.cmdStop }, from.data)); reqArr.push(
traceUPF(Object.assign({ cmd: from.cmdStop }, from.data))
);
} else {
const taskCode = from.taskCode;
if (!taskCode) {
message.warning({
content: t('views.traceManage.pcap.stopNotRun', {
title: from.title,
}),
duration: 3,
});
continue;
}
reqArr.push(
dumpStop(Object.assign({ taskCode: from.taskCode }, from.data))
);
} }
return dumpStop(Object.assign({ fileName: from.out }, from.data)); }
}); if (reqArr.length === 0) return;
const hide = message.loading(t('common.loading'), 0);
Promise.allSettled(reqArr) Promise.allSettled(reqArr)
.then(resArr => { .then(resArr => {
resArr.forEach((res, idx) => { resArr.forEach((res, idx) => {
const title = fromArr[idx].title; const title = fromArr[idx].title;
if (res.status === 'fulfilled') { if (res.status === 'fulfilled') {
const resV = res.value; const resV = res.value;
fromArr[idx].loading = false;
fromArr[idx].logMsg = '';
if (fromArr[idx].cmdStop) {
fromArr[idx].taskCode = '';
}
if (resV.code === RESULT_CODE_SUCCESS) { if (resV.code === RESULT_CODE_SUCCESS) {
fromArr[idx].out = resV.data.out; if (fromArr[idx].cmdStop) {
fromArr[idx].log = resV.data.log; fromArr[idx].taskCode = resV.data;
fromArr[idx].loading = false; } else {
fromArr[idx].logMsg = resV.msg;
}
message.success({ message.success({
content: t('views.traceManage.pcap.stopOk', { title }), content: t('views.traceManage.pcap.stopOk', { title }),
duration: 3, duration: 3,
@@ -314,11 +396,11 @@ function fnDownPCAP(row?: Record<string, any>) {
title: t('common.tipTitle'), title: t('common.tipTitle'),
content: t('views.traceManage.pcap.downTip', { title: row.neName }), content: t('views.traceManage.pcap.downTip', { title: row.neName }),
onOk() { onOk() {
const hide = message.loading(t('common.loading'), 0);
const fromArr = neIDs.map(id => modalState.from[id]); const fromArr = neIDs.map(id => modalState.from[id]);
const reqArr = []; const reqArr: any = [];
for (const from of fromArr) { for (const from of fromArr) {
if (!from.out) { const taskCode = from.taskCode;
if (!taskCode) {
message.warning({ message.warning({
content: t('views.traceManage.pcap.stopNotRun', { content: t('views.traceManage.pcap.stopNotRun', {
title: from.title, title: from.title,
@@ -327,24 +409,25 @@ function fnDownPCAP(row?: Record<string, any>) {
}); });
continue; continue;
} }
if (from.data.neType === 'UPF' && taskCode.startsWith('/tmp')) {
reqArr.push( const fileName = taskCode.substring(taskCode.lastIndexOf('/') + 1);
getNeFile( reqArr.push(
Object.assign( getNeFile(Object.assign({ path: '/tmp', fileName }, from.data))
{ );
path: '/tmp', } else {
fileName: `${from.out}.pcap`, reqArr.push(
}, dumpDownload(Object.assign({ taskCode: taskCode }, from.data))
from.data );
) }
)
);
} }
if (reqArr.length === 0) return;
const hide = message.loading(t('common.loading'), 0);
Promise.allSettled(reqArr) Promise.allSettled(reqArr)
.then(resArr => { .then(resArr => {
resArr.forEach((res, idx) => { resArr.forEach((res, idx) => {
const title = fromArr[idx].title; const title = fromArr[idx].title;
const taskCode = fromArr[idx].taskCode;
if (res.status === 'fulfilled') { if (res.status === 'fulfilled') {
const resV = res.value; const resV = res.value;
@@ -354,9 +437,10 @@ function fnDownPCAP(row?: Record<string, any>) {
duration: 3, duration: 3,
}); });
// 文件名 // 文件名
const fileName = `${fromArr[idx].out}.pcap`; if (taskCode.startsWith('/tmp')) {
if (fileName.length > 6) { saveAs(resV.data, `${title}_${Date.now()}.pcap`);
saveAs(resV.data, fileName); } else {
saveAs(resV.data, `${title}_${Date.now()}.zip`);
} }
} else { } else {
message.warning({ message.warning({
@@ -379,6 +463,24 @@ function fnDownPCAP(row?: Record<string, any>) {
}); });
} }
/**批量操作 */
function fnBatchOper(key: string) {
switch (key) {
case 'start':
fnRecordStart();
break;
case 'stop':
fnRecordStop();
break;
case 'down':
fnDownPCAP();
break;
default:
console.warn('undefined batch oper', key);
break;
}
}
/** /**
* 对话框弹出显示为 查看 * 对话框弹出显示为 查看
* @param dictId 字典编号id * @param dictId 字典编号id
@@ -387,7 +489,7 @@ function fnModalVisibleByVive(id: string | number) {
const from = modalState.from[id]; const from = modalState.from[id];
if (!from) return; if (!from) return;
modalState.visibleByView = true; modalState.visibleByView = true;
modalState.logMsg = from.log; modalState.logMsg = from.logMsg;
} }
/** /**
@@ -411,30 +513,28 @@ onMounted(() => {
<!-- 插槽-卡片左侧侧 --> <!-- 插槽-卡片左侧侧 -->
<template #title> <template #title>
<a-space :size="8" align="center"> <a-space :size="8" align="center">
<a-button <a-dropdown trigger="click">
type="primary" <a-button :disabled="tableState.selectedRowKeys.length <= 0">
:disabled="tableState.selectedRowKeys.length <= 0" {{ t('views.traceManage.pcap.batchOper') }}
@click.prevent="fnRecordStart()" <DownOutlined />
> </a-button>
<template #icon><PlayCircleOutlined /> </template> <template #overlay>
{{ t('views.traceManage.pcap.textStartBatch') }} <a-menu @click="({ key }:any) => fnBatchOper(key)">
</a-button> <a-menu-item key="start">
<a-button <PlayCircleOutlined />
danger {{ t('views.traceManage.pcap.batchStartText') }}
:disabled="tableState.selectedRowKeys.length <= 0" </a-menu-item>
@click.prevent="fnRecordStop()" <a-menu-item key="stop">
> <StopOutlined />
<template #icon><CloseSquareOutlined /> </template> {{ t('views.traceManage.pcap.batchStopText') }}
{{ t('views.traceManage.pcap.textStopBatch') }} </a-menu-item>
</a-button> <a-menu-item key="down">
<a-button <DownloadOutlined />
type="dashed" {{ t('views.traceManage.pcap.batchDownText') }}
:disabled="tableState.selectedRowKeys.length <= 0" </a-menu-item>
@click.prevent="fnDownPCAP()" </a-menu>
> </template>
<template #icon><DownloadOutlined /></template> </a-dropdown>
{{ t('views.traceManage.pcap.textDownBatch') }}
</a-button>
</a-space> </a-space>
</template> </template>
@@ -454,12 +554,12 @@ onMounted(() => {
<a-table <a-table
class="table" class="table"
row-key="id" row-key="id"
size="small" size="defatult"
:columns="tableColumns" :columns="tableColumns"
:loading="tableState.loading" :loading="tableState.loading"
:data-source="tableState.data" :data-source="tableState.data"
:pagination="false" :pagination="false"
:scroll="{ x: tableColumns.length * 120 }" :scroll="{ x: tableColumns.length * 170 }"
:row-selection="{ :row-selection="{
type: 'checkbox', type: 'checkbox',
selectedRowKeys: tableState.selectedRowKeys, selectedRowKeys: tableState.selectedRowKeys,
@@ -484,50 +584,77 @@ onMounted(() => {
</template> </template>
<template v-if="column.key === 'id'"> <template v-if="column.key === 'id'">
<a-space :size="8" align="start" direction="horizontal"> <a-space :size="8" align="start" direction="horizontal">
<a-button <a-tooltip placement="topRight">
type="primary" <template #title>
size="small" <div>{{ t('views.traceManage.pcap.textStart') }}</div>
:loading="modalState.from[record.id].loading" </template>
@click.prevent="fnRecordStart(record)" <a-button
> type="primary"
<template #icon><PlayCircleOutlined /> </template> size="small"
{{ t('views.traceManage.pcap.textStart') }} :disabled="modalState.from[record.id].loading"
</a-button> @click.prevent="fnRecordStart(record)"
>
<a-button <template #icon><PlayCircleOutlined /> </template>
type="default" </a-button>
danger </a-tooltip>
size="small" <a-tooltip
@click.prevent="fnRecordStop(record)" placement="topRight"
>
<template #icon><CloseSquareOutlined /> </template>
{{ t('views.traceManage.pcap.textStop') }}
</a-button>
<a-button
type="primary"
ghost
size="small"
@click.prevent="fnModalVisibleByVive(record.id)"
v-if="modalState.from[record.id].log"
>
<template #icon><FileTextOutlined /> </template>
{{ t('views.traceManage.pcap.textLog') }}
</a-button>
<a-button
type="primary"
ghost
size="small"
@click.prevent="fnDownPCAP(record)"
v-if=" v-if="
!modalState.from[record.id].loading && modalState.from[record.id].loading ||
modalState.from[record.id].out modalState.from[record.id].cmdStop
" "
> >
<template #icon><DownloadOutlined /></template> <template #title>
{{ t('views.traceManage.pcap.textDown') }} <div>{{ t('views.traceManage.pcap.textStop') }}</div>
</a-button> </template>
<a-button
type="default"
danger
size="small"
@click.prevent="fnRecordStop(record)"
>
<template #icon><StopOutlined /> </template>
</a-button>
</a-tooltip>
<a-tooltip
placement="topRight"
v-if="
!modalState.from[record.id].loading &&
!!modalState.from[record.id].logMsg
"
>
<template #title>
<div>{{ t('views.traceManage.pcap.textLog') }}</div>
</template>
<a-button
type="primary"
ghost
size="small"
@click.prevent="fnModalVisibleByVive(record.id)"
>
<template #icon><FileTextOutlined /> </template>
</a-button>
</a-tooltip>
<a-tooltip
placement="topRight"
v-if="
!modalState.from[record.id].loading &&
!!modalState.from[record.id].taskCode
"
>
<template #title>
<div>{{ t('views.traceManage.pcap.textDown') }}</div>
</template>
<a-button
type="primary"
ghost
size="small"
@click.prevent="fnDownPCAP(record)"
>
<template #icon><DownloadOutlined /> </template>
</a-button>
</a-tooltip>
</a-space> </a-space>
</template> </template>
</template> </template>
@@ -542,12 +669,13 @@ onMounted(() => {
:footer="false" :footer="false"
:maskClosable="false" :maskClosable="false"
:keyboard="false" :keyboard="false"
:body-style="{ padding: '12px' }"
:title="t('views.traceManage.pcap.textLogMsg')" :title="t('views.traceManage.pcap.textLogMsg')"
@cancel="fnModalCancel" @cancel="fnModalCancel"
> >
<a-textarea <a-textarea
v-model:value="modalState.logMsg" v-model:value="modalState.logMsg"
:auto-size="{ minRows: 2, maxRows: 24 }" :auto-size="{ minRows: 2, maxRows: 18 }"
:disabled="true" :disabled="true"
style="color: rgba(0, 0, 0, 0.85)" style="color: rgba(0, 0, 0, 0.85)"
/> />