GithubHelp home page GithubHelp logo

logging_timer's Introduction

Logging Timers for Rust

This crate provides a couple of simple timers that log messages indicating the elapsed time between their creation and dropping. Messages are output via the log crate.

Timers have names, and the log messages are constructed in such a way that they contain the module, filename and line number of the place where the timer was constructed.

Using the Timer Attributes

The simplest way to get started is to use one of the two attributes time or stime to instrument a function, the name of the function is used as the name of the timer:

use logging_timer::{time, stime};

#[time]
fn find_files(dir: PathBuf) -> Vec<PathBuf> {
    let files = vec![];

    // expensive operation here

    return files;
} // 'TimerFinished' message is logged here

Both attributes accept two optional arguments, to specify the log level and a pattern into which the name of the function will be substituted. The pattern is helpful to disambiguate functions when you have many in the same module with the same name: new might occur many times on different structs, for example.

#[time]                                 // Use default log level of Debug
#[time("info")]                         // Set custom log level
#[time("info", "FirstStruct::{}")]      // Logs "FirstStruct::new()" at Info
#[time("info", "SecondStruct::{}")]     // Logs "SecondStruct::new()" at Info
#[time("ThirdStruct::{}")]              // Logs "ThirdStruct::new()" at Debug
#[time("never")]                        // Turn off instrumentation at compile time

Valid values for the log level are error, warn, info, debug and trace. Debug is the default. You can also specify 'never' to completely disable the instrumentation at compile time. The log level should appear first but as shown above can be omitted. The macros distinguish the log level from the pattern by looking for "{}".

Using the Inline Timers

More flexibility, including logging extra information, is provided by the two function-like macro forms, timer! and stimer!. The difference is that timer! returns a timer that logs a message only when it is dropped, while stimer! returns a timer that logs a started message as soon as it is created, and a finished message when it is dropped. There are also two corresponding proc-macros called time and stimer which can be used to instrument functions with a timer.

In this example "FIND_FILES" is the name of the timer (using all UPPERCASE for the timer name is optional but helps make the name stand out in the log)

use logging_timer::{timer};

fn find_files(dir: PathBuf) -> Vec<PathBuf> {
    let _tmr = timer!("FIND_FILES");
    let files = vec![];

    // expensive operation here

    return files;
} // _tmr is dropped here and a 'TimerFinished' message is logged

You can replace timer! with stimer! to get a timer that logs a starting message as well, giving you a pair of 'bracketing' log messages.

In addition, both timer macros accept format_args! style parameters, allowing you to include extra information in the log messages.

let _tmr = timer!("FIND_FILES", "Directory = {}", dir);

Outputting Intermediate Messages

The executing! macro allows you to make the timer produce a message before it is dropped. You can call it as many times as you want. A pseudocode example:

use logging_timer::{timer, executing};

fn find_files(dir: PathBuf) -> Vec<PathBuf> {
    let tmr = timer!("FIND_FILES");
    let files = vec![];

    for dir in sub_dirs(dir) {
        // expensive operation
        executing!(tmr, "Processed {}", dir);
    }

    return files;
} // tmr is dropped here and a 'TimerFinished' message is logged

Controlling the Final Message

The finish! macro also makes the timer log a message, but it also has the side effect of suppressing the normal drop message. finish! is useful when you want the final message to include some information that you did not have access to until the calculation had finished.

use logging_timer::{timer, executing, finish};

fn find_files(dir: PathBuf) -> Vec<PathBuf> {
    let tmr = timer!("FIND_FILES");
    let files = vec![];

    finish!(tmr, "Found {} files", files.len());
    return files;
} // tmr is dropped here but no message is produced.

Setting the log level

By default both timer and stimer log at Debug level. An optional first parameter to these macros allows you to set the log level. To aid parsing of the macro arguments this first parameter is terminated by a semi-colon. For example:

let tmr1 = timer!(Level::Warn; "TIMER_AT_WARN");
let tmr2 = stimer!(Level::Info; "TIMER_AT_INFO");

Example of Timer Output

The overall format will depend on how you customize the output format of the log crate, but as an illustrative example:

2019-05-30T21:41:41.847982550Z DEBUG [TimerStarting] [dnscan/src/main.rs/63] DIRECTORY_ANALYSIS
2019-05-30T21:41:41.868690703Z INFO [dnlib::configuration] [dnlib/src/configuration.rs/116] Loaded configuration from "/home/phil/.dnscan/.dnscan.json"
2019-05-30T21:41:41.897609281Z DEBUG [TimerFinished] [dnlib/src/io.rs/67] FIND_FILES, Elapsed=28.835275ms, Dir="/home/phil/mydotnetprojects", NumSolutions=1 NumCsproj=45, NumOtherFiles=12
2019-05-30T21:41:41.955140835Z DEBUG [TimerFinished] [dnlib/src/analysis.rs/93] LOAD_SOLUTIONS, Elapsed=57.451736ms
2019-05-30T21:41:42.136762196Z DEBUG [TimerFinished] [dnlib/src/analysis.rs/108] LOAD_PROJECTS, Elapsed=181.563223ms, Found 43 linked projects and 2 orphaned projects
2019-05-30T21:41:42.136998556Z DEBUG [TimerStarting] [dnscan/src/main.rs/87] CALCULATE_PROJECT_GRAPH
2019-05-30T21:41:42.143072972Z DEBUG [TimerExecuting] [dnscan/src/main.rs/87] CALCULATE_PROJECT_GRAPH, Elapsed=6.075205ms, Individual graphs done
2019-05-30T21:41:42.149218039Z DEBUG [TimerFinished] [dnscan/src/main.rs/87] CALCULATE_PROJECT_GRAPH, Elapsed=12.219438ms, Found 19 redundant project relationships
2019-05-30T21:41:42.165724712Z DEBUG [TimerFinished] [dnscan/src/main.rs/108] WRITE_OUTPUT_FILES, Elapsed=16.459312ms
2019-05-30T21:41:42.166445Z INFO [TimerFinished] [dnscan/src/main.rs/63] DIRECTORY_ANALYSIS, Elapsed=318.48581ms

Here the [Timer*] blocks are the target field from log's Record struct and [dnscan/src/main.rs/63] is the filename and number from Record - this captures the place where the timer was instantiated. The module is also set, but is not shown in these examples.

What is Measured?

The time that is logged is the wall-clock time - the difference between the start of execution of the function and the drop of the timer at the end of the function.

The wall clock time is not necessarily length of time that the function spent executing on the CPU. This is true for a normal function due to thread pre-emption, but it especially true for async functions which may spend a large part of their lives suspended.

For more sophisticated usage you may want to look into the Tokio Tracing crate.

Code Examples

There is also an example program in the examples folder which demonstrates all the different usages. To run, clone the repository and in Linux do

RUST_LOG=debug cargo run --example logging_demo

If you're on Windows, in PowerShell you can do

$env:RUST_LOG="debug"
cargo run --example logging_demo

History

See the CHANGELOG.

Performance

The timer and stimer macros return an Option<LoggingTimer>. The method log_enabled is used to check whether logging is enabled at the requested level. If logging is not enabled then None is returned. This avoids most calculation in the case where the timer would be a no-op, such that the following loop will create and drop 1 million timers in about 4ms on my 2012-era Intel i7.

for _ in 0..1_000_000 {
    let _tmr = stimer!("TEMP");
}

In comparison, v0.3 of the library would always return a LoggingTimer, and the loop took ten times longer.

An Option<LoggingTimer> is 104 bytes in size on 64-bit Linux.

logging_timer's People

Contributors

phil-landmark avatar philipdaniels avatar s0c5 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

logging_timer's Issues

`time` macro should work with async functions

The macro sould work with async functions.

Given an async function

async func test(param1: &str) -> String {
  ...
  let _  another_function().await
}

When using the macro:

#[logging_timer::time("info")]
async func test(param1: &str) -> String {
  ...
  let _  another_function().await
}

The following error message is returned:

error: `await` is only allowed inside `async` functions and blocks
label: this is not `async`

That means, the resulting funciton, after macro rewrite, is not async.

The #[time] macro seems to strip the "unsafe" keyword from a decorated function

If I write an unsafe function and apply the #[time] decorator macro:

#[time]
pub unsafe fn do_something_risky() { ... }

I am now able to call this function outside of an unsafe block, e.g.

fn main() {
    do_something_risky();
}

Whereas I would expect to have to write:

fn main() {
    unsafe { 
        do_something_risky();
    }
}

If do_something_risky() needs to be unsafe to call other unsafe functions, then fortunately this generates a compiler error, but for other unsafe behaviour that the compiler can't know about, for example multi-threading invariants documented by the programmer, this is pretty serious, because there's currently no way for the programmer to require unsafe when calling such a decorated function.

A workaround is to create an unsafe wrapper function, so that #[time] is only on the inner function, but this is quite messy.

Every timer should have a unique id

Every timer should be given a unique Id and this should be output in the log. This will allow start/end messages to be correlated. It can be achieved simply by using an AtomicUsize.

fn new() -> Self {
        static ID: AtomicUsize = AtomicUsize::new(0);

        Self {
            id: ID.fetch_add(1, Ordering::SeqCst)
        }
    }

#[time]-decorated functions are flagged about not having documentation

If you use warn(missing_docs) in your project, then attempting to use #[time] or #[stime] on a pub function results in this warning:

   Compiling logging_timer_issue v0.1.0 (/Users/wkhan/workspace/logging_timer_issue)
warning: missing documentation for a function
 --> src/main.rs:8:1
  |
8 | #[stime]
  | ^^^^^^^^
  |
note: the lint level is defined here
 --> src/main.rs:3:9
  |
3 | #![warn(missing_docs)]
  |         ^^^^^^^^^^^^
  = note: this warning originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)

warning: 1 warning emitted

    Finished dev [unoptimized + debuginfo] target(s) in 0.31s

Repro code:

//! This crate has documentation.

#![warn(missing_docs)]

use logging_timer::stime;

/// This function has documentation, but gets flagged anyways!
#[stime]
pub fn time_me() {
    println!("Hello, world!");
}

fn main() {
    time_me();
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.