pyroscope 1.0.6-aarch64-linux → 1.0.7-aarch64-linux

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,348 @@
1
+ mod backend;
2
+
3
+ use rbspy::sampler::Sampler;
4
+ use remoteprocess::Pid;
5
+ use std::env;
6
+ use std::ffi::CStr;
7
+ use std::os::raw::c_char;
8
+
9
+ use crate::backend::Rbspy;
10
+ use pyroscope::backend::{BackendConfig, BackendImpl, Report, StackFrame, Tag};
11
+ use pyroscope::pyroscope::PyroscopeAgentBuilder;
12
+
13
+ const LOG_TAG: &str = "Pyroscope::rbspy::ffi";
14
+ const RBSPY_NAME: &str = "rbspy";
15
+ const RBSPY_VERSION: &str = env!("CARGO_PKG_VERSION");
16
+
17
+ fn transform_report_with_current_dir(report: Report) -> Report {
18
+ let cwd = env::current_dir().ok();
19
+ let cwd = cwd.as_deref().and_then(|p| p.to_str());
20
+ transform_report(report, cwd)
21
+ }
22
+
23
+ pub fn transform_report(report: Report, cwd: Option<&str>) -> Report {
24
+ let data = report
25
+ .data
26
+ .iter()
27
+ .map(|(stacktrace, count)| {
28
+ let new_frames = stacktrace
29
+ .frames
30
+ .iter()
31
+ .map(|frame| {
32
+ let frame = frame.to_owned();
33
+ let mut s = frame.filename.unwrap();
34
+ let stripped = cwd
35
+ .and_then(|c| s.strip_prefix(c))
36
+ .and_then(|rest| rest.strip_prefix('/'));
37
+ if let Some(rest) = stripped {
38
+ s = rest.to_string();
39
+ } else if let Some(i) = s.find("/gems/") {
40
+ s = s[(i + 1)..].to_string();
41
+ } else if let Some(i) = s.find("/ruby/") {
42
+ s = s[(i + 6)..].to_string();
43
+ if let Some(i) = s.find('/') {
44
+ s = s[(i + 1)..].to_string();
45
+ }
46
+ }
47
+
48
+ // something
49
+ StackFrame::new(
50
+ frame.module,
51
+ frame.name,
52
+ Some(s.to_string()),
53
+ frame.relative_path,
54
+ frame.absolute_path,
55
+ frame.line,
56
+ )
57
+ })
58
+ .collect();
59
+
60
+ let mut mystack = stacktrace.to_owned();
61
+
62
+ mystack.frames = new_frames;
63
+
64
+ (mystack, count.to_owned())
65
+ })
66
+ .collect();
67
+
68
+ Report::new(data).metadata(report.metadata.clone())
69
+ }
70
+
71
+ #[no_mangle]
72
+ pub extern "C" fn initialize_logging(logging_level: u32) -> bool {
73
+ // Force rustc to display the log messages in the console.
74
+ match logging_level {
75
+ 50 => {
76
+ std::env::set_var("RUST_LOG", "error");
77
+ }
78
+ 40 => {
79
+ std::env::set_var("RUST_LOG", "warn");
80
+ }
81
+ 30 => {
82
+ std::env::set_var("RUST_LOG", "info");
83
+ }
84
+ 20 => {
85
+ std::env::set_var("RUST_LOG", "debug");
86
+ }
87
+ 10 => {
88
+ std::env::set_var("RUST_LOG", "trace");
89
+ }
90
+ _ => {
91
+ std::env::set_var("RUST_LOG", "debug");
92
+ }
93
+ }
94
+
95
+ // Initialize the logger.
96
+ pretty_env_logger::init_timed();
97
+
98
+ true
99
+ }
100
+
101
+ #[no_mangle]
102
+ /// # Safety
103
+ /// All pointer arguments must be valid, non-null, null-terminated C strings.
104
+ pub unsafe extern "C" fn initialize_agent(
105
+ application_name: *const c_char,
106
+ server_address: *const c_char,
107
+ basic_auth_user: *const c_char,
108
+ basic_auth_password: *const c_char,
109
+ sample_rate: u32,
110
+ oncpu: bool,
111
+ report_pid: bool,
112
+ report_thread_id: bool,
113
+ tags: *const c_char,
114
+ tenant_id: *const c_char,
115
+ http_headers_json: *const c_char,
116
+ ) -> bool {
117
+ let application_name = unsafe { CStr::from_ptr(application_name) }
118
+ .to_str()
119
+ .unwrap()
120
+ .to_string();
121
+
122
+ let server_address = unsafe { CStr::from_ptr(server_address) }
123
+ .to_str()
124
+ .unwrap()
125
+ .to_string();
126
+
127
+ let basic_auth_user = unsafe { CStr::from_ptr(basic_auth_user) }
128
+ .to_str()
129
+ .unwrap()
130
+ .to_string();
131
+
132
+ let basic_auth_password = unsafe { CStr::from_ptr(basic_auth_password) }
133
+ .to_str()
134
+ .unwrap()
135
+ .to_string();
136
+
137
+ let tags_string = unsafe { CStr::from_ptr(tags) }
138
+ .to_str()
139
+ .unwrap()
140
+ .to_string();
141
+
142
+ let tenant_id = unsafe { CStr::from_ptr(tenant_id) }
143
+ .to_str()
144
+ .unwrap()
145
+ .to_string();
146
+
147
+ let http_headers_json = unsafe { CStr::from_ptr(http_headers_json) }
148
+ .to_str()
149
+ .unwrap()
150
+ .to_string();
151
+
152
+ let pid = std::process::id();
153
+
154
+ let backend_config = BackendConfig {
155
+ report_thread_id,
156
+ report_thread_name: false,
157
+ report_pid,
158
+ };
159
+
160
+ let sampler = Sampler::new(pid as Pid, sample_rate, false, None, false, None, oncpu);
161
+
162
+ let tags_ref = tags_string.as_str();
163
+ let tags = string_to_tags(tags_ref);
164
+ let rbspy = BackendImpl::new(Box::new(Rbspy::new(sampler, sample_rate, backend_config)));
165
+
166
+ let mut agent_builder = PyroscopeAgentBuilder::new(
167
+ server_address,
168
+ application_name,
169
+ sample_rate,
170
+ RBSPY_NAME,
171
+ RBSPY_VERSION,
172
+ rbspy,
173
+ )
174
+ .func(transform_report_with_current_dir)
175
+ .tags(tags);
176
+
177
+ if !basic_auth_user.is_empty() && !basic_auth_password.is_empty() {
178
+ agent_builder = agent_builder.basic_auth(basic_auth_user, basic_auth_password);
179
+ }
180
+
181
+ if !tenant_id.is_empty() {
182
+ agent_builder = agent_builder.tenant_id(tenant_id);
183
+ }
184
+
185
+ let http_headers = pyroscope::pyroscope::parse_http_headers_json(http_headers_json);
186
+ match http_headers {
187
+ Ok(http_headers) => {
188
+ agent_builder = agent_builder.http_headers(http_headers);
189
+ }
190
+ Err(e) => match e {
191
+ pyroscope::PyroscopeError::Json(e) => {
192
+ log::error!(target: LOG_TAG, "parse_http_headers_json error {}", e);
193
+ }
194
+ pyroscope::PyroscopeError::AdHoc(e) => {
195
+ log::error!(target: LOG_TAG, "parse_http_headers_json {}", e);
196
+ }
197
+ _ => {}
198
+ },
199
+ }
200
+
201
+ pyroscope::ffikit::run(agent_builder).is_ok()
202
+ }
203
+
204
+ #[no_mangle]
205
+ pub extern "C" fn drop_agent() -> bool {
206
+ pyroscope::ffikit::send(pyroscope::ffikit::Signal::Kill).is_ok()
207
+ }
208
+
209
+ #[no_mangle]
210
+ /// # Safety
211
+ /// `key` and `value` must be valid, non-null, null-terminated C strings.
212
+ pub unsafe extern "C" fn add_thread_tag(key: *const c_char, value: *const c_char) -> bool {
213
+ let key = unsafe { CStr::from_ptr(key) }.to_str().unwrap().to_owned();
214
+ let value = unsafe { CStr::from_ptr(value) }
215
+ .to_str()
216
+ .unwrap()
217
+ .to_owned();
218
+
219
+ pyroscope::ffikit::send(pyroscope::ffikit::Signal::AddThreadTag(
220
+ backend::self_thread_id(),
221
+ Tag { key, value },
222
+ ))
223
+ .is_ok()
224
+ }
225
+
226
+ #[no_mangle]
227
+ /// # Safety
228
+ /// `key` and `value` must be valid, non-null, null-terminated C strings.
229
+ pub unsafe extern "C" fn remove_thread_tag(key: *const c_char, value: *const c_char) -> bool {
230
+ let key = unsafe { CStr::from_ptr(key) }.to_str().unwrap().to_owned();
231
+ let value = unsafe { CStr::from_ptr(value) }
232
+ .to_str()
233
+ .unwrap()
234
+ .to_owned();
235
+
236
+ pyroscope::ffikit::send(pyroscope::ffikit::Signal::RemoveThreadTag(
237
+ backend::self_thread_id(),
238
+ Tag { key, value },
239
+ ))
240
+ .is_ok()
241
+ }
242
+
243
+ fn string_to_tags(tags: &str) -> Vec<(&str, &str)> {
244
+ let mut tags_vec = Vec::new();
245
+ // check if string is empty
246
+ if tags.is_empty() {
247
+ return tags_vec;
248
+ }
249
+
250
+ for tag in tags.split(',') {
251
+ let mut tag_split = tag.split('=');
252
+ let key = tag_split.next().unwrap();
253
+ let value = tag_split.next().unwrap();
254
+ tags_vec.push((key, value));
255
+ }
256
+
257
+ tags_vec
258
+ }
259
+
260
+ #[cfg(test)]
261
+ mod tests {
262
+ use super::*;
263
+ use pyroscope::backend::{Report, StackFrame, StackTrace};
264
+ use std::collections::HashMap;
265
+
266
+ fn report_with_filename(filename: &str) -> Report {
267
+ let stacktrace = StackTrace {
268
+ frames: vec![StackFrame {
269
+ filename: Some(filename.to_string()),
270
+ ..Default::default()
271
+ }],
272
+ ..Default::default()
273
+ };
274
+ Report::new(HashMap::from([(stacktrace, 1)]))
275
+ }
276
+
277
+ fn transformed_filename(report: Report) -> String {
278
+ let (stacktrace, _) = report.data.iter().next().unwrap();
279
+ stacktrace.frames[0].filename.clone().unwrap()
280
+ }
281
+
282
+ #[test]
283
+ fn transform_report_does_not_panic_when_cwd_is_a_suffix_of_filename() {
284
+ let report = report_with_filename("bin/rails");
285
+ let out = transform_report(report, Some("/rails"));
286
+ assert_eq!(transformed_filename(out), "bin/rails");
287
+ }
288
+
289
+ #[test]
290
+ fn transform_report_strips_cwd_prefix() {
291
+ let report = report_with_filename("/rails/app/models/user.rb");
292
+ let out = transform_report(report, Some("/rails"));
293
+ assert_eq!(transformed_filename(out), "app/models/user.rb");
294
+ }
295
+
296
+ #[test]
297
+ fn transform_report_rewrites_gems_path() {
298
+ let report =
299
+ report_with_filename("/usr/local/bundle/gems/activerecord-7.0.0/lib/active_record.rb");
300
+ let out = transform_report(report, None);
301
+ assert_eq!(
302
+ transformed_filename(out),
303
+ "gems/activerecord-7.0.0/lib/active_record.rb",
304
+ );
305
+ }
306
+
307
+ #[test]
308
+ fn transform_report_rewrites_ruby_stdlib_path() {
309
+ let report = report_with_filename("/usr/local/lib/ruby/3.3.0/json/common.rb");
310
+ let out = transform_report(report, None);
311
+ assert_eq!(transformed_filename(out), "json/common.rb");
312
+ }
313
+
314
+ #[test]
315
+ fn transform_report_leaves_unrelated_filename_unchanged() {
316
+ let report = report_with_filename("/tmp/unrelated.rb");
317
+ let out = transform_report(report, Some("/app"));
318
+ assert_eq!(transformed_filename(out), "/tmp/unrelated.rb");
319
+ }
320
+
321
+ #[test]
322
+ fn test_cargo_version_matches_ruby_version() {
323
+ let cargo_version = env!("CARGO_PKG_VERSION");
324
+ let version_rb = include_str!("../../../lib/pyroscope/version.rb");
325
+ let ruby_version = version_rb
326
+ .lines()
327
+ .find_map(|line| {
328
+ let line = line.trim();
329
+ if line.starts_with("VERSION") {
330
+ let start = line.find('\'')?;
331
+ let end = line.rfind('\'')?;
332
+ if start < end {
333
+ Some(&line[start + 1..end])
334
+ } else {
335
+ None
336
+ }
337
+ } else {
338
+ None
339
+ }
340
+ })
341
+ .expect("could not find VERSION in lib/pyroscope/version.rb");
342
+
343
+ assert_eq!(
344
+ cargo_version, ruby_version,
345
+ "Cargo.toml version ({cargo_version}) does not match lib/pyroscope/version.rb ({ruby_version})"
346
+ );
347
+ }
348
+ }
@@ -1,3 +1,3 @@
1
1
  module Pyroscope
2
- VERSION = '1.0.6'.freeze # x-release-please-version
2
+ VERSION = '1.0.7'.freeze # x-release-please-version
3
3
  end
data/lib/rbspy/rbspy.so CHANGED
Binary file
data/pyroscope.gemspec CHANGED
@@ -23,13 +23,19 @@ Gem::Specification.new do |s|
23
23
  "source_code_uri" => "https://github.com/grafana/pyroscope-ruby",
24
24
  }
25
25
 
26
- # TODO: s.files is incomplete — ext/rbspy/ sources are missing, so building
27
- # the gem from source does not work. See https://github.com/grafana/pyroscope-ruby/issues/2
28
26
  s.files = [
29
27
  "Gemfile",
30
28
  "Gemfile.lock",
31
29
  "LICENSE",
32
30
  "README.md",
31
+ "ext/rbspy/Cargo.lock",
32
+ "ext/rbspy/Cargo.toml",
33
+ "ext/rbspy/Rakefile",
34
+ "ext/rbspy/cbindgen.toml",
35
+ "ext/rbspy/extconf.rb",
36
+ "ext/rbspy/include/rbspy.h",
37
+ "ext/rbspy/src/backend.rs",
38
+ "ext/rbspy/src/lib.rs",
33
39
  "lib/pyroscope.rb",
34
40
  "lib/pyroscope/version.rb",
35
41
  "pyroscope.gemspec",
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pyroscope
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.6
4
+ version: 1.0.7
5
5
  platform: aarch64-linux
6
6
  authors:
7
7
  - Pyroscope Team
@@ -62,7 +62,14 @@ files:
62
62
  - Gemfile.lock
63
63
  - LICENSE
64
64
  - README.md
65
+ - ext/rbspy/Cargo.lock
66
+ - ext/rbspy/Cargo.toml
67
+ - ext/rbspy/Rakefile
68
+ - ext/rbspy/cbindgen.toml
65
69
  - ext/rbspy/extconf.rb
70
+ - ext/rbspy/include/rbspy.h
71
+ - ext/rbspy/src/backend.rs
72
+ - ext/rbspy/src/lib.rs
66
73
  - lib/pyroscope.rb
67
74
  - lib/pyroscope/version.rb
68
75
  - lib/rbspy/rbspy.so