Skip to main content

flowey_lib_hvlite/_jobs/
check_openvmm_hcl_size.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Compares the size of the OpenHCL binary in the current PR with the size of the binary from the last successful merge to the PR's base branch.
5
6use crate::build_openhcl_igvm_from_recipe;
7use crate::build_openvmm_hcl;
8use crate::common::CommonArch;
9use crate::common::CommonTriple;
10use flowey::node::prelude::*;
11use flowey_lib_common::download_gh_artifact;
12use flowey_lib_common::gh_workflow_id;
13use flowey_lib_common::git_merge_commit;
14
15pub fn artifact_name_openhcl_baseline(arch: CommonArch) -> &'static str {
16    match arch {
17        CommonArch::X86_64 => "x64-openhcl-baseline",
18        CommonArch::Aarch64 => "aarch64-openhcl-baseline",
19    }
20}
21
22flowey_request! {
23    pub struct Request {
24        pub target: CommonTriple,
25        pub done: WriteVar<SideEffect>,
26        pub pipeline_name: String,
27        pub job_name: String,
28    }
29}
30
31new_simple_flow_node!(struct Node);
32
33impl SimpleFlowNode for Node {
34    type Request = Request;
35
36    fn imports(ctx: &mut ImportCtx<'_>) {
37        ctx.import::<crate::build_xtask::Node>();
38        ctx.import::<crate::git_checkout_openvmm_repo::Node>();
39        ctx.import::<download_gh_artifact::Node>();
40        ctx.import::<git_merge_commit::Node>();
41        ctx.import::<gh_workflow_id::Node>();
42        ctx.import::<build_openhcl_igvm_from_recipe::Node>();
43        ctx.import::<build_openvmm_hcl::Node>();
44        ctx.import::<crate::_jobs::build_and_publish_openvmm_hcl_baseline::Node>();
45    }
46
47    fn process_request(request: Self::Request, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()> {
48        let Request {
49            target,
50            done,
51            pipeline_name,
52            job_name,
53        } = request;
54
55        let xtask_target = CommonTriple::Common {
56            arch: ctx.arch().try_into()?,
57            platform: ctx.platform().try_into()?,
58        };
59
60        let xtask = ctx.reqv(|v| crate::build_xtask::Request {
61            target: xtask_target,
62            xtask: v,
63        });
64        let openvmm_repo_path = ctx.reqv(crate::git_checkout_openvmm_repo::req::GetRepoDir);
65
66        let new_openvmm_hcl_baseline =
67            ctx.reqv(
68                |v| crate::_jobs::build_and_publish_openvmm_hcl_baseline::Request {
69                    target: target.clone(),
70                    baseline: v,
71                },
72            );
73
74        let file_name = artifact_name_openhcl_baseline(target.common_arch().unwrap());
75
76        // Compare against the PR's target branch, which is not always `main`
77        // (e.g. backports target a `release/*` branch).
78        let base_branch = if ctx.backend() == FlowBackend::Github {
79            ctx.get_gh_context_var().global().base_ref()
80        } else {
81            ReadVar::from_static("main".into())
82        };
83
84        let merge_commit = ctx.reqv(|v| git_merge_commit::Request {
85            repo_path: openvmm_repo_path.clone(),
86            merge_commit: v,
87            base_branch,
88        });
89
90        // The baseline is published by the build job, so accept a run that is
91        // still in progress once that job has succeeded. Waiting for the whole
92        // run would tie the baseline to its slowest jobs, such as VMM tests
93        // queued for scarce self-hosted runners.
94        let merge_run = ctx.reqv(|v| gh_workflow_id::Request {
95            repo_owner: crate::common::OPENVMM_GITHUB_OWNER.into(),
96            repo_name: crate::common::OPENVMM_GITHUB_REPO.into(),
97            commit_or_branch: gh_workflow_id::GitCommitOrBranch::Commit(merge_commit),
98            pipeline_name,
99            require_run_status: None,
100            require_successful_job_with_name: Some(job_name),
101            gh_workflow: v,
102        });
103
104        let run_id = merge_run.map(ctx, |r| r.id);
105        // TODO: this should return a `ReadVar<OpenvmmHclBaselineOutput>`
106        let merge_head_artifact = ctx.reqv(|old_openhcl| download_gh_artifact::Request {
107            repo_owner: crate::common::OPENVMM_GITHUB_OWNER.into(),
108            repo_name: crate::common::OPENVMM_GITHUB_REPO.into(),
109            file_name: file_name.into(),
110            path: old_openhcl,
111            run_id,
112        });
113
114        // Publish the built binary as an artifact for offline analysis.
115        //
116        // FUTURE: Flowey should have a general mechanism for this. We cannot
117        // use the existing artifact support because all artifacts are only
118        // published at the end of the job, if everything else succeeds.
119        //
120        // The general mechanism should also support typed artifacts.
121        let publish_artifact = if ctx.backend() == FlowBackend::Github {
122            let dir = ctx.emit_rust_stepv("collect openvmm_hcl files for analysis", |ctx| {
123                let new_openvmm_hcl_baseline = new_openvmm_hcl_baseline.clone().claim(ctx);
124                move |rt| {
125                    let new_openvmm_hcl_baseline = rt.read(new_openvmm_hcl_baseline);
126                    let path = Path::new("artifact");
127                    fs_err::create_dir_all(path)?;
128                    fs_err::copy(new_openvmm_hcl_baseline.bin, path.join("openhcl"))?;
129                    Ok(std::path::absolute(path)?
130                        .into_os_string()
131                        .into_string()
132                        .ok()
133                        .unwrap())
134                }
135            });
136            Some(
137                // actions/upload-artifact v7.0.1
138                ctx.emit_gh_step(
139                    "publish openvmm_hcl for analysis",
140                    "actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a",
141                )
142                .with("name", file_name)
143                .with("path", dir)
144                .finish(ctx),
145            )
146        } else {
147            None
148        };
149
150        let comparison = ctx.emit_rust_step("binary size comparison", |ctx| {
151            // Ensure the artifact is published before the analysis since this step may fail.
152            let _publish_artifact = publish_artifact.claim(ctx);
153            let xtask = xtask.claim(ctx);
154            let openvmm_repo_path = openvmm_repo_path.claim(ctx);
155            let old_openhcl = merge_head_artifact.claim(ctx);
156            let new_openhcl = new_openvmm_hcl_baseline.claim(ctx);
157            let merge_run = merge_run.claim(ctx);
158
159            move |rt| {
160                let xtask = match rt.read(xtask) {
161                    crate::build_xtask::XtaskOutput::LinuxBin { bin, .. } => bin,
162                    crate::build_xtask::XtaskOutput::WindowsBin { exe, .. } => exe,
163                };
164
165                let old_openhcl = rt.read(old_openhcl);
166                let new_openhcl = rt.read(new_openhcl);
167                let merge_run = rt.read(merge_run);
168
169                // The contents of the artifact should match `OpenvmmHclBaselineOutput`
170                let old_path = old_openhcl.join("openhcl");
171                let new_path = new_openhcl.bin;
172
173                println!(
174                    "comparing HEAD to merge commit {} and workflow {}",
175                    merge_run.commit, merge_run.id
176                );
177
178                let path = rt.read(openvmm_repo_path);
179                rt.sh.change_dir(path);
180                flowey::shell_cmd!(
181                    rt,
182                    "{xtask} verify-size --original {old_path} --new {new_path}"
183                )
184                .run()?;
185
186                Ok(())
187            }
188        });
189
190        ctx.emit_side_effect_step(vec![comparison], [done]);
191
192        Ok(())
193    }
194}