mirror of
https://github.com/apache/skywalking-booster-ui.git
synced 2025-10-15 12:49:17 +00:00
feat: Implement task timeline and policy list widget for continous profiling (#280)
This commit is contained in:
51
src/views/dashboard/related/continuous-profiling/Content.vue
Normal file
51
src/views/dashboard/related/continuous-profiling/Content.vue
Normal file
@@ -0,0 +1,51 @@
|
||||
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License. -->
|
||||
<template>
|
||||
<div class="flex-h content">
|
||||
<policy-list />
|
||||
<div class="flex-v list" v-loading="continousProfilingStore.instancesLoading">
|
||||
<instance-list :config="config" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { PropType } from "vue";
|
||||
import { useContinousProfilingStore } from "@/store/modules/continous-profiling";
|
||||
import PolicyList from "./components/PolicyList.vue";
|
||||
import InstanceList from "./components/InstanceList.vue";
|
||||
|
||||
const continousProfilingStore = useContinousProfilingStore();
|
||||
|
||||
/*global defineProps */
|
||||
defineProps({
|
||||
config: {
|
||||
type: Object as PropType<any>,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.content {
|
||||
height: calc(100% - 50px);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.list {
|
||||
height: 100%;
|
||||
flex-grow: 2;
|
||||
min-width: 600px;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
@@ -0,0 +1,134 @@
|
||||
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License. -->
|
||||
|
||||
<template>
|
||||
<div class="policy-list">
|
||||
<el-collapse v-model="activeNames">
|
||||
<el-collapse-item v-for="(_, index) in policyList" :key="index" :name="String(index)">
|
||||
<template #title>
|
||||
<div>
|
||||
<span class="title">{{ `Policy - ${index + 1}` }}</span>
|
||||
<Icon
|
||||
class="mr-5 cp"
|
||||
iconName="remove_circle_outline"
|
||||
size="middle"
|
||||
v-show="policyList.length !== 1"
|
||||
@click="removePolicy($event, index)"
|
||||
/>
|
||||
<Icon
|
||||
class="cp"
|
||||
v-show="index === policyList.length - 1"
|
||||
iconName="add_circle_outlinecontrol_point"
|
||||
size="middle"
|
||||
@click="createPolicy"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<Policy :policyList="policyList" @edit="changePolicy" :order="index" />
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
<div>
|
||||
<el-button @click="save" type="primary" class="save-btn">
|
||||
{{ t("save") }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref } from "vue";
|
||||
import type { PropType } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import Policy from "./Policy.vue";
|
||||
import type { StrategyItem, CheckItems } from "@/types/continous-profiling";
|
||||
|
||||
/* global defineEmits, defineProps */
|
||||
const props = defineProps({
|
||||
policyList: {
|
||||
type: Array as PropType<StrategyItem[]>,
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
const emits = defineEmits(["save"]);
|
||||
const { t } = useI18n();
|
||||
const activeNames = ref(["0"]);
|
||||
const policyList = ref<StrategyItem[]>([...props.policyList]);
|
||||
|
||||
function changePolicy(params: StrategyItem, order: number) {
|
||||
policyList.value = policyList.value.map((d: StrategyItem, index: number) => {
|
||||
if (order === index) {
|
||||
return params;
|
||||
}
|
||||
return d;
|
||||
});
|
||||
}
|
||||
|
||||
function removePolicy(e: PointerEvent, key: number) {
|
||||
e.stopPropagation();
|
||||
if (policyList.value.length === 1) {
|
||||
return;
|
||||
}
|
||||
policyList.value = policyList.value.filter((_, index: number) => index !== key);
|
||||
}
|
||||
|
||||
function createPolicy(e: PointerEvent) {
|
||||
e.stopPropagation();
|
||||
policyList.value.push({
|
||||
type: "",
|
||||
checkItems: [
|
||||
{
|
||||
type: "",
|
||||
threshold: "",
|
||||
period: NaN,
|
||||
count: NaN,
|
||||
},
|
||||
],
|
||||
});
|
||||
activeNames.value = [String(policyList.value.length - 1)];
|
||||
}
|
||||
|
||||
function save() {
|
||||
const params = [];
|
||||
for (const d of policyList.value) {
|
||||
const checkItems = d.checkItems.filter(
|
||||
(item: CheckItems) => item.type && item.threshold && item.period && item.count,
|
||||
);
|
||||
if (d.type && checkItems.length) {
|
||||
const v = {
|
||||
targetType: d.type,
|
||||
checkItems,
|
||||
};
|
||||
params.push(v);
|
||||
}
|
||||
}
|
||||
|
||||
emits("save", params);
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.policy-list {
|
||||
margin: 0 auto;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.save-btn {
|
||||
width: 300px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.title {
|
||||
display: inline-block;
|
||||
margin-right: 5px;
|
||||
}
|
||||
</style>
|
@@ -0,0 +1,185 @@
|
||||
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License. -->
|
||||
<template>
|
||||
<div class="header">
|
||||
{{ t("monitorInstances") }}
|
||||
</div>
|
||||
<el-table :data="currentInstances" style="width: 99%" height="440">
|
||||
<el-table-column type="expand">
|
||||
<template #default="props">
|
||||
<div class="child">
|
||||
<div class="title">{{ t("attributes") }}</div>
|
||||
<div v-for="(attr, index) in props.row.attributes" :key="index">
|
||||
{{ `${attr.name}: ${attr.value}` }}
|
||||
</div>
|
||||
<div class="title mt-10">{{ t("processes") }}</div>
|
||||
<el-table :data="props.row.processes" size="small" max-height="300">
|
||||
<el-table-column prop="name" label="Name">
|
||||
<template #default="scope">
|
||||
<span
|
||||
:class="config.processDashboardName ? 'link' : ''"
|
||||
@click="viewProcessDashboard(scope.row, props.row)"
|
||||
>
|
||||
{{ scope.row.name }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-for="item in HeaderChildLabels"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:prop="item.value"
|
||||
:width="item.width"
|
||||
/>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="name" label="Name">
|
||||
<template #default="scope">
|
||||
<span :class="config.instanceDashboardName ? 'link' : ''" @click="viewInstanceDashboard(scope.row)">
|
||||
{{ scope.row.name }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-for="item in HeaderLabels"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:prop="item.value"
|
||||
:width="item.width"
|
||||
/>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
class="mt-10"
|
||||
small
|
||||
background
|
||||
layout="prev, pager, next"
|
||||
:page-size="pageSize"
|
||||
:total="instances.length"
|
||||
@current-change="changePage"
|
||||
@prev-click="changePage"
|
||||
@next-click="changePage"
|
||||
/>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, watch } from "vue";
|
||||
import type { PropType } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useContinousProfilingStore } from "@/store/modules/continous-profiling";
|
||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||
import { useSelectorStore } from "@/store/modules/selectors";
|
||||
import type { MonitorInstance, MonitorProcess } from "@/types/continous-profiling";
|
||||
import router from "@/router";
|
||||
import { HeaderLabels, HeaderChildLabels } from "../data";
|
||||
import { EntityType } from "../../../data";
|
||||
import { dateFormat } from "@/utils/dateFormat";
|
||||
|
||||
/*global defineProps */
|
||||
const props = defineProps({
|
||||
config: {
|
||||
type: Object as PropType<any>,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
const { t } = useI18n();
|
||||
const dashboardStore = useDashboardStore();
|
||||
const selectorStore = useSelectorStore();
|
||||
const continousProfilingStore = useContinousProfilingStore();
|
||||
const pageSize = 10;
|
||||
const instances = computed(() => {
|
||||
return continousProfilingStore.instances
|
||||
.map((d: MonitorInstance) => {
|
||||
const processes = (d.processes || [])
|
||||
.sort((c: MonitorProcess, d: MonitorProcess) => d.lastTriggerTimestamp - c.lastTriggerTimestamp)
|
||||
.map((p: MonitorProcess) => {
|
||||
return {
|
||||
...p,
|
||||
lastTriggerTime: d.lastTriggerTimestamp ? dateFormat(d.lastTriggerTimestamp) : "",
|
||||
labels: p.labels.join("; "),
|
||||
};
|
||||
});
|
||||
|
||||
return { ...d, processes, lastTriggerTime: d.lastTriggerTimestamp ? dateFormat(d.lastTriggerTimestamp) : "" };
|
||||
})
|
||||
.sort((a: MonitorInstance, b: MonitorInstance) => b.lastTriggerTimestamp - a.lastTriggerTimestamp);
|
||||
});
|
||||
const currentInstances = ref<MonitorInstance[]>([]);
|
||||
|
||||
function viewProcessDashboard(process: MonitorProcess, instance: MonitorInstance) {
|
||||
if (!props.config.processDashboardName) {
|
||||
return;
|
||||
}
|
||||
router.push(
|
||||
`/dashboard/${dashboardStore.layerId}/${EntityType[8].value}/${selectorStore.currentService.id}/${instance.id}/${process.id}/${props.config.processDashboardName}`,
|
||||
);
|
||||
}
|
||||
|
||||
function viewInstanceDashboard(instance: MonitorInstance) {
|
||||
if (!props.config.instanceDashboardName) {
|
||||
return;
|
||||
}
|
||||
router.push(
|
||||
`/dashboard/${dashboardStore.layerId}/${EntityType[3].value}/${selectorStore.currentService.id}/${instance.id}/${props.config.instanceDashboardName}`,
|
||||
);
|
||||
}
|
||||
|
||||
async function changePage(pageIndex: number) {
|
||||
currentInstances.value = instances.value.filter((d: unknown, index: number) => {
|
||||
if (index >= (pageIndex - 1) * pageSize && index < pageIndex * pageSize) {
|
||||
return d;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
watch(
|
||||
() => instances.value,
|
||||
() => {
|
||||
currentInstances.value = instances.value.filter((_: unknown, index: number) => index < pageSize);
|
||||
},
|
||||
);
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.title {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.child {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.header {
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
border-bottom: 1px solid rgb(0 0 0 / 7%);
|
||||
padding: 10px 20px;
|
||||
background-color: #f3f4f9;
|
||||
}
|
||||
|
||||
.settings {
|
||||
padding: 1px 0;
|
||||
border: 1px solid #666;
|
||||
border-radius: 3px;
|
||||
color: #666;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.link {
|
||||
cursor: pointer;
|
||||
color: #409eff;
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
@@ -0,0 +1,226 @@
|
||||
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License. -->
|
||||
<template>
|
||||
<div>
|
||||
<div class="label">{{ t("targetTypes") }}</div>
|
||||
<Selector
|
||||
class="profile-input"
|
||||
size="small"
|
||||
:value="states.type"
|
||||
:options="TargetTypes"
|
||||
placeholder="Select a type"
|
||||
@change="changeType"
|
||||
/>
|
||||
</div>
|
||||
<div v-for="(item, index) in states.checkItems" :key="index">
|
||||
<div class="item-title">
|
||||
<span class="title">{{ `Item - ${index + 1}` }}</span>
|
||||
<Icon
|
||||
class="ml-5 cp"
|
||||
iconName="remove_circle_outline"
|
||||
size="middle"
|
||||
v-show="states.checkItems.length !== 1"
|
||||
@click="removeItem($event, index)"
|
||||
/>
|
||||
<Icon
|
||||
class="ml-5 cp"
|
||||
v-show="index === states.checkItems.length - 1"
|
||||
iconName="add_circle_outlinecontrol_point"
|
||||
size="middle"
|
||||
@click="createItem"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div class="label">{{ t("monitorType") }}</div>
|
||||
<Selector
|
||||
class="profile-input"
|
||||
size="small"
|
||||
:value="item.type"
|
||||
:options="MonitorType"
|
||||
placeholder="Select a type"
|
||||
@change="changeMonitorType($event, index)"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div class="label">{{ t("count") }}</div>
|
||||
<el-input-number size="small" class="profile-input" :min="0" v-model="item.count" @change="changeParam" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="label">
|
||||
<span class="mr-5">{{ t("threshold") }}</span>
|
||||
<span>({{ getNotice(item.type) }} )</span>
|
||||
</div>
|
||||
<el-input
|
||||
type="number"
|
||||
size="small"
|
||||
class="profile-input"
|
||||
v-model="item.threshold"
|
||||
@change="changeThreshold(index)"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div class="label">{{ t("period") }}</div>
|
||||
<el-input-number size="small" class="profile-input" :min="0" v-model="item.period" @change="changeParam" />
|
||||
</div>
|
||||
<div v-show="TYPES.includes(item.type)">
|
||||
<div class="label">{{ t("uriRegex") }}</div>
|
||||
<el-input size="small" class="profile-input" v-model="item.uriRegex" @change="changeParam(index)" />
|
||||
</div>
|
||||
<div v-show="TYPES.includes(item.type)">
|
||||
<div class="label">{{ t("uriList") }}</div>
|
||||
<div id="uri-param" contenteditable="true" @input="changeURI($event, index)" class="profile-input">
|
||||
{{ (item.uriList || []).join("; ") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { reactive } from "vue";
|
||||
import type { PropType } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { ElMessage } from "element-plus";
|
||||
import type { StrategyItem, CheckItems } from "@/types/continous-profiling";
|
||||
import { MonitorType, TargetTypes } from "../data";
|
||||
|
||||
/* global defineEmits, defineProps */
|
||||
const props = defineProps({
|
||||
policyList: {
|
||||
type: Object as PropType<StrategyItem[]>,
|
||||
default: () => ({}),
|
||||
},
|
||||
order: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
});
|
||||
const emits = defineEmits(["edit"]);
|
||||
const { t } = useI18n();
|
||||
const states = reactive<StrategyItem>(props.policyList[props.order]);
|
||||
const TYPES = ["HTTP_ERROR_RATE", "HTTP_AVG_RESPONSE_TIME"];
|
||||
function changeType(opt: { value: string }[]) {
|
||||
const types = props.policyList.map((item: StrategyItem) => item.type);
|
||||
if (types.includes(opt[0].value)) {
|
||||
return ElMessage.warning("Target type cannot be configured repeatedly.");
|
||||
}
|
||||
states.type = opt[0].value;
|
||||
emits("edit", states, props.order);
|
||||
}
|
||||
|
||||
function changeMonitorType(opt: { value: string }[], index: number) {
|
||||
const types = states.checkItems.map((item: CheckItems) => item.type);
|
||||
if (types.includes(opt[0].value)) {
|
||||
return ElMessage.warning("Monitor type cannot be configured repeatedly.");
|
||||
}
|
||||
states.checkItems[index].type = opt[0].value;
|
||||
emits("edit", states, props.order);
|
||||
}
|
||||
|
||||
function changeURI(event: any, index: number) {
|
||||
if (states.checkItems[index].uriRegex) {
|
||||
return ElMessage.warning("UriList or UriRegex only be configured with one option.");
|
||||
}
|
||||
const params = (event.target.textContent || "").replace(/\s+/g, "");
|
||||
const arr = params.splice(";");
|
||||
states.checkItems[index].uriList = arr.length ? arr : null;
|
||||
emits("edit", states, props.order);
|
||||
}
|
||||
|
||||
function changeThreshold(index: number) {
|
||||
let regex = /^(100(\.0{1,2})?|[1-9]?\d(\.\d{1,2})?)$/;
|
||||
if (MonitorType[1].value === states.checkItems[index].type) {
|
||||
regex = /^\d+$/;
|
||||
}
|
||||
if (MonitorType[2].value === states.checkItems[index].type) {
|
||||
regex = /^(\d+)(\.\d+)?$/;
|
||||
}
|
||||
if (MonitorType[4].value === states.checkItems[index].type) {
|
||||
regex = /^[+]{0,1}(\d+)$|^[+]{0,1}(\d+\.\d+)$/;
|
||||
}
|
||||
|
||||
if (!regex.test(states.checkItems[index].threshold)) {
|
||||
return ElMessage.error(getNotice(states.checkItems[index].type));
|
||||
}
|
||||
emits("edit", states, props.order);
|
||||
}
|
||||
|
||||
function changeParam(index?: any) {
|
||||
if (index !== undefined && (states.checkItems[index] || {}).uriList) {
|
||||
return ElMessage.warning("UriList or UriRegex only be configured with one option");
|
||||
}
|
||||
const checkItems = states.checkItems.map((d: CheckItems) => {
|
||||
d.count = Number(d.count);
|
||||
d.period = Number(d.period);
|
||||
return d;
|
||||
});
|
||||
emits("edit", { ...states, checkItems }, props.order);
|
||||
}
|
||||
|
||||
function createItem(e: PointerEvent) {
|
||||
e.stopPropagation();
|
||||
states.checkItems.push({
|
||||
type: "",
|
||||
threshold: "",
|
||||
period: NaN,
|
||||
count: NaN,
|
||||
});
|
||||
emits("edit", states, props.order);
|
||||
}
|
||||
|
||||
function removeItem(e: PointerEvent, key: number) {
|
||||
e.stopPropagation();
|
||||
if (states.checkItems.length === 1) {
|
||||
return;
|
||||
}
|
||||
states.checkItems = states.checkItems.filter((_, index: number) => index !== key);
|
||||
emits("edit", states, props.order);
|
||||
}
|
||||
|
||||
function getNotice(type: string) {
|
||||
const map: { [key: string]: string } = {
|
||||
PROCESS_CPU: "It is a percentage data",
|
||||
PROCESS_THREAD_COUNT: "It is a positive integer",
|
||||
SYSTEM_LOAD: "It is a floating point number",
|
||||
HTTP_ERROR_RATE: "It is percentage data",
|
||||
HTTP_AVG_RESPONSE_TIME: "It is a response time in milliseconds",
|
||||
};
|
||||
|
||||
return map[type];
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.profile-input {
|
||||
width: 300px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#uri-param {
|
||||
border: 1px solid #dcdfe6;
|
||||
cursor: text;
|
||||
padding: 0 5px;
|
||||
border-radius: 4px;
|
||||
color: #606266;
|
||||
outline: none;
|
||||
height: 100px;
|
||||
|
||||
&:focus {
|
||||
border-color: #409eff;
|
||||
}
|
||||
}
|
||||
|
||||
.item-title {
|
||||
margin-bottom: 5px;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
@@ -0,0 +1,205 @@
|
||||
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License. -->
|
||||
<template>
|
||||
<div class="profile-task-list flex-v" v-loading="continousProfilingStore.policyLoading">
|
||||
<div class="profile-task-wrapper flex-v">
|
||||
<div class="profile-t-tool">
|
||||
<span>{{ t("policyList") }}</span>
|
||||
<span class="new-task cp" @click="setStrategies">
|
||||
<Icon iconName="edit" size="middle" />
|
||||
</span>
|
||||
</div>
|
||||
<div class="profile-t-wrapper">
|
||||
<div class="no-data" v-show="!continousProfilingStore.strategyList.length">
|
||||
{{ t("noData") }}
|
||||
</div>
|
||||
<table class="profile-t">
|
||||
<tr
|
||||
class="profile-tr cp"
|
||||
v-for="(i, index) in continousProfilingStore.strategyList"
|
||||
@click="changePolicy(i)"
|
||||
:key="index"
|
||||
>
|
||||
<td
|
||||
class="profile-td"
|
||||
:class="{
|
||||
selected: continousProfilingStore.selectedStrategy.id === i.id,
|
||||
}"
|
||||
>
|
||||
<div class="ell">
|
||||
<span class="sm">
|
||||
{{ i.type }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="grey ell sm" v-for="(item, index) in i.checkItems" :key="index">
|
||||
<span class="sm">
|
||||
{{
|
||||
`${item.type} >= ${item.threshold}${
|
||||
[MonitorType[0].value, MonitorType[3].value].includes(item.type) ? "%" : ""
|
||||
}; `
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-dialog
|
||||
v-model="updateStrategies"
|
||||
:title="t('editStrategy')"
|
||||
:destroy-on-close="true"
|
||||
fullscreen
|
||||
@closed="updateStrategies = false"
|
||||
>
|
||||
<EditPolicy :policyList="continousProfilingStore.strategyList" @save="editStrategies" />
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useContinousProfilingStore } from "@/store/modules/continous-profiling";
|
||||
import { useSelectorStore } from "@/store/modules/selectors";
|
||||
import type { StrategyItem, CheckItems } from "@/types/continous-profiling";
|
||||
import { ElMessage } from "element-plus";
|
||||
import EditPolicy from "./EditPolicy.vue";
|
||||
import { MonitorType } from "../data";
|
||||
|
||||
const { t } = useI18n();
|
||||
const selectorStore = useSelectorStore();
|
||||
const continousProfilingStore = useContinousProfilingStore();
|
||||
const updateStrategies = ref<boolean>(false);
|
||||
const inProcess = ref<boolean>(false);
|
||||
|
||||
fetchStrategyList();
|
||||
|
||||
async function changePolicy(item: StrategyItem) {
|
||||
continousProfilingStore.setSelectedStrategy(item);
|
||||
const serviceId = (selectorStore.currentService && selectorStore.currentService.id) || "";
|
||||
await continousProfilingStore.getMonitoringInstances(serviceId);
|
||||
}
|
||||
|
||||
function setStrategies() {
|
||||
updateStrategies.value = true;
|
||||
}
|
||||
|
||||
async function editStrategies(
|
||||
targets: {
|
||||
targetType: string;
|
||||
checkItems: CheckItems[];
|
||||
}[],
|
||||
) {
|
||||
const serviceId = (selectorStore.currentService && selectorStore.currentService.id) || "";
|
||||
if (!serviceId) {
|
||||
return ElMessage.error("No Service ID");
|
||||
}
|
||||
const res = await continousProfilingStore.setContinuousProfilingPolicy(serviceId, targets);
|
||||
if (res.errors) {
|
||||
ElMessage.error(res.errors);
|
||||
return;
|
||||
}
|
||||
if (!res.data.strategy.status) {
|
||||
ElMessage.error(res.data.strategy.errorReason);
|
||||
return;
|
||||
}
|
||||
updateStrategies.value = false;
|
||||
await fetchStrategyList();
|
||||
}
|
||||
|
||||
async function fetchStrategyList() {
|
||||
const serviceId = (selectorStore.currentService && selectorStore.currentService.id) || "";
|
||||
const res = await continousProfilingStore.getStrategyList({
|
||||
serviceId,
|
||||
});
|
||||
|
||||
if (res.errors) {
|
||||
return ElMessage.error(res.errors);
|
||||
}
|
||||
if (!continousProfilingStore.strategyList.length) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => selectorStore.currentService,
|
||||
() => {
|
||||
inProcess.value = false;
|
||||
fetchStrategyList();
|
||||
},
|
||||
);
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.profile-task-list {
|
||||
width: 300px;
|
||||
height: 98%;
|
||||
overflow: auto;
|
||||
border-right: 1px solid rgb(0 0 0 / 10%);
|
||||
}
|
||||
|
||||
.item span {
|
||||
height: 21px;
|
||||
}
|
||||
|
||||
.profile-td {
|
||||
padding: 10px 5px 10px 10px;
|
||||
border-bottom: 1px solid rgb(0 0 0 / 7%);
|
||||
|
||||
&.selected {
|
||||
background-color: #ededed;
|
||||
}
|
||||
}
|
||||
|
||||
.no-data {
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.profile-t-wrapper {
|
||||
overflow: auto;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.profile-t {
|
||||
width: 100%;
|
||||
border-spacing: 0;
|
||||
table-layout: fixed;
|
||||
flex-grow: 1;
|
||||
position: relative;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.profile-tr {
|
||||
&:hover {
|
||||
background-color: rgb(0 0 0 / 4%);
|
||||
}
|
||||
}
|
||||
|
||||
.profile-t-tool {
|
||||
padding: 10px 5px 10px 10px;
|
||||
border-bottom: 1px solid rgb(0 0 0 / 7%);
|
||||
background: #f3f4f9;
|
||||
width: 100%;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.new-task {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.reload {
|
||||
margin-left: 30px;
|
||||
}
|
||||
</style>
|
44
src/views/dashboard/related/continuous-profiling/data.ts
Normal file
44
src/views/dashboard/related/continuous-profiling/data.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export const MonitorType: any = [
|
||||
{ label: "PROCESS_CPU", value: "PROCESS_CPU" },
|
||||
{ label: "PROCESS_THREAD_COUNT", value: "PROCESS_THREAD_COUNT" },
|
||||
{ label: "SYSTEM_LOAD", value: "SYSTEM_LOAD" },
|
||||
{ label: "HTTP_ERROR_RATE", value: "HTTP_ERROR_RATE" },
|
||||
{ label: "HTTP_AVG_RESPONSE_TIME", value: "HTTP_AVG_RESPONSE_TIME" },
|
||||
];
|
||||
|
||||
export const TargetTypes = [
|
||||
{ label: "ON_CPU", value: "ON_CPU" },
|
||||
{ label: "OFF_CPU", value: "OFF_CPU" },
|
||||
{ label: "NETWORK", value: "NETWORK" },
|
||||
];
|
||||
|
||||
export const ComponentType = "CONTINOUS_PROFILING";
|
||||
|
||||
export const HeaderLabels = [
|
||||
{ value: "triggeredCount", label: "Triggered Count", width: 150 },
|
||||
{ value: "lastTriggerTime", label: "Last Trigger Time", width: 170 },
|
||||
];
|
||||
|
||||
export const HeaderChildLabels = [
|
||||
{ value: "detectType", label: "Detect Type", width: 100 },
|
||||
{ value: "triggeredCount", label: "Triggered Count", width: 120 },
|
||||
{ value: "lastTriggerTime", label: "Last Trigger Time", width: 160 },
|
||||
{ value: "labels", label: "Labels" },
|
||||
];
|
@@ -33,6 +33,7 @@ limitations under the License. -->
|
||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||
import { EntityType } from "../../data";
|
||||
import { EBPFProfilingTriggerType } from "@/store/data";
|
||||
|
||||
/*global defineProps */
|
||||
const props = defineProps({
|
||||
@@ -54,6 +55,7 @@ limitations under the License. -->
|
||||
const res = await ebpfStore.getTaskList({
|
||||
serviceId,
|
||||
targets: ["ON_CPU", "OFF_CPU"],
|
||||
triggerType: EBPFProfilingTriggerType.FIXED_TIME,
|
||||
});
|
||||
|
||||
if (res.errors) {
|
||||
|
@@ -42,7 +42,7 @@ limitations under the License. -->
|
||||
/>
|
||||
<el-popover placement="bottom" :width="680" trigger="click" :persistent="false">
|
||||
<template #reference>
|
||||
<el-button type="primary" size="small">
|
||||
<el-button size="small">
|
||||
{{ t("processSelect") }}
|
||||
</el-button>
|
||||
</template>
|
||||
@@ -98,12 +98,21 @@ 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();
|
||||
const ebpfStore = useEbpfStore();
|
||||
/*global defineProps*/
|
||||
const props = defineProps({
|
||||
type: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
});
|
||||
const ebpfStore = props.type === ComponentType ? useContinousProfilingStore() : useEbpfStore();
|
||||
const pageSize = 5;
|
||||
const multipleTableRef = ref<InstanceType<typeof ElTable>>();
|
||||
const selectedProcesses = ref<string[]>([]);
|
||||
|
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License. -->
|
||||
<template>
|
||||
<div id="graph-stack" ref="graph">
|
||||
<span class="tip" v-show="ebpfStore.tip">{{ ebpfStore.tip }}</span>
|
||||
<span class="tip" v-show="ebpfStore.ebpfTips">{{ ebpfStore.ebpfTips }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
@@ -23,12 +23,20 @@ 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 type { StackElement } from "@/types/ebpf";
|
||||
import { AggregateTypes } from "./data";
|
||||
import "d3-flame-graph/dist/d3-flamegraph.css";
|
||||
|
||||
/*global Nullable*/
|
||||
const ebpfStore = useEbpfStore();
|
||||
/*global Nullable, defineProps*/
|
||||
const props = defineProps({
|
||||
type: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
});
|
||||
const ebpfStore = props.type === ComponentType ? useContinousProfilingStore() : useEbpfStore();
|
||||
const stackTree = ref<Nullable<StackElement>>(null);
|
||||
const selectStack = ref<Nullable<StackElement>>(null);
|
||||
const graph = ref<Nullable<HTMLDivElement>>(null);
|
||||
@@ -90,7 +98,7 @@ limitations under the License. -->
|
||||
.setColorMapper((d, originalColor) => (d.highlight ? "#6aff8f" : originalColor));
|
||||
const tip = (d3tip as any)()
|
||||
.attr("class", "d3-tip")
|
||||
.direction("w")
|
||||
.direction("s")
|
||||
.html((d: { data: StackElement } & { parent: { data: StackElement } }) => {
|
||||
const name = d.data.name.replace("<", "<").replace(">", ">");
|
||||
const valStr =
|
||||
@@ -111,7 +119,7 @@ limitations under the License. -->
|
||||
}</div>`;
|
||||
return `<div class="mb-5 name">Symbol: ${name}</div>${valStr}${rateOfParent}${rateOfRoot}`;
|
||||
})
|
||||
.style("max-width", "500px");
|
||||
.style("max-width", "400px");
|
||||
flameChart.value.tooltip(tip);
|
||||
d3.select("#graph-stack").datum(stackTree.value).call(flameChart.value);
|
||||
}
|
||||
|
@@ -34,7 +34,7 @@ limitations under the License. -->
|
||||
defineProps({
|
||||
config: {
|
||||
type: Object as PropType<any>,
|
||||
default: () => ({ graph: {} }),
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
const networkProfilingStore = useNetworkProfilingStore();
|
||||
|
@@ -19,7 +19,7 @@ limitations under the License. -->
|
||||
<g class="hex-polygon">
|
||||
<path :d="getHexPolygonVertices()" stroke="#D5DDF6" stroke-width="2" fill="none" />
|
||||
<text :x="0" :y="radius - 15" fill="#000" text-anchor="middle">
|
||||
{{ selectorStore.currentPod.label }}
|
||||
{{ selectorStore.currentPod && selectorStore.currentPod.label }}
|
||||
</text>
|
||||
</g>
|
||||
<g class="nodes">
|
||||
@@ -530,11 +530,11 @@ limitations under the License. -->
|
||||
border-radius: 3px;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
right: 20px;
|
||||
}
|
||||
|
||||
.range {
|
||||
right: 50px;
|
||||
right: 60px;
|
||||
}
|
||||
|
||||
.topo-call {
|
||||
|
@@ -77,6 +77,7 @@ limitations under the License. -->
|
||||
import getLocalTime from "@/utils/localtime";
|
||||
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||
import NewTask from "./NewTask.vue";
|
||||
import { EBPFProfilingTriggerType } from "@/store/data";
|
||||
|
||||
/*global Nullable */
|
||||
const { t } = useI18n();
|
||||
@@ -185,6 +186,7 @@ limitations under the License. -->
|
||||
serviceId,
|
||||
serviceInstanceId,
|
||||
targets: ["NETWORK"],
|
||||
triggerType: EBPFProfilingTriggerType.FIXED_TIME,
|
||||
});
|
||||
|
||||
if (res.errors) {
|
||||
|
40
src/views/dashboard/related/task-timeline/Content.vue
Normal file
40
src/views/dashboard/related/task-timeline/Content.vue
Normal file
@@ -0,0 +1,40 @@
|
||||
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License. -->
|
||||
<template>
|
||||
<div class="flex-v content">
|
||||
<Timeline />
|
||||
<ProfilingPanel :config="config" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { PropType } from "vue";
|
||||
import Timeline from "./components/Timeline.vue";
|
||||
import ProfilingPanel from "./components/ProfilingPanel.vue";
|
||||
|
||||
/* global defineProps */
|
||||
defineProps({
|
||||
config: {
|
||||
type: Object as PropType<any>,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.content {
|
||||
height: calc(100% - 50px);
|
||||
width: 100%;
|
||||
padding: 0 10px;
|
||||
}
|
||||
</style>
|
@@ -0,0 +1,83 @@
|
||||
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License. -->
|
||||
<template>
|
||||
<div class="content" v-if="taskTimelineStore.selectedTask.targetType === TargetTypes[2].value">
|
||||
<process-topology v-if="networkProfilingStore.nodes.length" :config="config" />
|
||||
</div>
|
||||
<div
|
||||
class="content"
|
||||
v-if="[TargetTypes[1].value, TargetTypes[0].value].includes(taskTimelineStore.selectedTask.targetType)"
|
||||
>
|
||||
<div class="schedules">
|
||||
<EBPFSchedules />
|
||||
</div>
|
||||
<div class="item">
|
||||
<EBPFStack />
|
||||
</div>
|
||||
</div>
|
||||
<div class="text" v-if="!taskTimelineStore.selectedTask.targetType">
|
||||
{{ t("noData") }}
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { PropType } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useTaskTimelineStore } from "@/store/modules/task-timeline";
|
||||
import { useNetworkProfilingStore } from "@/store/modules/network-profiling";
|
||||
import { TargetTypes } from "../../continuous-profiling/data";
|
||||
import ProcessTopology from "@/views/dashboard/related/network-profiling/components/ProcessTopology.vue";
|
||||
import EBPFSchedules from "@/views/dashboard/related/ebpf/components/EBPFSchedules.vue";
|
||||
import EBPFStack from "@/views/dashboard/related/ebpf/components/EBPFStack.vue";
|
||||
|
||||
/*global defineProps */
|
||||
defineProps({
|
||||
config: {
|
||||
type: Object as PropType<any>,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
const { t } = useI18n();
|
||||
const taskTimelineStore = useTaskTimelineStore();
|
||||
const networkProfilingStore = useNetworkProfilingStore();
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.content {
|
||||
width: 100%;
|
||||
height: calc(100% - 30px);
|
||||
flex-grow: 2;
|
||||
min-width: 700px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.text {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
margin-top: 100px;
|
||||
}
|
||||
|
||||
.item {
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
height: calc(100% - 100px);
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.schedules {
|
||||
height: 90px;
|
||||
border-bottom: 1px solid #ccc;
|
||||
padding-right: 10px;
|
||||
}
|
||||
</style>
|
@@ -0,0 +1,177 @@
|
||||
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License. -->
|
||||
<template>
|
||||
<div ref="timeline" class="task-timeline"></div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, onMounted, onUnmounted } from "vue";
|
||||
import dayjs from "dayjs";
|
||||
import { useThrottleFn } from "@vueuse/core";
|
||||
import { ElMessage } from "element-plus";
|
||||
import type { EBPFTaskList } from "@/types/ebpf";
|
||||
import { useTaskTimelineStore } from "@/store/modules/task-timeline";
|
||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||
import { useSelectorStore } from "@/store/modules/selectors";
|
||||
import { useContinousProfilingStore } from "@/store/modules/continous-profiling";
|
||||
import { DataSet, Timeline } from "vis-timeline/standalone";
|
||||
import "vis-timeline/styles/vis-timeline-graph2d.css";
|
||||
import { EBPFProfilingTriggerType } from "@/store/data";
|
||||
|
||||
const taskTimelineStore = useTaskTimelineStore();
|
||||
const selectorStore = useSelectorStore();
|
||||
const continousProfilingStore = useContinousProfilingStore();
|
||||
const dashboardStore = useDashboardStore();
|
||||
/* global defineProps, Nullable */
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
const timeline = ref<Nullable<HTMLDivElement>>(null);
|
||||
const visGraph = ref<Nullable<any>>(null);
|
||||
const oldVal = ref<{ width: number; height: number }>({ width: 0, height: 0 });
|
||||
const visDate = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") => dayjs(date).format(pattern);
|
||||
|
||||
init();
|
||||
onMounted(() => {
|
||||
oldVal.value = (timeline.value && timeline.value.getBoundingClientRect()) || {
|
||||
width: 0,
|
||||
height: 0,
|
||||
};
|
||||
useThrottleFn(resize, 500)();
|
||||
});
|
||||
|
||||
async function init() {
|
||||
const serviceId = (selectorStore.currentService && selectorStore.currentService.id) || "";
|
||||
const serviceInstanceId = (selectorStore.currentPod && selectorStore.currentPod.id) || "";
|
||||
const type = continousProfilingStore.selectedStrategy.type;
|
||||
const res = await taskTimelineStore.getContinousTaskList({
|
||||
serviceId,
|
||||
serviceInstanceId,
|
||||
targets: type ? [type] : null,
|
||||
triggerType: EBPFProfilingTriggerType.CONTINUOUS_PROFILING,
|
||||
});
|
||||
if (res.errors) {
|
||||
ElMessage.error(res.errors);
|
||||
return;
|
||||
}
|
||||
visTimeline();
|
||||
}
|
||||
|
||||
function visTimeline() {
|
||||
if (!timeline.value) {
|
||||
return;
|
||||
}
|
||||
if (visGraph.value) {
|
||||
visGraph.value.destroy();
|
||||
}
|
||||
const h = timeline.value.getBoundingClientRect().height;
|
||||
const taskList = taskTimelineStore.taskList.map((d: EBPFTaskList, index: number) => {
|
||||
return {
|
||||
id: index,
|
||||
// content: d.targetType,
|
||||
start: new Date(Number(d.taskStartTime)),
|
||||
end: new Date(Number(d.taskStartTime + d.fixedTriggerDuration * 1000)),
|
||||
data: d,
|
||||
className: d.targetType,
|
||||
};
|
||||
});
|
||||
const items: any = new DataSet(taskList);
|
||||
const options: any = {
|
||||
height: h,
|
||||
width: "100%",
|
||||
locale: "en",
|
||||
groupHeightMode: "fitItems",
|
||||
autoResize: false,
|
||||
tooltip: {
|
||||
overflowMethod: "cap",
|
||||
template(item: EBPFTaskList | any) {
|
||||
const data = item.data || {};
|
||||
const end = data.taskStartTime ? visDate(data.taskStartTime + data.fixedTriggerDuration * 1000) : "";
|
||||
|
||||
let tmp = `
|
||||
<div>Task ID: ${data.taskId || ""}</div>
|
||||
<div>Service Name: ${data.serviceName || ""}</div>
|
||||
<div>Service Instance Name: ${data.serviceInstanceName || ""}</div>
|
||||
<div>Service Process Name: ${data.processName || ""}</div>
|
||||
<div>Target Type: ${data.targetType || ""}</div>
|
||||
<div>Trigger Type: ${data.triggerType || ""}</div>
|
||||
<div>Start Time: ${data.taskStartTime ? visDate(data.taskStartTime) : ""}</div>
|
||||
<div>End Time: ${end}</div>
|
||||
<div>Process Labels: ${data.processLabels.join("; ") || ""}</div>`;
|
||||
let str = "";
|
||||
for (const item of data.continuousProfilingCauses || []) {
|
||||
str += `<div>${item.type}: ${getURI(item.uri)}${item.uri.threshold}>=${item.uri.current}</div>`;
|
||||
}
|
||||
return tmp + str;
|
||||
},
|
||||
},
|
||||
};
|
||||
visGraph.value = new Timeline(timeline.value, items, options);
|
||||
visGraph.value.on("select", async (properties: { items: number[] }) => {
|
||||
dashboardStore.selectWidget(props.data);
|
||||
const index = properties.items[0];
|
||||
const task = taskTimelineStore.taskList[index];
|
||||
|
||||
await taskTimelineStore.setSelectedTask(task);
|
||||
await taskTimelineStore.getGraphData();
|
||||
});
|
||||
}
|
||||
|
||||
function getURI(uri: { uriRegex: string; uriPath: string }) {
|
||||
return uri ? `(${uri.uriRegex || ""} | ${uri.uriPath || ""})` : "";
|
||||
}
|
||||
|
||||
function resize() {
|
||||
const observer = new ResizeObserver((entries) => {
|
||||
const entry = entries[0];
|
||||
const cr = entry.contentRect;
|
||||
if (Math.abs(cr.width - oldVal.value.width) < 3 && Math.abs(cr.height - oldVal.value.height) < 3) {
|
||||
return;
|
||||
}
|
||||
visTimeline();
|
||||
oldVal.value = { width: cr.width, height: cr.height };
|
||||
});
|
||||
if (timeline.value) {
|
||||
observer.observe(timeline.value);
|
||||
}
|
||||
}
|
||||
onUnmounted(() => {
|
||||
if (visGraph.value) {
|
||||
visGraph.value.destroy();
|
||||
}
|
||||
taskTimelineStore.setTaskList([]);
|
||||
});
|
||||
watch(
|
||||
() => selectorStore.currentPod,
|
||||
() => {
|
||||
init();
|
||||
},
|
||||
);
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.task-timeline {
|
||||
width: calc(100% - 5px);
|
||||
margin: 0 5px 5px 0;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.message {
|
||||
max-width: 400px;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
Reference in New Issue
Block a user