diff --git a/plugins/catalog-backend/src/tests/performance/getEntitiesPerformance.test.ts b/plugins/catalog-backend/src/tests/performance/getEntitiesPerformance.test.ts new file mode 100644 index 0000000000..47fd664b71 --- /dev/null +++ b/plugins/catalog-backend/src/tests/performance/getEntitiesPerformance.test.ts @@ -0,0 +1,146 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describePerformanceTest, performanceTraceEnabled } from './lib/env'; +import { startTestBackend, TestDatabases } from '@backstage/backend-test-utils'; +import { applyDatabaseMigrations } from '../../database/migrations'; +import { + SyntheticLoadEntitiesProcessor, + SyntheticLoadEntitiesProvider, + SyntheticLoadOptions, +} from './lib/catalogModuleSyntheticLoadEntities'; +import { CatalogClient } from '@backstage/catalog-client'; +import { + coreServices, + createBackendModule, + createServiceFactory, +} from '@backstage/backend-plugin-api'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; +import { Knex } from 'knex'; + +jest.setTimeout(600_000); + +const traceLog: typeof console.log = performanceTraceEnabled + ? console.log + : () => {}; + +const completionPolling = async (load: SyntheticLoadOptions, knex: Knex) => { + const { baseEntitiesCount, childrenCount } = load; + const expectedTotal = baseEntitiesCount + baseEntitiesCount * childrenCount; + + return new Promise((resolve, reject) => { + const interval = setInterval(async () => { + try { + const stitchedCount = await knex('final_entities') + .count({ count: '*' }) + .whereNotNull('final_entity') + .then(rows => Number(rows[0].count)); + + if (stitchedCount === expectedTotal) { + clearInterval(interval); + resolve(); + } + } catch (error) { + clearInterval(interval); + reject(error); + } + }, 1000); + }); +}; + +describePerformanceTest('getEntitiesPerformanceTest', () => { + const databases = TestDatabases.create({ + ids: [/* 'MYSQL_8', */ 'POSTGRES_16', /* 'POSTGRES_12',*/ 'SQLITE_3'], + disableDocker: false, + }); + + it.each(databases.eachSupportedId())( + 'fetch entities, %p', + async databaseId => { + const knex = await databases.init(databaseId); + await applyDatabaseMigrations(knex); + + const load: SyntheticLoadOptions = { + baseEntitiesCount: 2000, + baseRelationsCount: 3, + baseRelationsSkew: 0.3, + childrenCount: 3, + }; + + traceLog('Starting test backend'); + + const backend = await startTestBackend({ + features: [ + import('@backstage/plugin-catalog-backend/alpha'), + createServiceFactory({ + service: coreServices.database, + deps: {}, + factory: () => ({ getClient: async () => knex }), + }), + createBackendModule({ + pluginId: 'catalog', + moduleId: 'synthetic-load-entities', + register(reg) { + reg.registerInit({ + deps: { + catalog: catalogProcessingExtensionPoint, + }, + async init({ catalog }) { + catalog.addEntityProvider( + new SyntheticLoadEntitiesProvider(load, {}), + ); + catalog.addProcessor( + new SyntheticLoadEntitiesProcessor(load), + ); + }, + }); + }, + }), + ], + }); + + const expectedTotal = + load.baseEntitiesCount + load.baseEntitiesCount * load.childrenCount; + traceLog(`Waiting for completion polling of ${expectedTotal} entities`); + + await expect(completionPolling(load, knex)).resolves.toBeUndefined(); + + const client = new CatalogClient({ + discoveryApi: { + getBaseUrl: jest + .fn() + .mockResolvedValue( + `http://localhost:${backend.server.port()}/api/catalog`, + ), + }, + }); + const start = Date.now(); + traceLog(`[${databaseId}] Starting to fetch ${expectedTotal} entities`); + + // Fetch all entities + const response = await client.getEntities(); + expect(response.items).toHaveLength(expectedTotal); + + const fetchDuration = Date.now() - start; + traceLog( + `[${databaseId}] Fetched ${expectedTotal} entities in ${fetchDuration}ms`, + ); + + await backend.stop(); + await knex.destroy(); + }, + ); +}); diff --git a/plugins/catalog-backend/src/tests/performance/lib/env.ts b/plugins/catalog-backend/src/tests/performance/lib/env.ts index c272930292..a1b4e72260 100644 --- a/plugins/catalog-backend/src/tests/performance/lib/env.ts +++ b/plugins/catalog-backend/src/tests/performance/lib/env.ts @@ -14,9 +14,16 @@ * limitations under the License. */ -export const performanceTraceEnabled = !!process.env.PERFORMANCE_TRACE; +import yn from 'yn'; -export const describePerformanceTest: jest.Describe = process.env - .PERFORMANCE_TEST +export const performanceTraceEnabled = yn(process.env.PERFORMANCE_TRACE, { + default: false, +}); + +export const performanceTestEnabled = yn(process.env.PERFORMANCE_TEST, { + default: false, +}); + +export const describePerformanceTest: jest.Describe = performanceTestEnabled ? describe : describe.skip;