From ac2ec47af47c92a45bd0ab08d468376fb0cb8067 Mon Sep 17 00:00:00 2001 From: Gwen Lg Date: Fri, 8 Aug 2025 18:05:07 +0200 Subject: [PATCH 01/13] add a test to check PDF4 frame deserialization added a corresponding puffin file --- puffin/src/profile_view.rs | 12 ++++++++++++ puffin/tests/data/capture_PFD4.puffin | Bin 0 -> 6893 bytes 2 files changed, 12 insertions(+) create mode 100644 puffin/tests/data/capture_PFD4.puffin diff --git a/puffin/src/profile_view.rs b/puffin/src/profile_view.rs index 21999b48..e1b4ca84 100644 --- a/puffin/src/profile_view.rs +++ b/puffin/src/profile_view.rs @@ -441,3 +441,15 @@ impl FrameStats { self.total_ram_used = 0; } } + +#[cfg(all(test, feature = "serialization"))] +mod tests { + use super::FrameView; + + #[test] + fn read_pfd4_file() -> anyhow::Result<()> { + let mut file = std::fs::File::open("tests/data/capture_PFD4.puffin")?; + let _ = FrameView::read(&mut file)?; + Ok(()) + } +} diff --git a/puffin/tests/data/capture_PFD4.puffin b/puffin/tests/data/capture_PFD4.puffin new file mode 100644 index 0000000000000000000000000000000000000000..572a68558fa49b1d295dfc5c00aeb413bdea80ac GIT binary patch literal 6893 zcma*rc~lff9tZHA0TC2jL?JMu%m~Oa3JfuA zcmN`ahDfr8ji@1l7$vJO9vhFWiC1FOhyoH9RID{s)y?ZFd-iqzGjHBpzwf7h)jhxN zSZ#zyY{X=5ONL=AK;+(ifkXB;c!2k`>q60!Xn6*FL#Bfd&y+G5NIyEX+YG*Ln6co4 z%xGsBe7$yh%*f)4v#rh2moC*QGxIW(fl7_P9mB|(HO_MQft~B)?HQQ`dLhh}MTZ415|LTsO zxMk-|k;!EPa?(>%C4BbXe(x}_qO}+^fk)2#iO9Rypaxc#HNl5xO1WNd(7j1Yfj5Bg zo*fp+dtW)YsgQSffCu!-pqHv?!oK0irRVIi*j>y<2>9nzHS25B!3{*`MWT(AxWjQTHua2fVYn`FVHz+G50Dt8)FBm?C zt6NX80eJit7A+C8xnr4w0HYx=*J@Ytm_@QGN8HoyY7RryBrvZB3NSFMb`r!i{;+N# z#?)=-ftbug1E->}6vhg~hJ<4S7!>O~Ln7vIRgI$n<4IsjE*;`A507qhLYM*0J2(tg zQxsEZ4l$K|U$B_h${2{5(3^}?QMT>9-g z)SPa+!?vdKLOs;1-P(*}Q<@PVt=9x2z{K*F6p5OKK0|kb#*fgrw3_gmiWx~7q&W~Z zpVLq^MKxK;gP}%eF`U)>a>@#7?m9T**p$W=?Cd>*5unN3=>v(H3w;YT0u4D~^bK9e zYYL}q^G2GajURCus-~!>`HwWHS=qgm)f7Jb9%_2--@&md%}9`5>537+Xkk*OM2+u^ za&Li#oFC4+5oa`&=~zFcS?A@>X{ef_n)w|SP*d`thSjwE>;*N}--qMalx7t0j7Y%< zut#xbDXXa$JEhs!Yy95Uc#&hk%PGG7y$N#xyWx%xbD3UxkHb(U3CuIS(@^hmpFsFm z&&cHPM>N-hSyM;W5@<@PTaCk0COdFy#W_p?bHdBBC3Yw|=vSeMoCXF)J?b~bx?K4( zG5~d`z9x!8Q8fva{&T^Zyoazu?K`?y&4%`iP;=eo9*#|El;HfH-53FOc09_Ls7akZ zEkMvAavTV+isLno`Q77@COZE)r=eI3pJ6~XS15_2M3_0>_`KSO=(nMi1rCa zfY1GJu3$BX#r9jVYtMLrhMWi5m-O+PVyh)VNRwcj!D*Vh)&T@6-$Ez9C9LP?aJjj51#J|Mx3IbDmV^R zli=w858a*xdvmU06+7NI{Q3xDO1xg;)D*@Z7%Tj+0hG1ct&_OZ3`_S03oztBu<~>@ zj|uKB4MUj7ds{gSRg=KzKW={5RRuMEO^4Z?_KAB4)MOUK;n~5k$r9y5DOy<$HU1kbSq;;tmfvH(NMFZ zz#GS=G-JSU)fa-U19%*qSd1Rc?i`9j?b&_hQ_e%xBs}`h1EtoG)9~du?lMOc1-p}$nut?V7-vwMo`4M? z(?+*T;$DkC_F2^17;kbW7#nqz$81&|i$<76)8ib5s!3q`u8^dZ#TzqS>}rZlb~bnK590Tz#cTqAL#DXpHw2sGq0(9_n%YwE3Y;*jR6 zk$Ieks!3?{Z?rATAZCR2CA-mVpWcNS-@0y`n!>0-ouUI9KfDu@Xiw4TL8yg;{Y)($s5h{95sv0ziwT5JG|6ILCP zxX;2JPsa-|<^K7fwUf6xC?jzJ;3nHJm18 z%ip1UbCIBEY&iqyq)Ym%T_BOW_0^loSMRTfRy`BumN0+-qFZnM6U-l z**&ub81ml0JtM$l%6z{|M3~2p=ef>MH3`iBIcjWQ!|Q=tcMRvS*8}o^7m#zZ+2o%n zF2&J+Rn@z&0%U%ZeS+nP-XRRWd?8W5A?JXJF;{rbl$gf3h|`#Kg5yv%ML7jFm*GzP zdHa9Z=5%>hL(Or=1{|Bxc!Jvj`4|C$8wNE=)O7CqdhXjAA94_QZPm(azOFP#LYk`C g12_#;Q&eN<*a9`9U;K;J7)KRB&E&3+aBQsk6UeJR!T Date: Wed, 13 Aug 2025 17:18:45 +0200 Subject: [PATCH 02/13] add a test to check PFD3 frame deserialization added a corresponding puffin file --- puffin/src/profile_view.rs | 7 +++++++ puffin/tests/data/capture_PFD3.puffin | Bin 0 -> 8407 bytes 2 files changed, 7 insertions(+) create mode 100644 puffin/tests/data/capture_PFD3.puffin diff --git a/puffin/src/profile_view.rs b/puffin/src/profile_view.rs index e1b4ca84..d9f8b71e 100644 --- a/puffin/src/profile_view.rs +++ b/puffin/src/profile_view.rs @@ -452,4 +452,11 @@ mod tests { let _ = FrameView::read(&mut file)?; Ok(()) } + + #[test] + fn read_pfd3_file() -> anyhow::Result<()> { + let mut file = std::fs::File::open("tests/data/capture_PFD3.puffin")?; + let _ = FrameView::read(&mut file)?; + Ok(()) + } } diff --git a/puffin/tests/data/capture_PFD3.puffin b/puffin/tests/data/capture_PFD3.puffin new file mode 100644 index 0000000000000000000000000000000000000000..772a44c7553c4395e36966795f8dc594176caef1 GIT binary patch literal 8407 zcma*rc~DeG9>DPj0Tkp80$v0JW$@q_Fe-w|A%`cTMg<~*pxgs^pmIbUM2!k^C@QP6 zXv&SN*^LW`l!=O2jT+5DMKlqQwUkYX7tvJ~{UuBHbX!|?ulda%)D-pV^@rc9>F+cV z^Fll$LV~^fE4?qU=?NLq(AeM!1+~#xjlFNY^&)zIueYH0$5VSfy*C#aq{k&^c&kkE z512>CMBDdC)5IktXDqW=p1s6lkW%EHz1-K!XS|}PRJl!RIXW(;_nKg3`VqmOHA}uhK?WOqO9w^wOh8+pC&9)0^Dp>$BdPyhWh%V zgd_D~?v*5JgQ?$qy9R+Av8-+a`ICPisn#J6uCJFNZ|%SCN07tf?=hqx;mG&k!>-HJ z2El*7pNBwJcX#;Vkls3ESKZGt-+$lKZcpXx+E>DKc zvMUTB$RPEn3@J!B(gcdH*i##n+p0?mGE2XRZ00+qn|YQhXxY|~cOcDdlw)r;bDc8l z=C*NcH~0CU-F!oQd1!2}GQ8hzrW7b#*$;N!^r1KiYaCO7P+rQ|6N*%_=9I-%%akdW zmElC`(!Pf&1qxT1f~osBii4U%4|nO5G>rdmmPU4e2uCVubkwSbM>3?<)YRz&`HwlP z7*ddMq!}n-UDO5xouBSSj8ZwcdO8xxqSG3*R;E0!dNh+Lw^?;Fr9k0Ib1>QaoZ?{a zwb=a#<%XF(Gm%PGovQnwef_{;+crTdCwvye)b z9iK6EGNtpYBeRLJesCjG3KXuigj)ZV6bD1^|8x+cY}wZ|8>wX7sa<$dru3Vg7)g|6 z+38FvP`J_xdXjEY9N3hk{u!an_t8Wml`K3SNtrU`tn$z25v9rbdZrX8TxkujcVtl< zxL@f1SDkXhySB?;FE-9YDp`5*t~JS&$(_H=k5wYvs_vOAU`)ZnneW5l9|lq#tgx)F zLkx3XirE4rleH(@%|gZubDA4PhUvR0iYWyOSK2_HVI#%CiZx#z*D2q*Uw*A06NOW% zJxP1iD(y=dGI!~B(FECa@;XBb5{~Q-gL98l8(7BMo3Rl`e zTiGs(1DCd*Gl&P?88&?}Qpwu$Bz4AH$}Dnp+!g!6wHIlf3JfVoIMN;lHvCF$@Otsx zvxrfeMgN9H()2{A><%k&GUdlj9Yk5+*~OFsg)0ZZ)|?KCgQ8Qh7ZA#G6Q0E(N6FH2 zamVwwkn6}O&*dLXAP>CxHV>pg;mU!q-Yb#fpmA94cL?Q~UB?oTN|v7LU}+#564XnH zvc24&%~GIn2d?1d{$kP zid3@l?DwdZDP8w&Nhiv#+;XNAC|v0PO^vP;2ermmZy}T)w^yX&l-{&F0jf`0D`m=+ zu8TE9*;16klmdk-KY)oRexx{fc=T`oL@4*Sr)iK%mY%G<44E=2@k%C9zG(ZADFq5w zIzqQ{g5qF==k*SRGH+dbCQ`}Lv)$m1O!?9-emPP0w@+hAfx?we@YBni6bDBt=RZU! zAHB+0j#RSrRGDYVl!u=NF_V=xZn1W zDGg2s7TJxXvBdih1Avx1FB9*K?eT?5eoY50S`Q^Twi86a=f2I^DTsa!{ z&GVo*n5@@oi%@=l*lsgY$=Y*XGe9=Wuh%TvLX4GG&$H z=n|sb*gcjh1qxSsLP_@uii4ix6%GhxXr6BgQpwu$rN5s{`NwZ3ml7r12xm%x!j)c7 z98g7Z(57v6L@3YJOe;kyS$h^vIBLDyZ literal 0 HcmV?d00001 From 0f572d1fac38d702485cce3efb0f22edf21460d9 Mon Sep 17 00:00:00 2001 From: Gwen Lg Date: Wed, 13 Aug 2025 17:25:57 +0200 Subject: [PATCH 03/13] add a test to check PDF2 frame deserialization added a corresponding puffin file --- puffin/src/profile_view.rs | 7 +++++++ puffin/tests/data/capture_PFD2.puffin | Bin 0 -> 8171 bytes 2 files changed, 7 insertions(+) create mode 100644 puffin/tests/data/capture_PFD2.puffin diff --git a/puffin/src/profile_view.rs b/puffin/src/profile_view.rs index d9f8b71e..a5cae3bc 100644 --- a/puffin/src/profile_view.rs +++ b/puffin/src/profile_view.rs @@ -459,4 +459,11 @@ mod tests { let _ = FrameView::read(&mut file)?; Ok(()) } + + #[test] + fn read_pfd2_file() -> anyhow::Result<()> { + let mut file = std::fs::File::open("tests/data/capture_PFD2.puffin")?; + let _ = FrameView::read(&mut file)?; + Ok(()) + } } diff --git a/puffin/tests/data/capture_PFD2.puffin b/puffin/tests/data/capture_PFD2.puffin new file mode 100644 index 0000000000000000000000000000000000000000..5204245cdeba7c472ec22dbdaa18caccda701783 GIT binary patch literal 8171 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 literal 0 HcmV?d00001 From a4873fcd5f58cf987cbe2e38432e2f44411e6434 Mon Sep 17 00:00:00 2001 From: Gwen Lg Date: Sun, 17 Aug 2025 22:14:02 +0200 Subject: [PATCH 04/13] add a test to check PDF1 frame deserialization added a corresponding puffin file --- puffin/src/profile_view.rs | 7 +++++++ puffin/tests/data/capture_PFD1.puffin | Bin 0 -> 6378 bytes 2 files changed, 7 insertions(+) create mode 100644 puffin/tests/data/capture_PFD1.puffin diff --git a/puffin/src/profile_view.rs b/puffin/src/profile_view.rs index a5cae3bc..380f7a89 100644 --- a/puffin/src/profile_view.rs +++ b/puffin/src/profile_view.rs @@ -466,4 +466,11 @@ mod tests { let _ = FrameView::read(&mut file)?; Ok(()) } + + #[test] + fn read_pfd1_file() -> anyhow::Result<()> { + let mut file = std::fs::File::open("tests/data/capture_PFD1.puffin")?; + let _ = FrameView::read(&mut file)?; + Ok(()) + } } diff --git a/puffin/tests/data/capture_PFD1.puffin b/puffin/tests/data/capture_PFD1.puffin new file mode 100644 index 0000000000000000000000000000000000000000..033786d378d0d311330ebc1296aec45cf81fec2b GIT binary patch literal 6378 zcma*rdpOnE9>?*Yd$hM)IuwqOqrF2YNhZ|na_Q1QoJLcpMm48K4JxfVXRU8bt>cXS`~BnLd3e9}vh9zBPk@KLkH=Jp zr5wjePTIZZ=DDhHmTF2$O0R0vRTIJ&Cq_tuRX9!ZUTFN1B}+BPdt;U*iO2_LiOUnr zT+AG#oFqn9lT%BIkBZ`?tEAs$MM)OS ziHN)Wog1aXWvVH&u3pP+9(XK<@6_MzgRdRAqKtc-)a8@=%F4)cU%e7a{&%BVgt7)_ zyYpDpMc1A}x3JJ}^OQIhg0FaoFIVBJ)Kpl!-S+{s6rX$?egNBSpU{Ip`>k9R{PUZ> z!G7J|CN-2}xI!~RS^b@V=3tPMXtL5@@Tx5TBIz`w6!VUlk9~5M^3~7BsRMuPksIba zJJK{!*!5K+VLX1$-#_IkMKVY!=EoC$wFl*=_1b6v|FFb~@HW{Z)FMmJ7RIZy{N-Y2 zNGax>Fn`YC1Ikx?EY}2HQjw1Nt#)#4HoA+Va7` z=MFlF`5Q;y=%Hb{v4+C{sn z#NAH9d~Mbb=}m_ek58u0v~vyhv#%U71^?Bb zrpfE5|J~WL5#T?}If4CN9l0xN(T#E!=IgNjjCH3V#p9Fln`7GN9|*l*4*oB*ny_D_ zn&6IVckYo1^L1H&v;L2e;_=D)smOn#=l{~Lc_jF4jsL{H(Ww10RP}kar!ZfS^)F_% zLyE^I^Y>i(FX~U*tz`v%S=&(TZxNsNMAJ`7XAa>1&#fa_O`r9v(+nZS#CosO@u?iHQwhnyDo`uFL>-|F>WNLxosh~97d{>JJa zm$=aabTWOjhiFhdygAF7e33LpeJg$hxp_(-p(c@K-~iwHe!cO!XX@CIoh(p%mTB+NHtec$ATkmB*l^flgHO#Olm{W0MCXPIKZ^ueVd zv|r>JEX*Iq`tr@@kmB*l^bHwhMg8_&Bgl^fCw*3Bh5frmvS4&?@vJam{&3b0+a!S$ zk58s={xoaqZ%yuS0RKzxXW0MNz&8v{D!3OZ%r|0vQ*k$>cziN_TeiKVzRy^DNAO>? zOv3&oqeqdbvn(}6m~YJb=_lPG#p9Fd%ZZsv{YB5tjR(K6_$u~a#Ad{xLc3jY!h93f zM<<#g#p9FdlZ?DU{ezJn6TlA%_9uSS-Z)fOvp-Ro|995EGJh_lcziN_&khImeg8eZ zT);0~=7;@lm4_42)cgCAh54qe|41_cQi}a? z9M&S$AwF5cd<)j^{80)i9-r)=^I;q6cOP^10Kd1_9s9q2<)4MRx-)Wx`6F3BBx?$! zczm*djSKo-|IMXoUf@T2ZNPq=Xniianx~a7%(rCy>lHbW;_=D;-M8LM&+k2c{&etT zUo66Y`2yX1)Y|z&p)mg+tY6U+3n?C-?BC3AIrWVzZDxR9>+OjB;K$bsQF1{{(ExtG ztCtn)Klyq*q=E<>&p?tKa2g6pJU^dLRTR z;}?n^QT#;Oj9GB_7X7*S5wz7b7o&Zn4(}M?@cn!V>u1%?gOuXKJCgC68o7Y_gGT50 zfS)bP$G#}__zt9D^&QBo$%w`ToQe-=TC$0y_Wdizf5 zCwP1p0RHjU*RUVoVOxeGUg1CC1^wT_hV{kGEs)~z$@s<9_Wkl-nRq)8{N3)4uy0ga zb_ls;v>p}a+p@mg=rN>td@_E%Ay4V~FKVut5B~Q78?k@avF#`-IyAIWm_LT~<5f08 zipMA8cQrMi`ftK*g1~>+MgHe_Q{AW%wLDCy66V{n{@OhgA;sg9@yq_ejrta~Yd;46 zdi5skD^E$PLgh;gtA+XYtRJAa1yVdd89%k|zVk Date: Mon, 18 Aug 2025 00:54:09 +0200 Subject: [PATCH 05/13] add test of deserialize just serialized frames --- Cargo.lock | 10 ++++++++ puffin/Cargo.toml | 3 +++ puffin/src/profile_view.rs | 51 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 328f2fa9..1cde54cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1579,6 +1579,15 @@ version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +[[package]] +name = "memfile" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f64636fdb65a5f0740f920c4281f3dbb76a71e25e25914b6d27000739897d40e" +dependencies = [ + "libc", +] + [[package]] name = "memmap2" version = "0.9.7" @@ -2250,6 +2259,7 @@ dependencies = [ "itertools", "js-sys", "lz4_flex", + "memfile", "parking_lot", "ruzstd", "serde", diff --git a/puffin/Cargo.toml b/puffin/Cargo.toml index 348aed97..234fbd9f 100644 --- a/puffin/Cargo.toml +++ b/puffin/Cargo.toml @@ -60,6 +60,9 @@ web-time = { version = "0.2", optional = true } [dev-dependencies] criterion = "0.5" +[target.'cfg(target_os = "linux")'.dev-dependencies] +memfile = "0.3" + [[bench]] name = "benchmark" harness = false diff --git a/puffin/src/profile_view.rs b/puffin/src/profile_view.rs index 380f7a89..6bb46182 100644 --- a/puffin/src/profile_view.rs +++ b/puffin/src/profile_view.rs @@ -444,6 +444,13 @@ impl FrameStats { #[cfg(all(test, feature = "serialization"))] mod tests { + use std::{io::Seek, ops::DerefMut, sync::Arc, thread, time::Duration}; + + use memfile::MemFile; + use parking_lot::Mutex; + + use crate::{GlobalProfiler, profile_scope, set_scopes_on}; + use super::FrameView; #[test] @@ -473,4 +480,48 @@ mod tests { let _ = FrameView::read(&mut file)?; Ok(()) } + + fn run_write(file: MemFile) { + // Init profiler sink with sync wrinting + let writer = Arc::new(Mutex::new(file)); + let sink = GlobalProfiler::lock().add_sink(Box::new(move |frame_data| { + let mut writer = writer.lock(); + frame_data.write_into(None, writer.deref_mut()).unwrap(); + })); + + set_scopes_on(true); // need this to enable capture + // run frames + for idx in 0..4 { + profile_scope!("main", idx.to_string()); + { + profile_scope!("sleep 1ms"); + let sleep_duration = Duration::from_millis(1); + thread::sleep(sleep_duration); + } + { + profile_scope!("sleep 2ms"); + let sleep_duration = Duration::from_millis(2); + thread::sleep(sleep_duration); + } + GlobalProfiler::lock().new_frame(); + } + + set_scopes_on(false); + GlobalProfiler::lock().new_frame(); //Force to get last frame + GlobalProfiler::lock().remove_sink(sink); + } + + fn run_read(mut file: MemFile) { + file.rewind().unwrap(); + let _ = FrameView::read(&mut file).expect("read :"); + } + + #[test] + #[cfg(target_os = "linux")] + fn deserialize_serialized() { + let file = MemFile::create_default("deserialize_serialized.puffin").unwrap(); + run_write(file.try_clone().unwrap()); + thread::sleep(Duration::from_secs(1)); + run_read(file); + } } From 1716c2159de7b664ae03a255c8a2b76621eeff63 Mon Sep 17 00:00:00 2001 From: Gwen Lg Date: Wed, 6 Aug 2025 19:01:42 +0200 Subject: [PATCH 06/13] add integration test of profile a single frame --- puffin/tests/common/mod.rs | 17 +++++++++++++++++ puffin/tests/single_frame.rs | 25 +++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 puffin/tests/common/mod.rs create mode 100644 puffin/tests/single_frame.rs diff --git a/puffin/tests/common/mod.rs b/puffin/tests/common/mod.rs new file mode 100644 index 00000000..99dd10b0 --- /dev/null +++ b/puffin/tests/common/mod.rs @@ -0,0 +1,17 @@ +use std::{thread, time::Duration}; + +pub fn process_1() { + puffin::profile_function!(); + sub_process_1_1(); + (0..2).for_each(|_| sub_process_1_2()); +} + +fn sub_process_1_1() { + puffin::profile_function!(); + thread::sleep(Duration::from_millis(1)); +} + +fn sub_process_1_2() { + puffin::profile_function!(); + thread::sleep(Duration::from_micros(2)); +} diff --git a/puffin/tests/single_frame.rs b/puffin/tests/single_frame.rs new file mode 100644 index 00000000..1a090733 --- /dev/null +++ b/puffin/tests/single_frame.rs @@ -0,0 +1,25 @@ +mod common; + +use std::sync::Arc; + +use puffin::{FrameData, GlobalProfiler}; + +#[test] +fn single_frame() { + fn profiler_sink(frame_data: Arc) { + let frame_meta = frame_data.meta(); + assert_eq!(frame_meta.frame_index, 0); + assert_eq!(frame_meta.num_scopes, 4); + } + + // Init profiler sink and enable capture + let sink_id = GlobalProfiler::lock().add_sink(Box::new(profiler_sink)); + puffin::set_scopes_on(true); + + // Run process + common::process_1(); + + // End frame, and uninit profiler + puffin::GlobalProfiler::lock().new_frame(); + GlobalProfiler::lock().remove_sink(sink_id); +} From e18684d8dfe1b579fffdb515c1802dbbca5d7e99 Mon Sep 17 00:00:00 2001 From: Gwen Lg Date: Wed, 6 Aug 2025 19:26:13 +0200 Subject: [PATCH 07/13] add integration test of profile multiple frames --- puffin/tests/common/mod.rs | 96 ++++++++++++++++++++++++++++- puffin/tests/frame_serialization.rs | 17 +++++ puffin/tests/multiple_frames.rs | 39 ++++++++++++ 3 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 puffin/tests/frame_serialization.rs create mode 100644 puffin/tests/multiple_frames.rs diff --git a/puffin/tests/common/mod.rs b/puffin/tests/common/mod.rs index 99dd10b0..6d0e0d4a 100644 --- a/puffin/tests/common/mod.rs +++ b/puffin/tests/common/mod.rs @@ -1,4 +1,16 @@ -use std::{thread, time::Duration}; +#![allow(dead_code)] + +use std::{ + io::Write, + sync::Arc, + thread::{self, JoinHandle}, + time::Duration, +}; + +use parking_lot::Mutex; +#[cfg(feature = "serialization")] +use puffin::FrameData; +use puffin::{FrameSinkId, GlobalProfiler}; pub fn process_1() { puffin::profile_function!(); @@ -15,3 +27,85 @@ fn sub_process_1_2() { puffin::profile_function!(); thread::sleep(Duration::from_micros(2)); } + +pub fn example_run() { + for idx in 0..4 { + puffin::profile_scope!("main", idx.to_string()); + + { + puffin::profile_scope!("sleep 1ms"); + let sleep_duration = Duration::from_millis(1); + thread::sleep(sleep_duration); + } + + { + puffin::profile_scope!("sleep 2ms"); + let sleep_duration = Duration::from_millis(2); + thread::sleep(sleep_duration); + } + //println!("before new_frame {idx}"); + puffin::GlobalProfiler::lock().new_frame(); + //println!("after new_frame {idx}"); + } +} + +pub struct FrameWriterImpl { + writer: Arc>, +} + +impl FrameWriterImpl { + pub fn from_writer(mut writer: W) -> Self { + writer.write_all(b"PUF0").unwrap(); //Hack: should not be duplicated + Self { + writer: Arc::new(Mutex::new(writer)), + } + } + + #[cfg(feature = "serialization")] + fn write_frame(&self, frame_data: Arc) { + use std::ops::DerefMut; + + let mut writer = self.writer.lock(); + frame_data.write_into(None, writer.deref_mut()).unwrap(); + } +} + +pub struct FrameWriterSink { + sink_id: FrameSinkId, + write_thread: Option>, +} +impl Drop for FrameWriterSink { + fn drop(&mut self) { + GlobalProfiler::lock().remove_sink(self.sink_id); + if let Some(write_handle) = self.write_thread.take() { + let _ = write_handle.join(); + } + } +} + +#[cfg(feature = "serialization")] +#[must_use] +pub fn init_frames_writer(writer: impl Write + Send + 'static) -> FrameWriterSink { + use std::sync::mpsc; + + let frame_writer = FrameWriterImpl::from_writer(writer); + let (frame_sender, frames_recv) = mpsc::channel(); + + let write_thread = thread::Builder::new() + .name("frame_writer".into()) + .spawn(move || { + while let Ok(frame_data) = frames_recv.recv() { + frame_writer.write_frame(frame_data); + } + }) + .unwrap(); + + // Init profiler sink and enable capture + let sink_id = GlobalProfiler::lock().add_sink(Box::new(move |frame_data| { + frame_sender.send(frame_data).unwrap() + })); + FrameWriterSink { + sink_id, + write_thread: Some(write_thread), + } +} diff --git a/puffin/tests/frame_serialization.rs b/puffin/tests/frame_serialization.rs new file mode 100644 index 00000000..dd8594dc --- /dev/null +++ b/puffin/tests/frame_serialization.rs @@ -0,0 +1,17 @@ +mod common; + +#[cfg(feature = "serialization")] +#[test] +fn frame_serialization() { + let frame_data = Vec::new(); + let _frame_writer = common::init_frames_writer(frame_data); + + //println!("set_scopes_on(true)"); + puffin::set_scopes_on(true); // need this to enable capture + + common::example_run(); + + //println!("set_scopes_on(false)"); + puffin::set_scopes_on(false); + puffin::GlobalProfiler::lock().new_frame(); //Force to get last frame +} diff --git a/puffin/tests/multiple_frames.rs b/puffin/tests/multiple_frames.rs new file mode 100644 index 00000000..6f27abef --- /dev/null +++ b/puffin/tests/multiple_frames.rs @@ -0,0 +1,39 @@ +mod common; + +use std::sync::{ + Arc, + atomic::{AtomicUsize, Ordering}, +}; + +use puffin::{FrameData, GlobalProfiler}; + +#[test] +fn multiple_frames() { + const NB_LOOP: usize = 10; + fn profiler_sink(frame_data: Arc, frame_count: Arc) { + let previous_count = frame_count.fetch_add(1, Ordering::Relaxed); + let frame_meta = frame_data.meta(); + assert_eq!(frame_meta.frame_index, previous_count as u64); + assert_eq!(frame_meta.num_scopes, 4); + } + + // Init profiler sink and enable capture + let frame_count = Arc::new(AtomicUsize::default()); + let frame_count_clone = frame_count.clone(); + let sink_id = GlobalProfiler::lock().add_sink(Box::new(move |frame_data| { + profiler_sink(frame_data, frame_count_clone.clone()); + })); + puffin::set_scopes_on(true); + + // Run process + std::iter::repeat_n((), NB_LOOP).for_each(|_| { + common::process_1(); + puffin::GlobalProfiler::lock().new_frame(); + }); + + let frame_count = frame_count.load(Ordering::Relaxed); + assert_eq!(frame_count, NB_LOOP); + + // End frame, and uninit profiler + GlobalProfiler::lock().remove_sink(sink_id); +} From 78eba3387c9639be18753abc233a08b44f66ce0f Mon Sep 17 00:00:00 2001 From: Gwen Lg Date: Sun, 22 Dec 2024 18:48:14 +0100 Subject: [PATCH 08/13] style: move meta_serialized creation juste before use --- puffin/src/frame_data.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/puffin/src/frame_data.rs b/puffin/src/frame_data.rs index d1fa0992..f920f561 100644 --- a/puffin/src/frame_data.rs +++ b/puffin/src/frame_data.rs @@ -574,9 +574,10 @@ impl FrameData { use bincode::Options as _; use byteorder::{LE, WriteBytesExt as _}; - let meta_serialized = bincode::options().serialize(&self.meta)?; write.write_all(b"PFD4")?; + + let meta_serialized = bincode::options().serialize(&self.meta)?; write.write_all(&(meta_serialized.len() as u32).to_le_bytes())?; write.write_all(&meta_serialized)?; From 40c36147ad605fc1714d5644ff4dc43d717c33f1 Mon Sep 17 00:00:00 2001 From: Gwen Lg Date: Sun, 22 Dec 2024 18:49:51 +0100 Subject: [PATCH 09/13] perf!: remove manual seq length serialization this is useless as already done by serde serialization. BREAKING CHANGE: the frame serialization format change. Introducing PFD5 --- puffin/src/frame_data.rs | 44 +++++++++++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/puffin/src/frame_data.rs b/puffin/src/frame_data.rs index f920f561..1bc4c0d6 100644 --- a/puffin/src/frame_data.rs +++ b/puffin/src/frame_data.rs @@ -572,13 +572,11 @@ impl FrameData { write: &mut impl std::io::Write, ) -> anyhow::Result<()> { use bincode::Options as _; - use byteorder::{LE, WriteBytesExt as _}; + use byteorder::WriteBytesExt as _; - - write.write_all(b"PFD4")?; + write.write_all(b"PFD5")?; let meta_serialized = bincode::options().serialize(&self.meta)?; - write.write_all(&(meta_serialized.len() as u32).to_le_bytes())?; write.write_all(&meta_serialized)?; self.create_packed(); @@ -596,7 +594,6 @@ impl FrameData { }; let serialized_scopes = bincode::options().serialize(&to_serialize_scopes)?; - write.write_u32::(serialized_scopes.len() as u32)?; write.write_all(&serialized_scopes)?; Ok(()) } @@ -775,6 +772,43 @@ impl FrameData { .context("Can not deserialize scope details")? }; + let new_scopes: Vec<_> = deserialized_scopes + .into_iter() + .map(|x| Arc::new(x.clone())) + .collect(); + + Ok(Some(Self { + meta, + data: RwLock::new(FrameDataState::Packed(streams_compressed)), + scope_delta: new_scopes, + full_delta: false, + })) + } else if &header == b"PFD5" { + // Added 2024-12-22: remove useless manual sequence size serialization. + let meta = { + let mut meta = Vec::new(); + read.read_exact(&mut meta)?; + bincode::options() + .deserialize(&meta) + .context("bincode deserialize")? + }; + + let streams_compressed_length = read.read_u32::()? as usize; + let compression_kind = CompressionKind::from_u8(read.read_u8()?)?; + let streams_compressed = { + let mut streams_compressed = vec![0_u8; streams_compressed_length]; + read.read_exact(&mut streams_compressed)?; + PackedStreams::new(compression_kind, streams_compressed) + }; + + let deserialized_scopes: Vec = { + let mut serialized_scopes = Vec::new(); + read.read_exact(&mut serialized_scopes)?; + bincode::options() + .deserialize_from(serialized_scopes.as_slice()) + .context("Can not deserialize scope details")? + }; + let new_scopes: Vec<_> = deserialized_scopes .into_iter() .map(|x| Arc::new(x.clone())) From 6825571b02bc14414bfaa4be6981b36de60950f4 Mon Sep 17 00:00:00 2001 From: Gwen Lg Date: Sun, 22 Dec 2024 19:31:30 +0100 Subject: [PATCH 10/13] perf: avoid vector cloning for scope_delta during serialization --- puffin/src/frame_data.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/puffin/src/frame_data.rs b/puffin/src/frame_data.rs index 1bc4c0d6..51cf0f5e 100644 --- a/puffin/src/frame_data.rs +++ b/puffin/src/frame_data.rs @@ -587,13 +587,17 @@ impl FrameData { write.write_u8(packed_streams.compression_kind as u8)?; write.write_all(&packed_streams.bytes)?; - let to_serialize_scopes: Vec<_> = if let Some(scope_collection) = scope_collection { - scope_collection.scopes_by_id().values().cloned().collect() + let serialized_scopes = if let Some(scope_collection) = scope_collection { + let to_serialize_scopes = scope_collection + .scopes_by_id() + .values() + .cloned() + .collect::>(); + bincode::options().serialize(&to_serialize_scopes) } else { - self.scope_delta.clone() - }; + bincode::options().serialize(&self.scope_delta) + }?; - let serialized_scopes = bincode::options().serialize(&to_serialize_scopes)?; write.write_all(&serialized_scopes)?; Ok(()) } From fabb5aa2b1f9d8c523b414bb14e3dc42c51f9cf6 Mon Sep 17 00:00:00 2001 From: Gwen Lg Date: Mon, 23 Dec 2024 17:03:34 +0100 Subject: [PATCH 11/13] add a wrapper struct to ScopeCollection to impl serialize This allow to serialize scopes collection directly, without need to collect scope in a temporary Vec. --- puffin/src/scope_details.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/puffin/src/scope_details.rs b/puffin/src/scope_details.rs index f85e9da6..55549422 100644 --- a/puffin/src/scope_details.rs +++ b/puffin/src/scope_details.rs @@ -58,6 +58,28 @@ impl ScopeCollection { pub fn scopes_by_id(&self) -> &HashMap> { &self.0.scope_id_to_details } + + /// A wrapper than allow `Serialize` all the scopes values of `ScopeCollection`. + #[cfg(feature = "serialization")] + pub fn serializable(&self) -> Serializable<'_> { + Serializable(self) + } +} + +/// A wrapper than impl `Serialize` for `ScopeCollection`. +/// This `struct` is created by the [`serializable`] method on `ScopeCollection`. +#[cfg(feature = "serialization")] +pub struct Serializable<'a>(&'a crate::ScopeCollection); + +#[cfg(feature = "serialization")] +impl serde::Serialize for Serializable<'_> { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + let iter = self.0.scopes_by_id().values(); + serializer.collect_seq(iter) + } } /// Scopes are identified by user-provided name while functions are identified by the function name. From c5b08be550dd2ad7927df91af7e999e544f1c6ee Mon Sep 17 00:00:00 2001 From: Gwen Lg Date: Sun, 22 Dec 2024 16:50:28 +0100 Subject: [PATCH 12/13] directly serialize frame in writer instead of serialize in a temporary Vec before coping data in writer after. --- puffin/src/frame_data.rs | 41 ++++++++++++---------------------------- 1 file changed, 12 insertions(+), 29 deletions(-) diff --git a/puffin/src/frame_data.rs b/puffin/src/frame_data.rs index 51cf0f5e..b071b407 100644 --- a/puffin/src/frame_data.rs +++ b/puffin/src/frame_data.rs @@ -569,15 +569,13 @@ impl FrameData { pub fn write_into( &self, scope_collection: Option<&crate::ScopeCollection>, - write: &mut impl std::io::Write, + mut write: &mut impl std::io::Write, ) -> anyhow::Result<()> { use bincode::Options as _; use byteorder::WriteBytesExt as _; write.write_all(b"PFD5")?; - - let meta_serialized = bincode::options().serialize(&self.meta)?; - write.write_all(&meta_serialized)?; + bincode::options().serialize_into(&mut write, &self.meta)?; self.create_packed(); let packed_streams_lock = self.data.read(); @@ -587,18 +585,12 @@ impl FrameData { write.write_u8(packed_streams.compression_kind as u8)?; write.write_all(&packed_streams.bytes)?; - let serialized_scopes = if let Some(scope_collection) = scope_collection { - let to_serialize_scopes = scope_collection - .scopes_by_id() - .values() - .cloned() - .collect::>(); - bincode::options().serialize(&to_serialize_scopes) + if let Some(scope_collection) = scope_collection { + bincode::options().serialize_into(&mut write, &scope_collection.serializable())?; } else { - bincode::options().serialize(&self.scope_delta) - }?; + bincode::options().serialize_into(write, &self.scope_delta)?; + } - write.write_all(&serialized_scopes)?; Ok(()) } @@ -607,7 +599,7 @@ impl FrameData { /// [`None`] is returned if the end of the stream is reached (EOF), /// or an end-of-stream sentinel of `0u32` is read. #[cfg(feature = "serialization")] - pub fn read_next(read: &mut impl std::io::Read) -> anyhow::Result> { + pub fn read_next(mut read: &mut impl std::io::Read) -> anyhow::Result> { use anyhow::Context as _; use bincode::Options as _; use byteorder::{LE, ReadBytesExt}; @@ -788,12 +780,10 @@ impl FrameData { full_delta: false, })) } else if &header == b"PFD5" { - // Added 2024-12-22: remove useless manual sequence size serialization. + // Added 2024-12-22: remove useless manual sequence size serialization and temporary vector. let meta = { - let mut meta = Vec::new(); - read.read_exact(&mut meta)?; bincode::options() - .deserialize(&meta) + .deserialize_from(&mut read) .context("bincode deserialize")? }; @@ -805,23 +795,16 @@ impl FrameData { PackedStreams::new(compression_kind, streams_compressed) }; - let deserialized_scopes: Vec = { - let mut serialized_scopes = Vec::new(); - read.read_exact(&mut serialized_scopes)?; + let deserialized_scopes: Vec> = { bincode::options() - .deserialize_from(serialized_scopes.as_slice()) + .deserialize_from(read) // serialized_scopes.as_slice() .context("Can not deserialize scope details")? }; - let new_scopes: Vec<_> = deserialized_scopes - .into_iter() - .map(|x| Arc::new(x.clone())) - .collect(); - Ok(Some(Self { meta, data: RwLock::new(FrameDataState::Packed(streams_compressed)), - scope_delta: new_scopes, + scope_delta: deserialized_scopes, full_delta: false, })) } else { From 75fc3a67fdecdbd043f88e50d758a136dd392d8a Mon Sep 17 00:00:00 2001 From: Gwen Lg Date: Sun, 22 Dec 2024 21:36:51 +0100 Subject: [PATCH 13/13] refacto(server): allow client connect without frame --- puffin_http/src/server.rs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/puffin_http/src/server.rs b/puffin_http/src/server.rs index 1b78df44..62fdb101 100644 --- a/puffin_http/src/server.rs +++ b/puffin_http/src/server.rs @@ -1,4 +1,5 @@ use anyhow::Context as _; +use crossbeam_channel::TryRecvError; use puffin::{FrameSinkId, GlobalProfiler, ScopeCollection}; use std::{ io::Write as _, @@ -7,6 +8,7 @@ use std::{ Arc, atomic::{AtomicUsize, Ordering}, }, + time::Duration, }; /// Maximum size of the backlog of packets to send to a client if they aren't reading fast enough. @@ -261,13 +263,22 @@ impl Server { scope_collection: Default::default(), }; - while let Ok(frame) = rx.recv() { + loop { if let Err(err) = server_impl.accept_new_clients() { log::warn!("puffin server failure: {err}"); } - - if let Err(err) = server_impl.send(&frame) { - log::warn!("puffin server failure: {err}"); + match rx.try_recv() { + Ok(frame) => { + if let Err(err) = server_impl.send(&frame) { + log::warn!("puffin server failure: {err}"); + } + } + Err(TryRecvError::Empty) => { + std::thread::sleep(Duration::from_millis(1)); + } + Err(TryRecvError::Disconnected) => { + break; + } } } })