mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-21 09:35:38 -05:00
296 lines
8.5 KiB
TypeScript
296 lines
8.5 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
class RunTimeInformation {
|
|
runtimePerThreads: RuntimePerThread[];
|
|
public get actualRows(): number {
|
|
let total = 0;
|
|
if (this.runtimePerThreads) {
|
|
this.runtimePerThreads.forEach(element => {
|
|
total += element.actualRow;
|
|
});
|
|
}
|
|
|
|
return total;
|
|
}
|
|
|
|
public get actualExecutions(): number {
|
|
let total = 0;
|
|
if (this.runtimePerThreads) {
|
|
this.runtimePerThreads.forEach(element => {
|
|
total += element.actualExecutions;
|
|
});
|
|
}
|
|
return total;
|
|
}
|
|
}
|
|
|
|
class RuntimePerThread {
|
|
threadId: number;
|
|
actualRow: number;
|
|
actualExecutionMode: string;
|
|
actualExecutions: number;
|
|
}
|
|
|
|
class IndexObject {
|
|
database: string;
|
|
schema: string;
|
|
table: string;
|
|
index: string;
|
|
indexKind: string;
|
|
|
|
public get title() {
|
|
let title: string = '';
|
|
if (this.database && this.schema && this.table) {
|
|
title = `${this.database}.${this.schema}.${this.table}.${this.index}`;
|
|
if (this.indexKind && this.indexKind !== '') {
|
|
title += `(${this.indexKind})`;
|
|
}
|
|
}
|
|
return title;
|
|
}
|
|
}
|
|
|
|
class PlanNode {
|
|
root: PlanNode;
|
|
subtreeCost: number;
|
|
private childrenNodes: PlanNode[];
|
|
parent: PlanNode;
|
|
physicalOp: string;
|
|
logicalOp: string;
|
|
id: number;
|
|
estimateRows: string;
|
|
estimateIo: string;
|
|
estimateCpu: string;
|
|
parallel: boolean;
|
|
partitioned: boolean;
|
|
estimateRewinds: string;
|
|
estimateRebinds: string;
|
|
runtimeInfo: RunTimeInformation;
|
|
indexObject: IndexObject;
|
|
|
|
public addChildren(children: PlanNode[]): void {
|
|
if (children) {
|
|
children.forEach(element => {
|
|
element.parent = this;
|
|
});
|
|
}
|
|
this.childrenNodes = children;
|
|
}
|
|
|
|
public get totalSubTreeCost(): number {
|
|
let total = this.subtreeCost;
|
|
if (total === 0) {
|
|
this.children.forEach(element => {
|
|
total += element.subtreeCost;
|
|
});
|
|
}
|
|
return total;
|
|
}
|
|
|
|
public get children(): PlanNode[] {
|
|
return this.childrenNodes;
|
|
}
|
|
|
|
public get cost(): number {
|
|
let total = this.subtreeCost;
|
|
if (this.children && total !== 0) {
|
|
this.children.forEach(element => {
|
|
total -= element.subtreeCost;
|
|
});
|
|
}
|
|
return total;
|
|
}
|
|
|
|
public get relativeCost(): number {
|
|
let overallCost = this.root.totalSubTreeCost;
|
|
return overallCost > 0 ? this.cost / overallCost : 0;
|
|
}
|
|
|
|
public get estimatedOperatorCost(): number {
|
|
return Math.round(this.relativeCost * 100);
|
|
}
|
|
|
|
public get estimatedSubtreeCost(): number {
|
|
let total = this.estimatedOperatorCost;
|
|
if (this.children) {
|
|
this.children.forEach(element => {
|
|
total += element.estimatedSubtreeCost;
|
|
});
|
|
}
|
|
return total;
|
|
}
|
|
|
|
public get title(): string {
|
|
if (this.physicalOp === this.logicalOp) {
|
|
return this.physicalOp;
|
|
} else {
|
|
return `${this.physicalOp}(${this.logicalOp})`;
|
|
}
|
|
}
|
|
|
|
public get treeViewPrefix(): string {
|
|
return this.parent === undefined ? '' : `${this.parent.treeViewPrefix}-----`;
|
|
}
|
|
|
|
public get treeViewTitle(): string {
|
|
return `${this.treeViewPrefix}${this.title}`;
|
|
}
|
|
}
|
|
|
|
export class PlanXmlParser {
|
|
parser: DOMParser = new DOMParser();
|
|
doc: Document;
|
|
planXml: string;
|
|
root: PlanNode;
|
|
|
|
constructor(planXml: string) {
|
|
|
|
this.doc = this.parser.parseFromString(planXml, 'application/xml');
|
|
this.planXml = planXml;
|
|
let queryPlanNode = this.findChildren(this.doc.children[0], 'QueryPlan');
|
|
if (queryPlanNode && queryPlanNode.length > 0) {
|
|
this.root = new PlanNode();
|
|
let ops = this.createPlanNodes(queryPlanNode[0], 'RelOp', this.root);
|
|
|
|
this.root.addChildren(ops);
|
|
this.root.subtreeCost = 0;
|
|
}
|
|
}
|
|
|
|
public get topOperations(): PlanNode[] {
|
|
let operations: PlanNode[] = [];
|
|
if (this.root && this.root.children) {
|
|
operations = this.addOperationsToList(operations, this.root.children);
|
|
operations.sort((a, b) => {
|
|
if (a.estimatedOperatorCost > b.estimatedOperatorCost) {
|
|
return -1;
|
|
} else if (a.estimatedOperatorCost <= b.estimatedOperatorCost) {
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
});
|
|
}
|
|
return operations;
|
|
}
|
|
|
|
public get toTreeViewList(): PlanNode[] {
|
|
let operations: PlanNode[] = [];
|
|
operations = this.addOperationsToList(operations, this.root.children);
|
|
|
|
return operations;
|
|
}
|
|
|
|
private addOperationsToList(list: PlanNode[], nodes: PlanNode[]): PlanNode[] {
|
|
list = list.concat(nodes);
|
|
nodes.forEach(element => {
|
|
list = this.addOperationsToList(list, element.children);
|
|
});
|
|
return list;
|
|
}
|
|
|
|
private findChildren(element: Element, elementName: string, untilNode: string = undefined): Element[] {
|
|
let elements: Element[] = [];
|
|
if (element === undefined) {
|
|
return undefined;
|
|
}
|
|
|
|
for (let index = 0; index < element.childNodes.length; index++) {
|
|
if (element.childNodes[index].nodeName.toLocaleLowerCase() === elementName.toLocaleLowerCase()) {
|
|
elements = elements.concat(element.children[index]);
|
|
}
|
|
}
|
|
if (elements.length > 0) {
|
|
return elements;
|
|
}
|
|
for (let index = 0; index < element.childNodes.length; index++) {
|
|
if (untilNode && element.childNodes[index].nodeName === untilNode) {
|
|
continue;
|
|
}
|
|
let result = this.findChildren(element.children[index], elementName, untilNode);
|
|
if (result !== undefined) {
|
|
return result;
|
|
}
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
private createPlanNodes(element: Element, elementName: string, root: PlanNode): PlanNode[] {
|
|
let nodePlans: PlanNode[] = [];
|
|
|
|
let children = this.findChildren(element, elementName);
|
|
if (children) {
|
|
for (let index = 0; index < children.length; index++) {
|
|
let childNode = children[index];
|
|
|
|
let planNode = this.convertToPlanNode(childNode);
|
|
planNode.root = root;
|
|
planNode.addChildren(this.createPlanNodes(childNode, elementName, root));
|
|
planNode.runtimeInfo = new RunTimeInformation();
|
|
planNode.indexObject = new IndexObject();
|
|
|
|
let runtimeInfoNodes = this.findChildren(childNode, 'RunTimeCountersPerThread');
|
|
if (runtimeInfoNodes) {
|
|
planNode.runtimeInfo.runtimePerThreads = runtimeInfoNodes.map(x => this.convertToRuntimeInfo(x));
|
|
}
|
|
|
|
let objectNodes = this.findChildren(childNode, 'Object', 'RelOp');
|
|
if (objectNodes && objectNodes.length > 0) {
|
|
planNode.indexObject = this.convertToObject(objectNodes[0]);
|
|
}
|
|
nodePlans = nodePlans.concat(planNode);
|
|
}
|
|
}
|
|
|
|
return nodePlans;
|
|
}
|
|
|
|
private convertToPlanNode(element: Element): PlanNode {
|
|
let planNode = new PlanNode();
|
|
planNode.id = this.findAttribute(element.attributes, 'NodeId');
|
|
planNode.logicalOp = this.findAttribute(element.attributes, 'LogicalOp');
|
|
planNode.physicalOp = this.findAttribute(element.attributes, 'PhysicalOp');
|
|
planNode.subtreeCost = +this.findAttribute(element.attributes, 'EstimatedTotalSubtreeCost');
|
|
planNode.estimateRows = this.findAttribute(element.attributes, 'EstimateRows');
|
|
planNode.estimateCpu = this.findAttribute(element.attributes, 'EstimateCPU');
|
|
planNode.estimateIo = this.findAttribute(element.attributes, 'EstimateIO');
|
|
planNode.estimateRebinds = this.findAttribute(element.attributes, 'EstimateRebinds');
|
|
planNode.estimateRewinds = this.findAttribute(element.attributes, 'EstimateRewinds');
|
|
planNode.parallel = this.findAttribute(element.attributes, 'Parallel') === '1';
|
|
planNode.partitioned = this.findAttribute(element.attributes, 'Partitioned') === '1';
|
|
return planNode;
|
|
}
|
|
|
|
private convertToRuntimeInfo(element: Element): RuntimePerThread {
|
|
let runtimeNode = new RuntimePerThread();
|
|
runtimeNode.actualExecutionMode = this.findAttribute(element.attributes, 'ActualExecutionMode');
|
|
runtimeNode.actualExecutions = +this.findAttribute(element.attributes, 'ActualExecutions');
|
|
runtimeNode.actualRow = +this.findAttribute(element.attributes, 'ActualRows');
|
|
runtimeNode.threadId = +this.findAttribute(element.attributes, 'Thread');
|
|
return runtimeNode;
|
|
}
|
|
|
|
private convertToObject(element: Element): IndexObject {
|
|
let objectNode = new IndexObject();
|
|
objectNode.database = this.findAttribute(element.attributes, 'Database');
|
|
objectNode.index = this.findAttribute(element.attributes, 'Index');
|
|
objectNode.indexKind = this.findAttribute(element.attributes, 'IndexKind');
|
|
objectNode.schema = this.findAttribute(element.attributes, 'Schema');
|
|
objectNode.table = this.findAttribute(element.attributes, 'Table');
|
|
return objectNode;
|
|
}
|
|
|
|
private findAttribute(attributes: NamedNodeMap, attName: string): any {
|
|
for (let index = 0; index < attributes.length; index++) {
|
|
let attribute = attributes[index];
|
|
if (attribute.name === attName) {
|
|
return attribute.value;
|
|
}
|
|
}
|
|
}
|
|
}
|