Add /memuse command
This commit is contained in:
@ -46,6 +46,7 @@ lazy_static = "1.4.0"
|
||||
lender = "0.2.1"
|
||||
libc = "0.2.148"
|
||||
log = "0.4.20"
|
||||
memuse = "0.2.1"
|
||||
nonempty = { version = "0.8.1", features = ["serialize"] }
|
||||
signal-hook = "0.3.17"
|
||||
smallvec = "1.11.1"
|
||||
|
@ -19,6 +19,7 @@ use std::sync::Arc;
|
||||
use std::sync::RwLock;
|
||||
|
||||
use matrix_sdk::async_trait;
|
||||
use memuse::DynamicUsage;
|
||||
use ratatui::text::Text;
|
||||
use tokio::sync::mpsc::UnboundedReceiver;
|
||||
use tracing_error::ErrorLayer;
|
||||
@ -44,6 +45,15 @@ impl LogBuffer {
|
||||
}
|
||||
}
|
||||
|
||||
impl DynamicUsage for LogBuffer {
|
||||
fn dynamic_usage(&self) -> usize {
|
||||
self.lines.dynamic_usage()
|
||||
}
|
||||
fn dynamic_usage_bounds(&self) -> (usize, Option<usize>) {
|
||||
self.lines.dynamic_usage_bounds()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Buffer for LogBuffer {
|
||||
fn short_name(&self) -> String {
|
||||
|
@ -39,7 +39,7 @@ pub struct BufferItem<'buf> {
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait Buffer: Send + Sync {
|
||||
pub trait Buffer: Send + Sync + memuse::DynamicUsage {
|
||||
/// A short human-readable name for the room, eg. to show in compact buflist
|
||||
fn short_name(&self) -> String;
|
||||
/// If this is a room buffer, returns the associated room id.
|
||||
@ -53,6 +53,13 @@ pub trait Buffer: Send + Sync {
|
||||
///
|
||||
/// This should return immediately, not waiting for anything to be loaded.
|
||||
fn request_back_pagination(&self, num: u16) {}
|
||||
|
||||
fn dynamic_usage(&self) -> usize {
|
||||
memuse::DynamicUsage::dynamic_usage(self)
|
||||
}
|
||||
fn dynamic_usage_bounds(&self) -> (usize, Option<usize>) {
|
||||
memuse::DynamicUsage::dynamic_usage_bounds(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Buffers {
|
||||
|
@ -30,6 +30,7 @@ use matrix_sdk_ui::timeline::{
|
||||
BackPaginationStatus, PaginationOptions, RoomExt, Timeline, TimelineItem, TimelineItemKind,
|
||||
VirtualTimelineItem,
|
||||
};
|
||||
use memuse::DynamicUsage;
|
||||
use ratatui::text::Text;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
@ -44,6 +45,31 @@ pub enum OwnedBufferItemContent {
|
||||
Empty,
|
||||
}
|
||||
|
||||
impl DynamicUsage for OwnedBufferItemContent {
|
||||
fn dynamic_usage(&self) -> usize {
|
||||
std::mem::size_of::<Self>()
|
||||
+ match self {
|
||||
OwnedBufferItemContent::Text(s) | OwnedBufferItemContent::Divider(s) => s.dynamic_usage(),
|
||||
OwnedBufferItemContent::Empty => 0,
|
||||
}
|
||||
}
|
||||
fn dynamic_usage_bounds(&self) -> (usize, Option<usize>) {
|
||||
let (min, max) = match self {
|
||||
OwnedBufferItemContent::Text(s) | OwnedBufferItemContent::Divider(s) => {
|
||||
s.dynamic_usage_bounds()
|
||||
},
|
||||
OwnedBufferItemContent::Empty => (0, Some(0)),
|
||||
};
|
||||
match max {
|
||||
Some(max) => (
|
||||
min + std::mem::size_of::<Self>(),
|
||||
Some(max + std::mem::size_of::<Self>()),
|
||||
),
|
||||
None => (min + std::mem::size_of::<Self>(), None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Like `format!()` but returns OwnedBufferItemContent::Text
|
||||
macro_rules! text {
|
||||
($($tokens:tt)*) => {
|
||||
@ -61,6 +87,29 @@ pub struct SingleClientRoomBuffer {
|
||||
back_pagination_request: AtomicU16,
|
||||
}
|
||||
|
||||
impl DynamicUsage for SingleClientRoomBuffer {
|
||||
fn dynamic_usage(&self) -> usize {
|
||||
std::mem::size_of::<Self>()
|
||||
+ self
|
||||
.items
|
||||
.iter()
|
||||
.map(|(content, prerender)| content.dynamic_usage() + prerender.dynamic_usage())
|
||||
.sum::<usize>()
|
||||
}
|
||||
fn dynamic_usage_bounds(&self) -> (usize, Option<usize>) {
|
||||
let self_usage = std::mem::size_of::<Self>();
|
||||
crate::utils::sum_memory_bounds(
|
||||
self_usage,
|
||||
Some(self_usage),
|
||||
self
|
||||
.items
|
||||
.iter()
|
||||
.map(|item| item.0.dynamic_usage_bounds())
|
||||
.chain(self.items.iter().map(|item| item.1.dynamic_usage_bounds())),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl SingleClientRoomBuffer {
|
||||
async fn poll_updates(&mut self) {
|
||||
let back_pagination_request = self.back_pagination_request.swap(0, Ordering::Relaxed);
|
||||
@ -271,10 +320,23 @@ pub struct RoomBuffer {
|
||||
buffers: SmallVec<[SingleClientRoomBuffer; 1]>,
|
||||
}
|
||||
|
||||
fn f(b: RoomBuffer) {
|
||||
g(b);
|
||||
impl DynamicUsage for RoomBuffer {
|
||||
fn dynamic_usage(&self) -> usize {
|
||||
std::mem::size_of::<Self>()
|
||||
+ self
|
||||
.buffers
|
||||
.iter()
|
||||
.map(|buf| buf.dynamic_usage())
|
||||
.sum::<usize>()
|
||||
}
|
||||
fn dynamic_usage_bounds(&self) -> (usize, Option<usize>) {
|
||||
crate::utils::sum_memory_bounds(
|
||||
std::mem::size_of::<Self>(),
|
||||
Some(std::mem::size_of::<Self>()),
|
||||
self.buffers.iter().map(|buf| buf.dynamic_usage_bounds()),
|
||||
)
|
||||
}
|
||||
}
|
||||
fn g<B: Send>(b: B) {}
|
||||
|
||||
impl RoomBuffer {
|
||||
pub async fn new(initial_client: Client, room_id: OwnedRoomId) -> Result<Self> {
|
||||
|
@ -22,7 +22,8 @@ use tokio::sync::mpsc;
|
||||
|
||||
use crate::action::Action;
|
||||
|
||||
pub type CommandHandler = Box<dyn Fn(&str, &str, &mpsc::UnboundedSender<Action>) -> Result<()>>;
|
||||
pub type CommandHandler =
|
||||
Box<dyn Fn(&str, &str, &crate::App, &mpsc::UnboundedSender<Action>) -> Result<()>>;
|
||||
|
||||
pub trait RataCommand {
|
||||
fn name(&self) -> String;
|
||||
@ -74,7 +75,7 @@ pub fn run_command(
|
||||
};
|
||||
|
||||
match app.commands.0.get(&command_name.to_ascii_lowercase()) {
|
||||
Some(command) => command.handler()(command_name, args, action_tx),
|
||||
Some(command) => command.handler()(command_name, args, app, action_tx),
|
||||
None => bail!("Unknown command /{}", command_name),
|
||||
}
|
||||
}
|
||||
|
@ -22,10 +22,10 @@ use crate::commands::{CommandHandler, RataCommand};
|
||||
use crate::plugins::{Plugin, PluginGetter, PrePlugin};
|
||||
|
||||
inventory::submit! {
|
||||
PluginGetter(get_core)
|
||||
PluginGetter(get_plugin)
|
||||
}
|
||||
|
||||
fn get_core() -> Result<Box<dyn PrePlugin>> {
|
||||
fn get_plugin() -> Result<Box<dyn PrePlugin>> {
|
||||
Ok(Box::new(Core {}))
|
||||
}
|
||||
|
||||
@ -33,7 +33,7 @@ struct Core {}
|
||||
|
||||
impl PrePlugin for Core {
|
||||
fn name(&self) -> String {
|
||||
"core".to_owned()
|
||||
"Core".to_owned()
|
||||
}
|
||||
|
||||
fn help(&self) -> String {
|
||||
@ -88,7 +88,7 @@ impl RataCommand for ActionCommand {
|
||||
}
|
||||
fn handler(&self) -> CommandHandler {
|
||||
let action = self.action.clone();
|
||||
Box::new(move |_name, _args, action_tx| {
|
||||
Box::new(move |_name, _args, _app, action_tx| {
|
||||
action_tx
|
||||
.send(action.clone())
|
||||
.context("Could not queue action")
|
||||
|
120
src/plugins/memuse.rs
Normal file
120
src/plugins/memuse.rs
Normal file
@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Valentin Lorentz
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use color_eyre::eyre::{Result, WrapErr};
|
||||
use itertools::Itertools;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
use crate::action::Action;
|
||||
use crate::commands::{CommandHandler, RataCommand};
|
||||
use crate::plugins::{Plugin, PluginGetter, PrePlugin};
|
||||
|
||||
inventory::submit! {
|
||||
PluginGetter(get_plugin)
|
||||
}
|
||||
|
||||
fn get_plugin() -> Result<Box<dyn PrePlugin>> {
|
||||
Ok(Box::new(Memuse {}))
|
||||
}
|
||||
|
||||
struct Memuse {}
|
||||
|
||||
impl PrePlugin for Memuse {
|
||||
fn name(&self) -> String {
|
||||
"Memuse".to_owned()
|
||||
}
|
||||
|
||||
fn help(&self) -> String {
|
||||
"estimates memory usage".to_owned()
|
||||
}
|
||||
|
||||
fn load(self: Box<Self>, app: &mut crate::App) -> Result<Box<dyn Plugin>> {
|
||||
app.commands.register(Box::new(MemuseCommand {}));
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Plugin for Memuse {}
|
||||
|
||||
struct MemuseCommand {}
|
||||
|
||||
impl RataCommand for MemuseCommand {
|
||||
fn name(&self) -> String {
|
||||
"memuse".to_owned()
|
||||
}
|
||||
fn help(&self) -> String {
|
||||
"returns memory usage estimates".to_owned()
|
||||
}
|
||||
fn handler(&self) -> CommandHandler {
|
||||
Box::new(move |_name, _args, app, action_tx| {
|
||||
/*
|
||||
let client_usage = app
|
||||
.clients
|
||||
.iter()
|
||||
.map(|client| format_mem_usage(client.user_id().map(|user_id| user_id.as_str()).unwrap_or("<INITIALIZING>"), client))
|
||||
.join("\n");*/
|
||||
let client_usage = "todo";
|
||||
let buffer_usage = app
|
||||
.buffers
|
||||
.iter()
|
||||
.map(|buffer| format_mem_usage2(&buffer.short_name(), buffer))
|
||||
.join("\n");
|
||||
tracing::info!(
|
||||
"Client memory usage (matrix-sdk):\n{}\nBuffer memory usage:\n{}",
|
||||
client_usage,
|
||||
buffer_usage
|
||||
);
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn format_mem_usage<T: memuse::DynamicUsage>(name: &str, value: T) -> String {
|
||||
match value.dynamic_usage_bounds() {
|
||||
(min, Some(max)) => format!(
|
||||
"* {}: {} to {} bytes, approximately {}",
|
||||
name,
|
||||
min,
|
||||
max,
|
||||
value.dynamic_usage()
|
||||
),
|
||||
(min, None) => format!(
|
||||
"* {}: approximately {} bytes but no less than {}",
|
||||
name,
|
||||
min,
|
||||
value.dynamic_usage()
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Boo trait objects
|
||||
fn format_mem_usage2(name: &str, value: &dyn crate::buffers::Buffer) -> String {
|
||||
match crate::buffers::Buffer::dynamic_usage_bounds(value) {
|
||||
(min, Some(max)) => format!(
|
||||
"* {}: {} to {} bytes, approximately {}",
|
||||
name,
|
||||
min,
|
||||
max,
|
||||
crate::buffers::Buffer::dynamic_usage(value)
|
||||
),
|
||||
(min, None) => format!(
|
||||
"* {}: approximately {} bytes but no less than {}",
|
||||
name,
|
||||
min,
|
||||
crate::buffers::Buffer::dynamic_usage(value)
|
||||
),
|
||||
}
|
||||
}
|
@ -19,6 +19,7 @@ use color_eyre::eyre::{Result, WrapErr};
|
||||
use crate::App;
|
||||
|
||||
pub mod core;
|
||||
pub mod memuse;
|
||||
|
||||
/// A [`Plugin`] that is not initialized yet
|
||||
pub trait PrePlugin {
|
||||
|
23
src/utils.rs
23
src/utils.rs
@ -148,3 +148,26 @@ Config directory: {config_dir_path}
|
||||
Data directory: {data_dir_path}"
|
||||
)
|
||||
}
|
||||
|
||||
pub fn sum_memory_bounds(
|
||||
min: usize,
|
||||
max: Option<usize>,
|
||||
bounds: impl Iterator<Item = (usize, Option<usize>)>,
|
||||
) -> (usize, Option<usize>) {
|
||||
let mut total_min = min;
|
||||
let mut total_max = max;
|
||||
for item in bounds {
|
||||
match item {
|
||||
(min, Some(max)) => {
|
||||
total_min += min;
|
||||
total_max.as_mut().map(|total_max| *total_max += max);
|
||||
},
|
||||
(min, None) => {
|
||||
total_min += min;
|
||||
total_max = None;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
(total_min, total_max)
|
||||
}
|
||||
|
66
src/widgets/divider.rs
Normal file
66
src/widgets/divider.rs
Normal file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Valentin Lorentz
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use crate::widgets::OverlappableWidget;
|
||||
use ratatui::prelude::*;
|
||||
use ratatui::widgets::*;
|
||||
|
||||
pub struct Divider<'a> {
|
||||
pub line_type: BorderType,
|
||||
pub text: Paragraph<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Divider<'a> {
|
||||
pub fn new(text: Paragraph<'a>) -> Self {
|
||||
Divider {
|
||||
line_type: BorderType::default(),
|
||||
text,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> OverlappableWidget for Divider<'a> {
|
||||
fn height(&self, width: u16) -> u64 {
|
||||
1
|
||||
}
|
||||
|
||||
fn render_overlap(
|
||||
self,
|
||||
area: ratatui::layout::Rect,
|
||||
buf: &mut ratatui::buffer::Buffer,
|
||||
) -> (u16, u16) {
|
||||
if area.height == 0 {
|
||||
return (area.width, 0);
|
||||
}
|
||||
let symbol = BorderType::border_symbols(self.line_type).horizontal_top;
|
||||
for x in 0..area.width {
|
||||
buf
|
||||
.get_mut(area.x + x, area.y + area.height - 1)
|
||||
.set_symbol(symbol);
|
||||
}
|
||||
|
||||
self.text.render(
|
||||
Rect {
|
||||
y: area.y + area.height - 1,
|
||||
height: 1,
|
||||
..area
|
||||
},
|
||||
buf,
|
||||
);
|
||||
|
||||
(area.width, 1)
|
||||
}
|
||||
}
|
@ -16,6 +16,8 @@
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use memuse::DynamicUsage;
|
||||
|
||||
/// Storage for the result of pre-computations by the UI, with interior mutability
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct PrerenderInner {
|
||||
@ -44,3 +46,23 @@ impl Prerender {
|
||||
self.0.lock().unwrap().as_ref().map(|inner| inner.key)
|
||||
}
|
||||
}
|
||||
|
||||
impl DynamicUsage for Prerender {
|
||||
fn dynamic_usage(&self) -> usize {
|
||||
std::mem::size_of::<Self>()
|
||||
+ match self.0.lock().unwrap().as_ref() {
|
||||
Some(PrerenderInner {
|
||||
key: _,
|
||||
value: PrerenderValue::Rendered(buf),
|
||||
}) => {
|
||||
(buf.area().height as usize)
|
||||
* (buf.area().width as usize)
|
||||
* std::mem::size_of::<ratatui::buffer::Cell>()
|
||||
},
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
fn dynamic_usage_bounds(&self) -> (usize, Option<usize>) {
|
||||
(self.dynamic_usage(), Some(self.dynamic_usage()))
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user