perf: 消息内存按字节预算,杜绝大报文流撑爆内存
- 新增 MessageRing:条数上限 + 每连接 32MB 字节预算双重驱逐 (从最旧丢弃,均摊 O(1));预分配容量按设置钳制在 512 - 单条 payload 保留上限 256KB → 128KB;MqttRecord::retained_bytes 计入文本与原始字节,供预算驱逐与资源监控展示 - 清空消息统一走 clear_messages;设置页标注字节预算兜底说明 - 新增单元测试:条数驱逐、字节预算、超预算单条保留、驱逐顺序
This commit is contained in:
+14
-14
@@ -24,8 +24,8 @@ use gpui_kit::{
|
||||
use crate::aliyun::AliyunPreset;
|
||||
use crate::model::{
|
||||
AppSettings, ConnectionConfig, ConnectionStatus, Direction, GlobalVariable, LogEntry,
|
||||
LogLevel, MqttRecord, PublishParams, PublishPreset, SubscribeOptions, Subscription,
|
||||
ThemeModePref, retained_payload,
|
||||
LogLevel, MqttRecord, MessageRing, PublishParams, PublishPreset, SubscribeOptions,
|
||||
Subscription, ThemeModePref, retained_payload,
|
||||
};
|
||||
use crate::mqtt::{EngineEvent, MqttEngine};
|
||||
use crate::store::Storage;
|
||||
@@ -84,8 +84,8 @@ pub struct MqttXApp {
|
||||
// ── 运行时状态 ──
|
||||
pub statuses: HashMap<String, ConnectionStatus>,
|
||||
pub errors: HashMap<String, String>,
|
||||
/// 每连接消息环形缓冲:键为驻留后的连接 ID,容量受 max_messages 约束
|
||||
pub messages: HashMap<Arc<str>, VecDeque<MqttRecord>>,
|
||||
/// 每连接消息环形缓冲:键为驻留后的连接 ID,条数 + 字节预算双重约束
|
||||
pub messages: HashMap<Arc<str>, MessageRing>,
|
||||
pub logs: VecDeque<LogEntry>,
|
||||
pub seq: u64,
|
||||
|
||||
@@ -473,16 +473,16 @@ impl MqttXApp {
|
||||
}
|
||||
|
||||
fn push_message(&mut self, connection_id: Arc<str>, record: MqttRecord) {
|
||||
let cap = self.settings.max_messages.max(100);
|
||||
let list = self
|
||||
.messages
|
||||
let cap = self.settings.max_messages;
|
||||
self.messages
|
||||
.entry(connection_id)
|
||||
.or_insert_with(|| VecDeque::with_capacity(cap));
|
||||
// 环形缓冲:满则丢最旧,均摊 O(1)(原来的 Vec::drain 是 O(n) 搬移)
|
||||
while list.len() >= cap {
|
||||
list.pop_front();
|
||||
}
|
||||
list.push_back(record);
|
||||
.or_insert_with(|| MessageRing::new(cap))
|
||||
.push(record, cap);
|
||||
}
|
||||
|
||||
/// 清空指定连接的消息(内存缓冲随 drop 释放)。
|
||||
pub fn clear_messages(&mut self, id: &str) {
|
||||
self.messages.remove(id);
|
||||
}
|
||||
|
||||
fn push_log(&mut self, entry: LogEntry) {
|
||||
@@ -603,7 +603,7 @@ impl MqttXApp {
|
||||
self.engine.close(id, false);
|
||||
self.connections.retain(|c| c.id != id);
|
||||
self.subscriptions.retain(|s| s.connection_id != id);
|
||||
self.messages.remove(id);
|
||||
self.clear_messages(id);
|
||||
self.statuses.remove(id);
|
||||
self.errors.remove(id);
|
||||
self.views.remove(id);
|
||||
|
||||
@@ -1765,7 +1765,7 @@ impl ConnectionView {
|
||||
view.detail_format = DetailFormat::Auto;
|
||||
if let Some(app) = view.app.upgrade() {
|
||||
app.update(cx, |app, cx| {
|
||||
app.messages.remove(clear_id.as_str());
|
||||
app.clear_messages(&clear_id);
|
||||
cx.notify();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ use gpui_kit::{
|
||||
Styled as _, Window,
|
||||
};
|
||||
|
||||
use crate::model::{AppSettings, ThemeModePref};
|
||||
use crate::model::{AppSettings, ThemeModePref, MAX_CONNECTION_MESSAGE_BYTES};
|
||||
use crate::mqtt::MqttEngine;
|
||||
use crate::ui::app::MqttXApp;
|
||||
use crate::ui::widgets::{field, make_select, OptionDelegate};
|
||||
@@ -388,6 +388,15 @@ impl Render for SettingsDialog {
|
||||
"每条连接内存中保留的消息条数(100~100000)",
|
||||
Input::new(&self.max_messages),
|
||||
))
|
||||
.child(
|
||||
div()
|
||||
.text_xs()
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.child(format!(
|
||||
"另有每连接 {}MB 的字节预算兜底:大报文流会优先按字节从最旧开始驱逐",
|
||||
MAX_CONNECTION_MESSAGE_BYTES / (1024 * 1024)
|
||||
)),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_between()
|
||||
|
||||
Reference in New Issue
Block a user