feat: Implement task timeline and policy list widget for continous profiling (#280)

This commit is contained in:
Fine0830 2023-06-12 16:17:38 +08:00 committed by GitHub
parent 7738695601
commit 22db68646c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 2088 additions and 45 deletions

View File

@ -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. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. --> 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 t="1684376918107" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7954">
<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> <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.717637 65.25112 779.993149 65.25112zM869.404528 735.385696c0 73.992201-60.07319 133.972271-134.084834 133.972271L288.679283 869.357967c-74.096579 0-134.063345-59.98007-134.063345-133.972271L154.615938 288.61328c0-73.984015 59.966767-134.057205 134.063345-134.057205l446.639386 0c74.011644 0 134.084834 60.07319 134.084834 134.057205L869.403504 735.385696zM511.957533 243.967454l268.035616 0 0 89.319282 0 268.035616-89.326445 0-44.645826 0 0-44.673455 89.298815 0L735.319693 288.61328l-268.014126 0 0 89.326445-44.652989 0 0-133.971247L511.957533 243.968477z" p-id="7955"></path>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -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 0 0 10.66632-10.66632v-213.3264a42.66528 42.66528 0 0 0-42.66528-42.66528h-85.33056a42.66528 42.66528 0 0 0-42.66528 42.66528v213.3264a10.66632 10.66632 0 0 0 10.66632 10.66632zM970.635121 885.33784a10.66632 10.66632 0 0 0 10.66632-10.66632v-511.983361a42.66528 42.66528 0 0 0-42.66528-42.66528h-85.33056a42.66528 42.66528 0 0 0-42.66528 42.66528v511.983361a10.66632 10.66632 0 0 0 10.66632 10.66632z" p-id="12208"></path><path d="M149.32848 576.01456a85.33056 85.33056 0 0 0 85.33056-85.330561 84.51992 84.51992 0 0 0-4.266528-25.599168l135.120942-112.636339a83.410622 83.410622 0 0 0 104.273945-19.626029l106.6632 35.582844A85.33056 85.33056 0 0 0 746.642401 362.688159a83.79461 83.79461 0 0 0-9.85568-38.910735l140.240776-163.621349A85.074568 85.074568 0 1 0 831.972961 85.363839a83.709279 83.709279 0 0 0 3.967871 24.361875L688.190967 282.136111a82.429321 82.429321 0 0 0-91.346364 25.300511l-106.663201-35.540179A85.117234 85.117234 0 1 0 324.256128 302.956767L189.263182 415.763768A84.263928 84.263928 0 0 0 149.32848 405.353439a85.33056 85.33056 0 0 0 0 170.661121zM981.301441 938.66944H42.66528a43.347925 43.347925 0 0 0-42.66528 42.66528 42.66528 42.66528 0 0 0 42.66528 42.66528h938.636161a42.66528 42.66528 0 0 0 42.66528-42.66528 43.305259 43.305259 0 0 0-42.66528-42.66528z" p-id="12209"></path>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

17
src/assets/icons/edit.svg Normal file
View File

@ -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-32-32-32z" fill="#666666" p-id="2409"></path><path d="M405.333333 484.266667l-32 125.866666c-2.133333 10.666667 0 23.466667 8.533334 29.866667 6.4 6.4 14.933333 8.533333 23.466666 8.533333h8.533334l125.866666-32c6.4-2.133333 10.666667-4.266667 14.933334-8.533333l300.8-300.8c38.4-38.4 38.4-102.4 0-140.8-38.4-38.4-102.4-38.4-140.8 0L413.866667 469.333333c-4.266667 4.266667-6.4 8.533333-8.533334 14.933334z m59.733334 23.466666L761.6 213.333333c12.8-12.8 36.266667-12.8 49.066667 0 12.8 12.8 12.8 36.266667 0 49.066667L516.266667 558.933333l-66.133334 17.066667 14.933334-68.266667z" p-id="2410"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -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. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. --> 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.641q-0.328-0.234-0.094-0.656l2.016-3.469q0.188-0.328 0.609-0.188l2.484 0.984q0.984-0.703 1.688-0.984l0.375-2.625q0.094-0.422 0.469-0.422h4.031q0.375 0 0.469 0.422l0.375 2.625q0.891 0.375 1.688 0.984l2.484-0.984q0.422-0.141 0.609 0.188l2.016 3.469q0.234 0.422-0.094 0.656l-2.109 1.641q0.047 0.328 0.047 0.984t-0.047 0.984z"></path> <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.641q-0.328-0.234-0.094-0.656l2.016-3.469q0.188-0.328 0.609-0.188l2.484 0.984q0.984-0.703 1.688-0.984l0.375-2.625q0.094-0.422 0.469-0.422h4.031q0.375 0 0.469 0.422l0.375 2.625q0.891 0.375 1.688 0.984l2.484-0.984q0.422-0.141 0.609 0.188l2.016 3.469q0.234 0.422-0.094 0.656l-2.109 1.641q0.047 0.328 0.047 0.984t-0.047 0.984z"></path>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -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-30-13.4-30-30s13.4-30 30-30h590c16.6 0 30 13.4 30 30s-13.4 30-30 30zM771.4 457H347.6c-14.2 0-25.6-11.5-25.6-25.6v-8.7c0-14.2 11.5-25.6 25.6-25.6h423.7c14.2 0 25.6 11.5 25.6 25.6v8.7c0.1 14.1-11.4 25.6-25.5 25.6z" p-id="2709"></path><path d="M942 625H352c-16.6 0-30-13.4-30-30s13.4-30 30-30h590c16.6 0 30 13.4 30 30s-13.4 30-30 30zM771.4 799H347.6c-14.2 0-25.6-11.5-25.6-25.6v-8.7c0-14.2 11.5-25.6 25.6-25.6h423.7c14.2 0 25.6 11.5 25.6 25.6v8.7c0.1 14.1-11.4 25.6-25.5 25.6z" p-id="2710"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -27,7 +27,13 @@ limitations under the License. -->
:remote-method="remoteMethod" :remote-method="remoteMethod"
:filterable="filterable" :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-option>
</el-select> </el-select>
</template> </template>

View File

@ -33,9 +33,10 @@ export const createEBPFTask = {
}`, }`,
}; };
export const queryEBPFTasks = { export const queryEBPFTasks = {
variable: "$serviceId: ID, $serviceInstanceId: ID, $targets: [EBPFProfilingTargetType!]", variable:
"$serviceId: ID, $serviceInstanceId: ID, $targets: [EBPFProfilingTargetType!], $triggerType: EBPFProfilingTriggerType",
query: ` query: `
queryEBPFTasks: queryEBPFProfilingTasks(serviceId: $serviceId, serviceInstanceId: $serviceInstanceId, targets: $targets) { queryEBPFTasks: queryEBPFProfilingTasks(serviceId: $serviceId, serviceInstanceId: $serviceInstanceId, targets: $targets, triggerType: $triggerType) {
taskId taskId
serviceName serviceName
serviceId serviceId
@ -111,3 +112,26 @@ export const keepNetworkProfiling = {
errorReason 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
}
}`,
};

View File

@ -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
}
`,
};

View File

@ -23,6 +23,7 @@ import {
analysisEBPFResult, analysisEBPFResult,
createNetworkProfiling, createNetworkProfiling,
keepNetworkProfiling, keepNetworkProfiling,
monitoringInstances,
} from "../fragments/ebpf"; } from "../fragments/ebpf";
export const getCreateTaskData = `query queryCreateTaskData(${queryCreateTaskData.variable}) {${queryCreateTaskData.query}}`; 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 newNetworkProfiling = `mutation createNetworkProfiling(${createNetworkProfiling.variable}) {${createNetworkProfiling.query}}`;
export const aliveNetworkProfiling = `mutation keepNetworkProfiling(${keepNetworkProfiling.variable}) {${keepNetworkProfiling.query}}`; export const aliveNetworkProfiling = `mutation keepNetworkProfiling(${keepNetworkProfiling.variable}) {${keepNetworkProfiling.query}}`;
export const getMonitoringInstances = `query continuousProfilingMonitoringInstances(${monitoringInstances.variable}) {${monitoringInstances.query}}`;

View File

@ -21,6 +21,8 @@ import {
GetProfileTaskSegmentList, GetProfileTaskSegmentList,
GetProfileAnalyze, GetProfileAnalyze,
GetProfileTaskLogs, GetProfileTaskLogs,
GetStrategyList,
EditStrategy,
} from "../fragments/profile"; } from "../fragments/profile";
export const saveProfileTask = `mutation createProfileTask(${CreateProfileTask.variable}) {${CreateProfileTask.query}}`; 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 getProfileAnalyze = `query getProfileAnalyze(${GetProfileAnalyze.variable}) {${GetProfileAnalyze.query}}`;
export const getProfileTaskLogs = `query profileTaskLogs(${GetProfileTaskLogs.variable}) {${GetProfileTaskLogs.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}}`;

View File

@ -323,6 +323,7 @@ const msg = {
keywordsOfContentLogTips: "Current storage of SkyWalking OAP server does not support this.", keywordsOfContentLogTips: "Current storage of SkyWalking OAP server does not support this.",
setEvent: "Set Event", setEvent: "Set Event",
viewAttributes: "View", viewAttributes: "View",
attributes: "Attributes",
serviceEvents: "Service Events", serviceEvents: "Service Events",
select: "Select", select: "Select",
eventID: "Event ID", eventID: "Event ID",
@ -388,6 +389,19 @@ const msg = {
elasticsearch: "Elasticsearch", elasticsearch: "Elasticsearch",
mq: "MQ", mq: "MQ",
rabbitMQ: "RabbitMQ", 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", detailLabel: "Detail Label",
summary: "Summary", summary: "Summary",
detail: "Detail", detail: "Detail",

View File

@ -387,6 +387,20 @@ const msg = {
elasticsearch: "Elasticsearch", elasticsearch: "Elasticsearch",
mq: "MQ", mq: "MQ",
rabbitMQ: "RabbitMQ", 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", detailLabel: "Detail Label",
summary: "Summary", summary: "Summary",
detail: "Detail", detail: "Detail",

View File

@ -385,6 +385,20 @@ const msg = {
elasticsearch: "Elasticsearch", elasticsearch: "Elasticsearch",
mq: "消息队列", mq: "消息队列",
rabbitMQ: "RabbitMQ", rabbitMQ: "RabbitMQ",
save: "保存",
editStrategy: "编辑策略",
policyList: "策略列表",
targetTypes: "目标类型",
monitorType: "监视器类型",
count: "总数",
threshold: "阈值",
uriRegex: "URI规则",
uriList: "URI列表",
processes: "进程",
attributes: "属性",
monitorInstances: "监视实例",
processDashboards: "进程仪表板",
instanceDashboards: "实例仪表板",
detailLabel: "详细标签", detailLabel: "详细标签",
summary: "概括", summary: "概括",
detail: "详细", detail: "详细",

View File

@ -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: "", path: "",
redirect: "/dashboard/:layerId/:entity/:serviceId/:podId/:destServiceId/:destPodId/:name", redirect: "/dashboard/:layerId/:entity/:serviceId/:podId/:destServiceId/:destPodId/:name",

View File

@ -38,4 +38,18 @@ export const TimeRangeConfig = {
text: "text", 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",
}

View File

@ -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);
}

View File

@ -92,6 +92,7 @@ export const dashboardStore = defineStore({
metricTypes: [""], metricTypes: [""],
metrics: [""], metrics: [""],
}; };
if (type === "Widget") { if (type === "Widget") {
newItem.metricMode = MetricModes.Expression; newItem.metricMode = MetricModes.Expression;
} }

View File

@ -20,6 +20,7 @@ import type { EBPFTaskCreationRequest, EBPFProfilingSchedule, EBPFTaskList, Anal
import { store } from "@/store"; import { store } from "@/store";
import graphql from "@/graphql"; import graphql from "@/graphql";
import type { AxiosResponse } from "axios"; import type { AxiosResponse } from "axios";
import { EBPFProfilingTriggerType } from "../data";
interface EbpfState { interface EbpfState {
taskList: Array<Recordable<EBPFTaskList>>; taskList: Array<Recordable<EBPFTaskList>>;
eBPFSchedules: EBPFProfilingSchedule[]; eBPFSchedules: EBPFProfilingSchedule[];
@ -27,7 +28,7 @@ interface EbpfState {
analyzeTrees: AnalyzationTrees[]; analyzeTrees: AnalyzationTrees[];
labels: Option[]; labels: Option[];
couldProfiling: boolean; couldProfiling: boolean;
tip: string; ebpfTips: string;
selectedTask: Recordable<EBPFTaskList>; selectedTask: Recordable<EBPFTaskList>;
aggregateType: string; aggregateType: string;
} }
@ -41,7 +42,7 @@ export const ebpfStore = defineStore({
analyzeTrees: [], analyzeTrees: [],
labels: [{ value: "", label: "" }], labels: [{ value: "", label: "" }],
couldProfiling: false, couldProfiling: false,
tip: "", ebpfTips: "",
selectedTask: {}, selectedTask: {},
aggregateType: "COUNT", aggregateType: "COUNT",
}), }),
@ -77,6 +78,7 @@ export const ebpfStore = defineStore({
this.getTaskList({ this.getTaskList({
serviceId: param.serviceId, serviceId: param.serviceId,
targets: ["ON_CPU", "OFF_CPU"], targets: ["ON_CPU", "OFF_CPU"],
triggerType: EBPFProfilingTriggerType.FIXED_TIME,
}); });
return res.data; return res.data;
}, },
@ -86,7 +88,7 @@ export const ebpfStore = defineStore({
} }
const res: AxiosResponse = await graphql.query("getEBPFTasks").params(params); const res: AxiosResponse = await graphql.query("getEBPFTasks").params(params);
this.tip = ""; this.ebpfTips = "";
if (res.data.errors) { if (res.data.errors) {
return res.data; return res.data;
} }
@ -103,13 +105,14 @@ export const ebpfStore = defineStore({
if (!params.taskId) { if (!params.taskId) {
return new Promise((resolve) => resolve({})); return new Promise((resolve) => resolve({}));
} }
const res: AxiosResponse = await graphql.query("getEBPFSchedules").params({ ...params }); const res: AxiosResponse = await graphql.query("getEBPFSchedules").params({ ...params });
if (res.data.errors) { if (res.data.errors) {
this.eBPFSchedules = []; this.eBPFSchedules = [];
return res.data; return res.data;
} }
this.tip = ""; this.ebpfTips = "";
const { eBPFSchedules } = res.data.data; const { eBPFSchedules } = res.data.data;
this.eBPFSchedules = eBPFSchedules; this.eBPFSchedules = eBPFSchedules;
@ -138,7 +141,7 @@ export const ebpfStore = defineStore({
return res.data; return res.data;
} }
const { analysisEBPFResult } = res.data.data; const { analysisEBPFResult } = res.data.data;
this.tip = analysisEBPFResult.tip; this.ebpfTips = analysisEBPFResult.tip;
if (!analysisEBPFResult) { if (!analysisEBPFResult) {
this.analyzeTrees = []; this.analyzeTrees = [];
return res.data; return res.data;

View File

@ -65,6 +65,12 @@ export const networkProfilingStore = defineStore({
setLink(link: Call) { setLink(link: Call) {
this.call = link; this.call = link;
}, },
seNodes(nodes: Node[]) {
this.nodes = nodes;
},
setLinks(links: Call[]) {
this.calls = links;
},
setMetricsLayout(layout: LayoutConfig[]) { setMetricsLayout(layout: LayoutConfig[]) {
this.metricsLayout = layout; this.metricsLayout = layout;
}, },

View File

@ -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);
}

View File

@ -212,3 +212,6 @@ div.vis-tooltip {
div:has(> a.menu-title) { div:has(> a.menu-title) {
display: none; display: none;
} }
.el-input-number .el-input__inner {
text-align: left !important;
}

45
src/types/continous-profiling.d.ts vendored Normal file
View File

@ -0,0 +1,45 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export interface StrategyItem {
type: string;
checkItems: CheckItems[];
}
export type CheckItems = {
type: string;
threshold: string;
period: number;
count: number;
uriList?: string[];
uriRegex?: string;
};
export interface MonitorInstance {
id: string;
name: string;
attributes: { name: string; value: string }[];
triggeredCount: number;
lastTriggerTimestamp: number;
processes: MonitorProcess[];
}
interface MonitorProcess {
id: string;
name: string;
detectType: string;
labels: string[];
lastTriggerTimestamp: number;
triggeredCount: number;
}

19
src/types/ebpf.d.ts vendored
View File

@ -28,12 +28,31 @@ export interface EBPFTaskList {
taskId: string; taskId: string;
serviceName: string; serviceName: string;
serviceId: string; serviceId: string;
serviceInstanceId: string;
serviceInstanceName: string;
processId: string;
processName: string;
processLabels: string[]; processLabels: string[];
taskStartTime: number; taskStartTime: number;
fixedTriggerDuration: number; fixedTriggerDuration: number;
targetType: string; targetType: string;
createTime: number; createTime: number;
triggerType: string; 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 { export interface EBPFProfilingSchedule {

View File

@ -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>

View File

@ -21,6 +21,7 @@ import Topology from "./Topology.vue";
import Event from "./Event.vue"; import Event from "./Event.vue";
import TimeRange from "./TimeRange.vue"; import TimeRange from "./TimeRange.vue";
import ThirdPartyApp from "./ThirdPartyApp.vue"; import ThirdPartyApp from "./ThirdPartyApp.vue";
import ContinuousProfiling from "./ContinuousProfiling.vue";
export default { export default {
Text, Text,
@ -29,4 +30,5 @@ export default {
Event, Event,
TimeRange, TimeRange,
ThirdPartyApp, ThirdPartyApp,
ContinuousProfiling,
}; };

View File

@ -28,7 +28,7 @@ limitations under the License. -->
" "
/> />
</div> </div>
<div class="item mb-10" v-if="hasLabel"> <div class="item mb-10" v-if="hasLabel || isExpression">
<span class="label">{{ t("labels") }}</span> <span class="label">{{ t("labels") }}</span>
<el-input <el-input
class="input" class="input"
@ -70,7 +70,7 @@ limitations under the License. -->
" "
/> />
</div> </div>
<div class="item mb-10" v-show="isExec"> <div class="item mb-10" v-show="!isExpression">
<span class="label">{{ t("aggregation") }}</span> <span class="label">{{ t("aggregation") }}</span>
<SelectSingle <SelectSingle
:value="currentMetric.calculation" :value="currentMetric.calculation"
@ -111,7 +111,7 @@ limitations under the License. -->
import { SortOrder, CalculationOpts, MetricModes } from "../../../data"; import { SortOrder, CalculationOpts, MetricModes } from "../../../data";
import { useDashboardStore } from "@/store/modules/dashboard"; import { useDashboardStore } from "@/store/modules/dashboard";
import type { MetricConfigOpt } from "@/types/dashboard"; import type { MetricConfigOpt } from "@/types/dashboard";
import { ListChartTypes, ProtocolTypes, ExpressionResultType } from "../../../data"; import { ListChartTypes, ProtocolTypes } from "../../../data";
/*global defineEmits, defineProps */ /*global defineEmits, defineProps */
const props = defineProps({ const props = defineProps({
@ -137,11 +137,7 @@ limitations under the License. -->
const graph = dashboardStore.selectedGrid.graph || {}; const graph = dashboardStore.selectedGrid.graph || {};
return ( return (
ListChartTypes.includes(graph.type) || ListChartTypes.includes(graph.type) ||
[ [ProtocolTypes.ReadLabeledMetricsValues, ProtocolTypes.ReadMetricsValues].includes(metricType.value)
ProtocolTypes.ReadLabeledMetricsValues,
ProtocolTypes.ReadMetricsValues,
ExpressionResultType.TIME_SERIES_VALUES,
].includes(metricType.value)
); );
}); });
const isList = computed(() => { const isList = computed(() => {
@ -153,7 +149,7 @@ limitations under the License. -->
metricTypes.value[props.index], metricTypes.value[props.index],
), ),
); );
const isExec = computed(() => dashboardStore.selectedGrid.metricMode === MetricModes.General);
function updateConfig(index: number, param: { [key: string]: string }) { function updateConfig(index: number, param: { [key: string]: string }) {
const key = Object.keys(param)[0]; const key = Object.keys(param)[0];
if (!key) { if (!key) {

View File

@ -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>

View File

@ -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>

View File

@ -25,8 +25,10 @@ import Ebpf from "./Ebpf.vue";
import DemandLog from "./DemandLog.vue"; import DemandLog from "./DemandLog.vue";
import Event from "./Event.vue"; import Event from "./Event.vue";
import NetworkProfiling from "./NetworkProfiling.vue"; import NetworkProfiling from "./NetworkProfiling.vue";
import ContinuousProfiling from "./ContinuousProfiling.vue";
import TimeRange from "./TimeRange.vue"; import TimeRange from "./TimeRange.vue";
import ThirdPartyApp from "./ThirdPartyApp.vue"; import ThirdPartyApp from "./ThirdPartyApp.vue";
import TaskTimeline from "./TaskTimeline.vue";
export default { export default {
Tab, Tab,
@ -40,6 +42,8 @@ export default {
DemandLog, DemandLog,
Event, Event,
NetworkProfiling, NetworkProfiling,
ContinuousProfiling,
TimeRange, TimeRange,
ThirdPartyApp, ThirdPartyApp,
TaskTimeline,
}; };

View File

@ -24,8 +24,10 @@ import Ebpf from "./Ebpf.vue";
import DemandLog from "./DemandLog.vue"; import DemandLog from "./DemandLog.vue";
import Event from "./Event.vue"; import Event from "./Event.vue";
import NetworkProfiling from "./NetworkProfiling.vue"; import NetworkProfiling from "./NetworkProfiling.vue";
import ContinuousProfiling from "./ContinuousProfiling.vue";
import TimeRange from "./TimeRange.vue"; import TimeRange from "./TimeRange.vue";
import ThirdPartyApp from "./ThirdPartyApp.vue"; import ThirdPartyApp from "./ThirdPartyApp.vue";
import TaskTimeline from "./TaskTimeline.vue";
export default { export default {
Widget, Widget,
@ -40,4 +42,6 @@ export default {
NetworkProfiling, NetworkProfiling,
TimeRange, TimeRange,
ThirdPartyApp, ThirdPartyApp,
ContinuousProfiling,
TaskTimeline,
}; };

View File

@ -176,6 +176,7 @@ export const EntityType = [
}, },
{ value: "EndpointRelation", label: "Endpoint Relation", key: 4 }, { value: "EndpointRelation", label: "Endpoint Relation", key: 4 },
{ value: "ProcessRelation", label: "Process Relation", key: 5 }, { value: "ProcessRelation", label: "Process Relation", key: 5 },
{ value: "Process", label: "Process", key: 6 },
]; ];
export const ListEntity: any = { export const ListEntity: any = {
InstanceList: EntityType[3].value, InstanceList: EntityType[3].value,
@ -203,6 +204,7 @@ export const ServiceTools = [
{ name: "merge", content: "Add Trace", id: "addTrace" }, { name: "merge", content: "Add Trace", id: "addTrace" },
{ name: "timeline", content: "Add Trace Profiling", id: "addProfile" }, { name: "timeline", content: "Add Trace Profiling", id: "addProfile" },
{ name: "insert_chart", content: "Add eBPF Profiling", id: "addEbpf" }, { 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: "assignment", content: "Add Log", id: "addLog" },
{ name: "demand", content: "Add On Demand Log", id: "addDemandLog" }, { name: "demand", content: "Add On Demand Log", id: "addDemandLog" },
{ name: "event", content: "Add Event", id: "addEvent" }, { name: "event", content: "Add Event", id: "addEvent" },
@ -234,10 +236,16 @@ export const EndpointTools = [
{ name: "add_iframe", content: "Add Iframe", id: "addIframe" }, { name: "add_iframe", content: "Add Iframe", id: "addIframe" },
]; ];
export const ProcessTools = [ 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: "playlist_add", content: "Add Widget", id: "addWidget" },
{ name: "all_inbox", content: "Add Tabs", id: "addTab" }, { name: "all_inbox", content: "Add Tabs", id: "addTab" },
{ name: "library_books", content: "Add Text", id: "addText" }, { 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" }, { name: "add_iframe", content: "Add Iframe", id: "addIframe" },
]; ];
export const ServiceRelationTools = [ export const ServiceRelationTools = [

View File

@ -38,6 +38,7 @@
color: #409eff; color: #409eff;
display: inline-block; display: inline-block;
width: 100%; width: 100%;
text-decoration: underline;
} }
.search { .search {

View File

@ -27,7 +27,7 @@ limitations under the License. -->
class="selectors" class="selectors"
/> />
</div> </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"> <span class="label">
{{ ["EndpointRelation", "Endpoint"].includes(dashboardStore.entity) ? "$Endpoint" : "$ServiceInstance" }} {{ ["EndpointRelation", "Endpoint"].includes(dashboardStore.entity) ? "$Endpoint" : "$ServiceInstance" }}
</span> </span>
@ -42,7 +42,7 @@ limitations under the License. -->
:isRemote="['EndpointRelation', 'Endpoint'].includes(dashboardStore.entity)" :isRemote="['EndpointRelation', 'Endpoint'].includes(dashboardStore.entity)"
/> />
</div> </div>
<div class="selectors-item" v-if="key === 5"> <div class="selectors-item" v-if="key === 5 || key === 6">
<span class="label"> $Process </span> <span class="label"> $Process </span>
<Selector <Selector
v-model="states.currentProcess" v-model="states.currentProcess"
@ -145,6 +145,7 @@ limitations under the License. -->
InstanceRelationTools, InstanceRelationTools,
ServiceRelationTools, ServiceRelationTools,
ProcessTools, ProcessTools,
ProcessRelationTools,
} from "../data"; } from "../data";
import { useSelectorStore } from "@/store/modules/selectors"; import { useSelectorStore } from "@/store/modules/selectors";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
@ -216,10 +217,11 @@ limitations under the License. -->
EntityType[5].value, EntityType[5].value,
EntityType[6].value, EntityType[6].value,
EntityType[7].value, EntityType[7].value,
EntityType[8].value,
].includes(String(params.entity)) ].includes(String(params.entity))
) { ) {
setSourceSelector(); 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; return;
} }
setDestSelector(); setDestSelector();
@ -317,6 +319,7 @@ limitations under the License. -->
EntityType[5].value, EntityType[5].value,
EntityType[6].value, EntityType[6].value,
EntityType[7].value, EntityType[7].value,
EntityType[8].value,
].includes(dashboardStore.entity) ].includes(dashboardStore.entity)
) { ) {
await fetchPods(e, selectorStore.currentService.id, true); await fetchPods(e, selectorStore.currentService.id, true);
@ -337,11 +340,8 @@ limitations under the License. -->
selectorStore.setCurrentPod(null); selectorStore.setCurrentPod(null);
states.currentPod = ""; states.currentPod = "";
states.currentProcess = ""; states.currentProcess = "";
if (dashboardStore.entity === EntityType[7].value) { const e = dashboardStore.entity === EntityType[7].value ? EntityType[8].value : dashboardStore.entity;
fetchPods("Process", selectorStore.currentService.id, true); fetchPods(e, selectorStore.currentService.id, true);
} else {
fetchPods(dashboardStore.entity, selectorStore.currentService.id, true);
}
} else { } else {
selectorStore.setCurrentService(null); selectorStore.setCurrentService(null);
} }
@ -362,7 +362,7 @@ limitations under the License. -->
async function changePods(pod: Option[]) { async function changePods(pod: Option[]) {
selectorStore.setCurrentPod(pod[0] || null); selectorStore.setCurrentPod(pod[0] || null);
if (dashboardStore.entity === EntityType[7].value) { if ([EntityType[7].value, EntityType[8].value].includes(dashboardStore.entity)) {
selectorStore.setCurrentProcess(null); selectorStore.setCurrentProcess(null);
states.currentProcess = ""; states.currentProcess = "";
fetchProcess(true); fetchProcess(true);
@ -446,12 +446,18 @@ limitations under the License. -->
case "addNetworkProfiling": case "addNetworkProfiling":
dashboardStore.addTabControls("NetworkProfiling"); dashboardStore.addTabControls("NetworkProfiling");
break; break;
case "addContinuousProfiling":
dashboardStore.addTabControls("ContinuousProfiling");
break;
case "addTimeRange": case "addTimeRange":
dashboardStore.addTabControls("TimeRange"); dashboardStore.addTabControls("TimeRange");
break; break;
case "addIframe": case "addIframe":
dashboardStore.addTabControls("ThirdPartyApp"); dashboardStore.addTabControls("ThirdPartyApp");
break; break;
case "addTaskTimeline":
dashboardStore.addTabControls("TaskTimeline");
break;
default: default:
ElMessage.info("Don't support this control"); ElMessage.info("Don't support this control");
break; break;
@ -493,12 +499,18 @@ limitations under the License. -->
case "addNetworkProfiling": case "addNetworkProfiling":
dashboardStore.addControl("NetworkProfiling"); dashboardStore.addControl("NetworkProfiling");
break; break;
case "addContinuousProfiling":
dashboardStore.addControl("ContinuousProfiling");
break;
case "addTimeRange": case "addTimeRange":
dashboardStore.addControl("TimeRange"); dashboardStore.addControl("TimeRange");
break; break;
case "addIframe": case "addIframe":
dashboardStore.addControl("ThirdPartyApp"); dashboardStore.addControl("ThirdPartyApp");
break; break;
case "addTaskTimeline":
dashboardStore.addControl("TaskTimeline");
break;
default: default:
dashboardStore.addControl("Widget"); dashboardStore.addControl("Widget");
} }
@ -560,7 +572,7 @@ limitations under the License. -->
await fetchPods(EntityType[5].value, serviceId, setPod, param); await fetchPods(EntityType[5].value, serviceId, setPod, param);
resp = await fetchDestProcess(setPod); resp = await fetchDestProcess(setPod);
break; break;
case "Process": case EntityType[8].value:
await fetchPods(EntityType[3].value, serviceId, setPod, param); await fetchPods(EntityType[3].value, serviceId, setPod, param);
resp = await fetchProcess(setPod); resp = await fetchProcess(setPod);
break; break;
@ -691,6 +703,9 @@ limitations under the License. -->
toolIcons.value = EndpointRelationTools; toolIcons.value = EndpointRelationTools;
break; break;
case EntityType[7].value: case EntityType[7].value:
toolIcons.value = ProcessRelationTools;
break;
case EntityType[8].value:
toolIcons.value = ProcessTools; toolIcons.value = ProcessTools;
break; break;
default: default:
@ -722,8 +737,8 @@ limitations under the License. -->
<style lang="scss" scoped> <style lang="scss" scoped>
.dashboard-tool { .dashboard-tool {
text-align: right; text-align: right;
padding: 3px 5px 5px 5px; padding: 3px 5px 5px;
background: rgb(240, 242, 245); background: rgb(240 242 245);
border-bottom: 1px solid #dfe4e8; border-bottom: 1px solid #dfe4e8;
justify-content: space-between; justify-content: space-between;
} }

View File

@ -0,0 +1,51 @@
<!-- 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="flex-h content">
<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 { 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: () => ({}),
},
});
</script>
<style lang="scss" scoped>
.content {
height: calc(100% - 50px);
width: 100%;
}
.list {
height: 100%;
flex-grow: 2;
min-width: 600px;
overflow: hidden;
}
</style>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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" },
];

View File

@ -33,6 +33,7 @@ limitations under the License. -->
import { useDashboardStore } from "@/store/modules/dashboard"; import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
import { EntityType } from "../../data"; import { EntityType } from "../../data";
import { EBPFProfilingTriggerType } from "@/store/data";
/*global defineProps */ /*global defineProps */
const props = defineProps({ const props = defineProps({
@ -54,6 +55,7 @@ limitations under the License. -->
const res = await ebpfStore.getTaskList({ const res = await ebpfStore.getTaskList({
serviceId, serviceId,
targets: ["ON_CPU", "OFF_CPU"], targets: ["ON_CPU", "OFF_CPU"],
triggerType: EBPFProfilingTriggerType.FIXED_TIME,
}); });
if (res.errors) { if (res.errors) {

View File

@ -42,7 +42,7 @@ limitations under the License. -->
/> />
<el-popover placement="bottom" :width="680" trigger="click" :persistent="false"> <el-popover placement="bottom" :width="680" trigger="click" :persistent="false">
<template #reference> <template #reference>
<el-button type="primary" size="small"> <el-button size="small">
{{ t("processSelect") }} {{ t("processSelect") }}
</el-button> </el-button>
</template> </template>
@ -98,12 +98,21 @@ limitations under the License. -->
import type { Option } from "@/types/app"; import type { Option } from "@/types/app";
import { TableHeader, AggregateTypes } from "./data"; import { TableHeader, AggregateTypes } from "./data";
import { useEbpfStore } from "@/store/modules/ebpf"; import { useEbpfStore } from "@/store/modules/ebpf";
import { useContinousProfilingStore } from "@/store/modules/continous-profiling";
import type { EBPFProfilingSchedule, Process } from "@/types/ebpf"; import type { EBPFProfilingSchedule, Process } from "@/types/ebpf";
import { ElMessage, ElTable } from "element-plus"; import { ElMessage, ElTable } from "element-plus";
import { dateFormat } from "@/utils/dateFormat"; import { dateFormat } from "@/utils/dateFormat";
import { ComponentType } from "@/views/dashboard/related/continuous-profiling/data";
const { t } = useI18n(); 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 pageSize = 5;
const multipleTableRef = ref<InstanceType<typeof ElTable>>(); const multipleTableRef = ref<InstanceType<typeof ElTable>>();
const selectedProcesses = ref<string[]>([]); const selectedProcesses = ref<string[]>([]);

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. --> limitations under the License. -->
<template> <template>
<div id="graph-stack" ref="graph"> <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> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -23,12 +23,20 @@ limitations under the License. -->
import d3tip from "d3-tip"; import d3tip from "d3-tip";
import { flamegraph } from "d3-flame-graph"; import { flamegraph } from "d3-flame-graph";
import { useEbpfStore } from "@/store/modules/ebpf"; 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 type { StackElement } from "@/types/ebpf";
import { AggregateTypes } from "./data"; import { AggregateTypes } from "./data";
import "d3-flame-graph/dist/d3-flamegraph.css"; import "d3-flame-graph/dist/d3-flamegraph.css";
/*global Nullable*/ /*global Nullable, defineProps*/
const ebpfStore = useEbpfStore(); const props = defineProps({
type: {
type: String,
default: "",
},
});
const ebpfStore = props.type === ComponentType ? useContinousProfilingStore() : useEbpfStore();
const stackTree = ref<Nullable<StackElement>>(null); const stackTree = ref<Nullable<StackElement>>(null);
const selectStack = ref<Nullable<StackElement>>(null); const selectStack = ref<Nullable<StackElement>>(null);
const graph = ref<Nullable<HTMLDivElement>>(null); const graph = ref<Nullable<HTMLDivElement>>(null);
@ -90,7 +98,7 @@ limitations under the License. -->
.setColorMapper((d, originalColor) => (d.highlight ? "#6aff8f" : originalColor)); .setColorMapper((d, originalColor) => (d.highlight ? "#6aff8f" : originalColor));
const tip = (d3tip as any)() const tip = (d3tip as any)()
.attr("class", "d3-tip") .attr("class", "d3-tip")
.direction("w") .direction("s")
.html((d: { data: StackElement } & { parent: { data: StackElement } }) => { .html((d: { data: StackElement } & { parent: { data: StackElement } }) => {
const name = d.data.name.replace("<", "&lt;").replace(">", "&gt;"); const name = d.data.name.replace("<", "&lt;").replace(">", "&gt;");
const valStr = const valStr =
@ -111,7 +119,7 @@ limitations under the License. -->
}</div>`; }</div>`;
return `<div class="mb-5 name">Symbol: ${name}</div>${valStr}${rateOfParent}${rateOfRoot}`; return `<div class="mb-5 name">Symbol: ${name}</div>${valStr}${rateOfParent}${rateOfRoot}`;
}) })
.style("max-width", "500px"); .style("max-width", "400px");
flameChart.value.tooltip(tip); flameChart.value.tooltip(tip);
d3.select("#graph-stack").datum(stackTree.value).call(flameChart.value); d3.select("#graph-stack").datum(stackTree.value).call(flameChart.value);
} }

View File

@ -34,7 +34,7 @@ limitations under the License. -->
defineProps({ defineProps({
config: { config: {
type: Object as PropType<any>, type: Object as PropType<any>,
default: () => ({ graph: {} }), default: () => ({}),
}, },
}); });
const networkProfilingStore = useNetworkProfilingStore(); const networkProfilingStore = useNetworkProfilingStore();

View File

@ -19,7 +19,7 @@ limitations under the License. -->
<g class="hex-polygon"> <g class="hex-polygon">
<path :d="getHexPolygonVertices()" stroke="#D5DDF6" stroke-width="2" fill="none" /> <path :d="getHexPolygonVertices()" stroke="#D5DDF6" stroke-width="2" fill="none" />
<text :x="0" :y="radius - 15" fill="#000" text-anchor="middle"> <text :x="0" :y="radius - 15" fill="#000" text-anchor="middle">
{{ selectorStore.currentPod.label }} {{ selectorStore.currentPod && selectorStore.currentPod.label }}
</text> </text>
</g> </g>
<g class="nodes"> <g class="nodes">
@ -530,11 +530,11 @@ limitations under the License. -->
border-radius: 3px; border-radius: 3px;
position: absolute; position: absolute;
top: 10px; top: 10px;
right: 10px; right: 20px;
} }
.range { .range {
right: 50px; right: 60px;
} }
.topo-call { .topo-call {

View File

@ -77,6 +77,7 @@ limitations under the License. -->
import getLocalTime from "@/utils/localtime"; import getLocalTime from "@/utils/localtime";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
import NewTask from "./NewTask.vue"; import NewTask from "./NewTask.vue";
import { EBPFProfilingTriggerType } from "@/store/data";
/*global Nullable */ /*global Nullable */
const { t } = useI18n(); const { t } = useI18n();
@ -185,6 +186,7 @@ limitations under the License. -->
serviceId, serviceId,
serviceInstanceId, serviceInstanceId,
targets: ["NETWORK"], targets: ["NETWORK"],
triggerType: EBPFProfilingTriggerType.FIXED_TIME,
}); });
if (res.errors) { if (res.errors) {

View File

@ -0,0 +1,40 @@
<!-- 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="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>

View File

@ -0,0 +1,83 @@
<!-- 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" 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 { 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: () => ({}),
},
});
const { t } = useI18n();
const taskTimelineStore = useTaskTimelineStore();
const networkProfilingStore = useNetworkProfilingStore();
</script>
<style lang="scss" scoped>
.content {
width: 100%;
height: calc(100% - 30px);
flex-grow: 2;
min-width: 700px;
overflow: hidden;
position: relative;
}
.text {
width: 100%;
text-align: center;
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>

View File

@ -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>