Hone logo
Hone
Problems

Rust Instrumentation Library

Instrumentation is crucial for understanding the performance and behavior of software. This challenge asks you to create a simple instrumentation library in Rust that allows developers to track function execution times and log messages with configurable levels. This library will provide a foundation for more complex monitoring and debugging tools.

Problem Description

You are tasked with building a basic instrumentation library named rust_instrumentation. This library should provide the following functionalities:

  1. Function Timing: A macro timeit! that measures and logs the execution time of a given function. The macro should accept a function as an argument and print the function name and its execution time to the console.
  2. Logging: A Logger struct with a log method. The log method should accept a log level (e.g., INFO, DEBUG, ERROR) and a message. The logger should only print messages with a log level equal to or lower than a configured level.
  3. Configurable Log Level: The Logger struct should have a set_level method to change the configured log level.

Key Requirements:

  • The timeit! macro should be easy to use and minimally intrusive.
  • The Logger should provide a clear and concise interface for logging messages.
  • The log level configuration should be flexible and allow for different levels of verbosity.
  • The library should be thread-safe.

Expected Behavior:

  • When timeit! is used, the execution time of the enclosed function should be measured accurately and printed to the console in a readable format (e.g., "Function 'my_function' took 1.234ms").
  • The Logger should only print messages with a log level that is less than or equal to the configured log level.
  • Calling set_level should update the log level and affect subsequent log messages.

Edge Cases to Consider:

  • Functions with very short execution times (measurements might be inaccurate).
  • Functions that panic. The timeit! macro should handle panics gracefully (e.g., by printing an error message).
  • Concurrent access to the Logger from multiple threads.

Examples

Example 1:

// Assuming rust_instrumentation is added as a dependency
use rust_instrumentation::{timeit, Logger};

#[timeit]
fn my_function() -> i32 {
    let mut sum = 0;
    for i in 1..=100000 {
        sum += i;
    }
    sum
}

fn main() {
    let logger = Logger::new();
    logger.set_level(rust_instrumentation::LogLevel::Debug);
    logger.log(rust_instrumentation::LogLevel::Info, "Starting program");
    logger.log(rust_instrumentation::LogLevel::Debug, "Debug message");
    logger.log(rust_instrumentation::LogLevel::Error, "An error occurred");

    my_function();
}

Output:

Function 'my_function' took 1.234ms
Starting program
Debug message
An error occurred

Explanation: my_function's execution time is printed. The logger prints all messages because the level is set to Debug.

Example 2:

use rust_instrumentation::{timeit, Logger};

#[timeit]
fn panicking_function() {
    panic!("This function panics!");
}

fn main() {
    let logger = Logger::new();
    logger.set_level(rust_instrumentation::LogLevel::Info);
    panicking_function();
}

Output:

Function 'panicking_function' panicked!

Explanation: The timeit! macro still measures the time before the panic and prints a message indicating the panic.

Constraints

  • Performance: The instrumentation should have minimal overhead on the target code. Avoid unnecessary allocations or complex operations within the timeit! macro.
  • Input Format: The log level should be an enum with at least INFO, DEBUG, and ERROR variants.
  • Thread Safety: The Logger must be thread-safe, allowing multiple threads to log messages concurrently without data races. Use appropriate synchronization primitives (e.g., Mutex).
  • Dependencies: You are allowed to use the standard library and external crates for timing (e.g., std::time) and synchronization (e.g., std::sync::Mutex). Avoid using complex logging frameworks.

Notes

  • Consider using procedural macros to implement the timeit! macro.
  • The Logger struct can use a Mutex to protect its internal state from concurrent access.
  • Focus on the core functionality of timing and logging. Error handling and advanced features (e.g., configurable output format) are not required for this challenge.
  • Think about how to handle panics within the timed function gracefully. You don't need to recover from the panic, but you should ensure that the instrumentation doesn't crash the program.
  • The timeit! macro should not modify the original function's signature or behavior beyond measuring its execution time.
Loading editor...
rust