diff options
Diffstat (limited to 'yjit/src/stats.rs')
-rw-r--r-- | yjit/src/stats.rs | 182 |
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. /// |