Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
fcc9a7f
Increasing test workflow timeout to troubleshoot long running tests.
vvolkgang Sep 25, 2025
6e31d0a
Use -xlarge runners.
vvolkgang Sep 25, 2025
b0b8388
Remove --quite option from xcodebuild command
vvolkgang Sep 29, 2025
1d9bc42
Test -test-repetition-relaunch-enabled and -showBuildTimingSummary
vvolkgang Sep 29, 2025
1894dd0
Implement pyeetd
vvolkgang Sep 30, 2025
27180a3
Test single string output
vvolkgang Sep 30, 2025
dfe29e3
Disable spotlight indexing service
vvolkgang Sep 30, 2025
9f96d89
Unload Metadata.framework
vvolkgang Sep 30, 2025
4308891
Add Spotlight app process kill
vvolkgang Sep 30, 2025
c1a9004
Turning off additional services
vvolkgang Sep 30, 2025
79b1731
Let pyeetd pyeet!
vvolkgang Sep 30, 2025
c7f0c2c
Add action to optimize macOS runners
vvolkgang Oct 2, 2025
f0ef8ce
Split between OS and Simulator processes
vvolkgang Oct 2, 2025
b3254cd
Add process sort by cpu or mem
vvolkgang Oct 2, 2025
38b888e
Add print limit
vvolkgang Oct 2, 2025
272b2ef
Cleanup output
vvolkgang Oct 2, 2025
2098dc5
Add output prop to processinfo
vvolkgang Oct 2, 2025
fadbce9
Add process print every 60 seconds
vvolkgang Oct 2, 2025
ee09dc4
Fix action path
vvolkgang Oct 2, 2025
c52265a
Apply pyeetd to test-bwa too and set xcodebuild to quiet
vvolkgang Oct 2, 2025
6fb70a6
Fix action
vvolkgang Oct 2, 2025
f634c2c
Disable additional services
vvolkgang Oct 2, 2025
ccc1ad6
Improve output
vvolkgang Oct 2, 2025
af60637
Fix action
vvolkgang Oct 2, 2025
d95fda2
Cleanup script folder
vvolkgang Oct 2, 2025
6b767d2
Test normal runners
vvolkgang Oct 2, 2025
bf1c94c
Use runner tuneup action in build-any.yml
vvolkgang Oct 2, 2025
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
84 changes: 84 additions & 0 deletions .github/actions/macos-runner-tuneup/action.yml
Original file line number Diff line number Diff line change
@@ -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!"

3 changes: 3 additions & 0 deletions .github/workflows/_build-any.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 8 additions & 1 deletion .github/workflows/test-bwa.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ jobs:
test:
name: Test
runs-on: macos-26
timeout-minutes: 30
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

๐Ÿค” Do we still want to bump the timeouts, with everything else we're changing to make things lean?

timeout-minutes: 50
permissions:
contents: read

Expand All @@ -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
Expand Down Expand Up @@ -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 \
Expand All @@ -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()
Expand Down
17 changes: 16 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ jobs:
test:
name: Test
runs-on: macos-26
timeout-minutes: 30
timeout-minutes: 50
permissions:
contents: read

Expand All @@ -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
Expand Down Expand Up @@ -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 \
Expand All @@ -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
Comment on lines +139 to +145
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

โ“ Do we want this in the test-bwa as well?


- name: Print Logs Summary
if: always()
Expand Down
1 change: 1 addition & 0 deletions Scripts/pyeetd/.python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.12
139 changes: 139 additions & 0 deletions Scripts/pyeetd/main.py
Original file line number Diff line number Diff line change
@@ -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()