mirror of
https://github.com/DragonOS-Community/DragonOS.git
synced 2025-06-18 08:06:32 +00:00
增加内存分配日志监视器 (#424)
* 完成内存日志监视,并输出日志到文件 * 修复进程退出后,procfs查看进程status文件会崩溃的问题 * 修复signal唤醒进程的判断条件问题
This commit is contained in:
1
tools/debugging/logmonitor/.gitignore
vendored
Normal file
1
tools/debugging/logmonitor/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/logs/
|
17
tools/debugging/logmonitor/Cargo.toml
Normal file
17
tools/debugging/logmonitor/Cargo.toml
Normal file
@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "logmonitor"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
klog_types = { path = "../../../kernel/crates/klog_types" }
|
||||
crossterm = "0.26.1"
|
||||
ratatui = "0.24.0"
|
||||
clap = { version = "4.4.7", features = ["color", "error-context", "help", "std", "suggestions", "usage", "derive"] }
|
||||
rand = "0.8.5"
|
||||
goblin = "0.7.1"
|
||||
simple_logger = { git = "https://git.mirrors.dragonos.org/DragonOS-Community/rust-simple_logger.git", "rev" = "36ab404868" }
|
||||
log = "0.4.20"
|
||||
lazy_static = "1.4.0"
|
10
tools/debugging/logmonitor/README.md
Normal file
10
tools/debugging/logmonitor/README.md
Normal file
@ -0,0 +1,10 @@
|
||||
# 日志监视程序
|
||||
|
||||
本程序监视DragonOS内核的环形缓冲区日志,并将其显示在屏幕上。
|
||||
|
||||
|
||||
## 使用方法
|
||||
|
||||
1. 默认情况下,DragonOS内核已启用内存分配器的日志记录。
|
||||
2. 当qemu启动后,在DragonOS项目的根目录中,运行`make log-monitor`。
|
||||
3. 在`logs`目录查看日志文件。
|
212
tools/debugging/logmonitor/src/app.rs
Normal file
212
tools/debugging/logmonitor/src/app.rs
Normal file
@ -0,0 +1,212 @@
|
||||
use std::error;
|
||||
|
||||
use rand::{distributions::Uniform, prelude::Distribution, rngs::ThreadRng};
|
||||
use ratatui::widgets::ListState;
|
||||
|
||||
/// Application result type.
|
||||
pub type AppResult<T> = std::result::Result<T, Box<dyn error::Error>>;
|
||||
|
||||
/// Application.
|
||||
#[derive(Debug)]
|
||||
pub struct App<'a> {
|
||||
/// APP的标题
|
||||
pub title: &'a str,
|
||||
/// Is the application running?
|
||||
pub running: bool,
|
||||
|
||||
pub enhanced_graphics: bool,
|
||||
|
||||
/// counter
|
||||
pub counter: u8,
|
||||
|
||||
pub tabs: TabsState<'a>,
|
||||
|
||||
pub memory_log_sparkline: Signal<RandomSignal>,
|
||||
|
||||
logs: Vec<String>,
|
||||
pub stateful_logs: StatefulList<(&'a str, &'a str)>,
|
||||
|
||||
backend_log_receiver: Option<std::sync::mpsc::Receiver<String>>,
|
||||
}
|
||||
|
||||
impl<'a> App<'a> {
|
||||
/// Constructs a new instance of [`App`].
|
||||
pub fn new(title: &'a str) -> Self {
|
||||
let mut rand_signal = RandomSignal::new(0, 100);
|
||||
let sparkline_points = rand_signal.by_ref().take(300).collect();
|
||||
let sparkline = Signal {
|
||||
source: rand_signal,
|
||||
points: sparkline_points,
|
||||
tick_rate: 1,
|
||||
};
|
||||
|
||||
Self {
|
||||
title,
|
||||
running: true,
|
||||
enhanced_graphics: true,
|
||||
counter: 0,
|
||||
tabs: TabsState::new(vec!["Tab0", "Tab1", "Tab2"]),
|
||||
memory_log_sparkline: sparkline,
|
||||
logs: Vec::new(),
|
||||
stateful_logs: StatefulList::with_items(vec![]),
|
||||
backend_log_receiver: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_backend_log_receiver(&mut self, receiver: std::sync::mpsc::Receiver<String>) {
|
||||
self.backend_log_receiver = Some(receiver);
|
||||
}
|
||||
|
||||
/// Handles the tick event of the terminal.
|
||||
pub fn tick(&mut self) {
|
||||
self.memory_log_sparkline.on_tick();
|
||||
self.handle_logs_on_tick();
|
||||
}
|
||||
|
||||
/// 当到达tick时,处理日志
|
||||
fn handle_logs_on_tick(&mut self) {
|
||||
let logs_to_push = self
|
||||
.backend_log_receiver
|
||||
.as_ref()
|
||||
.map(|rv| rv.try_iter().collect::<Vec<String>>());
|
||||
|
||||
if let Some(logs) = logs_to_push {
|
||||
for log in logs {
|
||||
self.push_log(log);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Set running to false to quit the application.
|
||||
pub fn quit(&mut self) {
|
||||
self.running = false;
|
||||
}
|
||||
|
||||
pub fn increment_counter(&mut self) {
|
||||
if let Some(res) = self.counter.checked_add(1) {
|
||||
self.counter = res;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decrement_counter(&mut self) {
|
||||
if let Some(res) = self.counter.checked_sub(1) {
|
||||
self.counter = res;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push_log(&mut self, log: String) {
|
||||
self.logs.push(log);
|
||||
}
|
||||
|
||||
pub fn logs(&self) -> &Vec<String> {
|
||||
&self.logs
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TabsState<'a> {
|
||||
pub titles: Vec<&'a str>,
|
||||
pub index: usize,
|
||||
}
|
||||
|
||||
impl<'a> TabsState<'a> {
|
||||
pub fn new(titles: Vec<&'a str>) -> TabsState {
|
||||
TabsState { titles, index: 0 }
|
||||
}
|
||||
pub fn next(&mut self) {
|
||||
self.index = (self.index + 1) % self.titles.len();
|
||||
}
|
||||
|
||||
pub fn previous(&mut self) {
|
||||
if self.index > 0 {
|
||||
self.index -= 1;
|
||||
} else {
|
||||
self.index = self.titles.len() - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Signal<S: Iterator> {
|
||||
source: S,
|
||||
pub points: Vec<S::Item>,
|
||||
tick_rate: usize,
|
||||
}
|
||||
|
||||
impl<S> Signal<S>
|
||||
where
|
||||
S: Iterator,
|
||||
{
|
||||
fn on_tick(&mut self) {
|
||||
for _ in 0..self.tick_rate {
|
||||
self.points.remove(0);
|
||||
}
|
||||
self.points
|
||||
.extend(self.source.by_ref().take(self.tick_rate));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RandomSignal {
|
||||
distribution: Uniform<u64>,
|
||||
rng: ThreadRng,
|
||||
}
|
||||
|
||||
impl RandomSignal {
|
||||
pub fn new(lower: u64, upper: u64) -> RandomSignal {
|
||||
RandomSignal {
|
||||
distribution: Uniform::new(lower, upper),
|
||||
rng: rand::thread_rng(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for RandomSignal {
|
||||
type Item = u64;
|
||||
fn next(&mut self) -> Option<u64> {
|
||||
Some(self.distribution.sample(&mut self.rng))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct StatefulList<T> {
|
||||
pub state: ListState,
|
||||
pub items: Vec<T>,
|
||||
}
|
||||
|
||||
impl<T> StatefulList<T> {
|
||||
pub fn with_items(items: Vec<T>) -> StatefulList<T> {
|
||||
StatefulList {
|
||||
state: ListState::default(),
|
||||
items,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next(&mut self) {
|
||||
let i = match self.state.selected() {
|
||||
Some(i) => {
|
||||
if i >= self.items.len() - 1 {
|
||||
0
|
||||
} else {
|
||||
i + 1
|
||||
}
|
||||
}
|
||||
None => 0,
|
||||
};
|
||||
self.state.select(Some(i));
|
||||
}
|
||||
|
||||
pub fn previous(&mut self) {
|
||||
let i = match self.state.selected() {
|
||||
Some(i) => {
|
||||
if i == 0 {
|
||||
self.items.len() - 1
|
||||
} else {
|
||||
i - 1
|
||||
}
|
||||
}
|
||||
None => 0,
|
||||
};
|
||||
self.state.select(Some(i));
|
||||
}
|
||||
}
|
38
tools/debugging/logmonitor/src/backend/error.rs
Normal file
38
tools/debugging/logmonitor/src/backend/error.rs
Normal file
@ -0,0 +1,38 @@
|
||||
use std::{error::Error, fmt::Display};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum BackendErrorKind {
|
||||
FileNotFound,
|
||||
KernelLoadError,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BackendError {
|
||||
kind: BackendErrorKind,
|
||||
message: Option<String>,
|
||||
}
|
||||
|
||||
impl BackendError {
|
||||
pub fn new(kind: BackendErrorKind, message: Option<String>) -> Self {
|
||||
Self { kind, message }
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for BackendError {}
|
||||
|
||||
impl Display for BackendError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match &self.kind {
|
||||
BackendErrorKind::FileNotFound => {
|
||||
write!(f, "File not found: {:?}", self.message.as_ref().unwrap())
|
||||
}
|
||||
BackendErrorKind::KernelLoadError => {
|
||||
write!(
|
||||
f,
|
||||
"Failed to load kernel: {:?}",
|
||||
self.message.as_ref().unwrap()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
12
tools/debugging/logmonitor/src/backend/event.rs
Normal file
12
tools/debugging/logmonitor/src/backend/event.rs
Normal file
@ -0,0 +1,12 @@
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum BackendEvent {
|
||||
StartUp(StartUpEvent),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[allow(dead_code)]
|
||||
pub struct StartUpEvent {
|
||||
started: bool,
|
||||
message: String,
|
||||
timestamp: std::time::Instant,
|
||||
}
|
122
tools/debugging/logmonitor/src/backend/loader.rs
Normal file
122
tools/debugging/logmonitor/src/backend/loader.rs
Normal file
@ -0,0 +1,122 @@
|
||||
use std::{ops::Deref, path::PathBuf};
|
||||
|
||||
use goblin::elf::Sym;
|
||||
use log::info;
|
||||
|
||||
use crate::app::AppResult;
|
||||
|
||||
use super::error::{BackendError, BackendErrorKind};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct KernelLoader;
|
||||
|
||||
impl KernelLoader {
|
||||
pub fn load(kernel: &PathBuf) -> AppResult<KernelMetadata> {
|
||||
info!("Loading kernel: {:?}", kernel);
|
||||
let kernel_bytes = std::fs::read(kernel)?;
|
||||
let elf = goblin::elf::Elf::parse(&kernel_bytes).map_err(|e| {
|
||||
BackendError::new(
|
||||
BackendErrorKind::KernelLoadError,
|
||||
Some(format!("Failed to load kernel: {:?}", e)),
|
||||
)
|
||||
})?;
|
||||
let mut result = KernelMetadata::new(kernel.clone());
|
||||
|
||||
info!("Parsing symbols...");
|
||||
for sym in elf.syms.iter() {
|
||||
let name = elf.strtab.get_at(sym.st_name).unwrap_or("");
|
||||
result.add_symbol(sym.clone(), name.to_string());
|
||||
}
|
||||
info!("Parsed {} symbols", result.symbols().len());
|
||||
info!("Loaded kernel: {:?}", kernel);
|
||||
return Ok(result);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct KernelMetadata {
|
||||
pub kernel: PathBuf,
|
||||
sym_collection: SymbolCollection,
|
||||
}
|
||||
|
||||
impl KernelMetadata {
|
||||
pub fn new(kernel: PathBuf) -> Self {
|
||||
Self {
|
||||
kernel,
|
||||
sym_collection: SymbolCollection::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn symbols(&self) -> &[Symbol] {
|
||||
&self.sym_collection.symbols
|
||||
}
|
||||
|
||||
pub fn sym_collection(&self) -> &SymbolCollection {
|
||||
&self.sym_collection
|
||||
}
|
||||
|
||||
pub fn add_symbol(&mut self, sym: Sym, name: String) {
|
||||
self.sym_collection.add_symbol(sym, name);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SymbolCollection {
|
||||
symbols: Vec<Symbol>,
|
||||
}
|
||||
|
||||
impl SymbolCollection {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
symbols: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_symbol(&mut self, sym: Sym, name: String) {
|
||||
self.symbols.push(Symbol::new(sym, name));
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn len(&self) -> usize {
|
||||
self.symbols.len()
|
||||
}
|
||||
|
||||
pub fn find_by_name(&self, name: &str) -> Option<&Symbol> {
|
||||
self.symbols.iter().find(|sym| sym.name() == name)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Symbol {
|
||||
sym: Sym,
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl Symbol {
|
||||
pub fn new(sym: Sym, name: String) -> Self {
|
||||
Self { sym, name }
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
/// Returns the virtual address of the symbol.
|
||||
#[allow(dead_code)]
|
||||
pub fn vaddr(&self) -> usize {
|
||||
self.sym.st_value as usize
|
||||
}
|
||||
|
||||
/// Returns the offset of the symbol in the kernel memory.
|
||||
pub fn memory_offset(&self) -> u64 {
|
||||
self.sym.st_value & (!0xffff_8000_0000_0000)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Symbol {
|
||||
type Target = Sym;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.sym
|
||||
}
|
||||
}
|
127
tools/debugging/logmonitor/src/backend/mod.rs
Normal file
127
tools/debugging/logmonitor/src/backend/mod.rs
Normal file
@ -0,0 +1,127 @@
|
||||
use std::{
|
||||
path::PathBuf,
|
||||
sync::{mpsc, Arc, Mutex, RwLock, Weak},
|
||||
thread::JoinHandle,
|
||||
};
|
||||
|
||||
use log::info;
|
||||
|
||||
use crate::{command::CommandLineArgs, event::Event};
|
||||
|
||||
pub mod error;
|
||||
pub mod event;
|
||||
mod loader;
|
||||
mod monitor;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AppBackend {
|
||||
_command_line_args: CommandLineArgs,
|
||||
_sender_to_frontend: mpsc::Sender<Event>,
|
||||
data: Arc<Mutex<BackendData>>,
|
||||
main_thread: RwLock<Option<std::thread::JoinHandle<()>>>,
|
||||
/// All threads spawned by the backend.(Except the main thread)
|
||||
threads: Mutex<Vec<JoinHandle<()>>>,
|
||||
}
|
||||
|
||||
impl AppBackend {
|
||||
pub fn new(command_line_args: CommandLineArgs, sender: mpsc::Sender<Event>) -> Arc<Self> {
|
||||
let r = Arc::new(Self {
|
||||
_command_line_args: command_line_args.clone(),
|
||||
_sender_to_frontend: sender.clone(),
|
||||
data: Arc::new(Mutex::new(BackendData::new())),
|
||||
main_thread: RwLock::new(None),
|
||||
threads: Mutex::new(Vec::new()),
|
||||
});
|
||||
|
||||
r.data.lock().unwrap().kmem_path = Some(PathBuf::from(&command_line_args.kmem));
|
||||
|
||||
let main_thread = {
|
||||
let cmdargs = command_line_args.clone();
|
||||
let instance = r.clone();
|
||||
let sd = sender.clone();
|
||||
let dt = r.data.clone();
|
||||
std::thread::spawn(move || {
|
||||
let mut backend = BackendThread::new(cmdargs, sd, Arc::downgrade(&instance), dt);
|
||||
backend.run_main();
|
||||
})
|
||||
};
|
||||
|
||||
*r.main_thread.write().unwrap() = Some(main_thread);
|
||||
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct BackendData {
|
||||
kernel_metadata: Option<loader::KernelMetadata>,
|
||||
/// Path to the QEMU shm which contains the kernel memory.
|
||||
kmem_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl BackendData {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
kernel_metadata: None,
|
||||
kmem_path: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BackendThread {
|
||||
_sender_to_frontend: mpsc::Sender<Event>,
|
||||
command_line_args: CommandLineArgs,
|
||||
shared_data: Arc<Mutex<BackendData>>,
|
||||
backend_instance: Weak<AppBackend>,
|
||||
}
|
||||
|
||||
impl BackendThread {
|
||||
fn new(
|
||||
command_line_args: CommandLineArgs,
|
||||
sender: mpsc::Sender<Event>,
|
||||
backend_instance: Weak<AppBackend>,
|
||||
backend_data: Arc<Mutex<BackendData>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
command_line_args,
|
||||
_sender_to_frontend: sender,
|
||||
backend_instance,
|
||||
shared_data: backend_data,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_main(&mut self) {
|
||||
info!("DragonOS Log Monitor started.");
|
||||
self.load_kernel();
|
||||
self.run_mm_monitor();
|
||||
loop {
|
||||
// info!("BackendThread::run()");
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
}
|
||||
}
|
||||
|
||||
/// 启动内存管理监视器
|
||||
fn run_mm_monitor(&mut self) {
|
||||
info!("run_mm_monitor()");
|
||||
let mm_monitor = monitor::mm::MMLogMonitor::new(self.shared_data.clone());
|
||||
let handle = std::thread::spawn(move || {
|
||||
mm_monitor.run();
|
||||
});
|
||||
|
||||
self.backend_instance
|
||||
.upgrade()
|
||||
.unwrap()
|
||||
.threads
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push(handle);
|
||||
}
|
||||
|
||||
/// 加载DragonOS内核并初始化
|
||||
fn load_kernel(&self) {
|
||||
let res = loader::KernelLoader::load(&self.command_line_args.kernel)
|
||||
.expect("Failed to load kernel");
|
||||
self.shared_data.lock().unwrap().kernel_metadata = Some(res);
|
||||
}
|
||||
}
|
113
tools/debugging/logmonitor/src/backend/monitor/logset.rs
Normal file
113
tools/debugging/logmonitor/src/backend/monitor/logset.rs
Normal file
@ -0,0 +1,113 @@
|
||||
use std::{collections::BTreeMap, fmt::Debug, fs::File, io::Write, path::PathBuf};
|
||||
|
||||
use log::error;
|
||||
|
||||
use crate::constant::CMD_ARGS;
|
||||
|
||||
/// 日志集合
|
||||
///
|
||||
/// 所有的日志都会被存到这个集合中, 以便于进行各种操作
|
||||
///
|
||||
/// 日志集合的后端可以在日志插入前后做一些操作(需要实现[`LogSetBackend`])
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub struct LogSet<K, V> {
|
||||
inner: BTreeMap<K, V>,
|
||||
backend: Box<dyn LogSetBackend<K, V>>,
|
||||
name: String,
|
||||
file_path: PathBuf,
|
||||
log_file: Option<File>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl<K: Ord, V: Clone + PartialEq + Debug> LogSet<K, V> {
|
||||
pub fn new(name: String, backend: Option<Box<dyn LogSetBackend<K, V>>>) -> Self {
|
||||
let mut file_path = CMD_ARGS.read().unwrap().as_ref().unwrap().log_dir.clone();
|
||||
file_path.push(format!("{}-{}.log", name, std::process::id()));
|
||||
|
||||
let log_file = File::create(&file_path).expect("Failed to create log file.");
|
||||
|
||||
Self {
|
||||
inner: BTreeMap::new(),
|
||||
backend: backend.unwrap_or_else(|| Box::new(DefaultBackend::new())),
|
||||
name,
|
||||
file_path,
|
||||
log_file: Some(log_file),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, key: K, value: V) {
|
||||
let cloned_value = value.clone();
|
||||
self.backend.before_insert(&self.name, &value);
|
||||
|
||||
let prev = self.inner.insert(key, value);
|
||||
if let Some(prev) = prev {
|
||||
if prev.ne(&cloned_value) {
|
||||
error!(
|
||||
"LogSet::insert(): prev != cloned_value: prev: {:?}, cloned_value: {:?}",
|
||||
prev, cloned_value
|
||||
);
|
||||
}
|
||||
} else {
|
||||
self.log_file
|
||||
.as_mut()
|
||||
.map(|f| writeln!(f, "{:?}", cloned_value).ok());
|
||||
}
|
||||
|
||||
self.backend.after_insert(&self.name, &cloned_value);
|
||||
}
|
||||
|
||||
pub fn file_path(&self) -> &PathBuf {
|
||||
&self.file_path
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.inner.len()
|
||||
}
|
||||
|
||||
pub fn get(&self, key: &K) -> Option<&V> {
|
||||
self.inner.get(key)
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, key: &K) -> Option<&mut V> {
|
||||
self.inner.get_mut(key)
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, key: &K) -> Option<V> {
|
||||
self.inner.remove(key)
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.inner.clear();
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&K, &V)> {
|
||||
self.inner.iter()
|
||||
}
|
||||
|
||||
pub fn contains_key(&self, key: &K) -> bool {
|
||||
self.inner.contains_key(key)
|
||||
}
|
||||
}
|
||||
|
||||
/// 日志集合的后端, 用于在日志插入前后做一些操作
|
||||
pub trait LogSetBackend<K, V>: Debug {
|
||||
fn before_insert(&mut self, _log_set_name: &str, _log: &V) {}
|
||||
|
||||
fn after_insert(&mut self, _log_set_name: &str, _log: &V) {}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct DefaultBackend(());
|
||||
|
||||
impl DefaultBackend {
|
||||
pub const fn new() -> Self {
|
||||
Self(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> LogSetBackend<K, V> for DefaultBackend {
|
||||
fn before_insert(&mut self, _log_set_name: &str, _log: &V) {}
|
||||
|
||||
fn after_insert(&mut self, _log_set_name: &str, _log: &V) {}
|
||||
}
|
286
tools/debugging/logmonitor/src/backend/monitor/mm.rs
Normal file
286
tools/debugging/logmonitor/src/backend/monitor/mm.rs
Normal file
@ -0,0 +1,286 @@
|
||||
use std::{
|
||||
fs::File,
|
||||
mem::size_of,
|
||||
os::unix::prelude::FileExt,
|
||||
path::PathBuf,
|
||||
sync::{atomic::AtomicBool, mpsc, Arc, Mutex, Weak},
|
||||
thread::JoinHandle,
|
||||
};
|
||||
|
||||
use klog_types::{AllocatorLog, MMLogChannel};
|
||||
use log::info;
|
||||
|
||||
use crate::backend::{
|
||||
loader::Symbol,
|
||||
monitor::{logset::LogSet, ObjectWrapper},
|
||||
BackendData,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MMLogMonitor {
|
||||
channel_symbol: Option<Symbol>,
|
||||
shared_data: Arc<Mutex<BackendData>>,
|
||||
/// All threads spawned by the mm log monitor.
|
||||
threads: Mutex<Vec<JoinHandle<()>>>,
|
||||
stop_child_threads: AtomicBool,
|
||||
self_ref: Weak<Self>,
|
||||
|
||||
mm_log_receiver: Mutex<mpsc::Receiver<MMLogWorkerResult>>,
|
||||
mm_log_sender: mpsc::Sender<MMLogWorkerResult>,
|
||||
}
|
||||
|
||||
impl MMLogMonitor {
|
||||
pub fn new(shared_data: Arc<Mutex<BackendData>>) -> Arc<Self> {
|
||||
let guard = shared_data.lock().unwrap();
|
||||
let mm_log_buffer_symbol: Option<Symbol> = guard
|
||||
.kernel_metadata
|
||||
.as_ref()
|
||||
.map(|km| {
|
||||
km.sym_collection()
|
||||
.find_by_name("__MM_ALLOCATOR_LOG_CHANNEL")
|
||||
.map(|s| s.clone())
|
||||
})
|
||||
.flatten();
|
||||
drop(guard);
|
||||
|
||||
info!("mm_log_buffer_symbol: {:?}", mm_log_buffer_symbol);
|
||||
|
||||
let mm_log_worker_mpsc: (
|
||||
mpsc::Sender<MMLogWorkerResult>,
|
||||
mpsc::Receiver<MMLogWorkerResult>,
|
||||
) = mpsc::channel::<MMLogWorkerResult>();
|
||||
|
||||
let r = Self {
|
||||
channel_symbol: mm_log_buffer_symbol,
|
||||
shared_data,
|
||||
threads: Mutex::new(Vec::new()),
|
||||
stop_child_threads: AtomicBool::new(false),
|
||||
self_ref: Weak::new(),
|
||||
mm_log_receiver: Mutex::new(mm_log_worker_mpsc.1),
|
||||
mm_log_sender: mm_log_worker_mpsc.0,
|
||||
};
|
||||
|
||||
let r = Arc::new(r);
|
||||
unsafe {
|
||||
let self_ref = Arc::downgrade(&r);
|
||||
let r_ptr = r.as_ref() as *const Self as *mut Self;
|
||||
(*r_ptr).self_ref = self_ref;
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
pub fn run(&self) {
|
||||
info!("MMLogMonitor::run()");
|
||||
|
||||
self.create_threads();
|
||||
|
||||
let mut logs_set =
|
||||
LogSet::<usize, ObjectWrapper<AllocatorLog>>::new("mm_allocator_log".to_string(), None);
|
||||
|
||||
self.handle_logs(&mut logs_set);
|
||||
// std::thread::sleep(std::time::Duration::from_micros(50));
|
||||
}
|
||||
|
||||
fn handle_logs(&self, logs_set: &mut LogSet<usize, ObjectWrapper<AllocatorLog>>) {
|
||||
let mut last_cnt = 0;
|
||||
let mut last_time = std::time::Instant::now();
|
||||
let mm_log_receiver = self.mm_log_receiver.lock().unwrap();
|
||||
loop {
|
||||
let logs = mm_log_receiver.recv();
|
||||
if logs.is_err() {
|
||||
return;
|
||||
}
|
||||
|
||||
let logs = logs.unwrap();
|
||||
|
||||
for log in logs.logs {
|
||||
logs_set.insert(log.id as usize, log);
|
||||
}
|
||||
|
||||
let x = logs_set.len();
|
||||
// info!("logs_set.len(): {}", x);
|
||||
let current_time = std::time::Instant::now();
|
||||
if current_time.duration_since(last_time).as_secs() >= 1 {
|
||||
info!("memory log rate: {} logs/s", x - last_cnt);
|
||||
last_cnt = x;
|
||||
last_time = current_time;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fn show_speed(&self, )
|
||||
|
||||
fn create_threads(&self) {
|
||||
let km = self
|
||||
.shared_data
|
||||
.lock()
|
||||
.unwrap()
|
||||
.kmem_path
|
||||
.clone()
|
||||
.expect("DragonOS memory map file not specified.");
|
||||
let monitor_weak = self.self_ref.clone();
|
||||
|
||||
let handle = std::thread::spawn(move || {
|
||||
let mut monitor_thread = MMMonitorThread::new(monitor_weak, PathBuf::from(km));
|
||||
monitor_thread.run();
|
||||
});
|
||||
|
||||
self.threads.lock().unwrap().push(handle);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct MMMonitorThread {
|
||||
mm_log_monitor: Weak<MMLogMonitor>,
|
||||
kmem_path: PathBuf,
|
||||
}
|
||||
|
||||
impl MMMonitorThread {
|
||||
/// Constructs a new instance of [`MMMonitorThread`].
|
||||
///
|
||||
/// ## Parameters
|
||||
///
|
||||
/// - `mm_log_monitor`: The [`MMLogMonitor`] instance.
|
||||
/// - `kmem_path`: The path to the kernel memory file.
|
||||
pub fn new(mm_log_monitor: Weak<MMLogMonitor>, kmem_path: PathBuf) -> Self {
|
||||
Self {
|
||||
mm_log_monitor,
|
||||
kmem_path,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(&mut self) {
|
||||
info!("MMMonitorThread::run(): kmem_path: {:?}", self.kmem_path);
|
||||
|
||||
let mut kmem_file = self.open_kmem_file().expect("Failed to open kmem file.");
|
||||
|
||||
info!("Channel header loaded!");
|
||||
|
||||
let channel_header: ObjectWrapper<MMLogChannel<1>> = self.load_header(&mut kmem_file);
|
||||
|
||||
// 循环读取
|
||||
|
||||
self.process_logs(&mut kmem_file, &channel_header);
|
||||
}
|
||||
|
||||
/// 处理内核内存分配日志
|
||||
fn process_logs(&self, kmem_file: &mut File, channel_header: &ObjectWrapper<MMLogChannel<1>>) {
|
||||
let cap = channel_header.capacity;
|
||||
let mut buf = vec![0u8; (cap * channel_header.slot_size as u64) as usize];
|
||||
let symbol = self
|
||||
.mm_log_channel_symbol()
|
||||
.expect("Failed to get memory log channel symbol.");
|
||||
|
||||
let sym_offset = symbol.memory_offset();
|
||||
|
||||
let slots_offset = channel_header.slots_offset + sym_offset;
|
||||
let sender = self.mm_log_monitor.upgrade().unwrap().mm_log_sender.clone();
|
||||
loop {
|
||||
if self.should_stop() {
|
||||
break;
|
||||
}
|
||||
|
||||
let r = kmem_file
|
||||
.read_at(&mut buf, slots_offset)
|
||||
.expect("Failed to read kmem file.");
|
||||
assert!(r == buf.len());
|
||||
|
||||
let mut logs = Vec::new();
|
||||
|
||||
for chunck in buf.chunks(channel_header.slot_size as usize) {
|
||||
let log_item = {
|
||||
let log: Option<ObjectWrapper<AllocatorLog>> =
|
||||
ObjectWrapper::new(&chunck[0..channel_header.element_size as usize]);
|
||||
let log: ObjectWrapper<AllocatorLog> = log.unwrap();
|
||||
|
||||
if log.is_valid() {
|
||||
Some(log)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
if let Some(log_item) = log_item {
|
||||
logs.push(log_item);
|
||||
}
|
||||
}
|
||||
// 收集所有校验和正确的日志
|
||||
// info!("valid_cnt: {}, invalid_cnt: {}", valid_cnt, invalid_cnt);
|
||||
// info!("to send {} logs", logs.len());
|
||||
if !logs.is_empty() {
|
||||
sender.send(MMLogWorkerResult::new(logs)).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn open_kmem_file(&self) -> std::io::Result<std::fs::File> {
|
||||
std::fs::OpenOptions::new().read(true).open(&self.kmem_path)
|
||||
}
|
||||
|
||||
fn load_header(&self, kmem_file: &mut File) -> ObjectWrapper<MMLogChannel<1>> {
|
||||
let mut buf = [0u8; size_of::<MMLogChannel<1>>()];
|
||||
let symbol = self
|
||||
.mm_log_channel_symbol()
|
||||
.expect("Failed to get memory log channel symbol.");
|
||||
|
||||
let sym_offset = symbol.memory_offset();
|
||||
|
||||
let channel_header: Option<ObjectWrapper<MMLogChannel<1>>>;
|
||||
|
||||
loop {
|
||||
let _r = kmem_file.read_at(&mut buf, sym_offset);
|
||||
|
||||
let header: ObjectWrapper<MMLogChannel<1>> =
|
||||
ObjectWrapper::new(&buf).expect("Failed to parse MMLogChannel header.");
|
||||
if header.magic == MMLogChannel::<1>::MM_LOG_CHANNEL_MAGIC {
|
||||
info!("channel_header: {:?}", header);
|
||||
channel_header = Some(header);
|
||||
break;
|
||||
} else {
|
||||
info!("MM Log Channel not found... Maybe the kernel not started? Or the kernel version is not supported?");
|
||||
}
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
}
|
||||
|
||||
return channel_header.unwrap();
|
||||
}
|
||||
|
||||
/// Get the symbol of the memory log channel.
|
||||
fn mm_log_channel_symbol(&self) -> Option<Symbol> {
|
||||
self.mm_log_monitor
|
||||
.upgrade()
|
||||
.unwrap()
|
||||
.channel_symbol
|
||||
.clone()
|
||||
}
|
||||
|
||||
/// Check if the monitor worker thread should stop.
|
||||
fn should_stop(&self) -> bool {
|
||||
self.mm_log_monitor
|
||||
.upgrade()
|
||||
.map(|mm_log_monitor| {
|
||||
mm_log_monitor
|
||||
.stop_child_threads
|
||||
.load(std::sync::atomic::Ordering::Relaxed)
|
||||
})
|
||||
.unwrap_or(true)
|
||||
}
|
||||
}
|
||||
|
||||
/// 内存日志监视器工作线程处理的结果
|
||||
#[derive(Debug)]
|
||||
struct MMLogWorkerResult {
|
||||
logs: Vec<ObjectWrapper<AllocatorLog>>,
|
||||
}
|
||||
|
||||
impl MMLogWorkerResult {
|
||||
/// 创建一个新的内存日志监视器工作线程处理的结果
|
||||
///
|
||||
/// ## 参数
|
||||
///
|
||||
/// - `logs`:处理的日志
|
||||
pub fn new(logs: Vec<ObjectWrapper<AllocatorLog>>) -> Self {
|
||||
Self { logs }
|
||||
}
|
||||
}
|
45
tools/debugging/logmonitor/src/backend/monitor/mod.rs
Normal file
45
tools/debugging/logmonitor/src/backend/monitor/mod.rs
Normal file
@ -0,0 +1,45 @@
|
||||
use std::{
|
||||
fmt::Debug,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
pub mod logset;
|
||||
pub mod mm;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct ObjectWrapper<T> {
|
||||
object: Box<T>,
|
||||
}
|
||||
|
||||
impl<T: Debug + Sized> ObjectWrapper<T> {
|
||||
pub fn new(buf: &[u8]) -> Option<Self> {
|
||||
if buf.len() != std::mem::size_of::<T>() {
|
||||
println!(
|
||||
"ObjectWrapper::new(): buf.len() '{}' != std::mem::size_of::<T>(): '{}'",
|
||||
buf.len(),
|
||||
std::mem::size_of::<T>()
|
||||
);
|
||||
return None;
|
||||
}
|
||||
let x = unsafe { std::ptr::read(buf.as_ptr() as *const T) };
|
||||
|
||||
let object = Box::new(x);
|
||||
|
||||
// let object = ManuallyDrop::new(x);
|
||||
Some(Self { object })
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for ObjectWrapper<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.object
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Deref for ObjectWrapper<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.object
|
||||
}
|
||||
}
|
32
tools/debugging/logmonitor/src/command.rs
Normal file
32
tools/debugging/logmonitor/src/command.rs
Normal file
@ -0,0 +1,32 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
#[derive(Debug, Parser, Clone)]
|
||||
pub struct CommandLineArgs {
|
||||
#[arg(short, long)]
|
||||
/// The kernel ELF file to load.
|
||||
pub kernel: PathBuf,
|
||||
|
||||
/// The kernel memory file to load.
|
||||
#[arg(long, value_parser=kmem_file_parser, default_value = "/dev/shm/dragonos-qemu-shm.ram")]
|
||||
pub kmem: String,
|
||||
|
||||
/// If set, the monitor will start the TUI.
|
||||
#[arg(long, default_value = "false")]
|
||||
pub tui: bool,
|
||||
|
||||
/// The directory to store the log files.
|
||||
#[arg(long, default_value = "logs")]
|
||||
pub log_dir: PathBuf,
|
||||
}
|
||||
|
||||
/// 用于解析kmem参数的函数
|
||||
fn kmem_file_parser(s: &str) -> Result<String, String> {
|
||||
log::warn!("kmem_file_parser: {}", s);
|
||||
if s.len() == 0 {
|
||||
return Ok("/dev/shm/dragonos-qemu-shm.ram".to_string());
|
||||
} else {
|
||||
return Ok(s.to_string());
|
||||
}
|
||||
}
|
6
tools/debugging/logmonitor/src/constant/mod.rs
Normal file
6
tools/debugging/logmonitor/src/constant/mod.rs
Normal file
@ -0,0 +1,6 @@
|
||||
use std::sync::RwLock;
|
||||
|
||||
use crate::command::CommandLineArgs;
|
||||
|
||||
/// 启动时的命令行参数
|
||||
pub static CMD_ARGS: RwLock<Option<CommandLineArgs>> = RwLock::new(None);
|
83
tools/debugging/logmonitor/src/event.rs
Normal file
83
tools/debugging/logmonitor/src/event.rs
Normal file
@ -0,0 +1,83 @@
|
||||
use crate::app::AppResult;
|
||||
use crate::backend::event::BackendEvent;
|
||||
use crossterm::event::{self, Event as CrosstermEvent, KeyEvent, MouseEvent};
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
/// Terminal events.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Event {
|
||||
/// Terminal tick.
|
||||
Tick,
|
||||
/// Key press.
|
||||
Key(KeyEvent),
|
||||
/// Mouse click/scroll.
|
||||
Mouse(MouseEvent),
|
||||
/// Terminal resize.
|
||||
Resize(u16, u16),
|
||||
Backend(BackendEvent),
|
||||
}
|
||||
|
||||
/// Terminal event handler.
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug)]
|
||||
pub struct EventHandler {
|
||||
/// Event sender channel.
|
||||
sender: mpsc::Sender<Event>,
|
||||
/// Event receiver channel.
|
||||
receiver: mpsc::Receiver<Event>,
|
||||
/// Event handler thread.
|
||||
handler: thread::JoinHandle<()>,
|
||||
}
|
||||
|
||||
impl EventHandler {
|
||||
/// Constructs a new instance of [`EventHandler`].
|
||||
pub fn new(tick_rate: u64) -> Self {
|
||||
let tick_rate = Duration::from_millis(tick_rate);
|
||||
let (sender, receiver) = mpsc::channel();
|
||||
let handler = {
|
||||
let sender = sender.clone();
|
||||
thread::spawn(move || {
|
||||
let mut last_tick = Instant::now();
|
||||
loop {
|
||||
let timeout = tick_rate
|
||||
.checked_sub(last_tick.elapsed())
|
||||
.unwrap_or(tick_rate);
|
||||
|
||||
if event::poll(timeout).expect("no events available") {
|
||||
match event::read().expect("unable to read event") {
|
||||
CrosstermEvent::Key(e) => sender.send(Event::Key(e)),
|
||||
CrosstermEvent::Mouse(e) => sender.send(Event::Mouse(e)),
|
||||
CrosstermEvent::Resize(w, h) => sender.send(Event::Resize(w, h)),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
.expect("failed to send terminal event")
|
||||
}
|
||||
|
||||
if last_tick.elapsed() >= tick_rate {
|
||||
sender.send(Event::Tick).expect("failed to send tick event");
|
||||
last_tick = Instant::now();
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
Self {
|
||||
sender,
|
||||
receiver,
|
||||
handler,
|
||||
}
|
||||
}
|
||||
|
||||
/// Receive the next event from the handler thread.
|
||||
///
|
||||
/// This function will always block the current thread if
|
||||
/// there is no data available and it's possible for more data to be sent.
|
||||
pub fn next(&self) -> AppResult<Event> {
|
||||
Ok(self.receiver.recv()?)
|
||||
}
|
||||
|
||||
pub fn sender(&self) -> mpsc::Sender<Event> {
|
||||
self.sender.clone()
|
||||
}
|
||||
}
|
35
tools/debugging/logmonitor/src/handler.rs
Normal file
35
tools/debugging/logmonitor/src/handler.rs
Normal file
@ -0,0 +1,35 @@
|
||||
use crate::{
|
||||
app::{App, AppResult},
|
||||
backend::event::BackendEvent,
|
||||
};
|
||||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||
|
||||
/// Handles the key events and updates the state of [`App`].
|
||||
pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> {
|
||||
match key_event.code {
|
||||
// Exit application on `ESC` or `q`
|
||||
KeyCode::Esc | KeyCode::Char('q') => {
|
||||
app.quit();
|
||||
}
|
||||
// Exit application on `Ctrl-C`
|
||||
KeyCode::Char('c') | KeyCode::Char('C') => {
|
||||
if key_event.modifiers == KeyModifiers::CONTROL {
|
||||
app.quit();
|
||||
}
|
||||
}
|
||||
// Counter handlers
|
||||
KeyCode::Right => {
|
||||
app.increment_counter();
|
||||
}
|
||||
KeyCode::Left => {
|
||||
app.decrement_counter();
|
||||
}
|
||||
// Other handlers you could add here.
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn handle_backend_events(_backend_event: BackendEvent, _app: &mut App) -> AppResult<()> {
|
||||
return Ok(());
|
||||
}
|
25
tools/debugging/logmonitor/src/lib.rs
Normal file
25
tools/debugging/logmonitor/src/lib.rs
Normal file
@ -0,0 +1,25 @@
|
||||
#![allow(incomplete_features)]
|
||||
#![feature(generic_const_exprs)]
|
||||
|
||||
extern crate clap;
|
||||
|
||||
extern crate lazy_static;
|
||||
|
||||
/// Application.
|
||||
pub mod app;
|
||||
|
||||
/// Terminal events handler.
|
||||
pub mod event;
|
||||
|
||||
/// Widget renderer.
|
||||
pub mod ui;
|
||||
|
||||
/// Terminal user interface.
|
||||
pub mod tui;
|
||||
|
||||
pub mod backend;
|
||||
pub mod command;
|
||||
pub mod constant;
|
||||
/// Event handler.
|
||||
pub mod handler;
|
||||
pub mod logging;
|
51
tools/debugging/logmonitor/src/logging.rs
Normal file
51
tools/debugging/logmonitor/src/logging.rs
Normal file
@ -0,0 +1,51 @@
|
||||
use std::sync::mpsc;
|
||||
|
||||
use log::LevelFilter;
|
||||
use simple_logger::LogBackend;
|
||||
|
||||
use crate::command::CommandLineArgs;
|
||||
|
||||
/// Initialize the logging system.
|
||||
pub fn init(cmd_args: &CommandLineArgs) -> LoggingInitResult {
|
||||
let mut builder = simple_logger::SimpleLogger::new().with_level(LevelFilter::Info);
|
||||
|
||||
let mut result = LoggingInitResult::new(None);
|
||||
|
||||
if cmd_args.tui {
|
||||
let channel: (mpsc::Sender<String>, mpsc::Receiver<String>) = mpsc::channel::<String>();
|
||||
builder = builder.with_backend(Box::new(TUILoggingBackend::new(channel.0)));
|
||||
result.tui_receiver = Some(channel.1);
|
||||
}
|
||||
|
||||
builder.init().expect("failed to initialize logging");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LoggingInitResult {
|
||||
/// Logging backend receiver.
|
||||
pub tui_receiver: Option<mpsc::Receiver<String>>,
|
||||
}
|
||||
|
||||
impl LoggingInitResult {
|
||||
pub fn new(tui_receiver: Option<mpsc::Receiver<String>>) -> Self {
|
||||
Self { tui_receiver }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TUILoggingBackend {
|
||||
sender: mpsc::Sender<String>,
|
||||
}
|
||||
|
||||
impl TUILoggingBackend {
|
||||
pub fn new(sender: mpsc::Sender<String>) -> Self {
|
||||
Self { sender }
|
||||
}
|
||||
}
|
||||
|
||||
impl LogBackend for TUILoggingBackend {
|
||||
fn log(&self, message: String) {
|
||||
self.sender.send(message).ok();
|
||||
}
|
||||
}
|
98
tools/debugging/logmonitor/src/main.rs
Normal file
98
tools/debugging/logmonitor/src/main.rs
Normal file
@ -0,0 +1,98 @@
|
||||
use clap::Parser;
|
||||
use logmonitor::app::{App, AppResult};
|
||||
use logmonitor::command::{self, CommandLineArgs};
|
||||
use logmonitor::constant::CMD_ARGS;
|
||||
use logmonitor::event::{Event, EventHandler};
|
||||
use logmonitor::handler::{handle_backend_events, handle_key_events};
|
||||
use logmonitor::logging::LoggingInitResult;
|
||||
use logmonitor::tui::Tui;
|
||||
use ratatui::backend::CrosstermBackend;
|
||||
use ratatui::Terminal;
|
||||
use std::io;
|
||||
|
||||
extern crate log;
|
||||
|
||||
fn main() -> AppResult<()> {
|
||||
let command_line_args = command::CommandLineArgs::parse();
|
||||
*CMD_ARGS.write().unwrap() = Some(command_line_args.clone());
|
||||
println!("{:?}", command_line_args);
|
||||
prepare_env();
|
||||
|
||||
let logging_init_result = logmonitor::logging::init(&command_line_args);
|
||||
if !command_line_args.tui {
|
||||
return start_headless_app(command_line_args, logging_init_result);
|
||||
} else {
|
||||
return start_tui_app(command_line_args, logging_init_result);
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare_env() {
|
||||
// 创建日志文件夹
|
||||
let p = CMD_ARGS.read().unwrap().clone();
|
||||
let log_dir = p.unwrap().log_dir;
|
||||
std::fs::create_dir_all(log_dir).expect("Failed to create log directory.");
|
||||
}
|
||||
|
||||
/// 启动无界面应用
|
||||
fn start_headless_app(
|
||||
cmdargs: CommandLineArgs,
|
||||
_logging_init_result: LoggingInitResult,
|
||||
) -> AppResult<()> {
|
||||
let mut app = App::new("DragonOS Log Monitor");
|
||||
let events = EventHandler::new(250);
|
||||
let _app_backend = logmonitor::backend::AppBackend::new(cmdargs.clone(), events.sender());
|
||||
|
||||
while app.running {
|
||||
match events.next()? {
|
||||
Event::Tick => app.tick(),
|
||||
Event::Key(key_event) => handle_key_events(key_event, &mut app)?,
|
||||
Event::Mouse(_) => {}
|
||||
Event::Resize(_, _) => {}
|
||||
Event::Backend(e) => {
|
||||
handle_backend_events(e, &mut app)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
println!("Headless mode not implemented yet.");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 启动TUI应用
|
||||
fn start_tui_app(
|
||||
cmdargs: CommandLineArgs,
|
||||
logging_init_result: LoggingInitResult,
|
||||
) -> AppResult<()> {
|
||||
// Create an application.
|
||||
let mut app = App::new("DragonOS Log Monitor");
|
||||
if let Some(receiver) = logging_init_result.tui_receiver {
|
||||
app.set_backend_log_receiver(receiver);
|
||||
}
|
||||
|
||||
// Initialize the terminal user interface.
|
||||
let backend = CrosstermBackend::new(io::stderr());
|
||||
let terminal = Terminal::new(backend)?;
|
||||
let events = EventHandler::new(250);
|
||||
let mut tui = Tui::new(terminal, events);
|
||||
tui.init()?;
|
||||
let _app_backend = logmonitor::backend::AppBackend::new(cmdargs.clone(), tui.events.sender());
|
||||
|
||||
// Start the main loop.
|
||||
while app.running {
|
||||
// Render the user interface.
|
||||
tui.draw(&mut app)?;
|
||||
// Handle events.
|
||||
match tui.events.next()? {
|
||||
Event::Tick => app.tick(),
|
||||
Event::Key(key_event) => handle_key_events(key_event, &mut app)?,
|
||||
Event::Mouse(_) => {}
|
||||
Event::Resize(_, _) => {}
|
||||
Event::Backend(e) => {
|
||||
handle_backend_events(e, &mut app)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Exit the user interface.
|
||||
tui.exit()?;
|
||||
Ok(())
|
||||
}
|
77
tools/debugging/logmonitor/src/tui.rs
Normal file
77
tools/debugging/logmonitor/src/tui.rs
Normal file
@ -0,0 +1,77 @@
|
||||
use crate::app::{App, AppResult};
|
||||
use crate::event::EventHandler;
|
||||
use crate::ui;
|
||||
use crossterm::event::DisableMouseCapture;
|
||||
use crossterm::terminal::{self, EnterAlternateScreen, LeaveAlternateScreen};
|
||||
use std::io;
|
||||
use std::panic;
|
||||
|
||||
use ratatui::backend::Backend;
|
||||
use ratatui::Terminal;
|
||||
|
||||
/// Representation of a terminal user interface.
|
||||
///
|
||||
/// It is responsible for setting up the terminal,
|
||||
/// initializing the interface and handling the draw events.
|
||||
#[derive(Debug)]
|
||||
pub struct Tui<B: Backend> {
|
||||
/// Interface to the Terminal.
|
||||
terminal: Terminal<B>,
|
||||
/// Terminal event handler.
|
||||
pub events: EventHandler,
|
||||
}
|
||||
|
||||
impl<B: Backend> Tui<B> {
|
||||
/// Constructs a new instance of [`Tui`].
|
||||
pub fn new(terminal: Terminal<B>, events: EventHandler) -> Self {
|
||||
Self { terminal, events }
|
||||
}
|
||||
|
||||
/// Initializes the terminal interface.
|
||||
///
|
||||
/// It enables the raw mode and sets terminal properties.
|
||||
pub fn init(&mut self) -> AppResult<()> {
|
||||
terminal::enable_raw_mode()?;
|
||||
crossterm::execute!(io::stderr(), EnterAlternateScreen, DisableMouseCapture)?;
|
||||
|
||||
// Define a custom panic hook to reset the terminal properties.
|
||||
// This way, you won't have your terminal messed up if an unexpected error happens.
|
||||
let panic_hook = panic::take_hook();
|
||||
panic::set_hook(Box::new(move |panic| {
|
||||
Self::reset().expect("failed to reset the terminal");
|
||||
panic_hook(panic);
|
||||
}));
|
||||
|
||||
self.terminal.hide_cursor()?;
|
||||
self.terminal.clear()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// [`Draw`] the terminal interface by [`rendering`] the widgets.
|
||||
///
|
||||
/// [`Draw`]: ratatui::Terminal::draw
|
||||
/// [`rendering`]: crate::ui:render
|
||||
pub fn draw(&mut self, app: &mut App) -> AppResult<()> {
|
||||
self.terminal.draw(|frame| ui::render(app, frame))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Resets the terminal interface.
|
||||
///
|
||||
/// This function is also used for the panic hook to revert
|
||||
/// the terminal properties if unexpected errors occur.
|
||||
fn reset() -> AppResult<()> {
|
||||
terminal::disable_raw_mode()?;
|
||||
crossterm::execute!(io::stdout(), LeaveAlternateScreen, DisableMouseCapture)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Exits the terminal interface.
|
||||
///
|
||||
/// It disables the raw mode and reverts back the terminal properties.
|
||||
pub fn exit(&mut self) -> AppResult<()> {
|
||||
Self::reset()?;
|
||||
self.terminal.show_cursor()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
134
tools/debugging/logmonitor/src/ui.rs
Normal file
134
tools/debugging/logmonitor/src/ui.rs
Normal file
@ -0,0 +1,134 @@
|
||||
use ratatui::{
|
||||
prelude::{Constraint, Layout, Rect},
|
||||
style::{Color, Modifier, Style},
|
||||
symbols,
|
||||
text::{self, Span, Text},
|
||||
widgets::{Block, Borders, List, ListItem, Sparkline, Tabs},
|
||||
Frame,
|
||||
};
|
||||
|
||||
use crate::app::App;
|
||||
|
||||
/// Renders the user interface widgets.
|
||||
pub fn render(app: &mut App, frame: &mut Frame) {
|
||||
// This is where you add new widgets.
|
||||
// See the following resources:
|
||||
// - https://docs.rs/ratatui/latest/ratatui/widgets/index.html
|
||||
// - https://github.com/ratatui-org/ratatui/tree/master/examples
|
||||
// frame.render_widget(
|
||||
// Paragraph::new(format!(
|
||||
// "This is a tui template.\n\
|
||||
// Press `Esc`, `Ctrl-C` or `q` to stop running.\n\
|
||||
// Press left and right to increment and decrement the counter respectively.\n\
|
||||
// Counter: {}",
|
||||
// app.counter
|
||||
// ))
|
||||
// .block(
|
||||
// Block::default()
|
||||
// .title("Template")
|
||||
// .title_alignment(Alignment::Center)
|
||||
// .borders(Borders::ALL)
|
||||
// .border_type(BorderType::Rounded),
|
||||
// )
|
||||
// .style(Style::default().fg(Color::Cyan).bg(Color::Black))
|
||||
// .alignment(Alignment::Center),
|
||||
// frame.size(),
|
||||
// )
|
||||
|
||||
let chunks = Layout::default()
|
||||
.constraints([Constraint::Length(3), Constraint::Min(0)])
|
||||
.split(frame.size());
|
||||
let titles = app
|
||||
.tabs
|
||||
.titles
|
||||
.iter()
|
||||
.map(|t| text::Line::from(Span::styled(*t, Style::default().fg(Color::Green))))
|
||||
.collect();
|
||||
let tabs = Tabs::new(titles)
|
||||
.block(Block::default().borders(Borders::ALL).title(app.title))
|
||||
.highlight_style(Style::default().fg(Color::Yellow))
|
||||
.select(app.tabs.index);
|
||||
frame.render_widget(tabs, chunks[0]);
|
||||
|
||||
match app.tabs.index {
|
||||
0 => draw_first_tab(frame, app, chunks[1]),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_first_tab(f: &mut Frame, app: &mut App, area: Rect) {
|
||||
let chunks = Layout::default()
|
||||
.constraints([
|
||||
Constraint::Min(1),
|
||||
Constraint::Min(3),
|
||||
Constraint::Length(7),
|
||||
])
|
||||
.split(area);
|
||||
draw_memory_logging_speed_gauges(f, app, chunks[0]);
|
||||
// draw_charts(f, app, chunks[1]);
|
||||
draw_footer(f, app, chunks[2]);
|
||||
}
|
||||
|
||||
/// 绘制内存日志产生数量的图表
|
||||
fn draw_memory_logging_speed_gauges(f: &mut Frame, app: &mut App, area: Rect) {
|
||||
let chunks = Layout::default()
|
||||
.constraints([Constraint::Length(3)])
|
||||
.margin(1)
|
||||
.split(area);
|
||||
let block = Block::default().borders(Borders::ALL).title("Speed:");
|
||||
f.render_widget(block, area);
|
||||
|
||||
let sparkline = Sparkline::default()
|
||||
.block(Block::default().title("Memory Log Speed:"))
|
||||
.style(Style::default().fg(Color::Green))
|
||||
.data(&app.memory_log_sparkline.points)
|
||||
.bar_set(if app.enhanced_graphics {
|
||||
symbols::bar::NINE_LEVELS
|
||||
} else {
|
||||
symbols::bar::THREE_LEVELS
|
||||
});
|
||||
f.render_widget(sparkline, chunks[0]);
|
||||
}
|
||||
|
||||
fn draw_footer(f: &mut Frame, app: &mut App, area: Rect) {
|
||||
let _block = Block::default().borders(Borders::ALL).title(Span::styled(
|
||||
"Logs",
|
||||
Style::default()
|
||||
.fg(Color::Magenta)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
));
|
||||
|
||||
let info_style = Style::default().fg(Color::Blue);
|
||||
let warning_style = Style::default().fg(Color::Yellow);
|
||||
let error_style = Style::default().fg(Color::Magenta);
|
||||
let critical_style = Style::default().fg(Color::Red);
|
||||
|
||||
let binding = app.logs().clone();
|
||||
let log_list = binding
|
||||
.iter()
|
||||
.map(|log_str| {
|
||||
let _style = match log_str {
|
||||
log if log.contains("INFO") => info_style,
|
||||
log if log.contains("WARNING") => warning_style,
|
||||
log if log.contains("ERROR") => error_style,
|
||||
log if log.contains("CRITICAL") => critical_style,
|
||||
_ => Style::default().fg(Color::White),
|
||||
};
|
||||
|
||||
// println!("log_str: {}", log_str);
|
||||
|
||||
ListItem::new(Text::from(log_str.clone()))
|
||||
})
|
||||
.collect::<Vec<ListItem>>();
|
||||
|
||||
let items_num = 5;
|
||||
let list_to_show = log_list.split_at(if log_list.len() > items_num {
|
||||
log_list.len() - items_num
|
||||
} else {
|
||||
0
|
||||
});
|
||||
|
||||
let logs =
|
||||
List::new(list_to_show.1).block(Block::default().borders(Borders::ALL).title("List"));
|
||||
f.render_stateful_widget(logs, area, &mut app.stateful_logs.state);
|
||||
}
|
Reference in New Issue
Block a user