From b6b43b72b2535220dffd849cccc24d2e80ebcdff Mon Sep 17 00:00:00 2001 From: Malte Sandstede Date: Tue, 14 Sep 2021 12:14:37 -0700 Subject: [PATCH 01/14] add disk stat functions --- base/exports.jl | 3 ++ base/file.jl | 83 +++++++++++++++++++++++++++++++++++++++++++++++++ test/file.jl | 14 +++++++++ 3 files changed, 100 insertions(+) diff --git a/base/exports.jl b/base/exports.jl index ba454936cb7f3..4e0e074ab4b80 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -893,6 +893,9 @@ export chown, cp, ctime, + disk_available, + disk_total, + disk_used, download, filemode, filesize, diff --git a/base/file.jl b/base/file.jl index 85450ff2d3645..ce586172bbd32 100644 --- a/base/file.jl +++ b/base/file.jl @@ -1164,3 +1164,86 @@ 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 + fspare::UInt64 +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 = Libc.malloc(Base._sizeof_uv_fs) + err = ccall(:uv_fs_statfs, Int32, (Ptr{Cvoid}, Ptr{Cvoid}, Cstring, Ptr{Cvoid}), + C_NULL, req, path, C_NULL) + err < 0 && Base.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 + ccall(:uv_fs_req_cleanup, Cvoid, (Ptr{Cvoid},), req) + uv_fs_req_cleanup(req) + Libc.free(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 diff --git a/test/file.jl b/test/file.jl index 3d300668aadf3..9ddc338bfbdf4 100644 --- a/test/file.jl +++ b/test/file.jl @@ -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() < 16 * 2^40 + + @test disk_used() < disk_total() + @test disk_available() < disk_total() + @test disk_used() + disk_available() == disk_total() +end From b49cba950af03b78f00c03dd762b6c0aa77dd34b Mon Sep 17 00:00:00 2001 From: Malte Sandstede Date: Tue, 14 Sep 2021 12:18:32 -0700 Subject: [PATCH 02/14] test fix --- test/file.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/file.jl b/test/file.jl index 9ddc338bfbdf4..8c4871eb44eee 100644 --- a/test/file.jl +++ b/test/file.jl @@ -1700,7 +1700,7 @@ end # Sanity check assuming disk is smaller than 16TB TB = 2^40 - @test disk_total() < 16 * 2^40 + @test disk_total() < 16TB @test disk_used() < disk_total() @test disk_available() < disk_total() From 4b059dd6b0e91dae85bb6771f68911f8a1ea8bfc Mon Sep 17 00:00:00 2001 From: Malte Sandstede Date: Tue, 14 Sep 2021 12:23:23 -0700 Subject: [PATCH 03/14] cleanup --- base/file.jl | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/base/file.jl b/base/file.jl index ce586172bbd32..ee6487761856a 100644 --- a/base/file.jl +++ b/base/file.jl @@ -1187,7 +1187,8 @@ struct StatFS bavail::UInt64 files::UInt64 ffree::UInt64 - fspare::UInt64 + fspare1::UInt128 + fspare2::UInt128 end struct DiskStats @@ -1198,7 +1199,7 @@ struct DiskStats 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 = Libc.malloc(Base._sizeof_uv_fs) + 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 && Base.uv_error("statfs($(repr(path)))", err) @@ -1211,8 +1212,6 @@ struct DiskStats # Cleanup ccall(:uv_fs_req_cleanup, Cvoid, (Ptr{Cvoid},), req) - uv_fs_req_cleanup(req) - Libc.free(req) return disk_stats end From 0272fdad479e8733f4282de3f50b3d9295b105de Mon Sep 17 00:00:00 2001 From: Malte Sandstede Date: Tue, 14 Sep 2021 12:39:25 -0700 Subject: [PATCH 04/14] minor cleanup --- base/file.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/file.jl b/base/file.jl index ee6487761856a..8eef0d2a2db2c 100644 --- a/base/file.jl +++ b/base/file.jl @@ -1202,7 +1202,7 @@ struct DiskStats 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 && Base.uv_error("statfs($(repr(path)))", err) + 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)) @@ -1211,7 +1211,7 @@ struct DiskStats disk_stats = new(total, available) # Cleanup - ccall(:uv_fs_req_cleanup, Cvoid, (Ptr{Cvoid},), req) + uv_fs_req_cleanup(req) return disk_stats end From 7b18cdc133ae3f00066b2c5a3711b66df0480a43 Mon Sep 17 00:00:00 2001 From: Malte Sandstede Date: Tue, 14 Sep 2021 18:41:23 -0700 Subject: [PATCH 05/14] PR feedback --- base/file.jl | 41 +++++++++++++++++++++++++---------------- test/file.jl | 2 +- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/base/file.jl b/base/file.jl index 8eef0d2a2db2c..f11f096339bab 100644 --- a/base/file.jl +++ b/base/file.jl @@ -1194,27 +1194,27 @@ end struct DiskStats total::Int available::Int +end - function DiskStats(path::AbstractString) - isdir(path) || isfile(path) || throw(ArgumentError("'$path' is not a file or directory. Please provide a valid path.")) +function DiskStats(path::AbstractString) + ispath(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) + # Call libuv's cross-platform statfs implementation + req = Ref{NTuple{Int(_sizeof_uv_fs), UInt8}}() + err = ccall(:uv_fs_statfs, Cint, (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) + stats = unsafe_load(reinterpret(Ptr{StatFS}, statfs_ptr)) + total = Int(stats.bsize * stats.blocks) + available = Int(stats.bsize * stats.bavail) + disk_stats = DiskStats(total, available) - # Cleanup - uv_fs_req_cleanup(req) + # Cleanup + uv_fs_req_cleanup(req) - return disk_stats - end + return disk_stats end """ @@ -1223,6 +1223,9 @@ end 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. + +!!! compat "Julia 1.8" + This method was added in Julia 1.8. """ disk_total(path::AbstractString=pwd()) = DiskStats(path).total @@ -1232,6 +1235,9 @@ disk_total(path::AbstractString=pwd()) = DiskStats(path).total 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. + +!!! compat "Julia 1.8" + This method was added in Julia 1.8. """ disk_available(path::AbstractString=pwd()) = DiskStats(path).available @@ -1241,6 +1247,9 @@ disk_available(path::AbstractString=pwd()) = DiskStats(path).available 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. + +!!! compat "Julia 1.8" + This method was added in Julia 1.8. """ function disk_used(path::AbstractString=pwd()) disk_stats = DiskStats(path) diff --git a/test/file.jl b/test/file.jl index 8c4871eb44eee..198131a062b7c 100644 --- a/test/file.jl +++ b/test/file.jl @@ -1704,5 +1704,5 @@ end @test disk_used() < disk_total() @test disk_available() < disk_total() - @test disk_used() + disk_available() == disk_total() + @test disk_used() + disk_available() <= disk_total() end From 1fa0a7fb08b9ebcec5508cf778f35fd1c582079c Mon Sep 17 00:00:00 2001 From: Malte Sandstede Date: Thu, 16 Sep 2021 07:11:36 -0700 Subject: [PATCH 06/14] PR feedback --- base/exports.jl | 3 +++ base/file.jl | 20 ++++++++++++++++---- test/file.jl | 5 ++++- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/base/exports.jl b/base/exports.jl index 4e0e074ab4b80..c4a56e498fa0f 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -893,9 +893,11 @@ export chown, cp, ctime, + DiskStats, disk_available, disk_total, disk_used, + diskstat, download, filemode, filesize, @@ -926,6 +928,7 @@ export pwd, readlink, rm, + StatFs, stat, symlink, tempdir, diff --git a/base/file.jl b/base/file.jl index f11f096339bab..6edb988414872 100644 --- a/base/file.jl +++ b/base/file.jl @@ -1197,7 +1197,7 @@ struct DiskStats end function DiskStats(path::AbstractString) - ispath(path) || throw(ArgumentError("'$path' is not a file or directory. Please provide a valid path.")) + ispath(path) || throw(ArgumentError("'$path' is not a file or directory.")) # Call libuv's cross-platform statfs implementation req = Ref{NTuple{Int(_sizeof_uv_fs), UInt8}}() @@ -1217,6 +1217,18 @@ function DiskStats(path::AbstractString) return disk_stats end +""" + diskstat(path=pwd()) + +Returns statistics in bytes about the disk that contains the file or directory pointed at by +`path`. If no argument is passed, statistics about the disk that contains the current +working directory are returned. + +!!! compat "Julia 1.8" + This method was added in Julia 1.8. +""" +diskstat(path::AbstractString=pwd()) = DiskStats(path) + """ disk_total(path=pwd()) @@ -1227,7 +1239,7 @@ directory is returned. !!! compat "Julia 1.8" This method was added in Julia 1.8. """ -disk_total(path::AbstractString=pwd()) = DiskStats(path).total +disk_total(path::AbstractString=pwd()) = diskstat(path).total """ disk_available(path=pwd()) @@ -1239,7 +1251,7 @@ current working directory is returned. !!! compat "Julia 1.8" This method was added in Julia 1.8. """ -disk_available(path::AbstractString=pwd()) = DiskStats(path).available +disk_available(path::AbstractString=pwd()) = diskstat(path).available """ disk_used(path=pwd()) @@ -1252,6 +1264,6 @@ current working directory is returned. This method was added in Julia 1.8. """ function disk_used(path::AbstractString=pwd()) - disk_stats = DiskStats(path) + disk_stats = diskstat(path) return disk_stats.total - disk_stats.available end diff --git a/test/file.jl b/test/file.jl index 198131a062b7c..8c36155fd2309 100644 --- a/test/file.jl +++ b/test/file.jl @@ -1693,7 +1693,7 @@ end end end -@testset "Disk space calculations work" begin +@testset "Disk stats work" begin @test disk_total() == disk_total(pwd()) @test disk_used() == disk_used(pwd()) @test disk_available() == disk_available(pwd()) @@ -1705,4 +1705,7 @@ end @test disk_used() < disk_total() @test disk_available() < disk_total() @test disk_used() + disk_available() <= disk_total() + + stats = diskstat() + @test stats.available < stats.total end From 80a81132691301923ef5ee185e3b27632c3dadac Mon Sep 17 00:00:00 2001 From: Malte Sandstede Date: Sat, 25 Sep 2021 14:45:16 -0700 Subject: [PATCH 07/14] PR feedback --- base/exports.jl | 4 --- base/file.jl | 75 ++++++++++++++----------------------------------- test/file.jl | 20 ++++--------- 3 files changed, 27 insertions(+), 72 deletions(-) diff --git a/base/exports.jl b/base/exports.jl index c4a56e498fa0f..1fe2dd5009c03 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -893,10 +893,6 @@ export chown, cp, ctime, - DiskStats, - disk_available, - disk_total, - disk_used, diskstat, download, filemode, diff --git a/base/file.jl b/base/file.jl index b64a414a56b07..9a576660e42bf 100644 --- a/base/file.jl +++ b/base/file.jl @@ -8,6 +8,7 @@ export chown, cp, cptree, + diskstat, hardlink, mkdir, mkpath, @@ -1195,12 +1196,29 @@ struct StatFS fspare2::UInt128 end -struct DiskStats +""" + DiskStat + +Stores the total size, available space, and currently used space of the disk in bytes. +Populate by calling `diskstat`. +""" +struct DiskStat total::Int available::Int + used::Int end -function DiskStats(path::AbstractString) +""" + diskstat(path=pwd()) + +Returns statistics in bytes about the disk that contains the file or directory pointed at by +`path`. If no argument is passed, statistics about the disk that contains the current +working directory are returned. + +!!! compat "Julia 1.8" + This method was added in Julia 1.8. +""" +function diskstat(path::AbstractString=pwd()) ispath(path) || throw(ArgumentError("'$path' is not a file or directory.")) # Call libuv's cross-platform statfs implementation @@ -1213,61 +1231,10 @@ function DiskStats(path::AbstractString) stats = unsafe_load(reinterpret(Ptr{StatFS}, statfs_ptr)) total = Int(stats.bsize * stats.blocks) available = Int(stats.bsize * stats.bavail) - disk_stats = DiskStats(total, available) + disk_stats = DiskStat(total, available, total - available) # Cleanup uv_fs_req_cleanup(req) return disk_stats end - -""" - diskstat(path=pwd()) - -Returns statistics in bytes about the disk that contains the file or directory pointed at by -`path`. If no argument is passed, statistics about the disk that contains the current -working directory are returned. - -!!! compat "Julia 1.8" - This method was added in Julia 1.8. -""" -diskstat(path::AbstractString=pwd()) = DiskStats(path) - -""" - 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. - -!!! compat "Julia 1.8" - This method was added in Julia 1.8. -""" -disk_total(path::AbstractString=pwd()) = diskstat(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. - -!!! compat "Julia 1.8" - This method was added in Julia 1.8. -""" -disk_available(path::AbstractString=pwd()) = diskstat(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. - -!!! compat "Julia 1.8" - This method was added in Julia 1.8. -""" -function disk_used(path::AbstractString=pwd()) - disk_stats = diskstat(path) - return disk_stats.total - disk_stats.available -end diff --git a/test/file.jl b/test/file.jl index 8c36155fd2309..c623c1d2aa120 100644 --- a/test/file.jl +++ b/test/file.jl @@ -1693,19 +1693,11 @@ end end end -@testset "Disk stats work" begin - @test disk_total() == disk_total(pwd()) - @test disk_used() == disk_used(pwd()) - @test disk_available() == disk_available(pwd()) +@testset "diskstat() works" begin + dstat = diskstat() - # 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() - - stats = diskstat() - @test stats.available < stats.total + # Sanity check assuming disk is smaller than 32TB + TB = 2^41 + @test dstat.total < 32TB + @test dstat.used + dstat.available == dstat.total end From 54e4995f160d59af9e87bbe02a010e82f3bef5ed Mon Sep 17 00:00:00 2001 From: Malte Sandstede Date: Sat, 25 Sep 2021 14:46:14 -0700 Subject: [PATCH 08/14] remove export --- base/exports.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/base/exports.jl b/base/exports.jl index 1fe2dd5009c03..1b9bd6dd7b860 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -924,7 +924,6 @@ export pwd, readlink, rm, - StatFs, stat, symlink, tempdir, From 294a77c814ae3ec359d2e86d20fe617e08cd2220 Mon Sep 17 00:00:00 2001 From: Malte Sandstede Date: Wed, 6 Oct 2021 10:18:22 -0700 Subject: [PATCH 09/14] pr feedback --- base/file.jl | 33 +++++++++++++++++---------------- test/file.jl | 6 +++--- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/base/file.jl b/base/file.jl index 9a576660e42bf..043c7f7f831de 100644 --- a/base/file.jl +++ b/base/file.jl @@ -1192,8 +1192,17 @@ struct StatFS bavail::UInt64 files::UInt64 ffree::UInt64 - fspare1::UInt128 - fspare2::UInt128 + fspare::NTuple{4, UInt64} # reserved +end + +function statfs(path::AbstractString) + req = Ref{NTuple{Int(_sizeof_uv_fs), UInt8}}() + err = ccall(:uv_fs_statfs, Cint, (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) + + return unsafe_load(reinterpret(Ptr{StatFS}, statfs_ptr)) end """ @@ -1203,9 +1212,9 @@ Stores the total size, available space, and currently used space of the disk in Populate by calling `diskstat`. """ struct DiskStat - total::Int - available::Int - used::Int + total::UInt64 + available::UInt64 + used::UInt64 end """ @@ -1219,18 +1228,10 @@ working directory are returned. This method was added in Julia 1.8. """ function diskstat(path::AbstractString=pwd()) - ispath(path) || throw(ArgumentError("'$path' is not a file or directory.")) - - # Call libuv's cross-platform statfs implementation - req = Ref{NTuple{Int(_sizeof_uv_fs), UInt8}}() - err = ccall(:uv_fs_statfs, Cint, (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 = statfs(path) - stats = unsafe_load(reinterpret(Ptr{StatFS}, statfs_ptr)) - total = Int(stats.bsize * stats.blocks) - available = Int(stats.bsize * stats.bavail) + total = stats.bsize * stats.blocks + available = stats.bsize * stats.bavail disk_stats = DiskStat(total, available, total - available) # Cleanup diff --git a/test/file.jl b/test/file.jl index c623c1d2aa120..b67aaf835e6af 100644 --- a/test/file.jl +++ b/test/file.jl @@ -1696,8 +1696,8 @@ end @testset "diskstat() works" begin dstat = diskstat() - # Sanity check assuming disk is smaller than 32TB - TB = 2^41 - @test dstat.total < 32TB + # Sanity check assuming disk is smaller than 32PB + PB = 2^44 + @test dstat.total < 32PB @test dstat.used + dstat.available == dstat.total end From a3f2a0cdcb7125867b0ea2fed3026d6e48650801 Mon Sep 17 00:00:00 2001 From: Malte Sandstede Date: Tue, 26 Oct 2021 13:26:40 -0700 Subject: [PATCH 10/14] PR feedback --- base/file.jl | 53 +++++++++++++++++++++++++--------------------------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/base/file.jl b/base/file.jl index 043c7f7f831de..980260cd37034 100644 --- a/base/file.jl +++ b/base/file.jl @@ -1184,7 +1184,12 @@ end # 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 +""" + DiskStat + +Stores information about the disk in bytes. Populate by calling `diskstat`. +""" +struct DiskStat ftype::UInt64 bsize::UInt64 blocks::UInt64 @@ -1195,26 +1200,21 @@ struct StatFS fspare::NTuple{4, UInt64} # reserved end -function statfs(path::AbstractString) - req = Ref{NTuple{Int(_sizeof_uv_fs), UInt8}}() - err = ccall(:uv_fs_statfs, Cint, (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) - - return unsafe_load(reinterpret(Ptr{StatFS}, statfs_ptr)) +function Base.getproperty(stats::DiskStat, field::Symbol) + total = getfield(stats, :bsize) * getfield(stats, :blocks) + available = getfield(stats, :bsize) * getfield(stats, :bavail) + field === :available && return available + field === :total && return total + field === :used && return total - available + return getfield(stats, field) end -""" - DiskStat - -Stores the total size, available space, and currently used space of the disk in bytes. -Populate by calling `diskstat`. -""" -struct DiskStat - total::UInt64 - available::UInt64 - used::UInt64 +function Base.show(io::IO, x::DiskStat) + print(io, "DiskStat(total: $(x.total), available: $(x.available), used: $(x.used)") + for field in fieldnames(DiskStat)[1:end-1] + print(io, ", $(getfield(x, field))") + end + println(io, ")") end """ @@ -1228,14 +1228,11 @@ working directory are returned. This method was added in Julia 1.8. """ function diskstat(path::AbstractString=pwd()) - stats = statfs(path) - - total = stats.bsize * stats.blocks - available = stats.bsize * stats.bavail - disk_stats = DiskStat(total, available, total - available) - - # Cleanup - uv_fs_req_cleanup(req) + req = zeros(UInt8, _sizeof_uv_fs) + err = ccall(:uv_fs_statfs, Cint, (Ptr{Cvoid}, Ptr{Cvoid}, Cstring, Ptr{Cvoid}), + C_NULL, req, path, C_NULL) + err < 0 && uv_error("diskstat($(repr(path)))", err) + statfs_ptr = ccall(:jl_uv_fs_t_ptr, Ptr{Nothing}, (Ptr{Cvoid},), req) - return disk_stats + return unsafe_load(reinterpret(Ptr{DiskStat}, statfs_ptr)) end From 779c7e64f82131edf357bb4f61bb9a458b6af129 Mon Sep 17 00:00:00 2001 From: Malte Sandstede Date: Thu, 28 Oct 2021 17:23:15 -0700 Subject: [PATCH 11/14] PR feedback --- base/file.jl | 21 ++++++--------------- test/file.jl | 9 +++++++-- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/base/file.jl b/base/file.jl index 980260cd37034..74d5b3b20bc07 100644 --- a/base/file.jl +++ b/base/file.jl @@ -1171,17 +1171,6 @@ function chown(path::AbstractString, owner::Integer, group::Integer=-1) 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) """ @@ -1209,12 +1198,14 @@ function Base.getproperty(stats::DiskStat, field::Symbol) return getfield(stats, field) end +@eval Base.propertynames(stats::DiskStat) = $((fieldnames(DiskStat)[1:end-1]..., :available, :total, :used)) + function Base.show(io::IO, x::DiskStat) - print(io, "DiskStat(total: $(x.total), available: $(x.available), used: $(x.used)") - for field in fieldnames(DiskStat)[1:end-1] - print(io, ", $(getfield(x, field))") + print(io, "DiskStat(") + for field in 1:(nfields(x) - 1) + print(io, "$(getfield(x, field)), ") end - println(io, ")") + println("available: $(x.available), total: $(x.total), used: $(x.used))") end """ diff --git a/test/file.jl b/test/file.jl index 0590c9bcc3d11..915b4122c2718 100644 --- a/test/file.jl +++ b/test/file.jl @@ -1697,10 +1697,15 @@ end end @testset "diskstat() works" begin - dstat = diskstat() - # Sanity check assuming disk is smaller than 32PB PB = 2^44 + + dstat = diskstat() + @test dstat.total < 32PB + @test dstat.used + dstat.available == dstat.total + + # Test diskstat(::AbstractString) + dstat = diskstat(pwd()) @test dstat.total < 32PB @test dstat.used + dstat.available == dstat.total end From d65106d48784ff0323349b180372ee029dfbe268 Mon Sep 17 00:00:00 2001 From: Malte Sandstede Date: Thu, 28 Oct 2021 17:27:31 -0700 Subject: [PATCH 12/14] docs --- NEWS.md | 1 + doc/src/base/file.md | 1 + 2 files changed, 2 insertions(+) diff --git a/NEWS.md b/NEWS.md index 9fca99a785f3a..9c1485888b74c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -50,6 +50,7 @@ New library functions --------------------- * `hardlink(src, dst)` can be used to create hard links. ([#41639]) +* `diskstat(path=pwd())` can be used to return statistics about the disk. ([#42248]) New library features -------------------- diff --git a/doc/src/base/file.md b/doc/src/base/file.md index 40d1cc2ca7ef0..86a1f2bab5dcd 100644 --- a/doc/src/base/file.md +++ b/doc/src/base/file.md @@ -15,6 +15,7 @@ Base.Filesystem.chmod Base.Filesystem.chown Base.RawFD Base.stat +Base.Filesystem.diskstat Base.Filesystem.lstat Base.Filesystem.ctime Base.Filesystem.mtime From e3a48112da966e8bf422f80fdedcda2579011e5b Mon Sep 17 00:00:00 2001 From: Fredrik Ekre Date: Fri, 29 Oct 2021 13:24:46 +0200 Subject: [PATCH 13/14] Apply suggestions from code review --- test/file.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/file.jl b/test/file.jl index 915b4122c2718..3515b7a0a3de9 100644 --- a/test/file.jl +++ b/test/file.jl @@ -1698,12 +1698,12 @@ end @testset "diskstat() works" begin # Sanity check assuming disk is smaller than 32PB - PB = 2^44 + PB = Int64(2)^44 dstat = diskstat() @test dstat.total < 32PB @test dstat.used + dstat.available == dstat.total - + @test occursin(r"^DiskStat\(.*, available: \d+, total: \d+, used: \d+\)$", sprint(show, dstat)) # Test diskstat(::AbstractString) dstat = diskstat(pwd()) @test dstat.total < 32PB From cff7a66ca185a7df88a789242abbce7d1cc11360 Mon Sep 17 00:00:00 2001 From: Fredrik Ekre Date: Fri, 29 Oct 2021 13:25:09 +0200 Subject: [PATCH 14/14] Apply suggestions from code review --- base/file.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/file.jl b/base/file.jl index 74d5b3b20bc07..4cca1f51d0124 100644 --- a/base/file.jl +++ b/base/file.jl @@ -1205,7 +1205,7 @@ function Base.show(io::IO, x::DiskStat) for field in 1:(nfields(x) - 1) print(io, "$(getfield(x, field)), ") end - println("available: $(x.available), total: $(x.total), used: $(x.used))") + print(io, "available: $(x.available), total: $(x.total), used: $(x.used))") end """