-
Notifications
You must be signed in to change notification settings - Fork 439
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
printk ratelimiting #472
base: rust
Are you sure you want to change the base?
printk ratelimiting #472
Conversation
726b9c4
to
3807c6f
Compare
I think it's probably better to use similar approach to I.e. use |
static mut PRINTK_RS: core::mem::MaybeUninit<$crate::print::ratelimit_state> = | ||
core::mem::MaybeUninit::<$crate::print::ratelimit_state>::uninit(); | ||
static mut PRINTK_RS_PTR: core::sync::atomic::AtomicPtr<$crate::print::ratelimit_state> = | ||
core::sync::atomic::AtomicPtr::<$crate::print::ratelimit_state>::new(core::ptr::null_mut()); | ||
static mut PRINTK_RS_AVAILABLE: core::sync::atomic::AtomicBool = | ||
core::sync::atomic::AtomicBool::new(false); | ||
|
||
// SAFETY: [`PRINTK_RS_AVAILABLE`] and [`PRINTK_RS_PTR`] is used to | ||
// control the intialization of [`PRINTK_RS`] - a local | ||
// [`ratelimit_state`] initialized with [`DEFAULT_RATELIMIT_INTERVAL`] | ||
// and [`DEFAULT_RATELIMIT_BURST`]. All of these three variables are | ||
// static local variables to avoid dynamic memory allocation and no | ||
// lock is used because printk can be used in any circumstance. | ||
// | ||
// The hidden macro [`call_printk`] should only be called by the | ||
// documented printing macros which ensure the format string is one of | ||
// the fixed ones. | ||
// All `__LOG_PREFIX`s are null-terminated as they are generated | ||
// by the `module!` proc macro or fixed values defined in a kernel | ||
// crate. | ||
unsafe { | ||
if PRINTK_RS_AVAILABLE.compare_exchange( | ||
false, | ||
true, | ||
core::sync::atomic::Ordering::SeqCst, | ||
core::sync::atomic::Ordering::SeqCst | ||
).is_ok() { | ||
// Zero out [`PRINTK_RS`] then initialize it using | ||
// [`rust_helper_ratelimit_state_init`] helper function. | ||
*PRINTK_RS.as_mut_ptr() = $crate::print::ratelimit_state::default(); | ||
$crate::print::rust_helper_ratelimit_state_init(PRINTK_RS.as_mut_ptr()); | ||
|
||
// Make PRINTK_RS_PTR point at PRINTK_RS so later we can call | ||
// [`___ratelimit`] with it. | ||
if PRINTK_RS_PTR.load( | ||
core::sync::atomic::Ordering::SeqCst | ||
).is_null() { | ||
PRINTK_RS_PTR.store( | ||
PRINTK_RS.as_mut_ptr(), | ||
core::sync::atomic::Ordering::SeqCst | ||
); | ||
} | ||
} | ||
|
||
// [`PRINTK_RS`] can be uninitialized under concurrent situation, | ||
// if so, we print message without ratelimit control. | ||
// Otherwise we call __ratelimit to decide if | ||
// enforce ratelimit. | ||
if PRINTK_RS_PTR.load(core::sync::atomic::Ordering::SeqCst).is_null() || | ||
$crate::print::___ratelimit( | ||
PRINTK_RS_PTR.load(core::sync::atomic::Ordering::SeqCst), | ||
$crate::c_str!($where).as_char_ptr() | ||
) != 0 { | ||
$crate::print::call_printk( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think a better approach might be providing an abstraction of the ratelimiter instead of having these macro expanded each time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a problem, we need a static local ratelimite_state at each occurrence. Can we do this without macros??
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can have a Ratelimiter
that wraps ratelimit_state
and call a method on it each time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But how can we preserve the state??
Can you elaborate further?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You'll still need a static variable, but just static RATERLIMITER: Ratelimiter = Ratelimiter::new();
(or perhaps Lazy<Ratelimiter>
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I might need to use Lazy.
Does it work well in multithreading??
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are SyncLazy
in std (but not core) and once_cell::sync::Lazy
. We probably need to implement our own Lazy
in kernel.
I don't think merging them into one will be better. Because:
|
The C code has pr_* and pr_*_ratelimited due to the limitation of C macros. You can't do pattern matching in C macros but Rust allows that. The semantics is really just how often do you want the print to be executed. So essentially:
|
Ok, then that makes sense to me. I can turn this into inner implementation and write a universal pr_* as a dispatcher. |
What users gain by writing |
For me, |
Some reasons related how macros are namespaced in Rust:
Also, for me |
The same discussion could be had about the level itself, i.e. we could argue that a single I do not particularly mind one way or the other, and the documentation can indeed look funny with so many variations. However, we were asked to keep names as close to the C side as possible, thus if we do not have a strong reason otherwise, we should go with the C scheme. In fact, I think the best way to approach these changes is that we have a talk with potential improvements to common APIs for the Rust side, and see what is the input from kernel developers. We have accumulated quite a few of these ideas, thus we could put all together in a small talk and ask for feedback. If one of you is up to give such a talk in September, let me know. |
Well, in the end there was no discussion at LPC in the end about naming :( We can go with either. |
I think we should have them implemented together in the print_macro, and then we can decide/change later about how it is exposed. |
Yeah, probably with a more universal design Oh, it's been a while, sorry I'm busy recently... sigh. |
Hi Gary, Does your 'together' mean what you proposed before: I will take a look at the weekend. |
This commit adds equivalent pr_*_ratelimited to kernel crate. They are macros that define a static local `ratelimit_state` and uses Atomic and CAS to guard its initialization. That `ratelimit_state` will be used later by `___ratelimit()` to decide if to suppress the `printk` or not. Because we use Atomic & CAS to help initialize `ratelimit_state`, in concurrent situation some thread may reach `__ratelimit()` before `ratelimit_state` is initialized. If that happens, we call `printk` anyway. This commit does not implement pr_debug_ratelimited since it's special and way more complicated and deserves another dedicated commit. `printk_ratelimit` is also implemented in this commit Signed-off-by: Fox Chen <[email protected]>
3807c6f
to
45c3e2f
Compare
/// This function initializes ratelimit_state with DEFAULT_RATELIMIT_INTERVAL | ||
/// and DEFAULT_RATELIMIT_BURST. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// This function initializes ratelimit_state with DEFAULT_RATELIMIT_INTERVAL | |
/// and DEFAULT_RATELIMIT_BURST. | |
/// Initializes the parameter with `DEFAULT_RATELIMIT_INTERVAL` | |
/// and `DEFAULT_RATELIMIT_BURST`. |
/// [`pr_emerg`]: https://www.kernel.org/doc/html/latest/core-api/printk-basics.html#c.pr_emerg | ||
/// [`std::print!`]: https://doc.rust-lang.org/std/macro.print.html | ||
/// | ||
/// [`where`] parameter is a string literal that will be used as an |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
where
cannot be a link (rustdoc
warns); same for the others:
/// [`where`] parameter is a string literal that will be used as an | |
/// `where` parameter is a string literal that will be used as an |
/// identifier when ratelimite is triggered. In C this is populated with | ||
/// __function__. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// identifier when ratelimite is triggered. In C this is populated with | |
/// __function__. | |
/// identifier when the ratelimit is triggered. In C this is populated with | |
/// `__function__`. |
macro_rules! print_macro_ratelimited ( | ||
// The non-continuation cases (most of them, e.g. `INFO`). | ||
($format_string:path, false, $where:literal, $($arg:tt)+) => ({ | ||
static mut PRINTK_RS: core::mem::MaybeUninit<$crate::print::ratelimit_state> = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps use
MaybeUninit
, AtomicPtr
, SeqCst
, etc. to make this more readable?
if printk_ratelimit!("RustPrint::init") { | ||
pr_warn!("Inside printk_ratelimit {}", i); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please add a test for this in the CI? (we are testing for other messages too)
/// identifier when ratelimite is triggered. In C this is populated with | ||
/// __function__. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// identifier when ratelimite is triggered. In C this is populated with | |
/// __function__. | |
/// identifier when the ratelimit is triggered. In C this is populated with | |
/// `__function__`. |
// All `__LOG_PREFIX`s are null-terminated as they are generated | ||
// by the `module!` proc macro or fixed values defined in a kernel | ||
// crate. | ||
unsafe { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be best to reduce the scope of unsafe
.
// SAFETY: [`PRINTK_RS_AVAILABLE`] and [`PRINTK_RS_PTR`] is used to | ||
// control the intialization of [`PRINTK_RS`] - a local |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want all these as intradoc links?
@@ -190,6 +203,90 @@ macro_rules! print_macro ( | |||
); | |||
); | |||
|
|||
/// Same as [`print_macro`], but ratelimites messages with local ratelimit_state. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// Same as [`print_macro`], but ratelimites messages with local ratelimit_state. | |
/// Same as [`print_macro`], but ratelimits messages with a local `ratelimit_state`. |
pub use bindings::___ratelimit; | ||
pub use bindings::__printk_ratelimit; | ||
pub use bindings::ratelimit_state; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it is best to avoid use
ng things from binding
s to make it clear to the reader it comes from the C side.
I think Gary's suggestion is worth doing -- it will be useful elsewhere (similarly, a |
Ok, let me change interfaces to |
Sorry, I meant the one about factoring out the rate limiting into its own abstraction. Please leave the interfaces as they were for the moment. |
Features:
Add pr_*_ratelimited macros (not including pr_debug_ratelimited)
Add printk_ratelimit
Implementation:
pr_*_ratelimit
are macros that define a static localratelimit_state
and use Atomic and CAS to guard its initialization. Thatratelimit_state
will be used later by___ratelimit()
to decide if to suppress theprintk
or not.printk_ratelimit
wraps around kernel's__printk_ratelimit
which calls__ratelimit()
with a globally sharedratelimit_state
Future:
pr_debug_ratelimited
is special and more complicated, will implement it in future PRs.Issues: