This is an automated email from the ASF dual-hosted git repository.
wusheng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-booster-ui.git
The following commit(s) were added to refs/heads/main by this push:
new 22db6864 feat: Implement task timeline and policy list widget for
continous profiling (#280)
22db6864 is described below
commit 22db68646c470b78d99cc4819e9fb8cbed0492ca
Author: Fine0830 <[email protected]>
AuthorDate: Mon Jun 12 16:17:38 2023 +0800
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 +
.../style.scss => types/continous-profiling.d.ts} | 66 +++---
src/types/ebpf.d.ts | 19 ++
.../configuration/ContinuousProfiling.vue | 108 ++++++++++
src/views/dashboard/configuration/index.ts | 2 +
.../configuration/widget/metric/Standard.vue | 14 +-
.../dashboard/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 ++--
.../Content.vue | 36 ++--
.../continuous-profiling/components/EditPolicy.vue | 134 ++++++++++++
.../components/InstanceList.vue | 185 +++++++++++++++++
.../continuous-profiling/components/Policy.vue | 226 +++++++++++++++++++++
.../continuous-profiling/components/PolicyList.vue | 205 +++++++++++++++++++
.../dashboard/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 +-
.../related/network-profiling/components/Tasks.vue | 2 +
.../dashboard/related/task-timeline/Content.vue} | 31 ++-
.../components/ProfilingPanel.vue} | 56 +++--
.../related/task-timeline/components/Timeline.vue | 177 ++++++++++++++++
48 files changed, 1973 insertions(+), 130 deletions(-)
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. -->
-<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24"
viewBox="0 0 24 24">
-<path d="M15 15.984h6v3q0 0.797-0.609 1.406t-1.406 0.609h-13.969q-0.797
0-1.406-0.609t-0.609-1.406v-3h6q0 1.219 0.891 2.109t2.109 0.891 2.109-0.891
0.891-2.109zM18.984 9v-3.984h-13.969v3.984h3.984q0 1.219 0.891 2.109t2.109
0.891 2.109-0.891 0.891-2.109h3.984zM18.984 3q0.797 0 1.406 0.609t0.609
1.406v6.984q0 0.797-0.609 1.406t-1.406 0.609h-13.969q-0.797
0-1.406-0.609t-0.609-1.406v-6.984q0-0.797
0.609-1.406t1.406-0.609h13.969z"></path>
+<svg t="1684376918107" class="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="7954">
+ <path d="M243.921917 780.038686l357.445972 0L601.367889 422.592714
243.921917 422.592714 243.921917 780.038686zM288.679283 467.350081l268.036639 0
0 268.035616L288.679283 735.385696 288.679283 467.350081zM779.993149 65.25112
243.921917 65.25112c-98.640578 0-178.6314 79.990822-178.6314
178.716334L65.290517 780.038686c0 98.640578 79.990822 178.710194 178.6314
178.710194l536.071232 0c98.725512 0 178.716334-80.069617
178.716334-178.710194L958.709483 243.967454C958.709483 145.242965 878.717 [...]
</svg>
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 @@
+<!-- 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. -->
+<svg t="1684390612367" class="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="12207">
+ <path d="M202.66008 885.33784A10.66632 10.66632 0 0 0 213.3264
874.67152v-85.33056a42.66528 42.66528 0 0 0-42.66528-42.66528H85.33056a42.66528
42.66528 0 0 0-42.66528 42.66528v85.33056a10.66632 10.66632 0 0 0 10.66632
10.66632zM458.65176 885.33784a10.66632 10.66632 0 0 0
10.66632-10.66632v-298.65696a42.66528 42.66528 0 0
0-42.66528-42.665281H341.32224a42.66528 42.66528 0 0 0-42.66528
42.665281v298.65696a10.66632 10.66632 0 0 0 10.66632 10.66632zM714.643441
885.33784a10.66632 10.66632 0 [...]
+</svg>
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 @@
+<!-- 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. -->
+<svg t="1684722897341" class="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="2408">
+ <path d="M853.333333 501.333333c-17.066667 0-32 14.933333-32 32v320c0
6.4-4.266667 10.666667-10.666666 10.666667H170.666667c-6.4
0-10.666667-4.266667-10.666667-10.666667V213.333333c0-6.4 4.266667-10.666667
10.666667-10.666666h320c17.066667 0 32-14.933333
32-32s-14.933333-32-32-32H170.666667c-40.533333 0-74.666667 34.133333-74.666667
74.666666v640c0 40.533333 34.133333 74.666667 74.666667 74.666667h640c40.533333
0 74.666667-34.133333 74.666666-74.666667V533.333333c0-17.066667-14.933333-
[...]
+</svg>
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. -->
-<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24"
viewBox="0 0 24 24">
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M12 15.516q1.453 0
2.484-1.031t1.031-2.484-1.031-2.484-2.484-1.031-2.484 1.031-1.031 2.484 1.031
2.484 2.484 1.031zM19.453 12.984l2.109 1.641q0.328 0.234 0.094 0.656l-2.016
3.469q-0.188 0.328-0.609 0.188l-2.484-0.984q-0.984 0.703-1.688 0.984l-0.375
2.625q-0.094 0.422-0.469 0.422h-4.031q-0.375
0-0.469-0.422l-0.375-2.625q-0.891-0.375-1.688-0.984l-2.484 0.984q-0.422
0.141-0.609-0.188l-2.016-3.469q-0.234-0.422
0.094-0.656l2.109-1.641q-0.047-0.328-0.047-0.984t0.047-0.984l-2.109-1.641 [...]
</svg>
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 @@
+<!-- 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. -->
+<svg t="1685973573331" class="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="2708">
+ <path d="M138 573.8V450.2c48.2-12.1 84-55.8 84-107.7
0-51.9-35.8-95.6-84-107.7V68.1c0-14.9-12.1-27-27-27s-27 12.1-27 27v166.7C35.8
246.9 0 290.6 0 342.5c0 51.9 35.8 95.6 84 107.7v123.7c-48.2 12-84 55.7-84
107.6s35.8 95.6 84 107.7v166.7c0 14.9 12.1 27 27 27s27-12.1
27-27V789.2c48.2-12.1 84-55.8 84-107.7s-35.8-95.6-84-107.7zM60 342.5c0-28.1
22.9-51 51-51s51 22.9 51 51-22.9 51-51 51-51-22.9-51-51z m51 390c-28.1
0-51-22.9-51-51s22.9-51 51-51 51 22.9 51 51-22.9 51-51 51zM942 283H352c-16.6 0
[...]
+</svg>
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"
>
- <el-option v-for="item in options" :key="item.value || ''"
:label="item.label || ''" :value="item.value || ''">
+ <el-option
+ v-for="item in options"
+ :key="item.value || ''"
+ :label="item.label || ''"
+ :value="item.value || ''"
+ :disabled="item.disabled || false"
+ >
</el-option>
</el-select>
</template>
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<RouteRecordRaw> = [
},
],
},
+ {
+ 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<Recordable<StrategyItem>>;
+ selectedStrategy: Recordable<StrategyItem>;
+ taskList: Array<Recordable<EBPFTaskList>>;
+ selectedTask: Recordable<EBPFTaskList>;
+ errorTip: string;
+ errorReason: string;
+ instances: Instance[];
+ instance: Nullable<Instance>;
+ eBPFSchedules: EBPFProfilingSchedule[];
+ currentSchedule: EBPFProfilingSchedule | Record<string, never>;
+ 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<StrategyItem>) {
+ this.selectedStrategy = task || {};
+ },
+ setselectedTask(task: Recordable<EBPFTaskList>) {
+ this.selectedTask = task || {};
+ },
+ setCurrentSchedule(s: EBPFProfilingSchedule) {
+ this.currentSchedule = s;
+ },
+ setAnalyzeTrees(tree: AnalyzationTrees[]) {
+ this.analyzeTrees = tree;
+ },
+ setCurrentInstance(instance: Nullable<Instance>) {
+ 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<Nullable<AxiosResponse>> {
+ 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<Recordable<EBPFTaskList>>;
eBPFSchedules: EBPFProfilingSchedule[];
@@ -27,7 +28,7 @@ interface EbpfState {
analyzeTrees: AnalyzationTrees[];
labels: Option[];
couldProfiling: boolean;
- tip: string;
+ ebpfTips: string;
selectedTask: Recordable<EBPFTaskList>;
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<EBPFTaskList>;
+}
+
+export const taskTimelineStore = defineStore({
+ id: "taskTimeline",
+ state: (): taskTimelineState => ({
+ loading: false,
+ taskList: [],
+ selectedTask: {},
+ }),
+ actions: {
+ setSelectedTask(task: Recordable<EBPFTaskList>) {
+ 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/views/dashboard/graphs/style.scss
b/src/types/continous-profiling.d.ts
similarity index 58%
copy from src/views/dashboard/graphs/style.scss
copy to src/types/continous-profiling.d.ts
index 0ce4ac56..98c5b121 100644
--- a/src/views/dashboard/graphs/style.scss
+++ b/src/types/continous-profiling.d.ts
@@ -14,44 +14,32 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-.table {
- height: 100%;
- overflow: auto;
- padding: 0 10px 5px 0;
-}
-
-.list {
- margin-top: 10px;
- margin-bottom: 10px;
- height: calc(100% - 90px);
-}
-
-.pagination {
- width: 100%;
- text-align: center;
- height: 30px;
- padding: 8px 0;
-}
-
-.link {
- cursor: pointer;
- color: #409eff;
- display: inline-block;
- width: 100%;
-}
-
-.search {
- margin-top: 5px;
-}
-
-.input-with-search {
- width: 400px;
-}
-
-.chart {
- height: 60px;
-}
-.inputs {
- width: 300px;
+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 @@
+<!-- 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. -->
+<template>
+ <div class="item">
+ <div>{{ t("instanceDashboards") }}</div>
+ <Selector
+ :value="instanceDashboardName || ''"
+ :options="instanceDashboards"
+ size="small"
+ placeholder="Please select a dashboard name"
+ @change="changeDashboard({ instanceDashboardName: $event[0].value })"
+ class="selectors"
+ :clearable="true"
+ />
+ </div>
+ <div class="item">
+ <div>{{ t("processDashboards") }}</div>
+ <Selector
+ :value="processDashboardName || ''"
+ :options="processDashboards"
+ size="small"
+ placeholder="Please select a dashboard name"
+ @change="changeDashboard({ processDashboardName: $event[0].value })"
+ class="selectors"
+ :clearable="true"
+ />
+ </div>
+ <div class="footer">
+ <el-button size="small">
+ {{ t("cancel") }}
+ </el-button>
+ <el-button size="small" type="primary" @click="applyConfig">
+ {{ t("apply") }}
+ </el-button>
+ </div>
+</template>
+<script lang="ts" setup>
+ import { ref } from "vue";
+ import { useI18n } from "vue-i18n";
+ import { useDashboardStore } from "@/store/modules/dashboard";
+ import { EntityType } from "../data";
+ import type { DashboardItem } from "@/types/dashboard";
+
+ const { t } = useI18n();
+ const dashboardStore = useDashboardStore();
+ const instanceDashboardName =
ref<boolean>(dashboardStore.selectedGrid.instanceDashboardName);
+ const processDashboardName =
ref<number>(dashboardStore.selectedGrid.processDashboardName);
+ const list = JSON.parse(sessionStorage.getItem("dashboards") || "[]");
+ const instanceDashboards: Array<DashboardItem & { label: string; value:
string }> = [];
+ const processDashboards: Array<DashboardItem & { label: string; value:
string }> = [];
+
+ for (const item of list) {
+ if (item.layer === dashboardStore.layerId) {
+ const i = {
+ ...item,
+ label: item.name,
+ value: item.name,
+ };
+ if (item.entity === EntityType[8].value) {
+ processDashboards.push(i);
+ }
+ if (item.entity === EntityType[3].value) {
+ instanceDashboards.push(i);
+ }
+ }
+ }
+
+ function applyConfig() {
+ dashboardStore.setConfigs(dashboardStore.selectedGrid);
+ dashboardStore.setConfigPanel(false);
+ }
+ function changeDashboard(param: { [key: string]: unknown }) {
+ dashboardStore.selectWidget({
+ ...dashboardStore.selectedGrid,
+ ...param,
+ });
+ }
+</script>
+<style lang="scss" scoped>
+ .footer {
+ position: fixed;
+ bottom: 0;
+ right: 0;
+ border-top: 1px solid #eee;
+ padding: 10px;
+ text-align: right;
+ width: 100%;
+ background-color: #fff;
+ }
+
+ .item {
+ margin: 10px 0;
+ }
+
+ .selectors {
+ width: 500px;
+ }
+</style>
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. -->
"
/>
</div>
- <div class="item mb-10" v-if="hasLabel">
+ <div class="item mb-10" v-if="hasLabel || isExpression">
<span class="label">{{ t("labels") }}</span>
<el-input
class="input"
@@ -70,7 +70,7 @@ limitations under the License. -->
"
/>
</div>
- <div class="item mb-10" v-show="isExec">
+ <div class="item mb-10" v-show="!isExpression">
<span class="label">{{ t("aggregation") }}</span>
<SelectSingle
:value="currentMetric.calculation"
@@ -111,7 +111,7 @@ limitations under the License. -->
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 @@
+<!-- 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. -->
+<template>
+ <div class="profile-wrapper flex-v">
+ <div class="title">Continuous Profiling</div>
+ <el-popover placement="bottom" trigger="click" :width="100"
v-if="dashboardStore.editMode">
+ <template #reference>
+ <span class="operation cp">
+ <Icon iconName="ellipsis_v" size="middle" />
+ </span>
+ </template>
+ <div class="tools" @click="editConfig">
+ <span>{{ t("edit") }}</span>
+ </div>
+ <div class="tools" @click="removeWidget">
+ <span>{{ t("delete") }}</span>
+ </div>
+ </el-popover>
+ <Content :config="props.data" />
+ </div>
+</template>
+<script lang="ts" setup>
+ import type { PropType } from "vue";
+ import { useI18n } from "vue-i18n";
+ import { useDashboardStore } from "@/store/modules/dashboard";
+ import Content from "../related/continuous-profiling/Content.vue";
+
+ /*global defineProps */
+ const props = defineProps({
+ data: {
+ type: Object as PropType<any>,
+ default: () => ({ graph: {} }),
+ },
+ activeIndex: { type: String, default: "" },
+ needQuery: { type: Boolean, default: true },
+ });
+ const { t } = useI18n();
+ const dashboardStore = useDashboardStore();
+
+ function removeWidget() {
+ dashboardStore.removeControls(props.data);
+ }
+
+ function editConfig() {
+ dashboardStore.setConfigPanel(true);
+ dashboardStore.selectWidget(props.data);
+ }
+</script>
+<style lang="scss" scoped>
+ .profile-wrapper {
+ width: 100%;
+ height: 100%;
+ font-size: 12px;
+ position: relative;
+ }
+
+ .operation {
+ position: absolute;
+ top: 8px;
+ right: 3px;
+ }
+
+ .header {
+ padding: 10px;
+ font-size: 12px;
+ border-bottom: 1px solid #dcdfe6;
+ }
+
+ .tools {
+ padding: 5px 0;
+ color: #999;
+ cursor: pointer;
+ position: relative;
+ text-align: center;
+
+ &:hover {
+ color: #409eff;
+ background-color: #eee;
+ }
+ }
+
+ .title {
+ font-weight: bold;
+ line-height: 40px;
+ padding: 0 10px;
+ border-bottom: 1px solid #dcdfe6;
+ }
+</style>
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 @@
+<!-- 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. -->
+<template>
+ <div class="content-wrapper flex-v">
+ <div class="title">Task Timeline</div>
+ <el-popover placement="bottom" trigger="click" :width="100"
v-if="dashboardStore.editMode">
+ <template #reference>
+ <span class="operation cp">
+ <Icon iconName="ellipsis_v" size="middle" />
+ </span>
+ </template>
+ <div class="tools" @click="removeWidget">
+ <span>{{ t("delete") }}</span>
+ </div>
+ </el-popover>
+ <Content :config="props.data" />
+ </div>
+</template>
+<script lang="ts" setup>
+ import type { PropType } from "vue";
+ import { useI18n } from "vue-i18n";
+ import { useDashboardStore } from "@/store/modules/dashboard";
+ import Content from "../related/task-timeline/Content.vue";
+
+ /*global defineProps */
+ const props = defineProps({
+ data: {
+ type: Object as PropType<any>,
+ default: () => ({ graph: {} }),
+ },
+ activeIndex: { type: String, default: "" },
+ needQuery: { type: Boolean, default: true },
+ });
+ const { t } = useI18n();
+ const dashboardStore = useDashboardStore();
+
+ function removeWidget() {
+ dashboardStore.removeControls(props.data);
+ }
+</script>
+<style lang="scss" scoped>
+ .content-wrapper {
+ width: 100%;
+ height: 100%;
+ font-size: 12px;
+ position: relative;
+ }
+
+ .operation {
+ position: absolute;
+ top: 8px;
+ right: 3px;
+ }
+
+ .header {
+ padding: 10px;
+ font-size: 12px;
+ border-bottom: 1px solid #dcdfe6;
+ }
+
+ .tools {
+ padding: 5px 0;
+ color: #999;
+ cursor: pointer;
+ position: relative;
+ text-align: center;
+
+ &:hover {
+ color: #409eff;
+ background-color: #eee;
+ }
+ }
+
+ .title {
+ font-weight: bold;
+ line-height: 40px;
+ padding: 0 10px;
+ border-bottom: 1px solid #dcdfe6;
+ }
+</style>
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"
/>
</div>
- <div class="selectors-item" v-if="key === 3 || key === 4 || key === 5">
+ <div class="selectors-item" v-if="key === 3 || key === 4 || key === 5
|| key === 6">
<span class="label">
{{ ["EndpointRelation",
"Endpoint"].includes(dashboardStore.entity) ? "$Endpoint" : "$ServiceInstance"
}}
</span>
@@ -42,7 +42,7 @@ limitations under the License. -->
:isRemote="['EndpointRelation',
'Endpoint'].includes(dashboardStore.entity)"
/>
</div>
- <div class="selectors-item" v-if="key === 5">
+ <div class="selectors-item" v-if="key === 5 || key === 6">
<span class="label"> $Process </span>
<Selector
v-model="states.currentProcess"
@@ -145,6 +145,7 @@ limitations under the License. -->
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. -->
<style lang="scss" scoped>
.dashboard-tool {
text-align: right;
- padding: 3px 5px 5px 5px;
- background: rgb(240, 242, 245);
+ padding: 3px 5px 5px;
+ background: rgb(240 242 245);
border-bottom: 1px solid #dfe4e8;
justify-content: space-between;
}
diff --git a/src/views/dashboard/related/network-profiling/Content.vue
b/src/views/dashboard/related/continuous-profiling/Content.vue
similarity index 58%
copy from src/views/dashboard/related/network-profiling/Content.vue
copy to src/views/dashboard/related/continuous-profiling/Content.vue
index b08055fb..2c0ae63b 100644
--- a/src/views/dashboard/related/network-profiling/Content.vue
+++ b/src/views/dashboard/related/continuous-profiling/Content.vue
@@ -14,50 +14,38 @@ See the License for the specific language governing
permissions and
limitations under the License. -->
<template>
<div class="flex-h content">
- <Tasks />
- <div class="vis-graph ml-5" v-loading="networkProfilingStore.loadNodes">
- <process-topology v-if="networkProfilingStore.nodes.length"
:config="config" />
- <div class="text" v-else>
- {{ t("noData") }}
- </div>
+ <policy-list />
+ <div class="flex-v list"
v-loading="continousProfilingStore.instancesLoading">
+ <instance-list :config="config" />
</div>
</div>
</template>
<script lang="ts" setup>
import type { PropType } from "vue";
- import { useI18n } from "vue-i18n";
- import Tasks from "./components/Tasks.vue";
- import ProcessTopology from "./components/ProcessTopology.vue";
- import { useNetworkProfilingStore } from "@/store/modules/network-profiling";
+ import { useContinousProfilingStore } from
"@/store/modules/continous-profiling";
+ import PolicyList from "./components/PolicyList.vue";
+ import InstanceList from "./components/InstanceList.vue";
+
+ const continousProfilingStore = useContinousProfilingStore();
/*global defineProps */
defineProps({
config: {
type: Object as PropType<any>,
- default: () => ({ graph: {} }),
+ default: () => ({}),
},
});
- const networkProfilingStore = useNetworkProfilingStore();
- const { t } = useI18n();
</script>
<style lang="scss" scoped>
.content {
- height: calc(100% - 30px);
+ height: calc(100% - 50px);
width: 100%;
}
- .vis-graph {
+ .list {
height: 100%;
flex-grow: 2;
- min-width: 700px;
+ min-width: 600px;
overflow: hidden;
- position: relative;
- width: calc(100% - 330px);
- }
-
- .text {
- width: 100%;
- text-align: center;
- margin-top: 30px;
}
</style>
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 @@
+<!-- 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. -->
+
+<template>
+ <div class="policy-list">
+ <el-collapse v-model="activeNames">
+ <el-collapse-item v-for="(_, index) in policyList" :key="index"
:name="String(index)">
+ <template #title>
+ <div>
+ <span class="title">{{ `Policy - ${index + 1}` }}</span>
+ <Icon
+ class="mr-5 cp"
+ iconName="remove_circle_outline"
+ size="middle"
+ v-show="policyList.length !== 1"
+ @click="removePolicy($event, index)"
+ />
+ <Icon
+ class="cp"
+ v-show="index === policyList.length - 1"
+ iconName="add_circle_outlinecontrol_point"
+ size="middle"
+ @click="createPolicy"
+ />
+ </div>
+ </template>
+ <Policy :policyList="policyList" @edit="changePolicy" :order="index" />
+ </el-collapse-item>
+ </el-collapse>
+ <div>
+ <el-button @click="save" type="primary" class="save-btn">
+ {{ t("save") }}
+ </el-button>
+ </div>
+ </div>
+</template>
+<script lang="ts" setup>
+ import { ref } from "vue";
+ import type { PropType } from "vue";
+ import { useI18n } from "vue-i18n";
+ import Policy from "./Policy.vue";
+ import type { StrategyItem, CheckItems } from "@/types/continous-profiling";
+
+ /* global defineEmits, defineProps */
+ const props = defineProps({
+ policyList: {
+ type: Array as PropType<StrategyItem[]>,
+ default: () => [],
+ },
+ });
+ const emits = defineEmits(["save"]);
+ const { t } = useI18n();
+ const activeNames = ref(["0"]);
+ const policyList = ref<StrategyItem[]>([...props.policyList]);
+
+ function changePolicy(params: StrategyItem, order: number) {
+ policyList.value = policyList.value.map((d: StrategyItem, index: number)
=> {
+ if (order === index) {
+ return params;
+ }
+ return d;
+ });
+ }
+
+ function removePolicy(e: PointerEvent, key: number) {
+ e.stopPropagation();
+ if (policyList.value.length === 1) {
+ return;
+ }
+ policyList.value = policyList.value.filter((_, index: number) => index !==
key);
+ }
+
+ function createPolicy(e: PointerEvent) {
+ e.stopPropagation();
+ policyList.value.push({
+ type: "",
+ checkItems: [
+ {
+ type: "",
+ threshold: "",
+ period: NaN,
+ count: NaN,
+ },
+ ],
+ });
+ activeNames.value = [String(policyList.value.length - 1)];
+ }
+
+ function save() {
+ const params = [];
+ for (const d of policyList.value) {
+ const checkItems = d.checkItems.filter(
+ (item: CheckItems) => item.type && item.threshold && item.period &&
item.count,
+ );
+ if (d.type && checkItems.length) {
+ const v = {
+ targetType: d.type,
+ checkItems,
+ };
+ params.push(v);
+ }
+ }
+
+ emits("save", params);
+ }
+</script>
+<style lang="scss" scoped>
+ .policy-list {
+ margin: 0 auto;
+ width: 300px;
+ }
+
+ .save-btn {
+ width: 300px;
+ margin-top: 10px;
+ }
+
+ .title {
+ display: inline-block;
+ margin-right: 5px;
+ }
+</style>
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 @@
+<!-- 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. -->
+<template>
+ <div class="header">
+ {{ t("monitorInstances") }}
+ </div>
+ <el-table :data="currentInstances" style="width: 99%" height="440">
+ <el-table-column type="expand">
+ <template #default="props">
+ <div class="child">
+ <div class="title">{{ t("attributes") }}</div>
+ <div v-for="(attr, index) in props.row.attributes" :key="index">
+ {{ `${attr.name}: ${attr.value}` }}
+ </div>
+ <div class="title mt-10">{{ t("processes") }}</div>
+ <el-table :data="props.row.processes" size="small" max-height="300">
+ <el-table-column prop="name" label="Name">
+ <template #default="scope">
+ <span
+ :class="config.processDashboardName ? 'link' : ''"
+ @click="viewProcessDashboard(scope.row, props.row)"
+ >
+ {{ scope.row.name }}
+ </span>
+ </template>
+ </el-table-column>
+ <el-table-column
+ v-for="item in HeaderChildLabels"
+ :key="item.value"
+ :label="item.label"
+ :prop="item.value"
+ :width="item.width"
+ />
+ </el-table>
+ </div>
+ </template>
+ </el-table-column>
+ <el-table-column prop="name" label="Name">
+ <template #default="scope">
+ <span :class="config.instanceDashboardName ? 'link' : ''"
@click="viewInstanceDashboard(scope.row)">
+ {{ scope.row.name }}
+ </span>
+ </template>
+ </el-table-column>
+ <el-table-column
+ v-for="item in HeaderLabels"
+ :key="item.value"
+ :label="item.label"
+ :prop="item.value"
+ :width="item.width"
+ />
+ </el-table>
+ <el-pagination
+ class="mt-10"
+ small
+ background
+ layout="prev, pager, next"
+ :page-size="pageSize"
+ :total="instances.length"
+ @current-change="changePage"
+ @prev-click="changePage"
+ @next-click="changePage"
+ />
+</template>
+<script lang="ts" setup>
+ import { ref, computed, watch } from "vue";
+ import type { PropType } from "vue";
+ import { useI18n } from "vue-i18n";
+ import { useContinousProfilingStore } from
"@/store/modules/continous-profiling";
+ import { useDashboardStore } from "@/store/modules/dashboard";
+ import { useSelectorStore } from "@/store/modules/selectors";
+ import type { MonitorInstance, MonitorProcess } from
"@/types/continous-profiling";
+ import router from "@/router";
+ import { HeaderLabels, HeaderChildLabels } from "../data";
+ import { EntityType } from "../../../data";
+ import { dateFormat } from "@/utils/dateFormat";
+
+ /*global defineProps */
+ const props = defineProps({
+ config: {
+ type: Object as PropType<any>,
+ default: () => ({}),
+ },
+ });
+ const { t } = useI18n();
+ const dashboardStore = useDashboardStore();
+ const selectorStore = useSelectorStore();
+ const continousProfilingStore = useContinousProfilingStore();
+ const pageSize = 10;
+ const instances = computed(() => {
+ return continousProfilingStore.instances
+ .map((d: MonitorInstance) => {
+ const processes = (d.processes || [])
+ .sort((c: MonitorProcess, d: MonitorProcess) =>
d.lastTriggerTimestamp - c.lastTriggerTimestamp)
+ .map((p: MonitorProcess) => {
+ return {
+ ...p,
+ lastTriggerTime: d.lastTriggerTimestamp ?
dateFormat(d.lastTriggerTimestamp) : "",
+ labels: p.labels.join("; "),
+ };
+ });
+
+ return { ...d, processes, lastTriggerTime: d.lastTriggerTimestamp ?
dateFormat(d.lastTriggerTimestamp) : "" };
+ })
+ .sort((a: MonitorInstance, b: MonitorInstance) => b.lastTriggerTimestamp
- a.lastTriggerTimestamp);
+ });
+ const currentInstances = ref<MonitorInstance[]>([]);
+
+ function viewProcessDashboard(process: MonitorProcess, instance:
MonitorInstance) {
+ if (!props.config.processDashboardName) {
+ return;
+ }
+ router.push(
+
`/dashboard/${dashboardStore.layerId}/${EntityType[8].value}/${selectorStore.currentService.id}/${instance.id}/${process.id}/${props.config.processDashboardName}`,
+ );
+ }
+
+ function viewInstanceDashboard(instance: MonitorInstance) {
+ if (!props.config.instanceDashboardName) {
+ return;
+ }
+ router.push(
+
`/dashboard/${dashboardStore.layerId}/${EntityType[3].value}/${selectorStore.currentService.id}/${instance.id}/${props.config.instanceDashboardName}`,
+ );
+ }
+
+ async function changePage(pageIndex: number) {
+ currentInstances.value = instances.value.filter((d: unknown, index:
number) => {
+ if (index >= (pageIndex - 1) * pageSize && index < pageIndex * pageSize)
{
+ return d;
+ }
+ });
+ }
+
+ watch(
+ () => instances.value,
+ () => {
+ currentInstances.value = instances.value.filter((_: unknown, index:
number) => index < pageSize);
+ },
+ );
+</script>
+<style lang="scss" scoped>
+ .title {
+ font-size: 12px;
+ font-weight: bold;
+ }
+
+ .child {
+ padding-left: 20px;
+ }
+
+ .header {
+ font-size: 13px;
+ font-weight: bold;
+ border-bottom: 1px solid rgb(0 0 0 / 7%);
+ padding: 10px 20px;
+ background-color: #f3f4f9;
+ }
+
+ .settings {
+ padding: 1px 0;
+ border: 1px solid #666;
+ border-radius: 3px;
+ color: #666;
+ cursor: pointer;
+ }
+
+ .link {
+ cursor: pointer;
+ color: #409eff;
+ text-decoration: underline;
+ }
+</style>
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 @@
+<!-- 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. -->
+<template>
+ <div>
+ <div class="label">{{ t("targetTypes") }}</div>
+ <Selector
+ class="profile-input"
+ size="small"
+ :value="states.type"
+ :options="TargetTypes"
+ placeholder="Select a type"
+ @change="changeType"
+ />
+ </div>
+ <div v-for="(item, index) in states.checkItems" :key="index">
+ <div class="item-title">
+ <span class="title">{{ `Item - ${index + 1}` }}</span>
+ <Icon
+ class="ml-5 cp"
+ iconName="remove_circle_outline"
+ size="middle"
+ v-show="states.checkItems.length !== 1"
+ @click="removeItem($event, index)"
+ />
+ <Icon
+ class="ml-5 cp"
+ v-show="index === states.checkItems.length - 1"
+ iconName="add_circle_outlinecontrol_point"
+ size="middle"
+ @click="createItem"
+ />
+ </div>
+ <div>
+ <div class="label">{{ t("monitorType") }}</div>
+ <Selector
+ class="profile-input"
+ size="small"
+ :value="item.type"
+ :options="MonitorType"
+ placeholder="Select a type"
+ @change="changeMonitorType($event, index)"
+ />
+ </div>
+ <div>
+ <div class="label">{{ t("count") }}</div>
+ <el-input-number size="small" class="profile-input" :min="0"
v-model="item.count" @change="changeParam" />
+ </div>
+ <div>
+ <div class="label">
+ <span class="mr-5">{{ t("threshold") }}</span>
+ <span>({{ getNotice(item.type) }} )</span>
+ </div>
+ <el-input
+ type="number"
+ size="small"
+ class="profile-input"
+ v-model="item.threshold"
+ @change="changeThreshold(index)"
+ />
+ </div>
+ <div>
+ <div class="label">{{ t("period") }}</div>
+ <el-input-number size="small" class="profile-input" :min="0"
v-model="item.period" @change="changeParam" />
+ </div>
+ <div v-show="TYPES.includes(item.type)">
+ <div class="label">{{ t("uriRegex") }}</div>
+ <el-input size="small" class="profile-input" v-model="item.uriRegex"
@change="changeParam(index)" />
+ </div>
+ <div v-show="TYPES.includes(item.type)">
+ <div class="label">{{ t("uriList") }}</div>
+ <div id="uri-param" contenteditable="true" @input="changeURI($event,
index)" class="profile-input">
+ {{ (item.uriList || []).join("; ") }}
+ </div>
+ </div>
+ </div>
+</template>
+<script lang="ts" setup>
+ import { reactive } from "vue";
+ import type { PropType } from "vue";
+ import { useI18n } from "vue-i18n";
+ import { ElMessage } from "element-plus";
+ import type { StrategyItem, CheckItems } from "@/types/continous-profiling";
+ import { MonitorType, TargetTypes } from "../data";
+
+ /* global defineEmits, defineProps */
+ const props = defineProps({
+ policyList: {
+ type: Object as PropType<StrategyItem[]>,
+ default: () => ({}),
+ },
+ order: {
+ type: Number,
+ default: 0,
+ },
+ });
+ const emits = defineEmits(["edit"]);
+ const { t } = useI18n();
+ const states = reactive<StrategyItem>(props.policyList[props.order]);
+ const TYPES = ["HTTP_ERROR_RATE", "HTTP_AVG_RESPONSE_TIME"];
+ function changeType(opt: { value: string }[]) {
+ const types = props.policyList.map((item: StrategyItem) => item.type);
+ if (types.includes(opt[0].value)) {
+ return ElMessage.warning("Target type cannot be configured repeatedly.");
+ }
+ states.type = opt[0].value;
+ emits("edit", states, props.order);
+ }
+
+ function changeMonitorType(opt: { value: string }[], index: number) {
+ const types = states.checkItems.map((item: CheckItems) => item.type);
+ if (types.includes(opt[0].value)) {
+ return ElMessage.warning("Monitor type cannot be configured
repeatedly.");
+ }
+ states.checkItems[index].type = opt[0].value;
+ emits("edit", states, props.order);
+ }
+
+ function changeURI(event: any, index: number) {
+ if (states.checkItems[index].uriRegex) {
+ return ElMessage.warning("UriList or UriRegex only be configured with
one option.");
+ }
+ const params = (event.target.textContent || "").replace(/\s+/g, "");
+ const arr = params.splice(";");
+ states.checkItems[index].uriList = arr.length ? arr : null;
+ emits("edit", states, props.order);
+ }
+
+ function changeThreshold(index: number) {
+ let regex = /^(100(\.0{1,2})?|[1-9]?\d(\.\d{1,2})?)$/;
+ if (MonitorType[1].value === states.checkItems[index].type) {
+ regex = /^\d+$/;
+ }
+ if (MonitorType[2].value === states.checkItems[index].type) {
+ regex = /^(\d+)(\.\d+)?$/;
+ }
+ if (MonitorType[4].value === states.checkItems[index].type) {
+ regex = /^[+]{0,1}(\d+)$|^[+]{0,1}(\d+\.\d+)$/;
+ }
+
+ if (!regex.test(states.checkItems[index].threshold)) {
+ return ElMessage.error(getNotice(states.checkItems[index].type));
+ }
+ emits("edit", states, props.order);
+ }
+
+ function changeParam(index?: any) {
+ if (index !== undefined && (states.checkItems[index] || {}).uriList) {
+ return ElMessage.warning("UriList or UriRegex only be configured with
one option");
+ }
+ const checkItems = states.checkItems.map((d: CheckItems) => {
+ d.count = Number(d.count);
+ d.period = Number(d.period);
+ return d;
+ });
+ emits("edit", { ...states, checkItems }, props.order);
+ }
+
+ function createItem(e: PointerEvent) {
+ e.stopPropagation();
+ states.checkItems.push({
+ type: "",
+ threshold: "",
+ period: NaN,
+ count: NaN,
+ });
+ emits("edit", states, props.order);
+ }
+
+ function removeItem(e: PointerEvent, key: number) {
+ e.stopPropagation();
+ if (states.checkItems.length === 1) {
+ return;
+ }
+ states.checkItems = states.checkItems.filter((_, index: number) => index
!== key);
+ emits("edit", states, props.order);
+ }
+
+ function getNotice(type: string) {
+ const map: { [key: string]: string } = {
+ PROCESS_CPU: "It is a percentage data",
+ PROCESS_THREAD_COUNT: "It is a positive integer",
+ SYSTEM_LOAD: "It is a floating point number",
+ HTTP_ERROR_RATE: "It is percentage data",
+ HTTP_AVG_RESPONSE_TIME: "It is a response time in milliseconds",
+ };
+
+ return map[type];
+ }
+</script>
+<style lang="scss" scoped>
+ .profile-input {
+ width: 300px;
+ margin-bottom: 10px;
+ }
+
+ #uri-param {
+ border: 1px solid #dcdfe6;
+ cursor: text;
+ padding: 0 5px;
+ border-radius: 4px;
+ color: #606266;
+ outline: none;
+ height: 100px;
+
+ &:focus {
+ border-color: #409eff;
+ }
+ }
+
+ .item-title {
+ margin-bottom: 5px;
+ font-size: 14px;
+ }
+</style>
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 @@
+<!-- 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. -->
+<template>
+ <div class="profile-task-list flex-v"
v-loading="continousProfilingStore.policyLoading">
+ <div class="profile-task-wrapper flex-v">
+ <div class="profile-t-tool">
+ <span>{{ t("policyList") }}</span>
+ <span class="new-task cp" @click="setStrategies">
+ <Icon iconName="edit" size="middle" />
+ </span>
+ </div>
+ <div class="profile-t-wrapper">
+ <div class="no-data"
v-show="!continousProfilingStore.strategyList.length">
+ {{ t("noData") }}
+ </div>
+ <table class="profile-t">
+ <tr
+ class="profile-tr cp"
+ v-for="(i, index) in continousProfilingStore.strategyList"
+ @click="changePolicy(i)"
+ :key="index"
+ >
+ <td
+ class="profile-td"
+ :class="{
+ selected: continousProfilingStore.selectedStrategy.id === i.id,
+ }"
+ >
+ <div class="ell">
+ <span class="sm">
+ {{ i.type }}
+ </span>
+ </div>
+ <div class="grey ell sm" v-for="(item, index) in i.checkItems"
:key="index">
+ <span class="sm">
+ {{
+ `${item.type} >= ${item.threshold}${
+ [MonitorType[0].value,
MonitorType[3].value].includes(item.type) ? "%" : ""
+ }; `
+ }}
+ </span>
+ </div>
+ </td>
+ </tr>
+ </table>
+ </div>
+ </div>
+ </div>
+ <el-dialog
+ v-model="updateStrategies"
+ :title="t('editStrategy')"
+ :destroy-on-close="true"
+ fullscreen
+ @closed="updateStrategies = false"
+ >
+ <EditPolicy :policyList="continousProfilingStore.strategyList"
@save="editStrategies" />
+ </el-dialog>
+</template>
+<script lang="ts" setup>
+ import { ref, watch } from "vue";
+ import { useI18n } from "vue-i18n";
+ import { useContinousProfilingStore } from
"@/store/modules/continous-profiling";
+ import { useSelectorStore } from "@/store/modules/selectors";
+ import type { StrategyItem, CheckItems } from "@/types/continous-profiling";
+ import { ElMessage } from "element-plus";
+ import EditPolicy from "./EditPolicy.vue";
+ import { MonitorType } from "../data";
+
+ const { t } = useI18n();
+ const selectorStore = useSelectorStore();
+ const continousProfilingStore = useContinousProfilingStore();
+ const updateStrategies = ref<boolean>(false);
+ const inProcess = ref<boolean>(false);
+
+ fetchStrategyList();
+
+ async function changePolicy(item: StrategyItem) {
+ continousProfilingStore.setSelectedStrategy(item);
+ const serviceId = (selectorStore.currentService &&
selectorStore.currentService.id) || "";
+ await continousProfilingStore.getMonitoringInstances(serviceId);
+ }
+
+ function setStrategies() {
+ updateStrategies.value = true;
+ }
+
+ async function editStrategies(
+ targets: {
+ targetType: string;
+ checkItems: CheckItems[];
+ }[],
+ ) {
+ const serviceId = (selectorStore.currentService &&
selectorStore.currentService.id) || "";
+ if (!serviceId) {
+ return ElMessage.error("No Service ID");
+ }
+ const res = await
continousProfilingStore.setContinuousProfilingPolicy(serviceId, targets);
+ if (res.errors) {
+ ElMessage.error(res.errors);
+ return;
+ }
+ if (!res.data.strategy.status) {
+ ElMessage.error(res.data.strategy.errorReason);
+ return;
+ }
+ updateStrategies.value = false;
+ await fetchStrategyList();
+ }
+
+ async function fetchStrategyList() {
+ const serviceId = (selectorStore.currentService &&
selectorStore.currentService.id) || "";
+ const res = await continousProfilingStore.getStrategyList({
+ serviceId,
+ });
+
+ if (res.errors) {
+ return ElMessage.error(res.errors);
+ }
+ if (!continousProfilingStore.strategyList.length) {
+ return;
+ }
+ }
+
+ watch(
+ () => selectorStore.currentService,
+ () => {
+ inProcess.value = false;
+ fetchStrategyList();
+ },
+ );
+</script>
+<style lang="scss" scoped>
+ .profile-task-list {
+ width: 300px;
+ height: 98%;
+ overflow: auto;
+ border-right: 1px solid rgb(0 0 0 / 10%);
+ }
+
+ .item span {
+ height: 21px;
+ }
+
+ .profile-td {
+ padding: 10px 5px 10px 10px;
+ border-bottom: 1px solid rgb(0 0 0 / 7%);
+
+ &.selected {
+ background-color: #ededed;
+ }
+ }
+
+ .no-data {
+ text-align: center;
+ margin-top: 10px;
+ }
+
+ .profile-t-wrapper {
+ overflow: auto;
+ flex-grow: 1;
+ }
+
+ .profile-t {
+ width: 100%;
+ border-spacing: 0;
+ table-layout: fixed;
+ flex-grow: 1;
+ position: relative;
+ border: none;
+ }
+
+ .profile-tr {
+ &:hover {
+ background-color: rgb(0 0 0 / 4%);
+ }
+ }
+
+ .profile-t-tool {
+ padding: 10px 5px 10px 10px;
+ border-bottom: 1px solid rgb(0 0 0 / 7%);
+ background: #f3f4f9;
+ width: 100%;
+ font-weight: bold;
+ }
+
+ .new-task {
+ float: right;
+ }
+
+ .reload {
+ margin-left: 30px;
+ }
+</style>
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. -->
/>
<el-popover placement="bottom" :width="680" trigger="click"
:persistent="false">
<template #reference>
- <el-button type="primary" size="small">
+ <el-button size="small">
{{ t("processSelect") }}
</el-button>
</template>
@@ -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<InstanceType<typeof ElTable>>();
const selectedProcesses = ref<string[]>([]);
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. -->
<template>
<div id="graph-stack" ref="graph">
- <span class="tip" v-show="ebpfStore.tip">{{ ebpfStore.tip }}</span>
+ <span class="tip" v-show="ebpfStore.ebpfTips">{{ ebpfStore.ebpfTips
}}</span>
</div>
</template>
<script lang="ts" setup>
@@ -23,12 +23,20 @@ limitations under the License. -->
import d3tip from "d3-tip";
import { flamegraph } from "d3-flame-graph";
import { useEbpfStore } from "@/store/modules/ebpf";
+ import { useContinousProfilingStore } from
"@/store/modules/continous-profiling";
+ import { ComponentType } from
"@/views/dashboard/related/continuous-profiling/data";
import type { StackElement } from "@/types/ebpf";
import { AggregateTypes } from "./data";
import "d3-flame-graph/dist/d3-flamegraph.css";
- /*global Nullable*/
- const ebpfStore = useEbpfStore();
+ /*global Nullable, defineProps*/
+ const props = defineProps({
+ type: {
+ type: String,
+ default: "",
+ },
+ });
+ const ebpfStore = props.type === ComponentType ?
useContinousProfilingStore() : useEbpfStore();
const stackTree = ref<Nullable<StackElement>>(null);
const selectStack = ref<Nullable<StackElement>>(null);
const graph = ref<Nullable<HTMLDivElement>>(null);
@@ -90,7 +98,7 @@ limitations under the License. -->
.setColorMapper((d, originalColor) => (d.highlight ? "#6aff8f" :
originalColor));
const tip = (d3tip as any)()
.attr("class", "d3-tip")
- .direction("w")
+ .direction("s")
.html((d: { data: StackElement } & { parent: { data: StackElement } })
=> {
const name = d.data.name.replace("<", "<").replace(">", ">");
const valStr =
@@ -111,7 +119,7 @@ limitations under the License. -->
}</div>`;
return `<div class="mb-5 name">Symbol:
${name}</div>${valStr}${rateOfParent}${rateOfRoot}`;
})
- .style("max-width", "500px");
+ .style("max-width", "400px");
flameChart.value.tooltip(tip);
d3.select("#graph-stack").datum(stackTree.value).call(flameChart.value);
}
diff --git a/src/views/dashboard/related/network-profiling/Content.vue
b/src/views/dashboard/related/network-profiling/Content.vue
index b08055fb..26488680 100644
--- a/src/views/dashboard/related/network-profiling/Content.vue
+++ b/src/views/dashboard/related/network-profiling/Content.vue
@@ -34,7 +34,7 @@ limitations under the License. -->
defineProps({
config: {
type: Object as PropType<any>,
- default: () => ({ graph: {} }),
+ default: () => ({}),
},
});
const networkProfilingStore = useNetworkProfilingStore();
diff --git
a/src/views/dashboard/related/network-profiling/components/ProcessTopology.vue
b/src/views/dashboard/related/network-profiling/components/ProcessTopology.vue
index e1680daf..427cb923 100644
---
a/src/views/dashboard/related/network-profiling/components/ProcessTopology.vue
+++
b/src/views/dashboard/related/network-profiling/components/ProcessTopology.vue
@@ -19,7 +19,7 @@ limitations under the License. -->
<g class="hex-polygon">
<path :d="getHexPolygonVertices()" stroke="#D5DDF6" stroke-width="2"
fill="none" />
<text :x="0" :y="radius - 15" fill="#000" text-anchor="middle">
- {{ selectorStore.currentPod.label }}
+ {{ selectorStore.currentPod && selectorStore.currentPod.label }}
</text>
</g>
<g class="nodes">
@@ -530,11 +530,11 @@ limitations under the License. -->
border-radius: 3px;
position: absolute;
top: 10px;
- right: 10px;
+ right: 20px;
}
.range {
- right: 50px;
+ right: 60px;
}
.topo-call {
diff --git a/src/views/dashboard/related/network-profiling/components/Tasks.vue
b/src/views/dashboard/related/network-profiling/components/Tasks.vue
index 7986e101..598350cc 100644
--- a/src/views/dashboard/related/network-profiling/components/Tasks.vue
+++ b/src/views/dashboard/related/network-profiling/components/Tasks.vue
@@ -77,6 +77,7 @@ limitations under the License. -->
import getLocalTime from "@/utils/localtime";
import { useAppStoreWithOut } from "@/store/modules/app";
import NewTask from "./NewTask.vue";
+ import { EBPFProfilingTriggerType } from "@/store/data";
/*global Nullable */
const { t } = useI18n();
@@ -185,6 +186,7 @@ limitations under the License. -->
serviceId,
serviceInstanceId,
targets: ["NETWORK"],
+ triggerType: EBPFProfilingTriggerType.FIXED_TIME,
});
if (res.errors) {
diff --git a/src/assets/icons/all_inbox.svg
b/src/views/dashboard/related/task-timeline/Content.vue
similarity index 53%
copy from src/assets/icons/all_inbox.svg
copy to src/views/dashboard/related/task-timeline/Content.vue
index cf38fc98..c88fa898 100644
--- a/src/assets/icons/all_inbox.svg
+++ b/src/views/dashboard/related/task-timeline/Content.vue
@@ -5,13 +5,36 @@ 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
+ 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. -->
-<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24"
viewBox="0 0 24 24">
-<path d="M15 15.984h6v3q0 0.797-0.609 1.406t-1.406 0.609h-13.969q-0.797
0-1.406-0.609t-0.609-1.406v-3h6q0 1.219 0.891 2.109t2.109 0.891 2.109-0.891
0.891-2.109zM18.984 9v-3.984h-13.969v3.984h3.984q0 1.219 0.891 2.109t2.109
0.891 2.109-0.891 0.891-2.109h3.984zM18.984 3q0.797 0 1.406 0.609t0.609
1.406v6.984q0 0.797-0.609 1.406t-1.406 0.609h-13.969q-0.797
0-1.406-0.609t-0.609-1.406v-6.984q0-0.797
0.609-1.406t1.406-0.609h13.969z"></path>
-</svg>
+<template>
+ <div class="flex-v content">
+ <Timeline />
+ <ProfilingPanel :config="config" />
+ </div>
+</template>
+<script lang="ts" setup>
+ import type { PropType } from "vue";
+ import Timeline from "./components/Timeline.vue";
+ import ProfilingPanel from "./components/ProfilingPanel.vue";
+
+ /* global defineProps */
+ defineProps({
+ config: {
+ type: Object as PropType<any>,
+ default: () => ({}),
+ },
+ });
+</script>
+<style lang="scss" scoped>
+ .content {
+ height: calc(100% - 50px);
+ width: 100%;
+ padding: 0 10px;
+ }
+</style>
diff --git a/src/views/dashboard/related/network-profiling/Content.vue
b/src/views/dashboard/related/task-timeline/components/ProfilingPanel.vue
similarity index 53%
copy from src/views/dashboard/related/network-profiling/Content.vue
copy to src/views/dashboard/related/task-timeline/components/ProfilingPanel.vue
index b08055fb..cf2bc4a3 100644
--- a/src/views/dashboard/related/network-profiling/Content.vue
+++ b/src/views/dashboard/related/task-timeline/components/ProfilingPanel.vue
@@ -13,51 +13,71 @@ 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. -->
<template>
- <div class="flex-h content">
- <Tasks />
- <div class="vis-graph ml-5" v-loading="networkProfilingStore.loadNodes">
- <process-topology v-if="networkProfilingStore.nodes.length"
:config="config" />
- <div class="text" v-else>
- {{ t("noData") }}
- </div>
+ <div class="content" v-if="taskTimelineStore.selectedTask.targetType ===
TargetTypes[2].value">
+ <process-topology v-if="networkProfilingStore.nodes.length"
:config="config" />
+ </div>
+ <div
+ class="content"
+ v-if="[TargetTypes[1].value,
TargetTypes[0].value].includes(taskTimelineStore.selectedTask.targetType)"
+ >
+ <div class="schedules">
+ <EBPFSchedules />
+ </div>
+ <div class="item">
+ <EBPFStack />
</div>
</div>
+ <div class="text" v-if="!taskTimelineStore.selectedTask.targetType">
+ {{ t("noData") }}
+ </div>
</template>
<script lang="ts" setup>
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
- import Tasks from "./components/Tasks.vue";
- import ProcessTopology from "./components/ProcessTopology.vue";
+ import { useTaskTimelineStore } from "@/store/modules/task-timeline";
import { useNetworkProfilingStore } from "@/store/modules/network-profiling";
+ import { TargetTypes } from "../../continuous-profiling/data";
+ import ProcessTopology from
"@/views/dashboard/related/network-profiling/components/ProcessTopology.vue";
+ import EBPFSchedules from
"@/views/dashboard/related/ebpf/components/EBPFSchedules.vue";
+ import EBPFStack from
"@/views/dashboard/related/ebpf/components/EBPFStack.vue";
/*global defineProps */
defineProps({
config: {
type: Object as PropType<any>,
- default: () => ({ graph: {} }),
+ default: () => ({}),
},
});
- const networkProfilingStore = useNetworkProfilingStore();
const { t } = useI18n();
+ const taskTimelineStore = useTaskTimelineStore();
+ const networkProfilingStore = useNetworkProfilingStore();
</script>
<style lang="scss" scoped>
.content {
- height: calc(100% - 30px);
width: 100%;
- }
-
- .vis-graph {
- height: 100%;
+ height: calc(100% - 30px);
flex-grow: 2;
min-width: 700px;
overflow: hidden;
position: relative;
- width: calc(100% - 330px);
}
.text {
width: 100%;
text-align: center;
- margin-top: 30px;
+ margin-top: 100px;
+ }
+
+ .item {
+ width: 100%;
+ overflow: auto;
+ height: calc(100% - 100px);
+ padding-bottom: 10px;
+ }
+
+ .schedules {
+ height: 90px;
+ border-bottom: 1px solid #ccc;
+ padding-right: 10px;
}
</style>
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 @@
+<!-- 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. -->
+<template>
+ <div ref="timeline" class="task-timeline"></div>
+</template>
+<script lang="ts" setup>
+ import { ref, watch, onMounted, onUnmounted } from "vue";
+ import dayjs from "dayjs";
+ import { useThrottleFn } from "@vueuse/core";
+ import { ElMessage } from "element-plus";
+ import type { EBPFTaskList } from "@/types/ebpf";
+ import { useTaskTimelineStore } from "@/store/modules/task-timeline";
+ import { useDashboardStore } from "@/store/modules/dashboard";
+ import { useSelectorStore } from "@/store/modules/selectors";
+ import { useContinousProfilingStore } from
"@/store/modules/continous-profiling";
+ import { DataSet, Timeline } from "vis-timeline/standalone";
+ import "vis-timeline/styles/vis-timeline-graph2d.css";
+ import { EBPFProfilingTriggerType } from "@/store/data";
+
+ const taskTimelineStore = useTaskTimelineStore();
+ const selectorStore = useSelectorStore();
+ const continousProfilingStore = useContinousProfilingStore();
+ const dashboardStore = useDashboardStore();
+ /* global defineProps, Nullable */
+ const props = defineProps({
+ data: {
+ type: Object,
+ default: () => ({}),
+ },
+ });
+ const timeline = ref<Nullable<HTMLDivElement>>(null);
+ const visGraph = ref<Nullable<any>>(null);
+ const oldVal = ref<{ width: number; height: number }>({ width: 0, height: 0
});
+ const visDate = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") =>
dayjs(date).format(pattern);
+
+ init();
+ onMounted(() => {
+ oldVal.value = (timeline.value && timeline.value.getBoundingClientRect())
|| {
+ width: 0,
+ height: 0,
+ };
+ useThrottleFn(resize, 500)();
+ });
+
+ async function init() {
+ const serviceId = (selectorStore.currentService &&
selectorStore.currentService.id) || "";
+ const serviceInstanceId = (selectorStore.currentPod &&
selectorStore.currentPod.id) || "";
+ const type = continousProfilingStore.selectedStrategy.type;
+ const res = await taskTimelineStore.getContinousTaskList({
+ serviceId,
+ serviceInstanceId,
+ targets: type ? [type] : null,
+ triggerType: EBPFProfilingTriggerType.CONTINUOUS_PROFILING,
+ });
+ if (res.errors) {
+ ElMessage.error(res.errors);
+ return;
+ }
+ visTimeline();
+ }
+
+ function visTimeline() {
+ if (!timeline.value) {
+ return;
+ }
+ if (visGraph.value) {
+ visGraph.value.destroy();
+ }
+ const h = timeline.value.getBoundingClientRect().height;
+ const taskList = taskTimelineStore.taskList.map((d: EBPFTaskList, index:
number) => {
+ return {
+ id: index,
+ // content: d.targetType,
+ start: new Date(Number(d.taskStartTime)),
+ end: new Date(Number(d.taskStartTime + d.fixedTriggerDuration * 1000)),
+ data: d,
+ className: d.targetType,
+ };
+ });
+ const items: any = new DataSet(taskList);
+ const options: any = {
+ height: h,
+ width: "100%",
+ locale: "en",
+ groupHeightMode: "fitItems",
+ autoResize: false,
+ tooltip: {
+ overflowMethod: "cap",
+ template(item: EBPFTaskList | any) {
+ const data = item.data || {};
+ const end = data.taskStartTime ? visDate(data.taskStartTime +
data.fixedTriggerDuration * 1000) : "";
+
+ let tmp = `
+ <div>Task ID: ${data.taskId || ""}</div>
+ <div>Service Name: ${data.serviceName || ""}</div>
+ <div>Service Instance Name: ${data.serviceInstanceName || ""}</div>
+ <div>Service Process Name: ${data.processName || ""}</div>
+ <div>Target Type: ${data.targetType || ""}</div>
+ <div>Trigger Type: ${data.triggerType || ""}</div>
+ <div>Start Time: ${data.taskStartTime ? visDate(data.taskStartTime)
: ""}</div>
+ <div>End Time: ${end}</div>
+ <div>Process Labels: ${data.processLabels.join("; ") || ""}</div>`;
+ let str = "";
+ for (const item of data.continuousProfilingCauses || []) {
+ str += `<div>${item.type}:
${getURI(item.uri)}${item.uri.threshold}>=${item.uri.current}</div>`;
+ }
+ return tmp + str;
+ },
+ },
+ };
+ visGraph.value = new Timeline(timeline.value, items, options);
+ visGraph.value.on("select", async (properties: { items: number[] }) => {
+ dashboardStore.selectWidget(props.data);
+ const index = properties.items[0];
+ const task = taskTimelineStore.taskList[index];
+
+ await taskTimelineStore.setSelectedTask(task);
+ await taskTimelineStore.getGraphData();
+ });
+ }
+
+ function getURI(uri: { uriRegex: string; uriPath: string }) {
+ return uri ? `(${uri.uriRegex || ""} | ${uri.uriPath || ""})` : "";
+ }
+
+ function resize() {
+ const observer = new ResizeObserver((entries) => {
+ const entry = entries[0];
+ const cr = entry.contentRect;
+ if (Math.abs(cr.width - oldVal.value.width) < 3 && Math.abs(cr.height -
oldVal.value.height) < 3) {
+ return;
+ }
+ visTimeline();
+ oldVal.value = { width: cr.width, height: cr.height };
+ });
+ if (timeline.value) {
+ observer.observe(timeline.value);
+ }
+ }
+ onUnmounted(() => {
+ if (visGraph.value) {
+ visGraph.value.destroy();
+ }
+ taskTimelineStore.setTaskList([]);
+ });
+ watch(
+ () => selectorStore.currentPod,
+ () => {
+ init();
+ },
+ );
+</script>
+<style lang="scss" scoped>
+ .task-timeline {
+ width: calc(100% - 5px);
+ margin: 0 5px 5px 0;
+ height: 200px;
+ }
+
+ .message {
+ max-width: 400px;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ }
+</style>