rallo/
stats.rs

1use std::{borrow::Cow, collections::VecDeque, ffi::c_void, fmt::Debug, io::BufRead, path::Path};
2
3use serde::Serialize;
4
5#[derive(Debug, Clone)]
6pub struct FrameInfo {
7    /// Filename where the function call was made
8    pub filename: Option<std::path::PathBuf>,
9    /// Column number where the function call was made
10    pub colno: Option<u32>,
11    /// Line number where the function call was made
12    pub lineno: Option<u32>,
13    /// Address of the function
14    pub fn_address: Option<*mut c_void>,
15    /// Name of the function
16    pub fn_name: Option<String>,
17}
18
19#[derive(Debug)]
20pub struct Allocation {
21    /// Allocation size
22    pub allocation_size: usize,
23    /// Deallocation size
24    pub deallocation_size: usize,
25    /// address of the allocation
26    pub address: usize,
27    /// Stack trace
28    pub stack: VecDeque<FrameInfo>,
29}
30
31#[derive(Debug)]
32pub struct Stats {
33    /// Allocations
34    pub allocations: VecDeque<Allocation>,
35    /// Deallocations
36    pub deallocations: VecDeque<Allocation>,
37}
38
39impl Stats {
40    /// Transform the raw stats into a tree structure
41    pub fn into_tree(mut self) -> Result<Tree<Key>, Cow<'static, str>> {
42        let cwd = std::env::current_dir()
43            .map_err(|e| format!("failed to get current directory: {:?}", e))?;
44        let cwd = cwd.to_str().ok_or("current directory is not valid UTF-8")?;
45
46        let allocation = match self.allocations.pop_back() {
47            None => {
48                return Err("no allocations".into());
49            }
50            Some(allocation) => allocation,
51        };
52
53        let mut stack = allocation.stack;
54
55        let initial_key = loop {
56            let root = stack.pop_front();
57            let root = match root {
58                None => panic!("stack is empty"),
59                Some(root) => root,
60            };
61            let key = TryInto::<Key>::try_into(root);
62            if let Ok(key) = key {
63                break key;
64            }
65        };
66
67        let mut tree = Tree {
68            category: guess_category(cwd, initial_key.filename.as_str()),
69            key: initial_key.clone(),
70            allocation: 0,
71            allocation_count: 0,
72            deallocation: 0,
73            deallocation_count: 0,
74            children: Vec::new(),
75        };
76        let mut pointer = &mut tree;
77        let stack_len = stack.len();
78        for (index, info) in stack.into_iter().enumerate() {
79            let is_last = stack_len == index + 1;
80            let key: Key = match info.try_into() {
81                Ok(key) => key,
82                Err(_) => continue,
83            };
84
85            let mut c = Tree {
86                category: guess_category(cwd, key.filename.as_str()),
87                key,
88                allocation: 0,
89                allocation_count: 0,
90                deallocation: 0,
91                deallocation_count: 0,
92                children: Vec::new(),
93            };
94            if is_last {
95                c.allocation += allocation.allocation_size;
96                c.allocation_count += 1;
97                c.deallocation += allocation.deallocation_size;
98            }
99            pointer.children.push(c);
100            pointer = pointer.children.last_mut().unwrap();
101        }
102
103        for mut allocation in self.allocations {
104            let mut pointer = &mut tree;
105
106            // Ensure the first frame of the allocation is the same as the first frame of the tree
107            // Otherwise we have 2 roots.
108            // Technically we should merge the "root" tree with the current one.
109            // But, it is effortly to do so.
110            // So we just panic.
111            let first_key: Key = loop {
112                let s = match allocation.stack.pop_front() {
113                    None => panic!("stack is empty"),
114                    Some(s) => s,
115                };
116                match s.try_into() {
117                    Ok(k) => break k,
118                    Err(_) => continue,
119                };
120            };
121            if first_key != initial_key {
122                panic!("first frame of allocation is not the same as the first frame of the tree");
123            }
124
125            let stack_len = allocation.stack.len();
126            for (index, info) in allocation.stack.into_iter().enumerate() {
127                let is_last = stack_len == index + 1;
128                let key: Key = match info.try_into() {
129                    Ok(key) => key,
130                    Err(_) => continue,
131                };
132
133                let found = pointer.children.iter().position(|c| c.key == key);
134                pointer = if let Some(found) = found {
135                    pointer.children.get_mut(found).unwrap()
136                } else {
137                    let c = Tree {
138                        category: guess_category(cwd, key.filename.as_str()),
139                        key,
140                        allocation: 0,
141                        allocation_count: 0,
142                        deallocation: 0,
143                        deallocation_count: 0,
144                        children: Vec::new(),
145                    };
146                    pointer.children.push(c);
147                    pointer.children.last_mut().unwrap()
148                };
149
150                // Put the effort only on the last frame
151                if is_last {
152                    pointer.allocation += allocation.allocation_size;
153                    pointer.deallocation += allocation.deallocation_size;
154                    pointer.allocation_count += 1;
155                }
156            }
157        }
158
159        for mut allocation in self.deallocations {
160            let mut pointer = &mut tree;
161
162            // Ensure the first frame of the allocation is the same as the first frame of the tree
163            // Otherwise we have 2 roots.
164            // Technically we should merge the "root" tree with the current one.
165            // But, it is effortly to do so.
166            // So we just panic.
167            let first_key: Key = loop {
168                let s = match allocation.stack.pop_front() {
169                    None => panic!("stack is empty"),
170                    Some(s) => s,
171                };
172                match s.try_into() {
173                    Ok(k) => break k,
174                    Err(_) => continue,
175                };
176            };
177            if first_key != initial_key {
178                panic!("first frame of allocation is not the same as the first frame of the tree");
179            }
180
181            let stack_len = allocation.stack.len();
182            for (index, info) in allocation.stack.into_iter().enumerate() {
183                let is_last = stack_len == index + 1;
184                let key: Key = match info.try_into() {
185                    Ok(key) => key,
186                    Err(_) => continue,
187                };
188
189                let found = pointer.children.iter().position(|c| c.key == key);
190                pointer = if let Some(found) = found {
191                    pointer.children.get_mut(found).unwrap()
192                } else {
193                    let c = Tree {
194                        category: guess_category(cwd, key.filename.as_str()),
195                        key,
196                        allocation: 0,
197                        allocation_count: 0,
198                        deallocation: 0,
199                        deallocation_count: 0,
200                        children: Vec::new(),
201                    };
202                    pointer.children.push(c);
203                    pointer.children.last_mut().unwrap()
204                };
205
206                // Put the effort only on the last frame
207                if is_last {
208                    pointer.allocation += allocation.allocation_size;
209                    pointer.deallocation += allocation.deallocation_size;
210                    pointer.deallocation_count += 1;
211                }
212            }
213        }
214
215        tree.update_value();
216
217        Ok(tree)
218    }
219}
220
221#[derive(Debug, PartialEq, Eq, Serialize, Clone)]
222pub struct FileContent {
223    pub before: Vec<String>,
224    pub highlighted: String,
225    pub after: Vec<String>,
226}
227
228#[derive(Debug, PartialEq, Eq, Serialize, Clone)]
229pub struct Key {
230    pub filename: String,
231    pub colno: u32,
232    pub lineno: u32,
233    #[serde(skip_serializing)]
234    pub fn_address: *mut c_void,
235    pub fn_name: String,
236    pub file_content: Option<FileContent>,
237}
238
239impl TryFrom<FrameInfo> for Key {
240    type Error = &'static str;
241
242    fn try_from(value: FrameInfo) -> Result<Self, Self::Error> {
243        let filename = value.filename.ok_or("filename is None")?;
244        let filename = filename.to_str().ok_or("filename is not valid UTF-8")?;
245        let filename = filename.to_string();
246
247        let colno = value.colno.ok_or("colno is None")?;
248        let lineno = value.lineno.ok_or("lineno is None")?;
249        let fn_address = value.fn_address.ok_or("fn_address is None")?;
250        let fn_name = value.fn_name.ok_or("fn_name is None")?;
251
252        let fn_name = rustc_demangle::demangle(&fn_name).to_string();
253
254        let delta = 5;
255        let range_min = if lineno > (delta + 1) {
256            lineno - delta - 1
257        } else {
258            0
259        };
260        let file_content = std::fs::File::open(&filename).ok().and_then(|file| {
261            let lines = std::io::BufReader::new(file).lines();
262            let mut lines: Vec<_> = lines
263                .enumerate()
264                .filter_map(|(i, line)| Some((i, line.ok()?)))
265                .skip_while(|(index, _)| *index < range_min as usize)
266                .take_while(|(index, _)| *index < lineno as usize + delta as usize)
267                .collect();
268
269            let highlighted_index = match lines.iter().position(|(i, _)| *i as u32 == lineno) {
270                Some(i) => i,
271                None => {
272                    println!("not found");
273                    return None;
274                }
275            };
276
277            let mut after = lines.split_off(highlighted_index - 1);
278            let highlighted = after.remove(0);
279            let before = lines;
280
281            Some(FileContent {
282                before: before.into_iter().map(|(_, line)| line).collect(),
283                highlighted: highlighted.1,
284                after: after.into_iter().map(|(_, line)| line).collect(),
285            })
286        });
287
288        Ok(Key {
289            filename,
290            colno,
291            lineno,
292            fn_address,
293            fn_name,
294            file_content,
295        })
296    }
297}
298
299#[derive(Debug, Serialize)]
300#[cfg_attr(test, derive(PartialEq, Eq))]
301/// Tree structure for the flamegraph
302pub struct Tree<K: Debug + Serialize> {
303    pub key: K,
304    pub allocation: usize,
305    pub allocation_count: usize,
306    pub deallocation: usize,
307    pub deallocation_count: usize,
308    pub category: Category,
309    pub children: Vec<Tree<K>>,
310}
311
312impl<K: Debug + Serialize> Tree<K> {
313    /// Write an HTML file with the flamegraph at the given path
314    pub fn print_flamegraph<P>(&self, path: P)
315    where
316        P: AsRef<Path>,
317    {
318        let d = serde_json::to_string(&self).unwrap();
319        let html = include_str!("../template.html");
320        let html = html.replace("{ undefined }", &d);
321        std::fs::write(path, html).unwrap();
322    }
323
324    fn update_value(&mut self) {
325        let mut allocation = 0;
326        let mut allocation_count = 0;
327        let mut deallocation = 0;
328        let mut deallocation_count = 0;
329        for child in &mut self.children {
330            child.update_value();
331            allocation += child.allocation;
332            if child.allocation > 0 {
333                allocation_count += child.allocation_count;
334            }
335            deallocation += child.deallocation;
336            if child.deallocation > 0 {
337                deallocation_count += child.deallocation_count;
338            }
339        }
340        self.allocation += allocation;
341        self.allocation_count += allocation_count;
342        self.deallocation += deallocation;
343        self.deallocation_count += deallocation_count;
344    }
345}
346
347#[derive(Debug, Serialize)]
348#[cfg_attr(test, derive(PartialEq, Eq))]
349#[serde(rename_all = "lowercase")]
350/// Category of the allocation
351/// - `rustc`: Rust compiler
352/// - `ruststdlib`: Rust standard library
353/// - `deps`: Dependencies
354/// - `application`: Application code
355/// - `unknown`: Unknown code
356///
357/// The category is determined by the path of the file.
358pub enum Category {
359    RustStdLib,
360    RustC,
361    Deps,
362    Application,
363    Unknown,
364}
365
366fn guess_category(cwd: &str, filename: &str) -> Category {
367    if filename.contains("/rustc/") {
368        Category::RustC
369    } else if filename.contains("/rustlib/") {
370        Category::RustStdLib
371    } else if filename.contains("cargo/registry/src") {
372        Category::Deps
373    } else if filename.starts_with(cwd) {
374        Category::Application
375    } else {
376        Category::Unknown
377    }
378}
379
380#[cfg(test)]
381mod test {
382    use pretty_assertions::assert_eq;
383
384    use super::*;
385
386    #[test]
387    fn test_tree_value_1() {
388        let stats = Stats {
389            deallocations: VecDeque::new(),
390            allocations: VecDeque::from([Allocation {
391                allocation_size: 1024,
392                deallocation_size: 0,
393                address: 0,
394                stack: VecDeque::from([
395                    FrameInfo {
396                        filename: Some("foo.rs".into()),
397                        colno: Some(1),
398                        lineno: Some(1),
399                        fn_address: Some(std::ptr::null_mut()),
400                        fn_name: Some("foo".into()),
401                    },
402                    FrameInfo {
403                        filename: Some("foo2.rs".into()),
404                        colno: Some(1),
405                        lineno: Some(1),
406                        fn_address: Some(std::ptr::null_mut()),
407                        fn_name: Some("foo2".into()),
408                    },
409                    FrameInfo {
410                        filename: Some("foo3.rs".into()),
411                        colno: Some(1),
412                        lineno: Some(1),
413                        fn_address: Some(std::ptr::null_mut()),
414                        fn_name: Some("foo3".into()),
415                    },
416                ]),
417            }]),
418        };
419        let tree = stats.into_tree().unwrap();
420
421        assert_eq!(
422            tree,
423            Tree {
424                key: Key {
425                    filename: "foo.rs".to_string(),
426                    colno: 1,
427                    lineno: 1,
428                    fn_address: std::ptr::null_mut(),
429                    fn_name: "foo".to_string(),
430                    file_content: None,
431                },
432                allocation: 1024,
433                allocation_count: 1,
434                deallocation: 0,
435                deallocation_count: 0,
436                category: Category::Unknown,
437                children: vec![Tree {
438                    key: Key {
439                        filename: "foo2.rs".to_string(),
440                        colno: 1,
441                        lineno: 1,
442                        fn_address: std::ptr::null_mut(),
443                        fn_name: "foo2".to_string(),
444                        file_content: None,
445                    },
446                    allocation: 1024,
447                    allocation_count: 1,
448                    deallocation: 0,
449                    deallocation_count: 0,
450                    category: Category::Unknown,
451                    children: vec![Tree {
452                        key: Key {
453                            filename: "foo3.rs".to_string(),
454                            colno: 1,
455                            lineno: 1,
456                            fn_address: std::ptr::null_mut(),
457                            fn_name: "foo3".to_string(),
458                            file_content: None,
459                        },
460                        allocation: 1024,
461                        allocation_count: 1,
462                        deallocation: 0,
463                        deallocation_count: 0,
464                        category: Category::Unknown,
465                        children: vec![],
466                    }],
467                }],
468            }
469        );
470    }
471
472    #[test]
473    fn test_tree_value_2() {
474        let stats = Stats {
475            deallocations: VecDeque::new(),
476            allocations: VecDeque::from([
477                Allocation {
478                    allocation_size: 1024,
479                    deallocation_size: 0,
480                    address: 0,
481                    stack: VecDeque::from([
482                        FrameInfo {
483                            filename: Some("foo.rs".into()),
484                            colno: Some(1),
485                            lineno: Some(1),
486                            fn_address: Some(std::ptr::null_mut()),
487                            fn_name: Some("foo".into()),
488                        },
489                        FrameInfo {
490                            filename: Some("foo2.rs".into()),
491                            colno: Some(1),
492                            lineno: Some(1),
493                            fn_address: Some(std::ptr::null_mut()),
494                            fn_name: Some("foo2".into()),
495                        },
496                        FrameInfo {
497                            filename: Some("foo3.rs".into()),
498                            colno: Some(1),
499                            lineno: Some(1),
500                            fn_address: Some(std::ptr::null_mut()),
501                            fn_name: Some("foo3".into()),
502                        },
503                    ]),
504                },
505                Allocation {
506                    allocation_size: 1024,
507                    deallocation_size: 0,
508                    address: 0,
509                    stack: VecDeque::from([
510                        FrameInfo {
511                            filename: Some("foo.rs".into()),
512                            colno: Some(1),
513                            lineno: Some(1),
514                            fn_address: Some(std::ptr::null_mut()),
515                            fn_name: Some("foo".into()),
516                        },
517                        FrameInfo {
518                            filename: Some("foo2.rs".into()),
519                            colno: Some(1),
520                            lineno: Some(1),
521                            fn_address: Some(std::ptr::null_mut()),
522                            fn_name: Some("foo2".into()),
523                        },
524                        FrameInfo {
525                            filename: Some("foo3.rs".into()),
526                            colno: Some(1),
527                            lineno: Some(1),
528                            fn_address: Some(std::ptr::null_mut()),
529                            fn_name: Some("foo3".into()),
530                        },
531                    ]),
532                },
533            ]),
534        };
535        let tree = stats.into_tree().unwrap();
536
537        assert_eq!(
538            tree,
539            Tree {
540                key: Key {
541                    filename: "foo.rs".to_string(),
542                    colno: 1,
543                    lineno: 1,
544                    fn_address: std::ptr::null_mut(),
545                    fn_name: "foo".to_string(),
546                    file_content: None,
547                },
548                allocation: 1024 * 2,
549                allocation_count: 2,
550                deallocation: 0,
551                deallocation_count: 0,
552                category: Category::Unknown,
553                children: vec![Tree {
554                    key: Key {
555                        filename: "foo2.rs".to_string(),
556                        colno: 1,
557                        lineno: 1,
558                        fn_address: std::ptr::null_mut(),
559                        fn_name: "foo2".to_string(),
560                        file_content: None,
561                    },
562                    allocation: 1024 * 2,
563                    allocation_count: 2,
564                    deallocation: 0,
565                    deallocation_count: 0,
566                    category: Category::Unknown,
567                    children: vec![Tree {
568                        key: Key {
569                            filename: "foo3.rs".to_string(),
570                            colno: 1,
571                            lineno: 1,
572                            fn_address: std::ptr::null_mut(),
573                            fn_name: "foo3".to_string(),
574                            file_content: None,
575                        },
576                        allocation: 1024 * 2,
577                        allocation_count: 2,
578                        deallocation: 0,
579                        deallocation_count: 0,
580                        category: Category::Unknown,
581                        children: vec![],
582                    }],
583                }],
584            }
585        );
586    }
587
588    #[test]
589    fn test_tree_value_3() {
590        let stats = Stats {
591            deallocations: VecDeque::new(),
592            allocations: VecDeque::from([
593                Allocation {
594                    allocation_size: 1024,
595                    deallocation_size: 0,
596                    address: 0,
597                    stack: VecDeque::from([
598                        FrameInfo {
599                            filename: Some("foo.rs".into()),
600                            colno: Some(1),
601                            lineno: Some(1),
602                            fn_address: Some(std::ptr::null_mut()),
603                            fn_name: Some("foo".into()),
604                        },
605                        FrameInfo {
606                            filename: Some("foo2.rs".into()),
607                            colno: Some(1),
608                            lineno: Some(1),
609                            fn_address: Some(std::ptr::null_mut()),
610                            fn_name: Some("foo2".into()),
611                        },
612                        FrameInfo {
613                            filename: Some("foo3.rs".into()),
614                            colno: Some(1),
615                            lineno: Some(1),
616                            fn_address: Some(std::ptr::null_mut()),
617                            fn_name: Some("foo3".into()),
618                        },
619                    ]),
620                },
621                Allocation {
622                    allocation_size: 1024,
623                    deallocation_size: 0,
624                    address: 0,
625                    stack: VecDeque::from([
626                        FrameInfo {
627                            filename: Some("foo.rs".into()),
628                            colno: Some(1),
629                            lineno: Some(1),
630                            fn_address: Some(std::ptr::null_mut()),
631                            fn_name: Some("foo".into()),
632                        },
633                        FrameInfo {
634                            filename: Some("foo2.rs".into()),
635                            colno: Some(1),
636                            lineno: Some(1),
637                            fn_address: Some(std::ptr::null_mut()),
638                            fn_name: Some("foo2".into()),
639                        },
640                        FrameInfo {
641                            filename: Some("foo3.rs".into()),
642                            colno: Some(1),
643                            lineno: Some(1),
644                            fn_address: Some(std::ptr::null_mut()),
645                            fn_name: Some("foo3".into()),
646                        },
647                        FrameInfo {
648                            filename: Some("foo4.rs".into()),
649                            colno: Some(1),
650                            lineno: Some(1),
651                            fn_address: Some(std::ptr::null_mut()),
652                            fn_name: Some("foo4".into()),
653                        },
654                    ]),
655                },
656            ]),
657        };
658        let tree = stats.into_tree().unwrap();
659
660        assert_eq!(
661            tree,
662            Tree {
663                key: Key {
664                    filename: "foo.rs".to_string(),
665                    colno: 1,
666                    lineno: 1,
667                    fn_address: std::ptr::null_mut(),
668                    fn_name: "foo".to_string(),
669                    file_content: None,
670                },
671                allocation: 1024 * 2,
672                allocation_count: 2,
673                deallocation: 0,
674                deallocation_count: 0,
675                category: Category::Unknown,
676                children: vec![Tree {
677                    key: Key {
678                        filename: "foo2.rs".to_string(),
679                        colno: 1,
680                        lineno: 1,
681                        fn_address: std::ptr::null_mut(),
682                        fn_name: "foo2".to_string(),
683                        file_content: None,
684                    },
685                    allocation: 1024 * 2,
686                    allocation_count: 2,
687                    deallocation: 0,
688                    deallocation_count: 0,
689                    category: Category::Unknown,
690                    children: vec![Tree {
691                        key: Key {
692                            filename: "foo3.rs".to_string(),
693                            colno: 1,
694                            lineno: 1,
695                            fn_address: std::ptr::null_mut(),
696                            fn_name: "foo3".to_string(),
697                            file_content: None,
698                        },
699                        allocation: 1024 * 2,
700                        allocation_count: 2,
701                        deallocation: 0,
702                        deallocation_count: 0,
703                        category: Category::Unknown,
704                        children: vec![Tree {
705                            key: Key {
706                                filename: "foo4.rs".to_string(),
707                                colno: 1,
708                                lineno: 1,
709                                fn_address: std::ptr::null_mut(),
710                                fn_name: "foo4".to_string(),
711                                file_content: None,
712                            },
713                            allocation: 1024,
714                            allocation_count: 1,
715                            deallocation: 0,
716                            deallocation_count: 0,
717                            category: Category::Unknown,
718                            children: vec![],
719                        }],
720                    }],
721                }],
722            }
723        );
724    }
725
726    #[test]
727    fn test_tree_value_4() {
728        let stats = Stats {
729            deallocations: VecDeque::new(),
730            allocations: VecDeque::from([
731                Allocation {
732                    allocation_size: 1024,
733                    deallocation_size: 0,
734                    address: 0,
735                    stack: VecDeque::from([
736                        FrameInfo {
737                            filename: Some("foo.rs".into()),
738                            colno: Some(1),
739                            lineno: Some(1),
740                            fn_address: Some(std::ptr::null_mut()),
741                            fn_name: Some("foo".into()),
742                        },
743                        FrameInfo {
744                            filename: Some("foo2.rs".into()),
745                            colno: Some(1),
746                            lineno: Some(1),
747                            fn_address: Some(std::ptr::null_mut()),
748                            fn_name: Some("foo2".into()),
749                        },
750                        FrameInfo {
751                            filename: Some("foo3.rs".into()),
752                            colno: Some(1),
753                            lineno: Some(1),
754                            fn_address: Some(std::ptr::null_mut()),
755                            fn_name: Some("foo3".into()),
756                        },
757                    ]),
758                },
759                Allocation {
760                    allocation_size: 1024,
761                    deallocation_size: 0,
762                    address: 0,
763                    stack: VecDeque::from([
764                        FrameInfo {
765                            filename: Some("foo.rs".into()),
766                            colno: Some(1),
767                            lineno: Some(1),
768                            fn_address: Some(std::ptr::null_mut()),
769                            fn_name: Some("foo".into()),
770                        },
771                        FrameInfo {
772                            filename: Some("foo2.rs".into()),
773                            colno: Some(1),
774                            lineno: Some(1),
775                            fn_address: Some(std::ptr::null_mut()),
776                            fn_name: Some("foo2".into()),
777                        },
778                        FrameInfo {
779                            filename: Some("foo4.rs".into()),
780                            colno: Some(1),
781                            lineno: Some(1),
782                            fn_address: Some(std::ptr::null_mut()),
783                            fn_name: Some("foo4".into()),
784                        },
785                    ]),
786                },
787            ]),
788        };
789        let tree = stats.into_tree().unwrap();
790
791        assert_eq!(
792            tree,
793            Tree {
794                key: Key {
795                    filename: "foo.rs".to_string(),
796                    colno: 1,
797                    lineno: 1,
798                    fn_address: std::ptr::null_mut(),
799                    fn_name: "foo".to_string(),
800                    file_content: None,
801                },
802                allocation: 1024 * 2,
803                allocation_count: 2,
804                deallocation: 0,
805                deallocation_count: 0,
806                category: Category::Unknown,
807                children: vec![Tree {
808                    key: Key {
809                        filename: "foo2.rs".to_string(),
810                        colno: 1,
811                        lineno: 1,
812                        fn_address: std::ptr::null_mut(),
813                        fn_name: "foo2".to_string(),
814                        file_content: None,
815                    },
816                    allocation: 1024 * 2,
817                    allocation_count: 2,
818                    deallocation: 0,
819                    deallocation_count: 0,
820                    category: Category::Unknown,
821                    children: vec![
822                        Tree {
823                            key: Key {
824                                filename: "foo4.rs".to_string(),
825                                colno: 1,
826                                lineno: 1,
827                                fn_address: std::ptr::null_mut(),
828                                fn_name: "foo4".to_string(),
829                                file_content: None,
830                            },
831                            allocation: 1024,
832                            allocation_count: 1,
833                            deallocation: 0,
834                            deallocation_count: 0,
835                            category: Category::Unknown,
836                            children: vec![],
837                        },
838                        Tree {
839                            key: Key {
840                                filename: "foo3.rs".to_string(),
841                                colno: 1,
842                                lineno: 1,
843                                fn_address: std::ptr::null_mut(),
844                                fn_name: "foo3".to_string(),
845                                file_content: None,
846                            },
847                            allocation: 1024,
848                            allocation_count: 1,
849                            deallocation: 0,
850                            deallocation_count: 0,
851                            category: Category::Unknown,
852                            children: vec![],
853                        }
854                    ],
855                }],
856            }
857        );
858    }
859}