Skip to content

Commit

Permalink
fix: performance regression for projects using correct module graphs
Browse files Browse the repository at this point in the history
Fix massive performance regression for projects using correct module graphs
  • Loading branch information
lifeart authored Nov 7, 2024
2 parents 8d017bf + eaa8f48 commit 10c6b28
Show file tree
Hide file tree
Showing 16 changed files with 239 additions and 49 deletions.
11 changes: 11 additions & 0 deletions ember-language-server.code-workspace
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"folders": [
{
"path": "."
},
{
"path": "../vscode-ember"
}
],
"settings": {}
}
8 changes: 4 additions & 4 deletions src/builtin-addons/core/script-completion-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export default class ScriptCompletionProvider {
}

if (!this.meta.projectAddonsInfoInitialized) {
await mGetProjectAddonsInfo(root);
await mGetProjectAddonsInfo(root, this.project.dependencyMap);
this.enableRegistryCache('projectAddonsInfoInitialized');
this.project.invalidateRegistry();
}
Expand All @@ -107,7 +107,7 @@ export default class ScriptCompletionProvider {
}

if (!this.meta.projectAddonsInfoInitialized) {
await mGetProjectAddonsInfo(root);
await mGetProjectAddonsInfo(root, this.project.dependencyMap);
this.enableRegistryCache('projectAddonsInfoInitialized');
this.project.invalidateRegistry();
}
Expand All @@ -130,7 +130,7 @@ export default class ScriptCompletionProvider {
}

if (!this.meta.projectAddonsInfoInitialized) {
await mGetProjectAddonsInfo(root);
await mGetProjectAddonsInfo(root, this.project.dependencyMap);
this.enableRegistryCache('projectAddonsInfoInitialized');
this.project.invalidateRegistry();
}
Expand Down Expand Up @@ -185,7 +185,7 @@ export default class ScriptCompletionProvider {
}

if (!this.meta.projectAddonsInfoInitialized) {
await mGetProjectAddonsInfo(root);
await mGetProjectAddonsInfo(root, this.project.dependencyMap);
this.enableRegistryCache('projectAddonsInfoInitialized');
this.project.invalidateRegistry();
}
Expand Down
16 changes: 9 additions & 7 deletions src/builtin-addons/core/script-definition-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type ItemType = 'Model' | 'Transform' | 'Service';
// eslint-disable-line
type LayoutCollectorFn = (root: string, itemName: string, podModulePrefix?: string) => string[];

Check warning on line 30 in src/builtin-addons/core/script-definition-provider.ts

View workflow job for this annotation

GitHub Actions / Node 14.x - ubuntu

'LayoutCollectorFn' is defined but never used
type AsyncLayoutCollectorFn = (root: string, itemName: string, podModulePrefix?: string) => Promise<string[]>;

Check warning on line 31 in src/builtin-addons/core/script-definition-provider.ts

View workflow job for this annotation

GitHub Actions / Node 14.x - ubuntu

'AsyncLayoutCollectorFn' is defined but never used
type ProjectAwareCollectionFn = (project: Project, itemName: string, podModulePrefix?: string) => Promise<string[]>;

Check warning on line 32 in src/builtin-addons/core/script-definition-provider.ts

View workflow job for this annotation

GitHub Actions / Node 14.x - ubuntu

'ProjectAwareCollectionFn' is defined but never used

function joinPaths(...args: string[]) {
return ['.ts', '.js'].map((extName: string) => {
Expand All @@ -40,7 +41,8 @@ function joinPaths(...args: string[]) {
}

class PathResolvers {
[key: string]: LayoutCollectorFn | AsyncLayoutCollectorFn;
[key: string]: LayoutCollectorFn | AsyncLayoutCollectorFn | ProjectAwareCollectionFn;

muModelPaths(root: string, modelName: string) {
return joinPaths(root, 'src', 'data', 'models', modelName, 'model');
}
Expand Down Expand Up @@ -68,11 +70,11 @@ class PathResolvers {
podServicePaths(root: string, modelName: string, podPrefix: string) {
return joinPaths(root, 'app', podPrefix, modelName, 'service');
}
async addonServicePaths(root: string, serviceName: string): Promise<string[]> {
return await getAddonPathsForType(root, 'services', serviceName);
async addonServicePaths(project: Project, serviceName: string): Promise<string[]> {
return await getAddonPathsForType(project, 'services', serviceName);
}
async addonImportPaths(root: string, pathName: string) {
return await getAddonImport(root, pathName);
async addonImportPaths(project: Project, pathName: string) {
return await getAddonImport(project, pathName);
}
classicImportPaths(root: string, pathName: string) {
const pathParts = pathName.split('/');
Expand Down Expand Up @@ -123,7 +125,7 @@ export default class CoreScriptDefinitionProvider {
guessedPaths.push(pathLocation);
});

const addonImports = await this.resolvers.addonImportPaths(root, importPath);
const addonImports = await this.resolvers.addonImportPaths(this.project, importPath);

addonImports.forEach((pathLocation: string) => {
guessedPaths.push(pathLocation);
Expand All @@ -148,7 +150,7 @@ export default class CoreScriptDefinitionProvider {
}

if (fnName === 'Service') {
const paths = await this.resolvers.addonServicePaths(root, typeName);
const paths = await this.resolvers.addonServicePaths(this.project, typeName);

paths.forEach((item: string) => {
guessedPaths.push(item);
Expand Down
12 changes: 6 additions & 6 deletions src/builtin-addons/core/template-completion-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ export default class TemplateCompletionProvider {
await mListComponents(project);
this.enableRegistryCache('componentsRegistryInitialized');

await mGetProjectAddonsInfo(project.root);
await mGetProjectAddonsInfo(project.root, project.dependencyMap);
this.enableRegistryCache('projectAddonsInfoInitialized');

this.project.invalidateRegistry();
Expand All @@ -195,7 +195,7 @@ export default class TemplateCompletionProvider {
const items: CompletionItem[] = [];

if (!this.meta.projectAddonsInfoInitialized) {
await mGetProjectAddonsInfo(root);
await mGetProjectAddonsInfo(root, this.project.dependencyMap);
this.enableRegistryCache('projectAddonsInfoInitialized');
this.project.invalidateRegistry();
}
Expand Down Expand Up @@ -256,7 +256,7 @@ export default class TemplateCompletionProvider {
}
async getMustachePathCandidates(root: string) {
if (!this.meta.projectAddonsInfoInitialized) {
await mGetProjectAddonsInfo(root);
await mGetProjectAddonsInfo(root, this.project.dependencyMap);
this.enableRegistryCache('projectAddonsInfoInitialized');
this.project.invalidateRegistry();
}
Expand Down Expand Up @@ -305,7 +305,7 @@ export default class TemplateCompletionProvider {
}
async getBlockPathCandidates(root: string): Promise<CompletionItem[]> {
if (!this.meta.projectAddonsInfoInitialized) {
await mGetProjectAddonsInfo(root);
await mGetProjectAddonsInfo(root, this.project.dependencyMap);
this.enableRegistryCache('projectAddonsInfoInitialized');
this.project.invalidateRegistry();
}
Expand Down Expand Up @@ -340,7 +340,7 @@ export default class TemplateCompletionProvider {
}

if (!this.meta.projectAddonsInfoInitialized) {
await mGetProjectAddonsInfo(this.project.root);
await mGetProjectAddonsInfo(this.project.root, this.project.dependencyMap);
this.enableRegistryCache('projectAddonsInfoInitialized');
this.project.invalidateRegistry();
}
Expand Down Expand Up @@ -447,7 +447,7 @@ export default class TemplateCompletionProvider {
}

if (!this.meta.projectAddonsInfoInitialized) {
await mGetProjectAddonsInfo(root);
await mGetProjectAddonsInfo(root, this.project.dependencyMap);
this.enableRegistryCache('projectAddonsInfoInitialized');
this.project.invalidateRegistry();
}
Expand Down
14 changes: 13 additions & 1 deletion src/project-roots.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ export default class ProjectRoots {
ignore: ['**/.git/**', '**/bower_components/**', '**/dist/**', '**/node_modules/**', '**/tmp/**'],
});

logInfo(`ELS: Found ${roots.length} roots for ${workspaceRoot}`);

const start = Date.now();

for (const rootPath of roots) {
const filePath = path.join(workspaceRoot, rootPath);
const fullPath = path.dirname(filePath);
Expand All @@ -103,6 +107,8 @@ export default class ProjectRoots {
await this.onProjectAdd(fullPath);
}
}

logInfo(`ELS: iterating roots took ${Date.now() - start}ms`);
}

async initialize(workspaceRoot: string) {
Expand All @@ -122,6 +128,8 @@ export default class ProjectRoots {
if (this.projects.has(projectPath)) {
const project = this.projects.get(projectPath) as Project;

logInfo(`Project already existed at ${projectPath}`);

return {
initIssues: project.initIssues,
providers: project.providers,
Expand Down Expand Up @@ -154,12 +162,16 @@ export default class ProjectRoots {
};
}

logInfo(`Initializing new project at ${projectPath} with ${this.localAddons.length} ELS addons.`);

const project = new Project(projectPath, this.localAddons, info);

const start = Date.now();

await project.initialize(this.server);

this.projects.set(projectPath, project);
logInfo(`Ember CLI project added at ${projectPath}`);
logInfo(`Ember CLI project added at ${projectPath}. (took ${Date.now() - start}ms)`);
await project.init(this.server);

return {
Expand Down
13 changes: 12 additions & 1 deletion src/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,17 @@ export interface Executors {
}

export class Project extends BaseProject {
// Don't traverse dependencies we've already seen.
// correct package graph can sort of throw us in to cycles if we don't keep track of this.

dependencyMap: Map<
string,
{
package: PackageInfo;
root: string;
}
> = new Map();

providers!: ProjectProviders;
builtinProviders!: ProjectProviders;
addonsMeta: AddonMeta[] = [];
Expand Down Expand Up @@ -148,7 +159,7 @@ export class Project extends BaseProject {
this.providers = emptyProjectProviders();
this.flags.enableEagerRegistryInitialization = false;
} else if (server.options.type === 'node') {
this.providers = await collectProjectProviders(this.root, this.addons);
this.providers = await collectProjectProviders(this.root, this.addons, this.dependencyMap);
} else {
throw new Error(`Unknown server type: "${server.options.type}"`);
}
Expand Down
8 changes: 6 additions & 2 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,12 +168,14 @@ export default class Server {
hoverProvider!: HoverProvider;
codeActionProvider!: CodeActionProvider;
async executeInitializers() {
logInfo('UELS: executeInitializers');
logInfo('ELS: executeInitializers');
logInfo(`ELS: ${this.initializers.length} initializers`);

for (const initializer of this.initializers) {
await initializer();
}

logInfo(`ELS: clearing initializers because they've been initialized`);
this.initializers = [];
}
private onInitialized() {
Expand Down Expand Up @@ -216,7 +218,7 @@ export default class Server {
};
}

await mGetProjectAddonsInfo(project.root);
await mGetProjectAddonsInfo(project.root, project.dependencyMap);
project.invalidateRegistry();

return {
Expand Down Expand Up @@ -524,6 +526,8 @@ export default class Server {
this.initializers.push(async () => {
await this.projectRoots.initialize(rootPath as string);

logInfo(`Found ${workspaceFolders?.length ?? 0} workspace folders for ${rootPath}`);

if (workspaceFolders && Array.isArray(workspaceFolders)) {
for (const folder of workspaceFolders) {
const folderPath = URI.parse(folder.uri).fsPath;
Expand Down
19 changes: 16 additions & 3 deletions src/utils/addon-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
} from './layout-helpers';
import { TextDocument } from 'vscode-languageserver-textdocument';
import * as path from 'path';
import { log, logInfo, logError, safeStringify } from './logger';
import { log, logInfo, logError, safeStringify, instrumentTime } from './logger';
import Server from '../server';
import ASTPath from './../glimmer-utils';
import DAGMap from 'dag-map';
Expand Down Expand Up @@ -187,8 +187,17 @@ function requireUncached(module: string) {
return result;
}

export async function collectProjectProviders(root: string, addons: string[]): Promise<ProjectProviders> {
const [projectAddonsRoots, projectInRepoAddonsRoots] = await Promise.all([getProjectAddonsRoots(root), getProjectInRepoAddonsRoots(root)]);
export async function collectProjectProviders(root: string, addons: string[], dependencyMap: Project['dependencyMap']): Promise<ProjectProviders> {
const time = instrumentTime(`collectProjectProviders(${root})`);

time.log(`Starting`);
const [projectAddonsRoots, projectInRepoAddonsRoots] = await Promise.all([
getProjectAddonsRoots(root, dependencyMap),
getProjectInRepoAddonsRoots(root, dependencyMap),
]);

time.log(`found roots`);

const roots = addons
.concat([root])
.concat(projectAddonsRoots, projectInRepoAddonsRoots)
Expand Down Expand Up @@ -226,6 +235,8 @@ export async function collectProjectProviders(root: string, addons: string[]): P
}
}

time.log(`found ELS addons`);

const result: {
definitionProviders: DefinitionResolveFunction[];
referencesProviders: ReferenceResolveFunction[];
Expand Down Expand Up @@ -332,6 +343,8 @@ export async function collectProjectProviders(root: string, addons: string[]): P
}
});

time.log(`finished crawling dagMap`);

return result;
}

Expand Down
11 changes: 7 additions & 4 deletions src/utils/definition-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { URI } from 'vscode-uri';

import { podModulePrefixForRoot, hasAddonFolderInPath, getProjectAddonsRoots, getProjectInRepoAddonsRoots, asyncFilter } from './layout-helpers';
import { fsProvider } from '../fs-provider';
import { Project } from '..';

const mProjectAddonsRoots = memoize(getProjectAddonsRoots, {
length: 3,
Expand Down Expand Up @@ -166,7 +167,8 @@ export function getPathsForComponentTemplates(root: string, maybeComponentName:
return paths;
}

export async function getAddonImport(root: string, importPath: string) {
export async function getAddonImport(project: Project, importPath: string) {
const { root, dependencyMap } = project;
const importParts = importPath.split('/');
let addonName = importParts.shift();

Expand All @@ -179,7 +181,7 @@ export async function getAddonImport(root: string, importPath: string) {
}

const items: string[] = [];
const [addonRoots, inRepoRoots] = await Promise.all([mProjectAddonsRoots(root), mProjectInRepoAddonsRoots(root)]);
const [addonRoots, inRepoRoots] = await Promise.all([mProjectAddonsRoots(root, dependencyMap), mProjectInRepoAddonsRoots(root, dependencyMap)]);

const roots = items.concat(addonRoots, inRepoRoots);
let existingPaths: string[] = [];
Expand Down Expand Up @@ -228,9 +230,10 @@ export async function getAddonImport(root: string, importPath: string) {
return existingPaths;
}

export async function getAddonPathsForType(root: string, collection: 'services' | 'models' | 'modifiers' | 'helpers' | 'routes', name: string) {
export async function getAddonPathsForType(project: Project, collection: 'services' | 'models' | 'modifiers' | 'helpers' | 'routes', name: string) {
const { root, dependencyMap } = project;
const items: string[] = [];
const [addonRoots, inRepoRoots] = await Promise.all([mProjectAddonsRoots(root), mProjectInRepoAddonsRoots(root)]);
const [addonRoots, inRepoRoots] = await Promise.all([mProjectAddonsRoots(root, dependencyMap), mProjectInRepoAddonsRoots(root, dependencyMap)]);
const roots = items.concat(addonRoots, inRepoRoots);
let existingPaths: string[] = [];
let hasValidPath = false;
Expand Down
Loading

0 comments on commit 10c6b28

Please sign in to comment.