Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix massive performance regression for projects using correct module graphs #411

Merged
merged 17 commits into from
Nov 7, 2024
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
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 @@ -27,8 +27,9 @@

// barking on 'LayoutCollectorFn' is defined but never used @typescript-eslint/no-unused-vars
// 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 @@
}

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 @@
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 @@
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 @@
}

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
Loading