Skip to content
Draft
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
62 changes: 62 additions & 0 deletions .github/scripts/recover-mcp-chromium-traces.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/usr/bin/env node

import crypto from 'node:crypto';
import fs from 'node:fs';
import path from 'node:path';

const traceRoot = path.resolve('test-results/mcp-chromium-traces');
const retryDeadline = Date.now() + 10_000;

async function recoverTrace(source) {
const target = path.join(path.dirname(source), `recovered-${crypto.randomUUID()}.pftrace`);
while (true) {
let size;
try {
size = (await fs.promises.stat(source)).size;
} catch (error) {
if (error.code === 'ENOENT')
return;
throw error;
}

if (size) {
try {
await fs.promises.rename(source, target);
console.log(`Recovered Chromium startup trace: ${target}`);
return;
} catch (error) {
if (error.code === 'ENOENT')
return;
if (error.code !== 'EACCES' && error.code !== 'EPERM')
throw error;
}
}

if (Date.now() >= retryDeadline) {
console.warn(`Chromium startup trace has no recoverable data: ${source}`);
return;
}
await new Promise(resolve => setTimeout(resolve, 200));
}
}

let traceDirectories;
try {
traceDirectories = await fs.promises.readdir(traceRoot, { withFileTypes: true });
} catch (error) {
if (error.code === 'ENOENT')
process.exit(0);
throw error;
}

const pending = [];
for (const directory of traceDirectories) {
if (!directory.isDirectory())
continue;
const traceDirectory = path.join(traceRoot, directory.name);
for (const file of await fs.promises.readdir(traceDirectory)) {
if (!file.endsWith('.pftrace'))
pending.push(recoverTrace(path.join(traceDirectory, file)));
}
}
await Promise.all(pending);
64 changes: 64 additions & 0 deletions .github/scripts/run-mcp-startup-diagnostic.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#!/usr/bin/env bash

set -euo pipefail

project="$1"

run_tests() {
set +e
"$@"
local test_status=$?
node .github/scripts/recover-mcp-chromium-traces.mjs
local recovery_status=$?
if [[ "$test_status" != "0" ]]; then
return "$test_status"
fi
return "$recovery_status"
}

if [[ "$(uname)" != "Linux" ]]; then
run_tests npm run test-mcp -- --project="$project" --workers=1 --timeout=60000
exit $?
fi

health_dir="mcp-runner-health"
health_file="$health_dir/runner-health.tsv"
mkdir -p "$health_dir"

monitor_health() {
printf 'timestamp\tload1\tmem_available_kb\tswap_free_kb\tdisk_available_kb\tprocesses\tnode_rss_kb\tchromium_rss_kb\txvfb_rss_kb\n'
while true; do
local timestamp load1 mem_available swap_free disk_available processes node_rss chromium_rss xvfb_rss
timestamp="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
load1="$(awk '{ print $1 }' /proc/loadavg)"
mem_available="$(awk '/^MemAvailable:/ { print $2 }' /proc/meminfo)"
swap_free="$(awk '/^SwapFree:/ { print $2 }' /proc/meminfo)"
disk_available="$(df -Pk . | awk 'NR == 2 { print $4 }')"
processes="$(ps -e --no-headers | wc -l)"
node_rss="$(ps -eo rss=,comm= | awk '$2 == "node" { sum += $1 } END { print sum + 0 }')"
chromium_rss="$(ps -eo rss=,comm= | awk '$2 ~ /^(chrome|chromium)$/ { sum += $1 } END { print sum + 0 }')"
xvfb_rss="$(ps -eo rss=,comm= | awk '$2 == "Xvfb" { sum += $1 } END { print sum + 0 }')"
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
"$timestamp" "$load1" "$mem_available" "$swap_free" "$disk_available" \
"$processes" "$node_rss" "$chromium_rss" "$xvfb_rss"
sleep 15
done
}

monitor_health > "$health_file" &
monitor_pid=$!
cleanup() {
kill "$monitor_pid" 2>/dev/null || true
wait "$monitor_pid" 2>/dev/null || true
}
trap cleanup EXIT

set +e
run_tests timeout --signal=INT --kill-after=30s 35m npm run test-mcp -- --project="$project" --workers=1 --timeout=60000
status=$?
set -e

if [[ "$status" == "124" ]]; then
echo "::error::Ubuntu MCP diagnostic exceeded 35 minutes; stopping before the hosted runner is lost."
fi
exit "$status"
2 changes: 2 additions & 0 deletions .github/workflows/infra.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ on:
jobs:
doc-and-lint:
name: "docs & lint"
if: github.event_name != 'pull_request' || github.head_ref != 'skn0tt-diagnose-mcp-startup-stalls'
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
Expand All @@ -35,6 +36,7 @@ jobs:
continue-on-error: true
lint-snippets:
name: "Lint snippets"
if: github.event_name != 'pull_request' || github.head_ref != 'skn0tt-diagnose-mcp-startup-stalls'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/tests_components.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ env:
jobs:
test_components:
name: ${{ matrix.os }} - Node.js ${{ matrix.node-version }}
if: github.event_name != 'pull_request' || github.head_ref != 'skn0tt-diagnose-mcp-startup-stalls'
strategy:
fail-fast: false
matrix:
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/tests_extension.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ env:
jobs:
test_extension:
name: ${{ matrix.os }}
if: github.event_name != 'pull_request' || github.head_ref != 'skn0tt-diagnose-mcp-startup-stalls'
strategy:
fail-fast: false
matrix:
Expand Down
50 changes: 34 additions & 16 deletions .github/workflows/tests_mcp.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: MCP
name: MCP startup diagnostic (temporary)

on:
push:
Expand Down Expand Up @@ -30,24 +30,20 @@ env:

jobs:
test_mcp:
name: ${{ matrix.os }} - ${{ matrix.project }}
name: DIAGNOSTIC - ${{ matrix.os }} - ${{ matrix.project }} - sample ${{ matrix.sample }}
# Used for authentication of flakiness upload
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
strategy:
fail-fast: false
# Avoid losing an entire platform batch if the hosted runner fleet is under pressure.
max-parallel: 5
matrix:
os: [ubuntu-latest, macos-latest]
project: [chrome, chromium, firefox, webkit]
include:
- os: windows-latest
project: chrome
- os: windows-latest
project: chromium
- os: windows-latest
project: firefox
- os: windows-latest
project: webkit
- os: windows-latest
# Temporary Perfetto follow-up: ten fresh runners for each affected configuration.
os: [windows-latest, ubuntu-latest]
project: [chrome, msedge]
sample: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
exclude:
- os: ubuntu-latest
project: msedge
runs-on: ${{ matrix.os }}
permissions:
Expand All @@ -58,8 +54,30 @@ jobs:
- uses: ./.github/actions/run-test
with:
node-version: "22"
command: npm run test-mcp -- --project=${{ matrix.project }}
bot-name: "mcp-${{ matrix.os }}-${{ matrix.project }}"
# Keep the full suite and serial execution so setup/order/cleanup effects remain intact.
command: bash .github/scripts/run-mcp-startup-diagnostic.sh ${{ matrix.project }}
bot-name: "mcp-startup-diagnostic-${{ matrix.os }}-${{ matrix.project }}-sample-${{ matrix.sample }}"
flakiness-client-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_CLIENT_ID }}
flakiness-tenant-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_TENANT_ID }}
flakiness-subscription-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_SUBSCRIPTION_ID }}
- name: Upload raw MCP startup timelines
if: always()
uses: actions/upload-artifact@v4
with:
name: "mcp-startup-timelines-${{ matrix.os }}-${{ matrix.project }}-sample-${{ matrix.sample }}"
path: test-results/mcp-startup-timelines/*.jsonl
if-no-files-found: error
- name: Upload retained Chromium startup traces
if: always()
uses: actions/upload-artifact@v4
with:
name: "mcp-chromium-traces-${{ matrix.os }}-${{ matrix.project }}-sample-${{ matrix.sample }}"
path: test-results/mcp-chromium-traces/**/*.pftrace
if-no-files-found: warn
- name: Upload Ubuntu runner health
if: always() && matrix.os == 'ubuntu-latest'
uses: actions/upload-artifact@v4
with:
name: "mcp-runner-health-${{ matrix.os }}-${{ matrix.project }}-sample-${{ matrix.sample }}"
path: mcp-runner-health/runner-health.tsv
if-no-files-found: error
6 changes: 6 additions & 0 deletions .github/workflows/tests_primary.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ env:
jobs:
test_linux:
name: ${{ matrix.os }} (${{ matrix.browser }} - Node.js ${{ matrix.node-version }})
if: github.event_name != 'pull_request' || github.head_ref != 'skn0tt-diagnose-mcp-startup-stalls'
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
strategy:
fail-fast: false
Expand Down Expand Up @@ -67,6 +68,7 @@ jobs:

test_test_runner:
name: Test Runner
if: github.event_name != 'pull_request' || github.head_ref != 'skn0tt-diagnose-mcp-startup-stalls'
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
strategy:
fail-fast: false
Expand Down Expand Up @@ -142,6 +144,7 @@ jobs:

test_web_components:
name: Web Components - ${{ matrix.package }}
if: github.event_name != 'pull_request' || github.head_ref != 'skn0tt-diagnose-mcp-startup-stalls'
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
permissions:
id-token: write # This is required for OIDC login (azure/login) to succeed
Expand All @@ -165,6 +168,7 @@ jobs:

test_vscode_extension:
name: VSCode Extension
if: github.event_name != 'pull_request' || github.head_ref != 'skn0tt-diagnose-mcp-startup-stalls'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
Expand Down Expand Up @@ -201,6 +205,7 @@ jobs:

test_package_installations:
name: "Installation Test ${{ matrix.os }}"
if: github.event_name != 'pull_request' || github.head_ref != 'skn0tt-diagnose-mcp-startup-stalls'
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
strategy:
fail-fast: false
Expand Down Expand Up @@ -237,6 +242,7 @@ jobs:

test_clock_frozen_time_linux:
name: time library - ${{ matrix.clock }}
if: github.event_name != 'pull_request' || github.head_ref != 'skn0tt-diagnose-mcp-startup-stalls'
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
permissions:
id-token: write # This is required for OIDC login (azure/login) to succeed
Expand Down
5 changes: 5 additions & 0 deletions packages/playwright-core/src/server/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import fs from 'fs';

import { makeSocketPath } from '@utils/fileUtils';
import { createGuid } from '@utils/crypto';
import { startupTrace } from '@utils/startupTrace';
import { BrowserContext, validateBrowserContextOptions } from './browserContext';
import { Download } from './download';
import { SdkObject } from './instrumentation';
Expand Down Expand Up @@ -231,6 +232,7 @@ export class BrowserServer {
endpoint = this._pipeSocketPath;
}

startupTrace('browser.registry-publication.start', { browserPid: this._browser.options.browserProcess.process?.pid, title, endpoint });
const browserInfo: BrowserInfo = {
guid: this._browser.guid,
browserName: this._browser.options.browserType,
Expand All @@ -243,10 +245,12 @@ export class BrowserServer {
workspaceDir: options.workspaceDir,
metadata: options.metadata,
});
startupTrace('browser.registry-publication.end', { browserPid: this._browser.options.browserProcess.process?.pid, title, endpoint });
return { endpoint };
}

async stop() {
startupTrace('browser.server-teardown.start', { browserPid: this._browser.options.browserProcess.process?.pid });
if (!this._browser.options.userDataDir)
await serverRegistry.delete(this._browser.guid);
if (this._pipeSocketPath && process.platform !== 'win32')
Expand All @@ -256,6 +260,7 @@ export class BrowserServer {
this._pipeServer = undefined;
this._wsServer = undefined;
this._isStarted = false;
startupTrace('browser.server-teardown.end', { browserPid: this._browser.options.browserProcess.process?.pid });
}

private async _socketPath() {
Expand Down
Loading
Loading