Skip to content

Conversation

@ont-cwhiffin
Copy link

What this PR does / why we need it:

Replaces find-process with ps-list and a regex match.
All find-process is being used for is to check if a process which matches the given regex is running. We don't need any of the detailed information provided by it, like the PID, etc.

On Windows at least, this uses a bundled copy of fastlist, which seems to be a lot more efficient.

It also adds a flag to the _refresh function to prevent multiple runs occurring at once. It may have too many flags. It solved the issue on my machine, anyway.

Which issue(s) this PR fixes:

#488 waitForBuildProcess spawning multiple heavyweight Powershell instances on Windows.

@matepek
Copy link
Owner

matepek commented Oct 5, 2025

Hello, life is a bit busy, but I will check it.
Can you give me access to your branch?

@matepek
Copy link
Owner

matepek commented Oct 5, 2025

They released a new version for find-process. Can you please check the performance of that?

@ont-cwhiffin
Copy link
Author

I believe you've got access - the 'allow edits by maintainers' box is checked. Presumably you'd like it rebased on your main branch? I can do that if it helps.

I have bashed together this test script (sorry, I couldn't get the find import to work properly):

// Dependencies:
// "find-process": "^2.0.0",
// "@types/ps-list": "^6.0.0",

import psList from "ps-list";
import * as findProcess from "find-process";

interface Logger {
  info(message: string): void;
  exceptionS(error: any): void;
}

interface ProcessInfo {
  pid: number;
  name: string;
  cmd?: string;
}

class RunResults {
    method: string;
    avg_time: number;
    min_time: number;
    max_time: number;
    run_times: number[];

    constructor(method: string, avg_time: number, min_time: number, max_time: number, run_times: number[]) {
        this.method = method;
        this.avg_time = avg_time;
        this.min_time = min_time;
        this.max_time = max_time;
        this.run_times = run_times;
    }

    print(): void {
        console.log("\n=== Results ===");
        console.log(`${this.method} method:`);
        console.log(`  Average: ${this.avg_time.toFixed(2)}ms`);
        console.log(`  Min: ${this.min_time.toFixed(2)}ms`);
        console.log(`  Max: ${this.max_time.toFixed(2)}ms`);
    }
}

class ProcessMonitor {
  // https://en.wikipedia.org/wiki/List_of_compilers#C++_compilers
  private readonly _defaultPattern =
    /(^|[/\\])(bazel|cmake|make|ninja|cl|c\+\+|ld|clang|clang\+\+|gcc|g\+\+|link|icc|armcc|armclang)(-[^/\\]+)?(\.exe)?$/;

  private _log: Logger;

  constructor(logger: Logger) {
    this._log = logger;
  }

  async findprocess_method(
    pattern: RegExp = this._defaultPattern
  ): Promise<ProcessInfo[]> {
    // Note: find-process has incorrect type definition for RegExp handling
    // See: https://github.com/yibn2008/find-process/compare/1.4.11...2.0.0#diff-81b33228621820bded04ffbd7d49375fc742662fde6b7111ddb10457ceef7ae9R11
    const findFn = (findProcess as any).default.default;
    const processes = (await findFn(
      "name",
      pattern as unknown as string
    )) as ProcessInfo[];

    if (processes.length > 0) {
      this._log.info(
        "Found running build related processes: " +
          processes.map((x) => JSON.stringify(x, undefined, 0)).join(", ")
      );
    } else {
      this._log.info("Not found running build related process");
    }

    return processes;
  }

  private async pslist_method(
    pattern: RegExp = this._defaultPattern
  ): Promise<ProcessInfo[]> {
    const allProcesses = await psList();
    const processes = allProcesses.filter((proc: { name: string }) =>
      pattern.test(proc.name)
    );

    if (processes.length > 0) {
      this._log.info(
        "Found running build related processes: " +
          processes
            .map((x: { name: string }) => JSON.stringify(x, undefined, 0))
            .join(", ")
      );
    } else {
      this._log.info("Not found running build related process");
    }

    return processes as ProcessInfo[];
  }

  async checkPerformance(
    pattern: RegExp = this._defaultPattern,
    iterations: number = 10,
    fn: (pattern: RegExp) => Promise<ProcessInfo[]>,
    name: string
  ): Promise<RunResults> {
    console.log(`Running '${name}' ${iterations} times...`);
    console.log(`Warmup run...`);
    await fn(pattern).catch(() => {});

    const times: number[] = [];
    for (let i = 0; i < iterations; i++) {
      const start = performance.now();
      try {
        await fn(pattern);
      } catch (error) {}
      const end = performance.now();
      times.push(end - start);
    }

    // Calculate statistics
    const avg_time = times.reduce((a, b) => a + b, 0) / times.length;
    const min_time = Math.min(...times);
    const max_time = Math.max(...times);

    return new RunResults(name, avg_time, min_time, max_time, times);
  }

  // Performance comparison method
  async comparePerformance(
    pattern: RegExp = this._defaultPattern,
    iterations: number = 10
  ): Promise<void> {
    console.log(`Comparing performance over ${iterations} iterations...`);

    const [fp, ps] = await Promise.allSettled([
      this.checkPerformance(pattern, iterations, this.findprocess_method.bind(this), "find-process"),
      this.checkPerformance(pattern, iterations, this.pslist_method.bind(this), "ps-list")
    ]).then(results => [
      results[0].status === 'fulfilled' ? results[0].value : undefined,
      results[1].status === 'fulfilled' ? results[1].value : undefined
    ]);

    if (fp) fp.print();
    if (ps) ps.print();

    if (fp && ps) {
      const faster = fp.avg_time < ps.avg_time ? "find-process" : "ps-list";
      const speedup =
        (Math.abs(fp.avg_time - ps.avg_time) /
          Math.min(fp.avg_time, ps.avg_time)) *
        100;
      console.log(`\n${faster} is ${speedup.toFixed(1)}% faster on average`);
    }
  }
}

// Example usage
const logger: Logger = {
  info: (message: string) => console.log(`[INFO] ${message}`),
  exceptionS: (error: any) => console.error(`[ERROR] ${error}`),
};

const monitor = new ProcessMonitor(logger);
monitor.comparePerformance().catch(console.error);

Which, on my machine gives something like this:

npx ts-node --esm pslist_comparison.ts
(node:14412) [DEP0180] DeprecationWarning: fs.Stats constructor is deprecated.
(Use `node --trace-deprecation ...` to show where the warning was created)
(node:14412) ExperimentalWarning: Type Stripping is an experimental feature and might change at any time
Comparing performance over 10 iterations...
Running 'find-process' 10 times...
Warmup run...
Running 'ps-list' 10 times...
Warmup run...
[INFO] Found running build related processes: {"pid":34912,"ppid":33284,"name":"cmake.exe"}, {"pid":30564,"ppid":34912,"name":"ninja.exe"}, {"pid":38292,"ppid":35612,"name":"cl.exe"}
[INFO] Found running build related processes: {"pid":34912,"ppid":33284,"name":"cmake.exe"}, {"pid":30564,"ppid":34912,"name":"ninja.exe"}, {"pid":38292,"ppid":35612,"name":"cl.exe"}
...
[INFO] Found running build related processes: {"pid":30564,"ppid":34912,"bin":"C:\\Users\\cwhiffin\\AppData\\Local\\Microsoft\\WinGet\\Packages\\Ninja-build.Ninja_Microsoft.Winget.Source_8wekyb3d8bbwe\\ninja.exe","name":"ninja.exe","cmd":"C:/Users/cwhiffin/AppData/Local/Microsoft/WinGet/Packages/Ninja-build.Ninja_Microsoft.Winget.Source_8wekyb3d8bbwe/ninja.exe"}
[INFO] Found running build related processes: {"pid":30564,"ppid":34912,"bin":"C:\\Users\\cwhiffin\\AppData\\Local\\Microsoft\\WinGet\\Packages\\Ninja-build.Ninja_Microsoft.Winget.Source_8wekyb3d8bbwe\\ninja.exe","name":"ninja.exe","cmd":"C:/Users/cwhiffin/AppData/Local/Microsoft/WinGet/Packages/Ninja-build.Ninja_Microsoft.Winget.Source_8wekyb3d8bbwe/ninja.exe"}, {"pid":38772,"ppid":34052,"bin":"","name":"link.exe","cmd":""}

=== Results ===
find-process method:
  Average: 4495.09ms
  Min: 3518.67ms
  Max: 8101.49ms

=== Results ===
ps-list method:
  Average: 110.89ms
  Min: 98.51ms
  Max: 141.55ms

ps-list is 3953.6% faster on average

@ont-cwhiffin
Copy link
Author

haha maybe I should just use linux permanently- from WSL:

=== Results ===
find-process method:
  Average: 9.94ms
  Min: 8.36ms
  Max: 14.27ms

=== Results ===
ps-list method:
  Average: 9.79ms
  Min: 8.89ms
  Max: 14.27ms

ps-list is 1.6% faster on average

@matepek
Copy link
Owner

matepek commented Oct 10, 2025

pls review and test it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants