pyroscope_beta 0.1.1
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 +7 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +11 -0
- data/LICENSE +202 -0
- data/README.md +9 -0
- data/Rakefile +10 -0
- data/ext/rbspy/Cargo.lock +2059 -0
- data/ext/rbspy/Cargo.toml +13 -0
- data/ext/rbspy/Rakefile +157 -0
- data/ext/rbspy/extconf.rb +11 -0
- data/ext/rbspy/src/lib.rs +156 -0
- data/ext/thread_id/Cargo.lock +16 -0
- data/ext/thread_id/Cargo.toml +11 -0
- data/ext/thread_id/Rakefile +157 -0
- data/ext/thread_id/extconf.rb +11 -0
- data/ext/thread_id/src/lib.rs +4 -0
- data/lib/pyroscope/version.rb +3 -0
- data/lib/pyroscope_beta.rb +87 -0
- metadata +90 -0
data/ext/rbspy/Rakefile
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "shellwords"
|
4
|
+
|
5
|
+
class RakeCargoHelper
|
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
|
+
str = `rustc --version --verbose`
|
24
|
+
info = str.lines.map {|l| l.chomp.split(/:\s+/, 2)}.drop(1).to_h
|
25
|
+
info["host"]
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.cargo_target_dir
|
29
|
+
return @cargo_target_dir if defined? @cargo_target_dir
|
30
|
+
|
31
|
+
str = `cargo metadata --format-version 1 --offline --no-deps --quiet`
|
32
|
+
begin
|
33
|
+
require "json"
|
34
|
+
dir = JSON.parse(str)["target_directory"]
|
35
|
+
rescue LoadError # json is usually part of the stdlib, but just in case
|
36
|
+
/"target_directory"\s*:\s*"(?<dir>[^"]*)"/ =~ str
|
37
|
+
end
|
38
|
+
@cargo_target_dir = dir || "target"
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.flags
|
42
|
+
cc_flags = Shellwords.split(RbConfig.expand(RbConfig::MAKEFILE_CONFIG["CC"].dup))
|
43
|
+
|
44
|
+
["-C", "linker=#{cc_flags.shift}",
|
45
|
+
*cc_flags.flat_map {|a| ["-C", "link-arg=#{a}"] },
|
46
|
+
"-L", "native=#{RbConfig::CONFIG["libdir"]}",
|
47
|
+
*dld_flags,
|
48
|
+
*platform_flags]
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.dld_flags
|
52
|
+
Shellwords.split(RbConfig::CONFIG["DLDFLAGS"]).flat_map do |arg|
|
53
|
+
arg = arg.gsub(/\$\((\w+)\)/) do
|
54
|
+
$1 == "DEFFILE" ? nil : RbConfig::CONFIG[name]
|
55
|
+
end.strip
|
56
|
+
next [] if arg.empty?
|
57
|
+
|
58
|
+
transform_flag(arg)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.platform_flags
|
63
|
+
return unless RbConfig::CONFIG["target_os"] =~ /mingw/i
|
64
|
+
|
65
|
+
[*Shellwords.split(RbConfig::CONFIG["LIBRUBYARG"]).flat_map {|arg| transform_flag(arg)},
|
66
|
+
"-C", "link-arg=-Wl,--dynamicbase",
|
67
|
+
"-C", "link-arg=-Wl,--disable-auto-image-base",
|
68
|
+
"-C", "link-arg=-static-libgcc"]
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.transform_flag(arg)
|
72
|
+
k, v = arg.split(/(?<=..)/, 2)
|
73
|
+
case k
|
74
|
+
when "-L"
|
75
|
+
[k, "native=#{v}"]
|
76
|
+
when "-l"
|
77
|
+
[k, v]
|
78
|
+
when "-F"
|
79
|
+
["-l", "framework=#{v}"]
|
80
|
+
else
|
81
|
+
["-C", "link_arg=#{k}#{v}"]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def install_dir
|
86
|
+
File.expand_path(File.join("..", "..", "lib", gemname), __dir__)
|
87
|
+
end
|
88
|
+
|
89
|
+
def rust_name
|
90
|
+
prefix = "lib" unless Gem.win_platform?
|
91
|
+
suffix = if RbConfig::CONFIG["target_os"] =~ /darwin/i
|
92
|
+
".dylib"
|
93
|
+
elsif Gem.win_platform?
|
94
|
+
".dll"
|
95
|
+
else
|
96
|
+
".so"
|
97
|
+
end
|
98
|
+
"#{prefix}#{gemname}#{suffix}"
|
99
|
+
end
|
100
|
+
|
101
|
+
def ruby_name
|
102
|
+
"#{gemname}.#{RbConfig::CONFIG["DLEXT"]}"
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
task default: [:install, :clean]
|
108
|
+
|
109
|
+
desc "set dev mode for subsequent task, run like `rake dev install`"
|
110
|
+
task :dev do
|
111
|
+
@dev = true
|
112
|
+
end
|
113
|
+
|
114
|
+
desc "build gem native extension and copy to lib"
|
115
|
+
task install: [:cd, :build] do
|
116
|
+
helper = RakeCargoHelper.new
|
117
|
+
profile_dir = @dev ? "debug" : "release"
|
118
|
+
source = File.join(RakeCargoHelper.cargo_target_dir, profile_dir, helper.rust_name)
|
119
|
+
dest = File.join(helper.install_dir, helper.ruby_name)
|
120
|
+
mkdir_p(helper.install_dir)
|
121
|
+
rm(dest) if File.exist?(dest)
|
122
|
+
cp(source, dest)
|
123
|
+
end
|
124
|
+
|
125
|
+
desc "build gem native extension"
|
126
|
+
task build: [:cargo, :cd] do
|
127
|
+
sh "cargo", "rustc", *(["--locked", "--release"] unless @dev), "--", *RakeCargoHelper.flags
|
128
|
+
end
|
129
|
+
|
130
|
+
desc "clean up release build artifacts"
|
131
|
+
task clean: [:cargo, :cd] do
|
132
|
+
sh "cargo clean --release"
|
133
|
+
end
|
134
|
+
|
135
|
+
desc "clean up build artifacts"
|
136
|
+
task clobber: [:cargo, :cd] do
|
137
|
+
sh "cargo clean"
|
138
|
+
end
|
139
|
+
|
140
|
+
desc "check for cargo"
|
141
|
+
task :cargo do
|
142
|
+
raise <<-MSG unless RakeCargoHelper.command?("cargo")
|
143
|
+
This gem requires a Rust compiler and the `cargo' build tool to build the
|
144
|
+
gem's native extension. See https://www.rust-lang.org/tools/install for
|
145
|
+
how to install Rust. `cargo' is usually part of the Rust installation.
|
146
|
+
MSG
|
147
|
+
|
148
|
+
raise <<-MSG if Gem.win_platform? && RakeCargoHelper.rust_toolchain !~ /gnu/
|
149
|
+
Found Rust toolchain `#{RakeCargoHelper.rust_toolchain}' but the gem native
|
150
|
+
extension requires the gnu toolchain on Windows.
|
151
|
+
MSG
|
152
|
+
end
|
153
|
+
|
154
|
+
# ensure task is running in the right dir
|
155
|
+
task :cd do
|
156
|
+
cd(__dir__) unless __dir__ == pwd
|
157
|
+
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
use pyroscope::backend::Tag;
|
2
|
+
use pyroscope::PyroscopeAgent;
|
3
|
+
use pyroscope_rbspy::{rbspy_backend, RbspyConfig};
|
4
|
+
use std::ffi::CStr;
|
5
|
+
use std::mem::MaybeUninit;
|
6
|
+
use std::os::raw::c_char;
|
7
|
+
use std::sync::mpsc::{sync_channel, Receiver, SyncSender};
|
8
|
+
use std::sync::{Mutex, Once};
|
9
|
+
|
10
|
+
pub enum Signal {
|
11
|
+
Kill,
|
12
|
+
AddTag(u64, String, String),
|
13
|
+
RemoveTag(u64, String, String),
|
14
|
+
}
|
15
|
+
|
16
|
+
pub struct SignalPass {
|
17
|
+
inner_sender: Mutex<SyncSender<Signal>>,
|
18
|
+
inner_receiver: Mutex<Receiver<Signal>>,
|
19
|
+
}
|
20
|
+
|
21
|
+
fn signalpass() -> &'static SignalPass {
|
22
|
+
static mut SIGNAL_PASS: MaybeUninit<SignalPass> = MaybeUninit::uninit();
|
23
|
+
static ONCE: Once = Once::new();
|
24
|
+
|
25
|
+
ONCE.call_once(|| unsafe {
|
26
|
+
let (sender, receiver) = sync_channel(1);
|
27
|
+
let singleton = SignalPass {
|
28
|
+
inner_sender: Mutex::new(sender),
|
29
|
+
inner_receiver: Mutex::new(receiver),
|
30
|
+
};
|
31
|
+
SIGNAL_PASS = MaybeUninit::new(singleton);
|
32
|
+
});
|
33
|
+
|
34
|
+
unsafe { SIGNAL_PASS.assume_init_ref() }
|
35
|
+
}
|
36
|
+
|
37
|
+
#[link(name = "pyroscope_ffi", vers = "0.1")]
|
38
|
+
#[no_mangle]
|
39
|
+
pub fn initialize_agent(
|
40
|
+
application_name: *const c_char, server_address: *const c_char, sample_rate: u32,
|
41
|
+
detect_subprocesses: bool, tags: *const c_char,
|
42
|
+
) -> bool {
|
43
|
+
let application_name = unsafe { CStr::from_ptr(application_name) }
|
44
|
+
.to_str()
|
45
|
+
.unwrap()
|
46
|
+
.to_string();
|
47
|
+
let server_address = unsafe { CStr::from_ptr(server_address) }
|
48
|
+
.to_str()
|
49
|
+
.unwrap()
|
50
|
+
.to_string();
|
51
|
+
let tags_string = unsafe { CStr::from_ptr(tags) }
|
52
|
+
.to_str()
|
53
|
+
.unwrap()
|
54
|
+
.to_string();
|
55
|
+
|
56
|
+
let pid = std::process::id();
|
57
|
+
|
58
|
+
let s = signalpass();
|
59
|
+
|
60
|
+
std::thread::spawn(move || {
|
61
|
+
let rbspy_config = RbspyConfig::new(pid.try_into().unwrap())
|
62
|
+
.sample_rate(sample_rate)
|
63
|
+
.lock_process(false)
|
64
|
+
.with_subprocesses(detect_subprocesses);
|
65
|
+
|
66
|
+
let tags_ref = tags_string.as_str();
|
67
|
+
let tags = string_to_tags(tags_ref);
|
68
|
+
let rbspy = rbspy_backend(rbspy_config);
|
69
|
+
let agent = PyroscopeAgent::builder(server_address, application_name)
|
70
|
+
.backend(rbspy)
|
71
|
+
.tags(tags)
|
72
|
+
.build()
|
73
|
+
.unwrap();
|
74
|
+
|
75
|
+
let agent_running = agent.start().unwrap();
|
76
|
+
|
77
|
+
while let Ok(signal) = s.inner_receiver.lock().unwrap().recv() {
|
78
|
+
match signal {
|
79
|
+
Signal::Kill => {
|
80
|
+
agent_running.stop().unwrap();
|
81
|
+
break;
|
82
|
+
}
|
83
|
+
Signal::AddTag(thread_id, key, value) => {
|
84
|
+
let tag = Tag::new(key, value);
|
85
|
+
agent_running.add_thread_tag(thread_id, tag).unwrap();
|
86
|
+
}
|
87
|
+
Signal::RemoveTag(thread_id, key, value) => {
|
88
|
+
let tag = Tag::new(key, value);
|
89
|
+
agent_running.remove_thread_tag(thread_id, tag).unwrap();
|
90
|
+
}
|
91
|
+
}
|
92
|
+
}
|
93
|
+
});
|
94
|
+
|
95
|
+
true
|
96
|
+
}
|
97
|
+
|
98
|
+
#[link(name = "pyroscope_ffi", vers = "0.1")]
|
99
|
+
#[no_mangle]
|
100
|
+
pub fn drop_agent() -> bool {
|
101
|
+
let s = signalpass();
|
102
|
+
s.inner_sender.lock().unwrap().send(Signal::Kill).unwrap();
|
103
|
+
true
|
104
|
+
}
|
105
|
+
|
106
|
+
#[link(name = "pyroscope_ffi", vers = "0.1")]
|
107
|
+
#[no_mangle]
|
108
|
+
pub fn add_tag(thread_id: u64, key: *const c_char, value: *const c_char) -> bool {
|
109
|
+
let s = signalpass();
|
110
|
+
let key = unsafe { CStr::from_ptr(key) }.to_str().unwrap().to_owned();
|
111
|
+
let value = unsafe { CStr::from_ptr(value) }
|
112
|
+
.to_str()
|
113
|
+
.unwrap()
|
114
|
+
.to_owned();
|
115
|
+
s.inner_sender
|
116
|
+
.lock()
|
117
|
+
.unwrap()
|
118
|
+
.send(Signal::AddTag(thread_id, key, value))
|
119
|
+
.unwrap();
|
120
|
+
true
|
121
|
+
}
|
122
|
+
|
123
|
+
#[link(name = "pyroscope_ffi", vers = "0.1")]
|
124
|
+
#[no_mangle]
|
125
|
+
pub fn remove_tag(thread_id: u64, key: *const c_char, value: *const c_char) -> bool {
|
126
|
+
let s = signalpass();
|
127
|
+
let key = unsafe { CStr::from_ptr(key) }.to_str().unwrap().to_owned();
|
128
|
+
let value = unsafe { CStr::from_ptr(value) }
|
129
|
+
.to_str()
|
130
|
+
.unwrap()
|
131
|
+
.to_owned();
|
132
|
+
s.inner_sender
|
133
|
+
.lock()
|
134
|
+
.unwrap()
|
135
|
+
.send(Signal::RemoveTag(thread_id, key, value))
|
136
|
+
.unwrap();
|
137
|
+
true
|
138
|
+
}
|
139
|
+
|
140
|
+
// Convert a string of tags to a Vec<(&str, &str)>
|
141
|
+
fn string_to_tags<'a>(tags: &'a str) -> Vec<(&'a str, &'a str)> {
|
142
|
+
let mut tags_vec = Vec::new();
|
143
|
+
// check if string is empty
|
144
|
+
if tags.is_empty() {
|
145
|
+
return tags_vec;
|
146
|
+
}
|
147
|
+
|
148
|
+
for tag in tags.split(',') {
|
149
|
+
let mut tag_split = tag.split('=');
|
150
|
+
let key = tag_split.next().unwrap();
|
151
|
+
let value = tag_split.next().unwrap();
|
152
|
+
tags_vec.push((key, value));
|
153
|
+
}
|
154
|
+
|
155
|
+
tags_vec
|
156
|
+
}
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# This file is automatically @generated by Cargo.
|
2
|
+
# It is not intended for manual editing.
|
3
|
+
version = 3
|
4
|
+
|
5
|
+
[[package]]
|
6
|
+
name = "libc"
|
7
|
+
version = "0.2.125"
|
8
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
9
|
+
checksum = "5916d2ae698f6de9bfb891ad7a8d65c09d232dc58cc4ac433c7da3b2fd84bc2b"
|
10
|
+
|
11
|
+
[[package]]
|
12
|
+
name = "thread_id"
|
13
|
+
version = "0.1.0"
|
14
|
+
dependencies = [
|
15
|
+
"libc",
|
16
|
+
]
|
@@ -0,0 +1,157 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "shellwords"
|
4
|
+
|
5
|
+
class RakeCargoHelper
|
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
|
+
str = `rustc --version --verbose`
|
24
|
+
info = str.lines.map {|l| l.chomp.split(/:\s+/, 2)}.drop(1).to_h
|
25
|
+
info["host"]
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.cargo_target_dir
|
29
|
+
return @cargo_target_dir if defined? @cargo_target_dir
|
30
|
+
|
31
|
+
str = `cargo metadata --format-version 1 --offline --no-deps --quiet`
|
32
|
+
begin
|
33
|
+
require "json"
|
34
|
+
dir = JSON.parse(str)["target_directory"]
|
35
|
+
rescue LoadError # json is usually part of the stdlib, but just in case
|
36
|
+
/"target_directory"\s*:\s*"(?<dir>[^"]*)"/ =~ str
|
37
|
+
end
|
38
|
+
@cargo_target_dir = dir || "target"
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.flags
|
42
|
+
cc_flags = Shellwords.split(RbConfig.expand(RbConfig::MAKEFILE_CONFIG["CC"].dup))
|
43
|
+
|
44
|
+
["-C", "linker=#{cc_flags.shift}",
|
45
|
+
*cc_flags.flat_map {|a| ["-C", "link-arg=#{a}"] },
|
46
|
+
"-L", "native=#{RbConfig::CONFIG["libdir"]}",
|
47
|
+
*dld_flags,
|
48
|
+
*platform_flags]
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.dld_flags
|
52
|
+
Shellwords.split(RbConfig::CONFIG["DLDFLAGS"]).flat_map do |arg|
|
53
|
+
arg = arg.gsub(/\$\((\w+)\)/) do
|
54
|
+
$1 == "DEFFILE" ? nil : RbConfig::CONFIG[name]
|
55
|
+
end.strip
|
56
|
+
next [] if arg.empty?
|
57
|
+
|
58
|
+
transform_flag(arg)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.platform_flags
|
63
|
+
return unless RbConfig::CONFIG["target_os"] =~ /mingw/i
|
64
|
+
|
65
|
+
[*Shellwords.split(RbConfig::CONFIG["LIBRUBYARG"]).flat_map {|arg| transform_flag(arg)},
|
66
|
+
"-C", "link-arg=-Wl,--dynamicbase",
|
67
|
+
"-C", "link-arg=-Wl,--disable-auto-image-base",
|
68
|
+
"-C", "link-arg=-static-libgcc"]
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.transform_flag(arg)
|
72
|
+
k, v = arg.split(/(?<=..)/, 2)
|
73
|
+
case k
|
74
|
+
when "-L"
|
75
|
+
[k, "native=#{v}"]
|
76
|
+
when "-l"
|
77
|
+
[k, v]
|
78
|
+
when "-F"
|
79
|
+
["-l", "framework=#{v}"]
|
80
|
+
else
|
81
|
+
["-C", "link_arg=#{k}#{v}"]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def install_dir
|
86
|
+
File.expand_path(File.join("..", "..", "lib", gemname), __dir__)
|
87
|
+
end
|
88
|
+
|
89
|
+
def rust_name
|
90
|
+
prefix = "lib" unless Gem.win_platform?
|
91
|
+
suffix = if RbConfig::CONFIG["target_os"] =~ /darwin/i
|
92
|
+
".dylib"
|
93
|
+
elsif Gem.win_platform?
|
94
|
+
".dll"
|
95
|
+
else
|
96
|
+
".so"
|
97
|
+
end
|
98
|
+
"#{prefix}#{gemname}#{suffix}"
|
99
|
+
end
|
100
|
+
|
101
|
+
def ruby_name
|
102
|
+
"#{gemname}.#{RbConfig::CONFIG["DLEXT"]}"
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
task default: [:install, :clean]
|
108
|
+
|
109
|
+
desc "set dev mode for subsequent task, run like `rake dev install`"
|
110
|
+
task :dev do
|
111
|
+
@dev = true
|
112
|
+
end
|
113
|
+
|
114
|
+
desc "build gem native extension and copy to lib"
|
115
|
+
task install: [:cd, :build] do
|
116
|
+
helper = RakeCargoHelper.new
|
117
|
+
profile_dir = @dev ? "debug" : "release"
|
118
|
+
source = File.join(RakeCargoHelper.cargo_target_dir, profile_dir, helper.rust_name)
|
119
|
+
dest = File.join(helper.install_dir, helper.ruby_name)
|
120
|
+
mkdir_p(helper.install_dir)
|
121
|
+
rm(dest) if File.exist?(dest)
|
122
|
+
cp(source, dest)
|
123
|
+
end
|
124
|
+
|
125
|
+
desc "build gem native extension"
|
126
|
+
task build: [:cargo, :cd] do
|
127
|
+
sh "cargo", "rustc", *(["--locked", "--release"] unless @dev), "--", *RakeCargoHelper.flags
|
128
|
+
end
|
129
|
+
|
130
|
+
desc "clean up release build artifacts"
|
131
|
+
task clean: [:cargo, :cd] do
|
132
|
+
sh "cargo clean --release"
|
133
|
+
end
|
134
|
+
|
135
|
+
desc "clean up build artifacts"
|
136
|
+
task clobber: [:cargo, :cd] do
|
137
|
+
sh "cargo clean"
|
138
|
+
end
|
139
|
+
|
140
|
+
desc "check for cargo"
|
141
|
+
task :cargo do
|
142
|
+
raise <<-MSG unless RakeCargoHelper.command?("cargo")
|
143
|
+
This gem requires a Rust compiler and the `cargo' build tool to build the
|
144
|
+
gem's native extension. See https://www.rust-lang.org/tools/install for
|
145
|
+
how to install Rust. `cargo' is usually part of the Rust installation.
|
146
|
+
MSG
|
147
|
+
|
148
|
+
raise <<-MSG if Gem.win_platform? && RakeCargoHelper.rust_toolchain !~ /gnu/
|
149
|
+
Found Rust toolchain `#{RakeCargoHelper.rust_toolchain}' but the gem native
|
150
|
+
extension requires the gnu toolchain on Windows.
|
151
|
+
MSG
|
152
|
+
end
|
153
|
+
|
154
|
+
# ensure task is running in the right dir
|
155
|
+
task :cd do
|
156
|
+
cd(__dir__) unless __dir__ == pwd
|
157
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'ffi'
|
2
|
+
require 'fiddle'
|
3
|
+
|
4
|
+
$libm = Fiddle.dlopen(File.expand_path(File.dirname(__FILE__)) + '/thread_id/thread_id.so')
|
5
|
+
|
6
|
+
|
7
|
+
module Rust
|
8
|
+
extend FFI::Library
|
9
|
+
ffi_lib File.expand_path(File.dirname(__FILE__)) + '/rbspy/rbspy.' + FFI::Platform::LIBSUFFIX
|
10
|
+
attach_function :initialize_agent, [:string, :string, :int, :bool, :string], :bool
|
11
|
+
attach_function :add_tag, [:uint64, :string, :string], :bool
|
12
|
+
attach_function :remove_tag, [:uint64, :string, :string], :bool
|
13
|
+
attach_function :drop_agent, [], :bool
|
14
|
+
end
|
15
|
+
|
16
|
+
module Pyroscope
|
17
|
+
Config = Struct.new(:application_name, :server_address, :sample_rate, :detect_subprocesses, :log_level, :tags) do
|
18
|
+
def initialize(*)
|
19
|
+
super
|
20
|
+
self.application_name ||= ''
|
21
|
+
self.server_address ||= 'http://localhost:4040'
|
22
|
+
self.sample_rate ||= 100
|
23
|
+
self.detect_subprocesses ||= true
|
24
|
+
self.log_level ||= 'info'
|
25
|
+
self.tags ||= []
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class << self
|
30
|
+
def configure
|
31
|
+
@config = Config.new
|
32
|
+
|
33
|
+
# Pass config to the block
|
34
|
+
yield @config
|
35
|
+
|
36
|
+
Rust.initialize_agent(
|
37
|
+
@config.application_name,
|
38
|
+
@config.server_address,
|
39
|
+
@config.sample_rate,
|
40
|
+
@config.detect_subprocesses,
|
41
|
+
tags_to_string(@config.tags)
|
42
|
+
)
|
43
|
+
|
44
|
+
puts @config
|
45
|
+
end
|
46
|
+
|
47
|
+
def tag_wrapper(tags)
|
48
|
+
add_tags(tags)
|
49
|
+
begin
|
50
|
+
yield
|
51
|
+
ensure
|
52
|
+
remove_tags(tags)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def drop
|
57
|
+
Rust.drop_agent
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# convert tags object to string
|
63
|
+
def tags_to_string(tags)
|
64
|
+
tags.map { |k, v| "#{k}=#{v}" }.join(',')
|
65
|
+
end
|
66
|
+
|
67
|
+
# get thread id
|
68
|
+
def thread_id
|
69
|
+
thread_id = Fiddle::Function.new($libm['thread_id'], [], Fiddle::TYPE_INT64_T)
|
70
|
+
thread_id.call
|
71
|
+
end
|
72
|
+
|
73
|
+
# add tags
|
74
|
+
def add_tags(tags)
|
75
|
+
tags.each do |tag_name, tag_value|
|
76
|
+
thread_id = thread_id()
|
77
|
+
Rust.add_tag(thread_id, tag_name.to_s, tag_value.to_s)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# remove tags
|
82
|
+
def remove_tags(tags)
|
83
|
+
tags.each do |tag_name, tag_value|
|
84
|
+
thread_id = thread_id()
|
85
|
+
Rust.remove_tag(thread_id, tag_name.to_s, tag_value.to_s)
|
86
|
+
end
|
87
|
+
end
|