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.
@@ -1,4 +1,4 @@
1
- use pyroscope::{
1
+ use crate::{
2
2
  backend::{
3
3
  Backend, BackendConfig, Report, ReportBatch, ReportData, StackBuffer, StackFrame,
4
4
  StackTrace, ThreadTag, ThreadTagsSet,
@@ -192,8 +192,8 @@ impl From<(rbspy::StackTrace, &BackendConfig)> for StackTraceWrapper {
192
192
  }
193
193
  }
194
194
 
195
- pub fn self_thread_id() -> pyroscope::ThreadId {
195
+ pub fn self_thread_id() -> crate::ThreadId {
196
196
  // for rbspy we use pthread_t as thread id
197
197
  // https://github.com/ruby/ruby/blob/54a74c42033e42869e69e7dc9e67efa1faf225be/include/ruby/thread_native.h#L41
198
- pyroscope::ThreadId::pthread_self()
198
+ crate::ThreadId::pthread_self()
199
199
  }
@@ -0,0 +1,275 @@
1
+ use std::{
2
+ io::Write,
3
+ sync::mpsc::{sync_channel, Receiver, SyncSender},
4
+ thread::{self, JoinHandle},
5
+ time::Duration,
6
+ };
7
+
8
+ use crate::encode::gen::push::{PushRequest, RawProfileSeries, RawSample};
9
+ use crate::encode::gen::types::LabelPair;
10
+ use crate::{
11
+ backend::{Report, ReportBatch, ReportData},
12
+ encode::pprof,
13
+ pyroscope::PyroscopeConfig,
14
+ utils::get_time_range,
15
+ Result,
16
+ };
17
+ use libflate::gzip::Encoder;
18
+ use prost::Message;
19
+ use reqwest::Url;
20
+ use uuid::Uuid;
21
+
22
+ const LOG_TAG: &str = "Pyroscope::Session";
23
+ const LABEL_SCOPE_NAME: &str = "otel.scope.name";
24
+ const LABEL_SCOPE_VERSION: &str = "otel.scope.version";
25
+ const LABEL_PROCESS_RUNTIME_NAME: &str = "process.runtime.name";
26
+ const LABEL_PROCESS_RUNTIME_VERSION: &str = "process.runtime.version";
27
+ const LABEL_SERVICE_NAME: &str = "service_name";
28
+ const LABEL_PROFILE_NAME: &str = "__name__";
29
+ const SCOPE_NAME: &str = "com.grafana.pyroscope/ruby";
30
+
31
+ /// Session Signal
32
+ ///
33
+ /// This enum is used to send data to the session thread. It can also kill the session thread.
34
+ pub enum SessionSignal {
35
+ /// Send session data to the session thread.
36
+ Session(Box<Session>),
37
+ /// Kill the session thread.
38
+ Kill,
39
+ }
40
+
41
+ /// Manage sessions and send data to the server.
42
+ #[derive(Debug)]
43
+ pub struct SessionManager {
44
+ /// The SessionManager thread.
45
+ pub handle: Option<JoinHandle<Result<()>>>,
46
+ /// Channel to send data to the SessionManager thread.
47
+ pub tx: SyncSender<SessionSignal>,
48
+ }
49
+
50
+ impl SessionManager {
51
+ /// Create a new SessionManager
52
+ pub fn new() -> Result<Self> {
53
+ log::info!(target: LOG_TAG, "Creating SessionManager");
54
+
55
+ // Create a channel for sending and receiving sessions
56
+ let (tx, rx): (SyncSender<SessionSignal>, Receiver<SessionSignal>) = sync_channel(10);
57
+
58
+ // Create a thread for the SessionManager
59
+ let handle = Some(thread::spawn(move || {
60
+ log::trace!(target: LOG_TAG, "Started");
61
+ let client = reqwest::blocking::Client::new();
62
+ while let Ok(signal) = rx.recv() {
63
+ match signal {
64
+ SessionSignal::Session(session) => {
65
+ // Send the session
66
+ // Matching is done here (instead of ?) to avoid breaking
67
+ // the SessionManager thread if the server is not available.
68
+ match (*session).push(&client) {
69
+ Ok(_) => log::trace!("SessionManager - Session sent"),
70
+ Err(e) => log::error!("SessionManager - Failed to send session: {e}"),
71
+ }
72
+ }
73
+ SessionSignal::Kill => {
74
+ // Kill the session manager
75
+ log::trace!(target: LOG_TAG, "Kill signal received");
76
+ return Ok(());
77
+ }
78
+ }
79
+ }
80
+ Ok(())
81
+ }));
82
+
83
+ Ok(SessionManager { handle, tx })
84
+ }
85
+
86
+ /// Push a new session into the SessionManager
87
+ pub fn push(&self, session: SessionSignal) -> Result<()> {
88
+ // Push the session into the SessionManager
89
+ self.tx.send(session)?;
90
+
91
+ log::trace!(target: LOG_TAG, "SessionSignal pushed");
92
+
93
+ Ok(())
94
+ }
95
+ }
96
+
97
+ pub struct Session {
98
+ pub config: PyroscopeConfig,
99
+ pub batch: ReportBatch,
100
+ // unix time todo remove comment, use types
101
+ pub from: u64,
102
+ // unix time todo remove comment, use types
103
+ pub until: u64,
104
+ }
105
+
106
+ impl Session {
107
+ /// Create a new Session
108
+ /// # Example
109
+ /// ```ignore
110
+ /// let config = PyroscopeConfig::new("https://localhost:8080", "my-app", 100, "pyspy", "0.8.16");
111
+ /// let report = vec![1, 2, 3];
112
+ /// let until = 154065120;
113
+ /// let session = Session::new(until, config, report)?;
114
+ /// ```
115
+ pub fn new(until: u64, config: PyroscopeConfig, batch: ReportBatch) -> Result<Self> {
116
+ log::info!(target: LOG_TAG, "Creating Session");
117
+
118
+ // get_time_range should be used with "from". We balance this by reducing
119
+ // 10s from the returned range.
120
+ let time_range = get_time_range(until)?;
121
+
122
+ Ok(Self {
123
+ config,
124
+ batch,
125
+ from: time_range.from - 10,
126
+ until: time_range.until - 10,
127
+ })
128
+ }
129
+
130
+ fn push(self, client: &reqwest::blocking::Client) -> Result<()> {
131
+ log::info!(target: LOG_TAG, "Sending Session: {} - {}", self.from, self.until);
132
+
133
+ let ReportBatch { profile_type, data } = self.batch;
134
+
135
+ let raw_profile = match data {
136
+ ReportData::RawPprof(pprof_bytes) => {
137
+ if self.config.func.is_some() {
138
+ log::warn!(target: LOG_TAG, "report transform function is not supported with raw pprof backends (e.g. jemalloc)");
139
+ }
140
+ pprof_bytes
141
+ }
142
+ ReportData::Reports(reports) => {
143
+ let transformed: Vec<Report>;
144
+ let encode_input = match self.config.func {
145
+ None => &reports,
146
+ Some(f) => {
147
+ transformed = reports.iter().map(|r| f(r.to_owned())).collect();
148
+ &transformed
149
+ }
150
+ };
151
+ pprof::encode(
152
+ encode_input,
153
+ self.config.sample_rate,
154
+ self.from * 1_000_000_000,
155
+ (self.until - self.from) * 1_000_000_000,
156
+ )
157
+ .encode_to_vec()
158
+ }
159
+ };
160
+
161
+ let labels = labels_for_profile(&self.config, profile_type);
162
+ let req = PushRequest {
163
+ series: vec![RawProfileSeries {
164
+ labels,
165
+ samples: vec![RawSample {
166
+ raw_profile,
167
+ id: Uuid::new_v4().to_string(),
168
+ }],
169
+ }],
170
+ };
171
+
172
+ let req = Self::gzip(&req.encode_to_vec())?;
173
+
174
+ let mut url = Url::parse(&self.config.url)?;
175
+ url.path_segments_mut()
176
+ .unwrap()
177
+ .push("push.v1.PusherService")
178
+ .push("Push");
179
+
180
+ let mut req_builder = client
181
+ .post(url.as_str())
182
+ .header(
183
+ "User-Agent",
184
+ format!(
185
+ "pyroscope-rs/{}/{} reqwest",
186
+ self.config.spy_name, self.config.spy_version
187
+ ),
188
+ )
189
+ .header("Content-Type", "application/proto")
190
+ .header("Content-Encoding", "gzip");
191
+
192
+ if let Some(basic_auth) = &self.config.basic_auth {
193
+ req_builder = req_builder.basic_auth(
194
+ basic_auth.username.clone(),
195
+ Some(basic_auth.password.clone()),
196
+ );
197
+ }
198
+ if let Some(tenant_id) = &self.config.tenant_id {
199
+ req_builder = req_builder.header("X-Scope-OrgID", tenant_id);
200
+ }
201
+ for (k, v) in &self.config.http_headers {
202
+ req_builder = req_builder.header(k, v);
203
+ }
204
+
205
+ let mut response = req_builder
206
+ .body(req)
207
+ .timeout(Duration::from_secs(10))
208
+ .send()?;
209
+
210
+ let status = response.status();
211
+
212
+ if status.is_success() {
213
+ let mut sink = std::io::sink();
214
+ _ = response.copy_to(&mut sink);
215
+ } else {
216
+ let resp = response.text();
217
+ let resp = match &resp {
218
+ Ok(t) => t,
219
+ Err(_) => "",
220
+ };
221
+ log::error!(target: LOG_TAG, "Sending Session failed {} {}", status.as_u16(), resp);
222
+ }
223
+ Ok(())
224
+ }
225
+
226
+ fn gzip(report: &[u8]) -> Result<Vec<u8>> {
227
+ let mut encoder = Encoder::new(Vec::new())?;
228
+ encoder.write_all(report)?;
229
+ let compressed_data = encoder.finish().into_result()?;
230
+ Ok(compressed_data)
231
+ }
232
+ }
233
+
234
+ fn labels_for_profile(config: &PyroscopeConfig, profile_type: String) -> Vec<LabelPair> {
235
+ let mut labels: Vec<LabelPair> = Vec::with_capacity(6 + config.tags.len());
236
+ labels.push(LabelPair {
237
+ name: LABEL_PROFILE_NAME.to_string(),
238
+ value: profile_type,
239
+ });
240
+ for (k, v) in &config.tags {
241
+ if k == LABEL_PROFILE_NAME {
242
+ continue;
243
+ }
244
+ labels.push(LabelPair {
245
+ name: k.clone(),
246
+ value: v.clone(),
247
+ })
248
+ }
249
+ push_label_if_absent(&mut labels, LABEL_SERVICE_NAME, &config.application_name);
250
+ push_label_if_absent(&mut labels, LABEL_SCOPE_NAME, SCOPE_NAME);
251
+ push_label_if_absent(&mut labels, LABEL_SCOPE_VERSION, &config.spy_version);
252
+ push_label_if_absent(
253
+ &mut labels,
254
+ LABEL_PROCESS_RUNTIME_NAME,
255
+ &config.runtime_name,
256
+ );
257
+ push_label_if_absent(
258
+ &mut labels,
259
+ LABEL_PROCESS_RUNTIME_VERSION,
260
+ &config.runtime_version,
261
+ );
262
+
263
+ labels
264
+ }
265
+
266
+ fn push_label_if_absent(labels: &mut Vec<LabelPair>, name: &str, value: &str) {
267
+ if labels.iter().any(|label| label.name == name) {
268
+ return;
269
+ }
270
+
271
+ labels.push(LabelPair {
272
+ name: name.to_string(),
273
+ value: value.to_string(),
274
+ });
275
+ }
@@ -0,0 +1,268 @@
1
+ use super::TimerSignal;
2
+ use crate::{
3
+ utils::{check_err, get_time_range},
4
+ PyroscopeError, Result,
5
+ };
6
+
7
+ use std::sync::{
8
+ mpsc::{channel, 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)]
27
+ pub struct Timer {
28
+ /// A vector to store listeners
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 a dummy tx so the below thread does not terminate early
43
+ let (tx, _rx) = channel();
44
+ txs.lock()?.push(tx);
45
+
46
+ let timer_fd = Timer::set_timerfd(cycle)?;
47
+ let epoll_fd = Timer::create_epollfd(timer_fd)?;
48
+
49
+ let handle = Some({
50
+ let txs = txs.clone();
51
+ thread::spawn(move || {
52
+ loop {
53
+ // Exit thread if there are no listeners
54
+ if txs.lock()?.is_empty() {
55
+ // Close file descriptors
56
+ unsafe { libc::close(timer_fd) };
57
+ unsafe { libc::close(epoll_fd) };
58
+
59
+ log::info!(target: LOG_TAG, "Timer thread terminated");
60
+
61
+ return Ok::<_, PyroscopeError>(());
62
+ }
63
+
64
+ // Fire @ 10th sec
65
+ let res = Timer::epoll_wait(timer_fd, epoll_fd);
66
+ if matches!(&res, Err(PyroscopeError::Io(err)) if err.kind() == std::io::ErrorKind::Interrupted)
67
+ {
68
+ continue;
69
+ }
70
+ res?;
71
+
72
+ // Get the current time range
73
+ let from = TimerSignal::NextSnapshot(get_time_range(0)?.from);
74
+
75
+ log::trace!(target: LOG_TAG, "Timer fired @ {from}");
76
+
77
+ // Iterate through Senders
78
+ txs.lock()?.iter().for_each(|tx| {
79
+ // Send event to attached Sender
80
+ match tx.send(from) {
81
+ Ok(_) => {
82
+ log::trace!(target: LOG_TAG, "Sent event to listener @ {:?}", &tx)
83
+ }
84
+ Err(_e) => {} // There could be a less confusing message, or this
85
+ // refactored to avoid a first sender
86
+ //log::warn!(
87
+ //target: LOG_TAG,
88
+ //"Failed to send event to listener @ {:?} - {}",
89
+ //&tx,
90
+ //e
91
+ //),
92
+ }
93
+ });
94
+ }
95
+ })
96
+ });
97
+
98
+ Ok(Self { handle, txs })
99
+ }
100
+
101
+ /// create and set a timer file descriptor
102
+ fn set_timerfd(cycle: Duration) -> Result<libc::c_int> {
103
+ // Set the timer to use the system time.
104
+ let clockid: libc::clockid_t = libc::CLOCK_REALTIME;
105
+ // Non-blocking file descriptor
106
+ let clock_flags: libc::c_int = libc::TFD_NONBLOCK;
107
+
108
+ // Create timer fd
109
+ let tfd = timerfd_create(clockid, clock_flags)?;
110
+
111
+ // Get the next event time
112
+ let first_fire = get_time_range(0)?.until;
113
+
114
+ // new_value sets the Timer
115
+ let mut new_value = libc::itimerspec {
116
+ it_interval: libc::timespec {
117
+ tv_sec: cycle.as_secs() as libc::c_long,
118
+ tv_nsec: cycle.subsec_nanos() as libc::c_long,
119
+ },
120
+ it_value: libc::timespec {
121
+ tv_sec: first_fire as libc::c_long,
122
+ tv_nsec: 0,
123
+ },
124
+ };
125
+
126
+ // Empty itimerspec object
127
+ let mut old_value = libc::itimerspec {
128
+ it_interval: libc::timespec {
129
+ tv_sec: 0,
130
+ tv_nsec: 0,
131
+ },
132
+ it_value: libc::timespec {
133
+ tv_sec: 0,
134
+ tv_nsec: 0,
135
+ },
136
+ };
137
+
138
+ let set_flags = libc::TFD_TIMER_ABSTIME;
139
+
140
+ // Set the timer
141
+ timerfd_settime(tfd, set_flags, &mut new_value, &mut old_value)?;
142
+
143
+ // Return file descriptor
144
+ Ok(tfd)
145
+ }
146
+
147
+ /// Create a new epoll file descriptor and add the timer to its interests
148
+ fn create_epollfd(timer_fd: libc::c_int) -> Result<libc::c_int> {
149
+ // create a new epoll fd
150
+ let epoll_fd = epoll_create1(0)?;
151
+
152
+ // event to pull
153
+ let mut event = libc::epoll_event {
154
+ events: libc::EPOLLIN as u32,
155
+ u64: 1,
156
+ };
157
+
158
+ let epoll_flags = libc::EPOLL_CTL_ADD;
159
+
160
+ // add event to the epoll
161
+ epoll_ctl(epoll_fd, epoll_flags, timer_fd, &mut event)?;
162
+
163
+ // return epoll fd
164
+ Ok(epoll_fd)
165
+ }
166
+
167
+ /// Wait for an event on the epoll file descriptor
168
+ fn epoll_wait(timer_fd: libc::c_int, epoll_fd: libc::c_int) -> Result<()> {
169
+ // vector to store events
170
+ let mut events = Vec::with_capacity(1);
171
+
172
+ // wait for the timer to fire an event. This is function will block.
173
+ unsafe {
174
+ epoll_wait(epoll_fd, events.as_mut_ptr(), 1, -1)?;
175
+ }
176
+
177
+ // read the value from the timerfd. This is required to re-arm the timer.
178
+ let mut buffer: u64 = 0;
179
+ let bufptr: *mut _ = &mut buffer;
180
+ unsafe {
181
+ read(timer_fd, bufptr as *mut libc::c_void, 8)?;
182
+ }
183
+
184
+ Ok(())
185
+ }
186
+
187
+ /// Attach an mpsc::Sender to Timer
188
+ ///
189
+ /// Timer will dispatch an event with the timestamp of the current instant,
190
+ /// every 10th second to all attached senders
191
+ pub fn attach_listener(&mut self, tx: Sender<TimerSignal>) -> Result<()> {
192
+ // Push Sender to a Vector of Sender(s)
193
+ let txs = Arc::clone(&self.txs);
194
+ txs.lock()?.push(tx);
195
+
196
+ Ok(())
197
+ }
198
+
199
+ /// Clear the listeners (txs) from Timer. This will shutdown the Timer thread
200
+ pub fn drop_listeners(&mut self) -> Result<()> {
201
+ let txs = Arc::clone(&self.txs);
202
+ txs.lock()?.clear();
203
+
204
+ Ok(())
205
+ }
206
+ }
207
+
208
+ // Wrapper for libc functions.
209
+ //
210
+ // Error wrapper for some libc functions used by the library. This only does
211
+ // Error (-1 return) wrapping. Alternatively, the nix crate could be used
212
+ // instead of expanding this wrappers (if more functions and types are used
213
+ // from libc)
214
+
215
+ // libc::timerfd wrapper
216
+ pub fn timerfd_create(clockid: libc::clockid_t, clock_flags: libc::c_int) -> Result<i32> {
217
+ check_err(unsafe { libc::timerfd_create(clockid, clock_flags) })
218
+ }
219
+
220
+ /// libc::timerfd_settime wrapper
221
+ pub fn timerfd_settime(
222
+ timer_fd: i32,
223
+ set_flags: libc::c_int,
224
+ new_value: &mut libc::itimerspec,
225
+ old_value: &mut libc::itimerspec,
226
+ ) -> Result<()> {
227
+ check_err(unsafe { libc::timerfd_settime(timer_fd, set_flags, new_value, old_value) })?;
228
+ Ok(())
229
+ }
230
+
231
+ /// libc::epoll_create1 wrapper
232
+ pub fn epoll_create1(epoll_flags: libc::c_int) -> Result<i32> {
233
+ check_err(unsafe { libc::epoll_create1(epoll_flags) })
234
+ }
235
+
236
+ /// libc::epoll_ctl wrapper
237
+ pub fn epoll_ctl(
238
+ epoll_fd: i32,
239
+ epoll_flags: libc::c_int,
240
+ timer_fd: i32,
241
+ event: &mut libc::epoll_event,
242
+ ) -> Result<()> {
243
+ check_err(unsafe { libc::epoll_ctl(epoll_fd, epoll_flags, timer_fd, event) })?;
244
+ Ok(())
245
+ }
246
+
247
+ /// libc::epoll_wait wrapper
248
+ ///
249
+ /// # Safety
250
+ /// This function is a wrapper for libc::epoll_wait.
251
+ pub unsafe fn epoll_wait(
252
+ epoll_fd: i32,
253
+ events: *mut libc::epoll_event,
254
+ maxevents: libc::c_int,
255
+ timeout: libc::c_int,
256
+ ) -> Result<()> {
257
+ check_err(libc::epoll_wait(epoll_fd, events, maxevents, timeout))?;
258
+ Ok(())
259
+ }
260
+
261
+ /// libc::read wrapper
262
+ ///
263
+ /// # Safety
264
+ /// This function is a wrapper for libc::read.
265
+ pub unsafe fn read(timer_fd: i32, bufptr: *mut libc::c_void, count: libc::size_t) -> Result<()> {
266
+ check_err(libc::read(timer_fd, bufptr, count))?;
267
+ Ok(())
268
+ }