From 735cfcd29011ea9188b008e3389aa236529f0e5f Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Fri, 1 Sep 2023 16:21:43 +0200 Subject: [PATCH 1/6] Add formatting of bytes into the best unit of measurement --- core/fmt/doc.odin | 3 ++ core/fmt/fmt.odin | 61 +++++++++++++++++++++++++++++++ core/mem/mem.odin | 2 + core/runtime/core.odin | 2 + tests/core/Makefile | 3 ++ tests/core/fmt/test_core_fmt.odin | 59 ++++++++++++++++++++++++++++++ 6 files changed, 130 insertions(+) create mode 100644 tests/core/fmt/test_core_fmt.odin diff --git a/core/fmt/doc.odin b/core/fmt/doc.odin index 991058661..e758f9638 100644 --- a/core/fmt/doc.odin +++ b/core/fmt/doc.odin @@ -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.45mb + %M number of bytes in the best unit of measurement, e.g. 123.45MB 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 diff --git a/core/fmt/fmt.odin b/core/fmt/fmt.odin index b3b8067e8..fad2548c8 100644 --- a/core/fmt/fmt.odin +++ b/core/fmt/fmt.odin @@ -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 kb mb gb tb pb eb" +__MEMORY_UPPER := " B KB MB GB TB PB EB" +// 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 += 3 + + // 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 = 2 + } + } + + // 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 "+" 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) diff --git a/core/mem/mem.odin b/core/mem/mem.odin index a06579d71..dd985d5dd 100644 --- a/core/mem/mem.odin +++ b/core/mem/mem.odin @@ -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) diff --git a/core/runtime/core.odin b/core/runtime/core.odin index 4f85cf50e..0634f573a 100644 --- a/core/runtime/core.odin +++ b/core/runtime/core.odin @@ -337,6 +337,8 @@ Kilobyte :: 1024 * Byte Megabyte :: 1024 * Kilobyte Gigabyte :: 1024 * Megabyte Terabyte :: 1024 * Gigabyte +Petabyte :: 1024 * Terabyte +Exabyte :: 1024 * Petabyte // Logging stuff diff --git a/tests/core/Makefile b/tests/core/Makefile index 77d4b85a0..81bcdeeb8 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -57,3 +57,6 @@ c_libc_test: net_test: $(ODIN) run net -out:test_core_net + +fmt_test: + $(ODIN) run fmt -out:test_core_fmt diff --git a/tests/core/fmt/test_core_fmt.odin b/tests/core/fmt/test_core_fmt.odin new file mode 100644 index 000000000..e9acfa3a5 --- /dev/null +++ b/tests/core/fmt/test_core_fmt.odin @@ -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.00kb", "%m", mem.Kilobyte * 3) + check(t, "3kb", "%.0m", mem.Kilobyte * 3) + check(t, "3KB", "%.0M", mem.Kilobyte * 3) + check(t, "3.000 mb", "%#.3m", mem.Megabyte * 3) + check(t, "3.50 gb", "%#m", u32(mem.Gigabyte * 3.5)) + check(t, "001tb", "%5.0m", mem.Terabyte) + check(t, "-001tb", "%5.0m", -mem.Terabyte) + check(t, "2.50 pb", "%#5.m", uint(mem.Petabyte * 2.5)) + check(t, "1.00 EB", "%#M", mem.Exabyte) + check(t, "255 B", "%#M", u8(255)) + check(t, "0b", "%m", u8(0)) +} From 4cf176de0b8edfbb1c7c1c63d8e1a35eb20653b5 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Fri, 1 Sep 2023 19:51:53 +0200 Subject: [PATCH 2/6] actually run the test --- tests/core/Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/core/Makefile b/tests/core/Makefile index 81bcdeeb8..919262f85 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -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 From aab2fa1af9a0c65f9ec5a77eef90801ffa7bd4b8 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Fri, 1 Sep 2023 19:53:24 +0200 Subject: [PATCH 3/6] fix test case --- tests/core/fmt/test_core_fmt.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/fmt/test_core_fmt.odin b/tests/core/fmt/test_core_fmt.odin index e9acfa3a5..8928338b3 100644 --- a/tests/core/fmt/test_core_fmt.odin +++ b/tests/core/fmt/test_core_fmt.odin @@ -51,7 +51,7 @@ test_fmt_memory :: proc(t: ^testing.T) { check(t, "3.000 mb", "%#.3m", mem.Megabyte * 3) check(t, "3.50 gb", "%#m", u32(mem.Gigabyte * 3.5)) check(t, "001tb", "%5.0m", mem.Terabyte) - check(t, "-001tb", "%5.0m", -mem.Terabyte) + check(t, "-01tb", "%5.0m", -mem.Terabyte) check(t, "2.50 pb", "%#5.m", uint(mem.Petabyte * 2.5)) check(t, "1.00 EB", "%#M", mem.Exabyte) check(t, "255 B", "%#M", u8(255)) From 93caf5b31120dec6739c098207f5b00ad7aed299 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Fri, 1 Sep 2023 22:05:04 +0200 Subject: [PATCH 4/6] use memory formatting in tracking allocator doc --- core/mem/doc.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/mem/doc.odin b/core/mem/doc.odin index 295a69e96..e232428c2 100644 --- a/core/mem/doc.odin +++ b/core/mem/doc.odin @@ -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) From 287beaff35479bd25aa2fb7a76eb779a3f0761c8 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Tue, 12 Sep 2023 16:49:42 +0200 Subject: [PATCH 5/6] use KiB etc. instead of KB --- core/fmt/fmt.odin | 8 ++++---- tests/core/fmt/test_core_fmt.odin | 28 ++++++++++++++-------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/core/fmt/fmt.odin b/core/fmt/fmt.odin index fad2548c8..ab57209c3 100644 --- a/core/fmt/fmt.odin +++ b/core/fmt/fmt.odin @@ -1049,8 +1049,8 @@ _fmt_int_128 :: proc(fi: ^Info, u: u128, base: int, is_signed: bool, bit_size: i _pad(fi, s) } // Units of measurements: -__MEMORY_LOWER := " b kb mb gb tb pb eb" -__MEMORY_UPPER := " B KB MB GB TB PB EB" +__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: @@ -1069,13 +1069,13 @@ _fmt_memory :: proc(fi: ^Info, u: u64, is_signed: bool, bit_size: int, units: st div, off, unit_len := 1, 0, 1 for n := abs; n >= mem.Kilobyte; n /= mem.Kilobyte { div *= mem.Kilobyte - off += 3 + 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 = 2 + unit_len = 3 } } diff --git a/tests/core/fmt/test_core_fmt.odin b/tests/core/fmt/test_core_fmt.odin index 8928338b3..4459af609 100644 --- a/tests/core/fmt/test_core_fmt.odin +++ b/tests/core/fmt/test_core_fmt.odin @@ -42,18 +42,18 @@ test_fmt_memory :: proc(t: ^testing.T) { 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.00kb", "%m", mem.Kilobyte * 3) - check(t, "3kb", "%.0m", mem.Kilobyte * 3) - check(t, "3KB", "%.0M", mem.Kilobyte * 3) - check(t, "3.000 mb", "%#.3m", mem.Megabyte * 3) - check(t, "3.50 gb", "%#m", u32(mem.Gigabyte * 3.5)) - check(t, "001tb", "%5.0m", mem.Terabyte) - check(t, "-01tb", "%5.0m", -mem.Terabyte) - check(t, "2.50 pb", "%#5.m", uint(mem.Petabyte * 2.5)) - check(t, "1.00 EB", "%#M", mem.Exabyte) - check(t, "255 B", "%#M", u8(255)) - check(t, "0b", "%m", u8(0)) + 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)) } From d47b0eeee78961f3bc0f785337dfb4674e9092fd Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Tue, 12 Sep 2023 16:51:21 +0200 Subject: [PATCH 6/6] update doc --- core/fmt/doc.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/fmt/doc.odin b/core/fmt/doc.odin index e758f9638..597342e76 100644 --- a/core/fmt/doc.odin +++ b/core/fmt/doc.odin @@ -35,8 +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.45mb - %M number of bytes in the best unit of measurement, e.g. 123.45MB + %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