Skip to content
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

Add additional tracing instrumentation. #97

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

jswrenn
Copy link

@jswrenn jswrenn commented Apr 29, 2022

This commit lays the ground-work for making mini-redis a better example of an instrumented tokio application. While it does not go so far to turn mini-redis into a fully-featured guide for using tracing, it ensures that people who use mini-redis as a basis for experimenting with different subscribers get a more complete experience out-of-the-box.

E.g., with tracing-tree a tracing-tree subscriber and level=Debug:

 INFO mini_redis::server accepting inbound connections
┐mini_redis::db::purge_expired_tasks 
├─┐mini_redis::db::Shared::purge_expired_keys 
├─┘
┐mini_redis::server::Handler::run peer_addr=127.0.0.1:56574
├─┐mini_redis::server::Handler::process_frame 
│ ├─0ms DEBUG mini_redis::server cmd=Unknown(Unknown { command_name: "config" })
│ ├─0ms DEBUG mini_redis::cmd::unknown response=Error("ERR unknown command 'config'")
├─┘
├─┐mini_redis::server::Handler::process_frame 
│ ├─0ms DEBUG mini_redis::server cmd=Unknown(Unknown { command_name: "config" })
│ ├─0ms DEBUG mini_redis::cmd::unknown response=Error("ERR unknown command 'config'")
├─┘
├─┐mini_redis::server::Handler::process_frame 
│ ├─0ms  WARN mini_redis::connection connection closed abruptly by peer
├─┘
┘
┐mini_redis::server::Handler::run peer_addr=127.0.0.1:56576
├─┐mini_redis::server::Handler::process_frame 
│ ├─0ms DEBUG mini_redis::server cmd=Set(Set { key: "key:000000000027", value: b"VXK", expire: None })
├─┘
├─┐mini_redis::server::Handler::process_frame 
│ ├─0ms DEBUG mini_redis::server cmd=Set(Set { key: "key:000000000001", value: b"VXK", expire: None })
├─┘
├─┐mini_redis::server::Handler::process_frame 
├─┘
┘

This PR also reduces ConnectionReset 'errors' to warnings, as they do not reflect errors in mini-redis, but rather client misbehavior. (And you get them frequently when using redis-benchmark.)

@bryangarza
Copy link
Member

Looks pretty good to me

Copy link
Member

@hawkw hawkw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

at a glance, this seems good to me. one thought i had is that, since mini-redis is intended to be an educational example, it might be good to add some comments actually explaining the use of tracing in this code --- we could add comments summarizing how the instrument macro is being used, and so on?

@jswrenn
Copy link
Author

jswrenn commented May 6, 2022

@hawkw I've added a few such comments; e.g.:

  • /// # Traces
    ///
    /// Generates a TRACE-level span named `Command::from_frame` that includes
    /// the `Debug`-representation of `frame` as a field.
    #[instrument(level = "trace", name = "Command::from_frame")]
  • mini-redis/src/cmd/mod.rs

    Lines 109 to 113 in 49ef356

    /// # Traces
    ///
    /// Generates a `TRACE`-level span that includes the `Debug`-serializaiton
    /// of `self` (the `Command` being applied) as a field.
    #[instrument(level = "trace", name = "Command::apply", skip(db, dst, shutdown))]
  • mini-redis/src/server.rs

    Lines 378 to 393 in 49ef356

    // The frame was malformed and could not be parsed. This is
    // probably indicative of an issue with the client (as opposed
    // to our server), so we (1) emit a warning...
    //
    // The syntax here is a shorthand provided by the `tracing`
    // crate. It can be thought of as similar to:
    // warn! {
    // cause = format!("{}", cause),
    // "failed to parse command from frame"
    // };
    // `tracing` provides structured logging, so information is
    // "logged" as key-value pairs.
    warn! {
    %cause,
    "failed to parse command from frame"
    };

@bryangarza
Copy link
Member

So... LGTM? @jswrenn

Copy link
Member

@hawkw hawkw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

overall, this looks close. i suggested a few minor changes --- in particular, the ? syntax is being used incorrectly, so we should probably fix that. the rest of my suggestions are not blocking.

it might be interesting to add #[instrument(ret)] and/or #[instrument(err)] in a few places to also emit events with return values? but we can do that in a follow-up branch.

src/cmd/ping.rs Outdated Show resolved Hide resolved
src/cmd/set.rs Outdated Show resolved Hide resolved
src/server.rs Outdated Show resolved Hide resolved
src/connection.rs Outdated Show resolved Hide resolved
src/connection.rs Outdated Show resolved Hide resolved
src/db.rs Outdated Show resolved Hide resolved
src/server.rs Outdated Show resolved Hide resolved
Comment on lines +352 to +344
#[instrument(level = "debug", name = "Handler::process_frame", skip(self))]
async fn process_frame(&mut self) -> crate::Result<ControlFlow<(), ()>> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it could be interesting to add

Suggested change
#[instrument(level = "debug", name = "Handler::process_frame", skip(self))]
async fn process_frame(&mut self) -> crate::Result<ControlFlow<(), ()>> {
#[instrument(level = "debug", name = "Handler::process_frame", skip(self), err, ret)]
async fn process_frame(&mut self) -> crate::Result<ControlFlow<(), ()>> {

to this to also emit events with the return value (and whether it's Ok or Err)?

async fn process_frame(&mut self) -> crate::Result<ControlFlow<(), ()>> {
// While reading a request frame, also listen for the shutdown
// signal.
let maybe_frame = tokio::select! {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i might consider pulling the select! back out into run, and making process_frame be the function that's actually called with the received frame, so we could record the frame as a field on the process_frame span? not a blocker.

jswrenn and others added 6 commits July 28, 2022 14:47
This commit lays the ground-work for making mini-redis a better
example of an instrumented tokio application. While it does not
go so far to turn mini-redis into a fully-featured guide for
using `tracing`, it ensures that people who use mini-redis as a
basis for experimenting with different subscribers get a more
complete experience out-of-the-box.
`str::to_lowercase` had been used for the purposes of case-insensitive
comparisons of command names, but `str::make_ascii_lowercase` is
substantially more performant. Using:
    $ redis-benchmark -c 1 -r 128 -t set -n 100000

...with `str::to_lowercase`:
  throughput summary: 6651.59 requests per second
  latency summary (msec):
          avg       min       p50       p95       p99       max
        0.136     0.072     0.135     0.223     0.287     2.807

...with `str::make_ascii_lowercase`:
  throughput summary: 9102.49 requests per second
  latency summary (msec):
          avg       min       p50       p95       p99       max
        0.099     0.064     0.095     0.127     0.191     0.815

While micro-optimizations are usually not appropriate for mini-redis,
this pattern of case-insensitive string comparisons is common, and
using `str::to_lowercase` sets a bad example.
Without these, the flamegraphs produced with tracing-flamegraph
look rather sparse for `Command::from_frame`.
This helps draw a distinction between errors caused by bugs in the
server, and errors caused by bugs in the client. This latter kind,
at least as `Command` parse errors are concerned, is handled now
with an `Invalid` pseudo-command (akin to `Unknown`). Drawing this
distinction is pedagogically useful, as it helps set up an example
usage of `warn!` in `process_frame` (in contrast to other kinds of
errors, which are still traced with `error!` in `Listener::run`).
Co-authored-by: Eliza Weisman <eliza@buoyant.io>
Co-authored-by: Eliza Weisman <eliza@buoyant.io>
@jswrenn
Copy link
Author

jswrenn commented Jul 28, 2022

@hawkw I've applied most of the suggestions (will need to think more about the last two), and rebased (to fix a merge conflict).

@bryangarza
Copy link
Member

too bad this never got merged, it would have been useful

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants