Skip to content
Merged
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
5 changes: 4 additions & 1 deletion Sources/StorageScope/Stores/ScanStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -970,7 +970,10 @@ func setSelectedView(_ view: SmartView) {
// this the progress-consumer sibling would block on `for await`
// forever and the task group would hang waiting for it.
defer { progressContinuation.finish() }
let scanner = FileSystemScanner(hashCache: hashCache)
let scanner = FileSystemScanner(
hashCache: hashCache,
incrementalRescans: true
)
let scan = try scanner.scan(
root: url,
options: options,
Expand Down
28 changes: 21 additions & 7 deletions Sources/StorageScopeBenchmark/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ struct BenchmarkArguments {
/// Fraction in [0, 1] of `items` that become content-identical duplicates. Stored
/// as the raw user-supplied string so we can validate before parsing (e.g. 0.05 = 5%).
var duplicateRatio: Double = 0
var repeatCount = 1
}

func usage() -> String {
"""
Usage:
StorageScopeBenchmark [--show-full-path] <folder>
StorageScopeBenchmark [--show-full-path] [--repeat <n>] <folder>
StorageScopeBenchmark --synthetic [--keep-fixture] [--show-full-path]
StorageScopeBenchmark --synthetic --items <n> [--depth <d>] [--duplicates <0..1>] [--keep-fixture]

Expand All @@ -31,6 +32,7 @@ func usage() -> String {
Examples:
swift run StorageScopeBenchmark --synthetic --items 100000 --depth 8 --duplicates 0.2
swift run StorageScopeBenchmark --synthetic --items 500000 --depth 12 --keep-fixture
STORAGESCOPE_INCREMENTAL_RESCAN=1 swift run -c release StorageScopeBenchmark --repeat 3 <folder>
"""
}

Expand Down Expand Up @@ -73,6 +75,12 @@ func parseArguments(_ rawArguments: [String]) throws -> BenchmarkArguments {
throw BenchmarkError.invalidArgument("--duplicates requires a fraction in [0,1], got \(raw)")
}
arguments.duplicateRatio = parsed
case "--repeat":
let raw = try consumeNextValue(flag: value)
guard let parsed = Int(raw), parsed > 0 else {
throw BenchmarkError.invalidArgument("--repeat requires a positive integer, got \(raw)")
}
arguments.repeatCount = parsed
case "-h", "--help":
print(usage())
exit(0)
Expand Down Expand Up @@ -140,15 +148,21 @@ do {
cleanup?()
}

let report = try ScanBenchmarkRunner().run(
rootURL: rootURL,
showFullPath: arguments.showFullPath
)
print(report.text)
let runner = ScanBenchmarkRunner()
for run in 1...arguments.repeatCount {
if arguments.repeatCount > 1 {
print("Run \(run)/\(arguments.repeatCount)")
}
let report = try runner.run(
rootURL: rootURL,
showFullPath: arguments.showFullPath
)
print(report.text)
}
} catch {
let message = (error as? LocalizedError)?.errorDescription ?? "\(error)"
FileHandle.standardError.write(
Data("StorageScopeBenchmark failed: \(message)\n\n\(usage())\n".utf8)
)
exit(2)
}
}
51 changes: 50 additions & 1 deletion Sources/StorageScopeCore/Models/StorageScan.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ import Foundation
import os.signpost

public struct StorageScan: Sendable {
public enum RescanKind: String, Sendable {
case full
case incrementalUnchanged
case incrementalChanged
}

/// os_signpost surface for Instruments. Shares the scan subsystem/category so the
/// `lookup_build` span shows up alongside FileSystemScanner and ScanStore spans (#81-pattern).
private static let log = OSLog(subsystem: "com.rasputinkaiser.StorageScope", category: "scan")
Expand Down Expand Up @@ -33,6 +39,9 @@ public struct StorageScan: Sendable {
public let enumerateDuration: TimeInterval
public let cleanupCandidates: [CleanupCandidate]
public let isPartial: Bool
public let rescanKind: RescanKind
public let incrementalDirtySubtreeCount: Int
public let incrementalFallbackReason: String?
private let itemLookupByID: [String: StorageItem]

public init(
Expand Down Expand Up @@ -61,7 +70,10 @@ public struct StorageScan: Sendable {
duplicateVerificationBytesRead: Int64 = 0,
enumerateDuration: TimeInterval = 0,
cleanupCandidates: [CleanupCandidate],
isPartial: Bool = false
isPartial: Bool = false,
rescanKind: RescanKind = .full,
incrementalDirtySubtreeCount: Int = 0,
incrementalFallbackReason: String? = nil
) {
self.rootURL = rootURL
self.startedAt = startedAt
Expand Down Expand Up @@ -89,6 +101,9 @@ public struct StorageScan: Sendable {
self.enumerateDuration = enumerateDuration
self.cleanupCandidates = cleanupCandidates
self.isPartial = isPartial
self.rescanKind = rescanKind
self.incrementalDirtySubtreeCount = incrementalDirtySubtreeCount
self.incrementalFallbackReason = incrementalFallbackReason
self.itemLookupByID = Self.buildItemLookup(
retainedItems: retainedItems,
largestFiles: largestFiles,
Expand All @@ -104,6 +119,40 @@ public struct StorageScan: Sendable {
itemLookupByID[id]
}

func reusedIncrementally(startedAt: Date, finishedAt: Date) -> StorageScan {
StorageScan(
rootURL: rootURL,
startedAt: startedAt,
finishedAt: finishedAt,
rootItem: rootItem,
retainedItems: retainedItems,
scannedItemCount: scannedItemCount,
inaccessibleItemCount: inaccessibleItemCount,
totalBytes: totalBytes,
largestFiles: largestFiles,
largestFolders: largestFolders,
oldLargeFiles: oldLargeFiles,
typeBreakdown: typeBreakdown,
categoryBreakdown: categoryBreakdown,
duplicateSizeGroups: duplicateSizeGroups,
verifiedDuplicateGroups: verifiedDuplicateGroups,
duplicateCandidateItemLimit: duplicateCandidateItemLimit,
duplicateCandidateItemsRetained: duplicateCandidateItemsRetained,
duplicateCandidateItemsConsidered: duplicateCandidateItemsConsidered,
duplicateCandidateEvictionCount: duplicateCandidateEvictionCount,
duplicateCandidateLimitReached: duplicateCandidateLimitReached,
snapshotBuildCount: 0,
duplicateVerificationDuration: 0,
duplicateVerificationBytesRead: 0,
enumerateDuration: finishedAt.timeIntervalSince(startedAt),
cleanupCandidates: cleanupCandidates,
isPartial: false,
rescanKind: .incrementalUnchanged,
incrementalDirtySubtreeCount: 0,
incrementalFallbackReason: nil
)
}

private static func buildItemLookup(
retainedItems: [StorageItem],
largestFiles: [StorageItem],
Expand Down
Loading
Loading