pyroscope 1.0.9 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,159 @@
1
+ use std::collections::HashMap;
2
+
3
+ use crate::backend::types::Report;
4
+ use crate::encode::gen::google::{Function, Label, Line, Location, Profile, Sample, ValueType};
5
+
6
+ struct PProfBuilder {
7
+ profile: Profile,
8
+ strings: HashMap<String, i64>,
9
+ functions: HashMap<FunctionMirror, u64>,
10
+ locations: HashMap<LocationMirror, u64>,
11
+ }
12
+
13
+ #[derive(Hash, PartialEq, Eq, Clone)]
14
+ pub struct LocationMirror {
15
+ pub function_id: u64,
16
+ pub line: i64,
17
+ }
18
+
19
+ #[derive(Hash, PartialEq, Eq, Clone)]
20
+ pub struct FunctionMirror {
21
+ pub name: i64,
22
+ pub filename: i64,
23
+ }
24
+
25
+ impl PProfBuilder {
26
+ fn add_string(&mut self, s: &String) -> i64 {
27
+ let v = self.strings.get(s);
28
+ if let Some(v) = v {
29
+ return *v;
30
+ }
31
+ assert_ne!(self.strings.len(), self.profile.string_table.len() + 1);
32
+ let id: i64 = self.strings.len() as i64;
33
+ self.strings.insert(s.to_owned(), id);
34
+ self.profile.string_table.push(s.to_owned());
35
+ id
36
+ }
37
+
38
+ fn add_function(&mut self, fm: FunctionMirror) -> u64 {
39
+ let v = self.functions.get(&fm);
40
+ if let Some(v) = v {
41
+ return *v;
42
+ }
43
+ assert_ne!(self.functions.len(), self.profile.function.len() + 1);
44
+ let id: u64 = self.functions.len() as u64 + 1;
45
+ let f = Function {
46
+ id,
47
+ name: fm.name,
48
+ system_name: 0,
49
+ filename: fm.filename,
50
+ start_line: 0,
51
+ };
52
+ self.functions.insert(fm, id);
53
+ self.profile.function.push(f);
54
+ id
55
+ }
56
+
57
+ fn add_location(&mut self, lm: LocationMirror) -> u64 {
58
+ let v = self.locations.get(&lm);
59
+ if let Some(v) = v {
60
+ return *v;
61
+ }
62
+ assert_ne!(self.locations.len(), self.profile.location.len() + 1);
63
+ let id: u64 = self.locations.len() as u64 + 1;
64
+ let l = Location {
65
+ id,
66
+ mapping_id: 0,
67
+ address: 0,
68
+ line: vec![Line {
69
+ function_id: lm.function_id,
70
+ line: lm.line,
71
+ }],
72
+ is_folded: false,
73
+ };
74
+ self.locations.insert(lm, id);
75
+ self.profile.location.push(l);
76
+ id
77
+ }
78
+ }
79
+
80
+ pub fn encode(
81
+ reports: &Vec<Report>,
82
+ sample_rate: u32,
83
+ start_time_nanos: u64,
84
+ duration_nanos: u64,
85
+ ) -> Profile {
86
+ let mut b = PProfBuilder {
87
+ strings: HashMap::new(),
88
+ functions: HashMap::new(),
89
+ locations: HashMap::new(),
90
+ profile: Profile {
91
+ sample_type: vec![],
92
+ sample: vec![],
93
+ mapping: vec![],
94
+ location: vec![],
95
+ function: vec![],
96
+ string_table: vec![],
97
+ drop_frames: 0,
98
+ keep_frames: 0,
99
+ time_nanos: start_time_nanos as i64,
100
+ duration_nanos: duration_nanos as i64,
101
+ period_type: None,
102
+ period: 0,
103
+ comment: vec![],
104
+ default_sample_type: 0,
105
+ },
106
+ };
107
+ b.add_string(&"".to_string());
108
+ {
109
+ let cpu = b.add_string(&"cpu".to_string());
110
+ let nanoseconds = b.add_string(&"nanoseconds".to_string());
111
+ b.profile.sample_type.push(ValueType {
112
+ r#type: cpu,
113
+ unit: nanoseconds,
114
+ });
115
+ b.profile.period = 1_000_000_000 / sample_rate as i64;
116
+ b.profile.period_type = Some(ValueType {
117
+ r#type: cpu,
118
+ unit: nanoseconds,
119
+ });
120
+ }
121
+ for report in reports {
122
+ for (stacktrace, value) in &report.data {
123
+ let mut sample = Sample {
124
+ location_id: vec![],
125
+ value: vec![*value as i64 * b.profile.period],
126
+ label: vec![],
127
+ };
128
+ for sf in &stacktrace.frames {
129
+ let name = b.add_string(sf.name.as_ref().unwrap_or(&"".to_string()));
130
+ let filename = b.add_string(sf.filename.as_ref().unwrap_or(&"".to_string()));
131
+ let line = sf.line.unwrap_or(0) as i64;
132
+ let function_id = b.add_function(FunctionMirror { name, filename });
133
+ let location_id = b.add_location(LocationMirror { function_id, line });
134
+ sample.location_id.push(location_id);
135
+ }
136
+ let mut labels = HashMap::new();
137
+ for l in &stacktrace.metadata.tags {
138
+ let k = b.add_string(&l.key);
139
+ let v = b.add_string(&l.value);
140
+ labels.insert(k, v);
141
+ }
142
+ for l in &report.metadata.tags {
143
+ let k = b.add_string(&l.key);
144
+ let v = b.add_string(&l.value);
145
+ labels.insert(k, v);
146
+ }
147
+ for (k, v) in &labels {
148
+ sample.label.push(Label {
149
+ key: *k,
150
+ str: *v,
151
+ num: 0,
152
+ num_unit: 0,
153
+ })
154
+ }
155
+ b.profile.sample.push(sample);
156
+ }
157
+ }
158
+ b.profile
159
+ }
@@ -0,0 +1,67 @@
1
+ pub type Result<T> = std::result::Result<T, PyroscopeError>;
2
+
3
+ /// Error type of Pyroscope
4
+ #[non_exhaustive]
5
+ #[derive(thiserror::Error, Debug)]
6
+ pub enum PyroscopeError {
7
+ #[error("Other: {}", &.0)]
8
+ AdHoc(String),
9
+
10
+ #[error("{msg}: {source:?}")]
11
+ Compat {
12
+ msg: String,
13
+ #[source]
14
+ source: Box<dyn std::error::Error + Send + Sync + 'static>,
15
+ },
16
+
17
+ #[error("BackendImpl error")]
18
+ BackendImpl,
19
+
20
+ #[error(transparent)]
21
+ Reqwest(#[from] reqwest::Error),
22
+
23
+ #[error(transparent)]
24
+ ParseError(#[from] url::ParseError),
25
+
26
+ #[error(transparent)]
27
+ TimeSource(#[from] std::time::SystemTimeError),
28
+
29
+ #[error(transparent)]
30
+ Io(#[from] std::io::Error),
31
+
32
+ #[error(transparent)]
33
+ Json(#[from] serde_json::Error),
34
+ }
35
+
36
+ impl PyroscopeError {
37
+ /// Create a new instance of PyroscopeError
38
+ pub fn new(msg: &str) -> Self {
39
+ PyroscopeError::AdHoc(msg.to_string())
40
+ }
41
+
42
+ /// Create a new instance of PyroscopeError with source
43
+ pub fn new_with_source<E>(msg: &str, source: E) -> Self
44
+ where
45
+ E: std::error::Error + Send + Sync + 'static,
46
+ {
47
+ PyroscopeError::Compat {
48
+ msg: msg.to_string(),
49
+ source: Box::new(source),
50
+ }
51
+ }
52
+ }
53
+
54
+ impl<T> From<std::sync::PoisonError<T>> for PyroscopeError {
55
+ fn from(_err: std::sync::PoisonError<T>) -> Self {
56
+ PyroscopeError::AdHoc("Poison Error".to_owned())
57
+ }
58
+ }
59
+
60
+ impl<T: 'static + Send + Sync> From<std::sync::mpsc::SendError<T>> for PyroscopeError {
61
+ fn from(err: std::sync::mpsc::SendError<T>) -> Self {
62
+ PyroscopeError::Compat {
63
+ msg: String::from("SendError Error"),
64
+ source: Box::new(err),
65
+ }
66
+ }
67
+ }
@@ -0,0 +1,76 @@
1
+ use crate::backend::Tag;
2
+ use crate::error::{PyroscopeError, Result};
3
+ use crate::pyroscope::{PyroscopeAgentBuilder, PyroscopeAgentRunning};
4
+ use crate::{PyroscopeAgent, ThreadId};
5
+ use lazy_static::lazy_static;
6
+ use std::sync::{
7
+ mpsc::{self, Receiver, Sender},
8
+ Mutex,
9
+ };
10
+
11
+ #[derive(Debug, PartialEq, Clone)]
12
+ pub enum Signal {
13
+ Kill,
14
+ AddThreadTag(ThreadId, Tag),
15
+ RemoveThreadTag(ThreadId, Tag),
16
+ }
17
+
18
+ const TAG: &str = "pyroscope::ffikit";
19
+
20
+ lazy_static! {
21
+ static ref SENDER: Mutex<Option<Sender<Signal>>> = Mutex::new(None);
22
+ }
23
+ pub fn run(agent: PyroscopeAgentBuilder) -> Result<()> {
24
+ let mut sender_holder = SENDER.lock()?;
25
+ if (*sender_holder).is_some() {
26
+ return Err(PyroscopeError::new("FFI channel already initialized"));
27
+ }
28
+
29
+ let agent = agent.build()?;
30
+
31
+ let agent = agent.start()?;
32
+
33
+ let (sender, receiver): (Sender<Signal>, Receiver<Signal>) = mpsc::channel();
34
+
35
+ *sender_holder = Some(sender);
36
+
37
+ std::thread::spawn(move || {
38
+ while let Ok(signal) = receiver.recv() {
39
+ match signal {
40
+ Signal::Kill => {
41
+ if let Err(err) = stop(agent) {
42
+ log::error!(target: TAG, "failed to stop agent {err}");
43
+ }
44
+ break;
45
+ }
46
+ Signal::AddThreadTag(thread_id, tag) => {
47
+ if let Err(err) = agent.add_thread_tag(thread_id, tag) {
48
+ log::error!(target: TAG, "failed to add tag {err}");
49
+ }
50
+ }
51
+ Signal::RemoveThreadTag(thread_id, tag) => {
52
+ if let Err(err) = agent.remove_thread_tag(thread_id, tag) {
53
+ log::error!(target: TAG, "failed to remove tag {err}");
54
+ }
55
+ }
56
+ }
57
+ }
58
+ });
59
+
60
+ Ok(())
61
+ }
62
+
63
+ pub fn send(signal: Signal) -> Result<()> {
64
+ if let Some(sender) = &*SENDER.lock()? {
65
+ sender.send(signal)?;
66
+ } else {
67
+ return Err(PyroscopeError::new("FFI channel not initialized"));
68
+ }
69
+ Ok(())
70
+ }
71
+
72
+ fn stop(agent: PyroscopeAgent<PyroscopeAgentRunning>) -> Result<()> {
73
+ agent.stop()?;
74
+ *SENDER.lock()? = None;
75
+ Ok(())
76
+ }
data/ext/rbspy/src/lib.rs CHANGED
@@ -1,4 +1,17 @@
1
- mod backend;
1
+ pub use crate::pyroscope::PyroscopeAgent;
2
+ pub use error::{PyroscopeError, Result};
3
+
4
+ pub mod backend;
5
+ pub mod encode;
6
+ pub mod error;
7
+ pub mod ffikit;
8
+ pub mod pyroscope;
9
+ pub mod session;
10
+ pub mod timer;
11
+
12
+ mod rbspy_backend;
13
+ mod utils;
14
+ pub use utils::ThreadId;
2
15
 
3
16
  use rbspy::sampler::Sampler;
4
17
  use remoteprocess::Pid;
@@ -6,9 +19,9 @@ use std::env;
6
19
  use std::ffi::CStr;
7
20
  use std::os::raw::c_char;
8
21
 
9
- use crate::backend::Rbspy;
10
- use pyroscope::backend::{BackendConfig, BackendImpl, Report, StackFrame, Tag};
11
- use pyroscope::pyroscope::PyroscopeAgentBuilder;
22
+ use crate::backend::{BackendConfig, BackendImpl, Report, StackFrame, Tag};
23
+ use crate::pyroscope::PyroscopeAgentBuilder;
24
+ use crate::rbspy_backend::Rbspy;
12
25
 
13
26
  const LOG_TAG: &str = "Pyroscope::rbspy::ffi";
14
27
  const RBSPY_NAME: &str = "rbspy";
@@ -110,6 +123,8 @@ pub unsafe extern "C" fn initialize_agent(
110
123
  oncpu: bool,
111
124
  report_pid: bool,
112
125
  report_thread_id: bool,
126
+ runtime_name: *const c_char,
127
+ runtime_version: *const c_char,
113
128
  tags: *const c_char,
114
129
  tenant_id: *const c_char,
115
130
  http_headers_json: *const c_char,
@@ -134,6 +149,16 @@ pub unsafe extern "C" fn initialize_agent(
134
149
  .unwrap()
135
150
  .to_string();
136
151
 
152
+ let runtime_name = unsafe { CStr::from_ptr(runtime_name) }
153
+ .to_str()
154
+ .unwrap()
155
+ .to_string();
156
+
157
+ let runtime_version = unsafe { CStr::from_ptr(runtime_version) }
158
+ .to_str()
159
+ .unwrap()
160
+ .to_string();
161
+
137
162
  let tags_string = unsafe { CStr::from_ptr(tags) }
138
163
  .to_str()
139
164
  .unwrap()
@@ -172,7 +197,8 @@ pub unsafe extern "C" fn initialize_agent(
172
197
  rbspy,
173
198
  )
174
199
  .func(transform_report_with_current_dir)
175
- .tags(tags);
200
+ .tags(tags)
201
+ .runtime(runtime_name, runtime_version);
176
202
 
177
203
  if !basic_auth_user.is_empty() && !basic_auth_password.is_empty() {
178
204
  agent_builder = agent_builder.basic_auth(basic_auth_user, basic_auth_password);
@@ -182,28 +208,28 @@ pub unsafe extern "C" fn initialize_agent(
182
208
  agent_builder = agent_builder.tenant_id(tenant_id);
183
209
  }
184
210
 
185
- let http_headers = pyroscope::pyroscope::parse_http_headers_json(http_headers_json);
211
+ let http_headers = crate::pyroscope::parse_http_headers_json(http_headers_json);
186
212
  match http_headers {
187
213
  Ok(http_headers) => {
188
214
  agent_builder = agent_builder.http_headers(http_headers);
189
215
  }
190
216
  Err(e) => match e {
191
- pyroscope::PyroscopeError::Json(e) => {
217
+ crate::PyroscopeError::Json(e) => {
192
218
  log::error!(target: LOG_TAG, "parse_http_headers_json error {}", e);
193
219
  }
194
- pyroscope::PyroscopeError::AdHoc(e) => {
220
+ crate::PyroscopeError::AdHoc(e) => {
195
221
  log::error!(target: LOG_TAG, "parse_http_headers_json {}", e);
196
222
  }
197
223
  _ => {}
198
224
  },
199
225
  }
200
226
 
201
- pyroscope::ffikit::run(agent_builder).is_ok()
227
+ crate::ffikit::run(agent_builder).is_ok()
202
228
  }
203
229
 
204
230
  #[no_mangle]
205
231
  pub extern "C" fn drop_agent() -> bool {
206
- pyroscope::ffikit::send(pyroscope::ffikit::Signal::Kill).is_ok()
232
+ crate::ffikit::send(crate::ffikit::Signal::Kill).is_ok()
207
233
  }
208
234
 
209
235
  #[no_mangle]
@@ -216,8 +242,8 @@ pub unsafe extern "C" fn add_thread_tag(key: *const c_char, value: *const c_char
216
242
  .unwrap()
217
243
  .to_owned();
218
244
 
219
- pyroscope::ffikit::send(pyroscope::ffikit::Signal::AddThreadTag(
220
- backend::self_thread_id(),
245
+ crate::ffikit::send(crate::ffikit::Signal::AddThreadTag(
246
+ rbspy_backend::self_thread_id(),
221
247
  Tag { key, value },
222
248
  ))
223
249
  .is_ok()
@@ -233,8 +259,8 @@ pub unsafe extern "C" fn remove_thread_tag(key: *const c_char, value: *const c_c
233
259
  .unwrap()
234
260
  .to_owned();
235
261
 
236
- pyroscope::ffikit::send(pyroscope::ffikit::Signal::RemoveThreadTag(
237
- backend::self_thread_id(),
262
+ crate::ffikit::send(crate::ffikit::Signal::RemoveThreadTag(
263
+ rbspy_backend::self_thread_id(),
238
264
  Tag { key, value },
239
265
  ))
240
266
  .is_ok()
@@ -260,7 +286,7 @@ fn string_to_tags(tags: &str) -> Vec<(&str, &str)> {
260
286
  #[cfg(test)]
261
287
  mod tests {
262
288
  use super::*;
263
- use pyroscope::backend::{Report, StackFrame, StackTrace};
289
+ use crate::backend::{Report, StackFrame, StackTrace};
264
290
  use std::collections::HashMap;
265
291
 
266
292
  fn report_with_filename(filename: &str) -> Report {