Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions extensions/ql-vscode/src/common/interface-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,7 @@ export interface SetPerformanceComparisonQueries {
readonly t: "setPerformanceComparison";
readonly from: PerformanceComparisonDataFromLog;
readonly to: PerformanceComparisonDataFromLog;
readonly comparison: boolean;
}

export type FromComparePerformanceViewMessage = CommonFromViewMessages;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,10 @@ export class ComparePerformanceView extends AbstractWebview<
}

const [fromPerf, toPerf] = await Promise.all([
scanLogWithProgress(fromJsonLog, "1/2"),
scanLogWithProgress(toJsonLog, "2/2"),
fromJsonLog === ""
? new PerformanceOverviewScanner()
: scanLogWithProgress(fromJsonLog, "1/2"),
scanLogWithProgress(toJsonLog, fromJsonLog === "" ? "1/1" : "2/2"),
]);

// TODO: filter out irrelevant common predicates before transfer?
Expand All @@ -79,6 +81,7 @@ export class ComparePerformanceView extends AbstractWebview<
t: "setPerformanceComparison",
from: fromPerf.getData(),
to: toPerf.getData(),
comparison: fromJsonLog !== "",
});
}

Expand Down
15 changes: 10 additions & 5 deletions extensions/ql-vscode/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -927,7 +927,7 @@ async function activateWithInstalledDistribution(
): Promise<void> => showResultsForComparison(compareView, from, to),
async (
from: CompletedLocalQueryInfo,
to: CompletedLocalQueryInfo,
to: CompletedLocalQueryInfo | undefined,
): Promise<void> =>
showPerformanceComparison(comparePerformanceView, from, to),
);
Expand Down Expand Up @@ -1210,17 +1210,22 @@ async function showResultsForComparison(
async function showPerformanceComparison(
view: ComparePerformanceView,
from: CompletedLocalQueryInfo,
to: CompletedLocalQueryInfo,
to: CompletedLocalQueryInfo | undefined,
): Promise<void> {
const fromLog = from.evalutorLogPaths?.jsonSummary;
const toLog = to.evalutorLogPaths?.jsonSummary;
let fromLog = from.evalutorLogPaths?.jsonSummary;
let toLog = to?.evalutorLogPaths?.jsonSummary;

if (to === undefined) {
toLog = fromLog;
fromLog = "";
}
if (fromLog === undefined || toLog === undefined) {
return extLogger.showWarningMessage(
`Cannot compare performance as the structured logs are missing. Did they queries complete normally?`,
);
}
await extLogger.log(
`Comparing performance of ${from.getQueryName()} and ${to.getQueryName()}`,
`Comparing performance of ${from.getQueryName()} and ${to?.getQueryName() ?? "baseline"}`,
);

await view.showResults(fromLog, toLog);
Expand Down
66 changes: 61 additions & 5 deletions extensions/ql-vscode/src/query-history/query-history-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ export class QueryHistoryManager extends DisposableObject {
) => Promise<void>,
private readonly doComparePerformanceCallback: (
from: CompletedLocalQueryInfo,
to: CompletedLocalQueryInfo,
to: CompletedLocalQueryInfo | undefined,
) => Promise<void>,
) {
super();
Expand Down Expand Up @@ -706,17 +706,18 @@ export class QueryHistoryManager extends DisposableObject {

let toItem: CompletedLocalQueryInfo | undefined = undefined;
try {
toItem = await this.findOtherQueryToCompare(fromItem, multiSelect);
toItem = await this.findOtherQueryToComparePerformance(
fromItem,
multiSelect,
);
} catch (e) {
void showAndLogErrorMessage(
this.app.logger,
`Failed to compare queries: ${getErrorMessage(e)}`,
);
}

if (toItem !== undefined) {
await this.doComparePerformanceCallback(fromItem, toItem);
}
await this.doComparePerformanceCallback(fromItem, toItem);
}

async handleItemClicked(item: QueryHistoryInfo) {
Expand Down Expand Up @@ -1116,6 +1117,7 @@ export class QueryHistoryManager extends DisposableObject {
detail: item.completedQuery.message,
query: item,
}));

if (comparableQueryLabels.length < 1) {
throw new Error("No other queries available to compare with.");
}
Expand All @@ -1124,6 +1126,60 @@ export class QueryHistoryManager extends DisposableObject {
return choice?.query;
}

private async findOtherQueryToComparePerformance(
fromItem: CompletedLocalQueryInfo,
allSelectedItems: CompletedLocalQueryInfo[],
): Promise<CompletedLocalQueryInfo | undefined> {
const dbName = fromItem.databaseName;

// If exactly 2 items are selected, return the one that
// isn't being used as the "from" item.
if (allSelectedItems.length === 2) {
const otherItem =
fromItem === allSelectedItems[0]
? allSelectedItems[1]
: allSelectedItems[0];
if (otherItem.databaseName !== dbName) {
throw new Error("Query databases must be the same.");
}
return otherItem;
}

if (allSelectedItems.length > 2) {
throw new Error("Please select no more than 2 queries.");
}

// Otherwise, present a dialog so the user can choose the item they want to use.
const comparableQueryLabels = this.treeDataProvider.allHistory
.filter(this.isSuccessfulCompletedLocalQueryInfo)
.filter(
(otherItem) =>
otherItem !== fromItem && otherItem.databaseName === dbName,
)
.map((item) => ({
label: this.labelProvider.getLabel(item),
description: item.databaseName,
detail: item.completedQuery.message,
query: item,
}));
const comparableQueryLabelsWithDefault = [
{
label: "Single run",
description:
"Look at the performance of this run, compared to a trivial baseline",
detail: undefined,
query: undefined,
},
...comparableQueryLabels,
];
if (comparableQueryLabelsWithDefault.length < 1) {
throw new Error("No other queries available to compare with.");
}
const choice = await window.showQuickPick(comparableQueryLabelsWithDefault);

return choice?.query;
}

/**
* Updates the compare with source query. This ensures that all compare command invocations
* when exactly 2 queries are selected always have the proper _from_ query. Always use
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,14 +218,15 @@ const Dropdown = styled.select``;
interface PipelineStepProps {
before: number | undefined;
after: number | undefined;
comparison: boolean;
step: React.ReactNode;
}

/**
* Row with details of a pipeline step, or one of the high-level stats appearing above the pipelines (evaluation/iteration counts).
*/
function PipelineStep(props: PipelineStepProps) {
let { before, after, step } = props;
let { before, after, comparison, step } = props;
if (before != null && before < 0) {
before = undefined;
}
Expand All @@ -236,9 +237,11 @@ function PipelineStep(props: PipelineStepProps) {
return (
<PipelineStepTR>
<ChevronCell />
<NumberCell>{before != null ? formatDecimal(before) : ""}</NumberCell>
{comparison && (
<NumberCell>{before != null ? formatDecimal(before) : ""}</NumberCell>
)}
<NumberCell>{after != null ? formatDecimal(after) : ""}</NumberCell>
{delta != null ? renderDelta(delta) : <td></td>}
{comparison && (delta != null ? renderDelta(delta) : <td></td>)}
<NameCell>{step}</NameCell>
</PipelineStepTR>
);
Expand All @@ -251,10 +254,11 @@ const HeaderTR = styled.tr`
interface HighLevelStatsProps {
before: PredicateInfo;
after: PredicateInfo;
comparison: boolean;
}

function HighLevelStats(props: HighLevelStatsProps) {
const { before, after } = props;
const { before, after, comparison } = props;
const hasBefore = before.absentReason !== AbsentReason.NotSeen;
const hasAfter = after.absentReason !== AbsentReason.NotSeen;
const showEvaluationCount =
Expand All @@ -263,21 +267,25 @@ function HighLevelStats(props: HighLevelStatsProps) {
<>
<HeaderTR>
<ChevronCell></ChevronCell>
<NumberHeader>{hasBefore ? "Before" : ""}</NumberHeader>
{comparison && <NumberHeader>{hasBefore ? "Before" : ""}</NumberHeader>}
<NumberHeader>{hasAfter ? "After" : ""}</NumberHeader>
<NumberHeader>{hasBefore && hasAfter ? "Delta" : ""}</NumberHeader>
{comparison && (
<NumberHeader>{hasBefore && hasAfter ? "Delta" : ""}</NumberHeader>
)}
<NameHeader>Stats</NameHeader>
</HeaderTR>
{showEvaluationCount && (
<PipelineStep
before={before.evaluationCount || undefined}
after={after.evaluationCount || undefined}
comparison={comparison}
step="Number of evaluations"
/>
)}
<PipelineStep
before={before.iterationCount / before.evaluationCount || undefined}
after={after.iterationCount / after.evaluationCount || undefined}
comparison={comparison}
step={
showEvaluationCount
? "Number of iterations per evaluation"
Expand Down Expand Up @@ -379,6 +387,8 @@ function ComparePerformanceWithData(props: {
[data],
);

const comparison = data?.comparison;

const [expandedPredicates, setExpandedPredicates] = useState<Set<string>>(
() => new Set<string>(),
);
Expand Down Expand Up @@ -480,9 +490,9 @@ function ComparePerformanceWithData(props: {
<thead>
<HeaderTR>
<ChevronCell />
<NumberHeader>Before</NumberHeader>
<NumberHeader>After</NumberHeader>
<NumberHeader>Delta</NumberHeader>
{comparison && <NumberHeader>Before</NumberHeader>}
<NumberHeader>{comparison ? "After" : "Value"}</NumberHeader>
{comparison && <NumberHeader>Delta</NumberHeader>}
<NameHeader>Predicate</NameHeader>
</HeaderTR>
</thead>
Expand All @@ -505,33 +515,42 @@ function ComparePerformanceWithData(props: {
<ChevronCell>
<Chevron expanded={expandedPredicates.has(row.name)} />
</ChevronCell>
{renderAbsoluteValue(row.before, metric)}
{comparison && renderAbsoluteValue(row.before, metric)}
{renderAbsoluteValue(row.after, metric)}
{renderDelta(row.diff, metric.unit)}
{comparison && renderDelta(row.diff, metric.unit)}
<NameCell>{rowNames[rowIndex]}</NameCell>
</PredicateTR>
{expandedPredicates.has(row.name) && (
<>
<HighLevelStats before={row.before} after={row.after} />
<HighLevelStats
before={row.before}
after={row.after}
comparison={comparison}
/>
{collatePipelines(
row.before.pipelines,
row.after.pipelines,
).map(({ name, first, second }, pipelineIndex) => (
<Fragment key={pipelineIndex}>
<HeaderTR>
<td></td>
<NumberHeader>{first != null && "Before"}</NumberHeader>
{comparison && (
<NumberHeader>{first != null && "Before"}</NumberHeader>
)}
<NumberHeader>{second != null && "After"}</NumberHeader>
<NumberHeader>
{first != null && second != null && "Delta"}
</NumberHeader>
{comparison && (
<NumberHeader>
{first != null && second != null && "Delta"}
</NumberHeader>
)}
<NameHeader>
Tuple counts for &apos;{name}&apos; pipeline
{first == null
? " (after)"
: second == null
? " (before)"
: ""}
{comparison &&
(first == null
? " (after)"
: second == null
? " (before)"
: "")}
</NameHeader>
</HeaderTR>
{abbreviateRASteps(first?.steps ?? second!.steps).map(
Expand All @@ -540,6 +559,7 @@ function ComparePerformanceWithData(props: {
key={index}
before={first?.counts[index]}
after={second?.counts[index]}
comparison={comparison}
step={step}
/>
),
Expand All @@ -558,9 +578,11 @@ function ComparePerformanceWithData(props: {
</tr>
<tr key="total">
<ChevronCell />
<NumberCell>{formatDecimal(totalBefore)}</NumberCell>
{comparison && (
<NumberCell>{formatDecimal(totalBefore)}</NumberCell>
)}
<NumberCell>{formatDecimal(totalAfter)}</NumberCell>
{renderDelta(totalDiff)}
{comparison && renderDelta(totalDiff)}
<NameCell>TOTAL</NameCell>
</tr>
</tfoot>
Expand Down
Loading