Skip to content

Commit

Permalink
storagenode: display actual disk space when allocated is higher than …
Browse files Browse the repository at this point in the history
…actual

For cases when the allocated disk space is higher than the actual
disk space, the dashboard will display the actual disk space.

Also displays the min(free, actual free space on disk).
This partially fixes the largely reported disk space discrepancies
where the values displayed on the dashboard does not match
the actual disk values.

This shouldn't be a problem for the satellite site since the
node already reports the min(free for storj, actual free space on disk)

Change-Id: I537cd1e653394aab843be27e3f4c143fed6f6823
  • Loading branch information
profclems authored and Storj Robot committed Feb 2, 2024
1 parent 3801c56 commit d96c411
Show file tree
Hide file tree
Showing 11 changed files with 112 additions and 32 deletions.
9 changes: 9 additions & 0 deletions storagenode/blobstore/blob.go
Expand Up @@ -104,6 +104,8 @@ type Blobs interface {

// FreeSpace return how much free space is left on the whole disk, not just the allocated disk space.
FreeSpace(ctx context.Context) (int64, error)
// DiskInfo returns information about the disk.
DiskInfo(ctx context.Context) (DiskInfo, error)
// SpaceUsedForTrash returns the total space used by the trash.
SpaceUsedForTrash(ctx context.Context) (int64, error)
// SpaceUsedForBlobs adds up how much is used in all namespaces.
Expand Down Expand Up @@ -143,3 +145,10 @@ type BlobInfo interface {
// Stat does a stat on the on-disk blob file.
Stat(ctx context.Context) (os.FileInfo, error)
}

// DiskInfo contains information about the disk.
type DiskInfo struct {
ID string
TotalSpace int64
AvailableSpace int64
}
10 changes: 2 additions & 8 deletions storagenode/blobstore/filestore/dir.go
Expand Up @@ -873,17 +873,11 @@ func removeAllContent(ctx context.Context, path string) (err error) {
}
}

// DiskInfo contains statistics about this dir.
type DiskInfo struct {
ID string
AvailableSpace int64
}

// Info returns information about the current state of the dir.
func (dir *Dir) Info(ctx context.Context) (DiskInfo, error) {
func (dir *Dir) Info(ctx context.Context) (blobstore.DiskInfo, error) {
path, err := filepath.Abs(dir.path)
if err != nil {
return DiskInfo{}, err
return blobstore.DiskInfo{}, err
}
return diskInfoFromPath(path)
}
Expand Down
16 changes: 12 additions & 4 deletions storagenode/blobstore/filestore/dir_unix.go
Expand Up @@ -12,25 +12,33 @@ import (
"os"

"golang.org/x/sys/unix"

"storj.io/storj/storagenode/blobstore"
)

func isBusy(err error) bool {
err = underlyingError(err)
return errors.Is(err, unix.EBUSY)
}

func diskInfoFromPath(path string) (info DiskInfo, err error) {
func diskInfoFromPath(path string) (info blobstore.DiskInfo, err error) {
var stat unix.Statfs_t
err = unix.Statfs(path, &stat)
if err != nil {
return DiskInfo{"", -1}, err
return blobstore.DiskInfo{TotalSpace: -1, AvailableSpace: -1}, err
}

reservedBlocks := stat.Bfree - stat.Bavail
// the Bsize size depends on the OS and unconvert gives a false-positive
availableSpace := int64(stat.Bavail) * int64(stat.Bsize) //nolint: unconvert
totalSpace := int64(stat.Blocks-reservedBlocks) * int64(stat.Bsize) //nolint: unconvert
availableSpace := int64(stat.Bavail) * int64(stat.Bsize) //nolint: unconvert
filesystemID := fmt.Sprintf("%08x%08x", stat.Fsid.Val[0], stat.Fsid.Val[1])

return DiskInfo{filesystemID, availableSpace}, nil
return blobstore.DiskInfo{
ID: filesystemID,
TotalSpace: totalSpace,
AvailableSpace: availableSpace,
}, nil
}

// rename renames oldpath to newpath.
Expand Down
25 changes: 15 additions & 10 deletions storagenode/blobstore/filestore/dir_windows.go
Expand Up @@ -14,6 +14,8 @@ import (
"unsafe"

"golang.org/x/sys/windows"

"storj.io/storj/storagenode/blobstore"
)

var errSharingViolation = windows.Errno(32)
Expand All @@ -23,42 +25,45 @@ func isBusy(err error) bool {
return errors.Is(err, errSharingViolation)
}

func diskInfoFromPath(path string) (info DiskInfo, err error) {
func diskInfoFromPath(path string) (info blobstore.DiskInfo, err error) {
absPath, err := filepath.Abs(path)
if err != nil {
absPath = path
}
var filesystemID string
var availableSpace int64
var totalSpace int64

availableSpace, err = getDiskFreeSpace(absPath)
totalSpace, availableSpace, err = getDiskFreeSpace(absPath)
if err != nil {
return DiskInfo{"", -1}, err
return blobstore.DiskInfo{"", -1, -1}, err
}

filesystemID, err = getVolumeSerialNumber(absPath)
if err != nil {
return DiskInfo{"", availableSpace}, err
return blobstore.DiskInfo{"", totalSpace, availableSpace}, err
}

return DiskInfo{filesystemID, availableSpace}, nil
return blobstore.DiskInfo{filesystemID, totalSpace, availableSpace}, nil
}

var (
kernel32 = windows.MustLoadDLL("kernel32.dll")
procGetDiskFreeSpace = kernel32.MustFindProc("GetDiskFreeSpaceExW")
)

func getDiskFreeSpace(path string) (int64, error) {
func getDiskFreeSpace(path string) (total, free int64, err error) {
path16, err := windows.UTF16PtrFromString(path)
if err != nil {
return -1, err
return -1, -1, err
}
_, _, err = procGetDiskFreeSpace.Call(uintptr(unsafe.Pointer(path16)),
uintptr(unsafe.Pointer(&free)),
0,
uintptr(unsafe.Pointer(&total)))

var bytes int64
_, _, err = procGetDiskFreeSpace.Call(uintptr(unsafe.Pointer(path16)), uintptr(unsafe.Pointer(&bytes)), 0, 0)
err = ignoreSuccess(err)
return bytes, err
return total, free, err
}

func getVolumeSerialNumber(path string) (string, error) {
Expand Down
5 changes: 5 additions & 0 deletions storagenode/blobstore/filestore/store.go
Expand Up @@ -281,6 +281,11 @@ func (store *blobStore) FreeSpace(ctx context.Context) (int64, error) {
return info.AvailableSpace, nil
}

// DiskInfo returns information about the disk.
func (store *blobStore) DiskInfo(ctx context.Context) (blobstore.DiskInfo, error) {
return store.dir.Info(ctx)
}

// CheckWritability tests writability of the storage directory by creating and deleting a file.
func (store *blobStore) CheckWritability(ctx context.Context) error {
f, err := os.CreateTemp(store.dir.Path(), "write-test")
Expand Down
8 changes: 8 additions & 0 deletions storagenode/blobstore/testblobs/bad.go
Expand Up @@ -244,6 +244,14 @@ func (bad *BadBlobs) FreeSpace(ctx context.Context) (int64, error) {
return bad.blobs.FreeSpace(ctx)
}

// DiskInfo returns information about the disk.
func (bad *BadBlobs) DiskInfo(ctx context.Context) (blobstore.DiskInfo, error) {
if err := bad.err.Err(); err != nil {
return blobstore.DiskInfo{}, err
}
return bad.blobs.DiskInfo(ctx)
}

// SpaceUsedForBlobs adds up how much is used in all namespaces.
func (bad *BadBlobs) SpaceUsedForBlobs(ctx context.Context) (int64, error) {
if err := bad.err.Err(); err != nil {
Expand Down
8 changes: 8 additions & 0 deletions storagenode/blobstore/testblobs/limitedspace.go
Expand Up @@ -56,3 +56,11 @@ func newLimitedSpaceBlobs(log *zap.Logger, blobs blobstore.Blobs, freeSpace int6
func (limspace *LimitedSpaceBlobs) FreeSpace(ctx context.Context) (int64, error) {
return limspace.freeSpace, nil
}

// DiskInfo returns the disk info.
func (limspace *LimitedSpaceBlobs) DiskInfo(ctx context.Context) (blobstore.DiskInfo, error) {
return blobstore.DiskInfo{
TotalSpace: limspace.freeSpace,
AvailableSpace: limspace.freeSpace,
}, nil
}
8 changes: 8 additions & 0 deletions storagenode/blobstore/testblobs/slow.go
Expand Up @@ -196,6 +196,14 @@ func (slow *SlowBlobs) FreeSpace(ctx context.Context) (int64, error) {
return slow.blobs.FreeSpace(ctx)
}

// DiskInfo returns the disk space information.
func (slow *SlowBlobs) DiskInfo(ctx context.Context) (blobstore.DiskInfo, error) {
if err := slow.sleep(ctx); err != nil {
return blobstore.DiskInfo{}, errs.Wrap(err)
}
return slow.blobs.DiskInfo(ctx)
}

// SpaceUsedForBlobs adds up how much is used in all namespaces.
func (slow *SlowBlobs) SpaceUsedForBlobs(ctx context.Context) (int64, error) {
if err := slow.sleep(ctx); err != nil {
Expand Down
16 changes: 15 additions & 1 deletion storagenode/console/service.go
Expand Up @@ -55,7 +55,8 @@ type Service struct {
version *checker.Service
pingStats *contact.PingStats

allocatedDiskSpace memory.Size
allocatedDiskSpace memory.Size
storageStatusChecked bool // indicates if storage status was checked

walletAddress string
walletFeatures operator.WalletFeatures
Expand Down Expand Up @@ -224,6 +225,19 @@ func (s *Service) GetDashboardData(ctx context.Context) (_ *Dashboard, err error
return nil, SNOServiceErr.Wrap(err)
}

if !s.storageStatusChecked {
storageStatus, err := s.pieceStore.StorageStatus(ctx)
if err != nil {
s.log.Error("unable to get storage status", zap.Error(err))
s.storageStatusChecked = false
}

if storageStatus.DiskTotal > 0 && storageStatus.DiskTotal < s.allocatedDiskSpace.Int64() {
s.allocatedDiskSpace = memory.Size(storageStatus.DiskTotal)
}
s.storageStatusChecked = true
}

data.DiskSpace = DiskSpaceInfo{
Used: pieceTotal,
Available: s.allocatedDiskSpace.Int64(),
Expand Down
28 changes: 23 additions & 5 deletions storagenode/monitor/monitor.go
Expand Up @@ -34,6 +34,8 @@ var (
type DiskSpace struct {
// Allocated is the amount of disk space allocated to the storage node, in bytes.
Allocated int64
// Total is the total amount of disk space on the whole disk, not just allocated disk space, in bytes.
Total int64
// UsedForPieces is the amount of disk space used for pieces, in bytes.
UsedForPieces int64
// UsedForTrash is the amount of disk space used for trash, in bytes.
Expand Down Expand Up @@ -245,17 +247,22 @@ func (service *Service) AvailableSpace(ctx context.Context) (_ int64, err error)
return 0, err
}

freeSpaceForStorj := service.allocatedDiskSpace - usedSpace

diskStatus, err := service.store.StorageStatus(ctx)
if err != nil {
return 0, Error.Wrap(err)
}

allocated := service.allocatedDiskSpace
if isLowerThanAllocated(diskStatus.DiskTotal, allocated) {
allocated = diskStatus.DiskTotal
}

freeSpaceForStorj := allocated - usedSpace
if diskStatus.DiskFree < freeSpaceForStorj {
freeSpaceForStorj = diskStatus.DiskFree
}

mon.IntVal("allocated_space").Observe(service.allocatedDiskSpace)
mon.IntVal("allocated_space").Observe(allocated)
mon.IntVal("used_space").Observe(usedSpace)
mon.IntVal("available_space").Observe(freeSpaceForStorj)

Expand All @@ -282,7 +289,12 @@ func (service *Service) DiskSpace(ctx context.Context) (_ DiskSpace, err error)

overused := int64(0)

available := service.allocatedDiskSpace - (usedForPieces + usedForTrash)
allocated := service.allocatedDiskSpace
if isLowerThanAllocated(storageStatus.DiskTotal, allocated) {
allocated = storageStatus.DiskTotal
}

available := allocated - (usedForPieces + usedForTrash)
if available < 0 {
overused = -available
}
Expand All @@ -291,11 +303,17 @@ func (service *Service) DiskSpace(ctx context.Context) (_ DiskSpace, err error)
}

return DiskSpace{
Allocated: service.allocatedDiskSpace,
Total: storageStatus.DiskTotal,
Allocated: allocated,
UsedForPieces: usedForPieces,
UsedForTrash: usedForTrash,
Free: storageStatus.DiskFree,
Available: available,
Overused: overused,
}, nil
}

// isLowerThanAllocated checks if the disk space is lower than allocated.
func isLowerThanAllocated(actual, allocated int64) bool {
return actual > 0 && actual < allocated
}
11 changes: 7 additions & 4 deletions storagenode/pieces/store.go
Expand Up @@ -748,21 +748,24 @@ func (store *Store) GetV0PieceInfo(ctx context.Context, satellite storj.NodeID,

// StorageStatus contains information about the disk store is using.
type StorageStatus struct {
DiskUsed int64
// DiskTotal is the actual disk size (not just the allocated disk space), in bytes.
DiskTotal int64
DiskUsed int64
// DiskFree is the actual amount of free space on the whole disk, not just allocated disk space, in bytes.
DiskFree int64
}

// StorageStatus returns information about the disk.
func (store *Store) StorageStatus(ctx context.Context) (_ StorageStatus, err error) {
defer mon.Task()(&ctx)(&err)
diskFree, err := store.blobs.FreeSpace(ctx)
info, err := store.blobs.DiskInfo(ctx)
if err != nil {
return StorageStatus{}, err
}
return StorageStatus{
DiskUsed: -1, // TODO set value
DiskFree: diskFree,
DiskTotal: info.TotalSpace,
DiskUsed: -1, // TODO set value
DiskFree: info.AvailableSpace,
}, nil
}

Expand Down

1 comment on commit d96c411

@storjrobot
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This commit has been mentioned on Storj Community Forum (official). There might be relevant details there:

https://forum.storj.io/t/storage-allocated-disk-space-not-updating-in-config/25303/15

Please sign in to comment.