pyroscope 1.0.6-x86_64-linux → 1.0.7-x86_64-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.
- checksums.yaml +4 -4
- data/Gemfile.lock +2 -2
- data/ext/rbspy/Cargo.lock +2782 -0
- data/ext/rbspy/Cargo.toml +18 -0
- data/ext/rbspy/Rakefile +164 -0
- data/ext/rbspy/cbindgen.toml +17 -0
- data/ext/rbspy/include/rbspy.h +45 -0
- data/ext/rbspy/src/backend.rs +199 -0
- data/ext/rbspy/src/lib.rs +348 -0
- data/lib/pyroscope/version.rb +1 -1
- data/lib/rbspy/rbspy.so +0 -0
- data/pyroscope.gemspec +8 -2
- metadata +8 -1
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "ffiruby"
|
|
3
|
+
version = "1.0.7" # x-release-please-version
|
|
4
|
+
edition = "2021"
|
|
5
|
+
rust-version = "1.66"
|
|
6
|
+
|
|
7
|
+
[lib]
|
|
8
|
+
name = "rbspy"
|
|
9
|
+
crate-type = ["cdylib"]
|
|
10
|
+
|
|
11
|
+
[dependencies]
|
|
12
|
+
pyroscope = { version="2.0.0", default-features = false, features = ["native-tls"] }
|
|
13
|
+
rbspy = { version = "0.47" }
|
|
14
|
+
remoteprocess = "0.5"
|
|
15
|
+
pretty_env_logger = "0.5"
|
|
16
|
+
log = "0.4"
|
|
17
|
+
libc = "0.2"
|
|
18
|
+
anyhow = "1.0"
|
data/ext/rbspy/Rakefile
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "shellwords"
|
|
4
|
+
|
|
5
|
+
class RbspyRakeCargoHelper
|
|
6
|
+
attr_reader :gemname
|
|
7
|
+
|
|
8
|
+
def initialize(gemname="rbspy")
|
|
9
|
+
@gemname = gemname
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.command?(name)
|
|
13
|
+
exts = ENV["PATHEXT"] ? ENV["PATHEXT"].split(";") : [""]
|
|
14
|
+
ENV["PATH"].split(File::PATH_SEPARATOR).any? do |path|
|
|
15
|
+
exts.any? do |ext|
|
|
16
|
+
exe = File.join(path, "#{name}#{ext}")
|
|
17
|
+
File.executable?(exe) && !File.directory?(exe)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.rust_toolchain
|
|
23
|
+
# return env variable if set
|
|
24
|
+
target = ENV["RUST_TARGET"]
|
|
25
|
+
return target if target
|
|
26
|
+
|
|
27
|
+
str = `rustc --version --verbose`
|
|
28
|
+
info = str.lines.map {|l| l.chomp.split(/:\s+/, 2)}.drop(1).to_h
|
|
29
|
+
info["host"]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.cargo_target_dir
|
|
33
|
+
return @cargo_target_dir if defined? @cargo_target_dir
|
|
34
|
+
|
|
35
|
+
str = `cargo metadata --format-version 1 --offline --no-deps --quiet`
|
|
36
|
+
begin
|
|
37
|
+
require "json"
|
|
38
|
+
dir = JSON.parse(str)["target_directory"]
|
|
39
|
+
rescue LoadError # json is usually part of the stdlib, but just in case
|
|
40
|
+
/"target_directory"\s*:\s*"(?<dir>[^"]*)"/ =~ str
|
|
41
|
+
end
|
|
42
|
+
@cargo_target_dir = dir || "target"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.flags
|
|
46
|
+
cc_flags = Shellwords.split(RbConfig.expand(RbConfig::MAKEFILE_CONFIG["CC"].dup))
|
|
47
|
+
|
|
48
|
+
["-C", "linker=#{cc_flags.shift}",
|
|
49
|
+
*cc_flags.flat_map {|a| ["-C", "link-arg=#{a}"] },
|
|
50
|
+
"-L", "native=#{RbConfig::CONFIG["libdir"]}",
|
|
51
|
+
*dld_flags,
|
|
52
|
+
*platform_flags,
|
|
53
|
+
]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def self.dld_flags
|
|
57
|
+
Shellwords.split(RbConfig::CONFIG["DLDFLAGS"]).flat_map do |arg|
|
|
58
|
+
arg = arg.gsub(/\$\((\w+)\)/) do
|
|
59
|
+
$1 == "DEFFILE" ? nil : RbConfig::CONFIG[name]
|
|
60
|
+
end.strip
|
|
61
|
+
next [] if arg.empty?
|
|
62
|
+
|
|
63
|
+
transform_flag(arg)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def self.platform_flags
|
|
68
|
+
return unless RbConfig::CONFIG["target_os"] =~ /mingw/i
|
|
69
|
+
|
|
70
|
+
[*Shellwords.split(RbConfig::CONFIG["LIBRUBYARG"]).flat_map {|arg| transform_flag(arg)},
|
|
71
|
+
"-C", "link-arg=-Wl,--dynamicbase",
|
|
72
|
+
"-C", "link-arg=-Wl,--disable-auto-image-base",
|
|
73
|
+
"-C", "link-arg=-static-libgcc"]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def self.transform_flag(arg)
|
|
77
|
+
k, v = arg.split(/(?<=..)/, 2)
|
|
78
|
+
case k
|
|
79
|
+
when "-L"
|
|
80
|
+
[k, "native=#{v}"]
|
|
81
|
+
when "-l"
|
|
82
|
+
[k, v]
|
|
83
|
+
when "-F"
|
|
84
|
+
["-l", "framework=#{v}"]
|
|
85
|
+
else
|
|
86
|
+
["-C", "link_arg=#{k}#{v}"]
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def install_dir
|
|
91
|
+
File.expand_path(File.join("..", "..", "lib", gemname), __dir__)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def rust_name
|
|
95
|
+
prefix = "lib" unless Gem.win_platform?
|
|
96
|
+
suffix = if RbConfig::CONFIG["target_os"] =~ /darwin/i
|
|
97
|
+
".dylib"
|
|
98
|
+
elsif Gem.win_platform?
|
|
99
|
+
".dll"
|
|
100
|
+
else
|
|
101
|
+
".so"
|
|
102
|
+
end
|
|
103
|
+
"#{prefix}#{gemname}#{suffix}"
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def ruby_name
|
|
107
|
+
"#{gemname}.#{RbConfig::CONFIG["DLEXT"]}"
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
task default: [:rbspy_install, :rbspy_clean]
|
|
113
|
+
task rbspy: [:rbspy_install, :rbspy_clean]
|
|
114
|
+
|
|
115
|
+
desc "set dev mode for subsequent task, run like `rake dev install`"
|
|
116
|
+
task :rbspy_dev do
|
|
117
|
+
@dev = true
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
desc "build gem native extension and copy to lib"
|
|
121
|
+
task rbspy_install: [:rbspy_cd, :rbspy_build] do
|
|
122
|
+
helper = RbspyRakeCargoHelper.new
|
|
123
|
+
profile_dir = @dev ? "debug" : "release"
|
|
124
|
+
arch_dir = RbspyRakeCargoHelper.rust_toolchain
|
|
125
|
+
source = File.join(RbspyRakeCargoHelper.cargo_target_dir, arch_dir, profile_dir, helper.rust_name)
|
|
126
|
+
dest = File.join(helper.install_dir, helper.ruby_name)
|
|
127
|
+
mkdir_p(helper.install_dir)
|
|
128
|
+
rm(dest) if File.exist?(dest)
|
|
129
|
+
cp(source, dest)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
desc "build gem native extension"
|
|
133
|
+
task rbspy_build: [:rbspy_cargo, :rbspy_cd] do
|
|
134
|
+
sh "cargo", "rustc", *(["--locked", "--release"] unless @dev), "--target=#{RbspyRakeCargoHelper.rust_toolchain}", "--", *RbspyRakeCargoHelper.flags
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
desc "clean up release build artifacts"
|
|
138
|
+
task rbspy_clean: [:rbspy_cargo, :rbspy_cd] do
|
|
139
|
+
sh "cargo clean --release"
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
desc "clean up build artifacts"
|
|
143
|
+
task rbspy_clobber: [:rbspy_cargo, :rbspy_cd] do
|
|
144
|
+
sh "cargo clean"
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
desc "check for cargo"
|
|
148
|
+
task :rbspy_cargo do
|
|
149
|
+
raise <<-MSG unless RbspyRakeCargoHelper.command?("cargo")
|
|
150
|
+
This gem requires a Rust compiler and the `cargo' build tool to build the
|
|
151
|
+
gem's native extension. See https://www.rust-lang.org/tools/install for
|
|
152
|
+
how to install Rust. `cargo' is usually part of the Rust installation.
|
|
153
|
+
MSG
|
|
154
|
+
|
|
155
|
+
raise <<-MSG if Gem.win_platform? && RbspyRakeCargoHelper.rust_toolchain !~ /gnu/
|
|
156
|
+
Found Rust toolchain `#{RbspyRakeCargoHelper.rust_toolchain}' but the gem native
|
|
157
|
+
extension requires the gnu toolchain on Windows.
|
|
158
|
+
MSG
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# ensure task is running in the right dir
|
|
162
|
+
task :rbspy_cd do
|
|
163
|
+
cd(__dir__) unless __dir__ == pwd
|
|
164
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
language = "C"
|
|
2
|
+
documentation_style = "C"
|
|
3
|
+
|
|
4
|
+
style = "type"
|
|
5
|
+
|
|
6
|
+
include_guard = "RBSPY_H_"
|
|
7
|
+
include_version = false
|
|
8
|
+
|
|
9
|
+
header = "/* Licensed under Apache-2.0 */"
|
|
10
|
+
autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */"
|
|
11
|
+
|
|
12
|
+
braces = "SameLine"
|
|
13
|
+
tab_width = 2
|
|
14
|
+
line_length = 80
|
|
15
|
+
|
|
16
|
+
[parse]
|
|
17
|
+
parse_deps = false
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/* Licensed under Apache-2.0 */
|
|
2
|
+
|
|
3
|
+
#ifndef RBSPY_H_
|
|
4
|
+
#define RBSPY_H_
|
|
5
|
+
|
|
6
|
+
/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */
|
|
7
|
+
|
|
8
|
+
#include <stdarg.h>
|
|
9
|
+
#include <stdbool.h>
|
|
10
|
+
#include <stdint.h>
|
|
11
|
+
#include <stdlib.h>
|
|
12
|
+
|
|
13
|
+
bool initialize_logging(uint32_t logging_level);
|
|
14
|
+
|
|
15
|
+
/*
|
|
16
|
+
# Safety
|
|
17
|
+
All pointer arguments must be valid, non-null, null-terminated C strings.
|
|
18
|
+
*/
|
|
19
|
+
bool initialize_agent(const char *application_name,
|
|
20
|
+
const char *server_address,
|
|
21
|
+
const char *basic_auth_user,
|
|
22
|
+
const char *basic_auth_password,
|
|
23
|
+
uint32_t sample_rate,
|
|
24
|
+
bool oncpu,
|
|
25
|
+
bool report_pid,
|
|
26
|
+
bool report_thread_id,
|
|
27
|
+
const char *tags,
|
|
28
|
+
const char *tenant_id,
|
|
29
|
+
const char *http_headers_json);
|
|
30
|
+
|
|
31
|
+
bool drop_agent(void);
|
|
32
|
+
|
|
33
|
+
/*
|
|
34
|
+
# Safety
|
|
35
|
+
`key` and `value` must be valid, non-null, null-terminated C strings.
|
|
36
|
+
*/
|
|
37
|
+
bool add_thread_tag(const char *key, const char *value);
|
|
38
|
+
|
|
39
|
+
/*
|
|
40
|
+
# Safety
|
|
41
|
+
`key` and `value` must be valid, non-null, null-terminated C strings.
|
|
42
|
+
*/
|
|
43
|
+
bool remove_thread_tag(const char *key, const char *value);
|
|
44
|
+
|
|
45
|
+
#endif /* RBSPY_H_ */
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
use pyroscope::{
|
|
2
|
+
backend::{
|
|
3
|
+
Backend, BackendConfig, Report, ReportBatch, ReportData, StackBuffer, StackFrame,
|
|
4
|
+
StackTrace, ThreadTag, ThreadTagsSet,
|
|
5
|
+
},
|
|
6
|
+
error::{PyroscopeError, Result},
|
|
7
|
+
};
|
|
8
|
+
use rbspy::sampler::Sampler;
|
|
9
|
+
use std::{
|
|
10
|
+
ops::Deref,
|
|
11
|
+
sync::{
|
|
12
|
+
mpsc::{channel, sync_channel, Receiver, Sender, SyncSender},
|
|
13
|
+
Arc, Mutex,
|
|
14
|
+
},
|
|
15
|
+
thread::JoinHandle,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const LOG_TAG: &str = "Pyroscope::Rbspy";
|
|
19
|
+
|
|
20
|
+
pub struct Rbspy {
|
|
21
|
+
sample_rate: u32,
|
|
22
|
+
backend_config: BackendConfig,
|
|
23
|
+
sampler: Sampler,
|
|
24
|
+
/// Error Receiver
|
|
25
|
+
error_receiver: Option<Receiver<std::result::Result<(), anyhow::Error>>>,
|
|
26
|
+
/// Profiling buffer
|
|
27
|
+
buffer: Arc<Mutex<StackBuffer>>,
|
|
28
|
+
/// Rulset
|
|
29
|
+
ruleset: ThreadTagsSet,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
impl std::fmt::Debug for Rbspy {
|
|
33
|
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
34
|
+
write!(f, "Rbspy Backend")
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
impl Rbspy {
|
|
39
|
+
/// Create a new Rbspy instance
|
|
40
|
+
pub fn new(sampler: Sampler, sample_rate: u32, backend_config: BackendConfig) -> Self {
|
|
41
|
+
Rbspy {
|
|
42
|
+
sample_rate,
|
|
43
|
+
sampler,
|
|
44
|
+
backend_config,
|
|
45
|
+
error_receiver: None,
|
|
46
|
+
buffer: Arc::new(Mutex::new(StackBuffer::default())),
|
|
47
|
+
ruleset: ThreadTagsSet::default(),
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Type aliases
|
|
53
|
+
type ErrorSender = Sender<std::result::Result<(), anyhow::Error>>;
|
|
54
|
+
type ErrorReceiver = Receiver<std::result::Result<(), anyhow::Error>>;
|
|
55
|
+
|
|
56
|
+
impl Backend for Rbspy {
|
|
57
|
+
fn add_tag(&self, tag: ThreadTag) -> Result<()> {
|
|
58
|
+
self.ruleset.add(tag)?;
|
|
59
|
+
|
|
60
|
+
Ok(())
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
fn remove_tag(&self, tag: ThreadTag) -> Result<()> {
|
|
64
|
+
self.ruleset.remove(tag)?;
|
|
65
|
+
|
|
66
|
+
Ok(())
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/// Initialize the backend
|
|
70
|
+
fn initialize(&mut self) -> Result<()> {
|
|
71
|
+
// Channel for Errors generated by the RubySpy Sampler
|
|
72
|
+
let (error_sender, error_receiver): (ErrorSender, ErrorReceiver) = channel();
|
|
73
|
+
|
|
74
|
+
// This is provides enough space for 100 threads.
|
|
75
|
+
// It might be a better idea to figure out how many threads are running and determine the
|
|
76
|
+
// size of the channel based on that.
|
|
77
|
+
let queue_size: usize = self.sample_rate as usize * 10 * 100;
|
|
78
|
+
|
|
79
|
+
// Channel for StackTraces generated by the RubySpy Sampler
|
|
80
|
+
let (stack_sender, stack_receiver): (
|
|
81
|
+
SyncSender<rbspy::StackTrace>,
|
|
82
|
+
Receiver<rbspy::StackTrace>,
|
|
83
|
+
) = sync_channel(queue_size);
|
|
84
|
+
|
|
85
|
+
// Set Error and Stack Receivers
|
|
86
|
+
// self.stack_receiver = Some(stack_receiver);
|
|
87
|
+
self.error_receiver = Some(error_receiver);
|
|
88
|
+
|
|
89
|
+
self.sampler
|
|
90
|
+
.start(stack_sender, error_sender)
|
|
91
|
+
.map_err(|e| PyroscopeError::new(&format!("Rbspy: Sampler Error: {}", e)))?;
|
|
92
|
+
|
|
93
|
+
// Start own thread
|
|
94
|
+
//
|
|
95
|
+
// Get an Arc reference to the Report Buffer
|
|
96
|
+
let buffer = self.buffer.clone();
|
|
97
|
+
|
|
98
|
+
// ruleset reference
|
|
99
|
+
let ruleset = self.ruleset.clone();
|
|
100
|
+
|
|
101
|
+
let backend_config = self.backend_config;
|
|
102
|
+
|
|
103
|
+
let _: JoinHandle<Result<()>> = std::thread::spawn(move || {
|
|
104
|
+
// Iterate over the StackTrace
|
|
105
|
+
while let Ok(stack_trace) = stack_receiver.recv() {
|
|
106
|
+
// convert StackTrace
|
|
107
|
+
let own_trace: StackTrace =
|
|
108
|
+
Into::<StackTraceWrapper>::into((stack_trace, &backend_config)).into();
|
|
109
|
+
|
|
110
|
+
let stacktrace = own_trace.add_tag_rules(&ruleset);
|
|
111
|
+
|
|
112
|
+
buffer.lock()?.record(stacktrace)?;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
Ok(())
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
Ok(())
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
fn shutdown(self: Box<Self>) -> Result<()> {
|
|
122
|
+
log::trace!(target: LOG_TAG, "Shutting down sampler thread");
|
|
123
|
+
|
|
124
|
+
self.sampler.stop();
|
|
125
|
+
|
|
126
|
+
Ok(())
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
fn report(&mut self) -> Result<ReportBatch> {
|
|
130
|
+
let v8: StackBuffer = self.buffer.lock()?.deref().to_owned();
|
|
131
|
+
let reports: Vec<Report> = v8.into();
|
|
132
|
+
|
|
133
|
+
self.buffer.lock()?.clear();
|
|
134
|
+
|
|
135
|
+
Ok(ReportBatch {
|
|
136
|
+
profile_type: "process_cpu".into(),
|
|
137
|
+
data: ReportData::Reports(reports),
|
|
138
|
+
})
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
struct StackFrameWrapper(StackFrame);
|
|
143
|
+
|
|
144
|
+
impl From<StackFrameWrapper> for StackFrame {
|
|
145
|
+
fn from(frame: StackFrameWrapper) -> Self {
|
|
146
|
+
frame.0
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
impl From<rbspy::StackFrame> for StackFrameWrapper {
|
|
151
|
+
fn from(frame: rbspy::StackFrame) -> Self {
|
|
152
|
+
StackFrameWrapper(StackFrame {
|
|
153
|
+
module: None,
|
|
154
|
+
name: Some(frame.name),
|
|
155
|
+
filename: Some(frame.relative_path.clone()),
|
|
156
|
+
relative_path: Some(frame.relative_path),
|
|
157
|
+
absolute_path: frame.absolute_path,
|
|
158
|
+
line: frame.lineno.map(|l| l as u32),
|
|
159
|
+
})
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
struct StackTraceWrapper(StackTrace);
|
|
164
|
+
|
|
165
|
+
impl From<StackTraceWrapper> for StackTrace {
|
|
166
|
+
fn from(trace: StackTraceWrapper) -> Self {
|
|
167
|
+
trace.0
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
impl From<(rbspy::StackTrace, &BackendConfig)> for StackTraceWrapper {
|
|
172
|
+
fn from(arg: (rbspy::StackTrace, &BackendConfig)) -> Self {
|
|
173
|
+
let (trace, config) = arg;
|
|
174
|
+
|
|
175
|
+
let thread_id = trace.thread_id.map(|tid| {
|
|
176
|
+
// for rbspy we use pthread_t as thread id
|
|
177
|
+
// https://github.com/ruby/ruby/blob/54a74c42033e42869e69e7dc9e67efa1faf225be/include/ruby/thread_native.h#L41
|
|
178
|
+
let thread_id = tid as libc::pthread_t;
|
|
179
|
+
thread_id.into()
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
StackTraceWrapper(StackTrace::new(
|
|
183
|
+
config,
|
|
184
|
+
trace.pid.map(|pid| pid as u32),
|
|
185
|
+
thread_id,
|
|
186
|
+
None,
|
|
187
|
+
trace
|
|
188
|
+
.iter()
|
|
189
|
+
.map(|frame| Into::<StackFrameWrapper>::into(frame.clone()).into())
|
|
190
|
+
.collect(),
|
|
191
|
+
))
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
pub fn self_thread_id() -> pyroscope::ThreadId {
|
|
196
|
+
// for rbspy we use pthread_t as thread id
|
|
197
|
+
// https://github.com/ruby/ruby/blob/54a74c42033e42869e69e7dc9e67efa1faf225be/include/ruby/thread_native.h#L41
|
|
198
|
+
pyroscope::ThreadId::pthread_self()
|
|
199
|
+
}
|