summaryrefslogtreecommitdiff
path: root/yjit/src/stats.rs
diff options
context:
space:
mode:
Diffstat (limited to 'yjit/src/stats.rs')
-rw-r--r--yjit/src/stats.rs182
1 files changed, 118 insertions, 64 deletions
diff --git a/yjit/src/stats.rs b/yjit/src/stats.rs
index 0a63fab8b0..c49f8af553 100644
--- a/yjit/src/stats.rs
+++ b/yjit/src/stats.rs
@@ -10,8 +10,6 @@ use std::time::Instant;
use std::collections::HashMap;
use crate::codegen::CodegenGlobals;
-use crate::core::Context;
-use crate::core::for_each_iseq_payload;
use crate::cruby::*;
use crate::options::*;
use crate::yjit::yjit_enabled_p;
@@ -268,16 +266,19 @@ macro_rules! make_counters {
/// The list of counters that are available without --yjit-stats.
/// They are incremented only by `incr_counter!` and don't use `gen_counter_incr`.
-pub const DEFAULT_COUNTERS: [Counter; 16] = [
+pub const DEFAULT_COUNTERS: &'static [Counter] = &[
Counter::code_gc_count,
Counter::compiled_iseq_entry,
Counter::cold_iseq_entry,
Counter::compiled_iseq_count,
Counter::compiled_blockid_count,
Counter::compiled_block_count,
+ Counter::deleted_defer_block_count,
Counter::compiled_branch_count,
Counter::compile_time_ns,
Counter::max_inline_versions,
+ Counter::num_contexts_encoded,
+ Counter::context_cache_hits,
Counter::invalidation_count,
Counter::invalidate_method_lookup,
@@ -286,6 +287,7 @@ pub const DEFAULT_COUNTERS: [Counter; 16] = [
Counter::invalidate_constant_state_bump,
Counter::invalidate_constant_ic_fill,
Counter::invalidate_no_singleton_class,
+ Counter::invalidate_ep_escape,
];
/// Macro to increase a counter by name and count
@@ -376,6 +378,7 @@ make_counters! {
send_iseq_block_arg_type,
send_iseq_clobbering_block_arg,
send_iseq_complex_discard_extras,
+ send_iseq_forwarding,
send_iseq_leaf_builtin_block_arg_block_param,
send_iseq_kw_splat_non_nil,
send_iseq_kwargs_mismatch,
@@ -383,6 +386,7 @@ make_counters! {
send_iseq_has_no_kw,
send_iseq_accepts_no_kwarg,
send_iseq_materialized_block,
+ send_iseq_send_forwarding,
send_iseq_splat_not_array,
send_iseq_splat_with_kw,
send_iseq_missing_optional_kw,
@@ -412,6 +416,7 @@ make_counters! {
send_optimized_block_arg,
invokesuper_defined_class_mismatch,
+ invokesuper_forwarding,
invokesuper_kw_splat,
invokesuper_kwarg,
invokesuper_megamorphic,
@@ -448,6 +453,7 @@ make_counters! {
guard_send_instance_of_class_mismatch,
guard_send_interrupted,
guard_send_not_fixnums,
+ guard_send_not_fixnum,
guard_send_not_fixnum_or_flonum,
guard_send_not_string,
guard_send_respond_to_mid_mismatch,
@@ -553,9 +559,11 @@ make_counters! {
block_next_count,
defer_count,
defer_empty_count,
+ deleted_defer_block_count,
branch_insn_count,
branch_known_count,
max_inline_versions,
+ num_contexts_encoded,
freed_iseq_count,
@@ -568,6 +576,7 @@ make_counters! {
invalidate_constant_state_bump,
invalidate_constant_ic_fill,
invalidate_no_singleton_class,
+ invalidate_ep_escape,
// Currently, it's out of the ordinary (might be impossible) for YJIT to leave gaps in
// executable memory, so this should be 0.
@@ -609,6 +618,8 @@ make_counters! {
temp_reg_opnd,
temp_mem_opnd,
temp_spill,
+
+ context_cache_hits,
}
//===========================================================================
@@ -639,8 +650,8 @@ pub extern "C" fn rb_yjit_print_stats_p(_ec: EcPtr, _ruby_self: VALUE) -> VALUE
/// Primitive called in yjit.rb.
/// Export all YJIT statistics as a Ruby hash.
#[no_mangle]
-pub extern "C" fn rb_yjit_get_stats(_ec: EcPtr, _ruby_self: VALUE, context: VALUE) -> VALUE {
- with_vm_lock(src_loc!(), || rb_yjit_gen_stats_dict(context == Qtrue))
+pub extern "C" fn rb_yjit_get_stats(_ec: EcPtr, _ruby_self: VALUE, key: VALUE) -> VALUE {
+ with_vm_lock(src_loc!(), || rb_yjit_gen_stats_dict(key))
}
/// Primitive called in yjit.rb
@@ -699,21 +710,40 @@ pub extern "C" fn rb_yjit_incr_counter(counter_name: *const std::os::raw::c_char
}
/// Export all YJIT statistics as a Ruby hash.
-fn rb_yjit_gen_stats_dict(context: bool) -> VALUE {
+fn rb_yjit_gen_stats_dict(key: VALUE) -> VALUE {
// If YJIT is not enabled, return Qnil
if !yjit_enabled_p() {
return Qnil;
}
- macro_rules! hash_aset_usize {
- ($hash:ident, $counter_name:expr, $value:expr) => {
- let key = rust_str_to_sym($counter_name);
- let value = VALUE::fixnum_from_usize($value);
- rb_hash_aset($hash, key, value);
+ let hash = if key == Qnil {
+ unsafe { rb_hash_new() }
+ } else {
+ Qnil
+ };
+
+ macro_rules! set_stat {
+ ($hash:ident, $name:expr, $value:expr) => {
+ let rb_key = rust_str_to_sym($name);
+ if key == rb_key {
+ return $value;
+ } else if hash != Qnil {
+ rb_hash_aset($hash, rb_key, $value);
+ }
}
}
- let hash = unsafe { rb_hash_new() };
+ macro_rules! set_stat_usize {
+ ($hash:ident, $name:expr, $value:expr) => {
+ set_stat!($hash, $name, VALUE::fixnum_from_usize($value));
+ }
+ }
+
+ macro_rules! set_stat_double {
+ ($hash:ident, $name:expr, $value:expr) => {
+ set_stat!($hash, $name, rb_float_new($value));
+ }
+ }
unsafe {
// Get the inline and outlined code blocks
@@ -721,41 +751,39 @@ fn rb_yjit_gen_stats_dict(context: bool) -> VALUE {
let ocb = CodegenGlobals::get_outlined_cb();
// Inline code size
- hash_aset_usize!(hash, "inline_code_size", cb.code_size());
+ set_stat_usize!(hash, "inline_code_size", cb.code_size());
// Outlined code size
- hash_aset_usize!(hash, "outlined_code_size", ocb.unwrap().code_size());
+ set_stat_usize!(hash, "outlined_code_size", ocb.unwrap().code_size());
// GCed pages
let freed_page_count = cb.num_freed_pages();
- hash_aset_usize!(hash, "freed_page_count", freed_page_count);
+ set_stat_usize!(hash, "freed_page_count", freed_page_count);
// GCed code size
- hash_aset_usize!(hash, "freed_code_size", freed_page_count * cb.page_size());
+ set_stat_usize!(hash, "freed_code_size", freed_page_count * cb.page_size());
// Live pages
- hash_aset_usize!(hash, "live_page_count", cb.num_mapped_pages() - freed_page_count);
+ set_stat_usize!(hash, "live_page_count", cb.num_mapped_pages() - freed_page_count);
// Size of memory region allocated for JIT code
- hash_aset_usize!(hash, "code_region_size", cb.mapped_region_size());
+ set_stat_usize!(hash, "code_region_size", cb.mapped_region_size());
// Rust global allocations in bytes
- hash_aset_usize!(hash, "yjit_alloc_size", GLOBAL_ALLOCATOR.alloc_size.load(Ordering::SeqCst));
-
- // `context` is true at RubyVM::YJIT._print_stats for --yjit-stats. It's false by default
- // for RubyVM::YJIT.runtime_stats because counting all Contexts could be expensive.
- if context {
- let live_context_count = get_live_context_count();
- let context_size = std::mem::size_of::<Context>();
- hash_aset_usize!(hash, "live_context_count", live_context_count);
- hash_aset_usize!(hash, "live_context_size", live_context_count * context_size);
- }
+ set_stat_usize!(hash, "yjit_alloc_size", GLOBAL_ALLOCATOR.alloc_size.load(Ordering::SeqCst));
+
+ // How many bytes we are using to store context data
+ let context_data = CodegenGlobals::get_context_data();
+ set_stat_usize!(hash, "context_data_bytes", context_data.num_bytes());
+ set_stat_usize!(hash, "context_cache_bytes", crate::core::CTX_ENCODE_CACHE_BYTES + crate::core::CTX_DECODE_CACHE_BYTES);
// VM instructions count
- hash_aset_usize!(hash, "vm_insns_count", rb_vm_insns_count as usize);
+ set_stat_usize!(hash, "vm_insns_count", rb_vm_insns_count as usize);
+
+ set_stat_usize!(hash, "live_iseq_count", rb_yjit_live_iseq_count as usize);
+ set_stat_usize!(hash, "iseq_alloc_count", rb_yjit_iseq_alloc_count as usize);
- hash_aset_usize!(hash, "live_iseq_count", rb_yjit_live_iseq_count as usize);
- hash_aset_usize!(hash, "iseq_alloc_count", rb_yjit_iseq_alloc_count as usize);
+ set_stat!(hash, "object_shape_count", rb_object_shape_count());
}
// If we're not generating stats, put only default counters
@@ -766,9 +794,9 @@ fn rb_yjit_gen_stats_dict(context: bool) -> VALUE {
let counter_val = unsafe { *counter_ptr };
// Put counter into hash
- let key = rust_str_to_sym(&counter.get_name());
+ let key = &counter.get_name();
let value = VALUE::fixnum_from_usize(counter_val as usize);
- unsafe { rb_hash_aset(hash, key, value); }
+ unsafe { set_stat!(hash, key, value); }
}
return hash;
@@ -776,74 +804,100 @@ fn rb_yjit_gen_stats_dict(context: bool) -> VALUE {
unsafe {
// Indicate that the complete set of stats is available
- rb_hash_aset(hash, rust_str_to_sym("all_stats"), Qtrue);
+ set_stat!(hash, "all_stats", Qtrue);
// For each counter we track
for counter_name in COUNTER_NAMES {
// Get the counter value
let counter_ptr = get_counter_ptr(counter_name);
let counter_val = *counter_ptr;
-
- // Put counter into hash
- let key = rust_str_to_sym(counter_name);
- let value = VALUE::fixnum_from_usize(counter_val as usize);
- rb_hash_aset(hash, key, value);
+ set_stat_usize!(hash, counter_name, counter_val as usize);
}
+ let mut side_exits = 0;
+
// For each entry in exit_op_count, add a stats entry with key "exit_INSTRUCTION_NAME"
// and the value is the count of side exits for that instruction.
for op_idx in 0..VM_INSTRUCTION_SIZE_USIZE {
let op_name = insn_name(op_idx);
let key_string = "exit_".to_owned() + &op_name;
- let key = rust_str_to_sym(&key_string);
- let value = VALUE::fixnum_from_usize(EXIT_OP_COUNT[op_idx] as usize);
- rb_hash_aset(hash, key, value);
+ let count = EXIT_OP_COUNT[op_idx];
+ side_exits += count;
+ set_stat_usize!(hash, &key_string, count as usize);
}
+ set_stat_usize!(hash, "side_exit_count", side_exits as usize);
+
+ let total_exits = side_exits + *get_counter_ptr(&Counter::leave_interp_return.get_name());
+ set_stat_usize!(hash, "total_exit_count", total_exits as usize);
+
+ // Number of instructions that finish executing in YJIT.
+ // See :count-placement: about the subtraction.
+ let retired_in_yjit = *get_counter_ptr(&Counter::yjit_insns_count.get_name()) - side_exits;
+
+ // Average length of instruction sequences executed by YJIT
+ let avg_len_in_yjit: f64 = if total_exits > 0 {
+ retired_in_yjit as f64 / total_exits as f64
+ } else {
+ 0_f64
+ };
+ set_stat_double!(hash, "avg_len_in_yjit", avg_len_in_yjit);
+
+ // Proportion of instructions that retire in YJIT
+ let total_insns_count = retired_in_yjit + rb_vm_insns_count;
+ set_stat_usize!(hash, "total_insns_count", total_insns_count as usize);
+
+ let ratio_in_yjit: f64 = 100.0 * retired_in_yjit as f64 / total_insns_count as f64;
+ set_stat_double!(hash, "ratio_in_yjit", ratio_in_yjit);
+
+ // Set method call counts in a Ruby dict
fn set_call_counts(
calls_hash: VALUE,
method_name_to_idx: &mut Option<HashMap<String, usize>>,
method_call_count: &mut Option<Vec<u64>>,
) {
if let (Some(name_to_idx), Some(call_counts)) = (method_name_to_idx, method_call_count) {
+ // Create a list of (name, call_count) pairs
+ let mut pairs = Vec::new();
for (name, idx) in name_to_idx {
let count = call_counts[*idx];
+ pairs.push((name, count));
+ }
+
+ // Sort the vectors by decreasing call counts
+ pairs.sort_by_key(|e| -(e.1 as i64));
+
+ // Cap the number of counts reported to avoid
+ // bloating log files, etc.
+ pairs.truncate(20);
+
+ // Add the pairs to the dict
+ for (name, call_count) in pairs {
let key = rust_str_to_sym(name);
- let value = VALUE::fixnum_from_usize(count as usize);
+ let value = VALUE::fixnum_from_usize(call_count as usize);
unsafe { rb_hash_aset(calls_hash, key, value); }
}
}
}
// Create a hash for the cfunc call counts
- let cfunc_calls = rb_hash_new();
- rb_hash_aset(hash, rust_str_to_sym("cfunc_calls"), cfunc_calls);
- set_call_counts(cfunc_calls, &mut *addr_of_mut!(CFUNC_NAME_TO_IDX), &mut *addr_of_mut!(CFUNC_CALL_COUNT));
+ set_stat!(hash, "cfunc_calls", {
+ let cfunc_calls = rb_hash_new();
+ set_call_counts(cfunc_calls, &mut *addr_of_mut!(CFUNC_NAME_TO_IDX), &mut *addr_of_mut!(CFUNC_CALL_COUNT));
+ cfunc_calls
+ });
// Create a hash for the ISEQ call counts
- let iseq_calls = rb_hash_new();
- rb_hash_aset(hash, rust_str_to_sym("iseq_calls"), iseq_calls);
- set_call_counts(iseq_calls, &mut *addr_of_mut!(ISEQ_NAME_TO_IDX), &mut *addr_of_mut!(ISEQ_CALL_COUNT));
+ set_stat!(hash, "iseq_calls", {
+ let iseq_calls = rb_hash_new();
+ set_call_counts(iseq_calls, &mut *addr_of_mut!(ISEQ_NAME_TO_IDX), &mut *addr_of_mut!(ISEQ_CALL_COUNT));
+ iseq_calls
+ });
}
hash
}
-fn get_live_context_count() -> usize {
- let mut count = 0;
- for_each_iseq_payload(|iseq_payload| {
- for blocks in iseq_payload.version_map.iter() {
- for block in blocks.iter() {
- count += unsafe { block.as_ref() }.get_ctx_count();
- }
- }
- for block in iseq_payload.dead_blocks.iter() {
- count += unsafe { block.as_ref() }.get_ctx_count();
- }
- });
- count
-}
-
/// Record the backtrace when a YJIT exit occurs. This functionality requires
/// that the stats feature is enabled as well as the --yjit-trace-exits option.
///