1use std::time::Duration;
2
3use crate::annotations::*;
4use crate::display_params::*;
5use crate::draw::DrawingState;
6use crate::model::Model;
7use nu_ansi_term::AnsiString;
8use nu_ansi_term::Color::*;
9#[allow(unused_imports)]
10use std::convert::TryFrom;
11use tract_core::internal::*;
12use tract_core::num_traits::AsPrimitive;
13use tract_itertools::Itertools;
14
15pub fn render(
16 model: &dyn Model,
17 annotations: &Annotations,
18 options: &DisplayParams,
19) -> TractResult<()> {
20 if options.quiet {
21 return Ok(());
22 }
23 render_prefixed(model, "", &[], annotations, options)?;
24 if !model.properties().is_empty() {
25 println!("{}", White.bold().paint("# Properties"));
26 }
27 for (k, v) in model.properties().iter().sorted_by_key(|(k, _)| k.to_string()) {
28 println!("* {}: {:?}", White.paint(k), v)
29 }
30 let symbols = model.symbols();
31 if !symbols.all_assertions().is_empty() {
32 println!("{}", White.bold().paint("# Assertions"));
33 for a in symbols.all_assertions() {
34 println!(" * {a}");
35 }
36 }
37 for (ix, scenario) in symbols.all_scenarios().into_iter().enumerate() {
38 if ix == 0 {
39 println!("{}", White.bold().paint("# Scenarios"));
40 }
41 for a in scenario.1 {
42 println!(" * {}: {}", scenario.0, a);
43 }
44 }
45 Ok(())
46}
47
48pub fn render_node(
49 model: &dyn Model,
50 node_id: usize,
51 annotations: &Annotations,
52 options: &DisplayParams,
53) -> TractResult<()> {
54 render_node_prefixed(model, "", &[], node_id, None, annotations, options)
55}
56
57fn render_prefixed(
58 model: &dyn Model,
59 prefix: &str,
60 scope: &[(usize, String)],
61 annotations: &Annotations,
62 options: &DisplayParams,
63) -> TractResult<()> {
64 let mut drawing_state =
65 if options.should_draw() { Some(DrawingState::default()) } else { None };
66 let node_ids = options.order(model)?;
67 for node in node_ids {
68 if options.filter(model, scope, node)? {
69 render_node_prefixed(
70 model,
71 prefix,
72 scope,
73 node,
74 drawing_state.as_mut(),
75 annotations,
76 options,
77 )?
78 } else if let Some(ref mut ds) = drawing_state {
79 let _prefix = ds.draw_node_vprefix(model, node, options)?;
80 let _body = ds.draw_node_body(model, node, options)?;
81 let _suffix = ds.draw_node_vsuffix(model, node, options)?;
82 }
83 }
84 Ok(())
85}
86
87pub fn si_prefix(v: impl AsPrimitive<f64>, unit: &str) -> String {
88 radical_prefix(v, unit, 1000, "")
89}
90
91pub fn pow2_prefix(v: impl AsPrimitive<f64>, unit: &str) -> String {
92 radical_prefix(v, unit, 1024, "i")
93}
94
95pub fn radical_prefix(
96 v: impl AsPrimitive<f64>,
97 unit: &str,
98 radical: usize,
99 radical_prefix: &str,
100) -> String {
101 let v: f64 = v.as_();
102 let radical = radical as f64;
103 let radical3 = radical.powi(3);
104 let radical2 = radical.powi(2);
105 if v > radical3 {
106 format!("{:7.3} G{}{}", v / radical3, radical_prefix, unit)
107 } else if v > 1e6 {
108 format!("{:7.3} M{}{}", v / radical2, radical_prefix, unit)
109 } else if v > 1e3 {
110 format!("{:7.3} k{}{}", v / radical, radical_prefix, unit)
111 } else {
112 format!("{v:7.3} {unit}")
113 }
114}
115
116fn render_node_prefixed(
117 model: &dyn Model,
118 prefix: &str,
119 scope: &[(usize, String)],
120 node_id: usize,
121 mut drawing_state: Option<&mut DrawingState>,
122 annotations: &Annotations,
123 options: &DisplayParams,
124) -> TractResult<()> {
125 let qid = NodeQId(scope.into(), node_id);
126 let tags = annotations.tags.get(&qid).cloned().unwrap_or_default();
127 let name_color = tags.style.unwrap_or_else(|| White.into());
128 let node_name = model.node_name(node_id);
129 let node_op_name = model.node_op_name(node_id);
130 let profile_column_pad = format!("{:>1$}", "", options.profile as usize * 20);
131 let cost_column_pad = format!("{:>1$}", "", options.cost as usize * 25);
132 let mem_padding = if annotations.memory_summary.is_some() { 15 } else { 30 };
133 let tmp_mem_usage_column_pad =
134 format!("{:>1$}", "", options.tmp_mem_usage as usize * mem_padding);
135 let flops_column_pad = format!("{:>1$}", "", (options.profile && options.cost) as usize * 20);
136
137 if let Some(ds) = &mut drawing_state {
138 for l in ds.draw_node_vprefix(model, node_id, options)? {
139 println!(
140 "{cost_column_pad}{profile_column_pad}{flops_column_pad}{tmp_mem_usage_column_pad}{prefix}{l} "
141 );
142 }
143 }
144
145 let mut profile_column = tags.profile.map(|measure| {
147 let profile_summary = annotations.profile_summary.as_ref().unwrap();
148 let use_micros = profile_summary.sum < Duration::from_millis(1);
149 let ratio = measure.as_secs_f64() / profile_summary.sum.as_secs_f64();
150 let ratio_for_color = measure.as_secs_f64() / profile_summary.max.as_secs_f64();
151 let color = colorous::RED_YELLOW_GREEN.eval_continuous(1.0 - ratio_for_color);
152 let color = nu_ansi_term::Color::Rgb(color.r, color.g, color.b);
153 let label = format!(
154 "{:7.3} {}s/i {} ",
155 measure.as_secs_f64() * if use_micros { 1e6 } else { 1e3 },
156 if use_micros { "ยต" } else { "m" },
157 color.bold().paint(format!("{:>4.1}%", ratio * 100.0))
158 );
159 std::iter::once(label)
160 });
161
162 let mut cost_column = if options.cost {
164 Some(
165 tags.cost
166 .iter()
167 .map(|c| {
168 let key = format!("{:?}", c.0);
169 let value = render_tdim(&c.1);
170 let value_visible_len = c.1.to_string().len();
171 let padding = 24usize.saturating_sub(value_visible_len + key.len());
172 key + &*std::iter::repeat_n(' ', padding).join("") + &value.to_string() + " "
173 })
174 .peekable(),
175 )
176 } else {
177 None
178 };
179
180 let mut flops_column = if options.profile && options.cost {
182 let timing: f64 = tags.profile.as_ref().map(|d| d.as_secs_f64()).unwrap_or(0.0);
183 let flops_column_pad = flops_column_pad.clone();
184 let it = tags.cost.iter().map(move |c| {
185 if c.0.is_compute() {
186 let flops = c.1.to_usize().unwrap_or(0) as f64 / timing;
187 let unpadded = si_prefix(flops, "F/s");
188 format!("{:>1$} ", unpadded, 19)
189 } else {
190 flops_column_pad.clone()
191 }
192 });
193 Some(it)
194 } else {
195 None
196 };
197
198 let mut tmp_mem_usage_column = if options.tmp_mem_usage {
200 let it = tags.tmp_mem_usage.iter().map(move |mem| {
201 let unpadded = if let Ok(mem_size) = mem.to_usize() {
202 pow2_prefix(mem_size, "B")
203 } else {
204 format!("{mem:.3} B")
205 };
206 format!("{:>1$} ", unpadded, mem_padding - 1)
207 });
208 Some(it)
209 } else {
210 None
211 };
212
213 let mut drawing_lines: Box<dyn Iterator<Item = String>> =
215 if let Some(ds) = drawing_state.as_mut() {
216 let body = ds.draw_node_body(model, node_id, options)?;
217 let suffix = ds.draw_node_vsuffix(model, node_id, options)?;
218 let filler = ds.draw_node_vfiller(model, node_id)?;
219 Box::new(body.into_iter().chain(suffix).chain(std::iter::repeat(filler)))
220 } else {
221 Box::new(std::iter::repeat(cost_column_pad.clone()))
222 };
223
224 macro_rules! prefix {
225 () => {
226 let cost = cost_column
227 .as_mut()
228 .map(|it| it.next().unwrap_or_else(|| cost_column_pad.to_string()))
229 .unwrap_or("".to_string());
230 let profile = profile_column
231 .as_mut()
232 .map(|it| it.next().unwrap_or_else(|| profile_column_pad.to_string()))
233 .unwrap_or("".to_string());
234 let flops = flops_column
235 .as_mut()
236 .map(|it| it.next().unwrap_or_else(|| flops_column_pad.to_string()))
237 .unwrap_or("".to_string());
238 let tmp_mem_usage = tmp_mem_usage_column
239 .as_mut()
240 .map(|it| it.next().unwrap_or_else(|| tmp_mem_usage_column_pad.to_string()))
241 .unwrap_or("".to_string());
242 print!(
243 "{}{}{}{}{}{} ",
244 profile,
245 cost,
246 flops,
247 tmp_mem_usage,
248 prefix,
249 drawing_lines.next().unwrap(),
250 )
251 };
252 }
253
254 prefix!();
255 println!(
256 "{} {} {}",
257 White.bold().paint(format!("{node_id}")),
258 (if node_name == "UnimplementedOp" { Red.bold() } else { Blue.bold() }).paint(node_op_name),
259 name_color.italic().paint(node_name)
260 );
261 for label in tags.labels.iter() {
262 prefix!();
263 println!(" * {label}");
264 }
265 if let Io::Long = options.io {
266 for (ix, i) in model.node_inputs(node_id).iter().enumerate() {
267 let star = if ix == 0 { '*' } else { ' ' };
268 prefix!();
269 println!(
270 " {} input fact #{}: {} {}",
271 star,
272 ix,
273 White.bold().paint(format!("{i:?}")),
274 model.outlet_fact_format(*i),
275 );
276 }
277 for slot in 0..model.node_output_count(node_id) {
278 let star = if slot == 0 { '*' } else { ' ' };
279 let outlet = OutletId::new(node_id, slot);
280 let mut model_io = vec![];
281 for (ix, _) in model.input_outlets().iter().enumerate().filter(|(_, o)| **o == outlet) {
282 model_io.push(Cyan.bold().paint(format!("MODEL INPUT #{ix}")).to_string());
283 }
284 if let Some(t) = &tags.model_input {
285 model_io.push(t.to_string());
286 }
287 for (ix, _) in model.output_outlets().iter().enumerate().filter(|(_, o)| **o == outlet)
288 {
289 model_io.push(Yellow.bold().paint(format!("MODEL OUTPUT #{ix}")).to_string());
290 }
291 if let Some(t) = &tags.model_output {
292 model_io.push(t.to_string());
293 }
294 let successors = model.outlet_successors(outlet);
295 prefix!();
296 let mut axes =
297 tags.outlet_axes.get(slot).map(|s| s.join(",")).unwrap_or_else(|| "".to_string());
298 if !axes.is_empty() {
299 axes.push(' ')
300 }
301 println!(
302 " {} output fact #{}: {}{} {} {} {}",
303 star,
304 slot,
305 Green.bold().italic().paint(axes),
306 model.outlet_fact_format(outlet),
307 White.bold().paint(successors.iter().map(|s| format!("{s:?}")).join(" ")),
308 model_io.join(", "),
309 Blue.bold().italic().paint(
310 tags.outlet_labels
311 .get(slot)
312 .map(|s| s.join(","))
313 .unwrap_or_else(|| "".to_string())
314 )
315 );
316 if options.outlet_labels {
317 if let Some(label) = model.outlet_label(OutletId::new(node_id, slot)) {
318 prefix!();
319 println!(" {} ", White.italic().paint(label));
320 }
321 }
322 }
323 }
324 if options.info {
325 for info in model.node_op(node_id).info()? {
326 prefix!();
327 println!(" * {info}");
328 }
329 }
330 if options.invariants {
331 if let Some(typed) = model.downcast_ref::<TypedModel>() {
332 let node = typed.node(node_id);
333 let (inputs, outputs) = typed.node_facts(node.id)?;
334 let axes_mapping = node.op().as_typed().unwrap().axes_mapping(&inputs, &outputs)?;
335 prefix!();
336 println!(" * {axes_mapping}");
337 }
338 }
339 if options.debug_op {
340 prefix!();
341 println!(" * {:?}", model.node_op(node_id));
342 }
343 for section in tags.sections {
344 if section.is_empty() {
345 continue;
346 }
347 prefix!();
348 println!(" * {}", section[0]);
349 for s in §ion[1..] {
350 prefix!();
351 println!(" {s}");
352 }
353 }
354
355 if !options.folded {
356 for (label, sub) in model.nested_models(node_id) {
357 let prefix = drawing_lines.next().unwrap();
358 let mut scope: TVec<_> = scope.into();
359 scope.push((node_id, label));
360 let scope_prefix = scope.iter().map(|(_, p)| p).join("|");
361 render_prefixed(
362 sub,
363 &format!("{prefix} [{scope_prefix}] "),
364 &scope,
365 annotations,
366 options,
367 )?
368 }
369 }
370 if let Io::Short = options.io {
371 let same = !model.node_inputs(node_id).is_empty()
372 && model.node_output_count(node_id) == 1
373 && model.outlet_fact_format(node_id.into())
374 == model.outlet_fact_format(model.node_inputs(node_id)[0]);
375 if !same || model.output_outlets().iter().any(|o| o.node == node_id) {
376 let style = drawing_state
377 .map(|s| s.wires.last().and_then(|w| w.color).unwrap_or(s.latest_node_color))
378 .unwrap_or_else(|| White.into());
379 for ix in 0..model.node_output_count(node_id) {
380 prefix!();
381 println!(
382 " {}{}{} {}",
383 style.paint(box_drawing::heavy::HORIZONTAL),
384 style.paint(box_drawing::heavy::HORIZONTAL),
385 style.paint(box_drawing::heavy::HORIZONTAL),
386 model.outlet_fact_format((node_id, ix).into())
387 );
388 }
389 }
390 }
391
392 while cost_column.as_mut().map(|cost| cost.peek().is_some()).unwrap_or(false) {
393 prefix!();
394 println!();
395 }
396
397 Ok(())
398}
399
400pub fn render_summaries(
401 model: &dyn Model,
402 annotations: &Annotations,
403 options: &DisplayParams,
404) -> TractResult<()> {
405 let total = annotations.tags.values().sum::<NodeTags>();
406
407 if options.tmp_mem_usage {
408 if let Some(summary) = &annotations.memory_summary {
409 println!("{}", White.bold().paint("Memory summary"));
410 println!(" * Peak flushable memory: {}", pow2_prefix(summary.max, "B"));
411 }
412 }
413 if options.cost {
414 println!("{}", White.bold().paint("Cost summary"));
415 for (c, i) in &total.cost {
416 println!(" * {:?}: {}", c, render_tdim(i));
417 }
418 }
419
420 if options.profile {
421 let summary = annotations.profile_summary.as_ref().unwrap();
422
423 let have_accel_profiling =
424 annotations.tags.iter().any(|(_, tag)| tag.accelerator_profile.is_some());
425 println!(
426 "{}{}{}",
427 White.bold().paint(format!("{:<43}", "Most time consuming operations")),
428 White.bold().paint(format!("{:<17}", "CPU")),
429 White.bold().paint(if have_accel_profiling { "Accelerator" } else { "" }),
430 );
431
432 for (op, (cpu_dur, accel_dur, n)) in annotations
433 .tags
434 .iter()
435 .map(|(k, v)| {
436 (
437 k.model(model).unwrap().node_op_name(k.1),
438 (v.profile.unwrap_or_default(), v.accelerator_profile.unwrap_or_default()),
439 )
440 })
441 .sorted_by_key(|a| a.0.to_string())
442 .group_by(|(n, _)| n.clone())
443 .into_iter()
444 .map(|(a, group)| {
445 (
446 a,
447 group.into_iter().fold(
448 (Duration::default(), Duration::default(), 0),
449 |(accu, accel_accu, n), d| (accu + d.1.0, accel_accu + d.1.1, n + 1),
450 ),
451 )
452 })
453 .sorted_by_key(|(_, d)| if have_accel_profiling { d.1 } else { d.0 })
454 .rev()
455 {
456 println!(
457 " * {} {:3} nodes: {} {}",
458 Blue.bold().paint(format!("{op:22}")),
459 n,
460 dur_avg_ratio(cpu_dur, summary.sum),
461 if have_accel_profiling {
462 dur_avg_ratio(accel_dur, summary.accel_sum)
463 } else {
464 "".to_string()
465 }
466 );
467 }
468
469 println!("{}", White.bold().paint("By prefix"));
470 fn prefixes_for(s: &str) -> impl Iterator<Item = String> + '_ {
471 use tract_itertools::*;
472 let split = s.split('.').count();
473 (0..split).map(move |n| s.split('.').take(n).join("."))
474 }
475 let all_prefixes = annotations
476 .tags
477 .keys()
478 .flat_map(|id| prefixes_for(id.model(model).unwrap().node_name(id.1)))
479 .filter(|s| !s.is_empty())
480 .sorted()
481 .unique()
482 .collect::<Vec<String>>();
483
484 for prefix in &all_prefixes {
485 let sum = annotations
486 .tags
487 .iter()
488 .filter(|(k, _v)| k.model(model).unwrap().node_name(k.1).starts_with(prefix))
489 .map(|(_k, v)| v)
490 .sum::<NodeTags>();
491
492 let profiler =
493 if !have_accel_profiling { sum.profile } else { sum.accelerator_profile };
494 if profiler.unwrap_or_default().as_secs_f64() / summary.entire.as_secs_f64() < 0.01 {
495 continue;
496 }
497 print!("{} ", dur_avg_ratio(profiler.unwrap_or_default(), summary.sum));
498
499 for _ in prefix.chars().filter(|c| *c == '.') {
500 print!(" ");
501 }
502 println!("{prefix}");
503 }
504
505 println!(
506 "Not accounted by ops: {}",
507 dur_avg_ratio(summary.entire - summary.sum.min(summary.entire), summary.entire)
508 );
509
510 if have_accel_profiling {
511 println!(
512 "(Total CPU Op time - Total Accelerator Op time): {}",
513 dur_avg_ratio(summary.sum - summary.accel_sum.min(summary.sum), summary.entire)
514 );
515 }
516 println!("Entire network performance: {}", dur_avg(summary.entire));
517 }
518
519 Ok(())
520}
521
522pub fn dur_avg(measure: Duration) -> String {
524 White.bold().paint(format!("{:.3} ms/i", measure.as_secs_f64() * 1e3)).to_string()
525}
526
527pub fn dur_avg_ratio(measure: Duration, global: Duration) -> String {
530 format!(
531 "{} {}",
532 White.bold().paint(format!("{:7.3} ms/i", measure.as_secs_f64() * 1e3)),
533 Yellow
534 .bold()
535 .paint(format!("{:>4.1}%", measure.as_secs_f64() / global.as_secs_f64() * 100.)),
536 )
537}
538
539fn render_tdim(d: &TDim) -> AnsiString<'static> {
540 if let Ok(i) = d.to_i64() { render_big_integer(i) } else { d.to_string().into() }
541}
542
543fn render_big_integer(i: i64) -> nu_ansi_term::AnsiString<'static> {
544 let raw = i.to_string();
545 let mut blocks = raw
546 .chars()
547 .rev()
548 .chunks(3)
549 .into_iter()
550 .map(|mut c| c.join("").chars().rev().join(""))
551 .enumerate()
552 .map(|(ix, s)| if ix % 2 == 1 { White.bold().paint(s).to_string() } else { s })
553 .collect::<Vec<_>>();
554 blocks.reverse();
555 blocks.into_iter().join("").into()
556}