From 81425123ee466de7065d3dc17244fd021bee5699 Mon Sep 17 00:00:00 2001 From: Joonas Koivunen Date: Tue, 21 Mar 2023 12:10:29 +0200 Subject: [PATCH] wip: deserialize and serialize nice bytes this is nice, but fails in tests. looks like: { "Finished": { "before": { "config": { "evict_bytes": "92.23MiB" }, "freed_bytes": "0B" }, "planned": { "respecting_tenant_min_resident_size": { "config": { "evict_bytes": "92.23MiB" }, "freed_bytes": "95.28MiB" }, "fallback_to_global_lru": null }, "assumed": { "projected_after": { "config": { "evict_bytes": "92.23MiB" }, "freed_bytes": "95.28MiB" }, "failed": { "file_sizes": 0, "count": 0 } } } } --- Cargo.lock | 10 ++++++ pageserver/Cargo.toml | 1 + pageserver/src/http/openapi_spec.yml | 6 +++- pageserver/src/http/routes.rs | 52 ++++++++++++++++++++++++++++ 4 files changed, 68 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index ccf5fcef00..3e741dfd48 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2484,6 +2484,7 @@ dependencies = [ "tokio-util", "toml_edit", "tracing", + "ubyte", "url", "utils", "walkdir", @@ -4417,6 +4418,15 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +[[package]] +name = "ubyte" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c81f0dae7d286ad0d9366d7679a77934cfc3cf3a8d67e82669794412b2368fe6" +dependencies = [ + "serde", +] + [[package]] name = "uname" version = "0.1.1" diff --git a/pageserver/Cargo.toml b/pageserver/Cargo.toml index 0bc7eba95e..d51f0eb389 100644 --- a/pageserver/Cargo.toml +++ b/pageserver/Cargo.toml @@ -56,6 +56,7 @@ tokio-postgres.workspace = true tokio-util.workspace = true toml_edit = { workspace = true, features = [ "serde" ] } tracing.workspace = true +ubyte = { version = "0.10.3", features = ["serde"] } url.workspace = true walkdir.workspace = true metrics.workspace = true diff --git a/pageserver/src/http/openapi_spec.yml b/pageserver/src/http/openapi_spec.yml index 8b35c7e9dc..2fa24fe3da 100644 --- a/pageserver/src/http/openapi_spec.yml +++ b/pageserver/src/http/openapi_spec.yml @@ -40,7 +40,11 @@ paths: - evict_bytes properties: evict_bytes: - type: integer + description: Unsigned bytes or a human writable amount of bytes with IEC kibibyte suffixes. + oneOf: + - type: integer + - type: string + pattern: '^[0-9]+ ?(([KMGTP]i)?B)?$' responses: "200": description: | diff --git a/pageserver/src/http/routes.rs b/pageserver/src/http/routes.rs index 375f9090ca..87ca342cee 100644 --- a/pageserver/src/http/routes.rs +++ b/pageserver/src/http/routes.rs @@ -1208,14 +1208,66 @@ async fn disk_usage_eviction_run(mut r: Request) -> Result, #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)] struct Config { /// How many bytes to evict before reporting that pressure is relieved. + #[serde( + deserialize_with = "deserialize_bytes", + serialize_with = "serialize_bytes" + )] evict_bytes: u64, } + fn serialize_bytes(x: &u64, ser: S) -> Result { + use ubyte::ByteUnit; + + let x = ByteUnit::from(*x); + + // ByteUnit has a nice lossy serialization format as it's Display + ser.collect_str(&x) + } + + fn deserialize_bytes<'d, D: serde::Deserializer<'d>>(des: D) -> Result { + struct Visitor; + + impl<'de> serde::de::Visitor<'de> for Visitor { + type Value = u64; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("positive nsigned number of bytes or positive number of bytes with SI/IEC suffix in a string") + } + + fn visit_u64(self, v: u64) -> std::result::Result + where + E: serde::de::Error, + { + if v == 0 { + Err(serde::de::Error::invalid_value( + serde::de::Unexpected::Unsigned(v), + &self, + )) + } else { + Ok(v) + } + } + + fn visit_str(self, v: &str) -> std::result::Result + where + E: serde::de::Error, + { + use std::str::FromStr; + let bytes = ubyte::ByteUnit::from_str(v).map_err(serde::de::Error::custom)?; + let bytes = u64::from(bytes); + self.visit_u64(bytes) + } + } + + des.deserialize_any(Visitor) + } + #[derive(Debug, Clone, Copy, serde::Serialize)] struct Usage { // remains unchanged after instantiation of the struct config: Config, // updated by `add_available_bytes` + #[serde(serialize_with = "serialize_bytes")] freed_bytes: u64, }