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.
@@ -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
+ }