1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
//! Analysis options and detail-level presets, shared between the CLI binary
//! and the WASM library crate.
/// Controls the capture tier for --obj-graph and the collection-detail caps
/// for --collections / --full-analysis. Larger tiers produce richer reports
/// (more element-type samples, more collections tracked, more holder edges)
/// at the cost of more RSS and wall time during the analysis.
#[derive(Clone, Copy, PartialEq, Debug, Default)]
pub enum ReportSize {
Small, // obj-graph: edge_cap=100; collections: minimum-RSS caps
#[default]
Default, // obj-graph: edge_cap=150; collections: balanced caps (default)
Large, // obj-graph: edge_cap=300; collections: 2× balanced caps
Max, // obj-graph: edge_cap=500; collections: original caps (most detail)
}
impl ReportSize {
pub fn edge_cap(self) -> usize {
match self {
ReportSize::Small => 100,
ReportSize::Default => 150,
ReportSize::Large => 300,
ReportSize::Max => 500,
}
}
pub fn tier_name(self) -> &'static str {
match self {
ReportSize::Small => "small",
ReportSize::Default => "default",
ReportSize::Large => "large",
ReportSize::Max => "max",
}
}
/// Max holder→pointee edges collected under --collections (16 B each).
pub fn field_ref_cap(self) -> usize {
match self {
ReportSize::Small => 1_000_000, // 16 MB
ReportSize::Default => 2_500_000, // 40 MB
ReportSize::Large => 5_000_000, // 80 MB
ReportSize::Max => 10_000_000, // 160 MB (original)
}
}
/// Max container records collected under --collections.
pub fn container_cap(self) -> usize {
match self {
ReportSize::Small => 150_000,
ReportSize::Default => 375_000,
ReportSize::Large => 750_000,
ReportSize::Max => 1_500_000, // original
}
}
/// Max node/entry wrapper objects stored in the node-KV map.
pub fn node_kv_cap(self) -> usize {
match self {
ReportSize::Small => 500_000,
ReportSize::Default => 1_250_000,
ReportSize::Large => 2_500_000,
ReportSize::Max => 5_000_000, // original
}
}
/// Max element slots sampled per collection for the value-type breakdown.
pub fn coll_values_per_collection(self) -> usize {
match self {
ReportSize::Small => 64,
ReportSize::Default => 256,
ReportSize::Large => 512,
ReportSize::Max => 4_096, // original
}
}
/// Max distinct collections whose element types are tallied.
pub fn coll_values_group_cap(self) -> usize {
match self {
ReportSize::Small => 10_000,
ReportSize::Default => 50_000,
ReportSize::Large => 100_000,
ReportSize::Max => 200_000, // original
}
}
}
/// Output format for the analysis report.
#[derive(Clone, Copy, PartialEq)]
pub enum OutputFormat {
/// Human-readable Markdown.
Md,
/// Markdown with embedded graph/chart blocks.
MdGraphs,
/// Canonical Report JSON (deterministic field order).
Json,
/// Standalone HTML.
Html,
}
/// Default depth cap for bounded `path()` walks in edge-query planning; the
/// `--query-path-depth` flag overrides it. Aliases `query::DEFAULT_PATH_DEPTH_CAP`
/// so there is exactly one numeric source of truth.
pub const DEFAULT_QUERY_PATH_DEPTH: usize = crate::query::DEFAULT_PATH_DEPTH_CAP;
/// Always-applied per-analysis caps, populated from `--detail`. All four heavy
/// analyses (root paths, alloc sites, thread locals, dominator tree) now run
/// unconditionally; these caps bound their output size. `--detail default`
/// reproduces the historical cap values so MAT/golden parity is unchanged.
#[derive(Clone)]
pub struct AnalyzeOptions {
pub root_path_max_depth: usize,
pub alloc_sites_top: usize,
pub thread_locals_per_thread: usize,
pub dominator_tree_max_nodes: usize,
pub dominator_tree_max_depth: usize,
pub leak_children_cap: usize,
pub top_consumers: usize,
/// How many histogram rows (sorted by retained desc) get a root-path chain.
/// Capped to avoid O(k×n) scan regression on dumps with many unique classes.
pub hist_root_path_top: usize,
pub find_duplicates: bool,
pub collections: bool,
pub collection_config: Option<std::path::PathBuf>,
pub(crate) coll_descs: Vec<crate::pass2::CollDesc>,
/// Inline OQL query strings (from `--query`, repeatable).
pub queries: Vec<String>,
/// Optional file of OQL queries, one per non-empty/non-comment line.
pub query_file: Option<String>,
/// Max hops for OQL `path(a, b)` bounded walks (always > 0).
pub query_path_depth: usize,
/// Restrict OQL result rows to GC-reachable objects (Eclipse MAT parity).
/// The `query` subcommand sets this by default; analyze leaves it off so the
/// report stays byte-identical unless `--reachable-only` is passed.
pub reachable_only: bool,
/// Store field-name labels on forward edges so root-path steps show
/// `ParentClass.fieldName → ChildClass`. Gated: adds ~2 bytes per edge
/// (~100–500 MB extra RSS on multi-GB dumps).
pub ref_paths: bool,
/// Capture outbound-reference graph + dominator subtree for the top biggest
/// objects. Enables click-through in the HTML report. Adds ~1-3 MB to the
/// report JSON; captured in ~30 MB of peak RAM freed after build_model.
pub obj_graph: bool,
/// Capture tier for --obj-graph: controls edge_cap per object.
/// small=100 edges (default), medium=150, large=300.
pub report_size: ReportSize,
/// Embed the React bundle as an uncompressed inline <script> in the HTML
/// report so it is human-readable and editable in DevTools. Output is much
/// larger but easier to inspect/modify. Implies HTML output format.
pub dev_report: bool,
/// Read the React bundle from this path at runtime instead of using the
/// compile-time embedded bytes. Lets JS/CSS changes take effect without
/// rebuilding the binary. Only meaningful with dev_report=true.
pub bundle_path: Option<std::path::PathBuf>,
/// Skip build_model + render. Used by `mat caches` which discards the report.
pub skip_report: bool,
/// Compute per-class reference-field statistics (null/non-null counts, total
/// retained size of pointees). Opt-in; off by default. Adds O(n) pass.
pub field_stats: bool,
}
impl Default for AnalyzeOptions {
/// The `--detail default` preset (historical cap values).
fn default() -> Self {
DetailLevel::Default.options()
}
}
/// Output-size preset. `Default` reproduces the historical cap values so
/// MAT/golden parity is unchanged; `Minimal`/`Max` scale the caps down/up.
#[derive(Clone, Copy, PartialEq)]
pub enum DetailLevel {
Minimal,
Default,
Max,
}
impl DetailLevel {
pub fn options(self) -> AnalyzeOptions {
// (root_depth, alloc_top, thread_locals, dom_nodes, dom_depth,
// leak_children, top_consumers, hist_root_path_top)
let (rd, at, tl, dn, dd, lc, tc, hrpt) = match self {
DetailLevel::Minimal => (10, 15, 5, 500, 10, 15, 10, 10),
DetailLevel::Default => (30, 50, 20, 5000, 20, 50, 20, 20),
DetailLevel::Max => (200, 500, 100, 100_000, 50, 500, 100, 100),
};
AnalyzeOptions {
root_path_max_depth: rd,
alloc_sites_top: at,
thread_locals_per_thread: tl,
dominator_tree_max_nodes: dn,
dominator_tree_max_depth: dd,
leak_children_cap: lc,
top_consumers: tc,
hist_root_path_top: hrpt,
find_duplicates: false,
collections: false,
collection_config: None,
coll_descs: Vec::new(),
queries: Vec::new(),
query_file: None,
query_path_depth: DEFAULT_QUERY_PATH_DEPTH,
reachable_only: false,
ref_paths: false,
obj_graph: false,
report_size: ReportSize::Default,
dev_report: false,
bundle_path: None,
skip_report: false,
field_stats: false,
}
}
}