Skip to content

Commit db91182

Browse files
committed
imp(agent): enforce state transitions with the type system
1 parent 3a73d75 commit db91182

File tree

1 file changed

+47
-86
lines changed

1 file changed

+47
-86
lines changed

src/pyroscope.rs

Lines changed: 47 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::{
22
collections::HashMap,
3+
marker::PhantomData,
34
sync::{
45
mpsc::{self, Sender},
56
Arc, Condvar, Mutex,
@@ -152,7 +153,7 @@ impl PyroscopeAgentBuilder {
152153
}
153154

154155
/// Initialize the backend, timer and return a PyroscopeAgent object.
155-
pub fn build(self) -> Result<PyroscopeAgent> {
156+
pub fn build(self) -> Result<PyroscopeAgent<PyroscopeAgentReady>> {
156157
// Get the backend
157158
//let backend = Arc::clone(&self.backend);
158159

@@ -191,13 +192,22 @@ impl PyroscopeAgentBuilder {
191192
tx: None,
192193
handle: None,
193194
running: Arc::new((Mutex::new(false), Condvar::new())),
195+
_state: PhantomData,
194196
})
195197
}
196198
}
197199

200+
pub trait PyroscopeAgentState {}
201+
pub struct PyroscopeAgentBare;
202+
pub struct PyroscopeAgentReady;
203+
pub struct PyroscopeAgentRunning;
204+
impl PyroscopeAgentState for PyroscopeAgentBare {}
205+
impl PyroscopeAgentState for PyroscopeAgentReady {}
206+
impl PyroscopeAgentState for PyroscopeAgentRunning {}
207+
198208
/// 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.
199209
#[derive(Debug)]
200-
pub struct PyroscopeAgent {
210+
pub struct PyroscopeAgent<S: PyroscopeAgentState> {
201211
timer: Timer,
202212
session_manager: SessionManager,
203213
tx: Option<Sender<TimerSignal>>,
@@ -208,12 +218,13 @@ pub struct PyroscopeAgent {
208218
pub backend: BackendImpl<BackendReady>,
209219
/// Configuration Object
210220
pub config: PyroscopeConfig,
221+
_state: PhantomData<S>,
211222
}
212223

213224
/// Gracefully stop the profiler.
214-
impl Drop for PyroscopeAgent {
225+
impl<S: PyroscopeAgentState> PyroscopeAgent<S> {
215226
/// Properly shutdown the agent.
216-
fn drop(&mut self) {
227+
pub fn shutdown(mut self) {
217228
log::debug!(target: LOG_TAG, "PyroscopeAgent::drop()");
218229

219230
// Drop Timer listeners
@@ -258,7 +269,7 @@ impl Drop for PyroscopeAgent {
258269
}
259270
}
260271

261-
impl PyroscopeAgent {
272+
impl PyroscopeAgent<PyroscopeAgentBare> {
262273
/// Short-hand for PyroscopeAgentBuilder::build(). This is a convenience method.
263274
///
264275
/// # Example
@@ -269,14 +280,16 @@ impl PyroscopeAgent {
269280
// Build PyroscopeAgent
270281
PyroscopeAgentBuilder::new(url, application_name)
271282
}
283+
}
272284

285+
impl PyroscopeAgent<PyroscopeAgentReady> {
273286
/// Start profiling and sending data. The agent will keep running until stopped. The agent will send data to the server every 10s secondy.
274287
/// # Example
275288
/// ```ignore
276289
/// let agent = PyroscopeAgent::builder("http://localhost:8080", "my-app").build()?;
277290
/// agent.start()?;
278291
/// ```
279-
pub fn start(&mut self) -> Result<()> {
292+
pub fn start(mut self) -> Result<PyroscopeAgent<PyroscopeAgentRunning>> {
280293
log::debug!(target: LOG_TAG, "Starting");
281294

282295
// Create a clone of Backend
@@ -331,9 +344,21 @@ impl PyroscopeAgent {
331344
Ok(())
332345
}));
333346

334-
Ok(())
347+
let agent_running = PyroscopeAgent {
348+
timer: self.timer,
349+
session_manager: self.session_manager,
350+
tx: self.tx,
351+
handle: self.handle,
352+
running: self.running,
353+
backend: self.backend,
354+
config: self.config,
355+
_state: PhantomData,
356+
};
357+
358+
Ok(agent_running)
335359
}
336-
360+
}
361+
impl PyroscopeAgent<PyroscopeAgentRunning> {
337362
/// Stop the agent. The agent will stop profiling and send a last report to the server.
338363
/// # Example
339364
/// ```ignore
@@ -342,7 +367,7 @@ impl PyroscopeAgent {
342367
/// // Expensive operation
343368
/// agent.stop();
344369
/// ```
345-
pub fn stop(&mut self) -> Result<()> {
370+
pub fn stop(mut self) -> Result<PyroscopeAgent<PyroscopeAgentReady>> {
346371
log::debug!(target: LOG_TAG, "Stopping");
347372
// get tx and send termination signal
348373
if let Some(sender) = self.tx.take() {
@@ -361,43 +386,18 @@ impl PyroscopeAgent {
361386
// Create a clone of Backend
362387
//let backend = Arc::clone(&self.backend);
363388

364-
Ok(())
365-
}
366-
367-
/// Add tags. This will restart the agent.
368-
/// # Example
369-
/// ```ignore
370-
/// let agent = PyroscopeAgent::builder("http://localhost:8080", "my-app").build()?;
371-
/// agent.start()?;
372-
/// // Expensive operation
373-
/// agent.add_tags(vec!["tag", "value"])?;
374-
/// // Tagged operation
375-
/// agent.stop()?;
376-
/// ```
377-
pub fn add_tags(&mut self, tags: &[(&str, &str)]) -> Result<()> {
378-
log::debug!(target: LOG_TAG, "Adding tags");
379-
// Check that tags are not empty
380-
if tags.is_empty() {
381-
return Ok(());
382-
}
383-
384-
// Stop Agent
385-
self.stop()?;
386-
387-
// Convert &[(&str, &str)] to HashMap(String, String)
388-
let tags_hashmap: HashMap<String, String> = tags
389-
.to_owned()
390-
.iter()
391-
.cloned()
392-
.map(|(a, b)| (a.to_owned(), b.to_owned()))
393-
.collect();
394-
395-
self.config.tags.extend(tags_hashmap);
396-
397-
// Restart Agent
398-
self.start()?;
399-
400-
Ok(())
389+
let agent_running = PyroscopeAgent {
390+
timer: self.timer,
391+
session_manager: self.session_manager,
392+
tx: self.tx,
393+
handle: self.handle,
394+
running: self.running,
395+
backend: self.backend,
396+
config: self.config,
397+
_state: PhantomData,
398+
};
399+
400+
Ok(agent_running)
401401
}
402402

403403
pub fn tag_wrapper(
@@ -429,6 +429,7 @@ impl PyroscopeAgent {
429429
)
430430
}
431431

432+
// TODO: change &mut self to &self
432433
pub fn add_global_tag(&mut self, tag: Tag) -> Result<()> {
433434
let rule = Rule::GlobalTag(tag);
434435
self.backend.add_rule(rule)?;
@@ -456,44 +457,4 @@ impl PyroscopeAgent {
456457

457458
Ok(())
458459
}
459-
460-
/// Remove tags. This will restart the agent.
461-
/// # Example
462-
/// ```ignore
463-
/// # use pyroscope::*;
464-
/// # use std::result;
465-
/// # fn main() -> result::Result<(), error::PyroscopeError> {
466-
/// let agent = PyroscopeAgent::builder("http://localhost:8080", "my-app")
467-
/// .tags(vec![("tag", "value")])
468-
/// .build()?;
469-
/// agent.start()?;
470-
/// // Expensive operation
471-
/// agent.remove_tags(vec!["tag"])?;
472-
/// // Un-Tagged operation
473-
/// agent.stop()?;
474-
/// # Ok(())
475-
/// # }
476-
/// ```
477-
pub fn remove_tags(&mut self, tags: &[&str]) -> Result<()> {
478-
log::debug!(target: LOG_TAG, "Removing tags");
479-
480-
// Check that tags are not empty
481-
if tags.is_empty() {
482-
return Ok(());
483-
}
484-
485-
// Stop Agent
486-
self.stop()?;
487-
488-
// Iterate through every tag
489-
tags.iter().for_each(|key| {
490-
// Remove tag
491-
self.config.tags.remove(key.to_owned());
492-
});
493-
494-
// Restart Agent
495-
self.start()?;
496-
497-
Ok(())
498-
}
499460
}

0 commit comments

Comments
 (0)