Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add disk stat functions #42248

Merged
merged 18 commits into from
Oct 30, 2021
3 changes: 3 additions & 0 deletions base/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -893,6 +893,9 @@ export
chown,
cp,
ctime,
disk_available,
disk_total,
disk_used,
download,
filemode,
filesize,
Expand Down
82 changes: 82 additions & 0 deletions base/file.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1164,3 +1164,85 @@ function chown(path::AbstractString, owner::Integer, group::Integer=-1)
err < 0 && uv_error("chown($(repr(path)), $owner, $group)", err)
path
end


# typedef struct uv_statfs_s {
# uint64_t f_type;
# uint64_t f_bsize; <- block size
# uint64_t f_blocks; <- total blocks
# uint64_t f_bfree;
# uint64_t f_bavail; <- available blocks
# uint64_t f_files;
# uint64_t f_ffree;
# uint64_t f_spare[4];
# } uv_statfs_t;
# See also
# - http://docs.libuv.org/en/v1.x/fs.html#c.uv_fs_statfs (libuv function docs)
# - http://docs.libuv.org/en/v1.x/fs.html#c.uv_statfs_t (libuv docs of the returned struct)
struct StatFS
ftype::UInt64
bsize::UInt64
blocks::UInt64
bfree::UInt64
bavail::UInt64
files::UInt64
ffree::UInt64
fspare1::UInt128
fspare2::UInt128
end

struct DiskStats
total::Int
available::Int

function DiskStats(path::AbstractString)
isdir(path) || isfile(path) || throw(ArgumentError("'$path' is not a file or directory. Please provide a valid path."))

# Call libuv's cross-platform statfs implementation
req = Ref{NTuple{Int(_sizeof_uv_fs), UInt8}}()
err = ccall(:uv_fs_statfs, Int32, (Ptr{Cvoid}, Ptr{Cvoid}, Cstring, Ptr{Cvoid}),
C_NULL, req, path, C_NULL)
err < 0 && uv_error("statfs($(repr(path)))", err)
statfs_ptr = ccall(:jl_uv_fs_t_ptr, Ptr{Nothing}, (Ptr{Cvoid},), req)

stats = unsafe_load(reinterpret(Ptr{StatFS}, statfs_ptr))
total = Int(stats.bsize * stats.blocks)
available = Int(stats.bsize * stats.bavail)
disk_stats = new(total, available)

# Cleanup
uv_fs_req_cleanup(req)

return disk_stats
end
end

"""
disk_total(path=pwd())

Returns the size in bytes of the disk that contains the file or directory pointed at by
`path`. If no argument is passed, the size of the disk that contains the current working
directory is returned.
"""
disk_total(path::AbstractString=pwd()) = DiskStats(path).total

"""
disk_available(path=pwd())

Returns the available space in bytes on the disk that contains the file or directory pointed
at by `path`. If no argument is passed, the available space on the disk that contains the
current working directory is returned.
"""
disk_available(path::AbstractString=pwd()) = DiskStats(path).available

"""
disk_used(path=pwd())

Returns the used space in bytes of the disk that contains the file or directory pointed
at by `path`. If no argument is passed, the used space of the disk that contains the
current working directory is returned.
"""
function disk_used(path::AbstractString=pwd())
disk_stats = DiskStats(path)
return disk_stats.total - disk_stats.available
end
14 changes: 14 additions & 0 deletions test/file.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1692,3 +1692,17 @@ end
@test !isnothing(Base.Filesystem.getgroupname(s.gid))
end
end

@testset "Disk space calculations work" begin
@test disk_total() == disk_total(pwd())
@test disk_used() == disk_used(pwd())
@test disk_available() == disk_available(pwd())

# Sanity check assuming disk is smaller than 16TB
TB = 2^40
@test disk_total() < 16TB

@test disk_used() < disk_total()
@test disk_available() < disk_total()
@test disk_used() + disk_available() == disk_total()
end