11using GVFS . Common . FileSystem ;
22using GVFS . Common . Git ;
3+ using GVFS . Common . Tracing ;
34using System ;
5+ using System . Diagnostics ;
46using System . IO ;
57using System . Linq ;
68using System . Threading ;
@@ -41,35 +43,72 @@ public string ToMessage()
4143
4244 public static EnlistmentHydrationSummary CreateSummary (
4345 GVFSEnlistment enlistment ,
44- PhysicalFileSystem fileSystem ,
46+ PhysicalFileSystem fileSystem ,
47+ ITracer tracer ,
4548 CancellationToken cancellationToken = default )
4649 {
50+ Stopwatch totalStopwatch = Stopwatch . StartNew ( ) ;
51+ Stopwatch phaseStopwatch = new Stopwatch ( ) ;
52+
4753 try
4854 {
4955 /* Getting all the file paths from git index is slow and we only need the total count,
5056 * so we read the index file header instead of calling GetPathsFromGitIndex */
57+ phaseStopwatch . Restart ( ) ;
5158 int totalFileCount = GetIndexFileCount ( enlistment , fileSystem ) ;
59+ long indexReadMs = phaseStopwatch . ElapsedMilliseconds ;
5260
53- /* Getting all the directories is also slow, but not as slow as reading the entire index,
54- * GetTotalPathCount caches the count so this is only slow occasionally,
55- * and the GitStatusCache manager also calls this to ensure it is updated frequently. */
56- cancellationToken . ThrowIfCancellationRequested ( ) ;
57- int totalFolderCount = GetHeadTreeCount ( enlistment , fileSystem ) ;
61+ cancellationToken . ThrowIfCancellationRequested ( ) ;
5862
5963 EnlistmentPathData pathData = new EnlistmentPathData ( ) ;
6064
6165 /* FUTURE: These could be optimized to only deal with counts instead of full path lists */
66+ phaseStopwatch . Restart ( ) ;
6267 pathData . LoadPlaceholdersFromDatabase ( enlistment ) ;
63-
64- cancellationToken . ThrowIfCancellationRequested ( ) ;
65-
66- pathData . LoadModifiedPaths ( enlistment ) ;
68+ long placeholderLoadMs = phaseStopwatch . ElapsedMilliseconds ;
69+
70+ cancellationToken . ThrowIfCancellationRequested ( ) ;
71+
72+ phaseStopwatch . Restart ( ) ;
73+ pathData . LoadModifiedPaths ( enlistment , tracer ) ;
74+ long modifiedPathsLoadMs = phaseStopwatch . ElapsedMilliseconds ;
6775
68-
6976 cancellationToken . ThrowIfCancellationRequested ( ) ;
7077
7178 int hydratedFileCount = pathData . ModifiedFilePaths . Count + pathData . PlaceholderFilePaths . Count ;
7279 int hydratedFolderCount = pathData . ModifiedFolderPaths . Count + pathData . PlaceholderFolderPaths . Count ;
80+
81+ /* Getting the head tree count (used for TotalFolderCount) is potentially slower than the other parts
82+ * of the operation, so we do it last and check that the other parts would succeed before running it.
83+ */
84+ var soFar = new EnlistmentHydrationSummary ( )
85+ {
86+ HydratedFileCount = hydratedFileCount ,
87+ HydratedFolderCount = hydratedFolderCount ,
88+ TotalFileCount = totalFileCount ,
89+ TotalFolderCount = hydratedFolderCount + 1 , // Not calculated yet, use a dummy valid value.
90+ } ;
91+
92+ if ( ! soFar . IsValid )
93+ {
94+ soFar . TotalFolderCount = 0 ; // Set to default invalid value to avoid confusion with the dummy value above.
95+ tracer . RelatedWarning (
96+ $ "Hydration summary early exit: data invalid before tree count. " +
97+ $ "TotalFileCount={ totalFileCount } , HydratedFileCount={ hydratedFileCount } , " +
98+ $ "HydratedFolderCount={ hydratedFolderCount } ") ;
99+ EmitDurationTelemetry ( tracer , totalStopwatch . ElapsedMilliseconds , indexReadMs , placeholderLoadMs , modifiedPathsLoadMs , treeCountMs : 0 , earlyExit : true ) ;
100+ return soFar ;
101+ }
102+
103+ /* Getting all the directories is also slow, but not as slow as reading the entire index,
104+ * GetTotalPathCount caches the count so this is only slow occasionally,
105+ * and the GitStatusCache manager also calls this to ensure it is updated frequently. */
106+ phaseStopwatch . Restart ( ) ;
107+ int totalFolderCount = GetHeadTreeCount ( enlistment , fileSystem , tracer ) ;
108+ long treeCountMs = phaseStopwatch . ElapsedMilliseconds ;
109+
110+ EmitDurationTelemetry ( tracer , totalStopwatch . ElapsedMilliseconds , indexReadMs , placeholderLoadMs , modifiedPathsLoadMs , treeCountMs , earlyExit : false ) ;
111+
73112 return new EnlistmentHydrationSummary ( )
74113 {
75114 HydratedFileCount = hydratedFileCount ,
@@ -90,6 +129,7 @@ public static EnlistmentHydrationSummary CreateSummary(
90129 }
91130 catch ( Exception e )
92131 {
132+ tracer . RelatedError ( $ "Hydration summary failed with exception after { totalStopwatch . ElapsedMilliseconds } ms: { e . Message } ") ;
93133 return new EnlistmentHydrationSummary ( )
94134 {
95135 HydratedFileCount = - 1 ,
@@ -101,6 +141,29 @@ public static EnlistmentHydrationSummary CreateSummary(
101141 }
102142 }
103143
144+ private static void EmitDurationTelemetry (
145+ ITracer tracer ,
146+ long totalMs ,
147+ long indexReadMs ,
148+ long placeholderLoadMs ,
149+ long modifiedPathsLoadMs ,
150+ long treeCountMs ,
151+ bool earlyExit )
152+ {
153+ EventMetadata metadata = new EventMetadata ( ) ;
154+ metadata [ "TotalMs" ] = totalMs ;
155+ metadata [ "IndexReadMs" ] = indexReadMs ;
156+ metadata [ "PlaceholderLoadMs" ] = placeholderLoadMs ;
157+ metadata [ "ModifiedPathsLoadMs" ] = modifiedPathsLoadMs ;
158+ metadata [ "TreeCountMs" ] = treeCountMs ;
159+ metadata [ "EarlyExit" ] = earlyExit ;
160+ tracer . RelatedEvent (
161+ EventLevel . Informational ,
162+ "HydrationSummaryDuration" ,
163+ metadata ,
164+ Keywords . Telemetry ) ;
165+ }
166+
104167 /// <summary>
105168 /// Get the total number of files in the index.
106169 /// </summary>
@@ -142,12 +205,13 @@ internal static int GetIndexFileCount(GVFSEnlistment enlistment, PhysicalFileSys
142205 /// The number of subtrees at HEAD, which may be 0.
143206 /// Will return 0 if unsuccessful.
144207 /// </returns>
145- internal static int GetHeadTreeCount ( GVFSEnlistment enlistment , PhysicalFileSystem fileSystem )
208+ internal static int GetHeadTreeCount ( GVFSEnlistment enlistment , PhysicalFileSystem fileSystem , ITracer tracer )
146209 {
147210 var gitProcess = enlistment . CreateGitProcess ( ) ;
148211 var headResult = gitProcess . GetHeadTreeId ( ) ;
149212 if ( headResult . ExitCodeIsFailure )
150213 {
214+ tracer . RelatedError ( $ "Failed to get HEAD tree ID: \n Output: { headResult . Output } \n \n Error:{ headResult . Errors } ") ;
151215 return 0 ;
152216 }
153217 var headSha = headResult . Output . Trim ( ) ;
@@ -168,8 +232,9 @@ internal static int GetHeadTreeCount(GVFSEnlistment enlistment, PhysicalFileSyst
168232 return cachedCount ;
169233 }
170234 }
171- catch
235+ catch ( Exception ex )
172236 {
237+ tracer . RelatedWarning ( $ "Failed to read tree count cache file at { cacheFile } : { ex } ") ;
173238 // Ignore errors reading the cache
174239 }
175240 }
@@ -180,14 +245,22 @@ internal static int GetHeadTreeCount(GVFSEnlistment enlistment, PhysicalFileSyst
180245 line => totalPathCount ++ ,
181246 recursive : true ,
182247 showDirectories : true ) ;
248+
249+ if ( GitProcess . Result . SuccessCode != folderResult . ExitCode )
250+ {
251+ tracer . RelatedError ( $ "Failed to get tree count from HEAD: \n Output: { folderResult . Output } \n \n Error:{ folderResult . Errors } ") ;
252+ return 0 ;
253+ }
254+
183255 try
184256 {
185257 fileSystem . CreateDirectory ( Path . GetDirectoryName ( cacheFile ) ) ;
186258 fileSystem . WriteAllText ( cacheFile , $ "{ headSha } \n { totalPathCount } ") ;
187259 }
188- catch
260+ catch ( Exception ex )
189261 {
190262 // Ignore errors writing the cache
263+ tracer . RelatedWarning ( $ "Failed to write tree count cache file at { cacheFile } : { ex } ") ;
191264 }
192265
193266 return totalPathCount ;
0 commit comments