use std::collections::HashMap;
use std::convert::TryInto;
use std::fmt::Display;
use bitflags::bitflags;
use fxprof_processed_profile::*;
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
use super::elevated_helper::ElevatedRecordingProps;
use crate::shared::recording_props::{CoreClrProfileProps, ProfileCreationProps};
use crate::windows::profile_context::{KnownCategory, ProfileContext};
use super::etw_reader::event_properties_to_string;
use super::etw_reader::parser::{Parser, TryParse};
use super::etw_reader::schema::TypedEvent;
struct SavedMarkerInfo {
start_timestamp_raw: u64,
name: String,
description: String,
}
pub struct CoreClrContext {
props: CoreClrProfileProps,
last_marker_on_thread: HashMap<u32, (ThreadHandle, MarkerHandle)>,
gc_markers_on_thread: HashMap<u32, HashMap<&'static str, SavedMarkerInfo>>,
unknown_event_markers: bool,
}
impl CoreClrContext {
pub fn new(profile_creation_props: ProfileCreationProps) -> Self {
Self {
props: profile_creation_props.coreclr,
last_marker_on_thread: HashMap::new(),
gc_markers_on_thread: HashMap::new(),
unknown_event_markers: profile_creation_props.unknown_event_markers,
}
}
fn remove_last_event_for_thread(&mut self, tid: u32) -> Option<(ThreadHandle, MarkerHandle)> {
self.last_marker_on_thread.remove(&tid)
}
fn set_last_event_for_thread(&mut self, tid: u32, thread_marker: (ThreadHandle, MarkerHandle)) {
self.last_marker_on_thread.insert(tid, thread_marker);
}
fn save_gc_marker(
&mut self,
tid: u32,
start_timestamp_raw: u64,
event: &'static str,
name: String,
description: String,
) {
self.gc_markers_on_thread.entry(tid).or_default().insert(
event,
SavedMarkerInfo {
start_timestamp_raw,
name,
description,
},
);
}
fn remove_gc_marker(&mut self, tid: u32, event: &str) -> Option<SavedMarkerInfo> {
self.gc_markers_on_thread
.get_mut(&tid)
.and_then(|m| m.remove(event))
}
}
bitflags! {
#[derive(PartialEq, Eq)]
pub struct CoreClrMethodFlagsMap: u32 {
const dynamic = 0x1;
const generic = 0x2;
const has_shared_generic_code = 0x4;
const jitted = 0x8;
const jit_helper = 0x10;
const profiler_rejected_precompiled_code = 0x20;
const ready_to_run_rejected_precompiled_code = 0x40;
const opttier_bit0 = 0x80;
const opttier_bit1 = 0x100;
const opttier_bit2 = 0x200;
const extent_bit_0 = 0x10000000; const extent_bit_1 = 0x20000000; const extent_bit_2 = 0x40000000;
const extent_bit_3 = 0x80000000;
const _ = !0;
}
#[derive(PartialEq, Eq)]
pub struct TieredCompilationSettingsMap: u32 {
const None = 0x0;
const QuickJit = 0x1;
const QuickJitForLoops = 0x2;
const TieredPGO = 0x4;
const ReadyToRun = 0x8;
}
}
#[allow(unused)]
mod constants {
pub const CORECLR_GC_KEYWORD: u64 = 0x1; pub const CORECLR_GC_HANDLE_KEYWORD: u64 = 0x2;
pub const CORECLR_BINDER_KEYWORD: u64 = 0x4; pub const CORECLR_LOADER_KEYWORD: u64 = 0x8; pub const CORECLR_JIT_KEYWORD: u64 = 0x10; pub const CORECLR_NGEN_KEYWORD: u64 = 0x20; pub const CORECLR_RUNDOWN_START_KEYWORD: u64 = 0x00000040;
pub const CORECLR_INTEROP_KEYWORD: u64 = 0x2000; pub const CORECLR_CONTENTION_KEYWORD: u64 = 0x4000;
pub const CORECLR_EXCEPTION_KEYWORD: u64 = 0x8000; pub const CORECLR_THREADING_KEYWORD: u64 = 0x10000; pub const CORECLR_JIT_TO_NATIVE_METHOD_MAP_KEYWORD: u64 = 0x20000;
pub const CORECLR_GC_SAMPLED_OBJECT_ALLOCATION_HIGH_KEYWORD: u64 = 0x200000; pub const CORECLR_GC_HEAP_AND_TYPE_NAMES: u64 = 0x1000000;
pub const CORECLR_GC_SAMPLED_OBJECT_ALLOCATION_LOW_KEYWORD: u64 = 0x2000000;
pub const CORECLR_STACK_KEYWORD: u64 = 0x40000000; pub const CORECLR_COMPILATION_KEYWORD: u64 = 0x1000000000;
pub const CORECLR_COMPILATION_DIAGNOSTIC_KEYWORD: u64 = 0x2000000000;
pub const CORECLR_TYPE_DIAGNOSTIC_KEYWORD: u64 = 0x8000000000;
}
#[derive(Debug, Clone, FromPrimitive)]
enum GcReason {
AllocSmall = 0,
Induced,
LowMemory,
Empty,
AllocLarge,
OutOfSpaceSmallObjectHeap,
OutOfSpaceLargeObjectHeap,
InducedNoForce,
Stress,
InducedLowMemory,
}
impl Display for GcReason {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
GcReason::AllocSmall => f.write_str("Small object heap allocation"),
GcReason::Induced => f.write_str("Induced"),
GcReason::LowMemory => f.write_str("Low memory"),
GcReason::Empty => f.write_str("Empty"),
GcReason::AllocLarge => f.write_str("Large object heap allocation"),
GcReason::OutOfSpaceSmallObjectHeap => {
f.write_str("Out of space (for small object heap)")
}
GcReason::OutOfSpaceLargeObjectHeap => {
f.write_str("Out of space (for large object heap)")
}
GcReason::InducedNoForce => f.write_str("Induced but not forced as blocking"),
GcReason::Stress => f.write_str("Stress"),
GcReason::InducedLowMemory => f.write_str("Induced low memory"),
}
}
}
#[derive(Debug, Clone, FromPrimitive)]
enum GcSuspendEeReason {
Other = 0,
GC,
AppDomainShutdown,
CodePitching,
Shutdown,
Debugger,
GcPrep,
DebuggerSweep,
}
impl Display for GcSuspendEeReason {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
GcSuspendEeReason::Other => f.write_str("Other"),
GcSuspendEeReason::GC => f.write_str("GC"),
GcSuspendEeReason::AppDomainShutdown => f.write_str("AppDomain shutdown"),
GcSuspendEeReason::CodePitching => f.write_str("Code pitching"),
GcSuspendEeReason::Shutdown => f.write_str("Shutdown"),
GcSuspendEeReason::Debugger => f.write_str("Debugger"),
GcSuspendEeReason::GcPrep => f.write_str("GC prep"),
GcSuspendEeReason::DebuggerSweep => f.write_str("Debugger sweep"),
}
}
}
#[derive(Debug, Clone, FromPrimitive)]
enum GcType {
Blocking,
Background,
BlockingDuringBackground,
}
impl Display for GcType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
GcType::Blocking => f.write_str("Blocking GC"),
GcType::Background => f.write_str("Background GC"),
GcType::BlockingDuringBackground => f.write_str("Blocking GC during background GC"),
}
}
}
#[derive(Debug, Clone)]
pub struct CoreClrGcAllocMarker(StringHandle, f64, CategoryHandle);
impl StaticSchemaMarker for CoreClrGcAllocMarker {
const UNIQUE_MARKER_TYPE_NAME: &'static str = "GC Alloc";
const DESCRIPTION: Option<&'static str> = Some("GC Allocation.");
const LOCATIONS: MarkerLocations = MarkerLocations::MARKER_CHART
.union(MarkerLocations::MARKER_TABLE)
.union(MarkerLocations::TIMELINE_MEMORY);
const CHART_LABEL: Option<&'static str> = Some("GC Alloc");
const TOOLTIP_LABEL: Option<&'static str> =
Some("GC Alloc: {marker.data.clrtype} ({marker.data.size} bytes)");
const TABLE_LABEL: Option<&'static str> =
Some("GC Alloc: {marker.data.clrtype} ({marker.data.size} bytes)");
const FIELDS: &'static [StaticSchemaMarkerField] = &[
StaticSchemaMarkerField {
key: "clrtype",
label: "CLR Type",
format: MarkerFieldFormat::String,
flags: MarkerFieldFlags::SEARCHABLE,
},
StaticSchemaMarkerField {
key: "size",
label: "Size",
format: MarkerFieldFormat::Bytes,
flags: MarkerFieldFlags::empty(),
},
];
fn name(&self, profile: &mut Profile) -> StringHandle {
profile.intern_string("GC Alloc")
}
fn category(&self, _profile: &mut Profile) -> CategoryHandle {
self.2
}
fn string_field_value(&self, _field_index: u32) -> StringHandle {
self.0
}
fn number_field_value(&self, _field_index: u32) -> f64 {
self.1
}
}
#[derive(Debug, Clone)]
pub struct CoreClrGcEventMarker(StringHandle, StringHandle, CategoryHandle);
impl StaticSchemaMarker for CoreClrGcEventMarker {
const UNIQUE_MARKER_TYPE_NAME: &'static str = "GC Event";
const DESCRIPTION: Option<&'static str> = Some("Generic GC Event.");
const LOCATIONS: MarkerLocations = MarkerLocations::MARKER_CHART
.union(MarkerLocations::MARKER_TABLE)
.union(MarkerLocations::TIMELINE_MEMORY);
const CHART_LABEL: Option<&'static str> = Some("{marker.data.event}");
const TOOLTIP_LABEL: Option<&'static str> = Some("{marker.data.event}");
const TABLE_LABEL: Option<&'static str> = Some("{marker.name} - {marker.data.event}");
const FIELDS: &'static [StaticSchemaMarkerField] = &[StaticSchemaMarkerField {
key: "event",
label: "Event",
format: MarkerFieldFormat::String,
flags: MarkerFieldFlags::SEARCHABLE,
}];
fn name(&self, _profile: &mut Profile) -> StringHandle {
self.0
}
fn category(&self, _profile: &mut Profile) -> CategoryHandle {
self.2
}
fn string_field_value(&self, _field_index: u32) -> StringHandle {
self.1
}
fn number_field_value(&self, _field_index: u32) -> f64 {
unreachable!()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct DisplayUnknownIfNone<'a, T>(pub &'a Option<T>);
impl<T: Display> Display for DisplayUnknownIfNone<'_, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.0 {
Some(value) => value.fmt(f),
None => f.write_str("Unknown"),
}
}
}
pub fn coreclr_xperf_args(props: &ElevatedRecordingProps) -> Vec<String> {
let mut providers = vec![];
if !props.coreclr.any_enabled() {
return providers;
}
use constants::*;
let mut info_keywords = CORECLR_LOADER_KEYWORD;
if props.coreclr.event_stacks {
info_keywords |= CORECLR_STACK_KEYWORD;
}
if props.coreclr.gc_markers || props.coreclr.gc_suspensions || props.coreclr.gc_detailed_allocs
{
info_keywords |= CORECLR_GC_KEYWORD;
}
let verbose_keywords = CORECLR_JIT_KEYWORD | CORECLR_NGEN_KEYWORD;
let rundown_verbose_keywords = if props.is_attach {
CORECLR_LOADER_KEYWORD | CORECLR_JIT_KEYWORD | CORECLR_RUNDOWN_START_KEYWORD
} else {
0
};
if props.coreclr.gc_detailed_allocs {
info_keywords |= CORECLR_GC_SAMPLED_OBJECT_ALLOCATION_HIGH_KEYWORD
| CORECLR_GC_SAMPLED_OBJECT_ALLOCATION_LOW_KEYWORD;
}
if info_keywords != 0 {
providers.push(format!(
"Microsoft-Windows-DotNETRuntime:0x{:x}:4",
info_keywords
));
}
if verbose_keywords != 0 {
providers.push(format!(
"Microsoft-Windows-DotNETRuntime:0x{:x}:5",
verbose_keywords
));
}
if rundown_verbose_keywords != 0 {
providers.push(format!(
"Microsoft-Windows-DotNETRuntimeRundown:0x{:x}:5",
rundown_verbose_keywords
));
}
providers
}
pub fn handle_coreclr_event(
context: &mut ProfileContext,
coreclr_context: &mut CoreClrContext,
s: &TypedEvent,
parser: &mut Parser,
is_in_time_range: bool,
) {
let (gc_markers, gc_suspensions, gc_allocs, event_stacks) = (
coreclr_context.props.gc_markers,
coreclr_context.props.gc_suspensions,
coreclr_context.props.gc_detailed_allocs,
coreclr_context.props.event_stacks,
);
let timestamp_raw = s.timestamp() as u64;
let mut name_parts = s.name().splitn(3, '/');
let provider = name_parts.next().unwrap();
let task = name_parts.next().unwrap();
let opcode = name_parts.next().unwrap();
match provider {
"Microsoft-Windows-DotNETRuntime" | "Microsoft-Windows-DotNETRuntimeRundown" => {}
_ => {
panic!("Unexpected event {}", s.name())
}
}
let pid = s.process_id();
let tid = s.thread_id();
let mut handled = false;
if (task, opcode) != ("CLRStack", "CLRStackWalk") {
coreclr_context.remove_last_event_for_thread(tid);
}
match (task, opcode) {
("CLRMethod" | "CLRMethodRundown", method_event) => {
match method_event {
"MethodLoadVerbose" | "MethodDCStartVerbose"
=> {
let is_r2r = method_event == "R2RGetEntryPoint";
let method_basename: String = parser.parse("MethodName");
let method_namespace: String = parser.parse("MethodNamespace");
let method_signature: String = parser.parse("MethodSignature");
let method_start_address: u64 = if is_r2r { parser.parse("EntryPoint") } else { parser.parse("MethodStartAddress") };
let method_size: u32 = parser.parse("MethodSize");
let method_name = format!("{method_basename} [{method_namespace}] \u{2329}{method_signature}\u{232a}");
context.handle_coreclr_method_load(timestamp_raw, pid, method_name, method_start_address, method_size);
handled = true;
}
"ModuleLoad" | "ModuleDCStart" |
"ModuleUnload" | "ModuleDCEnd" => {
handled = true;
}
_ => {
handled = true;
}
}
}
("Type", "BulkType") => {
}
("CLRStack", "CLRStackWalk") => {
if !is_in_time_range {
return;
}
if !event_stacks {
return;
}
let Some(marker) = coreclr_context.remove_last_event_for_thread(tid) else {
return;
};
let first_addresses: Vec<u8> = parser.parse("Stack");
let address_iter = first_addresses
.chunks_exact(8)
.chain(parser.buffer.chunks_exact(8))
.map(|chunk| u64::from_le_bytes(chunk.try_into().unwrap()));
context.handle_coreclr_stack(timestamp_raw, tid, address_iter, marker);
handled = true;
}
("GarbageCollection", gc_event) => {
if !is_in_time_range {
return;
}
match gc_event {
"GCSampledObjectAllocation" => {
if !gc_allocs {
return;
}
let type_id: u64 = parser.parse("TypeID"); let _object_count: u32 = parser.parse("ObjectCountForTypeSample");
let total_size: u64 = parser.parse("TotalSizeForTypeSample");
let category = context.known_category(KnownCategory::CoreClrGc);
let clr_type = context.intern_profile_string(&format!("0x{:x}", type_id));
let mh = context.add_thread_instant_marker(
timestamp_raw,
tid,
CoreClrGcAllocMarker(clr_type, total_size as f64, category),
);
coreclr_context.set_last_event_for_thread(tid, mh);
handled = true;
}
"Triggered" => {
if !gc_markers {
return;
}
let reason: u32 = parser.parse("Reason");
let reason = GcReason::from_u32(reason).or_else(|| {
eprintln!("Unknown CLR GC Triggered reason: {}", reason);
None
});
let category = context.known_category(KnownCategory::CoreClrGc);
let name = context.intern_profile_string("GC Trigger");
let description = context.intern_profile_string(&format!(
"GC Trigger: {}",
DisplayUnknownIfNone(&reason)
));
let mh = context.add_thread_instant_marker(
timestamp_raw,
tid,
CoreClrGcEventMarker(name, description, category),
);
coreclr_context.set_last_event_for_thread(tid, mh);
handled = true;
}
"GCSuspendEEBegin" => {
if !gc_suspensions {
return;
}
let _count: u32 = parser.parse("Count");
let reason: u32 = parser.parse("Reason");
let reason = GcSuspendEeReason::from_u32(reason).or_else(|| {
eprintln!("Unknown CLR GCSuspendEEBegin reason: {}", reason);
None
});
coreclr_context.save_gc_marker(
tid,
timestamp_raw,
"GCSuspendEE",
"GC Suspended Thread".to_owned(),
format!("Suspended: {}", DisplayUnknownIfNone(&reason)),
);
handled = true;
}
"GCSuspendEEEnd" | "GCRestartEEBegin" => {
handled = true;
}
"GCRestartEEEnd" => {
if !gc_suspensions {
return;
}
if let Some(info) = coreclr_context.remove_gc_marker(tid, "GCSuspendEE") {
let category = context.known_category(KnownCategory::CoreClrGc);
let name = context.intern_profile_string(&info.name);
let description = context.intern_profile_string(&info.description);
context.add_thread_interval_marker(
info.start_timestamp_raw,
timestamp_raw,
tid,
CoreClrGcEventMarker(name, description, category),
);
}
handled = true;
}
"win:Start" => {
if !gc_markers {
return;
}
let count: u32 = parser.parse("Count");
let depth: u32 = parser.parse("Depth");
let reason: u32 = parser.parse("Reason");
let gc_type: u32 = parser.parse("Type");
let reason = GcReason::from_u32(reason).or_else(|| {
eprintln!("Unknown CLR GCStart reason: {}", reason);
None
});
let gc_type = GcType::from_u32(gc_type).or_else(|| {
eprintln!("Unknown CLR GCStart type: {}", gc_type);
None
});
coreclr_context.save_gc_marker(
tid,
timestamp_raw,
"GC",
"GC".to_owned(),
format!(
"{}: {} (GC #{}, gen{})",
DisplayUnknownIfNone(&gc_type),
DisplayUnknownIfNone(&reason),
count,
depth
),
);
handled = true;
}
"win:Stop" => {
if !gc_markers {
return;
}
if let Some(info) = coreclr_context.remove_gc_marker(tid, "GC") {
let category = context.known_category(KnownCategory::CoreClrGc);
let name = context.intern_profile_string(&info.name);
let description = context.intern_profile_string(&info.description);
context.add_thread_interval_marker(
info.start_timestamp_raw,
timestamp_raw,
tid,
CoreClrGcEventMarker(name, description, category),
);
}
handled = true;
}
"SetGCHandle" => {
}
"DestroyGCHandle" => {
}
"GCFinalizersBegin" | "GCFinalizersEnd" | "FinalizeObject" => {
handled = true;
}
"GCCreateSegment" | "GCFreeSegment" | "GCDynamicEvent" | "GCHeapStats" => {
handled = true;
}
_ => {
handled = true;
}
}
}
("CLRRuntimeInformation", _) => {
handled = true;
}
("CLRLoader", _) => {
handled = true;
}
_ => {}
}
if !handled && coreclr_context.unknown_event_markers {
let text = event_properties_to_string(s, parser, None);
let name = context.intern_profile_string(s.name().split_once('/').unwrap().1);
let description = context.intern_profile_string(&text);
let marker_handle = context.add_thread_instant_marker(
timestamp_raw,
tid,
OtherClrMarker(name, description),
);
coreclr_context.set_last_event_for_thread(tid, marker_handle);
}
}
#[derive(Debug, Clone)]
pub struct OtherClrMarker(StringHandle, StringHandle);
impl StaticSchemaMarker for OtherClrMarker {
const UNIQUE_MARKER_TYPE_NAME: &'static str = "OtherClrMarker";
const DESCRIPTION: Option<&'static str> = Some("CoreCLR marker of unknown type.");
const CHART_LABEL: Option<&'static str> = Some("{marker.data.name}");
const TOOLTIP_LABEL: Option<&'static str> = Some("{marker.data.name}");
const TABLE_LABEL: Option<&'static str> = Some("{marker.name} - {marker.data.name}");
const FIELDS: &'static [StaticSchemaMarkerField] = &[StaticSchemaMarkerField {
key: "name",
label: "Name",
format: MarkerFieldFormat::String,
flags: MarkerFieldFlags::SEARCHABLE,
}];
fn name(&self, _profile: &mut Profile) -> StringHandle {
self.0
}
fn category(&self, _profile: &mut Profile) -> CategoryHandle {
CategoryHandle::OTHER
}
fn string_field_value(&self, _field_index: u32) -> StringHandle {
self.1
}
fn number_field_value(&self, _field_index: u32) -> f64 {
unreachable!()
}
}