pyroscope 0.2.0 → 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE +1 -1
- data/README.md +36 -26
- data/elflib/rbspy/manylinux.sh +23 -0
- data/elflib/rbspy/pyproject.toml +7 -0
- data/{ext/pyroscope/lib/.gitkeep → elflib/rbspy/rbspy/__init__.py} +0 -0
- data/elflib/rbspy/setup.cfg +22 -0
- data/elflib/rbspy/setup.py +46 -0
- data/elflib/thread_id/manylinux.sh +23 -0
- data/elflib/thread_id/pyproject.toml +7 -0
- data/elflib/thread_id/setup.cfg +22 -0
- data/elflib/thread_id/setup.py +46 -0
- data/elflib/thread_id/thread_id/__init__.py +0 -0
- data/ext/rbspy/Cargo.toml +29 -0
- data/ext/rbspy/Rakefile +164 -0
- data/ext/rbspy/build.rs +12 -0
- data/ext/rbspy/cbindgen.toml +22 -0
- data/ext/rbspy/extconf.rb +11 -0
- data/ext/rbspy/src/lib.rs +253 -0
- data/ext/thread_id/Cargo.toml +20 -0
- data/ext/thread_id/Rakefile +163 -0
- data/ext/thread_id/build.rs +12 -0
- data/ext/thread_id/cbindgen.toml +22 -0
- data/ext/thread_id/extconf.rb +11 -0
- data/ext/thread_id/src/lib.rs +4 -0
- data/lib/pyroscope/version.rb +1 -1
- data/lib/pyroscope.rb +76 -33
- data/pyroscope.gemspec +28 -23
- data/scripts/docker.sh +16 -0
- metadata +45 -60
- data/.gitignore +0 -7
- data/Gemfile +0 -4
- data/Gemfile.lock +0 -48
- data/Rakefile +0 -56
- data/ext/pyroscope/extconf.rb +0 -78
- data/ext/pyroscope/lib/libpyroscope.rbspy.a +0 -0
- data/ext/pyroscope/lib/libpyroscope.rbspy.combo.a +0 -0
- data/ext/pyroscope/lib/librustdeps.a +0 -0
- data/ext/pyroscope/pyroscope.c +0 -87
- data/test.rb +0 -32
@@ -0,0 +1,253 @@
|
|
1
|
+
use ffikit::Signal;
|
2
|
+
use pyroscope::backend::{Report, StackFrame, Tag};
|
3
|
+
use pyroscope::PyroscopeAgent;
|
4
|
+
use pyroscope_rbspy::{rbspy_backend, RbspyConfig};
|
5
|
+
use std::collections::hash_map::DefaultHasher;
|
6
|
+
use std::ffi::CStr;
|
7
|
+
use std::hash::Hasher;
|
8
|
+
use std::os::raw::c_char;
|
9
|
+
use std::env;
|
10
|
+
|
11
|
+
pub fn transform_report(report: Report) -> Report {
|
12
|
+
let cwd = env::current_dir().unwrap();
|
13
|
+
let cwd = cwd.to_str().unwrap_or("");
|
14
|
+
|
15
|
+
let data = report
|
16
|
+
.data
|
17
|
+
.iter()
|
18
|
+
.map(|(stacktrace, count)| {
|
19
|
+
let new_frames = stacktrace
|
20
|
+
.frames
|
21
|
+
.iter()
|
22
|
+
.map(|frame| {
|
23
|
+
let frame = frame.to_owned();
|
24
|
+
let mut s = frame.filename.unwrap();
|
25
|
+
match s.find(cwd) {
|
26
|
+
Some(i) => {
|
27
|
+
s = s[(i+cwd.len()+1)..].to_string();
|
28
|
+
}
|
29
|
+
None => {
|
30
|
+
match s.find("/gems/") {
|
31
|
+
Some(i) => {
|
32
|
+
s = s[(i+1)..].to_string();
|
33
|
+
}
|
34
|
+
None => {
|
35
|
+
match s.find("/ruby/") {
|
36
|
+
Some(i) => {
|
37
|
+
s = s[(i+6)..].to_string();
|
38
|
+
match s.find("/") {
|
39
|
+
Some(i) => {
|
40
|
+
s = s[(i+1)..].to_string();
|
41
|
+
}
|
42
|
+
None => {
|
43
|
+
}
|
44
|
+
}
|
45
|
+
}
|
46
|
+
None => {
|
47
|
+
}
|
48
|
+
}
|
49
|
+
}
|
50
|
+
}
|
51
|
+
}
|
52
|
+
}
|
53
|
+
|
54
|
+
// something
|
55
|
+
StackFrame::new(
|
56
|
+
frame.module,
|
57
|
+
frame.name,
|
58
|
+
Some(s.to_string()),
|
59
|
+
frame.relative_path,
|
60
|
+
frame.absolute_path,
|
61
|
+
frame.line,
|
62
|
+
)
|
63
|
+
})
|
64
|
+
.collect();
|
65
|
+
|
66
|
+
let mut mystack = stacktrace.to_owned();
|
67
|
+
|
68
|
+
mystack.frames = new_frames;
|
69
|
+
|
70
|
+
(mystack, count.to_owned())
|
71
|
+
})
|
72
|
+
.collect();
|
73
|
+
|
74
|
+
let new_report = Report::new(data).metadata(report.metadata.clone());
|
75
|
+
|
76
|
+
new_report
|
77
|
+
}
|
78
|
+
|
79
|
+
#[no_mangle]
|
80
|
+
pub extern "C" fn initialize_agent(
|
81
|
+
application_name: *const c_char, server_address: *const c_char, auth_token: *const c_char,
|
82
|
+
sample_rate: u32, detect_subprocesses: bool, on_cpu: bool, report_pid: bool,
|
83
|
+
report_thread_id: bool, tags: *const c_char,
|
84
|
+
) -> bool {
|
85
|
+
// Initialize FFIKit
|
86
|
+
let recv = ffikit::initialize_ffi().unwrap();
|
87
|
+
|
88
|
+
let application_name = unsafe { CStr::from_ptr(application_name) }
|
89
|
+
.to_str()
|
90
|
+
.unwrap()
|
91
|
+
.to_string();
|
92
|
+
|
93
|
+
let server_address = unsafe { CStr::from_ptr(server_address) }
|
94
|
+
.to_str()
|
95
|
+
.unwrap()
|
96
|
+
.to_string();
|
97
|
+
|
98
|
+
let auth_token = unsafe { CStr::from_ptr(auth_token) }
|
99
|
+
.to_str()
|
100
|
+
.unwrap()
|
101
|
+
.to_string();
|
102
|
+
|
103
|
+
let tags_string = unsafe { CStr::from_ptr(tags) }
|
104
|
+
.to_str()
|
105
|
+
.unwrap()
|
106
|
+
.to_string();
|
107
|
+
|
108
|
+
let pid = std::process::id();
|
109
|
+
|
110
|
+
let rbspy_config = RbspyConfig::new(pid.try_into().unwrap())
|
111
|
+
.sample_rate(sample_rate)
|
112
|
+
.lock_process(false)
|
113
|
+
.with_subprocesses(detect_subprocesses)
|
114
|
+
.on_cpu(on_cpu)
|
115
|
+
.report_pid(report_pid)
|
116
|
+
.report_thread_id(report_thread_id);
|
117
|
+
|
118
|
+
let tags_ref = tags_string.as_str();
|
119
|
+
let tags = string_to_tags(tags_ref);
|
120
|
+
let rbspy = rbspy_backend(rbspy_config);
|
121
|
+
|
122
|
+
let mut agent_builder = PyroscopeAgent::builder(server_address, application_name)
|
123
|
+
.backend(rbspy)
|
124
|
+
.func(transform_report)
|
125
|
+
.tags(tags);
|
126
|
+
|
127
|
+
if auth_token != "" {
|
128
|
+
agent_builder = agent_builder.auth_token(auth_token);
|
129
|
+
}
|
130
|
+
|
131
|
+
let agent = agent_builder.build().unwrap();
|
132
|
+
|
133
|
+
let agent_running = agent.start().unwrap();
|
134
|
+
|
135
|
+
std::thread::spawn(move || {
|
136
|
+
while let Ok(signal) = recv.recv() {
|
137
|
+
match signal {
|
138
|
+
Signal::Kill => {
|
139
|
+
agent_running.stop().unwrap();
|
140
|
+
break;
|
141
|
+
}
|
142
|
+
Signal::AddGlobalTag(name, value) => {
|
143
|
+
agent_running.add_global_tag(Tag::new(name, value)).unwrap();
|
144
|
+
}
|
145
|
+
Signal::RemoveGlobalTag(name, value) => {
|
146
|
+
agent_running
|
147
|
+
.remove_global_tag(Tag::new(name, value))
|
148
|
+
.unwrap();
|
149
|
+
}
|
150
|
+
Signal::AddThreadTag(thread_id, key, value) => {
|
151
|
+
let tag = Tag::new(key, value);
|
152
|
+
agent_running.add_thread_tag(thread_id, tag).unwrap();
|
153
|
+
}
|
154
|
+
Signal::RemoveThreadTag(thread_id, key, value) => {
|
155
|
+
let tag = Tag::new(key, value);
|
156
|
+
agent_running.remove_thread_tag(thread_id, tag).unwrap();
|
157
|
+
}
|
158
|
+
}
|
159
|
+
}
|
160
|
+
});
|
161
|
+
|
162
|
+
true
|
163
|
+
}
|
164
|
+
|
165
|
+
#[no_mangle]
|
166
|
+
pub extern "C" fn drop_agent() -> bool {
|
167
|
+
// Send Kill signal to the FFI merge channel.
|
168
|
+
ffikit::send(ffikit::Signal::Kill).unwrap();
|
169
|
+
|
170
|
+
true
|
171
|
+
}
|
172
|
+
|
173
|
+
#[no_mangle]
|
174
|
+
pub extern "C" fn add_thread_tag(thread_id: u64, key: *const c_char, value: *const c_char) -> bool {
|
175
|
+
let key = unsafe { CStr::from_ptr(key) }.to_str().unwrap().to_owned();
|
176
|
+
let value = unsafe { CStr::from_ptr(value) }
|
177
|
+
.to_str()
|
178
|
+
.unwrap()
|
179
|
+
.to_owned();
|
180
|
+
|
181
|
+
let pid = std::process::id();
|
182
|
+
let mut hasher = DefaultHasher::new();
|
183
|
+
hasher.write_u64(thread_id % pid as u64);
|
184
|
+
let id = hasher.finish();
|
185
|
+
|
186
|
+
ffikit::send(ffikit::Signal::AddThreadTag(id, key, value)).unwrap();
|
187
|
+
|
188
|
+
true
|
189
|
+
}
|
190
|
+
|
191
|
+
#[no_mangle]
|
192
|
+
pub extern "C" fn remove_thread_tag(
|
193
|
+
thread_id: u64, key: *const c_char, value: *const c_char,
|
194
|
+
) -> bool {
|
195
|
+
let key = unsafe { CStr::from_ptr(key) }.to_str().unwrap().to_owned();
|
196
|
+
let value = unsafe { CStr::from_ptr(value) }
|
197
|
+
.to_str()
|
198
|
+
.unwrap()
|
199
|
+
.to_owned();
|
200
|
+
|
201
|
+
let pid = std::process::id();
|
202
|
+
let mut hasher = DefaultHasher::new();
|
203
|
+
hasher.write_u64(thread_id % pid as u64);
|
204
|
+
let id = hasher.finish();
|
205
|
+
|
206
|
+
ffikit::send(ffikit::Signal::RemoveThreadTag(id, key, value)).unwrap();
|
207
|
+
|
208
|
+
true
|
209
|
+
}
|
210
|
+
|
211
|
+
#[no_mangle]
|
212
|
+
pub extern "C" fn add_global_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
|
+
ffikit::send(ffikit::Signal::AddGlobalTag(key, value)).unwrap();
|
220
|
+
|
221
|
+
true
|
222
|
+
}
|
223
|
+
|
224
|
+
#[no_mangle]
|
225
|
+
pub extern "C" fn remove_global_tag(key: *const c_char, value: *const c_char) -> bool {
|
226
|
+
let key = unsafe { CStr::from_ptr(key) }.to_str().unwrap().to_owned();
|
227
|
+
let value = unsafe { CStr::from_ptr(value) }
|
228
|
+
.to_str()
|
229
|
+
.unwrap()
|
230
|
+
.to_owned();
|
231
|
+
|
232
|
+
ffikit::send(ffikit::Signal::RemoveGlobalTag(key, value)).unwrap();
|
233
|
+
|
234
|
+
true
|
235
|
+
}
|
236
|
+
|
237
|
+
// Convert a string of tags to a Vec<(&str, &str)>
|
238
|
+
fn string_to_tags<'a>(tags: &'a str) -> Vec<(&'a str, &'a str)> {
|
239
|
+
let mut tags_vec = Vec::new();
|
240
|
+
// check if string is empty
|
241
|
+
if tags.is_empty() {
|
242
|
+
return tags_vec;
|
243
|
+
}
|
244
|
+
|
245
|
+
for tag in tags.split(',') {
|
246
|
+
let mut tag_split = tag.split('=');
|
247
|
+
let key = tag_split.next().unwrap();
|
248
|
+
let value = tag_split.next().unwrap();
|
249
|
+
tags_vec.push((key, value));
|
250
|
+
}
|
251
|
+
|
252
|
+
tags_vec
|
253
|
+
}
|
@@ -0,0 +1,20 @@
|
|
1
|
+
[package]
|
2
|
+
name = "thread_id"
|
3
|
+
version = "0.1.0"
|
4
|
+
edition = "2021"
|
5
|
+
|
6
|
+
[lib]
|
7
|
+
name = "thread_id"
|
8
|
+
crate-type = ["cdylib"]
|
9
|
+
|
10
|
+
[dependencies]
|
11
|
+
libc = "*"
|
12
|
+
|
13
|
+
[build-dependencies]
|
14
|
+
cbindgen = "0.20.0"
|
15
|
+
|
16
|
+
[profile.release]
|
17
|
+
opt-level= "z"
|
18
|
+
debug = false
|
19
|
+
lto = true
|
20
|
+
codegen-units = 1
|
@@ -0,0 +1,163 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "shellwords"
|
4
|
+
|
5
|
+
class ThreadIdRakeCargoHelper
|
6
|
+
attr_reader :gemname
|
7
|
+
|
8
|
+
def initialize(gemname=File.basename(__dir__))
|
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
|
+
end
|
54
|
+
|
55
|
+
def self.dld_flags
|
56
|
+
Shellwords.split(RbConfig::CONFIG["DLDFLAGS"]).flat_map do |arg|
|
57
|
+
arg = arg.gsub(/\$\((\w+)\)/) do
|
58
|
+
$1 == "DEFFILE" ? nil : RbConfig::CONFIG[name]
|
59
|
+
end.strip
|
60
|
+
next [] if arg.empty?
|
61
|
+
|
62
|
+
transform_flag(arg)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.platform_flags
|
67
|
+
return unless RbConfig::CONFIG["target_os"] =~ /mingw/i
|
68
|
+
|
69
|
+
[*Shellwords.split(RbConfig::CONFIG["LIBRUBYARG"]).flat_map {|arg| transform_flag(arg)},
|
70
|
+
"-C", "link-arg=-Wl,--dynamicbase",
|
71
|
+
"-C", "link-arg=-Wl,--disable-auto-image-base",
|
72
|
+
"-C", "link-arg=-static-libgcc"]
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.transform_flag(arg)
|
76
|
+
k, v = arg.split(/(?<=..)/, 2)
|
77
|
+
case k
|
78
|
+
when "-L"
|
79
|
+
[k, "native=#{v}"]
|
80
|
+
when "-l"
|
81
|
+
[k, v]
|
82
|
+
when "-F"
|
83
|
+
["-l", "framework=#{v}"]
|
84
|
+
else
|
85
|
+
["-C", "link_arg=#{k}#{v}"]
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def install_dir
|
90
|
+
File.expand_path(File.join("..", "..", "lib", gemname), __dir__)
|
91
|
+
end
|
92
|
+
|
93
|
+
def rust_name
|
94
|
+
prefix = "lib" unless Gem.win_platform?
|
95
|
+
suffix = if RbConfig::CONFIG["target_os"] =~ /darwin/i
|
96
|
+
".dylib"
|
97
|
+
elsif Gem.win_platform?
|
98
|
+
".dll"
|
99
|
+
else
|
100
|
+
".so"
|
101
|
+
end
|
102
|
+
"#{prefix}#{gemname}#{suffix}"
|
103
|
+
end
|
104
|
+
|
105
|
+
def ruby_name
|
106
|
+
"#{gemname}.#{RbConfig::CONFIG["DLEXT"]}"
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
task default: [:thread_id_install, :thread_id_clean]
|
112
|
+
task thread_id: [:thread_id_install, :thread_id_clean]
|
113
|
+
|
114
|
+
desc "set dev mode for subsequent task, run like `rake dev install`"
|
115
|
+
task :thread_id_dev do
|
116
|
+
@dev = true
|
117
|
+
end
|
118
|
+
|
119
|
+
desc "build gem native extension and copy to lib"
|
120
|
+
task thread_id_install: [:thread_id_cd, :thread_id_build] do
|
121
|
+
helper = ThreadIdRakeCargoHelper.new
|
122
|
+
profile_dir = @dev ? "debug" : "release"
|
123
|
+
arch_dir = RbspyRakeCargoHelper.rust_toolchain
|
124
|
+
source = File.join(ThreadIdRakeCargoHelper.cargo_target_dir, arch_dir, profile_dir, helper.rust_name)
|
125
|
+
dest = File.join(helper.install_dir, helper.ruby_name)
|
126
|
+
mkdir_p(helper.install_dir)
|
127
|
+
rm(dest) if File.exist?(dest)
|
128
|
+
cp(source, dest)
|
129
|
+
end
|
130
|
+
|
131
|
+
desc "build gem native extension"
|
132
|
+
task thread_id_build: [:thread_id_cargo, :thread_id_cd] do
|
133
|
+
sh "cargo", "rustc", *(["--locked", "--release"] unless @dev), "--target=#{RbspyRakeCargoHelper.rust_toolchain}", "--", *RbspyRakeCargoHelper.flags
|
134
|
+
end
|
135
|
+
|
136
|
+
desc "clean up release build artifacts"
|
137
|
+
task thread_id_clean: [:thread_id_cargo, :thread_id_cd] do
|
138
|
+
sh "cargo clean --release"
|
139
|
+
end
|
140
|
+
|
141
|
+
desc "clean up build artifacts"
|
142
|
+
task thread_id_clobber: [:thread_id_cargo, :thread_id_cd] do
|
143
|
+
sh "cargo clean"
|
144
|
+
end
|
145
|
+
|
146
|
+
desc "check for cargo"
|
147
|
+
task :thread_id_cargo do
|
148
|
+
raise <<-MSG unless ThreadIdRakeCargoHelper.command?("cargo")
|
149
|
+
This gem requires a Rust compiler and the `cargo' build tool to build the
|
150
|
+
gem's native extension. See https://www.rust-lang.org/tools/install for
|
151
|
+
how to install Rust. `cargo' is usually part of the Rust installation.
|
152
|
+
MSG
|
153
|
+
|
154
|
+
raise <<-MSG if Gem.win_platform? && ThreadIdRakeCargoHelper.rust_toolchain !~ /gnu/
|
155
|
+
Found Rust toolchain `#{ThreadIdRakeCargoHelper.rust_toolchain}' but the gem native
|
156
|
+
extension requires the gnu toolchain on Windows.
|
157
|
+
MSG
|
158
|
+
end
|
159
|
+
|
160
|
+
# ensure task is running in the right dir
|
161
|
+
task :thread_id_cd do
|
162
|
+
cd(__dir__) unless __dir__ == pwd
|
163
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
extern crate cbindgen;
|
2
|
+
|
3
|
+
use cbindgen::Config;
|
4
|
+
|
5
|
+
fn main() {
|
6
|
+
let bindings = {
|
7
|
+
let crate_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
|
8
|
+
let config = Config::from_file("cbindgen.toml").unwrap();
|
9
|
+
cbindgen::generate_with_config(&crate_dir, config).unwrap()
|
10
|
+
};
|
11
|
+
bindings.write_to_file("include/thread_id.h");
|
12
|
+
}
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# The language to output bindings in
|
2
|
+
language = "C"
|
3
|
+
documentation_style = "C"
|
4
|
+
|
5
|
+
style = "type"
|
6
|
+
|
7
|
+
# An optional name to use as an include guard
|
8
|
+
include_guard = "RBSPY_H_"
|
9
|
+
# include a comment with the version of cbindgen used to generate the file
|
10
|
+
include_version = true
|
11
|
+
|
12
|
+
# An optional string of text to output at the beginning of the generated file
|
13
|
+
header = "/* Licensed under Apache-2.0 */"
|
14
|
+
autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */"
|
15
|
+
|
16
|
+
braces = "SameLine"
|
17
|
+
tab_width = 2
|
18
|
+
line_length = 80
|
19
|
+
|
20
|
+
[parse]
|
21
|
+
# Do not parse dependent crates
|
22
|
+
parse_deps = false
|
data/lib/pyroscope/version.rb
CHANGED
data/lib/pyroscope.rb
CHANGED
@@ -1,60 +1,103 @@
|
|
1
|
-
require
|
2
|
-
require "pyroscope_c"
|
1
|
+
require 'ffi'
|
3
2
|
|
4
3
|
module Pyroscope
|
5
|
-
|
4
|
+
module Rust
|
5
|
+
extend FFI::Library
|
6
|
+
ffi_lib File.expand_path(File.dirname(__FILE__)) + "/rbspy/rbspy.#{RbConfig::CONFIG["DLEXT"]}"
|
7
|
+
attach_function :initialize_agent, [:string, :string, :string, :int, :bool, :bool, :bool, :bool, :string], :bool
|
8
|
+
attach_function :add_thread_tag, [:uint64, :string, :string], :bool
|
9
|
+
attach_function :remove_thread_tag, [:uint64, :string, :string], :bool
|
10
|
+
attach_function :add_global_tag, [:string, :string], :bool
|
11
|
+
attach_function :remove_global_tag, [:string, :string], :bool
|
12
|
+
attach_function :drop_agent, [], :bool
|
13
|
+
end
|
14
|
+
|
15
|
+
module Utils
|
16
|
+
extend FFI::Library
|
17
|
+
ffi_lib File.expand_path(File.dirname(__FILE__)) + "/thread_id/thread_id.#{RbConfig::CONFIG["DLEXT"]}"
|
18
|
+
attach_function :thread_id, [], :uint64
|
19
|
+
end
|
20
|
+
|
21
|
+
Config = Struct.new(:application_name, :app_name, :server_address, :auth_token, :sample_rate, :detect_subprocesses, :on_cpu, :report_pid, :report_thread_id, :log_level, :tags) do
|
22
|
+
def initialize(*)
|
23
|
+
self.application_name = ''
|
24
|
+
self.server_address = 'http://localhost:4040'
|
25
|
+
self.auth_token = ''
|
26
|
+
self.sample_rate = 100
|
27
|
+
self.detect_subprocesses = false
|
28
|
+
self.on_cpu = true
|
29
|
+
self.report_pid = false
|
30
|
+
self.report_thread_id = false
|
31
|
+
self.log_level = 'info'
|
32
|
+
self.tags = {}
|
33
|
+
super
|
34
|
+
end
|
35
|
+
end
|
6
36
|
|
7
37
|
class << self
|
8
38
|
def configure
|
9
|
-
@
|
10
|
-
yield @configuration
|
11
|
-
_start(
|
12
|
-
@configuration.app_name,
|
13
|
-
@configuration.server_address,
|
14
|
-
@configuration.auth_token || "",
|
15
|
-
@configuration.sample_rate || 100,
|
16
|
-
@configuration.with_subprocesses || 0,
|
17
|
-
@configuration.log_level || "error",
|
18
|
-
)
|
19
|
-
tag(@configuration.tags) if @configuration.tags
|
20
|
-
end
|
39
|
+
@config = Config.new
|
21
40
|
|
22
|
-
|
23
|
-
|
24
|
-
end
|
41
|
+
# Pass config to the block
|
42
|
+
yield @config
|
25
43
|
|
26
|
-
|
27
|
-
|
44
|
+
Rust.initialize_agent(
|
45
|
+
@config.app_name || @config.application_name || "",
|
46
|
+
@config.server_address || "",
|
47
|
+
@config.auth_token || "",
|
48
|
+
@config.sample_rate || 100,
|
49
|
+
@config.detect_subprocesses || false,
|
50
|
+
@config.on_cpu || false,
|
51
|
+
@config.report_pid || false,
|
52
|
+
@config.report_thread_id || false,
|
53
|
+
tags_to_string(@config.tags || {})
|
54
|
+
)
|
28
55
|
end
|
29
56
|
|
30
57
|
def tag_wrapper(tags)
|
31
|
-
|
32
|
-
|
58
|
+
tid = thread_id
|
59
|
+
_add_tags(tid, tags)
|
33
60
|
begin
|
34
61
|
yield
|
35
62
|
ensure
|
36
|
-
|
63
|
+
_remove_tags(tid, tags)
|
37
64
|
end
|
38
65
|
end
|
39
66
|
|
40
67
|
def tag(tags)
|
41
|
-
|
42
|
-
_set_tag(key.to_s, val.to_s)
|
43
|
-
end
|
68
|
+
warn("deprecated. Use `Pyroscope.tag_wrapper` instead.")
|
44
69
|
end
|
45
70
|
|
46
|
-
def remove_tags(*
|
47
|
-
|
48
|
-
|
71
|
+
def remove_tags(*tags)
|
72
|
+
warn("deprecated. Use `Pyroscope.tag_wrapper` instead.")
|
73
|
+
end
|
74
|
+
|
75
|
+
# convert tags object to string
|
76
|
+
def tags_to_string(tags)
|
77
|
+
tags.map { |k, v| "#{k}=#{v}" }.join(',')
|
78
|
+
end
|
79
|
+
|
80
|
+
# get thread id
|
81
|
+
def thread_id
|
82
|
+
return Utils.thread_id
|
83
|
+
end
|
84
|
+
|
85
|
+
# add tags
|
86
|
+
def _add_tags(thread_id, tags)
|
87
|
+
tags.each do |tag_name, tag_value|
|
88
|
+
Rust.add_thread_tag(thread_id, tag_name.to_s, tag_value.to_s)
|
49
89
|
end
|
50
90
|
end
|
51
91
|
|
52
|
-
|
53
|
-
|
92
|
+
# remove tags
|
93
|
+
def _remove_tags(thread_id, tags)
|
94
|
+
tags.each do |tag_name, tag_value|
|
95
|
+
Rust.remove_thread_tag(thread_id, tag_name.to_s, tag_value.to_s)
|
96
|
+
end
|
54
97
|
end
|
55
98
|
|
56
|
-
def
|
57
|
-
|
99
|
+
def drop
|
100
|
+
Rust.drop_agent
|
58
101
|
end
|
59
102
|
end
|
60
103
|
end
|
data/pyroscope.gemspec
CHANGED
@@ -1,27 +1,32 @@
|
|
1
|
-
|
2
|
-
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
-
|
4
|
-
require 'pyroscope/version'
|
1
|
+
require_relative "lib/pyroscope/version"
|
5
2
|
|
6
3
|
Gem::Specification.new do |s|
|
7
|
-
s.name
|
8
|
-
s.version
|
9
|
-
s.summary
|
10
|
-
s.description
|
11
|
-
s.authors
|
12
|
-
s.email
|
13
|
-
s.
|
14
|
-
s.
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
s.
|
19
|
-
|
20
|
-
|
4
|
+
s.name = 'pyroscope'
|
5
|
+
s.version = Pyroscope::VERSION
|
6
|
+
s.summary = 'Pyroscope'
|
7
|
+
s.description = 'Pyroscope FFI Integration for Ruby'
|
8
|
+
s.authors = ['Pyroscope Team']
|
9
|
+
s.email = ['contact@pyroscope.io']
|
10
|
+
s.homepage = 'https://pyroscope.io'
|
11
|
+
s.license = 'Apache-2.0'
|
12
|
+
|
13
|
+
# Specify which files should be added to the gem when it is released.
|
14
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
15
|
+
#s.files = Dir.chdir(__dir__) do
|
16
|
+
#`git ls-files -z`.split("\x0").reject do |f|
|
17
|
+
#(f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
|
18
|
+
#end
|
19
|
+
#end
|
20
|
+
s.files = `git ls-files -z`.split("\0").reject { |f| f =~ /^(\.|G|spec|Rakefile)/ }
|
21
|
+
|
22
|
+
s.platform = Gem::Platform::RUBY
|
23
|
+
|
24
|
+
s.required_ruby_version = ">= 2.5.9"
|
25
|
+
|
26
|
+
s.extensions = ['ext/rbspy/extconf.rb', 'ext/thread_id/extconf.rb']
|
27
|
+
|
28
|
+
s.add_dependency 'ffi'
|
21
29
|
|
22
|
-
s.add_development_dependency
|
23
|
-
s.add_development_dependency
|
24
|
-
s.add_development_dependency "rake-compiler"
|
25
|
-
s.add_development_dependency "rubygems-tasks"
|
26
|
-
s.add_development_dependency "rspec"
|
30
|
+
s.add_development_dependency 'bundler'
|
31
|
+
s.add_development_dependency 'rake', '~> 13.0'
|
27
32
|
end
|