More updates to report types

This commit is contained in:
Luke Street 2024-08-18 13:42:41 -06:00
parent a733a950a3
commit faebddbc5e
4 changed files with 228 additions and 170 deletions

View File

@ -2,84 +2,134 @@ syntax = "proto3";
package objdiff.report; package objdiff.report;
message Report { // Progress info for a report or unit
message Measures {
// Overall match percent, including partially matched functions and data
float fuzzy_match_percent = 1; float fuzzy_match_percent = 1;
// Total size of code in bytes
uint64 total_code = 2; uint64 total_code = 2;
// Fully matched code size in bytes
uint64 matched_code = 3; uint64 matched_code = 3;
// Fully matched code percent
float matched_code_percent = 4; float matched_code_percent = 4;
// Total size of data in bytes
uint64 total_data = 5; uint64 total_data = 5;
// Fully matched data size in bytes
uint64 matched_data = 6; uint64 matched_data = 6;
// Fully matched data percent
float matched_data_percent = 7; float matched_data_percent = 7;
// Total number of functions
uint32 total_functions = 8; uint32 total_functions = 8;
// Fully matched functions
uint32 matched_functions = 9; uint32 matched_functions = 9;
// Fully matched functions percent
float matched_functions_percent = 10; float matched_functions_percent = 10;
repeated ReportUnit units = 11;
} }
// Project progress report
message Report {
// Overall progress info
Measures measures = 1;
// Units within this report
repeated ReportUnit units = 2;
}
// A unit of the report (usually a translation unit)
message ReportUnit { message ReportUnit {
// The name of the unit
string name = 1; string name = 1;
float fuzzy_match_percent = 2; // Progress info for this unit
uint64 total_code = 3; Measures measures = 2;
uint64 matched_code = 4; // Sections within this unit
uint64 total_data = 5; repeated ReportItem sections = 3;
uint64 matched_data = 6; // Functions within this unit
uint32 total_functions = 7; repeated ReportItem functions = 4;
uint32 matched_functions = 8; // Extra metadata for this unit
optional bool complete = 9; optional ReportUnitMetadata metadata = 5;
optional string module_name = 10;
optional uint32 module_id = 11;
repeated ReportItem sections = 12;
repeated ReportItem functions = 13;
} }
// Extra metadata for a unit
message ReportUnitMetadata {
// Whether this unit is marked as complete (or "linked")
optional bool complete = 1;
// The name of the module this unit belongs to
optional string module_name = 2;
// The ID of the module this unit belongs to
optional uint32 module_id = 3;
// The path to the source file of this unit
optional string source_path = 4;
}
// A section or function within a unit
message ReportItem { message ReportItem {
// The name of the item
string name = 1; string name = 1;
// The size of the item in bytes
uint64 size = 2; uint64 size = 2;
// The overall match percent for this item
float fuzzy_match_percent = 3; float fuzzy_match_percent = 3;
optional string demangled_name = 4; // Extra metadata for this item
optional uint64 address = 5; optional ReportItemMetadata metadata = 4;
} }
// Used as stdin for the changes command // Extra metadata for an item
message ReportItemMetadata {
// The demangled name of the function
optional string demangled_name = 1;
// The virtual address of the function or section
optional uint64 virtual_address = 2;
}
// A pair of reports to compare and generate changes
message ChangesInput { message ChangesInput {
// The previous report
Report from = 1; Report from = 1;
// The current report
Report to = 2; Report to = 2;
} }
// Changes between two reports
message Changes { message Changes {
ChangeInfo from = 1; // The progress info for the previous report
ChangeInfo to = 2; Measures from = 1;
// The progress info for the current report
Measures to = 2;
// Units that changed
repeated ChangeUnit units = 3; repeated ChangeUnit units = 3;
} }
message ChangeInfo { // A changed unit
float fuzzy_match_percent = 1;
uint64 total_code = 2;
uint64 matched_code = 3;
float matched_code_percent = 4;
uint64 total_data = 5;
uint64 matched_data = 6;
float matched_data_percent = 7;
uint32 total_functions = 8;
uint32 matched_functions = 9;
float matched_functions_percent = 10;
}
message ChangeUnit { message ChangeUnit {
// The name of the unit
string name = 1; string name = 1;
optional ChangeInfo from = 2; // The previous progress info (omitted if new)
optional ChangeInfo to = 3; optional Measures from = 2;
// The current progress info (omitted if removed)
optional Measures to = 3;
// Sections that changed
repeated ChangeItem sections = 4; repeated ChangeItem sections = 4;
// Functions that changed
repeated ChangeItem functions = 5; repeated ChangeItem functions = 5;
// Extra metadata for this unit
optional ReportUnitMetadata metadata = 6;
} }
// A changed section or function
message ChangeItem { message ChangeItem {
// The name of the item
string name = 1; string name = 1;
// The previous progress info (omitted if new)
optional ChangeItemInfo from = 2; optional ChangeItemInfo from = 2;
// The current progress info (omitted if removed)
optional ChangeItemInfo to = 3; optional ChangeItemInfo to = 3;
// Extra metadata for this item
optional ReportItemMetadata metadata = 4;
} }
// Progress info for a section or function
message ChangeItemInfo { message ChangeItemInfo {
// The overall match percent for this item
float fuzzy_match_percent = 1; float fuzzy_match_percent = 1;
// The size of the item in bytes
uint64 size = 2; uint64 size = 2;
} }

View File

@ -19,8 +19,8 @@ use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator};
use tracing::{info, warn}; use tracing::{info, warn};
use crate::util::report::{ use crate::util::report::{
ChangeInfo, ChangeItem, ChangeItemInfo, ChangeUnit, Changes, ChangesInput, Report, ReportItem, ChangeItem, ChangeItemInfo, ChangeUnit, Changes, ChangesInput, Measures, Report, ReportItem,
ReportUnit, ReportItemMetadata, ReportUnit, ReportUnitMetadata,
}; };
#[derive(FromArgs, PartialEq, Debug)] #[derive(FromArgs, PartialEq, Debug)]
@ -90,7 +90,7 @@ impl OutputFormat {
fn from_str(s: &str) -> Result<Self> { fn from_str(s: &str) -> Result<Self> {
match s { match s {
"json" => Ok(Self::Json), "json" => Ok(Self::Json),
"binpb" | "proto" | "protobuf" => Ok(Self::Proto), "binpb" | "pb" | "proto" | "protobuf" => Ok(Self::Proto),
_ => bail!("Invalid output format: {}", s), _ => bail!("Invalid output format: {}", s),
} }
} }
@ -117,7 +117,7 @@ fn generate(args: GenerateArgs) -> Result<()> {
); );
let start = Instant::now(); let start = Instant::now();
let mut report = Report::default(); let mut units = vec![];
let mut existing_functions: HashSet<String> = HashSet::new(); let mut existing_functions: HashSet<String> = HashSet::new();
if args.deduplicate { if args.deduplicate {
// If deduplicating, we need to run single-threaded // If deduplicating, we need to run single-threaded
@ -129,11 +129,11 @@ fn generate(args: GenerateArgs) -> Result<()> {
project.base_dir.as_deref(), project.base_dir.as_deref(),
Some(&mut existing_functions), Some(&mut existing_functions),
)? { )? {
report.units.push(unit); units.push(unit);
} }
} }
} else { } else {
let units = project let vec = project
.objects .objects
.par_iter_mut() .par_iter_mut()
.map(|object| { .map(|object| {
@ -146,38 +146,10 @@ fn generate(args: GenerateArgs) -> Result<()> {
) )
}) })
.collect::<Result<Vec<Option<ReportUnit>>>>()?; .collect::<Result<Vec<Option<ReportUnit>>>>()?;
report.units = units.into_iter().flatten().collect(); units = vec.into_iter().flatten().collect();
} }
for unit in &report.units { let measures = units.iter().flat_map(|u| u.measures.into_iter()).collect();
report.fuzzy_match_percent += unit.fuzzy_match_percent * unit.total_code as f32; let report = Report { measures: Some(measures), units };
report.total_code += unit.total_code;
report.matched_code += unit.matched_code;
report.total_data += unit.total_data;
report.matched_data += unit.matched_data;
report.total_functions += unit.total_functions;
report.matched_functions += unit.matched_functions;
}
if report.total_code == 0 {
report.fuzzy_match_percent = 100.0;
} else {
report.fuzzy_match_percent /= report.total_code as f32;
}
report.matched_code_percent = if report.total_code == 0 {
100.0
} else {
report.matched_code as f32 / report.total_code as f32 * 100.0
};
report.matched_data_percent = if report.total_data == 0 {
100.0
} else {
report.matched_data as f32 / report.total_data as f32 * 100.0
};
report.matched_functions_percent = if report.total_functions == 0 {
100.0
} else {
report.matched_functions as f32 / report.total_functions as f32 * 100.0
};
let duration = start.elapsed(); let duration = start.elapsed();
info!("Report generated in {}.{:03}s", duration.as_secs(), duration.subsec_millis()); info!("Report generated in {}.{:03}s", duration.as_secs(), duration.subsec_millis());
write_output(&report, args.output.as_deref(), output_format)?; write_output(&report, args.output.as_deref(), output_format)?;
@ -258,18 +230,21 @@ fn report_object(
}) })
.transpose()?; .transpose()?;
let result = diff::diff_objs(&config, target.as_ref(), base.as_ref(), None)?; let result = diff::diff_objs(&config, target.as_ref(), base.as_ref(), None)?;
let mut unit = ReportUnit {
name: object.name().to_string(), let metadata = ReportUnitMetadata {
complete: object.complete, complete: object.complete,
module_name: target module_name: target
.as_ref() .as_ref()
.and_then(|o| o.split_meta.as_ref()) .and_then(|o| o.split_meta.as_ref())
.and_then(|m| m.module_name.clone()), .and_then(|m| m.module_name.clone()),
module_id: target.as_ref().and_then(|o| o.split_meta.as_ref()).and_then(|m| m.module_id), module_id: target.as_ref().and_then(|o| o.split_meta.as_ref()).and_then(|m| m.module_id),
..Default::default() source_path: None, // TODO
}; };
let obj = target.as_ref().or(base.as_ref()).unwrap(); let mut measures = Measures::default();
let mut sections = vec![];
let mut functions = vec![];
let obj = target.as_ref().or(base.as_ref()).unwrap();
let obj_diff = result.left.as_ref().or(result.right.as_ref()).unwrap(); let obj_diff = result.left.as_ref().or(result.right.as_ref()).unwrap();
for (section, section_diff) in obj.sections.iter().zip(&obj_diff.sections) { for (section, section_diff) in obj.sections.iter().zip(&obj_diff.sections) {
let section_match_percent = section_diff.match_percent.unwrap_or_else(|| { let section_match_percent = section_diff.match_percent.unwrap_or_else(|| {
@ -281,19 +256,21 @@ fn report_object(
0.0 0.0
} }
}); });
unit.sections.push(ReportItem { sections.push(ReportItem {
name: section.name.clone(), name: section.name.clone(),
demangled_name: None,
fuzzy_match_percent: section_match_percent, fuzzy_match_percent: section_match_percent,
size: section.size, size: section.size,
address: section.virtual_address, metadata: Some(ReportItemMetadata {
demangled_name: None,
virtual_address: section.virtual_address,
}),
}); });
match section.kind { match section.kind {
ObjSectionKind::Data | ObjSectionKind::Bss => { ObjSectionKind::Data | ObjSectionKind::Bss => {
unit.total_data += section.size; measures.total_data += section.size;
if section_match_percent == 100.0 { if section_match_percent == 100.0 {
unit.matched_data += section.size; measures.matched_data += section.size;
} }
continue; continue;
} }
@ -321,76 +298,35 @@ fn report_object(
0.0 0.0
} }
}); });
unit.fuzzy_match_percent += match_percent * symbol.size as f32; measures.fuzzy_match_percent += match_percent * symbol.size as f32;
unit.total_code += symbol.size; measures.total_code += symbol.size;
if match_percent == 100.0 { if match_percent == 100.0 {
unit.matched_code += symbol.size; measures.matched_code += symbol.size;
} }
unit.functions.push(ReportItem { functions.push(ReportItem {
name: symbol.name.clone(), name: symbol.name.clone(),
demangled_name: symbol.demangled_name.clone(),
size: symbol.size, size: symbol.size,
fuzzy_match_percent: match_percent, fuzzy_match_percent: match_percent,
address: symbol.virtual_address, metadata: Some(ReportItemMetadata {
demangled_name: symbol.demangled_name.clone(),
virtual_address: symbol.virtual_address,
}),
}); });
if match_percent == 100.0 { if match_percent == 100.0 {
unit.matched_functions += 1; measures.matched_functions += 1;
} }
unit.total_functions += 1; measures.total_functions += 1;
}
}
if unit.total_code == 0 {
unit.fuzzy_match_percent = 100.0;
} else {
unit.fuzzy_match_percent /= unit.total_code as f32;
}
Ok(Some(unit))
}
impl From<&Report> for ChangeInfo {
fn from(report: &Report) -> Self {
Self {
fuzzy_match_percent: report.fuzzy_match_percent,
total_code: report.total_code,
matched_code: report.matched_code,
matched_code_percent: report.matched_code_percent,
total_data: report.total_data,
matched_data: report.matched_data,
matched_data_percent: report.matched_data_percent,
total_functions: report.total_functions,
matched_functions: report.matched_functions,
matched_functions_percent: report.matched_functions_percent,
}
}
}
impl From<&ReportUnit> for ChangeInfo {
fn from(value: &ReportUnit) -> Self {
Self {
fuzzy_match_percent: value.fuzzy_match_percent,
total_code: value.total_code,
matched_code: value.matched_code,
matched_code_percent: if value.total_code == 0 {
100.0
} else {
value.matched_code as f32 / value.total_code as f32 * 100.0
},
total_data: value.total_data,
matched_data: value.matched_data,
matched_data_percent: if value.total_data == 0 {
100.0
} else {
value.matched_data as f32 / value.total_data as f32 * 100.0
},
total_functions: value.total_functions,
matched_functions: value.matched_functions,
matched_functions_percent: if value.total_functions == 0 {
100.0
} else {
value.matched_functions as f32 / value.total_functions as f32 * 100.0
},
} }
} }
measures.calc_fuzzy_match_percent();
measures.calc_matched_percent();
Ok(Some(ReportUnit {
name: object.name().to_string(),
measures: Some(measures),
sections,
functions,
metadata: Some(metadata),
}))
} }
impl From<&ReportItem> for ChangeItemInfo { impl From<&ReportItem> for ChangeItemInfo {
@ -417,25 +353,25 @@ fn changes(args: ChangesArgs) -> Result<()> {
let current = read_report(&args.current)?; let current = read_report(&args.current)?;
(previous, current) (previous, current)
}; };
let mut changes = Changes { let mut changes = Changes { from: previous.measures, to: current.measures, units: vec![] };
from: Some(ChangeInfo::from(&previous)),
to: Some(ChangeInfo::from(&current)),
units: vec![],
};
for prev_unit in &previous.units { for prev_unit in &previous.units {
let curr_unit = current.units.iter().find(|u| u.name == prev_unit.name); let curr_unit = current.units.iter().find(|u| u.name == prev_unit.name);
let sections = process_items(prev_unit, curr_unit, |u| &u.sections); let sections = process_items(prev_unit, curr_unit, |u| &u.sections);
let functions = process_items(prev_unit, curr_unit, |u| &u.functions); let functions = process_items(prev_unit, curr_unit, |u| &u.functions);
let prev_unit_info = ChangeInfo::from(prev_unit); let prev_measures = prev_unit.measures;
let curr_unit_info = curr_unit.map(ChangeInfo::from); let curr_measures = curr_unit.and_then(|u| u.measures);
if !functions.is_empty() || !matches!(&curr_unit_info, Some(v) if v == &prev_unit_info) { if !functions.is_empty() || prev_measures != curr_measures {
changes.units.push(ChangeUnit { changes.units.push(ChangeUnit {
name: prev_unit.name.clone(), name: prev_unit.name.clone(),
from: Some(prev_unit_info), from: prev_measures,
to: curr_unit_info, to: curr_measures,
sections, sections,
functions, functions,
metadata: curr_unit
.as_ref()
.and_then(|u| u.metadata.clone())
.or_else(|| prev_unit.metadata.clone()),
}); });
} }
} }
@ -444,9 +380,10 @@ fn changes(args: ChangesArgs) -> Result<()> {
changes.units.push(ChangeUnit { changes.units.push(ChangeUnit {
name: curr_unit.name.clone(), name: curr_unit.name.clone(),
from: None, from: None,
to: Some(ChangeInfo::from(curr_unit)), to: curr_unit.measures,
sections: process_new_items(&curr_unit.sections), sections: process_new_items(&curr_unit.sections),
functions: process_new_items(&curr_unit.functions), functions: process_new_items(&curr_unit.functions),
metadata: curr_unit.metadata.clone(),
}); });
} }
} }
@ -473,6 +410,7 @@ fn process_items<F: Fn(&ReportUnit) -> &Vec<ReportItem>>(
name: prev_func.name.clone(), name: prev_func.name.clone(),
from: Some(prev_func_info), from: Some(prev_func_info),
to: Some(curr_func_info), to: Some(curr_func_info),
metadata: curr_func.as_ref().unwrap().metadata.clone(),
}); });
} }
} else { } else {
@ -480,6 +418,7 @@ fn process_items<F: Fn(&ReportUnit) -> &Vec<ReportItem>>(
name: prev_func.name.clone(), name: prev_func.name.clone(),
from: Some(prev_func_info), from: Some(prev_func_info),
to: None, to: None,
metadata: prev_func.metadata.clone(),
}); });
} }
} }
@ -489,6 +428,7 @@ fn process_items<F: Fn(&ReportUnit) -> &Vec<ReportItem>>(
name: curr_func.name.clone(), name: curr_func.name.clone(),
from: None, from: None,
to: Some(ChangeItemInfo::from(curr_func)), to: Some(ChangeItemInfo::from(curr_func)),
metadata: curr_func.metadata.clone(),
}); });
} }
} }
@ -498,6 +438,7 @@ fn process_items<F: Fn(&ReportUnit) -> &Vec<ReportItem>>(
name: prev_func.name.clone(), name: prev_func.name.clone(),
from: Some(ChangeItemInfo::from(prev_func)), from: Some(ChangeItemInfo::from(prev_func)),
to: None, to: None,
metadata: prev_func.metadata.clone(),
}); });
} }
} }
@ -507,7 +448,12 @@ fn process_items<F: Fn(&ReportUnit) -> &Vec<ReportItem>>(
fn process_new_items(items: &[ReportItem]) -> Vec<ChangeItem> { fn process_new_items(items: &[ReportItem]) -> Vec<ChangeItem> {
items items
.iter() .iter()
.map(|f| ChangeItem { name: f.name.clone(), from: None, to: Some(ChangeItemInfo::from(f)) }) .map(|item| ChangeItem {
name: item.name.clone(),
from: None,
to: Some(ChangeItemInfo::from(item)),
metadata: item.metadata.clone(),
})
.collect() .collect()
} }

View File

@ -39,6 +39,56 @@ impl Report {
} }
} }
impl Measures {
/// Average the fuzzy match percentage over total code bytes.
pub fn calc_fuzzy_match_percent(&mut self) {
if self.total_code == 0 {
self.fuzzy_match_percent = 100.0;
} else {
self.fuzzy_match_percent /= self.total_code as f32;
}
}
/// Calculate the percentage of matched code, data, and functions.
pub fn calc_matched_percent(&mut self) {
self.matched_code_percent = if self.total_code == 0 {
100.0
} else {
self.matched_code as f32 / self.total_code as f32 * 100.0
};
self.matched_data_percent = if self.total_data == 0 {
100.0
} else {
self.matched_data as f32 / self.total_data as f32 * 100.0
};
self.matched_functions_percent = if self.total_functions == 0 {
100.0
} else {
self.matched_functions as f32 / self.total_functions as f32 * 100.0
};
}
}
/// Allows [collect](Iterator::collect) to be used on an iterator of [Measures].
impl FromIterator<Measures> for Measures {
fn from_iter<T>(iter: T) -> Self
where T: IntoIterator<Item = Measures> {
let mut measures = Measures::default();
for other in iter {
measures.fuzzy_match_percent += other.fuzzy_match_percent * other.total_code as f32;
measures.total_code += other.total_code;
measures.matched_code += other.matched_code;
measures.total_data += other.total_data;
measures.matched_data += other.matched_data;
measures.total_functions += other.total_functions;
measures.matched_functions += other.matched_functions;
}
measures.calc_fuzzy_match_percent();
measures.calc_matched_percent();
measures
}
}
// Older JSON report types // Older JSON report types
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
struct LegacyReport { struct LegacyReport {
@ -58,16 +108,18 @@ struct LegacyReport {
impl From<LegacyReport> for Report { impl From<LegacyReport> for Report {
fn from(value: LegacyReport) -> Self { fn from(value: LegacyReport) -> Self {
Self { Self {
fuzzy_match_percent: value.fuzzy_match_percent, measures: Some(Measures {
total_code: value.total_code, fuzzy_match_percent: value.fuzzy_match_percent,
matched_code: value.matched_code, total_code: value.total_code,
matched_code_percent: value.matched_code_percent, matched_code: value.matched_code,
total_data: value.total_data, matched_code_percent: value.matched_code_percent,
matched_data: value.matched_data, total_data: value.total_data,
matched_data_percent: value.matched_data_percent, matched_data: value.matched_data,
total_functions: value.total_functions, matched_data_percent: value.matched_data_percent,
matched_functions: value.matched_functions, total_functions: value.total_functions,
matched_functions_percent: value.matched_functions_percent, matched_functions: value.matched_functions,
matched_functions_percent: value.matched_functions_percent,
}),
units: value.units.into_iter().map(ReportUnit::from).collect(), units: value.units.into_iter().map(ReportUnit::from).collect(),
} }
} }
@ -95,8 +147,7 @@ struct LegacyReportUnit {
impl From<LegacyReportUnit> for ReportUnit { impl From<LegacyReportUnit> for ReportUnit {
fn from(value: LegacyReportUnit) -> Self { fn from(value: LegacyReportUnit) -> Self {
Self { let mut measures = Measures {
name: value.name.clone(),
fuzzy_match_percent: value.fuzzy_match_percent, fuzzy_match_percent: value.fuzzy_match_percent,
total_code: value.total_code, total_code: value.total_code,
matched_code: value.matched_code, matched_code: value.matched_code,
@ -104,11 +155,20 @@ impl From<LegacyReportUnit> for ReportUnit {
matched_data: value.matched_data, matched_data: value.matched_data,
total_functions: value.total_functions, total_functions: value.total_functions,
matched_functions: value.matched_functions, matched_functions: value.matched_functions,
complete: value.complete, ..Default::default()
module_name: value.module_name.clone(), };
module_id: value.module_id, measures.calc_matched_percent();
Self {
name: value.name.clone(),
measures: Some(measures),
sections: value.sections.into_iter().map(ReportItem::from).collect(), sections: value.sections.into_iter().map(ReportItem::from).collect(),
functions: value.functions.into_iter().map(ReportItem::from).collect(), functions: value.functions.into_iter().map(ReportItem::from).collect(),
metadata: Some(ReportUnitMetadata {
complete: value.complete,
module_name: value.module_name.clone(),
module_id: value.module_id,
..Default::default()
}),
} }
} }
} }
@ -133,10 +193,12 @@ impl From<LegacyReportItem> for ReportItem {
fn from(value: LegacyReportItem) -> Self { fn from(value: LegacyReportItem) -> Self {
Self { Self {
name: value.name, name: value.name,
demangled_name: value.demangled_name,
address: value.address,
size: value.size, size: value.size,
fuzzy_match_percent: value.fuzzy_match_percent, fuzzy_match_percent: value.fuzzy_match_percent,
metadata: Some(ReportItemMetadata {
demangled_name: value.demangled_name,
virtual_address: value.address,
}),
} }
} }
} }