Skip to content
Closed
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
111 changes: 111 additions & 0 deletions .github/workflows/profiling.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
name: Profiling Report
# This configuration runs two separate workflows for PRs
# 1. On 'pull_request': Builds and run pprof profiling scripts
# 2. On 'pull_request_target': Deploys the artifacts from (1) with write permissions
on:
pull_request:
pull_request_target:

permissions:
contents: write # Needed for gh-pages push
pull-requests: write # Needed for PR comment

jobs:
# Job 1: Run on the PR from the fork with a read-only token
build_and_profile:
# This job only runs for the 'pull_request' event, not 'pull_request_target'
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- name: Checkout source
uses: actions/checkout@v4
# This explicitly checks out the head of the pull request
with:
ref: ${{ github.event.pull_request.head.sha }}

- name: Build Kepler
run: |
make build

- name: Enable fake cpu meter
shell: bash
run: |
sed -i '/fake-cpu-meter:/{n;s/enabled: false/enabled: true/}' hack/config.yaml

- name: Run Kepler in the background
run: |
nohup ./bin/kepler --config.file=hack/config.yaml > kepler.log 2>&1 &

- name: Run profiling script
run: |
./hack/reports/profiling.sh

- name: Upload profiling artifacts
uses: actions/upload-artifact@v4
with:
name: profiling-results-${{ github.event.pull_request.number }}
path: ./tmp
retention-days: 1 # Keep artifact for 1 day

- name: Run must gather
shell: bash
run: |
echo "::group::Get logs for kepler"
cat kepler.log || true
echo "::endgroup::"

echo "::group::Fetch metrics from localhost:28282"
curl -s http://localhost:28282/metrics || true
echo "::endgroup::"

# Job 2: Run in the context of the base repo with a write token
deploy_and_comment:
# This job only runs for the 'pull_request_target' event
if: github.event_name == 'pull_request_target'
runs-on: ubuntu-latest
# It requires the build_and_profile job from the corresponding 'pull_request' workflow to succeed
needs: build_and_profile
steps:
- name: Checkout source
uses: actions/checkout@v4
# Checks out the base branch of the PR, NOT the fork's code

- name: Download rofiling artifacts
uses: actions/download-artifact@v4
with:
name: profiling-results-${{ github.event.pull_request.number }}
path: ./tmp

- name: Deploy to GitHub Pages
id: deployment
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./tmp
destination_dir: pr-${{ github.event.pull_request.number }}
keep_files: false
user_name: github-actions[bot]
user_email: github-actions[bot]@users.noreply.github.com

- name: Generate comment message
id: generate_message
run: |
{
echo "message<<EOF"
echo "Profiling reports are ready to be viewed."
echo ""
echo "CPU Profiling Reports:"
echo " * Graph (CPU): https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}/pr-${{ github.event.pull_request.number }}/cpu-profile/graph-cpu.html"
echo " * Flamegraph (CPU): https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}/pr-${{ github.event.pull_request.number }}/cpu-profile/flamegraph-cpu.html"
echo ""
echo "Memory Profiling Reports:"
echo " * Graph (Allocated Objects): https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}/pr-${{ github.event.pull_request.number }}/mem-profile/graph-alloc_objects.html"
echo " * Flamegraph (Allocated Space): https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}/pr-${{ github.event.pull_request.number }}/mem-profile/flamegraph-alloc_space.html"
echo ""
echo "EOF"
} >> $GITHUB_OUTPUT

- name: Create PR Comment with report links
uses: thollander/actions-comment-pull-request@v3
with:
message: ${{ steps.generate_message.outputs.message }}
91 changes: 91 additions & 0 deletions hack/reports/profiling.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#!/usr/bin/env bash

set -eu -o pipefail
trap cleanup INT EXIT

# config
declare DURATION=${DURATION:-30}
declare KEPLER_PORT=${KEPLER_PORT:-28282}

# constants
PROJECT_ROOT="$(git rev-parse --show-toplevel)"
declare -r PROJECT_ROOT
declare -r TMP_DIR="$PROJECT_ROOT/tmp"
declare -r CPU_PROFILE_DIR="$TMP_DIR/cpu-profile"
declare -r MEM_PROFILE_DIR="$TMP_DIR/mem-profile"
declare -r KEPLER_BIN_DIR="$PROJECT_ROOT/bin"
declare -r CPU_HTTP_PORT=29000
declare -r MEM_HTTP_PORT=29001

source "$PROJECT_ROOT/hack/utils.bash"

cleanup() {
info "Cleaning up ..."
# Terminate all background jobs (e.g. pprof servers)
{ jobs -p | xargs -I {} -- pkill -TERM -P {}; } || true
wait
sleep 1

return 0
}

capture_cpu_profile() {
run go tool pprof -proto -seconds "$DURATION" \
-output "$CPU_PROFILE_DIR"/profile.pb.gz "$KEPLER_BIN_DIR/kepler" \
"http://localhost:$KEPLER_PORT/debug/pprof/profile" || return 1

# Start pprof web server in background
run go tool pprof --http "localhost:$CPU_HTTP_PORT" --no_browser \
"$KEPLER_BIN_DIR/kepler" "$CPU_PROFILE_DIR/profile.pb.gz" </dev/null &
sleep 1
# Fetch visualizations
for sample in {cpu,samples}; do
curl --fail "http://localhost:$CPU_HTTP_PORT/ui/?si=$sample" -o "$CPU_PROFILE_DIR/graph-$sample.html" || return 1
curl --fail "http://localhost:$CPU_HTTP_PORT/ui/flamegraph?si=$sample" -o "$CPU_PROFILE_DIR/flamegraph-$sample.html" || return 1
for page in top peek source disasm; do
curl --fail "http://localhost:$CPU_HTTP_PORT/ui/$page?si=$sample" -o "$CPU_PROFILE_DIR/$page-$sample.html" || return 1
done
done

return 0
}

capture_mem_profile() {
run go tool pprof -proto -seconds "$DURATION" \
-output "$MEM_PROFILE_DIR"/profile.pb.gz "$KEPLER_BIN_DIR/kepler" \
"http://localhost:$KEPLER_PORT/debug/pprof/heap" || return 1

# Start pprof web server in background
run go tool pprof --http "localhost:$MEM_HTTP_PORT" --no_browser \
"$KEPLER_BIN_DIR/kepler" "$MEM_PROFILE_DIR/profile.pb.gz" </dev/null &
sleep 1
# Fetch visualizations
for sample in {alloc,inuse}_{objects,space}; do
curl --fail "http://localhost:$MEM_HTTP_PORT/ui/?si=$sample" -o "$MEM_PROFILE_DIR/graph-$sample.html" || return 1
curl --fail "http://localhost:$MEM_HTTP_PORT/ui/flamegraph?si=$sample" -o "$MEM_PROFILE_DIR/flamegraph-$sample.html" || return 1
for page in top peek source disasm; do
curl --fail "http://localhost:$MEM_HTTP_PORT/ui/$page?si=$sample" -o "$MEM_PROFILE_DIR/$page-$sample.html" || return 1
done
done

return 0
}

main() {
cd "$PROJECT_ROOT"
mkdir -p "${CPU_PROFILE_DIR}"
mkdir -p "${MEM_PROFILE_DIR}"

header "Running CPU Profiling"
capture_cpu_profile || {
fail "CPU Profiling failed"
return 1
}
header "Running Memory Profiling"
capture_mem_profile || {
fail "Memory Profiling failed"
return 1
}
}

main "$@"
Loading