metrics: count symlink/mkdir in symlink forest

Bug: 260029212
Test: inspect out/bp2build_metrics.pb
Change-Id: Ia84c095a8d7b129cca629256b6c626c726cbfab1
diff --git a/bp2build/metrics.go b/bp2build/metrics.go
index bd21629..d6e5cf3 100644
--- a/bp2build/metrics.go
+++ b/bp2build/metrics.go
@@ -148,11 +148,26 @@
 func (metrics *CodegenMetrics) AddEvent(event *bp2build_metrics_proto.Event) {
 	metrics.serialized.Events = append(metrics.serialized.Events, event)
 }
+
 func (metrics *CodegenMetrics) AddUnconvertedModule(moduleType string) {
 	metrics.serialized.UnconvertedModuleCount += 1
 	metrics.serialized.TotalModuleTypeCount[moduleType] += 1
 }
 
+func (metrics *CodegenMetrics) SetSymlinkCount(n uint64) {
+	if m := metrics.serialized.WorkspaceSymlinkCount; m != 0 {
+		fmt.Fprintf(os.Stderr, "unexpected non-zero workspaceSymlinkCount of %d", m)
+	}
+	metrics.serialized.WorkspaceSymlinkCount = n
+}
+
+func (metrics *CodegenMetrics) SetMkDirCount(n uint64) {
+	if m := metrics.serialized.WorkspaceMkDirCount; m != 0 {
+		fmt.Fprintf(os.Stderr, "unexpected non-zero workspaceDirCount of %d", m)
+	}
+	metrics.serialized.WorkspaceMkDirCount = n
+}
+
 func (metrics *CodegenMetrics) TotalModuleCount() uint64 {
 	return metrics.serialized.HandCraftedModuleCount +
 		metrics.serialized.GeneratedModuleCount +
diff --git a/bp2build/symlink_forest.go b/bp2build/symlink_forest.go
index 81ec7ee..4a4ae9e 100644
--- a/bp2build/symlink_forest.go
+++ b/bp2build/symlink_forest.go
@@ -46,9 +46,11 @@
 	topdir  string // $TOPDIR
 
 	// State
-	wg    sync.WaitGroup
-	depCh chan string
-	okay  atomic.Bool // Whether the forest was successfully constructed
+	wg           sync.WaitGroup
+	depCh        chan string
+	mkdirCount   atomic.Uint64
+	symlinkCount atomic.Uint64
+	okay         atomic.Bool // Whether the forest was successfully constructed
 }
 
 // A simple thread pool to limit concurrency on system calls.
@@ -288,6 +290,7 @@
 		fmt.Fprintf(os.Stderr, "Cannot mkdir '%s': %s\n", forestDir, err)
 		os.Exit(1)
 	}
+	context.mkdirCount.Add(1)
 
 	for f := range allEntries {
 		if f[0] == '.' {
@@ -318,6 +321,7 @@
 		if instructionsChild != nil && instructionsChild.excluded {
 			if bExists {
 				symlinkIntoForest(context.topdir, forestChild, buildFilesChild)
+				context.symlinkCount.Add(1)
 			}
 			continue
 		}
@@ -334,6 +338,7 @@
 			} else {
 				// Not in the source tree, symlink BUILD file
 				symlinkIntoForest(context.topdir, forestChild, buildFilesChild)
+				context.symlinkCount.Add(1)
 			}
 		} else if !bExists {
 			if sDir && instructionsChild != nil {
@@ -344,6 +349,7 @@
 			} else {
 				// Not in the build file tree, symlink source tree, carry on
 				symlinkIntoForest(context.topdir, forestChild, srcChild)
+				context.symlinkCount.Add(1)
 			}
 		} else if sDir && bDir {
 			// Both are directories. Descend.
@@ -417,15 +423,17 @@
 	wg.Wait()
 }
 
-// Creates a symlink forest by merging the directory tree at "buildFiles" and
+// PlantSymlinkForest Creates a symlink forest by merging the directory tree at "buildFiles" and
 // "srcDir" while excluding paths listed in "exclude". Returns the set of paths
 // under srcDir on which readdir() had to be called to produce the symlink
 // forest.
-func PlantSymlinkForest(verbose bool, topdir string, forest string, buildFiles string, exclude []string) []string {
+func PlantSymlinkForest(verbose bool, topdir string, forest string, buildFiles string, exclude []string) (deps []string, mkdirCount, symlinkCount uint64) {
 	context := &symlinkForestContext{
-		verbose: verbose,
-		topdir:  topdir,
-		depCh:   make(chan string),
+		verbose:      verbose,
+		topdir:       topdir,
+		depCh:        make(chan string),
+		mkdirCount:   atomic.Uint64{},
+		symlinkCount: atomic.Uint64{},
 	}
 
 	context.okay.Store(true)
@@ -440,7 +448,6 @@
 		close(context.depCh)
 	}()
 
-	deps := make([]string, 0)
 	for dep := range context.depCh {
 		deps = append(deps, dep)
 	}
@@ -449,5 +456,5 @@
 		os.Exit(1)
 	}
 
-	return deps
+	return deps, context.mkdirCount.Load(), context.symlinkCount.Load()
 }