Repository: dundee/gdu
Pull request #47 (2021-04-04 10:48:30)

Added the ability to set GOMAXPROCS via the command line flag
Added the ability to set GOMAXPROCS via the command line flag

My first pull-request to someone else's repository

codecov[bot]: # [Codecov](https://codecov.io/gh/dundee/gdu/pull/47?src=pr&el=h1) Report
> Merging [#47](https://codecov.io/gh/dundee/gdu/pull/47?src=pr&el=desc) (e0983fd) into [master](https://codecov.io/gh/dundee/gdu/commit/ee65dc6198241dfc89a8486bfefeb3abb4eecd31?el=desc) (ee65dc6) will **decrease** coverage by `0.20%`.
> The diff coverage is `71.42%`.

[![Impacted file tree graph](https://codecov.io/gh/dundee/gdu/pull/47/graphs/tree.svg?width=650&height=150&src=pr&token=4zcK7hGXWy)](https://codecov.io/gh/dundee/gdu/pull/47?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master      #47      +/-   ##
==========================================
- Coverage   98.20%   98.00%   -0.21%     
==========================================
  Files          13       13              
  Lines         893      900       +7     
==========================================
+ Hits          877      882       +5     
- Misses         12       13       +1     
- Partials        4        5       +1     
```


| [Impacted Files](https://codecov.io/gh/dundee/gdu/pull/47?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [cmd/app/app.go](https://codecov.io/gh/dundee/gdu/pull/47/diff?src=pr&el=tree#diff-Y21kL2FwcC9hcHAuZ28=) | `96.49% <71.42%> (-3.51%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/dundee/gdu/pull/47?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/dundee/gdu/pull/47?src=pr&el=footer). Last update [ee65dc6...e0983fd](https://codecov.io/gh/dundee/gdu/pull/47?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

dundee: Please add test. Nice contribution otherwise!

Patch:
diff --git a/.gitignore b/.gitignore
index 1a99efb..2aad036 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,6 @@
 /.vscode
+/.idea
 /coverage.txt
 /dist
-/test_dir
\ No newline at end of file
+/test_dir
+
diff --git a/cmd/app/app.go b/cmd/app/app.go
index 60d725e..d3b6c40 100644
--- a/cmd/app/app.go
+++ b/cmd/app/app.go
@@ -5,6 +5,8 @@ import (
 	"io"
 	"log"
 	"os"
+	"runtime"
+	"strconv"
 
 	"github.com/dundee/gdu/v4/build"
 	"github.com/dundee/gdu/v4/common"
@@ -19,6 +21,7 @@ import (
 type Flags struct {
 	LogFile          string
 	IgnoreDirs       []string
+	MaxCores         int
 	ShowDisks        bool
 	ShowApparentSize bool
 	ShowVersion      bool
@@ -40,6 +43,8 @@ type App struct {
 
 // Run starts gdu main logic
 func (a *App) Run() error {
+	a.setMaxProcs()
+
 	if a.Flags.ShowVersion {
 		fmt.Fprintln(a.Writer, "Version:\t", build.Version)
 		fmt.Fprintln(a.Writer, "Built time:\t", build.Time)
@@ -77,6 +82,17 @@ func (a *App) Run() error {
 	return ui.StartUILoop()
 }
 
+func (a *App) setMaxProcs() {
+	if a.Flags.MaxCores < 1 || a.Flags.MaxCores > runtime.NumCPU() {
+		return
+	}
+
+	runtime.GOMAXPROCS(a.Flags.MaxCores)
+
+	// runtime.GOMAXPROCS(n) with n < 1 doesn't change current setting so we use it to check current value
+	fmt.Fprintln(a.Writer, "Max cores set to "+strconv.Itoa(runtime.GOMAXPROCS(0)))
+}
+
 func (a *App) createUI() common.UI {
 	var ui common.UI
 
diff --git a/cmd/root.go b/cmd/root.go
index 028361c..774fdb5 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"os"
 	"runtime"
+	"strconv"
 
 	"github.com/dundee/gdu/v4/cmd/app"
 	"github.com/dundee/gdu/v4/device"
@@ -33,6 +34,7 @@ func init() {
 	flags := rootCmd.Flags()
 	flags.StringVarP(&af.LogFile, "log-file", "l", "/dev/null", "Path to a logfile")
 	flags.StringSliceVarP(&af.IgnoreDirs, "ignore-dirs", "i", []string{"/proc", "/dev", "/sys", "/run"}, "Absolute paths to ignore (separated by comma)")
+	flags.IntVarP(&af.MaxCores, "maxcores", "m", runtime.NumCPU(), "Set max cores that GDU will use. " + strconv.Itoa(runtime.NumCPU()) + " cores available")
 	flags.BoolVarP(&af.ShowDisks, "show-disks", "d", false, "Show all mounted disks")
 	flags.BoolVarP(&af.ShowApparentSize, "show-apparent-size", "a", false, "Show apparent size")
 	flags.BoolVarP(&af.ShowVersion, "version", "v", false, "Print version")
diff --git a/device/dev_freebsd.go b/device/dev_freebsd.go
new file mode 100644
index 0000000..adceefe
--- /dev/null
+++ b/device/dev_freebsd.go
@@ -0,0 +1,93 @@
+package device
+
+import (
+	"bufio"
+	"bytes"
+	"errors"
+	"io"
+	"os/exec"
+	"strings"
+	"syscall"
+)
+
+// FreeBSDDevicesInfoGetter returns info for FreeBSD devices
+type FreeBSDDevicesInfoGetter struct {
+	MountCmd string
+}
+
+// Getter is current instance of DevicesInfoGetter
+var Getter DevicesInfoGetter = FreeBSDDevicesInfoGetter{MountCmd: "/sbin/mount"}
+
+// GetMounts returns all mounted filesystems from /proc/mounts
+func (t FreeBSDDevicesInfoGetter) GetMounts() (Devices, error) {
+	out, err := exec.Command(t.MountCmd).Output()
+	if err != nil {
+		return nil, err
+	}
+
+	rdr := bytes.NewReader(out)
+
+	return readMountOutput(rdr)
+}
+
+// GetDevicesInfo returns result of GetMounts with usage info about mounted devices (by calling Statfs syscall)
+func (t FreeBSDDevicesInfoGetter) GetDevicesInfo() (Devices, error) {
+	mounts, err := t.GetMounts()
+	if err != nil {
+		return nil, err
+	}
+
+	return processMounts(mounts)
+}
+
+func readMountOutput(rdr io.Reader) (Devices, error) {
+	mounts := Devices{}
+
+	scanner := bufio.NewScanner(rdr)
+	for scanner.Scan() {
+		line := scanner.Text()
+
+		parts := strings.Fields(line)
+
+		if len(parts) < 4 {
+			return nil, errors.New("Cannot parse mount output")
+		}
+
+		if len(parts[3]) < 3 {
+			return nil, errors.New("Cannot parse mount output")
+		}
+
+		fstype := parts[3][1:len(parts[3])-1]
+
+		device := &Device{
+			Name:       parts[0],
+			MountPoint: parts[2],
+			Fstype:     fstype,
+		}
+		mounts = append(mounts, device)
+	}
+
+	if err := scanner.Err(); err != nil {
+		return nil, err
+	}
+
+	return mounts, nil
+}
+
+func processMounts(mounts Devices) (Devices, error) {
+	devices := Devices{}
+
+	for _, mount := range mounts {
+		if strings.HasPrefix(mount.Name, "/dev") || mount.Fstype == "zfs" {
+			info := &syscall.Statfs_t{}
+			syscall.Statfs(mount.MountPoint, info)
+
+			mount.Size = int64(info.Bsize) * int64(info.Blocks)
+			mount.Free = int64(info.Bsize) * int64(info.Bavail)
+
+			devices = append(devices, mount)
+		}
+	}
+
+	return devices, nil
+}
diff --git a/device/dev_other.go b/device/dev_other.go
index ad0d1f8..055a3a0 100644
--- a/device/dev_other.go
+++ b/device/dev_other.go
@@ -1,4 +1,4 @@
-// +build windows darwin openbsd freebsd netbsd plan9
+// +build windows darwin openbsd netbsd plan9
 
 package device
 


Test patch:
diff --git a/cmd/app/app_test.go b/cmd/app/app_test.go
index fdd8e33..6f1d50c 100644
--- a/cmd/app/app_test.go
+++ b/cmd/app/app_test.go
@@ -2,6 +2,7 @@ package app
 
 import (
 	"bytes"
+	"runtime"
 	"strings"
 	"testing"
 
@@ -155,6 +156,45 @@ func TestListDevicesWithGui(t *testing.T) {
 	assert.Empty(t, out)
 }
 
+func TestMaxCores(t *testing.T) {
+	out, err := runApp(
+		&Flags{LogFile: "/dev/null", MaxCores: 1},
+		[]string{},
+		true,
+		testdev.DevicesInfoGetterMock{},
+	)
+
+	assert.Equal(t, 1, runtime.GOMAXPROCS(0))
+	assert.Contains(t, out, "set to 1")
+	assert.Nil(t, err)
+}
+
+func TestMaxCoresHighEdge(t *testing.T) {
+	out, err := runApp(
+		&Flags{LogFile: "/dev/null", MaxCores: runtime.NumCPU() + 1},
+		[]string{},
+		true,
+		testdev.DevicesInfoGetterMock{},
+	)
+
+	assert.NotEqual(t, runtime.NumCPU(), runtime.GOMAXPROCS(0))
+	assert.Empty(t, out)
+	assert.Nil(t, err)
+}
+
+func TestMaxCoresLowEdge(t *testing.T) {
+	out, err := runApp(
+		&Flags{LogFile: "/dev/null", MaxCores: -100},
+		[]string{},
+		true,
+		testdev.DevicesInfoGetterMock{},
+	)
+
+	assert.NotEqual(t, runtime.NumCPU(), runtime.GOMAXPROCS(0))
+	assert.Empty(t, out)
+	assert.Nil(t, err)
+}
+
 func runApp(flags *Flags, args []string, istty bool, getter device.DevicesInfoGetter) (string, error) {
 	buff := bytes.NewBufferString("")
 
diff --git a/device/dev_freebsd_test.go b/device/dev_freebsd_test.go
new file mode 100644
index 0000000..b6c3492
--- /dev/null
+++ b/device/dev_freebsd_test.go
@@ -0,0 +1,45 @@
+// +build freebsd
+
+package device
+
+import (
+	"strings"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestGetDevicesInfo(t *testing.T) {
+	getter := FreeBSDDevicesInfoGetter{MountCmd: "/sbin/mount"}
+	devices, _ := getter.GetDevicesInfo()
+	assert.IsType(t, Devices{}, devices)
+}
+
+func TestGetDevicesInfoFail(t *testing.T) {
+	getter := FreeBSDDevicesInfoGetter{MountCmd: "/nonexistent"}
+	_, err := getter.GetDevicesInfo()
+	assert.Equal(t, "fork/exec /nonexistent: no such file or directory", err.Error())
+}
+
+func TestZfsMountsShown(t *testing.T) {
+	mounts, _ := readMountOutput(strings.NewReader(`/dev/ada0p2 on / (ufs, local, soft-updates)
+devfs on /dev (devfs)
+tmpfs on /tmp (tmpfs, local)
+fdescfs on /dev/fd (fdescfs)
+procfs on /proc (procfs, local)
+t on /t (zfs, local, nfsv4acls)
+t/db on /t/db (zfs, local, nfsv4acls)
+t/vm on /t/vm (zfs, local, nfsv4acls)
+t/log/pflog on /var/log/pflog (zfs, local, nfsv4acls)
+t/log on /t/log (zfs, local, nfsv4acls)
+devfs on /compat/linux/dev (devfs)
+fdescfs on /compat/linux/dev/fd (fdescfs)
+tmpfs on /compat/linux/dev/shm (tmpfs, local)
+map -hosts on /net (autofs)
+argon:/usr/src on /usr/src (nfs)
+argon:/usr/obj on /usr/obj (nfs)`))
+
+	devices, err := processMounts(mounts)
+	assert.Len(t, devices, 6)
+	assert.Nil(t, err)
+}
diff --git a/device/dev_linux_test.go b/device/dev_linux_test.go
new file mode 100644
index 0000000..d955e84
--- /dev/null
+++ b/device/dev_linux_test.go
@@ -0,0 +1,47 @@
+// +build linux
+
+package device
+
+import (
+	"strings"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestGetDevicesInfo(t *testing.T) {
+	getter := LinuxDevicesInfoGetter{MountsPath: "/proc/mounts"}
+	devices, _ := getter.GetDevicesInfo()
+	assert.IsType(t, Devices{}, devices)
+}
+
+func TestGetDevicesInfoFail(t *testing.T) {
+	getter := LinuxDevicesInfoGetter{MountsPath: "/xxxyyy"}
+	_, err := getter.GetDevicesInfo()
+	assert.Equal(t, "open /xxxyyy: no such file or directory", err.Error())
+}
+
+func TestSnapMountsNotShown(t *testing.T) {
+	mounts, _ := readMountsFile(strings.NewReader(`/dev/loop4 /var/lib/snapd/snap/core18/1944 squashfs ro,nodev,relatime 0 0
+/dev/loop3 /var/lib/snapd/snap/core20/904 squashfs ro,nodev,relatime 0 0
+/dev/nvme0n1p1 /boot vfat rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=ascii,shortname=mixed,utf8,errors=remount-ro 0 0`))
+
+	devices, err := processMounts(mounts)
+	assert.Len(t, devices, 1)
+	assert.Nil(t, err)
+}
+
+func TestZfsMountsShown(t *testing.T) {
+	mounts, _ := readMountsFile(strings.NewReader(`rootpool/opt /opt zfs rw,nodev,relatime,xattr,posixacl 0 0
+rootpool/usr/local /usr/local zfs rw,nodev,relatime,xattr,posixacl 0 0
+rootpool/home/root /root zfs rw,nodev,relatime,xattr,posixacl 0 0
+rootpool/usr/games /usr/games zfs rw,nodev,relatime,xattr,posixacl 0 0
+rootpool/home /home zfs rw,nodev,relatime,xattr,posixacl 0 0
+/dev/loop4 /var/lib/snapd/snap/core18/1944 squashfs ro,nodev,relatime 0 0
+/dev/loop3 /var/lib/snapd/snap/core20/904 squashfs ro,nodev,relatime 0 0
+/dev/nvme0n1p1 /boot vfat rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=ascii,shortname=mixed,utf8,errors=remount-ro 0 0`))
+
+	devices, err := processMounts(mounts)
+	assert.Len(t, devices, 6)
+	assert.Nil(t, err)
+}
diff --git a/device/dev_test.go b/device/dev_test.go
index 0a35fbb..9138b62 100644
--- a/device/dev_test.go
+++ b/device/dev_test.go
@@ -1,49 +1,11 @@
 package device
 
 import (
-	"strings"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
 )
 
-func TestGetDevicesInfo(t *testing.T) {
-	getter := LinuxDevicesInfoGetter{MountsPath: "/proc/mounts"}
-	devices, _ := getter.GetDevicesInfo()
-	assert.IsType(t, Devices{}, devices)
-}
-
-func TestGetDevicesInfoFail(t *testing.T) {
-	getter := LinuxDevicesInfoGetter{MountsPath: "/xxxyyy"}
-	_, err := getter.GetDevicesInfo()
-	assert.Equal(t, "open /xxxyyy: no such file or directory", err.Error())
-}
-
-func TestSnapMountsNotShown(t *testing.T) {
-	mounts, _ := readMountsFile(strings.NewReader(`/dev/loop4 /var/lib/snapd/snap/core18/1944 squashfs ro,nodev,relatime 0 0
-/dev/loop3 /var/lib/snapd/snap/core20/904 squashfs ro,nodev,relatime 0 0
-/dev/nvme0n1p1 /boot vfat rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=ascii,shortname=mixed,utf8,errors=remount-ro 0 0`))
-
-	devices, err := processMounts(mounts)
-	assert.Len(t, devices, 1)
-	assert.Nil(t, err)
-}
-
-func TestZfsMountsShown(t *testing.T) {
-	mounts, _ := readMountsFile(strings.NewReader(`rootpool/opt /opt zfs rw,nodev,relatime,xattr,posixacl 0 0
-rootpool/usr/local /usr/local zfs rw,nodev,relatime,xattr,posixacl 0 0
-rootpool/home/root /root zfs rw,nodev,relatime,xattr,posixacl 0 0
-rootpool/usr/games /usr/games zfs rw,nodev,relatime,xattr,posixacl 0 0
-rootpool/home /home zfs rw,nodev,relatime,xattr,posixacl 0 0
-/dev/loop4 /var/lib/snapd/snap/core18/1944 squashfs ro,nodev,relatime 0 0
-/dev/loop3 /var/lib/snapd/snap/core20/904 squashfs ro,nodev,relatime 0 0
-/dev/nvme0n1p1 /boot vfat rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=ascii,shortname=mixed,utf8,errors=remount-ro 0 0`))
-
-	devices, err := processMounts(mounts)
-	assert.Len(t, devices, 6)
-	assert.Nil(t, err)
-}
-
 func TestNested(t *testing.T) {
 	item := &Device{
 		MountPoint: "/xxx",