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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
use
|
|
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() ->
|
|
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
|
-
|
|
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
|
+
}
|