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
26 changes: 16 additions & 10 deletions filestore.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,19 +92,17 @@ func readEntriesFromReader(r io.Reader) ([]PathEntry, error) {

var mu sync.Mutex

// writeFileStore ages the entries if they have grown past the rank threshold,
// then writes them out.
func writeFileStore(entries []PathEntry) {
var cumulativeRank float64
for _, entry := range entries {
cumulativeRank += entry.Rank
}

// writeFileStore ages the entries if the store has grown past the rank
// threshold, then writes them out. priorRank is the total read out of the data
// file: fasd sums the ranks of the lines it reads, before applying any
// rank + 1/rank increment and without counting the paths being added, so the
// caller supplies that rather than the post-update total.
func writeFileStore(entries []PathEntry, priorRank float64) {
// Apply decay if cumulative rank exceeds threshold
const threshold = 2000.0
const decayFactor = 0.9

if cumulativeRank > threshold {
if priorRank > threshold {
logger.Log.Println("Rank threshold met. Decaying...")
for i := range entries {
entries[i].Rank *= decayFactor
Expand Down Expand Up @@ -217,6 +215,14 @@ func AddToStore(paths ...string) {

entries = pruneDecayed(entries)

// Totalled here, after the prune and before any increment, because that is
// what fasd's `count += $2` accumulates: the ranks of the surviving lines
// as they were read, with the paths being added excluded.
var priorRank float64
for _, entry := range entries {
priorRank += entry.Rank
}

now := time.Now().Unix()
seen := make(map[string]bool, len(paths))

Expand Down Expand Up @@ -254,5 +260,5 @@ func AddToStore(paths ...string) {
}

// Write updated entries back to the file
writeFileStore(entries)
writeFileStore(entries, priorRank)
}
80 changes: 80 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -815,3 +815,83 @@ func TestAddToStoreCountsRepeatedPathsOnce(t *testing.T) {
t.Fatalf("Expected a single entry at rank 1, but got %v", entries)
}
}

func TestDecayIgnoresNewlyAddedPaths(t *testing.T) {
cwd, readEntries := setupProcTest(t)

high := filepath.Join(cwd, "high")
first := filepath.Join(cwd, "first")
second := filepath.Join(cwd, "second")
for _, dir := range []string{high, first, second} {
if err := os.MkdirAll(dir, 0700); err != nil {
t.Fatal(err)
}
}
// Just under the threshold. Counting the two new paths would push the
// total to 2001 and age the store, but fasd's `count += $2` only sums the
// lines it read, so 1999 stands and nothing is aged.
writeEntries([]PathEntry{{Path: high, Rank: 1999, LastAccessed: 100}})

AddPaths([]string{first, second})

expected := map[string]float64{high: 1999, first: 1, second: 1}
assertRanks(t, readEntries(), expected)
}

func TestDecayIgnoresTheRankIncrement(t *testing.T) {
cwd, readEntries := setupProcTest(t)

high := filepath.Join(cwd, "high")
if err := os.MkdirAll(high, 0700); err != nil {
t.Fatal(err)
}
// Exactly at the threshold, which is not over it. Re-adding this path
// raises it to 2000.0005, but fasd totals the pre-increment rank, so the
// store is not aged.
writeEntries([]PathEntry{{Path: high, Rank: 2000, LastAccessed: 100}})

AddPaths([]string{high})

assertRanks(t, readEntries(), map[string]float64{high: 2000 + 1.0/2000})
}

func TestDecayIgnoresPrunedEntries(t *testing.T) {
cwd, readEntries := setupProcTest(t)

high := filepath.Join(cwd, "high")
faded := filepath.Join(cwd, "faded")
fresh := filepath.Join(cwd, "fresh")
for _, dir := range []string{high, faded, fresh} {
if err := os.MkdirAll(dir, 0700); err != nil {
t.Fatal(err)
}
}
// fasd accumulates count inside the `$2 >= 1` block, so a line below the
// prune threshold contributes nothing to the decay decision.
writeEntries([]PathEntry{
{Path: high, Rank: 1999.5, LastAccessed: 100},
{Path: faded, Rank: 0.9, LastAccessed: 200},
})

AddPaths([]string{fresh})

expected := map[string]float64{high: 1999.5, fresh: 1}
assertRanks(t, readEntries(), expected)
}

func assertRanks(t *testing.T, entries []PathEntry, expected map[string]float64) {
t.Helper()

if len(entries) != len(expected) {
t.Fatalf("Expected %d entries, but got %v", len(expected), entries)
}
for _, entry := range entries {
want, ok := expected[entry.Path]
if !ok {
t.Fatalf("Unexpected entry %v", entry)
}
if math.Abs(entry.Rank-want) > 1e-6 {
t.Fatalf("Expected %s at rank %v, but got %v", entry.Path, want, entry.Rank)
}
}
}
Loading