Skip to content

Commit 5a843de

Browse files
committed
initial commit
0 parents  commit 5a843de

File tree

12 files changed

+270
-0
lines changed

12 files changed

+270
-0
lines changed

.github/workflows/build_test.yml

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
name: build and test
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
build:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v2
14+
- name: Set up Go
15+
uses: actions/setup-go@v2
16+
with:
17+
go-version: 1.17
18+
- name: Build
19+
run: go build -v ./...
20+
- name: Test
21+
run: go test -v ./...

.gitignore

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Binaries for programs and plugins
2+
*.exe
3+
*.exe~
4+
*.dll
5+
*.so
6+
*.dylib
7+
8+
# Test binary, built with `go test -c`
9+
*.test
10+
11+
# Output of the go coverage tool, specifically when used with LiteIDE
12+
*.out
13+
14+
# Dependency directories (remove the comment below to include it)
15+
# vendor/

Dockerfile

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
FROM golang:1.17-alpine AS build
2+
3+
WORKDIR /src
4+
5+
# download dependencies separately for caching
6+
COPY go.mod ./
7+
COPY go.sum ./
8+
RUN go mod download
9+
10+
COPY fstat/ ./fstat
11+
COPY main.go ./
12+
13+
RUN CGO_ENABLED=0 GOOS=linux go build -o /bin/diskmon
14+
15+
FROM scratch
16+
17+
COPY --from=build /bin/diskmon /bin/diskmon
18+
19+
ENTRYPOINT ["/bin/diskmon"]

LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2021 teleivo
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Diskmon
2+
3+
Diskmon will notify you if a disk has reached a configurable size limit.
4+
5+
## Get started
6+
7+
### Using Binary
8+
9+
Build the binary or run directly using
10+
11+
```sh
12+
go run main.go -basedir /home
13+
```
14+
15+
### Using Docker
16+
17+
Build the image
18+
19+
```sh
20+
docker build -t dockermon .
21+
```
22+
23+
Run the image
24+
25+
```sh
26+
docker run --volume /home:/home:ro diskmon -basedir /hom
27+
```
28+
29+
## Limitations

TODO

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
* figure out why I get syscall errors on certain directories?
2+
* run check regularly without having to run binary as a cron job. allow
3+
configuration via flags (using cron like syntax?)
4+
* send notification to slack

docker-compose.yml

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
version: "3.9"
3+
4+
services:
5+
diskmon:
6+
image: diskmon
7+
container_name: diskmon
8+
entrypoint:
9+
- "/bin/diskmon"
10+
- "-basedir"
11+
- "/home"
12+
volumes:
13+
- "/home:/home:ro"

fstat/fstat.go

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package fstat
2+
3+
import (
4+
"golang.org/x/sys/unix"
5+
)
6+
7+
type FilesystemStat unix.Statfs_t
8+
9+
func (fs FilesystemStat) Used() uint64 {
10+
return (fs.Blocks - fs.Bavail) * uint64(fs.Bsize)
11+
}
12+
13+
func (fs FilesystemStat) Free() uint64 {
14+
return fs.Bavail * uint64(fs.Bsize)
15+
}
16+
17+
func (fs FilesystemStat) Total() uint64 {
18+
return fs.Blocks * uint64(fs.Bsize)
19+
}
20+
21+
func (fs FilesystemStat) IsExceedingLimit(limit uint64) bool {
22+
return uint64((float64(fs.Used())/float64(fs.Total()))*100) > limit
23+
}
24+
25+
func GetFilesystemStat(path string) (FilesystemStat, error) {
26+
var stat unix.Statfs_t
27+
err := unix.Statfs(path, &stat)
28+
if err != nil {
29+
return FilesystemStat{}, err
30+
}
31+
32+
return FilesystemStat(stat), nil
33+
}

fstat/fstat_test.go

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package fstat
2+
3+
import (
4+
"testing"
5+
6+
"golang.org/x/sys/unix"
7+
)
8+
9+
func TestFilesystemStat(t *testing.T) {
10+
t.Run("IsExceedingLimit", func(t *testing.T) {
11+
// TODO use Bavail or Bfree?
12+
fs := FilesystemStat(unix.Statfs_t{
13+
Bavail: 100,
14+
Blocks: 400,
15+
Bsize: 1024,
16+
})
17+
18+
if want := uint64(100 * 1024); fs.Free() != want {
19+
t.Errorf("FilesystemStat.Free got %d, want %d", fs.Free(), want)
20+
}
21+
if want := uint64(400 * 1024); fs.Total() != want {
22+
t.Errorf("FilesystemStat.Total got %d, want %d", fs.Free(), want)
23+
}
24+
if want := uint64((400 - 100) * 1024); fs.Used() != want {
25+
t.Errorf("FilesystemStat.Used got %d, want %d", fs.Free(), want)
26+
}
27+
28+
if fs.IsExceedingLimit(74) != true {
29+
t.Errorf("IsExceedingLimit(74) should be true")
30+
}
31+
32+
if fs.IsExceedingLimit(75) != false {
33+
t.Errorf("IsExceedingLimit(75) should be false")
34+
}
35+
36+
if fs.IsExceedingLimit(76) != false {
37+
t.Errorf("IsExceedingLimit(76) should be false")
38+
}
39+
})
40+
}

go.mod

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
module github.com/teleivo/diskmon
2+
3+
go 1.17
4+
5+
require (
6+
github.com/dustin/go-humanize v1.0.0
7+
golang.org/x/sys v0.0.0-20210903071746-97244b99971b
8+
)

go.sum

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
2+
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
3+
golang.org/x/sys v0.0.0-20210903071746-97244b99971b h1:3Dq0eVHn0uaQJmPO+/aYPI/fRMqdrVDbu7MQcku54gg=
4+
golang.org/x/sys v0.0.0-20210903071746-97244b99971b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

main.go

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package main
2+
3+
import (
4+
"errors"
5+
"flag"
6+
"fmt"
7+
"io"
8+
"io/ioutil"
9+
"os"
10+
11+
"github.com/dustin/go-humanize"
12+
"github.com/teleivo/diskmon/fstat"
13+
)
14+
15+
func main() {
16+
if err := run(os.Args, os.Stdout); err != nil {
17+
fmt.Fprint(os.Stderr, err, "\n")
18+
os.Exit(1)
19+
}
20+
}
21+
22+
func run(args []string, out io.Writer) error {
23+
24+
flags := flag.NewFlagSet(args[0], flag.ExitOnError)
25+
basedir := flags.String("basedir", "", "statfs syscall information will be printed for each directory (depth 1) in this base directory")
26+
limit := flags.Uint64("limit", 80, "percentage of disk usage at which notification should be sent")
27+
err := flags.Parse(args[1:])
28+
if err != nil {
29+
return err
30+
}
31+
32+
if *basedir == "" {
33+
return errors.New("basedir must be provided")
34+
}
35+
36+
files, err := ioutil.ReadDir(*basedir)
37+
if err != nil {
38+
return fmt.Errorf("error reading basedir: %w", err)
39+
}
40+
41+
for _, file := range files {
42+
if !file.IsDir() {
43+
continue
44+
}
45+
46+
fmt.Printf("file.Name()= %+v\n", file.Name())
47+
fstat, err := fstat.GetFilesystemStat(file.Name())
48+
if err != nil {
49+
// TODO what do to with such errors? also send a notification?
50+
// errors are weird since the directories do exist
51+
// why is statfs_t returning an error like this?
52+
err = fmt.Errorf("error getting filesystem stats from %q: %w", file.Name(), err)
53+
fmt.Fprint(out, err, "\n")
54+
continue
55+
}
56+
57+
if fstat.IsExceedingLimit(*limit) {
58+
fmt.Fprintf(out, "Free/Total %s/%s %q - reached limit of %d%%\n", humanize.Bytes(fstat.Free()), humanize.Bytes(fstat.Total()), file.Name(), *limit)
59+
}
60+
}
61+
62+
return nil
63+
}

0 commit comments

Comments
 (0)