Improve compare_perf.yml, cherry_pick.yml (#41606)

Conrad Irwin and Nia Espera created

Release Notes:

- N/A

---------

Co-authored-by: Nia Espera <nia@zed.dev>

Change summary

.github/workflows/cherry_pick.yml                 | 25 ++++++++
.github/workflows/compare_perf.yml                | 53 ++++++++++++++++
Cargo.lock                                        |  4 
Cargo.toml                                        |  2 
script/cherry-pick                                | 33 ++++++++++
tooling/xtask/src/tasks/workflows.rs              |  2 
tooling/xtask/src/tasks/workflows/cherry_pick.rs  | 33 ++++++++++
tooling/xtask/src/tasks/workflows/compare_perf.rs | 46 ++++++++++++-
tooling/xtask/src/tasks/workflows/steps.rs        |  6 +
tooling/xtask/src/tasks/workflows/vars.rs         | 33 +++++++++
10 files changed, 224 insertions(+), 13 deletions(-)

Detailed changes

.github/workflows/cherry_pick.yml 🔗

@@ -0,0 +1,25 @@
+# Generated from xtask::workflows::cherry_pick
+# Rebuild with `cargo xtask workflows`.
+name: cherry_pick
+on:
+  workflow_dispatch:
+    inputs:
+      commit:
+        description: commit
+        required: true
+        type: string
+      branch:
+        description: branch
+        required: true
+        type: string
+jobs:
+  run_cherry_pick:
+    runs-on: namespace-profile-2x4-ubuntu-2404
+    steps:
+    - name: steps::checkout_repo
+      uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+      with:
+        clean: false
+    - name: cherry_pick::run_cherry_pick::cherry_pick
+      run: ./scripts/cherry-pick ${{ inputs.branch }} ${{ inputs.commit }}
+      shell: bash -euxo pipefail {0}

.github/workflows/compare_perf.yml 🔗

@@ -2,12 +2,61 @@
 # Rebuild with `cargo xtask workflows`.
 name: compare_perf
 on:
-  workflow_dispatch: {}
+  workflow_dispatch:
+    inputs:
+      head:
+        description: head
+        required: true
+        type: string
+      base:
+        description: base
+        required: true
+        type: string
 jobs:
   run_perf:
-    runs-on: namespace-profile-2x4-ubuntu-2404
+    runs-on: namespace-profile-16x32-ubuntu-2204
     steps:
     - name: steps::checkout_repo
       uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
       with:
         clean: false
+    - name: steps::setup_cargo_config
+      run: |
+        mkdir -p ./../.cargo
+        cp ./.cargo/ci-config.toml ./../.cargo/config.toml
+      shell: bash -euxo pipefail {0}
+    - name: steps::setup_linux
+      run: ./script/linux
+      shell: bash -euxo pipefail {0}
+    - name: steps::install_mold
+      run: ./script/install-mold
+      shell: bash -euxo pipefail {0}
+    - name: compare_perf::run_perf::install_hyperfine
+      run: cargo install hyperfine
+      shell: bash -euxo pipefail {0}
+    - name: steps::git_checkout
+      run: git fetch origin ${{ inputs.base }} && git checkout ${{ inputs.base }}
+      shell: bash -euxo pipefail {0}
+    - name: compare_perf::run_perf::cargo_perf_test
+      run: cargo perf-test -p gpui -- --json=${{ inputs.base }}
+      shell: bash -euxo pipefail {0}
+    - name: steps::git_checkout
+      run: git fetch origin ${{ inputs.head }} && git checkout ${{ inputs.head }}
+      shell: bash -euxo pipefail {0}
+    - name: compare_perf::run_perf::cargo_perf_test
+      run: cargo perf-test -p gpui -- --json=${{ inputs.head }}
+      shell: bash -euxo pipefail {0}
+    - name: compare_perf::run_perf::compare_runs
+      run: cargo perf-compare --save=results.md ${{ inputs.base }} ${{ inputs.head }}
+      shell: bash -euxo pipefail {0}
+    - name: '@actions/upload-artifact results.md'
+      uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
+      with:
+        name: results.md
+        path: results.md
+        if-no-files-found: error
+    - name: steps::cleanup_cargo_config
+      if: always()
+      run: |
+        rm -rf ./../.cargo
+      shell: bash -euxo pipefail {0}

Cargo.lock 🔗

@@ -6961,7 +6961,7 @@ dependencies = [
 [[package]]
 name = "gh-workflow"
 version = "0.8.0"
-source = "git+https://github.com/zed-industries/gh-workflow?rev=0090c6b6ef82fff02bc8616645953e778d1acc08#0090c6b6ef82fff02bc8616645953e778d1acc08"
+source = "git+https://github.com/zed-industries/gh-workflow?rev=3eaa84abca0778eb54272f45a312cb24f9a0b435#3eaa84abca0778eb54272f45a312cb24f9a0b435"
 dependencies = [
  "async-trait",
  "derive_more 2.0.1",
@@ -6978,7 +6978,7 @@ dependencies = [
 [[package]]
 name = "gh-workflow-macros"
 version = "0.8.0"
-source = "git+https://github.com/zed-industries/gh-workflow?rev=0090c6b6ef82fff02bc8616645953e778d1acc08#0090c6b6ef82fff02bc8616645953e778d1acc08"
+source = "git+https://github.com/zed-industries/gh-workflow?rev=3eaa84abca0778eb54272f45a312cb24f9a0b435#3eaa84abca0778eb54272f45a312cb24f9a0b435"
 dependencies = [
  "heck 0.5.0",
  "quote",

Cargo.toml 🔗

@@ -508,7 +508,7 @@ fork = "0.2.0"
 futures = "0.3"
 futures-batch = "0.6.1"
 futures-lite = "1.13"
-gh-workflow = { git = "https://github.com/zed-industries/gh-workflow", rev = "0090c6b6ef82fff02bc8616645953e778d1acc08" }
+gh-workflow = { git = "https://github.com/zed-industries/gh-workflow", rev = "3eaa84abca0778eb54272f45a312cb24f9a0b435" }
 git2 = { version = "0.20.1", default-features = false }
 globset = "0.4"
 handlebars = "4.3"

script/cherry-pick 🔗

@@ -0,0 +1,33 @@
+# #!/bin/bash
+set -euxo pipefail
+
+if [ "$#" -ne 2 ]; then
+    echo "Usage: $0 <branch-name> <commit-sha>"
+    exit 1
+fi
+
+BRANCH_NAME="$1"
+COMMIT_SHA="$2"
+
+SHORT_SHA="${COMMIT_SHA:0:8}"
+NEW_BRANCH="cherry-pick-${BRANCH_NAME}-${SHORT_SHA}"
+git fetch origin
+git checkout "$BRANCH_NAME"
+git checkout -b "$NEW_BRANCH"
+
+git cherry-pick "$COMMIT_SHA"
+
+git push origin "$NEW_BRANCH"
+COMMIT_TITLE=$(git log -1 --pretty=format:"%s" "$COMMIT_SHA")
+COMMIT_BODY=$(git log -1 --pretty=format:"%b" "$COMMIT_SHA")
+
+# Check if commit title ends with (#number)
+if [[ "$COMMIT_TITLE" =~ \(#([0-9]+)\)$ ]]; then
+    PR_NUMBER="${BASH_REMATCH[1]}"
+    PR_BODY="Cherry-pick of #${PR_NUMBER}"$'\n'$'\n'"----"$'\n'"${COMMIT_BODY}"
+else
+    PR_BODY="Cherry-pick of ${COMMIT_SHA}"$'\n'$'\n'"----"$'\n'"${COMMIT_BODY}"
+fi
+
+# Create a pull request
+gh pr create --base "$BRANCH_NAME" --head "$NEW_BRANCH" --title "$COMMIT_TITLE (cherry-pick)" --body "$PR_BODY"

tooling/xtask/src/tasks/workflows.rs 🔗

@@ -3,6 +3,7 @@ use clap::Parser;
 use std::fs;
 use std::path::Path;
 
+mod cherry_pick;
 mod compare_perf;
 mod danger;
 mod nix_build;
@@ -28,6 +29,7 @@ pub fn run_workflows(_: GenerateWorkflowArgs) -> Result<()> {
         ("release_nightly.yml", release_nightly::release_nightly()),
         ("run_tests.yml", run_tests::run_tests()),
         ("release.yml", release::release()),
+        ("cherry_pick.yml", cherry_pick::cherry_pick()),
         ("compare_perf.yml", compare_perf::compare_perf()),
         ("run_unit_evals.yml", run_agent_evals::run_unit_evals()),
         ("run_agent_evals.yml", run_agent_evals::run_agent_evals()),

tooling/xtask/src/tasks/workflows/cherry_pick.rs 🔗

@@ -0,0 +1,33 @@
+use gh_workflow::*;
+
+use crate::tasks::workflows::{
+    runners,
+    steps::{self, NamedJob, named},
+    vars::Input,
+};
+
+pub fn cherry_pick() -> Workflow {
+    let branch = Input::string("branch", None);
+    let commit = Input::string("commit", None);
+    let cherry_pick = run_cherry_pick(&branch, &commit);
+    named::workflow()
+        .on(Event::default().workflow_dispatch(
+            WorkflowDispatch::default()
+                .add_input(commit.name, commit.input())
+                .add_input(branch.name, branch.input()),
+        ))
+        .add_job(cherry_pick.name, cherry_pick.job)
+}
+
+fn run_cherry_pick(branch: &Input, commit: &Input) -> NamedJob {
+    fn cherry_pick(branch: &str, commit: &str) -> Step<Run> {
+        named::bash(&format!("./scripts/cherry-pick {branch} {commit}"))
+    }
+
+    named::job(
+        Job::default()
+            .runs_on(runners::LINUX_SMALL)
+            .add_step(steps::checkout_repo())
+            .add_step(cherry_pick(&branch.var(), &commit.var())),
+    )
+}

tooling/xtask/src/tasks/workflows/compare_perf.rs 🔗

@@ -1,22 +1,56 @@
 use gh_workflow::*;
 
+use crate::tasks::workflows::run_bundling::upload_artifact;
+use crate::tasks::workflows::steps::FluentBuilder;
 use crate::tasks::workflows::{
     runners,
     steps::{self, NamedJob, named},
+    vars::Input,
 };
 
-/// Generates the danger.yml workflow
 pub fn compare_perf() -> Workflow {
-    let run_perf = run_perf();
+    let head = Input::string("head", None);
+    let base = Input::string("base", None);
+    let run_perf = run_perf(&base, &head);
     named::workflow()
-        .on(Event::default().workflow_dispatch(WorkflowDispatch::default()))
+        .on(Event::default().workflow_dispatch(
+            WorkflowDispatch::default()
+                .add_input(head.name, head.input())
+                .add_input(base.name, base.input()),
+        ))
         .add_job(run_perf.name, run_perf.job)
 }
 
-pub fn run_perf() -> NamedJob {
+pub fn run_perf(base: &Input, head: &Input) -> NamedJob {
+    fn cargo_perf_test(ref_name: String) -> Step<Run> {
+        // TODO: vim not gpui, and ideally allow args
+        named::bash(&format!("cargo perf-test -p gpui -- --json={ref_name}"))
+    }
+
+    fn install_hyperfine() -> Step<Run> {
+        named::bash("cargo install hyperfine")
+    }
+
+    fn compare_runs(head: String, base: String) -> Step<Run> {
+        // TODO: this should really be swapped...
+        named::bash(&format!(
+            "cargo perf-compare --save=results.md {base} {head}"
+        ))
+    }
+
     named::job(
         Job::default()
-            .runs_on(runners::LINUX_SMALL)
-            .add_step(steps::checkout_repo()),
+            .runs_on(runners::LINUX_DEFAULT)
+            .add_step(steps::checkout_repo())
+            .add_step(steps::setup_cargo_config(runners::Platform::Linux))
+            .map(steps::install_linux_dependencies)
+            .add_step(install_hyperfine())
+            .add_step(steps::git_checkout(&base.var()))
+            .add_step(cargo_perf_test(base.var()))
+            .add_step(steps::git_checkout(&head.var()))
+            .add_step(cargo_perf_test(head.var()))
+            .add_step(compare_runs(head.var(), base.var()))
+            .add_step(upload_artifact("results.md"))
+            .add_step(steps::cleanup_cargo_config(runners::Platform::Linux)),
     )
 }

tooling/xtask/src/tasks/workflows/steps.rs 🔗

@@ -298,3 +298,9 @@ pub(crate) mod named {
             .join("::")
     }
 }
+
+pub fn git_checkout(ref_name: &str) -> Step<Run> {
+    named::bash(&format!(
+        "git fetch origin {ref_name} && git checkout {ref_name}"
+    ))
+}

tooling/xtask/src/tasks/workflows/vars.rs 🔗

@@ -1,6 +1,6 @@
 use std::cell::RefCell;
 
-use gh_workflow::{Concurrency, Env, Expression};
+use gh_workflow::{Concurrency, Env, Expression, WorkflowDispatchInput};
 
 use crate::tasks::workflows::{runners::Platform, steps::NamedJob};
 
@@ -107,7 +107,7 @@ impl PathCondition {
             name: job.name,
             job: job
                 .job
-                .add_needs(set_by_step.clone())
+                .add_need(set_by_step.clone())
                 .cond(Expression::new(format!(
                     "needs.{}.outputs.{} == 'true'",
                     &set_by_step, self.name
@@ -116,6 +116,35 @@ impl PathCondition {
     }
 }
 
+pub(crate) struct Input {
+    pub input_type: &'static str,
+    pub name: &'static str,
+    pub default: Option<String>,
+}
+
+impl Input {
+    pub fn string(name: &'static str, default: Option<String>) -> Self {
+        Self {
+            input_type: "string",
+            name,
+            default,
+        }
+    }
+
+    pub fn var(&self) -> String {
+        format!("${{{{ inputs.{} }}}}", self.name)
+    }
+
+    pub fn input(&self) -> WorkflowDispatchInput {
+        WorkflowDispatchInput {
+            description: self.name.to_owned(),
+            required: self.default.is_none(),
+            input_type: self.input_type.to_owned(),
+            default: self.default.clone(),
+        }
+    }
+}
+
 pub mod assets {
     // NOTE: these asset names also exist in the zed.dev codebase.
     pub const MAC_AARCH64: &str = "Zed-aarch64.dmg";