增加内存分配日志监视器 (#424)

* 完成内存日志监视,并输出日志到文件
* 修复进程退出后,procfs查看进程status文件会崩溃的问题
* 修复signal唤醒进程的判断条件问题
This commit is contained in:
LoGin 2023-11-07 21:39:27 +08:00 committed by GitHub
parent 70a4e5550a
commit 7b32f5080f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 2033 additions and 59 deletions

1
.gitignore vendored
View File

@ -19,3 +19,4 @@ cppcheck.xml
Cargo.lock
.cache
compile_commands.json
/logs/

View File

@ -177,11 +177,10 @@
},
"C_Cpp.errorSquiggles": "enabled",
"esbonio.sphinx.confDir": "",
"rust-analyzer.cargo.target": "x86_64-unknown-none",
"rust-analyzer.checkOnSave.allTargets": false,
"rust-analyzer.linkedProjects": [
"./kernel/Cargo.toml",
"./kernel/src/libs/ida/Cargo.toml"
"./tools/Cargo.toml",
],
"rust-analyzer.check.overrideCommand": [
"make",

View File

@ -1,4 +1,4 @@
SUBDIRS = kernel user
SUBDIRS = kernel user tools
# ifndef $(EMULATOR)
ifeq ($(EMULATOR), )
@ -157,6 +157,10 @@ fmt:
FMT_CHECK=$(FMT_CHECK) $(MAKE) fmt -C kernel
FMT_CHECK=$(FMT_CHECK) $(MAKE) fmt -C user
log-monitor:
@echo "启动日志监控"
@sh -c "cd tools/debugging/logmonitor && cargo run --release -- --log-dir $(ROOT_PATH)/logs/ --kernel $(ROOT_PATH)/bin/kernel/kernel.elf"
help:
@echo "编译:"
@echo " make all -j <n> - 本地编译,不运行,n为要用于编译的CPU核心数"

View File

@ -10,13 +10,17 @@ edition = "2021"
crate-type = ["staticlib"]
[workspace]
members = [ "src/libs/intertrait" ]
members = [
"crates/*",
"src/libs/intertrait"
]
[features]
default = ["backtrace"]
# 内核栈回溯
backtrace = []
# 运行时依赖项
[dependencies]
x86 = "0.52.0"
@ -25,8 +29,6 @@ bit_field = "0.10"
bitflags = "1.3.2"
bitfield-struct = "0.5.3"
virtio-drivers = { git = "https://git.mirrors.dragonos.org/DragonOS-Community/virtio-drivers.git", rev = "f1d1cbb" }
# 一个无锁MPSC队列
thingbuf = { version = "0.1.3", default-features = false, features = ["alloc"] }
smoltcp = { git = "https://git.mirrors.dragonos.org/DragonOS-Community/smoltcp.git", rev = "9027825", default-features = false, features = ["log", "alloc", "socket-raw", "socket-udp", "socket-tcp", "socket-icmp", "socket-dhcpv4", "socket-dns", "proto-ipv4", "proto-ipv6"]}
# num-traits 0.2.15
num-traits = { git = "https://git.mirrors.dragonos.org/DragonOS-Community/num-traits.git", rev="1597c1c", default-features = false }
@ -35,7 +37,6 @@ num-derive = "0.3"
# 一个no_std的hashmap、hashset
hashbrown = "0.13.2"
elf = { version = "0.7.2", default-features = false }
memoffset = "0.9.0"
atomic_enum = "0.2.0"
raw-cpuid = "11.0.1"
acpi = { git = "https://git.mirrors.dragonos.org/DragonOS-Community/acpi-rs.git", rev = "fb69243dcf" }
@ -43,6 +44,9 @@ intertrait = { path = "src/libs/intertrait" }
linkme = "0.2"
ida = { path = "src/libs/ida" }
mini-backtrace = { git = "https://git.mirrors.dragonos.org/DragonOS-Community/mini-backtrace.git", rev = "ba98506685" }
klog_types = { path = "crates/klog_types" }
kdepends = { path = "crates/kdepends" }
# 构建时依赖项
[build-dependencies]

View File

@ -90,7 +90,6 @@ impl CFilesBuilder {
#[cfg(target_arch = "x86_64")]
c.define("__x86_64__", None);
}
}
fn setup_global_include_dir(c: &mut Build) {

View File

@ -1,22 +1,9 @@
#![cfg_attr(not(test), no_std)]
#![feature(const_for)]
#![feature(const_mut_refs)]
#![feature(const_trait_impl)]
#[cfg(test)]
extern crate std;
pub mod crc64;
pub mod tables;
pub fn add(left: usize, right: usize) -> usize {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}

View File

@ -0,0 +1,18 @@
[package]
name = "kdepends"
version = "0.1.0"
edition = "2021"
description = "需要导出的依赖项(为保持内核依赖版本与调试器依赖项版本相同,因此把公共依赖项写在这里)"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
memoffset = "0.9.0"
crc = { path = "../crc" }
# 一个无锁MPSC队列
[dependencies.thingbuf]
git = "https://git.mirrors.dragonos.org/DragonOS-Community/thingbuf.git"
rev = "2dded730c3"
default-features = false
features = ["alloc", "static"]

View File

@ -0,0 +1,9 @@
#![no_std]
#[allow(unused)]
#[macro_use]
pub extern crate thingbuf;
pub extern crate memoffset;
pub extern crate crc;

View File

@ -0,0 +1,9 @@
[package]
name = "klog_types"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
kdepends = { path = "../kdepends" }

View File

@ -0,0 +1,241 @@
#![no_std]
#![feature(const_refs_to_cell)]
#![feature(const_size_of_val)]
extern crate alloc;
use core::{fmt::Debug, mem::size_of_val};
use alloc::format;
use kdepends::{memoffset::offset_of, thingbuf::StaticThingBuf};
#[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct AllocatorLog {
/// 日志的id
pub id: u64,
/// 日志类型
pub type_: AllocatorLogType,
/// 日志的时间
pub time: u64,
/// 日志的来源
pub source: LogSource,
/// 日志的来源pid
pub pid: Option<usize>,
pub checksum: u64,
}
impl AllocatorLog {
/// 创建一个日志
///
/// ## 参数
///
/// - `id`日志的id
/// - `type_`:日志类型
/// - `source`:日志来源
/// - `pid`日志来源的pid
/// - `time`:日志的时间
pub fn new(
id: u64,
type_: AllocatorLogType,
source: LogSource,
pid: Option<usize>,
time: u64,
) -> Self {
let mut x = Self {
id,
type_,
time,
source,
pid,
checksum: 0,
};
let checksum = Self::calculate_checksum(&x);
x.checksum = checksum;
return x;
}
pub const fn zeroed() -> Self {
return Self {
id: 0,
type_: AllocatorLogType::Undefined,
time: 0,
source: LogSource::Undefined,
pid: None,
checksum: 0,
};
}
/// 计算日志的校验和
pub fn calculate_checksum(value: &Self) -> u64 {
let buf = unsafe {
core::slice::from_raw_parts(
value as *const _ as *const u8,
core::mem::size_of::<Self>() - core::mem::size_of::<u64>(),
)
};
let checksum = kdepends::crc::crc64::crc64_be(0, buf);
return checksum;
}
/// 验证日志的校验和
pub fn validate_checksum(&self) -> bool {
let checksum = Self::calculate_checksum(self);
return checksum == self.checksum;
}
/// 当前日志是否有效
pub fn is_valid(&self) -> bool {
if self.validate_checksum() == false {
return false;
}
if self.id == 0 {
return false;
}
return true;
}
}
impl PartialOrd for AllocatorLog {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
return self.id.partial_cmp(&other.id);
}
}
impl Ord for AllocatorLog {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
return self.id.cmp(&other.id);
}
}
/// 内存分配器日志类型
#[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum AllocatorLogType {
Undefined,
Alloc(AllocLogItem),
AllocZeroed(AllocLogItem),
Free(AllocLogItem),
}
#[repr(C)]
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct AllocLogItem {
pub layout: core::alloc::Layout,
pub vaddr: Option<usize>,
pub paddr: Option<usize>,
}
impl AllocLogItem {
pub fn new(layout: core::alloc::Layout, vaddr: Option<usize>, paddr: Option<usize>) -> Self {
return Self {
layout,
vaddr,
paddr,
};
}
}
impl Debug for AllocLogItem {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("AllocLogItem")
.field("layout", &self.layout)
.field(
"vaddr",
&format_args!("{:#x}", *self.vaddr.as_ref().unwrap_or(&0)),
)
.field(
"paddr",
&format_args!("{:#x}", self.paddr.as_ref().unwrap_or(&0)),
)
.finish()
}
}
#[repr(u8)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum LogSource {
Undefined = 0,
Bump = 1,
Buddy = 2,
Slab = 3,
}
pub struct MMLogCycle;
impl MMLogCycle {
pub const fn new() -> Self {
Self {}
}
}
impl kdepends::thingbuf::Recycle<AllocatorLog> for MMLogCycle {
fn new_element(&self) -> AllocatorLog {
AllocatorLog::zeroed()
}
fn recycle(&self, element: &mut AllocatorLog) {
*element = AllocatorLog::zeroed();
}
}
/// 内存分配器日志通道
#[repr(C)]
pub struct MMLogChannel<const CAP: usize> {
pub magic: u32,
/// 日志元素的大小
pub element_size: u32,
/// 日志通道每个槽的大小(字节)
pub slot_size: u32,
pub capacity: u64,
pub slots_offset: u64,
pub buf: StaticThingBuf<AllocatorLog, CAP, MMLogCycle>,
}
impl<const CAP: usize> Debug for MMLogChannel<CAP> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("MMLogChannel")
.field("magic", &format!("{:#x}", self.magic))
.field("element_size", &self.element_size)
.field("capacity", &self.capacity)
.field("slots_offset", &self.slots_offset)
.field(
"buf",
&format!(
"StaticThingBuf<AllocatorLog, {}, MMLogCycle>",
self.capacity
),
)
.finish()
}
}
impl<const CAP: usize> MMLogChannel<CAP> {
/// 日志通道的魔数
pub const MM_LOG_CHANNEL_MAGIC: u32 = 0x4d4c4348;
/// 创建一个大小为`capacity`日志通道
pub const fn new(capacity: usize) -> Self {
let buffer = StaticThingBuf::with_recycle(MMLogCycle::new());
assert!(buffer.offset_of_slots() != 0);
let slot_total_size = size_of_val(&buffer) - buffer.offset_of_slots();
let slot_size = slot_total_size / capacity;
assert!(slot_size != 0);
assert!(slot_size > size_of_val(&AllocatorLog::zeroed()));
let r = Self {
magic: Self::MM_LOG_CHANNEL_MAGIC,
element_size: core::mem::size_of::<AllocatorLog>() as u32,
capacity: capacity as u64,
slot_size: slot_size as u32,
slots_offset: (offset_of!(MMLogChannel<CAP>, buf) + buffer.offset_of_slots()) as u64,
buf: buffer,
};
return r;
}
}

View File

@ -11,7 +11,7 @@ use alloc::{
vec::Vec,
};
use memoffset::offset_of;
use kdepends::memoffset::offset_of;
use x86::{controlregs::Cr4, segmentation::SegmentSelector};
use crate::{

View File

@ -4,7 +4,7 @@ use core::{
sync::atomic::{compiler_fence, AtomicBool, Ordering},
};
use memoffset::offset_of;
use kdepends::memoffset::offset_of;
use crate::{
arch::process::table::TSSManager, exception::InterruptArch,

View File

@ -0,0 +1,78 @@
extern crate klog_types;
use core::intrinsics::unlikely;
use klog_types::{AllocatorLog, AllocatorLogType, LogSource, MMLogChannel};
use crate::{
arch::CurrentTimeArch,
process::{Pid, ProcessManager},
time::TimeArch,
};
/// 全局的内存分配器日志通道
///
/// 标记为`no_mangle`是为了让调试器能够找到这个变量
#[no_mangle]
static __MM_ALLOCATOR_LOG_CHANNEL: MMLogChannel<{ MMDebugLogManager::MAX_ALLOC_LOG_NUM }> =
MMLogChannel::new(MMDebugLogManager::MAX_ALLOC_LOG_NUM);
/// 全局的内存分配器日志id分配器
///
/// id从1开始, 因为0是无效的id
static __MM_DEBUG_LOG_IDA: ida::IdAllocator = ida::IdAllocator::new(1, usize::MAX);
/// 记录内存分配器的日志
///
/// ## 参数
///
/// - `log_type`:日志类型
/// - `source`:日志来源
pub fn mm_debug_log(log_type: AllocatorLogType, source: LogSource) {
let pid = if unlikely(!ProcessManager::initialized()) {
Some(Pid::new(0))
} else {
Some(ProcessManager::current_pcb().pid())
};
MMDebugLogManager::log(log_type, source, pid);
}
#[derive(Debug)]
pub(super) struct MMDebugLogManager;
impl MMDebugLogManager {
/// 最大的内存分配器日志数量
pub const MAX_ALLOC_LOG_NUM: usize = 100000;
/// 记录内存分配器的日志
///
/// ## 参数
///
/// - `log_type`:日志类型
/// - `source`:日志来源
/// - `pid`日志来源的pid
pub fn log(log_type: AllocatorLogType, source: LogSource, pid: Option<Pid>) {
let id = __MM_DEBUG_LOG_IDA.alloc().unwrap();
let log = AllocatorLog::new(
id as u64,
log_type,
source,
pid.map(|p| p.data()),
CurrentTimeArch::get_cycles() as u64,
);
let mut log = log;
loop {
let r = __MM_ALLOCATOR_LOG_CHANNEL.buf.push(log);
if let Err(r) = r {
// 如果日志通道满了,就把最早的日志丢弃
if __MM_ALLOCATOR_LOG_CHANNEL.buf.remaining() == 0 {
__MM_ALLOCATOR_LOG_CHANNEL.buf.pop();
}
log = r.into_inner();
} else {
break;
}
}
}
}

View File

@ -0,0 +1 @@
pub mod mm;

1
kernel/src/debug/mod.rs Normal file
View File

@ -0,0 +1 @@
pub mod klog;

View File

@ -26,7 +26,7 @@ use crate::{
use super::{super::device::DeviceState, platform_bus, platform_bus_device, CompatibleTable};
/// 平台设备id分配器
static PLATFORM_DEVID_IDA: IdAllocator = IdAllocator::new(i32::MAX as usize);
static PLATFORM_DEVID_IDA: IdAllocator = IdAllocator::new(0, i32::MAX as usize);
#[inline(always)]
pub fn platform_device_manager() -> &'static PlatformDeviceManager {

View File

@ -2,7 +2,7 @@ use core::intrinsics::unlikely;
use alloc::string::String;
use thingbuf::mpsc::{
use kdepends::thingbuf::mpsc::{
self,
errors::{TryRecvError, TrySendError},
};

View File

@ -179,23 +179,24 @@ impl ProcFSInode {
);
pdata.append(&mut format!("\nvrtime:\t{}", vrtime).as_bytes().to_owned());
let binding = pcb.basic().user_vm().unwrap();
let address_space_guard = binding.read();
// todo: 当前进程运行过程中占用内存的峰值
let hiwater_vm: u64 = 0;
// 进程代码段的大小
let text = (address_space_guard.end_code - address_space_guard.start_code) / 1024;
// 进程数据段的大小
let data = (address_space_guard.end_data - address_space_guard.start_data) / 1024;
drop(address_space_guard);
if let Some(user_vm) = pcb.basic().user_vm() {
let address_space_guard = user_vm.read();
// todo: 当前进程运行过程中占用内存的峰值
let hiwater_vm: u64 = 0;
// 进程代码段的大小
let text = (address_space_guard.end_code - address_space_guard.start_code) / 1024;
// 进程数据段的大小
let data = (address_space_guard.end_data - address_space_guard.start_data) / 1024;
drop(address_space_guard);
pdata.append(
&mut format!("\nVmPeak:\t{} kB", hiwater_vm)
.as_bytes()
.to_owned(),
);
pdata.append(&mut format!("\nVmData:\t{} kB", data).as_bytes().to_owned());
pdata.append(&mut format!("\nVmExe:\t{} kB", text).as_bytes().to_owned());
}
pdata.append(
&mut format!("\nVmPeak:\t{} kB", hiwater_vm)
.as_bytes()
.to_owned(),
);
pdata.append(&mut format!("\nVmData:\t{} kB", data).as_bytes().to_owned());
pdata.append(&mut format!("\nVmExe:\t{} kB", text).as_bytes().to_owned());
pdata.append(
&mut format!("\nflags: {:?}\n", pcb.flags().clone())
.as_bytes()

View File

@ -50,10 +50,13 @@ impl Signal {
kwarn!("No such process.");
return retval;
}
let pcb = pcb.unwrap();
// println!("Target pcb = {:?}", pcb.as_ref().unwrap());
compiler_fence(core::sync::atomic::Ordering::SeqCst);
// 发送信号
retval = self.send_signal(info, pcb.unwrap(), PidType::PID);
retval = self.send_signal(info, pcb.clone(), PidType::PID);
compiler_fence(core::sync::atomic::Ordering::SeqCst);
return retval;
}
@ -282,8 +285,33 @@ fn signal_wake_up(pcb: Arc<ProcessControlBlock>, _guard: SpinLockGuard<SignalStr
// 如果不是 fatal 的就只唤醒 stop 的进程来响应
// kdebug!("signal_wake_up");
// 如果目标进程已经在运行则发起一个ipi使得它陷入内核
let r = ProcessManager::wakeup_stop(&pcb);
if r.is_ok() {
let state = pcb.sched_info().state();
let mut wakeup_ok = true;
if state.is_blocked_interruptable() {
ProcessManager::wakeup(&pcb).unwrap_or_else(|e| {
wakeup_ok = false;
kwarn!(
"Current pid: {:?}, signal_wake_up target {:?} error: {:?}",
ProcessManager::current_pcb().pid(),
pcb.pid(),
e
);
});
} else if state.is_stopped() {
ProcessManager::wakeup_stop(&pcb).unwrap_or_else(|e| {
wakeup_ok = false;
kwarn!(
"Current pid: {:?}, signal_wake_up target {:?} error: {:?}",
ProcessManager::current_pcb().pid(),
pcb.pid(),
e
);
});
} else {
wakeup_ok = false;
}
if wakeup_ok {
ProcessManager::kick(&pcb);
} else {
if fatal {

View File

@ -39,6 +39,7 @@ mod arch;
mod libs;
#[macro_use]
mod include;
mod debug;
mod driver; // 如果driver依赖了libs应该在libs后面导出
mod exception;
mod filesystem;
@ -60,17 +61,17 @@ extern crate bitflags;
extern crate elf;
#[macro_use]
extern crate lazy_static;
extern crate memoffset;
extern crate num;
#[macro_use]
extern crate num_derive;
extern crate smoltcp;
extern crate thingbuf;
#[macro_use]
extern crate intertrait;
#[cfg(target_arch = "x86_64")]
extern crate x86;
extern crate klog_types;
use crate::mm::allocator::kernel_allocator::KernelAllocator;
use crate::process::ProcessManager;

View File

@ -16,9 +16,9 @@ pub struct IdAllocator {
impl IdAllocator {
/// 创建一个新的id分配器
pub const fn new(max_id: usize) -> Self {
pub const fn new(initial_id: usize, max_id: usize) -> Self {
Self {
current_id: AtomicUsize::new(0),
current_id: AtomicUsize::new(initial_id),
max_id,
dead: AtomicBool::new(false),
}

View File

@ -1,5 +1,8 @@
use klog_types::AllocLogItem;
use crate::{
arch::mm::LockedFrameAllocator,
debug::klog::mm::mm_debug_log,
libs::align::page_align_up,
mm::{MMArch, MemoryManagementArch, VirtAddr},
};
@ -81,15 +84,46 @@ impl LocalAlloc for KernelAllocator {
/// 为内核slab分配器实现GlobalAlloc特性
unsafe impl GlobalAlloc for KernelAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
return self.local_alloc(layout);
let r = self.local_alloc(layout);
mm_debug_log(
klog_types::AllocatorLogType::Alloc(AllocLogItem::new(
layout.clone(),
Some(r as usize),
None,
)),
klog_types::LogSource::Buddy,
);
return r;
// self.local_alloc_zeroed(layout, 0)
}
unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
self.local_alloc_zeroed(layout)
let r = self.local_alloc_zeroed(layout);
mm_debug_log(
klog_types::AllocatorLogType::AllocZeroed(AllocLogItem::new(
layout.clone(),
Some(r as usize),
None,
)),
klog_types::LogSource::Buddy,
);
return r;
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
mm_debug_log(
klog_types::AllocatorLogType::Free(AllocLogItem::new(
layout.clone(),
Some(ptr as usize),
None,
)),
klog_types::LogSource::Buddy,
);
self.local_dealloc(ptr, layout);
}
}

View File

@ -121,6 +121,11 @@ impl ProcessManager {
kinfo!("Process Manager initialized.");
}
/// 判断进程管理器是否已经初始化完成
pub fn initialized() -> bool {
unsafe { __PROCESS_MANAGEMENT_INIT_DONE }
}
/// 获取当前进程的pcb
pub fn current_pcb() -> Arc<ProcessControlBlock> {
if unlikely(unsafe { !__PROCESS_MANAGEMENT_INIT_DONE }) {
@ -461,6 +466,11 @@ impl ProcessState {
return matches!(self, ProcessState::Blocked(_));
}
#[inline(always)]
pub fn is_blocked_interruptable(&self) -> bool {
return matches!(self, ProcessState::Blocked(true));
}
#[inline(always)]
pub fn is_exited(&self) -> bool {
return matches!(self, ProcessState::Exited(_));

6
tools/Cargo.toml Normal file
View File

@ -0,0 +1,6 @@
[workspace]
members = [
"debugging/logmonitor",
]
resolver = "2"

9
tools/Makefile Normal file
View File

@ -0,0 +1,9 @@
.PHONY: fmt
fmt:
@cargo fmt --all $(FMT_CHECK)
clean:
@cargo clean
check:
@cargo check --all

1
tools/debugging/logmonitor/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/logs/

View 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"

View File

@ -0,0 +1,10 @@
# 日志监视程序
本程序监视DragonOS内核的环形缓冲区日志并将其显示在屏幕上。
## 使用方法
1. 默认情况下DragonOS内核已启用内存分配器的日志记录。
2. 当qemu启动后在DragonOS项目的根目录中运行`make log-monitor`
3. 在`logs`目录查看日志文件。

View 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));
}
}

View 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()
)
}
}
}
}

View 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,
}

View 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
}
}

View 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);
}
}

View 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) {}
}

View 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 }
}
}

View 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
}
}

View 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());
}
}

View File

@ -0,0 +1,6 @@
use std::sync::RwLock;
use crate::command::CommandLineArgs;
/// 启动时的命令行参数
pub static CMD_ARGS: RwLock<Option<CommandLineArgs>> = RwLock::new(None);

View 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()
}
}

View 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(());
}

View 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;

View 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();
}
}

View 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(())
}

View 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(())
}
}

View 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);
}

View File

@ -51,6 +51,9 @@ fi
QEMU=qemu-system-x86_64
QEMU_DISK_IMAGE="../bin/disk.img"
QEMU_MEMORY="512M"
QEMU_MEMORY_BACKEND="dragonos-qemu-shm.ram"
QEMU_MEMORY_BACKEND_PATH_PREFIX="/dev/shm"
QEMU_SHM_OBJECT="-object memory-backend-file,size=${QEMU_MEMORY},id=${QEMU_MEMORY_BACKEND},mem-path=${QEMU_MEMORY_BACKEND_PATH_PREFIX}/${QEMU_MEMORY_BACKEND},share=on "
QEMU_SMP="2,cores=2,threads=1,sockets=1"
QEMU_MONITOR="stdio"
QEMU_TRACE="${qemu_trace_std}"
@ -65,17 +68,20 @@ if [ -n "${qemu_accel}" ]; then
QEMU_ACCELARATE="-machine accel=${qemu_accel} -enable-kvm "
fi
QEMU_MACHINE=" -machine q35,memory-backend=${QEMU_MEMORY_BACKEND} "
# ps: 下面这条使用tap的方式无法dhcp获取到ip暂时不知道为什么
# QEMU_DEVICES="-device ahci,id=ahci -device ide-hd,drive=disk,bus=ahci.0 -net nic,netdev=nic0 -netdev tap,id=nic0,model=virtio-net-pci,script=qemu/ifup-nat,downscript=qemu/ifdown-nat -usb -device qemu-xhci,id=xhci,p2=8,p3=4 -machine q35 "
QEMU_DEVICES="-device ahci,id=ahci -device ide-hd,drive=disk,bus=ahci.0 -netdev user,id=hostnet0,hostfwd=tcp::12580-:12580 -device virtio-net-pci,vectors=5,netdev=hostnet0,id=net0 -usb -device qemu-xhci,id=xhci,p2=8,p3=4 -machine q35 "
# QEMU_DEVICES="-device ahci,id=ahci -device ide-hd,drive=disk,bus=ahci.0 -net nic,netdev=nic0 -netdev tap,id=nic0,model=virtio-net-pci,script=qemu/ifup-nat,downscript=qemu/ifdown-nat -usb -device qemu-xhci,id=xhci,p2=8,p3=4 "
QEMU_DEVICES="-device ahci,id=ahci -device ide-hd,drive=disk,bus=ahci.0 -netdev user,id=hostnet0,hostfwd=tcp::12580-:12580 -device virtio-net-pci,vectors=5,netdev=hostnet0,id=net0 -usb -device qemu-xhci,id=xhci,p2=8,p3=4 "
# E1000E
# QEMU_DEVICES="-device ahci,id=ahci -device ide-hd,drive=disk,bus=ahci.0 -netdev user,id=hostnet0,hostfwd=tcp::12580-:12580 -net nic,model=e1000e,netdev=hostnet0,id=net0 -netdev user,id=hostnet1,hostfwd=tcp::12581-:12581 -device virtio-net-pci,vectors=5,netdev=hostnet1,id=net1 -usb -device qemu-xhci,id=xhci,p2=8,p3=4 -machine q35 "
# QEMU_DEVICES="-device ahci,id=ahci -device ide-hd,drive=disk,bus=ahci.0 -netdev user,id=hostnet0,hostfwd=tcp::12580-:12580 -net nic,model=e1000e,netdev=hostnet0,id=net0 -netdev user,id=hostnet1,hostfwd=tcp::12581-:12581 -device virtio-net-pci,vectors=5,netdev=hostnet1,id=net1 -usb -device qemu-xhci,id=xhci,p2=8,p3=4 "
QEMU_ARGUMENT="-d ${QEMU_DISK_IMAGE} -m ${QEMU_MEMORY} -smp ${QEMU_SMP} -boot order=d -monitor ${QEMU_MONITOR} -d ${qemu_trace_std} "
QEMU_ARGUMENT+="-s -S -cpu ${QEMU_CPU_FEATURES} -rtc ${QEMU_RTC_CLOCK} -serial ${QEMU_SERIAL} -drive ${QEMU_DRIVE} ${QEMU_DEVICES}"
QEMU_ARGUMENT+="-s -S ${QEMU_MACHINE} -cpu ${QEMU_CPU_FEATURES} -rtc ${QEMU_RTC_CLOCK} -serial ${QEMU_SERIAL} -drive ${QEMU_DRIVE} ${QEMU_DEVICES}"
QEMU_ARGUMENT+=" ${QEMU_SHM_OBJECT} "
QEMU_ARGUMENT+=" ${QEMU_ACCELARATE} "
if [ $flag_can_run -eq 1 ]; then
while true;do
case "$1" in
@ -100,6 +106,9 @@ if [ $flag_can_run -eq 1 ]; then
esac
done
# 删除共享内存
sudo rm -rf ${QEMU_MEMORY_BACKEND_PATH_PREFIX}/${QEMU_MEMORY_BACKEND}
if [ ${BIOS_TYPE} == uefi ] ;then
if [ ${ARCH} == x86_64 ] ;then
sudo ${QEMU} -bios arch/x86_64/efi/OVMF-pure-efi.fd ${QEMU_ARGUMENT}
@ -109,7 +118,8 @@ if [ ${BIOS_TYPE} == uefi ] ;then
else
sudo ${QEMU} ${QEMU_ARGUMENT}
fi
# 删除共享内存
sudo rm -rf ${QEMU_MEMORY_BACKEND_PATH_PREFIX}/${QEMU_MEMORY_BACKEND}
else
echo "不满足运行条件"
fi