Skip to content

Commit 8519ad2

Browse files
committed
Auto merge of #14238 - epage:traces, r=arlosi
Add more profiling traces ### What does this PR try to resolve? Someone suggested I look at [zed](https://github.com/zed-industries/zed). It has some interesting performance characteristics compared to projects I looked at before: - A lot of workspace members - git dependencies - patching with git dependencies This adds traces that help provide more context for those scenarios when looking at them. ![image](https://github.com/rust-lang/cargo/assets/60961/4bee9a61-e019-477c-af67-6d8a1b1398e9) ### How should we test and review this PR? ### Additional information Some of these areas might be of interest for optimizing: - On every invocation, we do a git2 `copy_to` call for each git checkout - On every invocation, we do a `du` on each git checkout - It'd be great to avoid parsing every manifest in a git checkout but we need to do a full parse to make sure we find all packages (see https://rust-lang.zulipchat.com/#narrow/stream/246057-t-cargo/topic/Redundant.20code.20in.20.60GitSouce.60.3F) - Its suspicious how much time we spend in the "poison" step of resolving when its a no-op
2 parents e55aad0 + 89eb406 commit 8519ad2

File tree

6 files changed

+102
-83
lines changed

6 files changed

+102
-83
lines changed

src/cargo/core/global_cache_tracker.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1799,6 +1799,7 @@ pub fn is_silent_error(e: &anyhow::Error) -> bool {
17991799
}
18001800

18011801
/// Returns the disk usage for a git checkout directory.
1802+
#[tracing::instrument]
18021803
pub fn du_git_checkout(path: &Path) -> CargoResult<u64> {
18031804
// !.git is used because clones typically use hardlinks for the git
18041805
// contents. TODO: Verify behavior on Windows.

src/cargo/core/registry.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,7 @@ impl<'gctx> PackageRegistry<'gctx> {
336336
/// The return value is a `Vec` of patches that should *not* be locked.
337337
/// This happens when the patch is locked, but the patch has been updated
338338
/// so the locked value is no longer correct.
339+
#[tracing::instrument(skip(self, patch_deps))]
339340
pub fn patch(
340341
&mut self,
341342
url: &Url,
@@ -784,6 +785,7 @@ impl<'gctx> Registry for PackageRegistry<'gctx> {
784785
}
785786
}
786787

788+
#[tracing::instrument(skip_all)]
787789
fn block_until_ready(&mut self) -> CargoResult<()> {
788790
if cfg!(debug_assertions) {
789791
// Force borrow to catch invalid borrows, regardless of which source is used and how it

src/cargo/core/workspace.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -723,6 +723,7 @@ impl<'gctx> Workspace<'gctx> {
723723
/// verifies that those are all valid packages to point to. Otherwise, this
724724
/// will transitively follow all `path` dependencies looking for members of
725725
/// the workspace.
726+
#[tracing::instrument(skip_all)]
726727
fn find_members(&mut self) -> CargoResult<()> {
727728
let Some(workspace_config) = self.load_workspace_config()? else {
728729
debug!("find_members - only me as a member");
@@ -886,6 +887,7 @@ impl<'gctx> Workspace<'gctx> {
886887
/// 1. A workspace only has one root.
887888
/// 2. All workspace members agree on this one root as the root.
888889
/// 3. The current crate is a member of this workspace.
890+
#[tracing::instrument(skip_all)]
889891
fn validate(&mut self) -> CargoResult<()> {
890892
// The rest of the checks require a VirtualManifest or multiple members.
891893
if self.root_manifest.is_none() {
@@ -954,6 +956,7 @@ impl<'gctx> Workspace<'gctx> {
954956
}
955957
}
956958

959+
#[tracing::instrument(skip_all)]
957960
fn validate_members(&mut self) -> CargoResult<()> {
958961
for member in self.members.clone() {
959962
let root = self.find_root(&member)?;
@@ -1812,6 +1815,7 @@ impl WorkspaceRootConfig {
18121815
self.members.is_some()
18131816
}
18141817

1818+
#[tracing::instrument(skip_all)]
18151819
fn members_paths(&self, globs: &[String]) -> CargoResult<Vec<PathBuf>> {
18161820
let mut expanded_list = Vec::new();
18171821

src/cargo/ops/cargo_read_manifest.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ pub fn read_package(
3434
Ok(Package::new(manifest, path))
3535
}
3636

37+
#[tracing::instrument(skip(source_id, gctx))]
3738
pub fn read_packages(
3839
path: &Path,
3940
source_id: SourceId,

src/cargo/ops/resolve.rs

Lines changed: 93 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,7 @@ pub fn resolve_with_previous<'gctx>(
387387
register_previous_locks(ws, registry, r, &keep, dev_deps);
388388

389389
// Prefer to use anything in the previous lock file, aka we want to have conservative updates.
390+
let _span = tracing::span!(tracing::Level::TRACE, "prefer_package_id").entered();
390391
for id in r.iter().filter(keep) {
391392
debug!("attempting to prefer {}", id);
392393
version_prefs.prefer_package_id(id);
@@ -397,20 +398,22 @@ pub fn resolve_with_previous<'gctx>(
397398
registry.lock_patches();
398399
}
399400

400-
let summaries: Vec<(Summary, ResolveOpts)> = ws
401-
.members_with_features(specs, cli_features)?
402-
.into_iter()
403-
.map(|(member, features)| {
404-
let summary = registry.lock(member.summary().clone());
405-
(
406-
summary,
407-
ResolveOpts {
408-
dev_deps,
409-
features: RequestedFeatures::CliFeatures(features),
410-
},
411-
)
412-
})
413-
.collect();
401+
let summaries: Vec<(Summary, ResolveOpts)> = {
402+
let _span = tracing::span!(tracing::Level::TRACE, "registry.lock").entered();
403+
ws.members_with_features(specs, cli_features)?
404+
.into_iter()
405+
.map(|(member, features)| {
406+
let summary = registry.lock(member.summary().clone());
407+
(
408+
summary,
409+
ResolveOpts {
410+
dev_deps,
411+
features: RequestedFeatures::CliFeatures(features),
412+
},
413+
)
414+
})
415+
.collect()
416+
};
414417

415418
let replace = lock_replacements(ws, previous, &keep);
416419

@@ -498,6 +501,7 @@ pub fn get_resolved_packages<'gctx>(
498501
///
499502
/// Note that this function, at the time of this writing, is basically the
500503
/// entire fix for issue #4127.
504+
#[tracing::instrument(skip_all)]
501505
fn register_previous_locks(
502506
ws: &Workspace<'_>,
503507
registry: &mut PackageRegistry<'_>,
@@ -578,60 +582,63 @@ fn register_previous_locks(
578582
// crates from crates.io* are not locked (aka added to `avoid_locking`).
579583
// For dependencies like `log` their previous version in the lock file will
580584
// come up first before newer version, if newer version are available.
581-
let mut path_deps = ws.members().cloned().collect::<Vec<_>>();
582-
let mut visited = HashSet::new();
583-
while let Some(member) = path_deps.pop() {
584-
if !visited.insert(member.package_id()) {
585-
continue;
586-
}
587-
let is_ws_member = ws.is_member(&member);
588-
for dep in member.dependencies() {
589-
// If this dependency didn't match anything special then we may want
590-
// to poison the source as it may have been added. If this path
591-
// dependencies is **not** a workspace member, however, and it's an
592-
// optional/non-transitive dependency then it won't be necessarily
593-
// be in our lock file. If this shows up then we avoid poisoning
594-
// this source as otherwise we'd repeatedly update the registry.
595-
//
596-
// TODO: this breaks adding an optional dependency in a
597-
// non-workspace member and then simultaneously editing the
598-
// dependency on that crate to enable the feature. For now,
599-
// this bug is better than the always-updating registry though.
600-
if !is_ws_member && (dep.is_optional() || !dep.is_transitive()) {
585+
{
586+
let _span = tracing::span!(tracing::Level::TRACE, "poison").entered();
587+
let mut path_deps = ws.members().cloned().collect::<Vec<_>>();
588+
let mut visited = HashSet::new();
589+
while let Some(member) = path_deps.pop() {
590+
if !visited.insert(member.package_id()) {
601591
continue;
602592
}
593+
let is_ws_member = ws.is_member(&member);
594+
for dep in member.dependencies() {
595+
// If this dependency didn't match anything special then we may want
596+
// to poison the source as it may have been added. If this path
597+
// dependencies is **not** a workspace member, however, and it's an
598+
// optional/non-transitive dependency then it won't be necessarily
599+
// be in our lock file. If this shows up then we avoid poisoning
600+
// this source as otherwise we'd repeatedly update the registry.
601+
//
602+
// TODO: this breaks adding an optional dependency in a
603+
// non-workspace member and then simultaneously editing the
604+
// dependency on that crate to enable the feature. For now,
605+
// this bug is better than the always-updating registry though.
606+
if !is_ws_member && (dep.is_optional() || !dep.is_transitive()) {
607+
continue;
608+
}
603609

604-
// If dev-dependencies aren't being resolved, skip them.
605-
if !dep.is_transitive() && !dev_deps {
606-
continue;
607-
}
610+
// If dev-dependencies aren't being resolved, skip them.
611+
if !dep.is_transitive() && !dev_deps {
612+
continue;
613+
}
608614

609-
// If this is a path dependency, then try to push it onto our
610-
// worklist.
611-
if let Some(pkg) = path_pkg(dep.source_id()) {
612-
path_deps.push(pkg);
613-
continue;
614-
}
615+
// If this is a path dependency, then try to push it onto our
616+
// worklist.
617+
if let Some(pkg) = path_pkg(dep.source_id()) {
618+
path_deps.push(pkg);
619+
continue;
620+
}
615621

616-
// If we match *anything* in the dependency graph then we consider
617-
// ourselves all ok, and assume that we'll resolve to that.
618-
if resolve.iter().any(|id| dep.matches_ignoring_source(id)) {
619-
continue;
620-
}
622+
// If we match *anything* in the dependency graph then we consider
623+
// ourselves all ok, and assume that we'll resolve to that.
624+
if resolve.iter().any(|id| dep.matches_ignoring_source(id)) {
625+
continue;
626+
}
621627

622-
// Ok if nothing matches, then we poison the source of these
623-
// dependencies and the previous lock file.
624-
debug!(
625-
"poisoning {} because {} looks like it changed {}",
626-
dep.source_id(),
627-
member.package_id(),
628-
dep.package_name()
629-
);
630-
for id in resolve
631-
.iter()
632-
.filter(|id| id.source_id() == dep.source_id())
633-
{
634-
add_deps(resolve, id, &mut avoid_locking);
628+
// Ok if nothing matches, then we poison the source of these
629+
// dependencies and the previous lock file.
630+
debug!(
631+
"poisoning {} because {} looks like it changed {}",
632+
dep.source_id(),
633+
member.package_id(),
634+
dep.package_name()
635+
);
636+
for id in resolve
637+
.iter()
638+
.filter(|id| id.source_id() == dep.source_id())
639+
{
640+
add_deps(resolve, id, &mut avoid_locking);
641+
}
635642
}
636643
}
637644
}
@@ -661,28 +668,31 @@ fn register_previous_locks(
661668
let keep = |id: &PackageId| keep(id) && !avoid_locking.contains(id);
662669

663670
registry.clear_lock();
664-
for node in resolve.iter().filter(keep) {
665-
let deps = resolve
666-
.deps_not_replaced(node)
667-
.map(|p| p.0)
668-
.filter(keep)
669-
.collect::<Vec<_>>();
670-
671-
// In the v2 lockfile format and prior the `branch=master` dependency
672-
// directive was serialized the same way as the no-branch-listed
673-
// directive. Nowadays in Cargo, however, these two directives are
674-
// considered distinct and are no longer represented the same way. To
675-
// maintain compatibility with older lock files we register locked nodes
676-
// for *both* the master branch and the default branch.
677-
//
678-
// Note that this is only applicable for loading older resolves now at
679-
// this point. All new lock files are encoded as v3-or-later, so this is
680-
// just compat for loading an old lock file successfully.
681-
if let Some(node) = master_branch_git_source(node, resolve) {
682-
registry.register_lock(node, deps.clone());
683-
}
671+
{
672+
let _span = tracing::span!(tracing::Level::TRACE, "register_lock").entered();
673+
for node in resolve.iter().filter(keep) {
674+
let deps = resolve
675+
.deps_not_replaced(node)
676+
.map(|p| p.0)
677+
.filter(keep)
678+
.collect::<Vec<_>>();
679+
680+
// In the v2 lockfile format and prior the `branch=master` dependency
681+
// directive was serialized the same way as the no-branch-listed
682+
// directive. Nowadays in Cargo, however, these two directives are
683+
// considered distinct and are no longer represented the same way. To
684+
// maintain compatibility with older lock files we register locked nodes
685+
// for *both* the master branch and the default branch.
686+
//
687+
// Note that this is only applicable for loading older resolves now at
688+
// this point. All new lock files are encoded as v3-or-later, so this is
689+
// just compat for loading an old lock file successfully.
690+
if let Some(node) = master_branch_git_source(node, resolve) {
691+
registry.register_lock(node, deps.clone());
692+
}
684693

685-
registry.register_lock(node, deps);
694+
registry.register_lock(node, deps);
695+
}
686696
}
687697

688698
/// Recursively add `node` and all its transitive dependencies to `set`.

src/cargo/sources/git/utils.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ impl GitRemote {
164164

165165
impl GitDatabase {
166166
/// Checkouts to a revision at `dest`ination from this database.
167+
#[tracing::instrument(skip(self, gctx))]
167168
pub fn copy_to(
168169
&self,
169170
rev: git2::Oid,

0 commit comments

Comments
 (0)