From fae4db393bb2ce7f4fc757f79ac2ae875a6e1d1b Mon Sep 17 00:00:00 2001 From: akvlad Date: Wed, 10 Jul 2024 20:59:27 +0300 Subject: [PATCH 1/7] init --- pyroscope/pprof-bin/Cargo.toml | 1 + pyroscope/pprof-bin/pkg/pprof_bin.d.ts | 9 ++ pyroscope/pprof-bin/pkg/pprof_bin.js | 47 +++++++ pyroscope/pprof-bin/pkg/pprof_bin_bg.wasm | Bin 84520 -> 96479 bytes .../pprof-bin/pkg/pprof_bin_bg.wasm.d.ts | 1 + pyroscope/pprof-bin/src/lib.rs | 133 +++++++++++++++++- 6 files changed, 190 insertions(+), 1 deletion(-) diff --git a/pyroscope/pprof-bin/Cargo.toml b/pyroscope/pprof-bin/Cargo.toml index daf87289..1b114a9c 100644 --- a/pyroscope/pprof-bin/Cargo.toml +++ b/pyroscope/pprof-bin/Cargo.toml @@ -24,6 +24,7 @@ lazy_static = "1.4.0" # all the `std::fmt` and `std::panicking` infrastructure, so isn't great for # code size when deploying. console_error_panic_hook = { version = "0.1.7", optional = true } +base64 = "0.22.1" [dev-dependencies] wasm-bindgen-test = "0.3.34" diff --git a/pyroscope/pprof-bin/pkg/pprof_bin.d.ts b/pyroscope/pprof-bin/pkg/pprof_bin.d.ts index 87b92058..30346558 100644 --- a/pyroscope/pprof-bin/pkg/pprof_bin.d.ts +++ b/pyroscope/pprof-bin/pkg/pprof_bin.d.ts @@ -17,6 +17,15 @@ export function merge_tree(id: number, bytes: Uint8Array): void; */ export function export_tree(id: number): Uint8Array; /** +* @param {Uint32Array} ids +* @param {string} period_type +* @param {string} period_unit +* @param {string} _sample_types +* @param {string} _sample_units +* @returns {Uint8Array} +*/ +export function export_trees_pprof(ids: Uint32Array, period_type: string, period_unit: string, _sample_types: string, _sample_units: string): Uint8Array; +/** * @param {number} id */ export function drop_tree(id: number): void; diff --git a/pyroscope/pprof-bin/pkg/pprof_bin.js b/pyroscope/pprof-bin/pkg/pprof_bin.js index 6d0e9559..130d4b35 100644 --- a/pyroscope/pprof-bin/pkg/pprof_bin.js +++ b/pyroscope/pprof-bin/pkg/pprof_bin.js @@ -148,6 +148,53 @@ module.exports.export_tree = function(id) { } }; +let cachedUint32Memory0 = null; + +function getUint32Memory0() { + if (cachedUint32Memory0 === null || cachedUint32Memory0.byteLength === 0) { + cachedUint32Memory0 = new Uint32Array(wasm.memory.buffer); + } + return cachedUint32Memory0; +} + +function passArray32ToWasm0(arg, malloc) { + const ptr = malloc(arg.length * 4, 4) >>> 0; + getUint32Memory0().set(arg, ptr / 4); + WASM_VECTOR_LEN = arg.length; + return ptr; +} +/** +* @param {Uint32Array} ids +* @param {string} period_type +* @param {string} period_unit +* @param {string} _sample_types +* @param {string} _sample_units +* @returns {Uint8Array} +*/ +module.exports.export_trees_pprof = function(ids, period_type, period_unit, _sample_types, _sample_units) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const ptr0 = passArray32ToWasm0(ids, wasm.__wbindgen_malloc); + const len0 = WASM_VECTOR_LEN; + const ptr1 = passStringToWasm0(period_type, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + const ptr2 = passStringToWasm0(period_unit, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len2 = WASM_VECTOR_LEN; + const ptr3 = passStringToWasm0(_sample_types, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len3 = WASM_VECTOR_LEN; + const ptr4 = passStringToWasm0(_sample_units, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len4 = WASM_VECTOR_LEN; + wasm.export_trees_pprof(retptr, ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3, ptr4, len4); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var v6 = getArrayU8FromWasm0(r0, r1).slice(); + wasm.__wbindgen_free(r0, r1 * 1, 1); + return v6; + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } +}; + /** * @param {number} id */ diff --git a/pyroscope/pprof-bin/pkg/pprof_bin_bg.wasm b/pyroscope/pprof-bin/pkg/pprof_bin_bg.wasm index b7f92a70897577fa3a3a669b97b1917753cb3f45..0e2b9ae51e7b864b87bfdfb95603ae3c3b3e80d7 100644 GIT binary patch delta 45210 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 CVFEn> delta 34513 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 i64 { + let mut idx = 0; + for i in 0..prof.string_table.len() { + if prof.string_table[i] == s { + idx = i as i64; + break; + } + } + if idx == 0 { + idx = prof.string_table.len() as i64; + prof.string_table.push(s); + } + idx +} + +fn upsert_function(prof: &mut Profile, fn_id: u64, fn_name_id: i64) { + for f in prof.function.iter() { + if f.id == fn_id { + return; + } + } + let mut func = Function::default(); + func.name = fn_name_id; + func.id = fn_id; + func.filename = upsert_string(prof, "unknown".to_string()); + func.system_name = upsert_string(prof, "unknown".to_string()); + prof.function.push(func); +} + +fn inject_locations(prof: &mut Profile, tree: &Tree) { + for n in tree.names_map.iter() { + let hash = *n.1 as u64; + let name = tree.names[hash as usize].clone(); + let fn_idx = upsert_string(prof, name); + upsert_function(prof, *n.0, fn_idx); + let mut loc = Location::default(); + let mut line = Line::default(); + line.function_id = *n.0; + loc.id = *n.0; + loc.line = vec![line]; + prof.location.push(loc) + } +} + +fn upsert_sample(prof: &mut Profile, loc_id: Vec, val: i64, val_idx: i64) -> i64 { + let mut idx = -1; + for i in 0..prof.sample.len() { + if prof.sample[i].location_id.len() != loc_id.len() { + continue; + } + let mut found = true; + for j in 0..prof.sample[i].location_id.len() { + if prof.sample[i].location_id[j] != loc_id[j] { + found = false; + break; + } + } + if found { + idx = i as i64; + break; + } + } + if idx == -1 { + let mut sample = Sample::default(); + sample.location_id = loc_id.clone(); + sample.location_id.reverse(); + idx = prof.sample.len() as i64; + prof.sample.push(sample); + } + while prof.sample[idx as usize].value.len() <= val_idx as usize { + prof.sample[idx as usize].value.push(0) + } + prof.sample[idx as usize].value[val_idx as usize] += val; + idx +} + +fn inject_functions(prof: &mut Profile, tree: &Tree, parent_id: u64, + loc_ids: Vec, val_idx: i64) { + if !tree.nodes.contains_key(&parent_id) { + return; + } + let children = tree.nodes.get(&parent_id).unwrap(); + for node in children.iter() { + let mut _loc_ids = loc_ids.clone(); + _loc_ids.push(node.fn_id); + upsert_sample(prof, _loc_ids.clone(), node.slf as i64, val_idx); + if tree.nodes.contains_key(&node.node_id) { + inject_functions(prof, tree, node.node_id, _loc_ids, val_idx); + } + } +} + +fn merge_profile(tree: & Tree, prof: &mut Profile, sample_type: String, sample_unit: String) { + let mut value_type = ValueType::default(); + value_type.r#type=upsert_string(prof, sample_type); + value_type.unit = upsert_string(prof, sample_unit); + prof.sample_type.push(value_type); + let type_idx = prof.sample_type.len() as i64 - 1; + inject_locations(prof, tree); + inject_functions(prof, tree, 0, vec![], type_idx); +} + #[wasm_bindgen] pub fn merge_prof(id: u32, bytes: &[u8], sample_type: String) { let p = panic::catch_unwind(|| { @@ -345,7 +449,6 @@ pub fn merge_prof(id: u32, bytes: &[u8], sample_type: String) { match p { Ok(res) => {} Err(err) => panic!(err) - } } @@ -392,6 +495,34 @@ pub fn export_tree(id: u32) -> Vec { } } +#[wasm_bindgen] +pub fn export_trees_pprof(ids: &[u32], + period_type: String, period_unit: String, + _sample_types: String, _sample_units: String) -> Vec { + let p = panic::catch_unwind(|| { + let sample_types: Vec<&str> = _sample_types.split(';').collect(); + let sample_units: Vec<&str> = _sample_units.split(';').collect(); + let mut res = &mut Profile::default(); + let mut period = ValueType::default(); + period.r#type = upsert_string(res, period_type); + period.unit = upsert_string(res, period_unit); + res.string_table = vec!["".to_string()]; + res.period_type = Some(period); + let mut ctx = CTX.lock().unwrap(); + for i in 0..ids.len() { + upsert_tree(&mut ctx, ids[i]); + let tree = ctx.get(&ids[i]).unwrap(); + merge_profile(tree, res, + sample_types[i].to_string(), sample_units[i].to_string()) + } + return res.encode_to_vec() + }); + match p { + Ok(res) => return res, + Err(err) => panic!(err) + } +} + #[wasm_bindgen] pub fn drop_tree(id: u32) { let mut ctx = CTX.lock().unwrap(); From f31d864d385460f44291c9772d40270bce4d0756 Mon Sep 17 00:00:00 2001 From: akvlad Date: Mon, 15 Jul 2024 10:39:02 +0300 Subject: [PATCH 2/7] merge porting --- pyroscope/pprof-bin/Cargo.toml | 1 + pyroscope/pprof-bin/src/ch64.rs | 154 ++++---- pyroscope/pprof-bin/src/lib.rs | 431 ++++++++++++-------- pyroscope/pprof-bin/src/merge.rs | 659 +++++++++++++++++++++++++++++++ 4 files changed, 997 insertions(+), 248 deletions(-) create mode 100644 pyroscope/pprof-bin/src/merge.rs diff --git a/pyroscope/pprof-bin/Cargo.toml b/pyroscope/pprof-bin/Cargo.toml index 1b114a9c..0f481fb5 100644 --- a/pyroscope/pprof-bin/Cargo.toml +++ b/pyroscope/pprof-bin/Cargo.toml @@ -18,6 +18,7 @@ bytes = "1.5.0" prost = "0.12.3" json = "0.12.4" lazy_static = "1.4.0" +bytemuck = "1.16.1" # The `console_error_panic_hook` crate provides better debugging of panics by # logging them with `console.error`. This is great for development, but requires diff --git a/pyroscope/pprof-bin/src/ch64.rs b/pyroscope/pprof-bin/src/ch64.rs index eb11e98c..dfb419bd 100644 --- a/pyroscope/pprof-bin/src/ch64.rs +++ b/pyroscope/pprof-bin/src/ch64.rs @@ -1,5 +1,4 @@ - -pub fn read_uint64_le(bytes: &[u8]) -> (u64) { +pub fn read_uint64_le(bytes: &[u8]) -> u64 { let mut res: u64 = 0; for i in 0..8 { res |= (bytes[i] as u64) << (i * 8); @@ -7,34 +6,34 @@ pub fn read_uint64_le(bytes: &[u8]) -> (u64) { res } -const kMul: u64 = 0x9ddfea08eb382d69; +const K_MUL: u64 = 0x9ddfea08eb382d69; pub fn hash_128_to_64(l: u64, h: u64) -> u64 { - let mut a = (l ^ h).wrapping_mul(kMul); - a ^= (a >> 47); - let mut b = (h ^ a).wrapping_mul(kMul); - b ^= (b >> 47); - b = b.wrapping_mul(kMul); + let mut a = (l ^ h).wrapping_mul(K_MUL); + a ^= a >> 47; + let mut b = (h ^ a).wrapping_mul(K_MUL); + b ^= b >> 47; + b = b.wrapping_mul(K_MUL); b } -const k0: u64 = 0xc3a5c85c97cb3127; -const k2: u64 = 0x9ae16a3b2f90404f; -const k1: u64 = 0xb492b66fbe98f273; -const k3: u64 = 0xc949d7c7509e6557; +const K0: u64 = 0xc3a5c85c97cb3127; +const K2: u64 = 0x9ae16a3b2f90404f; +const K1: u64 = 0xb492b66fbe98f273; +const K3: u64 = 0xc949d7c7509e6557; fn ch16(u: u64, v: u64) -> u64 { hash_128_to_64(u, v) } fn rot64(val: u64, shift: usize) -> u64 { if shift == 0 { - return val + return val; } - return (val >> shift) | val<<(64-shift) + return (val >> shift) | val << (64 - shift); } -fn shiftMix(val: u64) -> u64 { - return val ^ (val >> 47) +fn shift_mix(val: u64) -> u64 { + return val ^ (val >> 47); } fn hash16(u: u64, v: u64) -> u64 { @@ -51,89 +50,87 @@ fn fetch32(p: &[u8]) -> u32 { fn ch33to64(s: &[u8], length: usize) -> u64 { let mut z = read_uint64_le(&s[24..]); - let mut a = read_uint64_le(&s) + - (length as u64+read_uint64_le(&s[length-16..])).wrapping_mul(k0); - let mut b = rot64(a+z, 52); - let mut c= rot64(a, 37); + let mut a = + read_uint64_le(&s) + (length as u64 + read_uint64_le(&s[length - 16..])).wrapping_mul(K0); + let mut b = rot64(a + z, 52); + let mut c = rot64(a, 37); a += read_uint64_le(&s[8..]); c += rot64(a, 7); a += read_uint64_le(&s[16..]); - let vf= a + z; - let vs= b + rot64(a, 31) + c; + let vf = a + z; + let vs = b + rot64(a, 31) + c; - a = read_uint64_le(&s[16..]) + read_uint64_le(&s[length-32..]); - z = read_uint64_le(&s[length-8..]); - b = rot64(a+z, 52); + a = read_uint64_le(&s[16..]) + read_uint64_le(&s[length - 32..]); + z = read_uint64_le(&s[length - 8..]); + b = rot64(a + z, 52); c = rot64(a, 37); - a += read_uint64_le(&s[length-24..]); + a += read_uint64_le(&s[length - 24..]); c += rot64(a, 7); - a += read_uint64_le(&s[length-16..]); + a += read_uint64_le(&s[length - 16..]); - let wf= a + z; - let ws= b + rot64(a, 31) + c; - let r= shiftMix((vf+ws).wrapping_mul(k2) + (wf+vs).wrapping_mul(k0)); - return shiftMix(r.wrapping_mul(k0)+vs).wrapping_mul(k2) + let wf = a + z; + let ws = b + rot64(a, 31) + c; + let r = shift_mix((vf + ws).wrapping_mul(K2) + (wf + vs).wrapping_mul(K0)); + return shift_mix(r.wrapping_mul(K0) + vs).wrapping_mul(K2); } fn ch17to32(s: &[u8], length: usize) -> u64 { - let a = read_uint64_le(s).wrapping_mul(k1); - let b= read_uint64_le(&s[8..]); - let c= read_uint64_le(&s[length-8..]).wrapping_mul(k2); - let d= read_uint64_le(&s[length-16..]).wrapping_mul(k0); + let a = read_uint64_le(s).wrapping_mul(K1); + let b = read_uint64_le(&s[8..]); + let c = read_uint64_le(&s[length - 8..]).wrapping_mul(K2); + let d = read_uint64_le(&s[length - 16..]).wrapping_mul(K0); return hash16( - rot64(a-b, 43)+rot64(c, 30)+d, - a+rot64(b^k3, 20)-c+(length as u64), - ) + rot64(a - b, 43) + rot64(c, 30) + d, + a + rot64(b ^ K3, 20) - c + (length as u64), + ); } fn ch0to16(s: &[u8], length: usize) -> u64 { if length > 8 { let a = read_uint64_le(s); - let b= read_uint64_le(&s[length-8..]); - return ch16(a, rot64(b+(length as u64), (length))) ^ b; + let b = read_uint64_le(&s[length - 8..]); + return ch16(a, rot64(b + (length as u64), length)) ^ b; } if length >= 4 { - let a = (fetch32(s) as u64); - return ch16((length as u64)+(a<<3), (fetch32(&s[length-4..]) as u64)); + let a = fetch32(s) as u64; + return ch16((length as u64) + (a << 3), fetch32(&s[length - 4..]) as u64); } if length > 0 { let a = s[0]; - let b = s[length>>1]; - let c= s[length-1]; + let b = s[length >> 1]; + let c = s[length - 1]; let y = (a as u32) + ((b as u32) << 8); let z = (length as u32) + ((c as u32) << 2); - return shiftMix( - (y as u64).wrapping_mul(k2)^ - (z as u64).wrapping_mul(k3)) - .wrapping_mul(k2); + return shift_mix((y as u64).wrapping_mul(K2) ^ (z as u64).wrapping_mul(K3)) + .wrapping_mul(K2); } - return k2 + return K2; } -fn weakHash32Seeds(w: u64, x: u64, y: u64, z: u64, _a: u64, _b: u64) -> (u64, u64) { +fn weak_hash32_seeds(w: u64, x: u64, y: u64, z: u64, _a: u64, _b: u64) -> (u64, u64) { let mut a = _a + w; - let mut b = rot64(_b+a+z, 21); + let mut b = rot64(_b + a + z, 21); let c = a; a += x; a += y; b += rot64(a, 44); - return (a+z, b+c) + return (a + z, b + c); } // Return a 16-byte hash for s[0] ... s[31], a, and b. Quick and dirty. -fn weakHash32SeedsByte(s: &[u8], a: u64, b: u64) -> (u64, u64) { +fn weak_hash32_seeds_byte(s: &[u8], a: u64, b: u64) -> (u64, u64) { _ = s[31]; - return weakHash32Seeds( - read_uint64_le(&s[0..0+8]), - read_uint64_le(&s[8..8+8]), - read_uint64_le(&s[16..16+8]), - read_uint64_le(&s[24..24+8]), + return weak_hash32_seeds( + read_uint64_le(&s[0..0 + 8]), + read_uint64_le(&s[8..8 + 8]), + read_uint64_le(&s[16..16 + 8]), + read_uint64_le(&s[24..24 + 8]), a, b, ); } -fn nearestMultiple64(b: &[u8]) -> usize { +fn nearest_multiple_64(b: &[u8]) -> usize { return ((b.len()) - 1) & !63; } @@ -141,41 +138,40 @@ fn nearestMultiple64(b: &[u8]) -> usize { pub fn city_hash_64(s: &[u8]) -> u64 { let length = s.len(); if length <= 16 { - return ch0to16(s, length) + return ch0to16(s, length); } if length <= 32 { - return ch17to32(s, length) + return ch17to32(s, length); } if length <= 64 { - return ch33to64(s, length) + return ch33to64(s, length); } - let x= read_uint64_le(s); - let y= read_uint64_le(&s[length-16..]) ^ k1; - let mut z = read_uint64_le(&s[length-56..]) ^ k0; + let x = read_uint64_le(s); + let y = read_uint64_le(&s[length - 16..]) ^ K1; + let mut z = read_uint64_le(&s[length - 56..]) ^ K0; - let mut v= weakHash32SeedsByte(&s[length-64..], (length as u64), y); - let mut w= weakHash32SeedsByte(&s[length-32..], (length as u64).wrapping_mul(k1), k0); - z += shiftMix(v.1).wrapping_mul(k1); - let mut x = rot64(z+x, 39).wrapping_mul(k1); - let mut y = rot64(y, 33).wrapping_mul(k1); + let mut v = weak_hash32_seeds_byte(&s[length - 64..], length as u64, y); + let mut w = weak_hash32_seeds_byte(&s[length - 32..], (length as u64).wrapping_mul(K1), K0); + z += shift_mix(v.1).wrapping_mul(K1); + let mut x = rot64(z + x, 39).wrapping_mul(K1); + let mut y = rot64(y, 33).wrapping_mul(K1); // Decrease len to the nearest multiple of 64, and operate on 64-byte chunks. - let mut _s = &s[..nearestMultiple64(s)]; + let mut _s = &s[..nearest_multiple_64(s)]; while _s.len() > 0 { - x = rot64(x+y+v.0+read_uint64_le(&s[16..]), 37).wrapping_mul(k1); - y = rot64(y+v.1+read_uint64_le(&s[48..]), 42).wrapping_mul(k1); + x = rot64(x + y + v.0 + read_uint64_le(&s[16..]), 37).wrapping_mul(K1); + y = rot64(y + v.1 + read_uint64_le(&s[48..]), 42).wrapping_mul(K1); x ^= w.1; y ^= v.0; - z = rot64(z^w.0, 33); - v = weakHash32SeedsByte(s, v.1.wrapping_mul(k1), x+w.0); - w = weakHash32SeedsByte(&s[32..], z+w.1, y); + z = rot64(z ^ w.0, 33); + v = weak_hash32_seeds_byte(s, v.1.wrapping_mul(K1), x + w.0); + w = weak_hash32_seeds_byte(&s[32..], z + w.1, y); (z, x) = (x, z); _s = &_s[64..]; } return ch16( - ch16(v.0, w.0)+shiftMix(y).wrapping_mul(k1)+z, - ch16(v.1, w.1)+x, + ch16(v.0, w.0) + shift_mix(y).wrapping_mul(K1) + z, + ch16(v.1, w.1) + x, ); } - diff --git a/pyroscope/pprof-bin/src/lib.rs b/pyroscope/pprof-bin/src/lib.rs index ab3ae44d..a826ed74 100644 --- a/pyroscope/pprof-bin/src/lib.rs +++ b/pyroscope/pprof-bin/src/lib.rs @@ -1,25 +1,25 @@ #![allow(unused_assignments)] -mod utils; mod ch64; +mod merge; +mod utils; +use crate::pprof_pb::google::v1::{Line, ValueType}; +use ch64::city_hash_64; +use ch64::read_uint64_le; use lazy_static::lazy_static; use pprof_pb::google::v1::Function; use pprof_pb::google::v1::Location; use pprof_pb::google::v1::Profile; use pprof_pb::google::v1::Sample; -use pprof_pb::querier::v1::Level; use pprof_pb::querier::v1::FlameGraph; +use pprof_pb::querier::v1::Level; use pprof_pb::querier::v1::SelectMergeStacktracesResponse; -use std::panic; use prost::Message; use std::collections::{HashMap, HashSet}; -use std::slice::SliceIndex; +use std::panic; use std::sync::Mutex; use std::vec::Vec; use wasm_bindgen::prelude::*; -use ch64::city_hash_64; -use ch64::read_uint64_le; -use crate::pprof_pb::google::v1::{Line, ValueType}; pub mod pprof_pb { @@ -41,20 +41,20 @@ pub mod pprof_pb { } struct TreeNodeV2 { - parent_id: u64, + //parent_id: u64, fn_id: u64, node_id: u64, - slf: u64, - total: u64 + slf: Vec, + total: Vec, } struct Tree { names: Vec, names_map: HashMap, - nodes: HashMap>, - sample_type: String, - max_self: i64, - nodes_num: i32 + nodes: HashMap>, + sample_types: Vec, + max_self: Vec, + nodes_num: i32, } fn find_node(id: u64, nodes: &Vec) -> i32 { @@ -75,7 +75,7 @@ fn get_node_id(parent_id: u64, name_hash: u64, level: u16) -> u64 { node_bytes[i] = ((parent_id >> (i * 8)) & 0xFF) as u8; } for i in 0..8 { - node_bytes[i+8] = ((name_hash >> (i * 8)) & 0xFF) as u8; + node_bytes[i + 8] = ((name_hash >> (i * 8)) & 0xFF) as u8; } let mut _level = level; if _level > 511 { @@ -84,6 +84,56 @@ fn get_node_id(parent_id: u64, name_hash: u64, level: u16) -> u64 { (city_hash_64(&node_bytes[0..]) >> 9) | ((_level as u64) << 55) } +struct MergeTotalsProcessor { + from_idx: Vec, +} + +impl MergeTotalsProcessor { + fn new(tree: &Tree, p: &Profile) -> MergeTotalsProcessor { + let mut from_idx: Vec = vec![-1; tree.sample_types.len()]; + for i in 0..tree.sample_types.len() { + let sample_type_to = &tree.sample_types[i]; + for j in 0..p.sample_type.len() { + let sample_type_from = format!( + "{}:{}", + p.string_table[p.sample_type[j].r#type as usize], + p.string_table[p.sample_type[j].unit as usize] + ); + if sample_type_from == *sample_type_to { + from_idx[i] = j as i32; + break; + } + } + } + MergeTotalsProcessor { from_idx } + } + + fn merge_totals( + &self, + node: &mut TreeNodeV2, + _max_self: &Vec, + sample: &Sample, + merge_self: bool, + ) -> Vec { + let mut max_self = _max_self.clone(); + for i in 0..self.from_idx.len() { + if self.from_idx[i] == -1 { + continue; + } + node.total[i] += sample.value[self.from_idx[i] as usize]; + if merge_self { + node.slf[i] += sample.value[self.from_idx[i] as usize]; + for i in 0..max_self.len() { + if max_self[i] < node.slf[i] { + max_self[i] = node.slf[i]; + } + } + } + } + max_self + } +} + fn merge(tree: &mut Tree, p: &Profile) { let mut functions: HashMap = HashMap::new(); for f in p.function.iter() { @@ -94,23 +144,7 @@ fn merge(tree: &mut Tree, p: &Profile) { locations.insert(l.id, &l); } - let mut value_idx: i32 = -1; - for i in 0..p.sample_type.len() { - let sample_type = format!( - "{}:{}", - p.string_table[p.sample_type[i].r#type as usize], - p.string_table[p.sample_type[i].unit as usize] - ); - if tree.sample_type == sample_type { - value_idx = i as i32; - break; - } - } - - if value_idx == -1 { - return; - } - let u_value_idx = value_idx as usize; + let m = MergeTotalsProcessor::new(tree, p); for l in p.location.iter() { let line = &p.string_table[functions[&l.line[0].function_id].name as usize]; let line_hash = city_hash_64(line.as_bytes()); @@ -127,38 +161,39 @@ fn merge(tree: &mut Tree, p: &Profile) { let location = locations[&s.location_id[i]]; let name = &p.string_table[functions[&location.line[0].function_id].name as usize]; let name_hash = city_hash_64(name.as_bytes()); - let node_id = get_node_id( - parent_id, name_hash,(s.location_id.len() - i) as u16 - ); - if !tree.nodes.contains_key(&parent_id) && tree.nodes_num < 2000000{ + let node_id = get_node_id(parent_id, name_hash, (s.location_id.len() - i) as u16); + if !tree.nodes.contains_key(&parent_id) && tree.nodes_num < 2000000 { tree.nodes.insert(parent_id, Vec::new()); } - let mut slf: u64 = 0; - if i == 0 { - slf = s.value[u_value_idx] as u64; - } - if tree.max_self < slf as i64 { - tree.max_self = slf as i64; - } let mut fake_children: Vec = Vec::new(); - let mut children = tree.nodes - .get_mut(&parent_id) - .unwrap_or(&mut fake_children); - let n = find_node(node_id, children); + let children = tree.nodes.get_mut(&parent_id).unwrap_or(&mut fake_children); + let mut n = find_node(node_id, children); if n == -1 { children.push(TreeNodeV2 { - parent_id, + //parent_id, fn_id: name_hash, node_id, - slf, - total: s.value[u_value_idx] as u64 + slf: vec![0; tree.sample_types.len()], + total: vec![0; tree.sample_types.len()], }); + let idx = children.len().clone() - 1; + let max_self = m.merge_totals( + children.get_mut(idx).unwrap(), + tree.max_self.as_ref(), + s, + i == 0, + ); + tree.max_self = max_self; + n = idx as i32; } else if tree.nodes_num < 2000000 { - children.get_mut(n as usize).unwrap().total += s.value[u_value_idx] as u64; - children.get_mut(n as usize).unwrap().slf += slf; + m.merge_totals( + children.get_mut(n as usize).unwrap(), + &tree.max_self, + s, + i == 0, + ); tree.nodes_num += 1; } - parent_id = node_id; } } @@ -178,38 +213,49 @@ fn read_uleb128(bytes: &[u8]) -> (usize, usize) { (result, shift) } - - -fn bfs(t: &Tree, res: &mut Vec) { - let mut total: u64 = 0; +fn bfs(t: &Tree, res: &mut Vec, sample_type: String) { + let mut total: i64 = 0; let mut root_children: &Vec = &Vec::new(); if t.nodes.contains_key(&(0u64)) { root_children = t.nodes.get(&(0u64)).unwrap(); } + + let mut _sample_type_index: i32 = -1; + for i in 0..t.sample_types.len() { + if t.sample_types[i] == sample_type { + _sample_type_index = i as i32; + break; + } + } + if _sample_type_index == -1 { + return; + } + let sample_type_index = _sample_type_index as usize; + for i in root_children.iter() { - total += i.total; + total += i.total[sample_type_index]; } let mut lvl = Level::default(); - lvl.values.extend([0, total as i64, 0, 0]); + lvl.values.extend([0, total, 0, 0]); res.push(lvl); - let totalNode: TreeNodeV2 = TreeNodeV2 { - slf: 0, - total: total, + let mut totals = vec![0; t.sample_types.len()]; + totals[sample_type_index] = total; + let total_node: TreeNodeV2 = TreeNodeV2 { + slf: vec![0; t.sample_types.len()], + total: totals, node_id: 0, fn_id: 0, - parent_id: 0 + //parent_id: 0 }; - let mut prepend_map: HashMap = HashMap::new(); + let mut prepend_map: HashMap = HashMap::new(); let mut reviewed: HashSet = HashSet::new(); - let mut refs: Vec<&TreeNodeV2> = vec![&totalNode]; - let mut refLen: usize = 1; - let mut i = 0; - while refLen > 0 { - i+=1; - let mut prepend: u64 = 0; + let mut refs: Vec<&TreeNodeV2> = vec![&total_node]; + let mut ref_len: usize = 1; + while ref_len > 0 { + let mut prepend: i64 = 0; let _refs = refs.clone(); refs.clear(); lvl = Level::default(); @@ -218,10 +264,9 @@ fn bfs(t: &Tree, res: &mut Vec) { let opt = t.nodes.get(&parent.node_id); if opt.is_none() { - prepend += parent.total; + prepend += parent.total[sample_type_index]; continue; } - let mut totalSum: u64 = 0; for n in opt.unwrap().iter() { if reviewed.contains(&n.node_id) { // PANIC!!! WE FOUND A LOOP @@ -231,21 +276,18 @@ fn bfs(t: &Tree, res: &mut Vec) { } prepend_map.insert(n.node_id, prepend); refs.push(n); - totalSum += n.total; - lvl.values.extend( - [ - prepend as i64, - n.total as i64, - n.slf as i64, - *t.names_map.get(&n.fn_id).unwrap_or(&1) as i64 - ] - ); + lvl.values.extend([ + prepend as i64, + n.total[sample_type_index], + n.slf[sample_type_index], + *t.names_map.get(&n.fn_id).unwrap_or(&1) as i64, + ]); prepend = 0; } - prepend += parent.slf; + prepend += parent.slf[sample_type_index]; } res.push(lvl.clone()); - refLen = refs.len(); + ref_len = refs.len(); } } @@ -253,84 +295,122 @@ lazy_static! { static ref CTX: Mutex> = Mutex::new(HashMap::new()); } -fn upsert_tree(ctx: &mut HashMap, id: u32) { +fn upsert_tree(ctx: &mut HashMap, id: u32, sample_types: Vec) { if !ctx.contains_key(&id) { + let _len = sample_types.len().clone(); ctx.insert( id, Tree { names: vec!["total".to_string(), "n/a".to_string()], names_map: HashMap::new(), nodes: HashMap::new(), - sample_type: "".to_string(), - max_self: 0, - nodes_num: 1 + sample_types, + max_self: vec![0; _len], + nodes_num: 1, }, ); } } -fn merge_trie(tree: &mut Tree, bytes: &[u8]) { - let mut size = 0; - let mut offs = 0; - (size, offs) = read_uleb128(bytes); +struct TrieReader { + bytes: Vec, + offs: usize, +} + +impl TrieReader { + fn new(bytes: &[u8]) -> TrieReader { + TrieReader { + bytes: bytes.to_vec(), + offs: 0, + } + } + + fn read_uint64_le(&mut self) -> u64 { + let res = read_uint64_le(&self.bytes[self.offs..]); + self.offs += 8; + res + } + + fn read_size(&mut self) -> usize { + let res = read_uleb128(&self.bytes[self.offs..]); + self.offs += res.1; + res.0 + } + + fn read_string(&mut self) -> String { + let size = self.read_size(); + let string = String::from_utf8_lossy(&self.bytes[self.offs..self.offs + size]).to_string(); + self.offs += size; + string + } + /*fn end(&self) -> bool { + self.offs >= self.bytes.len() + }*/ +} + +fn merge_trie(tree: &mut Tree, bytes: &[u8], samples_type: &String) { + let _sample_type_index = tree.sample_types.iter().position(|x| x == samples_type); + if _sample_type_index.is_none() { + return; + } + let sample_type_index = _sample_type_index.unwrap(); + let mut reader = TrieReader::new(bytes); + let mut size = reader.read_size(); for _i in 0..size { - let id = read_uint64_le(&bytes[offs..]); - offs += 8; - let mut _offs: usize = 0; - let mut _size: usize = 0; - (_size, _offs) = read_uleb128(&bytes[offs..]); - offs += _offs; + let id = reader.read_uint64_le(); + let func = reader.read_string(); if !tree.names_map.contains_key(&id) && tree.names.len() < 2000000 { - tree.names.push(String::from_utf8_lossy(&bytes[offs..offs + _size]).to_string()); + tree.names.push(func); tree.names_map.insert(id, tree.names.len() - 1); } - offs += _size; } - let mut _offs: usize = 0; - (size, _offs) = read_uleb128(&bytes[offs..]); - offs += _offs; + size = reader.read_size(); for _i in 0..size { - let parent_id = read_uint64_le(&bytes[offs..]); - offs += 8; - let fn_id = read_uint64_le(&bytes[offs..]); - offs += 8; - let node_id = read_uint64_le(&bytes[offs..]); - offs += 8; - let slf = read_uint64_le(&bytes[offs..]); - offs += 8; - let total = read_uint64_le(&bytes[offs..]); - if tree.max_self < slf as i64 { - tree.max_self = slf as i64; + let parent_id = reader.read_uint64_le(); + let fn_id = reader.read_uint64_le(); + let node_id = reader.read_uint64_le(); + let _slf = reader.read_uint64_le() as i64; + let _total = reader.read_uint64_le() as i64; + if tree.max_self[sample_type_index] < _slf { + tree.max_self[sample_type_index] = _slf; } - offs += 8; + let mut slf = vec![0; tree.sample_types.len()]; + slf[sample_type_index] = _slf; + let mut total = vec![0; tree.sample_types.len()]; + total[sample_type_index] = _total; + let mut n: i32 = -1; if tree.nodes.contains_key(&parent_id) { - let n = find_node(node_id, tree.nodes.get(&parent_id).unwrap()); - if n != -1 { - tree.nodes.get_mut(&parent_id).unwrap().get_mut(n as usize).unwrap().total += total; - tree.nodes.get_mut(&parent_id).unwrap().get_mut(n as usize).unwrap().slf += slf; - } else if tree.nodes_num < 2000000 { - tree.nodes.get_mut(&parent_id).unwrap().push(TreeNodeV2 { - fn_id, - parent_id, - node_id, - slf, - total - }); - tree.nodes_num+=1; - } - - } else if tree.nodes_num < 2000000 { + n = find_node(node_id, tree.nodes.get(&parent_id).unwrap()); + } + if n != -1 { + tree.nodes + .get_mut(&parent_id) + .unwrap() + .get_mut(n as usize) + .unwrap() + .total[sample_type_index] += total[sample_type_index]; + tree.nodes + .get_mut(&parent_id) + .unwrap() + .get_mut(n as usize) + .unwrap() + .slf[sample_type_index] += slf[sample_type_index]; + } + if tree.nodes_num >= 2000000 { + return; + } + if !tree.nodes.contains_key(&parent_id) { tree.nodes.insert(parent_id, Vec::new()); - tree.nodes.get_mut(&parent_id).unwrap().push(TreeNodeV2 { - fn_id, - parent_id, - node_id, - slf, - total - }); - tree.nodes_num+=1; } + tree.nodes.get_mut(&parent_id).unwrap().push(TreeNodeV2 { + fn_id, + //parent_id, + node_id, + slf, + total, + }); + tree.nodes_num += 1; } } @@ -410,8 +490,13 @@ fn upsert_sample(prof: &mut Profile, loc_id: Vec, val: i64, val_idx: i64) - idx } -fn inject_functions(prof: &mut Profile, tree: &Tree, parent_id: u64, - loc_ids: Vec, val_idx: i64) { +fn inject_functions( + prof: &mut Profile, + tree: &Tree, + parent_id: u64, + loc_ids: Vec, + val_idx: i64, +) { if !tree.nodes.contains_key(&parent_id) { return; } @@ -419,16 +504,17 @@ fn inject_functions(prof: &mut Profile, tree: &Tree, parent_id: u64, for node in children.iter() { let mut _loc_ids = loc_ids.clone(); _loc_ids.push(node.fn_id); - upsert_sample(prof, _loc_ids.clone(), node.slf as i64, val_idx); + //TODO: + upsert_sample(prof, _loc_ids.clone(), node.slf[0 /*TODO*/] as i64, val_idx); if tree.nodes.contains_key(&node.node_id) { inject_functions(prof, tree, node.node_id, _loc_ids, val_idx); } } } -fn merge_profile(tree: & Tree, prof: &mut Profile, sample_type: String, sample_unit: String) { +fn merge_profile(tree: &Tree, prof: &mut Profile, sample_type: String, sample_unit: String) { let mut value_type = ValueType::default(); - value_type.r#type=upsert_string(prof, sample_type); + value_type.r#type = upsert_string(prof, sample_type); value_type.unit = upsert_string(prof, sample_unit); prof.sample_type.push(value_type); let type_idx = prof.sample_type.len() as i64 - 1; @@ -440,69 +526,72 @@ fn merge_profile(tree: & Tree, prof: &mut Profile, sample_type: String, sample_u pub fn merge_prof(id: u32, bytes: &[u8], sample_type: String) { let p = panic::catch_unwind(|| { let mut ctx = CTX.lock().unwrap(); - upsert_tree(&mut ctx, id); + upsert_tree(&mut ctx, id, vec![sample_type]); let mut tree = ctx.get_mut(&id).unwrap(); - tree.sample_type = sample_type; let prof = Profile::decode(bytes).unwrap(); merge(&mut tree, &prof); }); match p { - Ok(res) => {} - Err(err) => panic!(err) + Ok(_) => {} + Err(err) => panic!("{:?}", err), } } #[wasm_bindgen] -pub fn merge_tree(id: u32, bytes: &[u8]) { +pub fn merge_tree(id: u32, bytes: &[u8], sample_type: String) { let result = panic::catch_unwind(|| { let mut ctx = CTX.lock().unwrap(); - upsert_tree(&mut ctx, id); + upsert_tree(&mut ctx, id, vec![sample_type.clone()]); let mut tree = ctx.get_mut(&id).unwrap(); - merge_trie(&mut tree, bytes); + merge_trie(&mut tree, bytes, &sample_type); 0 }); match result { - Ok(res) => {} - Err(err) => panic!(err) + Ok(_) => {} + Err(err) => panic!("{:?}", err), } } #[wasm_bindgen] -pub fn export_tree(id: u32) -> Vec { +pub fn export_tree(id: u32, sample_type: String) -> Vec { let p = panic::catch_unwind(|| { let mut ctx = CTX.lock().unwrap(); let mut res = SelectMergeStacktracesResponse::default(); - upsert_tree(&mut ctx, id); - let mut tree = ctx.get_mut(&id).unwrap(); + upsert_tree(&mut ctx, id, vec![sample_type.clone()]); + let tree = ctx.get_mut(&id).unwrap(); let mut fg = FlameGraph::default(); fg.names = tree.names.clone(); - fg.max_self = tree.max_self; + fg.max_self = tree.max_self[0 /* TODO */]; fg.total = 0; let mut root_children: &Vec = &vec![]; if tree.nodes.contains_key(&(0u64)) { root_children = tree.nodes.get(&(0u64)).unwrap(); } for n in root_children.iter() { - fg.total += n.total as i64; + fg.total += n.total[0 /*TODO*/] as i64; } - bfs(tree, &mut fg.levels); + bfs(tree, &mut fg.levels, sample_type.clone()); res.flamegraph = Some(fg); - return res.encode_to_vec(); + return res.encode_to_vec(); }); match p { Ok(res) => return res, - Err(err) => panic!(err) + Err(err) => panic!("{:?}", err), } } #[wasm_bindgen] -pub fn export_trees_pprof(ids: &[u32], - period_type: String, period_unit: String, - _sample_types: String, _sample_units: String) -> Vec { +pub fn export_trees_pprof( + ids: &[u32], + period_type: String, + period_unit: String, + _sample_types: String, + _sample_units: String, +) -> Vec { let p = panic::catch_unwind(|| { let sample_types: Vec<&str> = _sample_types.split(';').collect(); let sample_units: Vec<&str> = _sample_units.split(';').collect(); - let mut res = &mut Profile::default(); + let res = &mut Profile::default(); let mut period = ValueType::default(); period.r#type = upsert_string(res, period_type); period.unit = upsert_string(res, period_unit); @@ -510,16 +599,20 @@ pub fn export_trees_pprof(ids: &[u32], res.period_type = Some(period); let mut ctx = CTX.lock().unwrap(); for i in 0..ids.len() { - upsert_tree(&mut ctx, ids[i]); + upsert_tree(&mut ctx, ids[i], vec![] /*TODO*/); let tree = ctx.get(&ids[i]).unwrap(); - merge_profile(tree, res, - sample_types[i].to_string(), sample_units[i].to_string()) + merge_profile( + tree, + res, + sample_types[i].to_string(), + sample_units[i].to_string(), + ) } - return res.encode_to_vec() + return res.encode_to_vec(); }); match p { Ok(res) => return res, - Err(err) => panic!(err) + Err(err) => panic!("{:?}", err), } } diff --git a/pyroscope/pprof-bin/src/merge.rs b/pyroscope/pprof-bin/src/merge.rs new file mode 100644 index 00000000..59e5e9bd --- /dev/null +++ b/pyroscope/pprof-bin/src/merge.rs @@ -0,0 +1,659 @@ +use crate::ch64::city_hash_64; +use crate::pprof_pb::google::v1::Function; +use crate::pprof_pb::google::v1::Line; +use crate::pprof_pb::google::v1::Location; +use crate::pprof_pb::google::v1::Mapping; +use crate::pprof_pb::google::v1::Sample; +use crate::pprof_pb::google::v1::ValueType; +use crate::pprof_pb::google::v1::{Label, Profile}; +use bytemuck; +use prost::Message; +use std::cmp::Ordering; +use std::collections::HashMap; +use std::hash::{Hash, Hasher}; + +use base64::{engine::general_purpose, Engine as _}; + +struct ProfileMerge { + prof: Option, + tmp: Vec, + + string_table: Option>, + function_table: Option>, + mapping_table: Option>, + location_table: Option>, + sample_table: Option>, +} + +impl ProfileMerge { + fn new() -> ProfileMerge { + ProfileMerge { + prof: Option::None, + tmp: Vec::new(), + + string_table: Option::None, + function_table: Option::None, + mapping_table: Option::None, + location_table: Option::None, + sample_table: Option::None, + } + } + fn merge(&mut self, p: &mut Profile) { + if p.sample.len() == 0 || p.string_table.len() < 2 { + return; + } + + sanitize_profile(&mut Some(p)); + + let mut initial = false; + if self.prof.is_none() { + self.init(p); + initial = true; + } + + self.tmp.resize(p.string_table.len(), 0); + self.string_table + .as_mut() + .unwrap() + .index(&mut self.tmp, &p.string_table); + + rewrite_strings(p, &mut self.tmp); + if initial { + rewrite_strings(self.prof.as_mut().unwrap(), &mut self.tmp) + } + + combine_headers(self.prof.as_mut().unwrap(), p); + + self.tmp.resize(p.function.len(), 0); + self.function_table + .as_mut() + .unwrap() + .index(&mut self.tmp, &p.function); + rewrite_functions(p, &mut self.tmp); + + self.tmp.resize(p.mapping.len(), 0); + self.mapping_table + .as_mut() + .unwrap() + .index(&mut self.tmp, &p.mapping); + rewrite_mappings(p, &mut self.tmp); + + self.tmp.resize(p.location.len(), 0); + self.location_table + .as_mut() + .unwrap() + .index(&mut self.tmp, &p.location); + rewrite_locations(p, &mut self.tmp); + + self.tmp.resize(p.sample.len(), 0); + self.sample_table + .as_mut() + .unwrap() + .index(&mut self.tmp, &p.sample); + + for i in 0..self.tmp.len() { + let idx = self.tmp[i]; + let dst = &mut self.sample_table.as_mut().unwrap().s[idx as usize].value; + let src = p.sample[self.tmp[i] as usize].value.clone(); + for j in 0..src.len() { + dst[j] += src[j]; + } + } + } + + fn init(&mut self, p: &mut Profile) { + let factor = 2; + self.string_table = Some(RewriteTable::new( + factor * p.string_table.len(), + |s| s.clone(), + |s| s.clone(), + )); + self.function_table = Some(RewriteTable::new( + factor * p.function.len(), + FunctionKey::get, + |s| s.clone(), + )); + self.mapping_table = Some(RewriteTable::new( + factor * p.mapping.len(), + MappingKey::get, + |s| s.clone(), + )); + self.location_table = Some(RewriteTable::new( + factor * p.location.len(), + LocationKey::get, + |s| s.clone(), + )); + self.sample_table = Some(RewriteTable::new( + factor * p.sample.len(), + SampleKey::get, + |s| s.clone(), + )); + let mut _prof = Profile::default(); + _prof.sample_type = vec![]; + + _prof.drop_frames = p.drop_frames.clone(); + _prof.keep_frames = p.keep_frames.clone(); + _prof.time_nanos = p.time_nanos.clone(); + _prof.period_type = p.period_type.clone(); + _prof.period = p.period.clone(); + _prof.default_sample_type = p.default_sample_type.clone(); + for s in 0..p.sample_type.len() { + _prof.sample_type.push(p.sample_type[s].clone()); + } + self.prof = Some(_prof); + } +} + +fn rewrite_strings(p: &mut Profile, n: &Vec) { + for i in 0..p.sample_type.len() { + let t = &mut p.sample_type[i]; + if t.unit != 0 { + t.unit = n[t.unit as usize] as i64; + } + if t.r#type != 0 { + t.r#type = n[t.r#type as usize] as i64; + } + } + for i in 0..p.sample.len() { + let s = &mut p.sample[i]; + for j in 0..s.label.len() { + let l = &mut s.label[j]; + l.key = n[l.key as usize] as i64; + l.str = n[l.str as usize] as i64; + } + } + + for i in 0..p.mapping.len() { + let m = &mut p.mapping[i]; + m.filename = n[m.filename as usize] as i64; + m.build_id = n[m.build_id as usize] as i64; + } + + for i in 0..p.function.len() { + let f = &mut p.function[i]; + f.name = n[f.name as usize] as i64; + f.filename = n[f.filename as usize] as i64; + f.system_name = n[f.system_name as usize] as i64; + } + p.drop_frames = n[p.drop_frames as usize] as i64; + p.keep_frames = n[p.keep_frames as usize] as i64; + if !p.period_type.is_none() { + if p.period_type.as_mut().unwrap().r#type != 0 { + p.period_type.as_mut().unwrap().r#type = + n[p.period_type.as_mut().unwrap().r#type as usize] as i64; + } + if p.period_type.as_mut().unwrap().unit != 0 { + p.period_type.as_mut().unwrap().unit = + n[p.period_type.as_mut().unwrap().unit as usize] as i64; + } + } + + for i in 0..p.comment.len() { + let x = p.comment[i]; + p.comment[i] = n[x as usize] as i64; + } + p.default_sample_type = n[p.default_sample_type as usize] as i64; +} + +fn rewrite_functions(p: &mut Profile, n: &Vec) { + for i in 0..p.location.len() { + let loc = &mut p.location[i]; + for j in 0..loc.line.len() { + let line = &mut loc.line[j]; + if line.function_id > 0 { + line.function_id = n[line.function_id as usize - 1] as u64 + 1; + } + } + } +} + +fn rewrite_mappings(p: &mut Profile, n: &mut Vec) { + for i in 0..p.location.len() { + let loc = &mut p.location[i]; + if loc.mapping_id > 0 { + loc.mapping_id = n[loc.mapping_id as usize - 1] as u64 + 1; + } + } +} + +fn rewrite_locations(p: &mut Profile, n: &mut Vec) { + for i in 0..p.sample.len() { + let s = &mut p.sample[i]; + for j in 0..s.location_id.len() { + if s.location_id[j] > 0 { + s.location_id[j] = n[s.location_id[j] as usize - 1] as u64 + 1; + } + } + } +} + +fn sanitize_profile(_p: &mut Option<&mut Profile>) { + if _p.is_none() { + return; + } + let p = _p.as_mut().unwrap(); + let mut ms = p.string_table.len() as i64; + let mut z: i64 = -1; + for i in 0..p.string_table.len() { + let s = &p.string_table[i]; + if s == "" { + z = i as i64; + break; + } + } + if z == -1 { + z = ms; + p.string_table.push("".to_string()); + ms += 1; + } + let tmp = p.string_table[0].clone(); + p.string_table[0] = p.string_table[z as usize].clone(); + p.string_table[z as usize] = tmp; + + let str = |i: i64| -> i64 { + if i == 0 && z > 0 { + return z; + } + if i == z || i >= ms || i < 0 { + return 0; + } + return i; + }; + p.sample_type = remove_in_place(&mut p.sample_type, &mut |x, _| -> bool { + x.r#type = str(x.r#type); + x.unit = str(x.unit); + false + }); + + if !p.period_type.is_none() { + p.period_type.as_mut().unwrap().r#type = str(p.period_type.as_mut().unwrap().r#type); + p.period_type.as_mut().unwrap().unit = str(p.period_type.as_mut().unwrap().unit); + } + + p.default_sample_type = str(p.default_sample_type); + p.drop_frames = str(p.drop_frames); + p.keep_frames = str(p.keep_frames); + for i in 0..p.comment.len() { + p.comment[i] = str(p.comment[i]); + } + + let mut t: HashMap = HashMap::new(); + let mut j: u64 = 1; + p.mapping = remove_in_place(&mut p.mapping, &mut |x, _| -> bool { + x.build_id = str(x.build_id); + x.filename = str(x.filename); + t.insert(x.id, j); + x.id = j; + j += 1; + false + }); + + let mut mapping: Option = Option::None; + let p_mapping = &mut p.mapping; + p.location = remove_in_place(&mut p.location, &mut |x, _| -> bool { + if x.mapping_id == 0 { + if mapping.is_none() { + let mut _mapping = Mapping::default(); + _mapping.id = p_mapping.len() as u64 + 1; + mapping = Some(_mapping.clone()); + p_mapping.push(_mapping); + } + x.mapping_id = mapping.as_ref().unwrap().id; + return false; + } + x.mapping_id = t[&x.mapping_id]; + return x.mapping_id == 0; + }); + + t.clear(); + + j = 1; + p.function = remove_in_place(&mut p.function, &mut |x, _| -> bool { + x.name = str(x.name); + x.system_name = str(x.system_name); + x.filename = str(x.filename); + t.insert(x.id, j); + x.id = j; + j += 1; + false + }); + + p.location = remove_in_place(&mut p.location, &mut |x, _| -> bool { + for i in 0..x.line.len() { + let line = &mut x.line[i]; + line.function_id = t[&line.function_id]; + if line.function_id == 0 { + return true; + } + } + return false; + }); + + t.clear(); + j = 1; + for i in 0..p.location.len() { + let x = &mut p.location[i]; + t.insert(x.id, j); + x.id = j; + j += 1; + } + + let vs = p.sample_type.len(); + p.sample = remove_in_place(&mut p.sample, &mut |x, _| -> bool { + if x.value.len() != vs { + return true; + } + for i in 0..x.location_id.len() { + x.location_id[i] = t[&x.location_id[i]]; + if x.location_id[i] == 0 { + return true; + } + } + for i in 0..x.label.len() { + let l = &mut x.label[i]; + l.key = str(l.key); + l.str = str(l.str); + l.num_unit = str(l.num_unit); + } + false + }); +} + +fn remove_in_place bool>( + collection: &mut Vec, + predicate: &mut F, +) -> Vec { + let mut i: usize = 0; + for j in 0..collection.len() { + if !predicate(&mut collection[j], j as i64) { + let tmp = collection[i].clone(); + collection[i] = collection[j].clone(); + collection[j] = tmp; + i += 1; + } + } + return collection[..i].to_vec(); + /* + i := 0 + for j, x := range collection { + if !predicate(x, j) { + collection[j], collection[i] = collection[i], collection[j] + i++ + } + } + return collection[:i] + + */ +} + +fn combine_headers(a: &mut Profile, b: &Profile) { + compatible(a, b); + if a.time_nanos == 0 || b.time_nanos < a.time_nanos { + a.time_nanos = b.time_nanos + } + a.duration_nanos += b.duration_nanos; + if a.period == 0 || a.period < b.period { + a.period = b.period + } + if a.default_sample_type == 0 { + a.default_sample_type = b.default_sample_type + } +} +fn compatible(a: &Profile, b: &Profile) { + if !equal_value_type(&a.period_type, &b.period_type) { + panic!( + "incompatible period types {:?} and {:?}", + a.period_type, b.period_type + ); + } + if b.sample_type.len() != a.sample_type.len() { + panic!( + "incompatible sample types {:?} and {:?}", + a.sample_type, b.sample_type + ); + } + for i in 0..a.sample_type.len() { + if !equal_value_type( + &Some(a.sample_type[i].clone()), + &Some(b.sample_type[i].clone()), + ) { + panic!( + "incompatible sample types {:?} and {:?}", + a.sample_type, b.sample_type + ); + } + } +} + +fn equal_value_type(st1: &Option, st2: &Option) -> bool { + if st1.is_none() || st2.is_none() { + return false; + } + return st1.as_ref().unwrap().r#type == st2.as_ref().unwrap().r#type + && st1.as_ref().unwrap().unit == st2.as_ref().unwrap().unit; +} + +struct FunctionKey { + start_line: u32, + name: u32, + system_name: u32, + file_name: u32, +} + +impl FunctionKey { + fn get(f: &Function) -> FunctionKey { + return FunctionKey { + start_line: f.start_line as u32, + name: f.name as u32, + system_name: f.system_name as u32, + file_name: f.filename as u32, + }; + } +} + +impl PartialEq for FunctionKey { + fn eq(&self, other: &Self) -> bool { + return self.name == other.name + && self.system_name == other.system_name + && self.file_name == other.file_name + && self.start_line == other.start_line; + } +} + +impl Eq for FunctionKey {} + +impl Hash for FunctionKey { + fn hash(&self, state: &mut H) { + state.write_u32(self.name); + state.write_u32(self.system_name); + state.write_u32(self.file_name); + state.write_u32(self.start_line); + } +} + +struct MappingKey { + size: u64, + offset: u64, + build_id_or_file: i64, +} + +impl MappingKey { + fn get(m: &Mapping) -> MappingKey { + let mapsize_rounding = 0x1000; + let mut size = m.memory_limit - m.memory_start; + size = size + mapsize_rounding - 1; + size = size - (size % mapsize_rounding); + let mut k = MappingKey { + size: size, + offset: m.file_offset, + build_id_or_file: 0, + }; + if m.build_id != 0 { + k.build_id_or_file = m.build_id; + } + if m.filename != 0 { + k.build_id_or_file = m.filename; + } + k + } +} + +impl PartialEq for MappingKey { + fn eq(&self, other: &Self) -> bool { + return self.build_id_or_file == other.build_id_or_file + && self.offset == other.offset + && self.size == other.size; + } +} + +impl Eq for MappingKey {} + +impl Hash for MappingKey { + fn hash(&self, state: &mut H) { + state.write_i64(self.build_id_or_file); + state.write_u64(self.offset); + state.write_u64(self.size); + } +} + +struct LocationKey { + addr: u64, + lines: u64, + mapping_id: u64, +} + +impl LocationKey { + fn get(l: &Location) -> LocationKey { + return LocationKey { + addr: l.address, + lines: hash_lines(&l.line), + mapping_id: l.mapping_id, + }; + } +} + +impl PartialEq for LocationKey { + fn eq(&self, other: &Self) -> bool { + return self.lines == other.lines + && self.mapping_id == other.mapping_id + && self.addr == other.addr; + } +} + +impl Eq for LocationKey {} + +impl Hash for LocationKey { + fn hash(&self, state: &mut H) { + state.write_u64(self.lines); + state.write_u64(self.mapping_id); + state.write_u64(self.addr); + } +} + +fn hash_lines(s: &Vec) -> u64 { + let mut x = vec![0 as u64; s.len()]; + for i in 0..s.len() { + x[i] = s[i].function_id | ((s[i].line as u64) << 32) + } + let u64_arr = x.as_slice(); + let u8_arr: &[u8] = bytemuck::cast_slice(u64_arr); + return city_hash_64(u8_arr); +} + +struct SampleKey { + locations: u64, + labels: u64, +} + +impl SampleKey { + fn get(s: &Sample) -> SampleKey { + return SampleKey { + locations: hash_locations(&s.location_id), + labels: hash_labels(&s.label), + }; + } +} + +impl PartialEq for SampleKey { + fn eq(&self, other: &Self) -> bool { + return self.locations == other.locations && self.labels == other.labels; + } +} + +impl Eq for SampleKey {} + +impl Hash for SampleKey { + fn hash(&self, state: &mut H) { + state.write_u64(self.locations); + state.write_u64(self.labels); + } +} + +fn hash_labels(labels: &Vec