[PM-26455] Improve CI runtimes by disabling cpu intensive processes (#1995)

This commit is contained in:
Álison Fernandes 2025-10-02 17:23:14 +01:00 committed by GitHub
parent 11f7358bd8
commit 0b9b9d89c4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 251 additions and 2 deletions

View File

@ -0,0 +1,84 @@
name: 'macOS Runner Tuneup'
description: 'Optimizes macOS GitHub runners by disabling resource-heavy background processes'
author: 'Bitwarden'
runs:
using: 'composite'
steps:
- name: Optimize macOS Runner
shell: bash
run: |
echo "🚀 Starting macOS Runner Tuneup..."
echo "=================================="
echo "🔍 Disabling Spotlight..."
sudo mdutil -a -i off # disables indexing on all volumes
sudo mdutil -a -d # disables spotlight activity on all volumes
echo "🔍 Disabling Spotlight Knowledge Daemon..."
sudo launchctl disable system/com.apple.spotlightknowledged || true
sudo pkill -SIGKILL spotlightknowledged || true
echo "🛑 Disabling metadata services..."
sudo launchctl disable system/com.apple.metadata.mds || true
sudo launchctl disable system/com.apple.metadata.mds.index || true
sudo launchctl disable system/com.apple.metadata.mds.scan || true
echo "📦 Stopping metadata services..."
sudo launchctl bootout system/com.apple.metadata.mds || true
sudo launchctl bootout system/com.apple.metadata.mds.index || true
sudo launchctl bootout system/com.apple.metadata.mds.scan || true
sudo launchctl bootout system/com.apple.metadata.mdwrite || true
echo "⚡ Killing metadata processes..."
sudo pkill -SIGKILL -f "Metadata.framework/Versions/A/Support/mds" || true
sudo pkill -SIGKILL Spotlight || true
echo "✅ Spotlight disabled!"
echo "💥 Disabling ReportCrash..."
sudo launchctl disable system/com.apple.ReportCrash || true
sudo launchctl disable system/com.apple.ReportCrash.Root || true
echo "💥 Unloading ReportCrash..."
sudo launchctl bootout system/com.apple.ReportCrash || true
sudo launchctl bootout system/com.apple.ReportCrash.Root || true
sudo defaults write com.apple.CrashReporter DialogType none || true
echo "✅ ReportCrash disabled!"
echo "🌱 Disabling EcosystemD..."
sudo launchctl unload -w /System/Library/LaunchDaemons/com.apple.ecosystemd.plist || true
sudo pkill -SIGKILL -f "ecosystemd" || true
echo "✅ EcosystemD disabled!"
echo "🌱 Disabling EcosystemAnalyticsD..."
sudo launchctl bootout system/com.apple.ecosystemanalyticsd || true
echo "✅ EcosystemAnalyticsD disabled!"
echo "🌱 Disabling SubmitDiagInfo..."
sudo defaults write /Library/Preferences/com.apple.SubmitDiagInfo AutoSubmit -bool false || true
echo "🌍 Disabling location services..."
sudo defaults write /Library/Preferences/com.apple.locationd.plist LocationServicesEnabled -int 0 || true
echo "🔍 Disabling Siri..."
sudo defaults write com.apple.assistant.support Assistant\ Enabled -bool false || true
sudo defaults write com.apple.Siri StatusMenuVisible -bool false || true
echo "🔒 Disabling iCloud analytics and usage tracking..."
sudo defaults write com.apple.UsageTracking CoreDonationsEnabled -bool false || true
sudo defaults write com.apple.UsageTracking UDCAutomationEnabled -bool false || true
echo "🔍 Disabling Spotlight suggestions..."
sudo defaults write com.apple.lookup.shared LookupSuggestionsDisabled -bool true || true
echo "📊 Process Information After Tuning"
echo "=================================="
echo "🧠 Sorted by memory usage:"
head -n20 < <(ps -emo pid,pcpu,pmem,comm)
echo ""
echo "🔥 Sorted by CPU usage:"
head -n20 < <(ps -ero pid,pcpu,pmem,comm)
echo ""
echo "🎉 macOS Runner Tuneup Complete!"

View File

@ -55,6 +55,9 @@ jobs:
- name: Check out repo
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
- name: Optimize macOS Runner
uses: ./.github/actions/macos-runner-tuneup
- name: Read Xcode version from file if not provided
run: |
if [ -z "$_XCODE_VERSION" ]; then

View File

@ -60,7 +60,7 @@ jobs:
test:
name: Test
runs-on: macos-26
timeout-minutes: 30
timeout-minutes: 50
permissions:
contents: read
@ -73,6 +73,9 @@ jobs:
- name: Check out repo
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
- name: Optimize macOS Runner
uses: ./.github/actions/macos-runner-tuneup
- name: Read Xcode version and simulator configuration from file if not provided
run: |
if [ -z "$_XCODE_VERSION" ]; then
@ -123,6 +126,7 @@ jobs:
- name: Build and test
run: |
python Scripts/pyeetd/main.py & PYEETD_PID=$!
xcrun xcodebuild test \
-workspace Bitwarden.xcworkspace \
-scheme Authenticator \
@ -132,7 +136,10 @@ jobs:
-derivedDataPath build/DerivedData \
-test-timeouts-enabled yes \
-maximum-test-execution-time-allowance 1 \
-retry-tests-on-failure \
-test-repetition-relaunch-enabled YES \
-quiet
kill $PYEETD_PID
- name: Print Logs Summary
if: always()

View File

@ -55,7 +55,7 @@ jobs:
test:
name: Test
runs-on: macos-26
timeout-minutes: 30
timeout-minutes: 50
permissions:
contents: read
@ -68,6 +68,9 @@ jobs:
- name: Check out repo
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
- name: Optimize macOS Runner
uses: ./.github/actions/macos-runner-tuneup
- name: Read Xcode version and simulator configuration from file if not provided
run: |
if [ -z "$_XCODE_VERSION" ]; then
@ -118,6 +121,7 @@ jobs:
- name: Build and test
run: |
python Scripts/pyeetd/main.py & PYEETD_PID=$!
xcrun xcodebuild test \
-workspace Bitwarden.xcworkspace \
-scheme Bitwarden \
@ -127,7 +131,18 @@ jobs:
-derivedDataPath build/DerivedData \
-test-timeouts-enabled yes \
-maximum-test-execution-time-allowance 1 \
-retry-tests-on-failure \
-test-repetition-relaunch-enabled YES \
-quiet
kill $PYEETD_PID
- name: Output processes
run: |
echo "Sorted by memory usage"
ps -em -o pid,pcpu,pmem,comm | head -n40
echo "--------------------------------"
echo "Sorted by CPU usage"
ps -er -o pid,pcpu,pmem,comm | head -n40
- name: Print Logs Summary
if: always()

View File

@ -0,0 +1 @@
3.12

139
Scripts/pyeetd/main.py Normal file
View File

@ -0,0 +1,139 @@
#!/usr/bin/env python3
"""
pyeetd - based on https://github.com/biscuitehh/yeetd
how to use:
python Scripts/pyeetd/main.py & PYEETD_PID=$!
...
kill $PYEETD_PID
"""
import os
import signal
import time
import subprocess
import re
from dataclasses import dataclass
from enum import Enum
OS_PROCESSES = {
"Spotlight",
"ReportCrash",
"ecosystemanalyticsd"
"com.apple.ecosystemd",
"com.apple.metadata.mds",
}
SIMULATOR_PROCESSES = {
"AegirPoster",
"InfographPoster",
"CollectionsPoster",
"ExtragalacticPoster",
"KaleidoscopePoster",
"EmojiPosterExtension",
"AmbientPhotoFramePosterProvider",
"PhotosPosterProvider",
"AvatarPosterExtension",
"GradientPosterExtension",
"MonogramPosterExtension"
}
SIMULATOR_PATH_SEARCH_KEY = "simruntime/Contents/Resources/RuntimeRoot"
# How long to sleep between checks in seconds
SLEEP_DELAY = 5
# How often to print process info (in seconds)
PRINT_PROCESSES_INTERVAL = 60
@dataclass
class ProcessInfo:
pid: int
cpu_percent: float
memory_percent: float
name: str
is_simulator: bool
@property
def environment(self) -> str:
return "Simulator" if self.is_simulator else "OS"
@property
def output_string(self) -> str:
return f"{self.pid}\t{self.cpu_percent}%\t{self.memory_percent}%\t{self.name}\t{self.environment}"
class ProcessSort(Enum):
CPU = "cpu"
MEMORY = "memory"
def get_processes(sort_by=ProcessSort.CPU):
"""Get all processes using ps command - equivalent to Swift's proc_listallpids"""
sorty_by = "-ero" if sort_by == ProcessSort.CPU else "-emo"
result = subprocess.run(['ps', sorty_by, 'pid,pcpu,pmem,comm'],
capture_output=True, text=True, check=True)
processes = []
for line in result.stdout.splitlines()[1:]: # Skip header
parts = line.strip().split(None, 3)
if len(parts) >= 3:
pid = int(parts[0])
cpu_percent = float(parts[1])
memory_percent = float(parts[2])
name = parts[3]
is_simulator = SIMULATOR_PATH_SEARCH_KEY in name
processes.append(ProcessInfo(pid, cpu_percent, memory_percent, name, is_simulator))
return processes
def print_processes(processes, limit=-1):
output = []
output.append("================================")
output.append("⚡️ Processes sorted by CPU usage:")
output.append("PID\tCPU%\tMemory%\tName\tEnvironment")
limit = len(processes) if limit == -1 else limit
for p in processes[:limit]:
output.append(p.output_string)
output.append("--------------------------------")
output.append("🧠 Processes sorted by memory usage:")
output.append("PID\tCPU%\tMemory%\tName\tEnvironment")
processes_sorted_by_memory = sorted(processes, key=lambda x: x.memory_percent, reverse=True)
for p in processes_sorted_by_memory[:limit]:
output.append(p.output_string)
output.append("================================")
print("\n".join(output))
def find_unwanted(processes):
yeeting = []
for p in processes:
process_target_list = SIMULATOR_PROCESSES if p.is_simulator else OS_PROCESSES
for k in process_target_list:
if k in p.name:
yeeting.append(p)
return yeeting
def yeet(processes):
output = []
for p in processes:
output.append(f"🤠 pyeetd: Stopping - {p.output_string}")
os.killpg(p.pid, signal.SIGKILL)
return output
def main():
print_cycles = PRINT_PROCESSES_INTERVAL // SLEEP_DELAY
i = 0
while True:
output = []
processes = get_processes(ProcessSort.CPU)
processes_to_yeet = find_unwanted(processes)
output.extend(yeet(processes_to_yeet))
output.append(f"🤠 {time.strftime('%Y-%m-%d %H:%M:%S')} - pyeetd {len(processes_to_yeet)} processes.")
print("\n".join(output))
if i % print_cycles == 0:
print_processes(processes, 10)
i += 1
time.sleep(SLEEP_DELAY)
if __name__ == '__main__':
main()