diff --git a/.gitignore b/.gitignore index 9f94ff4..e4bc648 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ bin *debug* tmp -.idea \ No newline at end of file +.idea +*.log \ No newline at end of file diff --git a/.tool-versions b/.tool-versions index cc7ab7e..1346cca 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -go 1.23.5 \ No newline at end of file +golang 1.25.1 \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index a79f32e..bc3118c 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,7 +9,10 @@ "type": "go", "request": "launch", "mode": "auto", - "program": "${workspaceFolder}${pathSeparator}cmd/main.go" + "program": "${workspaceFolder}${pathSeparator}cmd/main.go", + "args": [ + "-v" + ] } ] } \ No newline at end of file diff --git a/Makefile b/Makefile index 07e9759..d2719f8 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,14 @@ build: @go build -C cmd -o ..\bin\app.exe build-nix: + @rm ../bin/app @go build -C cmd -o ../bin/app run: @make build - @".\build\app.exe" + @".\build\app.exe -v" test: @go test ./... dev: + @$$GOPATH/bin/air --build.cmd "make build" --build.bin ".\bin\app.exe" --build.args_bin "-v" --build.exclude_dir "templates,build,bin,.git,node_modules" +dev-nix: @$$GOPATH/bin/air --build.cmd "make build" --build.bin ".\bin\app.exe" --build.exclude_dir "templates,build,bin,.git,node_modules" \ No newline at end of file diff --git a/api/api.go b/api/api.go index ff4b517..2421c69 100644 --- a/api/api.go +++ b/api/api.go @@ -3,11 +3,14 @@ package api import ( "context" "encoding/json" - "github.com/brinestone/dfs/fs" "log/slog" "net" "net/http" + "net/url" "time" + + "github.com/brinestone/dfs/fs" + "github.com/brinestone/dfs/storage" ) type Config struct { @@ -15,43 +18,37 @@ type Config struct { Ctx context.Context Logger *slog.Logger FileServer *fs.FileServer + Store *storage.Store + BasePath string + Verbose bool } -type Server struct { +type Api struct { Config mux *http.ServeMux server *http.Server listener net.Listener } -func (s *Server) Start() (err error) { - l, err := net.Listen("tcp", s.Addr) +func (s *Api) Start() (err error) { + s.listener, err = net.Listen("tcp", s.Addr) if err != nil { return } - s.listener = l - s.server = &http.Server{ - Handler: s.mux, - ReadTimeout: 5 * time.Second, - WriteTimeout: 10 * time.Second, - IdleTimeout: 15 * time.Second, - } - - s.server.RegisterOnShutdown(func() { - s.Logger.Info("Server is shutting down", "addr", s.Addr) - }) - s.Logger.Info("Server started successfully", "addr", s.Addr) - err = s.server.Serve(l) + s.server = &http.Server{Handler: s.mux} + s.Logger.Info("Api started successfully", "addr", s.Addr) + err = s.server.Serve(s.listener) return } -func (s *Server) Stop() error { - return s.server.Shutdown(s.Ctx) +func (s *Api) Stop() error { + s.server.Shutdown(s.Ctx) + return nil } -func NewServer(c Config) (server *Server) { - server = &Server{ +func NewApi(c Config) (server *Api) { + server = &Api{ Config: c, mux: http.NewServeMux(), } @@ -62,19 +59,50 @@ func NewServer(c Config) (server *Server) { return } -func (s *Server) setupHandlers() { +func (s *Api) makeApiPath(path string) string { + pathname, err := url.JoinPath(s.BasePath, path) + if err != nil { + panic(err) + } + return pathname +} + +func (s *Api) makeApiPathWithBase(path string, base string) string { + pathname, err := url.JoinPath(s.BasePath, base, path) + if err != nil { + panic(err) + } + return pathname +} + +func (s *Api) registerHandler(path string, handler http.Handler) { + // pathname := s.makeApiPath(path) + s.mux.Handle(path, handler) + if s.Verbose { + s.Logger.Debug("registered handler", "path", path) + } +} + +func (s *Api) registerLoggedHandler(path string, handler http.Handler) { // Logger middleware logMiddleware := func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - s.Logger.Info("Request received", "method", r.Method, "path", r.URL.Path, "remote", r.RemoteAddr) + start := time.Now() next.ServeHTTP(w, r) + timeTaken := time.Since(start) + s.Logger.Info("Request received", "duration", timeTaken, "method", r.Method, "path", r.URL.Path, "remote", r.RemoteAddr) }) } + // s.mux.Handle(path, logMiddleware(handler)) + s.registerHandler(path, logMiddleware(handler)) +} - s.mux.Handle( - "/", - logMiddleware(http.HandlerFunc(homeHandler)), +func (s *Api) setupHandlers() { + s.registerLoggedHandler( + s.makeApiPath("/health"), + http.HandlerFunc(homeHandler), ) + s.setupStorageEndpointHandlers() } func homeHandler(w http.ResponseWriter, r *http.Request) { diff --git a/api/storage.go b/api/storage.go new file mode 100644 index 0000000..8b2d462 --- /dev/null +++ b/api/storage.go @@ -0,0 +1,26 @@ +package api + +import ( + "encoding/json" + "net/http" + + "github.com/brinestone/dfs/storage" +) + +const storageUrlPath = "storage" + +func getStorageStatsHandler(api *Api, store *storage.Store) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + s, err := json.Marshal(store.GetStats()) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + api.Logger.Error("error while handling request", "err", err) + return + } + w.Write(s) + }) +} + +func (api *Api) setupStorageEndpointHandlers() { + api.registerLoggedHandler(api.makeApiPathWithBase("stats", storageUrlPath), getStorageStatsHandler(api, api.Store)) +} diff --git a/cmd/main.go b/cmd/main.go index 35c54ee..a3be625 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -4,15 +4,18 @@ import ( "context" "crypto/md5" "encoding/hex" - "flag" "fmt" - "github.com/google/uuid" "log/slog" "os" "os/signal" "path" + "path/filepath" "syscall" + "github.com/akamensky/argparse" + + "github.com/google/uuid" + "github.com/brinestone/dfs/api" "github.com/brinestone/dfs/fs" "github.com/brinestone/dfs/p2p" @@ -20,56 +23,60 @@ import ( ) var ( - storageRoot = flag.String("root", path.Join(homeDir, ".dfs"), "Filesystem path to be used as root.") - bufferSize = flag.Int64("bs", 4096, "Buffer Size") + storageRoot *string + verbose *bool + bufferSize *int homeDir, _ = os.UserHomeDir() - logger = slog.Default().WithGroup("DFS") + logger *slog.Logger ctx = context.Background() ) -func makeServer(ctx context.Context, listenAddr string, id string) (*fs.FileServer, context.CancelFunc) { +func makeStore(root string) (store *storage.Store) { + storageConfig := storage.StoreConfig{ + TransformKey: storage.CASKeyTransformer(root), + Root: root, + Logger: logger.WithGroup("store"), + } + + store = storage.NewStore(storageConfig) + return +} + +func makeServer(ctx context.Context, listenAddr string, id string, store *storage.Store) (*fs.FileServer, context.CancelFunc) { + fsLogger := logger.WithGroup("fs") tcpTransportConfig := p2p.TcpTransportConfig{ ListenAddr: listenAddr, Handshake: p2p.NoopHandshake, Decoder: p2p.DefaultDecoder{ EncodingConfig: p2p.EncodingConfig{ - BufferSize: *bufferSize, + BufferSize: int64(*bufferSize), }, }, - Logger: logger.WithGroup("FS/TCP"), + Logger: fsLogger.WithGroup("tcp"), } tcpTransport := p2p.NewTcpTransport(tcpTransportConfig) - serverGroup := fmt.Sprintf("fs-%s", id) - hash := md5.Sum([]byte(serverGroup)) - root := path.Join(*storageRoot, hex.EncodeToString(hash[:])) c, cancel := context.WithCancel(ctx) serverConfig := fs.FileServerConfig{ ListenAddr: listenAddr, - StorageRoot: root, - KeyTransformer: storage.CASKeyTransformer(root), Transport: tcpTransport, Id: id, Context: c, - Logger: logger.WithGroup("FS/Server"), - StreamChunkSize: *bufferSize, + Logger: fsLogger.WithGroup("server"), + StreamChunkSize: int64(*bufferSize), } - s := fs.NewFileServer(serverConfig) + s := fs.NewFileServer(serverConfig, store) return s, cancel } -func startFileServer() *fs.FileServer { - serverId, err := os.Hostname() - if err != nil { - serverId = uuid.NewString() // todo: rethink this - } - server, cancel := makeServer(ctx, ":5060", serverId) +func startFileServer(serverId string, store *storage.Store) *fs.FileServer { + server, cancel := makeServer(ctx, ":5060", serverId, store) go func() { defer cancel() - if err = server.Start(); err != nil { + if err := server.Start(); err != nil { logger.Error("error while starting file server", "reason", err.Error()) } }() @@ -77,18 +84,22 @@ func startFileServer() *fs.FileServer { return server } -func startApi(addr string) *api.Server { - apiServer := api.NewServer(api.Config{ - Addr: addr, - Ctx: ctx, - Logger: logger.WithGroup("API"), +func startApi(addr string, store *storage.Store, f *fs.FileServer) *api.Api { + apiServer := api.NewApi(api.Config{ + Addr: addr, + BasePath: "/api", + Verbose: *verbose, + Ctx: ctx, + Logger: logger.WithGroup("api"), + Store: store, + FileServer: f, }) go func() { err := apiServer.Start() if err != nil { defer apiServer.Stop() - logger.Error("error while starting API", err.Error()) + logger.Error("error while starting API", "err", err.Error()) return } }() @@ -96,11 +107,42 @@ func startApi(addr string) *api.Server { return apiServer } +func parseArgs() { + parser := argparse.NewParser("print", "Prints the provided string to stdout") + storageRoot = parser.String("r", "storage-root", &argparse.Options{Required: false, Default: path.Join(homeDir, ".dfs"), Help: "The file system path to be used to store data"}) + bufferSize = parser.Int("b", "buffer-size", &argparse.Options{Help: "The buffer-size to use", Default: 4096}) + verbose = parser.Flag("v", "verbose", &argparse.Options{Help: "Enable verbose logs", Default: true}) + if err := parser.Parse(os.Args); err != nil { + fmt.Print(parser.Usage(err)) + os.Exit(1) + } +} + +func setupLogger() { + if *verbose { + slog.SetLogLoggerLevel(slog.LevelDebug) + } + logger = slog.Default().WithGroup("dfs") +} + func main() { - flag.Parsed() - ctx = context.Background() - apiServer := startApi(":8000") - fileServer := startFileServer() + parseArgs() + setupLogger() + serverId, err := os.Hostname() + if err != nil { + serverId = uuid.NewString() // todo: rethink this + } + + serverGroup := fmt.Sprintf("fs-%s", serverId) + hash := md5.Sum([]byte(serverGroup)) + root, err := filepath.Abs(path.Join(*storageRoot, hex.EncodeToString(hash[:]))) + if err != nil { + panic(err) + } + + store := makeStore(root) + fileServer := startFileServer(serverId, store) + apiServer := startApi(":8000", store, fileServer) apiServer.Config.FileServer = fileServer endCh := make(chan os.Signal, 1) @@ -114,7 +156,7 @@ func main() { logger.Info("Shutting down") err := fileServer.Shutdown() if err != nil { - logger.Error("error while stopping File Server", "cause", err.Error()) + logger.Error("error while stopping File Api", "cause", err.Error()) } err = apiServer.Stop() if err != nil { diff --git a/fs/server.go b/fs/server.go index c275ec3..3fe0129 100644 --- a/fs/server.go +++ b/fs/server.go @@ -16,8 +16,6 @@ import ( type FileServerConfig struct { ListenAddr string - KeyTransformer storage.KeyTransformer - StorageRoot string Transport p2p.Transport Context context.Context Logger *slog.Logger @@ -49,7 +47,7 @@ type StoreCommand struct { type Message struct { Timestamp time.Time - Payload interface{} + Payload any } // Writes the data to the disk and also broadcasts it to other nodes and returns a channel which reports the status of the storing operation. @@ -83,18 +81,13 @@ func (s *FileServer) StoreData(key string, size int64, in io.Reader) error { return nil } -func NewFileServer(config FileServerConfig) *FileServer { +func NewFileServer(config FileServerConfig, store *storage.Store) *FileServer { var server *FileServer ctx, cancel := context.WithCancel(config.Context) - storageConfig := storage.StoreConfig{ - TransformKey: config.KeyTransformer, - Root: config.StorageRoot, - Logger: config.Logger, - } server = &FileServer{ - store: storage.NewStore(storageConfig), + store: store, FileServerConfig: config, shutdownFunc: cancel, ctx: ctx, diff --git a/go.mod b/go.mod index d78aee8..e11952c 100644 --- a/go.mod +++ b/go.mod @@ -5,12 +5,25 @@ go 1.23.0 require ( github.com/google/uuid v1.6.0 github.com/stretchr/testify v1.9.0 + google.golang.org/grpc v1.73.0 ) -require gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect +require ( + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect + github.com/shirou/gopsutil/v3 v3.24.5 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect +) require ( + github.com/akamensky/argparse v1.4.0 github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + golang.org/x/net v0.38.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/text v0.23.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect + google.golang.org/protobuf v1.36.6 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 1afae4d..5f7999d 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,17 @@ +github.com/akamensky/argparse v1.4.0 h1:YGzvsTqCvbEZhL8zZu2AiA5nq805NZh75JNj4ajn1xc= +github.com/akamensky/argparse v1.4.0/go.mod h1:S5kwC7IuDcEr5VeXtGPRVZ5o/FdhcMlQz4IZQuw64xA= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= @@ -9,8 +21,40 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= +github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= +go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= +go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= +go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= +go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= +go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= +go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= +go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= +go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= +google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/monitoring/monitoring.go b/monitoring/monitoring.go new file mode 100644 index 0000000..f98f43a --- /dev/null +++ b/monitoring/monitoring.go @@ -0,0 +1,54 @@ +package monitoring + +import ( + "context" + "time" +) + +type HeartbeatFunc func() error +type MonitorConfig struct { + InitialDelay int `json:"initialDelay"` + Interval int `json:"interval"` + Id string `json:"id"` + DisplayName string `json:"displayName"` +} + +type MonitoringAgent struct { + heartbeat HeartbeatFunc + started bool + Healthy bool + MonitorConfig +} + +func NewAgent(ctx context.Context, f HeartbeatFunc, config MonitorConfig) *MonitoringAgent { + agent := &MonitoringAgent{ + MonitorConfig: config, + heartbeat: f, + } + agent.start(ctx) + return agent +} + +func (m *MonitoringAgent) start(parentCtx context.Context) { + ctx, cancel := context.WithCancel(parentCtx) + go func() { + defer cancel() + time.AfterFunc(time.Duration(m.InitialDelay), func() { + ticker := time.NewTicker(time.Duration(m.Interval)) + defer ticker.Stop() + for { + select { + case <-ticker.C: + err := m.heartbeat() + m.Healthy = err == nil + case <-ctx.Done(): + m.started = false + return + } + } + }) + }() + m.started = true +} + +// func (m *MonitoringAgent) diff --git a/storage/store.go b/storage/store.go index 26289d6..6e5c4c4 100644 --- a/storage/store.go +++ b/storage/store.go @@ -10,9 +10,19 @@ import ( "log/slog" "os" "path" + "runtime" "strings" + + "github.com/shirou/gopsutil/v3/disk" ) +type StoreStats struct { + TotalSpace uint64 `json:"totalSpace"` + TotalUsed uint64 `json:"totalUsed"` + StoreSize uint64 `json:"storeSize"` + Root string `json:"rootPath"` +} + type PathKey struct { Pathname string Root string @@ -89,6 +99,37 @@ func NewStore(config StoreConfig) *Store { } } +func getRootDiskStats(rootPath string) (usage *disk.UsageStat, err error) { + usage, err = disk.Usage(rootPath) + return +} + +func (s *Store) GetStats() StoreStats { + var rootPath string + if runtime.GOOS == "windows" { + rootPath = "C:" + } else { + rootPath = "/" + } + + stats, err := getRootDiskStats(rootPath) + if err != nil { + panic(err) + } + ans := StoreStats{ + TotalSpace: stats.Total, + TotalUsed: stats.Free, + StoreSize: 0, + Root: s.Root, + } + stats, _ = getRootDiskStats(s.Root) + if stats != nil { + ans.StoreSize = stats.Used + } + + return ans +} + func (s *Store) Clean() error { return os.RemoveAll(s.Root) } diff --git a/tmp/build-errors.log b/tmp/build-errors.log index 66edf70..9e6afba 100644 --- a/tmp/build-errors.log +++ b/tmp/build-errors.log @@ -1 +1 @@ -exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2 \ No newline at end of file +exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1 \ No newline at end of file