Show details Citus query plan instead of just 'Custom Scan'

pull/9265/head
Royston Shufflebotham 2024-11-02 22:47:59 +00:00
parent 91ad54d17b
commit 58b71804d8
6 changed files with 212 additions and 0 deletions

View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.1"
viewBox="0 0 64 64"
id="svg2"
sodipodi:docname="ex_citus.svg"
inkscape:version="1.4 (e7c3feb1, 2024-10-09)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs2" />
<sodipodi:namedview
id="namedview2"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="2.368263"
inkscape:cx="-1.055626"
inkscape:cy="63.970936"
inkscape:window-width="1440"
inkscape:window-height="786"
inkscape:window-x="0"
inkscape:window-y="25"
inkscape:window-maximized="1"
inkscape:current-layer="svg2" />
<path
fill="#1b324f"
d="M 39.722625,27.973765 C 40.337554,24.89912 31.523573,23.600937 26.467491,29.818551 20.79648,36.787746 32.206827,44.098567 45.18866,34.32803 53.456038,28.04209 56.12073,19.433085 51.269624,15.060257 41.840714,6.4512526 19.020018,14.445328 11.36757,31.526687 19.020018,18.61318 38.219466,12.532216 45.871914,20.389641 c 3.211296,3.279621 1.434835,9.907188 -4.441153,12.708531 -9.497235,4.441154 -8.609005,-5.261058 -1.708136,-5.124407"
id="path1"
style="stroke-width:0.683254" />
<path
fill="#239646"
d="M 39.244347,21.209547 C 23.529497,16.495092 2.2119613,35.762864 13.144031,48.471395 20.386527,56.943749 45.735264,52.844223 53.86599,27.29051 44.232104,44.918473 26.057538,47.514839 19.156669,42.458757 11.025942,36.514444 21.138107,19.843038 39.244347,21.209547"
id="path2"
style="stroke-width:0.683254" />
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
<defs>
<style>
.cls-5{fill:#34495e;}
.cls-6{fill:none;stroke-linecap:round;}
.cls-5,.cls-6{stroke-linejoin:round;stroke:#34495e;}
</style>
</defs>
<polygon class="cls-5" transform="translate(8)" points="43.3 54.7 42.3 51.6 40 54.8"/>
<line class="cls-6" x1="49.6" x2="41.5" y1="53.3" y2="47.6"/>
<polygon class="cls-5" transform="translate(8)" points="43.3 9.32 42.3 12.4 40 9.21"/>
<line class="cls-6" x1="49.6" x2="41.5" y1="10.6" y2="16.4"/>
<polygon class="cls-5" transform="translate(8)" points="45.4 31.6 42.8 29.6 42.8 33.6"/>
<line class="cls-6" x1="51.2" x2="41.4" y1="31.5" y2="31.5"/>
<polygon class="cls-5" transform="translate(8)" points="45 19 41.9 18 43.2 21.7"/>
<line class="cls-6" x1="51" x2="41.6" y1="19.6" y2="23"/>
<polygon class="cls-5" transform="translate(8)" points="45 43.5 41.9 44.4 43.2 40.7"/>
<line class="cls-6" x1="51" x2="41.6" y1="42.8" y2="39.5"/>
<g transform="translate(51.6 26.3)">
<path d="m-23.3 3.39c0.389-1.95-5.19-2.77-8.39 1.17-3.59 4.41 3.63 9.04 11.9 2.86 5.24-3.98 6.92-9.43 3.85-12.2-5.97-5.45-20.4-0.389-25.3 10.4 4.85-8.18 17-12 21.8-7.05 2.03 2.08 0.909 6.27-2.81 8.05-6.01 2.81-5.45-3.33-1.08-3.25" fill="#1b324f" style="stroke-width:.433"/>
<path d="m-23.6-0.895c-9.95-2.99-23.5 9.22-16.5 17.3 4.59 5.37 20.6 2.77 25.8-13.4-6.1 11.2-17.6 12.8-22 9.61-5.15-3.76 1.25-14.3 12.7-13.5" fill="#239646" style="stroke-width:.433"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
<defs>
<style>
.cls-5{fill:#34495e;}
.cls-6{fill:none;stroke-linecap:round;}
.cls-5,.cls-6{stroke-linejoin:round;stroke:#34495e;}
</style>
</defs>
<polygon class="cls-5" transform="translate(8)" points="45.4 31.6 42.8 29.6 42.8 33.6"/>
<line class="cls-6" x1="51.2" x2="41.4" y1="31.5" y2="31.5"/>
<g transform="translate(51.6 26.3)">
<path d="m-23.3 3.39c0.389-1.95-5.19-2.77-8.39 1.17-3.59 4.41 3.63 9.04 11.9 2.86 5.24-3.98 6.92-9.43 3.85-12.2-5.97-5.45-20.4-0.389-25.3 10.4 4.85-8.18 17-12 21.8-7.05 2.03 2.08 0.909 6.27-2.81 8.05-6.01 2.81-5.45-3.33-1.08-3.25" fill="#1b324f" style="stroke-width:.433"/>
<path d="m-23.6-0.895c-9.95-2.99-23.5 9.22-16.5 17.3 4.59 5.37 20.6 2.77 25.8-13.4-6.1 11.2-17.6 12.8-22 9.61-5.15-3.76 1.25-14.3 12.7-13.5" fill="#239646" style="stroke-width:.433"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 965 B

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg version="1.1" viewBox="0 0 64 64" xml:space="preserve" xmlns="http://www.w3.org/2000/svg">
<defs>
<style>
.lights { fill:#fff }
.other-worker &gt; rect { stroke-width:0.5; fill:#addff3; stroke: #2980b9; }
.other-worker .squares, .other-worker .side-handles { fill: #2980b9; }
.selected-worker &gt; rect { stroke-width:0.5; fill:#f0ecb6; stroke: #c18f35; }
.selected-worker .squares, .selected-worker .side-handles { fill: #c18f35; }
</style></defs>
<g class="other-worker" transform="matrix(1.2 0 0 1.25 -7.14 -1.2)"><rect x="12.3" y="11.9" width="39.8" height="8.41" ry=".942"/><path class="squares" d="m19.2 13.4h2.27v2.27h-2.27zm3.05 0h2.27v2.27h-2.27zm3.05 0h2.27v2.27h-2.27zm3.05 0h2.27v2.27h-2.27zm-9.16 3.07h2.27v2.27h-2.27zm3.05 0h2.27v2.27h-2.27zm3.05 0h2.27v2.27h-2.27zm3.05 0h2.27v2.27h-2.27z"/><path class="side-handles" d="m14.1 13h1.24v6.1h-1.24zm34.5 0h1.24v6.1h-1.24z"/><g class="lights"><rect x="35.3" y="13.9" width="1.83" height="1.83" ry=".6"/><rect x="38.1" y="13.9" width="1.83" height="1.83" ry=".6"/><rect x="41" y="13.9" width="1.83" height="1.83" ry=".6"/></g></g>
<g class="other-worker" transform="matrix(1.2 0 0 1.25 -7.14 24.7)"><rect x="12.3" y="11.9" width="39.8" height="8.41" ry=".942"/><path class="squares" d="m19.2 13.4h2.27v2.27h-2.27zm3.05 0h2.27v2.27h-2.27zm3.05 0h2.27v2.27h-2.27zm3.05 0h2.27v2.27h-2.27zm-9.16 3.07h2.27v2.27h-2.27zm3.05 0h2.27v2.27h-2.27zm3.05 0h2.27v2.27h-2.27zm3.05 0h2.27v2.27h-2.27z"/><path class="side-handles" d="m14.1 13h1.24v6.1h-1.24zm34.5 0h1.24v6.1h-1.24z"/><g class="lights"><rect x="35.3" y="13.9" width="1.83" height="1.83" ry=".6"/><rect x="38.1" y="13.9" width="1.83" height="1.83" ry=".6"/><rect x="41" y="13.9" width="1.83" height="1.83" ry=".6"/></g></g>
<g class="selected-worker" transform="matrix(1.2 0 0 1.25 -6.84 11.7)"><rect x="12.3" y="11.9" width="39.8" height="8.41" ry=".942"/><path class="squares" d="m19.2 13.4h2.27v2.27h-2.27zm3.05 0h2.27v2.27h-2.27zm3.05 0h2.27v2.27h-2.27zm3.05 0h2.27v2.27h-2.27zm-9.16 3.07h2.27v2.27h-2.27zm3.05 0h2.27v2.27h-2.27zm3.05 0h2.27v2.27h-2.27zm3.05 0h2.27v2.27h-2.27z"/><path class="side-handles" d="m14.1 13h1.24v6.1h-1.24zm34.5 0h1.24v6.1h-1.24z"/><g class="lights"><rect x="35.3" y="13.9" width="1.83" height="1.83" ry=".6"/><rect x="38.1" y="13.9" width="1.83" height="1.83" ry=".6"/><rect x="41" y="13.9" width="1.83" height="1.83" ry=".6"/></g></g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -41,10 +41,86 @@ const ImageMapper = {
'image': 'ex_bmp_or.svg',
'image_text': 'Bitmap OR',
},
'Citus Job': function(data) {
// A 'Citus Job' represents a distributed query operation.
// The details of the distributed operation are in the sub-plans,
// but this node contains task count information, showing how many shards
// the query is being distributed to.
const taskCount = data['Task Count'];
const tasksShown = data['Tasks Shown'];
// "Task Count" is the number of shard operations being run.
// "Tasks Shown" is either "All" or "One of N" depending on whether the returned query plan
// contains one sample task or all of them.
// We show single-shard or multi-shard with different images, and we show the
// literal value of 'Tasks Shown' as the image text.
const image = (taskCount === 1)
? 'ex_citus_distributed_one_of_one.svg'
: 'ex_citus_distributed_one_of_many.svg';
return {
'image': image,
'image_text': tasksShown
};
},
'Citus Task': function(data) {
// A 'Citus Task' represents a Task executed on a particular worker node.
// The details of the Task are in the sub-plans, so for this node we just show
// some details of the worker node.
const node = data['Node'];
// "Node" has a value like "host=citus-worker-7 port=8394 dbname=postgres"
// That's a bit long to display, so we shrink it to 'citus-worker-7:8394 postgres'
const hostMatch = node.match(/host=(\S+)/);
const portMatch = node.match(/port=(\S+)/);
const dbnameMatch = node.match(/dbname=(\S+)/);
const host = hostMatch ? hostMatch[1] : '';
let port = portMatch ? portMatch[1] : '';
if (port === '5432') {
// Default port. Don't bother showing.
port = '';
}
const dbname = dbnameMatch ? dbnameMatch[1] : '';
let imageText = `Task ${host}`;
if (port) {
imageText += `:${port}`;
}
if (dbname) {
imageText += ` ${dbname}`;
}
return {
'image': 'ex_citus_worker_task.svg',
'image_text': imageText
};
},
'CTE Scan': {
'image': 'ex_cte_scan.svg',
'image_text': 'CTE Scan',
},
'Custom Scan': function(data) {
const customPlanProvider = data['Custom Plan Provider'];
let image;
switch (customPlanProvider) {
case 'Citus Adaptive':
image = 'ex_citus.svg';
break;
default:
image = 'ex_unknown.svg';
break;
}
return {
'image': image,
'image_text': data['Custom Plan Provider']
};
},
'Function Scan': {
'image': 'ex_result.svg',
'image_text': 'Function Scan',

View File

@ -350,6 +350,46 @@ function parsePlan(data, ctx) {
}
}
const citusDistributedQuery = data['Distributed Query'];
if (citusDistributedQuery) {
// This is a Citus Distributed Query plan.
// It contains a 'Job' with one or more 'Tasks' in it.
// We'll convert those Tasks into sub-Plans of this main plan and process it
// with the regular Plan layout code.
delete data['Distributed Query'];
// Convert the Job into a 'Citus Job' sub-plan.
// That allows us to show details of the Task count etc.
const citusJob = citusDistributedQuery['Job'];
const jobPlan = {
'Node Type': 'Citus Job',
...citusJob
};
data['Plans'] = [jobPlan];
// Convert each of the Tasks into 'Citus Task' sub-plans of the Job plan.
const citusTasks = jobPlan['Tasks'];
if (citusTasks) {
delete jobPlan['Tasks'];
const citusTaskPlans = citusTasks.map(citusJobTask => {
const taskPlan = {
'Node Type': 'Citus Task',
...citusJobTask
};
// A Citus Task contains a 'Remote Plan' which is the actual plan
// executed on the worker nodes. It's actually an array of arrays.
const remotePlan = taskPlan['Remote Plan'];
delete taskPlan['Remote Plan'];
// A Remote Plan is an array of arrays of Plans.
taskPlan['Plans'] = remotePlan.flatMap(arr => arr.map(planLevel1Entry => planLevel1Entry['Plan']));
return taskPlan;
});
jobPlan['Plans'] = citusTaskPlans;
}
}
// Start calculating xpos, ypos, width and height for child plans if any
if ('Plans' in data) {
data['width'] += offsetX;