Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions GVFS/GVFS.Common/GitStatusCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using GVFS.Common.NamedPipes;
using GVFS.Common.Tracing;
using System;
using System.Collections.Generic;
using System.ComponentModel;

using System.IO;
Expand Down Expand Up @@ -57,6 +58,7 @@ public class GitStatusCache : IDisposable
private readonly Lock hydrationStatisticsLock = new Lock();

private Func<int> projectedFolderCountProvider;
private Func<IEnumerable<string>> modifiedPathsProvider;

private volatile CacheState cacheState = CacheState.Dirty;

Expand Down Expand Up @@ -97,6 +99,12 @@ public void SetProjectedFolderCountProvider(Func<int> provider)
this.projectedFolderCountProvider = provider;
}

public void SetModifiedPathsProvider(Func<IEnumerable<string>> provider)
{
ArgumentNullException.ThrowIfNull(provider);
this.modifiedPathsProvider = provider;
}

public virtual void Initialize()
{
this.isInitialized = true;
Expand Down Expand Up @@ -491,9 +499,10 @@ internal void UpdateHydrationSummary()
EnlistmentHydrationSummary hydrationSummary =
this.CreateHydrationSummary(diagnostics);
NamedPipeMessages.HydrationStatus.CacheState state = hydrationSummary.IsValid
? diagnostics.CountsComplete ? NamedPipeMessages.HydrationStatus.CacheState.Available : NamedPipeMessages.HydrationStatus.CacheState.IncompleteData
? NamedPipeMessages.HydrationStatus.CacheState.Available
: diagnostics.Outcome == HydrationSummaryOutcome.Cancelled ? NamedPipeMessages.HydrationStatus.CacheState.Cancelled
: diagnostics.Outcome == HydrationSummaryOutcome.Failed ? NamedPipeMessages.HydrationStatus.CacheState.Failed
: diagnostics.Outcome == HydrationSummaryOutcome.IncompleteData ? NamedPipeMessages.HydrationStatus.CacheState.IncompleteData
: NamedPipeMessages.HydrationStatus.CacheState.InvalidCounts;
this.hydrationCache = new HydrationCacheSnapshot(hydrationSummary, state, invalidationVersion);
EventMetadata metadata = new EventMetadata();
Expand Down Expand Up @@ -557,7 +566,7 @@ internal virtual HydrationStatusCircuitBreaker CreateHydrationCircuitBreaker()
internal virtual EnlistmentHydrationSummary CreateHydrationSummary(HydrationSummaryDiagnostics diagnostics)
{
return EnlistmentHydrationSummary.CreateSummary(this.context.Enlistment, this.context.FileSystem, this.context.Tracer,
this.projectedFolderCountProvider, this.shutdownTokenSource.Token, diagnostics);
this.projectedFolderCountProvider, new EnlistmentPathData(this.modifiedPathsProvider), this.shutdownTokenSource.Token, diagnostics);
}

private void RecordHydrationSkip(NamedPipeMessages.HydrationStatus.CacheState state)
Expand Down
48 changes: 22 additions & 26 deletions GVFS/GVFS.Common/HealthCalculator/EnlistmentHydrationSummary.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using GVFS.Common.FileSystem;
using GVFS.Common.NamedPipes;
using GVFS.Common.Tracing;
using System;
using System.Diagnostics;
Expand All @@ -18,33 +19,32 @@ public class EnlistmentHydrationSummary
public Exception Error { get; private set; } = null;
public HydrationSummaryDiagnostics Diagnostics { get; private set; }

public int HydratedFileCount => PlaceholderFileCount + ModifiedFileCount;
public int HydratedFolderCount => PlaceholderFolderCount + ModifiedFolderCount;
public long HydratedFileCount => (long)this.PlaceholderFileCount + this.ModifiedFileCount;
public long HydratedFolderCount => (long)this.PlaceholderFolderCount + this.ModifiedFolderCount;

public bool IsValid => this.Diagnostics?.CountsComplete == true && this.ToResponse().IsValid;

public bool IsValid
public string ToMessage()
{
get
if (!this.IsValid)
{
return PlaceholderFileCount >= 0
&& PlaceholderFolderCount >= 0
&& ModifiedFileCount >= 0
&& ModifiedFolderCount >= 0
&& TotalFileCount >= HydratedFileCount
&& TotalFolderCount >= HydratedFolderCount;
return "Error calculating hydration summary. Run 'gvfs health' at the repository root for hydration status details.";
}

return this.ToResponse().ToDisplayMessage();
}

public string ToMessage()
public NamedPipeMessages.HydrationStatus.Response ToResponse()
{
if (!IsValid)
return new NamedPipeMessages.HydrationStatus.Response
{
return "Error calculating hydration summary. Run 'gvfs health' at the repository root for hydration status details.";
}

int fileHydrationPercent = TotalFileCount == 0 ? 0 : (int)((100L * HydratedFileCount) / TotalFileCount);
int folderHydrationPercent = TotalFolderCount == 0 ? 0 : (int)((100L * HydratedFolderCount) / TotalFolderCount);
return $"{fileHydrationPercent}% of files and {folderHydrationPercent}% of folders hydrated. Run 'gvfs health' at the repository root for details.";
PlaceholderFileCount = this.PlaceholderFileCount,
PlaceholderFolderCount = this.PlaceholderFolderCount,
ModifiedFileCount = this.ModifiedFileCount,
ModifiedFolderCount = this.ModifiedFolderCount,
TotalFileCount = this.TotalFileCount,
TotalFolderCount = this.TotalFolderCount,
};
}

public static EnlistmentHydrationSummary CreateSummary(
Expand Down Expand Up @@ -96,14 +96,10 @@ internal static EnlistmentHydrationSummary CreateSummary(
diagnostics.FinishPhase(diagnostics.ModifiedPathsState == HydrationDataState.NotStarted ? HydrationDataState.Complete : diagnostics.ModifiedPathsState);
cancellationToken.ThrowIfCancellationRequested();

// Preserve the early-exit check without reporting its temporary folder total as an observation.
summary.TotalFolderCount = summary.HydratedFolderCount + 1;
if (!summary.IsValid)
if (summary.TotalFileCount < 0)
{
summary.TotalFolderCount = 0;
diagnostics.EarlyExit = true;
diagnostics.Outcome = diagnostics.IndexState == HydrationDataState.Failed
? HydrationSummaryOutcome.IncompleteData : HydrationSummaryOutcome.InvalidCounts;
diagnostics.Outcome = HydrationSummaryOutcome.IncompleteData;
return summary;
}

Expand All @@ -112,8 +108,8 @@ internal static EnlistmentHydrationSummary CreateSummary(
summary.TotalFolderCount = projectedFolderCountProvider();
diagnostics.TotalFolderCount = summary.TotalFolderCount;
diagnostics.FinishPhase(HydrationDataState.Complete);
diagnostics.Outcome = !summary.IsValid || (diagnostics.GetValidationFailures() & ~HydrationValidationFailure.MissingCounts) != HydrationValidationFailure.None ? HydrationSummaryOutcome.InvalidCounts
: diagnostics.CountsComplete ? HydrationSummaryOutcome.Success : HydrationSummaryOutcome.IncompleteData;
diagnostics.Outcome = !diagnostics.CountsComplete ? HydrationSummaryOutcome.IncompleteData
: summary.IsValid ? HydrationSummaryOutcome.Success : HydrationSummaryOutcome.InvalidCounts;
return summary;
}
catch (OperationCanceledException e)
Expand Down
20 changes: 20 additions & 0 deletions GVFS/GVFS.Common/HealthCalculator/EnlistmentPathData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ namespace GVFS.Common
{
public class EnlistmentPathData
{
private readonly Func<IEnumerable<string>> modifiedPathsProvider;

public List<string> GitFolderPaths;
public List<string> GitFilePaths;
public List<string> PlaceholderFolderPaths;
Expand All @@ -21,7 +23,13 @@ public class EnlistmentPathData
public List<string> GitTrackingPaths;

public EnlistmentPathData()
: this(null)
{
}

public EnlistmentPathData(Func<IEnumerable<string>> modifiedPathsProvider)
{
this.modifiedPathsProvider = modifiedPathsProvider;
this.GitFolderPaths = new List<string>();
this.GitFilePaths = new List<string>();
this.PlaceholderFolderPaths = new List<string>();
Expand Down Expand Up @@ -115,6 +123,18 @@ public void LoadPathsFromGitIndex(GVFSEnlistment enlistment)

public virtual void LoadModifiedPaths(GVFSEnlistment enlistment, ITracer tracer, HydrationSummaryDiagnostics diagnostics = null)
{
if (this.modifiedPathsProvider != null)
{
// Match the pipe response's empty-entry filtering.
this.AddModifiedPaths(this.modifiedPathsProvider().Where(path => !string.IsNullOrEmpty(path)));
if (diagnostics != null)
{
diagnostics.ModifiedPathsState = HydrationDataState.Complete;
}

return;
}

if (TryLoadModifiedPathsFromPipe(enlistment, tracer, diagnostics))
{
if (diagnostics != null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,8 @@ public HydrationValidationFailure GetValidationFailures()

public EventMetadata GetMetadata()
{
long? fileCountDelta = (long?)this.PlaceholderFileCount + this.ModifiedFileCount - this.TotalFileCount;
long? folderCountDelta = (long?)this.PlaceholderFolderCount + this.ModifiedFolderCount - this.TotalFolderCount;
return new EventMetadata
{
["SchemaVersion"] = 1,
Expand All @@ -187,6 +189,11 @@ public EventMetadata GetMetadata()
[nameof(this.TotalFolderCount)] = this.TotalFolderCount,
["HydratedFileCount"] = (long?)this.PlaceholderFileCount + this.ModifiedFileCount,
["HydratedFolderCount"] = (long?)this.PlaceholderFolderCount + this.ModifiedFolderCount,
["FileCountSource"] = "IndexHeader",
["FileCountDelta"] = fileCountDelta,
["FolderCountDelta"] = folderCountDelta,
["FileCountExcess"] = fileCountDelta.HasValue ? Math.Max(0, fileCountDelta.Value) : null,
["FolderCountExcess"] = folderCountDelta.HasValue ? Math.Max(0, folderCountDelta.Value) : null,
[nameof(this.IndexState)] = this.IndexState,
[nameof(this.PlaceholderState)] = this.PlaceholderState,
[nameof(this.ModifiedPathsState)] = this.ModifiedPathsState,
Expand Down
47 changes: 38 additions & 9 deletions GVFS/GVFS.Common/NamedPipes/HydrationStatusNamedPipeMessages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public enum ParseOutcome
TooFewFields,
InvalidNumber,
InvalidCounts,
IncompleteData,
}

public sealed class CacheInfo
Expand Down Expand Up @@ -95,16 +96,20 @@ public class Response
public int TotalFileCount { get; set; }
public int TotalFolderCount { get; set; }

public int HydratedFileCount => this.PlaceholderFileCount + this.ModifiedFileCount;
public int HydratedFolderCount => this.PlaceholderFolderCount + this.ModifiedFolderCount;
public long HydratedFileCount => (long)this.PlaceholderFileCount + this.ModifiedFileCount;
public long HydratedFolderCount => (long)this.PlaceholderFolderCount + this.ModifiedFolderCount;
public long FileCountDelta => this.HydratedFileCount - this.TotalFileCount;
public long FolderCountDelta => this.HydratedFolderCount - this.TotalFolderCount;
public long FileCountExcess => Math.Max(0, this.FileCountDelta);
public long FolderCountExcess => Math.Max(0, this.FolderCountDelta);

public bool IsValid =>
this.PlaceholderFileCount >= 0
&& this.PlaceholderFolderCount >= 0
&& this.ModifiedFileCount >= 0
&& this.ModifiedFolderCount >= 0
&& this.TotalFileCount >= this.HydratedFileCount
&& this.TotalFolderCount >= this.HydratedFolderCount;
&& this.TotalFileCount >= 0
&& this.TotalFolderCount >= 0;

public string ToDisplayMessage()
{
Expand All @@ -113,9 +118,9 @@ public string ToDisplayMessage()
return null;
}

int filePercent = this.TotalFileCount == 0 ? 0 : (int)((100L * this.HydratedFileCount) / this.TotalFileCount);
int folderPercent = this.TotalFolderCount == 0 ? 0 : (int)((100L * this.HydratedFolderCount) / this.TotalFolderCount);
return $"{filePercent}% of files and {folderPercent}% of folders hydrated. Run 'gvfs health' at the repository root for details.";
return $"Estimated hydration: files {FormatEstimate(this.HydratedFileCount, this.TotalFileCount)}; "
+ $"folders {FormatEstimate(this.HydratedFolderCount, this.TotalFolderCount)}. "
+ "Run 'gvfs health' at the repository root for details.";
}

public string ToBody(CacheInfo info = null)
Expand Down Expand Up @@ -172,8 +177,32 @@ public static bool TryParse(string body, out Response response, out ParseOutcome
TotalFolderCount = totalFolderCount,
};

outcome = response.IsValid ? ParseOutcome.Success : ParseOutcome.InvalidCounts;
return response.IsValid;
if (!response.IsValid)
{
outcome = ParseOutcome.InvalidCounts;
return false;
}

if (CacheInfo.TryParse(body, out CacheInfo info, hasCounts: true) && !info.CountsComplete)
{
outcome = ParseOutcome.IncompleteData;
return false;
}

outcome = ParseOutcome.Success;
return true;
}

private static string FormatEstimate(long hydratedCount, int totalCount)
{
if (totalCount == 0)
{
return hydratedCount == 0 ? "0%" : "n/a (no current denominator)";
}

return hydratedCount > totalCount
? "100% (counts exceed current total)"
: ((100L * hydratedCount) / totalCount).ToString(CultureInfo.InvariantCulture) + "%";
}
}
}
Expand Down
Loading
Loading