Skip to main content Link Menu Expand (external link) Document Search Copy Copied

Statistics

Table of contents

  1. Data structures
  2. Thread safety
  3. Integration (optional)
    1. Collecting statistics
    2. Displaying statistics

When you are running a server, it is often useful to collect information on response time, requests per second, error codes, and other metrics that tell you about the performance of your server.

In this part, you will implement a very simplified statistics collection module for your HTTP server. This will do two things:

  1. Record the number of requests resulting in a given status code from the time the server was started. For example, there may be 15 requests resulting in a 200 OK response, 2 requests resulting in a 403 Forbidden response, and 4 requests resulting in a 404 Not Found response.
  2. Display these counts at the URL /stats, overriding any files/directories stored at that location.

Data structures

The src/stats.rs file contains a Stats struct that holds a HashMap mapping status codes to counts (the number of requests that resulted in that status code):

pub struct Stats {
    statuses: HashMap<StatusCode, usize>,
}

To start, fill in the implementation of stats::Stats::incr in the impl Stats block. The stats::Stats::incr method takes in a status code and simply adds one to the count for that status code. Do not worry about thread safety at this stage.

You may find it helpful to reference the documentation for Rust’s HashMap. Hint: Look at the HashMap::entry method.

After you are done, you should be passing the incr_items, incr_items_four, and items_sorted tests in tests/stats.rs.

Thread safety

Your web server may be handling several requests at a time, so it is important that you update the counters in Stats in a thread-safe manner. The stats::Stats::incr method you wrote in the previous section should not have been thread-safe. In other words, concurrent execution of your previous stats::Stats::incr method would result in undefined behavior.

To ensure that only one thread can modify the counters at a time, we’ll wrap our Stats struct in a tokio::sync::RwLock (not to be confused with std::sync::RwLock). When we acquire the lock, we’ll be able to modify the contents of the inner Stats struct. Rust will ensure that we do not modify the Stats struct unless we have acquired the lock.

Since multiple threads need to have ownership of the RwLock wrapping Stats, we need to wrap both of these in an Arc (atomic reference count). See here for a longer discussion on why this is necessary.

To avoid having to think about all this repeatedly, we define a new type, which we call StatsPtr:

pub type StatsPtr = Arc<RwLock<Stats>>;

A StatsPtr is an atomically reference counted read-write lock guarding a single Stats struct. Using an instance of StatsPtr, we can modify the inner Stats struct safely from multiple threads.

This specific example from the documentation may come in handy for understanding how an Arc<RwLock<_>> can be used. Keep in mind that this example isn’t intended to tell you which method(s) to call, but rather to show you how to interact with an Arc<RwLock<_>>. You will need to look through the documentation for the appropriate method(s) yourself.

Fill in the stats::incr method in src/stats.rs (this is the one not inside the impl block). Your implementation should call the stats::Stats::incr method you wrote previously.

After you finish implementing stats::incr, the incr_stats_ptr test in tests/stats.rs should pass.

Integration (optional)

This part is optional and not worth any additional credit.

Collecting statistics

Now integrate your stats collection routines into your file/directory serving logic. How to do this depends on how your code is structured, but you will likely want to do the following:

  1. Initialize a new StatsPtr instance in server::main.
  2. Pass an Arc::clone‘d StatsPtr to your file/directory serving routines.
  3. Increment the appropriate status code in your file/directory serving routines whenever a response is returned.

If you prefer to use global variables instead of passing around references, you may use the lazy_static crate.

Displaying statistics

The last step is to display all the statistics you’ve collected! Modify your web server so that when someone visits the /stats URL, you display all non-zero counts next to the corresponding HTTP messages. For example, your statistics page should show:

OK: 7
Not Found: 2

Do not include to requests to /stats in your statistics. That is, a visit to /stats should not change the counts for any status code. You should use the stats::Stats::items method provided to you. This will make our autograder happy by ordering response codes consistently. Note: you won’t be able to call this directly, since your Stats instance should be guarded by the lock in StatsPtr.

Set the Content-Type header to text/plain and return a 200 OK status code (unless there was some sort of error). You do not need to provide the Content-Length header. Each line should be formatted as {response_message}: {count}. To get the response message from the status code, you may find the http::response_message function useful.