feat: implement Async Profiling widget (#434)

This commit is contained in:
Fine0830 2024-11-28 17:50:43 +08:00 committed by GitHub
parent e164d87209
commit fb0817eed8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 1185 additions and 21 deletions

View 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

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

View File

@ -28,6 +28,7 @@ import * as alarm from "./query/alarm";
import * as event from "./query/event"; import * as event from "./query/event";
import * as ebpf from "./query/ebpf"; import * as ebpf from "./query/ebpf";
import * as demandLog from "./query/demand-log"; import * as demandLog from "./query/demand-log";
import * as asyncProfile from "./query/async-profile";
const query: { [key: string]: string } = { const query: { [key: string]: string } = {
...app, ...app,
@ -41,6 +42,7 @@ const query: { [key: string]: string } = {
...event, ...event,
...ebpf, ...ebpf,
...demandLog, ...demandLog,
...asyncProfile,
}; };
class Graphql { class Graphql {
private queryData = ""; private queryData = "";

View 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}}`;

View File

@ -390,5 +390,10 @@ const msg = {
mappingTip: "Notice: The mapping key is a Regex string, e.g. ^([0-9])$", mappingTip: "Notice: The mapping key is a Regex string, e.g. ^([0-9])$",
valueDashboard: "Data Value Related Dashboard", valueDashboard: "Data Value Related Dashboard",
viewValueDashboard: "View Dashboard", viewValueDashboard: "View Dashboard",
errorInstances: "Error Instances",
successInstances: "Success Instances",
profilingEvents: "Async Profiling Events",
execArgs: "Exec Args",
instances: "Instances",
}; };
export default msg; export default msg;

View File

@ -390,5 +390,10 @@ const msg = {
mappingTip: "Aviso: La clave de mapeo es una cadena Regex, p. ej. ^([0-9])$", mappingTip: "Aviso: La clave de mapeo es una cadena Regex, p. ej. ^([0-9])$",
valueDashboard: "Data Value Related Dashboard", valueDashboard: "Data Value Related Dashboard",
viewValueDashboard: "View Dashboard", viewValueDashboard: "View Dashboard",
errorInstances: "Error Instances",
successInstances: "Success Instances",
profilingEvents: "Async Profiling Events",
execArgs: "Exec Args",
instances: "Instances",
}; };
export default msg; export default msg;

View File

@ -388,5 +388,10 @@ const msg = {
mappingTip: "注意: 映射键是一个正则表达式字符串,比如 ^([0-9])$", mappingTip: "注意: 映射键是一个正则表达式字符串,比如 ^([0-9])$",
valueDashboard: "数据值相关的仪表板", valueDashboard: "数据值相关的仪表板",
viewValueDashboard: "查看仪表板", viewValueDashboard: "查看仪表板",
errorInstances: "错误的实例",
successInstances: "成功的实例",
profilingEvents: "异步分析事件",
execArgs: "String任务扩展",
instances: "实例",
}; };
export default msg; export default msg;

View File

@ -47,6 +47,7 @@ export const ControlsTypes = [
WidgetType.DemandLog, WidgetType.DemandLog,
WidgetType.Ebpf, WidgetType.Ebpf,
WidgetType.NetworkProfiling, WidgetType.NetworkProfiling,
WidgetType.AsyncProfiling,
WidgetType.ThirdPartyApp, WidgetType.ThirdPartyApp,
WidgetType.ContinuousProfiling, WidgetType.ContinuousProfiling,
WidgetType.TaskTimeline, WidgetType.TaskTimeline,

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

View File

@ -139,6 +139,10 @@
margin-bottom: 10px; margin-bottom: 10px;
} }
.mb-20 {
margin-bottom: 20px;
}
.mr-5 { .mr-5 {
margin-right: 5px; margin-right: 5px;
} }

View 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[];
};

View File

@ -12,6 +12,8 @@ declare module 'vue' {
ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem'] ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
ElButton: typeof import('element-plus/es')['ElButton'] ElButton: typeof import('element-plus/es')['ElButton']
ElCard: typeof import('element-plus/es')['ElCard'] 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'] ElCollapse: typeof import('element-plus/es')['ElCollapse']
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem'] ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
ElDialog: typeof import('element-plus/es')['ElDialog'] ElDialog: typeof import('element-plus/es')['ElDialog']

View File

@ -41,6 +41,9 @@ export interface TaskListItem {
dumpPeriod: number; dumpPeriod: number;
maxSamplingCount: number; maxSamplingCount: number;
logs: TaskLog[]; logs: TaskLog[];
errorInstanceIds: string[];
successInstanceIds: string[];
serviceInstanceIds: string[];
} }
export interface SegmentSpan { export interface SegmentSpan {
spanId: string; spanId: string;

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

View File

@ -26,6 +26,7 @@ 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 ContinuousProfiling from "./ContinuousProfiling.vue";
import AsyncProfiling from "./AsyncProfiling.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"; import TaskTimeline from "./TaskTimeline.vue";
@ -43,6 +44,7 @@ export default {
Event, Event,
NetworkProfiling, NetworkProfiling,
ContinuousProfiling, ContinuousProfiling,
AsyncProfiling,
TimeRange, TimeRange,
ThirdPartyApp, ThirdPartyApp,
TaskTimeline, TaskTimeline,

View File

@ -25,6 +25,7 @@ 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 ContinuousProfiling from "./ContinuousProfiling.vue";
import AsyncProfiling from "./AsyncProfiling.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"; import TaskTimeline from "./TaskTimeline.vue";
@ -43,5 +44,6 @@ export default {
TimeRange, TimeRange,
ThirdPartyApp, ThirdPartyApp,
ContinuousProfiling, ContinuousProfiling,
AsyncProfiling,
TaskTimeline, TaskTimeline,
}; };

View File

@ -150,6 +150,7 @@ export enum WidgetType {
Event = "Event", Event = "Event",
NetworkProfiling = "NetworkProfiling", NetworkProfiling = "NetworkProfiling",
ContinuousProfiling = "ContinuousProfiling", ContinuousProfiling = "ContinuousProfiling",
AsyncProfiling = "AsyncProfiling",
ThirdPartyApp = "ThirdPartyApp", ThirdPartyApp = "ThirdPartyApp",
TaskTimeline = "TaskTimeline", TaskTimeline = "TaskTimeline",
} }
@ -171,6 +172,7 @@ export const ServiceTools = [
{ name: "timeline", content: "Add Trace Profiling", id: WidgetType.Profile }, { name: "timeline", content: "Add Trace Profiling", id: WidgetType.Profile },
{ name: "insert_chart", content: "Add eBPF Profiling", id: WidgetType.Ebpf }, { name: "insert_chart", content: "Add eBPF Profiling", id: WidgetType.Ebpf },
{ name: "continuous_profiling", content: "Add Continuous Profiling", id: WidgetType.ContinuousProfiling }, { 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: "assignment", content: "Add Log", id: WidgetType.Log },
{ name: "demand", content: "Add On Demand Log", id: WidgetType.DemandLog }, { name: "demand", content: "Add On Demand Log", id: WidgetType.DemandLog },
{ name: "event", content: "Add Event", id: WidgetType.Event }, { name: "event", content: "Add Event", id: WidgetType.Event },

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

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

View File

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

View File

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

View File

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

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.
*/
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";

View File

@ -29,8 +29,6 @@ export const TargetTypes = [
{ label: "NETWORK", value: "NETWORK" }, { label: "NETWORK", value: "NETWORK" },
]; ];
export const ComponentType = "CONTINOUS_PROFILING";
export const HeaderLabels = [ export const HeaderLabels = [
{ value: "triggeredCount", label: "Triggered Count", width: 150 }, { value: "triggeredCount", label: "Triggered Count", width: 150 },
{ value: "lastTriggerTime", label: "Last Trigger Time", width: 170 }, { value: "lastTriggerTime", label: "Last Trigger Time", width: 170 },

View File

@ -97,21 +97,12 @@ 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();
/*global defineProps*/ const ebpfStore = useEbpfStore();
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[]>([]);
@ -126,12 +117,12 @@ limitations under the License. -->
return attr.map((d: { name: string; value: string }) => `${d.name}=${d.value}`).join("; "); 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); const arr = opt.map((d) => d.value);
selectedLabels.value = arr; selectedLabels.value = arr;
} }
function changeAggregateType(opt: any[]) { function changeAggregateType(opt: Option[]) {
aggregateType.value = opt[0].value; aggregateType.value = opt[0].value;
ebpfStore.setAnalyzeTrees([]); ebpfStore.setAnalyzeTrees([]);
} }

View File

@ -23,10 +23,10 @@ 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 { useAsyncProfilingStore } from "@/store/modules/async-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 { JFREventType, ComponentType } from "@/views/dashboard/related/async-profiling/components/data";
import "d3-flame-graph/dist/d3-flamegraph.css"; import "d3-flame-graph/dist/d3-flamegraph.css";
import { treeForeach } from "@/utils/flameGraph"; import { treeForeach } from "@/utils/flameGraph";
@ -37,7 +37,7 @@ limitations under the License. -->
default: "", default: "",
}, },
}); });
const ebpfStore = props.type === ComponentType ? useContinousProfilingStore() : useEbpfStore(); const ebpfStore = props.type === ComponentType ? useAsyncProfilingStore() : 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);
@ -102,10 +102,7 @@ limitations under the License. -->
.direction("s") .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 = tooltipContent(d.data);
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 rateOfParent = const rateOfParent =
(d.parent && (d.parent &&
`<div class="mb-5">Percentage Of Selected: ${ `<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); 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() { function countRange() {
const list = []; const list = [];
for (const tree of ebpfStore.analyzeTrees) { for (const tree of ebpfStore.analyzeTrees) {