1 use cargo
::core
::compiler
::{CompileKind, RustcTargetData}
;
2 use cargo
::core
::resolver
::features
::{CliFeatures, FeatureOpts, FeatureResolver, ForceAllTargets}
;
3 use cargo
::core
::resolver
::{HasDevUnits, ResolveBehavior}
;
4 use cargo
::core
::{PackageIdSpec, Workspace}
;
5 use cargo
::ops
::WorkspaceResolve
;
7 use criterion
::{criterion_group, criterion_main, Criterion}
;
9 use std
::path
::{Path, PathBuf}
;
10 use std
::process
::Command
;
13 // This is an arbitrary commit that existed when I started. This helps
14 // ensure consistent results. It can be updated if needed, but that can
15 // make it harder to compare results with older versions of cargo.
16 const CRATES_IO_COMMIT
: &str = "85f7bfd61ea4fee08ec68c468762e886b2aebec6";
25 fn root() -> PathBuf
{
26 let mut p
= PathBuf
::from(env
!("CARGO_TARGET_TMPDIR"));
31 fn target_dir() -> PathBuf
{
37 fn cargo_home() -> PathBuf
{
43 fn index() -> PathBuf
{
49 fn workspaces_path() -> PathBuf
{
55 fn registry_url() -> Url
{
56 Url
::from_file_path(index()).unwrap()
60 let home
= cargo_home();
62 fs
::create_dir_all(&home
).unwrap();
65 home
.join("config.toml"),
69 replace-with = 'local-snapshot'
71 [source.local-snapshot]
80 fn create_target_dir() {
81 // This is necessary to ensure the .rustc_info.json file is written.
82 // Otherwise it won't be written, and it is very expensive to create.
83 if !target_dir().exists() {
84 std
::fs
::create_dir_all(target_dir()).unwrap();
88 /// This clones crates.io at a specific point in time into tmp/index.
91 let maybe_git
= |command
: &str| {
92 let status
= Command
::new("git")
94 .args(command
.split_whitespace().collect
::<Vec
<_
>>())
96 .expect("git should be installed");
99 let git
= |command
: &str| {
100 if !maybe_git(command
) {
101 panic
!("failed to run git command: {}", command
);
105 if maybe_git(&format
!(
106 "rev-parse -q --verify {}^{{commit}}",
113 fs
::create_dir_all(&index
).unwrap();
115 git("remote add origin https://github.com/rust-lang/crates.io-index");
117 git(&format
!("fetch origin {}", CRATES_IO_COMMIT
));
118 git("branch -f master FETCH_HEAD");
121 /// This unpacks the compressed workspace skeletons into tmp/workspaces.
122 fn unpack_workspaces() {
123 let ws_dir
= Path
::new(env
!("CARGO_MANIFEST_DIR"))
127 let archives
= fs
::read_dir(ws_dir
)
129 .map(|e
| e
.unwrap().path())
130 .filter(|p
| p
.extension() == Some(std
::ffi
::OsStr
::new("tgz")));
131 for archive
in archives
{
132 let name
= archive
.file_stem().unwrap();
133 let f
= fs
::File
::open(&archive
).unwrap();
134 let f
= flate2
::read
::GzDecoder
::new(f
);
135 let dest
= workspaces_path().join(&name
);
137 fs
::remove_dir_all(&dest
).unwrap();
139 let mut archive
= tar
::Archive
::new(f
);
140 archive
.unpack(workspaces_path()).unwrap();
144 struct ResolveInfo
<'cfg
> {
146 requested_kinds
: [CompileKind
; 1],
147 target_data
: RustcTargetData
<'cfg
>,
148 cli_features
: CliFeatures
,
149 specs
: Vec
<PackageIdSpec
>,
150 has_dev_units
: HasDevUnits
,
151 force_all_targets
: ForceAllTargets
,
152 ws_resolve
: WorkspaceResolve
<'cfg
>,
155 /// Vec of `(ws_name, ws_root)`.
156 fn workspaces() -> Vec
<(String
, PathBuf
)> {
157 // CARGO_BENCH_WORKSPACES can be used to override, otherwise it just uses
158 // the workspaces in the workspaces directory.
159 let mut ps
: Vec
<_
> = match std
::env
::var_os("CARGO_BENCH_WORKSPACES") {
160 Some(s
) => std
::env
::split_paths(&s
).collect(),
161 None
=> fs
::read_dir(workspaces_path())
163 .map(|e
| e
.unwrap().path())
164 // These currently fail in most cases on Windows due to long
165 // filenames in the git checkouts.
168 && matches
!(p
.file_name().unwrap().to_str().unwrap(), "servo" | "tikv"))
172 // Sort so it is consistent.
175 .map(|p
| (p
.file_name().unwrap().to_str().unwrap().to_owned(), p
))
179 /// Helper for resolving a workspace. This will run the resolver once to
180 /// download everything, and returns all the data structures that are used
181 /// during resolution.
182 fn do_resolve
<'cfg
>(config
: &'cfg Config
, ws_root
: &Path
) -> ResolveInfo
<'cfg
> {
183 let requested_kinds
= [CompileKind
::Host
];
184 let ws
= cargo
::core
::Workspace
::new(&ws_root
.join("Cargo.toml"), config
).unwrap();
185 let target_data
= RustcTargetData
::new(&ws
, &requested_kinds
).unwrap();
186 let cli_features
= CliFeatures
::from_command_line(&[], false, true).unwrap();
187 let pkgs
= cargo
::ops
::Packages
::Default
;
188 let specs
= pkgs
.to_package_id_specs(&ws
).unwrap();
189 let has_dev_units
= HasDevUnits
::Yes
;
190 let force_all_targets
= ForceAllTargets
::No
;
191 // Do an initial run to download anything necessary so that it does
192 // not confuse criterion's warmup.
193 let ws_resolve
= cargo
::ops
::resolve_ws_with_opts(
215 /// Creates a new Config.
217 /// This is separate from `do_resolve` to deal with the ownership and lifetime.
218 fn make_config(ws_root
: &Path
) -> Config
{
219 let shell
= cargo
::core
::Shell
::new();
220 let mut config
= cargo
::util
::Config
::new(shell
, ws_root
.to_path_buf(), cargo_home());
221 // Configure is needed to set the target_dir which is needed to write
222 // the .rustc_info.json file which is very expensive.
239 /// Benchmark of the full `resovle_ws_with_opts` which runs the resolver
240 /// twice, the feature resolver, and more. This is a major component of a
241 /// regular cargo build.
242 fn resolve_ws(c
: &mut Criterion
) {
244 let mut group
= c
.benchmark_group("resolve_ws");
245 for (ws_name
, ws_root
) in workspaces() {
246 let config
= make_config(&ws_root
);
247 // The resolver info is initialized only once in a lazy fashion. This
248 // allows criterion to skip this workspace if the user passes a filter
249 // on the command-line (like `cargo bench -- resolve_ws/tikv`).
251 // Due to the way criterion works, it tends to only run the inner
252 // iterator once, and we don't want to call `do_resolve` in every
253 // "step", since that would just be some useless work.
254 let mut lazy_info
= None
;
255 group
.bench_function(&ws_name
, |b
| {
265 } = lazy_info
.get_or_insert_with(|| do_resolve(&config
, &ws_root
));
267 cargo
::ops
::resolve_ws_with_opts(
283 /// Benchmark of the feature resolver.
284 fn feature_resolver(c
: &mut Criterion
) {
286 let mut group
= c
.benchmark_group("feature_resolver");
287 for (ws_name
, ws_root
) in workspaces() {
288 let config
= make_config(&ws_root
);
289 let mut lazy_info
= None
;
290 group
.bench_function(&ws_name
, |b
| {
300 } = lazy_info
.get_or_insert_with(|| do_resolve(&config
, &ws_root
));
302 let feature_opts
= FeatureOpts
::new_behavior(ResolveBehavior
::V2
, *has_dev_units
);
303 FeatureResolver
::resolve(
306 &ws_resolve
.targeted_resolve
,
320 // Criterion complains about the measurement time being too small, but the
321 // measurement time doesn't seem important to me, what is more important is
322 // the number of iterations which defaults to 100, which seems like a
323 // reasonable default. Otherwise, the measurement time would need to be
324 // changed per workspace. We wouldn't want to spend 60s on every workspace,
325 // that would take too long and isn't necessary for the smaller workspaces.
326 criterion_group
!(benches
, resolve_ws
, feature_resolver
);
327 criterion_main
!(benches
);