//! An example that determines the time stamp counter frequency (RDTSC, RDTSCP).
//!
//! This example only compiles on x86 platforms.
extern crate raw_cpuid;

use std::time;

const MHZ_TO_HZ: u64 = 1000000;
const KHZ_TO_HZ: u64 = 1000;

#[cfg(target_arch = "x86_64")]
#[rustversion::nightly]
use core::arch::x86_64::_rdtsc as rdtsc;

#[cfg(target_arch = "x86")]
#[rustversion::nightly]
use core::arch::x86::_rdtsc as rdtsc;

#[rustversion::not(nightly)]
unsafe fn rdtsc() -> u64 {
    0
}

fn main() {
    let cpuid = raw_cpuid::CpuId::new();
    let has_tsc = cpuid
        .get_feature_info()
        .map_or(false, |finfo| finfo.has_tsc());

    let has_invariant_tsc = cpuid
        .get_advanced_power_mgmt_info()
        .map_or(false, |efinfo| efinfo.has_invariant_tsc());

    let tsc_frequency_hz = cpuid.get_tsc_info().map(|tinfo| {
        if tinfo.nominal_frequency() != 0 {
            tinfo.tsc_frequency()
        } else if tinfo.numerator() != 0 && tinfo.denominator() != 0 {
            // Skylake and Kabylake don't report the crystal clock, approximate with base frequency:
            cpuid
                .get_processor_frequency_info()
                .map(|pinfo| pinfo.processor_base_frequency() as u64 * MHZ_TO_HZ)
                .map(|cpu_base_freq_hz| {
                    let crystal_hz =
                        cpu_base_freq_hz * tinfo.denominator() as u64 / tinfo.numerator() as u64;
                    crystal_hz * tinfo.numerator() as u64 / tinfo.denominator() as u64
                })
        } else {
            None
        }
    });

    if has_tsc {
        // Try to figure out TSC frequency with CPUID
        println!(
            "TSC Frequency is: {} ({})",
            match tsc_frequency_hz {
                Some(x) => format!("{} Hz", x.unwrap_or(0)),
                None => String::from("unknown"),
            },
            if has_invariant_tsc {
                "invariant"
            } else {
                "TSC frequency varies with speed-stepping"
            }
        );

        // Check if we run in a VM and the hypervisor can give us the TSC frequency
        cpuid.get_hypervisor_info().map(|hv| {
            hv.tsc_frequency().map(|tsc_khz| {
                let virtual_tsc_frequency_hz = tsc_khz as u64 * KHZ_TO_HZ;
                println!(
                    "Hypervisor reports TSC Frequency at: {} Hz",
                    virtual_tsc_frequency_hz
                );
            })
        });

        // Determine TSC frequency by measuring it (loop for a second, record ticks)
        let one_second = time::Duration::from_secs(1);
        let now = time::Instant::now();
        let start = unsafe { rdtsc() };
        if start > 0 {
            loop {
                if now.elapsed() >= one_second {
                    break;
                }
            }
            let end = unsafe { rdtsc() };
            println!(
                "Empirical measurement of TSC frequency was: {} Hz",
                (end - start)
            );
        } else {
            // Don't have rdtsc on stable!
        }
    } else {
        println!("System does not have a TSC.")
    }
}
