blob: c207025b4e88ddbd3ecf14e34c1ed3b824c12fd9 [file] [log] [blame]
//! Key types to index the _MALLCTL NAMESPACE_.
//!
//! The [`Name`] and [`Mib`]/[`MibStr`] types are provided as safe indices into
//! the _MALLCTL NAMESPACE_. These are constructed from null-terminated strings
//! via the [`AsName`] trait. The [`Access`] trait provides provides safe access
//! into the `_MALLCTL NAMESPACE_`.
//!
//! # Example
//!
//! ```
//! #[global_allocator]
//! static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
//!
//! fn main() {
//! use tikv_jemalloc_ctl::{Access, AsName, Name, Mib};
//! use libc::{c_uint, c_char};
//! let name = b"arenas.nbins\0".name();
//! let nbins: c_uint = name.read().unwrap();
//! let mut mib: Mib<[usize; 4]> = b"arenas.bin.0.size\0".name().mib().unwrap();
//! for i in 0..4 {
//! mib[2] = i;
//! let bin_size: usize = mib.read().unwrap();
//! println!("arena bin {} has size {}", i, bin_size);
//! }
//! }
//! ```
#![allow(clippy::uninlined_format_args)]
use crate::error::Result;
use crate::std::str;
use crate::{fmt, ops, raw};
/// A `Name` in the _MALLCTL NAMESPACE_.
#[repr(transparent)]
#[derive(PartialEq, Eq)]
pub struct Name([u8]);
/// Converts a null-terminated byte-string into a [`Name`].
pub trait AsName {
/// Converts a null-terminated byte-string into a [`Name`].
fn name(&self) -> &Name;
}
impl AsName for [u8] {
fn name(&self) -> &Name {
assert!(
!self.is_empty(),
"cannot create Name from empty byte-string"
);
assert_eq!(
*self.last().unwrap(),
b'\0',
"cannot create Name from non-null-terminated byte-string \"{}\"",
str::from_utf8(self).unwrap()
);
unsafe { &*(self as *const Self as *const Name) }
}
}
impl AsName for str {
fn name(&self) -> &Name {
self.as_bytes().name()
}
}
impl Name {
/// Returns the [`Mib`] of `self`.
pub fn mib<T: MibArg>(&self) -> Result<Mib<T>> {
let mut mib: Mib<T> = Mib::default();
raw::name_to_mib(&self.0, mib.0.as_mut())?;
Ok(mib)
}
/// Returns the [`MibStr`] of `self` which is a key whose value is a string.
pub fn mib_str<T: MibArg>(&self) -> Result<MibStr<T>> {
assert!(
self.value_type_str(),
"key \"{}\" does not refer to a string",
self
);
let mut mib: MibStr<T> = MibStr::default();
raw::name_to_mib(&self.0, mib.0.as_mut())?;
Ok(mib)
}
/// Returns `true` if `self` is a key in the _MALLCTL NAMESPCE_ referring to
/// a null-terminated string.
pub fn value_type_str(&self) -> bool {
// remove the null-terminator:
let name = self.0.split_at(self.0.len() - 1).0;
if name.is_empty() {
return false;
}
debug_assert_ne!(*name.last().unwrap(), b'\0');
match name {
b"version"
| b"config.malloc_conf"
| b"opt.metadata_thp"
| b"opt.dss"
| b"opt.percpu_arena"
| b"opt.stats_print_opts"
| b"opt.junk"
| b"opt.thp"
| b"opt.prof_prefix"
| b"thread.prof.name"
| b"prof.dump" => true,
v if v.starts_with(b"arena.") && v.ends_with(b".dss") => true,
v if v.starts_with(b"stats.arenas.") && v.ends_with(b".dss") => {
true
}
_ => false,
}
}
/// Returns the name as null-terminated byte-string.
pub fn as_bytes(&self) -> &'static [u8] {
unsafe { &*(self as *const Self as *const [u8]) }
}
}
impl fmt::Debug for Name {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", str::from_utf8(&self.0).unwrap())
}
}
impl fmt::Display for Name {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", str::from_utf8(&self.0).unwrap())
}
}
/// Management Information Base of a non-string value.
#[repr(transparent)]
#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
pub struct Mib<T: MibArg>(T);
/// Management Information Base of a string value.
#[repr(transparent)]
#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
pub struct MibStr<T: MibArg>(T);
impl<T: MibArg> AsRef<[usize]> for Mib<T> {
fn as_ref(&self) -> &[usize] {
self.0.as_ref()
}
}
impl<T: MibArg> AsMut<[usize]> for Mib<T> {
fn as_mut(&mut self) -> &mut [usize] {
self.0.as_mut()
}
}
impl<T: MibArg> ops::Index<usize> for Mib<T> {
type Output = usize;
fn index(&self, idx: usize) -> &Self::Output {
&self.0.as_ref()[idx]
}
}
impl<T: MibArg> ops::IndexMut<usize> for Mib<T> {
fn index_mut(&mut self, idx: usize) -> &mut Self::Output {
&mut self.0.as_mut()[idx]
}
}
impl<T: MibArg> ops::Index<usize> for MibStr<T> {
type Output = usize;
fn index(&self, idx: usize) -> &Self::Output {
&self.0.as_ref()[idx]
}
}
impl<T: MibArg> ops::IndexMut<usize> for MibStr<T> {
fn index_mut(&mut self, idx: usize) -> &mut Self::Output {
&mut self.0.as_mut()[idx]
}
}
/// Safe read access to the _MALLCTL NAMESPACE_.
pub trait Access<T> {
/// Read the key at `self`.
fn read(&self) -> Result<T>;
/// Write `value` at the key `self`.
fn write(&self, value: T) -> Result<()>;
/// Write `value` at the key `self` returning its previous value.
fn update(&self, value: T) -> Result<T>;
}
macro_rules! impl_access {
($id:ty) => {
impl<T: MibArg> Access<$id> for Mib<T> {
fn read(&self) -> Result<$id> {
unsafe { raw::read_mib(self.0.as_ref()) }
}
fn write(&self, value: $id) -> Result<()> {
unsafe { raw::write_mib(self.0.as_ref(), value) }
}
fn update(&self, value: $id) -> Result<$id> {
unsafe { raw::update_mib(self.0.as_ref(), value) }
}
}
impl Access<$id> for Name {
fn read(&self) -> Result<$id> {
unsafe { raw::read(&self.0) }
}
fn write(&self, value: $id) -> Result<()> {
unsafe { raw::write(&self.0, value) }
}
fn update(&self, value: $id) -> Result<$id> {
unsafe { raw::update(&self.0, value) }
}
}
};
}
impl_access!(u32);
impl_access!(u64);
impl_access!(isize);
impl_access!(usize);
impl<T: MibArg> Access<bool> for Mib<T> {
fn read(&self) -> Result<bool> {
unsafe {
let v: u8 = raw::read_mib(self.0.as_ref())?;
assert!(v == 0 || v == 1);
Ok(v == 1)
}
}
fn write(&self, value: bool) -> Result<()> {
unsafe { raw::write_mib(self.0.as_ref(), value) }
}
fn update(&self, value: bool) -> Result<bool> {
unsafe {
let v: u8 = raw::update_mib(self.0.as_ref(), value as u8)?;
Ok(v == 1)
}
}
}
impl Access<bool> for Name {
fn read(&self) -> Result<bool> {
unsafe {
let v: u8 = raw::read(&self.0)?;
assert!(v == 0 || v == 1);
Ok(v == 1)
}
}
fn write(&self, value: bool) -> Result<()> {
unsafe { raw::write(&self.0, value) }
}
fn update(&self, value: bool) -> Result<bool> {
unsafe {
let v: u8 = raw::update(&self.0, value as u8)?;
Ok(v == 1)
}
}
}
impl<T: MibArg> Access<&'static [u8]> for MibStr<T> {
fn read(&self) -> Result<&'static [u8]> {
// this is safe because the only safe way to construct a `MibStr` is by
// validating that the key refers to a byte-string value
unsafe { raw::read_str_mib(self.0.as_ref()) }
}
fn write(&self, value: &'static [u8]) -> Result<()> {
raw::write_str_mib(self.0.as_ref(), value)
}
fn update(&self, value: &'static [u8]) -> Result<&'static [u8]> {
// this is safe because the only safe way to construct a `MibStr` is by
// validating that the key refers to a byte-string value
unsafe { raw::update_str_mib(self.0.as_ref(), value) }
}
}
impl Access<&'static [u8]> for Name {
fn read(&self) -> Result<&'static [u8]> {
assert!(
self.value_type_str(),
"the name \"{:?}\" does not refer to a byte string",
self
);
// this is safe because the key refers to a byte string:
unsafe { raw::read_str(&self.0) }
}
fn write(&self, value: &'static [u8]) -> Result<()> {
assert!(
self.value_type_str(),
"the name \"{:?}\" does not refer to a byte string",
self
);
raw::write_str(&self.0, value)
}
fn update(&self, value: &'static [u8]) -> Result<&'static [u8]> {
assert!(
self.value_type_str(),
"the name \"{:?}\" does not refer to a byte string",
self
);
// this is safe because the key refers to a byte string:
unsafe { raw::update_str(&self.0, value) }
}
}
impl<T: MibArg> Access<&'static str> for MibStr<T> {
fn read(&self) -> Result<&'static str> {
// this is safe because the only safe way to construct a `MibStr` is by
// validating that the key refers to a byte-string value
let s = unsafe { raw::read_str_mib(self.0.as_ref())? };
Ok(str::from_utf8(s).unwrap())
}
fn write(&self, value: &'static str) -> Result<()> {
raw::write_str_mib(self.0.as_ref(), value.as_bytes())
}
fn update(&self, value: &'static str) -> Result<&'static str> {
// this is safe because the only safe way to construct a `MibStr` is by
// validating that the key refers to a byte-string value
let s =
unsafe { raw::update_str_mib(self.0.as_ref(), value.as_bytes())? };
Ok(str::from_utf8(s).unwrap())
}
}
impl Access<&'static str> for Name {
fn read(&self) -> Result<&'static str> {
assert!(
self.value_type_str(),
"the name \"{:?}\" does not refer to a byte string",
self
);
// this is safe because the key refers to a byte string:
let s = unsafe { raw::read_str(&self.0)? };
Ok(str::from_utf8(s).unwrap())
}
fn write(&self, value: &'static str) -> Result<()> {
assert!(
self.value_type_str(),
"the name \"{:?}\" does not refer to a byte string",
self
);
raw::write_str(&self.0, value.as_bytes())
}
fn update(&self, value: &'static str) -> Result<&'static str> {
assert!(
self.value_type_str(),
"the name \"{:?}\" does not refer to a byte string",
self
);
// this is safe because the key refers to a byte string:
let s = unsafe { raw::update_str(&self.0, value.as_bytes())? };
Ok(str::from_utf8(s).unwrap())
}
}
#[cfg(test)]
mod tests {
use super::{Access, AsName, Mib, MibStr};
#[test]
fn bool_rw() {
let name = b"thread.tcache.enabled\0".name();
let tcache: bool = name.read().unwrap();
let new_tcache = !tcache;
name.write(new_tcache).unwrap();
let mib: Mib<[usize; 3]> = name.mib().unwrap();
let r: bool = mib.read().unwrap();
assert_eq!(r, new_tcache);
}
#[test]
fn u32_r() {
let name = b"arenas.bin.0.nregs\0".name();
let v: u32 = name.read().unwrap();
let mib: Mib<[usize; 4]> = name.mib().unwrap();
let r: u32 = mib.read().unwrap();
assert_eq!(r, v);
}
#[test]
fn size_t_r() {
let name = b"arenas.lextent.0.size\0".name();
let v: libc::size_t = name.read().unwrap();
let mib: Mib<[usize; 4]> = name.mib().unwrap();
let r: libc::size_t = mib.read().unwrap();
assert_eq!(r, v);
}
#[test]
fn ssize_t_rw() {
let name = b"arenas.dirty_decay_ms\0".name();
let v: libc::ssize_t = name.read().unwrap();
name.write(v).unwrap();
let mib: Mib<[usize; 2]> = name.mib().unwrap();
let r: libc::ssize_t = mib.read().unwrap();
assert_eq!(r, v);
}
#[test]
fn u64_rw() {
let name = b"epoch\0".name();
let epoch: u64 = name.read().unwrap();
name.write(epoch).unwrap();
let mib: Mib<[usize; 1]> = name.mib().unwrap();
let epoch: u64 = mib.read().unwrap();
mib.write(epoch).unwrap();
}
#[test]
fn str_rw() {
let name = b"arena.0.dss\0".name();
let dss: &'static [u8] = name.read().unwrap();
name.write(dss).unwrap();
let mib: MibStr<[usize; 3]> = name.mib_str().unwrap();
let dss2: &'static [u8] = mib.read().unwrap();
mib.write(dss2).unwrap();
assert_eq!(dss, dss2);
}
}
pub trait MibArg:
Copy
+ Clone
+ PartialEq
+ Default
+ fmt::Debug
+ AsRef<[usize]>
+ AsMut<[usize]>
{
}
impl<T> MibArg for T where
T: Copy
+ Clone
+ PartialEq
+ Default
+ fmt::Debug
+ AsRef<[usize]>
+ AsMut<[usize]>
{
}