Skip to content

Commit 55dc691

Browse files
ovrigorlukanin
authored andcommitted
chore(native): Benchmark python functionality (#9994)
1 parent e07d1b9 commit 55dc691

File tree

9 files changed

+419
-5
lines changed

9 files changed

+419
-5
lines changed

packages/cubejs-backend-native/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@ index.node
66
**/node_modules
77
**/.DS_Store
88
npm-debug.log
9+
benchmarks/result.txt
10+
test/__pycache__/
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
from cube import config, file_repository
2+
3+
config.schema_path = "models"
4+
config.pg_sql_port = 5555
5+
config.telemetry = False
6+
7+
8+
@config
9+
async def query_rewrite(query, ctx):
10+
# Removed print statements for benchmarking
11+
return query
12+
13+
14+
@config
15+
async def check_auth(req, authorization):
16+
# Removed print statements for benchmarking
17+
return {
18+
"security_context": {"sub": "1234567890", "iat": 1516239022, "user_id": 42},
19+
"ignoredField": "should not be visible",
20+
}
21+
22+
23+
@config('extend_context')
24+
async def extend_context(req):
25+
# Removed print statements for benchmarking
26+
if "securityContext" not in req:
27+
return {
28+
"security_context": {
29+
"error": "missing",
30+
}
31+
}
32+
33+
req["securityContext"]["extended_by_config"] = True
34+
35+
return {
36+
"security_context": req["securityContext"],
37+
}
38+
39+
40+
@config
41+
async def repository_factory(ctx):
42+
# Removed print statements for benchmarking
43+
return file_repository(ctx["securityContext"]["schemaPath"])
44+
45+
46+
@config
47+
async def context_to_api_scopes():
48+
# Removed print statements for benchmarking
49+
return ["meta", "data", "jobs"]
50+
51+
52+
@config
53+
async def scheduled_refresh_time_zones(ctx):
54+
# Removed print statements for benchmarking
55+
return ["Europe/Kyiv", "Antarctica/Troll", "Australia/Sydney"]
56+
57+
58+
@config
59+
async def scheduled_refresh_contexts(ctx):
60+
# Removed print statements for benchmarking
61+
return [
62+
{
63+
"securityContext": {
64+
"appid": 'test1', "u": { "prop1": "value1" }
65+
}
66+
},
67+
{
68+
"securityContext": {
69+
"appid": 'test2', "u": { "prop1": "value2" }
70+
}
71+
},
72+
{
73+
"securityContext": {
74+
"appid": 'test3', "u": { "prop1": "value3" }
75+
}
76+
},
77+
]
78+
79+
80+
@config
81+
async def schema_version(ctx):
82+
# Removed print statements for benchmarking
83+
return "1"
84+
85+
86+
@config
87+
async def pre_aggregations_schema(ctx):
88+
# Removed print statements for benchmarking
89+
return "schema"
90+
91+
92+
@config
93+
async def logger(msg, params):
94+
# Removed print statements for benchmarking
95+
pass
96+
97+
98+
@config
99+
async def context_to_roles(ctx):
100+
# Removed print statements for benchmarking
101+
return [
102+
"admin",
103+
]
104+
105+
106+
@config
107+
async def context_to_groups(ctx):
108+
# Removed print statements for benchmarking
109+
return [
110+
"dev",
111+
"analytics",
112+
]
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
from cube import config, file_repository
2+
3+
config.schema_path = "models"
4+
config.pg_sql_port = 5555
5+
config.telemetry = False
6+
7+
8+
@config
9+
def query_rewrite(query, ctx):
10+
# Removed print statements for benchmarking
11+
return query
12+
13+
14+
@config
15+
def check_auth(req, authorization):
16+
# Removed print statements for benchmarking
17+
return {
18+
"security_context": {"sub": "1234567890", "iat": 1516239022, "user_id": 42},
19+
"ignoredField": "should not be visible",
20+
}
21+
22+
23+
@config('extend_context')
24+
def extend_context(req):
25+
# Removed print statements for benchmarking
26+
if "securityContext" not in req:
27+
return {
28+
"security_context": {
29+
"error": "missing",
30+
}
31+
}
32+
33+
req["securityContext"]["extended_by_config"] = True
34+
35+
return {
36+
"security_context": req["securityContext"],
37+
}
38+
39+
40+
@config
41+
def repository_factory(ctx):
42+
# Removed print statements for benchmarking
43+
return file_repository(ctx["securityContext"]["schemaPath"])
44+
45+
46+
@config
47+
def context_to_api_scopes():
48+
# Removed print statements for benchmarking
49+
return ["meta", "data", "jobs"]
50+
51+
52+
@config
53+
def scheduled_refresh_time_zones(ctx):
54+
# Removed print statements for benchmarking
55+
return ["Europe/Kyiv", "Antarctica/Troll", "Australia/Sydney"]
56+
57+
58+
@config
59+
def scheduled_refresh_contexts(ctx):
60+
# Removed print statements for benchmarking
61+
return [
62+
{
63+
"securityContext": {
64+
"appid": 'test1', "u": { "prop1": "value1" }
65+
}
66+
},
67+
{
68+
"securityContext": {
69+
"appid": 'test2', "u": { "prop1": "value2" }
70+
}
71+
},
72+
{
73+
"securityContext": {
74+
"appid": 'test3', "u": { "prop1": "value3" }
75+
}
76+
},
77+
]
78+
79+
80+
@config
81+
def schema_version(ctx):
82+
# Removed print statements for benchmarking
83+
return "1"
84+
85+
86+
@config
87+
def pre_aggregations_schema(ctx):
88+
# Removed print statements for benchmarking
89+
return "schema"
90+
91+
92+
@config
93+
def logger(msg, params):
94+
# Removed print statements for benchmarking
95+
pass
96+
97+
98+
@config
99+
def context_to_roles(ctx):
100+
# Removed print statements for benchmarking
101+
return [
102+
"admin",
103+
]
104+
105+
106+
@config
107+
def context_to_groups(ctx):
108+
# Removed print statements for benchmarking
109+
return [
110+
"dev",
111+
"analytics",
112+
]
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import fs from 'fs/promises';
2+
import path from 'path';
3+
import { benchmarkSuite } from 'jest-bench';
4+
5+
import * as native from '../js';
6+
import { PyConfiguration } from '../js';
7+
8+
async function loadConfigurationFile(fileName: string): Promise<PyConfiguration> {
9+
const fullFileName = path.join(process.cwd(), 'benchmarks', 'fixtures', fileName);
10+
const content = await fs.readFile(fullFileName, 'utf8');
11+
12+
return native.pythonLoadConfig(content, {
13+
fileName: fullFileName
14+
});
15+
}
16+
17+
// Global variables to hold loaded configs and test data
18+
let configPy: PyConfiguration;
19+
let configAsyncPy: PyConfiguration;
20+
let configContent: string;
21+
let configAsyncContent: string;
22+
23+
describe('Python Configuration Loading', () => {
24+
beforeAll(async () => {
25+
// Load file contents once for all benchmarks
26+
const configPath = path.join(process.cwd(), 'benchmarks', 'fixtures', 'config.py');
27+
const configAsyncPath = path.join(process.cwd(), 'benchmarks', 'fixtures', 'config-async.py');
28+
const oldConfigPath = path.join(process.cwd(), 'test', 'old-config.py');
29+
30+
[configContent, configAsyncContent] = await Promise.all([
31+
fs.readFile(configPath, 'utf8'),
32+
fs.readFile(configAsyncPath, 'utf8'),
33+
]);
34+
35+
// Pre-load configurations for function benchmarks
36+
configPy = await loadConfigurationFile('config.py');
37+
configAsyncPy = await loadConfigurationFile('config-async.py');
38+
});
39+
40+
benchmarkSuite('Config Loading', {
41+
'Load config.py': async () => {
42+
const fullFileName = path.join(process.cwd(), 'benchmarks', 'fixtures', 'config.py');
43+
await native.pythonLoadConfig(configContent, { fileName: fullFileName });
44+
},
45+
46+
'Load config-async.py': async () => {
47+
const fullFileName = path.join(process.cwd(), 'benchmarks', 'fixtures', 'config-async.py');
48+
await native.pythonLoadConfig(configAsyncContent, { fileName: fullFileName });
49+
}
50+
});
51+
52+
benchmarkSuite('Data Conversion Performance', {
53+
'Small payload (3 fields)': async () => {
54+
const smallPayload = { simple_string: 'test', number: 42, boolean: true };
55+
await configPy.queryRewrite!(smallPayload, {});
56+
},
57+
58+
'Medium payload (100 users)': async () => {
59+
const mediumPayload = {
60+
users: Array.from({ length: 100 }, (_, i) => ({
61+
id: i, name: `User ${i}`, active: i % 2 === 0
62+
}))
63+
};
64+
await configPy.queryRewrite!(mediumPayload, {});
65+
},
66+
67+
'Large payload (1000 items)': async () => {
68+
const largePayload = {
69+
data: Array.from({ length: 1000 }, (_, i) => ({
70+
id: i, value: `Value ${i}`, nested: { prop: i * 2 }
71+
}))
72+
};
73+
await configPy.queryRewrite!(largePayload, {});
74+
}
75+
});
76+
77+
benchmarkSuite('Function Execution', {
78+
'checkAuth - sync version (sequential)': async () => {
79+
await configPy.checkAuth!({ requestId: 'sync-bench' }, 'SYNC_TOKEN');
80+
},
81+
82+
// It should help to identify any potential issues with GIL
83+
'checkAuth - sync version (parallel 50x)': async () => {
84+
await Promise.all(
85+
Array.from({ length: 50 }, () => configPy.checkAuth!({ requestId: 'sync-bench' }, 'SYNC_TOKEN'))
86+
);
87+
},
88+
89+
'checkAuth - async version (sequential)': async () => {
90+
await configAsyncPy.checkAuth!({ requestId: 'async-bench' }, 'ASYNC_TOKEN');
91+
},
92+
93+
// It should help to identify any potential issues with GIL
94+
'checkAuth - async version (parallel 50x)': async () => {
95+
await Promise.all(
96+
Array.from({ length: 50 }, () => configAsyncPy.checkAuth!({ requestId: 'async-bench' }, 'ASYNC_TOKEN'))
97+
);
98+
},
99+
100+
'extendContext - sync version': async () => {
101+
await configPy.extendContext!({
102+
securityContext: { sub: '1234567890', iat: 1516239022, user_id: 42 }
103+
});
104+
},
105+
106+
'extendContext - async version (sequential)': async () => {
107+
await configAsyncPy.extendContext!({
108+
securityContext: { sub: '1234567890', iat: 1516239022, user_id: 42 }
109+
});
110+
},
111+
112+
'queryRewrite - sync version': async () => {
113+
const testQuery = { str: 'string', int_number: 1, bool_true: true };
114+
await configPy.queryRewrite!(testQuery, {});
115+
},
116+
117+
'queryRewrite - async version (sequential)': async () => {
118+
const testQuery = { str: 'string', int_number: 1, bool_true: true };
119+
await configAsyncPy.queryRewrite!(testQuery, {});
120+
},
121+
});
122+
});
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
const base = require('../../jest.base.config');
2+
3+
/** @type {import('jest').Config} */
4+
module.exports = {
5+
...base,
6+
rootDir: '.',
7+
testEnvironment: 'jest-bench/environment',
8+
testEnvironmentOptions: {
9+
testEnvironment: 'node',
10+
testEnvironmentOptions: {}
11+
},
12+
// Include default reporter for error reporting alongside jest-bench reporter
13+
reporters: ['default', 'jest-bench/reporter'],
14+
// Pick up *.bench.ts files
15+
testRegex: '\\.bench\\.(ts|tsx|js)$',
16+
roots: [
17+
'<rootDir>/dist/benchmarks/'
18+
],
19+
snapshotResolver: '<rootDir>/test/snapshotResolver.js',
20+
// Coverage is not needed for benchmarks
21+
collectCoverage: false
22+
};

packages/cubejs-backend-native/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"unit": "jest --forceExit",
2525
"test:unit": "yarn run unit",
2626
"test:cargo": "cargo test",
27+
"bench": "jest --config jest-bench.config.js --forceExit",
2728
"lint": "eslint test/ js/ --ext .ts",
2829
"lint:fix": "eslint --fix test/ js/ --ext .ts"
2930
},
@@ -39,6 +40,7 @@
3940
"@types/node": "^20",
4041
"cargo-cp-artifact": "^0.1.9",
4142
"jest": "^29",
43+
"jest-bench": "^29",
4244
"pg": "^8.11.3",
4345
"typescript": "~5.2.2",
4446
"uuid": "^8.3.2"

0 commit comments

Comments
 (0)