Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: show top largest files #391

Merged
merged 3 commits into from
Nov 30, 2024
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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ Flags:
--si Show sizes with decimal SI prefixes (kB, MB, GB) instead of binary prefixes (KiB, MiB, GiB)
--storage-path string Path to persistent key-value storage directory (default "/tmp/badger")
-s, --summarize Show only a total in non-interactive mode
-t, --top int Show only top X largest files in non-interactive mode
--use-storage Use persistent key-value storage for analysis data (experimental)
-v, --version Print version
--write-config Write current configuration to file (default is $HOME/.gdu.yaml)
Expand Down Expand Up @@ -104,6 +105,7 @@ Basic list of actions in interactive mode (show help modal for more):
gdu -n / # only print stats, do not start interactive mode
gdu -np / # do not show progress, useful when using its output in a script
gdu -nps /some/dir # show only total usage for given dir
gdu -nt 10 / # show top 10 largest files
gdu / > file # write stats to file, do not start interactive mode

gdu -o- / | gzip -c >report.json.gz # write all info to JSON file for later analysis
Expand Down
2 changes: 2 additions & 0 deletions cmd/gdu/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ type Flags struct {
StoragePath string `yaml:"storage-path"`
ReadFromStorage bool `yaml:"read-from-storage"`
Summarize bool `yaml:"summarize"`
Top int `yaml:"top"`
UseSIPrefix bool `yaml:"use-si-prefix"`
NoPrefix bool `yaml:"no-prefix"`
WriteConfig bool `yaml:"-"`
Expand Down Expand Up @@ -242,6 +243,7 @@ func (a *App) createUI() (UI, error) {
a.Flags.ConstGC,
a.Flags.UseSIPrefix,
a.Flags.NoPrefix,
a.Flags.Top,
)
if a.Flags.NoUnicode {
stdoutUI.UseOldProgressRunes()
Expand Down
1 change: 1 addition & 0 deletions cmd/gdu/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ func init() {
flags.BoolVarP(&af.NoProgress, "no-progress", "p", false, "Do not show progress in non-interactive mode")
flags.BoolVarP(&af.NoUnicode, "no-unicode", "u", false, "Do not use Unicode symbols (for size bar)")
flags.BoolVarP(&af.Summarize, "summarize", "s", false, "Show only a total in non-interactive mode")
flags.IntVarP(&af.Top, "top", "t", 0, "Show only top X largest files in non-interactive mode")
flags.BoolVar(&af.UseSIPrefix, "si", false, "Show sizes with decimal SI prefixes (kB, MB, GB) instead of binary prefixes (KiB, MiB, GiB)")
flags.BoolVar(&af.NoPrefix, "no-prefix", false, "Show sizes as raw numbers without any prefixes (SI or binary) in non-interactive mode")
flags.BoolVar(&af.NoMouse, "no-mouse", false, "Do not use mouse")
Expand Down
2 changes: 2 additions & 0 deletions gdu.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ non-interactive mode

**-s**, **\--summarize**\[=false\] Show only a total in non-interactive mode

**-t**, **\--top**\[=0\] Show only top X largest files in non-interactive mode

**-d**, **\--show-disks**\[=false\] Show all mounted disks

**-a**, **\--show-apparent-size**\[=false\] Show apparent size
Expand Down
53 changes: 53 additions & 0 deletions pkg/analyze/top.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package analyze

import (
"sort"

"github.com/dundee/gdu/v5/pkg/fs"
)

// TopList is a list of top largest files
type TopList struct {
Count int
Items fs.Files
MinSize int64
}

// NewTopList creates new TopList
func NewTopList(count int) *TopList {
return &TopList{Count: count}
}

// Add adds file to the list
func (tl *TopList) Add(file fs.Item) {
if len(tl.Items) < tl.Count {
tl.Items = append(tl.Items, file)
if file.GetSize() > tl.MinSize {
tl.MinSize = file.GetSize()
}
} else if file.GetSize() > tl.MinSize {
tl.Items = append(tl.Items, file)
tl.MinSize = file.GetSize()
if len(tl.Items) > tl.Count {
sort.Sort(fs.ByApparentSize(tl.Items))
tl.Items = tl.Items[1:]
}
}
}

func CollectTopFiles(dir fs.Item, count int) fs.Files {
topList := NewTopList(count)
walkDir(dir, topList)
sort.Sort(sort.Reverse(fs.ByApparentSize(topList.Items)))
return topList.Items
}

func walkDir(dir fs.Item, topList *TopList) {
for _, item := range dir.GetFiles() {
if item.IsDir() {
walkDir(item, topList)
} else {
topList.Add(item)
}
}
}
38 changes: 38 additions & 0 deletions pkg/analyze/top_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package analyze

import (
"testing"

"github.com/dundee/gdu/v5/internal/testdir"
"github.com/stretchr/testify/assert"
)

func TestCollectTopFiles2(t *testing.T) {
fin := testdir.CreateTestDir()
defer fin()

dir := CreateAnalyzer().AnalyzeDir(
"test_dir", func(_, _ string) bool { return false }, false,
)

topFiles := CollectTopFiles(dir, 2)
assert.Equal(t, 2, len(topFiles))
assert.Equal(t, "file", topFiles[0].GetName())
assert.Equal(t, int64(5), topFiles[0].GetSize())
assert.Equal(t, "file2", topFiles[1].GetName())
assert.Equal(t, int64(2), topFiles[1].GetSize())
}

func TestCollectTopFiles1(t *testing.T) {
fin := testdir.CreateTestDir()
defer fin()

dir := CreateAnalyzer().AnalyzeDir(
"test_dir", func(_, _ string) bool { return false }, false,
)

topFiles := CollectTopFiles(dir, 1)
assert.Equal(t, 1, len(topFiles))
assert.Equal(t, "file", topFiles[0].GetName())
assert.Equal(t, int64(5), topFiles[0].GetSize())
}
45 changes: 41 additions & 4 deletions stdout/stdout.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
blue *color.Color
summarize bool
noPrefix bool
top int
}

var (
Expand All @@ -45,6 +46,7 @@
constGC bool,
useSIPrefix bool,
noPrefix bool,
top int,
) *UI {
ui := &UI{
UI: &common.UI{
Expand All @@ -59,6 +61,7 @@
output: output,
summarize: summarize,
noPrefix: noPrefix,
top: top,
}

ui.red = color.New(color.FgRed).Add(color.Bold)
Expand Down Expand Up @@ -167,9 +170,12 @@

wait.Wait()

if ui.summarize {
switch {
case ui.top > 0:
ui.printTopFiles(dir)
case ui.summarize:
ui.printTotalItem(dir)
} else {
default:
ui.showDir(dir)
}

Expand All @@ -187,9 +193,12 @@
return err
}

if ui.summarize {
switch {
case ui.top > 0:
ui.printTopFiles(dir)
case ui.summarize:

Check warning on line 199 in stdout/stdout.go

View check run for this annotation

Codecov / codecov/patch

stdout/stdout.go#L196-L199

Added lines #L196 - L199 were not covered by tests
ui.printTotalItem(dir)
} else {
default:

Check warning on line 201 in stdout/stdout.go

View check run for this annotation

Codecov / codecov/patch

stdout/stdout.go#L201

Added line #L201 was not covered by tests
ui.showDir(dir)
}
return nil
Expand All @@ -203,6 +212,13 @@
}
}

func (ui *UI) printTopFiles(file fs.Item) {
collected := analyze.CollectTopFiles(file, ui.top)
for _, file := range collected {
ui.printItemPath(file)
}
}

func (ui *UI) printTotalItem(file fs.Item) {
var lineFormat string
if ui.UseColors {
Expand Down Expand Up @@ -256,6 +272,27 @@
}
}

func (ui *UI) printItemPath(file fs.Item) {
var lineFormat string
if ui.UseColors {
lineFormat = "%20s %s\n"
} else {
lineFormat = "%9s %s\n"
}

var size int64
if ui.ShowApparentSize {
size = file.GetSize()
} else {
size = file.GetUsage()
}

fmt.Fprintf(ui.output,
lineFormat,
ui.formatSize(size),
file.GetPath())
}

// ReadAnalysis reads analysis report from JSON file
func (ui *UI) ReadAnalysis(input io.Reader) error {
var (
Expand Down
2 changes: 1 addition & 1 deletion stdout/stdout_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func TestShowDevicesWithErr(t *testing.T) {
output := bytes.NewBuffer(make([]byte, 10))

getter := device.LinuxDevicesInfoGetter{MountsPath: "/xyzxyz"}
ui := CreateStdoutUI(output, false, true, false, false, false, false, false, false)
ui := CreateStdoutUI(output, false, true, false, false, false, false, false, false, 0)
err := ui.ListDevices(getter)

assert.Contains(t, err.Error(), "no such file")
Expand Down
Loading
Loading