Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Greenplum #686

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
6 changes: 6 additions & 0 deletions src/assets/scss/_plan-node.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

$compact-width: 50px;
$bg-color: #fff;
.execution-memory-node {
border-radius: 4px;
padding: 10px 8px;
background-color: #f9f9f9;
margin: 10px 0;
}
.plan-node {
cursor: default;
text-decoration: none;
Expand Down
84 changes: 84 additions & 0 deletions src/components/Plan.vue
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ import {
type FlexHierarchyPointLink,
type FlexHierarchyPointNode,
} from "d3-flextree"
import {
faChevronLeft,
faChevronRight,
} from "@fortawesome/free-solid-svg-icons"
import SliceDetail from "@/components/SliceDetail.vue"
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"

interface Props {
planSource: string
Expand All @@ -64,6 +70,23 @@ const plan = ref<IPlan>()
const planEl = ref()
let planStats = reactive<IPlanStats>({} as IPlanStats)
const rootNode = computed(() => plan.value && plan.value.content.Plan)
const Slice = computed(() => plan.value && plan.value.content.Slice)
const showSlice = ref<boolean>(true)

// Determine if there is a Data Segments attribute
const hasDataSegments = computed(() => {
if (!rootNode.value || !rootNode.value.Plans) {
return false
}
const plans = rootNode.value.Plans
for (let i = 0; i < plans.length; i++) {
if (plans[i]["Data Slice Count"] !== undefined) {
return true
}
}
return false
})

const selectedNodeId = ref<number>(NaN)
const selectedNode = ref<Node | undefined>(undefined)
const highlightedNodeId = ref<number>(NaN)
Expand All @@ -80,6 +103,12 @@ const planService = new PlanService()

// Vertical padding between 2 nodes in the tree layout
const padding = 40

// Parallel node digital offset
const numPositionX = 20
const numPositionY1 = 10
const numPositionY2 = 35

const transform = ref("")
const scale = ref(1)
const edgeWeight = computed(() => {
Expand Down Expand Up @@ -138,6 +167,8 @@ onBeforeMount(() => {
(content["Total Runtime"] as number) ||
NaN
planStats.planningTime = (content["Planning Time"] as number) || NaN
planStats.memoryUsed = (content["Memory used"] as number) || NaN
planStats.optimizer = (content["Optimizer"] as string) || ""
planStats.maxRows = content.maxRows || NaN
planStats.maxCost = content.maxCost || NaN
planStats.maxDuration = content.maxDuration || NaN
Expand Down Expand Up @@ -614,6 +645,32 @@ function updateNodeSize(node: Node, size: [number, number]) {
</button>
</div>
</div>
<div
class="position-absolute top-0 end-0 d-flex align-items-center"
v-if="Slice"
>
<span
class="text-secondary"
@click.prevent.stop="showSlice = !showSlice"
>
<FontAwesomeIcon
fixed-width
:icon="faChevronRight"
v-if="showSlice"
></FontAwesomeIcon>
<FontAwesomeIcon
fixed-width
:icon="faChevronLeft"
v-else
></FontAwesomeIcon>
</span>
<div v-if="showSlice" class="plan-node">
<div v-for="(item, index) in Slice" :key="index">
<SliceDetail :memory-details="item"></SliceDetail>
</div>
</div>
</div>

<svg width="100%" height="100%">
<g :transform="transform">
<!-- Links -->
Expand Down Expand Up @@ -645,6 +702,33 @@ function updateNodeSize(node: Node, size: [number, number]) {
stroke-linecap="square"
fill="none"
/>
<g v-if="hasDataSegments">
<text
v-for="(item, index) in layoutRootNode?.descendants()"
:key="`text${index}`"
:x="item.x + numPositionX"
:y="item.y - numPositionY1"
font-size="12"
fill="black"
text-anchor="middle"
dominant-baseline="central"
>
{{ item.data[NodeProp.NODE_COUNT] }}
</text>
<text
v-for="(item, index) in layoutRootNode?.descendants()"
:key="`text${index}`"
:x="item.x + numPositionX"
:y="item.y + item.ySize - numPositionY2"
font-size="12"
fill="black"
text-anchor="middle"
dominant-baseline="central"
>
{{ item.data[NodeProp.DATA_SLICE_COUNT] }}
</text>
</g>

<foreignObject
v-for="(item, index) in layoutRootNode?.descendants()"
:key="index"
Expand Down
26 changes: 26 additions & 0 deletions src/components/PlanNode.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"
import {
faChevronDown,
faChevronUp,
faHourglassHalf,
faPizzaSlice,
faSearch,
} from "@fortawesome/free-solid-svg-icons"

Expand Down Expand Up @@ -165,6 +167,30 @@ function centerCte() {
</a>
</div>
</header>
<div
v-if="
/(Motion)$/.test(node[NodeProp.NODE_TYPE]) &&
viewOptions.highlightType !== HighlightType.NONE &&
highlightValue !== null
"
>
<br />
<FontAwesomeIcon
fixed-width
:icon="faPizzaSlice"
class="text-secondary"
></FontAwesomeIcon>
{{ node[NodeProp.SLICE_ID] }}
<br />
<FontAwesomeIcon
fixed-width
:icon="faHourglassHalf"
class="text-secondary"
></FontAwesomeIcon>
{{ "The Slowest: " + node[NodeProp.ACTUAL_TOTAL_TIME] }}
<br />
</div>
<hr />
<div class="text-start font-monospace">
<div
v-if="
Expand Down
30 changes: 29 additions & 1 deletion src/components/PlanStats.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { PlanKey } from "@/symbols"
import { computed, inject, ref } from "vue"
import type { IPlan, ITrigger, Node } from "@/interfaces"
import { HelpService } from "@/services/help-service"
import { duration, durationClass } from "@/filters"
import { duration, durationClass, formatMemoryUsage } from "@/filters"
import { directive as vTippy } from "vue-tippy"
import { NodeProp } from "../enums"
import { formatNodeProp } from "@/filters"
Expand Down Expand Up @@ -134,6 +134,34 @@ function averageIO(node: Node) {
</span>
</template>
</div>
<div class="d-inline-block border-start px-2">
Memory used:
<template v-if="!plan.planStats.memoryUsed">
<span class="text-secondary"> N/A </span>
</template>
<template v-else>
<span class="stat-value">
<span v-html="formatMemoryUsage(plan.planStats.memoryUsed)"></span>
</span>
</template>
</div>
<div class="d-inline-block border-start px-2">
Optimizer:
<template v-if="!plan.planStats.optimizer">
<span class="text-secondary">
<FontAwesomeIcon
:icon="faInfoCircle"
class="cursor-help"
v-tippy="getHelpMessage('missing planning time')"
></FontAwesomeIcon>
</span>
</template>
<template v-else>
<span class="stat-value">
<span v-html="plan.planStats.optimizer"></span>
</span>
</template>
</div>
<div
class="d-inline-block border-start px-2"
v-if="plan.planStats.jitTime && plan.planStats.executionTime"
Expand Down
119 changes: 119 additions & 0 deletions src/components/SliceDetail.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<template>
<div ref="outerEl" @dblclick.stop>
<div class="execution-memory-node">
<header class="d-flex">
<FontAwesomeIcon
fixed-width
:icon="faPizzaSlice"
class="text-secondary py-1"
></FontAwesomeIcon>
<span class="text-body px-1">
Slice : {{ memoryDetails[SliceProp.SLICE_NUM] }}
</span>
</header>
<header class="d-flex justify-content-between">
<h4
class="btn py-0 px-0 d-flex"
@click.prevent.stop="showDetails = !showDetails"
>
<span class="text-secondary">
<FontAwesomeIcon
fixed-width
:icon="faChevronUp"
v-if="showDetails"
></FontAwesomeIcon>
<FontAwesomeIcon
fixed-width
:icon="faChevronDown"
v-else
></FontAwesomeIcon>
</span>
<span></span>
ExecutorMemory
</h4>
</header>
<div
v-if="showDetails"
class="d-flex flex-column justify-content-around px-3"
>
<span
v-if="
memoryDetails[SliceProp.EXECUTOR_MEMORY]?.[
ExecutorMemoryEnum.AVERAGE_MEMORY
]
"
class="text-secondary"
>
Average memory :
{{
memoryDetails[SliceProp.EXECUTOR_MEMORY]?.[
ExecutorMemoryEnum.AVERAGE_MEMORY
]
}}
</span>

<span
v-if="
memoryDetails[SliceProp.EXECUTOR_MEMORY]?.[
ExecutorMemoryEnum.NUMBER_OF_WORKER_THREADS
]
"
class="text-secondary"
>
Number of worker threads :
{{
memoryDetails[SliceProp.EXECUTOR_MEMORY]?.[
ExecutorMemoryEnum.NUMBER_OF_WORKER_THREADS
]
}}
</span>

<span
v-if="
memoryDetails[SliceProp.EXECUTOR_MEMORY]?.[
ExecutorMemoryEnum.MAXIMUM_MEMORY
]
"
class="text-secondary"
>
Maximum memory :

{{
memoryDetails[SliceProp.EXECUTOR_MEMORY]?.[
ExecutorMemoryEnum.MAXIMUM_MEMORY
]
}}
</span>
</div>

<span
v-if="memoryDetails[SliceProp.WORK_MEMORY]"
class="text-secondary mt-3 px-3"
>
WorkMemory : {{ memoryDetails[SliceProp.WORK_MEMORY] }}
</span>
</div>
</div>
</template>

<script lang="ts" setup>
import { reactive, ref } from "vue"
import {
faChevronDown,
faChevronUp,
faPizzaSlice,
} from "@fortawesome/free-solid-svg-icons"
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"
import { ExecutorMemoryEnum, SliceProp } from "@/enums"
import type { Slice } from "@/interfaces"

interface Props {
memoryDetails: Slice
}

const props = defineProps<Props>()
const memoryDetails = reactive<Slice>(props.memoryDetails)
const showDetails = ref<boolean>(false)

const outerEl = ref<Element | null>(null)
</script>
17 changes: 17 additions & 0 deletions src/enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ export enum NodeProp {
ACTUAL_LOOPS = "Actual Loops",
STARTUP_COST = "Startup Cost",
TOTAL_COST = "Total Cost",
DATA_SLICE_COUNT = "Data Slice Count",
NODE_COUNT = "Node Count",
SLICE_ID = "Slice Id",
SEGMENTS_COUNT = "Segments Count",
DYNAMIC_SCAN_ID = "Dynamic Scan Id",
PLANS = "Plans",
RELATION_NAME = "Relation Name",
SCHEMA = "Schema",
Expand Down Expand Up @@ -137,6 +142,18 @@ export enum NodeProp {
PEV_PLAN_TAG = "plan_",
}

export enum SliceProp {
SLICE_NUM = "Slice Num",
EXECUTOR_MEMORY = "ExecutorMemory",
WORK_MEMORY = "WorkMemory",
}

export enum ExecutorMemoryEnum {
AVERAGE_MEMORY = "Average memory",
NUMBER_OF_WORKER_THREADS = "Number of worker threads",
MAXIMUM_MEMORY = "Maximum memory",
}

export enum PropType {
blocks,
boolean,
Expand Down
15 changes: 15 additions & 0 deletions src/filters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,21 @@ export function duration(value: number | undefined): string {
return result.slice(0, 2).join(" ")
}

export function formatMemoryUsage(memoryInKB: number): string {
const units: string[] = ["B", "KB", "MB", "GB", "TB"]
let unitIndex = 1 // Start with KB, which is the input unit
let value: number = memoryInKB

// Convert to the appropriate unit
while (value >= 1024 && unitIndex < units.length - 1) {
value /= 1024
unitIndex++
}

// Format the output to two decimal places
return `${value.toFixed(2)} ${units[unitIndex]}`
}

export function cost(value: number): string {
if (value === undefined) {
return "N/A"
Expand Down
Loading