mirror of
https://github.com/apache/skywalking-booster-ui.git
synced 2025-10-14 20:01:28 +00:00
feat: implement Async Profiling widget (#434)
This commit is contained in:
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) {
|
||||
|
Reference in New Issue
Block a user