Skip to content

Commit b170b5f

Browse files
feat(trace): Add trace formatter
Moved the trace formatter tool which we used for prettifying Noir traces.
1 parent 4789737 commit b170b5f

File tree

9 files changed

+240
-1
lines changed

9 files changed

+240
-1
lines changed

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
[workspace]
22
resolver = "2"
3-
members = ["runtime_tracing", "runtime_tracing_cli"]
3+
members = ["runtime_tracing", "runtime_tracing_cli", "trace_formatter"]
44

55
[workspace.dependencies]
66
runtime_tracing = { path = "runtime_tracing/" }
7+
trace_formatter = { path = "trace_formatter/"}

runtime_tracing_cli/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,6 @@ edition = "2021"
88
[dependencies]
99
clap = { version = "4.5.40", features = ["derive"] }
1010
runtime_tracing.workspace = true
11+
trace_formatter.workspace = true
12+
serde = { version = "1.0", features = ["derive"] }
13+
serde_json = "1.0"
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
use clap::Args;
2+
use serde_json::Value;
3+
use trace_formatter::{
4+
prettify::{correct_path, prettify_value},
5+
read_write_json::{save_to_file, serialize_file},
6+
};
7+
8+
#[derive(Debug, Clone, Args)]
9+
pub(crate) struct FmtTraceCommand {
10+
/// Trace file which we want to format
11+
source_file: String,
12+
13+
/// Path where the formatted trace will be saved
14+
target_file: String,
15+
}
16+
17+
pub(crate) fn run(args: FmtTraceCommand) {
18+
let ser_json: Value = serialize_file(args.source_file);
19+
20+
let prettified_json: String = prettify_value(ser_json, "", false);
21+
let final_pretty_json: String = correct_path(&prettified_json);
22+
23+
save_to_file(args.target_file, final_pretty_json);
24+
}

runtime_tracing_cli/src/main.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ use std::path::Path;
33
use clap::{Args, Parser, Subcommand};
44
use runtime_tracing::{TraceEventsFileFormat, Tracer};
55

6+
use crate::fmt_trace_cmd::FmtTraceCommand;
7+
mod fmt_trace_cmd;
8+
69
#[derive(Debug, Clone, Args)]
710
struct ConvertCommand {
811
input_file: String,
@@ -14,6 +17,8 @@ struct ConvertCommand {
1417
enum RuntimeTracingCliCommand {
1518
/// Convert from one trace file format to another
1619
Convert(ConvertCommand),
20+
/// Format a trace which is in JSON file format
21+
FormatTrace(FmtTraceCommand),
1722
}
1823

1924
#[derive(Parser, Debug)]
@@ -43,6 +48,9 @@ fn main() {
4348
let mut trace = Tracer::new("", &[]);
4449
trace.load_trace_events(Path::new(&convert_command.input_file), input_file_format).unwrap();
4550
trace.store_trace_events(Path::new(&convert_command.output_file), output_file_format).unwrap();
51+
},
52+
RuntimeTracingCliCommand::FormatTrace(fmt_trace_cmd) => {
53+
fmt_trace_cmd::run(fmt_trace_cmd);
4654
}
4755
}
4856
}

trace_formatter/Cargo.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
name = "trace_formatter"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
serde = "1.0"
8+
serde_json = { version = "1.0", features = ["preserve_order"] }
9+
regex = "1.10.5"
10+
11+
[lib]
12+
name = "trace_formatter"
13+
path = "src/lib.rs"

trace_formatter/README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Format-trace-tool
2+
## Overview
3+
This tool is used for formatting json files, especially ones that are generated from the command:
4+
```bash
5+
nargo trace
6+
```
7+
## Usage
8+
You need to provide two arguments, first being the source file containing the json, second is the destination file name.
9+
Example:
10+
```bash
11+
cargo run src.json des.json
12+
```
13+
This will generate a file in the current directory named "des.json" containing the output of our program.
14+
### Trace formatting example
15+
Input:
16+
```json
17+
[{"a":1},{"b":"bbb"},{"c":{"f1":3,"f2":"0"}}]
18+
```
19+
Output:
20+
```json
21+
[
22+
{ "a": 1 },
23+
{ "b": "bbb" },
24+
{ "c": { "f1": 3, "f2": "0" } }
25+
]
26+
```

trace_formatter/src/lib.rs

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
pub mod prettify;
2+
pub mod read_write_json;
3+
4+
#[cfg(test)]
5+
mod tests {
6+
use crate::prettify::correct_path;
7+
8+
use super::*;
9+
fn generate_pretty_json(input_json: &str) -> String {
10+
let ser_json = serde_json::from_str(input_json).expect("Failed to parse the json input");
11+
let prettified_json: String = prettify::prettify_value(ser_json, "", false);
12+
let mut final_pretty_json: String = correct_path(&prettified_json);
13+
final_pretty_json.push('\n'); //this is done automatically when saving the json to a file
14+
final_pretty_json
15+
}
16+
17+
#[test]
18+
fn test_single_json_object() {
19+
let input_json = r#"[{"Key":"val"}]"#;
20+
let expected = r#"[
21+
{ "Key": "val" }
22+
]
23+
"#;
24+
let final_pretty_json = generate_pretty_json(input_json);
25+
assert_eq!(final_pretty_json, expected);
26+
}
27+
28+
#[test]
29+
fn test_non_absolute_path_json() {
30+
let input_json = r#"[{"Path":"?"},{"Path":"src/dir/main.nr"}]"#;
31+
let expected = r#"[
32+
{ "Path": "?" },
33+
{ "Path": "src/dir/main.nr" }
34+
]
35+
"#;
36+
let final_pretty_json = generate_pretty_json(input_json);
37+
assert_eq!(final_pretty_json, expected);
38+
}
39+
40+
#[test]
41+
fn test_absolute_path_json() {
42+
let input_json = r#"[{"Path":"?"},{"Path":"some/absolute/path/src/dir/main.nr"}]"#;
43+
let expected = r#"[
44+
{ "Path": "?" },
45+
{ "Path": "<relative-to-this>/src/dir/main.nr" }
46+
]
47+
"#;
48+
let final_pretty_json = generate_pretty_json(input_json);
49+
assert_eq!(final_pretty_json, expected);
50+
}
51+
52+
#[test]
53+
fn test_basic_nested_array_json() {
54+
let input_json = r#"[{"arr":[{"nested_arr":[{"key":"val"}]}]}]"#;
55+
let expected = r#"[
56+
{ "arr": [
57+
{ "nested_arr": [
58+
{ "key": "val" }
59+
] }
60+
] }
61+
]
62+
"#;
63+
let final_pretty_json = generate_pretty_json(input_json);
64+
assert_eq!(final_pretty_json, expected);
65+
}
66+
67+
#[test]
68+
fn test_basic_nested_json_objects() {
69+
let input_json = r#"[{"key":{"inner_key1":"inner_value1","inner_key2":"inner_value2"}}]"#;
70+
let expected = r#"[
71+
{ "key": { "inner_key1": "inner_value1", "inner_key2": "inner_value2" } }
72+
]
73+
"#;
74+
let final_pretty_json = generate_pretty_json(input_json);
75+
assert_eq!(final_pretty_json, expected);
76+
}
77+
78+
#[test]
79+
fn test_arrays_nested_objects_full_json() {
80+
let input_json = r#"[{"a":"111"},{"b":[]},{"c":[{"arr":"arr1", "abb" : 1},"#.to_string()
81+
+ r#"{"arr":"arr2","abb" : 2},{"arr":"arr3","abb" : 3}]},{"long":"a1","along1":"a2"},"#
82+
+ r#"{ "Value": { "variable_id": 0, "value": { "kind": "Int", "i": 4, "type_id": 1 } } }]"#;
83+
84+
let expected = r#"[
85+
{ "a": "111" },
86+
{ "b": [] },
87+
{ "c": [
88+
{ "arr": "arr1", "abb": 1 },
89+
{ "arr": "arr2", "abb": 2 },
90+
{ "arr": "arr3", "abb": 3 }
91+
] },
92+
{ "long": "a1", "along1": "a2" },
93+
{ "Value": { "variable_id": 0, "value": { "kind": "Int", "i": 4, "type_id": 1 } } }
94+
]
95+
"#;
96+
let final_pretty_json = generate_pretty_json(&input_json);
97+
assert_eq!(final_pretty_json, expected);
98+
}
99+
}

trace_formatter/src/prettify.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
use regex::Regex;
2+
use serde_json::Value;
3+
4+
pub fn prettify_value(root_value: Value, indent: &str, is_in_array: bool) -> String {
5+
let content = match root_value {
6+
Value::Array(elements) => {
7+
let new_indent = indent.to_string() + " ";
8+
let parts: Vec<String> = elements
9+
.into_iter()
10+
.map(|el| prettify_value(el, new_indent.as_str(), true))
11+
.collect::<Vec<String>>();
12+
if parts.is_empty() {
13+
"[]".to_string()
14+
} else {
15+
let lines = parts.join(",\n");
16+
format!("[\n{lines}\n{indent}]")
17+
}
18+
}
19+
Value::Object(map) => {
20+
let parts: Vec<String> = map
21+
.into_iter()
22+
.map(|(k, v)| {
23+
let head = format!("\"{k}\"");
24+
let rest = prettify_value(v, indent, false);
25+
format!("{head}: {rest}")
26+
})
27+
.collect();
28+
let json_object_string = parts.join(", ");
29+
format!("{{ {json_object_string} }}")
30+
}
31+
_ => root_value.to_string(),
32+
};
33+
let indent = if is_in_array { indent } else { "" };
34+
format!("{indent}{content}")
35+
}
36+
37+
/// Replaces all absolute paths in the trace with relative paths
38+
pub fn correct_path(pretty_json: &str) -> String {
39+
let re = Regex::new(r#" \{ "Path": (?<abs_path>.*)(?<rel_path>/src/.*)"#);
40+
let result = re.map(|regex| {
41+
regex.replace_all(pretty_json, |caps: &regex::Captures| {
42+
format!(" {{ \"Path\": \"<relative-to-this>{}", &caps["rel_path"])
43+
})
44+
});
45+
result.map(|result_string| result_string.into_owned()).unwrap_or(pretty_json.to_string())
46+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
use serde_json::Value;
2+
use std::fs;
3+
4+
pub fn serialize_file(src_filename: String) -> Value {
5+
let file_content = fs::read_to_string(src_filename).expect("Failed to read the file");
6+
7+
serde_json::from_str(&file_content)
8+
.expect("Failed to parse the json file that was given as a source")
9+
}
10+
11+
pub fn save_to_file(dest_filename: String, json_string: String) {
12+
let mut json_string_copy = json_string.clone();
13+
if !json_string_copy.ends_with('\n') {
14+
json_string_copy.push('\n');
15+
}
16+
fs::write(&dest_filename, json_string_copy).unwrap_or_else(|_| {
17+
panic!("Unable to write to destination file: {}", dest_filename.as_str())
18+
});
19+
}

0 commit comments

Comments
 (0)