Skip to content

Commit

Permalink
Merge pull request #2784 from laytan/fmt-memory-sizes
Browse files Browse the repository at this point in the history
Add formatting of bytes into the best unit of measurement
  • Loading branch information
gingerBill authored Sep 14, 2023
2 parents d928f42 + d47b0ee commit 6e49b1c
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 2 deletions.
3 changes: 3 additions & 0 deletions core/fmt/doc.odin
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
61 changes: 61 additions & 0 deletions core/fmt/fmt.odin
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion core/mem/doc.odin
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions core/mem/mem.odin
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions core/runtime/core.odin
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,8 @@ Kilobyte :: 1024 * Byte
Megabyte :: 1024 * Kilobyte
Gigabyte :: 1024 * Megabyte
Terabyte :: 1024 * Gigabyte
Petabyte :: 1024 * Terabyte
Exabyte :: 1024 * Petabyte

// Logging stuff

Expand Down
6 changes: 5 additions & 1 deletion tests/core/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 changes: 59 additions & 0 deletions tests/core/fmt/test_core_fmt.odin
Original file line number Diff line number Diff line change
@@ -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))
}

0 comments on commit 6e49b1c

Please sign in to comment.