pyroscope 1.0.9-aarch64-linux → 1.1.0-aarch64-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 +1 -1
- data/ext/rbspy/Cargo.lock +201 -292
- data/ext/rbspy/Cargo.toml +10 -3
- data/ext/rbspy/include/rbspy.h +2 -0
- data/ext/rbspy/src/backend/backend.rs +134 -0
- data/ext/rbspy/src/backend/mod.rs +8 -0
- data/ext/rbspy/src/backend/ruleset.rs +82 -0
- data/ext/rbspy/src/backend/tests.rs +378 -0
- data/ext/rbspy/src/backend/types.rs +303 -0
- data/ext/rbspy/src/encode/gen/google.rs +233 -0
- data/ext/rbspy/src/encode/gen/mod.rs +3 -0
- data/ext/rbspy/src/encode/gen/push.rs +31 -0
- data/ext/rbspy/src/encode/gen/types.rs +11 -0
- data/ext/rbspy/src/encode/mod.rs +2 -0
- data/ext/rbspy/src/encode/pprof.rs +159 -0
- data/ext/rbspy/src/error.rs +67 -0
- data/ext/rbspy/src/ffikit.rs +76 -0
- data/ext/rbspy/src/lib.rs +41 -15
- data/ext/rbspy/src/pyroscope.rs +726 -0
- data/ext/rbspy/src/{backend.rs → rbspy_backend.rs} +3 -3
- data/ext/rbspy/src/session.rs +275 -0
- data/ext/rbspy/src/timer/epoll.rs +268 -0
- data/ext/rbspy/src/timer/kqueue.rs +191 -0
- data/ext/rbspy/src/timer/mod.rs +38 -0
- data/ext/rbspy/src/timer/sleep.rs +114 -0
- data/ext/rbspy/src/utils.rs +141 -0
- data/lib/pyroscope/version.rb +1 -1
- data/lib/pyroscope.rb +11 -1
- data/lib/rbspy/rbspy.so +0 -0
- data/pyroscope.gemspec +1 -3
- metadata +22 -2
|
@@ -0,0 +1,726 @@
|
|
|
1
|
+
use std::{
|
|
2
|
+
collections::HashMap,
|
|
3
|
+
marker::PhantomData,
|
|
4
|
+
sync::{
|
|
5
|
+
mpsc::{self, Sender},
|
|
6
|
+
Arc, Condvar, Mutex,
|
|
7
|
+
},
|
|
8
|
+
thread::JoinHandle,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
use crate::{
|
|
12
|
+
backend::{BackendReady, BackendUninitialized, Report, Tag},
|
|
13
|
+
error::Result,
|
|
14
|
+
session::{Session, SessionManager, SessionSignal},
|
|
15
|
+
timer::{Timer, TimerSignal},
|
|
16
|
+
utils::get_time_range,
|
|
17
|
+
PyroscopeError,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
use crate::backend::{BackendImpl, ThreadTag};
|
|
21
|
+
|
|
22
|
+
const LOG_TAG: &str = "Pyroscope::Agent";
|
|
23
|
+
const PPROFRS_SPY_NAME: &str = "pyroscope-rs";
|
|
24
|
+
const PPROFRS_SPY_VERSION: &str = env!("CARGO_PKG_VERSION");
|
|
25
|
+
|
|
26
|
+
/// Pyroscope Agent Configuration. This is the configuration that is passed to the agent.
|
|
27
|
+
///
|
|
28
|
+
/// # Example
|
|
29
|
+
/// ```
|
|
30
|
+
/// use pyroscope::pyroscope::PyroscopeConfig;
|
|
31
|
+
/// let config = PyroscopeConfig::new("http://localhost:8080", "my-app", 100, "pyspy", "0.8.16");
|
|
32
|
+
/// ```
|
|
33
|
+
#[derive(Clone, Debug)]
|
|
34
|
+
pub struct PyroscopeConfig {
|
|
35
|
+
/// Pyroscope Server Address
|
|
36
|
+
pub url: String,
|
|
37
|
+
/// Application Name
|
|
38
|
+
pub application_name: String,
|
|
39
|
+
/// Tags
|
|
40
|
+
pub tags: HashMap<String, String>,
|
|
41
|
+
/// Sample Rate
|
|
42
|
+
pub sample_rate: u32,
|
|
43
|
+
/// Spy Name
|
|
44
|
+
pub spy_name: String,
|
|
45
|
+
/// Spy Version
|
|
46
|
+
pub spy_version: String,
|
|
47
|
+
/// Runtime Name
|
|
48
|
+
pub runtime_name: String,
|
|
49
|
+
/// Runtime Version
|
|
50
|
+
pub runtime_version: String,
|
|
51
|
+
pub basic_auth: Option<BasicAuth>,
|
|
52
|
+
/// Function to apply
|
|
53
|
+
pub func: Option<fn(Report) -> Report>,
|
|
54
|
+
pub tenant_id: Option<String>,
|
|
55
|
+
pub http_headers: HashMap<String, String>,
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
#[derive(Clone, Debug)]
|
|
59
|
+
pub struct BasicAuth {
|
|
60
|
+
pub username: String,
|
|
61
|
+
pub password: String,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
impl Default for PyroscopeConfig {
|
|
65
|
+
fn default() -> Self {
|
|
66
|
+
Self {
|
|
67
|
+
url: "http://localhost:4040".to_string(),
|
|
68
|
+
application_name: "undefined".to_string(),
|
|
69
|
+
tags: HashMap::new(),
|
|
70
|
+
sample_rate: 100u32,
|
|
71
|
+
spy_name: PPROFRS_SPY_NAME.to_string(),
|
|
72
|
+
spy_version: PPROFRS_SPY_VERSION.to_string(),
|
|
73
|
+
runtime_name: String::new(),
|
|
74
|
+
runtime_version: String::new(),
|
|
75
|
+
basic_auth: None,
|
|
76
|
+
func: None,
|
|
77
|
+
tenant_id: None,
|
|
78
|
+
http_headers: HashMap::new(),
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
impl PyroscopeConfig {
|
|
84
|
+
/// Create a new PyroscopeConfig object.
|
|
85
|
+
///
|
|
86
|
+
/// # Example
|
|
87
|
+
/// ```
|
|
88
|
+
/// use pyroscope::pyroscope::PyroscopeConfig;
|
|
89
|
+
/// let config = PyroscopeConfig::new("http://localhost:8080", "my-app", 100, "pyspy", "0.8.16");
|
|
90
|
+
/// ```
|
|
91
|
+
pub fn new(
|
|
92
|
+
url: impl AsRef<str>,
|
|
93
|
+
application_name: impl AsRef<str>,
|
|
94
|
+
sample_rate: u32,
|
|
95
|
+
spy_name: impl AsRef<str>,
|
|
96
|
+
spy_version: impl AsRef<str>,
|
|
97
|
+
) -> Self {
|
|
98
|
+
Self {
|
|
99
|
+
url: url.as_ref().to_owned(),
|
|
100
|
+
application_name: application_name.as_ref().to_owned(),
|
|
101
|
+
tags: HashMap::new(),
|
|
102
|
+
sample_rate,
|
|
103
|
+
spy_name: spy_name.as_ref().to_owned(),
|
|
104
|
+
spy_version: spy_version.as_ref().to_owned(),
|
|
105
|
+
runtime_name: String::new(),
|
|
106
|
+
runtime_version: String::new(),
|
|
107
|
+
basic_auth: None,
|
|
108
|
+
func: None,
|
|
109
|
+
tenant_id: None,
|
|
110
|
+
http_headers: HashMap::new(),
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Set the Pyroscope Server URL
|
|
115
|
+
pub fn url(self, url: impl AsRef<str>) -> Self {
|
|
116
|
+
Self {
|
|
117
|
+
url: url.as_ref().to_owned(),
|
|
118
|
+
..self
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
pub fn basic_auth(self, username: String, password: String) -> Self {
|
|
123
|
+
Self {
|
|
124
|
+
basic_auth: Some(BasicAuth { username, password }),
|
|
125
|
+
..self
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/// Set the Function.
|
|
130
|
+
pub fn func(self, func: fn(Report) -> Report) -> Self {
|
|
131
|
+
Self {
|
|
132
|
+
func: Some(func),
|
|
133
|
+
..self
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/// Set the tags.
|
|
138
|
+
///
|
|
139
|
+
/// # Example
|
|
140
|
+
/// ```
|
|
141
|
+
/// use pyroscope::pyroscope::PyroscopeConfig;
|
|
142
|
+
/// let config = PyroscopeConfig::new("http://localhost:8080", "my-app", 100, "pyroscope-rs", "0.1.0")
|
|
143
|
+
/// .tags(vec![("env", "dev")]);
|
|
144
|
+
/// ```
|
|
145
|
+
pub fn tags(self, tags: Vec<(&str, &str)>) -> Self {
|
|
146
|
+
// Convert &[(&str, &str)] to HashMap(String, String)
|
|
147
|
+
let tags_hashmap: HashMap<String, String> = tags
|
|
148
|
+
.to_owned()
|
|
149
|
+
.iter()
|
|
150
|
+
.cloned()
|
|
151
|
+
.map(|(a, b)| (a.to_owned(), b.to_owned()))
|
|
152
|
+
.collect();
|
|
153
|
+
|
|
154
|
+
Self {
|
|
155
|
+
tags: tags_hashmap,
|
|
156
|
+
..self
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
pub fn runtime(self, runtime_name: String, runtime_version: String) -> Self {
|
|
161
|
+
Self {
|
|
162
|
+
runtime_name,
|
|
163
|
+
runtime_version,
|
|
164
|
+
..self
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
pub fn tenant_id(self, tenant_id: String) -> Self {
|
|
169
|
+
Self {
|
|
170
|
+
tenant_id: Some(tenant_id),
|
|
171
|
+
..self
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
pub fn http_headers(self, http_headers: HashMap<String, String>) -> Self {
|
|
176
|
+
Self {
|
|
177
|
+
http_headers,
|
|
178
|
+
..self
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/// PyroscopeAgent Builder
|
|
184
|
+
///
|
|
185
|
+
/// # Example
|
|
186
|
+
/// ```no_run
|
|
187
|
+
/// use pyroscope::pyroscope::PyroscopeAgentBuilder;
|
|
188
|
+
/// use pyroscope::backend::{pprof_backend, PprofConfig, BackendConfig};
|
|
189
|
+
///
|
|
190
|
+
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
191
|
+
/// let agent = PyroscopeAgentBuilder::new(
|
|
192
|
+
/// "http://localhost:8080", "my-app", 100, "pyroscope-rs", "0.1.0",
|
|
193
|
+
/// pprof_backend(PprofConfig::default(), BackendConfig::default()),
|
|
194
|
+
/// )
|
|
195
|
+
/// .build()?;
|
|
196
|
+
/// # Ok(())
|
|
197
|
+
/// # }
|
|
198
|
+
/// ```
|
|
199
|
+
pub struct PyroscopeAgentBuilder {
|
|
200
|
+
/// Profiler backend
|
|
201
|
+
backend: BackendImpl<BackendUninitialized>,
|
|
202
|
+
/// Configuration Object
|
|
203
|
+
config: PyroscopeConfig,
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
impl PyroscopeAgentBuilder {
|
|
207
|
+
/// Create a new PyroscopeAgentBuilder object.
|
|
208
|
+
///
|
|
209
|
+
/// # Example
|
|
210
|
+
/// ```no_run
|
|
211
|
+
/// use pyroscope::pyroscope::PyroscopeAgentBuilder;
|
|
212
|
+
/// use pyroscope::backend::{pprof_backend, PprofConfig, BackendConfig};
|
|
213
|
+
///
|
|
214
|
+
/// let builder = PyroscopeAgentBuilder::new(
|
|
215
|
+
/// "http://localhost:8080", "my-app", 100, "pyroscope-rs", "0.1.0",
|
|
216
|
+
/// pprof_backend(PprofConfig::default(), BackendConfig::default()),
|
|
217
|
+
/// );
|
|
218
|
+
/// ```
|
|
219
|
+
pub fn new(
|
|
220
|
+
url: impl AsRef<str>,
|
|
221
|
+
application_name: impl AsRef<str>,
|
|
222
|
+
sample_rate: u32,
|
|
223
|
+
spy_name: impl AsRef<str>,
|
|
224
|
+
spy_version: impl AsRef<str>,
|
|
225
|
+
backend: BackendImpl<BackendUninitialized>,
|
|
226
|
+
) -> Self {
|
|
227
|
+
Self {
|
|
228
|
+
backend,
|
|
229
|
+
config: PyroscopeConfig::new(url, application_name, sample_rate, spy_name, spy_version),
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/// Override the Pyroscope Server URL.
|
|
234
|
+
///
|
|
235
|
+
/// # Example
|
|
236
|
+
/// ```no_run
|
|
237
|
+
/// use pyroscope::pyroscope::PyroscopeAgentBuilder;
|
|
238
|
+
/// use pyroscope::backend::{pprof_backend, PprofConfig, BackendConfig};
|
|
239
|
+
///
|
|
240
|
+
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
241
|
+
/// let agent = PyroscopeAgentBuilder::new(
|
|
242
|
+
/// "http://localhost:4040", "my-app", 100, "pyroscope-rs", "0.1.0",
|
|
243
|
+
/// pprof_backend(PprofConfig::default(), BackendConfig::default()),
|
|
244
|
+
/// )
|
|
245
|
+
/// .url("http://localhost:8080")
|
|
246
|
+
/// .build()?;
|
|
247
|
+
/// # Ok(())
|
|
248
|
+
/// # }
|
|
249
|
+
/// ```
|
|
250
|
+
pub fn url(self, url: impl AsRef<str>) -> Self {
|
|
251
|
+
Self {
|
|
252
|
+
config: self.config.url(url),
|
|
253
|
+
..self
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
pub fn basic_auth(self, username: impl AsRef<str>, password: impl AsRef<str>) -> Self {
|
|
258
|
+
Self {
|
|
259
|
+
config: self
|
|
260
|
+
.config
|
|
261
|
+
.basic_auth(username.as_ref().to_owned(), password.as_ref().to_owned()),
|
|
262
|
+
..self
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/// Set the Function.
|
|
267
|
+
/// This is optional. If not set, the agent will not apply any function.
|
|
268
|
+
/// # Example
|
|
269
|
+
/// ```no_run
|
|
270
|
+
/// use pyroscope::pyroscope::PyroscopeAgentBuilder;
|
|
271
|
+
/// use pyroscope::backend::{pprof_backend, PprofConfig, BackendConfig};
|
|
272
|
+
///
|
|
273
|
+
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
274
|
+
/// let agent = PyroscopeAgentBuilder::new(
|
|
275
|
+
/// "http://localhost:8080", "my-app", 100, "pyroscope-rs", "0.1.0",
|
|
276
|
+
/// pprof_backend(PprofConfig::default(), BackendConfig::default()),
|
|
277
|
+
/// )
|
|
278
|
+
/// .func(|report| report)
|
|
279
|
+
/// .build()?;
|
|
280
|
+
/// # Ok(())
|
|
281
|
+
/// # }
|
|
282
|
+
/// ```
|
|
283
|
+
pub fn func(self, func: fn(Report) -> Report) -> Self {
|
|
284
|
+
Self {
|
|
285
|
+
config: self.config.func(func),
|
|
286
|
+
..self
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/// Set tags. Default is empty.
|
|
291
|
+
///
|
|
292
|
+
/// # Example
|
|
293
|
+
/// ```no_run
|
|
294
|
+
/// use pyroscope::pyroscope::PyroscopeAgentBuilder;
|
|
295
|
+
/// use pyroscope::backend::{pprof_backend, PprofConfig, BackendConfig};
|
|
296
|
+
///
|
|
297
|
+
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
298
|
+
/// let agent = PyroscopeAgentBuilder::new(
|
|
299
|
+
/// "http://localhost:8080", "my-app", 100, "pyroscope-rs", "0.1.0",
|
|
300
|
+
/// pprof_backend(PprofConfig::default(), BackendConfig::default()),
|
|
301
|
+
/// )
|
|
302
|
+
/// .tags(vec![("env", "dev")])
|
|
303
|
+
/// .build()?;
|
|
304
|
+
/// # Ok(())
|
|
305
|
+
/// # }
|
|
306
|
+
/// ```
|
|
307
|
+
pub fn tags(self, tags: Vec<(&str, &str)>) -> Self {
|
|
308
|
+
Self {
|
|
309
|
+
config: self.config.tags(tags),
|
|
310
|
+
..self
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
pub fn runtime(self, runtime_name: String, runtime_version: String) -> Self {
|
|
315
|
+
Self {
|
|
316
|
+
config: self.config.runtime(runtime_name, runtime_version),
|
|
317
|
+
..self
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
pub fn tenant_id(self, tenant_id: String) -> Self {
|
|
322
|
+
Self {
|
|
323
|
+
config: self.config.tenant_id(tenant_id),
|
|
324
|
+
..self
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
pub fn http_headers(self, http_headers: HashMap<String, String>) -> Self {
|
|
329
|
+
Self {
|
|
330
|
+
config: self.config.http_headers(http_headers),
|
|
331
|
+
..self
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/// Initialize the backend, timer and return a PyroscopeAgent with Ready
|
|
336
|
+
/// state. While you can call this method, you should call it through the
|
|
337
|
+
/// `PyroscopeAgent.build()` method.
|
|
338
|
+
pub fn build(self) -> Result<PyroscopeAgent<PyroscopeAgentReady>> {
|
|
339
|
+
let config = self.config;
|
|
340
|
+
|
|
341
|
+
// Set Global Tags
|
|
342
|
+
// for (key, value) in config.tags.iter() {
|
|
343
|
+
// todo!("implement")
|
|
344
|
+
// }
|
|
345
|
+
|
|
346
|
+
// Initialize the Backend
|
|
347
|
+
let backend_ready = self.backend.initialize()?;
|
|
348
|
+
log::trace!(target: LOG_TAG, "Backend initialized");
|
|
349
|
+
|
|
350
|
+
// Start the Timer
|
|
351
|
+
let timer = Timer::initialize(std::time::Duration::from_secs(10))?;
|
|
352
|
+
log::trace!(target: LOG_TAG, "Timer initialized");
|
|
353
|
+
|
|
354
|
+
// Start the SessionManager
|
|
355
|
+
let session_manager = SessionManager::new()?;
|
|
356
|
+
log::trace!(target: LOG_TAG, "SessionManager initialized");
|
|
357
|
+
|
|
358
|
+
// Return PyroscopeAgent
|
|
359
|
+
Ok(PyroscopeAgent {
|
|
360
|
+
backend: backend_ready,
|
|
361
|
+
config,
|
|
362
|
+
timer,
|
|
363
|
+
session_manager,
|
|
364
|
+
tx: None,
|
|
365
|
+
handle: None,
|
|
366
|
+
running: Arc::new((
|
|
367
|
+
#[allow(clippy::mutex_atomic)]
|
|
368
|
+
Mutex::new(false),
|
|
369
|
+
Condvar::new(),
|
|
370
|
+
)),
|
|
371
|
+
_state: PhantomData,
|
|
372
|
+
})
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/// This trait is used to encode the state of the agent.
|
|
377
|
+
pub trait PyroscopeAgentState {}
|
|
378
|
+
|
|
379
|
+
/// Marker struct for an Uninitialized state.
|
|
380
|
+
#[derive(Debug)]
|
|
381
|
+
pub struct PyroscopeAgentBare;
|
|
382
|
+
|
|
383
|
+
/// Marker struct for a Ready state.
|
|
384
|
+
#[derive(Debug)]
|
|
385
|
+
pub struct PyroscopeAgentReady;
|
|
386
|
+
|
|
387
|
+
/// Marker struct for a Running state.
|
|
388
|
+
#[derive(Debug)]
|
|
389
|
+
pub struct PyroscopeAgentRunning;
|
|
390
|
+
|
|
391
|
+
impl PyroscopeAgentState for PyroscopeAgentBare {}
|
|
392
|
+
|
|
393
|
+
impl PyroscopeAgentState for PyroscopeAgentReady {}
|
|
394
|
+
|
|
395
|
+
impl PyroscopeAgentState for PyroscopeAgentRunning {}
|
|
396
|
+
|
|
397
|
+
/// PyroscopeAgent is the main object of the library. It is used to start and stop the profiler, schedule the timer, and send the profiler data to the server.
|
|
398
|
+
pub struct PyroscopeAgent<S: PyroscopeAgentState> {
|
|
399
|
+
/// Instance of the Timer
|
|
400
|
+
timer: Timer,
|
|
401
|
+
/// Instance of the SessionManager
|
|
402
|
+
session_manager: SessionManager,
|
|
403
|
+
/// Channel sender for the timer thread
|
|
404
|
+
tx: Option<Sender<TimerSignal>>,
|
|
405
|
+
/// Handle to the thread that runs the Pyroscope Agent
|
|
406
|
+
handle: Option<JoinHandle<Result<()>>>,
|
|
407
|
+
/// A structure to signal thread termination
|
|
408
|
+
running: Arc<(Mutex<bool>, Condvar)>,
|
|
409
|
+
/// Profiler backend
|
|
410
|
+
pub backend: BackendImpl<BackendReady>,
|
|
411
|
+
/// Configuration Object
|
|
412
|
+
pub config: PyroscopeConfig,
|
|
413
|
+
/// PyroscopeAgent State
|
|
414
|
+
_state: PhantomData<S>,
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
impl<S: PyroscopeAgentState> PyroscopeAgent<S> {
|
|
418
|
+
/// Transition the PyroscopeAgent to a new state.
|
|
419
|
+
fn transition<D: PyroscopeAgentState>(self) -> PyroscopeAgent<D> {
|
|
420
|
+
PyroscopeAgent {
|
|
421
|
+
timer: self.timer,
|
|
422
|
+
session_manager: self.session_manager,
|
|
423
|
+
tx: self.tx,
|
|
424
|
+
handle: self.handle,
|
|
425
|
+
running: self.running,
|
|
426
|
+
backend: self.backend,
|
|
427
|
+
config: self.config,
|
|
428
|
+
_state: PhantomData,
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
impl<S: PyroscopeAgentState> PyroscopeAgent<S> {
|
|
434
|
+
/// Properly shutdown the agent.
|
|
435
|
+
pub fn shutdown(mut self) {
|
|
436
|
+
log::debug!(target: LOG_TAG, "PyroscopeAgent::drop()");
|
|
437
|
+
|
|
438
|
+
// Shutdown Backend
|
|
439
|
+
match self.backend.shutdown() {
|
|
440
|
+
Ok(_) => log::debug!(target: LOG_TAG, "Backend shutdown"),
|
|
441
|
+
Err(e) => log::error!(target: LOG_TAG, "Backend shutdown error: {e}"),
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Drop Timer listeners
|
|
445
|
+
match self.timer.drop_listeners() {
|
|
446
|
+
Ok(_) => log::trace!(target: LOG_TAG, "Dropped timer listeners"),
|
|
447
|
+
Err(_) => log::error!(target: LOG_TAG, "Error Dropping timer listeners"),
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Wait for the Timer thread to finish
|
|
451
|
+
if let Some(handle) = self.timer.handle.take() {
|
|
452
|
+
match handle.join() {
|
|
453
|
+
Ok(_) => log::trace!(target: LOG_TAG, "Dropped timer thread"),
|
|
454
|
+
Err(_) => log::error!(target: LOG_TAG, "Error Dropping timer thread"),
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Stop the SessionManager
|
|
459
|
+
match self.session_manager.push(SessionSignal::Kill) {
|
|
460
|
+
Ok(_) => log::trace!(target: LOG_TAG, "Sent kill signal to SessionManager"),
|
|
461
|
+
Err(_) => log::error!(
|
|
462
|
+
target: LOG_TAG,
|
|
463
|
+
"Error sending kill signal to SessionManager"
|
|
464
|
+
),
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
if let Some(handle) = self.session_manager.handle.take() {
|
|
468
|
+
match handle.join() {
|
|
469
|
+
Ok(_) => log::trace!(target: LOG_TAG, "Dropped SessionManager thread"),
|
|
470
|
+
Err(_) => log::error!(target: LOG_TAG, "Error Dropping SessionManager thread"),
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Wait for main thread to finish
|
|
475
|
+
if let Some(handle) = self.handle.take() {
|
|
476
|
+
match handle.join() {
|
|
477
|
+
Ok(_) => log::trace!(target: LOG_TAG, "Dropped main thread"),
|
|
478
|
+
Err(_) => log::error!(target: LOG_TAG, "Error Dropping main thread"),
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
log::debug!(target: LOG_TAG, "Agent Shutdown");
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
impl PyroscopeAgent<PyroscopeAgentReady> {
|
|
487
|
+
/// Start profiling and sending data. The agent will keep running until stopped. The agent will send data to the server every 10s seconds.
|
|
488
|
+
///
|
|
489
|
+
/// # Example
|
|
490
|
+
/// ```no_run
|
|
491
|
+
/// # use pyroscope::pyroscope::PyroscopeAgentBuilder;
|
|
492
|
+
/// # use pyroscope::backend::{pprof_backend, PprofConfig, BackendConfig};
|
|
493
|
+
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
494
|
+
/// # let agent = PyroscopeAgentBuilder::new("http://localhost:4040", "my-app", 100, "pyroscope-rs", "0.1.0", pprof_backend(PprofConfig::default(), BackendConfig::default())).build()?;
|
|
495
|
+
/// let agent_running = agent.start()?;
|
|
496
|
+
/// # Ok(())
|
|
497
|
+
/// # }
|
|
498
|
+
/// ```
|
|
499
|
+
pub fn start(mut self) -> Result<PyroscopeAgent<PyroscopeAgentRunning>> {
|
|
500
|
+
log::debug!(target: LOG_TAG, "Starting");
|
|
501
|
+
|
|
502
|
+
// Create a clone of Backend
|
|
503
|
+
let backend = Arc::clone(&self.backend.backend);
|
|
504
|
+
// Call start()
|
|
505
|
+
|
|
506
|
+
// set running to true
|
|
507
|
+
let pair = Arc::clone(&self.running);
|
|
508
|
+
let (lock, _cvar) = &*pair;
|
|
509
|
+
let mut running = lock.lock()?;
|
|
510
|
+
*running = true;
|
|
511
|
+
drop(running);
|
|
512
|
+
|
|
513
|
+
// Create a channel to listen for timer signals
|
|
514
|
+
let (tx, rx) = mpsc::channel();
|
|
515
|
+
self.timer.attach_listener(tx.clone())?;
|
|
516
|
+
self.tx = Some(tx);
|
|
517
|
+
|
|
518
|
+
let config = self.config.clone();
|
|
519
|
+
|
|
520
|
+
// Clone SessionManager Sender
|
|
521
|
+
let stx = self.session_manager.tx.clone();
|
|
522
|
+
|
|
523
|
+
self.handle = Some(std::thread::spawn(move || {
|
|
524
|
+
log::trace!(target: LOG_TAG, "Main Thread started");
|
|
525
|
+
|
|
526
|
+
while let Ok(signal) = rx.recv() {
|
|
527
|
+
match signal {
|
|
528
|
+
TimerSignal::NextSnapshot(until) => {
|
|
529
|
+
log::trace!(target: LOG_TAG, "Sending session {until}");
|
|
530
|
+
|
|
531
|
+
// Generate report from backend
|
|
532
|
+
let report = backend
|
|
533
|
+
.lock()?
|
|
534
|
+
.as_mut()
|
|
535
|
+
.ok_or_else(|| {
|
|
536
|
+
PyroscopeError::AdHoc(
|
|
537
|
+
"PyroscopeAgent - Failed to unwrap backend".to_string(),
|
|
538
|
+
)
|
|
539
|
+
})?
|
|
540
|
+
.report()?;
|
|
541
|
+
|
|
542
|
+
// Send new Session to SessionManager
|
|
543
|
+
stx.send(SessionSignal::Session(Box::new(Session::new(
|
|
544
|
+
until,
|
|
545
|
+
config.clone(),
|
|
546
|
+
report,
|
|
547
|
+
)?)))?
|
|
548
|
+
}
|
|
549
|
+
TimerSignal::Terminate => {
|
|
550
|
+
log::trace!(target: LOG_TAG, "Session Killed");
|
|
551
|
+
|
|
552
|
+
// Notify the Stop function
|
|
553
|
+
let (lock, cvar) = &*pair;
|
|
554
|
+
let mut running = lock.lock()?;
|
|
555
|
+
*running = false;
|
|
556
|
+
cvar.notify_one();
|
|
557
|
+
|
|
558
|
+
// Kill the internal thread
|
|
559
|
+
return Ok(());
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
Ok(())
|
|
564
|
+
}));
|
|
565
|
+
|
|
566
|
+
Ok(self.transition())
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
impl PyroscopeAgent<PyroscopeAgentRunning> {
|
|
571
|
+
/// Stop the agent. The agent will stop profiling and send a last report to the server.
|
|
572
|
+
///
|
|
573
|
+
/// # Example
|
|
574
|
+
/// ```no_run
|
|
575
|
+
/// # use pyroscope::pyroscope::PyroscopeAgentBuilder;
|
|
576
|
+
/// # use pyroscope::backend::{pprof_backend, PprofConfig, BackendConfig};
|
|
577
|
+
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
578
|
+
/// # let agent = PyroscopeAgentBuilder::new("http://localhost:4040", "my-app", 100, "pyroscope-rs", "0.1.0", pprof_backend(PprofConfig::default(), BackendConfig::default())).build()?;
|
|
579
|
+
/// # let agent_running = agent.start()?;
|
|
580
|
+
/// let agent_ready = agent_running.stop()?;
|
|
581
|
+
/// # Ok(())
|
|
582
|
+
/// # }
|
|
583
|
+
/// ```
|
|
584
|
+
pub fn stop(mut self) -> Result<PyroscopeAgent<PyroscopeAgentReady>> {
|
|
585
|
+
log::debug!(target: LOG_TAG, "Stopping");
|
|
586
|
+
// get tx and send termination signal
|
|
587
|
+
if let Some(sender) = self.tx.take() {
|
|
588
|
+
// Send last session
|
|
589
|
+
sender.send(TimerSignal::NextSnapshot(get_time_range(0)?.until))?;
|
|
590
|
+
// Terminate PyroscopeAgent internal thread
|
|
591
|
+
sender.send(TimerSignal::Terminate)?;
|
|
592
|
+
} else {
|
|
593
|
+
log::error!("PyroscopeAgent - Missing sender")
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// Wait for the Thread to finish
|
|
597
|
+
let pair = Arc::clone(&self.running);
|
|
598
|
+
let (lock, cvar) = &*pair;
|
|
599
|
+
let _guard = cvar.wait_while(lock.lock()?, |running| *running)?;
|
|
600
|
+
|
|
601
|
+
Ok(self.transition())
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/// Return a tuple of functions to add and remove tags to the agent across
|
|
605
|
+
/// thread boundaries. This function can be called multiple times.
|
|
606
|
+
///
|
|
607
|
+
/// # Example
|
|
608
|
+
/// ```no_run
|
|
609
|
+
/// # use pyroscope::pyroscope::PyroscopeAgentBuilder;
|
|
610
|
+
/// # use pyroscope::backend::{pprof_backend, PprofConfig, BackendConfig};
|
|
611
|
+
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
612
|
+
/// # let agent = PyroscopeAgentBuilder::new("http://localhost:4040", "my-app", 100, "pyroscope-rs", "0.1.0", pprof_backend(PprofConfig::default(), BackendConfig::default())).build()?;
|
|
613
|
+
/// # let agent_running = agent.start()?;
|
|
614
|
+
/// let (add_tag, remove_tag) = agent_running.tag_wrapper();
|
|
615
|
+
/// # Ok(())
|
|
616
|
+
/// # }
|
|
617
|
+
/// ```
|
|
618
|
+
///
|
|
619
|
+
/// The functions can be later called from any thread.
|
|
620
|
+
///
|
|
621
|
+
/// # Example
|
|
622
|
+
/// ```ignore
|
|
623
|
+
/// add_tag("key".to_string(), "value".to_string());
|
|
624
|
+
/// // some computation
|
|
625
|
+
/// remove_tag("key".to_string(), "value".to_string());
|
|
626
|
+
/// ```
|
|
627
|
+
#[allow(clippy::type_complexity)]
|
|
628
|
+
pub fn tag_wrapper(
|
|
629
|
+
&self,
|
|
630
|
+
) -> (
|
|
631
|
+
impl Fn(String, String) -> Result<()>,
|
|
632
|
+
impl Fn(String, String) -> Result<()>,
|
|
633
|
+
) {
|
|
634
|
+
let backend_add = self.backend.backend.clone();
|
|
635
|
+
let backend_remove = self.backend.backend.clone();
|
|
636
|
+
|
|
637
|
+
(
|
|
638
|
+
move |key, value| {
|
|
639
|
+
// https://github.com/tikv/pprof-rs/blob/01cff82dbe6fe110a707bf2b38d8ebb1d14a18f8/src/profiler.rs#L405
|
|
640
|
+
let thread_id = crate::utils::ThreadId::pthread_self();
|
|
641
|
+
let rule = ThreadTag::new(thread_id, Tag::new(key, value));
|
|
642
|
+
let backend = backend_add.lock()?;
|
|
643
|
+
backend
|
|
644
|
+
.as_ref()
|
|
645
|
+
.ok_or_else(|| {
|
|
646
|
+
PyroscopeError::AdHoc(
|
|
647
|
+
"PyroscopeAgent - Failed to unwrap backend".to_string(),
|
|
648
|
+
)
|
|
649
|
+
})?
|
|
650
|
+
.add_tag(rule)?;
|
|
651
|
+
|
|
652
|
+
Ok(())
|
|
653
|
+
},
|
|
654
|
+
move |key, value| {
|
|
655
|
+
// https://github.com/tikv/pprof-rs/blob/01cff82dbe6fe110a707bf2b38d8ebb1d14a18f8/src/profiler.rs#L405
|
|
656
|
+
let thread_id = crate::utils::ThreadId::pthread_self();
|
|
657
|
+
let rule = ThreadTag::new(thread_id, Tag::new(key, value));
|
|
658
|
+
let backend = backend_remove.lock()?;
|
|
659
|
+
backend
|
|
660
|
+
.as_ref()
|
|
661
|
+
.ok_or_else(|| {
|
|
662
|
+
PyroscopeError::AdHoc(
|
|
663
|
+
"PyroscopeAgent - Failed to unwrap backend".to_string(),
|
|
664
|
+
)
|
|
665
|
+
})?
|
|
666
|
+
.remove_tag(rule)?;
|
|
667
|
+
|
|
668
|
+
Ok(())
|
|
669
|
+
},
|
|
670
|
+
)
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/// Add a thread Tag rule to the backend Ruleset. For tagging, it's
|
|
674
|
+
/// recommended to use the `tag_wrapper` function.
|
|
675
|
+
pub fn add_thread_tag(&self, thread_id: crate::utils::ThreadId, tag: Tag) -> Result<()> {
|
|
676
|
+
let rule = ThreadTag::new(thread_id, tag);
|
|
677
|
+
self.backend.add_tag(rule)?;
|
|
678
|
+
|
|
679
|
+
Ok(())
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
/// Remove a thread Tag rule from the backend Ruleset. For tagging, it's
|
|
683
|
+
/// recommended to use the `tag_wrapper` function.
|
|
684
|
+
pub fn remove_thread_tag(&self, thread_id: crate::utils::ThreadId, tag: Tag) -> Result<()> {
|
|
685
|
+
let rule = ThreadTag::new(thread_id, tag);
|
|
686
|
+
self.backend.remove_tag(rule)?;
|
|
687
|
+
|
|
688
|
+
Ok(())
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
pub fn parse_http_headers_json(http_headers_json: String) -> Result<HashMap<String, String>> {
|
|
693
|
+
let mut http_headers = HashMap::new();
|
|
694
|
+
let parsed: serde_json::Value = serde_json::from_str(&http_headers_json)?;
|
|
695
|
+
let parsed = parsed
|
|
696
|
+
.as_object()
|
|
697
|
+
.ok_or_else(|| PyroscopeError::AdHoc(format!("expected object, got {parsed}")))?;
|
|
698
|
+
for (k, v) in parsed {
|
|
699
|
+
if let Some(value) = v.as_str() {
|
|
700
|
+
http_headers.insert(k.to_string(), value.to_string());
|
|
701
|
+
} else {
|
|
702
|
+
return Err(PyroscopeError::AdHoc(format!(
|
|
703
|
+
"invalid http header value, not a string: {v}"
|
|
704
|
+
)));
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
Ok(http_headers)
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
pub fn parse_vec_string_json(s: String) -> Result<Vec<String>> {
|
|
711
|
+
let parsed: serde_json::Value = serde_json::from_str(&s)?;
|
|
712
|
+
let parsed = parsed
|
|
713
|
+
.as_array()
|
|
714
|
+
.ok_or_else(|| PyroscopeError::AdHoc(format!("expected array, got {parsed}")))?;
|
|
715
|
+
let mut res = Vec::with_capacity(parsed.len());
|
|
716
|
+
for v in parsed {
|
|
717
|
+
if let Some(s) = v.as_str() {
|
|
718
|
+
res.push(s.to_string());
|
|
719
|
+
} else {
|
|
720
|
+
return Err(PyroscopeError::AdHoc(format!(
|
|
721
|
+
"invalid element value, not a string: {v}"
|
|
722
|
+
)));
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
Ok(res)
|
|
726
|
+
}
|