fix: escape text format label values - #347
Karthik-Chowdary wants to merge 3 commits into
Conversation
Signed-off-by: Karthik Chowdary <karthikchowdary2001@gmail.com>
krisztianfekete
left a comment
There was a problem hiding this comment.
Thanks for picking this up, the escaping logic looks correct.
Per the earlier discussion in #151, we need a benchmark to show escaping doesn't slow down encoding. I ran the existing text bench against this branch and it's about 12% slower than master (15.2 ms -> 17.1 ms), mostly on label values that don't need escaping at all.
Could you please:
- Add a fast path that writes the value unchanged when it contains no
\,"or\n. A quick byte check at the top ofwrite_strbrought the regression down to about 5% locally.memchr::memchr3may close the gap entirely, if you want to try it. - Add a bench case to
benches/encoding/text.rswhose label values need escaping, so both paths are measured. - Paste the before/after criterion output into the PR description:
cargo bench --bench text -- --save-baseline master # on master
cargo bench --bench text -- --baseline master # on this branchAvoid scanning label values twice when they do not contain escapable bytes, and benchmark both escaped and unescaped values. Signed-off-by: Karthik Chowdary <21139050+Karthik-Chowdary@users.noreply.github.com>
|
Addressed the requested fast path and added a dedicated escaping benchmark in signed-off commit |
krisztianfekete
left a comment
There was a problem hiding this comment.
Thanks for the update, the escaping looks right and clearing the buffer in the bench was a good catch!
I dug into the numbers a bit more though. The existing bench only uses tiny label values (1–3 bytes), which hides the cost of the check. With more realistic labels like routes and pod names, encode ends up ~19% slower than master on my machine.
A few small changes get most of that back:
- Swap
anyfor afold. With no early exit, LLVM vectorizes it, and a 32-byte value takes ~2ns to check instead of ~12ns:s.bytes().fold(false, |found, b| found | needs_escape(b))
- Skip the check for integer/float/bool label values, since they can never need escaping. A small crate-private write_str_unescaped on LabelValueEncoder does the trick.
- Move the escaping loop into a #[cold] fn and loop over bytes instead of chars (all three special chars are ASCII).
- Add a quickcheck test against a simple reference escaper. Right now both tests still pass if the check forgets about \n.
- Add a bench case with realistic string labels so we catch this kind of thing in future.
With those, I see about −2.5% on encode and +6% on string-heavy labels. That's fine for a fix like this I think. Happy to share the patch if it helps!
The bigger win would probably be caching the encoded labels for histograms (there's a TODO for it), since right now we re-encode and re-check them on every bucket line. This can be a separate PR though.
Signed-off-by: Karthik Chowdary <21139050+Karthik-Chowdary@users.noreply.github.com>
|
Implemented the follow-up performance and coverage suggestions in
Local validation passed: Thanks for the detailed profiling and concrete suggestions. |
Fixes #346.
Escape backslashes, double quotes, and line feeds when encoding label values in the text exposition format. This prevents label content from breaking out of its quoted value while preserving carriage returns and UTF-8 text.
The tests cover each special character and parse an injection-style value with the Python Prometheus client.
Validation:
cargo fmt --checkcargo test(64 unit tests and 33 doc tests)Benchmark comparison (
cargo bench --bench text, Criterion 0.8.2, same host; benchmark buffer cleared between iterations):The escaped case is expected to do additional writes; the common no-escape path is statistically unchanged.
Additional validation on
79c8c88:cargo fmt --all -- --checkcargo clippy --all-targets --all-features -- -D warningsThe full unit suite reached 49 passes and 15 environment-only failures because the embedded Python environment does not contain the
prometheus_clientmodule; no Rust assertion failed.