From 22db68646c470b78d99cc4819e9fb8cbed0492ca Mon Sep 17 00:00:00 2001 From: Fine0830 Date: Mon, 12 Jun 2023 16:17:38 +0800 Subject: [PATCH] feat: Implement task timeline and policy list widget for continous profiling (#280) --- src/assets/icons/all_inbox.svg | 4 +- src/assets/icons/continuous_profiling.svg | 17 ++ src/assets/icons/edit.svg | 17 ++ src/assets/icons/settings.svg | 2 +- src/assets/icons/task_timeline.svg | 17 ++ src/components/Selector.vue | 8 +- src/graphql/fragments/ebpf.ts | 28 ++- src/graphql/fragments/profile.ts | 26 ++ src/graphql/query/ebpf.ts | 3 + src/graphql/query/profile.ts | 6 + src/locales/lang/en.ts | 14 ++ src/locales/lang/es.ts | 14 ++ src/locales/lang/zh.ts | 14 ++ src/router/dashboard.ts | 21 ++ src/store/data.ts | 16 +- src/store/modules/continous-profiling.ts | 146 +++++++++++ src/store/modules/dashboard.ts | 1 + src/store/modules/ebpf.ts | 13 +- src/store/modules/network-profiling.ts | 6 + src/store/modules/task-timeline.ts | 131 ++++++++++ src/styles/reset.scss | 3 + src/types/continous-profiling.d.ts | 45 ++++ src/types/ebpf.d.ts | 19 ++ .../configuration/ContinuousProfiling.vue | 108 +++++++++ src/views/dashboard/configuration/index.ts | 2 + .../configuration/widget/metric/Standard.vue | 14 +- .../controls/ContinuousProfiling.vue | 100 ++++++++ src/views/dashboard/controls/TaskTimeline.vue | 92 +++++++ src/views/dashboard/controls/index.ts | 4 + src/views/dashboard/controls/tab.ts | 4 + src/views/dashboard/data.ts | 10 +- src/views/dashboard/graphs/style.scss | 1 + src/views/dashboard/panel/Tool.vue | 39 ++- .../related/continuous-profiling/Content.vue | 51 ++++ .../components/EditPolicy.vue | 134 +++++++++++ .../components/InstanceList.vue | 185 ++++++++++++++ .../components/Policy.vue | 226 ++++++++++++++++++ .../components/PolicyList.vue | 205 ++++++++++++++++ .../related/continuous-profiling/data.ts | 44 ++++ src/views/dashboard/related/ebpf/Header.vue | 2 + .../related/ebpf/components/EBPFSchedules.vue | 13 +- .../related/ebpf/components/EBPFStack.vue | 18 +- .../related/network-profiling/Content.vue | 2 +- .../components/ProcessTopology.vue | 6 +- .../network-profiling/components/Tasks.vue | 2 + .../related/task-timeline/Content.vue | 40 ++++ .../components/ProfilingPanel.vue | 83 +++++++ .../task-timeline/components/Timeline.vue | 177 ++++++++++++++ 48 files changed, 2088 insertions(+), 45 deletions(-) create mode 100644 src/assets/icons/continuous_profiling.svg create mode 100644 src/assets/icons/edit.svg create mode 100644 src/assets/icons/task_timeline.svg create mode 100644 src/store/modules/continous-profiling.ts create mode 100644 src/store/modules/task-timeline.ts create mode 100644 src/types/continous-profiling.d.ts create mode 100644 src/views/dashboard/configuration/ContinuousProfiling.vue create mode 100644 src/views/dashboard/controls/ContinuousProfiling.vue create mode 100644 src/views/dashboard/controls/TaskTimeline.vue create mode 100644 src/views/dashboard/related/continuous-profiling/Content.vue create mode 100644 src/views/dashboard/related/continuous-profiling/components/EditPolicy.vue create mode 100644 src/views/dashboard/related/continuous-profiling/components/InstanceList.vue create mode 100644 src/views/dashboard/related/continuous-profiling/components/Policy.vue create mode 100644 src/views/dashboard/related/continuous-profiling/components/PolicyList.vue create mode 100644 src/views/dashboard/related/continuous-profiling/data.ts create mode 100644 src/views/dashboard/related/task-timeline/Content.vue create mode 100644 src/views/dashboard/related/task-timeline/components/ProfilingPanel.vue create mode 100644 src/views/dashboard/related/task-timeline/components/Timeline.vue diff --git a/src/assets/icons/all_inbox.svg b/src/assets/icons/all_inbox.svg index cf38fc98..07330101 100644 --- a/src/assets/icons/all_inbox.svg +++ b/src/assets/icons/all_inbox.svg @@ -12,6 +12,6 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> - - + + diff --git a/src/assets/icons/continuous_profiling.svg b/src/assets/icons/continuous_profiling.svg new file mode 100644 index 00000000..fec271e6 --- /dev/null +++ b/src/assets/icons/continuous_profiling.svg @@ -0,0 +1,17 @@ + + + + diff --git a/src/assets/icons/edit.svg b/src/assets/icons/edit.svg new file mode 100644 index 00000000..3610dedb --- /dev/null +++ b/src/assets/icons/edit.svg @@ -0,0 +1,17 @@ + + + + diff --git a/src/assets/icons/settings.svg b/src/assets/icons/settings.svg index f394ff29..7b18be5f 100644 --- a/src/assets/icons/settings.svg +++ b/src/assets/icons/settings.svg @@ -12,6 +12,6 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> - + diff --git a/src/assets/icons/task_timeline.svg b/src/assets/icons/task_timeline.svg new file mode 100644 index 00000000..4ea31234 --- /dev/null +++ b/src/assets/icons/task_timeline.svg @@ -0,0 +1,17 @@ + + + + diff --git a/src/components/Selector.vue b/src/components/Selector.vue index 5c2d9ca6..30be63bc 100644 --- a/src/components/Selector.vue +++ b/src/components/Selector.vue @@ -27,7 +27,13 @@ limitations under the License. --> :remote-method="remoteMethod" :filterable="filterable" > - + diff --git a/src/graphql/fragments/ebpf.ts b/src/graphql/fragments/ebpf.ts index b32333f7..252214b0 100644 --- a/src/graphql/fragments/ebpf.ts +++ b/src/graphql/fragments/ebpf.ts @@ -33,9 +33,10 @@ export const createEBPFTask = { }`, }; export const queryEBPFTasks = { - variable: "$serviceId: ID, $serviceInstanceId: ID, $targets: [EBPFProfilingTargetType!]", + variable: + "$serviceId: ID, $serviceInstanceId: ID, $targets: [EBPFProfilingTargetType!], $triggerType: EBPFProfilingTriggerType", query: ` - queryEBPFTasks: queryEBPFProfilingTasks(serviceId: $serviceId, serviceInstanceId: $serviceInstanceId, targets: $targets) { + queryEBPFTasks: queryEBPFProfilingTasks(serviceId: $serviceId, serviceInstanceId: $serviceInstanceId, targets: $targets, triggerType: $triggerType) { taskId serviceName serviceId @@ -111,3 +112,26 @@ export const keepNetworkProfiling = { errorReason }`, }; + +export const monitoringInstances = { + variable: "$serviceId: ID!, $target: ContinuousProfilingTargetType!", + query: ` + instances: queryContinuousProfilingMonitoringInstances(serviceId: $serviceId, target: $target) { + id + name + attributes { + name + value + } + triggeredCount + lastTriggerTimestamp + processes { + id + name + detectType + labels + lastTriggerTimestamp + triggeredCount + } + }`, +}; diff --git a/src/graphql/fragments/profile.ts b/src/graphql/fragments/profile.ts index 01744eea..0e6fdca8 100644 --- a/src/graphql/fragments/profile.ts +++ b/src/graphql/fragments/profile.ts @@ -123,3 +123,29 @@ export const GetProfileTaskLogs = { } `, }; +export const GetStrategyList = { + variable: "$serviceId: ID!", + query: ` + strategyList: queryContinuousProfilingServiceTargets(serviceId: $serviceId) { + type + checkItems { + type + threshold + period + count + uriList + uriRegex + } + } + `, +}; + +export const EditStrategy = { + variable: "$request: ContinuousProfilingPolicyCreation!", + query: ` + strategy: setContinuousProfilingPolicy(request: $request) { + errorReason + status + } + `, +}; diff --git a/src/graphql/query/ebpf.ts b/src/graphql/query/ebpf.ts index 052fc7af..65a3eac0 100644 --- a/src/graphql/query/ebpf.ts +++ b/src/graphql/query/ebpf.ts @@ -23,6 +23,7 @@ import { analysisEBPFResult, createNetworkProfiling, keepNetworkProfiling, + monitoringInstances, } from "../fragments/ebpf"; export const getCreateTaskData = `query queryCreateTaskData(${queryCreateTaskData.variable}) {${queryCreateTaskData.query}}`; @@ -38,3 +39,5 @@ export const getEBPFResult = `query analysisEBPFResult(${analysisEBPFResult.vari export const newNetworkProfiling = `mutation createNetworkProfiling(${createNetworkProfiling.variable}) {${createNetworkProfiling.query}}`; export const aliveNetworkProfiling = `mutation keepNetworkProfiling(${keepNetworkProfiling.variable}) {${keepNetworkProfiling.query}}`; + +export const getMonitoringInstances = `query continuousProfilingMonitoringInstances(${monitoringInstances.variable}) {${monitoringInstances.query}}`; diff --git a/src/graphql/query/profile.ts b/src/graphql/query/profile.ts index 6b762f02..044ffe0f 100644 --- a/src/graphql/query/profile.ts +++ b/src/graphql/query/profile.ts @@ -21,6 +21,8 @@ import { GetProfileTaskSegmentList, GetProfileAnalyze, GetProfileTaskLogs, + GetStrategyList, + EditStrategy, } from "../fragments/profile"; export const saveProfileTask = `mutation createProfileTask(${CreateProfileTask.variable}) {${CreateProfileTask.query}}`; @@ -34,3 +36,7 @@ export const getProfileTaskSegmentList = `query getProfileTaskSegmentList(${GetP export const getProfileAnalyze = `query getProfileAnalyze(${GetProfileAnalyze.variable}) {${GetProfileAnalyze.query}}`; export const getProfileTaskLogs = `query profileTaskLogs(${GetProfileTaskLogs.variable}) {${GetProfileTaskLogs.query}}`; + +export const getStrategyList = `query getStrategyList(${GetStrategyList.variable}) {${GetStrategyList.query}}`; + +export const editStrategy = `mutation editStrategy(${EditStrategy.variable}) {${EditStrategy.query}}`; diff --git a/src/locales/lang/en.ts b/src/locales/lang/en.ts index 494853dc..6bc4cacf 100644 --- a/src/locales/lang/en.ts +++ b/src/locales/lang/en.ts @@ -323,6 +323,7 @@ const msg = { keywordsOfContentLogTips: "Current storage of SkyWalking OAP server does not support this.", setEvent: "Set Event", viewAttributes: "View", + attributes: "Attributes", serviceEvents: "Service Events", select: "Select", eventID: "Event ID", @@ -388,6 +389,19 @@ const msg = { elasticsearch: "Elasticsearch", mq: "MQ", rabbitMQ: "RabbitMQ", + save: "Save", + editStrategy: "Edit Policies", + policyList: "Policy List", + targetTypes: "Target Type", + monitorType: "Monitor Type", + count: "Count", + threshold: "Threshold", + uriRegex: "URI Regex", + uriList: "URI List", + processes: "Processes", + monitorInstances: "Monitor Instances", + processDashboards: "Process Dashboards", + instanceDashboards: "Instance Dashboards", detailLabel: "Detail Label", summary: "Summary", detail: "Detail", diff --git a/src/locales/lang/es.ts b/src/locales/lang/es.ts index 26740948..2f5637e9 100644 --- a/src/locales/lang/es.ts +++ b/src/locales/lang/es.ts @@ -387,6 +387,20 @@ const msg = { elasticsearch: "Elasticsearch", mq: "MQ", rabbitMQ: "RabbitMQ", + save: "Salvar", + editStrategy: "Estrategia editorial", + policyList: "Lista de políticas", + targetTypes: "Tipo de objetivo", + monitorType: "Tipo de Monitor", + count: "Contar", + threshold: "Umbral", + uriRegex: "Lista URI", + uriList: "Lista URI", + processes: "Proceso", + attributes: "Atributos", + monitorInstances: "Ejemplo de Monitor", + processDashboards: "Tablero de proceso", + instanceDashboards: "Tablero de ejemplo", detailLabel: "Detail Label", summary: "Summary", detail: "Detail", diff --git a/src/locales/lang/zh.ts b/src/locales/lang/zh.ts index 53d64576..4bf623b8 100644 --- a/src/locales/lang/zh.ts +++ b/src/locales/lang/zh.ts @@ -385,6 +385,20 @@ const msg = { elasticsearch: "Elasticsearch", mq: "消息队列", rabbitMQ: "RabbitMQ", + save: "保存", + editStrategy: "编辑策略", + policyList: "策略列表", + targetTypes: "目标类型", + monitorType: "监视器类型", + count: "总数", + threshold: "阈值", + uriRegex: "URI规则", + uriList: "URI列表", + processes: "进程", + attributes: "属性", + monitorInstances: "监视实例", + processDashboards: "进程仪表板", + instanceDashboards: "实例仪表板", detailLabel: "详细标签", summary: "概括", detail: "详细", diff --git a/src/router/dashboard.ts b/src/router/dashboard.ts index f4f799ce..32be6261 100644 --- a/src/router/dashboard.ts +++ b/src/router/dashboard.ts @@ -128,6 +128,27 @@ export const routesDashboard: Array = [ }, ], }, + { + path: "", + redirect: "/dashboard/:layerId/:entity/:serviceId/:podId/:processId/:name", + component: () => import("@/views/dashboard/Edit.vue"), + name: "ViewProcess", + meta: { + notShow: true, + }, + children: [ + { + path: "/dashboard/:layerId/:entity/:serviceId/:podId/:processId/:name", + component: () => import("@/views/dashboard/Edit.vue"), + name: "ViewProcess", + }, + { + path: "/dashboard/:layerId/:entity/:serviceId/:podId/:processId/:name/tab/:activeTabIndex", + component: () => import("@/views/dashboard/Edit.vue"), + name: "ViewProcessActiveTabIndex", + }, + ], + }, { path: "", redirect: "/dashboard/:layerId/:entity/:serviceId/:podId/:destServiceId/:destPodId/:name", diff --git a/src/store/data.ts b/src/store/data.ts index 04a51f52..43d0d4d2 100644 --- a/src/store/data.ts +++ b/src/store/data.ts @@ -38,4 +38,18 @@ export const TimeRangeConfig = { text: "text", }; -export const ControlsTypes = ["Trace", "Profile", "Log", "DemandLog", "Ebpf", "NetworkProfiling", "ThirdPartyApp"]; +export const ControlsTypes = [ + "Trace", + "Profile", + "Log", + "DemandLog", + "Ebpf", + "NetworkProfiling", + "ThirdPartyApp", + "ContinuousProfiling", + "TaskTimeline", +]; +export enum EBPFProfilingTriggerType { + FIXED_TIME = "FIXED_TIME", + CONTINUOUS_PROFILING = "CONTINUOUS_PROFILING", +} diff --git a/src/store/modules/continous-profiling.ts b/src/store/modules/continous-profiling.ts new file mode 100644 index 00000000..12182fff --- /dev/null +++ b/src/store/modules/continous-profiling.ts @@ -0,0 +1,146 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { defineStore } from "pinia"; +import type { StrategyItem, CheckItems } from "@/types/continous-profiling"; +import type { EBPFTaskList, EBPFProfilingSchedule, AnalyzationTrees } from "@/types/ebpf"; +import type { Instance } from "@/types/selector"; +import { store } from "@/store"; +import graphql from "@/graphql"; +import type { AxiosResponse } from "axios"; + +interface ContinousProfilingState { + strategyList: Array>; + selectedStrategy: Recordable; + taskList: Array>; + selectedTask: Recordable; + errorTip: string; + errorReason: string; + instances: Instance[]; + instance: Nullable; + eBPFSchedules: EBPFProfilingSchedule[]; + currentSchedule: EBPFProfilingSchedule | Record; + analyzeTrees: AnalyzationTrees[]; + ebpfTips: string; + aggregateType: string; + instancesLoading: boolean; + policyLoading: boolean; +} + +export const continousProfilingStore = defineStore({ + id: "continousProfiling", + state: (): ContinousProfilingState => ({ + strategyList: [], + selectedStrategy: {}, + taskList: [], + selectedTask: {}, + errorReason: "", + errorTip: "", + ebpfTips: "", + instances: [], + eBPFSchedules: [], + currentSchedule: {}, + analyzeTrees: [], + aggregateType: "COUNT", + instance: null, + instancesLoading: false, + policyLoading: false, + }), + actions: { + setSelectedStrategy(task: Recordable) { + this.selectedStrategy = task || {}; + }, + setselectedTask(task: Recordable) { + this.selectedTask = task || {}; + }, + setCurrentSchedule(s: EBPFProfilingSchedule) { + this.currentSchedule = s; + }, + setAnalyzeTrees(tree: AnalyzationTrees[]) { + this.analyzeTrees = tree; + }, + setCurrentInstance(instance: Nullable) { + this.instance = instance; + }, + async setContinuousProfilingPolicy( + serviceId: string, + targets: { + targetType: string; + checkItems: CheckItems[]; + }[], + ) { + const res: AxiosResponse = await graphql.query("editStrategy").params({ + request: { + serviceId, + targets, + }, + }); + + if (res.data.errors) { + return res.data; + } + + return res.data; + }, + async getStrategyList(params: { serviceId: string }) { + if (!params.serviceId) { + return new Promise((resolve) => resolve({})); + } + this.policyLoading = true; + const res: AxiosResponse = await graphql.query("getStrategyList").params(params); + + this.policyLoading = false; + if (res.data.errors) { + return res.data; + } + this.strategyList = (res.data.data.strategyList || []).map((d: StrategyItem, index: number) => { + return { + ...d, + id: index, + }; + }); + this.setSelectedStrategy(this.strategyList[0] || {}); + if (!this.strategyList.length) { + this.taskList = []; + } + if (!this.selectedStrategy.type) { + return res.data; + } + this.getMonitoringInstances(params.serviceId); + return res.data; + }, + async getMonitoringInstances(serviceId: string): Promise> { + this.instancesLoading = true; + if (!serviceId) { + return null; + } + const res: AxiosResponse = await graphql.query("getMonitoringInstances").params({ + serviceId, + target: this.selectedStrategy.type, + }); + this.instancesLoading = false; + if (!res.data.errors) { + this.instances = res.data.data.instances || []; + this.instance = this.instances[0] || null; + } + return res.data; + }, + }, +}); + +export function useContinousProfilingStore(): Recordable { + return continousProfilingStore(store); +} diff --git a/src/store/modules/dashboard.ts b/src/store/modules/dashboard.ts index c1bc22f9..c2c32da5 100644 --- a/src/store/modules/dashboard.ts +++ b/src/store/modules/dashboard.ts @@ -92,6 +92,7 @@ export const dashboardStore = defineStore({ metricTypes: [""], metrics: [""], }; + if (type === "Widget") { newItem.metricMode = MetricModes.Expression; } diff --git a/src/store/modules/ebpf.ts b/src/store/modules/ebpf.ts index 7bc26a46..69f90ecb 100644 --- a/src/store/modules/ebpf.ts +++ b/src/store/modules/ebpf.ts @@ -20,6 +20,7 @@ import type { EBPFTaskCreationRequest, EBPFProfilingSchedule, EBPFTaskList, Anal import { store } from "@/store"; import graphql from "@/graphql"; import type { AxiosResponse } from "axios"; +import { EBPFProfilingTriggerType } from "../data"; interface EbpfState { taskList: Array>; eBPFSchedules: EBPFProfilingSchedule[]; @@ -27,7 +28,7 @@ interface EbpfState { analyzeTrees: AnalyzationTrees[]; labels: Option[]; couldProfiling: boolean; - tip: string; + ebpfTips: string; selectedTask: Recordable; aggregateType: string; } @@ -41,7 +42,7 @@ export const ebpfStore = defineStore({ analyzeTrees: [], labels: [{ value: "", label: "" }], couldProfiling: false, - tip: "", + ebpfTips: "", selectedTask: {}, aggregateType: "COUNT", }), @@ -77,6 +78,7 @@ export const ebpfStore = defineStore({ this.getTaskList({ serviceId: param.serviceId, targets: ["ON_CPU", "OFF_CPU"], + triggerType: EBPFProfilingTriggerType.FIXED_TIME, }); return res.data; }, @@ -86,7 +88,7 @@ export const ebpfStore = defineStore({ } const res: AxiosResponse = await graphql.query("getEBPFTasks").params(params); - this.tip = ""; + this.ebpfTips = ""; if (res.data.errors) { return res.data; } @@ -103,13 +105,14 @@ export const ebpfStore = defineStore({ if (!params.taskId) { return new Promise((resolve) => resolve({})); } + const res: AxiosResponse = await graphql.query("getEBPFSchedules").params({ ...params }); if (res.data.errors) { this.eBPFSchedules = []; return res.data; } - this.tip = ""; + this.ebpfTips = ""; const { eBPFSchedules } = res.data.data; this.eBPFSchedules = eBPFSchedules; @@ -138,7 +141,7 @@ export const ebpfStore = defineStore({ return res.data; } const { analysisEBPFResult } = res.data.data; - this.tip = analysisEBPFResult.tip; + this.ebpfTips = analysisEBPFResult.tip; if (!analysisEBPFResult) { this.analyzeTrees = []; return res.data; diff --git a/src/store/modules/network-profiling.ts b/src/store/modules/network-profiling.ts index 071bfa57..5a6d6ce8 100644 --- a/src/store/modules/network-profiling.ts +++ b/src/store/modules/network-profiling.ts @@ -65,6 +65,12 @@ export const networkProfilingStore = defineStore({ setLink(link: Call) { this.call = link; }, + seNodes(nodes: Node[]) { + this.nodes = nodes; + }, + setLinks(links: Call[]) { + this.calls = links; + }, setMetricsLayout(layout: LayoutConfig[]) { this.metricsLayout = layout; }, diff --git a/src/store/modules/task-timeline.ts b/src/store/modules/task-timeline.ts new file mode 100644 index 00000000..14272164 --- /dev/null +++ b/src/store/modules/task-timeline.ts @@ -0,0 +1,131 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { defineStore } from "pinia"; +import { ElMessage } from "element-plus"; +import { store } from "@/store"; +import graphql from "@/graphql"; +import type { AxiosResponse } from "axios"; +import { useAppStoreWithOut } from "@/store/modules/app"; +import type { EBPFTaskList } from "@/types/ebpf"; +import { useNetworkProfilingStore } from "@/store/modules/network-profiling"; +import { useSelectorStore } from "@/store/modules/selectors"; +import { useEbpfStore } from "@/store/modules/ebpf"; +import dateFormatStep from "@/utils/dateFormat"; +import getLocalTime from "@/utils/localtime"; +import { TargetTypes } from "@/views/dashboard/related/continuous-profiling/data"; +interface taskTimelineState { + loading: boolean; + taskList: EBPFTaskList[]; + selectedTask: Recordable; +} + +export const taskTimelineStore = defineStore({ + id: "taskTimeline", + state: (): taskTimelineState => ({ + loading: false, + taskList: [], + selectedTask: {}, + }), + actions: { + setSelectedTask(task: Recordable) { + this.selectedTask = task || {}; + }, + setTaskList(list: EBPFTaskList[]) { + this.taskList = list; + }, + async getContinousTaskList(params: { + serviceId: string; + serviceInstanceId: string; + targets: string[]; + triggerType: string; + }) { + if (!params.serviceId) { + return new Promise((resolve) => resolve({})); + } + this.loading = true; + const res: AxiosResponse = await graphql.query("getEBPFTasks").params(params); + + this.loading = false; + this.errorTip = ""; + if (res.data.errors) { + return res.data; + } + this.taskList = res.data.data.queryEBPFTasks || []; + // this.selectedTask = this.taskList[0] || {}; + // await this.getGraphData(); + return res.data; + }, + async getGraphData() { + let res: any = {}; + + if (this.selectedTask.targetType === TargetTypes[2].value) { + res = await this.getTopology(); + } else { + const ebpfStore = useEbpfStore(); + res = await ebpfStore.getEBPFSchedules({ + taskId: this.selectedTask.taskId, + }); + } + + if (res.errors) { + ElMessage.error(res.errors); + } + }, + async getTopology() { + const networkProfilingStore = useNetworkProfilingStore(); + const appStore = useAppStoreWithOut(); + const selectorStore = useSelectorStore(); + networkProfilingStore.setSelectedNetworkTask(this.selectedTask); + const { taskStartTime, fixedTriggerDuration } = this.selectedTask; + const startTime = + fixedTriggerDuration > 1800 ? taskStartTime + fixedTriggerDuration * 1000 - 30 * 60 * 1000 : taskStartTime; + let endTime = taskStartTime + fixedTriggerDuration * 1000; + if (taskStartTime + fixedTriggerDuration * 1000 > new Date().getTime()) { + endTime = new Date().getTime(); + } + const resp = await networkProfilingStore.getProcessTopology({ + serviceInstanceId: (selectorStore.currentPod || {}).id || "", + duration: { + start: dateFormatStep(getLocalTime(appStore.utc, new Date(startTime)), appStore.duration.step, true), + end: dateFormatStep(getLocalTime(appStore.utc, new Date(endTime)), appStore.duration.step, true), + step: appStore.duration.step, + }, + }); + if (resp.errors) { + ElMessage.error(resp.errors); + } + return resp; + }, + async preAnalyzeTask() { + if (this.selectedStrategy.type === "NETWORK") { + const networkProfilingStore = useNetworkProfilingStore(); + await networkProfilingStore.setSelectedNetworkTask(this.selectedTask); + return; + } + const res = await this.getEBPFSchedules({ + taskId: this.selectedTask.taskId, + }); + if (res.errors) { + ElMessage.error(res.errors); + } + }, + }, +}); + +export function useTaskTimelineStore(): Recordable { + return taskTimelineStore(store); +} diff --git a/src/styles/reset.scss b/src/styles/reset.scss index 7187b779..f483bdc4 100644 --- a/src/styles/reset.scss +++ b/src/styles/reset.scss @@ -212,3 +212,6 @@ div.vis-tooltip { div:has(> a.menu-title) { display: none; } +.el-input-number .el-input__inner { + text-align: left !important; +} diff --git a/src/types/continous-profiling.d.ts b/src/types/continous-profiling.d.ts new file mode 100644 index 00000000..98c5b121 --- /dev/null +++ b/src/types/continous-profiling.d.ts @@ -0,0 +1,45 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface StrategyItem { + type: string; + checkItems: CheckItems[]; +} +export type CheckItems = { + type: string; + threshold: string; + period: number; + count: number; + uriList?: string[]; + uriRegex?: string; +}; +export interface MonitorInstance { + id: string; + name: string; + attributes: { name: string; value: string }[]; + triggeredCount: number; + lastTriggerTimestamp: number; + processes: MonitorProcess[]; +} +interface MonitorProcess { + id: string; + name: string; + detectType: string; + labels: string[]; + lastTriggerTimestamp: number; + triggeredCount: number; +} diff --git a/src/types/ebpf.d.ts b/src/types/ebpf.d.ts index 9a4e524a..36ed2082 100644 --- a/src/types/ebpf.d.ts +++ b/src/types/ebpf.d.ts @@ -28,12 +28,31 @@ export interface EBPFTaskList { taskId: string; serviceName: string; serviceId: string; + serviceInstanceId: string; + serviceInstanceName: string; + processId: string; + processName: string; processLabels: string[]; taskStartTime: number; fixedTriggerDuration: number; targetType: string; createTime: number; triggerType: string; + continuousProfilingCauses: ProfilingCause[]; +} + +interface ProfilingCause { + type: string; + singleValue: { + threshold: number; + current: number; + }; + uri: { + uriRegex: string; + uriPath: string; + threshold: number; + current: number; + }; } export interface EBPFProfilingSchedule { diff --git a/src/views/dashboard/configuration/ContinuousProfiling.vue b/src/views/dashboard/configuration/ContinuousProfiling.vue new file mode 100644 index 00000000..77b77846 --- /dev/null +++ b/src/views/dashboard/configuration/ContinuousProfiling.vue @@ -0,0 +1,108 @@ + +
+
{{ t("instanceDashboards") }}
+ +
+
+
{{ t("processDashboards") }}
+ +
+ + + + diff --git a/src/views/dashboard/configuration/index.ts b/src/views/dashboard/configuration/index.ts index 1fb47e4f..aadb8f33 100644 --- a/src/views/dashboard/configuration/index.ts +++ b/src/views/dashboard/configuration/index.ts @@ -21,6 +21,7 @@ import Topology from "./Topology.vue"; import Event from "./Event.vue"; import TimeRange from "./TimeRange.vue"; import ThirdPartyApp from "./ThirdPartyApp.vue"; +import ContinuousProfiling from "./ContinuousProfiling.vue"; export default { Text, @@ -29,4 +30,5 @@ export default { Event, TimeRange, ThirdPartyApp, + ContinuousProfiling, }; diff --git a/src/views/dashboard/configuration/widget/metric/Standard.vue b/src/views/dashboard/configuration/widget/metric/Standard.vue index 5026b972..4e7ad7ec 100644 --- a/src/views/dashboard/configuration/widget/metric/Standard.vue +++ b/src/views/dashboard/configuration/widget/metric/Standard.vue @@ -28,7 +28,7 @@ limitations under the License. --> " /> -
+
{{ t("labels") }} " />
-
+
{{ t("aggregation") }} import { SortOrder, CalculationOpts, MetricModes } from "../../../data"; import { useDashboardStore } from "@/store/modules/dashboard"; import type { MetricConfigOpt } from "@/types/dashboard"; - import { ListChartTypes, ProtocolTypes, ExpressionResultType } from "../../../data"; + import { ListChartTypes, ProtocolTypes } from "../../../data"; /*global defineEmits, defineProps */ const props = defineProps({ @@ -137,11 +137,7 @@ limitations under the License. --> const graph = dashboardStore.selectedGrid.graph || {}; return ( ListChartTypes.includes(graph.type) || - [ - ProtocolTypes.ReadLabeledMetricsValues, - ProtocolTypes.ReadMetricsValues, - ExpressionResultType.TIME_SERIES_VALUES, - ].includes(metricType.value) + [ProtocolTypes.ReadLabeledMetricsValues, ProtocolTypes.ReadMetricsValues].includes(metricType.value) ); }); const isList = computed(() => { @@ -153,7 +149,7 @@ limitations under the License. --> metricTypes.value[props.index], ), ); - const isExec = computed(() => dashboardStore.selectedGrid.metricMode === MetricModes.General); + function updateConfig(index: number, param: { [key: string]: string }) { const key = Object.keys(param)[0]; if (!key) { diff --git a/src/views/dashboard/controls/ContinuousProfiling.vue b/src/views/dashboard/controls/ContinuousProfiling.vue new file mode 100644 index 00000000..ee1c1650 --- /dev/null +++ b/src/views/dashboard/controls/ContinuousProfiling.vue @@ -0,0 +1,100 @@ + + + + diff --git a/src/views/dashboard/controls/TaskTimeline.vue b/src/views/dashboard/controls/TaskTimeline.vue new file mode 100644 index 00000000..4651abe6 --- /dev/null +++ b/src/views/dashboard/controls/TaskTimeline.vue @@ -0,0 +1,92 @@ + + + + diff --git a/src/views/dashboard/controls/index.ts b/src/views/dashboard/controls/index.ts index 6b4c5fde..01329d33 100644 --- a/src/views/dashboard/controls/index.ts +++ b/src/views/dashboard/controls/index.ts @@ -25,8 +25,10 @@ import Ebpf from "./Ebpf.vue"; import DemandLog from "./DemandLog.vue"; import Event from "./Event.vue"; import NetworkProfiling from "./NetworkProfiling.vue"; +import ContinuousProfiling from "./ContinuousProfiling.vue"; import TimeRange from "./TimeRange.vue"; import ThirdPartyApp from "./ThirdPartyApp.vue"; +import TaskTimeline from "./TaskTimeline.vue"; export default { Tab, @@ -40,6 +42,8 @@ export default { DemandLog, Event, NetworkProfiling, + ContinuousProfiling, TimeRange, ThirdPartyApp, + TaskTimeline, }; diff --git a/src/views/dashboard/controls/tab.ts b/src/views/dashboard/controls/tab.ts index b6194609..008c8081 100644 --- a/src/views/dashboard/controls/tab.ts +++ b/src/views/dashboard/controls/tab.ts @@ -24,8 +24,10 @@ import Ebpf from "./Ebpf.vue"; import DemandLog from "./DemandLog.vue"; import Event from "./Event.vue"; import NetworkProfiling from "./NetworkProfiling.vue"; +import ContinuousProfiling from "./ContinuousProfiling.vue"; import TimeRange from "./TimeRange.vue"; import ThirdPartyApp from "./ThirdPartyApp.vue"; +import TaskTimeline from "./TaskTimeline.vue"; export default { Widget, @@ -40,4 +42,6 @@ export default { NetworkProfiling, TimeRange, ThirdPartyApp, + ContinuousProfiling, + TaskTimeline, }; diff --git a/src/views/dashboard/data.ts b/src/views/dashboard/data.ts index 5abd837f..28c0affb 100644 --- a/src/views/dashboard/data.ts +++ b/src/views/dashboard/data.ts @@ -176,6 +176,7 @@ export const EntityType = [ }, { value: "EndpointRelation", label: "Endpoint Relation", key: 4 }, { value: "ProcessRelation", label: "Process Relation", key: 5 }, + { value: "Process", label: "Process", key: 6 }, ]; export const ListEntity: any = { InstanceList: EntityType[3].value, @@ -203,6 +204,7 @@ export const ServiceTools = [ { name: "merge", content: "Add Trace", id: "addTrace" }, { name: "timeline", content: "Add Trace Profiling", id: "addProfile" }, { name: "insert_chart", content: "Add eBPF Profiling", id: "addEbpf" }, + { name: "continuous_profiling", content: "Add Continuous Profiling", id: "addContinuousProfiling" }, { name: "assignment", content: "Add Log", id: "addLog" }, { name: "demand", content: "Add On Demand Log", id: "addDemandLog" }, { name: "event", content: "Add Event", id: "addEvent" }, @@ -234,10 +236,16 @@ export const EndpointTools = [ { name: "add_iframe", content: "Add Iframe", id: "addIframe" }, ]; export const ProcessTools = [ + { name: "playlist_add", content: "Add Widget", id: "addWidget" }, + { name: "all_inbox", content: "Add Tabs", id: "addTab" }, + { name: "task_timeline", content: "Add Task Timeline", id: "addTaskTimeline" }, + { name: "library_books", content: "Add Text", id: "addText" }, + { name: "add_iframe", content: "Add Iframe", id: "addIframe" }, +]; +export const ProcessRelationTools = [ { name: "playlist_add", content: "Add Widget", id: "addWidget" }, { name: "all_inbox", content: "Add Tabs", id: "addTab" }, { name: "library_books", content: "Add Text", id: "addText" }, - { name: "time_range", content: "Add Time Range Text", id: "addTimeRange" }, { name: "add_iframe", content: "Add Iframe", id: "addIframe" }, ]; export const ServiceRelationTools = [ diff --git a/src/views/dashboard/graphs/style.scss b/src/views/dashboard/graphs/style.scss index 0ce4ac56..656da757 100644 --- a/src/views/dashboard/graphs/style.scss +++ b/src/views/dashboard/graphs/style.scss @@ -38,6 +38,7 @@ color: #409eff; display: inline-block; width: 100%; + text-decoration: underline; } .search { diff --git a/src/views/dashboard/panel/Tool.vue b/src/views/dashboard/panel/Tool.vue index ce4d474a..e11efff0 100644 --- a/src/views/dashboard/panel/Tool.vue +++ b/src/views/dashboard/panel/Tool.vue @@ -27,7 +27,7 @@ limitations under the License. --> class="selectors" />
-
+
{{ ["EndpointRelation", "Endpoint"].includes(dashboardStore.entity) ? "$Endpoint" : "$ServiceInstance" }} @@ -42,7 +42,7 @@ limitations under the License. --> :isRemote="['EndpointRelation', 'Endpoint'].includes(dashboardStore.entity)" />
-
+
$Process InstanceRelationTools, ServiceRelationTools, ProcessTools, + ProcessRelationTools, } from "../data"; import { useSelectorStore } from "@/store/modules/selectors"; import { ElMessage } from "element-plus"; @@ -216,10 +217,11 @@ limitations under the License. --> EntityType[5].value, EntityType[6].value, EntityType[7].value, + EntityType[8].value, ].includes(String(params.entity)) ) { setSourceSelector(); - if ([EntityType[2].value, EntityType[3].value].includes(String(params.entity))) { + if ([EntityType[2].value, EntityType[3].value, EntityType[8].value].includes(String(params.entity))) { return; } setDestSelector(); @@ -317,6 +319,7 @@ limitations under the License. --> EntityType[5].value, EntityType[6].value, EntityType[7].value, + EntityType[8].value, ].includes(dashboardStore.entity) ) { await fetchPods(e, selectorStore.currentService.id, true); @@ -337,11 +340,8 @@ limitations under the License. --> selectorStore.setCurrentPod(null); states.currentPod = ""; states.currentProcess = ""; - if (dashboardStore.entity === EntityType[7].value) { - fetchPods("Process", selectorStore.currentService.id, true); - } else { - fetchPods(dashboardStore.entity, selectorStore.currentService.id, true); - } + const e = dashboardStore.entity === EntityType[7].value ? EntityType[8].value : dashboardStore.entity; + fetchPods(e, selectorStore.currentService.id, true); } else { selectorStore.setCurrentService(null); } @@ -362,7 +362,7 @@ limitations under the License. --> async function changePods(pod: Option[]) { selectorStore.setCurrentPod(pod[0] || null); - if (dashboardStore.entity === EntityType[7].value) { + if ([EntityType[7].value, EntityType[8].value].includes(dashboardStore.entity)) { selectorStore.setCurrentProcess(null); states.currentProcess = ""; fetchProcess(true); @@ -446,12 +446,18 @@ limitations under the License. --> case "addNetworkProfiling": dashboardStore.addTabControls("NetworkProfiling"); break; + case "addContinuousProfiling": + dashboardStore.addTabControls("ContinuousProfiling"); + break; case "addTimeRange": dashboardStore.addTabControls("TimeRange"); break; case "addIframe": dashboardStore.addTabControls("ThirdPartyApp"); break; + case "addTaskTimeline": + dashboardStore.addTabControls("TaskTimeline"); + break; default: ElMessage.info("Don't support this control"); break; @@ -493,12 +499,18 @@ limitations under the License. --> case "addNetworkProfiling": dashboardStore.addControl("NetworkProfiling"); break; + case "addContinuousProfiling": + dashboardStore.addControl("ContinuousProfiling"); + break; case "addTimeRange": dashboardStore.addControl("TimeRange"); break; case "addIframe": dashboardStore.addControl("ThirdPartyApp"); break; + case "addTaskTimeline": + dashboardStore.addControl("TaskTimeline"); + break; default: dashboardStore.addControl("Widget"); } @@ -560,7 +572,7 @@ limitations under the License. --> await fetchPods(EntityType[5].value, serviceId, setPod, param); resp = await fetchDestProcess(setPod); break; - case "Process": + case EntityType[8].value: await fetchPods(EntityType[3].value, serviceId, setPod, param); resp = await fetchProcess(setPod); break; @@ -691,6 +703,9 @@ limitations under the License. --> toolIcons.value = EndpointRelationTools; break; case EntityType[7].value: + toolIcons.value = ProcessRelationTools; + break; + case EntityType[8].value: toolIcons.value = ProcessTools; break; default: @@ -722,8 +737,8 @@ limitations under the License. --> diff --git a/src/views/dashboard/related/continuous-profiling/components/EditPolicy.vue b/src/views/dashboard/related/continuous-profiling/components/EditPolicy.vue new file mode 100644 index 00000000..59cc6922 --- /dev/null +++ b/src/views/dashboard/related/continuous-profiling/components/EditPolicy.vue @@ -0,0 +1,134 @@ + + + + + diff --git a/src/views/dashboard/related/continuous-profiling/components/InstanceList.vue b/src/views/dashboard/related/continuous-profiling/components/InstanceList.vue new file mode 100644 index 00000000..e059b996 --- /dev/null +++ b/src/views/dashboard/related/continuous-profiling/components/InstanceList.vue @@ -0,0 +1,185 @@ + + + + diff --git a/src/views/dashboard/related/continuous-profiling/components/Policy.vue b/src/views/dashboard/related/continuous-profiling/components/Policy.vue new file mode 100644 index 00000000..8e315dbe --- /dev/null +++ b/src/views/dashboard/related/continuous-profiling/components/Policy.vue @@ -0,0 +1,226 @@ + + + + diff --git a/src/views/dashboard/related/continuous-profiling/components/PolicyList.vue b/src/views/dashboard/related/continuous-profiling/components/PolicyList.vue new file mode 100644 index 00000000..314a1990 --- /dev/null +++ b/src/views/dashboard/related/continuous-profiling/components/PolicyList.vue @@ -0,0 +1,205 @@ + + + + diff --git a/src/views/dashboard/related/continuous-profiling/data.ts b/src/views/dashboard/related/continuous-profiling/data.ts new file mode 100644 index 00000000..fb9d0c5c --- /dev/null +++ b/src/views/dashboard/related/continuous-profiling/data.ts @@ -0,0 +1,44 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const MonitorType: any = [ + { label: "PROCESS_CPU", value: "PROCESS_CPU" }, + { label: "PROCESS_THREAD_COUNT", value: "PROCESS_THREAD_COUNT" }, + { label: "SYSTEM_LOAD", value: "SYSTEM_LOAD" }, + { label: "HTTP_ERROR_RATE", value: "HTTP_ERROR_RATE" }, + { label: "HTTP_AVG_RESPONSE_TIME", value: "HTTP_AVG_RESPONSE_TIME" }, +]; + +export const TargetTypes = [ + { label: "ON_CPU", value: "ON_CPU" }, + { label: "OFF_CPU", value: "OFF_CPU" }, + { label: "NETWORK", value: "NETWORK" }, +]; + +export const ComponentType = "CONTINOUS_PROFILING"; + +export const HeaderLabels = [ + { value: "triggeredCount", label: "Triggered Count", width: 150 }, + { value: "lastTriggerTime", label: "Last Trigger Time", width: 170 }, +]; + +export const HeaderChildLabels = [ + { value: "detectType", label: "Detect Type", width: 100 }, + { value: "triggeredCount", label: "Triggered Count", width: 120 }, + { value: "lastTriggerTime", label: "Last Trigger Time", width: 160 }, + { value: "labels", label: "Labels" }, +]; diff --git a/src/views/dashboard/related/ebpf/Header.vue b/src/views/dashboard/related/ebpf/Header.vue index 52dd6701..989feb7f 100644 --- a/src/views/dashboard/related/ebpf/Header.vue +++ b/src/views/dashboard/related/ebpf/Header.vue @@ -33,6 +33,7 @@ limitations under the License. --> import { useDashboardStore } from "@/store/modules/dashboard"; import { useAppStoreWithOut } from "@/store/modules/app"; import { EntityType } from "../../data"; + import { EBPFProfilingTriggerType } from "@/store/data"; /*global defineProps */ const props = defineProps({ @@ -54,6 +55,7 @@ limitations under the License. --> const res = await ebpfStore.getTaskList({ serviceId, targets: ["ON_CPU", "OFF_CPU"], + triggerType: EBPFProfilingTriggerType.FIXED_TIME, }); if (res.errors) { diff --git a/src/views/dashboard/related/ebpf/components/EBPFSchedules.vue b/src/views/dashboard/related/ebpf/components/EBPFSchedules.vue index d722fddf..bc199117 100644 --- a/src/views/dashboard/related/ebpf/components/EBPFSchedules.vue +++ b/src/views/dashboard/related/ebpf/components/EBPFSchedules.vue @@ -42,7 +42,7 @@ limitations under the License. --> /> @@ -98,12 +98,21 @@ limitations under the License. --> import type { Option } from "@/types/app"; import { TableHeader, AggregateTypes } from "./data"; import { useEbpfStore } from "@/store/modules/ebpf"; + import { useContinousProfilingStore } from "@/store/modules/continous-profiling"; import type { EBPFProfilingSchedule, Process } from "@/types/ebpf"; import { ElMessage, ElTable } from "element-plus"; import { dateFormat } from "@/utils/dateFormat"; + import { ComponentType } from "@/views/dashboard/related/continuous-profiling/data"; const { t } = useI18n(); - const ebpfStore = useEbpfStore(); + /*global defineProps*/ + const props = defineProps({ + type: { + type: String, + default: "", + }, + }); + const ebpfStore = props.type === ComponentType ? useContinousProfilingStore() : useEbpfStore(); const pageSize = 5; const multipleTableRef = ref>(); const selectedProcesses = ref([]); diff --git a/src/views/dashboard/related/ebpf/components/EBPFStack.vue b/src/views/dashboard/related/ebpf/components/EBPFStack.vue index 0d5f3db1..988f2193 100644 --- a/src/views/dashboard/related/ebpf/components/EBPFStack.vue +++ b/src/views/dashboard/related/ebpf/components/EBPFStack.vue @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. --> + diff --git a/src/views/dashboard/related/task-timeline/components/ProfilingPanel.vue b/src/views/dashboard/related/task-timeline/components/ProfilingPanel.vue new file mode 100644 index 00000000..cf2bc4a3 --- /dev/null +++ b/src/views/dashboard/related/task-timeline/components/ProfilingPanel.vue @@ -0,0 +1,83 @@ + + + + diff --git a/src/views/dashboard/related/task-timeline/components/Timeline.vue b/src/views/dashboard/related/task-timeline/components/Timeline.vue new file mode 100644 index 00000000..bc8afcad --- /dev/null +++ b/src/views/dashboard/related/task-timeline/components/Timeline.vue @@ -0,0 +1,177 @@ + + + +