Skip to content

Commit b6f2a0f

Browse files
authored
chore: support for pprof as transport format in ruby
2 parents 74d2ea7 + 0225ec4 commit b6f2a0f

File tree

14 files changed

+809
-38
lines changed

14 files changed

+809
-38
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
.idea/
12
/target/
23
Cargo.lock
34
**/*.rs.bk

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ reqwest = { version = "0.11", features = ["blocking", "rustls-tls-native-roots"]
5858
url = "2.2.2"
5959
libflate = "1.2.0"
6060
libc = "^0.2.124"
61+
prost = "0.10"
6162

6263
[dev-dependencies]
6364
tokio = { version = "1.18", features = ["full"] }

pyroscope_cli/Cargo.lock

Lines changed: 34 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyroscope_ffi/ruby/ext/rbspy/src/lib.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use pyroscope_rbspy::{rbspy_backend, RbspyConfig};
1010

1111
use pyroscope::{pyroscope::Compression, PyroscopeAgent};
1212
use pyroscope::backend::{Report, StackFrame, Tag};
13+
use pyroscope::pyroscope::ReportEncoding;
1314

1415
pub fn transform_report(report: Report) -> Report {
1516
let cwd = env::current_dir().unwrap();
@@ -107,7 +108,8 @@ pub extern "C" fn initialize_logging(logging_level: u32) -> bool {
107108
pub extern "C" fn initialize_agent(
108109
application_name: *const c_char, server_address: *const c_char, auth_token: *const c_char,
109110
sample_rate: u32, detect_subprocesses: bool, oncpu: bool, report_pid: bool,
110-
report_thread_id: bool, tags: *const c_char, compression: *const c_char
111+
report_thread_id: bool, tags: *const c_char, compression: *const c_char,
112+
report_encoding: *const c_char
111113
) -> bool {
112114
// Initialize FFIKit
113115
let recv = ffikit::initialize_ffi().unwrap();
@@ -137,7 +139,14 @@ pub extern "C" fn initialize_agent(
137139
.unwrap()
138140
.to_string();
139141

142+
let report_encoding = unsafe { CStr::from_ptr(report_encoding) }
143+
.to_str()
144+
.unwrap()
145+
.to_string();
146+
140147
let compression = Compression::from_str(&compression_string);
148+
let report_encoding = ReportEncoding::from_str(&report_encoding)
149+
.unwrap_or(ReportEncoding::FOLDED);
141150

142151
let pid = std::process::id();
143152

@@ -156,7 +165,8 @@ pub extern "C" fn initialize_agent(
156165
let mut agent_builder = PyroscopeAgent::builder(server_address, application_name)
157166
.backend(rbspy)
158167
.func(transform_report)
159-
.tags(tags);
168+
.tags(tags)
169+
.report_encoding(report_encoding);
160170

161171
if auth_token != "" {
162172
agent_builder = agent_builder.auth_token(auth_token);

pyroscope_ffi/ruby/lib/pyroscope.rb

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ module Rust
88
extend FFI::Library
99
ffi_lib File.expand_path(File.dirname(__FILE__)) + "/rbspy/rbspy.#{RbConfig::CONFIG["DLEXT"]}"
1010
attach_function :initialize_logging, [:int], :bool
11-
attach_function :initialize_agent, [:string, :string, :string, :int, :bool, :bool, :bool, :bool, :string, :string], :bool
11+
attach_function :initialize_agent, [:string, :string, :string, :int, :bool, :bool, :bool, :bool, :string, :string, :string], :bool
1212
attach_function :add_thread_tag, [:uint64, :string, :string], :bool
1313
attach_function :remove_thread_tag, [:uint64, :string, :string], :bool
1414
attach_function :add_global_tag, [:string, :string], :bool
@@ -22,7 +22,7 @@ module Utils
2222
attach_function :thread_id, [], :uint64
2323
end
2424

25-
Config = Struct.new(:application_name, :app_name, :server_address, :auth_token, :log_level, :sample_rate, :detect_subprocesses, :oncpu, :report_pid, :report_thread_id, :tags, :compression) do
25+
Config = Struct.new(:application_name, :app_name, :server_address, :auth_token, :log_level, :sample_rate, :detect_subprocesses, :oncpu, :report_pid, :report_thread_id, :tags, :compression, :report_encoding) do
2626
def initialize(*)
2727
super
2828
# defaults:
@@ -37,6 +37,7 @@ def initialize(*)
3737
self.log_level = 'error'
3838
self.tags = {}
3939
self.compression = 'gzip'
40+
self.report_encoding = 'pprof'
4041
end
4142
end
4243

@@ -79,7 +80,8 @@ def configure
7980
@config.report_pid || false,
8081
@config.report_thread_id || false,
8182
tags_to_string(@config.tags || {}),
82-
@config.compression || ""
83+
@config.compression || "",
84+
@config.report_encoding || "pprof"
8385
)
8486
end
8587

src/backend/types.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,15 @@ pub struct Report {
130130
pub metadata: Metadata,
131131
}
132132

133+
#[derive(Debug)]
134+
pub struct EncodedReport {
135+
pub format: String,
136+
pub content_type: String,
137+
pub content_encoding: String,
138+
pub data: Vec<u8>,
139+
pub metadata: Metadata,
140+
}
141+
133142
/// Custom implementation of the Hash trait for Report.
134143
/// Only the metadata is hashed.
135144
impl Hash for Report {

src/encode/folded.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
2+
use crate::backend;
3+
4+
use backend::types::{Report, EncodedReport};
5+
6+
pub fn encode(reports: &Vec<Report>) -> Vec<EncodedReport> {
7+
reports.into_iter()
8+
.map(|r| {
9+
EncodedReport {
10+
format: "folded".to_string(),
11+
content_type: "binary/octet-stream".to_string(),
12+
content_encoding: "".to_string(),
13+
data: r.to_string().into_bytes(),
14+
metadata: r.metadata.to_owned(),
15+
}
16+
}).collect()
17+
}

src/encode/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pub mod folded;
2+
pub mod pprof;
3+
pub mod profiles;
4+
5+

src/encode/pprof.rs

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
use std::collections::HashMap;
2+
3+
use prost::Message;
4+
5+
use crate::backend::types::{EncodedReport, Report};
6+
use crate::encode::profiles::{Function, Label, Line, Location, Profile, Sample, ValueType};
7+
8+
9+
struct PProfBuilder {
10+
profile: Profile,
11+
strings: HashMap<String, i64>,
12+
functions: HashMap<FunctionMirror, u64>,
13+
locations: HashMap<LocationMirror, u64>,
14+
}
15+
16+
#[derive(Hash, PartialEq, Eq, Clone)]
17+
pub struct LocationMirror {
18+
pub function_id: u64,
19+
pub line: i64,
20+
}
21+
22+
#[derive(Hash, PartialEq, Eq, Clone)]
23+
pub struct FunctionMirror {
24+
pub name: i64,
25+
pub filename: i64,
26+
}
27+
28+
impl PProfBuilder {
29+
fn add_string(&mut self, s: &String) -> i64 {
30+
let v = self.strings.get(s);
31+
if v.is_some() {
32+
return *v.unwrap();
33+
}
34+
assert!(self.strings.len() != self.profile.string_table.len() + 1);
35+
let id: i64 = self.strings.len() as i64;
36+
self.strings.insert(s.to_owned(), id);
37+
self.profile.string_table.push(s.to_owned());
38+
id
39+
}
40+
41+
fn add_function(&mut self, fm: FunctionMirror) -> u64 {
42+
let v = self.functions.get(&fm);
43+
if v.is_some() {
44+
return *v.unwrap();
45+
}
46+
assert!(self.functions.len() != self.profile.function.len() + 1);
47+
let id: u64 = self.functions.len() as u64 + 1;
48+
let f = Function {
49+
id: id,
50+
name: fm.name,
51+
system_name: 0,
52+
filename: fm.filename,
53+
start_line: 0,
54+
};
55+
self.functions.insert(fm, id);
56+
self.profile.function.push(f);
57+
id
58+
}
59+
60+
fn add_location(&mut self, lm: LocationMirror) -> u64 {
61+
let v = self.locations.get(&lm);
62+
if v.is_some() {
63+
return *v.unwrap();
64+
}
65+
assert!(self.locations.len() != self.profile.location.len() + 1);
66+
let id: u64 = self.locations.len() as u64 + 1;
67+
let l = Location {
68+
id,
69+
mapping_id: 0,
70+
address: 0,
71+
line: vec![Line {
72+
function_id: lm.function_id,
73+
line: lm.line,
74+
}],
75+
is_folded: false,
76+
};
77+
self.locations.insert(lm, id);
78+
self.profile.location.push(l);
79+
id
80+
}
81+
}
82+
83+
pub fn encode(reports: &Vec<Report>, sample_rate: u32, start_time_nanos: u64, duration_nanos: u64) -> Vec<EncodedReport> {
84+
let mut b = PProfBuilder {
85+
strings: HashMap::new(),
86+
functions: HashMap::new(),
87+
locations: HashMap::new(),
88+
profile: Profile {
89+
sample_type: vec![],
90+
sample: vec![],
91+
mapping: vec![],
92+
location: vec![],
93+
function: vec![],
94+
string_table: vec![],
95+
drop_frames: 0,
96+
keep_frames: 0,
97+
time_nanos: start_time_nanos as i64,
98+
duration_nanos: duration_nanos as i64,
99+
period_type: None,
100+
period: 0,
101+
comment: vec![],
102+
default_sample_type: 0,
103+
},
104+
};
105+
{
106+
let count = b.add_string(&"count".to_string());
107+
let samples = b.add_string(&"samples".to_string());
108+
let milliseconds = b.add_string(&"milliseconds".to_string());
109+
b.profile.sample_type.push(ValueType {
110+
r#type: samples,
111+
unit: count,
112+
});
113+
b.profile.period = 1_000 / sample_rate as i64;
114+
b.profile.period_type = Some(ValueType {
115+
r#type: 0,
116+
unit: milliseconds,
117+
})
118+
}
119+
for report in reports {
120+
for (stacktrace, value) in &report.data {
121+
let mut sample = Sample {
122+
location_id: vec![],
123+
value: vec![*value as i64],
124+
label: vec![],
125+
};
126+
for sf in &stacktrace.frames {
127+
let name = b.add_string(&sf.name.as_ref().unwrap_or(&"".to_string()));
128+
let filename = b.add_string(&sf.filename.as_ref().unwrap_or(&"".to_string()));
129+
let line = sf.line.unwrap_or(0) as i64;
130+
let function_id = b.add_function(FunctionMirror {
131+
name: name,
132+
filename: filename,
133+
});
134+
let location_id = b.add_location(LocationMirror {
135+
function_id: function_id,
136+
line: line,
137+
});
138+
sample.location_id.push(location_id as u64);
139+
}
140+
let mut labels = HashMap::new();
141+
for l in &stacktrace.metadata.tags {
142+
let k = b.add_string(&l.key);
143+
let v = b.add_string(&l.value);
144+
labels.insert(k, v);
145+
}
146+
for l in &report.metadata.tags {
147+
let k = b.add_string(&l.key);
148+
let v = b.add_string(&l.value);
149+
labels.insert(k, v);
150+
}
151+
for (k, v) in &labels {
152+
sample.label.push(Label {
153+
key: *k,
154+
str: *v,
155+
num: 0,
156+
num_unit: 0,
157+
})
158+
}
159+
b.profile.sample.push(sample);
160+
}
161+
}
162+
163+
vec![EncodedReport {
164+
format: "pprof".to_string(),
165+
content_type: "binary/octet-stream".to_string(),
166+
content_encoding: "".to_string(),
167+
data: b.profile.encode_to_vec(),
168+
metadata: Default::default(),
169+
}]
170+
}

0 commit comments

Comments
 (0)