Skip to content

Commit e11ef3d

Browse files
authored
Merge pull request #98 from data-pup/add-diff-summary-pr
Total & remaining rows, and csv output for diff.
2 parents 4da1853 + ea70105 commit e11ef3d

8 files changed

+191
-44
lines changed

analyze/analyze.rs

Lines changed: 108 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#![deny(missing_docs)]
44
#![deny(missing_debug_implementations)]
55

6+
extern crate serde;
67
#[macro_use]
78
extern crate serde_derive;
89
extern crate csv;
@@ -14,8 +15,9 @@ extern crate twiggy_traits as traits;
1415

1516
mod json;
1617

18+
use serde::ser::SerializeStruct;
1719
use std::cmp;
18-
use std::collections::{BTreeMap, BTreeSet};
20+
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
1921
use std::fmt;
2022
use std::io;
2123

@@ -1119,6 +1121,18 @@ impl Ord for DiffEntry {
11191121
}
11201122
}
11211123

1124+
impl serde::Serialize for DiffEntry {
1125+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1126+
where
1127+
S: serde::Serializer,
1128+
{
1129+
let mut state = serializer.serialize_struct("DiffEntry", 2)?;
1130+
state.serialize_field("DeltaBytes", &format!("{:+}", self.delta))?;
1131+
state.serialize_field("Item", &self.name)?;
1132+
state.end()
1133+
}
1134+
}
1135+
11221136
impl traits::Emit for Diff {
11231137
#[cfg(feature = "emit_text")]
11241138
fn emit_text(
@@ -1131,9 +1145,10 @@ impl traits::Emit for Diff {
11311145
(Align::Left, "Item".to_string()),
11321146
]);
11331147

1134-
for entry in &self.deltas {
1135-
table.add_row(vec![format!("{:+}", entry.delta), entry.name.clone()]);
1136-
}
1148+
self.deltas
1149+
.iter()
1150+
.map(|entry| vec![format!("{:+}", entry.delta), entry.name.clone()])
1151+
.for_each(|row| table.add_row(row));
11371152

11381153
write!(dest, "{}", &table)?;
11391154
Ok(())
@@ -1147,7 +1162,7 @@ impl traits::Emit for Diff {
11471162
) -> Result<(), traits::Error> {
11481163
let mut arr = json::array(dest)?;
11491164

1150-
for entry in &self.deltas {
1165+
for entry in self.deltas.iter() {
11511166
let mut obj = arr.object()?;
11521167
obj.field("delta_bytes", entry.delta as f64)?;
11531168
obj.field("name", entry.name.as_str())?;
@@ -1157,8 +1172,15 @@ impl traits::Emit for Diff {
11571172
}
11581173

11591174
#[cfg(feature = "emit_csv")]
1160-
fn emit_csv(&self, _items: &ir::Items, _dest: &mut io::Write) -> Result<(), traits::Error> {
1161-
unimplemented!();
1175+
fn emit_csv(&self, _items: &ir::Items, dest: &mut io::Write) -> Result<(), traits::Error> {
1176+
let mut wtr = csv::Writer::from_writer(dest);
1177+
1178+
for entry in self.deltas.iter() {
1179+
wtr.serialize(entry)?;
1180+
wtr.flush()?;
1181+
}
1182+
1183+
Ok(())
11621184
}
11631185
}
11641186

@@ -1168,48 +1190,92 @@ pub fn diff(
11681190
new_items: &mut ir::Items,
11691191
opts: &opt::Diff,
11701192
) -> Result<Box<traits::Emit>, traits::Error> {
1171-
let old_items_by_name: BTreeMap<&str, &ir::Item> =
1172-
old_items.iter().map(|item| (item.name(), item)).collect();
1173-
let new_items_by_name: BTreeMap<&str, &ir::Item> =
1174-
new_items.iter().map(|item| (item.name(), item)).collect();
1175-
1176-
let mut deltas = vec![];
1177-
1178-
for (name, old_item) in &old_items_by_name {
1179-
match new_items_by_name.get(name) {
1180-
None => deltas.push(DiffEntry {
1181-
name: name.to_string(),
1182-
delta: -(old_item.size() as i64),
1183-
}),
1184-
Some(new_item) => {
1185-
let delta = new_item.size() as i64 - old_item.size() as i64;
1186-
if delta != 0 {
1187-
deltas.push(DiffEntry {
1188-
name: name.to_string(),
1189-
delta,
1190-
});
1191-
}
1192-
}
1193-
}
1193+
let max_items = opts.max_items() as usize;
1194+
1195+
// Given a set of items, create a HashMap of the items' names and sizes.
1196+
fn get_names_and_sizes(items: &ir::Items) -> HashMap<&str, i64> {
1197+
items
1198+
.iter()
1199+
.map(|item| (item.name(), item.size() as i64))
1200+
.collect()
11941201
}
11951202

1196-
for (name, new_item) in &new_items_by_name {
1197-
if !old_items_by_name.contains_key(name) {
1198-
deltas.push(DiffEntry {
1199-
name: name.to_string(),
1200-
delta: new_item.size() as i64,
1201-
});
1203+
// Collect the names and sizes of the items in the old and new collections.
1204+
let old_sizes = get_names_and_sizes(old_items);
1205+
let new_sizes = get_names_and_sizes(new_items);
1206+
1207+
// Given an item name, create a `DiffEntry` object representing the
1208+
// change in size, or an error if the name could not be found in
1209+
// either of the item collections.
1210+
let get_item_delta = |name: String| -> Result<DiffEntry, traits::Error> {
1211+
let old_size = old_sizes.get::<str>(&name);
1212+
let new_size = new_sizes.get::<str>(&name);
1213+
let delta: i64 = match (old_size, new_size) {
1214+
(Some(old_size), Some(new_size)) => new_size - old_size,
1215+
(Some(old_size), None) => -old_size,
1216+
(None, Some(new_size)) => *new_size,
1217+
(None, None) => {
1218+
return Err(traits::Error::with_msg(format!(
1219+
"Could not find item with name `{}`",
1220+
name
1221+
)))
1222+
}
1223+
};
1224+
Ok(DiffEntry { name, delta })
1225+
};
1226+
1227+
// Given a result returned by `get_item_delta`, return false if the result
1228+
// represents an unchanged item. Ignore errors, these are handled separately.
1229+
let unchanged_items_filter = |res: &Result<DiffEntry, traits::Error>| -> bool {
1230+
if let Ok(DiffEntry { name: _, delta: 0 }) = res {
1231+
false
1232+
} else {
1233+
true
12021234
}
1203-
}
1235+
};
12041236

1205-
deltas.push(DiffEntry {
1206-
name: "<total>".to_string(),
1207-
delta: new_items.size() as i64 - old_items.size() as i64,
1208-
});
1237+
// Create a set of item names from the new and old item collections.
1238+
let names = old_sizes
1239+
.keys()
1240+
.chain(new_sizes.keys())
1241+
.map(|k| k.to_string())
1242+
.collect::<HashSet<_>>();
12091243

1244+
// Iterate through the set of item names, and use the closure above to map
1245+
// each item into a `DiffEntry` object. Then, sort the collection.
1246+
let mut deltas = names
1247+
.into_iter()
1248+
.map(get_item_delta)
1249+
.filter(unchanged_items_filter)
1250+
.collect::<Result<Vec<_>, traits::Error>>()?;
12101251
deltas.sort();
1211-
deltas.truncate(opts.max_items() as usize);
12121252

1253+
// Create an entry to summarize the diff rows that will be truncated.
1254+
let (rem_cnt, rem_delta): (u32, i64) = deltas
1255+
.iter()
1256+
.skip(max_items)
1257+
.fold((0, 0), |(cnt, rem_delta), DiffEntry { name: _, delta }| {
1258+
(cnt + 1, rem_delta + delta)
1259+
});
1260+
let remaining = DiffEntry {
1261+
name: format!("... and {} more.", rem_cnt),
1262+
delta: rem_delta,
1263+
};
1264+
1265+
// Create a `DiffEntry` representing the net change, and total row count.
1266+
let total = DiffEntry {
1267+
name: format!("Σ [{} Total Rows]", deltas.len()),
1268+
delta: new_items.size() as i64 - old_items.size() as i64,
1269+
};
1270+
1271+
// Now that the 'remaining' and 'total' summary entries have been created,
1272+
// truncate the vector of deltas before we box up the result, and push
1273+
// the remaining and total rows to the deltas vector.
1274+
deltas.truncate(max_items);
1275+
deltas.push(remaining);
1276+
deltas.push(total);
1277+
1278+
// Return the results so that they can be emitted.
12131279
let diff = Diff { deltas };
12141280
Ok(Box::new(diff) as Box<traits::Emit>)
12151281
}

twiggy/tests/expectations/diff_wee_alloc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
Delta Bytes │ Item
22
─────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
3-
-1476 ┊ <total>
43
-1034 ┊ data[3]
54
-593 ┊ "function names" subsection
65
+395 ┊ wee_alloc::alloc_first_fit::he2a4ddf96981c0ce
@@ -20,3 +19,6 @@
2019
-8 ┊ type[4]
2120
-6 ┊ <wee_alloc::LargeAllocPolicy as wee_alloc::AllocPolicy>::min_cell_size::hc7cee2a550987099
2221
+6 ┊ alloc::alloc::oom::h45ae3f22a516fb04
22+
-5 ┊ <wee_alloc::size_classes::SizeClassAllocPolicy<'a> as wee_alloc::AllocPolicy>::min_cell_size::h6f746be886573355
23+
-21 ┊ ... and 13 more.
24+
-1476 ┊ Σ [33 Total Rows]
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
DeltaBytes,Item
2+
-1034,data[3]
3+
-593,"""function names"" subsection"
4+
+395,wee_alloc::alloc_first_fit::he2a4ddf96981c0ce
5+
+243,goodbye
6+
-225,wee_alloc::alloc_first_fit::h9a72de3af77ef93f
7+
-152,wee_alloc::alloc_with_refill::hb32c1bbce9ebda8e
8+
+145,"<wee_alloc::neighbors::Neighbors<'a, T>>::remove::hc9e5d4284e8233b8"
9+
-136,<wee_alloc::size_classes::SizeClassAllocPolicy<'a> as wee_alloc::AllocPolicy>::new_cell_for_free_list::h3987e3054b8224e6
10+
-76,<wee_alloc::LargeAllocPolicy as wee_alloc::AllocPolicy>::new_cell_for_free_list::h8f071b7bce0301ba
11+
-25,data[1]
12+
-25,data[2]
13+
+15,hello
14+
+15,import env::rust_oom
15+
+12,custom section 'linking'
16+
-12,elem[0]
17+
+8,global[0]
18+
-8,type[4]
19+
-6,<wee_alloc::LargeAllocPolicy as wee_alloc::AllocPolicy>::min_cell_size::hc7cee2a550987099
20+
+6,alloc::alloc::oom::h45ae3f22a516fb04
21+
-5,<wee_alloc::size_classes::SizeClassAllocPolicy<'a> as wee_alloc::AllocPolicy>::min_cell_size::h6f746be886573355
22+
-21,... and 13 more.
23+
-1476,Σ [33 Total Rows]
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
DeltaBytes,Item
2+
-1034,data[3]
3+
-593,"""function names"" subsection"
4+
+395,wee_alloc::alloc_first_fit::he2a4ddf96981c0ce
5+
+243,goodbye
6+
-225,wee_alloc::alloc_first_fit::h9a72de3af77ef93f
7+
-265,... and 28 more.
8+
-1476,Σ [33 Total Rows]
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
[{"delta_bytes":-1476,"name":"<total>"},{"delta_bytes":-1034,"name":"data[3]"},{"delta_bytes":-593,"name":"\"function names\" subsection"},{"delta_bytes":395,"name":"wee_alloc::alloc_first_fit::he2a4ddf96981c0ce"},{"delta_bytes":243,"name":"goodbye"}]
1+
[{"delta_bytes":-1034,"name":"data[3]"},{"delta_bytes":-593,"name":"\"function names\" subsection"},{"delta_bytes":395,"name":"wee_alloc::alloc_first_fit::he2a4ddf96981c0ce"},{"delta_bytes":243,"name":"goodbye"},{"delta_bytes":-225,"name":"wee_alloc::alloc_first_fit::h9a72de3af77ef93f"},{"delta_bytes":-152,"name":"wee_alloc::alloc_with_refill::hb32c1bbce9ebda8e"},{"delta_bytes":145,"name":"<wee_alloc::neighbors::Neighbors<'a, T>>::remove::hc9e5d4284e8233b8"},{"delta_bytes":-136,"name":"<wee_alloc::size_classes::SizeClassAllocPolicy<'a> as wee_alloc::AllocPolicy>::new_cell_for_free_list::h3987e3054b8224e6"},{"delta_bytes":-76,"name":"<wee_alloc::LargeAllocPolicy as wee_alloc::AllocPolicy>::new_cell_for_free_list::h8f071b7bce0301ba"},{"delta_bytes":-25,"name":"data[1]"},{"delta_bytes":-25,"name":"data[2]"},{"delta_bytes":15,"name":"hello"},{"delta_bytes":15,"name":"import env::rust_oom"},{"delta_bytes":12,"name":"custom section 'linking'"},{"delta_bytes":-12,"name":"elem[0]"},{"delta_bytes":8,"name":"global[0]"},{"delta_bytes":-8,"name":"type[4]"},{"delta_bytes":-6,"name":"<wee_alloc::LargeAllocPolicy as wee_alloc::AllocPolicy>::min_cell_size::hc7cee2a550987099"},{"delta_bytes":6,"name":"alloc::alloc::oom::h45ae3f22a516fb04"},{"delta_bytes":-5,"name":"<wee_alloc::size_classes::SizeClassAllocPolicy<'a> as wee_alloc::AllocPolicy>::min_cell_size::h6f746be886573355"},{"delta_bytes":-21,"name":"... and 13 more."},{"delta_bytes":-1476,"name":"Σ [33 Total Rows]"}]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[{"delta_bytes":-1034,"name":"data[3]"},{"delta_bytes":-593,"name":"\"function names\" subsection"},{"delta_bytes":395,"name":"wee_alloc::alloc_first_fit::he2a4ddf96981c0ce"},{"delta_bytes":243,"name":"goodbye"},{"delta_bytes":-225,"name":"wee_alloc::alloc_first_fit::h9a72de3af77ef93f"},{"delta_bytes":-265,"name":"... and 28 more."},{"delta_bytes":-1476,"name":"Σ [33 Total Rows]"}]
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Delta Bytes │ Item
2+
─────────────┼──────────────────────────────────────────────
3+
-1034 ┊ data[3]
4+
-593 ┊ "function names" subsection
5+
+395 ┊ wee_alloc::alloc_first_fit::he2a4ddf96981c0ce
6+
+243 ┊ goodbye
7+
-225 ┊ wee_alloc::alloc_first_fit::h9a72de3af77ef93f
8+
-265 ┊ ... and 28 more.
9+
-1476 ┊ Σ [33 Total Rows]

twiggy/tests/tests.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,17 +352,55 @@ test!(
352352
"./fixtures/wee_alloc.2.wasm"
353353
);
354354

355+
test!(
356+
diff_wee_alloc_top_5,
357+
"diff",
358+
"./fixtures/wee_alloc.wasm",
359+
"./fixtures/wee_alloc.2.wasm",
360+
"-n",
361+
"5"
362+
);
363+
355364
test!(
356365
diff_wee_alloc_json,
357366
"diff",
358367
"./fixtures/wee_alloc.wasm",
359368
"./fixtures/wee_alloc.2.wasm",
360369
"-f",
370+
"json"
371+
);
372+
373+
test!(
374+
diff_wee_alloc_json_top_5,
375+
"diff",
376+
"./fixtures/wee_alloc.wasm",
377+
"./fixtures/wee_alloc.2.wasm",
378+
"-f",
361379
"json",
362380
"-n",
363381
"5"
364382
);
365383

384+
test!(
385+
diff_wee_alloc_csv,
386+
"diff",
387+
"./fixtures/wee_alloc.wasm",
388+
"./fixtures/wee_alloc.2.wasm",
389+
"-f",
390+
"csv"
391+
);
392+
393+
test!(
394+
diff_wee_alloc_csv_top_5,
395+
"diff",
396+
"./fixtures/wee_alloc.wasm",
397+
"./fixtures/wee_alloc.2.wasm",
398+
"-f",
399+
"csv",
400+
"-n",
401+
"5"
402+
);
403+
366404
test!(garbage, "garbage", "./fixtures/garbage.wasm");
367405

368406
test!(

0 commit comments

Comments
 (0)