Merge pull request #2784 from laytan/fmt-memory-sizes

Add formatting of bytes into the best unit of measurement
This commit is contained in:
gingerBill
2023-09-14 11:00:48 +01:00
committed by GitHub
7 changed files with 133 additions and 2 deletions
+3
View File
@@ -35,6 +35,8 @@ Floating-point, complex numbers, and quaternions:
%F synonym for %f
%h hexadecimal (lower-case) representation with 0h prefix (0h01234abcd)
%H hexadecimal (upper-case) representation with 0H prefix (0h01234ABCD)
%m number of bytes in the best unit of measurement, e.g. 123.45mib
%M number of bytes in the best unit of measurement, e.g. 123.45MiB
String and slice of bytes
%s the uninterpreted bytes of the string or slice
%q a double-quoted string safely escaped with Odin syntax
@@ -85,6 +87,7 @@ Other flags:
add leading 0z for dozenal (%#z)
add leading 0x or 0X for hexadecimal (%#x or %#X)
remove leading 0x for %p (%#p)
add a space between bytes and the unit of measurement (%#m or %#M)
' ' (space) leave a space for elided sign in numbers (% d)
0 pad with leading zeros rather than spaces
+61
View File
@@ -1048,6 +1048,65 @@ _fmt_int_128 :: proc(fi: ^Info, u: u128, base: int, is_signed: bool, bit_size: i
fi.zero = false
_pad(fi, s)
}
// Units of measurements:
__MEMORY_LOWER := " b kib mib gib tib pib eib"
__MEMORY_UPPER := " B KiB MiB GiB TiB PiB EiB"
// Formats an integer value as bytes with the best representation.
//
// Inputs:
// - fi: A pointer to an Info structure
// - u: The integer value to format
// - is_signed: A boolean indicating if the integer is signed
// - bit_size: The bit size of the integer
// - digits: A string containing the digits for formatting
//
_fmt_memory :: proc(fi: ^Info, u: u64, is_signed: bool, bit_size: int, units: string) {
abs, neg := strconv.is_integer_negative(u, is_signed, bit_size)
// Default to a precision of 2, but if less than a kb, 0
prec := fi.prec if (fi.prec_set || abs < mem.Kilobyte) else 2
div, off, unit_len := 1, 0, 1
for n := abs; n >= mem.Kilobyte; n /= mem.Kilobyte {
div *= mem.Kilobyte
off += 4
// First iteration is slightly different because you go from
// units of length 1 to units of length 2.
if unit_len == 1 {
off = 2
unit_len = 3
}
}
// If hash, we add a space between the value and the suffix.
if fi.hash {
unit_len += 1
} else {
off += 1
}
amt := f64(abs) / f64(div)
if neg {
amt = -amt
}
buf: [256]byte
str := strconv.append_float(buf[:], amt, 'f', prec, 64)
// Add the unit at the end.
copy(buf[len(str):], units[off:off+unit_len])
str = string(buf[:len(str)+unit_len])
if !fi.plus {
// Strip sign from "+<value>" but not "+Inf".
if str[0] == '+' && str[1] != 'I' {
str = str[1:]
}
}
_pad(fi, str)
}
// Hex Values:
__DIGITS_LOWER := "0123456789abcdefx"
__DIGITS_UPPER := "0123456789ABCDEFX"
@@ -1096,6 +1155,8 @@ fmt_int :: proc(fi: ^Info, u: u64, is_signed: bool, bit_size: int, verb: rune) {
io.write_string(fi.writer, "U+", &fi.n)
_fmt_int(fi, u, 16, false, bit_size, __DIGITS_UPPER)
}
case 'm': _fmt_memory(fi, u, is_signed, bit_size, __MEMORY_LOWER)
case 'M': _fmt_memory(fi, u, is_signed, bit_size, __MEMORY_UPPER)
case:
fmt_bad_verb(fi, verb)
+1 -1
View File
@@ -24,7 +24,7 @@ main :: proc() {
_main()
for _, leak in track.allocation_map {
fmt.printf("%v leaked %v bytes\n", leak.location, leak.size)
fmt.printf("%v leaked %m\n", leak.location, leak.size)
}
for bad_free in track.bad_free_array {
fmt.printf("%v allocation %p was freed badly\n", bad_free.location, bad_free.memory)
+2
View File
@@ -8,6 +8,8 @@ Kilobyte :: runtime.Kilobyte
Megabyte :: runtime.Megabyte
Gigabyte :: runtime.Gigabyte
Terabyte :: runtime.Terabyte
Petabyte :: runtime.Petabyte
Exabyte :: runtime.Exabyte
set :: proc "contextless" (data: rawptr, value: byte, len: int) -> rawptr {
return runtime.memset(data, i32(value), len)
+2
View File
@@ -337,6 +337,8 @@ Kilobyte :: 1024 * Byte
Megabyte :: 1024 * Kilobyte
Gigabyte :: 1024 * Megabyte
Terabyte :: 1024 * Gigabyte
Petabyte :: 1024 * Terabyte
Exabyte :: 1024 * Petabyte
// Logging stuff
+5 -1
View File
@@ -2,7 +2,8 @@ ODIN=../../odin
PYTHON=$(shell which python3)
all: download_test_assets image_test compress_test strings_test hash_test crypto_test noise_test encoding_test \
math_test linalg_glsl_math_test filepath_test reflect_test os_exit_test i18n_test match_test c_libc_test net_test
math_test linalg_glsl_math_test filepath_test reflect_test os_exit_test i18n_test match_test c_libc_test net_test \
fmt_test
download_test_assets:
$(PYTHON) download_assets.py
@@ -57,3 +58,6 @@ c_libc_test:
net_test:
$(ODIN) run net -out:test_core_net
fmt_test:
$(ODIN) run fmt -out:test_core_fmt
+59
View File
@@ -0,0 +1,59 @@
package test_core_fmt
import "core:fmt"
import "core:os"
import "core:testing"
import "core:mem"
TEST_count := 0
TEST_fail := 0
when ODIN_TEST {
expect :: testing.expect
log :: testing.log
} else {
expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
TEST_count += 1
if !condition {
TEST_fail += 1
fmt.printf("[%v] %v\n", loc, message)
return
}
}
log :: proc(t: ^testing.T, v: any, loc := #caller_location) {
fmt.printf("[%v] ", loc)
fmt.printf("log: %v\n", v)
}
}
main :: proc() {
t := testing.T{}
test_fmt_memory(&t)
fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
if TEST_fail > 0 {
os.exit(1)
}
}
test_fmt_memory :: proc(t: ^testing.T) {
check :: proc(t: ^testing.T, exp: string, format: string, args: ..any, loc := #caller_location) {
got := fmt.tprintf(format, ..args)
expect(t, got == exp, fmt.tprintf("(%q, %v): %q != %q", format, args, got, exp), loc)
}
check(t, "5b", "%m", 5)
check(t, "5B", "%M", 5)
check(t, "-5B", "%M", -5)
check(t, "3.00kib", "%m", mem.Kilobyte * 3)
check(t, "3kib", "%.0m", mem.Kilobyte * 3)
check(t, "3KiB", "%.0M", mem.Kilobyte * 3)
check(t, "3.000 mib", "%#.3m", mem.Megabyte * 3)
check(t, "3.50 gib", "%#m", u32(mem.Gigabyte * 3.5))
check(t, "01tib", "%5.0m", mem.Terabyte)
check(t, "-1tib", "%5.0m", -mem.Terabyte)
check(t, "2.50 pib", "%#5.m", uint(mem.Petabyte * 2.5))
check(t, "1.00 EiB", "%#M", mem.Exabyte)
check(t, "255 B", "%#M", u8(255))
check(t, "0b", "%m", u8(0))
}