Skip to content

Commit

Permalink
Merge branch 'export-report'
Browse files Browse the repository at this point in the history
  • Loading branch information
dundee committed Jul 20, 2021
2 parents f9a1ac3 + c8331e9 commit acc1c82
Show file tree
Hide file tree
Showing 22 changed files with 1,052 additions and 54 deletions.
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,15 @@ Flags:
-h, --help help for gdu
-i, --ignore-dirs strings Absolute paths to ignore (separated by comma) (default [/proc,/dev,/sys,/run])
-I, --ignore-dirs-pattern strings Absolute path patterns to ignore (separated by comma)
-f, --input-file string Import analysis from JSON file
-l, --log-file string Path to a logfile (default "/dev/null")
-m, --max-cores int Set max cores that GDU will use. 8 cores available (default 8)
-c, --no-color Do not use colorized output
-x, --no-cross Do not cross filesystem boundaries
-H, --no-hidden Ignore hidden directories (beggining with dot)
-H, --no-hidden Ignore hidden directories (beginning with dot)
-p, --no-progress Do not show progress in non-interactive mode
-n, --non-interactive Do not run in interactive mode
-o, --output-file string Export all info into file as JSON
-a, --show-apparent-size Show apparent size
-d, --show-disks Show all mounted disks
-v, --version Print version
Expand All @@ -103,10 +105,15 @@ Flags:
gdu -np / # do not show progress, useful when using its output in a script
gdu / > file # write stats to file, do not start interactive mode

Gdu has two modes: interactive (default) and non-interactive.
gdu -o- / | gzip >report.json.gz # write all info to JSON file for later analysis
zcat report.json.gz | gdu -f- # read analysis from file

Gdu has three modes: interactive (default), non-interactive and export.

Non-interactive mode is started automtically when TTY is not detected (using [go-isatty](https://github.com/mattn/go-isatty)), for example if the output is being piped to a file, or it can be started explicitly by using a flag.

Export mode (flag `-o`) outputs all usage data as JSON, which can then be later opened using the `-f` flag.

Hard links are counted only once.

## File flags
Expand Down
47 changes: 44 additions & 3 deletions cmd/gdu/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/dundee/gdu/v5/internal/common"
"github.com/dundee/gdu/v5/pkg/analyze"
"github.com/dundee/gdu/v5/pkg/device"
"github.com/dundee/gdu/v5/report"
"github.com/dundee/gdu/v5/stdout"
"github.com/dundee/gdu/v5/tui"
"github.com/gdamore/tcell/v2"
Expand All @@ -22,6 +23,7 @@ import (
type UI interface {
ListDevices(getter device.DevicesInfoGetter) error
AnalyzePath(path string, parentDir *analyze.Dir) error
ReadAnalysis(input io.Reader) error
SetIgnoreDirPaths(paths []string)
SetIgnoreDirPatterns(paths []string) error
SetIgnoreHidden(value bool)
Expand All @@ -31,6 +33,8 @@ type UI interface {
// Flags define flags accepted by Run
type Flags struct {
LogFile string
InputFile string
OutputFile string
IgnoreDirs []string
IgnoreDirPatterns []string
MaxCores int
Expand Down Expand Up @@ -71,7 +75,10 @@ func (a *App) Run() error {
log.SetOutput(f)

path := a.getPath()
ui := a.createUI()
ui, err := a.createUI()
if err != nil {
return err
}

if err := a.setNoCross(path); err != nil {
return err
Expand Down Expand Up @@ -116,9 +123,28 @@ func (a *App) setMaxProcs() {
log.Printf("Max cores set to %d", runtime.GOMAXPROCS(0))
}

func (a *App) createUI() UI {
func (a *App) createUI() (UI, error) {
var ui UI

if a.Flags.OutputFile != "" {
var output io.Writer
var err error
if a.Flags.OutputFile == "-" {
output = os.Stdout
} else {
output, err = os.OpenFile(a.Flags.OutputFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
return nil, fmt.Errorf("opening output file: %w", err)
}
}
ui = report.CreateExportUI(
a.Writer,
output,
!a.Flags.NoProgress && a.Istty,
)
return ui, nil
}

if a.Flags.NonInteractive || !a.Istty {
ui = stdout.CreateStdoutUI(
a.Writer,
Expand All @@ -134,7 +160,7 @@ func (a *App) createUI() UI {
}
tview.Styles.BorderColor = tcell.ColorDefault
}
return ui
return ui, nil
}

func (a *App) setNoCross(path string) error {
Expand All @@ -154,6 +180,21 @@ func (a *App) runAction(ui UI, path string) error {
if err := ui.ListDevices(a.Getter); err != nil {
return fmt.Errorf("loading mount points: %w", err)
}
} else if a.Flags.InputFile != "" {
var input io.Reader
var err error
if a.Flags.InputFile == "-" {
input = os.Stdin
} else {
input, err = os.OpenFile(a.Flags.InputFile, os.O_RDONLY, 0644)
if err != nil {
return fmt.Errorf("opening input file: %w", err)
}
}

if err := ui.ReadAnalysis(input); err != nil {
return fmt.Errorf("reading analysis: %w", err)
}
} else {
if err := ui.AnalyzePath(path, nil); err != nil {
return fmt.Errorf("scanning dir: %w", err)
Expand Down
12 changes: 12 additions & 0 deletions cmd/gdu/app/app_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,15 @@ func TestLogError(t *testing.T) {
assert.Empty(t, out)
assert.Contains(t, err.Error(), "permission denied")
}

func TestOutputFileError(t *testing.T) {
out, err := runApp(
&Flags{LogFile: "/dev/null", OutputFile: "/xyzxyz"},
[]string{},
false,
testdev.DevicesInfoGetterMock{},
)

assert.Empty(t, out)
assert.Contains(t, err.Error(), "permission denied")
}
74 changes: 74 additions & 0 deletions cmd/gdu/app/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package app

import (
"bytes"
"os"
"runtime"
"strings"
"testing"
Expand Down Expand Up @@ -99,6 +100,61 @@ func TestAnalyzePathWithGui(t *testing.T) {
assert.Nil(t, err)
}

func TestAnalyzePathWithExport(t *testing.T) {
fin := testdir.CreateTestDir()
defer fin()
defer func() {
os.Remove("output.json")
}()

out, err := runApp(
&Flags{LogFile: "/dev/null", OutputFile: "output.json"},
[]string{"test_dir"},
true,
testdev.DevicesInfoGetterMock{},
)

assert.NotEmpty(t, out)
assert.Nil(t, err)
}

func TestReadAnalysisFromFile(t *testing.T) {
out, err := runApp(
&Flags{LogFile: "/dev/null", InputFile: "../../../internal/testdata/test.json"},
[]string{"test_dir"},
false,
testdev.DevicesInfoGetterMock{},
)

assert.NotEmpty(t, out)
assert.Contains(t, out, "main.go")
assert.Nil(t, err)
}

func TestReadWrongAnalysisFromFile(t *testing.T) {
out, err := runApp(
&Flags{LogFile: "/dev/null", InputFile: "../../../internal/testdata/wrong.json"},
[]string{"test_dir"},
false,
testdev.DevicesInfoGetterMock{},
)

assert.Empty(t, out)
assert.Contains(t, err.Error(), "Array of maps not found")
}

func TestReadWrongAnalysisFromNotExistingFile(t *testing.T) {
out, err := runApp(
&Flags{LogFile: "/dev/null", InputFile: "xxx.json"},
[]string{"test_dir"},
false,
testdev.DevicesInfoGetterMock{},
)

assert.Empty(t, out)
assert.Contains(t, err.Error(), "no such file or directory")
}

func TestAnalyzePathWithErr(t *testing.T) {
fin := testdir.CreateTestDir()
defer fin()
Expand Down Expand Up @@ -144,6 +200,24 @@ func TestListDevices(t *testing.T) {
assert.Nil(t, err)
}

func TestListDevicesToFile(t *testing.T) {
fin := testdir.CreateTestDir()
defer fin()
defer func() {
os.Remove("output.json")
}()

out, err := runApp(
&Flags{LogFile: "/dev/null", ShowDisks: true, OutputFile: "output.json"},
[]string{},
false,
testdev.DevicesInfoGetterMock{},
)

assert.Equal(t, "", out)
assert.Contains(t, err.Error(), "not supported")
}

func TestListDevicesWithGui(t *testing.T) {
fin := testdir.CreateTestDir()
defer fin()
Expand Down
4 changes: 3 additions & 1 deletion cmd/gdu/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ func init() {
af = &app.Flags{}
flags := rootCmd.Flags()
flags.StringVarP(&af.LogFile, "log-file", "l", "/dev/null", "Path to a logfile")
flags.StringVarP(&af.OutputFile, "output-file", "o", "", "Export all info into file as JSON")
flags.StringVarP(&af.InputFile, "input-file", "f", "", "Import analysis from JSON file")
flags.IntVarP(&af.MaxCores, "max-cores", "m", runtime.NumCPU(), fmt.Sprintf("Set max cores that GDU will use. %d cores available", runtime.NumCPU()))
flags.BoolVarP(&af.ShowVersion, "version", "v", false, "Print version")

Expand Down Expand Up @@ -61,7 +63,7 @@ func runE(command *cobra.Command, args []string) error {

var termApp *tview.Application

if !af.ShowVersion && !af.NonInteractive && istty {
if !af.ShowVersion && !af.NonInteractive && istty && af.OutputFile == "" {
screen, err := tcell.NewScreen()
if err != nil {
return fmt.Errorf("Error creating screen: %w", err)
Expand Down
9 changes: 8 additions & 1 deletion gdu.1
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.\" Automatically generated by Pandoc 2.13
.\" Automatically generated by Pandoc 2.14.0.2
.\"
.TH "gdu" "1" "Jan 2021" "" ""
.hy
Expand Down Expand Up @@ -47,6 +47,13 @@ interactive mode
.PP
\f[B]-a\f[R], \f[B]--show-apparent-size\f[R][=false] Show apparent size
.PP
\f[B]-f\f[R], \f[B]-\[em]input-file\f[R] Import analysis from JSON file.
If the file is \[dq]-\[dq], read from standard input.
.PP
\f[B]-o\f[R], \f[B]-\[em]output-file\f[R] Export all info into file as
JSON.
If the file is \[dq]-\[dq], write to standard output.
.PP
\f[B]-v\f[R], \f[B]--version\f[R][=false] Print version
.SH FILE FLAGS
.PP
Expand Down
4 changes: 4 additions & 0 deletions gdu.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ non-interactive mode

**-a**, **\--show-apparent-size**\[=false\] Show apparent size

**-f**, **\----input-file** Import analysis from JSON file. If the file is \"-\", read from standard input.

**-o**, **\----output-file** Export all info into file as JSON. If the file is \"-\", write to standard output.

**-v**, **\--version**\[=false\] Print version

# FILE FLAGS
Expand Down
18 changes: 18 additions & 0 deletions internal/common/ui.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,21 @@ type UI struct {
ShowApparentSize bool
PathChecker func(string) (fs.FileInfo, error)
}

// file size constants
const (
_ = iota
KB float64 = 1 << (10 * iota)
MB
GB
TB
PB
EB
)

// file count constants
const (
K int = 1e3
M int = 1e6
G int = 1e9
)
7 changes: 7 additions & 0 deletions internal/testdata/test.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[1,2,{"progname":"gdu","progver":"development","timestamp":1626807263},
[{"name":"/home/gdu"},
[{"name":"app"},
{"name":"app.go","asize":4638,"dsize":8192},
{"name":"app_linux_test.go","asize":1410,"dsize":4096},
{"name":"app_test.go","asize":4974,"dsize":8192}],
{"name":"main.go","asize":3205,"dsize":4096}]]
1 change: 1 addition & 0 deletions internal/testdata/wrong.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[1,2,3,4]
Loading

0 comments on commit acc1c82

Please sign in to comment.