-
Notifications
You must be signed in to change notification settings - Fork 22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Attempt at UnifiedLog Iterator #26
Conversation
hey @jrouaix, i finally have time to try to improve this library a bit to reduce memory usage. Apologies for the delay I see in ur PR #22 you are also making some changes. I added a function to iterate through the log files in chunks.
Originally the library would parse entire tracev3 file at once. Looping through and parsing and decompressing all data. Then return all log data for the user to then build (build_log) This PR adds an iterator to now parse one gzip compressed chunk at a time From the example projects in the repo: Based on my tests it looks like most of the memory usage is coming from the strings we read and cache at I added an example project in this PR that uses the new iterator approach to parsing log files. |
will do ! |
|
||
let mut count = 0; | ||
for mut chunk in log_iterator { | ||
chunk.oversize.append(&mut oversize_strings.oversize); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feels clunky, you move all oversize_strings to this chunk, and a few lines later you'll move the same lines back (with a few more).
Could it be that you just lend over a &oversize_strings.oversize
, and get only the new missings to append later ?
Hi @puffyCid ! On a standard run on our product, I can see around 200+ MB max memory used (over 900+, more than 20% but it's not all unified logs so it represent a bigger improvement on your lib only). Yet, there is a pretty big perf regression when using this version : real 13m29.036s
user 13m56.268s
sys 1m18.550s previous one : real 9m57.806s
user 11m13.398s
sys 0m45.651s I wonder if can be caused by the |
Thanks for the benchmarks, I'll take a look and troubleshoot the performance |
@jrouaix I made some minor fixes to the Performance for In the example below,
Do you still see a performance regression? |
I'm so sorry @puffyCid, I didn't have much time in the previous week (won't be better in the next ones) |
@puffyCid I have more intel here, yet I can't find where/why it's slightly slower
2 implementation on our product fn parse_file_new(&mut self, full_path: &str) {
let Ok(log_bytes) = fs::read(full_path) else {
return;
};
let log_iterator = macos_unifiedlogs::iterator::UnifiedLogIterator { data: log_bytes, header: Vec::new() };
let mut missing_unified_log_data_vec = UnifiedLogData { header: Vec::new(), catalog_data: Vec::new(), oversize: Vec::new() };
for mut chunk in log_iterator {
chunk.oversize.append(&mut self.oversize_strings);
let (results, mut missing_logs) = build_log(&chunk, &self.string_results, &self.shared_strings_results, &self.timesync_data, true);
self.oversize_strings = chunk.oversize;
self.logs_count += results.len();
self.chuncks_count += 1;
missing_unified_log_data_vec.header.append(&mut missing_logs.header);
missing_unified_log_data_vec.catalog_data.append(&mut missing_logs.catalog_data);
missing_unified_log_data_vec.oversize.append(&mut missing_logs.oversize);
}
self.leftover_logs.push(missing_unified_log_data_vec);
} the old one is fn parse_file(&mut self, full_path: &str) {
let Ok(mut log_data) = parse_log(full_path).map_err(|e| tracing::error!("unified log parse_file error : {e}")) else {
return;
};
log_data.oversize.append(&mut self.oversize_strings);
// Get all constructed logs and any log data that failed to get constructed (exclude_missing = true)
let Ok(iterator) = iter_log(&log_data, &self.string_results, &self.shared_strings_results, &self.timesync_data, true)
.map_err(|e| tracing::warn!("Failed to iterate over logs: {e}"))
else {
return;
};
let mut missing_unified_log_data_vec = UnifiedLogData { header: Vec::new(), catalog_data: Vec::new(), oversize: Vec::new() };
for (results, mut missing_logs) in iterator {
// Track Oversize entries
self.logs_count += results.len();
self.chuncks_count += 1;
missing_unified_log_data_vec.header.append(&mut missing_logs.header);
missing_unified_log_data_vec.catalog_data.append(&mut missing_logs.catalog_data);
missing_unified_log_data_vec.oversize.append(&mut missing_logs.oversize);
}
self.oversize_strings = log_data.oversize;
self.leftover_logs.push(missing_unified_log_data_vec);
} |
@jrouaix let me see if can make some local benchmarks based on ur implementation One interesting observation:
I think in the example I track missing data as a Perhaps appending this data 3 times individually for every iteration (vs once per file in the original implementation) could be the cause of the regression? As mentioned let me try to recreate this locally and see if I can replicate the performance regression |
Another idea, what if u check first for empty values first. If header, catalog_data, and oversize are all empty Do not append to |
@@ -311,13 +311,15 @@ fn parse_trace_file( | |||
} | |||
|
|||
exclude_missing = false; | |||
println!("Oversize cache size: {}", oversize_strings.oversize.len()); | |||
println!("Logs with missing oversize strings: {}", missing_data.len()); | |||
println!("Checking Oversize cache one more time..."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@puffyCid Are these leftovers? Either way I believe it's best to use eprintln!
as per my previous suggestion for separating output and logs.
Basic PR to try to create an iterator for tracev3 files.
Allows caller to iterate through chunk(s) of tracev3. The original implementation of the parser, parsed all chunks at once and returned everything.
Now we offer a way to parse one chunk at a time. With the goal of reducing memory usage a little bit