Skip to content
Closed
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
88 changes: 88 additions & 0 deletions packages/cubejs-schema-compiler/src/compiler/PerfTracker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { performance, PerformanceObserver } from 'perf_hooks';

interface PerfMetric {
count: number;
totalTime: number;
avgTime: number;
}

interface PerfStats {
[key: string]: PerfMetric;
}

class PerfTracker {
private metrics: PerfStats = {};

private globalMetric: string | null = null;

public constructor() {
const obs = new PerformanceObserver((items) => {
for (const entry of items.getEntries()) {
const { name } = entry;
if (!this.metrics[name]) {
this.metrics[name] = { count: 0, totalTime: 0, avgTime: 0 };
}
const m = this.metrics[name];
m.count++;
m.totalTime += entry.duration;
m.avgTime = m.totalTime / m.count;
}
});
obs.observe({ entryTypes: ['measure'] });
}

public start(name: string, global: boolean = false): { end: () => void } {
const uid = `${name}-${performance.now()}`;
const startMark = `${uid}-start`;
const endMark = `${uid}-end`;
performance.mark(startMark);

if (global && !this.globalMetric) {
this.globalMetric = name;
}

let ended = false;

return {
end: () => {
if (ended) return;
performance.mark(endMark);
performance.measure(name, startMark, endMark);
ended = true;
}
};
}

public printReport() {
console.log('\nπŸš€ PERFORMANCE REPORT πŸš€\n');
console.log('═'.repeat(90));

const sorted = Object.entries(this.metrics)
.sort(([, a], [, b]) => b.totalTime - a.totalTime);

if (!sorted.length) {
console.log('No performance data collected.');
return;
}

let totalTime: number = 0;

if (this.globalMetric) {
totalTime = this.metrics[this.globalMetric]?.totalTime;
} else {
totalTime = sorted.reduce((sum, [, m]) => sum + m.totalTime, 0);
}

console.log(`⏱️ TOTAL TIME: ${totalTime.toFixed(2)}ms\n`);

sorted.forEach(([name, m]) => {
const pct = totalTime > 0 ? (m.totalTime / totalTime * 100) : 0;
console.log(` ${name.padEnd(40)} β”‚ ${m.totalTime.toFixed(2).padStart(8)}ms β”‚ ${m.avgTime.toFixed(2).padStart(7)}ms avg β”‚ ${pct.toFixed(1).padStart(5)}% β”‚ ${m.count.toString().padStart(4)} calls`);
});

console.log('═'.repeat(90));
console.log('🎯 End of Performance Report\n');
}
}

export const perfTracker = new PerfTracker();
26 changes: 21 additions & 5 deletions packages/cubejs-schema-compiler/src/compiler/YamlCompiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ type EscapeStateStack = {
depth?: number;
};

const PY_TEMPLATE_SYNTAX = /\{.*}/ms;

export class YamlCompiler {
public dataSchemaCompiler: DataSchemaCompiler | null = null;

Expand Down Expand Up @@ -141,24 +143,38 @@ export class YamlCompiler {
const fullPath = propertyPath.join('.');
if (fullPath.match(p)) {
if (typeof obj === 'string' && ['sql', 'sqlTable'].includes(propertyPath[propertyPath.length - 1])) {
return this.parsePythonIntoArrowFunction(`f"${this.escapeDoubleQuotes(obj)}"`, cubeName, obj, errorsReport);
if (obj.match(PY_TEMPLATE_SYNTAX)) {
return this.parsePythonIntoArrowFunction(`f"${this.escapeDoubleQuotes(obj)}"`, cubeName, obj, errorsReport);
} else {
// Optimization: directly create arrow function returning string instead of parsing Python
return t.arrowFunctionExpression([], t.stringLiteral(obj));
}
} else if (typeof obj === 'string') {
return this.parsePythonIntoArrowFunction(obj, cubeName, obj, errorsReport);
if (obj.match(PY_TEMPLATE_SYNTAX)) {
return this.parsePythonIntoArrowFunction(obj, cubeName, obj, errorsReport);
} else {
// Optimization: directly create arrow function returning identifier instead of parsing Python
return this.astIntoArrowFunction(t.program([t.expressionStatement(t.identifier(obj))]), obj, cubeName);
}
} else if (Array.isArray(obj)) {
const resultAst = t.program([t.expressionStatement(t.arrayExpression(obj.map(code => {
let ast: t.Program | t.NullLiteral | t.BooleanLiteral | t.NumericLiteral | null = null;
let ast: t.Program | t.NullLiteral | t.BooleanLiteral | t.NumericLiteral | t.StringLiteral | null = null;
// Special case for accessPolicy.rowLevel.filter.values and other values-like fields
if (propertyPath[propertyPath.length - 1] === 'values') {
if (typeof code === 'string') {
ast = this.parsePythonAndTranspileToJs(`f"${this.escapeDoubleQuotes(code)}"`, errorsReport);
if (code.match(PY_TEMPLATE_SYNTAX)) {
ast = this.parsePythonAndTranspileToJs(`f"${this.escapeDoubleQuotes(code)}"`, errorsReport);
} else {
ast = t.stringLiteral(code);
}
} else if (typeof code === 'boolean') {
ast = t.booleanLiteral(code);
} else if (typeof code === 'number') {
ast = t.numericLiteral(code);
} else if (code instanceof Date) {
// Special case when dates are defined in YAML as strings without quotes
// YAML parser treats them as Date objects, but for conversion we need them as strings
ast = this.parsePythonAndTranspileToJs(`f"${this.escapeDoubleQuotes(code.toISOString())}"`, errorsReport);
ast = t.stringLiteral(code.toISOString());
}
}
if (ast === null) {
Expand Down
Loading