diff --git a/src/store/modules/dashboard.ts b/src/store/modules/dashboard.ts index bad5e8ef..3be6f0da 100644 --- a/src/store/modules/dashboard.ts +++ b/src/store/modules/dashboard.ts @@ -113,18 +113,12 @@ export const dashboardStore = defineStore({ : 3, }; } - if ( - [ - "Trace", - "Profile", - "Log", - "DemandLog", - "Ebpf", - "NetworkProfiling", - ].includes(type) - ) { + if (["Trace", "Profile", "Log", "DemandLog", "Ebpf"].includes(type)) { newItem.h = 36; } + if (["NetworkProfiling"].includes(type)) { + newItem.h = 44; + } if (type === "Text") { newItem.h = 6; newItem.graph = TextConfig; @@ -184,18 +178,12 @@ export const dashboardStore = defineStore({ showDepth: true, }; } - if ( - [ - "Trace", - "Profile", - "Log", - "DemandLog", - "Ebpf", - "NetworkProfiling", - ].includes(type) - ) { + if (["Trace", "Profile", "Log", "DemandLog", "Ebpf"].includes(type)) { newItem.h = 32; } + if (["NetworkProfiling"].includes(type)) { + newItem.h = 44; + } if (type === "Text") { newItem.h = 6; newItem.graph = TextConfig; diff --git a/src/views/dashboard/related/components/D3Graph/nodeProcess.ts b/src/views/dashboard/related/components/D3Graph/nodeProcess.ts new file mode 100644 index 00000000..1216f126 --- /dev/null +++ b/src/views/dashboard/related/components/D3Graph/nodeProcess.ts @@ -0,0 +1,54 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import icons from "@/assets/img/icons"; +import { Node } from "@/types/topology"; + +export default (d3: any, graph: any, funcs: any, tip: any) => { + const nodeEnter = graph + .append("g") + .call( + d3 + .drag() + .on("start", funcs.dragstart) + .on("drag", funcs.dragged) + .on("end", funcs.dragended) + ) + .on("mouseover", function (event: unknown, d: Node) { + tip.html(funcs.tipHtml).show(d, this); + }) + .on("mouseout", function () { + tip.hide(this); + }); + nodeEnter + .append("image") + .attr("width", 40) + .attr("height", 40) + .attr("x", (d: any) => d.x) + .attr("y", (d: any) => d.y) + .attr("style", "cursor: move;") + .attr("xlink:href", icons.CUBE); + nodeEnter + .append("text") + .attr("fill", "#000") + .attr("text-anchor", "middle") + .attr("x", (d: any) => d.x + 15) + .attr("y", (d: any) => d.y + 50) + .text((d: { name: string }) => + d.name.length > 20 ? `${d.name.substring(0, 20)}...` : d.name + ); + return nodeEnter; +}; diff --git a/src/views/dashboard/related/network-profiling/Content.vue b/src/views/dashboard/related/network-profiling/Content.vue index 13a059c9..693a726d 100644 --- a/src/views/dashboard/related/network-profiling/Content.vue +++ b/src/views/dashboard/related/network-profiling/Content.vue @@ -49,7 +49,7 @@ const networkProfilingStore = useNetworkProfilingStore(); .item { width: 100%; height: calc(100% - 210px); - background-color: #333840; + // background-color: #333840; position: relative; } diff --git a/src/views/dashboard/related/network-profiling/components/Graph/layout.ts b/src/views/dashboard/related/network-profiling/components/Graph/layout.ts new file mode 100644 index 00000000..6a5a5414 --- /dev/null +++ b/src/views/dashboard/related/network-profiling/components/Graph/layout.ts @@ -0,0 +1,144 @@ +/** + * 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. + */ +class Orientation { + public f0 = 0; + public f1 = 0; + public f2 = 0; + public f3 = 0; + public b0? = 0; + public b1? = 0; + public b2? = 0; + public b3? = 0; + public start_angle? = 0; + constructor( + f0: number, + f1: number, + f2: number, + f3: number, + b0: number, + b1: number, + b2: number, + b3: number, + start_angle: number + ) { + this.f0 = f0; + this.f1 = f1; + this.f2 = f2; + this.f3 = f3; + this.b0 = b0; + this.b1 = b1; + this.b2 = b2; + this.b3 = b3; + this.start_angle = start_angle; + } +} + +const SQRT3 = Math.sqrt(3.0); +class Layout { + static Pointy = new Orientation( + SQRT3, + SQRT3 / 2.0, + 0.0, + 3.0 / 2.0, + SQRT3 / 3.0, + -1.0 / 3.0, + 0.0, + 2.0 / 3.0, + 0.5 + ); + static Flat = new Orientation( + 3.0 / 2.0, + 0.0, + SQRT3 / 2.0, + SQRT3, + 2.0 / 3.0, + 0.0, + -1.0 / 3.0, + SQRT3 / 3.0, + 0.0 + ); + + static spacing(radius: number, isPointy = false): number[] { + return isPointy + ? [SQRT3 * radius, 2 * radius * (3 / 4)] + : [2 * radius * (3 / 4), SQRT3 * radius]; + } + + private radius = 1; + private orientation: Orientation = { f0: 0, f1: 0, f2: 0, f3: 0 }; + private origin = [0, 0]; + + constructor(radius: number, origin = [0, 0], orientation?: Orientation) { + this.radius = radius; //Layout.spacing( radius, ( orientation === Layout.Pointy ) ); + this.orientation = orientation || Layout.Flat; + this.origin = origin; + } + + // Same as HexToPixel, Except it takes raw coords instead of hex object. + axialToPixel(ax: number, ay: number): number[] { + const M = this.orientation; + const x = (M.f0 * ax + M.f1 * ay) * this.radius; + const y = (M.f2 * ax + M.f3 * ay) * this.radius; + + return [x + this.origin[0], y + this.origin[1]]; + } + + hexToPixel(h: { x: number; y: number }): number[] { + const M = this.orientation; + const x = (M.f0 * h.x + M.f1 * h.y) * this.radius; + const y = (M.f2 * h.x + M.f3 * h.y) * this.radius; + + return [x + this.origin[0], y + this.origin[1]]; + } +} + +class Hex extends Int16Array { + constructor(x: number, y: number, z = null) { + super(3); + this.xyz(x, y, z); + } + + xyz(x: number, y: number, z: number | null = null): Hex { + if (z == null) z = -x - y; + if (x + y + z != 0) { + console.log("Bad Axial Coordinate : : q %d r %d s %d", x, y, z); + } + + this[0] = x; + this[1] = y; + this[2] = z; + return this; + } + + get x(): number { + return this[0]; + } + get y(): number { + return this[1]; + } + get z(): number { + return this[2]; + } + + get len(): number { + return Math.floor( + (Math.abs(this[0]) + Math.abs(this[1]) + Math.abs(this[2])) / 2 + ); + } +} + +export { Hex, Orientation, Layout }; diff --git a/src/views/dashboard/related/network-profiling/components/ProcessTopology.vue b/src/views/dashboard/related/network-profiling/components/ProcessTopology.vue index 9ac5a9c0..b219c6ca 100644 --- a/src/views/dashboard/related/network-profiling/components/ProcessTopology.vue +++ b/src/views/dashboard/related/network-profiling/components/ProcessTopology.vue @@ -36,16 +36,12 @@ import router from "@/router"; import { useNetworkProfilingStore } from "@/store/modules/network-profiling"; import { useDashboardStore } from "@/store/modules/dashboard"; import d3tip from "d3-tip"; -import { - simulationInit, - simulationSkip, -} from "../../components/D3Graph/simulation"; import { linkElement, anchorElement, arrowMarker, } from "../../components/D3Graph/linkElement"; -import nodeElement from "../../components/D3Graph/nodeElement"; +import nodeElement from "../../components/D3Graph/nodeProcess"; import { Call } from "@/types/topology"; // import zoom from "../../components/D3Graph/zoom"; import { ProcessNode } from "@/types/ebpf"; @@ -53,6 +49,7 @@ import { useThrottleFn } from "@vueuse/core"; import Settings from "./Settings.vue"; import { EntityType } from "@/views/dashboard/data"; import getDashboard from "@/hooks/useDashboardsSession"; +import { Layout } from "./Graph/layout"; /*global Nullable, defineProps */ const props = defineProps({ @@ -66,7 +63,6 @@ const dashboardStore = useDashboardStore(); const networkProfilingStore = useNetworkProfilingStore(); const height = ref(100); const width = ref(100); -const simulation = ref(null); const svg = ref>(null); const chart = ref>(null); const tip = ref>(null); @@ -108,14 +104,8 @@ function drawGraph() { graph.value = svg.value .append("g") .attr("class", "svg-graph") - .attr("transform", `translate(-250, -220)`); + .attr("transform", `translate(200, 200)`); graph.value.call(tip.value); - simulation.value = simulationInit( - d3, - networkProfilingStore.nodes, - networkProfilingStore.calls, - ticked - ); node.value = graph.value.append("g").selectAll(".topo-node"); link.value = graph.value.append("g").selectAll(".topo-line"); anchor.value = graph.value.append("g").selectAll(".topo-line-anchor"); @@ -130,15 +120,73 @@ function drawGraph() { }); useThrottleFn(resize, 500)(); } + +function hexGrid(n = 1, radius = 1, origin = [0, 0]) { + let x, y, yn, p; + const gLayout = new Layout(radius, origin); + const pos = []; + // x = -1; n = 1.5 + for (x = -n; x <= n; x++) { + y = Math.max(-n, -x - n); // 0 + yn = Math.min(n, -x + n); // 1 + // y = 0 yn = 1 + for (y; y <= yn; y++) { + p = gLayout.axialToPixel(x, y); + pos.push(...p); + } + } + return pos; +} + +function createPolygon(radius: number, sides = 6, offset = 0) { + const poly: number[][] = []; + let i, rad; + for (i = 0; i < sides; i++) { + rad = Math.PI * 2 * (i / sides); + poly.push([ + Math.cos(rad + offset) * radius, + Math.sin(rad + offset) * radius, + ]); + } + return poly; +} + function update() { - // node element if (!node.value || !link.value) { return; } - node.value = node.value.data( - networkProfilingStore.nodes, - (d: ProcessNode) => d.id - ); + const obj: any = (chart.value && chart.value.getBoundingClientRect()) || {}; + const p = { + hexagonParam: [27, 0.04, 5, 0.04, 0], + count: 1, + radius: obj.width / 5, // layout hexagons radius + }; + const polygon = createPolygon(p.radius, 6, 0); + const origin = [0, 0]; + const vertices: any = []; // a hexagon vertices + for (let v = 0; v < polygon.length; v++) { + vertices.push([origin[0] + polygon[v][0], origin[1] + polygon[v][1]]); + } + const linePath = d3.line(); + linePath.curve(d3.curveLinearClosed); + graph.value + .append("path") + .attr("d", linePath(vertices)) + .attr("stroke", "#ccc") + .attr("stroke-width", 2) + .style("fill", "none"); + const centers = hexGrid(p.count, 50, origin); // cube centers + const pos = hexGrid(p.count, 50, [p.radius * 2 + 20.0]); // cube centers + // console.log(centers); + const nodeArr = networkProfilingStore.nodes; + for (let v = 0; v < 7; v++) { + const x = origin[0] + centers[v * 2]; + const y = origin[1] + centers[v * 2 + 1]; + nodeArr[v].x = x; + nodeArr[v].y = y; + } + node.value = node.value.data(nodeArr, (d: ProcessNode) => d.id); + node.value.exit().remove(); node.value = nodeElement( d3, @@ -150,62 +198,6 @@ function update() { }, tip.value ).merge(node.value); - // line element - link.value = link.value.data(networkProfilingStore.calls, (d: Call) => d.id); - link.value.exit().remove(); - link.value = linkElement(link.value.enter()).merge(link.value); - // anchorElement - anchor.value = anchor.value.data( - networkProfilingStore.calls, - (d: Call) => d.id - ); - anchor.value.exit().remove(); - anchor.value = anchorElement( - anchor.value.enter(), - { - handleLinkClick: handleLinkClick, - tipHtml: (data: Call) => { - const html = `
${t( - "detectPoint" - )}:${data.detectPoints.join(" | ")}
`; - return html; - }, - }, - tip.value - ).merge(anchor.value); - // arrow marker - arrow.value = arrow.value.data( - networkProfilingStore.calls, - (d: Call) => d.id - ); - arrow.value.exit().remove(); - arrow.value = arrowMarker(arrow.value.enter()).merge(arrow.value); - // force element - simulation.value.nodes(networkProfilingStore.nodes); - simulation.value - .force("link") - .links(networkProfilingStore.calls) - .id((d: Call) => d.id); - simulationSkip(d3, simulation.value, ticked); - const loopMap: any = {}; - for (let i = 0; i < networkProfilingStore.calls.length; i++) { - const link: any = networkProfilingStore.calls[i]; - link.loopFactor = 1; - for (let j = 0; j < networkProfilingStore.calls.length; j++) { - if (i === j || loopMap[i]) { - continue; - } - const otherLink = networkProfilingStore.calls[j]; - if ( - link.source.id === otherLink.target.id && - link.target.id === otherLink.source.id - ) { - link.loopFactor = -1; - loopMap[j] = 1; - break; - } - } - } } function handleLinkClick(event: any, d: Call) { @@ -306,6 +298,8 @@ watch( margin: 0 5px 5px 0; height: 100%; min-height: 150px; + min-width: 300px; + overflow: auto; } .process-svg { diff --git a/src/views/dashboard/related/network-profiling/components/Tasks.vue b/src/views/dashboard/related/network-profiling/components/Tasks.vue index 913cca94..f664e5da 100644 --- a/src/views/dashboard/related/network-profiling/components/Tasks.vue +++ b/src/views/dashboard/related/network-profiling/components/Tasks.vue @@ -143,7 +143,6 @@ async function createTask() { (selectorStore.currentService && selectorStore.currentService.id) || ""; const serviceInstanceId = (selectorStore.currentPod && selectorStore.currentPod.id) || ""; - console.log(selectorStore.currentPod); if (!serviceId) { return; } @@ -235,5 +234,6 @@ async function fetchTasks() { .new-task { float: right; + margin-right: 3px; } diff --git a/src/views/dashboard/related/network-profiling/components/backup.vue b/src/views/dashboard/related/network-profiling/components/backup.vue new file mode 100644 index 00000000..812d65dc --- /dev/null +++ b/src/views/dashboard/related/network-profiling/components/backup.vue @@ -0,0 +1,343 @@ + + + +