Last active
January 22, 2019 17:08
-
-
Save pervognsen/73597a3a732a10922418d62c6c86a427 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// zzz - very sleepy replacement, based on etw | |
#define INITGUID | |
#include <windows.h> | |
#include <psapi.h> | |
#include <tdh.h> | |
#include <evntrace.h> | |
#include <guiddef.h> | |
#pragma warning(disable: 4091) | |
#include <dbghelp.h> | |
#include <winternl.h> | |
#include <stdio.h> | |
#include <stdint.h> | |
#include <stdarg.h> | |
#include <vector> | |
#include <algorithm> | |
#include <unordered_map> | |
#pragma comment(lib, "dbghelp.lib") | |
#pragma comment(lib, "ntdll.lib") | |
#define Assert(x) \ | |
if (!(x)) { MessageBoxA(0, #x, "Assertion Failure", MB_OK); __debugbreak(); } | |
HANDLE output_file = INVALID_HANDLE_VALUE; | |
void Outputf(char *format, ...) { | |
static char buffer[1024 * 1024]; | |
va_list arg_list; | |
va_start(arg_list, format); | |
uint32_t length = vsprintf_s(buffer, sizeof(buffer), format, arg_list); | |
va_end(arg_list); | |
OutputDebugStringA(buffer); | |
if (output_file != INVALID_HANDLE_VALUE) { | |
DWORD written_length; | |
WriteFile(output_file, buffer, length, &written_length, 0); | |
} | |
} | |
uint64_t timer_frequency; | |
void ResetTimer(uint64_t *timer) { | |
QueryPerformanceCounter((LARGE_INTEGER *)timer); | |
} | |
uint64_t GetTimerMilliseconds(uint64_t *timer) { | |
LARGE_INTEGER current_time; | |
QueryPerformanceCounter(¤t_time); | |
uint64_t duration = current_time.QuadPart - *timer; | |
return (1000 * duration) / timer_frequency; | |
} | |
enum { PAGE_SIZE = 4096, GB = 1024 * 1024 * 1024 }; | |
struct Range { | |
char *base; | |
char *commit_end; | |
char *reserve_end; | |
}; | |
void AllocateRange(Range *range, size_t reserve_size) { | |
reserve_size = (reserve_size + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); | |
range->base = (char *)VirtualAlloc(0, reserve_size, MEM_RESERVE, PAGE_READWRITE); | |
Assert(range->base); | |
size_t initial_commit_size = PAGE_SIZE; | |
void *pointer = VirtualAlloc(range->base, initial_commit_size, MEM_COMMIT, PAGE_READWRITE); | |
Assert(pointer); | |
range->commit_end = range->base + initial_commit_size; | |
range->reserve_end = range->base + reserve_size; | |
} | |
void FreeRange(Range *range) { | |
VirtualFree(range->base, range->reserve_end - range->base, MEM_RELEASE); | |
range->base = 0; | |
range->commit_end = 0; | |
range->reserve_end = 0; | |
} | |
void CommitRange(Range *range, void *base, size_t size) { | |
char *pointer = (char *)base; | |
Assert(range->base <= pointer && pointer + size < range->reserve_end); | |
if (pointer + size >= range->commit_end) { | |
size_t commit_growth = (pointer + size - range->commit_end + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); | |
VirtualAlloc(range->commit_end, commit_growth, MEM_COMMIT, PAGE_READWRITE); | |
range->commit_end += commit_growth; | |
} | |
} | |
void ElevatePrivilege() { | |
BOOL success; | |
HANDLE token = 0; | |
success = OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token); | |
Assert(success); | |
TOKEN_PRIVILEGES token_privileges; | |
ZeroMemory(&token_privileges, sizeof(token_privileges)); | |
token_privileges.PrivilegeCount = 1; | |
token_privileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; | |
LookupPrivilegeValue(NULL, SE_SYSTEM_PROFILE_NAME, &token_privileges.Privileges[0].Luid); | |
AdjustTokenPrivileges(token, FALSE, &token_privileges, 0, (PTOKEN_PRIVILEGES)NULL, 0); | |
::CloseHandle(token); | |
} | |
struct ProfileCapture { | |
bool delete_old_capture; | |
WCHAR *filename; | |
ULONG maximum_size_in_megabytes; | |
TRACEHANDLE trace_handle; | |
EVENT_TRACE_PROPERTIES *properties; | |
}; | |
DEFINE_GUID( | |
PerfInfoGuid, | |
0xce1dbfb4, | |
0x137e, | |
0x4da6, | |
0x87, 0xb0, 0x3f, 0x59, 0xaa, 0x10, 0x2c, 0xbc | |
); | |
DEFINE_GUID( | |
StackWalkGuid, | |
0xdef2fe46, | |
0x7bd6, | |
0x4b80, | |
0xbd, 0x94, 0xf5, 0x7f, 0xe2, 0x0d, 0x0c, 0xe3 | |
); | |
DEFINE_GUID( | |
ProcessGuid, | |
0x3d6fa8d0, | |
0xfe05, | |
0x11d0, | |
0x9d, 0xda, 0x00, 0xc0, 0x4f, 0xd7, 0xba, 0x7c | |
); | |
DEFINE_GUID( | |
ThreadGuid, | |
0x3d6fa8d1, | |
0xfe05, | |
0x11d0, | |
0x9d, 0xda, 0x00, 0xc0, 0x4f, 0xd7, 0xba, 0x7c | |
); | |
DEFINE_GUID( | |
ImageLoadGuid, | |
0x2cb15d1d, | |
0x5fc1, | |
0x11d2, | |
0xab, 0xe1, 0x00, 0xa0, 0xc9, 0x11, 0xf5, 0x18 | |
); | |
enum EventType { | |
EVENT_STACK_WALK = 32, | |
EVENT_SAMPLED_PROFILE = 46, | |
EVENT_PROCESS_START = 1, | |
EVENT_PROCESS_END = 2, | |
EVENT_PROCESS_DC_START = 3, | |
EVENT_PROCESS_DC_END = 4, | |
EVENT_THREAD_START = 1, | |
EVENT_THREAD_END = 2, | |
EVENT_THREAD_DC_START = 3, | |
EVENT_THREAD_DC_END = 4, | |
EVENT_IMAGE_LOAD = 10, | |
EVENT_IMAGE_UNLOAD = 2, | |
EVENT_IMAGE_DC_LOAD = 3, | |
EVENT_IMAGE_DC_UNLOAD = 4, | |
}; | |
struct Event_SampledProfile { | |
void *instruction_pointer; | |
uint32_t thread_id; | |
}; | |
struct Event_StackWalk { | |
uint64_t timestamp; | |
uint32_t process_id; | |
uint32_t thread_id; | |
void *instruction_pointers[1]; | |
}; | |
struct Event_Process { | |
void *unique_process_key; | |
uint32_t process_id; | |
uint32_t parent_id; | |
uint32_t session_id; | |
int32_t exit_status; | |
void *directory_table_base; | |
void *user_token[2]; | |
SID sid; | |
}; | |
struct Event_Thread { | |
uint32_t process_id; | |
uint32_t thread_id; | |
}; | |
struct Event_ImageLoad { | |
void *image_base; | |
size_t image_size; | |
uint32_t process_id; | |
uint32_t image_checksum; | |
uint32_t time_date_stamp; | |
uint32_t reserved0; | |
void *default_base; | |
uint32_t reserved1[4]; | |
WCHAR filename[1]; | |
}; | |
struct ProfileProcess; | |
struct ProfileImage { | |
uint64_t load_time; | |
uint64_t unload_time; | |
ProfileProcess *process; | |
char *name; | |
char *filename; | |
void *base; | |
size_t size; | |
ProfileImage *next_image; | |
}; | |
struct ProfileStack; | |
struct ProfileSymbol { | |
char *name; | |
ProfileImage *image; | |
uint32_t exclusive_count; | |
uint32_t inclusive_count; | |
uint64_t exclusive_time; | |
uint64_t inclusive_time; | |
}; | |
struct ProfileProcess { | |
uint64_t start_time; | |
uint64_t stop_time; | |
uint32_t process_id; | |
char *filename; | |
ProfileImage *first_image; | |
}; | |
struct ProfileThread { | |
uint64_t start_time; | |
uint64_t stop_time; | |
uint32_t thread_id; | |
uint64_t last_stack_time; | |
ProfileProcess *process; | |
}; | |
struct ProfileStack { | |
uint64_t time; | |
uint64_t timespan; | |
ProfileThread *thread; | |
void **instruction_pointers; | |
ProfileSymbol **symbols; | |
size_t instruction_pointers_count; | |
}; | |
struct ProfileAnalysis { | |
uint64_t performance_frequency; | |
Range processes_range; | |
ProfileProcess *processes; | |
size_t processes_count; | |
Range threads_range; | |
ProfileThread *threads; | |
size_t threads_count; | |
Range images_range; | |
ProfileImage *images; | |
size_t images_count; | |
Range stacks_range; | |
ProfileStack *stacks; | |
size_t stacks_count; | |
Range symbols_range; | |
ProfileSymbol *symbols; | |
size_t symbols_count; | |
Range data_range; | |
char *data; | |
char *data_end; | |
}; | |
void *AllocateProfileData(ProfileAnalysis *analysis, size_t size) { | |
uintptr_t address = (uintptr_t)analysis->data_end; | |
address = (address + 7) & ~7; | |
char *pointer = (char *)address; | |
CommitRange(&analysis->data_range, pointer, (pointer - analysis->data_end) + size); | |
analysis->data_end = pointer + size; | |
return pointer; | |
} | |
ProfileImage *AddProfileImage(ProfileAnalysis *analysis) { | |
ProfileImage *image = analysis->images + analysis->images_count; | |
CommitRange(&analysis->images_range, image, sizeof(ProfileImage)); | |
analysis->images_count++; | |
return image; | |
} | |
ProfileProcess *AddProfileProcess(ProfileAnalysis *analysis) { | |
ProfileProcess *process = analysis->processes + analysis->processes_count; | |
CommitRange(&analysis->processes_range, process, sizeof(ProfileProcess)); | |
analysis->processes_count++; | |
return process; | |
} | |
ProfileThread *AddProfileThread(ProfileAnalysis *analysis) { | |
ProfileThread *thread = analysis->threads + analysis->threads_count; | |
CommitRange(&analysis->threads_range, thread, sizeof(ProfileThread)); | |
analysis->threads_count++; | |
return thread; | |
} | |
ProfileStack *AddProfileStack(ProfileAnalysis *analysis) { | |
ProfileStack *stack = analysis->stacks + analysis->stacks_count; | |
CommitRange(&analysis->stacks_range, stack, sizeof(ProfileStack)); | |
analysis->stacks_count++; | |
return stack; | |
} | |
ProfileSymbol *AddProfileSymbol(ProfileAnalysis *analysis) { | |
ProfileSymbol *symbol = analysis->symbols + analysis->symbols_count; | |
CommitRange(&analysis->symbols_range, symbol, sizeof(ProfileSymbol)); | |
analysis->symbols_count++; | |
return symbol; | |
} | |
struct EventRecordCallbackContext { | |
ProfileAnalysis *analysis; | |
EVENT_TRACE_LOGFILE *event_trace_logfile; | |
std::unordered_map<uint32_t, ProfileProcess *> processes; | |
std::unordered_map<uint32_t, ProfileThread *> threads; | |
}; | |
void WINAPI EventRecordCallback(EVENT_RECORD *event_record) { | |
EventRecordCallbackContext *context = (EventRecordCallbackContext *)event_record->UserContext; | |
ProfileAnalysis *analysis = context->analysis; | |
std::unordered_map<uint32_t, ProfileProcess *> &processes = context->processes; | |
std::unordered_map<uint32_t, ProfileThread *> &threads = context->threads; | |
GUID provider_guid = event_record->EventHeader.ProviderId; | |
uint8_t event_type = event_record->EventHeader.EventDescriptor.Opcode; | |
uint32_t event_process_id = event_record->EventHeader.ProcessId; | |
uint8_t event_version = event_record->EventHeader.EventDescriptor.Version; | |
uint64_t event_time = event_record->EventHeader.TimeStamp.QuadPart; | |
if (IsEqualGUID(provider_guid, StackWalkGuid)) { | |
Assert(event_version == 2); | |
if (event_type == EVENT_STACK_WALK) { | |
uint32_t header_size = sizeof(uint64_t) + sizeof(uint32_t) + sizeof(uint32_t); | |
uint32_t instruction_pointers_count = (event_record->UserDataLength - header_size) / sizeof(void *); | |
Event_StackWalk *event_stack = (Event_StackWalk *)event_record->UserData; | |
ProfileThread *thread = threads[event_stack->thread_id]; | |
if (thread) { | |
ProfileStack *stack = AddProfileStack(analysis); | |
stack->time = event_time; | |
stack->thread = thread; | |
if (thread->last_stack_time) { | |
stack->timespan = event_time - thread->last_stack_time; | |
} else { | |
stack->timespan = 0; | |
} | |
thread->last_stack_time = event_time; | |
stack->instruction_pointers_count = instruction_pointers_count; | |
size_t instruction_pointers_size = instruction_pointers_count * sizeof(void *); | |
stack->instruction_pointers = (void **)AllocateProfileData(analysis, instruction_pointers_size); | |
memcpy(stack->instruction_pointers, event_stack->instruction_pointers, instruction_pointers_size); | |
size_t symbols_size = instruction_pointers_count * sizeof(ProfileSymbol *); | |
stack->symbols = (ProfileSymbol **)AllocateProfileData(analysis, symbols_size); | |
memset(stack->symbols, 0, symbols_size); | |
} | |
} | |
} else if (IsEqualGUID(provider_guid, ProcessGuid)) { | |
Assert(event_version == 3); | |
Event_Process *event_process = (Event_Process *)event_record->UserData; | |
switch (event_type) { | |
case EVENT_PROCESS_DC_START: | |
case EVENT_PROCESS_START: { | |
uint32_t sid_length = GetLengthSid(&event_process->sid); | |
char *filename = (char *)&event_process->sid + sid_length; | |
ProfileProcess *process = AddProfileProcess(analysis); | |
process->start_time = event_time; | |
process->stop_time = 0; | |
process->process_id = event_process->process_id; | |
size_t filename_size = strlen(filename) + 1; | |
process->filename = (char *)AllocateProfileData(analysis, filename_size); | |
memcpy(process->filename, filename, filename_size); | |
process->first_image = 0; | |
processes[process->process_id] = process; | |
break; | |
} | |
case EVENT_PROCESS_DC_END: | |
case EVENT_PROCESS_END: { | |
auto it = processes.find(event_process->process_id); | |
if (it != processes.end()) { | |
ProfileProcess *process = it->second; | |
process->stop_time = event_time; | |
if (event_type == EVENT_PROCESS_END) { | |
processes.erase(it); | |
} | |
} | |
break; | |
} | |
} | |
} else if (IsEqualGUID(provider_guid, ThreadGuid)) { | |
Assert(event_version == 3); | |
Event_Thread *event_thread = (Event_Thread *)event_record->UserData; | |
switch (event_type) { | |
case EVENT_THREAD_DC_START: | |
case EVENT_THREAD_START: { | |
ProfileProcess *process = processes[event_thread->process_id]; | |
if (process) { | |
ProfileThread *thread = AddProfileThread(analysis); | |
thread->start_time = event_time; | |
thread->stop_time = 0; | |
thread->last_stack_time = 0; | |
thread->thread_id = event_thread->thread_id; | |
thread->process = process; | |
threads[thread->thread_id] = thread; | |
} | |
break; | |
} | |
case EVENT_THREAD_DC_END: | |
case EVENT_THREAD_END: { | |
auto it = threads.find(event_thread->thread_id); | |
if (it != threads.end()) { | |
ProfileThread *thread = it->second; | |
thread->stop_time = event_time; | |
if (event_type == EVENT_THREAD_END) { | |
threads.erase(it); | |
} | |
} | |
break; | |
} | |
} | |
} else if (IsEqualGUID(provider_guid, ImageLoadGuid)) { | |
Assert(event_version == 2); | |
Event_ImageLoad *event_image = (Event_ImageLoad*)event_record->UserData; | |
switch (event_type) { | |
case EVENT_IMAGE_DC_LOAD: | |
case EVENT_IMAGE_LOAD: { | |
ProfileProcess *process = processes[event_image->process_id]; | |
Assert(process); | |
ProfileImage *image = AddProfileImage(analysis); | |
image->next_image = process->first_image; | |
process->first_image = image; | |
image->load_time = event_time; | |
image->unload_time = 0; | |
image->process = process; | |
image->base = event_image->image_base; | |
image->size = event_image->image_size; | |
uint32_t filename_size = WideCharToMultiByte(CP_ACP, 0, event_image->filename, -1, image->filename, 0, 0, 0) + 1; | |
image->filename = (char *)AllocateProfileData(analysis, filename_size); | |
WideCharToMultiByte(CP_ACP, 0, event_image->filename, -1, image->filename, filename_size, 0, 0); | |
image->filename[filename_size - 1] = 0; | |
image->name = image->filename; | |
image->name = image->filename + filename_size - 1; | |
while (image->name != image->filename && *image->name != '\\') { | |
image->name--; | |
} | |
if (*image->name == '\\') { | |
image->name++; | |
} | |
break; | |
} | |
case EVENT_IMAGE_DC_UNLOAD: | |
case EVENT_IMAGE_UNLOAD: { | |
ProfileProcess *process = processes[event_image->process_id]; | |
Assert(process); | |
for (ProfileImage *image = process->first_image; image; image = image->next_image) { | |
if (image->base == event_image->image_base) { | |
image->unload_time = event_time; | |
break; | |
} | |
} | |
break; | |
} | |
} | |
} | |
} | |
void InitializeProfileAnalysis(ProfileAnalysis *analysis) { | |
AllocateRange(&analysis->processes_range, GB); | |
analysis->processes = (ProfileProcess *)analysis->processes_range.base; | |
analysis->processes_count = 0; | |
AllocateRange(&analysis->threads_range, GB); | |
analysis->threads = (ProfileThread *)analysis->threads_range.base; | |
analysis->threads_count = 0; | |
AllocateRange(&analysis->images_range, GB); | |
analysis->images = (ProfileImage *)analysis->images_range.base; | |
analysis->images_count = 0; | |
AllocateRange(&analysis->stacks_range, GB); | |
analysis->stacks = (ProfileStack *)analysis->stacks_range.base; | |
analysis->stacks_count = 0; | |
AllocateRange(&analysis->symbols_range, GB); | |
analysis->symbols = (ProfileSymbol *)analysis->symbols_range.base; | |
analysis->symbols_count = 0; | |
AllocateRange(&analysis->data_range, GB); | |
analysis->data = (char *)analysis->data_range.base; | |
analysis->data_end = analysis->data; | |
} | |
void ParseProfileCapture(ProfileAnalysis *analysis, ProfileCapture *capture) { | |
EVENT_TRACE_LOGFILE event_trace_logfile = {0}; | |
event_trace_logfile.LogFileName = capture->filename; | |
event_trace_logfile.ProcessTraceMode = PROCESS_TRACE_MODE_EVENT_RECORD | PROCESS_TRACE_MODE_RAW_TIMESTAMP; | |
event_trace_logfile.EventRecordCallback = EventRecordCallback; | |
EventRecordCallbackContext context; | |
context.analysis = analysis; | |
context.event_trace_logfile = &event_trace_logfile; | |
event_trace_logfile.Context = &context; | |
TRACEHANDLE trace_handle = OpenTrace(&event_trace_logfile); | |
analysis->performance_frequency = event_trace_logfile.LogfileHeader.PerfFreq.QuadPart; | |
Assert(event_trace_logfile.EventsLost == 0); | |
ProcessTrace(&trace_handle, 1, 0, 0); | |
CloseTrace(trace_handle); | |
} | |
HANDLE OpenImageFile(char *filename) { | |
int filename_length = (int)strlen(filename); | |
int unicode_filename_size = sizeof(WCHAR) * filename_length; | |
WCHAR *unicode_filename = (WCHAR *)_alloca(unicode_filename_size); | |
int unicode_filename_length = MultiByteToWideChar(CP_ACP, 0, filename, filename_length, unicode_filename, unicode_filename_size); | |
Assert(unicode_filename_length == filename_length); | |
UNICODE_STRING unicode_string; | |
unicode_string.Buffer = unicode_filename; | |
unicode_string.Length = unicode_filename_size; | |
unicode_string.MaximumLength = unicode_filename_size; | |
OBJECT_ATTRIBUTES object_attributes = {0}; | |
InitializeObjectAttributes(&object_attributes, &unicode_string, OBJ_CASE_INSENSITIVE, 0, 0); | |
IO_STATUS_BLOCK io_status_block = {0}; | |
HANDLE file; | |
NTSTATUS status = NtCreateFile(&file, FILE_READ_DATA, &object_attributes, &io_status_block, 0, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OPEN, 0, 0, 0); | |
if (NT_SUCCESS(status)) { | |
return file; | |
} else { | |
return INVALID_HANDLE_VALUE; | |
} | |
} | |
ProfileImage *GetInstructionPointerImage(ProfileAnalysis *analysis, ProfileProcess *process, void *instruction_pointer) { | |
for (ProfileImage *image = process->first_image; image; image = image->next_image) { | |
if (image->base <= instruction_pointer && instruction_pointer < (char *)image->base + image->size) { | |
return image; | |
} | |
} | |
ProfileProcess *process0 = analysis->processes; | |
if (process0) { | |
for (ProfileImage *image = process0->first_image; image; image = image->next_image) { | |
if (image->base <= instruction_pointer && instruction_pointer < (char *)image->base + image->size) { | |
return image; | |
} | |
} | |
} | |
return 0; | |
} | |
float GetProfileTimeInSeconds(ProfileAnalysis *analysis, uint64_t time) { | |
return (float)time / analysis->performance_frequency; | |
} | |
void SymbolizeProfileAnalysisProcess(ProfileAnalysis *analysis, uint32_t process_id) { | |
ProfileProcess *process = 0; | |
for (size_t i = 0; i < analysis->processes_count; i++) { | |
if (analysis->processes[i].process_id == process_id) { | |
process = analysis->processes + i; | |
break; | |
} | |
} | |
Assert(process); | |
Outputf("Process: %s, %.2fs\n", process->filename, GetProfileTimeInSeconds(analysis, process->stop_time - process->start_time)); | |
HANDLE handle = (HANDLE)1; | |
BOOL success = SymInitialize(handle, 0, FALSE); | |
Assert(success); | |
for (ProfileImage *image = process->first_image; image; image = image->next_image) { | |
HANDLE image_file = OpenImageFile(image->filename); | |
if (image_file == INVALID_HANDLE_VALUE) { | |
continue; | |
} | |
DWORD64 base_address = SymLoadModuleExW(handle, image_file, 0, 0, (DWORD64)image->base, 0, 0, 0); | |
CloseHandle(image_file); | |
} | |
std::unordered_map<ULONG, ProfileSymbol *> pdb_index_to_symbol; | |
std::unordered_map<void *, ProfileSymbol *> instruction_pointer_to_symbol; | |
Range last_stack_range = {0}; | |
AllocateRange(&last_stack_range, GB); | |
ProfileStack **last_stack = (ProfileStack **)last_stack_range.base; | |
ProfileStack **last_stack_end = last_stack + analysis->symbols_count; | |
CommitRange(&last_stack_range, last_stack, analysis->symbols_count * sizeof(ProfileStack *)); | |
for (size_t i = 0; i < analysis->stacks_count; i++) { | |
ProfileStack *stack = (ProfileStack *)analysis->stacks + i; | |
if (stack->thread->process != process) { | |
continue; | |
} | |
for (size_t j = 0; j < stack->instruction_pointers_count; j++) { | |
void *instruction_pointer = stack->instruction_pointers[j]; | |
auto it = instruction_pointer_to_symbol.find(instruction_pointer); | |
ProfileSymbol *symbol = 0; | |
if (it == instruction_pointer_to_symbol.end()) { | |
enum { MAX_PDB_SYMBOL_SIZE = 1024 }; | |
char pdb_symbol_data[MAX_PDB_SYMBOL_SIZE]; | |
SYMBOL_INFO *pdb_symbol = (SYMBOL_INFO *)pdb_symbol_data; | |
pdb_symbol->SizeOfStruct = sizeof(SYMBOL_INFO); | |
pdb_symbol->MaxNameLen = MAX_PDB_SYMBOL_SIZE - sizeof(SYMBOL_INFO) - 1; | |
if (SymFromAddr(handle, (DWORD64)instruction_pointer, 0, pdb_symbol)) { | |
symbol = pdb_index_to_symbol[pdb_symbol->Index]; | |
if (!symbol) { | |
symbol = AddProfileSymbol(analysis); | |
symbol->image = GetInstructionPointerImage(analysis, process, instruction_pointer); | |
symbol->name = (char *)AllocateProfileData(analysis, pdb_symbol->NameLen + 1); | |
symbol->exclusive_count = 0; | |
symbol->inclusive_count = 0; | |
symbol->exclusive_time = 0; | |
symbol->inclusive_time = 0; | |
memcpy(symbol->name, pdb_symbol->Name, pdb_symbol->NameLen + 1); | |
CommitRange(&last_stack_range, last_stack_end, sizeof(ProfileStack *)); | |
last_stack_end++; | |
pdb_index_to_symbol[pdb_symbol->Index] = symbol; | |
} | |
} | |
instruction_pointer_to_symbol[instruction_pointer] = symbol; | |
} else { | |
symbol = it->second; | |
} | |
stack->symbols[j] = symbol; | |
if (symbol) { | |
if (j == 0) { | |
symbol->exclusive_count++; | |
symbol->exclusive_time += stack->timespan; | |
} | |
size_t symbol_index = symbol - analysis->symbols; | |
if (last_stack[symbol_index] != stack) { | |
symbol->inclusive_count++; | |
symbol->inclusive_time += stack->timespan; | |
last_stack[symbol_index] = stack; | |
} | |
} | |
} | |
} | |
FreeRange(&last_stack_range); | |
SymCleanup(handle); | |
} | |
void OutputInclusiveCounts(ProfileAnalysis *analysis, uint32_t process_id) { | |
std::vector<std::pair<int, ProfileSymbol *>> counts; | |
Outputf("Inclusive counts:\n"); | |
for (size_t i = 0; i < analysis->symbols_count; i++) { | |
ProfileSymbol *symbol = analysis->symbols + i; | |
if (symbol->inclusive_count && symbol->image->process->process_id == process_id) { | |
counts.push_back(std::make_pair(symbol->inclusive_count, symbol)); | |
} | |
} | |
std::sort(counts.begin(), counts.end()); | |
for (auto it = counts.rbegin(); it != counts.rend(); ++it) { | |
Outputf(" %5ux %s\n", it->first, it->second->name); | |
} | |
} | |
void OutputExclusiveCounts(ProfileAnalysis *analysis, uint32_t process_id) { | |
std::vector<std::pair<int, ProfileSymbol *>> counts; | |
Outputf("Exclusive counts:\n"); | |
for (size_t i = 0; i < analysis->symbols_count; i++) { | |
ProfileSymbol *symbol = analysis->symbols + i; | |
if (symbol->exclusive_count && symbol->image->process->process_id == process_id) { | |
counts.push_back(std::make_pair(symbol->exclusive_count, symbol)); | |
} | |
} | |
std::sort(counts.begin(), counts.end()); | |
for (auto it = counts.rbegin(); it != counts.rend(); ++it) { | |
Outputf(" %5ux %s\n", it->first, it->second->name); | |
} | |
} | |
void OutputInclusiveTime(ProfileAnalysis *analysis, uint32_t process_id) { | |
std::vector<std::pair<uint64_t, ProfileSymbol *>> times; | |
Outputf("Inclusive time:\n"); | |
for (size_t i = 0; i < analysis->symbols_count; i++) { | |
ProfileSymbol *symbol = analysis->symbols + i; | |
if (symbol->inclusive_time && symbol->image->process->process_id == process_id) { | |
times.push_back(std::make_pair(symbol->inclusive_time, symbol)); | |
} | |
} | |
std::sort(times.begin(), times.end()); | |
for (auto it = times.rbegin(); it != times.rend(); ++it) { | |
Outputf(" %5.2fs %s\n", GetProfileTimeInSeconds(analysis, it->first), it->second->name); | |
} | |
} | |
void OutputExclusiveTime(ProfileAnalysis *analysis, uint32_t process_id) { | |
std::vector<std::pair<uint64_t, ProfileSymbol *>> times; | |
Outputf("Exclusive time:\n"); | |
for (size_t i = 0; i < analysis->symbols_count; i++) { | |
ProfileSymbol *symbol = analysis->symbols + i; | |
if (symbol->exclusive_time && symbol->image->process->process_id == process_id) { | |
times.push_back(std::make_pair(symbol->exclusive_time, symbol)); | |
} | |
} | |
std::sort(times.begin(), times.end()); | |
for (auto it = times.rbegin(); it != times.rend(); ++it) { | |
Outputf(" %5.2fs %s\n", GetProfileTimeInSeconds(analysis, it->first), it->second->name); | |
} | |
} | |
// Profile recording | |
uint32_t FindProfileId(ProfileAnalysis *analysis, char *filename) { | |
for (size_t i = 0; i < analysis->processes_count; i++) { | |
if (strcmp(analysis->processes[i].filename, filename) == 0) { | |
return analysis->processes[i].process_id; | |
} | |
} | |
return 0; | |
} | |
void StartProfileCapture(ProfileCapture *capture) { | |
if (capture->delete_old_capture) { | |
if (!DeleteFile(capture->filename)) { | |
Assert(GetLastError() == ERROR_SUCCESS || GetLastError() == ERROR_FILE_NOT_FOUND); | |
} | |
} | |
size_t filename_size = (wcslen(capture->filename) + 1) * sizeof(WCHAR); | |
size_t buffer_size = sizeof(EVENT_TRACE_PROPERTIES) + sizeof(KERNEL_LOGGER_NAME) + filename_size; | |
capture->properties = (EVENT_TRACE_PROPERTIES *)malloc(buffer_size); | |
Assert(capture->properties); | |
ZeroMemory(capture->properties, buffer_size); | |
capture->properties->Wnode.BufferSize = (ULONG)buffer_size; | |
capture->properties->Wnode.Flags = WNODE_FLAG_TRACED_GUID; | |
capture->properties->Wnode.ClientContext = 1; // QueryPerformanceCounter | |
capture->properties->Wnode.Guid = SystemTraceControlGuid; | |
capture->properties->EnableFlags = EVENT_TRACE_FLAG_PROFILE | EVENT_TRACE_FLAG_IMAGE_LOAD | EVENT_TRACE_FLAG_THREAD | EVENT_TRACE_FLAG_PROCESS; | |
capture->properties->LogFileMode = EVENT_TRACE_FILE_MODE_APPEND; | |
capture->properties->MaximumFileSize = capture->maximum_size_in_megabytes; | |
capture->properties->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES); | |
capture->properties->LogFileNameOffset = capture->properties->LoggerNameOffset + sizeof(KERNEL_LOGGER_NAME); | |
CopyMemory((char *)capture->properties + capture->properties->LogFileNameOffset, capture->filename, filename_size); | |
ULONG status; | |
status = ControlTrace(0, KERNEL_LOGGER_NAME, capture->properties, EVENT_TRACE_CONTROL_STOP); | |
Assert(status == ERROR_SUCCESS || status == ERROR_WMI_INSTANCE_NOT_FOUND); | |
status = StartTrace((PTRACEHANDLE)&capture->trace_handle, KERNEL_LOGGER_NAME, capture->properties); | |
Assert(status == ERROR_SUCCESS); | |
CLASSIC_EVENT_ID event_id = {PerfInfoGuid, EVENT_SAMPLED_PROFILE}; | |
status = TraceSetInformation(capture->trace_handle, TraceStackTracingInfo, &event_id, sizeof(event_id)); | |
Assert(status == ERROR_SUCCESS); | |
} | |
void StopProfileCapture(ProfileCapture *capture) { | |
ULONG status = ControlTrace(capture->trace_handle, KERNEL_LOGGER_NAME, capture->properties, EVENT_TRACE_CONTROL_STOP); | |
Assert(status == ERROR_SUCCESS); | |
} | |
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { | |
QueryPerformanceFrequency((LARGE_INTEGER *)&timer_frequency); | |
ElevatePrivilege(); | |
ProfileCapture capture; | |
capture.filename = L"d:\\temp\\temp.etl"; | |
capture.delete_old_capture = true; | |
capture.maximum_size_in_megabytes = 100; | |
output_file = CreateFileA("zzz_output.txt", GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); | |
#if 0 | |
StartProfileCapture(&capture); | |
MessageBoxA(0, "Press enter to stop trace.", "zzz", MB_OK); | |
StopProfileCapture(&capture); | |
return 0; | |
#endif | |
uint64_t timer; | |
ProfileAnalysis analysis; | |
InitializeProfileAnalysis(&analysis); | |
ResetTimer(&timer); | |
ParseProfileCapture(&analysis, &capture); | |
Outputf("Parsing time: %llums\n", GetTimerMilliseconds(&timer)); | |
uint32_t process_id = FindProfileId(&analysis, "BTree.exe"); | |
Assert(process_id); | |
ResetTimer(&timer); | |
SymbolizeProfileAnalysisProcess(&analysis, 0); | |
SymbolizeProfileAnalysisProcess(&analysis, process_id); | |
Outputf("Symbolization time: %llums\n", GetTimerMilliseconds(&timer)); | |
OutputExclusiveTime(&analysis, process_id); | |
OutputInclusiveTime(&analysis, process_id); | |
OutputExclusiveCounts(&analysis, process_id); | |
OutputInclusiveCounts(&analysis, process_id); | |
if (output_file != INVALID_HANDLE_VALUE) { | |
CloseHandle(output_file); | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment