dsh-flash-mem-mon 0.1.5 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -38
- package/README.zh-CN.md +38 -38
- package/cordis.patch.yml +11 -11
- package/dist/index.js +440 -116
- package/dist/pure.js +119 -0
- package/lib/client.js +2595 -2071
- package/package.json +23 -14
package/lib/client.js
CHANGED
|
@@ -1,308 +1,497 @@
|
|
|
1
|
-
// dsh-flash-mem-mon — Memory monitor client half
|
|
2
|
-
// Single file, no build step. Loaded via window.__ModuleLoader__.
|
|
3
|
-
//
|
|
4
|
-
// Migrated from dock-flash/lib/client.js:
|
|
5
|
-
// - Memory i18n keys (zh/en)
|
|
6
|
-
// - createHostMemoryAlertProvider() → provider id dsh-flash-mem-mon:host-memory-alert
|
|
7
|
-
// - Memory enable toggle → dsh-flash-mem-mon:monitor-memory
|
|
8
|
-
// - MONITOR_SLIDER_FIELDS.memory (9 sliders)
|
|
9
|
-
// - MonitorConfigModal (memory-only branch)
|
|
10
|
-
// - _memPrefs preference system
|
|
11
|
-
//
|
|
12
|
-
// Route: /plugins/dsh-flash-mem-mon/memory-trend (own route)
|
|
13
|
-
// Cross-plugin: /plugins/
|
|
14
|
-
//
|
|
15
|
-
// Provider id: dsh-flash-mem-mon:host-memory-alert
|
|
16
|
-
// Toggle key: dsh-flash-mem-mon:monitor-memory
|
|
17
|
-
// Settings namespace: dsh-flash-mem-mon
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
// GC
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
1
|
+
// dsh-flash-mem-mon — Memory monitor client half
|
|
2
|
+
// Single file, no build step. Loaded via window.__ModuleLoader__.
|
|
3
|
+
//
|
|
4
|
+
// Migrated from dock-flash/lib/client.js:
|
|
5
|
+
// - Memory i18n keys (zh/en)
|
|
6
|
+
// - createHostMemoryAlertProvider() → provider id dsh-flash-mem-mon:host-memory-alert
|
|
7
|
+
// - Memory enable toggle → dsh-flash-mem-mon:monitor-memory
|
|
8
|
+
// - MONITOR_SLIDER_FIELDS.memory (9 sliders)
|
|
9
|
+
// - MonitorConfigModal (memory-only branch)
|
|
10
|
+
// - _memPrefs preference system
|
|
11
|
+
//
|
|
12
|
+
// Route: /plugins/dsh-flash-mem-mon/memory-trend (own route)
|
|
13
|
+
// Cross-plugin: /plugins/dsh-flash/health (read-only, for live host memory data)
|
|
14
|
+
//
|
|
15
|
+
// Provider id: dsh-flash-mem-mon:host-memory-alert
|
|
16
|
+
// Toggle key: dsh-flash-mem-mon:monitor-memory
|
|
17
|
+
// Settings namespace: dsh-flash-mem-mon
|
|
18
|
+
//
|
|
19
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
20
|
+
// FILE MAP — region layout (collapsible, in file order; line numbers are not
|
|
21
|
+
// maintained, the markers are the source of truth):
|
|
22
|
+
// #region i18n zh/en message tables + the `locale`
|
|
23
|
+
// service proxy (t / L / bindLocaleService)
|
|
24
|
+
// #region Preference system _MEM_ALERT_DEFAULTS, _memPrefs,
|
|
25
|
+
// _alertPref / loadMemPrefs,
|
|
26
|
+
// remote settings namespace bridge
|
|
27
|
+
// #region Shared trend cache _summaryCache + _subscribeSummary
|
|
28
|
+
// #region AlertProvider createHostMemoryAlertProvider:
|
|
29
|
+
// poll + emitIfChanged hysteresis
|
|
30
|
+
// #region Icon helpers small inline SVG icons
|
|
31
|
+
// #region ErrorBoundary class error boundary for modal
|
|
32
|
+
// #region Styles inline stylesheet objects
|
|
33
|
+
// #region MonitorConfigModal memory-only config modal + trend
|
|
34
|
+
// #region Monitor toggle helpers enable/disable toggle plumbing
|
|
35
|
+
// #region Client factory inject/apply/dispose entry point
|
|
36
|
+
//
|
|
37
|
+
// DEPENDENCY/NOTE: this is deliberately a SINGLE FILE with no build step.
|
|
38
|
+
// • Everything shares the `factory(require)` closure scope; no cross-file
|
|
39
|
+
// verbundling. Threshold DEFAULT values mirror the HOST constants in
|
|
40
|
+
// src/index.ts (change one, change both — see _MEM_ALERT_DEFAULTS).
|
|
41
|
+
// • `mode=summary` polling is de-duplicated via the Shared trend cache;
|
|
42
|
+
// `mode=full` (sparkline) is fetched independently by the modal.
|
|
43
|
+
//
|
|
44
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
window.__ModuleLoader__.load({
|
|
47
|
+
id: 'dsh-flash-mem-mon',
|
|
48
|
+
factory: (require) => {
|
|
49
|
+
|
|
50
|
+
var React = require('react')
|
|
51
|
+
var h = React.createElement
|
|
52
|
+
var useState = React.useState
|
|
53
|
+
var useEffect = React.useEffect
|
|
54
|
+
var useRef = React.useRef
|
|
55
|
+
var Component = React.Component
|
|
56
|
+
var ReactDOMClient = null
|
|
57
|
+
try { ReactDOMClient = require('react-dom/client') } catch (_) {}
|
|
58
|
+
|
|
59
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
60
|
+
//#region i18n ────────────────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
var zh = {
|
|
63
|
+
alertMemCluster: '性能监控',
|
|
64
|
+
monitorOn: '已开启',
|
|
65
|
+
monitorOff: '已关闭',
|
|
66
|
+
monitorConfig: '配置',
|
|
67
|
+
// Memory thresholds
|
|
68
|
+
memConfigGroupRss: 'RSS 阈值 / 轮询',
|
|
69
|
+
memConfigGroupGc: 'Major GC 阈值',
|
|
70
|
+
memThresholdInfo: 'RSS 信息阈值',
|
|
71
|
+
memThresholdInfoTip: 'RSS(常驻内存集)超过此值时触发信息级告警。信息级仅提示,不影响行为。建议设为正常负载的 1.5–2 倍。',
|
|
72
|
+
memThresholdWarning: 'RSS 警告阈值',
|
|
73
|
+
memThresholdWarningTip: 'RSS 超过此值时触发警告级告警。此时内存压力较大,应关注是否有泄漏。必须大于信息阈值。',
|
|
74
|
+
memThresholdError: 'RSS 错误阈值',
|
|
75
|
+
memThresholdErrorTip: 'RSS 超过此值时触发错误级告警。内存已接近危险区,建议尽快排查。必须大于警告阈值。',
|
|
76
|
+
memThresholdCritical: 'RSS 严重阈值',
|
|
77
|
+
memThresholdCriticalTip: 'RSS 超过此值时触发严重级告警。进程可能即将被 OOM Killer 终止,需立即处理。必须大于错误阈值。',
|
|
78
|
+
memPollBase: '内存轮询基础间隔',
|
|
79
|
+
memPollBaseTip: '堆使用率较低时的采样间隔(天花板)。空闲时以这个频率采样,堆越满采样越密。默认 30 秒,一般不需要调低。',
|
|
80
|
+
memPollMin: '内存轮询最小间隔',
|
|
81
|
+
memPollMinTip: '堆使用率很高时的采样间隔(地板)。无论堆多满,两次采样不会比这更密,避免过度消耗 CPU。默认 2 秒。',
|
|
82
|
+
memGrowthThreshold: 'RSS 增长率阈值',
|
|
83
|
+
memGrowthThresholdTip: 'RSS 线性回归增长率超过此值(且趋势上升)时触发“持续增长”告警。只有一个固定阈值,不区分告警级别。默认 1 MB/min。',
|
|
84
|
+
// GC thresholds
|
|
85
|
+
gcThresholdInfo: 'Major GC 信息阈值',
|
|
86
|
+
gcThresholdInfoTip: '过去 5 分钟内 Major GC(MarkSweep/MarkCompact)频率超过此值时触发信息级告警。1/min 即每分钟一次 Full GC,正常负载下应远低于此。',
|
|
87
|
+
gcThresholdWarning: 'Major GC 警告阈值',
|
|
88
|
+
gcThresholdWarningTip: 'Major GC 频率超过此值时触发警告级告警。GC 已频繁到影响吞吐量,堆分配/回收振荡剧烈。必须大于信息阈值。',
|
|
89
|
+
gcThresholdError: 'Major GC 错误阈值',
|
|
90
|
+
gcThresholdErrorTip: 'Major GC 频率超过此值时触发错误级告警。GC 自身开销占比很高,接近或已进入泄漏状态。必须大于警告阈值。',
|
|
91
|
+
// Alert preview
|
|
92
|
+
alertPreviewTitle: '告警示例',
|
|
93
|
+
alertPreviewToggle: '查看告警消息样例',
|
|
94
|
+
alertPreviewCollapse: '收起',
|
|
95
|
+
alertPreviewCriteriaRss: 'RSS 绝对值',
|
|
96
|
+
alertPreviewCriteriaGrowth: 'RSS 增长率',
|
|
97
|
+
alertPreviewCriteriaGc: 'Major GC 频率',
|
|
98
|
+
alertPreviewTrigger: '触发',
|
|
99
|
+
// Alert title (includes severity level and threshold value)
|
|
100
|
+
alertHostMemInfo: 'RSS 超过信息级阈值 {threshold} MB',
|
|
101
|
+
alertHostMemWarning: 'RSS 超过警告级阈值 {threshold} MB',
|
|
102
|
+
alertHostMemError: 'RSS 超过错误级阈值 {threshold} MB',
|
|
103
|
+
alertHostMemCritical: 'RSS 超过严重级阈值 {threshold} MB',
|
|
104
|
+
alertHostMemMsg: 'RSS {v} MB | 堆 {heap}/{heapTotal} MB ({pct}%) | 外部 {ext} MB | ArrayBuffer {ab} MB | 趋势 {trend} | 增长 +{slope} MB/min{gcInfo}',
|
|
105
|
+
alertHostMemGrowth: 'RSS 持续增长',
|
|
106
|
+
alertHostMemGrowthMsg: '+{r} MB/min | RSS {rss} MB | 堆 {heap}/{heapTotal} MB ({pct}%) | ArrayBuffer {ab} MB | 趋势 {trend}',
|
|
107
|
+
alertHostGcInfo: 'GC 频率较高',
|
|
108
|
+
alertHostGcWarning: 'GC 频率异常',
|
|
109
|
+
alertHostGcError: 'GC 频率极高',
|
|
110
|
+
alertHostGcMsg: 'Major {v} 次/min | 暂停 {pause} ms/min | Minor {minor} 次 | RSS {rss} MB | 堆 {heap}/{heapTotal} MB ({pct}%)',
|
|
111
|
+
// Alert disposal buttons (registered via `actions` — see dsh-flash contract)
|
|
112
|
+
alertDisposeDumpHeap: '立即转储堆快照',
|
|
113
|
+
alertDumping: '正在转储堆快照…',
|
|
114
|
+
alertDumpSaved: '已保存快照 {name}({size}),可在 DevTools → Memory → Load 中载入分析',
|
|
115
|
+
alertDumpFailed: '堆快照失败:{error}',
|
|
116
|
+
alertSevInfo: '信息',
|
|
117
|
+
alertSevWarning: '警告',
|
|
118
|
+
alertSevError: '错误',
|
|
119
|
+
alertSevCritical: '严重',
|
|
120
|
+
// Memory live data
|
|
121
|
+
memLiveData: '当前数据',
|
|
122
|
+
// Host process memory
|
|
123
|
+
memHostSection: 'Host 进程',
|
|
124
|
+
memHostRSS: '常驻内存',
|
|
125
|
+
memHostHeapTotal: '堆总大小',
|
|
126
|
+
memHostHeapUsed: '堆已用',
|
|
127
|
+
memHostExternal: 'C++ 外部',
|
|
128
|
+
memHostArrayBuffers: 'ArrayBuffer',
|
|
129
|
+
memHostHeapRatio: '堆使用率',
|
|
130
|
+
memHostUnavailable: '无法获取 Host 内存数据',
|
|
131
|
+
// GC stats
|
|
132
|
+
memGcMajorPerMin: 'Major GC/min',
|
|
133
|
+
memGcPausePerMin: 'GC 暂停/min',
|
|
134
|
+
memGcMinorCount: 'Minor GC',
|
|
135
|
+
memGcMajorCount: 'Major GC',
|
|
136
|
+
memGcUnavailable: 'GC 监控不可用',
|
|
137
|
+
memGcEventList: 'Full GC 记录',
|
|
138
|
+
memGcEventDuration: '耗时',
|
|
139
|
+
memGcEventTime: '时间',
|
|
140
|
+
memGcEventClick: '点击定位',
|
|
141
|
+
memGcNoEvents: '近期无 Full GC',
|
|
142
|
+
memGcEventRssDrop: '内存释放',
|
|
143
|
+
// Memory trend
|
|
144
|
+
memTrend: '内存趋势',
|
|
145
|
+
memTrendPeak: 'RSS 峰值',
|
|
146
|
+
memTrendDirection: '趋势',
|
|
147
|
+
memTrendUp: '↑ 上升',
|
|
148
|
+
memTrendStable: '→ 稳定',
|
|
149
|
+
memTrendDown: '↓ 下降',
|
|
150
|
+
memTrendSamples: '采样数',
|
|
151
|
+
memTrendSpan: '跨度',
|
|
152
|
+
memTrendHeapRatio: '堆使用率',
|
|
153
|
+
memTrendNoData: '暂无数据',
|
|
154
|
+
memTrendHoverHeap: '堆',
|
|
155
|
+
memTrendHoverRSS: 'RSS',
|
|
156
|
+
memTrendHoverTime: '时间',
|
|
157
|
+
memTrendGrowthRate: 'RSS 增长率',
|
|
158
|
+
memTrendAxisHeap: '堆',
|
|
159
|
+
memTrendAxisTime: '时间',
|
|
160
|
+
memTrendInfo: '趋势信息',
|
|
161
|
+
// Session footprint
|
|
162
|
+
footprintTitle: '会话足迹',
|
|
163
|
+
footprintTotalSessions: '总会话数',
|
|
164
|
+
footprintArchivedSessions: '归档会话数',
|
|
165
|
+
footprintOnDisk: '磁盘占用',
|
|
166
|
+
footprintNoData: '暂无足迹数据',
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
var en = {
|
|
170
|
+
alertMemCluster: 'Performance Monitor',
|
|
171
|
+
monitorOn: 'Enabled',
|
|
172
|
+
monitorOff: 'Disabled',
|
|
173
|
+
monitorConfig: 'Configure',
|
|
174
|
+
// Memory thresholds
|
|
175
|
+
memConfigGroupRss: 'RSS / Polling',
|
|
176
|
+
memConfigGroupGc: 'Major GC',
|
|
177
|
+
memThresholdInfo: 'RSS Info Threshold',
|
|
178
|
+
memThresholdInfoTip: 'Triggers an info-level alert when RSS (Resident Set Size) exceeds this value. Info is informational only. Recommended: 1.5–2× your normal workload RSS.',
|
|
179
|
+
memThresholdWarning: 'RSS Warning Threshold',
|
|
180
|
+
memThresholdWarningTip: 'Triggers a warning alert when RSS exceeds this value. Memory pressure is significant — watch for leaks. Must be greater than the Info threshold.',
|
|
181
|
+
memThresholdError: 'RSS Error Threshold',
|
|
182
|
+
memThresholdErrorTip: 'Triggers an error alert when RSS exceeds this value. Memory is in the danger zone — investigate promptly. Must be greater than the Warning threshold.',
|
|
183
|
+
memThresholdCritical: 'RSS Critical Threshold',
|
|
184
|
+
memThresholdCriticalTip: 'Triggers a critical alert when RSS exceeds this value. The process may be OOM-killed soon — act immediately. Must be greater than the Error threshold.',
|
|
185
|
+
memPollBase: 'Memory Poll Base Interval',
|
|
186
|
+
memPollBaseTip: 'Sampling interval when heap usage is low (the ceiling). At idle, sampling runs at this pace; as heap fills up, sampling gets denser automatically. Default 30s — rarely needs lowering.',
|
|
187
|
+
memPollMin: 'Memory Poll Min Interval',
|
|
188
|
+
memPollMinTip: 'Minimum sampling interval even when heap is full (the floor). Prevents excessive CPU use from too-frequent sampling. Default 2s.',
|
|
189
|
+
memGrowthThreshold: 'RSS Growth Rate Threshold',
|
|
190
|
+
memGrowthThresholdTip: 'Triggers the "steadily growing" alert when the RSS linear-regression slope exceeds this value (and the trend is rising). Single fixed threshold, no severity tiers. Default 1 MB/min.',
|
|
191
|
+
// GC thresholds
|
|
192
|
+
gcThresholdInfo: 'Major GC Info Threshold',
|
|
193
|
+
gcThresholdInfoTip: 'Triggers an info alert when Major GC (MarkSweep/MarkCompact) frequency over the last 5 minutes exceeds this value. 1/min means one Full GC per minute — normal workloads should be well below this.',
|
|
194
|
+
gcThresholdWarning: 'Major GC Warning Threshold',
|
|
195
|
+
gcThresholdWarningTip: 'Triggers a warning when Major GC frequency exceeds this value. GC is frequent enough to hurt throughput — the heap is oscillating heavily. Must be greater than the Info threshold.',
|
|
196
|
+
gcThresholdError: 'Major GC Error Threshold',
|
|
197
|
+
gcThresholdErrorTip: 'Triggers an error when Major GC frequency exceeds this value. GC overhead is dominant — likely a leak. Must be greater than the Warning threshold.',
|
|
198
|
+
// Alert preview
|
|
199
|
+
alertPreviewTitle: 'Alert Preview',
|
|
200
|
+
alertPreviewToggle: 'View alert message samples',
|
|
201
|
+
alertPreviewCollapse: 'Collapse',
|
|
202
|
+
alertPreviewCriteriaRss: 'RSS Absolute',
|
|
203
|
+
alertPreviewCriteriaGrowth: 'RSS Growth',
|
|
204
|
+
alertPreviewCriteriaGc: 'Major GC Freq',
|
|
205
|
+
alertPreviewTrigger: 'trigger',
|
|
206
|
+
// Alert title (includes severity level and threshold value)
|
|
207
|
+
alertHostMemInfo: 'RSS exceeded info threshold {threshold} MB',
|
|
208
|
+
alertHostMemWarning: 'RSS exceeded warning threshold {threshold} MB',
|
|
209
|
+
alertHostMemError: 'RSS exceeded error threshold {threshold} MB',
|
|
210
|
+
alertHostMemCritical: 'RSS exceeded critical threshold {threshold} MB',
|
|
211
|
+
alertHostMemMsg: 'RSS {v} MB | Heap {heap}/{heapTotal} MB ({pct}%) | Ext {ext} MB | ArrayBuffer {ab} MB | Trend {trend} | Growth +{slope} MB/min{gcInfo}',
|
|
212
|
+
alertHostMemGrowth: 'RSS steadily growing',
|
|
213
|
+
alertHostMemGrowthMsg: '+{r} MB/min | RSS {rss} MB | Heap {heap}/{heapTotal} MB ({pct}%) | ArrayBuffer {ab} MB | Trend {trend}',
|
|
214
|
+
alertHostGcInfo: 'GC frequency elevated',
|
|
215
|
+
alertHostGcWarning: 'GC frequency abnormal',
|
|
216
|
+
alertHostGcError: 'GC frequency critical',
|
|
217
|
+
alertHostGcMsg: 'Major {v}/min | Pause {pause} ms/min | Minor {minor} | RSS {rss} MB | Heap {heap}/{heapTotal} MB ({pct}%)',
|
|
218
|
+
// Alert disposal buttons (registered via `actions` — see dsh-flash contract)
|
|
219
|
+
alertDisposeDumpHeap: 'Dump heap now',
|
|
220
|
+
alertDumping: 'Dumping heap snapshot…',
|
|
221
|
+
alertDumpSaved: 'Saved snapshot {name} ({size}). Load it in DevTools → Memory to analyze.',
|
|
222
|
+
alertDumpFailed: 'Heap dump failed: {error}',
|
|
223
|
+
alertSevInfo: 'Info',
|
|
224
|
+
alertSevWarning: 'Warning',
|
|
225
|
+
alertSevError: 'Error',
|
|
226
|
+
alertSevCritical: 'Critical',
|
|
227
|
+
// Memory live data
|
|
228
|
+
memLiveData: 'Live Data',
|
|
229
|
+
// Host process memory
|
|
230
|
+
memHostSection: 'Host Process',
|
|
231
|
+
memHostRSS: 'RSS',
|
|
232
|
+
memHostHeapTotal: 'Heap Total',
|
|
233
|
+
memHostHeapUsed: 'Heap Used',
|
|
234
|
+
memHostExternal: 'C++ External',
|
|
235
|
+
memHostArrayBuffers: 'ArrayBuffer',
|
|
236
|
+
memHostHeapRatio: 'Heap Usage',
|
|
237
|
+
memHostUnavailable: 'Host memory data unavailable',
|
|
238
|
+
// GC stats
|
|
239
|
+
memGcMajorPerMin: 'Major GC/min',
|
|
240
|
+
memGcPausePerMin: 'GC Pause/min',
|
|
241
|
+
memGcMinorCount: 'Minor GC',
|
|
242
|
+
memGcMajorCount: 'Major GC',
|
|
243
|
+
memGcUnavailable: 'GC monitoring unavailable',
|
|
244
|
+
memGcEventList: 'Full GC Events',
|
|
245
|
+
memGcEventDuration: 'Duration',
|
|
246
|
+
memGcEventTime: 'Time',
|
|
247
|
+
memGcEventClick: 'Click to locate',
|
|
248
|
+
memGcNoEvents: 'No Full GC recently',
|
|
249
|
+
memGcEventRssDrop: 'Mem Release',
|
|
250
|
+
// Memory trend
|
|
251
|
+
memTrend: 'Memory Trend',
|
|
252
|
+
memTrendPeak: 'RSS Peak',
|
|
253
|
+
memTrendDirection: 'Trend',
|
|
254
|
+
memTrendUp: '↑ Rising',
|
|
255
|
+
memTrendStable: '→ Stable',
|
|
256
|
+
memTrendDown: '↓ Falling',
|
|
257
|
+
memTrendSamples: 'Samples',
|
|
258
|
+
memTrendSpan: 'Span',
|
|
259
|
+
memTrendHeapRatio: 'Heap Usage',
|
|
260
|
+
memTrendNoData: 'No data yet',
|
|
261
|
+
memTrendHoverHeap: 'Heap',
|
|
262
|
+
memTrendHoverRSS: 'RSS',
|
|
263
|
+
memTrendHoverTime: 'Time',
|
|
264
|
+
memTrendGrowthRate: 'RSS Growth Rate',
|
|
265
|
+
memTrendAxisHeap: 'Heap',
|
|
266
|
+
memTrendAxisTime: 'Time',
|
|
267
|
+
memTrendInfo: 'Trend Info',
|
|
268
|
+
// Session footprint
|
|
269
|
+
footprintTitle: 'Session Footprint',
|
|
270
|
+
footprintTotalSessions: 'Total Sessions',
|
|
271
|
+
footprintArchivedSessions: 'Archived Sessions',
|
|
272
|
+
footprintOnDisk: 'On Disk',
|
|
273
|
+
footprintNoData: 'No footprint data yet',
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// ── i18n: a proxy over DSH's official locale framework ─────────────────
|
|
277
|
+
// The authority is the client Cordis service `locale`, published by
|
|
278
|
+
// @deepseek-ai/dsh-client-locale. It owns the active-locale preference
|
|
279
|
+
// (Host-backed, browser-derived until the Host answers), the dictionary
|
|
280
|
+
// registry, the `locale/change` event AND `<html lang>` — which is why
|
|
281
|
+
// nothing here touches that attribute any more. The MutationObserver this
|
|
282
|
+
// file used to keep watched an attribute ANOTHER package owns and inferred a
|
|
283
|
+
// preference from it: it could not see a switch that had not reached the DOM
|
|
284
|
+
// yet, and it fired for a value nobody had chosen. Registering our tables
|
|
285
|
+
// with the service and reading through it removes both problems, and makes
|
|
286
|
+
// this plugin follow the global language by contract instead of by accident.
|
|
287
|
+
//
|
|
288
|
+
// The public surface is unchanged — t(key) / L(key) / t.onLocaleChange() —
|
|
289
|
+
// because every call site in this file already reads through it. There is
|
|
290
|
+
// deliberately NO t.setLocale(): this plugin is a pure reader, the language
|
|
291
|
+
// row belongs to dsh-flash, and a second writer would be exactly the drift
|
|
292
|
+
// this block exists to remove.
|
|
293
|
+
|
|
294
|
+
var I18N_NS = 'dsh-flash-mem-mon'
|
|
295
|
+
var LOCALES = { zh: zh, en: en }
|
|
296
|
+
var FALLBACK_LOCALE = 'en'
|
|
297
|
+
var _localeListeners = new Set()
|
|
298
|
+
|
|
299
|
+
/** Fallback active locale — what the official runtime does with no explicit
|
|
300
|
+
* choice: the browser's own language, matched on the PRIMARY subtag. */
|
|
301
|
+
function resolveFallbackLocale() {
|
|
302
|
+
try {
|
|
303
|
+
if (typeof navigator === 'undefined') return FALLBACK_LOCALE
|
|
304
|
+
var tags = [].concat(navigator.languages || [], navigator.language || [])
|
|
305
|
+
for (var i = 0; i < tags.length; i++) {
|
|
306
|
+
var primary = String(tags[i] || '').toLowerCase().split('-')[0]
|
|
307
|
+
if (LOCALES[primary]) return primary
|
|
308
|
+
}
|
|
309
|
+
} catch (_) {}
|
|
310
|
+
return FALLBACK_LOCALE
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// The active-locale source. `get()` reads the authority; `id` records WHICH
|
|
314
|
+
// authority answered, so a service that arrives LATER (module order can hand
|
|
315
|
+
// us nothing at apply time) can be adopted, and a lost one released.
|
|
316
|
+
var _fallbackLocaleKey = resolveFallbackLocale()
|
|
317
|
+
var _localeSource = { id: 'fallback', get: function () { return _fallbackLocaleKey } }
|
|
318
|
+
|
|
319
|
+
// Single fan-out point for "the active locale actually changed". Drives two
|
|
320
|
+
// kinds of consumer: the imperative surfaces this plugin does NOT render
|
|
321
|
+
// (Quick Control row, alert panel — nudged via refreshLocaleSurfaces, hoisted
|
|
322
|
+
// from the toggle-helpers region), and the React subscribers registered with
|
|
323
|
+
// t.onLocaleChange() (the open config modal). Called from the factory's
|
|
324
|
+
// `locale/change` listener, and from the fallback path in bindLocaleService.
|
|
325
|
+
function notifyLocaleChange() {
|
|
326
|
+
try { refreshLocaleSurfaces() } catch (_) {}
|
|
327
|
+
_localeListeners.forEach(function (fn) { try { fn() } catch (_) {} })
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Point the i18n proxy at DSH's `locale` service: hand the zh/en tables to
|
|
332
|
+
* the official registry under I18N_NS and route every READ through it.
|
|
333
|
+
*
|
|
334
|
+
* Change NOTIFICATION is not wired here. The service's `subscribe()` is the
|
|
335
|
+
* LocaleFace render channel and fires on every revision bump, including
|
|
336
|
+
* dictionary registration — wrong trigger for our imperative nudge. The
|
|
337
|
+
* factory listens to `locale/change` (emitted only on a real switch) and
|
|
338
|
+
* calls notifyLocaleChange(). See the factory's step 0 for why.
|
|
339
|
+
*
|
|
340
|
+
* Two throws in the official API shape this function:
|
|
341
|
+
* - `register(ns, dicts)` REJECTS a locale already registered under the
|
|
342
|
+
* namespace, so any prior claim is released before a new one is made and
|
|
343
|
+
* the claim is never attempted twice for one service;
|
|
344
|
+
* - `setLocale(id)` REJECTS an unregistered id — not our problem here,
|
|
345
|
+
* since this plugin never writes the locale.
|
|
346
|
+
*
|
|
347
|
+
* Idempotent, because apply() calls it from two places: eagerly, for the
|
|
348
|
+
* case where `locale` is already provided, and from `ctx.inject(['locale'])`
|
|
349
|
+
* for the case where it arrives later. Neither is a second registration.
|
|
350
|
+
*
|
|
351
|
+
* @param {any} localeService - the `locale` service, a thunk returning it,
|
|
352
|
+
* or undefined to keep the built-in fallback.
|
|
353
|
+
* @returns {boolean} whether the official service is now in force.
|
|
354
|
+
*/
|
|
355
|
+
function bindLocaleService(localeService) {
|
|
356
|
+
var svc = null
|
|
357
|
+
try { svc = typeof localeService === 'function' ? localeService() : localeService } catch (_) { svc = undefined }
|
|
358
|
+
var owns = !!svc && typeof svc.register === 'function' && typeof svc.bind === 'function'
|
|
359
|
+
&& typeof svc.getSnapshot === 'function'
|
|
360
|
+
if (!owns) {
|
|
361
|
+
// Nothing to bind. A read that TRANSIENTLY finds no service must not
|
|
362
|
+
// tear down a working registration, so only the fallback is refreshed —
|
|
363
|
+
// which is also the first call, where the browser tag may have settled
|
|
364
|
+
// after this module was evaluated.
|
|
365
|
+
if (_localeSource.id !== 'fallback') return false
|
|
366
|
+
var next = resolveFallbackLocale()
|
|
367
|
+
if (next === _fallbackLocaleKey) return false
|
|
368
|
+
_fallbackLocaleKey = next
|
|
369
|
+
notifyLocaleChange()
|
|
370
|
+
return false
|
|
371
|
+
}
|
|
372
|
+
if (_localeSource.id === 'service' && _localeSource.service === svc) return true
|
|
373
|
+
// A different instance is taking over: give up the old claim on the
|
|
374
|
+
// namespace before making a new one, or the new one would be refused.
|
|
375
|
+
releaseLocaleBinding()
|
|
376
|
+
var disposeRegister = function () {}
|
|
377
|
+
try {
|
|
378
|
+
disposeRegister = svc.register(I18N_NS, { zh: zh, en: en }) || disposeRegister
|
|
379
|
+
} catch (err) {
|
|
380
|
+
console.warn('[dsh-flash-mem-mon] the `locale` service refused our dictionaries — ' +
|
|
381
|
+
'keeping the built-in zh/en tables for this session:', err)
|
|
382
|
+
return false
|
|
383
|
+
}
|
|
384
|
+
_localeSource = {
|
|
385
|
+
id: 'service',
|
|
386
|
+
service: svc,
|
|
387
|
+
get: function () { try { return svc.getSnapshot().active } catch (_) { return undefined } },
|
|
388
|
+
dispose: function () {
|
|
389
|
+
try { disposeRegister() } catch (_) {}
|
|
390
|
+
},
|
|
391
|
+
}
|
|
392
|
+
// Do NOT re-notify here on purpose. `register()` bumps the snapshot
|
|
393
|
+
// revision so already-rendered LocaleFace outlets pick up our late
|
|
394
|
+
// dictionaries, but a registration is not a language switch — nothing
|
|
395
|
+
// this plugin renders changed language. The refresh nudge is driven only
|
|
396
|
+
// by `locale/change`, wired in the factory.
|
|
397
|
+
return true
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/** Undo whatever dictionary claim is in force and fall back.
|
|
401
|
+
*
|
|
402
|
+
* One claim, one release, both scoped to the plugin fiber: a remount or a
|
|
403
|
+
* disable→enable cycle that re-registered the namespace each time is
|
|
404
|
+
* exactly what `register`'s duplicate guard punishes. */
|
|
405
|
+
function releaseLocaleBinding() {
|
|
406
|
+
var source = _localeSource
|
|
407
|
+
if (source.id !== 'service') return
|
|
408
|
+
_localeSource = { id: 'fallback', get: function () { return _fallbackLocaleKey } }
|
|
409
|
+
try { source.dispose() } catch (_) {}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/** Current locale key ('zh' | 'en'), whatever authority is in force. */
|
|
413
|
+
function currentLocaleKey() {
|
|
414
|
+
var active = _localeSource.get()
|
|
415
|
+
return LOCALES[active] ? active : FALLBACK_LOCALE
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/** i18n lookup — a read of the official service whenever it is bound. */
|
|
419
|
+
function t(key) {
|
|
420
|
+
if (_localeSource.id === 'service') {
|
|
421
|
+
try {
|
|
422
|
+
var value = _localeSource.service.bind(I18N_NS)(key)
|
|
423
|
+
// The official lookup ends at the key itself; ours ends at the en
|
|
424
|
+
// table, so a zh-only key still reads as English rather than as its
|
|
425
|
+
// own name.
|
|
426
|
+
if (value !== undefined && value !== key) return value
|
|
427
|
+
} catch (_) {}
|
|
428
|
+
}
|
|
429
|
+
var dict = LOCALES[currentLocaleKey()] || LOCALES[FALLBACK_LOCALE]
|
|
430
|
+
return dict[key] || LOCALES[FALLBACK_LOCALE][key] || key
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/** Get the current locale key ('zh' | 'en'). */
|
|
434
|
+
t.getLocale = currentLocaleKey
|
|
435
|
+
|
|
436
|
+
/** Subscribe to locale changes; returns a disposer. */
|
|
437
|
+
t.onLocaleChange = function (fn) {
|
|
438
|
+
_localeListeners.add(fn)
|
|
439
|
+
return function () { _localeListeners.delete(fn) }
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/** Functional label helper — returns a function that calls t() so the label
|
|
443
|
+
* updates when the locale service publishes a new active locale. */
|
|
444
|
+
function L(key) {
|
|
445
|
+
return function () { return t(key) }
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
//#endregion ───────────────────────────────────────────────────────────────
|
|
449
|
+
|
|
450
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
451
|
+
//#region Preference system ──────────────────────────────────────────────
|
|
452
|
+
|
|
453
|
+
// NOTE: these defaults MIRROR the DEFAULT_MEM_*/DEFAULT_GC_* constants in
|
|
454
|
+
// the HOST half (src/index.ts). They are only used as a last-resort fallback
|
|
455
|
+
// when the remote 'dsh-flash-mem-mon' settings namespace is empty/unreadable;
|
|
456
|
+
// at runtime the authoritative values come from that namespace. If you change
|
|
457
|
+
// a threshold default, update src/index.ts AND this table together.
|
|
458
|
+
var _MEM_ALERT_DEFAULTS = {
|
|
459
|
+
memThresholdInfo: 256,
|
|
460
|
+
memThresholdWarning: 512,
|
|
461
|
+
memThresholdError: 1024,
|
|
462
|
+
memThresholdCritical: 1536,
|
|
463
|
+
memPollBase: 60000,
|
|
464
|
+
memPollMin: 2000,
|
|
465
|
+
gcThresholdInfo: 2,
|
|
466
|
+
gcThresholdWarning: 5,
|
|
467
|
+
gcThresholdError: 10,
|
|
468
|
+
memGrowthAlertPerMin: 1,
|
|
469
|
+
}
|
|
470
|
+
var _memPrefs = Object.assign({}, _MEM_ALERT_DEFAULTS)
|
|
471
|
+
var _prefCtx = null
|
|
472
|
+
var MEM_MON_NS = 'dsh-flash-mem-mon'
|
|
473
|
+
var _hostRevision = null
|
|
474
|
+
var _prefWriteTail = Promise.resolve()
|
|
475
|
+
var _pendingRevision = null
|
|
476
|
+
|
|
477
|
+
function _fenceRevision() {
|
|
478
|
+
if (_pendingRevision !== null) return _pendingRevision
|
|
479
|
+
return _hostRevision === null ? undefined : _hostRevision
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function _remoteSettings(ctx) {
|
|
483
|
+
try {
|
|
484
|
+
if (ctx && ctx.remote && ctx.remote.settings) return ctx.remote.settings
|
|
485
|
+
} catch (e) {
|
|
486
|
+
console.warn('[dsh-flash-mem-mon] ctx.remote.settings threw:', e && e.message)
|
|
487
|
+
}
|
|
488
|
+
try {
|
|
489
|
+
var remote = ctx && ctx.get ? ctx.get('remote') : undefined
|
|
490
|
+
if (remote && remote.settings) return remote.settings
|
|
491
|
+
} catch (e) { console.warn('[dsh-flash-mem-mon] ctx.get("remote") threw:', e && e.message) }
|
|
492
|
+
return undefined
|
|
493
|
+
}
|
|
494
|
+
|
|
306
495
|
/** Normalize a `describe()` result into a promise. NEVER let this throw.
|
|
307
496
|
*
|
|
308
497
|
* Calling `.then` directly on a `describe()` result is not safe: the SAME
|
|
@@ -324,1769 +513,2104 @@ window.__ModuleLoader__.load({
|
|
|
324
513
|
catch (err) { return Promise.reject(err) }
|
|
325
514
|
}
|
|
326
515
|
|
|
327
|
-
var _PREFS_MAX_RETRIES = 8
|
|
328
|
-
var _PREFS_RETRY_DELAY_MS = 750
|
|
329
|
-
|
|
330
|
-
function loadMemPrefs(ctx, _retryCount) {
|
|
331
|
-
if (typeof _retryCount !== 'number') _retryCount = 0
|
|
332
|
-
var settings = _remoteSettings(ctx)
|
|
333
|
-
if (!settings) return Promise.resolve(false)
|
|
334
|
-
if (typeof settings.describe !== 'function') return Promise.resolve(false)
|
|
335
|
-
return _describeAsync(settings).then(function (desc) {
|
|
336
|
-
if (!desc) return false
|
|
337
|
-
var view = desc.value || desc
|
|
338
|
-
var list = view && Array.isArray(view.namespaces)
|
|
339
|
-
? view.namespaces
|
|
340
|
-
: (Array.isArray(view) ? view : null)
|
|
341
|
-
if (!list) return false
|
|
342
|
-
var ns = list.find(function (n) {
|
|
343
|
-
return (n && (n.ns || n.namespace)) === MEM_MON_NS
|
|
344
|
-
})
|
|
345
|
-
if (!ns) {
|
|
346
|
-
if (_retryCount < _PREFS_MAX_RETRIES) {
|
|
347
|
-
return new Promise(function (resolve) {
|
|
348
|
-
setTimeout(function () {
|
|
349
|
-
resolve(loadMemPrefs(ctx, _retryCount + 1))
|
|
350
|
-
}, _PREFS_RETRY_DELAY_MS)
|
|
351
|
-
})
|
|
352
|
-
}
|
|
353
|
-
return false
|
|
354
|
-
}
|
|
355
|
-
var resolved = ns.value || ns.resolved
|
|
356
|
-
if (!resolved) return false
|
|
357
|
-
_memPrefs = {
|
|
358
|
-
memThresholdInfo: resolved.memThresholdInfo,
|
|
359
|
-
memThresholdWarning: resolved.memThresholdWarning,
|
|
360
|
-
memThresholdError: resolved.memThresholdError,
|
|
361
|
-
memThresholdCritical: resolved.memThresholdCritical,
|
|
362
|
-
memPollBase: resolved.memPollBase,
|
|
363
|
-
memPollMin: resolved.memPollMin,
|
|
364
|
-
gcThresholdInfo: resolved.gcThresholdInfo,
|
|
365
|
-
gcThresholdWarning: resolved.gcThresholdWarning,
|
|
366
|
-
gcThresholdError: resolved.gcThresholdError,
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
if (
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
var
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
})
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
})
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
},
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
}
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
},
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
},
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
},
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
},
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
color: 'var(--dsw-alias-label-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
//
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
},
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
var
|
|
1192
|
-
|
|
1193
|
-
},
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
var
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
var
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
)
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
)
|
|
1368
|
-
)
|
|
1369
|
-
|
|
1370
|
-
//
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
h('
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
)
|
|
1448
|
-
|
|
1449
|
-
}
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
h('span', { style: S.
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
)
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
h('span', { style: S.
|
|
1523
|
-
h('span', { style: S.
|
|
1524
|
-
)
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
h('
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
)
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
var
|
|
1751
|
-
//
|
|
1752
|
-
|
|
1753
|
-
var
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
}
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
)
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
},
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
)
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
)
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
if (
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
}
|
|
516
|
+
var _PREFS_MAX_RETRIES = 8
|
|
517
|
+
var _PREFS_RETRY_DELAY_MS = 750
|
|
518
|
+
|
|
519
|
+
function loadMemPrefs(ctx, _retryCount) {
|
|
520
|
+
if (typeof _retryCount !== 'number') _retryCount = 0
|
|
521
|
+
var settings = _remoteSettings(ctx)
|
|
522
|
+
if (!settings) return Promise.resolve(false)
|
|
523
|
+
if (typeof settings.describe !== 'function') return Promise.resolve(false)
|
|
524
|
+
return _describeAsync(settings).then(function (desc) {
|
|
525
|
+
if (!desc) return false
|
|
526
|
+
var view = desc.value || desc
|
|
527
|
+
var list = view && Array.isArray(view.namespaces)
|
|
528
|
+
? view.namespaces
|
|
529
|
+
: (Array.isArray(view) ? view : null)
|
|
530
|
+
if (!list) return false
|
|
531
|
+
var ns = list.find(function (n) {
|
|
532
|
+
return (n && (n.ns || n.namespace)) === MEM_MON_NS
|
|
533
|
+
})
|
|
534
|
+
if (!ns) {
|
|
535
|
+
if (_retryCount < _PREFS_MAX_RETRIES) {
|
|
536
|
+
return new Promise(function (resolve) {
|
|
537
|
+
setTimeout(function () {
|
|
538
|
+
resolve(loadMemPrefs(ctx, _retryCount + 1))
|
|
539
|
+
}, _PREFS_RETRY_DELAY_MS)
|
|
540
|
+
})
|
|
541
|
+
}
|
|
542
|
+
return false
|
|
543
|
+
}
|
|
544
|
+
var resolved = ns.value || ns.resolved
|
|
545
|
+
if (!resolved) return false
|
|
546
|
+
_memPrefs = {
|
|
547
|
+
memThresholdInfo: resolved.memThresholdInfo,
|
|
548
|
+
memThresholdWarning: resolved.memThresholdWarning,
|
|
549
|
+
memThresholdError: resolved.memThresholdError,
|
|
550
|
+
memThresholdCritical: resolved.memThresholdCritical,
|
|
551
|
+
memPollBase: resolved.memPollBase,
|
|
552
|
+
memPollMin: resolved.memPollMin,
|
|
553
|
+
gcThresholdInfo: resolved.gcThresholdInfo,
|
|
554
|
+
gcThresholdWarning: resolved.gcThresholdWarning,
|
|
555
|
+
gcThresholdError: resolved.gcThresholdError,
|
|
556
|
+
memGrowthAlertPerMin: resolved.memGrowthAlertPerMin,
|
|
557
|
+
}
|
|
558
|
+
if (typeof ns.revision === 'number') _hostRevision = ns.revision
|
|
559
|
+
console.log('[dsh-flash-mem-mon] preferences loaded from ' + MEM_MON_NS + ' namespace')
|
|
560
|
+
return true
|
|
561
|
+
}).catch(function () {
|
|
562
|
+
return false
|
|
563
|
+
})
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/** Read an alert preference from _memPrefs, falling back to the built-in default. */
|
|
567
|
+
function _alertPref(key) {
|
|
568
|
+
var v = _memPrefs && _memPrefs[key]
|
|
569
|
+
return (typeof v === 'number' && isFinite(v)) ? v : _MEM_ALERT_DEFAULTS[key]
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/** Write an alert preference through the preference bridge (host + memory). */
|
|
573
|
+
function _writeAlertPref(ctx, key, value, onError) {
|
|
574
|
+
savePrefs(ctx, Object.fromEntries([[key, value]]), null, onError)
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/** Save preferences: update _memPrefs in memory, then serialize a write to the 'dsh-flash-mem-mon' settings namespace. */
|
|
578
|
+
function savePrefs(ctx, patch, localWrites, onError) {
|
|
579
|
+
var saved = {}
|
|
580
|
+
for (var k in patch) if (Object.prototype.hasOwnProperty.call(patch, k)) saved[k] = _memPrefs && _memPrefs[k]
|
|
581
|
+
if (_memPrefs) {
|
|
582
|
+
for (var k in patch) if (Object.prototype.hasOwnProperty.call(patch, k)) _memPrefs[k] = patch[k]
|
|
583
|
+
}
|
|
584
|
+
try { if (localWrites) localWrites() } catch (_) {}
|
|
585
|
+
return _queuePrefWrite(ctx, patch, onError, saved)
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
function _queuePrefWrite(ctx, patch, onError, saved) {
|
|
589
|
+
var task = _prefWriteTail.then(function () {
|
|
590
|
+
var settings = _remoteSettings(ctx || _prefCtx)
|
|
591
|
+
if (!settings || typeof settings.update !== 'function') {
|
|
592
|
+
if (onError) onError(patch, function () {
|
|
593
|
+
for (var k in saved) if (Object.prototype.hasOwnProperty.call(saved, k)) _memPrefs[k] = saved[k]
|
|
594
|
+
})
|
|
595
|
+
return false
|
|
596
|
+
}
|
|
597
|
+
return settings.update(MEM_MON_NS, patch, _fenceRevision())
|
|
598
|
+
.then(function (res) {
|
|
599
|
+
try {
|
|
600
|
+
if (res && res.ok === false) {
|
|
601
|
+
_pendingRevision = null
|
|
602
|
+
if (onError) onError(patch, function () {
|
|
603
|
+
for (var k in saved) if (Object.prototype.hasOwnProperty.call(saved, k)) _memPrefs[k] = saved[k]
|
|
604
|
+
})
|
|
605
|
+
try {
|
|
606
|
+
_describeAsync(settings).then(function (desc) {
|
|
607
|
+
if (desc && desc.ok !== false) {
|
|
608
|
+
var view2 = desc.value || desc
|
|
609
|
+
var list2 = view2 && Array.isArray(view2.namespaces)
|
|
610
|
+
? view2.namespaces
|
|
611
|
+
: (Array.isArray(view2) ? view2 : null)
|
|
612
|
+
var ns2 = list2 && list2.find(function (n) {
|
|
613
|
+
return (n && (n.ns || n.namespace)) === MEM_MON_NS
|
|
614
|
+
})
|
|
615
|
+
if (ns2 && typeof ns2.revision === 'number') _hostRevision = ns2.revision
|
|
616
|
+
}
|
|
617
|
+
}).catch(function () {})
|
|
618
|
+
} catch (_) {}
|
|
619
|
+
return false
|
|
620
|
+
}
|
|
621
|
+
var v = res && res.value
|
|
622
|
+
if (v && typeof v.revision === 'number') {
|
|
623
|
+
_pendingRevision = v.revision
|
|
624
|
+
_hostRevision = v.revision
|
|
625
|
+
}
|
|
626
|
+
} catch (_) {}
|
|
627
|
+
return true
|
|
628
|
+
}).catch(function (err) {
|
|
629
|
+
_pendingRevision = null
|
|
630
|
+
if (onError) onError(patch, function () {
|
|
631
|
+
for (var k in saved) if (Object.prototype.hasOwnProperty.call(saved, k)) _memPrefs[k] = saved[k]
|
|
632
|
+
})
|
|
633
|
+
return false
|
|
634
|
+
})
|
|
635
|
+
})
|
|
636
|
+
_prefWriteTail = task.then(function () {}, function () {})
|
|
637
|
+
return task
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
//#endregion ───────────────────────────────────────────────────────────────
|
|
641
|
+
|
|
642
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
643
|
+
//#region Shared trend cache ──────────────────────────────────────────────
|
|
644
|
+
|
|
645
|
+
// The alert provider polls `?mode=summary` for alerting. The config modal
|
|
646
|
+
// ALSO needs the same summary for its trend panel. Rather than both polling
|
|
647
|
+
// the same route independently (which doubles the host traffic while the
|
|
648
|
+
// modal is open), the provider writes each successful summary into this
|
|
649
|
+
// module-level cache and notifies subscribers. The modal reads the cache on
|
|
650
|
+
// mount and subscribes for live updates instead of running its own summary
|
|
651
|
+
// poll. `mode=full` (sparkline raw samples + GC events) is heavier and is
|
|
652
|
+
// still fetched by the modal on its own cadence — sharing that would couple
|
|
653
|
+
// the modal's chart to the provider's poll, which is undesirable.
|
|
654
|
+
var _summaryCache = null
|
|
655
|
+
var _summaryVersion = 0
|
|
656
|
+
var _summarySubscribers = []
|
|
657
|
+
|
|
658
|
+
function _setSummaryCache(d) {
|
|
659
|
+
_summaryCache = d
|
|
660
|
+
_summaryVersion++
|
|
661
|
+
var subs = _summarySubscribers.slice()
|
|
662
|
+
_summarySubscribers.length = 0
|
|
663
|
+
for (var i = 0; i < subs.length; i++) {
|
|
664
|
+
try { subs[i](d, _summaryVersion) } catch (_) {}
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/** Subscribe to the shared summary cache. Immediately invokes `cb` with the
|
|
669
|
+
* current cache (possibly null). Returns an unsubscribe function. */
|
|
670
|
+
function _subscribeSummary(cb) {
|
|
671
|
+
var active = true
|
|
672
|
+
try { cb(_summaryCache, _summaryVersion) } catch (_) {}
|
|
673
|
+
_summarySubscribers.push(function (d, v) { if (active) { try { cb(d, v) } catch (_) {} } })
|
|
674
|
+
return function () { active = false }
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
//#endregion ───────────────────────────────────────────────────────────────
|
|
678
|
+
|
|
679
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
680
|
+
//#region AlertProvider ──────────────────────────────────────────────────
|
|
681
|
+
|
|
682
|
+
function createHostMemoryAlertProvider() {
|
|
683
|
+
var timer = null
|
|
684
|
+
var callback = null
|
|
685
|
+
|
|
686
|
+
// Hysteresis / de-duplication: remember the last severity emitted for
|
|
687
|
+
// each alert id. A candidate is pushed downstream only when its severity
|
|
688
|
+
// CHANGES (first appearance, escalation, or de-escalation). Repeated
|
|
689
|
+
// polls at the SAME severity are suppressed, so the same problem does not
|
|
690
|
+
// re-fire every poll cycle (which would flood the alert feed).
|
|
691
|
+
// Severity order: info < warning < error < critical.
|
|
692
|
+
var _SEV_RANK = { info: 0, warning: 1, error: 2, critical: 3 }
|
|
693
|
+
var _armed = {}
|
|
694
|
+
var _seenIds = {}
|
|
695
|
+
|
|
696
|
+
/** Push `alert` only if its severity changed since the previous poll. */
|
|
697
|
+
function emitIfChanged(alerts, alert) {
|
|
698
|
+
var id = alert.id
|
|
699
|
+
var rank = _SEV_RANK[alert.severity] || 0
|
|
700
|
+
_seenIds[id] = true
|
|
701
|
+
if (_armed[id] === rank) return // still armed at the same severity → suppress
|
|
702
|
+
_armed[id] = rank
|
|
703
|
+
alerts.push(alert)
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
/** Disarm any alert id that is no longer triggering this poll. */
|
|
707
|
+
function disarmAbsent() {
|
|
708
|
+
for (var id in _armed) {
|
|
709
|
+
if (_armed.hasOwnProperty(id) && !_seenIds[id]) delete _armed[id]
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
/** Called at the end of every poll cycle: disarm ids that stopped
|
|
714
|
+
* triggering. Ids still armed (and suppressed at the same severity) are
|
|
715
|
+
* kept so they continue to be suppressed on the next cycle instead of
|
|
716
|
+
* re-firing as if new. */
|
|
717
|
+
function settleArmed() {
|
|
718
|
+
disarmAbsent()
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
/** Disposal actions attached to every memory/GC alert. dsh-flash renders
|
|
722
|
+
* one button per entry and calls onClick(alert, api). Contract in
|
|
723
|
+
* dsh-flash/lib/client.js `_showAlertDetail` → plugin-registered action
|
|
724
|
+
* buttons. Returning a Promise keeps the button disabled until settled;
|
|
725
|
+
* returning {message} keeps the detail modal open so the saved location
|
|
726
|
+
* stays visible (the user pulls down the .heapsnapshot browser download
|
|
727
|
+
* we trigger programmatically — the status line is plain text, not HTML,
|
|
728
|
+
* so we cannot render a live link into it). */
|
|
729
|
+
function _dumpHeapActions() {
|
|
730
|
+
return [{
|
|
731
|
+
id: 'dump-heap',
|
|
732
|
+
label: function () { return t('alertDisposeDumpHeap') },
|
|
733
|
+
primary: true,
|
|
734
|
+
onClick: function (_alert, api) {
|
|
735
|
+
api.status(t('alertDumping'), 'info')
|
|
736
|
+
return fetch('/plugins/dsh-flash-mem-mon/dump-heap', { method: 'POST' })
|
|
737
|
+
.then(function (r) { return r.json().catch(function () { return null }) })
|
|
738
|
+
.then(function (d) {
|
|
739
|
+
if (!d || !d.ok || !d.name) {
|
|
740
|
+
var err = (d && d.error) || 'unknown'
|
|
741
|
+
api.status(t('alertDumpFailed').replace('{error}', err), 'error')
|
|
742
|
+
return { message: t('alertDumpFailed').replace('{error}', err), level: 'error' }
|
|
743
|
+
}
|
|
744
|
+
// Stream the snapshot down as a browser download (same origin).
|
|
745
|
+
var a = document.createElement('a')
|
|
746
|
+
a.href = '/plugins/dsh-flash-mem-mon/heap-dump'
|
|
747
|
+
a.download = d.name
|
|
748
|
+
document.body.appendChild(a)
|
|
749
|
+
a.click()
|
|
750
|
+
a.remove()
|
|
751
|
+
var size = d.size >= 1048576
|
|
752
|
+
? (d.size / 1048576).toFixed(1) + ' MB'
|
|
753
|
+
: Math.max(1, Math.round(d.size / 1024)) + ' KB'
|
|
754
|
+
var msg = t('alertDumpSaved').replace('{name}', d.name).replace('{size}', size)
|
|
755
|
+
api.status(msg, 'info')
|
|
756
|
+
return { message: msg, level: 'info' }
|
|
757
|
+
})
|
|
758
|
+
.catch(function (err) {
|
|
759
|
+
var msg = t('alertDumpFailed').replace('{error}', (err && err.message) || 'network')
|
|
760
|
+
api.status(msg, 'error')
|
|
761
|
+
return { message: msg, level: 'error' }
|
|
762
|
+
})
|
|
763
|
+
},
|
|
764
|
+
}]
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
function poll() {
|
|
768
|
+
if (!callback) return
|
|
769
|
+
var alerts = []
|
|
770
|
+
_seenIds = {}
|
|
771
|
+
fetch('/plugins/dsh-flash-mem-mon/memory-trend?mode=summary')
|
|
772
|
+
.then(function (r) { return r.json() })
|
|
773
|
+
.then(function (d) {
|
|
774
|
+
if (!d || !d.current) {
|
|
775
|
+
settleArmed()
|
|
776
|
+
try { callback(alerts) } catch (_) {}
|
|
777
|
+
return
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
var rssMB = Math.round(d.current.rss / 1048576)
|
|
781
|
+
var heapMB = Math.round(d.current.heapUsed / 1048576)
|
|
782
|
+
var heapTotalMB = Math.round(d.current.heapTotal / 1048576)
|
|
783
|
+
var extMB = Math.round(d.current.external / 1048576)
|
|
784
|
+
var abMB = Math.round(d.current.arrayBuffers / 1048576)
|
|
785
|
+
var heapPct = d.current.heapTotal > 0 ? Math.round(d.current.heapUsed / d.current.heapTotal * 100) : 0
|
|
786
|
+
var trendLabel = d.trend === 'up' ? '↑' : d.trend === 'down' ? '↓' : '→'
|
|
787
|
+
var rssSlope = d.rssSlopePerMin || 0
|
|
788
|
+
var slopeLabel = rssSlope.toFixed(1)
|
|
789
|
+
// GC summary snippet for RSS alerts
|
|
790
|
+
var gcInfoSnippet = ''
|
|
791
|
+
if (d.gc && d.gc.majorPerMin > 0) {
|
|
792
|
+
gcInfoSnippet = ' | GC Major ' + d.gc.majorPerMin.toFixed(1) + '/min 暂停 ' + (d.gc.gcPausePerMin || 0).toFixed(0) + 'ms/min'
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// ── Criterion 1: RSS absolute value ────────────────────────
|
|
796
|
+
var tInfo = _alertPref('memThresholdInfo')
|
|
797
|
+
var tWarn = _alertPref('memThresholdWarning')
|
|
798
|
+
var tErr = _alertPref('memThresholdError')
|
|
799
|
+
var tCrit = _alertPref('memThresholdCritical')
|
|
800
|
+
// Validate: info < warning < error < critical
|
|
801
|
+
if (!(tInfo > 0 && tWarn > tInfo && tErr > tWarn && tCrit > tErr)) {
|
|
802
|
+
tInfo = 256; tWarn = 512; tErr = 1024; tCrit = 1536
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// Shared RSS alert message formatter.
|
|
806
|
+
var _rssMsg = function () {
|
|
807
|
+
return t('alertHostMemMsg')
|
|
808
|
+
.replace('{v}', rssMB)
|
|
809
|
+
.replace('{heap}', heapMB)
|
|
810
|
+
.replace('{heapTotal}', heapTotalMB)
|
|
811
|
+
.replace('{pct}', heapPct)
|
|
812
|
+
.replace('{ext}', extMB)
|
|
813
|
+
.replace('{ab}', abMB)
|
|
814
|
+
.replace('{trend}', trendLabel)
|
|
815
|
+
.replace('{slope}', slopeLabel)
|
|
816
|
+
.replace('{gcInfo}', gcInfoSnippet)
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
if (rssMB >= tCrit) {
|
|
820
|
+
emitIfChanged(alerts, {
|
|
821
|
+
id: 'host-mem-critical',
|
|
822
|
+
severity: 'critical',
|
|
823
|
+
title: function () { return t('alertHostMemCritical').replace('{threshold}', tCrit) },
|
|
824
|
+
message: _rssMsg,
|
|
825
|
+
icon: '🔴',
|
|
826
|
+
timestamp: Date.now(),
|
|
827
|
+
dismissible: true,
|
|
828
|
+
actions: _dumpHeapActions(),
|
|
829
|
+
})
|
|
830
|
+
} else if (rssMB >= tErr) {
|
|
831
|
+
emitIfChanged(alerts, {
|
|
832
|
+
id: 'host-mem-error',
|
|
833
|
+
severity: 'error',
|
|
834
|
+
title: function () { return t('alertHostMemError').replace('{threshold}', tErr) },
|
|
835
|
+
message: _rssMsg,
|
|
836
|
+
icon: '🟠',
|
|
837
|
+
timestamp: Date.now(),
|
|
838
|
+
dismissible: true,
|
|
839
|
+
actions: _dumpHeapActions(),
|
|
840
|
+
})
|
|
841
|
+
} else if (rssMB >= tWarn) {
|
|
842
|
+
emitIfChanged(alerts, {
|
|
843
|
+
id: 'host-mem-warning',
|
|
844
|
+
severity: 'warning',
|
|
845
|
+
title: function () { return t('alertHostMemWarning').replace('{threshold}', tWarn) },
|
|
846
|
+
message: _rssMsg,
|
|
847
|
+
icon: '🟡',
|
|
848
|
+
timestamp: Date.now(),
|
|
849
|
+
dismissible: true,
|
|
850
|
+
actions: _dumpHeapActions(),
|
|
851
|
+
})
|
|
852
|
+
} else if (rssMB >= tInfo) {
|
|
853
|
+
emitIfChanged(alerts, {
|
|
854
|
+
id: 'host-mem-info',
|
|
855
|
+
severity: 'info',
|
|
856
|
+
title: function () { return t('alertHostMemInfo').replace('{threshold}', tInfo) },
|
|
857
|
+
message: _rssMsg,
|
|
858
|
+
icon: '🔵',
|
|
859
|
+
timestamp: Date.now(),
|
|
860
|
+
dismissible: true,
|
|
861
|
+
actions: _dumpHeapActions(),
|
|
862
|
+
})
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// ── Criterion 2: RSS growth rate ───────────────────────────
|
|
866
|
+
// Threshold is configurable (memGrowthAlertPerMin); the "trend up"
|
|
867
|
+
// guard is redundant with slope > 0 but kept for clarity.
|
|
868
|
+
var growthThresh = _alertPref('memGrowthAlertPerMin')
|
|
869
|
+
if (growthThresh > 0 && rssSlope > growthThresh && d.trend === 'up') {
|
|
870
|
+
var hasRssAlert = alerts.some(function (a) {
|
|
871
|
+
return a.id.indexOf('host-mem-') === 0
|
|
872
|
+
})
|
|
873
|
+
if (!hasRssAlert) {
|
|
874
|
+
emitIfChanged(alerts, {
|
|
875
|
+
id: 'host-mem-growth',
|
|
876
|
+
severity: 'info',
|
|
877
|
+
title: function () { return t('alertHostMemGrowth') },
|
|
878
|
+
message: function () {
|
|
879
|
+
return t('alertHostMemGrowthMsg')
|
|
880
|
+
.replace('{r}', rssSlope.toFixed(1))
|
|
881
|
+
.replace('{rss}', rssMB)
|
|
882
|
+
.replace('{heap}', heapMB)
|
|
883
|
+
.replace('{heapTotal}', heapTotalMB)
|
|
884
|
+
.replace('{pct}', heapPct)
|
|
885
|
+
.replace('{ab}', abMB)
|
|
886
|
+
.replace('{trend}', trendLabel)
|
|
887
|
+
},
|
|
888
|
+
icon: '📈',
|
|
889
|
+
timestamp: Date.now(),
|
|
890
|
+
dismissible: true,
|
|
891
|
+
actions: _dumpHeapActions(),
|
|
892
|
+
})
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
// ── Criterion 3: Major GC frequency ────────────────────────
|
|
897
|
+
if (d.gc && d.gc.majorPerMin > 0) {
|
|
898
|
+
var gcInfo = _alertPref('gcThresholdInfo')
|
|
899
|
+
var gcWarn = _alertPref('gcThresholdWarning')
|
|
900
|
+
var gcErr = _alertPref('gcThresholdError')
|
|
901
|
+
if (!(gcInfo > 0 && gcWarn > gcInfo && gcErr > gcWarn)) {
|
|
902
|
+
gcInfo = 2; gcWarn = 5; gcErr = 10
|
|
903
|
+
}
|
|
904
|
+
var mpm = d.gc.majorPerMin
|
|
905
|
+
var gcPausePerMin = d.gc.gcPausePerMin || 0
|
|
906
|
+
var minorCount = d.gc.minorCount || 0
|
|
907
|
+
|
|
908
|
+
// Shared GC alert message formatter.
|
|
909
|
+
var _gcMsg = function () {
|
|
910
|
+
return t('alertHostGcMsg')
|
|
911
|
+
.replace('{v}', mpm.toFixed(1))
|
|
912
|
+
.replace('{pause}', gcPausePerMin.toFixed(1))
|
|
913
|
+
.replace('{minor}', minorCount)
|
|
914
|
+
.replace('{rss}', rssMB)
|
|
915
|
+
.replace('{heap}', heapMB)
|
|
916
|
+
.replace('{heapTotal}', heapTotalMB)
|
|
917
|
+
.replace('{pct}', heapPct)
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
if (mpm >= gcErr) {
|
|
921
|
+
emitIfChanged(alerts, {
|
|
922
|
+
id: 'host-gc-error',
|
|
923
|
+
severity: 'error',
|
|
924
|
+
title: function () { return t('alertHostGcError') },
|
|
925
|
+
message: _gcMsg,
|
|
926
|
+
icon: '🟠',
|
|
927
|
+
timestamp: Date.now(),
|
|
928
|
+
dismissible: true,
|
|
929
|
+
actions: _dumpHeapActions(),
|
|
930
|
+
})
|
|
931
|
+
} else if (mpm >= gcWarn) {
|
|
932
|
+
emitIfChanged(alerts, {
|
|
933
|
+
id: 'host-gc-warning',
|
|
934
|
+
severity: 'warning',
|
|
935
|
+
title: function () { return t('alertHostGcWarning') },
|
|
936
|
+
message: _gcMsg,
|
|
937
|
+
icon: '🟡',
|
|
938
|
+
timestamp: Date.now(),
|
|
939
|
+
dismissible: true,
|
|
940
|
+
actions: _dumpHeapActions(),
|
|
941
|
+
})
|
|
942
|
+
} else if (mpm >= gcInfo) {
|
|
943
|
+
emitIfChanged(alerts, {
|
|
944
|
+
id: 'host-gc-info',
|
|
945
|
+
severity: 'info',
|
|
946
|
+
title: function () { return t('alertHostGcInfo') },
|
|
947
|
+
message: _gcMsg,
|
|
948
|
+
icon: '🔵',
|
|
949
|
+
timestamp: Date.now(),
|
|
950
|
+
dismissible: true,
|
|
951
|
+
actions: _dumpHeapActions(),
|
|
952
|
+
})
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
// Publish the summary to the shared cache so the config modal can
|
|
957
|
+
// render its trend panel without issuing its own summary poll
|
|
958
|
+
// (dedup of the /memory-trend?mode=summary round-trip).
|
|
959
|
+
_setSummaryCache(d)
|
|
960
|
+
|
|
961
|
+
try { settleArmed(); callback(alerts) } catch (_) {}
|
|
962
|
+
})
|
|
963
|
+
.catch(function () {
|
|
964
|
+
settleArmed()
|
|
965
|
+
try { callback([]) } catch (_) {}
|
|
966
|
+
})
|
|
967
|
+
|
|
968
|
+
// Schedule next poll
|
|
969
|
+
var pollBase = _alertPref('memPollBase')
|
|
970
|
+
var nextInterval = pollBase
|
|
971
|
+
timer = setTimeout(poll, nextInterval)
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
return {
|
|
975
|
+
id: 'dsh-flash-mem-mon:host-memory-alert',
|
|
976
|
+
start: function (cb) {
|
|
977
|
+
callback = cb
|
|
978
|
+
poll()
|
|
979
|
+
},
|
|
980
|
+
stop: function () {
|
|
981
|
+
callback = null
|
|
982
|
+
if (timer) { clearTimeout(timer); timer = null }
|
|
983
|
+
},
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
//#endregion ───────────────────────────────────────────────────────────────
|
|
988
|
+
|
|
989
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
990
|
+
//#region Icon helpers ────────────────────────────────────────────────────
|
|
991
|
+
|
|
992
|
+
var _ICON_PATHS = {
|
|
993
|
+
bolt: ['M9 2 3.5 9H8l-1 5 5.5-7H8l1-5z'],
|
|
994
|
+
signal: ['M3.2 11.6a6.8 6.8 0 0 1 9.6 0M5.6 9.2a4.2 4.2 0 0 1 4.8 0M8 6.8c.6 0 1.2.2 1.6.5M8 13a.9.9 0 1 0 0-1.8.9.9 0 0 0 0 1.8z'],
|
|
995
|
+
shield: ['M8 2 3 3.8v3.5c0 3.7 2.2 6.1 5 6.7 2.8-.6 5-3 5-6.7V3.8z', 'M6.2 8.1l1.2 1.2 2.4-2.6'],
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
function _switchIcon(icon, style) {
|
|
999
|
+
var paths = typeof icon === 'string' ? _ICON_PATHS[icon] : null
|
|
1000
|
+
if (!paths) return h('span', { style: style }, icon)
|
|
1001
|
+
return h('span', { style: style, 'aria-hidden': 'true' },
|
|
1002
|
+
h('svg', {
|
|
1003
|
+
width: 12, height: 12, viewBox: '0 0 16 16',
|
|
1004
|
+
fill: 'none', xmlns: 'http://www.w3.org/2000/svg',
|
|
1005
|
+
style: { display: 'block', margin: 'auto', color: 'currentColor' },
|
|
1006
|
+
}, paths.map(function (d, i) {
|
|
1007
|
+
return h('path', { key: i, d: d, stroke: 'currentColor', strokeWidth: 1.4,
|
|
1008
|
+
strokeLinecap: 'round', strokeLinejoin: 'round' })
|
|
1009
|
+
}))
|
|
1010
|
+
)
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
//#endregion ───────────────────────────────────────────────────────────────
|
|
1014
|
+
|
|
1015
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
1016
|
+
//#region ErrorBoundary ──────────────────────────────────────────────────
|
|
1017
|
+
|
|
1018
|
+
class PanelErrorBoundary extends Component {
|
|
1019
|
+
constructor(props) {
|
|
1020
|
+
super(props)
|
|
1021
|
+
this.state = { hasError: false, error: null }
|
|
1022
|
+
}
|
|
1023
|
+
static getDerivedStateFromError(error) {
|
|
1024
|
+
return { hasError: true, error: error }
|
|
1025
|
+
}
|
|
1026
|
+
componentDidCatch(error, info) {
|
|
1027
|
+
console.error('[dsh-flash-mem-mon] Panel render error (caught by boundary):', error, info)
|
|
1028
|
+
}
|
|
1029
|
+
render() {
|
|
1030
|
+
if (this.state.hasError) {
|
|
1031
|
+
return h('div', {
|
|
1032
|
+
style: {
|
|
1033
|
+
padding: '12px 16px',
|
|
1034
|
+
color: 'var(--dsw-alias-label-secondary, #8b949e)',
|
|
1035
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
1036
|
+
fontSize: '12px',
|
|
1037
|
+
},
|
|
1038
|
+
},
|
|
1039
|
+
h('div', { style: { marginBottom: '6px', color: 'var(--dsw-alias-label-warning, #d29922)' } },
|
|
1040
|
+
'⚠ mem-mon render error'),
|
|
1041
|
+
h('div', null, String(this.state.error && this.state.error.message || this.state.error || 'Unknown error')),
|
|
1042
|
+
h('button', {
|
|
1043
|
+
style: {
|
|
1044
|
+
marginTop: '8px',
|
|
1045
|
+
padding: '2px 10px',
|
|
1046
|
+
fontSize: '11px',
|
|
1047
|
+
cursor: 'pointer',
|
|
1048
|
+
borderRadius: '4px',
|
|
1049
|
+
border: '1px solid var(--dsw-alias-border-l2, #21262d)',
|
|
1050
|
+
background: 'var(--dsw-alias-bg-layer-2, rgba(255,255,255,0.85))',
|
|
1051
|
+
color: 'var(--dsw-alias-label-primary, #c9d1d9)',
|
|
1052
|
+
},
|
|
1053
|
+
onClick: function () { this.setState({ hasError: false, error: null }) }.bind(this),
|
|
1054
|
+
}, 'Retry'),
|
|
1055
|
+
)
|
|
1056
|
+
}
|
|
1057
|
+
return this.props.children
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
//#endregion ───────────────────────────────────────────────────────────────
|
|
1062
|
+
|
|
1063
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
1064
|
+
//#region Styles ─────────────────────────────────────────────────────────
|
|
1065
|
+
|
|
1066
|
+
var R = {
|
|
1067
|
+
xs: 'var(--dsw-radius-xs, 4px)',
|
|
1068
|
+
sm: 'var(--dsw-radius-sm, 8px)',
|
|
1069
|
+
md: 'var(--dsw-radius-md, 12px)',
|
|
1070
|
+
lg: 'var(--dsw-radius-lg, 16px)',
|
|
1071
|
+
xl: 'var(--dsw-radius-xl, 20px)',
|
|
1072
|
+
panel: 'var(--dsw-radius-panel, 28px)',
|
|
1073
|
+
}
|
|
1074
|
+
var E = {
|
|
1075
|
+
prominent: 'var(--dsw-elevation-prominent, 0 0 0 0.5px var(--dsw-alias-border-l4, #0003), 0 3px 8px 0 rgba(0,0,0,.04), 0 0 20px 0 rgba(0,0,0,.05))',
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
var S = {
|
|
1079
|
+
// Switch row / slider (used by MonitorConfigModal)
|
|
1080
|
+
switchRow: {
|
|
1081
|
+
display: 'flex',
|
|
1082
|
+
alignItems: 'center',
|
|
1083
|
+
gap: '8px',
|
|
1084
|
+
marginBottom: '8px',
|
|
1085
|
+
},
|
|
1086
|
+
switchLabel: {
|
|
1087
|
+
display: 'flex',
|
|
1088
|
+
alignItems: 'center',
|
|
1089
|
+
gap: '6px',
|
|
1090
|
+
minWidth: '120px',
|
|
1091
|
+
fontSize: '12px',
|
|
1092
|
+
color: 'var(--dsw-alias-label-primary, #c9d1d9)',
|
|
1093
|
+
},
|
|
1094
|
+
switchIcon: {
|
|
1095
|
+
display: 'inline-flex',
|
|
1096
|
+
verticalAlign: '-2px',
|
|
1097
|
+
marginRight: '2px',
|
|
1098
|
+
},
|
|
1099
|
+
sliderRow: {
|
|
1100
|
+
flex: 1,
|
|
1101
|
+
display: 'flex',
|
|
1102
|
+
alignItems: 'center',
|
|
1103
|
+
gap: '8px',
|
|
1104
|
+
minWidth: 0,
|
|
1105
|
+
marginBottom: '8px',
|
|
1106
|
+
},
|
|
1107
|
+
slider: {
|
|
1108
|
+
flex: 1,
|
|
1109
|
+
minWidth: 0,
|
|
1110
|
+
},
|
|
1111
|
+
value: {
|
|
1112
|
+
fontSize: '11px',
|
|
1113
|
+
color: 'var(--dsw-alias-label-secondary, #8b949e)',
|
|
1114
|
+
minWidth: '42px',
|
|
1115
|
+
textAlign: 'right',
|
|
1116
|
+
},
|
|
1117
|
+
// Monitor config modal
|
|
1118
|
+
monitorModalMask: {
|
|
1119
|
+
position: 'fixed',
|
|
1120
|
+
inset: 0,
|
|
1121
|
+
zIndex: 2200,
|
|
1122
|
+
background: 'var(--dsw-alias-bg-mask-1, #0000003d)',
|
|
1123
|
+
display: 'flex',
|
|
1124
|
+
alignItems: 'center',
|
|
1125
|
+
justifyContent: 'center',
|
|
1126
|
+
},
|
|
1127
|
+
monitorModal: {
|
|
1128
|
+
background: 'var(--dsw-alias-bg-layer-2, #fff)',
|
|
1129
|
+
border: 0,
|
|
1130
|
+
borderRadius: R.panel,
|
|
1131
|
+
width: '90vw',
|
|
1132
|
+
maxWidth: '680px',
|
|
1133
|
+
maxHeight: '80vh',
|
|
1134
|
+
display: 'flex',
|
|
1135
|
+
flexDirection: 'column',
|
|
1136
|
+
overflow: 'hidden',
|
|
1137
|
+
boxShadow: E.prominent,
|
|
1138
|
+
},
|
|
1139
|
+
monitorModalHead: {
|
|
1140
|
+
display: 'flex',
|
|
1141
|
+
alignItems: 'center',
|
|
1142
|
+
justifyContent: 'space-between',
|
|
1143
|
+
padding: '10px 14px',
|
|
1144
|
+
borderBottom: '0.5px solid var(--dsw-alias-border-l2, #0003)',
|
|
1145
|
+
},
|
|
1146
|
+
monitorModalTitle: {
|
|
1147
|
+
fontWeight: 500,
|
|
1148
|
+
fontSize: '16px',
|
|
1149
|
+
lineHeight: '24px',
|
|
1150
|
+
color: 'var(--dsw-alias-label-primary, #000)',
|
|
1151
|
+
},
|
|
1152
|
+
monitorModalClose: {
|
|
1153
|
+
display: 'inline-flex',
|
|
1154
|
+
alignItems: 'center',
|
|
1155
|
+
justifyContent: 'center',
|
|
1156
|
+
width: '28px',
|
|
1157
|
+
height: '28px',
|
|
1158
|
+
border: 0,
|
|
1159
|
+
borderRadius: R.sm,
|
|
1160
|
+
background: 'transparent',
|
|
1161
|
+
color: 'var(--dsw-alias-label-secondary, #61666b)',
|
|
1162
|
+
cursor: 'pointer',
|
|
1163
|
+
fontSize: '14px',
|
|
1164
|
+
padding: 0,
|
|
1165
|
+
},
|
|
1166
|
+
monitorModalBody: {
|
|
1167
|
+
flex: 1,
|
|
1168
|
+
overflowY: 'auto',
|
|
1169
|
+
padding: '10px 14px',
|
|
1170
|
+
scrollbarWidth: 'thin',
|
|
1171
|
+
scrollbarColor: 'var(--dsw-alias-scrollbar-bg-l1, #ccc) var(--dsw-alias-bg-layer-2, transparent)',
|
|
1172
|
+
},
|
|
1173
|
+
monitorModalDivider: {
|
|
1174
|
+
marginTop: '12px',
|
|
1175
|
+
paddingTop: '10px',
|
|
1176
|
+
borderTop: '0.5px solid var(--dsw-alias-border-l2, #0003)',
|
|
1177
|
+
},
|
|
1178
|
+
monitorModalSectionTitle: {
|
|
1179
|
+
display: 'block',
|
|
1180
|
+
fontWeight: 500,
|
|
1181
|
+
fontSize: '14px',
|
|
1182
|
+
lineHeight: '22px',
|
|
1183
|
+
marginBottom: '8px',
|
|
1184
|
+
color: 'var(--dsw-alias-label-secondary, #61666b)',
|
|
1185
|
+
},
|
|
1186
|
+
monitorFieldsGrid: {
|
|
1187
|
+
display: 'grid',
|
|
1188
|
+
gridTemplateColumns: '1fr 1fr',
|
|
1189
|
+
columnGap: '16px',
|
|
1190
|
+
rowGap: '12px',
|
|
1191
|
+
},
|
|
1192
|
+
monitorConfigTwoCol: {
|
|
1193
|
+
display: 'grid',
|
|
1194
|
+
gridTemplateColumns: '1fr 1fr',
|
|
1195
|
+
columnGap: '16px',
|
|
1196
|
+
},
|
|
1197
|
+
monitorConfigGroupTitle: {
|
|
1198
|
+
display: 'block',
|
|
1199
|
+
fontWeight: 600,
|
|
1200
|
+
fontSize: '12px',
|
|
1201
|
+
marginBottom: '6px',
|
|
1202
|
+
color: 'var(--dsw-alias-label-secondary, #8b949e)',
|
|
1203
|
+
},
|
|
1204
|
+
// Alert preview
|
|
1205
|
+
alertPreviewToggle: {
|
|
1206
|
+
background: 'none',
|
|
1207
|
+
border: 0,
|
|
1208
|
+
padding: 0,
|
|
1209
|
+
cursor: 'pointer',
|
|
1210
|
+
color: 'var(--dsw-alias-label-secondary, #8b949e)',
|
|
1211
|
+
fontSize: '12px',
|
|
1212
|
+
},
|
|
1213
|
+
alertPreviewCard: {
|
|
1214
|
+
padding: '8px 12px',
|
|
1215
|
+
background: 'var(--dsw-alias-bg-layer-1, #f5f5f5)',
|
|
1216
|
+
borderRadius: R.md,
|
|
1217
|
+
fontSize: '12px',
|
|
1218
|
+
lineHeight: '1.5',
|
|
1219
|
+
},
|
|
1220
|
+
alertPreviewGroup: {
|
|
1221
|
+
marginBottom: '8px',
|
|
1222
|
+
},
|
|
1223
|
+
alertPreviewGroupLabel: {
|
|
1224
|
+
display: 'block',
|
|
1225
|
+
fontWeight: 600,
|
|
1226
|
+
fontSize: '11px',
|
|
1227
|
+
marginBottom: '4px',
|
|
1228
|
+
color: 'var(--dsw-alias-label-primary, #c9d1d9)',
|
|
1229
|
+
},
|
|
1230
|
+
alertPreviewRow: {
|
|
1231
|
+
display: 'flex',
|
|
1232
|
+
alignItems: 'center',
|
|
1233
|
+
gap: '6px',
|
|
1234
|
+
marginBottom: '2px',
|
|
1235
|
+
},
|
|
1236
|
+
alertPreviewIcon: {
|
|
1237
|
+
flex: 'none',
|
|
1238
|
+
fontSize: '11px',
|
|
1239
|
+
},
|
|
1240
|
+
alertPreviewMsg: {
|
|
1241
|
+
flex: 1,
|
|
1242
|
+
color: 'var(--dsw-alias-label-secondary, #8b949e)',
|
|
1243
|
+
},
|
|
1244
|
+
alertPreviewCond: {
|
|
1245
|
+
color: 'var(--dsw-alias-label-secondary, #8b949e)',
|
|
1246
|
+
fontSize: '11px',
|
|
1247
|
+
opacity: 0.6,
|
|
1248
|
+
},
|
|
1249
|
+
// Memory live data
|
|
1250
|
+
memColumnCard: {
|
|
1251
|
+
padding: '8px 12px',
|
|
1252
|
+
background: 'var(--dsw-alias-bg-layer-1, #f5f5f5)',
|
|
1253
|
+
borderRadius: R.md,
|
|
1254
|
+
},
|
|
1255
|
+
memSubSectionLabel: {
|
|
1256
|
+
display: 'block',
|
|
1257
|
+
fontWeight: 600,
|
|
1258
|
+
fontSize: '11px',
|
|
1259
|
+
marginBottom: '6px',
|
|
1260
|
+
color: 'var(--dsw-alias-label-secondary, #8b949e)',
|
|
1261
|
+
},
|
|
1262
|
+
memLiveDataGrid: {
|
|
1263
|
+
display: 'grid',
|
|
1264
|
+
gridTemplateColumns: 'auto auto',
|
|
1265
|
+
columnGap: '10px',
|
|
1266
|
+
rowGap: '4px',
|
|
1267
|
+
fontSize: '12px',
|
|
1268
|
+
},
|
|
1269
|
+
memLiveDataLabel: {
|
|
1270
|
+
color: 'var(--dsw-alias-label-secondary, #8b949e)',
|
|
1271
|
+
},
|
|
1272
|
+
memLiveDataValue: {
|
|
1273
|
+
color: 'var(--dsw-alias-label-primary, #c9d1d9)',
|
|
1274
|
+
fontWeight: 500,
|
|
1275
|
+
},
|
|
1276
|
+
memUsageBarTrack: {
|
|
1277
|
+
height: '4px',
|
|
1278
|
+
borderRadius: '2px',
|
|
1279
|
+
background: 'rgba(255,255,255,0.08)',
|
|
1280
|
+
overflow: 'hidden',
|
|
1281
|
+
marginTop: '2px',
|
|
1282
|
+
},
|
|
1283
|
+
memUsageBarFill: {
|
|
1284
|
+
height: '100%',
|
|
1285
|
+
borderRadius: '2px',
|
|
1286
|
+
transition: 'width 0.3s ease, background 0.3s ease',
|
|
1287
|
+
},
|
|
1288
|
+
// Sparkline
|
|
1289
|
+
sparklineWrap: {
|
|
1290
|
+
position: 'relative',
|
|
1291
|
+
height: '80px',
|
|
1292
|
+
marginTop: '6px',
|
|
1293
|
+
marginBottom: '2px',
|
|
1294
|
+
},
|
|
1295
|
+
sparklineSvg: {
|
|
1296
|
+
position: 'absolute',
|
|
1297
|
+
inset: 0,
|
|
1298
|
+
width: '100%',
|
|
1299
|
+
height: '100%',
|
|
1300
|
+
},
|
|
1301
|
+
sparklineHover: {
|
|
1302
|
+
position: 'absolute',
|
|
1303
|
+
top: 0,
|
|
1304
|
+
bottom: 0,
|
|
1305
|
+
width: '1px',
|
|
1306
|
+
background: 'rgba(255,255,255,0.2)',
|
|
1307
|
+
pointerEvents: 'none',
|
|
1308
|
+
},
|
|
1309
|
+
sparklineTooltip: {
|
|
1310
|
+
position: 'absolute',
|
|
1311
|
+
top: '-2px',
|
|
1312
|
+
left: '50%',
|
|
1313
|
+
transform: 'translateX(-50%)',
|
|
1314
|
+
fontSize: '10px',
|
|
1315
|
+
lineHeight: '14px',
|
|
1316
|
+
padding: '2px 6px',
|
|
1317
|
+
borderRadius: '4px',
|
|
1318
|
+
background: 'var(--dsw-alias-bg-layer-1, #f5f5f5)',
|
|
1319
|
+
border: '0.5px solid var(--dsw-alias-border-l4, #0003)',
|
|
1320
|
+
color: 'var(--dsw-alias-label-primary, #c9d1d9)',
|
|
1321
|
+
whiteSpace: 'nowrap',
|
|
1322
|
+
pointerEvents: 'none',
|
|
1323
|
+
},
|
|
1324
|
+
// Trend chart axes (HTML overlays — the SVG uses preserveAspectRatio:none
|
|
1325
|
+
// so SVG <text> would stretch illegibly).
|
|
1326
|
+
trendAxisWrap: {
|
|
1327
|
+
display: 'flex',
|
|
1328
|
+
alignItems: 'stretch',
|
|
1329
|
+
gap: '4px',
|
|
1330
|
+
},
|
|
1331
|
+
trendAxisY: {
|
|
1332
|
+
display: 'flex',
|
|
1333
|
+
flexDirection: 'column',
|
|
1334
|
+
justifyContent: 'space-between',
|
|
1335
|
+
width: '34px',
|
|
1336
|
+
fontSize: '9px',
|
|
1337
|
+
lineHeight: '10px',
|
|
1338
|
+
textAlign: 'right',
|
|
1339
|
+
color: 'var(--dsw-alias-label-secondary, #8b949e)',
|
|
1340
|
+
userSelect: 'none',
|
|
1341
|
+
},
|
|
1342
|
+
trendChartWrap: {
|
|
1343
|
+
display: 'flex',
|
|
1344
|
+
flexDirection: 'column',
|
|
1345
|
+
flex: '1 1 auto',
|
|
1346
|
+
minWidth: '0',
|
|
1347
|
+
},
|
|
1348
|
+
trendAxisX: {
|
|
1349
|
+
display: 'flex',
|
|
1350
|
+
justifyContent: 'space-between',
|
|
1351
|
+
fontSize: '9px',
|
|
1352
|
+
lineHeight: '12px',
|
|
1353
|
+
color: 'var(--dsw-alias-label-secondary, #8b949e)',
|
|
1354
|
+
marginTop: '2px',
|
|
1355
|
+
userSelect: 'none',
|
|
1356
|
+
},
|
|
1357
|
+
// GC event list
|
|
1358
|
+
gcEventListWrap: {
|
|
1359
|
+
maxHeight: '120px',
|
|
1360
|
+
overflowY: 'auto',
|
|
1361
|
+
scrollbarWidth: 'thin',
|
|
1362
|
+
},
|
|
1363
|
+
gcEventRow: {
|
|
1364
|
+
display: 'flex',
|
|
1365
|
+
alignItems: 'center',
|
|
1366
|
+
gap: '8px',
|
|
1367
|
+
padding: '2px 0',
|
|
1368
|
+
fontSize: '11px',
|
|
1369
|
+
cursor: 'pointer',
|
|
1370
|
+
borderRadius: '3px',
|
|
1371
|
+
},
|
|
1372
|
+
gcEventRowHighlight: {
|
|
1373
|
+
background: 'rgba(218, 54, 51, 0.12)',
|
|
1374
|
+
},
|
|
1375
|
+
gcEventTimeCol: {
|
|
1376
|
+
color: 'var(--dsw-alias-label-secondary, #8b949e)',
|
|
1377
|
+
minWidth: '56px',
|
|
1378
|
+
},
|
|
1379
|
+
gcEventDurationCol: {
|
|
1380
|
+
color: 'var(--dsw-alias-label-primary, #c9d1d9)',
|
|
1381
|
+
minWidth: '100px',
|
|
1382
|
+
},
|
|
1383
|
+
gcEventKindCol: {
|
|
1384
|
+
color: 'var(--dsw-alias-label-secondary, #8b949e)',
|
|
1385
|
+
},
|
|
1386
|
+
gcEventEmpty: {
|
|
1387
|
+
color: 'var(--dsw-alias-label-secondary, #8b949e)',
|
|
1388
|
+
fontSize: '11px',
|
|
1389
|
+
opacity: 0.5,
|
|
1390
|
+
},
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
//#endregion ───────────────────────────────────────────────────────────────
|
|
1394
|
+
|
|
1395
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
1396
|
+
//#region MonitorConfigModal (memory only) ──────────────────────────────
|
|
1397
|
+
|
|
1398
|
+
var MONITOR_SLIDER_FIELDS = {
|
|
1399
|
+
memory: [
|
|
1400
|
+
{ key: 'memThresholdInfo', labelKey: 'memThresholdInfo', tooltipKey: 'memThresholdInfoTip', group: 'rss', min: 64, max: 1024, step: 64, format: function (v) { return v + ' MB' } },
|
|
1401
|
+
{ key: 'memThresholdWarning', labelKey: 'memThresholdWarning', tooltipKey: 'memThresholdWarningTip', group: 'rss', min: 128, max: 1536, step: 64, format: function (v) { return v + ' MB' } },
|
|
1402
|
+
{ key: 'memThresholdError', labelKey: 'memThresholdError', tooltipKey: 'memThresholdErrorTip', group: 'rss', min: 256, max: 4096, step: 64, format: function (v) { return v + ' MB' } },
|
|
1403
|
+
{ key: 'memThresholdCritical',labelKey: 'memThresholdCritical',tooltipKey: 'memThresholdCriticalTip',group: 'rss', min: 512, max: 4096, step: 64, format: function (v) { return v + ' MB' } },
|
|
1404
|
+
{ key: 'memPollBase', labelKey: 'memPollBase', tooltipKey: 'memPollBaseTip', group: 'rss', min: 5000, max: 60000, step: 1000, format: function (v) { return v + 'ms' } },
|
|
1405
|
+
{ key: 'memPollMin', labelKey: 'memPollMin', tooltipKey: 'memPollMinTip', group: 'rss', min: 1000, max: 10000, step: 500, format: function (v) { return v + 'ms' } },
|
|
1406
|
+
{ key: 'memGrowthAlertPerMin',labelKey: 'memGrowthThreshold', tooltipKey: 'memGrowthThresholdTip', group: 'rss', min: 1, max: 100, step: 1, format: function (v) { return v + ' MB/min' } },
|
|
1407
|
+
{ key: 'gcThresholdInfo', labelKey: 'gcThresholdInfo', tooltipKey: 'gcThresholdInfoTip', group: 'gc', min: 1, max: 5, step: 1, format: function (v) { return v + '/min' } },
|
|
1408
|
+
{ key: 'gcThresholdWarning', labelKey: 'gcThresholdWarning', tooltipKey: 'gcThresholdWarningTip', group: 'gc', min: 2, max: 8, step: 1, format: function (v) { return v + '/min' } },
|
|
1409
|
+
{ key: 'gcThresholdError', labelKey: 'gcThresholdError', tooltipKey: 'gcThresholdErrorTip', group: 'gc', min: 4, max: 15, step: 1, format: function (v) { return v + '/min' } },
|
|
1410
|
+
],
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
var MONITOR_TITLE_KEY = {
|
|
1414
|
+
memory: 'alertMemCluster',
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
var _monitorConfigRoot = null
|
|
1418
|
+
var _monitorConfigHost = null
|
|
1419
|
+
|
|
1420
|
+
function closeMonitorConfig() {
|
|
1421
|
+
if (_monitorConfigRoot) { try { _monitorConfigRoot.unmount() } catch (_) {} _monitorConfigRoot = null }
|
|
1422
|
+
if (_monitorConfigHost) { try { _monitorConfigHost.remove() } catch (_) {} _monitorConfigHost = null }
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
function openMonitorConfig(monitor) {
|
|
1426
|
+
if (!ReactDOMClient || !ReactDOMClient.createRoot) return
|
|
1427
|
+
closeMonitorConfig()
|
|
1428
|
+
var host = document.createElement('div')
|
|
1429
|
+
host.style.cssText = 'position:fixed;inset:0;z-index:2200;'
|
|
1430
|
+
document.body.appendChild(host)
|
|
1431
|
+
_monitorConfigHost = host
|
|
1432
|
+
try {
|
|
1433
|
+
_monitorConfigRoot = ReactDOMClient.createRoot(host)
|
|
1434
|
+
_monitorConfigRoot.render(h(MonitorConfigModal, {
|
|
1435
|
+
monitor: monitor,
|
|
1436
|
+
onClose: closeMonitorConfig,
|
|
1437
|
+
}))
|
|
1438
|
+
} catch (e) {
|
|
1439
|
+
console.error('[dsh-flash-mem-mon] failed to open monitor config:', e)
|
|
1440
|
+
closeMonitorConfig()
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
function MonitorConfigModal(props) {
|
|
1445
|
+
var monitor = props.monitor
|
|
1446
|
+
var onClose = props.onClose
|
|
1447
|
+
var fields = MONITOR_SLIDER_FIELDS[monitor] || []
|
|
1448
|
+
var _tick = useState(0)
|
|
1449
|
+
var tick = _tick[0], setTick = _tick[1]
|
|
1450
|
+
|
|
1451
|
+
// Host process memory — fetched from /health route (cross-plugin, dock-flash).
|
|
1452
|
+
var _hostMem = useState(null)
|
|
1453
|
+
var hostMem = _hostMem[0], setHostMem = _hostMem[1]
|
|
1454
|
+
|
|
1455
|
+
// Memory trend summary — fetched from own /memory-trend route.
|
|
1456
|
+
var _trend = useState(null)
|
|
1457
|
+
var trendData = _trend[0], setTrendData = _trend[1]
|
|
1458
|
+
|
|
1459
|
+
// Memory trend full samples — fetched on demand for sparkline.
|
|
1460
|
+
var _trendSamples = useState(null)
|
|
1461
|
+
var trendSamples = _trendSamples[0], setTrendSamples = _trendSamples[1]
|
|
1462
|
+
|
|
1463
|
+
// GC events from the same full fetch — for sparkline overlay markers.
|
|
1464
|
+
var _gcEvents = useState(null)
|
|
1465
|
+
var gcEvents = _gcEvents[0], setGcEvents = _gcEvents[1]
|
|
1466
|
+
|
|
1467
|
+
// Session footprint — total/archived session counts and on-disk usage
|
|
1468
|
+
// from our own /session-footprint route (cached host snapshot).
|
|
1469
|
+
var _footprint = useState(null)
|
|
1470
|
+
var footprint = _footprint[0], setFootprint = _footprint[1]
|
|
1471
|
+
|
|
1472
|
+
// Sparkline hover index (null = not hovering).
|
|
1473
|
+
var _hoverIdx = useState(null)
|
|
1474
|
+
var hoverIdx = _hoverIdx[0], setHoverIdx = _hoverIdx[1]
|
|
1475
|
+
|
|
1476
|
+
// GC event highlight index (null = none selected).
|
|
1477
|
+
var _gcHighlightIdx = useState(null)
|
|
1478
|
+
var gcHighlightIdx = _gcHighlightIdx[0], setGcHighlightIdx = _gcHighlightIdx[1]
|
|
1479
|
+
|
|
1480
|
+
// Close on Escape.
|
|
1481
|
+
useEffect(function () {
|
|
1482
|
+
function onKey(e) { if (e.key === 'Escape') onClose() }
|
|
1483
|
+
document.addEventListener('keydown', onKey)
|
|
1484
|
+
return function () { document.removeEventListener('keydown', onKey) }
|
|
1485
|
+
}, [onClose])
|
|
1486
|
+
|
|
1487
|
+
// Re-render when the global language changes. Every string in this modal is
|
|
1488
|
+
// a `t()` call, so a re-render is the whole fix; without this lever the
|
|
1489
|
+
// open modal would keep its old language until the next slider drag or the
|
|
1490
|
+
// next 2s host-memory poll.
|
|
1491
|
+
useEffect(function () {
|
|
1492
|
+
return t.onLocaleChange(function () { setTick(function (v) { return v + 1 }) })
|
|
1493
|
+
}, [])
|
|
1494
|
+
|
|
1495
|
+
// Auto-refresh host memory data every 2 seconds while the modal is open.
|
|
1496
|
+
useEffect(function () {
|
|
1497
|
+
function fetchHost() {
|
|
1498
|
+
fetch('/plugins/dsh-flash/health').then(function (r) { return r.json() }).then(function (d) {
|
|
1499
|
+
if (d && d.memory) setHostMem(d.memory)
|
|
1500
|
+
}).catch(function () {})
|
|
1501
|
+
}
|
|
1502
|
+
fetchHost()
|
|
1503
|
+
var hostTimer = setInterval(fetchHost, 2000)
|
|
1504
|
+
return function () { clearInterval(hostTimer) }
|
|
1505
|
+
}, [])
|
|
1506
|
+
|
|
1507
|
+
// Trend summary comes from the SHARED cache populated by the alert
|
|
1508
|
+
// provider's poll — subscribing (instead of issuing our own summary poll)
|
|
1509
|
+
// avoids double-fetching /memory-trend?mode=summary while the modal is
|
|
1510
|
+
// open. The provider polls roughly every memPollBase, so live updates
|
|
1511
|
+
// arrive at that cadence.
|
|
1512
|
+
useEffect(function () {
|
|
1513
|
+
var unsub = _subscribeSummary(function (d) {
|
|
1514
|
+
if (d) setTrendData(d)
|
|
1515
|
+
})
|
|
1516
|
+
// If the shared cache is empty (e.g. the alert provider is disabled or
|
|
1517
|
+
// has not polled yet), fetch once so the trend panel still renders.
|
|
1518
|
+
if (!_summaryCache) {
|
|
1519
|
+
fetch('/plugins/dsh-flash-mem-mon/memory-trend?mode=summary')
|
|
1520
|
+
.then(function (r) { return r.json() })
|
|
1521
|
+
.then(function (d) { if (d) _setSummaryCache(d) })
|
|
1522
|
+
.catch(function () {})
|
|
1523
|
+
}
|
|
1524
|
+
return function () { unsub() }
|
|
1525
|
+
}, [])
|
|
1526
|
+
|
|
1527
|
+
// Fetch full samples for sparkline (once on mount + periodic refresh).
|
|
1528
|
+
useEffect(function () {
|
|
1529
|
+
function fetchSamples() {
|
|
1530
|
+
fetch('/plugins/dsh-flash-mem-mon/memory-trend?mode=full')
|
|
1531
|
+
.then(function (r) { return r.json() })
|
|
1532
|
+
.then(function (d) {
|
|
1533
|
+
if (d && d.samples) setTrendSamples(d.samples.slice(-360))
|
|
1534
|
+
if (d && d.gcEvents) setGcEvents(d.gcEvents)
|
|
1535
|
+
})
|
|
1536
|
+
.catch(function () {})
|
|
1537
|
+
}
|
|
1538
|
+
fetchSamples()
|
|
1539
|
+
var sampleTimer = setInterval(fetchSamples, 5000)
|
|
1540
|
+
return function () { clearInterval(sampleTimer) }
|
|
1541
|
+
}, [])
|
|
1542
|
+
|
|
1543
|
+
// Session footprint — the host collector rescans no more often than every
|
|
1544
|
+
// ~5 min and serves a cached snapshot with the routes' last `scannedAt`, so
|
|
1545
|
+
// the panel is fetched only occasionally; a low-frequency fallback keeps
|
|
1546
|
+
// the panel live even if the shared cache never lands here.
|
|
1547
|
+
useEffect(function () {
|
|
1548
|
+
function fetchFootprint() {
|
|
1549
|
+
fetch('/plugins/dsh-flash-mem-mon/session-footprint')
|
|
1550
|
+
.then(function (r) { return r.json() })
|
|
1551
|
+
.then(function (d) { if (d) setFootprint(d) })
|
|
1552
|
+
.catch(function () {})
|
|
1553
|
+
}
|
|
1554
|
+
fetchFootprint()
|
|
1555
|
+
var footprintTimer = setInterval(fetchFootprint, 60000)
|
|
1556
|
+
return function () { clearInterval(footprintTimer) }
|
|
1557
|
+
}, [])
|
|
1558
|
+
|
|
1559
|
+
// Render a single slider field row.
|
|
1560
|
+
function renderSliderField(f) {
|
|
1561
|
+
var val = Number(_alertPref(f.key)) || 0
|
|
1562
|
+
var hintEl = f.hintKey
|
|
1563
|
+
? h('div', { style: { fontSize: '11px', opacity: 0.4, marginTop: '-2px' } }, t(f.hintKey))
|
|
1564
|
+
: null
|
|
1565
|
+
return h('div', { key: f.key, style: S.switchRow },
|
|
1566
|
+
h('span', { style: S.switchLabel },
|
|
1567
|
+
h('span', { style: S.switchIcon }, '⚙'),
|
|
1568
|
+
h('span', null, t(f.labelKey)),
|
|
1569
|
+
hintEl,
|
|
1570
|
+
),
|
|
1571
|
+
h('div', { style: S.sliderRow },
|
|
1572
|
+
h('input', {
|
|
1573
|
+
type: 'range',
|
|
1574
|
+
min: f.min, max: f.max, step: f.step,
|
|
1575
|
+
value: val,
|
|
1576
|
+
style: S.slider,
|
|
1577
|
+
onChange: function (e) {
|
|
1578
|
+
var next = Number(e.target.value)
|
|
1579
|
+
_writeAlertPref(_prefCtx, f.key, next, function (patch, rollback) { rollback() })
|
|
1580
|
+
setTick(function (v) { return v + 1 })
|
|
1581
|
+
},
|
|
1582
|
+
}),
|
|
1583
|
+
h('span', { style: S.value }, f.format(val)),
|
|
1584
|
+
),
|
|
1585
|
+
)
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
// For memory config: two-column layout (RSS + polling on the left, GC on the right).
|
|
1589
|
+
var fieldEls
|
|
1590
|
+
if (fields.some(function (f) { return f.group })) {
|
|
1591
|
+
var rssFields = fields.filter(function (f) { return f.group === 'rss' })
|
|
1592
|
+
var gcFields = fields.filter(function (f) { return f.group === 'gc' })
|
|
1593
|
+
fieldEls = h('div', { style: S.monitorConfigTwoCol },
|
|
1594
|
+
h('div', null,
|
|
1595
|
+
h('span', { style: S.monitorConfigGroupTitle }, t('memConfigGroupRss')),
|
|
1596
|
+
rssFields.map(renderSliderField),
|
|
1597
|
+
),
|
|
1598
|
+
h('div', null,
|
|
1599
|
+
h('span', { style: S.monitorConfigGroupTitle }, t('memConfigGroupGc')),
|
|
1600
|
+
gcFields.map(renderSliderField),
|
|
1601
|
+
),
|
|
1602
|
+
)
|
|
1603
|
+
} else {
|
|
1604
|
+
fieldEls = fields.map(renderSliderField)
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
// ── Alert preview section ──────────────────────────────────────────
|
|
1608
|
+
var _showPreview = useState(false)
|
|
1609
|
+
var showPreview = _showPreview[0], setShowPreview = _showPreview[1]
|
|
1610
|
+
|
|
1611
|
+
var alertRows = []
|
|
1612
|
+
// ── Criterion 1: RSS absolute value ──
|
|
1613
|
+
var _tI = _alertPref('memThresholdInfo')
|
|
1614
|
+
var _tW = _alertPref('memThresholdWarning')
|
|
1615
|
+
var _tE = _alertPref('memThresholdError')
|
|
1616
|
+
var _tC = _alertPref('memThresholdCritical')
|
|
1617
|
+
// If user thresholds violate ordering (or are unset), fall back to the
|
|
1618
|
+
// canonical defaults (mirrored from host DEFAULT_MEM_*).
|
|
1619
|
+
if (!(_tI > 0 && _tW > _tI && _tE > _tW && _tC > _tE)) {
|
|
1620
|
+
_tI = _MEM_ALERT_DEFAULTS.memThresholdInfo
|
|
1621
|
+
_tW = _MEM_ALERT_DEFAULTS.memThresholdWarning
|
|
1622
|
+
_tE = _MEM_ALERT_DEFAULTS.memThresholdError
|
|
1623
|
+
_tC = _MEM_ALERT_DEFAULTS.memThresholdCritical
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
// Helper to fill all RSS alert template placeholders with sample values for preview.
|
|
1627
|
+
var _rssPreviewMsg = function (rssVal) {
|
|
1628
|
+
return t('alertHostMemMsg')
|
|
1629
|
+
.replace('{v}', rssVal + '')
|
|
1630
|
+
.replace('{heap}', '…')
|
|
1631
|
+
.replace('{heapTotal}', '…')
|
|
1632
|
+
.replace('{pct}', '…')
|
|
1633
|
+
.replace('{ext}', '…')
|
|
1634
|
+
.replace('{ab}', '…')
|
|
1635
|
+
.replace('{trend}', '→')
|
|
1636
|
+
.replace('{slope}', '0.0')
|
|
1637
|
+
.replace('{gcInfo}', '')
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
alertRows.push(
|
|
1641
|
+
h('div', { key: 'rss', style: S.alertPreviewGroup },
|
|
1642
|
+
h('span', { style: S.alertPreviewGroupLabel }, t('alertPreviewCriteriaRss')),
|
|
1643
|
+
h('div', { style: S.alertPreviewRow },
|
|
1644
|
+
h('span', { style: S.alertPreviewIcon }, '🔵'),
|
|
1645
|
+
h('span', { style: S.alertPreviewMsg }, t('alertHostMemInfo').replace('{threshold}', _tI)),
|
|
1646
|
+
h('span', { style: S.alertPreviewCond }, '≥ ' + _tI + ' MB'),
|
|
1647
|
+
),
|
|
1648
|
+
h('div', { style: S.alertPreviewRow },
|
|
1649
|
+
h('span', { style: S.alertPreviewIcon }, '🟡'),
|
|
1650
|
+
h('span', { style: S.alertPreviewMsg }, t('alertHostMemWarning').replace('{threshold}', _tW)),
|
|
1651
|
+
h('span', { style: S.alertPreviewCond }, '≥ ' + _tW + ' MB'),
|
|
1652
|
+
),
|
|
1653
|
+
h('div', { style: S.alertPreviewRow },
|
|
1654
|
+
h('span', { style: S.alertPreviewIcon }, '🟠'),
|
|
1655
|
+
h('span', { style: S.alertPreviewMsg }, t('alertHostMemError').replace('{threshold}', _tE)),
|
|
1656
|
+
h('span', { style: S.alertPreviewCond }, '≥ ' + _tE + ' MB'),
|
|
1657
|
+
),
|
|
1658
|
+
h('div', { style: S.alertPreviewRow },
|
|
1659
|
+
h('span', { style: S.alertPreviewIcon }, '🔴'),
|
|
1660
|
+
h('span', { style: S.alertPreviewMsg }, t('alertHostMemCritical').replace('{threshold}', _tC)),
|
|
1661
|
+
h('span', { style: S.alertPreviewCond }, '≥ ' + _tC + ' MB'),
|
|
1662
|
+
),
|
|
1663
|
+
),
|
|
1664
|
+
)
|
|
1665
|
+
|
|
1666
|
+
// ── Criterion 2: RSS growth rate ──
|
|
1667
|
+
var _growthPreviewMsg = function () {
|
|
1668
|
+
return t('alertHostMemGrowthMsg')
|
|
1669
|
+
.replace('{r}', 'N')
|
|
1670
|
+
.replace('{rss}', '…')
|
|
1671
|
+
.replace('{heap}', '…')
|
|
1672
|
+
.replace('{heapTotal}', '…')
|
|
1673
|
+
.replace('{pct}', '…')
|
|
1674
|
+
.replace('{ab}', '…')
|
|
1675
|
+
.replace('{trend}', '↑')
|
|
1676
|
+
}
|
|
1677
|
+
alertRows.push(
|
|
1678
|
+
h('div', { key: 'growth', style: S.alertPreviewGroup },
|
|
1679
|
+
h('span', { style: S.alertPreviewGroupLabel }, t('alertPreviewCriteriaGrowth')),
|
|
1680
|
+
h('div', { style: S.alertPreviewRow },
|
|
1681
|
+
h('span', { style: S.alertPreviewIcon }, '📈'),
|
|
1682
|
+
h('span', { style: S.alertPreviewMsg }, t('alertHostMemGrowth') + ': ' + _growthPreviewMsg()),
|
|
1683
|
+
h('span', { style: S.alertPreviewCond }, '> 1 MB/min + ↑'),
|
|
1684
|
+
),
|
|
1685
|
+
),
|
|
1686
|
+
)
|
|
1687
|
+
|
|
1688
|
+
// ── Criterion 3: Major GC frequency ──
|
|
1689
|
+
var _gI = _alertPref('gcThresholdInfo')
|
|
1690
|
+
var _gW = _alertPref('gcThresholdWarning')
|
|
1691
|
+
var _gE = _alertPref('gcThresholdError')
|
|
1692
|
+
if (!(_gI > 0 && _gW > _gI && _gE > _gW)) { _gI = 2; _gW = 5; _gE = 10 }
|
|
1693
|
+
|
|
1694
|
+
// Helper to fill all GC alert template placeholders with sample values for preview.
|
|
1695
|
+
var _gcPreviewMsg = function (gcVal) {
|
|
1696
|
+
return t('alertHostGcMsg')
|
|
1697
|
+
.replace('{v}', gcVal + '')
|
|
1698
|
+
.replace('{pause}', '…')
|
|
1699
|
+
.replace('{minor}', '…')
|
|
1700
|
+
.replace('{rss}', '…')
|
|
1701
|
+
.replace('{heap}', '…')
|
|
1702
|
+
.replace('{heapTotal}', '…')
|
|
1703
|
+
.replace('{pct}', '…')
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
alertRows.push(
|
|
1707
|
+
h('div', { key: 'gc', style: S.alertPreviewGroup },
|
|
1708
|
+
h('span', { style: S.alertPreviewGroupLabel }, t('alertPreviewCriteriaGc')),
|
|
1709
|
+
h('div', { style: S.alertPreviewRow },
|
|
1710
|
+
h('span', { style: S.alertPreviewIcon }, '🔵'),
|
|
1711
|
+
h('span', { style: S.alertPreviewMsg }, t('alertHostGcInfo') + ': ' + _gcPreviewMsg(_gI)),
|
|
1712
|
+
h('span', { style: S.alertPreviewCond }, '≥ ' + _gI + '/min'),
|
|
1713
|
+
),
|
|
1714
|
+
h('div', { style: S.alertPreviewRow },
|
|
1715
|
+
h('span', { style: S.alertPreviewIcon }, '🟡'),
|
|
1716
|
+
h('span', { style: S.alertPreviewMsg }, t('alertHostGcWarning') + ': ' + _gcPreviewMsg(_gW)),
|
|
1717
|
+
h('span', { style: S.alertPreviewCond }, '≥ ' + _gW + '/min'),
|
|
1718
|
+
),
|
|
1719
|
+
h('div', { style: S.alertPreviewRow },
|
|
1720
|
+
h('span', { style: S.alertPreviewIcon }, '🟠'),
|
|
1721
|
+
h('span', { style: S.alertPreviewMsg }, t('alertHostGcError') + ': ' + _gcPreviewMsg(_gE)),
|
|
1722
|
+
h('span', { style: S.alertPreviewCond }, '≥ ' + _gE + '/min'),
|
|
1723
|
+
),
|
|
1724
|
+
),
|
|
1725
|
+
)
|
|
1726
|
+
|
|
1727
|
+
var alertPreviewEl = h('div', { key: 'alert-preview' },
|
|
1728
|
+
h('div', { style: S.monitorModalDivider }),
|
|
1729
|
+
h('button', {
|
|
1730
|
+
type: 'button',
|
|
1731
|
+
style: S.alertPreviewToggle,
|
|
1732
|
+
onClick: function () { setShowPreview(function (v) { return !v }) },
|
|
1733
|
+
},
|
|
1734
|
+
(showPreview ? '▾ ' : '▸ ') + t('alertPreviewToggle'),
|
|
1735
|
+
),
|
|
1736
|
+
showPreview ? h('div', { style: Object.assign({}, S.alertPreviewCard, { marginTop: '6px' }) },
|
|
1737
|
+
alertRows,
|
|
1738
|
+
) : null,
|
|
1739
|
+
)
|
|
1740
|
+
|
|
1741
|
+
// ── Live memory data section ──────────────────────────────────────
|
|
1742
|
+
void hostMem // force React to re-render when host data arrives
|
|
1743
|
+
void trendData // force React to re-render when trend data arrives
|
|
1744
|
+
void trendSamples
|
|
1745
|
+
void gcEvents
|
|
1746
|
+
void gcHighlightIdx
|
|
1747
|
+
|
|
1748
|
+
var memLiveDataEl = null
|
|
1749
|
+
// Threshold values in MB for RSS bar colour.
|
|
1750
|
+
var tInfo = _alertPref('memThresholdInfo')
|
|
1751
|
+
var tWarn = _alertPref('memThresholdWarning')
|
|
1752
|
+
var tErr = _alertPref('memThresholdError')
|
|
1753
|
+
var tCrit = _alertPref('memThresholdCritical')
|
|
1754
|
+
if (!(tInfo > 0 && tWarn > tInfo && tErr > tWarn && tCrit > tErr)) { tInfo = 256; tWarn = 512; tErr = 1024; tCrit = 1536 }
|
|
1755
|
+
var rssBarBaseline = Math.max(tCrit, 2048)
|
|
1756
|
+
|
|
1757
|
+
// ── Host-side Node.js process ──────────────────────────────────
|
|
1758
|
+
var hostSectionEl = null
|
|
1759
|
+
if (hostMem) {
|
|
1760
|
+
var rssMB = (hostMem.rss / 1048576).toFixed(1)
|
|
1761
|
+
var rssMBNum = hostMem.rss / 1048576
|
|
1762
|
+
var heapTotalMB = (hostMem.heapTotal / 1048576).toFixed(1)
|
|
1763
|
+
var heapUsedMB = (hostMem.heapUsed / 1048576).toFixed(1)
|
|
1764
|
+
var externalMB = (hostMem.external / 1048576).toFixed(1)
|
|
1765
|
+
var arrayBufMB = (hostMem.arrayBuffers / 1048576).toFixed(1)
|
|
1766
|
+
var heapPct = hostMem.heapTotal > 0 ? Math.round(hostMem.heapUsed / hostMem.heapTotal * 100) : 0
|
|
1767
|
+
var hBarColor = 'var(--dsw-alias-brand-primary, #58a6ff)'
|
|
1768
|
+
if (rssMBNum >= tCrit) hBarColor = '#f85149'
|
|
1769
|
+
else if (rssMBNum >= tErr) hBarColor = '#f0883e'
|
|
1770
|
+
else if (rssMBNum >= tWarn) hBarColor = '#d29922'
|
|
1771
|
+
else if (rssMBNum >= tInfo) hBarColor = '#58a6ff'
|
|
1772
|
+
var rssBarPct = Math.min(Math.round(rssMBNum / rssBarBaseline * 100), 100)
|
|
1773
|
+
hostSectionEl = h('div', { style: S.memColumnCard },
|
|
1774
|
+
h('span', { style: S.memSubSectionLabel }, t('memHostSection')),
|
|
1775
|
+
h('div', { style: S.monitorConfigTwoCol },
|
|
1776
|
+
// ── Left: Process-level memory ──
|
|
1777
|
+
h('div', null,
|
|
1778
|
+
h('div', { style: S.memLiveDataGrid },
|
|
1779
|
+
h('span', { style: S.memLiveDataLabel }, t('memHostRSS')),
|
|
1780
|
+
h('span', { style: S.memLiveDataValue }, rssMB + ' MB'),
|
|
1781
|
+
h('span', { style: { gridColumn: '1 / -1' } },
|
|
1782
|
+
h('div', { style: S.memUsageBarTrack },
|
|
1783
|
+
h('div', {
|
|
1784
|
+
style: Object.assign({}, S.memUsageBarFill, {
|
|
1785
|
+
width: rssBarPct + '%',
|
|
1786
|
+
background: hBarColor,
|
|
1787
|
+
}),
|
|
1788
|
+
})
|
|
1789
|
+
)
|
|
1790
|
+
),
|
|
1791
|
+
h('span', { style: S.memLiveDataLabel }, t('memHostExternal')),
|
|
1792
|
+
h('span', { style: S.memLiveDataValue }, externalMB + ' MB'),
|
|
1793
|
+
h('span', { style: S.memLiveDataLabel }, t('memHostArrayBuffers')),
|
|
1794
|
+
h('span', { style: S.memLiveDataValue }, arrayBufMB + ' MB'),
|
|
1795
|
+
),
|
|
1796
|
+
),
|
|
1797
|
+
// ── Right: Heap memory ──
|
|
1798
|
+
h('div', null,
|
|
1799
|
+
h('div', { style: S.memLiveDataGrid },
|
|
1800
|
+
h('span', { style: S.memLiveDataLabel }, t('memHostHeapUsed')),
|
|
1801
|
+
h('span', { style: S.memLiveDataValue }, heapUsedMB + ' MB'),
|
|
1802
|
+
h('span', { style: S.memLiveDataLabel }, t('memHostHeapTotal')),
|
|
1803
|
+
h('span', { style: S.memLiveDataValue }, heapTotalMB + ' MB'),
|
|
1804
|
+
h('span', { style: S.memLiveDataLabel }, t('memHostHeapRatio')),
|
|
1805
|
+
h('span', { style: S.memLiveDataValue }, heapPct + '%'),
|
|
1806
|
+
h('span', { style: { gridColumn: '1 / -1' } },
|
|
1807
|
+
h('div', { style: S.memUsageBarTrack },
|
|
1808
|
+
h('div', {
|
|
1809
|
+
style: Object.assign({}, S.memUsageBarFill, {
|
|
1810
|
+
width: heapPct + '%',
|
|
1811
|
+
background: heapPct >= 95 ? '#f85149' : heapPct >= 85 ? '#f0883e' : heapPct >= 70 ? '#d29922' : 'var(--dsw-alias-brand-primary, #58a6ff)',
|
|
1812
|
+
}),
|
|
1813
|
+
})
|
|
1814
|
+
)
|
|
1815
|
+
),
|
|
1816
|
+
),
|
|
1817
|
+
),
|
|
1818
|
+
),
|
|
1819
|
+
)
|
|
1820
|
+
} else {
|
|
1821
|
+
hostSectionEl = h('div', { style: S.memColumnCard },
|
|
1822
|
+
h('span', { style: S.memSubSectionLabel }, t('memHostSection')),
|
|
1823
|
+
h('span', { style: S.memLiveDataLabel }, t('memHostUnavailable')),
|
|
1824
|
+
)
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
// ── Memory trend section ───────────────────────────────────────
|
|
1828
|
+
var trendSectionEl = null
|
|
1829
|
+
if (trendData && trendData.current) {
|
|
1830
|
+
var cur = trendData.current
|
|
1831
|
+
|
|
1832
|
+
// Trend direction
|
|
1833
|
+
var dirKey = trendData.trend === 'up' ? 'memTrendUp' : trendData.trend === 'down' ? 'memTrendDown' : 'memTrendStable'
|
|
1834
|
+
var dirIcon = trendData.trend === 'up' ? '↗' : trendData.trend === 'down' ? '↘' : '→'
|
|
1835
|
+
var dirColor = trendData.trend === 'up' ? '#f85149' : trendData.trend === 'down' ? '#3fb950' : 'var(--dsw-alias-label-secondary, #8b949e)'
|
|
1836
|
+
|
|
1837
|
+
// Peak (RSS)
|
|
1838
|
+
var peakEl = null
|
|
1839
|
+
if (trendData.peak) {
|
|
1840
|
+
var peakMB = (trendData.peak.rss / 1048576).toFixed(1)
|
|
1841
|
+
peakEl = h('span', { style: S.memLiveDataValue }, peakMB + ' MB')
|
|
1842
|
+
} else {
|
|
1843
|
+
peakEl = h('span', { style: S.memLiveDataValue }, '—')
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
// Trend summary stats
|
|
1847
|
+
var trendStats = []
|
|
1848
|
+
trendStats.push(
|
|
1849
|
+
h('span', { style: S.memLiveDataLabel }, t('memTrendDirection')),
|
|
1850
|
+
h('span', { style: Object.assign({}, S.memLiveDataValue, { color: dirColor }) }, dirIcon + ' ' + t(dirKey)),
|
|
1851
|
+
)
|
|
1852
|
+
trendStats.push(
|
|
1853
|
+
h('span', { style: S.memLiveDataLabel }, t('memTrendPeak')),
|
|
1854
|
+
peakEl,
|
|
1855
|
+
)
|
|
1856
|
+
if (trendData.sampleCount) {
|
|
1857
|
+
trendStats.push(
|
|
1858
|
+
h('span', { style: S.memLiveDataLabel }, t('memTrendSamples')),
|
|
1859
|
+
h('span', { style: S.memLiveDataValue }, '' + trendData.sampleCount),
|
|
1860
|
+
)
|
|
1861
|
+
}
|
|
1862
|
+
if (trendData.spanMs) {
|
|
1863
|
+
var spanMin = Math.round(trendData.spanMs / 60000)
|
|
1864
|
+
trendStats.push(
|
|
1865
|
+
h('span', { style: S.memLiveDataLabel }, t('memTrendSpan')),
|
|
1866
|
+
h('span', { style: S.memLiveDataValue }, spanMin + ' min'),
|
|
1867
|
+
)
|
|
1868
|
+
}
|
|
1869
|
+
if (typeof cur.heapRatio === 'number') {
|
|
1870
|
+
trendStats.push(
|
|
1871
|
+
h('span', { style: S.memLiveDataLabel }, t('memTrendHeapRatio')),
|
|
1872
|
+
h('span', { style: S.memLiveDataValue }, (cur.heapRatio * 100).toFixed(0) + '%'),
|
|
1873
|
+
)
|
|
1874
|
+
}
|
|
1875
|
+
if (trendData.rssSlopePerMin) {
|
|
1876
|
+
trendStats.push(
|
|
1877
|
+
h('span', { style: S.memLiveDataLabel }, t('memTrendGrowthRate')),
|
|
1878
|
+
h('span', { style: S.memLiveDataValue }, (trendData.rssSlopePerMin > 0 ? '+' : '') + trendData.rssSlopePerMin.toFixed(2) + ' MB/min'),
|
|
1879
|
+
)
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
// GC summary
|
|
1883
|
+
if (trendData.gc) {
|
|
1884
|
+
trendStats.push(
|
|
1885
|
+
h('span', { style: S.memLiveDataLabel }, t('memGcMajorPerMin')),
|
|
1886
|
+
h('span', { style: S.memLiveDataValue }, trendData.gc.majorPerMin.toFixed(2)),
|
|
1887
|
+
)
|
|
1888
|
+
trendStats.push(
|
|
1889
|
+
h('span', { style: S.memLiveDataLabel }, t('memGcMinorCount')),
|
|
1890
|
+
h('span', { style: S.memLiveDataValue }, '' + trendData.gc.minorCount),
|
|
1891
|
+
)
|
|
1892
|
+
trendStats.push(
|
|
1893
|
+
h('span', { style: S.memLiveDataLabel }, t('memGcMajorCount')),
|
|
1894
|
+
h('span', { style: S.memLiveDataValue }, '' + trendData.gc.majorCount),
|
|
1895
|
+
)
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
// Sparkline
|
|
1899
|
+
var sparklineEl = null
|
|
1900
|
+
if (trendSamples && trendSamples.length > 1) {
|
|
1901
|
+
var svgW = 280
|
|
1902
|
+
var svgH = 80
|
|
1903
|
+
var padY = 4
|
|
1904
|
+
var rssValues = trendSamples.map(function (s) { return s.rss })
|
|
1905
|
+
var maxRss = Math.max.apply(null, rssValues)
|
|
1906
|
+
// Shared Y-axis: min fixed at 0, max = RSS peak — both lines on the same absolute scale.
|
|
1907
|
+
var yAxisMax = maxRss
|
|
1908
|
+
var yAxisRange = yAxisMax || 1
|
|
1909
|
+
var xStep = svgW / (trendSamples.length - 1)
|
|
1910
|
+
|
|
1911
|
+
// Build polyline points for RSS (0-based scale)
|
|
1912
|
+
var points = []
|
|
1913
|
+
for (var i = 0; i < trendSamples.length; i++) {
|
|
1914
|
+
var x = i * xStep
|
|
1915
|
+
var y = svgH - padY - (rssValues[i] / yAxisRange) * (svgH - 2 * padY)
|
|
1916
|
+
points.push(x.toFixed(1) + ',' + y.toFixed(1))
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1919
|
+
// Heap line (same 0-based scale)
|
|
1920
|
+
var heapPoints = null
|
|
1921
|
+
var heapValues = trendSamples.map(function (s) { return s.heapUsed })
|
|
1922
|
+
if (Math.max.apply(null, heapValues) > 0) {
|
|
1923
|
+
heapPoints = []
|
|
1924
|
+
for (var i = 0; i < trendSamples.length; i++) {
|
|
1925
|
+
var x = i * xStep
|
|
1926
|
+
var y = svgH - padY - (heapValues[i] / yAxisRange) * (svgH - 2 * padY)
|
|
1927
|
+
heapPoints.push(x.toFixed(1) + ',' + y.toFixed(1))
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
// GC event markers
|
|
1932
|
+
// Only Full GC events (non-scavenge, i.e. kind bit 1 unset) are drawn
|
|
1933
|
+
// on the trend chart, scoped to the same visible window as
|
|
1934
|
+
// `trendSamples` (majors outside [tsFirst, tsLast] are skipped so they
|
|
1935
|
+
// don't map off-viewBox), and the x is clamped so an in-window Full GC
|
|
1936
|
+
// is always drawn. Each marker's identity is its original index in the
|
|
1937
|
+
// `gcEvents` payload so clicking a Full GC row in the list (which sets
|
|
1938
|
+
// `gcHighlightIdx` to that same index) can make its marker stand out.
|
|
1939
|
+
var gcMarkers = null
|
|
1940
|
+
var gcMarkerPositions = [] // [{geIdx, rx, ts}] for click hit-testing in overlay
|
|
1941
|
+
if (gcEvents && gcEvents.length > 0) {
|
|
1942
|
+
var tsFirst = trendSamples[0].ts
|
|
1943
|
+
var tsLast = trendSamples[trendSamples.length - 1].ts
|
|
1944
|
+
var tsRange = tsLast - tsFirst || 1
|
|
1945
|
+
gcMarkers = gcEvents.map(function (ge, geIdx) {
|
|
1946
|
+
if ((ge.kind & 1) !== 0) return null // not a Full GC
|
|
1947
|
+
if (ge.ts < tsFirst || ge.ts > tsLast) return null // outside window
|
|
1948
|
+
var x = Math.max(0, Math.min(((ge.ts - tsFirst) / tsRange) * svgW, svgW))
|
|
1949
|
+
var baseWidth = Math.max(1.5, svgW / (trendSamples.length - 1) / 4)
|
|
1950
|
+
// Store position for overlay click hit-testing.
|
|
1951
|
+
gcMarkerPositions.push({ geIdx: geIdx, rx: x / svgW, ts: ge.ts })
|
|
1952
|
+
// Selected (or none selected) → prominent; otherwise dimmed so the
|
|
1953
|
+
// current Full GC visually pops against the rest.
|
|
1954
|
+
var isSel = gcHighlightIdx === geIdx
|
|
1955
|
+
var width = isSel ? baseWidth * 1.4 : baseWidth * 0.6
|
|
1956
|
+
return h('g', { key: 'gc-' + geIdx },
|
|
1957
|
+
h('rect', {
|
|
1958
|
+
x: x - width / 2, y: 0, width: width, height: svgH,
|
|
1959
|
+
fill: isSel ? 'rgba(218, 54, 51, 0.25)' : 'rgba(240, 136, 62, 0.07)',
|
|
1960
|
+
}),
|
|
1961
|
+
h('line', {
|
|
1962
|
+
x1: x, y1: 0, x2: x, y2: svgH,
|
|
1963
|
+
stroke: isSel ? '#da3633' : 'rgba(240, 136, 62, 0.30)',
|
|
1964
|
+
strokeWidth: isSel ? 1.5 : 1,
|
|
1965
|
+
}),
|
|
1966
|
+
)
|
|
1967
|
+
})
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
// Hover line + tooltip
|
|
1971
|
+
var hoverLineEl = null
|
|
1972
|
+
var hoverTooltipEl = null
|
|
1973
|
+
if (hoverIdx !== null && hoverIdx >= 0 && hoverIdx < trendSamples.length) {
|
|
1974
|
+
var hx = hoverIdx * xStep
|
|
1975
|
+
hoverLineEl = h('line', {
|
|
1976
|
+
x1: hx, y1: 0, x2: hx, y2: svgH,
|
|
1977
|
+
stroke: 'rgba(255,255,255,0.2)', strokeWidth: 1,
|
|
1978
|
+
})
|
|
1979
|
+
var hSample = trendSamples[hoverIdx]
|
|
1980
|
+
var hRssMB = (hSample.rss / 1048576).toFixed(1)
|
|
1981
|
+
var hHeapMB = hSample.heapUsed ? (hSample.heapUsed / 1048576).toFixed(1) : null
|
|
1982
|
+
var hTime = new Date(hSample.ts)
|
|
1983
|
+
var hTimeStr = hTime.getHours().toString().padStart(2, '0') + ':' +
|
|
1984
|
+
hTime.getMinutes().toString().padStart(2, '0') + ':' +
|
|
1985
|
+
hTime.getSeconds().toString().padStart(2, '0')
|
|
1986
|
+
hoverTooltipEl = h('div', {
|
|
1987
|
+
style: Object.assign({}, S.sparklineTooltip, {
|
|
1988
|
+
left: Math.min(hx, svgW - 80) + 'px',
|
|
1989
|
+
}),
|
|
1990
|
+
},
|
|
1991
|
+
t('memTrendHoverRSS') + ': ' + hRssMB + ' MB',
|
|
1992
|
+
hHeapMB ? ' ' + t('memTrendHoverHeap') + ': ' + hHeapMB + ' MB' : '',
|
|
1993
|
+
' ' + t('memTrendHoverTime') + ': ' + hTimeStr,
|
|
1994
|
+
)
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1997
|
+
// ---- Coordinate axes (HTML overlays; the SVG viewBox uses
|
|
1998
|
+
// preserveAspectRatio:none so SVG <text> would stretch illegibly) ----
|
|
1999
|
+
var _fmtHM = function (ts) {
|
|
2000
|
+
var d = new Date(ts)
|
|
2001
|
+
return d.getHours().toString().padStart(2, '0') + ':' +
|
|
2002
|
+
d.getMinutes().toString().padStart(2, '0')
|
|
2003
|
+
}
|
|
2004
|
+
var _fmtMB = function (v) { return Math.round(v / 1048576) }
|
|
2005
|
+
|
|
2006
|
+
// X axis: first / middle / last sample times (HH:MM).
|
|
2007
|
+
var tMid = trendSamples[Math.floor((trendSamples.length - 1) / 2)].ts
|
|
2008
|
+
var tLast = trendSamples[trendSamples.length - 1].ts
|
|
2009
|
+
var xAxisEl = h('div', { style: S.trendAxisX },
|
|
2010
|
+
h('span', null, _fmtHM(trendSamples[0].ts)),
|
|
2011
|
+
h('span', null, _fmtHM(tMid)),
|
|
2012
|
+
h('span', null, _fmtHM(tLast)),
|
|
2013
|
+
)
|
|
2014
|
+
|
|
2015
|
+
// Y axis: 0 / mid / max (0-based scale, both lines share it).
|
|
2016
|
+
var yAxisEl = h('div', {
|
|
2017
|
+
style: Object.assign({}, S.trendAxisY, { height: svgH + 'px' }),
|
|
2018
|
+
},
|
|
2019
|
+
h('span', null, _fmtMB(yAxisMax) + 'M'),
|
|
2020
|
+
h('span', null, _fmtMB(yAxisMax / 2) + 'M'),
|
|
2021
|
+
h('span', null, '0'),
|
|
2022
|
+
)
|
|
2023
|
+
|
|
2024
|
+
sparklineEl = h('div', { style: S.trendAxisWrap },
|
|
2025
|
+
yAxisEl,
|
|
2026
|
+
h('div', { style: S.trendChartWrap },
|
|
2027
|
+
h('div', { style: S.sparklineWrap },
|
|
2028
|
+
h('svg', {
|
|
2029
|
+
viewBox: '0 0 ' + svgW + ' ' + svgH,
|
|
2030
|
+
preserveAspectRatio: 'none',
|
|
2031
|
+
style: S.sparklineSvg,
|
|
2032
|
+
},
|
|
2033
|
+
heapPoints ? h('polyline', {
|
|
2034
|
+
points: heapPoints.join(' '),
|
|
2035
|
+
fill: 'none',
|
|
2036
|
+
stroke: 'rgba(88, 166, 255, 0.3)',
|
|
2037
|
+
strokeWidth: 1,
|
|
2038
|
+
}) : null,
|
|
2039
|
+
h('polyline', {
|
|
2040
|
+
points: points.join(' '),
|
|
2041
|
+
fill: 'none',
|
|
2042
|
+
stroke: '#3fb950',
|
|
2043
|
+
strokeWidth: 1.2,
|
|
2044
|
+
}),
|
|
2045
|
+
gcMarkers,
|
|
2046
|
+
hoverLineEl,
|
|
2047
|
+
),
|
|
2048
|
+
hoverTooltipEl,
|
|
2049
|
+
// Mouse interaction overlay
|
|
2050
|
+
h('div', {
|
|
2051
|
+
style: { position: 'absolute', inset: 0, cursor: 'crosshair' },
|
|
2052
|
+
onMouseMove: function (e) {
|
|
2053
|
+
var rect = e.currentTarget.getBoundingClientRect()
|
|
2054
|
+
var rx = (e.clientX - rect.left) / rect.width
|
|
2055
|
+
var idx = Math.round(rx * (trendSamples.length - 1))
|
|
2056
|
+
setHoverIdx(Math.max(0, Math.min(idx, trendSamples.length - 1)))
|
|
2057
|
+
// Switch cursor to pointer when hovering near a GC marker.
|
|
2058
|
+
var nearGc = false
|
|
2059
|
+
var mousePx = e.clientX - rect.left
|
|
2060
|
+
for (var gi = 0; gi < gcMarkerPositions.length; gi++) {
|
|
2061
|
+
if (Math.abs(gcMarkerPositions[gi].rx * rect.width - mousePx) <= 5) {
|
|
2062
|
+
nearGc = true
|
|
2063
|
+
break
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
e.currentTarget.style.cursor = nearGc ? 'pointer' : 'crosshair'
|
|
2067
|
+
},
|
|
2068
|
+
onMouseLeave: function (e) {
|
|
2069
|
+
setHoverIdx(null)
|
|
2070
|
+
e.currentTarget.style.cursor = 'crosshair'
|
|
2071
|
+
},
|
|
2072
|
+
onClick: function (e) {
|
|
2073
|
+
// Check if click is near a Full GC marker.
|
|
2074
|
+
// Use pixel distance: only match if within ±5px of a marker.
|
|
2075
|
+
var rect = e.currentTarget.getBoundingClientRect()
|
|
2076
|
+
var clickPx = e.clientX - rect.left
|
|
2077
|
+
var pxPerUnit = rect.width / svgW
|
|
2078
|
+
var hitPx = 5
|
|
2079
|
+
var hitGc = null
|
|
2080
|
+
var hitDist = Infinity
|
|
2081
|
+
for (var gi = 0; gi < gcMarkerPositions.length; gi++) {
|
|
2082
|
+
var markerPx = gcMarkerPositions[gi].rx * rect.width
|
|
2083
|
+
var d = Math.abs(clickPx - markerPx)
|
|
2084
|
+
if (d <= hitPx && d < hitDist) {
|
|
2085
|
+
hitGc = gcMarkerPositions[gi]
|
|
2086
|
+
hitDist = d
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
if (hitGc) {
|
|
2090
|
+
// Toggle: clicking the same marker again deselects.
|
|
2091
|
+
setGcHighlightIdx(gcHighlightIdx === hitGc.geIdx ? null : hitGc.geIdx)
|
|
2092
|
+
// Move hover to the nearest sample.
|
|
2093
|
+
var bestIdx = 0
|
|
2094
|
+
var bestDist2 = Math.abs(trendSamples[0].ts - hitGc.ts)
|
|
2095
|
+
for (var si2 = 1; si2 < trendSamples.length; si2++) {
|
|
2096
|
+
var dist2 = Math.abs(trendSamples[si2].ts - hitGc.ts)
|
|
2097
|
+
if (dist2 < bestDist2) { bestDist2 = dist2; bestIdx = si2 }
|
|
2098
|
+
}
|
|
2099
|
+
setHoverIdx(bestIdx)
|
|
2100
|
+
} else {
|
|
2101
|
+
// Clicked empty area → clear GC highlight.
|
|
2102
|
+
setGcHighlightIdx(null)
|
|
2103
|
+
}
|
|
2104
|
+
},
|
|
2105
|
+
}),
|
|
2106
|
+
),
|
|
2107
|
+
xAxisEl,
|
|
2108
|
+
),
|
|
2109
|
+
)
|
|
2110
|
+
}
|
|
2111
|
+
|
|
2112
|
+
// GC event list
|
|
2113
|
+
var gcEventListEl = (function () {
|
|
2114
|
+
if (!trendSamples || trendSamples.length === 0) {
|
|
2115
|
+
return h('div', null,
|
|
2116
|
+
h('span', { style: S.monitorConfigGroupTitle }, t('memGcEventList')),
|
|
2117
|
+
h('div', { style: S.gcEventEmpty }, t('memGcNoEvents')),
|
|
2118
|
+
)
|
|
2119
|
+
}
|
|
2120
|
+
var _dropThresh = 10 * 1048576 // 10 MB
|
|
2121
|
+
var dropEvents = []
|
|
2122
|
+
// A) RSS drops from samples
|
|
2123
|
+
for (var si = 1; si < trendSamples.length; si++) {
|
|
2124
|
+
var rssDrop = trendSamples[si - 1].rss - trendSamples[si].rss
|
|
2125
|
+
if (rssDrop >= _dropThresh) {
|
|
2126
|
+
dropEvents.push({
|
|
2127
|
+
idx: dropEvents.length,
|
|
2128
|
+
ts: trendSamples[si].ts,
|
|
2129
|
+
rssDropMB: (rssDrop / 1048576).toFixed(1),
|
|
2130
|
+
source: 'sample',
|
|
2131
|
+
sampleIdx: si,
|
|
2132
|
+
})
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
// B) gcEvents (kind=2, already filtered server-side)
|
|
2136
|
+
if (gcEvents && gcEvents.length > 0) {
|
|
2137
|
+
var tsFirst = trendSamples[0].ts
|
|
2138
|
+
var tsLast = trendSamples[trendSamples.length - 1].ts
|
|
2139
|
+
// Track which sample RSS-drop segments have already been attributed to a
|
|
2140
|
+
// Full GC event. Without this, two Full GCs that fire around the same
|
|
2141
|
+
// memory release can both grab the same -XX MB drop and double count it.
|
|
2142
|
+
var sampleDropClaimed = {}
|
|
2143
|
+
for (var ei = 0; ei < gcEvents.length; ei++) {
|
|
2144
|
+
var ge = gcEvents[ei]
|
|
2145
|
+
if (ge.ts < tsFirst || ge.ts > tsLast) continue
|
|
2146
|
+
// Try to find the RSS drop around this GC event from samples.
|
|
2147
|
+
var rssDropMB = null
|
|
2148
|
+
var merged = false
|
|
2149
|
+
// First: check if it merges with an existing sample-sourced RSS drop event (within 5s).
|
|
2150
|
+
for (var di = 0; di < dropEvents.length; di++) {
|
|
2151
|
+
var dropItem = dropEvents[di]
|
|
2152
|
+
if (dropItem.source === 'sample' && Math.abs(dropItem.ts - ge.ts) < 5000) {
|
|
2153
|
+
if (!sampleDropClaimed[dropItem.sampleIdx]) {
|
|
2154
|
+
sampleDropClaimed[dropItem.sampleIdx] = true
|
|
2155
|
+
rssDropMB = dropItem.rssDropMB
|
|
2156
|
+
dropItem.source = 'gc'
|
|
2157
|
+
dropItem.duration = ge.duration
|
|
2158
|
+
dropItem.gcIdx = ei
|
|
2159
|
+
merged = true
|
|
2160
|
+
}
|
|
2161
|
+
// If this drop was already claimed by an earlier GC, don't
|
|
2162
|
+
// attribute it again — fall through and add this GC as its own
|
|
2163
|
+
// row without reusing the same memory-release number.
|
|
2164
|
+
break
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
// Second: if no existing RSS drop merged, scan nearby samples for a
|
|
2168
|
+
// drop, skipping segments already claimed by an earlier event.
|
|
2169
|
+
if (!merged) {
|
|
2170
|
+
for (var si2 = 1; si2 < trendSamples.length; si2++) {
|
|
2171
|
+
var timeDiff = Math.abs(trendSamples[si2].ts - ge.ts)
|
|
2172
|
+
if (timeDiff > 10000) continue // only look within ±10s
|
|
2173
|
+
if (sampleDropClaimed[si2]) continue // drop already attributed elsewhere
|
|
2174
|
+
var nearDrop = trendSamples[si2 - 1].rss - trendSamples[si2].rss
|
|
2175
|
+
if (nearDrop > 0) {
|
|
2176
|
+
sampleDropClaimed[si2] = true
|
|
2177
|
+
rssDropMB = (nearDrop / 1048576).toFixed(1)
|
|
2178
|
+
break
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
dropEvents.push({
|
|
2182
|
+
idx: dropEvents.length,
|
|
2183
|
+
ts: ge.ts,
|
|
2184
|
+
rssDropMB: rssDropMB,
|
|
2185
|
+
source: 'gc',
|
|
2186
|
+
gcIdx: ei,
|
|
2187
|
+
duration: ge.duration,
|
|
2188
|
+
})
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
// Merge both sources back into strict chronological order. A Full GC event
|
|
2193
|
+
// found outside any sample's RSS-drop window is appended after the sample
|
|
2194
|
+
// events, which may leave the combined list out of order.
|
|
2195
|
+
dropEvents.sort(function (a, b) { return a.ts - b.ts })
|
|
2196
|
+
if (dropEvents.length === 0 && (!trendData.gc || trendData.gc.majorCount === 0)) {
|
|
2197
|
+
return h('div', null,
|
|
2198
|
+
h('span', { style: S.monitorConfigGroupTitle }, t('memGcEventList')),
|
|
2199
|
+
h('div', { style: S.gcEventEmpty }, t('memGcNoEvents')),
|
|
2200
|
+
)
|
|
2201
|
+
}
|
|
2202
|
+
var listContent = null
|
|
2203
|
+
if (dropEvents.length > 0) {
|
|
2204
|
+
listContent = h('div', { style: S.gcEventListWrap },
|
|
2205
|
+
dropEvents.map(function (item) {
|
|
2206
|
+
var evIdx = item.idx
|
|
2207
|
+
var evTs = item.ts
|
|
2208
|
+
var evTime = new Date(evTs)
|
|
2209
|
+
var evTimeStr = evTime.getHours().toString().padStart(2, '0') + ':' +
|
|
2210
|
+
evTime.getMinutes().toString().padStart(2, '0') + ':' +
|
|
2211
|
+
evTime.getSeconds().toString().padStart(2, '0')
|
|
2212
|
+
var isGcSource = item.source === 'gc'
|
|
2213
|
+
// Highlight identity must match the chart marker key, which is
|
|
2214
|
+
// the original index in the `gcEvents` payload. GC-sourced rows
|
|
2215
|
+
// carry that index in `item.gcIdx`; RSS-drop rows have no chart
|
|
2216
|
+
// marker, so fall back to the row index.
|
|
2217
|
+
var evKey = isGcSource ? item.gcIdx : evIdx
|
|
2218
|
+
var isHighlighted = gcHighlightIdx === evKey
|
|
2219
|
+
// Build detail string: show both duration and memory released when available.
|
|
2220
|
+
var detailParts = []
|
|
2221
|
+
if (isGcSource && item.duration) {
|
|
2222
|
+
detailParts.push(item.duration.toFixed(1) + ' ms')
|
|
2223
|
+
}
|
|
2224
|
+
if (item.rssDropMB && item.rssDropMB !== '—') {
|
|
2225
|
+
detailParts.push('-' + item.rssDropMB + ' MB')
|
|
2226
|
+
}
|
|
2227
|
+
var detailStr = detailParts.length > 0 ? detailParts.join(' | ') : '—'
|
|
2228
|
+
var kindStr = isGcSource ? 'Full GC' : t('memGcEventRssDrop')
|
|
2229
|
+
return h('div', {
|
|
2230
|
+
key: 'gc-ev-' + evIdx,
|
|
2231
|
+
ref: isHighlighted ? function (el) {
|
|
2232
|
+
if (el) el.scrollIntoView({ block: 'nearest', behavior: 'smooth' })
|
|
2233
|
+
} : undefined,
|
|
2234
|
+
style: Object.assign({}, S.gcEventRow, isHighlighted ? S.gcEventRowHighlight : {}),
|
|
2235
|
+
onClick: function () {
|
|
2236
|
+
if (!trendSamples || trendSamples.length === 0) return
|
|
2237
|
+
var bestIdx = 0
|
|
2238
|
+
var bestDist = Math.abs(trendSamples[0].ts - evTs)
|
|
2239
|
+
for (var si2 = 1; si2 < trendSamples.length; si2++) {
|
|
2240
|
+
var dist = Math.abs(trendSamples[si2].ts - evTs)
|
|
2241
|
+
if (dist < bestDist) { bestDist = dist; bestIdx = si2 }
|
|
2242
|
+
}
|
|
2243
|
+
setHoverIdx(bestIdx)
|
|
2244
|
+
setGcHighlightIdx(evKey)
|
|
2245
|
+
},
|
|
2246
|
+
onMouseEnter: function (e) {
|
|
2247
|
+
e.currentTarget.style.background = 'rgba(218, 54, 51, 0.18)'
|
|
2248
|
+
},
|
|
2249
|
+
onMouseLeave: function (e) {
|
|
2250
|
+
e.currentTarget.style.background = isHighlighted ? 'rgba(218, 54, 51, 0.12)' : ''
|
|
2251
|
+
},
|
|
2252
|
+
},
|
|
2253
|
+
h('span', { style: S.gcEventTimeCol }, evTimeStr),
|
|
2254
|
+
h('span', { style: S.gcEventDurationCol }, detailStr),
|
|
2255
|
+
h('span', { style: S.gcEventKindCol }, kindStr),
|
|
2256
|
+
)
|
|
2257
|
+
})
|
|
2258
|
+
)
|
|
2259
|
+
} else {
|
|
2260
|
+
listContent = h('div', { style: S.gcEventEmpty }, t('memGcNoEvents'))
|
|
2261
|
+
}
|
|
2262
|
+
return h('div', null,
|
|
2263
|
+
h('span', { style: S.monitorConfigGroupTitle }, t('memGcEventList')),
|
|
2264
|
+
listContent,
|
|
2265
|
+
)
|
|
2266
|
+
})()
|
|
2267
|
+
|
|
2268
|
+
trendSectionEl = h('div', { style: S.memColumnCard },
|
|
2269
|
+
h('span', { style: S.memSubSectionLabel }, t('memTrend')),
|
|
2270
|
+
// Trend chart on top.
|
|
2271
|
+
sparklineEl,
|
|
2272
|
+
// Below: two columns — trend info on the left, Full GC list on the right.
|
|
2273
|
+
h('div', { style: Object.assign({}, S.monitorConfigTwoCol, { marginTop: '8px' }) },
|
|
2274
|
+
h('div', null,
|
|
2275
|
+
h('span', { style: S.monitorConfigGroupTitle }, t('memTrendInfo')),
|
|
2276
|
+
h('div', { style: S.memLiveDataGrid }, trendStats),
|
|
2277
|
+
),
|
|
2278
|
+
gcEventListEl,
|
|
2279
|
+
),
|
|
2280
|
+
)
|
|
2281
|
+
} else {
|
|
2282
|
+
trendSectionEl = h('div', { style: S.memColumnCard },
|
|
2283
|
+
h('span', { style: S.memSubSectionLabel }, t('memTrend')),
|
|
2284
|
+
h('span', { style: S.memLiveDataLabel }, t('memTrendNoData')),
|
|
2285
|
+
)
|
|
2286
|
+
}
|
|
2287
|
+
|
|
2288
|
+
var footprintSectionEl = (function () {
|
|
2289
|
+
function formatBytesSync(n) {
|
|
2290
|
+
if (!isFinite(n) || n < 0) return '—'
|
|
2291
|
+
if (n < 1024) return n + ' B'
|
|
2292
|
+
var units = ['KB', 'MB', 'GB', 'TB', 'PB']
|
|
2293
|
+
var v = n
|
|
2294
|
+
var u = -1
|
|
2295
|
+
do { v /= 1024; u++ } while (v >= 1024 && u < units.length - 1)
|
|
2296
|
+
return v.toFixed(v >= 100 ? 0 : 1) + ' ' + units[u]
|
|
2297
|
+
}
|
|
2298
|
+
var fp = footprint
|
|
2299
|
+
var totalBytes = 0
|
|
2300
|
+
var totalSessions = 0
|
|
2301
|
+
var totalArchived = 0
|
|
2302
|
+
var archivedKnown = false
|
|
2303
|
+
if (fp && Array.isArray(fp.workspaces)) {
|
|
2304
|
+
fp.workspaces.forEach(function (w) {
|
|
2305
|
+
totalSessions += Number(w.totalSessions) || 0
|
|
2306
|
+
totalArchived += Number(w.archivedSessions) || 0
|
|
2307
|
+
totalBytes += Number(w.bytes) || 0
|
|
2308
|
+
if (typeof w.archivedSessions === 'number') archivedKnown = true
|
|
2309
|
+
})
|
|
2310
|
+
}
|
|
2311
|
+
if (!fp) {
|
|
2312
|
+
return h('div', { style: Object.assign({}, S.memColumnCard, { marginTop: '8px' }) },
|
|
2313
|
+
h('span', { style: S.memSubSectionLabel }, t('footprintTitle')),
|
|
2314
|
+
h('span', { style: S.memLiveDataLabel }, t('footprintNoData')),
|
|
2315
|
+
)
|
|
2316
|
+
}
|
|
2317
|
+
return h('div', { style: Object.assign({}, S.memColumnCard, { marginTop: '8px' }) },
|
|
2318
|
+
h('span', { style: S.memSubSectionLabel }, t('footprintTitle')),
|
|
2319
|
+
h('div', { style: S.memLiveDataGrid },
|
|
2320
|
+
h('span', { style: S.memLiveDataLabel }, t('footprintTotalSessions')),
|
|
2321
|
+
h('span', { style: S.memLiveDataValue }, String(totalSessions)),
|
|
2322
|
+
h('span', { style: S.memLiveDataLabel }, t('footprintArchivedSessions')),
|
|
2323
|
+
h('span', { style: S.memLiveDataValue },
|
|
2324
|
+
archivedKnown
|
|
2325
|
+
? String(totalArchived)
|
|
2326
|
+
: '—',
|
|
2327
|
+
),
|
|
2328
|
+
h('span', { style: S.memLiveDataLabel }, t('footprintOnDisk')),
|
|
2329
|
+
h('span', { style: S.memLiveDataValue }, formatBytesSync(totalBytes)),
|
|
2330
|
+
),
|
|
2331
|
+
)
|
|
2332
|
+
})()
|
|
2333
|
+
|
|
2334
|
+
memLiveDataEl = h('div', { key: 'live-data' },
|
|
2335
|
+
h('div', { style: S.monitorModalDivider }),
|
|
2336
|
+
h('span', { style: S.monitorModalSectionTitle }, t('memLiveData')),
|
|
2337
|
+
hostSectionEl,
|
|
2338
|
+
trendSectionEl,
|
|
2339
|
+
footprintSectionEl,
|
|
2340
|
+
)
|
|
2341
|
+
|
|
2342
|
+
return h('div', {
|
|
2343
|
+
style: S.monitorModalMask,
|
|
2344
|
+
onMouseDown: function (e) { if (e.target === e.currentTarget) onClose() },
|
|
2345
|
+
},
|
|
2346
|
+
h('div', { style: S.monitorModal },
|
|
2347
|
+
h('div', { style: S.monitorModalHead },
|
|
2348
|
+
h('span', { style: S.monitorModalTitle },
|
|
2349
|
+
t(MONITOR_TITLE_KEY[monitor] || 'alertMemCluster') + ' — ' + t('monitorConfig')),
|
|
2350
|
+
h('button', {
|
|
2351
|
+
type: 'button',
|
|
2352
|
+
'data-dock-flash-focus': '',
|
|
2353
|
+
style: S.monitorModalClose,
|
|
2354
|
+
onClick: onClose,
|
|
2355
|
+
}, '✕'),
|
|
2356
|
+
),
|
|
2357
|
+
h('div', { style: S.monitorModalBody },
|
|
2358
|
+
fieldEls,
|
|
2359
|
+
alertPreviewEl,
|
|
2360
|
+
memLiveDataEl,
|
|
2361
|
+
),
|
|
2362
|
+
),
|
|
2363
|
+
)
|
|
2364
|
+
}
|
|
2365
|
+
|
|
2366
|
+
//#endregion ───────────────────────────────────────────────────────────────
|
|
2367
|
+
|
|
2368
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
2369
|
+
//#region Monitor toggle helpers ──────────────────────────────────────────
|
|
2370
|
+
|
|
2371
|
+
/** Check whether dock-flash's system-alerts master toggle is ON. */
|
|
2372
|
+
function _getAlertsOn() {
|
|
2373
|
+
try { return localStorage.getItem('dock-flash:system-alerts') !== '0' } catch (_) { return true }
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2376
|
+
var TOGGLE_KEY = 'dsh-flash-mem-mon:monitor-memory'
|
|
2377
|
+
var PROVIDER_ID = 'dsh-flash-mem-mon:host-memory-alert'
|
|
2378
|
+
|
|
2379
|
+
// The two registries this plugin registers INTO, kept so an imperative
|
|
2380
|
+
// surface can be nudged after the fact. Only the locale listener uses them.
|
|
2381
|
+
var _qcRegistry = null
|
|
2382
|
+
var _alertRegistryRef = null
|
|
2383
|
+
|
|
2384
|
+
/**
|
|
2385
|
+
* Re-render the surfaces this plugin draws IMPERATIVELY after the locale
|
|
2386
|
+
* changed.
|
|
2387
|
+
*
|
|
2388
|
+
* `t()` is a live read, so anything rendered inside a React render is
|
|
2389
|
+
* already correct on the next paint — but the Quick Control row is drawn by
|
|
2390
|
+
* dsh-flash's registry, which re-reads `label` / `subtitle` only when it is
|
|
2391
|
+
* told to. `notifyChange(id)` is that nudge, and `alerts.notify()` lets the
|
|
2392
|
+
* alert panel re-read the `title` / `message` FUNCTIONS this plugin emits
|
|
2393
|
+
* (they are functions precisely so they re-read the dictionary). Without
|
|
2394
|
+
* this, a language switch would sit half-applied — modal and rows in the
|
|
2395
|
+
* old language until the next poll — which is exactly the "language is
|
|
2396
|
+
* sticky per component" bug the official framework removes.
|
|
2397
|
+
*/
|
|
2398
|
+
function refreshLocaleSurfaces() {
|
|
2399
|
+
if (_qcRegistry && typeof _qcRegistry.notifyChange === 'function') {
|
|
2400
|
+
try { _qcRegistry.notifyChange(TOGGLE_KEY) } catch (_) {}
|
|
2401
|
+
}
|
|
2402
|
+
if (_alertRegistryRef && typeof _alertRegistryRef.notify === 'function') {
|
|
2403
|
+
try { _alertRegistryRef.notify() } catch (_) {}
|
|
2404
|
+
}
|
|
2405
|
+
// The config modal re-renders itself — it subscribes to t.onLocaleChange()
|
|
2406
|
+
// where it is mounted, because that is where its re-render lever lives.
|
|
2407
|
+
}
|
|
2408
|
+
|
|
2409
|
+
var _MONITOR_TOGGLES = {
|
|
2410
|
+
memory: {
|
|
2411
|
+
key: TOGGLE_KEY,
|
|
2412
|
+
providers: [PROVIDER_ID],
|
|
2413
|
+
},
|
|
2414
|
+
}
|
|
2415
|
+
var _MONITOR_DEFAULT = { memory: true }
|
|
2416
|
+
|
|
2417
|
+
function _getMonitorOn(monitor) {
|
|
2418
|
+
var def = _MONITOR_DEFAULT[monitor] !== false
|
|
2419
|
+
try { return localStorage.getItem(_MONITOR_TOGGLES[monitor].key) !== '0' } catch (_) { return def }
|
|
2420
|
+
}
|
|
2421
|
+
|
|
2422
|
+
function _setMonitorOn(monitor, on, alertRegistry) {
|
|
2423
|
+
var providers = _MONITOR_TOGGLES[monitor].providers
|
|
2424
|
+
for (var i = 0; i < providers.length; i++) {
|
|
2425
|
+
try { alertRegistry.setProviderEnabled(providers[i], on) } catch (_) {}
|
|
2426
|
+
}
|
|
2427
|
+
try { localStorage.setItem(_MONITOR_TOGGLES[monitor].key, on ? '1' : '0') } catch (_) {}
|
|
2428
|
+
}
|
|
2429
|
+
|
|
2430
|
+
function _monitorSubtitle(m) {
|
|
2431
|
+
return function () {
|
|
2432
|
+
return _getMonitorOn(m) ? t('monitorOn') : t('monitorOff')
|
|
2433
|
+
}
|
|
2434
|
+
}
|
|
2435
|
+
|
|
2436
|
+
//#endregion ───────────────────────────────────────────────────────────────
|
|
2437
|
+
|
|
2438
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
2439
|
+
//#region Client factory ─────────────────────────────────────────────────
|
|
2440
|
+
|
|
2441
|
+
var client = {
|
|
2442
|
+
inject: ['remote', 'remote.settings'],
|
|
2443
|
+
async apply(ctx) {
|
|
2444
|
+
// A throw anywhere inside the real apply used to REJECT this async
|
|
2445
|
+
// apply — and a failed apply FAILS the plugin's Cordis fiber, so the
|
|
2446
|
+
// plugin never activates and its switch silently never appears. That
|
|
2447
|
+
// is not hypothetical: a `settings.describe().then(...)` TypeError did
|
|
2448
|
+
// exactly this. Registration failures are REPORTED here, never fatal.
|
|
2449
|
+
try {
|
|
2450
|
+
await client._applyInner(ctx)
|
|
2451
|
+
} catch (err) {
|
|
2452
|
+
console.error('[dsh-flash-mem-mon] apply failed (non-fatal):', err)
|
|
2453
|
+
}
|
|
2454
|
+
},
|
|
2455
|
+
|
|
2456
|
+
async _applyInner(ctx) {
|
|
2457
|
+
_prefCtx = ctx
|
|
2458
|
+
|
|
2459
|
+
// 0. i18n: hand our dictionaries to DSH's official locale framework.
|
|
2460
|
+
// The `locale` service (@deepseek-ai/dsh-client-locale) is the single
|
|
2461
|
+
// authority for the active UI language. Resolved lazily — it may not
|
|
2462
|
+
// be provided yet at apply() time — and adopted for good by the
|
|
2463
|
+
// inject watcher below. The claim lives on THIS fiber so a dispose or
|
|
2464
|
+
// a disable→enable cycle gives the namespace back instead of leaving
|
|
2465
|
+
// a stale registration that `register` would then refuse.
|
|
2466
|
+
try {
|
|
2467
|
+
ctx.effect(function () {
|
|
2468
|
+
var svc
|
|
2469
|
+
try { svc = ctx.get('locale') } catch (_) { svc = undefined }
|
|
2470
|
+
bindLocaleService(svc)
|
|
2471
|
+
return releaseLocaleBinding
|
|
2472
|
+
}, 'dsh-flash-mem-mon: i18n locale dictionaries')
|
|
2473
|
+
} catch (e) {
|
|
2474
|
+
console.warn('[dsh-flash-mem-mon] could not claim the locale namespace; i18n stays on the browser-language fallback:', e)
|
|
2475
|
+
}
|
|
2476
|
+
|
|
2477
|
+
// …and adopt it if it turns up LATER. `dsh.client.inject` orders the
|
|
2478
|
+
// MODULES, not the service registrations, so a provided-late locale
|
|
2479
|
+
// plugin is a real case. Without this watcher the plugin would sit on
|
|
2480
|
+
// the browser-language fallback for the whole session and follow the
|
|
2481
|
+
// global language only by accident. A fiber waiting for a service
|
|
2482
|
+
// that never arrives is harmless — the fallback already renders every
|
|
2483
|
+
// string — and bindLocaleService is idempotent, so repeating the
|
|
2484
|
+
// eager claim above costs nothing.
|
|
2485
|
+
try {
|
|
2486
|
+
ctx.inject(['locale'], function (scope) {
|
|
2487
|
+
bindLocaleService(scope && scope.locale)
|
|
2488
|
+
})
|
|
2489
|
+
} catch (e) {
|
|
2490
|
+
console.warn('[dsh-flash-mem-mon] ctx.inject(["locale"]) failed; i18n stays on the browser-language fallback:', e)
|
|
2491
|
+
}
|
|
2492
|
+
|
|
2493
|
+
// A language switch is only half done by `t()` becoming a live read.
|
|
2494
|
+
// The Quick Control row and the alert panel are drawn by dsh-flash, so
|
|
2495
|
+
// they need the imperative nudge below.
|
|
2496
|
+
//
|
|
2497
|
+
// The trigger is the `locale/change` EVENT, not `subscribe()`. The
|
|
2498
|
+
// official runtime publishes on two channels and they are not the
|
|
2499
|
+
// same: `subscribe()` (LocaleFace) fires on EVERY revision bump —
|
|
2500
|
+
// including a dictionary registration, which happens once per plugin
|
|
2501
|
+
// at boot — while `locale/change` is emitted only when the ACTIVE
|
|
2502
|
+
// locale actually switches (client-locale's `publish(active, changed)`
|
|
2503
|
+
// passes `false` for registrations precisely so "registration-heavy
|
|
2504
|
+
// boot cannot storm event listeners"). Nudging the panel on every
|
|
2505
|
+
// registration would re-render it under a language that has not
|
|
2506
|
+
// changed. One listener, fiber-scoped, so a disable→enable cycle
|
|
2507
|
+
// cannot stack a second one on the module-level listener set.
|
|
2508
|
+
try {
|
|
2509
|
+
ctx.on('locale/change', notifyLocaleChange)
|
|
2510
|
+
} catch (e) {
|
|
2511
|
+
console.warn('[dsh-flash-mem-mon] could not listen for locale/change; i18n will not refresh the imperative surfaces:', e)
|
|
2512
|
+
}
|
|
2513
|
+
|
|
2514
|
+
// 1. Load mem-mon preferences from the 'dsh-flash-mem-mon' settings namespace
|
|
2515
|
+
loadMemPrefs(ctx).then(function (ok) {
|
|
2516
|
+
// Sync alert providers with the localStorage-backed state.
|
|
2517
|
+
var lsOn
|
|
2518
|
+
try { lsOn = localStorage.getItem(TOGGLE_KEY) !== '0' } catch (_) { lsOn = true }
|
|
2519
|
+
try {
|
|
2520
|
+
var reg = ctx.get && ctx.get('quickControl')
|
|
2521
|
+
if (reg && typeof reg.notifyChange === 'function') {
|
|
2522
|
+
reg.notifyChange(TOGGLE_KEY)
|
|
2523
|
+
}
|
|
2524
|
+
} catch (_) {}
|
|
2525
|
+
var alertReg = ctx.get && ctx.get('dockFlashAlerts')
|
|
2526
|
+
if (alertReg) {
|
|
2527
|
+
try { alertReg.setProviderEnabled(PROVIDER_ID, lsOn) } catch (_) {}
|
|
2528
|
+
}
|
|
2529
|
+
})
|
|
2530
|
+
|
|
2531
|
+
// 2. Register with dock-flash's quickControl and dockFlashAlerts services.
|
|
2532
|
+
var _registered = false
|
|
2533
|
+
|
|
2534
|
+
function _registerServices(registry, alertRegistry) {
|
|
2535
|
+
if (_registered) return
|
|
2536
|
+
if (!registry || !alertRegistry) {
|
|
2537
|
+
console.warn('[dsh-flash-mem-mon] _registerServices: missing service — quickControl=' + !!registry + ', alerts=' + !!alertRegistry)
|
|
2538
|
+
return
|
|
2539
|
+
}
|
|
2540
|
+
_registered = true
|
|
2541
|
+
_qcRegistry = registry
|
|
2542
|
+
_alertRegistryRef = alertRegistry
|
|
2543
|
+
console.log('[dsh-flash-mem-mon] registering provider and switch')
|
|
2544
|
+
|
|
2545
|
+
// 3. Register memory alert provider
|
|
2546
|
+
var memProvider = createHostMemoryAlertProvider()
|
|
2547
|
+
alertRegistry.registerProvider(memProvider)
|
|
2548
|
+
|
|
2549
|
+
// 4. Register memory monitor toggle switch
|
|
2550
|
+
ctx.effect(function () {
|
|
2551
|
+
var dispose = registry.registerSwitch({
|
|
2552
|
+
id: TOGGLE_KEY,
|
|
2553
|
+
label: L('alertMemCluster'),
|
|
2554
|
+
icon: 'chip',
|
|
2555
|
+
type: 'toggle',
|
|
2556
|
+
group: 'system',
|
|
2557
|
+
cluster: 'system-alerts',
|
|
2558
|
+
order: 58,
|
|
2559
|
+
visible: function () {
|
|
2560
|
+
return _getAlertsOn()
|
|
2561
|
+
},
|
|
2562
|
+
config: function () { openMonitorConfig('memory') },
|
|
2563
|
+
getValue: function () { return _getMonitorOn('memory') },
|
|
2564
|
+
setValue: function (v) {
|
|
2565
|
+
_setMonitorOn('memory', v, alertRegistry)
|
|
2566
|
+
registry.notifyChange(TOGGLE_KEY)
|
|
2567
|
+
try { alertRegistry.notify() } catch (_) {}
|
|
2568
|
+
},
|
|
2569
|
+
})
|
|
2570
|
+
return dispose
|
|
2571
|
+
}, 'dsh-flash-mem-mon: monitor-memory switch')
|
|
2572
|
+
|
|
2573
|
+
// 5. Apply persisted toggle gates — before the alert registry auto-starts
|
|
2574
|
+
Object.keys(_MONITOR_TOGGLES).forEach(function (m) {
|
|
2575
|
+
if (!_getMonitorOn(m)) _setMonitorOn(m, false, alertRegistry)
|
|
2576
|
+
})
|
|
2577
|
+
}
|
|
2578
|
+
|
|
2579
|
+
// Passive: listen for dock-flash:ready event
|
|
2580
|
+
try {
|
|
2581
|
+
ctx.on('dock-flash:ready', function (payload) {
|
|
2582
|
+
console.log('[dsh-flash-mem-mon] dock-flash:ready event received — quickControl=' + !!(payload && payload.quickControl) + ', alerts=' + !!(payload && payload.alerts))
|
|
2583
|
+
_registerServices(payload.quickControl, payload.alerts)
|
|
2584
|
+
})
|
|
2585
|
+
} catch (e) {
|
|
2586
|
+
console.warn('[dsh-flash-mem-mon] ctx.on("dock-flash:ready") failed:', e)
|
|
2587
|
+
}
|
|
2588
|
+
|
|
2589
|
+
// Active: check if dock-flash already loaded
|
|
2590
|
+
var registry = ctx.get('quickControl')
|
|
2591
|
+
var alertRegistry = ctx.get('dockFlashAlerts')
|
|
2592
|
+
console.log('[dsh-flash-mem-mon] active check — quickControl=' + !!registry + ', alerts=' + !!alertRegistry)
|
|
2593
|
+
if (registry && alertRegistry) _registerServices(registry, alertRegistry)
|
|
2594
|
+
|
|
2595
|
+
// Fallback: poll briefly
|
|
2596
|
+
if (!_registered) {
|
|
2597
|
+
var _retryCount = 0
|
|
2598
|
+
function _retryGet() {
|
|
2599
|
+
if (_registered) return
|
|
2600
|
+
var r = ctx.get('quickControl')
|
|
2601
|
+
var a = ctx.get('dockFlashAlerts')
|
|
2602
|
+
if (r && a) { _registerServices(r, a); return }
|
|
2603
|
+
if (++_retryCount < 15) setTimeout(_retryGet, 200)
|
|
2604
|
+
}
|
|
2605
|
+
setTimeout(_retryGet, 0)
|
|
2606
|
+
}
|
|
2607
|
+
},
|
|
2608
|
+
|
|
2609
|
+
dispose() {
|
|
2610
|
+
closeMonitorConfig()
|
|
2611
|
+
},
|
|
2612
|
+
}
|
|
2613
|
+
return client
|
|
2614
|
+
//#endregion ───────────────────────────────────────────────────────────────
|
|
2615
|
+
},
|
|
2616
|
+
})
|