Conversation
The primary use case of the tool is for the data analysed to be used later on in some other tool (or this one in future). Loading and *parsing* an entire `.json` file to render a chart for a single entry is wasteful and takes too long. This commit implements a new binary format output. Data from the analyses is writen to an SQLite database. As of this commit all analyses reside in the same table and have the format (name, binary_data_blob). The binary_data_blob is generated as the `serde` structure serialised through postcard (a compact and fast binary format). It also changes the `analyse` subcommand arguments defaults to generate only the binary output.
This commit implements a function and CLI for extracting data from the binary output and fixes few naming inconsistencies in related structures
skoudmar
left a comment
There was a problem hiding this comment.
Hi, thanks for the PR.
If you use cargo clippy, the code in the PR will be more uniform with the codebase. I know there are already too many warnings, so let's not add any more, please.
Also, maybe try Copilot, it found the most significant errors:
Found 3 blocking issues in PR #7:
src/analyses/mod.rs:106writes "message_latencies" butsrc/extract/mod.rs:56reads "message_latency" (so latency extraction fails);src/utils/binary_sql_store.rs:24/53/67never clears existing bundles, always INSERTs, then reads with
LIMIT 1(so reruns can return stale data); andsrc/analyses/analysis/dependency_graph.rs:521uses todo!() for missing callback callers (runtime panic path).
@wentasah Can you review and test it after the changes? I have already spent way more time with this PR than I planned. This review is not complete.
| let timers = self.timer_nodes.iter().map(|(k, v)| { | ||
| let n = k.0.lock().unwrap(); | ||
| ActivationDelayExport { | ||
| interface: format!("Timer({})", n.get_period().unwrap_or(0).to_string()), |
There was a problem hiding this comment.
| interface: format!("Timer({})", n.get_period().unwrap_or(0).to_string()), | |
| interface: format!("Timer({})", n.get_period().unwrap_or(0)), |
to_string applied to a type that implements Display in format! args
for further information visit https://rust-lang.github.io/rust-clippy/rust-1.93.0/index.html#to_string_in_format_args
| .map_or(WeakKnown::Unknown, |node_weak| { | ||
| get_node_name_from_weak(&node_weak.get_weak()) | ||
| }) | ||
| .unwrap_or("".to_owned()), |
There was a problem hiding this comment.
| .unwrap_or("".to_owned()), | |
| .unwrap_or(String::new()), |
This is preferred and potentially faster.
| .map_or(WeakKnown::Unknown, |node_weak| { | ||
| get_node_name_from_weak(&node_weak.get_weak()) | ||
| }) | ||
| .unwrap_or("".to_owned()), |
There was a problem hiding this comment.
| .unwrap_or("".to_owned()), | |
| .unwrap_or(String::new()), |
| let n = k.0.lock().unwrap(); | ||
|
|
||
| PublicationDelayExport { | ||
| interface: format!("Publisher({})", n.get_topic().to_string()), |
There was a problem hiding this comment.
| interface: format!("Publisher({})", n.get_topic().to_string()), | |
| interface: format!("Publisher({})", n.get_topic()), |
| .map_or(WeakKnown::Unknown, |node_weak| { | ||
| get_node_name_from_weak(&node_weak.get_weak()) | ||
| }) | ||
| .unwrap_or("".to_owned()), |
There was a problem hiding this comment.
| .unwrap_or("".to_owned()), | |
| .unwrap_or(String::new()), |
| Self::Unknown | Self::Dropped => default, | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
Add this to compile the message_latency suggestion.
| #[inline] | |
| pub fn unwrap_or_else(self, default: impl FnOnce() -> T) -> T { | |
| match self { | |
| Self::Known(value) => value, | |
| Self::Unknown | Self::Dropped => default(), | |
| } | |
| } |
| fn from(value: MessageLatencyStats) -> Self { | ||
| let subscriber = value.subscriber.lock().unwrap(); | ||
| let subscriber_node = subscriber | ||
| .get_node() | ||
| .map(|node| get_node_name_from_weak(&node.get_weak()).unwrap_or("Unknown".to_string())) | ||
| .unwrap_or("Unknown".to_string()); | ||
| let publisher_node = value.publisher.as_ref().map_or_else( | ||
| || "Unknown".to_string(), | ||
| |p| { | ||
| let publisher = p.lock().unwrap().get_node(); | ||
| publisher | ||
| .map(|node| { | ||
| get_node_name_from_weak(&node.get_weak()).unwrap_or("Unknown".to_string()) | ||
| let target_node = value | ||
| .subscriber | ||
| .lock() | ||
| .map(|s| { | ||
| s.get_node().map(|v| { | ||
| get_node_name_from_weak(&v.get_weak()).unwrap_or("Unknown".to_string()) | ||
| }) | ||
| }) | ||
| .map(|v| v.to_string()) | ||
| .unwrap_or("Unknown".into()); | ||
|
|
||
| let source_node = value | ||
| .publisher | ||
| .map(|p| { | ||
| p.lock() | ||
| .map(|s| { | ||
| s.get_node().map(|v| { | ||
| get_node_name_from_weak(&v.get_weak()).unwrap_or("Unknown".to_string()) | ||
| }) | ||
| }) | ||
| .unwrap_or("Unknown".to_string()) | ||
| }, | ||
| ); | ||
| .map(|v| v.to_string()) | ||
| .unwrap_or("Unknown".into()) | ||
| }) | ||
| .unwrap_or("Unknown".into()); | ||
|
|
||
| Self { | ||
| topic: value.topic, | ||
| subscriber_node, | ||
| publisher_node, | ||
| source_node, | ||
| target_node, | ||
| latencies: value.latencies, | ||
| } | ||
| } |
There was a problem hiding this comment.
Use closures for Unknown strings. If the strings are not needed, they are not allocated.
I did use them in some places, so let's unite the design here.
Requires: https://github.com/skoudmar/Ros2TraceAnalyzer/pull/7/changes#r2836312238
| fn from(value: MessageLatencyStats) -> Self { | |
| let subscriber = value.subscriber.lock().unwrap(); | |
| let subscriber_node = subscriber | |
| .get_node() | |
| .map(|node| get_node_name_from_weak(&node.get_weak()).unwrap_or("Unknown".to_string())) | |
| .unwrap_or("Unknown".to_string()); | |
| let publisher_node = value.publisher.as_ref().map_or_else( | |
| || "Unknown".to_string(), | |
| |p| { | |
| let publisher = p.lock().unwrap().get_node(); | |
| publisher | |
| .map(|node| { | |
| get_node_name_from_weak(&node.get_weak()).unwrap_or("Unknown".to_string()) | |
| let target_node = value | |
| .subscriber | |
| .lock() | |
| .map(|s| { | |
| s.get_node().map(|v| { | |
| get_node_name_from_weak(&v.get_weak()).unwrap_or("Unknown".to_string()) | |
| }) | |
| }) | |
| .map(|v| v.to_string()) | |
| .unwrap_or("Unknown".into()); | |
| let source_node = value | |
| .publisher | |
| .map(|p| { | |
| p.lock() | |
| .map(|s| { | |
| s.get_node().map(|v| { | |
| get_node_name_from_weak(&v.get_weak()).unwrap_or("Unknown".to_string()) | |
| }) | |
| }) | |
| .unwrap_or("Unknown".to_string()) | |
| }, | |
| ); | |
| .map(|v| v.to_string()) | |
| .unwrap_or("Unknown".into()) | |
| }) | |
| .unwrap_or("Unknown".into()); | |
| Self { | |
| topic: value.topic, | |
| subscriber_node, | |
| publisher_node, | |
| source_node, | |
| target_node, | |
| latencies: value.latencies, | |
| } | |
| } | |
| fn from(value: MessageLatencyStats) -> Self { | |
| let target_node = value | |
| .subscriber | |
| .lock() | |
| .map(|s| { | |
| s.get_node().map(|v| { | |
| get_node_name_from_weak(&v.get_weak()).unwrap_or_else(|| "Unknown".to_string()) | |
| }) | |
| }) | |
| .map_or_else(|_| "Unknown".to_string(), |v| v.to_string()); | |
| let source_node = value | |
| .publisher | |
| .and_then(|p| { | |
| p.lock() | |
| .map(|s| { | |
| s.get_node().map(|v| { | |
| get_node_name_from_weak(&v.get_weak()) | |
| .unwrap_or_else(|| "Unknown".to_string()) | |
| }) | |
| }) | |
| .ok() | |
| .map(|v| v.to_string()) | |
| }) | |
| .unwrap_or_else(|| "Unknown".to_string()); | |
| Self { | |
| topic: value.topic, | |
| source_node, | |
| target_node, | |
| latencies: value.latencies, | |
| } | |
| } |
src/utils/binary_sql_store.rs
Outdated
| sqlite_connection | ||
| .execute("DROP TABLE IF EXISTS blobs", ()) | ||
| .map_err(|e| BinarySQLStoreError::SQLiteError(e))?; |
There was a problem hiding this comment.
Not sure why are you deleting table from new file that is empty. If you expect that something else accessed the table between the creation of the database file and now, the initialization should be done in a transaction to prevent race conditions. If nothing will access it then this code is redundant.
| ), | ||
| v => v.to_string(), | ||
| }, | ||
| None => todo!(), |
There was a problem hiding this comment.
Did you forget to implement it or if it should panic use panic!
Definitely. But it will be a bit slower as I'm on vacation next week. I should have some time at least some evenings. |
|
Thank you for all the feedback, I will start working on the fixes tomorrow. I'm sorry for taking this much of your time, if you don't manage to write a detailed description for each change, don't worry, just a say what you'd like to get fixed in general if you can, thanks. I expect there to be only one other major PR, so there is no rush. Some of the issues you mentioned (namely the analysis name inconsistencies) were fixed in later commits, which I did not / forgot to include here, but the code as a whole is tested and works (to the best of my knowledge), just not these partial PRs. |
wentasah
left a comment
There was a problem hiding this comment.
So far just a few comments about UX. Try to address them and I'll then go deeper.
src/argsv2/analysis_args.rs
Outdated
|
|
||
| /// Output formats to save the analyses as | ||
| /// | ||
| /// Defaults to only 'bundle' |
There was a problem hiding this comment.
--help says confusingly:
Defaults to only 'bundle'
[default: binary]
So what is the default?
src/argsv2/analysis_args.rs
Outdated
| /// | ||
| /// Defaults to only 'bundle' | ||
| #[arg(long, short = 'f', value_delimiter = ',', default_values_t = vec![OutputFormat::Binary])] | ||
| output_format: Vec<OutputFormat>, |
There was a problem hiding this comment.
The value is a vector, but from the doc string, it's not clear what the individual values mean and how to specify them. I guess the doc should mention "This option can be specified multiple times" (but see below).
From the code, it looks like this option acts as a filter to not write certain files. This is quite weird user interface. I guess users don't care about the formats but about the analyses, which they can already enable individually.
I think you just want a switch to say whether to use the new "bundle" format or the old formats. Can you think of a use case, where one wants both formats at the same time?
src/argsv2/analysis_args.rs
Outdated
| pub const REAL_UTILIZATION: &str = "real_utilization.txt"; | ||
| pub const SPIN_DURATION: &str = "spin_duration.json"; | ||
|
|
||
| pub const BINARY_BUNDLE: &str = "binary_bundle.sqlite"; |
There was a problem hiding this comment.
This name is not much meaningful to users. It describes an implementation detail - it's binary. What about calling it r2ta_results.sqlite?
src/argsv2/extract_args.rs
Outdated
| use clap::{Args, ValueEnum, ValueHint}; | ||
| use derive_more::Display; | ||
|
|
||
| const DEFAULT_BUNDLE_NAME: &str = "binary_bundle.sqlite"; |
There was a problem hiding this comment.
It would be better not to duplicate the default from analysis_args.rs.
src/argsv2/extract_args.rs
Outdated
| use derive_more::Display; | ||
|
|
||
| const DEFAULT_BUNDLE_NAME: &str = "binary_bundle.sqlite"; | ||
| const DEFAULT_OUTPUT_NAME: &str = "extract_data.json"; |
There was a problem hiding this comment.
Is is necessary to have the default output name? I guess the extract command will be called mainly by the GUI and it can always supply some value (or even read it from stdout).
src/argsv2/extract_args.rs
Outdated
| pub struct ExtractArgs { | ||
| /// Identifier of the element for which to extract the data | ||
| /// | ||
| /// - For nodes (graphviz nodes) the namespace (ROS node), type (ROS interface) and parameters (ROS topic) need to be specified |
There was a problem hiding this comment.
Wrap the text to reasonable width. Separate the paragraph from the next with an empty line (otherwise --help prints both on a single line.
Give an example, in which format these things are specified, because it's not clear from your description.
Also, namespace and ROS node name are different things. Clarify what you mean.
src/argsv2/extract_args.rs
Outdated
| /// - For nodes (graphviz nodes) the namespace (ROS node), type (ROS interface) and parameters (ROS topic) need to be specified | ||
| /// - For edges (graphviz edges) name (type + topic) of the source and target node should be provided | ||
| /// | ||
| /// The expected format is URL encoded map |
There was a problem hiding this comment.
Correct would be URL-encoded, but I don't know which kind of "map" do you have in mind so more information (and an example) is needed.
src/argsv2/extract_args.rs
Outdated
| CallbackDuration, | ||
| /// Delays between callback or timer activations | ||
| #[display("Delays between activations")] | ||
| ActivationsDelay, |
There was a problem hiding this comment.
| ActivationsDelay, | |
| ActivationDelays, |
(this is for --help output)
src/argsv2/extract_args.rs
Outdated
| /// The expected format is URL encoded map | ||
| element_id: String, | ||
|
|
||
| /// The property to extract from the node |
There was a problem hiding this comment.
| /// The property to extract from the node | |
| /// The property to extract from the node. |
README.md
Outdated
| ## Analyze | ||
| This command analyzes the traces and saves relevant information for later use into JSON, TXT and DOT files. | ||
|
|
||
| <!-- `$ cargo run analyze -h | sed 's/ \[default:/\n \[default:/g'` --> |
There was a problem hiding this comment.
| <!-- `$ cargo run analyze -h | sed 's/ \[default:/\n \[default:/g'` --> | |
| <!-- `$ cargo run analyze --help | sed 's/ \[default:/\n \[default:/g'` --> |
Please keep the full, detailed --help output instead of a summary with -h.
src/argsv2/analysis_args.rs
Outdated
| /// Flag whether to bundle all outputs into a single file or not | ||
| /// | ||
| /// Defaults to only 'bundle' | ||
| #[arg(long, short = 'f', value_delimiter = ',', default_values_t = vec![OutputFormat::Binary])] | ||
| output_format: Vec<OutputFormat>, | ||
| /// Defaults to only `true` | ||
| #[arg(long, default_value = "true")] | ||
| bundle_output: bool, |
There was a problem hiding this comment.
Clap has a default action ArgAction::SetTrue:
- If this flag is not provided, it is
true(the default value) - If this flag is provided, it is set to
true(by the default action)
So this can never be false.
Note: Changing the action to something like set to false is an antipattern. A better approach is to rename it to no_bundle_output or a similar name and invert the logic.
There was a problem hiding this comment.
Even better would be --output-separate-files or something similar.
Fixed impossible bundle_output flag for the analyze subcommand
|
It seems that the "bundle" format uses two different binary formats. One is "sqlite" and the second is "postcard". sqlite is used only for what was previously a separate file, and postcard for the content of the file. The division between the two seems a bit arbitrary. If the trace is huge, extracting the data for a single figure (let's say message latency) would currently require deserializing message latencies of all messages and then throwing all of them but one. Wouldn't it be better to have separate sqlite rows for latencies of different messages so that extract will deserialize just what's needed? And similarly for other types of data. |
|
I did consider that as an option. It will take a bit more manual |
This commit changes how the data is stored inside the database. Before it stored each analyssed property as row with all the data in a binary blob, now it stores each property in its own table and each element as a row
|
The discussion does not give me the impression of thorough research or a plan. So I would like to get some things clarified.
If you have already decided to use SQLite because you think it is the best option:
|
|
I believe there was enough research, but to answer your questions:
I'm in no way decided for SQLite, while I think it is a decent option here, there certainly may be better options (
|
This commit simplifies the identification of nodes during data extraction from output bundle by using the dependency graph serial ids instead of full name. This also restrucutred the SQLite schema by adding new columns to the tables. All properties stored in the bundled output are obtained from the dependency graph analysis. The dependency graph DOT file is now included in the bundled SQLite file and can be extracted through the CLI.
wentasah
left a comment
There was a problem hiding this comment.
Few more comments related to UI.
src/argsv2/analysis_args.rs
Outdated
|
|
||
| /// Flag whether to bundle all outputs into a single file or not | ||
| /// | ||
| /// Defaults to only `true` |
There was a problem hiding this comment.
Why only? And this can be removed as clap prints default values automatically, where it makes sense.
src/argsv2/analysis_args.rs
Outdated
| /// | ||
| /// Defaults to only `true` | ||
| #[arg(long)] | ||
| no_bundle_output: bool, |
There was a problem hiding this comment.
I still think that non-negative name (such as --legacy-output-files or --separate-output-files would be a better name.
README.md
Outdated
| ## Viewer | ||
| This command is reserved for later use. Builtin .dot graphs viewer. | ||
|
|
||
| <!-- `$ cargo run viewer --help | sed 's/ \[default:/\n \[default:/g'` --> |
There was a problem hiding this comment.
| <!-- `$ cargo run viewer --help | sed 's/ \[default:/\n \[default:/g'` --> | |
| <!-- `$ cargo run viewer --help` --> |
The sed command seems to add second empty line before each default. I don't understand why it's needed. I think one empty line is sufficient.
README.md
Outdated
|
|
||
| <!-- `$ cargo run analyze --help | sed 's/ \[default:/\n \[default:/g'` --> | ||
| ``` | ||
| Analyze a ROS 2 trace and generate graphs, JSON or bundle outputs |
There was a problem hiding this comment.
| Analyze a ROS 2 trace and generate graphs, JSON or bundle outputs | |
| Analyze a ROS 2 trace and store the result either as a binary bundle or separate files. See the extract subcommand for how to work with the binary bundle. |
README.md
Outdated
| --binary-bundle [<FILENAME>] | ||
| Filename or directory of the binary bundle output |
There was a problem hiding this comment.
Can it really be a directory? If yes, then the metavar should be something like FILENAME_OR_DIRECTORY. But I think it can be just a filename (either absolute or relative). If it's relative, it's relative to OUT_DIR.
README.md
Outdated
| -i, --input-path <INPUT> | ||
| The input path, either a file of the data or a folder containing the default named file with the necessary data |
There was a problem hiding this comment.
Which kind of input is this? Should that be the r2ta_results.sqlite file? Or something else?
README.md
Outdated
| ``` | ||
|
|
||
| ## Extract | ||
| This command retreives data from binary analysis output for the specified ROS interface or channel |
There was a problem hiding this comment.
| This command retreives data from binary analysis output for the specified ROS interface or channel | |
| This command retrieves various data from the "binary bundle" produced by the analysis subcommand. |
README.md
Outdated
|
|
||
| <!-- `$ cargo run extract --help | sed 's/ \[default:/\n \[default:/g'` --> | ||
| ``` | ||
| Retreive data from bundled analysis results file into JSON format |
There was a problem hiding this comment.
Don't mention JSON as graph is probably in dot format. Or I prefer it to be.
README.md
Outdated
| Options: | ||
| -i, --input-path <INPUT> The input path, either a file of the data or a folder containing the default named file with the necessary data | ||
| -v, --verbose... Increase logging verbosity | ||
| -o, --output-path <OUTPUT> The output path, either a folder to which the file will be generated or a file to write into |
There was a problem hiding this comment.
Why to distinguish between files and filters again? I'd prefer just -o, --output <FILE>. If this is not specified, the output would go to stdout.
README.md
Outdated
| help Print this message or the help of the given subcommand(s) | ||
|
|
||
| Options: | ||
| -i, --input-path <INPUT> The input path, either a file of the data or a folder containing the default named file with the necessary data |
There was a problem hiding this comment.
| -i, --input-path <INPUT> The input path, either a file of the data or a folder containing the default named file with the necessary data | |
| -i, --input <FILE> The input binary bundle. [default: r2ta_results.sqlite] |
This commit addresses several issues related to the CLI
src/argsv2/analysis_args.rs
Outdated
| /// Flag whether to bundle all outputs into a single file or not | ||
| /// | ||
| /// Defaults to only `true` | ||
| /// Flag whether to bundle all outputs into a single file or export each analysis as a separate file |
There was a problem hiding this comment.
| /// Flag whether to bundle all outputs into a single file or export each analysis as a separate file | |
| /// Store the results into multiple files rather than to the binary bundle |
README.md
Outdated
| -o, --output-path <OUTPUT> The output path, either a folder to which the file will be generated or a file to write into | ||
| -q, --quiet... Decrease logging verbosity | ||
| -h, --help Print help | ||
| -i, --input-path <FILENAME> Path to the r2ta_results.sqlite file from which to retreive the data [default: r2ta_results.sqlite] |
There was a problem hiding this comment.
Outdated. Needs to be regenerated.
src/argsv2/chart_args.rs
Outdated
| /// The output path, either a folder to which the file will be generated or a file to write into | ||
| #[clap(long, short = 'o', value_name = "OUTPUT", value_hint = ValueHint::AnyPath)] | ||
| #[clap(long, short = 'o', value_name = "FILENAME", value_hint = ValueHint::AnyPath)] | ||
| output_path: Option<PathBuf>, |
There was a problem hiding this comment.
| output_path: Option<PathBuf>, | |
| output: Option<PathBuf>, |
(to be consistent with input)
| #[clap(long, short = 'i', value_name = "FILENAME", value_hint = ValueHint::FilePath, default_value = analysis_args::filenames::BINARY_BUNDLE)] | ||
| input: Option<PathBuf>, | ||
|
|
||
| /// The output path, either a folder to which the file will be generated or a file to write into |
There was a problem hiding this comment.
The doc comment should stay here. Perhaps something like: "Store the extracted data to the given FILENAME"
src/argsv2/mod.rs
Outdated
| #[display("viewer")] | ||
| Viewer(viewer_args::ViewerArgs), | ||
|
|
||
| /// Retreive data from bundled analysis results file into JSON format |
There was a problem hiding this comment.
| /// Retreive data from bundled analysis results file into JSON format | |
| /// Retrieve data from binary bundle produced by the analysis |
src/analyses/mod.rs
Outdated
| store.write_into( | ||
| "metadata", | ||
| "(version, graph)", | ||
| [(1, dot_graph.to_string())].into_iter(), |
There was a problem hiding this comment.
Version 1 should be defined somewhere else so that extract and other commands can access it.
It would be better if the version is actually stored in binary_sql_store.rs and used just internally. See other comments.
There was a problem hiding this comment.
I agree and I would prefer to not expose any implementation details (like structure or table/column names) of the SQLite tables outside of the sql store. If it needs to be exposed use rust typing system instead of strings (structs, enums, ...).
src/analyses/mod.rs
Outdated
| AnalysisProperty::MessageLatencies.table_name(), | ||
| &[ | ||
| "id int", | ||
| "source_node text", | ||
| "destination_node text", | ||
| "topic text", | ||
| "data blob", | ||
| ], | ||
| )?; | ||
| store.write_into( | ||
| AnalysisProperty::MessageLatencies.table_name(), | ||
| "(id, source_node, destination_node, topic, data)", | ||
| a.message_latencies(&dot_graph).iter().map(|m| { | ||
| ( | ||
| m.id, | ||
| &m.name.source_node, | ||
| &m.name.destination_node, | ||
| &m.name.topic, | ||
| postcard::to_allocvec(&m.messages_latencies).unwrap(), | ||
| ) | ||
| }), | ||
| 5, |
There was a problem hiding this comment.
The definition of the "binary store" format is split at multiple places. Here, in extract and I don't know where else. It would be better if all code related to the actual format is stored at one place, perhaps in binary_sql_store.rs. This would provide typed interface for writing and reading data. This way, it would be easier to maintain store version numbers correctly, i.e. incrementing it when something changes in the format and checking, which read operations are compatible with which versions.
| store | ||
| .read::<CallbackDurationExport>( | ||
| property.table_name(), | ||
| "id, data, interface, node", | ||
| "id = ?1", | ||
| (element_id,), | ||
| ) | ||
| .map_err(DataExtractionError::SourceDataParseError)? | ||
| .callback_durations, |
There was a problem hiding this comment.
As noted elsewhere, this would be better moved into binary_sql_store.rs. You should also add a check for store version number compatibility to detect attempts to read from old (or newer) format versions. For now, the version check could produce just a warning about version mismatch. Failing immediately is probably neither good nor necessary.
This commit adds proper versioning to the output files and aggregates all code related to the structure of the output file in a single place.
src/analyses/mod.rs
Outdated
| store.write_into( | ||
| "metadata", | ||
| "(version, graph)", | ||
| [(1, dot_graph.to_string())].into_iter(), |
There was a problem hiding this comment.
I agree and I would prefer to not expose any implementation details (like structure or table/column names) of the SQLite tables outside of the sql store. If it needs to be exposed use rust typing system instead of strings (structs, enums, ...).
src/extract/mod.rs
Outdated
| } | ||
|
|
||
| pub fn extract_graph(input: &Path) -> color_eyre::eyre::Result<String> { | ||
| let store = BinarySqlStoreV1::from_file(input, false)?; |
There was a problem hiding this comment.
Do not create the sql store file here if it does not exist. rusqlite::Connection::open creates missing files.
| input: &Path, | ||
| element_id: i64, | ||
| property: &AnalysisProperty, | ||
| ) -> color_eyre::eyre::Result<ChartableData> { |
There was a problem hiding this comment.
Do not create the sql store file here if it does not exist. rusqlite::Connection::open creates missing files.
src/argsv2/extract_args.rs
Outdated
| /// Path to the r2ta_results.sqlite file from which to retreive the data | ||
| #[clap(long, short = 'i', value_name = "FILENAME", value_hint = ValueHint::FilePath, default_value = super::analysis_args::filenames::BINARY_BUNDLE)] | ||
| input: Option<PathBuf>, |
There was a problem hiding this comment.
Is it only filename or also directory path? ExtractArgs::input_path suggests that it can also be a directory.
There was a problem hiding this comment.
| /// Path to the r2ta_results.sqlite file from which to retreive the data | |
| #[clap(long, short = 'i', value_name = "FILENAME", value_hint = ValueHint::FilePath, default_value = super::analysis_args::filenames::BINARY_BUNDLE)] | |
| input: Option<PathBuf>, | |
| /// Path to the `r2ta_results.sqlite` file from which to retrieve the data | |
| #[clap(long, short = 'i', value_name = "FILENAME", value_hint = ValueHint::FilePath, default_value = super::analysis_args::filenames::BINARY_BUNDLE)] | |
| input: Option<PathBuf>, |
src/analyses/mod.rs
Outdated
| if args.bundle_output() | ||
| && let Some(path) = args.binary_bundle_path() | ||
| { | ||
| let mut store = BinarySqlStoreV1::from_file(&path, true)?; |
There was a problem hiding this comment.
Move this inside the following if because it is not used elsewhere.
src/utils/binary_sql_store/mod.rs
Outdated
|
|
||
| if reusing_file { | ||
| if clear { | ||
| store.clear()?; |
There was a problem hiding this comment.
Does this clear and refresh the metadata or does this just delete them?
Changed the way missing and existing .sqlite result files are handled during creation
wentasah
left a comment
There was a problem hiding this comment.
Comments based on our off-line discussion.
| publisher_node: String, | ||
| latencies: Vec<i64>, | ||
| #[derive(Debug, Serialize, Deserialize)] | ||
| pub struct MessageLatencyExport { |
There was a problem hiding this comment.
This has the same name as struct MessageLatencyExport in dependency_graph.rs. It's probably not a good idea.
| #[derive(Debug, Serialize, Deserialize)] | ||
| pub struct MessageLatencyExport { | ||
| pub topic: String, | ||
| pub source_node: String, |
There was a problem hiding this comment.
Add a comment explaining a change from subscriber/publisher to source/destination. Something like
| pub source_node: String, | |
| pub source_node: String, // typically publisher or service client/server? or timer? |
| let spin_durations: Vec<SpinDurationEntry> = self | ||
| .processing_durations | ||
| .iter() | ||
| .map(|(node, durations)| SpinDurationEntry { | ||
| node: node.0.lock().unwrap().get_full_name().unwrap().to_owned(), | ||
| spin_duration: durations.clone(), | ||
| }) | ||
| .collect(); | ||
|
|
||
| serde_json::to_writer(file, &spin_durations) | ||
| serde_json::to_writer(file, &self.export()) |
There was a problem hiding this comment.
Not needed to separate body to other function. Revert it back.
| let latencies: Vec<ExportEntry> = self | ||
| .latencies | ||
| .iter() | ||
| .map(|(callback, latencies)| ExportEntry { | ||
| topic: callback | ||
| .0 | ||
| .lock() | ||
| .unwrap() | ||
| .get_caller() | ||
| .unwrap() | ||
| .get_caller_as_string() | ||
| .unwrap(), | ||
| latencies: latencies.clone(), | ||
| }) | ||
| .collect(); | ||
|
|
||
| serde_json::to_writer(file, &latencies) | ||
| serde_json::to_writer(file, &self.export_latencies()) | ||
| } |
| if args.bundle_output() | ||
| && let Some(path) = args.binary_bundle_path() | ||
| { | ||
| if let Some(a) = &self.dependency_graph { |
There was a problem hiding this comment.
| if let Some(a) = &self.dependency_graph { | |
| if let Some(graph) = &self.dependency_graph { |
|
|
||
| store.insert( | ||
| BinarySqlStoreV1Table::Property(AnalysisProperty::MessageLatencies), | ||
| a.message_latencies(&dot_graph).iter().map(|m| { |
There was a problem hiding this comment.
I'd prefer something like:
| a.message_latencies(&dot_graph).iter().map(|m| { | |
| graph.message_latencies(&dot_graph.edge_ids).iter().map(|m| { |
| let from = dot_graph.node_to_id[&k.source()]; | ||
| let to = dot_graph.node_to_id[&k.target()]; | ||
|
|
||
| let edge_id = dot_graph | ||
| .edges | ||
| .iter() | ||
| .enumerate() | ||
| .find(|(_, e)| e.source == from && e.target == to); |
There was a problem hiding this comment.
Don't calculate this multiple times, move it to display_as_dot or DisplayAsDot::new.
|
|
||
| fn metadata_table(&self) -> Self::Table; | ||
|
|
||
| fn tables(&self) -> &HashMap<Self::Table, SqlTable>; |
There was a problem hiding this comment.
| fn tables(&self) -> &HashMap<Self::Table, SqlTable>; | |
| fn tables(table: Self::Table) -> SqlTable; |
| } | ||
| } | ||
|
|
||
| pub trait BinarySqlStoreBase: Sized { |
There was a problem hiding this comment.
Does this need to be separate from BinarySqlStore?
| impl FromRow for ActivationDelayExport { | ||
| fn from_row(row: &rusqlite::Row) -> Result<Self, rusqlite::Error> { | ||
| Ok(ActivationDelayExport { | ||
| id: row.get("id")?, | ||
| name: RosInterfaceCompleteName { | ||
| interface: row.get("interface")?, | ||
| node: row.get("node")?, | ||
| }, | ||
| activation_delays: postcard::from_bytes(&row.get::<_, Vec<_>>("data")?) | ||
| .expect("Data must be a serialised list of integers"), | ||
| }) | ||
| } | ||
| } |
There was a problem hiding this comment.
Don't define this here, keep the database structure solely in binary_sql_store. It may not be generic at all. Conversion to row can be done directly in store.insert_actiovation_delay() or similar.


The primary use case of the tool is for the data analysed to be used later on in some other tool (or this one in future). Loading and parsing an entire
.jsonfile to render a chart for a single entry is wasteful and takes too long.This PR implements a new binary format output. Data from the analyses is written to an SQLite database. As of this commit, all analyses reside in the same table and have the format
(name, binary_data_blob). The binary blob is generated as theserdestructure serialised through postcard (a compact and fast binary format).