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,191 @@
1
+ use super::TimerSignal;
2
+ use crate::{
3
+ utils::{check_err, get_time_range},
4
+ Result,
5
+ };
6
+
7
+ use std::sync::{
8
+ mpsc::{channel, Receiver, Sender},
9
+ Arc, Mutex,
10
+ };
11
+ use std::{
12
+ thread::{self, JoinHandle},
13
+ time::Duration,
14
+ };
15
+
16
+ const LOG_TAG: &str = "Pyroscope::Timer";
17
+
18
+ /// A thread that sends a notification every 10th second
19
+ ///
20
+ /// Timer will send an event to attached listeners (mpsc::Sender) every 10th
21
+ /// second (...10, ...20, ...)
22
+ ///
23
+ /// The Timer thread will run continously until all Senders are dropped.
24
+ /// The Timer thread will be joined when all Senders are dropped.
25
+
26
+ #[derive(Debug, Default)]
27
+ pub struct Timer {
28
+ /// A vector to store listeners (mpsc::Sender)
29
+ txs: Arc<Mutex<Vec<Sender<TimerSignal>>>>,
30
+
31
+ /// Thread handle
32
+ pub handle: Option<JoinHandle<Result<()>>>,
33
+ }
34
+
35
+ impl Timer {
36
+ /// Initialize Timer and run a thread to send events to attached listeners
37
+ pub fn initialize(cycle: Duration) -> Result<Self> {
38
+ log::info!(target: LOG_TAG, "Initializing Timer");
39
+
40
+ let txs = Arc::new(Mutex::new(Vec::new()));
41
+
42
+ // Add Default tx
43
+ let (tx, _rx): (Sender<TimerSignal>, Receiver<TimerSignal>) = channel();
44
+ txs.lock()?.push(tx);
45
+
46
+ let kqueue = kqueue()?;
47
+
48
+ let handle = Some({
49
+ let txs = txs.clone();
50
+ thread::spawn(move || {
51
+ // Wait for initial expiration
52
+ let initial_event = Timer::register_initial_expiration(kqueue)?;
53
+ Timer::wait_event(kqueue, [initial_event].as_mut_ptr())?;
54
+
55
+ // Register loop event
56
+ let loop_event = Timer::register_loop_expiration(kqueue, cycle)?;
57
+
58
+ // Loop 10s
59
+ loop {
60
+ // Exit thread if there are no listeners
61
+ if txs.lock()?.is_empty() {
62
+ // TODO: should close file descriptors?
63
+ log::info!(target: LOG_TAG, "Timer thread terminated");
64
+ return Ok(());
65
+ }
66
+
67
+ // Get current time
68
+ let from = TimerSignal::NextSnapshot(get_time_range(0)?.from);
69
+
70
+ // Iterate through Senders
71
+ txs.lock()?.iter().for_each(|tx| {
72
+ // Send event to attached Sender
73
+ match tx.send(from) {
74
+ Ok(_) => {
75
+ log::trace!(target: LOG_TAG, "Sent event to listener @ {:?}", &tx)
76
+ }
77
+ Err(_e) => {} // There could be a less confusing message, or this
78
+ // refactored to avoid a first sender
79
+ //log::warn!(
80
+ //target: LOG_TAG,
81
+ //"Failed to send event to listener @ {:?} - {}",
82
+ //&tx,
83
+ //e
84
+ //),
85
+ }
86
+ });
87
+
88
+ // Wait 10s
89
+ Timer::wait_event(kqueue, [loop_event].as_mut_ptr())?;
90
+ }
91
+ })
92
+ });
93
+
94
+ Ok(Self { handle, txs })
95
+ }
96
+
97
+ /// Attach an mpsc::Sender to Timer
98
+ ///
99
+ /// Timer will dispatch an event with the timestamp of the current instant,
100
+ /// every 10th second to all attached senders
101
+ pub fn attach_listener(&mut self, tx: Sender<TimerSignal>) -> Result<()> {
102
+ // Push Sender to a Vector of Sender(s)
103
+ let txs = Arc::clone(&self.txs);
104
+ txs.lock()?.push(tx);
105
+
106
+ Ok(())
107
+ }
108
+
109
+ /// Clear the listeners (txs) from Timer. This will shutdown the Timer thread
110
+ pub fn drop_listeners(&mut self) -> Result<()> {
111
+ let txs = Arc::clone(&self.txs);
112
+ txs.lock()?.clear();
113
+
114
+ Ok(())
115
+ }
116
+
117
+ /// Wait for the timer event
118
+ fn wait_event(kqueue: i32, events: *mut libc::kevent) -> Result<()> {
119
+ kevent(kqueue, [].as_mut_ptr(), 0, events, 1, std::ptr::null())?;
120
+ Ok(())
121
+ }
122
+
123
+ /// Register an initial expiration event
124
+ fn register_initial_expiration(kqueue: i32) -> Result<libc::kevent> {
125
+ // Get first event time
126
+ let first_fire = get_time_range(0)?.until;
127
+
128
+ let initial_event = libc::kevent {
129
+ ident: 1,
130
+ filter: libc::EVFILT_TIMER,
131
+ flags: libc::EV_ADD | libc::EV_ENABLE | libc::EV_ONESHOT,
132
+ fflags: libc::NOTE_ABSOLUTE | libc::NOTE_SECONDS,
133
+ data: first_fire as isize,
134
+ udata: std::ptr::null_mut::<libc::c_void>(),
135
+ };
136
+
137
+ // add first event
138
+ kevent(
139
+ kqueue,
140
+ [initial_event].as_ptr(),
141
+ 1,
142
+ [].as_mut_ptr(),
143
+ 0,
144
+ std::ptr::null(),
145
+ )?;
146
+
147
+ Ok(initial_event)
148
+ }
149
+
150
+ /// Register a loop expiration event
151
+ fn register_loop_expiration(kqueue: i32, duration: Duration) -> Result<libc::kevent> {
152
+ let loop_event = libc::kevent {
153
+ ident: 1,
154
+ filter: libc::EVFILT_TIMER,
155
+ flags: libc::EV_ADD | libc::EV_ENABLE,
156
+ fflags: 0,
157
+ data: duration.as_millis() as isize,
158
+ udata: std::ptr::null_mut::<libc::c_void>(),
159
+ };
160
+
161
+ // add loop event
162
+ kevent(
163
+ kqueue,
164
+ [loop_event].as_ptr(),
165
+ 1,
166
+ [].as_mut_ptr(),
167
+ 0,
168
+ std::ptr::null(),
169
+ )?;
170
+
171
+ Ok(loop_event)
172
+ }
173
+ }
174
+
175
+ /// libc::kqueue wrapper
176
+ fn kqueue() -> Result<i32> {
177
+ check_err(unsafe { libc::kqueue() })
178
+ }
179
+
180
+ /// libc::kevent wrapper
181
+ fn kevent(
182
+ kqueue: i32,
183
+ change: *const libc::kevent,
184
+ c_count: libc::c_int,
185
+ events: *mut libc::kevent,
186
+ e_count: libc::c_int,
187
+ timeout: *const libc::timespec,
188
+ ) -> Result<()> {
189
+ check_err(unsafe { libc::kevent(kqueue, change, c_count, events, e_count, timeout) })?;
190
+ Ok(())
191
+ }
@@ -0,0 +1,38 @@
1
+ /// A signal sent to the timer.
2
+ ///
3
+ /// Either schedules another wake-up, or asks
4
+ /// the timer thread to terminate.
5
+ #[derive(Debug, Clone, Copy)]
6
+ pub enum TimerSignal {
7
+ // Thread termination was requested.
8
+ Terminate,
9
+ // When to take the next snapshot using the `Backend`.
10
+ NextSnapshot(u64),
11
+ }
12
+
13
+ impl std::fmt::Display for TimerSignal {
14
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
15
+ match self {
16
+ Self::Terminate => write!(f, "Terminate"),
17
+ Self::NextSnapshot(when) => write!(f, "NextSnapshot({when})"),
18
+ }
19
+ }
20
+ }
21
+
22
+ // Possibly: ios, netbsd, openbsd, freebsd
23
+ #[cfg(target_os = "macos")]
24
+ pub mod kqueue;
25
+
26
+ #[cfg(target_os = "macos")]
27
+ pub use kqueue::Timer;
28
+
29
+ // Possibly: android
30
+ #[cfg(target_os = "linux")]
31
+ pub mod epoll;
32
+ #[cfg(target_os = "linux")]
33
+ pub use epoll::Timer;
34
+
35
+ #[cfg(not(any(target_os = "linux", target_os = "macos")))]
36
+ pub mod sleep;
37
+ #[cfg(not(any(target_os = "linux", target_os = "macos")))]
38
+ pub use sleep::Timer;
@@ -0,0 +1,114 @@
1
+ use super::TimerSignal;
2
+ use crate::{utils::get_time_range, Result};
3
+
4
+ use std::{
5
+ sync::{
6
+ mpsc::{channel, Receiver, Sender},
7
+ Arc, Mutex,
8
+ },
9
+ thread::{self, JoinHandle},
10
+ time::Duration,
11
+ };
12
+
13
+ const LOG_TAG: &str = "Pyroscope::Timer";
14
+
15
+ /// A thread that sends a notification every 10th second
16
+ ///
17
+ /// Timer will send an event to attached listeners (mpsc::Sender) every 10th
18
+ /// second (...10, ...20, ...)
19
+ ///
20
+ /// The Timer thread will run continously until all Senders are dropped.
21
+ /// The Timer thread will be joined when all Senders are dropped.
22
+
23
+ #[derive(Debug, Default)]
24
+ pub struct Timer {
25
+ /// A vector to store listeners (mpsc::Sender)
26
+ txs: Arc<Mutex<Vec<Sender<TimerSignal>>>>,
27
+
28
+ /// Thread handle
29
+ pub handle: Option<JoinHandle<Result<()>>>,
30
+ }
31
+
32
+ impl Timer {
33
+ /// Initialize Timer and run a thread to send events to attached listeners
34
+ pub fn initialize(cycle: Duration) -> Result<Self> {
35
+ log::info!(target: LOG_TAG, "Initializing Timer");
36
+
37
+ let txs = Arc::new(Mutex::new(Vec::new()));
38
+
39
+ // Add Default tx
40
+ let (tx, _rx): (Sender<TimerSignal>, Receiver<TimerSignal>) = channel();
41
+ txs.lock()?.push(tx);
42
+
43
+ // Spawn a Thread
44
+ let handle = Some({
45
+ let txs = txs.clone();
46
+
47
+ thread::spawn(move || {
48
+ // Get remaining time for 10th second fire event
49
+ let rem = get_time_range(0)?.rem;
50
+
51
+ // Sleep for rem seconds
52
+ thread::sleep(Duration::from_secs(rem));
53
+
54
+ loop {
55
+ // Exit thread if there are no listeners
56
+ if txs.lock()?.len() == 0 {
57
+ log::info!(target: LOG_TAG, "Timer thread terminated");
58
+
59
+ return Ok(());
60
+ }
61
+
62
+ // Get current time
63
+ let from = TimerSignal::NextSnapshot(get_time_range(0)?.from);
64
+
65
+ log::trace!(target: LOG_TAG, "Timer fired @ {}", from);
66
+
67
+ // Iterate through Senders
68
+ txs.lock()?.iter().for_each(|tx| {
69
+ // Send event to attached Sender
70
+ // Send event to attached Sender
71
+ match tx.send(from) {
72
+ Ok(_) => {
73
+ log::trace!(target: LOG_TAG, "Sent event to listener @ {:?}", &tx)
74
+ }
75
+ Err(_e) => {} // There could be a less confusing message, or this
76
+ // refactored to avoid a first sender
77
+ //log::warn!(
78
+ //target: LOG_TAG,
79
+ //"Failed to send event to listener @ {:?} - {}",
80
+ //&tx,
81
+ //e
82
+ //),
83
+ }
84
+ });
85
+
86
+ // Sleep for 10s
87
+ thread::sleep(cycle);
88
+ }
89
+ })
90
+ });
91
+
92
+ Ok(Self { handle, txs })
93
+ }
94
+
95
+ /// Attach an mpsc::Sender to Timer
96
+ ///
97
+ /// Timer will dispatch an event with the timestamp of the current instant,
98
+ /// every 10th second to all attached senders
99
+ pub fn attach_listener(&mut self, tx: Sender<TimerSignal>) -> Result<()> {
100
+ // Push Sender to a Vector of Sender(s)
101
+ let txs = Arc::clone(&self.txs);
102
+ txs.lock()?.push(tx);
103
+
104
+ Ok(())
105
+ }
106
+
107
+ /// Clear the listeners (txs) from Timer. This will shutdown the Timer thread
108
+ pub fn drop_listeners(&mut self) -> Result<()> {
109
+ let txs = Arc::clone(&self.txs);
110
+ txs.lock()?.clear();
111
+
112
+ Ok(())
113
+ }
114
+ }
@@ -0,0 +1,141 @@
1
+ use std::fmt;
2
+
3
+ use crate::{error::Result, PyroscopeError};
4
+
5
+ /// Error Wrapper for libc return. Only check for errors.
6
+ pub fn check_err<T: Ord + Default>(num: T) -> Result<T> {
7
+ if num < T::default() {
8
+ return Err(PyroscopeError::from(std::io::Error::last_os_error()));
9
+ }
10
+ Ok(num)
11
+ }
12
+
13
+ #[cfg(test)]
14
+ mod check_err_tests {
15
+ use crate::utils::check_err;
16
+
17
+ #[test]
18
+ fn check_err_success() {
19
+ assert_eq!(check_err(1).unwrap(), 1)
20
+ }
21
+
22
+ #[test]
23
+ fn check_err_error() {
24
+ assert!(check_err(-1).is_err())
25
+ }
26
+ }
27
+
28
+ #[derive(Debug, Eq, PartialEq, Hash, Clone)]
29
+ pub struct ThreadId {
30
+ pthread: libc::pthread_t,
31
+ }
32
+
33
+ // SAFETY: pthread_t is an opaque thread identifier used as a handle,
34
+ // never dereferenced. On musl it's *mut c_void, on glibc it's c_ulong.
35
+ unsafe impl Send for ThreadId {}
36
+ unsafe impl Sync for ThreadId {}
37
+
38
+ impl From<libc::pthread_t> for ThreadId {
39
+ fn from(value: libc::pthread_t) -> Self {
40
+ Self { pthread: value }
41
+ }
42
+ }
43
+ impl ThreadId {
44
+ pub fn pthread_self() -> Self {
45
+ Self {
46
+ pthread: unsafe { libc::pthread_self() },
47
+ }
48
+ }
49
+ }
50
+
51
+ impl fmt::Display for ThreadId {
52
+ #[cfg(target_env = "musl")]
53
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54
+ write!(f, "{}", { self.pthread as libc::uintptr_t })
55
+ }
56
+ #[cfg(not(target_env = "musl"))]
57
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58
+ write!(f, "{}", { self.pthread })
59
+ }
60
+ }
61
+
62
+ /// Return the current time in seconds.
63
+ pub fn get_current_time_secs() -> Result<u64> {
64
+ Ok(std::time::SystemTime::now()
65
+ .duration_since(std::time::UNIX_EPOCH)?
66
+ .as_secs())
67
+ }
68
+
69
+ #[cfg(test)]
70
+ mod get_current_time_secs_tests {
71
+ use crate::utils::get_current_time_secs;
72
+
73
+ #[test]
74
+ fn get_current_time_secs_success() {
75
+ assert!(get_current_time_secs().is_ok())
76
+ }
77
+ }
78
+
79
+ /// A representation of a time range. The time range is represented by a start
80
+ /// time, an end time, a current time and remaining time in seconds. The
81
+ /// remaining time is the duration in seconds until the end time.
82
+ #[derive(Debug, PartialEq)]
83
+ pub struct TimeRange {
84
+ pub from: u64,
85
+ pub until: u64,
86
+ pub current: u64,
87
+ pub rem: u64,
88
+ }
89
+
90
+ /// Return a range of timestamps in the form [start, end).
91
+ /// The range is inclusive of start and exclusive of end.
92
+ pub fn get_time_range(timestamp: u64) -> Result<TimeRange> {
93
+ // if timestamp is 0, then get the current time
94
+ if timestamp == 0 {
95
+ return get_time_range(get_current_time_secs()?);
96
+ }
97
+
98
+ // Determine the start and end of the range
99
+ Ok(TimeRange {
100
+ from: timestamp / 10 * 10,
101
+ until: timestamp / 10 * 10 + 10,
102
+ current: timestamp,
103
+ rem: 10 - (timestamp % 10),
104
+ })
105
+ }
106
+
107
+ #[cfg(test)]
108
+ mod get_time_range_tests {
109
+ use crate::utils::{get_time_range, TimeRange};
110
+
111
+ #[test]
112
+ fn get_time_range_verify() {
113
+ assert_eq!(
114
+ get_time_range(1644194479).unwrap(),
115
+ TimeRange {
116
+ from: 1644194470,
117
+ until: 1644194480,
118
+ current: 1644194479,
119
+ rem: 1,
120
+ }
121
+ );
122
+ assert_eq!(
123
+ get_time_range(1644194470).unwrap(),
124
+ TimeRange {
125
+ from: 1644194470,
126
+ until: 1644194480,
127
+ current: 1644194470,
128
+ rem: 10,
129
+ }
130
+ );
131
+ assert_eq!(
132
+ get_time_range(1644194476).unwrap(),
133
+ TimeRange {
134
+ from: 1644194470,
135
+ until: 1644194480,
136
+ current: 1644194476,
137
+ rem: 4,
138
+ }
139
+ );
140
+ }
141
+ }
@@ -1,3 +1,3 @@
1
1
  module Pyroscope
2
- VERSION = '1.0.9'.freeze # x-release-please-version
2
+ VERSION = '1.1.0'.freeze # x-release-please-version
3
3
  end
data/lib/pyroscope.rb CHANGED
@@ -9,7 +9,7 @@ module Pyroscope
9
9
  extend FFI::Library
10
10
  ffi_lib File.expand_path(File.dirname(__FILE__)) + "/rbspy/rbspy.#{RbConfig::CONFIG["DLEXT"]}"
11
11
  attach_function :initialize_logging, [:int], :bool
12
- attach_function :initialize_agent, [:string, :string, :string, :string, :int, :bool, :bool, :bool, :string, :string, :string], :bool
12
+ attach_function :initialize_agent, [:string, :string, :string, :string, :int, :bool, :bool, :bool, :string, :string, :string, :string, :string], :bool
13
13
  attach_function :add_thread_tag, [:string, :string], :bool
14
14
  attach_function :remove_thread_tag, [:string, :string], :bool
15
15
  attach_function :drop_agent, [], :bool
@@ -103,6 +103,8 @@ module Pyroscope
103
103
  @config.oncpu || false,
104
104
  @config.report_pid || false,
105
105
  @config.report_thread_id || false,
106
+ runtime_name,
107
+ runtime_version,
106
108
  tags_to_string(@config.tags || {}),
107
109
  @config.tenant_id || "",
108
110
  http_headers_to_json(@config.http_headers || {})
@@ -159,6 +161,14 @@ module Pyroscope
159
161
 
160
162
  private
161
163
 
164
+ def runtime_name
165
+ defined?(RUBY_ENGINE) ? RUBY_ENGINE : "ruby"
166
+ end
167
+
168
+ def runtime_version
169
+ defined?(RUBY_ENGINE_VERSION) ? RUBY_ENGINE_VERSION : RUBY_VERSION
170
+ end
171
+
162
172
  def tags_to_string(tags)
163
173
  tags.map { |k, v| "#{k}=#{v}" }.join(',')
164
174
  end
data/pyroscope.gemspec CHANGED
@@ -34,12 +34,10 @@ Gem::Specification.new do |s|
34
34
  "ext/rbspy/cbindgen.toml",
35
35
  "ext/rbspy/extconf.rb",
36
36
  "ext/rbspy/include/rbspy.h",
37
- "ext/rbspy/src/backend.rs",
38
- "ext/rbspy/src/lib.rs",
39
37
  "lib/pyroscope.rb",
40
38
  "lib/pyroscope/version.rb",
41
39
  "pyroscope.gemspec",
42
- ]
40
+ ] + Dir["ext/rbspy/src/**/*.rs"].sort
43
41
  s.platform = Gem::Platform::RUBY
44
42
 
45
43
  s.required_ruby_version = ">= 1.9.3"
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.9
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pyroscope Team
@@ -69,8 +69,28 @@ files:
69
69
  - ext/rbspy/cbindgen.toml
70
70
  - ext/rbspy/extconf.rb
71
71
  - ext/rbspy/include/rbspy.h
72
- - ext/rbspy/src/backend.rs
72
+ - ext/rbspy/src/backend/backend.rs
73
+ - ext/rbspy/src/backend/mod.rs
74
+ - ext/rbspy/src/backend/ruleset.rs
75
+ - ext/rbspy/src/backend/tests.rs
76
+ - ext/rbspy/src/backend/types.rs
77
+ - ext/rbspy/src/encode/gen/google.rs
78
+ - ext/rbspy/src/encode/gen/mod.rs
79
+ - ext/rbspy/src/encode/gen/push.rs
80
+ - ext/rbspy/src/encode/gen/types.rs
81
+ - ext/rbspy/src/encode/mod.rs
82
+ - ext/rbspy/src/encode/pprof.rs
83
+ - ext/rbspy/src/error.rs
84
+ - ext/rbspy/src/ffikit.rs
73
85
  - ext/rbspy/src/lib.rs
86
+ - ext/rbspy/src/pyroscope.rs
87
+ - ext/rbspy/src/rbspy_backend.rs
88
+ - ext/rbspy/src/session.rs
89
+ - ext/rbspy/src/timer/epoll.rs
90
+ - ext/rbspy/src/timer/kqueue.rs
91
+ - ext/rbspy/src/timer/mod.rs
92
+ - ext/rbspy/src/timer/sleep.rs
93
+ - ext/rbspy/src/utils.rs
74
94
  - lib/pyroscope.rb
75
95
  - lib/pyroscope/version.rb
76
96
  - pyroscope.gemspec