mirror of
https://github.com/apache/skywalking-booster-ui.git
synced 2025-05-02 07:34:04 +00:00
feat: implement Async Profiling widget (#434)
This commit is contained in:
parent
e164d87209
commit
fb0817eed8
18
src/assets/icons/async_profiling.svg
Normal file
18
src/assets/icons/async_profiling.svg
Normal file
@ -0,0 +1,18 @@
|
||||
<!-- 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 class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M512 992c-83.2 0-166.4-19.2-243.2-64-89.6-51.2-160-134.4-204.8-230.4C25.6 601.6 19.2 486.4 51.2 390.4c25.6-102.4 89.6-192 172.8-256C307.2 70.4 409.6 38.4 518.4 38.4c19.2 0 32 12.8 32 32s-19.2 25.6-38.4 25.6c-89.6 0-179.2 32-256 83.2S128 313.6 108.8 403.2s-12.8 185.6 19.2 268.8c32 83.2 96 153.6 179.2 198.4 76.8 44.8 172.8 64 262.4 51.2 89.6-12.8 172.8-51.2 236.8-115.2s108.8-147.2 115.2-236.8c12.8-89.6-6.4-185.6-51.2-262.4-6.4-12.8-6.4-32 12.8-44.8 12.8-12.8 38.4-6.4 44.8 12.8 51.2 89.6 76.8 198.4 57.6 300.8-12.8 102.4-64 204.8-134.4 275.2-76.8 76.8-172.8 121.6-275.2 134.4-19.2 6.4-44.8 6.4-64 6.4z" p-id="8538"></path><path d="M512 480c-19.2 0-32-12.8-32-32V64c0-19.2 12.8-32 32-32s32 12.8 32 32v384c0 19.2-12.8 32-32 32z" p-id="8539"></path><path d="M512 608c-12.8 0-25.6 0-38.4-6.4-12.8-6.4-19.2-12.8-32-19.2-6.4-12.8-12.8-19.2-19.2-32-6.4-12.8-6.4-25.6-6.4-38.4 0-25.6 12.8-51.2 25.6-70.4 38.4-38.4 102.4-38.4 134.4 0 19.2 19.2 32 44.8 32 70.4 0 25.6-12.8 51.2-25.6 70.4-19.2 12.8-44.8 25.6-70.4 25.6z m0-128c-6.4 0-19.2 6.4-25.6 6.4 0 6.4-6.4 19.2-6.4 25.6v12.8c0 6.4 6.4 6.4 6.4 12.8 0 0 6.4 6.4 12.8 6.4 12.8 6.4 25.6 0 32-6.4 6.4-6.4 12.8-19.2 12.8-25.6 0-6.4-6.4-19.2-6.4-25.6-6.4 0-19.2-6.4-25.6-6.4z" p-id="8540"></path><path d="M512 800c-51.2 0-102.4-12.8-147.2-38.4-57.6-32-96-83.2-121.6-140.8-19.2-57.6-25.6-121.6-6.4-185.6 19.2-64 51.2-115.2 102.4-153.6C384 243.2 448 224 512 224c19.2 0 32 12.8 32 32s-12.8 32-32 32c-51.2 0-96 19.2-134.4 44.8-38.4 32-70.4 76.8-83.2 121.6s-6.4 96 12.8 140.8c19.2 44.8 51.2 83.2 96 108.8 44.8 25.6 89.6 32 140.8 25.6 51.2-6.4 96-32 128-64s57.6-83.2 64-128c6.4-51.2-6.4-96-25.6-140.8-12.8-12.8-6.4-32 6.4-38.4 12.8-6.4 32-6.4 44.8 12.8 32 57.6 44.8 121.6 38.4 179.2-6.4 64-38.4 121.6-83.2 166.4-44.8 44.8-102.4 76.8-166.4 83.2H512z"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 2.6 KiB |
80
src/graphql/fragments/async-profile.ts
Normal file
80
src/graphql/fragments/async-profile.ts
Normal file
@ -0,0 +1,80 @@
|
||||
/**
|
||||
* 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 GetAsyncTaskList = {
|
||||
variable: "$request: AsyncProfilerTaskListRequest!",
|
||||
query: `
|
||||
asyncTaskList: queryAsyncProfilerTaskList(request: $request) {
|
||||
errorReason
|
||||
tasks {
|
||||
id
|
||||
serviceId
|
||||
serviceInstanceIds
|
||||
createTime
|
||||
events
|
||||
duration
|
||||
execArgs
|
||||
}
|
||||
}
|
||||
`,
|
||||
};
|
||||
|
||||
export const GetAsyncProfileTaskProcess = {
|
||||
variable: "$taskId: String!",
|
||||
query: `
|
||||
taskProgress: queryAsyncProfilerTaskProgress(taskId: $taskId) {
|
||||
logs {
|
||||
id
|
||||
instanceId
|
||||
instanceName
|
||||
operationType
|
||||
operationTime
|
||||
}
|
||||
errorInstanceIds
|
||||
successInstanceIds
|
||||
}
|
||||
`,
|
||||
};
|
||||
|
||||
export const CreateAsyncProfileTask = {
|
||||
variable: "$asyncProfilerTaskCreationRequest: AsyncProfilerTaskCreationRequest!",
|
||||
query: `
|
||||
task: createAsyncProfilerTask(asyncProfilerTaskCreationRequest: $asyncProfilerTaskCreationRequest) {
|
||||
id
|
||||
errorReason
|
||||
code
|
||||
}
|
||||
`,
|
||||
};
|
||||
|
||||
export const GetAsyncProfileAnalyze = {
|
||||
variable: "$request: AsyncProfilerAnalyzationRequest!",
|
||||
query: `
|
||||
analysisResult: queryAsyncProfilerAnalyze(request: $request) {
|
||||
tree {
|
||||
type
|
||||
elements {
|
||||
id
|
||||
parentId
|
||||
symbol: codeSignature
|
||||
dumpCount: total
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
};
|
@ -28,6 +28,7 @@ import * as alarm from "./query/alarm";
|
||||
import * as event from "./query/event";
|
||||
import * as ebpf from "./query/ebpf";
|
||||
import * as demandLog from "./query/demand-log";
|
||||
import * as asyncProfile from "./query/async-profile";
|
||||
|
||||
const query: { [key: string]: string } = {
|
||||
...app,
|
||||
@ -41,6 +42,7 @@ const query: { [key: string]: string } = {
|
||||
...event,
|
||||
...ebpf,
|
||||
...demandLog,
|
||||
...asyncProfile,
|
||||
};
|
||||
class Graphql {
|
||||
private queryData = "";
|
||||
|
31
src/graphql/query/async-profile.ts
Normal file
31
src/graphql/query/async-profile.ts
Normal file
@ -0,0 +1,31 @@
|
||||
/**
|
||||
* 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 {
|
||||
GetAsyncTaskList,
|
||||
GetAsyncProfileTaskProcess,
|
||||
CreateAsyncProfileTask,
|
||||
GetAsyncProfileAnalyze,
|
||||
} from "../fragments/async-profile";
|
||||
|
||||
export const getAsyncTaskList = `query getAsyncTaskList(${GetAsyncTaskList.variable}) {${GetAsyncTaskList.query}}`;
|
||||
|
||||
export const getAsyncProfileTaskProcess = `query getAsyncProfileTaskProcess(${GetAsyncProfileTaskProcess.variable}) {${GetAsyncProfileTaskProcess.query}}`;
|
||||
|
||||
export const saveAsyncProfileTask = `mutation createAsyncProfileTask(${CreateAsyncProfileTask.variable}) {${CreateAsyncProfileTask.query}}`;
|
||||
|
||||
export const getAsyncProfileAnalyze = `query getAsyncProfileAnalyze(${GetAsyncProfileAnalyze.variable}) {${GetAsyncProfileAnalyze.query}}`;
|
@ -390,5 +390,10 @@ const msg = {
|
||||
mappingTip: "Notice: The mapping key is a Regex string, e.g. ^([0-9])$",
|
||||
valueDashboard: "Data Value Related Dashboard",
|
||||
viewValueDashboard: "View Dashboard",
|
||||
errorInstances: "Error Instances",
|
||||
successInstances: "Success Instances",
|
||||
profilingEvents: "Async Profiling Events",
|
||||
execArgs: "Exec Args",
|
||||
instances: "Instances",
|
||||
};
|
||||
export default msg;
|
||||
|
@ -390,5 +390,10 @@ const msg = {
|
||||
mappingTip: "Aviso: La clave de mapeo es una cadena Regex, p. ej. ^([0-9])$",
|
||||
valueDashboard: "Data Value Related Dashboard",
|
||||
viewValueDashboard: "View Dashboard",
|
||||
errorInstances: "Error Instances",
|
||||
successInstances: "Success Instances",
|
||||
profilingEvents: "Async Profiling Events",
|
||||
execArgs: "Exec Args",
|
||||
instances: "Instances",
|
||||
};
|
||||
export default msg;
|
||||
|
@ -388,5 +388,10 @@ const msg = {
|
||||
mappingTip: "注意: 映射键是一个正则表达式字符串,比如 ^([0-9])$",
|
||||
valueDashboard: "数据值相关的仪表板",
|
||||
viewValueDashboard: "查看仪表板",
|
||||
errorInstances: "错误的实例",
|
||||
successInstances: "成功的实例",
|
||||
profilingEvents: "异步分析事件",
|
||||
execArgs: "String任务扩展",
|
||||
instances: "实例",
|
||||
};
|
||||
export default msg;
|
||||
|
@ -47,6 +47,7 @@ export const ControlsTypes = [
|
||||
WidgetType.DemandLog,
|
||||
WidgetType.Ebpf,
|
||||
WidgetType.NetworkProfiling,
|
||||
WidgetType.AsyncProfiling,
|
||||
WidgetType.ThirdPartyApp,
|
||||
WidgetType.ContinuousProfiling,
|
||||
WidgetType.TaskTimeline,
|
||||
|
136
src/store/modules/async-profiling.ts
Normal file
136
src/store/modules/async-profiling.ts
Normal file
@ -0,0 +1,136 @@
|
||||
/**
|
||||
* 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 {
|
||||
AsyncProfilingTask,
|
||||
AsyncProfileTaskCreationRequest,
|
||||
AsyncProfilerStackElement,
|
||||
AsyncProfilerTaskProgress,
|
||||
} from "@/types/async-profiling";
|
||||
import { store } from "@/store";
|
||||
import graphql from "@/graphql";
|
||||
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||
import { useSelectorStore } from "@/store/modules/selectors";
|
||||
import type { AxiosResponse } from "axios";
|
||||
import type { Instance } from "@/types/selector";
|
||||
|
||||
interface AsyncProfilingState {
|
||||
taskList: Array<Recordable<AsyncProfilingTask>>;
|
||||
selectedTask: Recordable<AsyncProfilingTask>;
|
||||
taskProgress: Recordable<AsyncProfilerTaskProgress>;
|
||||
instances: Instance[];
|
||||
analyzeTrees: AsyncProfilerStackElement[];
|
||||
loadingTree: boolean;
|
||||
}
|
||||
|
||||
export const asyncProfilingStore = defineStore({
|
||||
id: "asyncProfiling",
|
||||
state: (): AsyncProfilingState => ({
|
||||
taskList: [],
|
||||
selectedTask: {},
|
||||
taskProgress: {},
|
||||
instances: [],
|
||||
analyzeTrees: [],
|
||||
loadingTree: false,
|
||||
}),
|
||||
actions: {
|
||||
setSelectedTask(task: Recordable<AsyncProfilingTask>) {
|
||||
this.selectedTask = task || {};
|
||||
},
|
||||
setAnalyzeTrees(tree: AsyncProfilerStackElement[]) {
|
||||
this.analyzeTrees = tree;
|
||||
},
|
||||
async getTaskList() {
|
||||
const { duration } = useAppStoreWithOut();
|
||||
const selectorStore = useSelectorStore();
|
||||
const res: AxiosResponse = await graphql.query("getAsyncTaskList").params({
|
||||
request: {
|
||||
startTime: duration.start.getTime(),
|
||||
endTime: duration.end.getTime(),
|
||||
serviceId: selectorStore.currentService.id,
|
||||
},
|
||||
});
|
||||
if (res.data.errors) {
|
||||
return res.data;
|
||||
}
|
||||
this.taskList = res.data.data.asyncTaskList.tasks || [];
|
||||
this.selectedTask = this.taskList[0] || {};
|
||||
this.setAnalyzeTrees([]);
|
||||
this.setSelectedTask(this.selectedTask);
|
||||
if (!this.taskList.length) {
|
||||
return res.data;
|
||||
}
|
||||
return res.data;
|
||||
},
|
||||
async getTaskLogs(param: { taskID: string }) {
|
||||
const res: AxiosResponse = await graphql.query("getAsyncProfileTaskProcess").params(param);
|
||||
|
||||
if (res.data.errors) {
|
||||
return res.data;
|
||||
}
|
||||
this.taskProgress = res.data.data.taskProgress;
|
||||
return res.data;
|
||||
},
|
||||
async getServiceInstances(param: { serviceId: string; isRelation: boolean }): Promise<Nullable<AxiosResponse>> {
|
||||
if (!param.serviceId) {
|
||||
return null;
|
||||
}
|
||||
const res: AxiosResponse = await graphql.query("queryInstances").params({
|
||||
serviceId: param.serviceId,
|
||||
duration: useAppStoreWithOut().durationTime,
|
||||
});
|
||||
if (!res.data.errors) {
|
||||
this.instances = res.data.data.pods || [];
|
||||
}
|
||||
return res.data;
|
||||
},
|
||||
async createTask(param: AsyncProfileTaskCreationRequest) {
|
||||
const res: AxiosResponse = await graphql
|
||||
.query("saveAsyncProfileTask")
|
||||
.params({ asyncProfilerTaskCreationRequest: param });
|
||||
|
||||
if (res.data.errors) {
|
||||
return res.data;
|
||||
}
|
||||
this.getTaskList();
|
||||
return res.data;
|
||||
},
|
||||
async getAsyncProfilingAnalyze(params: { taskId: string; instanceIds: Array<string>; eventType: string }) {
|
||||
if (!params.instanceIds.length) {
|
||||
return new Promise((resolve) => resolve({}));
|
||||
}
|
||||
this.loadingTree = true;
|
||||
const res: AxiosResponse = await graphql.query("getAsyncProfileAnalyze").params({ request: params });
|
||||
this.loadingTree = false;
|
||||
if (res.data.errors) {
|
||||
this.analyzeTrees = [];
|
||||
return res.data;
|
||||
}
|
||||
const { analysisResult } = res.data.data;
|
||||
if (!analysisResult) {
|
||||
this.analyzeTrees = [];
|
||||
return res.data;
|
||||
}
|
||||
this.analyzeTrees = [analysisResult.tree];
|
||||
return res.data;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export function useAsyncProfilingStore(): Recordable {
|
||||
return asyncProfilingStore(store);
|
||||
}
|
@ -139,6 +139,10 @@
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.mb-20 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.mr-5 {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
68
src/types/async-profiling.ts
Normal file
68
src/types/async-profiling.ts
Normal file
@ -0,0 +1,68 @@
|
||||
/**
|
||||
* 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 type AsyncProfilingTask = {
|
||||
id: string;
|
||||
serviceId: string;
|
||||
serviceInstanceIds: string[];
|
||||
createTime: number;
|
||||
events: string;
|
||||
duration: number;
|
||||
execArgs: string;
|
||||
};
|
||||
|
||||
export type AsyncProfileTaskCreationRequest = {
|
||||
serviceId: string;
|
||||
serviceInstanceIds: string[];
|
||||
duration: number;
|
||||
events: string[];
|
||||
execArgs: string;
|
||||
};
|
||||
|
||||
export type AsyncProfilerStackElement = {
|
||||
id: string;
|
||||
parentId: string;
|
||||
codeSignature: string;
|
||||
total: number;
|
||||
self: number;
|
||||
};
|
||||
|
||||
export type AsyncProfilerTaskProgress = {
|
||||
errorInstanceIds: string[];
|
||||
successInstanceIds: string[];
|
||||
logs: AsyncProfilerTaskLog[];
|
||||
};
|
||||
|
||||
type AsyncProfilerTaskLog = {
|
||||
id: string;
|
||||
instanceId: string;
|
||||
instanceName: string;
|
||||
operationType: string;
|
||||
operationTime: number;
|
||||
};
|
||||
|
||||
export type StackElement = {
|
||||
id: string;
|
||||
originId: string;
|
||||
name: string;
|
||||
parentId: string;
|
||||
codeSignature: string;
|
||||
total: number;
|
||||
self: number;
|
||||
value: number;
|
||||
children?: StackElement[];
|
||||
};
|
2
src/types/components.d.ts
vendored
2
src/types/components.d.ts
vendored
@ -12,6 +12,8 @@ declare module 'vue' {
|
||||
ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
|
||||
ElButton: typeof import('element-plus/es')['ElButton']
|
||||
ElCard: typeof import('element-plus/es')['ElCard']
|
||||
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
|
||||
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
|
||||
ElCollapse: typeof import('element-plus/es')['ElCollapse']
|
||||
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
|
||||
ElDialog: typeof import('element-plus/es')['ElDialog']
|
||||
|
3
src/types/profile.d.ts
vendored
3
src/types/profile.d.ts
vendored
@ -41,6 +41,9 @@ export interface TaskListItem {
|
||||
dumpPeriod: number;
|
||||
maxSamplingCount: number;
|
||||
logs: TaskLog[];
|
||||
errorInstanceIds: string[];
|
||||
successInstanceIds: string[];
|
||||
serviceInstanceIds: string[];
|
||||
}
|
||||
export interface SegmentSpan {
|
||||
spanId: string;
|
||||
|
85
src/views/dashboard/controls/AsyncProfiling.vue
Normal file
85
src/views/dashboard/controls/AsyncProfiling.vue
Normal file
@ -0,0 +1,85 @@
|
||||
<!-- 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">
|
||||
<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>
|
||||
<Header />
|
||||
<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/async-profiling/Content.vue";
|
||||
import Header from "../related/async-profiling/Header.vue";
|
||||
|
||||
/*global defineProps*/
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object as PropType<any>,
|
||||
default: () => ({ graph: {} }),
|
||||
},
|
||||
activeIndex: { type: String, default: "" },
|
||||
});
|
||||
const { t } = useI18n();
|
||||
const dashboardStore = useDashboardStore();
|
||||
|
||||
function removeWidget() {
|
||||
dashboardStore.removeControls(props.data);
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.profile-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: $font-size-smaller;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.operation {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 3px;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 10px;
|
||||
font-size: $font-size-smaller;
|
||||
border-bottom: 1px solid $border-color;
|
||||
}
|
||||
|
||||
.tools {
|
||||
padding: 5px 0;
|
||||
color: #999;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
|
||||
&:hover {
|
||||
color: $active-color;
|
||||
background-color: $popper-hover-bg-color;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -26,6 +26,7 @@ import DemandLog from "./DemandLog.vue";
|
||||
import Event from "./Event.vue";
|
||||
import NetworkProfiling from "./NetworkProfiling.vue";
|
||||
import ContinuousProfiling from "./ContinuousProfiling.vue";
|
||||
import AsyncProfiling from "./AsyncProfiling.vue";
|
||||
import TimeRange from "./TimeRange.vue";
|
||||
import ThirdPartyApp from "./ThirdPartyApp.vue";
|
||||
import TaskTimeline from "./TaskTimeline.vue";
|
||||
@ -43,6 +44,7 @@ export default {
|
||||
Event,
|
||||
NetworkProfiling,
|
||||
ContinuousProfiling,
|
||||
AsyncProfiling,
|
||||
TimeRange,
|
||||
ThirdPartyApp,
|
||||
TaskTimeline,
|
||||
|
@ -25,6 +25,7 @@ import DemandLog from "./DemandLog.vue";
|
||||
import Event from "./Event.vue";
|
||||
import NetworkProfiling from "./NetworkProfiling.vue";
|
||||
import ContinuousProfiling from "./ContinuousProfiling.vue";
|
||||
import AsyncProfiling from "./AsyncProfiling.vue";
|
||||
import TimeRange from "./TimeRange.vue";
|
||||
import ThirdPartyApp from "./ThirdPartyApp.vue";
|
||||
import TaskTimeline from "./TaskTimeline.vue";
|
||||
@ -43,5 +44,6 @@ export default {
|
||||
TimeRange,
|
||||
ThirdPartyApp,
|
||||
ContinuousProfiling,
|
||||
AsyncProfiling,
|
||||
TaskTimeline,
|
||||
};
|
||||
|
@ -150,6 +150,7 @@ export enum WidgetType {
|
||||
Event = "Event",
|
||||
NetworkProfiling = "NetworkProfiling",
|
||||
ContinuousProfiling = "ContinuousProfiling",
|
||||
AsyncProfiling = "AsyncProfiling",
|
||||
ThirdPartyApp = "ThirdPartyApp",
|
||||
TaskTimeline = "TaskTimeline",
|
||||
}
|
||||
@ -171,6 +172,7 @@ export const ServiceTools = [
|
||||
{ name: "timeline", content: "Add Trace Profiling", id: WidgetType.Profile },
|
||||
{ name: "insert_chart", content: "Add eBPF Profiling", id: WidgetType.Ebpf },
|
||||
{ name: "continuous_profiling", content: "Add Continuous Profiling", id: WidgetType.ContinuousProfiling },
|
||||
{ name: "async_profiling", content: "Add Async Profiling", id: WidgetType.AsyncProfiling },
|
||||
{ name: "assignment", content: "Add Log", id: WidgetType.Log },
|
||||
{ name: "demand", content: "Add On Demand Log", id: WidgetType.DemandLog },
|
||||
{ name: "event", content: "Add Event", id: WidgetType.Event },
|
||||
|
69
src/views/dashboard/related/async-profiling/Content.vue
Normal file
69
src/views/dashboard/related/async-profiling/Content.vue
Normal file
@ -0,0 +1,69 @@
|
||||
<!-- 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">
|
||||
<TaskList />
|
||||
<div class="vis-graph ml-5">
|
||||
<div class="mb-20">
|
||||
<Filter />
|
||||
</div>
|
||||
<div class="stack" v-loading="asyncProfilingStore.loadingTree">
|
||||
<EBPFStack :type="ComponentType" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { onMounted } from "vue";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { useAsyncProfilingStore } from "@/store/modules/async-profiling";
|
||||
import { useSelectorStore } from "@/store/modules/selectors";
|
||||
import TaskList from "./components/TaskList.vue";
|
||||
import Filter from "./components/Filter.vue";
|
||||
import EBPFStack from "@/views/dashboard/related/ebpf/components/EBPFStack.vue";
|
||||
import { ComponentType } from "./components/data";
|
||||
|
||||
const asyncProfilingStore = useAsyncProfilingStore();
|
||||
const selectorStore = useSelectorStore();
|
||||
|
||||
onMounted(async () => {
|
||||
const resp = await asyncProfilingStore.getServiceInstances({ serviceId: selectorStore.currentService.id });
|
||||
if (resp && resp.errors) {
|
||||
ElMessage.error(resp.errors);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.content {
|
||||
height: calc(100% - 30px);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.vis-graph {
|
||||
height: 100%;
|
||||
flex-grow: 2;
|
||||
min-width: 700px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
width: calc(100% - 330px);
|
||||
}
|
||||
|
||||
.stack {
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
height: calc(100% - 100px);
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
</style>
|
50
src/views/dashboard/related/async-profiling/Header.vue
Normal file
50
src/views/dashboard/related/async-profiling/Header.vue
Normal file
@ -0,0 +1,50 @@
|
||||
<!-- 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 header">
|
||||
<div class="title">Async Profiling</div>
|
||||
<el-button class="mr-20" size="small" type="primary" @click="() => (newTask = true)">
|
||||
{{ t("newTask") }}
|
||||
</el-button>
|
||||
</div>
|
||||
<el-dialog v-model="newTask" :destroy-on-close="true" fullscreen @closed="newTask = false">
|
||||
<NewTask @close="newTask = false" />
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import NewTask from "./components/NewTask.vue";
|
||||
|
||||
const { t } = useI18n();
|
||||
const newTask = ref<boolean>(false);
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.header {
|
||||
padding: 10px;
|
||||
font-size: $font-size-smaller;
|
||||
border-bottom: 1px solid $border-color;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.name {
|
||||
width: 270px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: bold;
|
||||
line-height: 24px;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,104 @@
|
||||
<!-- 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">
|
||||
<Selector
|
||||
class="filter-selector"
|
||||
:multiple="true"
|
||||
:value="serviceInstanceIds"
|
||||
size="small"
|
||||
:options="instances"
|
||||
placeholder="Select instances"
|
||||
@change="changeInstances"
|
||||
/>
|
||||
<Selector
|
||||
class="filter-events"
|
||||
:value="selectedEventType"
|
||||
size="small"
|
||||
:options="eventTypes"
|
||||
placeholder="Select a event"
|
||||
@change="changeEventType"
|
||||
/>
|
||||
<el-button type="primary" size="small" @click="analyzeProfiling">
|
||||
{{ t("analyze") }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, watch } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { useAsyncProfilingStore } from "@/store/modules/async-profiling";
|
||||
import type { Instance } from "@/types/selector";
|
||||
import type { Option } from "@/types/app";
|
||||
import { EventsMap } from "./data";
|
||||
|
||||
const { t } = useI18n();
|
||||
const asyncProfilingStore = useAsyncProfilingStore();
|
||||
const serviceInstanceIds = ref<string[]>([]);
|
||||
const selectedEventType = ref<string>("");
|
||||
const eventTypes = computed(() =>
|
||||
(asyncProfilingStore.selectedTask.events ?? []).map((d: string) => ({ label: d, value: d })),
|
||||
);
|
||||
const instances = computed(() =>
|
||||
asyncProfilingStore.instances.filter((d: Instance) =>
|
||||
(asyncProfilingStore.selectedTask.successInstanceIds ?? []).includes(d.id),
|
||||
),
|
||||
);
|
||||
|
||||
function changeInstances(options: Option[]) {
|
||||
serviceInstanceIds.value = options.map((d: Option) => d.value);
|
||||
asyncProfilingStore.setAnalyzeTrees([]);
|
||||
}
|
||||
|
||||
function changeEventType(options: Option[]) {
|
||||
selectedEventType.value = options[0].value;
|
||||
asyncProfilingStore.setAnalyzeTrees([]);
|
||||
}
|
||||
|
||||
async function analyzeProfiling() {
|
||||
const instanceIds = asyncProfilingStore.instances
|
||||
.filter((d: Instance) => (serviceInstanceIds.value ?? []).includes(d.value))
|
||||
.map((d: Instance) => d.id);
|
||||
const res = await asyncProfilingStore.getAsyncProfilingAnalyze({
|
||||
instanceIds,
|
||||
taskId: asyncProfilingStore.selectedTask.id,
|
||||
eventType: (EventsMap as any)[selectedEventType.value],
|
||||
});
|
||||
if (res.data && res.data.errors) {
|
||||
ElMessage.error(res.data.errors);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => asyncProfilingStore.selectedTask.successInstanceIds,
|
||||
() => {
|
||||
serviceInstanceIds.value = [];
|
||||
selectedEventType.value = "";
|
||||
},
|
||||
);
|
||||
</script>
|
||||
<style>
|
||||
.filter-selector {
|
||||
width: 400px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.filter-events {
|
||||
width: 200px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,166 @@
|
||||
<!-- 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="async-profile-task">
|
||||
<div>
|
||||
<div class="label">{{ t("instance") }}</div>
|
||||
<Selector
|
||||
class="profile-input"
|
||||
:multiple="true"
|
||||
:value="serviceInstanceIds"
|
||||
size="small"
|
||||
:options="asyncProfilingStore.instances"
|
||||
placeholder="Select instances"
|
||||
@change="changeInstances"
|
||||
:filterable="false"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div class="label">{{ t("duration") }}</div>
|
||||
<Radio class="mb-5" :value="duration" :options="DurationOptions" @change="changeDuration" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="label">{{ t("profilingEvents") }}</div>
|
||||
<el-checkbox-group v-model="asyncEvents" class="profile-input mb-5">
|
||||
<el-checkbox v-for="event in ProfilingEvents" :label="event.label" :value="event.value" :key="event.value" />
|
||||
</el-checkbox-group>
|
||||
</div>
|
||||
<div>
|
||||
<div class="label">
|
||||
<span class="mr-5 cp">{{ t("execArgs") }}</span>
|
||||
<el-popover placement="right" :width="480" trigger="hover" title="Async profiler extension parameters">
|
||||
<template #reference>
|
||||
<span>
|
||||
<Icon iconName="help" />
|
||||
</span>
|
||||
</template>
|
||||
<div>
|
||||
<p>
|
||||
<span class="mr-10">live </span>
|
||||
<span>- build allocation profile from live objects only</span>
|
||||
</p>
|
||||
<p>
|
||||
<span class="mr-10">lock[=DURATION] </span>
|
||||
<span>- profile contended locks overflowing the DURATION ns bucket (default: 10us)</span>
|
||||
</p>
|
||||
<p>
|
||||
<span class="mr-10">alloc[=BYTES] </span>
|
||||
<span>- profile allocations with BYTES interval</span>
|
||||
</p>
|
||||
<p>
|
||||
<span class="mr-10">interval=N </span>
|
||||
<span>- sampling interval in ns (default: 10'000'000, i.e. 10 ms)</span>
|
||||
</p>
|
||||
<p>
|
||||
<span class="mr-10">jstackdepth=N </span>
|
||||
<span>- maximum Java stack depth (default: 2048)</span>
|
||||
</p>
|
||||
<p>
|
||||
<span class="mr-10">chunksize=N </span>
|
||||
<span>- approximate size of JFR chunk in bytes (default: 100 MB)</span>
|
||||
</p>
|
||||
<p>
|
||||
<span class="mr-10">chunktime=N </span>
|
||||
<span>- duration of JFR chunk in seconds (default: 1 hour)</span>
|
||||
</p>
|
||||
</div>
|
||||
</el-popover>
|
||||
</div>
|
||||
<el-input size="small" class="profile-input" v-model="execArgs" />
|
||||
</div>
|
||||
<div>
|
||||
<el-button @click="createTask" type="primary" class="create-task-btn" :loading="loading">
|
||||
{{ t("createTask") }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useAsyncProfilingStore } from "@/store/modules/async-profiling";
|
||||
import { useSelectorStore } from "@/store/modules/selectors";
|
||||
import { ElMessage } from "element-plus";
|
||||
import type { Option } from "@/types/app";
|
||||
import { DurationOptions, ProfilingEvents } from "./data";
|
||||
|
||||
/* global defineEmits */
|
||||
const emits = defineEmits(["close"]);
|
||||
const asyncProfilingStore = useAsyncProfilingStore();
|
||||
const selectorStore = useSelectorStore();
|
||||
const { t } = useI18n();
|
||||
const serviceInstanceIds = ref<string[]>([]);
|
||||
const asyncEvents = ref<string[]>([ProfilingEvents[0].value]);
|
||||
const duration = ref<string>(DurationOptions[0].value);
|
||||
const execArgs = ref<string>("");
|
||||
const loading = ref<boolean>(false);
|
||||
|
||||
function changeDuration(val: string) {
|
||||
duration.value = val;
|
||||
}
|
||||
|
||||
function changeInstances(options: Option[]) {
|
||||
serviceInstanceIds.value = options.map((d: Option) => d.value);
|
||||
}
|
||||
|
||||
async function createTask() {
|
||||
const params = {
|
||||
serviceId: selectorStore.currentService.id,
|
||||
serviceInstanceIds: serviceInstanceIds.value,
|
||||
duration: Number(duration.value),
|
||||
events: asyncEvents.value,
|
||||
execArgs: execArgs.value,
|
||||
};
|
||||
loading.value = true;
|
||||
const res = await asyncProfilingStore.createTask(params);
|
||||
loading.value = false;
|
||||
if (res.errors) {
|
||||
ElMessage.error(res.errors);
|
||||
return;
|
||||
}
|
||||
const { errorReason } = res.data;
|
||||
if (errorReason) {
|
||||
ElMessage.error(errorReason);
|
||||
return;
|
||||
}
|
||||
emits("close");
|
||||
ElMessage.success("Task created successfully");
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.async-profile-task {
|
||||
margin: 0 auto;
|
||||
width: 600px;
|
||||
}
|
||||
|
||||
.date {
|
||||
font-size: $font-size-smaller;
|
||||
}
|
||||
|
||||
.label {
|
||||
margin-top: 10px;
|
||||
font-size: $font-size-normal;
|
||||
}
|
||||
|
||||
.profile-input {
|
||||
width: 600px;
|
||||
}
|
||||
|
||||
.create-task-btn {
|
||||
width: 600px;
|
||||
margin-top: 50px;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,267 @@
|
||||
<!-- 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="loading">
|
||||
<div class="profile-t-tool flex-h">{{ t("taskList") }}</div>
|
||||
<div class="profile-t-wrapper">
|
||||
<div class="no-data" v-show="!asyncProfilingStore.taskList.length">
|
||||
{{ t("noData") }}
|
||||
</div>
|
||||
<table class="profile-t">
|
||||
<tr
|
||||
class="profile-tr cp"
|
||||
v-for="(i, index) in asyncProfilingStore.taskList"
|
||||
@click="changeTask(i)"
|
||||
:key="index"
|
||||
:class="{
|
||||
selected: asyncProfilingStore.selectedTask.id === i.id,
|
||||
}"
|
||||
>
|
||||
<td class="profile-td">
|
||||
<div class="ell">
|
||||
<span>{{ i.id }}</span>
|
||||
<a class="profile-btn r" @click="() => (showDetail = true)">
|
||||
<Icon iconName="view" size="middle" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="grey ell sm">
|
||||
<span class="mr-10 sm">
|
||||
{{ dateFormat(i.createTime) }}
|
||||
</span>
|
||||
<span class="mr-10 sm">
|
||||
{{ dateFormat(i.createTime + i.duration * 60 * 1000) }}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<el-dialog v-model="showDetail" :destroy-on-close="true" fullscreen @closed="showDetail = false">
|
||||
<div class="profile-detail flex-v" v-if="asyncProfilingStore.selectedTask.id">
|
||||
<div>
|
||||
<h5 class="mb-10">{{ t("task") }}.</h5>
|
||||
<div class="mb-10 clear item">
|
||||
<span class="g-sm-4 grey">{{ t("id") }}:</span>
|
||||
<span class="g-sm-8 wba">{{ asyncProfilingStore.selectedTask.id }}</span>
|
||||
</div>
|
||||
<div class="mb-10 clear item">
|
||||
<span class="g-sm-4 grey">{{ t("service") }}:</span>
|
||||
<span class="g-sm-8 wba">{{ service }}</span>
|
||||
</div>
|
||||
<div class="mb-10 clear item">
|
||||
<span class="g-sm-4 grey">{{ t("execArgs") }}:</span>
|
||||
<span class="g-sm-8 wba">{{ asyncProfilingStore.selectedTask.execArgs }}</span>
|
||||
</div>
|
||||
<div class="mb-10 clear item">
|
||||
<span class="g-sm-4 grey">{{ t("duration") }}:</span>
|
||||
<span class="g-sm-8 wba">{{ asyncProfilingStore.selectedTask.duration }}</span>
|
||||
</div>
|
||||
<div class="mb-10 clear item">
|
||||
<span class="g-sm-4 grey">{{ t("events") }}:</span>
|
||||
<span class="g-sm-8 wba"> {{ asyncProfilingStore.selectedTask.events.join(", ") }} </span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h5
|
||||
class="mb-5 mt-10"
|
||||
v-show="asyncProfilingStore.selectedTask.logs && asyncProfilingStore.selectedTask.logs.length"
|
||||
>
|
||||
{{ t("logs") }}.
|
||||
</h5>
|
||||
<div v-for="(i, index) in Object.keys(instanceLogs)" :key="index">
|
||||
<div class="sm">
|
||||
<span class="mr-10 grey">{{ t("instance") }}:</span>
|
||||
<span>{{ i }}</span>
|
||||
</div>
|
||||
<div v-for="(d, index) in instanceLogs[i]" :key="index">
|
||||
<span class="mr-10 grey">{{ t("operationType") }}:</span>
|
||||
<span class="mr-20">{{ d.operationType }}</span>
|
||||
<span class="mr-10 grey">{{ t("time") }}:</span>
|
||||
<span>{{ dateFormat(d.operationTime) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h5 class="mb-10 mt-10" v-show="errorInstances.length"> {{ t("errorInstances") }}</h5>
|
||||
<div v-for="(instance, index) in errorInstances" :key="instance.value || index">
|
||||
<div class="mb-10 sm">
|
||||
<span class="mr-10 grey">{{ t("instance") }}:</span>
|
||||
<span>{{ instance.label }}</span>
|
||||
</div>
|
||||
<div v-for="(d, index) in instance.attributes" :key="d.value + index">
|
||||
<span class="mr-10 grey">{{ d.name }}:</span>
|
||||
<span class="mr-20">{{ d.value }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h5 class="mb-10 mt-10" v-show="successInstances.length"> {{ t("successInstances") }}</h5>
|
||||
<div v-for="(instance, index) in successInstances" :key="instance.value || index">
|
||||
<div class="mb-10 sm">
|
||||
<span class="mr-10 grey">{{ t("instance") }}:</span>
|
||||
<span>{{ instance.label }}</span>
|
||||
</div>
|
||||
<div v-for="(d, index) in instance.attributes" :key="d.value + index">
|
||||
<span class="mr-10 grey">{{ d.name }}:</span>
|
||||
<span class="mr-20">{{ d.value }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, watch } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useSelectorStore } from "@/store/modules/selectors";
|
||||
import { useAsyncProfilingStore } from "@/store/modules/async-profiling";
|
||||
import type { TaskLog, TaskListItem } from "@/types/profile";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { dateFormat } from "@/utils/dateFormat";
|
||||
import type { Instance, Service } from "@/types/selector";
|
||||
|
||||
const { t } = useI18n();
|
||||
const asyncProfilingStore = useAsyncProfilingStore();
|
||||
const selectorStore = useSelectorStore();
|
||||
const showDetail = ref<boolean>(false);
|
||||
const service = ref<string>("");
|
||||
const instanceLogs = ref<TaskLog | any>({});
|
||||
const errorInstances = ref<Instance[]>([]);
|
||||
const successInstances = ref<Instance[]>([]);
|
||||
const loading = ref<boolean>(false);
|
||||
|
||||
onMounted(() => {
|
||||
fetchTasks();
|
||||
});
|
||||
|
||||
async function fetchTasks() {
|
||||
loading.value = true;
|
||||
const res = await asyncProfilingStore.getTaskList();
|
||||
loading.value = false;
|
||||
if (res.errors) {
|
||||
return ElMessage.error(res.errors);
|
||||
}
|
||||
if (res.data.errorReason) {
|
||||
ElMessage.error(res.errors);
|
||||
}
|
||||
}
|
||||
|
||||
async function changeTask(item: TaskListItem) {
|
||||
asyncProfilingStore.setSelectedTask(item);
|
||||
asyncProfilingStore.setAnalyzeTrees([]);
|
||||
service.value = (selectorStore.services.filter((s: Service) => s.id === item.serviceId)[0] ?? {}).label;
|
||||
const res = await asyncProfilingStore.getTaskLogs({ taskId: item.id });
|
||||
|
||||
if (res.errors) {
|
||||
ElMessage.error(res.errors);
|
||||
return;
|
||||
}
|
||||
item = {
|
||||
...item,
|
||||
...asyncProfilingStore.taskProgress,
|
||||
};
|
||||
asyncProfilingStore.setSelectedTask(item);
|
||||
errorInstances.value = asyncProfilingStore.instances.filter(
|
||||
(d: Instance) => d.id && item.errorInstanceIds.includes(d.id),
|
||||
);
|
||||
successInstances.value = asyncProfilingStore.instances.filter(
|
||||
(d: Instance) => d.id && item.successInstanceIds.includes(d.id),
|
||||
);
|
||||
instanceLogs.value = {};
|
||||
for (const d of item.logs) {
|
||||
if (instanceLogs.value[d.instanceName]) {
|
||||
instanceLogs.value[d.instanceName].push({
|
||||
operationType: d.operationType,
|
||||
operationTime: d.operationTime,
|
||||
});
|
||||
} else {
|
||||
instanceLogs.value[d.instanceName] = [{ operationType: d.operationType, operationTime: d.operationTime }];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => selectorStore.currentService,
|
||||
() => {
|
||||
fetchTasks();
|
||||
},
|
||||
);
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.profile-task-list {
|
||||
width: 300px;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
border-right: 1px solid var(--sw-trace-list-border);
|
||||
}
|
||||
|
||||
.item span {
|
||||
height: 21px;
|
||||
}
|
||||
|
||||
.profile-td {
|
||||
padding: 5px 10px;
|
||||
border-bottom: 1px solid var(--sw-trace-list-border);
|
||||
}
|
||||
|
||||
.selected {
|
||||
background-color: var(--sw-list-selected);
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.profile-tr {
|
||||
&:hover {
|
||||
background-color: var(--sw-list-selected);
|
||||
}
|
||||
}
|
||||
|
||||
.profile-segment {
|
||||
border-top: 1px solid var(--sw-trace-list-border);
|
||||
}
|
||||
|
||||
.profile-t-tool {
|
||||
padding: 5px 10px;
|
||||
font-weight: bold;
|
||||
border-right: 1px solid var(--sw-trace-list-border);
|
||||
border-bottom: 1px solid var(--sw-trace-list-border);
|
||||
background-color: var(--sw-table-header);
|
||||
}
|
||||
|
||||
.profile-btn {
|
||||
color: $font-color;
|
||||
padding: 1px 3px;
|
||||
border-radius: 2px;
|
||||
font-size: $font-size-smaller;
|
||||
float: right;
|
||||
}
|
||||
</style>
|
@ -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.
|
||||
*/
|
||||
|
||||
export const DurationOptions = [
|
||||
{ value: "5", label: "5 min" },
|
||||
{ value: "10", label: "10 min" },
|
||||
{ value: "15", label: "15 min" },
|
||||
];
|
||||
|
||||
export const ProfilingEvents = [
|
||||
{ value: "CPU", label: "CPU" },
|
||||
{ value: "ALLOC", label: "ALLOC" },
|
||||
{ value: "WALL", label: "WALL" },
|
||||
{ value: "LOCK", label: "LOCK" },
|
||||
{ value: "CTIMER", label: "CTIMER" },
|
||||
{ value: "ITIMER", label: "ITIMER" },
|
||||
];
|
||||
|
||||
export enum EventsMap {
|
||||
CPU = "EXECUTION_SAMPLE",
|
||||
WALL = "EXECUTION_SAMPLE",
|
||||
CTIMER = "EXECUTION_SAMPLE",
|
||||
ITIMER = "EXECUTION_SAMPLE",
|
||||
LOCK = "JAVA_MONITOR_ENTER",
|
||||
ALLOC = "OBJECT_ALLOCATION_OUTSIDE_TLAB",
|
||||
}
|
||||
|
||||
export enum JFREventType {
|
||||
EXECUTION_SAMPLE = "EXECUTION_SAMPLE",
|
||||
JAVA_MONITOR_ENTER = "JAVA_MONITOR_ENTER",
|
||||
THREAD_PARK = "THREAD_PARK",
|
||||
OBJECT_ALLOCATION_IN_NEW_TLAB = "OBJECT_ALLOCATION_IN_NEW_TLAB",
|
||||
OBJECT_ALLOCATION_OUTSIDE_TLAB = "OBJECT_ALLOCATION_OUTSIDE_TLAB",
|
||||
PROFILER_LIVE_OBJECT = "PROFILER_LIVE_OBJECT",
|
||||
}
|
||||
|
||||
export const ComponentType = "ASYNC_PROFILING";
|
@ -29,8 +29,6 @@ export const TargetTypes = [
|
||||
{ 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 },
|
||||
|
@ -97,21 +97,12 @@ limitations under the License. -->
|
||||
import type { Option } from "@/types/app";
|
||||
import { TableHeader, AggregateTypes } from "./data";
|
||||
import { useEbpfStore } from "@/store/modules/ebpf";
|
||||
import { useContinousProfilingStore } from "@/store/modules/continous-profiling";
|
||||
import type { EBPFProfilingSchedule, Process } from "@/types/ebpf";
|
||||
import { ElMessage, ElTable } from "element-plus";
|
||||
import { dateFormat } from "@/utils/dateFormat";
|
||||
import { ComponentType } from "@/views/dashboard/related/continuous-profiling/data";
|
||||
|
||||
const { t } = useI18n();
|
||||
/*global defineProps*/
|
||||
const props = defineProps({
|
||||
type: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
});
|
||||
const ebpfStore = props.type === ComponentType ? useContinousProfilingStore() : useEbpfStore();
|
||||
const ebpfStore = useEbpfStore();
|
||||
const pageSize = 5;
|
||||
const multipleTableRef = ref<InstanceType<typeof ElTable>>();
|
||||
const selectedProcesses = ref<string[]>([]);
|
||||
@ -126,12 +117,12 @@ limitations under the License. -->
|
||||
return attr.map((d: { name: string; value: string }) => `${d.name}=${d.value}`).join("; ");
|
||||
};
|
||||
|
||||
function changeLabels(opt: any[]) {
|
||||
function changeLabels(opt: Option[]) {
|
||||
const arr = opt.map((d) => d.value);
|
||||
selectedLabels.value = arr;
|
||||
}
|
||||
|
||||
function changeAggregateType(opt: any[]) {
|
||||
function changeAggregateType(opt: Option[]) {
|
||||
aggregateType.value = opt[0].value;
|
||||
ebpfStore.setAnalyzeTrees([]);
|
||||
}
|
||||
|
@ -23,10 +23,10 @@ limitations under the License. -->
|
||||
import d3tip from "d3-tip";
|
||||
import { flamegraph } from "d3-flame-graph";
|
||||
import { useEbpfStore } from "@/store/modules/ebpf";
|
||||
import { useContinousProfilingStore } from "@/store/modules/continous-profiling";
|
||||
import { ComponentType } from "@/views/dashboard/related/continuous-profiling/data";
|
||||
import { useAsyncProfilingStore } from "@/store/modules/async-profiling";
|
||||
import type { StackElement } from "@/types/ebpf";
|
||||
import { AggregateTypes } from "./data";
|
||||
import { JFREventType, ComponentType } from "@/views/dashboard/related/async-profiling/components/data";
|
||||
import "d3-flame-graph/dist/d3-flamegraph.css";
|
||||
import { treeForeach } from "@/utils/flameGraph";
|
||||
|
||||
@ -37,7 +37,7 @@ limitations under the License. -->
|
||||
default: "",
|
||||
},
|
||||
});
|
||||
const ebpfStore = props.type === ComponentType ? useContinousProfilingStore() : useEbpfStore();
|
||||
const ebpfStore = props.type === ComponentType ? useAsyncProfilingStore() : useEbpfStore();
|
||||
const stackTree = ref<Nullable<StackElement>>(null);
|
||||
const selectStack = ref<Nullable<StackElement>>(null);
|
||||
const graph = ref<Nullable<HTMLDivElement>>(null);
|
||||
@ -102,10 +102,7 @@ limitations under the License. -->
|
||||
.direction("s")
|
||||
.html((d: { data: StackElement } & { parent: { data: StackElement } }) => {
|
||||
const name = d.data.name.replace("<", "<").replace(">", ">");
|
||||
const valStr =
|
||||
ebpfStore.aggregateType === AggregateTypes[0].value
|
||||
? `<div class="mb-5">Dump Count: ${d.data.dumpCount}</div>`
|
||||
: `<div class="mb-5">Duration: ${d.data.dumpCount} ns</div>`;
|
||||
const valStr = tooltipContent(d.data);
|
||||
const rateOfParent =
|
||||
(d.parent &&
|
||||
`<div class="mb-5">Percentage Of Selected: ${
|
||||
@ -125,6 +122,26 @@ limitations under the License. -->
|
||||
d3.select("#graph-stack").datum(stackTree.value).call(flameChart.value);
|
||||
}
|
||||
|
||||
function tooltipContent(item: StackElement) {
|
||||
if (props.type === ComponentType) {
|
||||
if (!ebpfStore.analyzeTrees.length) {
|
||||
return;
|
||||
}
|
||||
const { type } = ebpfStore.analyzeTrees[0];
|
||||
if ([JFREventType.JAVA_MONITOR_ENTER, JFREventType.THREAD_PARK].includes(type)) {
|
||||
return `<div class="mb-5">Duration: ${item.dumpCount} ms</div>`;
|
||||
}
|
||||
if (type === JFREventType.EXECUTION_SAMPLE) {
|
||||
return `<div class="mb-5">Count: ${item.dumpCount}</div>`;
|
||||
}
|
||||
return `<div class="mb-5">Memory: ${item.dumpCount} byte</div>`;
|
||||
}
|
||||
|
||||
ebpfStore.aggregateType === AggregateTypes[0].value
|
||||
? `<div class="mb-5">Dump Count: ${item.dumpCount}</div>`
|
||||
: `<div class="mb-5">Duration: ${item.dumpCount} ns</div>`;
|
||||
}
|
||||
|
||||
function countRange() {
|
||||
const list = [];
|
||||
for (const tree of ebpfStore.analyzeTrees) {
|
||||
|
Loading…
Reference in New Issue
Block a user