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
26 changed files with 1185 additions and 21 deletions

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

View File

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

View File

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

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" },
];
export const ComponentType = "CONTINOUS_PROFILING";
export const HeaderLabels = [
{ value: "triggeredCount", label: "Triggered Count", width: 150 },
{ 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 { 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([]);
}

View File

@@ -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("<", "&lt;").replace(">", "&gt;");
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) {