Skip to content

Commit e9c602b

Browse files
committed
aya-log-ebpf: zero copy!
1 parent 8354cbc commit e9c602b

File tree

5 files changed

+495
-368
lines changed

5 files changed

+495
-368
lines changed

aya-log-common/src/lib.rs

Lines changed: 163 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,11 @@
22

33
use core::{
44
net::{IpAddr, Ipv4Addr, Ipv6Addr},
5-
num::{NonZeroUsize, TryFromIntError},
5+
num::TryFromIntError,
66
};
77

88
use num_enum::IntoPrimitive;
99

10-
pub const LOG_FIELDS: usize = 6;
11-
1210
pub type LogValueLength = u16;
1311

1412
#[repr(u8)]
@@ -93,8 +91,8 @@ impl UpperMacFormatter for [u8; 6] {}
9391

9492
#[repr(u8)]
9593
#[derive(Copy, Clone, Debug, IntoPrimitive)]
96-
pub enum RecordField {
97-
Target = 1,
94+
pub enum RecordFieldKind {
95+
Target,
9896
Level,
9997
Module,
10098
File,
@@ -106,7 +104,7 @@ pub enum RecordField {
106104
/// programs to userspace.
107105
#[repr(u8)]
108106
#[derive(Copy, Clone, Debug, IntoPrimitive)]
109-
pub enum Argument {
107+
pub enum ArgumentKind {
110108
DisplayHint,
111109

112110
I8,
@@ -158,172 +156,207 @@ pub enum DisplayHint {
158156
UpperMac,
159157
}
160158

161-
// Must be inlined, else the BPF backend emits:
162-
//
163-
// llvm: <unknown>:0:0: in function _ZN14aya_log_common5write17hc9ed05433e23a663E { i64, i64 } (i8, ptr, i64, ptr, i64): only integer returns supported
164-
#[inline(always)]
165-
pub(crate) fn write(tag: u8, value: &[u8], buf: &mut [u8]) -> Option<NonZeroUsize> {
166-
// TODO(https://github.com/rust-lang/rust-clippy/issues/14112): Remove this allowance when the
167-
// lint behaves more sensibly.
168-
#[expect(clippy::manual_ok_err)]
169-
let wire_len: LogValueLength = match value.len().try_into() {
170-
Ok(wire_len) => Some(wire_len),
171-
Err(TryFromIntError { .. }) => None,
172-
}?;
173-
let mut size = 0;
174-
for slice in [&[tag][..], &wire_len.to_ne_bytes()[..], value] {
175-
let buf = buf.get_mut(size..)?;
176-
let buf = buf.get_mut(..slice.len())?;
177-
buf.copy_from_slice(slice);
178-
size += slice.len();
179-
}
180-
NonZeroUsize::new(size)
181-
}
182-
183-
pub trait WriteToBuf {
184-
fn write(self, buf: &mut [u8]) -> Option<NonZeroUsize>;
159+
pub trait Argument {
160+
fn as_argument(&self) -> (ArgumentKind, impl AsRef<[u8]>);
185161
}
186162

187-
macro_rules! impl_write_to_buf {
188-
($type:ident, $arg_type:expr) => {
189-
impl WriteToBuf for $type {
190-
// This need not be inlined because the return value is Option<N> where N is
191-
// mem::size_of<$type>, which is a compile-time constant.
192-
#[inline(never)]
193-
fn write(self, buf: &mut [u8]) -> Option<NonZeroUsize> {
194-
write($arg_type.into(), &self.to_ne_bytes(), buf)
163+
macro_rules! impl_argument {
164+
($self:ident, $arg_type:expr) => {
165+
impl Argument for $self {
166+
fn as_argument(&self) -> (ArgumentKind, impl AsRef<[u8]>) {
167+
($arg_type, self.to_ne_bytes())
195168
}
196169
}
197170
};
198171
}
199172

200-
impl_write_to_buf!(i8, Argument::I8);
201-
impl_write_to_buf!(i16, Argument::I16);
202-
impl_write_to_buf!(i32, Argument::I32);
203-
impl_write_to_buf!(i64, Argument::I64);
204-
impl_write_to_buf!(isize, Argument::Isize);
173+
impl_argument!(i8, ArgumentKind::I8);
174+
impl_argument!(i16, ArgumentKind::I16);
175+
impl_argument!(i32, ArgumentKind::I32);
176+
impl_argument!(i64, ArgumentKind::I64);
177+
impl_argument!(isize, ArgumentKind::Isize);
178+
179+
impl_argument!(u8, ArgumentKind::U8);
180+
impl_argument!(u16, ArgumentKind::U16);
181+
impl_argument!(u32, ArgumentKind::U32);
182+
impl_argument!(u64, ArgumentKind::U64);
183+
impl_argument!(usize, ArgumentKind::Usize);
205184

206-
impl_write_to_buf!(u8, Argument::U8);
207-
impl_write_to_buf!(u16, Argument::U16);
208-
impl_write_to_buf!(u32, Argument::U32);
209-
impl_write_to_buf!(u64, Argument::U64);
210-
impl_write_to_buf!(usize, Argument::Usize);
185+
impl_argument!(f32, ArgumentKind::F32);
186+
impl_argument!(f64, ArgumentKind::F64);
211187

212-
impl_write_to_buf!(f32, Argument::F32);
213-
impl_write_to_buf!(f64, Argument::F64);
188+
enum Either<L, R> {
189+
Left(L),
190+
Right(R),
191+
}
214192

215-
impl WriteToBuf for IpAddr {
216-
fn write(self, buf: &mut [u8]) -> Option<NonZeroUsize> {
193+
impl<L, R> AsRef<[u8]> for Either<L, R>
194+
where
195+
L: AsRef<[u8]>,
196+
R: AsRef<[u8]>,
197+
{
198+
fn as_ref(&self) -> &[u8] {
217199
match self {
218-
IpAddr::V4(ipv4_addr) => write(Argument::Ipv4Addr.into(), &ipv4_addr.octets(), buf),
219-
IpAddr::V6(ipv6_addr) => write(Argument::Ipv6Addr.into(), &ipv6_addr.octets(), buf),
200+
Either::Left(l) => l.as_ref(),
201+
Either::Right(r) => r.as_ref(),
220202
}
221203
}
222204
}
223205

224-
impl WriteToBuf for Ipv4Addr {
225-
fn write(self, buf: &mut [u8]) -> Option<NonZeroUsize> {
226-
write(Argument::Ipv4Addr.into(), &self.octets(), buf)
206+
impl Argument for IpAddr {
207+
fn as_argument(&self) -> (ArgumentKind, impl AsRef<[u8]>) {
208+
match self {
209+
IpAddr::V4(ipv4_addr) => {
210+
let (kind, value) = ipv4_addr.as_argument();
211+
(kind, Either::Left(value))
212+
}
213+
IpAddr::V6(ipv6_addr) => {
214+
let (kind, value) = ipv6_addr.as_argument();
215+
(kind, Either::Right(value))
216+
}
217+
}
227218
}
228219
}
229220

230-
impl WriteToBuf for [u8; 4] {
231-
// This need not be inlined because the return value is Option<N> where N is 16, which is a
232-
// compile-time constant.
233-
#[inline(never)]
234-
fn write(self, buf: &mut [u8]) -> Option<NonZeroUsize> {
235-
write(Argument::ArrU8Len4.into(), &self, buf)
221+
impl Argument for Ipv4Addr {
222+
fn as_argument(&self) -> (ArgumentKind, impl AsRef<[u8]>) {
223+
(ArgumentKind::Ipv4Addr, self.octets())
236224
}
237225
}
238226

239-
impl WriteToBuf for Ipv6Addr {
240-
fn write(self, buf: &mut [u8]) -> Option<NonZeroUsize> {
241-
write(Argument::Ipv6Addr.into(), &self.octets(), buf)
227+
impl Argument for [u8; 4] {
228+
fn as_argument(&self) -> (ArgumentKind, impl AsRef<[u8]>) {
229+
(ArgumentKind::ArrU8Len4, self)
242230
}
243231
}
244232

245-
impl WriteToBuf for [u8; 16] {
246-
// This need not be inlined because the return value is Option<N> where N is 16, which is a
247-
// compile-time constant.
248-
#[inline(never)]
249-
fn write(self, buf: &mut [u8]) -> Option<NonZeroUsize> {
250-
write(Argument::ArrU8Len16.into(), &self, buf)
233+
impl Argument for Ipv6Addr {
234+
fn as_argument(&self) -> (ArgumentKind, impl AsRef<[u8]>) {
235+
(ArgumentKind::Ipv6Addr, self.octets())
251236
}
252237
}
253238

254-
impl WriteToBuf for [u16; 8] {
255-
// This need not be inlined because the return value is Option<N> where N is 16, which is a
256-
// compile-time constant.
257-
#[inline(never)]
258-
fn write(self, buf: &mut [u8]) -> Option<NonZeroUsize> {
259-
let bytes = unsafe { core::mem::transmute::<[u16; 8], [u8; 16]>(self) };
260-
write(Argument::ArrU16Len8.into(), &bytes, buf)
239+
impl Argument for [u8; 16] {
240+
fn as_argument(&self) -> (ArgumentKind, impl AsRef<[u8]>) {
241+
(ArgumentKind::ArrU8Len16, self)
261242
}
262243
}
263244

264-
impl WriteToBuf for [u8; 6] {
265-
// This need not be inlined because the return value is Option<N> where N is 6, which is a
266-
// compile-time constant.
267-
#[inline(never)]
268-
fn write(self, buf: &mut [u8]) -> Option<NonZeroUsize> {
269-
write(Argument::ArrU8Len6.into(), &self, buf)
245+
impl Argument for [u16; 8] {
246+
fn as_argument(&self) -> (ArgumentKind, impl AsRef<[u8]>) {
247+
let bytes = unsafe { core::mem::transmute::<&[u16; 8], &[u8; 16]>(self) };
248+
(ArgumentKind::ArrU16Len8, bytes)
270249
}
271250
}
272251

273-
impl WriteToBuf for &[u8] {
274-
// Must be inlined, else the BPF backend emits:
275-
//
276-
// llvm: <unknown>:0:0: in function _ZN63_$LT$$RF$$u5b$u8$u5d$$u20$as$u20$aya_log_common..WriteToBuf$GT$5write17h08f30a45f7b9f09dE { i64, i64 } (ptr, i64, ptr, i64): only integer returns supported
277-
#[inline(always)]
278-
fn write(self, buf: &mut [u8]) -> Option<NonZeroUsize> {
279-
write(Argument::Bytes.into(), self, buf)
252+
impl Argument for [u8; 6] {
253+
fn as_argument(&self) -> (ArgumentKind, impl AsRef<[u8]>) {
254+
(ArgumentKind::ArrU8Len6, self)
280255
}
281256
}
282257

283-
impl WriteToBuf for &str {
284-
// Must be inlined, else the BPF backend emits:
285-
//
286-
// llvm: <unknown>:0:0: in function _ZN54_$LT$$RF$str$u20$as$u20$aya_log_common..WriteToBuf$GT$5write17h7e2d1ccaa758e2b5E { i64, i64 } (ptr, i64, ptr, i64): only integer returns supported
287-
#[inline(always)]
288-
fn write(self, buf: &mut [u8]) -> Option<NonZeroUsize> {
289-
write(Argument::Str.into(), self.as_bytes(), buf)
258+
impl Argument for &[u8] {
259+
fn as_argument(&self) -> (ArgumentKind, impl AsRef<[u8]>) {
260+
(ArgumentKind::Bytes, self)
290261
}
291262
}
292263

293-
impl WriteToBuf for DisplayHint {
294-
// This need not be inlined because the return value is Option<N> where N is 1, which is a
295-
// compile-time constant.
296-
#[inline(never)]
297-
fn write(self, buf: &mut [u8]) -> Option<NonZeroUsize> {
298-
let v: u8 = self.into();
299-
write(Argument::DisplayHint.into(), &v.to_ne_bytes(), buf)
264+
impl Argument for &str {
265+
fn as_argument(&self) -> (ArgumentKind, impl AsRef<[u8]>) {
266+
(ArgumentKind::Str, self.as_bytes())
267+
}
268+
}
269+
270+
impl Argument for DisplayHint {
271+
fn as_argument(&self) -> (ArgumentKind, impl AsRef<[u8]>) {
272+
let v: u8 = (*self).into();
273+
(ArgumentKind::DisplayHint, v.to_ne_bytes())
274+
}
275+
}
276+
277+
fn wire_len(value: &[u8]) -> Option<[u8; 2]> {
278+
match LogValueLength::try_from(value.len()) {
279+
Ok(wire_len) => Some(wire_len.to_ne_bytes()),
280+
Err(TryFromIntError { .. }) => None,
281+
}
282+
}
283+
284+
#[doc(hidden)]
285+
pub struct Field<T>([u8; 1], [u8; 2], T);
286+
287+
impl<T: AsRef<[u8]>> Field<T> {
288+
pub fn new(kind: impl Into<u8>, value: T) -> Option<Self> {
289+
let wire_len = wire_len(value.as_ref())?;
290+
Some(Self([kind.into()], wire_len, value))
291+
}
292+
293+
pub fn with_bytes(&self, op: &mut impl FnMut(&[u8]) -> Option<()>) -> Option<()> {
294+
let Self(kind, wire_len, value) = self;
295+
op(&kind[..])?;
296+
op(&wire_len[..])?;
297+
op(value.as_ref())?;
298+
Some(())
300299
}
301300
}
302301

303302
#[doc(hidden)]
304-
#[inline(always)] // This function takes too many arguments to not be inlined.
305-
pub fn write_record_header(
306-
buf: &mut [u8],
307-
target: &str,
308-
level: Level,
309-
module: &str,
310-
file: &str,
311-
line: u32,
312-
num_args: usize,
313-
) -> Option<NonZeroUsize> {
314-
let level: u8 = level.into();
315-
let mut size = 0;
316-
for (tag, value) in [
317-
(RecordField::Target, target.as_bytes()),
318-
(RecordField::Level, &level.to_ne_bytes()),
319-
(RecordField::Module, module.as_bytes()),
320-
(RecordField::File, file.as_bytes()),
321-
(RecordField::Line, &line.to_ne_bytes()),
322-
(RecordField::NumArgs, &num_args.to_ne_bytes()),
323-
] {
324-
let buf = buf.get_mut(size..)?;
325-
let len = write(tag.into(), value, buf)?;
326-
size += len.get();
303+
pub struct Header<'a> {
304+
target: Field<&'a [u8]>,
305+
level: Field<[u8; 1]>,
306+
module: Field<&'a [u8]>,
307+
file: Field<&'a [u8]>,
308+
line: Field<[u8; 4]>,
309+
num_args: Field<[u8; 4]>,
310+
}
311+
312+
impl<'a> Header<'a> {
313+
pub fn new(
314+
target: &'a str,
315+
level: Level,
316+
module: &'a str,
317+
file: &'a str,
318+
line: u32,
319+
num_args: u32,
320+
) -> Option<Self> {
321+
let target = target.as_bytes();
322+
let level: u8 = level.into();
323+
let level = level.to_ne_bytes();
324+
let module = module.as_bytes();
325+
let file = file.as_bytes();
326+
let line = line.to_ne_bytes();
327+
let num_args = num_args.to_ne_bytes();
328+
let target = Field::new(RecordFieldKind::Target, target)?;
329+
let level = Field::new(RecordFieldKind::Level, level)?;
330+
let module = Field::new(RecordFieldKind::Module, module)?;
331+
let file = Field::new(RecordFieldKind::File, file)?;
332+
let line = Field::new(RecordFieldKind::Line, line)?;
333+
let num_args = Field::new(RecordFieldKind::NumArgs, num_args)?;
334+
335+
Some(Self {
336+
target,
337+
level,
338+
module,
339+
file,
340+
line,
341+
num_args,
342+
})
343+
}
344+
345+
pub fn with_bytes(&self, op: &mut impl FnMut(&[u8]) -> Option<()>) -> Option<()> {
346+
let Self {
347+
target,
348+
level,
349+
module,
350+
file,
351+
line,
352+
num_args,
353+
} = self;
354+
target.with_bytes(op)?;
355+
level.with_bytes(op)?;
356+
module.with_bytes(op)?;
357+
file.with_bytes(op)?;
358+
line.with_bytes(op)?;
359+
num_args.with_bytes(op)?;
360+
Some(())
327361
}
328-
NonZeroUsize::new(size)
329362
}

0 commit comments

Comments
 (0)