Add 'projects/rocprofiler/' from commit '16ae2e90c6157e98e846d2bccbaaf533ca5e662a'
git-subtree-dir: projects/rocprofiler git-subtree-mainline:2a52e3974dgit-subtree-split:16ae2e90c6
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
# ##############################################################################
|
||||
# # Copyright (c) 2022 Advanced Micro Devices, Inc. # # Permission is hereby
|
||||
# granted, free of charge, to any person obtaining a copy # of this software and
|
||||
# associated documentation files (the "Software"), to # deal in the Software
|
||||
# without restriction, including without limitation the # rights to use, copy,
|
||||
# modify, merge, publish, distribute, sublicense, and/or # sell copies of the
|
||||
# Software, and to permit persons to whom the Software is # furnished to do so,
|
||||
# subject to the following conditions: # # The above copyright notice and this
|
||||
# permission notice shall be included in # all copies or substantial portions of
|
||||
# the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
|
||||
# KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
|
||||
# EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
# DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
# OTHERWISE, ARISING # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
# USE OR OTHER DEALINGS # IN THE SOFTWARE.
|
||||
# ##############################################################################
|
||||
|
||||
find_library(
|
||||
ROCPROFV2_ATT rocprofv2_att
|
||||
HINTS ${CMAKE_INSTALL_PREFIX}
|
||||
PATHS ${ROCM_PATH}
|
||||
PATH_SUFFIXES hsa-amd-aqlprofile)
|
||||
|
||||
set(ENV{ROCPROFV2_ATT_LIB_PATH} $ROCPROFV2_ATT)
|
||||
|
||||
# Building att plugin library
|
||||
file(GLOB ROCPROFILER_UTIL_SRC_FILES ${PROJECT_SOURCE_DIR}/src/utils/helper.cpp)
|
||||
file(GLOB FILE_SOURCES att.cpp disassembly.cpp code_printing.cpp)
|
||||
add_library(att_plugin SHARED ${FILE_SOURCES} ${ROCPROFILER_UTIL_SRC_FILES})
|
||||
|
||||
set_target_properties(
|
||||
att_plugin
|
||||
PROPERTIES CXX_VISIBILITY_PRESET hidden
|
||||
LINK_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/../exportmap
|
||||
LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib/rocprofiler
|
||||
INSTALL_RPATH "${ROCM_APPEND_PRIVLIB_RPATH}")
|
||||
|
||||
target_compile_definitions(att_plugin PRIVATE HIP_PROF_HIP_API_STRING=1
|
||||
__HIP_PLATFORM_AMD__=1)
|
||||
|
||||
target_include_directories(att_plugin PRIVATE ${PROJECT_SOURCE_DIR}
|
||||
${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_options(
|
||||
att_plugin PRIVATE -Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/../exportmap
|
||||
-Wl,--no-undefined)
|
||||
target_link_libraries(att_plugin PRIVATE rocprofiler-v2 hsa-runtime64::hsa-runtime64 stdc++fs dw elf amd_comgr)
|
||||
|
||||
install(TARGETS att_plugin LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/${PROJECT_NAME}
|
||||
COMPONENT asan)
|
||||
install(TARGETS att_plugin LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/${PROJECT_NAME}
|
||||
COMPONENT runtime)
|
||||
|
||||
configure_file(att.py att/att.py COPYONLY)
|
||||
configure_file(trace_view.py att/trace_view.py COPYONLY)
|
||||
configure_file(stitch.py att/stitch.py COPYONLY)
|
||||
configure_file(drawing.py att/drawing.py COPYONLY)
|
||||
configure_file(att_to_csv.py att/att_to_csv.py COPYONLY)
|
||||
configure_file(service.py att/service.py COPYONLY)
|
||||
configure_file(ui/index.html att/ui/index.html COPYONLY)
|
||||
configure_file(ui/logo.svg att/ui/logo.svg COPYONLY)
|
||||
configure_file(ui/styles.css att/ui/styles.css COPYONLY)
|
||||
configure_file(ui/httpserver.py att/ui/httpserver.py COPYONLY)
|
||||
install(
|
||||
DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/att
|
||||
DESTINATION ${CMAKE_INSTALL_LIBEXECDIR}/rocprofiler
|
||||
COMPONENT runtime)
|
||||
@@ -0,0 +1,309 @@
|
||||
/* Copyright (c) 2022 Advanced Micro Devices, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE. */
|
||||
|
||||
#include <cxxabi.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <hsa/hsa.h>
|
||||
#include <mutex>
|
||||
#include <sys/stat.h>
|
||||
#include <regex>
|
||||
|
||||
#include "rocprofiler.h"
|
||||
#include "rocprofiler_plugin.h"
|
||||
#include "../utils.h"
|
||||
#include "../../src/core/session/att/att_header.h"
|
||||
|
||||
#include "src/utils/filesystem.hpp"
|
||||
|
||||
#define ATT_FILENAME_MAXBYTES 90
|
||||
#define TEST_INVALID_KERNEL size_t(-1)
|
||||
|
||||
static bool env_var_search(std::string& s) {
|
||||
std::smatch m;
|
||||
std::regex e("(.*)\\%\\q\\{([^}]+)\\}(.*)");
|
||||
std::regex_match(s, m, e);
|
||||
|
||||
if (m.size() != 4) return false;
|
||||
|
||||
while (m.size() == 4) {
|
||||
const char* envvar = getenv(m[2].str().c_str());
|
||||
if (!envvar) envvar = "";
|
||||
s = m[1].str() + envvar + m[3].str();
|
||||
std::regex_match(s, m, e);
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
class att_plugin_t {
|
||||
public:
|
||||
att_plugin_t(void* data) {
|
||||
std::vector<const char*> mpivars = {"MPI_RANK", "OMPI_COMM_WORLD_RANK", "MV2_COMM_WORLD_RANK"};
|
||||
|
||||
for (const char* envvar : mpivars)
|
||||
if (const char* env = getenv(envvar)) {
|
||||
MPI_RANK = atoi(env);
|
||||
MPI_ENABLE = true;
|
||||
break;
|
||||
}
|
||||
|
||||
header.raw = reinterpret_cast<uint64_t>(data);
|
||||
header.reserved = 0x11;
|
||||
}
|
||||
|
||||
bool MPI_ENABLE = false;
|
||||
int MPI_RANK = 0;
|
||||
static std::mutex writing_lock;
|
||||
bool is_valid_{true};
|
||||
rocprofiler::att_header_packet_t header{.raw = 0};
|
||||
std::string output_dir = ".";
|
||||
|
||||
bool CheckAddrMatches(uint64_t kernel_addr, uint64_t base_address, uint64_t size)
|
||||
{
|
||||
return (kernel_addr >= base_address) && (kernel_addr < base_address + size);
|
||||
}
|
||||
|
||||
void InitOutputDir()
|
||||
{
|
||||
static bool bIsInit = false;
|
||||
if (bIsInit) return;
|
||||
bIsInit = true;
|
||||
|
||||
if (const char* env = getenv("OUTPUT_PATH")) output_dir = std::string(env);
|
||||
env_var_search(output_dir);
|
||||
|
||||
if (!output_dir.size()) return;
|
||||
|
||||
try {
|
||||
rocprofiler::common::filesystem::create_directories(output_dir);
|
||||
} catch (...) {}
|
||||
output_dir += '/';
|
||||
}
|
||||
|
||||
inline bool att_file_exists(const std::string& name) {
|
||||
struct stat buffer;
|
||||
return stat(name.c_str(), &buffer) == 0;
|
||||
}
|
||||
|
||||
bool IsValid() const { return is_valid_; }
|
||||
|
||||
int FlushATTRecord(const rocprofiler_record_att_tracer_t* att_tracer_record,
|
||||
rocprofiler_session_id_t session_id, rocprofiler_buffer_id_t buffer_id) {
|
||||
|
||||
if (!att_tracer_record) return ROCPROFILER_STATUS_ERROR;
|
||||
InitOutputDir();
|
||||
|
||||
std::string kernel_name_mangled;
|
||||
// Found problem with rocprofiler API for invalid kernel_ids;
|
||||
if (att_tracer_record->kernel_id.handle != TEST_INVALID_KERNEL) {
|
||||
size_t name_length;
|
||||
CHECK_ROCPROFILER(rocprofiler_query_kernel_info_size(
|
||||
ROCPROFILER_KERNEL_NAME, att_tracer_record->kernel_id, &name_length));
|
||||
const char* kernel_name_c = nullptr;
|
||||
CHECK_ROCPROFILER(rocprofiler_query_kernel_info(
|
||||
ROCPROFILER_KERNEL_NAME, att_tracer_record->kernel_id, &kernel_name_c));
|
||||
|
||||
assert(kernel_name_c && "Rocprofv2 returned an invalid kernel name");
|
||||
|
||||
kernel_name_mangled = std::string(kernel_name_c);
|
||||
free(const_cast<char*>(kernel_name_c));
|
||||
} else { // Temporary. Adding a valid string.
|
||||
kernel_name_mangled = "test_kernel";
|
||||
}
|
||||
|
||||
|
||||
std::string name_demangled =
|
||||
rocprofiler::truncate_name(rocprofiler::cxx_demangle(kernel_name_mangled));
|
||||
|
||||
if (name_demangled.size() > ATT_FILENAME_MAXBYTES) // Limit filename size
|
||||
name_demangled = name_demangled.substr(0, ATT_FILENAME_MAXBYTES);
|
||||
|
||||
std::string outfilepath = output_dir + '/' + name_demangled;
|
||||
outfilepath.reserve(output_dir.size() + 128); // Max filename size
|
||||
if (MPI_ENABLE) outfilepath += "_rank" + std::to_string(MPI_RANK);
|
||||
outfilepath += "_v";
|
||||
|
||||
// Find if this filename already exists. If so, increment vname.
|
||||
int file_iteration = 0;
|
||||
while (att_file_exists(outfilepath + std::to_string(file_iteration) + "_kernel.txt"))
|
||||
file_iteration += 1;
|
||||
|
||||
outfilepath += std::to_string(file_iteration);
|
||||
auto writer_id = att_tracer_record->writer_id;
|
||||
|
||||
std::string fname = outfilepath + "_kernel.txt";
|
||||
std::ofstream kernel_txt_file((outfilepath + "_kernel.txt").c_str());
|
||||
kernel_txt_file << name_demangled << " dispatch[" << writer_id << "] GPU["
|
||||
<< att_tracer_record->gpu_id.handle << "]: " << kernel_name_mangled
|
||||
<< '\n';
|
||||
|
||||
// iterate over each shader engine att trace
|
||||
header.navi = !(att_tracer_record->intercept_list.userdata & 0x1);
|
||||
int se_num = att_tracer_record->shader_engine_data_count;
|
||||
for (int i = 0; i < se_num; i++)
|
||||
{
|
||||
if (!att_tracer_record->shader_engine_data ||
|
||||
!att_tracer_record->shader_engine_data[i].buffer_ptr)
|
||||
continue;
|
||||
printf("--------------collecting data for shader_engine %d---------------\n", i);
|
||||
header.SEID = i;
|
||||
rocprofiler_record_se_att_data_t* se_att_trace = &att_tracer_record->shader_engine_data[i];
|
||||
char* data_buffer_ptr = reinterpret_cast<char*>(se_att_trace->buffer_ptr);
|
||||
|
||||
// dump data in binary format
|
||||
std::ofstream out(outfilepath + "_se" + std::to_string(i) + ".att", std::ios::binary);
|
||||
if (!out.is_open()) {
|
||||
std::cerr << "ATT Failed to open file: " << outfilepath << "_se" << i << ".att\n";
|
||||
return ROCPROFILER_STATUS_ERROR;
|
||||
}
|
||||
if (header.enable && !header.navi)
|
||||
out.write((const char*)&header, sizeof(header.raw));
|
||||
out.write(data_buffer_ptr, se_att_trace->buffer_size);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < att_tracer_record->intercept_list.count; i++)
|
||||
{
|
||||
const auto& symbol = att_tracer_record->intercept_list.symbols[i];
|
||||
if (!symbol.filepath) continue;
|
||||
|
||||
std::string sfilepath(symbol.filepath);
|
||||
bool bCopiedData = symbol.data && symbol.data_size;
|
||||
|
||||
if (bCopiedData)
|
||||
{
|
||||
auto pos = sfilepath.find("://");
|
||||
auto rpos = sfilepath.rfind('/');
|
||||
|
||||
if (pos == std::string::npos || pos+3 >= sfilepath.size()) continue;
|
||||
|
||||
std::string type(sfilepath.begin(), sfilepath.begin()+pos);
|
||||
std::string cut(sfilepath.begin()+rpos+1, sfilepath.end());
|
||||
sfilepath = type + cut + ".out";
|
||||
}
|
||||
|
||||
kernel_txt_file << std::hex << "0x" << symbol.base_address << " 0x" << symbol.mem_size
|
||||
<< ' ' << std::dec << symbol.att_marker_id << ' ' << sfilepath << '\n';
|
||||
|
||||
sfilepath = output_dir + '/' + sfilepath;
|
||||
if (!bCopiedData || att_file_exists(sfilepath)) continue;
|
||||
|
||||
std::ofstream isafile(sfilepath, std::ios::binary);
|
||||
if (!isafile.is_open()) {
|
||||
std::cerr << "Could not open file: " << sfilepath << std::endl;
|
||||
return ROCPROFILER_STATUS_ERROR;
|
||||
}
|
||||
|
||||
isafile.write(symbol.data, symbol.data_size);
|
||||
}
|
||||
|
||||
return ROCPROFILER_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
int WriteBufferRecords(const rocprofiler_record_header_t* begin,
|
||||
const rocprofiler_record_header_t* end,
|
||||
rocprofiler_session_id_t session_id, rocprofiler_buffer_id_t buffer_id) {
|
||||
while (begin < end) {
|
||||
if (!begin) return ROCPROFILER_STATUS_ERROR;
|
||||
switch (begin->kind) {
|
||||
case ROCPROFILER_PROFILER_RECORD:
|
||||
case ROCPROFILER_TRACER_RECORD:
|
||||
case ROCPROFILER_PC_SAMPLING_RECORD:
|
||||
case ROCPROFILER_SPM_RECORD:
|
||||
case ROCPROFILER_COUNTERS_SAMPLER_RECORD:
|
||||
rocprofiler::warning("Invalid record Kind: %d\n", begin->kind);
|
||||
break;
|
||||
|
||||
case ROCPROFILER_ATT_TRACER_RECORD: {
|
||||
rocprofiler_record_att_tracer_t* att_record =
|
||||
const_cast<rocprofiler_record_att_tracer_t*>(
|
||||
reinterpret_cast<const rocprofiler_record_att_tracer_t*>(begin));
|
||||
FlushATTRecord(att_record, session_id, buffer_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
int status = rocprofiler_next_record(begin, &begin, session_id, buffer_id);
|
||||
if (status != ROCPROFILER_STATUS_SUCCESS) return status;
|
||||
}
|
||||
|
||||
return ROCPROFILER_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
att_plugin_t* att_plugin = nullptr;
|
||||
std::mutex att_plugin_t::writing_lock;
|
||||
|
||||
ROCPROFILER_EXPORT int rocprofiler_plugin_initialize(uint32_t rocprofiler_major_version,
|
||||
uint32_t rocprofiler_minor_version,
|
||||
void* data) {
|
||||
if (rocprofiler_major_version != ROCPROFILER_VERSION_MAJOR ||
|
||||
rocprofiler_minor_version < ROCPROFILER_VERSION_MINOR)
|
||||
return ROCPROFILER_STATUS_ERROR;
|
||||
|
||||
std::lock_guard<std::mutex> lock(att_plugin_t::writing_lock);
|
||||
if (att_plugin != nullptr) return ROCPROFILER_STATUS_ERROR;
|
||||
|
||||
att_plugin = new att_plugin_t(data);
|
||||
if (att_plugin->IsValid()) return ROCPROFILER_STATUS_SUCCESS;
|
||||
|
||||
// The plugin failed to initialied, destroy it and return an error.
|
||||
delete att_plugin;
|
||||
att_plugin = nullptr;
|
||||
return ROCPROFILER_STATUS_ERROR;
|
||||
}
|
||||
|
||||
ROCPROFILER_EXPORT void rocprofiler_plugin_finalize() {
|
||||
std::lock_guard<std::mutex> lock(att_plugin_t::writing_lock);
|
||||
if (!att_plugin) return;
|
||||
delete att_plugin;
|
||||
att_plugin = nullptr;
|
||||
}
|
||||
|
||||
ROCPROFILER_EXPORT int rocprofiler_plugin_write_buffer_records(
|
||||
const rocprofiler_record_header_t* begin, const rocprofiler_record_header_t* end,
|
||||
rocprofiler_session_id_t session_id, rocprofiler_buffer_id_t buffer_id) {
|
||||
std::lock_guard<std::mutex> lock(att_plugin_t::writing_lock);
|
||||
if (!att_plugin || !att_plugin->IsValid()) return ROCPROFILER_STATUS_ERROR;
|
||||
return att_plugin->WriteBufferRecords(begin, end, session_id, buffer_id);
|
||||
}
|
||||
|
||||
ROCPROFILER_EXPORT int rocprofiler_plugin_write_record(rocprofiler_record_tracer_t record) {
|
||||
std::lock_guard<std::mutex> lock(att_plugin_t::writing_lock);
|
||||
if (!att_plugin || !att_plugin->IsValid()) return ROCPROFILER_STATUS_ERROR;
|
||||
if (record.header.id.handle == 0) return ROCPROFILER_STATUS_SUCCESS;
|
||||
return ROCPROFILER_STATUS_SUCCESS;
|
||||
}
|
||||
Executable
+641
@@ -0,0 +1,641 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
|
||||
if sys.version_info[0] < 3:
|
||||
raise Exception("Must be using Python 3")
|
||||
|
||||
import os
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
from ctypes import *
|
||||
import ctypes
|
||||
from copy import deepcopy
|
||||
from trace_view import view_trace
|
||||
import sys
|
||||
import glob
|
||||
import numpy as np
|
||||
import stitch
|
||||
import gc
|
||||
from collections import defaultdict
|
||||
import service
|
||||
|
||||
ATT_VERSION = 5
|
||||
|
||||
class pcInfo(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("addr", c_uint64),
|
||||
("marker_id", c_uint64)
|
||||
]
|
||||
def to_v2_pc(self):
|
||||
if self.marker_id == 0:
|
||||
return self.addr
|
||||
return self.addr | (self.marker_id<<service.ID_OFFSET) | (1<<service.HEADER_OFFSET)
|
||||
|
||||
class TraceData(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("type", c_uint64, 8),
|
||||
("hitcount", c_uint64, 56),
|
||||
("latency", c_uint64),
|
||||
("pc", pcInfo)
|
||||
]
|
||||
|
||||
class TraceDataTranslated:
|
||||
def __init__(self, inst):
|
||||
self.type = inst.type
|
||||
self.num_waves = inst.hitcount
|
||||
self.cycles = inst.latency
|
||||
if self.type == stitch.PCINFO:
|
||||
self.cycles = inst.pc.to_v2_pc()
|
||||
|
||||
class Trace:
|
||||
def __init__(self, traceid, tracesize, instructions_array):
|
||||
self.instructions = [TraceDataTranslated(instructions_array[k]) for k in range(tracesize)]
|
||||
self.traceid = traceid
|
||||
|
||||
class PerfEvent(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("time", c_uint64),
|
||||
("event0", c_uint16),
|
||||
("event1", c_uint16),
|
||||
("event2", c_uint16),
|
||||
("event3", c_uint16),
|
||||
("cu", c_uint8),
|
||||
("bank", c_uint8),
|
||||
]
|
||||
|
||||
def toTuple(self):
|
||||
return (
|
||||
int(self.time),
|
||||
int(self.event0),
|
||||
int(self.event1),
|
||||
int(self.event2),
|
||||
int(self.event3),
|
||||
int(self.cu),
|
||||
int(self.bank),
|
||||
)
|
||||
|
||||
|
||||
class CodeWrapped(ctypes.Structure):
|
||||
""" Matches CodeWrapped on the python side """
|
||||
_fields_ = [('line', ctypes.c_char_p),
|
||||
('loc', ctypes.c_char_p),
|
||||
('to_line', ctypes.c_int),
|
||||
('value', ctypes.c_int),
|
||||
('index', ctypes.c_int),
|
||||
('line_num', ctypes.c_int),
|
||||
('addr', ctypes.c_int64)]
|
||||
|
||||
|
||||
class KvPair(ctypes.Structure):
|
||||
""" Matches pair<int, int> = (key, value) on the python side """
|
||||
_fields_ = [('key', ctypes.c_int),
|
||||
('value', ctypes.c_int)]
|
||||
|
||||
|
||||
class ReturnAssemblyInfo(ctypes.Structure):
|
||||
""" Matches ReturnAssemblyInfo on the python side """
|
||||
_fields_ = [('code', POINTER(CodeWrapped)),
|
||||
('jumps', POINTER(KvPair)),
|
||||
('code_len', ctypes.c_int),
|
||||
('jumps_len', ctypes.c_int)]
|
||||
|
||||
|
||||
class WaveState(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("type", c_int32),
|
||||
("duration", c_int32)
|
||||
]
|
||||
|
||||
class WaveInstruction(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("time", c_int64),
|
||||
("duration", c_int64)
|
||||
]
|
||||
|
||||
class Wave(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("simd", ctypes.c_uint8),
|
||||
("wave_id", ctypes.c_uint8),
|
||||
("trap_status", ctypes.c_uint8),
|
||||
("reserved", ctypes.c_uint8),
|
||||
|
||||
# total VMEM/FLAT/LDS/SMEM instructions issued
|
||||
# VMEM Pipeline: instrs and stalls
|
||||
("num_vmem_instrs", ctypes.c_int),
|
||||
("num_vmem_stalls", ctypes.c_int),
|
||||
# FLAT instrs and stalls
|
||||
("num_flat_instrs", ctypes.c_int),
|
||||
("num_flat_stalls", ctypes.c_int),
|
||||
# LDS instr and stalls
|
||||
("num_lds_instrs", ctypes.c_int),
|
||||
("num_lds_stalls", ctypes.c_int),
|
||||
# SCA instrs stalls
|
||||
("num_salu_instrs", ctypes.c_int),
|
||||
("num_smem_instrs", ctypes.c_int),
|
||||
("num_salu_stalls", ctypes.c_int),
|
||||
("num_smem_stalls", ctypes.c_int),
|
||||
# Branch
|
||||
("num_branch_instrs", ctypes.c_int),
|
||||
("num_branch_taken_instrs", ctypes.c_int),
|
||||
("num_branch_stalls", ctypes.c_int),
|
||||
|
||||
# total issued memory instructions
|
||||
("num_mem_instrs", ctypes.c_int),
|
||||
# total valus insts and stalls
|
||||
("num_valu_stalls", ctypes.c_int),
|
||||
("num_valu_instrs", ctypes.c_size_t),
|
||||
# total issued instructions (compute + memory)
|
||||
("num_issued_instrs", ctypes.c_size_t),
|
||||
# Begin and end cycle
|
||||
("begin_time", ctypes.c_int64),
|
||||
("end_time", ctypes.c_int64),
|
||||
("traceid", ctypes.c_int64),
|
||||
|
||||
("timeline_size", ctypes.c_size_t),
|
||||
("instructions_size", ctypes.c_size_t),
|
||||
("timeline_array", POINTER(WaveState)),
|
||||
("instructions_array", POINTER(WaveInstruction)),
|
||||
]
|
||||
|
||||
|
||||
class PythonWave:
|
||||
def __init__(self, sourcew):
|
||||
for property, value in Wave._fields_:
|
||||
try:
|
||||
setattr(self, deepcopy(property), deepcopy(getattr(sourcew, property)))
|
||||
except:
|
||||
pass
|
||||
|
||||
self.timeline = [
|
||||
(int(sourcew.timeline_array[k].type), int(sourcew.timeline_array[k].duration))
|
||||
for k in range(self.timeline_size)
|
||||
]
|
||||
self.timeline_array = None
|
||||
|
||||
self.instructions = [
|
||||
(int(sourcew.instructions_array[k].time), int(sourcew.instructions_array[k].duration))
|
||||
for k in range(self.instructions_size)
|
||||
]
|
||||
self.instructions_array = None
|
||||
|
||||
|
||||
class ReturnInfo(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("flags", ctypes.c_uint64),
|
||||
("binaryID", ctypes.c_uint64),
|
||||
("num_traces", ctypes.c_uint64),
|
||||
("tracesizes", POINTER(ctypes.c_uint64)),
|
||||
("traceIDs", POINTER(ctypes.c_int64)),
|
||||
("tracedata", POINTER(POINTER(TraceData))),
|
||||
|
||||
("num_events", ctypes.c_uint64),
|
||||
("perfevents", POINTER(PerfEvent)),
|
||||
("occupancy", POINTER(ctypes.c_uint64)),
|
||||
("num_occupancy", ctypes.c_uint64),
|
||||
("kernel_id_addr", POINTER(pcInfo)),
|
||||
("num_kernel_ids", ctypes.c_uint64),
|
||||
|
||||
("wavedata", POINTER(Wave)),
|
||||
("num_waves", ctypes.c_uint64),
|
||||
]
|
||||
|
||||
|
||||
rocprofv2_att_lib = os.getenv("ROCPROFV2_ATT_LIB_PATH")
|
||||
if rocprofv2_att_lib is None:
|
||||
print(
|
||||
"ATT Lib path not set. Use export ROCPROFV2_ATT_LIB_PATH=/path/to/librocprofv2_att.so"
|
||||
)
|
||||
quit()
|
||||
path_to_parser = os.path.abspath(rocprofv2_att_lib)
|
||||
SO = CDLL(path_to_parser)
|
||||
|
||||
SO.AnalyseBinary.restype = ReturnInfo
|
||||
SO.AnalyseBinary.argtypes = [ctypes.c_char_p]
|
||||
SO.wrapped_parse_binary.argtypes = [ctypes.c_char_p, ctypes.c_char_p]
|
||||
SO.wrapped_parse_binary.restype = ReturnAssemblyInfo
|
||||
SO.FreeBinary.argtypes = [ctypes.c_uint64]
|
||||
|
||||
def parse_binary(filename, kernel=None):
|
||||
if kernel is None or kernel == "":
|
||||
kernel = ctypes.c_char_p(0)
|
||||
else:
|
||||
with open(glob.glob(kernel)[0], "r") as file:
|
||||
kernel = file.readlines()
|
||||
kernel = kernel[0].split(": ")[1].split(".kd")[0]
|
||||
kernel = str(kernel).encode("utf-8")
|
||||
filename = os.path.abspath(str(filename))
|
||||
info = SO.wrapped_parse_binary(str(filename).encode("utf-8"), kernel)
|
||||
|
||||
code = []
|
||||
for k in range(info.code_len):
|
||||
code_entry = info.code[k]
|
||||
|
||||
line = deepcopy(code_entry.line.decode("utf-8"))
|
||||
loc = deepcopy(code_entry.loc.decode("utf-8"))
|
||||
|
||||
to_line = int(code_entry.to_line) if (code_entry.to_line >= 0) else None
|
||||
loc = loc if len(loc) > 0 else None
|
||||
|
||||
# asm, inst_type, addr, loc, index, line_num, hitcount, cycles
|
||||
code.append([line, int(code_entry.value), to_line, loc, int(code_entry.index),
|
||||
int(code_entry.line_num), int(code_entry.addr), 0, 0])
|
||||
|
||||
jumps = {}
|
||||
for k in range(info.jumps_len):
|
||||
jumps[info.jumps[k].key] = info.jumps[k].value
|
||||
|
||||
return code, jumps
|
||||
|
||||
|
||||
def getWaves_binary(name):
|
||||
filename = os.path.abspath(str(name))
|
||||
info = SO.AnalyseBinary(filename.encode("utf-8"))
|
||||
|
||||
isValid = info.flags & 0x1
|
||||
if isValid == 0:
|
||||
print('Invalid trace ', name)
|
||||
return ([], [], [], [], None, [])
|
||||
flags = "navi" if (info.flags & 0x2) else "vega"
|
||||
kernel_addr = [info.kernel_id_addr[k].to_v2_pc() for k in range(info.num_kernel_ids)]
|
||||
events = [deepcopy(info.perfevents[k]) for k in range(info.num_events)]
|
||||
occupancy = [int(info.occupancy[k]) for k in range(int(info.num_occupancy))]
|
||||
|
||||
assert(((info.flags >> 3) & 0x1FFF == ATT_VERSION)) # Check ATT parser version
|
||||
|
||||
traces_python = {}
|
||||
for T in range(info.num_traces):
|
||||
if info.tracesizes[T] > 2:
|
||||
id = info.traceIDs[T]
|
||||
traces_python[id] = Trace(id, int(info.tracesizes[T]), info.tracedata[T])
|
||||
|
||||
waves_python = []
|
||||
for k in range(info.num_waves):
|
||||
if info.wavedata[k].instructions_size > 2:
|
||||
waves_python.append(PythonWave(info.wavedata[k]))
|
||||
|
||||
SO.FreeBinary(info.binaryID)
|
||||
|
||||
return (traces_python, waves_python, events, occupancy, flags, kernel_addr, info.flags & 0x4)
|
||||
|
||||
|
||||
def getWaves_stitch(traces, code, jumps, flags, latency_map, hitcount_map, bIsAuto, codeobjservice):
|
||||
for id in traces.keys():
|
||||
traces[id].instructions = stitch.stitch(traces[id].instructions, code, jumps, flags, bIsAuto, codeobjservice)
|
||||
if len(code) > hitcount_map.size:
|
||||
hitcount_map = np.pad(hitcount_map, [0,len(code)-hitcount_map.size])
|
||||
latency_map = np.pad(latency_map, [0,len(code)-latency_map.size])
|
||||
if traces[id].instructions is not None:
|
||||
for inst in traces[id].instructions[0]:
|
||||
hitcount_map[inst.asmline] += inst.num_waves
|
||||
latency_map[inst.asmline] += inst.cycles
|
||||
return hitcount_map, latency_map
|
||||
|
||||
|
||||
def persist(trace_file, SIMD, traces):
|
||||
trace = Path(trace_file).name
|
||||
simds, waves = [], []
|
||||
begin_time, end_time, timeline, instructions, trace_ids = [], [], [], [], []
|
||||
mem_ins, issued_ins, valu_ins, valu_stalls = [], [], [], []
|
||||
vmem_ins, vmem_stalls, flat_ins, flat_stalls = [], [], [], []
|
||||
lds_ins, lds_stalls, salu_ins, salu_stalls = [], [], [], []
|
||||
smem_ins, smem_stalls, br_ins, br_taken_ins, br_stalls = [], [], [], [], []
|
||||
|
||||
for wave in SIMD:
|
||||
try:
|
||||
if wave.instructions is None or traces[wave.traceid].instructions is None:
|
||||
continue
|
||||
except:
|
||||
continue
|
||||
simds.append(wave.simd)
|
||||
waves.append(wave.wave_id)
|
||||
begin_time.append(wave.begin_time)
|
||||
end_time.append(wave.end_time)
|
||||
trace_ids.append(wave.traceid)
|
||||
mem_ins.append(wave.num_mem_instrs)
|
||||
issued_ins.append(wave.num_issued_instrs)
|
||||
valu_ins.append(wave.num_valu_instrs)
|
||||
valu_stalls.append(wave.num_valu_stalls)
|
||||
vmem_ins.append(wave.num_vmem_instrs)
|
||||
vmem_stalls.append(wave.num_vmem_stalls)
|
||||
flat_ins.append(wave.num_flat_instrs)
|
||||
flat_stalls.append(wave.num_flat_stalls)
|
||||
lds_ins.append(wave.num_lds_instrs)
|
||||
lds_stalls.append(wave.num_lds_stalls)
|
||||
salu_ins.append(wave.num_salu_instrs)
|
||||
salu_stalls.append(wave.num_salu_stalls)
|
||||
smem_ins.append(wave.num_smem_instrs)
|
||||
smem_stalls.append(wave.num_smem_stalls)
|
||||
br_ins.append(wave.num_branch_instrs)
|
||||
br_taken_ins.append(wave.num_branch_taken_instrs)
|
||||
br_stalls.append(wave.num_branch_stalls)
|
||||
timeline.append(wave.timeline)
|
||||
|
||||
cc = 0
|
||||
insts = []
|
||||
skips = traces[wave.traceid].instructions[-1]
|
||||
try:
|
||||
for v in traces[wave.traceid].instructions[0]:
|
||||
while cc in skips:
|
||||
cc += 1
|
||||
t = wave.instructions[cc]
|
||||
insts.append((t[0], v.type, 0, t[1], v.asmline))
|
||||
cc += 1
|
||||
except:
|
||||
pass # Incomplete waves
|
||||
instructions.append((insts,) + traces[wave.traceid].instructions[1:-1])
|
||||
|
||||
df = {
|
||||
"name": [trace for _ in range(len(begin_time))],
|
||||
"id": [i for i in range(len(begin_time))],
|
||||
"simd": simds,
|
||||
"wave_slot": waves,
|
||||
"begin_time": begin_time,
|
||||
"end_time": end_time,
|
||||
"mem_ins": mem_ins,
|
||||
"issued_ins": issued_ins,
|
||||
"valu_ins": valu_ins,
|
||||
"valu_stalls": valu_stalls,
|
||||
"vmem_ins": vmem_ins,
|
||||
"vmem_stalls": vmem_stalls,
|
||||
"flat_ins": flat_ins,
|
||||
"flat_stalls": flat_stalls,
|
||||
"lds_ins": lds_ins,
|
||||
"lds_stalls": lds_stalls,
|
||||
"salu_ins": salu_ins,
|
||||
"salu_stalls": salu_stalls,
|
||||
"smem_ins": smem_ins,
|
||||
"smem_stalls": smem_stalls,
|
||||
"br_ins": br_ins,
|
||||
"br_taken_ins": br_taken_ins,
|
||||
"br_stalls": br_stalls,
|
||||
"timeline": timeline,
|
||||
"instructions": instructions,
|
||||
"traceids": trace_ids,
|
||||
}
|
||||
return df
|
||||
|
||||
|
||||
def mem_max(array):
|
||||
mem_dict = {}
|
||||
for SE in array:
|
||||
for wave in SE:
|
||||
for inst in wave:
|
||||
try:
|
||||
mem_dict[inst[0]][0] = max(mem_dict[inst[0]][0], inst[1])
|
||||
except:
|
||||
mem_dict[inst[0]] = inst[1:]
|
||||
assert mem_dict[inst[0]][1] == inst[2]
|
||||
|
||||
return mem_dict
|
||||
|
||||
|
||||
def lgk(count):
|
||||
return "lgkmcnt({0})".format(count)
|
||||
|
||||
|
||||
def vmc(count):
|
||||
return "vmcnt({0})".format(count)
|
||||
|
||||
|
||||
def both_cnt(count):
|
||||
return lgk(count) + " " + vmc(count)
|
||||
|
||||
|
||||
def insert_waitcnt(flight_count, assembly_code):
|
||||
flight_count = mem_max(flight_count)
|
||||
for key in sorted(flight_count):
|
||||
line_n = key
|
||||
(
|
||||
issue_amount,
|
||||
waitcnt_amount,
|
||||
) = flight_count[key]
|
||||
if "vmcnt" in assembly_code[line_n] and "lgkmcnt" in assembly_code[line_n]:
|
||||
counter_type = both_cnt
|
||||
elif "vmcnt" in assembly_code[line_n]:
|
||||
counter_type = vmc
|
||||
elif "lgkmcnt" in assembly_code[line_n]:
|
||||
counter_type = lgk
|
||||
else:
|
||||
print("Error: Line mismatch")
|
||||
exit(-1)
|
||||
|
||||
for count in range(waitcnt_amount + 1, issue_amount):
|
||||
print("Inserted line: " + str(line_n))
|
||||
as_index = line_n - count / (issue_amount + 1)
|
||||
assembly_code[as_index] = "\ts_waitcnt {0}\t\t; Timing analysis.".format(
|
||||
counter_type(count)
|
||||
)
|
||||
as_index += 0.5 / (issue_amount + 1)
|
||||
assembly_code[as_index] = "\ts_nop 0\t\t\t\t\t\t; Counters: " + str(
|
||||
issue_amount
|
||||
)
|
||||
|
||||
return assembly_code
|
||||
|
||||
|
||||
def gen_timelines(DBFILES):
|
||||
TIMELINES = [np.zeros(int(1E6), dtype=np.float32) for k in range(5)]
|
||||
TIME_RESOLUTION = 16
|
||||
for df in DBFILES:
|
||||
for T in range(len(df["timeline"])):
|
||||
timeline = df["timeline"][T]
|
||||
time_acc = 0
|
||||
tuples3 = [(0, df["begin_time"][T])] + [(int(t[0]), int(t[1])) for t in timeline]
|
||||
|
||||
for state in tuples3:
|
||||
t_end = (time_acc + state[1])//TIME_RESOLUTION
|
||||
if t_end > 1E8:
|
||||
print("Warning: Time limit reached for ", state[0], state[1])
|
||||
break
|
||||
elif t_end > TIMELINES[state[0]].size:
|
||||
TIMELINES[state[0]] = np.hstack(
|
||||
[TIMELINES[state[0]], np.zeros_like(TIMELINES[state[0]])]
|
||||
)
|
||||
TIMELINES[state[0]][time_acc//TIME_RESOLUTION : t_end] += 1
|
||||
time_acc += state[1]
|
||||
return TIMELINES
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pathenv = os.getenv("OUTPUT_PATH")
|
||||
if pathenv is None:
|
||||
pathenv = "."
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"assembly_code", help="Path to the assembly code. Must be the first parameter."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--trace_file", help="Filter for trace files", default=None, type=str
|
||||
)
|
||||
parser.add_argument(
|
||||
"--att_kernel", help="Kernel file", type=str, default=os.path.join(pathenv, "*_kernel.txt")
|
||||
)
|
||||
parser.add_argument("--ports", help="Server and websocket ports, default: 8000,18000")
|
||||
parser.add_argument(
|
||||
"--mode",
|
||||
help="""ATT analysis modes:\n
|
||||
off: Only run ATT collection, disable analysis.\n
|
||||
file: dump json files to disk.""",
|
||||
type=str,
|
||||
default="off",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
args.mode = args.mode.lower().split(',')
|
||||
|
||||
CSV_MODE = False
|
||||
FILE_MODE = False
|
||||
if 'csv' in args.mode:
|
||||
CSV_MODE = True
|
||||
if 'file' in args.mode:
|
||||
FILE_MODE = True
|
||||
|
||||
if not CSV_MODE and not FILE_MODE:
|
||||
print("Skipping analysis.")
|
||||
quit()
|
||||
|
||||
if os.getenv("COUNTERS_PATH"):
|
||||
with open(os.getenv("COUNTERS_PATH"), "r") as f:
|
||||
lines = [l.split("//")[0] for l in f.readlines()]
|
||||
|
||||
EVENT_NAMES = []
|
||||
clean = lambda x: x.split("=")[1].split(" ")[0].split("\n")[0]
|
||||
for line in lines:
|
||||
if "PERFCOUNTER_ID=" in line:
|
||||
EVENT_NAMES += ["id: " + clean(line)]
|
||||
for line in lines:
|
||||
if "PERFCOUNTER=" in line:
|
||||
EVENT_NAMES += [clean(line).split("SQ_")[1].lower()]
|
||||
|
||||
att_kernel_list = glob.glob(args.att_kernel)
|
||||
|
||||
if len(att_kernel_list) == 0:
|
||||
print("Could not find att output kernel:", args.att_kernel)
|
||||
quit()
|
||||
|
||||
for att_kernel in att_kernel_list:
|
||||
print('Parsing:', att_kernel)
|
||||
att_kernel_f = []
|
||||
with open(att_kernel, 'r') as f:
|
||||
for line in f:
|
||||
att_kernel_f.append(line.split('\n')[0])
|
||||
|
||||
# returns element index of the file:// or memory:// filepath in the string
|
||||
get_path_loc = lambda x: np.sum([len(m) for m in x.split(' ')[:3]])+3
|
||||
# adds the OUTPUT_PATH env variable to a filepath if necessary
|
||||
add_pathnv = lambda x: x[:get_path_loc(x)] + os.path.join(pathenv, x[get_path_loc(x):])
|
||||
# returns the memory address in the string
|
||||
get_addr = lambda x: int(x.split(' ')[0][2:], 16)
|
||||
# Sets a preference for 'file' paths to be added before 'memory' paths. Sorts by addr.
|
||||
get_addr_preference = lambda x: [0 if 'file' in x[get_path_loc(x):] else 1<<60][0]
|
||||
|
||||
# Get the GPU id in the string
|
||||
gpu_id = int(att_kernel_f[0].split(' ')[2].split('GPU[')[1].split(']')[0])
|
||||
# Eliminame first line in the att_kernel txt file and adds the OUTPUT_PATH as needed
|
||||
att_kernel_f = [add_pathnv(p) if '.out' == p[-4:] else p for p in att_kernel_f[1:]]
|
||||
# Sorts the list of codeobj by address, with 'file' given preference
|
||||
att_kernel_f = sorted(att_kernel_f, key=lambda x: get_addr(x)+get_addr_preference(x))
|
||||
assembly_code = deepcopy(args.assembly_code)
|
||||
|
||||
# Assembly parsing
|
||||
bIsAuto = False
|
||||
if assembly_code.lower().strip() == 'auto':
|
||||
bIsAuto = True
|
||||
code = [['; Begin ATT ASM', 100, 0, '', 0, 0, 0, 0, 0]]
|
||||
jumps = []
|
||||
elif not Path(assembly_code).is_file():
|
||||
print("Invalid assembly_code('{0}')!".format(assembly_code))
|
||||
sys.exit(1)
|
||||
else:
|
||||
code, jumps = parse_binary(assembly_code, att_kernel)
|
||||
|
||||
# Trace Parsing
|
||||
trace_instance_name = att_kernel.split("_kernel.txt")[0]
|
||||
if args.trace_file is None:
|
||||
filenames = glob.glob(trace_instance_name + "_*.att")
|
||||
else:
|
||||
filenames = glob.glob(args.trace_file)
|
||||
|
||||
if len(filenames) == 0:
|
||||
print("Could not find trace files for", att_kernel)
|
||||
continue
|
||||
|
||||
print('Att kernel:', att_kernel)
|
||||
|
||||
DBFILES = []
|
||||
EVENTS = []
|
||||
OCCUPANCY = []
|
||||
GFXV = []
|
||||
analysed_filenames = []
|
||||
occupancy_filenames = []
|
||||
kernel_addr = {}
|
||||
|
||||
latency_map = np.zeros((len(code)), dtype=np.int64)
|
||||
hitcount_map = np.zeros((len(code)), dtype=np.int32)
|
||||
|
||||
gc.collect()
|
||||
if bIsAuto:
|
||||
codeservice = service.CodeobjService(gpu_id, att_kernel_f, SO.classify_asm_line)
|
||||
else:
|
||||
codeservice = None
|
||||
|
||||
for name in filenames:
|
||||
traces, waves, perfevents, occupancy, gfxv, addrs, ftrace = getWaves_binary(name)
|
||||
if gfxv is None:
|
||||
continue
|
||||
if CSV_MODE == False and ftrace == 0:
|
||||
print('Generating occupancy information')
|
||||
|
||||
for id, addr in enumerate(addrs):
|
||||
kernel_addr[id] = addr
|
||||
if len(occupancy) > 1:
|
||||
OCCUPANCY.append( occupancy )
|
||||
occupancy_filenames.append(name)
|
||||
|
||||
if np.sum([0]+[len(s.instructions) for id, s in traces.items()]) == 0:
|
||||
print("No traces from", name)
|
||||
continue
|
||||
|
||||
hitcount_map, latency_map = getWaves_stitch(traces, code, jumps, gfxv, latency_map, hitcount_map, bIsAuto, codeservice)
|
||||
|
||||
analysed_filenames.append(name)
|
||||
EVENTS.append(perfevents)
|
||||
DBFILES.append( persist(name, waves, traces) )
|
||||
GFXV.append(gfxv)
|
||||
|
||||
gc.collect()
|
||||
for k in range(len(code)):
|
||||
code[k][-2] = int(hitcount_map[k])
|
||||
code[k][-1] = int(latency_map[k])
|
||||
try:
|
||||
code[k][-3] = codeservice.ToRawPC(code[k][-3])
|
||||
except:
|
||||
pass
|
||||
if k > 0 and code[k-1][-3] == 0:
|
||||
code[k-1][-3] = code[k][-3]
|
||||
|
||||
if CSV_MODE:
|
||||
from att_to_csv import dump_csv
|
||||
dump_csv(code, trace_instance_name, bIsAuto)
|
||||
|
||||
if FILE_MODE:
|
||||
try:
|
||||
dispatchNames = {id: codeservice.getSymbolName(addr) for id, addr in kernel_addr.items()}
|
||||
except:
|
||||
dispatchNames = {id: "#addr"+str(addr) for id, addr in kernel_addr.items()}
|
||||
drawinfo = {
|
||||
"TIMELINES": gen_timelines(DBFILES),
|
||||
"EVENTS": EVENTS,
|
||||
"EVENT_NAMES": EVENT_NAMES,
|
||||
"OCCUPANCY": OCCUPANCY,
|
||||
"ShaderNames": occupancy_filenames,
|
||||
"DispatchNames": dispatchNames
|
||||
}
|
||||
view_trace(
|
||||
code,
|
||||
DBFILES,
|
||||
analysed_filenames,
|
||||
0,
|
||||
gfxv,
|
||||
drawinfo,
|
||||
trace_instance_name
|
||||
)
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import numpy as np
|
||||
import csv
|
||||
import os
|
||||
|
||||
def dump_csv(code, trace_instance_name, bIsAuto):
|
||||
outpath = os.getenv("OUT_FILE_NAME")
|
||||
if outpath is None:
|
||||
outpath = "att_output"
|
||||
elif os.path.dirname(outpath) != '':
|
||||
os.makedirs(os.path.dirname(outpath), exist_ok=True)
|
||||
|
||||
outpath += '_' + os.path.basename(trace_instance_name) + '.csv'
|
||||
print('Generating', outpath)
|
||||
|
||||
with open(outpath, 'w') as f:
|
||||
writer = csv.writer(f)
|
||||
if bIsAuto:
|
||||
writer.writerow(['Addr', 'Instruction', 'Hitcount', 'Cycles', 'C++ Reference'])
|
||||
[writer.writerow([hex(m[6]), m[0], m[7], m[8], m[3]]) for m in code]
|
||||
else:
|
||||
writer.writerow(['Line', 'Instruction', 'Hitcount', 'Cycles', 'C++ Reference'])
|
||||
[writer.writerow([m[5], m[0], m[7], m[8], m[3]]) for m in code]
|
||||
@@ -0,0 +1,383 @@
|
||||
/* Copyright (c) 2022 Advanced Micro Devices, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE. */
|
||||
|
||||
#include "code_printing.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <hsa/amd_hsa_elf.h>
|
||||
#include "../utils.h"
|
||||
#include <cxxabi.h>
|
||||
#include <elfutils/libdw.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#define C_API_BEGIN try {
|
||||
|
||||
#define C_API_END(returndata) \
|
||||
} catch (std::exception& e) \
|
||||
{ \
|
||||
std::string s = e.what(); \
|
||||
if (s.find("memory protocol not supported!") == std::string::npos) \
|
||||
std::cerr << "Codeobj API lookup: " << e.what() << std::endl; \
|
||||
return returndata; \
|
||||
} \
|
||||
catch (std::string& s) \
|
||||
{ \
|
||||
if (s.find("memory protocol not supported!") == std::string::npos) \
|
||||
std::cerr << "Codeobj API lookup: " << s << std::endl; \
|
||||
return returndata; \
|
||||
} \
|
||||
catch (...) \
|
||||
{ \
|
||||
return returndata; \
|
||||
}
|
||||
|
||||
CodeObjDecoderComponent::CodeObjDecoderComponent(
|
||||
const char* codeobj_data,
|
||||
uint64_t codeobj_size,
|
||||
uint64_t gpu_id
|
||||
) {
|
||||
if (
|
||||
codeobj_size <= 4 ||
|
||||
codeobj_data[0] != ELFMAG0 ||
|
||||
codeobj_data[1] != ELFMAG1 ||
|
||||
codeobj_data[2] != ELFMAG2 ||
|
||||
codeobj_data[3] != ELFMAG3
|
||||
)
|
||||
throw std::invalid_argument("Invalid ELF file");
|
||||
|
||||
m_fd = -1;
|
||||
#if defined(_GNU_SOURCE) && defined(MFD_ALLOW_SEALING) && defined(MFD_CLOEXEC)
|
||||
m_fd = ::memfd_create(m_uri.c_str(), MFD_ALLOW_SEALING | MFD_CLOEXEC);
|
||||
#endif
|
||||
if (m_fd == -1) // If fail, attempt under /tmp
|
||||
m_fd = ::open("/tmp", O_TMPFILE | O_RDWR, 0666);
|
||||
|
||||
if (m_fd == -1) {
|
||||
printf("could not create a temporary file for code object\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (size_t size = ::write(m_fd, codeobj_data, codeobj_size); size != codeobj_size) {
|
||||
printf("could not write to the temporary file\n");
|
||||
return;
|
||||
}
|
||||
::lseek(m_fd, 0, SEEK_SET);
|
||||
fsync(m_fd);
|
||||
|
||||
m_line_number_map = {};
|
||||
|
||||
std::unique_ptr<Dwarf, void (*)(Dwarf*)> dbg(dwarf_begin(m_fd, DWARF_C_READ),
|
||||
[](Dwarf* dbg) { dwarf_end(dbg); });
|
||||
|
||||
/*if (!dbg) {
|
||||
rocprofiler::warning("Error opening Dwarf!\n");
|
||||
return;
|
||||
} */
|
||||
|
||||
if (dbg) {
|
||||
Dwarf_Off cu_offset{0}, next_offset;
|
||||
size_t header_size;
|
||||
|
||||
std::map<uint64_t, std::string> used_addrs;
|
||||
|
||||
while (!dwarf_nextcu(dbg.get(), cu_offset, &next_offset, &header_size, nullptr, nullptr,
|
||||
nullptr)) {
|
||||
Dwarf_Die die;
|
||||
if (!dwarf_offdie(dbg.get(), cu_offset + header_size, &die)) continue;
|
||||
|
||||
Dwarf_Lines* lines;
|
||||
size_t line_count;
|
||||
if (dwarf_getsrclines(&die, &lines, &line_count)) continue;
|
||||
|
||||
for (size_t i = 0; i < line_count; ++i) {
|
||||
Dwarf_Addr addr;
|
||||
int line_number;
|
||||
Dwarf_Line* line = dwarf_onesrcline(lines, i);
|
||||
|
||||
if (line && !dwarf_lineaddr(line, &addr) && !dwarf_lineno(line, &line_number) && line_number)
|
||||
{
|
||||
std::string src = dwarf_linesrc(line, nullptr, nullptr);
|
||||
auto dwarf_line = src + ':' + std::to_string(line_number);
|
||||
|
||||
if (used_addrs.find(addr) != used_addrs.end())
|
||||
{
|
||||
used_addrs.at(addr) += ' ' + dwarf_line;
|
||||
continue;
|
||||
}
|
||||
|
||||
used_addrs.emplace(addr, std::move(dwarf_line));
|
||||
}
|
||||
}
|
||||
cu_offset = next_offset;
|
||||
}
|
||||
|
||||
auto it = used_addrs.begin();
|
||||
if(it != used_addrs.end())
|
||||
{
|
||||
while(std::next(it) != used_addrs.end())
|
||||
{
|
||||
uint64_t delta = std::next(it)->first - it->first;
|
||||
auto segment = address_range_t{it->first, delta, 0};
|
||||
m_line_number_map.emplace(segment, std::move(it->second));
|
||||
it++;
|
||||
}
|
||||
auto segment = address_range_t{it->first, codeobj_size - it->first, 0};
|
||||
m_line_number_map.emplace(segment, std::move(it->second));
|
||||
}
|
||||
}
|
||||
|
||||
// Can throw
|
||||
disassembly = std::make_unique<DisassemblyInstance>(codeobj_data, codeobj_size, gpu_id);
|
||||
try {
|
||||
m_symbol_map = disassembly->GetKernelMap(); // Can throw
|
||||
} catch(...) {}
|
||||
|
||||
//disassemble_kernels();
|
||||
}
|
||||
|
||||
CodeObjDecoderComponent::~CodeObjDecoderComponent() {
|
||||
if (m_fd) ::close(m_fd);
|
||||
}
|
||||
|
||||
std::optional<SymbolInfo> CodeObjDecoderComponent::find_symbol(uint64_t vaddr) {
|
||||
/* Load the symbol table. */
|
||||
auto it = m_symbol_map.upper_bound(vaddr);
|
||||
if (it == m_symbol_map.begin())
|
||||
return std::nullopt;
|
||||
|
||||
auto&& [symbol_vaddr, symbol] = *std::prev(it);
|
||||
if (vaddr >= symbol_vaddr + symbol.mem_size)
|
||||
return std::nullopt;
|
||||
|
||||
std::string symbol_name = symbol.name;
|
||||
|
||||
int status = 0;
|
||||
auto* demangled_name = abi::__cxa_demangle(symbol_name.c_str(), nullptr, nullptr, &status);
|
||||
if (status == 0 && demangled_name)
|
||||
{
|
||||
symbol_name = demangled_name;
|
||||
free(demangled_name);
|
||||
}
|
||||
return SymbolInfo{symbol_name, symbol.faddr, symbol.mem_size};
|
||||
}
|
||||
|
||||
std::pair<instruction_instance_t, size_t>
|
||||
CodeObjDecoderComponent::disassemble_instruction(uint64_t faddr, uint64_t vaddr)
|
||||
{
|
||||
if (!disassembly)
|
||||
throw std::exception();
|
||||
|
||||
const char* cpp_line = nullptr;
|
||||
|
||||
auto it = m_line_number_map.find({vaddr, 0, 0});
|
||||
if(it != m_line_number_map.end()) cpp_line = it->second.data();
|
||||
|
||||
size_t size = disassembly->ReadInstruction(faddr, vaddr, cpp_line);
|
||||
return {disassembly->last_instruction, size};
|
||||
}
|
||||
|
||||
void CodeObjDecoderComponent::disassemble_kernel(uint64_t faddr, uint64_t vaddr)
|
||||
{
|
||||
if (!disassembly) return;
|
||||
auto symbol = find_symbol(vaddr);
|
||||
|
||||
if (!symbol)
|
||||
{
|
||||
std::cerr << "No symbol found at address 0x" << std::hex << faddr << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << "Dumping ISA for " << symbol->name << std::endl;
|
||||
|
||||
uint64_t end_addr = faddr + symbol->mem_size;
|
||||
while (faddr < end_addr)
|
||||
{
|
||||
size_t size;
|
||||
instruction_instance_t inst;
|
||||
std::tie(inst, size) = this->disassemble_instruction(faddr, vaddr);
|
||||
instructions.push_back(inst);
|
||||
faddr += size;
|
||||
vaddr += size;
|
||||
}
|
||||
}
|
||||
|
||||
void CodeObjDecoderComponent::disassemble_kernels() {
|
||||
for (auto& [vaddr, v] : m_symbol_map) disassemble_kernel(v.faddr, vaddr);
|
||||
}
|
||||
|
||||
void CodeObjDecoderComponent::disassemble_single_kernel(uint64_t kaddr) {
|
||||
for (auto& [vaddr, v] : m_symbol_map)
|
||||
if (kaddr >= vaddr && kaddr < vaddr + v.mem_size)
|
||||
disassemble_kernel(v.faddr, vaddr);
|
||||
}
|
||||
|
||||
CodeobjDecoder::CodeobjDecoder(
|
||||
const char* filepath,
|
||||
uint64_t loadbase,
|
||||
uint64_t mem_size,
|
||||
uint64_t gpu_id
|
||||
): loadbase(loadbase), load_end(loadbase + mem_size)
|
||||
{
|
||||
if (!filepath) throw "Empty filepath.";
|
||||
|
||||
std::string_view fpath(filepath);
|
||||
|
||||
if (fpath.rfind(".out") + 4 == fpath.size())
|
||||
{
|
||||
std::ifstream file(filepath, std::ios::in | std::ios::binary);
|
||||
|
||||
if (!file.is_open())
|
||||
throw "Invalid filename " + std::string(filepath);
|
||||
|
||||
std::vector<char> buffer;
|
||||
file.seekg(0, file.end);
|
||||
buffer.resize(file.tellg());
|
||||
file.seekg(0, file.beg);
|
||||
file.read(buffer.data(), buffer.size());
|
||||
|
||||
decoder = std::make_unique<CodeObjDecoderComponent>(buffer.data(), buffer.size(), gpu_id);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::unique_ptr<CodeObjectBinary> binary = std::make_unique<CodeObjectBinary>(filepath);
|
||||
auto& buffer = binary->buffer;
|
||||
decoder = std::make_unique<CodeObjDecoderComponent>(buffer.data(), buffer.size(), gpu_id);
|
||||
}
|
||||
|
||||
auto elf_segments = decoder->disassembly->getSegments();
|
||||
}
|
||||
|
||||
bool CodeobjDecoder::add_to_map(uint64_t faddr, uint64_t vaddr, uint64_t voffset)
|
||||
{
|
||||
try
|
||||
{
|
||||
decoded_map[vaddr] = decoder->disassemble_instruction(faddr, voffset);
|
||||
}
|
||||
catch(std::exception& e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CodeobjDecoder::decode_single_at_offset(uint64_t vaddr, uint64_t voffset)
|
||||
{
|
||||
auto faddr = decoder->disassembly->va2fo(voffset);
|
||||
if (!faddr)
|
||||
return false;
|
||||
|
||||
return add_to_map(*faddr, vaddr, voffset);
|
||||
}
|
||||
|
||||
bool CodeobjDecoder::decode_single(uint64_t vaddr)
|
||||
{
|
||||
if (!decoder || vaddr < loadbase) return false;
|
||||
return decode_single_at_offset(vaddr, vaddr-loadbase);
|
||||
}
|
||||
|
||||
std::pair<instruction_instance_t, size_t>& CodeobjDecoder::getDecoded(uint64_t addr)
|
||||
{
|
||||
if (decoded_map.find(addr) != decoded_map.end())
|
||||
return decoded_map[addr];
|
||||
|
||||
if (!decode_single(addr))
|
||||
{
|
||||
std::cerr << "Invalid addr: " << std::hex << addr << std::dec << std::endl;
|
||||
throw std::exception();
|
||||
}
|
||||
|
||||
return decoded_map[addr];
|
||||
}
|
||||
|
||||
#define PUBLIC_API __attribute__((visibility("default")))
|
||||
|
||||
CodeobjTableTranslation table;
|
||||
|
||||
extern "C"
|
||||
{
|
||||
PUBLIC_API int addDecoder(
|
||||
const char* filename,
|
||||
uint32_t id,
|
||||
uint64_t loadbase,
|
||||
uint64_t memsize,
|
||||
uint64_t gpu_id
|
||||
) {
|
||||
C_API_BEGIN
|
||||
|
||||
table.addDecoder(filename, id, loadbase, memsize, gpu_id);
|
||||
return 0;
|
||||
|
||||
C_API_END(1)
|
||||
}
|
||||
PUBLIC_API int removeDecoder(uint32_t id, uint64_t loadbase)
|
||||
{
|
||||
return table.removeDecoder(id, loadbase) != false;
|
||||
}
|
||||
PUBLIC_API instruction_info_t getInstructionFromAddr(uint64_t vaddr)
|
||||
{
|
||||
static instruction_info_t default_info{nullptr, nullptr, 0};
|
||||
C_API_BEGIN
|
||||
|
||||
return table.get(vaddr);
|
||||
|
||||
C_API_END(default_info)
|
||||
}
|
||||
PUBLIC_API instruction_info_t getInstructionFromID(uint32_t id, uint64_t offset)
|
||||
{
|
||||
static instruction_info_t default_info{nullptr, nullptr, 0};
|
||||
C_API_BEGIN
|
||||
|
||||
return table.get(id, offset);
|
||||
|
||||
C_API_END(default_info)
|
||||
}
|
||||
PUBLIC_API const char* getSymbolName(uint64_t addr)
|
||||
{
|
||||
C_API_BEGIN
|
||||
|
||||
return table.getSymbolName(addr);
|
||||
|
||||
C_API_END(nullptr)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
/* Copyright (c) 2022 Advanced Micro Devices, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE. */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rocprofiler.h"
|
||||
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "disassembly.hpp"
|
||||
#include "segment.hpp"
|
||||
|
||||
struct DSourceLine
|
||||
{
|
||||
uint64_t vaddr;
|
||||
uint64_t size;
|
||||
std::string str;
|
||||
uint64_t begin() const { return vaddr; }
|
||||
bool inrange(uint64_t addr) const { return addr >= vaddr && addr < vaddr+size; }
|
||||
};
|
||||
|
||||
class CodeObjDecoderComponent
|
||||
{
|
||||
public:
|
||||
std::optional<SymbolInfo> find_symbol(uint64_t address);
|
||||
|
||||
CodeObjDecoderComponent(const char* codeobj_data, uint64_t codeobj_size, uint64_t gpu_id);
|
||||
~CodeObjDecoderComponent();
|
||||
|
||||
std::pair<instruction_instance_t, size_t>
|
||||
disassemble_instruction(uint64_t faddr, uint64_t vaddr);
|
||||
void disassemble_kernel(uint64_t faddr, uint64_t vaddr);
|
||||
void disassemble_single_kernel(uint64_t kaddr);
|
||||
void disassemble_kernels();
|
||||
|
||||
int m_fd;
|
||||
|
||||
std::map<address_range_t, std::string> m_line_number_map{};
|
||||
std::map<uint64_t, SymbolInfo> m_symbol_map{};
|
||||
|
||||
std::string m_uri;
|
||||
std::vector<instruction_instance_t> instructions{};
|
||||
std::unique_ptr<DisassemblyInstance> disassembly{};
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
const char* inst;
|
||||
const char* cpp;
|
||||
size_t size;
|
||||
} instruction_info_t;
|
||||
|
||||
class CodeobjDecoder
|
||||
{
|
||||
public:
|
||||
CodeobjDecoder(const char* filepath, uint64_t loadbase, uint64_t memsize, uint64_t gpu_id);
|
||||
|
||||
bool decode_single(uint64_t vaddr);
|
||||
bool decode_single_at_offset(uint64_t vaddr, uint64_t voffset);
|
||||
bool add_to_map(uint64_t faddr, uint64_t vaddr, uint64_t voffset);
|
||||
|
||||
std::pair<instruction_instance_t, size_t>& getDecoded(uint64_t addr);
|
||||
const char* getInstruction(uint64_t addr) { return getDecoded(addr).first.instruction; }
|
||||
const char* getCppref(uint64_t addr) { return getDecoded(addr).first.cpp_reference; }
|
||||
size_t getSize(uint64_t addr) { return getDecoded(addr).second; }
|
||||
instruction_info_t get(uint64_t addr) {
|
||||
auto& inst = getDecoded(addr);
|
||||
return {inst.first.instruction, inst.first.cpp_reference, inst.second};
|
||||
}
|
||||
|
||||
uint64_t begin() const { return loadbase; };
|
||||
uint64_t end() const { return load_end; }
|
||||
uint64_t size() const { return load_end-loadbase; }
|
||||
bool inrange(uint64_t addr) const { return addr >= begin() && addr < end(); }
|
||||
|
||||
const char* getSymbolName(uint64_t addr) const {
|
||||
if (!decoder) return nullptr;
|
||||
|
||||
auto it = decoder->m_symbol_map.find(addr-loadbase);
|
||||
if (it != decoder->m_symbol_map.end())
|
||||
return it->second.name.data();
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
std::vector<std::pair<uint64_t, uint64_t>> elf_segments{};
|
||||
|
||||
private:
|
||||
const uint64_t loadbase;
|
||||
uint64_t load_end = 0;
|
||||
|
||||
std::unordered_map<uint64_t, std::pair<instruction_instance_t, size_t>> decoded_map;
|
||||
std::unique_ptr<CodeObjDecoderComponent> decoder{nullptr};
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Maps ID and offsets into instructions
|
||||
*/
|
||||
class CodeobjList
|
||||
{
|
||||
public:
|
||||
CodeobjList() = default;
|
||||
|
||||
virtual void addDecoder(
|
||||
const char* filepath,
|
||||
uint32_t id,
|
||||
uint64_t loadbase,
|
||||
uint64_t memsize,
|
||||
uint64_t gpu_id
|
||||
)
|
||||
{
|
||||
decoders[id] = std::make_shared<CodeobjDecoder>(filepath, loadbase, memsize, gpu_id);
|
||||
}
|
||||
|
||||
virtual bool removeDecoder(uint32_t id)
|
||||
{
|
||||
return decoders.erase(id) != 0;
|
||||
}
|
||||
|
||||
instruction_info_t get(uint32_t id, uint64_t offset)
|
||||
{
|
||||
auto& decoder = decoders.at(id);
|
||||
auto& inst = decoder->getDecoded(decoder->begin() + offset);
|
||||
return {inst.first.instruction, inst.first.cpp_reference, inst.second};
|
||||
}
|
||||
|
||||
const char* getSymbolName(uint32_t id, uint64_t offset)
|
||||
{
|
||||
auto& decoder = decoders.at(id);
|
||||
uint64_t vaddr = decoder->begin() + offset;
|
||||
if (decoder->inrange(vaddr))
|
||||
return decoder->getSymbolName(vaddr);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
protected:
|
||||
std::unordered_map<uint32_t, std::shared_ptr<CodeobjDecoder>> decoders{};
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Translates virtual addresses to elf file offsets
|
||||
*/
|
||||
class CodeobjTableTranslation : protected CodeobjList
|
||||
{
|
||||
using Super = CodeobjList;
|
||||
public:
|
||||
CodeobjTableTranslation() = default;
|
||||
|
||||
virtual void addDecoder(
|
||||
const char* filepath,
|
||||
uint32_t id,
|
||||
uint64_t loadbase,
|
||||
uint64_t memsize,
|
||||
uint64_t gpu_id
|
||||
) override
|
||||
{
|
||||
this->Super::addDecoder(filepath, id, loadbase, memsize, gpu_id);
|
||||
auto ptr = decoders.at(id);
|
||||
table.insert(address_range_t{ptr->begin(), static_cast<uint32_t>(ptr->size()), id});
|
||||
}
|
||||
|
||||
virtual bool removeDecoder(uint32_t id, uint64_t loadbase)
|
||||
{
|
||||
return table.remove(loadbase) && this->Super::removeDecoder(id);
|
||||
}
|
||||
|
||||
instruction_info_t get(uint64_t vaddr)
|
||||
{
|
||||
auto addr_range = table.find_codeobj_in_range(vaddr);
|
||||
return get(addr_range.id, vaddr - addr_range.addr);
|
||||
}
|
||||
instruction_info_t get(uint32_t id, uint64_t offset) { return this->Super::get(id, offset); }
|
||||
|
||||
const char* getSymbolName(uint64_t vaddr)
|
||||
{
|
||||
for (auto& [_, decoder] : decoders)
|
||||
{
|
||||
if (!decoder->inrange(vaddr)) continue;
|
||||
return decoder->getSymbolName(vaddr);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
CodeobjTableTranslator table;
|
||||
};
|
||||
@@ -0,0 +1,386 @@
|
||||
/* Copyright (c) 2022 Advanced Micro Devices, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE. */
|
||||
|
||||
#if !defined(_GNU_SOURCE) || !defined(_XOPEN_SOURCE)
|
||||
#define _XOPEN_SOURCE 700
|
||||
#endif
|
||||
|
||||
#include <sys/mman.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <elf.h>
|
||||
#include <cxxabi.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#include <elfutils/libdw.h>
|
||||
#include "../utils.h"
|
||||
#include "code_printing.hpp"
|
||||
#include <hsa/amd_hsa_elf.h>
|
||||
|
||||
#define THROW_COMGR(call) \
|
||||
if (amd_comgr_status_s status = call) { \
|
||||
const char* reason = ""; \
|
||||
amd_comgr_status_string(status, &reason); \
|
||||
std::cerr << __FILE__ << ':' << __LINE__ << " code: " \
|
||||
<< status << " failed: " << reason << std::endl; \
|
||||
throw std::exception(); \
|
||||
}
|
||||
|
||||
#define RETURN_COMGR(call) \
|
||||
if (amd_comgr_status_s status = call) { \
|
||||
const char* reason = ""; \
|
||||
amd_comgr_status_string(status, &reason); \
|
||||
std::cerr << __FILE__ << ':' << __LINE__ << " code: " \
|
||||
<< status << " failed: " << reason << std::endl; \
|
||||
return AMD_COMGR_STATUS_ERROR; \
|
||||
}
|
||||
|
||||
std::unordered_map<uint64_t, std::string> DisassemblyInstance::agent_isa_name{};
|
||||
|
||||
CodeObjectBinary::CodeObjectBinary(const std::string& uri) : m_uri(uri) {
|
||||
const std::string protocol_delim{"://"};
|
||||
|
||||
size_t protocol_end = m_uri.find(protocol_delim);
|
||||
std::string protocol = m_uri.substr(0, protocol_end);
|
||||
protocol_end += protocol_delim.length();
|
||||
|
||||
std::transform(protocol.begin(), protocol.end(), protocol.begin(),
|
||||
[](unsigned char c) { return std::tolower(c); });
|
||||
|
||||
std::string path;
|
||||
size_t path_end = m_uri.find_first_of("#?", protocol_end);
|
||||
if (path_end != std::string::npos) {
|
||||
path = m_uri.substr(protocol_end, path_end++ - protocol_end);
|
||||
} else {
|
||||
path = m_uri.substr(protocol_end);
|
||||
}
|
||||
|
||||
/* %-decode the string. */
|
||||
std::string decoded_path;
|
||||
decoded_path.reserve(path.length());
|
||||
for (size_t i = 0; i < path.length(); ++i)
|
||||
{
|
||||
if (path[i] == '%' && std::isxdigit(path[i + 1]) && std::isxdigit(path[i + 2])) {
|
||||
decoded_path += std::stoi(path.substr(i + 1, 2), 0, 16);
|
||||
i += 2;
|
||||
} else {
|
||||
decoded_path += path[i];
|
||||
}
|
||||
}
|
||||
|
||||
/* Tokenize the query/fragment. */
|
||||
std::vector<std::string> tokens;
|
||||
size_t pos, last = path_end;
|
||||
while ((pos = m_uri.find('&', last)) != std::string::npos) {
|
||||
tokens.emplace_back(m_uri.substr(last, pos - last));
|
||||
last = pos + 1;
|
||||
}
|
||||
if (last != std::string::npos) {
|
||||
tokens.emplace_back(m_uri.substr(last));
|
||||
}
|
||||
|
||||
/* Create a tag-value map from the tokenized query/fragment. */
|
||||
std::unordered_map<std::string, std::string> params;
|
||||
std::for_each(tokens.begin(), tokens.end(), [&](std::string& token) {
|
||||
size_t delim = token.find('=');
|
||||
if (delim != std::string::npos) {
|
||||
params.emplace(token.substr(0, delim), token.substr(delim + 1));
|
||||
}
|
||||
});
|
||||
|
||||
buffer = std::vector<char>{};
|
||||
size_t offset{0}, size{0};
|
||||
|
||||
if (auto offset_it = params.find("offset"); offset_it != params.end()) {
|
||||
offset = std::stoul(offset_it->second, nullptr, 0);
|
||||
}
|
||||
|
||||
if (auto size_it = params.find("size"); size_it != params.end()) {
|
||||
if (!(size = std::stoul(size_it->second, nullptr, 0))) return;
|
||||
}
|
||||
|
||||
if (protocol != "file") throw protocol + " protocol not supported!";
|
||||
|
||||
std::ifstream file(decoded_path, std::ios::in | std::ios::binary);
|
||||
if (!file || !file.is_open()) throw "could not open " + decoded_path;
|
||||
|
||||
if (!size) {
|
||||
file.ignore(std::numeric_limits<std::streamsize>::max());
|
||||
size_t bytes = file.gcount();
|
||||
file.clear();
|
||||
|
||||
if (bytes < offset)
|
||||
throw "invalid uri " + decoded_path + " (file size < offset)";
|
||||
|
||||
size = bytes - offset;
|
||||
}
|
||||
|
||||
file.seekg(offset, std::ios_base::beg);
|
||||
buffer.resize(size);
|
||||
file.read(&buffer[0], size);
|
||||
}
|
||||
|
||||
DisassemblyInstance::DisassemblyInstance(
|
||||
const char* codeobj_data,
|
||||
uint64_t codeobj_size,
|
||||
uint64_t gpu_id
|
||||
)
|
||||
{
|
||||
if (
|
||||
codeobj_size <= 4 ||
|
||||
codeobj_data[0] != ELFMAG0 ||
|
||||
codeobj_data[1] != ELFMAG1 ||
|
||||
codeobj_data[2] != ELFMAG2 ||
|
||||
codeobj_data[3] != ELFMAG3
|
||||
)
|
||||
throw std::invalid_argument("Invalid ELF file");
|
||||
|
||||
buffer = std::vector<char>(codeobj_size, 0);
|
||||
std::memcpy(buffer.data(), codeobj_data, codeobj_size);
|
||||
|
||||
THROW_COMGR(amd_comgr_create_data(AMD_COMGR_DATA_KIND_EXECUTABLE, &data));
|
||||
THROW_COMGR(amd_comgr_set_data(data, buffer.size(), buffer.data()));
|
||||
|
||||
std::string input_isa{};
|
||||
if (agent_isa_name.find(gpu_id) == agent_isa_name.end())
|
||||
{
|
||||
size_t isa_size = 128;
|
||||
input_isa.resize(isa_size);
|
||||
THROW_COMGR(amd_comgr_get_data_isa_name(data, &isa_size, input_isa.data()));
|
||||
agent_isa_name[gpu_id] = input_isa;
|
||||
}
|
||||
else
|
||||
{
|
||||
input_isa = agent_isa_name.at(gpu_id);
|
||||
}
|
||||
|
||||
THROW_COMGR(amd_comgr_create_disassembly_info(
|
||||
input_isa.data(),
|
||||
&DisassemblyInstance::memory_callback, &DisassemblyInstance::inst_callback,
|
||||
[](uint64_t address, void* user_data) {}, &info));
|
||||
|
||||
}
|
||||
|
||||
static bool IsKernelType(amd_comgr_symbol_type_t type)
|
||||
{
|
||||
if (type == AMD_COMGR_SYMBOL_TYPE_FUNC)
|
||||
return true;
|
||||
#ifdef AMD_COMGR_SYMBOL_TYPE_AMDGPU_HSA_KERNEL // To be deprecated
|
||||
if (type == AMD_COMGR_SYMBOL_TYPE_AMDGPU_HSA_KERNEL)
|
||||
return true;
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
amd_comgr_status_t DisassemblyInstance::symbol_callback(amd_comgr_symbol_t symbol,
|
||||
void* user_data) {
|
||||
amd_comgr_symbol_type_t type;
|
||||
RETURN_COMGR(amd_comgr_symbol_get_info(symbol, AMD_COMGR_SYMBOL_INFO_TYPE, &type));
|
||||
|
||||
if (!IsKernelType(type))
|
||||
return AMD_COMGR_STATUS_SUCCESS;
|
||||
|
||||
uint64_t vaddr;
|
||||
uint64_t mem_size;
|
||||
uint64_t name_size;
|
||||
RETURN_COMGR(amd_comgr_symbol_get_info(symbol, AMD_COMGR_SYMBOL_INFO_VALUE, &vaddr));
|
||||
RETURN_COMGR(amd_comgr_symbol_get_info(symbol, AMD_COMGR_SYMBOL_INFO_SIZE, &mem_size));
|
||||
RETURN_COMGR(amd_comgr_symbol_get_info(symbol, AMD_COMGR_SYMBOL_INFO_NAME_LENGTH, &name_size));
|
||||
|
||||
std::string name;
|
||||
name.resize(name_size);
|
||||
|
||||
RETURN_COMGR(amd_comgr_symbol_get_info(symbol, AMD_COMGR_SYMBOL_INFO_NAME, name.data()));
|
||||
|
||||
DisassemblyInstance& instance = *static_cast<DisassemblyInstance*>(user_data);
|
||||
std::optional<uint64_t> faddr = instance.va2fo(vaddr);
|
||||
|
||||
if (faddr)
|
||||
instance.symbol_map[vaddr] = {name, *faddr, mem_size};
|
||||
return AMD_COMGR_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
std::map<uint64_t, SymbolInfo>& DisassemblyInstance::GetKernelMap() {
|
||||
symbol_map = {};
|
||||
THROW_COMGR(amd_comgr_iterate_symbols(data, &DisassemblyInstance::symbol_callback, this));
|
||||
|
||||
return symbol_map;
|
||||
}
|
||||
|
||||
DisassemblyInstance::~DisassemblyInstance() {
|
||||
amd_comgr_release_data(data);
|
||||
amd_comgr_destroy_disassembly_info(info);
|
||||
}
|
||||
|
||||
uint64_t DisassemblyInstance::ReadInstruction(uint64_t faddr, uint64_t vaddr, const char* cpp_line)
|
||||
{
|
||||
uint64_t size_read;
|
||||
uint64_t addr_in_buffer = reinterpret_cast<uint64_t>(buffer.data()) + faddr;
|
||||
|
||||
THROW_COMGR(amd_comgr_disassemble_instruction(info, addr_in_buffer, (void*)this, &size_read));
|
||||
|
||||
last_instruction.address = vaddr;
|
||||
last_instruction.cpp_reference = cpp_line;
|
||||
return size_read;
|
||||
}
|
||||
|
||||
uint64_t DisassemblyInstance::memory_callback(uint64_t from, char* to, uint64_t size,
|
||||
void* user_data) {
|
||||
DisassemblyInstance& instance = *static_cast<DisassemblyInstance*>(user_data);
|
||||
int64_t copysize = reinterpret_cast<int64_t>(instance.buffer.data())
|
||||
+ instance.buffer.size() - static_cast<int64_t>(from);
|
||||
copysize = std::min<int64_t>(size, copysize);
|
||||
std::memcpy(to, (char*)from, copysize);
|
||||
return copysize;
|
||||
}
|
||||
|
||||
void DisassemblyInstance::inst_callback(const char* instruction, void* user_data) {
|
||||
DisassemblyInstance& instance = *static_cast<DisassemblyInstance*>(user_data);
|
||||
instance.last_instruction.instruction = strdup(instruction);
|
||||
}
|
||||
|
||||
#define CHECK_VA2FO(x, msg) if (!(x)) { \
|
||||
std::cerr << __FILE__ << ' ' << __LINE__ << ' ' << msg << std::endl; \
|
||||
return std::nullopt; \
|
||||
}
|
||||
|
||||
// mem - input argument, start of the elf
|
||||
// va - input argument, virtual address
|
||||
// return file offset, if found
|
||||
std::optional<uint64_t> DisassemblyInstance::va2fo(uint64_t va)
|
||||
{
|
||||
CHECK_VA2FO(buffer.size(), "buffer is not large enough");
|
||||
|
||||
uint8_t *e_ident = (uint8_t*)buffer.data();
|
||||
CHECK_VA2FO(e_ident, "e_ident is nullptr");
|
||||
|
||||
CHECK_VA2FO(
|
||||
e_ident[EI_MAG0] == ELFMAG0 ||
|
||||
e_ident[EI_MAG1] == ELFMAG1 ||
|
||||
e_ident[EI_MAG2] == ELFMAG2 ||
|
||||
e_ident[EI_MAG3] == ELFMAG3, "unexpected ei_mag");
|
||||
|
||||
CHECK_VA2FO(e_ident[EI_CLASS] == ELFCLASS64, "unexpected ei_class");
|
||||
CHECK_VA2FO(e_ident[EI_DATA] == ELFDATA2LSB, "unexpected ei_data");
|
||||
CHECK_VA2FO(e_ident[EI_VERSION] == EV_CURRENT, "unexpected ei_version");
|
||||
CHECK_VA2FO(e_ident[EI_OSABI] == 64, "unexpected ei_osabi"); // ELFOSABI_AMDGPU_HSA
|
||||
|
||||
CHECK_VA2FO(
|
||||
e_ident[EI_ABIVERSION] == 2 || // ELFABIVERSION_AMDGPU_HSA_V4
|
||||
e_ident[EI_ABIVERSION] == 3 || // ELFABIVERSION_AMDGPU_HSA_V5
|
||||
e_ident[EI_ABIVERSION] == 4, "unexpected ei_abiversion"); // ELFABIVERSION_AMDGPU_HSA_V6
|
||||
|
||||
Elf64_Ehdr *ehdr = (Elf64_Ehdr*)buffer.data();
|
||||
CHECK_VA2FO(ehdr, "ehdr is nullptr");
|
||||
CHECK_VA2FO(ehdr->e_type == ET_DYN, "unexpected e_type");
|
||||
CHECK_VA2FO(ehdr->e_machine == ELF::EM_AMDGPU, "unexpected e_machine");
|
||||
|
||||
CHECK_VA2FO(buffer.size() > sizeof(Elf64_Ehdr), "buffer is not large enough");
|
||||
CHECK_VA2FO(ehdr->e_phoff != 0, "unexpected e_phoff");
|
||||
|
||||
CHECK_VA2FO(buffer.size() > ehdr->e_phoff + sizeof(Elf64_Phdr), "buffer is not large enough");
|
||||
|
||||
Elf64_Phdr *phdr = (Elf64_Phdr*)((uint8_t*)buffer.data() + ehdr->e_phoff);
|
||||
CHECK_VA2FO(phdr, "phdr is nullptr");
|
||||
|
||||
for (uint16_t i = 0; i < ehdr->e_phnum; ++i)
|
||||
{
|
||||
if (phdr[i].p_type != PT_LOAD)
|
||||
continue;
|
||||
if (va < phdr[i].p_vaddr || va >= (phdr[i].p_vaddr + phdr[i].p_memsz))
|
||||
continue;
|
||||
|
||||
return va + phdr[i].p_offset - phdr[i].p_vaddr;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
#undef CHECK_VA2FO
|
||||
#define CHECK_VA2FO(x, msg) if (!(x)) { \
|
||||
std::cerr << __FILE__ << ' ' << __LINE__ << ' ' << msg << std::endl; \
|
||||
return {}; \
|
||||
}
|
||||
|
||||
std::vector<std::pair<uint64_t, uint64_t>> DisassemblyInstance::getSegments()
|
||||
{
|
||||
CHECK_VA2FO(buffer.size(), "buffer is not large enough");
|
||||
|
||||
uint8_t *e_ident = (uint8_t*)buffer.data();
|
||||
CHECK_VA2FO(e_ident, "e_ident is nullptr");
|
||||
|
||||
CHECK_VA2FO(
|
||||
e_ident[EI_MAG0] == ELFMAG0 ||
|
||||
e_ident[EI_MAG1] == ELFMAG1 ||
|
||||
e_ident[EI_MAG2] == ELFMAG2 ||
|
||||
e_ident[EI_MAG3] == ELFMAG3, "unexpected ei_mag");
|
||||
|
||||
CHECK_VA2FO(e_ident[EI_CLASS] == ELFCLASS64, "unexpected ei_class");
|
||||
CHECK_VA2FO(e_ident[EI_DATA] == ELFDATA2LSB, "unexpected ei_data");
|
||||
CHECK_VA2FO(e_ident[EI_VERSION] == EV_CURRENT, "unexpected ei_version");
|
||||
CHECK_VA2FO(e_ident[EI_OSABI] == 64, "unexpected ei_osabi"); // ELFOSABI_AMDGPU_HSA
|
||||
|
||||
CHECK_VA2FO(
|
||||
e_ident[EI_ABIVERSION] == 2 || // ELFABIVERSION_AMDGPU_HSA_V4
|
||||
e_ident[EI_ABIVERSION] == 3 || // ELFABIVERSION_AMDGPU_HSA_V5
|
||||
e_ident[EI_ABIVERSION] == 4, "unexpected ei_abiversion"); // ELFABIVERSION_AMDGPU_HSA_V6
|
||||
|
||||
Elf64_Ehdr *ehdr = (Elf64_Ehdr*)buffer.data();
|
||||
CHECK_VA2FO(ehdr, "ehdr is nullptr");
|
||||
CHECK_VA2FO(ehdr->e_type == ET_DYN, "unexpected e_type");
|
||||
CHECK_VA2FO(ehdr->e_machine == ELF::EM_AMDGPU, "unexpected e_machine");
|
||||
|
||||
CHECK_VA2FO(buffer.size() > sizeof(Elf64_Ehdr), "buffer is not large enough");
|
||||
CHECK_VA2FO(ehdr->e_phoff != 0, "unexpected e_phoff");
|
||||
|
||||
CHECK_VA2FO(buffer.size() > ehdr->e_phoff + sizeof(Elf64_Phdr), "buffer is not large enough");
|
||||
|
||||
Elf64_Phdr *phdr = (Elf64_Phdr*)((uint8_t*)buffer.data() + ehdr->e_phoff);
|
||||
CHECK_VA2FO(phdr, "phdr is nullptr");
|
||||
|
||||
std::vector<std::pair<uint64_t, uint64_t>> segments;
|
||||
for (Elf64_Half i = 0; i < ehdr->e_phnum; ++i)
|
||||
{
|
||||
if (phdr[i].p_type != PT_LOAD)
|
||||
continue;
|
||||
|
||||
segments.push_back({phdr[i].p_vaddr - phdr[i].p_offset, phdr[i].p_memsz});
|
||||
}
|
||||
|
||||
return segments;
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/* Copyright (c) 2022 Advanced Micro Devices, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE. */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <amd_comgr/amd_comgr.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <limits>
|
||||
|
||||
typedef struct {
|
||||
const char* instruction;
|
||||
const char* cpp_reference;
|
||||
uint64_t address;
|
||||
} instruction_instance_t;
|
||||
|
||||
class CodeObjectBinary {
|
||||
public:
|
||||
CodeObjectBinary(const std::string& uri);
|
||||
std::string m_uri;
|
||||
std::vector<char> buffer;
|
||||
};
|
||||
|
||||
struct SymbolInfo
|
||||
{
|
||||
std::string name;
|
||||
uint64_t faddr;
|
||||
uint64_t mem_size;
|
||||
};
|
||||
|
||||
class DisassemblyInstance {
|
||||
public:
|
||||
DisassemblyInstance(
|
||||
const char* codeobj_data,
|
||||
uint64_t codeobj_size,
|
||||
uint64_t gpu_id
|
||||
);
|
||||
~DisassemblyInstance();
|
||||
|
||||
uint64_t ReadInstruction(uint64_t faddr, uint64_t vaddr, const char* cpp_line);
|
||||
std::map<uint64_t, SymbolInfo>& GetKernelMap();
|
||||
|
||||
static uint64_t memory_callback(uint64_t from, char* to, uint64_t size, void* user_data);
|
||||
static void inst_callback(const char* instruction, void* user_data);
|
||||
static amd_comgr_status_t symbol_callback(amd_comgr_symbol_t symbol, void* user_data);
|
||||
// Per-gpu_id isa_name
|
||||
static std::unordered_map<uint64_t, std::string> agent_isa_name;
|
||||
|
||||
std::optional<uint64_t> va2fo(uint64_t va);
|
||||
std::vector<std::pair<uint64_t, uint64_t>> getSegments();
|
||||
|
||||
std::vector<char> buffer;
|
||||
instruction_instance_t last_instruction;
|
||||
amd_comgr_disassembly_info_t info;
|
||||
amd_comgr_data_t data;
|
||||
std::map<uint64_t, SymbolInfo> symbol_map;
|
||||
};
|
||||
@@ -0,0 +1,361 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
|
||||
if sys.version_info[0] < 3:
|
||||
raise Exception("Must be using Python 3")
|
||||
|
||||
import numpy as np
|
||||
from io import BytesIO
|
||||
import matplotlib.pyplot as plt
|
||||
from copy import deepcopy
|
||||
import json
|
||||
|
||||
COUNTERS_MAX_CAPTURES = 1 << 12
|
||||
|
||||
|
||||
class Readable:
|
||||
def __init__(self, jsonstring):
|
||||
self.jsonstr = json.dumps(jsonstring)
|
||||
self.seek = 0
|
||||
|
||||
def read(self, length=0):
|
||||
if length <= 0:
|
||||
return self.jsonstr
|
||||
else:
|
||||
if self.seek >= len(self):
|
||||
self.seek = 0
|
||||
return None
|
||||
response = self.jsonstr[self.seek : self.seek + length]
|
||||
self.seek += length
|
||||
return bytes(response, "utf-8")
|
||||
|
||||
def __len__(self):
|
||||
return len(self.jsonstr)
|
||||
|
||||
|
||||
class FileBytesIO:
|
||||
def __init__(self, iobytes):
|
||||
self.iobytes = deepcopy(iobytes)
|
||||
self.seek = 0
|
||||
|
||||
def __len__(self):
|
||||
return self.iobytes.getbuffer().nbytes
|
||||
|
||||
def read(self, length=0):
|
||||
if length <= 0:
|
||||
return bytes(self.iobytes.getbuffer())
|
||||
else:
|
||||
if self.seek >= self.iobytes.getbuffer().nbytes:
|
||||
self.seek = 0
|
||||
return None
|
||||
response = self.iobytes.getbuffer()[self.seek : self.seek + length]
|
||||
self.seek += length
|
||||
return bytes(response)
|
||||
|
||||
|
||||
def get_delta_time(events):
|
||||
try:
|
||||
CUS = [[e.time for e in events if e.cu == k and e.bank == 0] for k in range(16)]
|
||||
CUS = [np.asarray(c).astype(np.int64) for c in CUS if len(c) > 2]
|
||||
return np.min([np.min(abs(c[1:] - c[:-1])) for c in CUS])
|
||||
except:
|
||||
return 1
|
||||
|
||||
|
||||
def draw_wave_metrics(selections, normalize, TIMELINES, EVENTS, EVENT_NAMES):
|
||||
plt.figure(figsize=(15, 4))
|
||||
|
||||
delta_step = 8
|
||||
quad_delta_time = max(
|
||||
delta_step, int(0.5 + np.min([get_delta_time(events) for events in EVENTS]))
|
||||
)
|
||||
maxtime = (
|
||||
np.max([np.max([e.time for e in events]) for events in EVENTS]) / quad_delta_time
|
||||
+ 1
|
||||
)
|
||||
|
||||
if maxtime * delta_step >= COUNTERS_MAX_CAPTURES:
|
||||
delta_step = 1
|
||||
while maxtime >= COUNTERS_MAX_CAPTURES:
|
||||
quad_delta_time *= 2
|
||||
maxtime /= 2
|
||||
|
||||
maxtime = int(min(maxtime * delta_step, COUNTERS_MAX_CAPTURES))
|
||||
event_timeline = np.zeros((16, maxtime), dtype=np.int32)
|
||||
print("Delta:", quad_delta_time)
|
||||
print("Max_cycles:", maxtime * quad_delta_time * 4 // delta_step)
|
||||
|
||||
cycles = 4 * quad_delta_time // delta_step * np.arange(maxtime)
|
||||
kernel = len(EVENTS) * quad_delta_time
|
||||
|
||||
for events in EVENTS:
|
||||
for e in range(len(events) - 1):
|
||||
bk = events[e].bank * 4
|
||||
start = events[e].time // (quad_delta_time // delta_step)
|
||||
end = start + delta_step
|
||||
event_timeline[bk : bk + 4, start:end] += np.asarray(
|
||||
events[e].toTuple()[1:5]
|
||||
)[:, None]
|
||||
start = events[-1].time
|
||||
event_timeline[bk : bk + 4, start : start + delta_step] += np.asarray(
|
||||
events[-1].toTuple()[1:5]
|
||||
)[:, None]
|
||||
|
||||
event_timeline = [
|
||||
np.convolve(e, [kernel for k in range(3)])[1:-1] for e in event_timeline
|
||||
]
|
||||
# event_timeline = [e/kernel for e in event_timeline]
|
||||
|
||||
if normalize:
|
||||
event_timeline = [100 * e / max(e.max(), 1e-5) for e in event_timeline]
|
||||
|
||||
colors = [
|
||||
"blue",
|
||||
"green",
|
||||
"gray",
|
||||
"red",
|
||||
"orange",
|
||||
"cyan",
|
||||
"black",
|
||||
"darkviolet",
|
||||
"yellow",
|
||||
"darkred",
|
||||
"pink",
|
||||
"lime",
|
||||
"gold",
|
||||
"tan",
|
||||
"aqua",
|
||||
"olive",
|
||||
]
|
||||
[
|
||||
plt.plot(cycles, e, "-", label=n, color=c)
|
||||
for e, n, c, sel in zip(event_timeline, EVENT_NAMES, colors, selections)
|
||||
if sel
|
||||
]
|
||||
|
||||
plt.legend()
|
||||
if normalize:
|
||||
plt.ylabel("As % of maximum")
|
||||
else:
|
||||
plt.ylabel("Value")
|
||||
plt.xlabel("Cycle")
|
||||
plt.subplots_adjust(left=0.04, right=1, top=1, bottom=0.1)
|
||||
|
||||
figure_bytes = BytesIO()
|
||||
plt.savefig(figure_bytes, dpi=150)
|
||||
return EVENT_NAMES, FileBytesIO(figure_bytes)
|
||||
|
||||
|
||||
def draw_wave_states(selections, normalize, TIMELINES):
|
||||
plot_indices = [1, 2, 3, 4]
|
||||
STATES = [["Empty", "Idle", "Exec", "Wait", "Stall"][k] for k in plot_indices]
|
||||
colors = [["gray", "orange", "green", "red", "blue"][k] for k in plot_indices]
|
||||
|
||||
plt.figure(figsize=(15, 4))
|
||||
|
||||
|
||||
maxtime = max([np.max((TIMELINES[k]!=0)*np.arange(0,TIMELINES[k].size)) for k in plot_indices])
|
||||
maxtime = max(maxtime, 1)
|
||||
timelines = [deepcopy(TIMELINES[k][:maxtime]) for k in plot_indices]
|
||||
timelines = [np.pad(t, [0, maxtime - t.size]) for t in timelines]
|
||||
|
||||
if normalize:
|
||||
timelines = np.array(timelines) / np.maximum(np.sum(timelines, 0) * 1e-2, 1e-7)
|
||||
|
||||
trim = max(maxtime // 5000, 1)
|
||||
cycles = np.arange(0, timelines[0].size // trim, 1) * trim
|
||||
timelines = [
|
||||
time[: trim * (time.size // trim)].reshape((-1, trim)).mean(-1)
|
||||
if len(time) > 0
|
||||
else cycles * 0
|
||||
for time in timelines
|
||||
]
|
||||
kernsize = 15
|
||||
kernel = np.asarray([
|
||||
np.exp(-abs(10 * k / kernsize)) for k in range(-kernsize // 2, kernsize // 2 + 1)
|
||||
])
|
||||
kernel /= np.sum(kernel)
|
||||
|
||||
timelines = [
|
||||
np.convolve(time, kernel)[kernsize // 2 : -kernsize // 2]
|
||||
for time in timelines if len(time) > 0
|
||||
]
|
||||
maxtime *= 16
|
||||
cycles *= 16
|
||||
[
|
||||
plt.plot(cycles, t, label="State " + s, linewidth=1.1, color=c)
|
||||
for t, s, c, sel in zip(timelines, STATES, colors, selections)
|
||||
if sel
|
||||
]
|
||||
|
||||
plt.legend()
|
||||
if normalize:
|
||||
plt.ylabel("Waves state %")
|
||||
else:
|
||||
plt.ylabel("Waves state total")
|
||||
plt.xlabel("Cycle")
|
||||
plt.ylim(-1)
|
||||
plt.xlim(-maxtime // 200, maxtime + maxtime // 200 + 1)
|
||||
plt.subplots_adjust(left=0.04, right=1, top=1, bottom=0.1)
|
||||
figure_bytes = BytesIO()
|
||||
plt.savefig(figure_bytes, dpi=150)
|
||||
return STATES, FileBytesIO(figure_bytes)
|
||||
|
||||
|
||||
def draw_occupancy_per_dispatch(selections, normalize, OCCUPANCY, dispatchnames):
|
||||
plt.figure(figsize=(15, 4))
|
||||
maxtime = 1
|
||||
delta = 1
|
||||
|
||||
for k in range(len(OCCUPANCY)):
|
||||
if len(OCCUPANCY[k]) <= 1:
|
||||
continue
|
||||
for ev in OCCUPANCY[k]:
|
||||
maxtime = max(maxtime, ev[0])
|
||||
|
||||
NUM_DOTS = 1600 # number of points taken for graphing
|
||||
delta = max(1, maxtime // NUM_DOTS) # Spacing between data points. Waves will be averaged over this interval.
|
||||
# Holds occupancy data
|
||||
chart = np.zeros((len(dispatchnames), maxtime // delta + 2), dtype=np.float32)
|
||||
|
||||
for occ in OCCUPANCY:
|
||||
if len(occ) <= 1:
|
||||
continue
|
||||
# Number of waves multiplied by number of events
|
||||
small_chart = np.zeros_like(chart)
|
||||
# Holds number of events in that time period, for averaging.
|
||||
norm_fact = np.zeros_like(chart)
|
||||
norm_fact += 1E-5
|
||||
|
||||
# Holds last known state per dispatch
|
||||
current_time = [0 for k in range(len(dispatchnames))]
|
||||
# Holds occupancy per Dispatch
|
||||
total_value = [0 for k in range(len(dispatchnames))]
|
||||
|
||||
for time, en, kid in occ:
|
||||
b = current_time[kid]
|
||||
e = max(b + 1, time // delta)
|
||||
small_chart[kid][b:e] += total_value[kid]
|
||||
norm_fact[kid][b:e] += 1
|
||||
|
||||
# Enable = 1 means a new wave started, enable = 0 means a wave has ended on that kernel ID.
|
||||
total_value[kid] += 2*en - 1
|
||||
current_time[kid] = time // delta
|
||||
for small, norm, time, value in zip(small_chart, norm_fact, current_time, total_value):
|
||||
small[time] += value
|
||||
norm[time] += value
|
||||
|
||||
chart += small_chart/norm_fact # small_chart / norm_fact is the mean number of waves a tthat time point
|
||||
|
||||
for (id, name), occ in zip(dispatchnames.items(), chart):
|
||||
plt.plot(np.arange(occ.size) * delta * 8, occ, label=str(id)+'#'+name, linewidth=1.1)
|
||||
|
||||
plt.legend()
|
||||
if normalize:
|
||||
plt.ylabel("Occupancy %")
|
||||
else:
|
||||
plt.ylabel("Occupancy total")
|
||||
plt.xlabel("Cycle")
|
||||
plt.ylim(-1)
|
||||
plt.xlim(-maxtime // 200, maxtime + maxtime // 200 + delta + 1)
|
||||
plt.subplots_adjust(left=0.04, right=1, top=1, bottom=0.1)
|
||||
figure_bytes = BytesIO()
|
||||
plt.savefig(figure_bytes, dpi=150)
|
||||
return dispatchnames, FileBytesIO(figure_bytes)
|
||||
|
||||
|
||||
def draw_occupancy(selections, normalize, OCCUPANCY, shadernames, numdispatchid):
|
||||
plt.figure(figsize=(15, 4))
|
||||
names = []
|
||||
|
||||
g_maxtime = 1
|
||||
g_delta = 1
|
||||
for name, occ in zip(shadernames, OCCUPANCY):
|
||||
if len(occ) <= 1:
|
||||
continue
|
||||
|
||||
occ_values = [0]
|
||||
occ_times = [0]
|
||||
|
||||
for time, en, _ in occ:
|
||||
occ_times.append(time)
|
||||
occ_values.append(occ_values[-1] + 2*en - 1) # If enable = 1, increment. Else, decrement occupancy.
|
||||
|
||||
try:
|
||||
names.append('SE'+name.split('_se')[1].split('.att')[0])
|
||||
except:
|
||||
names.append(name)
|
||||
|
||||
NUM_DOTS = 1500 # Number of points taken for graphing
|
||||
maxtime = occ_times[-1]+1
|
||||
delta = max(1, maxtime // NUM_DOTS)
|
||||
g_maxtime = max(g_maxtime, maxtime)
|
||||
g_delta = max(g_delta, delta)
|
||||
chart = np.zeros((maxtime // delta + 1), dtype=np.float32)
|
||||
norm_fact = np.zeros_like(chart)
|
||||
norm_fact += 1E-6
|
||||
|
||||
for i in range(len(occ_times)-1):
|
||||
b = occ_times[i] // delta
|
||||
e = max(b + 1, occ_times[i + 1] // delta)
|
||||
chart[b:e] += occ_values[i]
|
||||
norm_fact[b:e] += 1
|
||||
|
||||
chart /= norm_fact
|
||||
if normalize:
|
||||
chart /= max(chart.max(), 1e-6)
|
||||
|
||||
plt.plot(np.arange(chart.size) * delta, chart, label=names[-1], linewidth=1.1)
|
||||
|
||||
plt.legend()
|
||||
if normalize:
|
||||
plt.ylabel("Occupancy %")
|
||||
else:
|
||||
plt.ylabel("Occupancy total")
|
||||
plt.xlabel("Cycle")
|
||||
plt.ylim(-1)
|
||||
plt.xlim(-g_maxtime // 200, g_maxtime + g_maxtime // 200 + g_delta + 1)
|
||||
plt.subplots_adjust(left=0.04, right=1, top=1, bottom=0.1)
|
||||
figure_bytes = BytesIO()
|
||||
plt.savefig(figure_bytes, dpi=150)
|
||||
return names, FileBytesIO(figure_bytes)
|
||||
|
||||
|
||||
def getocc(u):
|
||||
# Parser struct occupancy_info_t
|
||||
# Bits 23:63 Time= Time divided by 8
|
||||
# Bit 18 = Enable (Wave start if 1, Wave end if 0)
|
||||
# Bits 0:11 is the kernel ID running on that wave
|
||||
return 8*int(u>>23), (u>>18) & 1, u&0xFFF
|
||||
|
||||
|
||||
def GeneratePIC(drawinfo, selections=[True for k in range(16)], normalize=False):
|
||||
EVENTS = drawinfo["EVENTS"]
|
||||
|
||||
response = {}
|
||||
figures = {}
|
||||
|
||||
OCCUPANCY = drawinfo["OCCUPANCY"]
|
||||
# Transforms returned data into a array of events with each event being a tuple (time, enable, kernel ID)
|
||||
OCCUPANCY = [[getocc(u) for u in OCCUPANCY[k]] for k in range(len(OCCUPANCY))]
|
||||
|
||||
states, figure = draw_occupancy(selections, normalize, OCCUPANCY, drawinfo["ShaderNames"], len(drawinfo["DispatchNames"]))
|
||||
response["occupancy.png"] = states
|
||||
figures["occupancy.png"] = figure
|
||||
|
||||
states, figure = draw_occupancy_per_dispatch(selections, normalize, OCCUPANCY, drawinfo["DispatchNames"])
|
||||
response["dispatches.png"] = states
|
||||
figures["dispatches.png"] = figure
|
||||
|
||||
states, figure = draw_wave_states(selections, normalize, drawinfo["TIMELINES"])
|
||||
response["timeline.png"] = states
|
||||
figures["timeline.png"] = figure
|
||||
|
||||
if len(EVENTS) > 0 and np.sum([len(e) for e in EVENTS]) > 32:
|
||||
EVENT_NAMES, figure = draw_wave_metrics(
|
||||
selections, normalize, drawinfo["TIMELINES"], EVENTS, drawinfo["EVENT_NAMES"]
|
||||
)
|
||||
response["counters.png"] = EVENT_NAMES
|
||||
figures["counters.png"] = figure
|
||||
|
||||
return Readable(response), figures
|
||||
@@ -0,0 +1,77 @@
|
||||
/* Copyright (c) 2023 Advanced Micro Devices, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE. */
|
||||
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <random>
|
||||
#include <set>
|
||||
#include <algorithm>
|
||||
|
||||
struct address_range_t
|
||||
{
|
||||
uint64_t addr{0};
|
||||
uint64_t size{0};
|
||||
uint64_t id{0};
|
||||
|
||||
bool operator==(const address_range_t& other) const
|
||||
{
|
||||
return (addr >= other.addr && addr < other.addr + other.size) ||
|
||||
(other.addr >= addr && other.addr < addr + size);
|
||||
}
|
||||
bool operator<(const address_range_t& other) const
|
||||
{
|
||||
if(*this == other) return false;
|
||||
return addr < other.addr;
|
||||
}
|
||||
bool inrange(uint64_t _addr) const { return addr <= _addr && addr + size > _addr; };
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Finds a candidate codeobj for the given vaddr
|
||||
*/
|
||||
class CodeobjTableTranslator : public std::set<address_range_t>
|
||||
{
|
||||
using Super = std::set<address_range_t>;
|
||||
|
||||
public:
|
||||
address_range_t find_codeobj_in_range(uint64_t addr)
|
||||
{
|
||||
if(!cached_segment.inrange(addr))
|
||||
{
|
||||
auto it = this->find(address_range_t{addr, 0, 0});
|
||||
if(it == this->end()) throw std::exception();
|
||||
cached_segment = *it;
|
||||
}
|
||||
return cached_segment;
|
||||
}
|
||||
|
||||
void clear_cache() { cached_segment = {}; }
|
||||
bool remove(const address_range_t& range)
|
||||
{
|
||||
clear_cache();
|
||||
return this->erase(range) != 0;
|
||||
}
|
||||
bool remove(uint64_t addr) { return remove(address_range_t{addr, 0, 0}); }
|
||||
|
||||
private:
|
||||
address_range_t cached_segment{};
|
||||
};
|
||||
@@ -0,0 +1,141 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
|
||||
if sys.version_info[0] < 3:
|
||||
raise Exception("Must be using Python 3")
|
||||
|
||||
import ctypes
|
||||
from ctypes import *
|
||||
import os
|
||||
|
||||
HEADER_OFFSET = 62
|
||||
HEADER_MASK = 0x3
|
||||
ID_OFFSET = 34
|
||||
ID_MASK = (1<<28)-1
|
||||
OFFSET_MASK = (1<<ID_OFFSET)-1
|
||||
|
||||
pluginpath = '../../../lib/rocprofiler/libatt_plugin.so'
|
||||
filedir = os.path.dirname(os.path.realpath(__file__))
|
||||
attplugin = CDLL(os.path.join(filedir, pluginpath))
|
||||
|
||||
attplugin.getSymbolName.restype = c_char_p
|
||||
attplugin.getSymbolName.argtypes = [c_uint64]
|
||||
|
||||
class instruction_info_t(ctypes.Structure):
|
||||
_fields_ = [('inst', c_char_p),
|
||||
('cpp', c_char_p),
|
||||
('size', c_size_t)]
|
||||
|
||||
attplugin.getInstructionFromAddr.restype = instruction_info_t
|
||||
attplugin.getInstructionFromAddr.argtypes = [c_uint64]
|
||||
|
||||
attplugin.getInstructionFromID.restype = instruction_info_t
|
||||
attplugin.getInstructionFromID.argtypes = [c_uint32, c_uint64]
|
||||
|
||||
attplugin.addDecoder.restype = c_int
|
||||
attplugin.addDecoder.argtypes = [c_char_p, c_uint32, c_uint64, c_uint64, c_uint64]
|
||||
|
||||
attplugin.removeDecoder.restype = c_int
|
||||
attplugin.removeDecoder.argtypes = [c_uint32, c_uint64]
|
||||
|
||||
def IsRawPC(addr):
|
||||
return addr >> HEADER_OFFSET == 0
|
||||
|
||||
def getID(addr):
|
||||
return (addr >> ID_OFFSET) & ID_MASK
|
||||
|
||||
def getOffset(addr):
|
||||
return addr & OFFSET_MASK
|
||||
|
||||
class CodeobjInstance:
|
||||
def __init__(self, gpu_id, line):
|
||||
tokens = line.split(' ')
|
||||
self.load_base = int(tokens[0], 16)
|
||||
self.memsize = int(tokens[1], 16)
|
||||
self.att_id = int(tokens[2])
|
||||
self.fpath = tokens[3]
|
||||
|
||||
path = self.fpath.encode('utf-8')
|
||||
self.error = attplugin.addDecoder(path, self.att_id, self.load_base, self.memsize, gpu_id)
|
||||
if self.error != 0:
|
||||
print('Warning: Could not open', line)
|
||||
raise
|
||||
|
||||
def release(self):
|
||||
attplugin.removeDecoder(self.att_id, self.load_base)
|
||||
|
||||
|
||||
class CodeobjService:
|
||||
def __init__(self, gpu_id, att_kernel_txt, cfunc):
|
||||
cfunc.restype = ctypes.c_int
|
||||
cfunc.argtypes = [ctypes.c_char_p, ctypes.c_size_t]
|
||||
|
||||
self.classifier = cfunc
|
||||
self.last_instance = None
|
||||
self.services = {}
|
||||
for line in att_kernel_txt:
|
||||
try:
|
||||
if 'memory://' == line[0:len('memory://')]:
|
||||
continue
|
||||
service = CodeobjInstance(gpu_id, line)
|
||||
self.services[service.att_id] = service
|
||||
except:
|
||||
pass
|
||||
|
||||
def ToRawPC(self, addr):
|
||||
if IsRawPC(addr):
|
||||
return addr
|
||||
return self.services[getID(addr)].load_base + getOffset(addr)
|
||||
|
||||
def release(self):
|
||||
for _, instance in self.services.items():
|
||||
instance.release()
|
||||
|
||||
def GetInstruction(self, addr):
|
||||
if not IsRawPC(addr):
|
||||
return self.GetInstructionFromID(getID(addr), getOffset(addr))
|
||||
else:
|
||||
return self.GetInstructionFromAddr(addr)
|
||||
|
||||
def GetInstructionFromAddr(self, addr):
|
||||
info_inst = attplugin.getInstructionFromAddr(addr)
|
||||
if info_inst.size == 0 or info_inst.inst is None:
|
||||
return None
|
||||
inst = info_inst.inst.decode()
|
||||
cpp = info_inst.cpp
|
||||
if cpp:
|
||||
cpp = cpp.decode()
|
||||
|
||||
while len(inst) and (inst[0] == '\t' or inst[0] == ' '):
|
||||
inst = inst[1:]
|
||||
while len(inst) and (inst[-1] == '\t' or inst[-1] == ' '):
|
||||
inst = inst[:-1]
|
||||
|
||||
return (self.classifier(info_inst.inst, len(inst)), inst, cpp, info_inst.size)
|
||||
|
||||
def GetInstructionFromID(self, id, offset):
|
||||
info_inst = attplugin.getInstructionFromID(id, offset)
|
||||
if info_inst.size == 0 or info_inst.inst is None:
|
||||
return None
|
||||
inst = info_inst.inst.decode()
|
||||
cpp = info_inst.cpp
|
||||
if cpp:
|
||||
cpp = cpp.decode()
|
||||
else:
|
||||
cpp = ''
|
||||
|
||||
while len(inst) and (inst[0] == '\t' or inst[0] == ' '):
|
||||
inst = inst[1:]
|
||||
while len(inst) and (inst[-1] == '\t' or inst[-1] == ' '):
|
||||
inst = inst[:-1]
|
||||
|
||||
return (self.classifier(info_inst.inst, len(inst)), inst, cpp, info_inst.size)
|
||||
|
||||
def getSymbolName(self, addr):
|
||||
try:
|
||||
name = attplugin.getSymbolName(self.ToRawPC(addr))
|
||||
if name:
|
||||
return name.decode()
|
||||
return "Addr #"+hex(self.ToRawPC(addr))
|
||||
except:
|
||||
return "Addr #"+hex(addr)
|
||||
@@ -0,0 +1,574 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
|
||||
if sys.version_info[0] < 3:
|
||||
raise Exception("Must be using Python 3")
|
||||
|
||||
from collections import defaultdict
|
||||
from copy import deepcopy
|
||||
|
||||
MAX_STITCHED_TOKENS = 200000000
|
||||
MAX_FAILED_STITCHES = 256
|
||||
|
||||
SKIP = 0
|
||||
SMEM = 1
|
||||
SALU = 2
|
||||
VMEM = 3
|
||||
FLAT = 4
|
||||
LDS = 5
|
||||
VALU = 6
|
||||
JUMP = 7
|
||||
NEXT = 8
|
||||
IMMED = 9
|
||||
BRANCH = 10
|
||||
GETPC = 11
|
||||
SETPC = 12
|
||||
SWAPPC = 13
|
||||
LANEIO = 14
|
||||
PCINFO = 15
|
||||
WAVE_ENDED = 16
|
||||
DONT_KNOW = 100
|
||||
|
||||
# Keeps track of register states for hipcc-generated assembly
|
||||
class RegisterWatchList:
|
||||
def __init__(self, labels, code, jump_map, insts):
|
||||
self.registers = {"v" + str(k): [[] for m in range(64)] for k in range(64)}
|
||||
for k in range(128):
|
||||
self.registers["s" + str(k)] = []
|
||||
self.labels = labels
|
||||
|
||||
self.code = code
|
||||
self.jump_map = jump_map
|
||||
self.insts = insts
|
||||
|
||||
def jump(self, as_line):
|
||||
return self.jump_map[as_line[2]]
|
||||
|
||||
def getcode(self, line):
|
||||
return self.code[line], 1
|
||||
|
||||
def getincrement(self, line):
|
||||
return 1
|
||||
|
||||
def try_translate(self, tok):
|
||||
if tok[0] in ["s"]:
|
||||
return self.registers[self.range(tok)[0]]
|
||||
elif "@" in tok:
|
||||
return self.labels[tok.split("@")[0]] + 1
|
||||
|
||||
def range(self, r):
|
||||
reg = r.split(":")
|
||||
if len(reg) == 1:
|
||||
return reg
|
||||
else:
|
||||
r0 = reg[0].split("[")
|
||||
return [r0[0] + str(k) for k in range(int(r0[1]), int(reg[1][:-1]) + 1)]
|
||||
|
||||
def tokenize(self, line):
|
||||
return [
|
||||
u for u in [t.split(",")[0].strip() for t in line.split(" ")] if len(u) > 0
|
||||
]
|
||||
|
||||
def getpc(self, line, next_line):
|
||||
try:
|
||||
dst = line.split(" ")[1].strip()
|
||||
label_dests = []
|
||||
try:
|
||||
label_dests = next_line[0].split(", ")
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
label_dests.append(next_line[0].split(", ")[-1].split("@")[0])
|
||||
except:
|
||||
pass
|
||||
|
||||
for label_dst in label_dests:
|
||||
try:
|
||||
cur_label = self.labels[label_dst]
|
||||
for reg in self.range(dst):
|
||||
self.registers[reg] = deepcopy(cur_label)
|
||||
except:
|
||||
pass
|
||||
except:
|
||||
pass
|
||||
|
||||
def swappc(self, line, line_num, inst_num):
|
||||
try:
|
||||
tokens = self.tokenize(line)
|
||||
dst = tokens[1]
|
||||
src = tokens[2]
|
||||
|
||||
popped = deepcopy(self.registers[self.range(src)[0]])
|
||||
self.registers[self.range(dst)[0]] = line_num + 1
|
||||
return popped
|
||||
except:
|
||||
return -1
|
||||
|
||||
def setpc(self, line, inst_num):
|
||||
try:
|
||||
src = line.split(' ')[1].strip()
|
||||
return deepcopy(self.registers[self.range(src)[0]])
|
||||
except:
|
||||
return -1
|
||||
|
||||
def scratch(self, line):
|
||||
try:
|
||||
tokens = self.tokenize(line)
|
||||
if "_load" in tokens[0]:
|
||||
dst = tokens[1]
|
||||
src = tokens[3] + tokens[4]
|
||||
else:
|
||||
src = tokens[2]
|
||||
dst = tokens[3] + tokens[4]
|
||||
self.registers[dst] = deepcopy(self.registers[src])
|
||||
except:
|
||||
pass
|
||||
|
||||
def move(self, line):
|
||||
try:
|
||||
tokens = self.tokenize(line)
|
||||
if tokens[2][0] in ["s", "d"] and tokens[1][0] in ["s", "d"]:
|
||||
self.registers[self.range(tokens[1])[0]] = deepcopy(
|
||||
self.registers[self.range(tokens[2])[0]]
|
||||
)
|
||||
except:
|
||||
pass
|
||||
|
||||
def updatelane(self, line):
|
||||
tokens = self.tokenize(line)
|
||||
try:
|
||||
if "v_readlane" in tokens[0]:
|
||||
self.registers[tokens[1]] = deepcopy(self.registers[tokens[2]][int(tokens[3])])
|
||||
elif "v_writelane" in tokens[0]:
|
||||
self.registers[tokens[1]][int(tokens[3])] = deepcopy(self.registers[tokens[2]])
|
||||
except:
|
||||
pass
|
||||
|
||||
# Matches tokens in reverse order
|
||||
def try_match_swapped(self, i, line, increment):
|
||||
try:
|
||||
return self.insts[i + 1].type == self.code[line][1] and self.insts[i].type == self.code[line + 1][1]
|
||||
except:
|
||||
return False
|
||||
|
||||
# Translates PC values to instructions, for auto captured ISA
|
||||
class PCTranslator:
|
||||
def __init__(self, insts, code, raw_code, reverse_map, codeservice):
|
||||
self.codeservice = codeservice
|
||||
|
||||
self.insts = insts
|
||||
self.addrmap = {c[-3] : (c, self.codeservice.GetInstruction(c[-3])[3]) for c in code if c[-3] > 0}
|
||||
|
||||
self.code = code
|
||||
self.raw_code = raw_code
|
||||
self.reverse_map = reverse_map
|
||||
self.jump_map = {c[-3] : self.getjump_loc(c) for c in code if c[1] == BRANCH}
|
||||
|
||||
def jump(self, as_line):
|
||||
return self.jump_map[as_line[-3]]
|
||||
|
||||
def addsymbol(self, addr):
|
||||
if addr in self.addrmap:
|
||||
return
|
||||
|
||||
symbol = self.codeservice.getSymbolName(addr)
|
||||
if symbol is None:
|
||||
symbol = "Unkown symbol at 0x" + hex(addr)
|
||||
|
||||
last_line = self.raw_code[-1]
|
||||
newline = ['; ' + symbol, DONT_KNOW, last_line[2], '', last_line[4], last_line[5], 0, 0, 0]
|
||||
self.raw_code.append(newline)
|
||||
|
||||
def getcode(self, addr):
|
||||
try:
|
||||
return self.addrmap[addr]
|
||||
except Exception as ex:
|
||||
new_inst = self.codeservice.GetInstruction(addr)
|
||||
if new_inst and new_inst[3]: # Check returned size > 0
|
||||
last_line = self.raw_code[-1]
|
||||
newline = [new_inst[1], new_inst[0], len(self.raw_code), new_inst[2], last_line[4]+1, last_line[5]+1, addr, 0, 0]
|
||||
if new_inst[0] == BRANCH:
|
||||
self.jump_map[addr] = self.getjump_loc(newline)
|
||||
self.addrmap[addr] = (newline, new_inst[3])
|
||||
|
||||
next = len(self.code)
|
||||
self.reverse_map[addr] = len(self.raw_code)
|
||||
self.raw_code.append(newline)
|
||||
self.code.append(newline)
|
||||
return newline, new_inst[3]
|
||||
else:
|
||||
raise ex
|
||||
|
||||
def jump(self, asm_line):
|
||||
try:
|
||||
return self.jump_map[asm_line[-3]]
|
||||
except:
|
||||
loc = self.getjump_loc(asm_line)
|
||||
self.jump_map[asm_line[-3]] = loc
|
||||
return loc
|
||||
|
||||
def getjump_loc(self, asm_line):
|
||||
try:
|
||||
dest = int(asm_line[0].split(' ')[-1])
|
||||
if dest >= 32768: dest -= 65536
|
||||
return asm_line[-3] + 4*dest+4
|
||||
except:
|
||||
return -1
|
||||
|
||||
def getincrement(self, addr):
|
||||
return self.getcode(addr)[1]
|
||||
|
||||
def try_translate(self, tok):
|
||||
pass
|
||||
def range(self, r):
|
||||
pass
|
||||
def tokenize(self, line):
|
||||
pass
|
||||
def getpc(self, line, next_line):
|
||||
pass
|
||||
def swappc(self, line, line_num, inst_index):
|
||||
try:
|
||||
inst_pos = inst_index+1
|
||||
while self.insts[inst_pos].type != PCINFO:
|
||||
inst_pos += 1
|
||||
return self.getcode(self.insts[inst_pos].cycles)[0][-3]
|
||||
except:
|
||||
print('SWAPPC warning: Could not find addr for', inst_index, line)
|
||||
return -1
|
||||
def setpc(self, line, inst_index):
|
||||
try:
|
||||
inst_pos = inst_index+1
|
||||
while self.insts[inst_pos].type != PCINFO:
|
||||
inst_pos += 1
|
||||
return self.getcode(self.insts[inst_pos].cycles)[0][-3]
|
||||
except:
|
||||
print('SETPC warning: Could not find addr for', inst_index, line)
|
||||
return -1
|
||||
def scratch(self, line):
|
||||
pass
|
||||
def move(self, line):
|
||||
pass
|
||||
def updatelane(self, line):
|
||||
pass
|
||||
|
||||
# Matches tokens in reverse order
|
||||
def try_match_swapped(self, i, addr, increment):
|
||||
try:
|
||||
return self.insts[i + 1].type == self.getcode(addr)[0][1] and \
|
||||
self.insts[i].type == self.getcode(addr + increment)[0][1]
|
||||
except Exception as e:
|
||||
return False
|
||||
|
||||
|
||||
def stitch(insts, raw_code, jumps, gfxv, bIsAuto, codeservice):
|
||||
bGFX9 = gfxv == 'vega'
|
||||
|
||||
result, i, loopCount = [], 0, defaultdict(int)
|
||||
|
||||
SMEM_INST = [] # scalar memory
|
||||
VLMEM_INST = [] # vector memory load
|
||||
VSMEM_INST = [] # vector memory store
|
||||
FLAT_INST = []
|
||||
NUM_SMEM = 0
|
||||
NUM_VLMEM = 0
|
||||
NUM_VSMEM = 0
|
||||
NUM_FLAT = 0
|
||||
skipped_immed = 0
|
||||
|
||||
mem_unroll = []
|
||||
flight_count = []
|
||||
|
||||
labels = {}
|
||||
jump_map = [0]
|
||||
|
||||
# Clean the code and remove comments
|
||||
code = [raw_code[0]]
|
||||
for c in raw_code[1:]:
|
||||
c = list(c)
|
||||
c[0] = c[0].split(";")[0].split("//")[0].strip()
|
||||
jump_map.append(len(code))
|
||||
|
||||
if c[1] != DONT_KNOW:
|
||||
code.append(c)
|
||||
elif ":" in c[0]:
|
||||
labels[c[0].split(":")[0]] = len(code)
|
||||
|
||||
reverse_map = {}
|
||||
if bIsAuto:
|
||||
for k, v in enumerate(jump_map):
|
||||
try:
|
||||
reverse_map[code[v][-3]] = k
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
for k, v in enumerate(jump_map):
|
||||
reverse_map[v] = k
|
||||
|
||||
jumps = {jump_map[j] + 1: j for j in jumps}
|
||||
|
||||
# Checks if we have guaranteed ordering in memory operations
|
||||
smem_ordering = 0
|
||||
vlmem_ordering = 0
|
||||
vsmem_ordering = 0
|
||||
|
||||
num_failed_stitches = 0
|
||||
loops = 0
|
||||
maxline = 0
|
||||
|
||||
pcskip = []
|
||||
if bIsAuto:
|
||||
try:
|
||||
firstinst = insts[0]
|
||||
|
||||
if firstinst.type != PCINFO:
|
||||
print('Warning: Waves without PCINFO')
|
||||
return None
|
||||
elif firstinst.cycles == 0:
|
||||
print('Info: Some waves started before the trace')
|
||||
return None
|
||||
|
||||
watchlist = PCTranslator(insts, code, raw_code, reverse_map, codeservice)
|
||||
watchlist.addsymbol(firstinst.cycles)
|
||||
line = firstinst.cycles
|
||||
lineincrement = watchlist.getincrement(line)
|
||||
except KeyError as e:
|
||||
print('Warning: Waves from addr', hex(e.args[0]), 'have no codeobj info.')
|
||||
for i in range(len(insts)):
|
||||
insts[i].asmline = 0
|
||||
return [i for k, i in enumerate(insts) if i.type != PCINFO], [], [], [], 1, 0, [k for k, i in enumerate(insts) if i.type == PCINFO]
|
||||
except Exception as e:
|
||||
print('Unknown error', e)
|
||||
return None
|
||||
else:
|
||||
line = 0
|
||||
lineincrement = 1
|
||||
watchlist = RegisterWatchList(labels=labels, code=code, jump_map=jump_map, insts=insts)
|
||||
|
||||
N = len(insts)
|
||||
|
||||
while i < N and line >= 0 and loops < MAX_STITCHED_TOKENS:
|
||||
if insts[i].type == PCINFO:
|
||||
pcskip.append(i)
|
||||
i += 1
|
||||
continue
|
||||
loops += 1
|
||||
|
||||
inst = insts[i]
|
||||
try:
|
||||
as_line, lineincrement = watchlist.getcode(line)
|
||||
except:
|
||||
break
|
||||
|
||||
matched = True
|
||||
next = line + lineincrement
|
||||
|
||||
if not bIsAuto:
|
||||
if '_mov_' in as_line[0]:
|
||||
watchlist.move(as_line[0])
|
||||
elif 'scratch_' in as_line[0]:
|
||||
watchlist.scratch(as_line[0])
|
||||
|
||||
if as_line[1] == DONT_KNOW or (as_line[1] == SKIP and not bGFX9):
|
||||
matched = False
|
||||
elif as_line[1] == GETPC:
|
||||
try:
|
||||
watchlist.getpc(as_line[0], watchlist.getcode(next)[0])
|
||||
matched = inst.type in [SALU, JUMP]
|
||||
except:
|
||||
matched = False
|
||||
elif as_line[1] == LANEIO:
|
||||
watchlist.updatelane(as_line[0])
|
||||
matched = inst.type == VALU
|
||||
elif as_line[1] == SETPC:
|
||||
next = watchlist.setpc(as_line[0], i)
|
||||
matched = inst.type in [SALU, JUMP]
|
||||
i += 1
|
||||
pcskip.append(i)
|
||||
while bIsAuto and next < 0 and i+1 < len(insts):
|
||||
i += 1
|
||||
if insts[i].type == PCINFO:
|
||||
pcskip.append(i)
|
||||
next = watchlist.setpc(as_line[0], i-1)
|
||||
else:
|
||||
inst.cycles += insts[i].cycles
|
||||
if next < 0:
|
||||
print('Jump to unknown location in line', as_line[0])
|
||||
break
|
||||
elif as_line[1] == SWAPPC:
|
||||
matched = inst.type in [SALU, JUMP]
|
||||
next = watchlist.swappc(as_line[0], line, i)
|
||||
i += 1
|
||||
pcskip.append(i)
|
||||
while bIsAuto and next < 0 and i+1 < len(insts):
|
||||
i += 1
|
||||
if insts[i].type == PCINFO:
|
||||
next = watchlist.swappc(as_line[0], line, i-1)
|
||||
pcskip.append(i)
|
||||
else:
|
||||
inst.cycles += insts[i].cycles
|
||||
if next < 0:
|
||||
print('Jump to unknown location in line', as_line[0])
|
||||
break
|
||||
elif inst.type == as_line[1]:
|
||||
if line in jumps:
|
||||
loopCount[jumps[line] - 1] += 1
|
||||
num_inflight = NUM_FLAT + NUM_SMEM + NUM_VLMEM + NUM_VSMEM
|
||||
|
||||
if inst.type == SMEM or inst.type == LDS:
|
||||
smem_ordering = 1 if inst.type == SMEM else smem_ordering
|
||||
SMEM_INST.append([reverse_map[line], num_inflight])
|
||||
NUM_SMEM += 1
|
||||
elif inst.type == VMEM or (inst.type == FLAT and "global_" in as_line[0]):
|
||||
inc_ordering = False
|
||||
if "flat_" in as_line[0]:
|
||||
inc_ordering = True
|
||||
|
||||
if not bGFX9 and "store" in as_line[0]:
|
||||
VSMEM_INST.append([reverse_map[line], num_inflight])
|
||||
NUM_VSMEM += 1
|
||||
if inc_ordering:
|
||||
vsmem_ordering = 1
|
||||
else:
|
||||
VLMEM_INST.append([reverse_map[line], num_inflight])
|
||||
NUM_VLMEM += 1
|
||||
if inc_ordering:
|
||||
vlmem_ordering = 1
|
||||
elif inst.type == FLAT:
|
||||
smem_ordering = 1
|
||||
vlmem_ordering = 1
|
||||
vsmem_ordering = 1
|
||||
FLAT_INST.append([reverse_map[line], num_inflight])
|
||||
NUM_FLAT += 1
|
||||
elif inst.type == IMMED and "s_wait" in as_line[0] and not "s_wait_alu" in as_line[0]:
|
||||
if "lgkmcnt" in as_line[0] or "dscnt" in as_line[0] or "kmcnt" in as_line[0]:
|
||||
try:
|
||||
wait_N = int(as_line[0].split("lgkmcnt(")[1].split(")")[0])
|
||||
except:
|
||||
wait_N = 0
|
||||
flight_count.append([as_line[5], num_inflight, wait_N])
|
||||
if wait_N == 0:
|
||||
smem_ordering = 0
|
||||
if smem_ordering == 0:
|
||||
offset = len(SMEM_INST) - wait_N
|
||||
mem_unroll.append(
|
||||
[reverse_map[line], SMEM_INST[:offset] + FLAT_INST]
|
||||
)
|
||||
SMEM_INST = SMEM_INST[offset:]
|
||||
NUM_SMEM = len(SMEM_INST)
|
||||
FLAT_INST = []
|
||||
NUM_FLAT = 0
|
||||
else:
|
||||
NUM_SMEM = min(max(wait_N - NUM_FLAT, 0), NUM_SMEM)
|
||||
NUM_FLAT = min(max(wait_N - NUM_SMEM, 0), NUM_FLAT)
|
||||
num_inflight = NUM_FLAT + NUM_SMEM + NUM_VLMEM + NUM_VSMEM
|
||||
|
||||
if "vmcnt" in as_line[0] or "loadcnt" in as_line[0]:
|
||||
try:
|
||||
wait_N = int(as_line[0].split("vmcnt(")[1].split(")")[0])
|
||||
except:
|
||||
wait_N = 0
|
||||
flight_count.append([as_line[5], num_inflight, wait_N])
|
||||
if wait_N == 0:
|
||||
vlmem_ordering = 0
|
||||
if vlmem_ordering == 0:
|
||||
offset = len(VLMEM_INST) - wait_N
|
||||
mem_unroll.append(
|
||||
[reverse_map[line], VLMEM_INST[:offset] + FLAT_INST]
|
||||
)
|
||||
VLMEM_INST = VLMEM_INST[offset:]
|
||||
NUM_VLMEM = len(VLMEM_INST)
|
||||
FLAT_INST = []
|
||||
NUM_FLAT = 0
|
||||
else:
|
||||
NUM_VLMEM = min(max(wait_N - NUM_FLAT, 0), NUM_VLMEM)
|
||||
NUM_FLAT = min(max(wait_N - NUM_VLMEM, 0), NUM_FLAT)
|
||||
num_inflight = NUM_FLAT + NUM_SMEM + NUM_VLMEM + NUM_VSMEM
|
||||
|
||||
if "vscnt" in as_line[0] or (bGFX9 and "vmcnt" in as_line[0]) or "storecnt" in as_line[0]:
|
||||
try:
|
||||
wait_N = int(as_line[0].split('vscnt(')[1].split(')')[0])
|
||||
except:
|
||||
try:
|
||||
wait_N = int(as_line[0].split('vmcnt(')[1].split(')')[0])
|
||||
except:
|
||||
wait_N = 0
|
||||
flight_count.append([as_line[5], num_inflight, wait_N])
|
||||
if wait_N == 0:
|
||||
vsmem_ordering = 0
|
||||
if vsmem_ordering == 0:
|
||||
offset = len(VSMEM_INST) - wait_N
|
||||
mem_unroll.append(
|
||||
[reverse_map[line], VSMEM_INST[:offset] + FLAT_INST]
|
||||
)
|
||||
VSMEM_INST = VSMEM_INST[offset:]
|
||||
NUM_VSMEM = len(VSMEM_INST)
|
||||
FLAT_INST = []
|
||||
NUM_FLAT = 0
|
||||
else:
|
||||
NUM_VSMEM = min(max(wait_N - NUM_FLAT, 0), NUM_VSMEM)
|
||||
NUM_FLAT = min(max(wait_N - NUM_VSMEM, 0), NUM_FLAT)
|
||||
num_inflight = NUM_FLAT + NUM_SMEM + NUM_VLMEM + NUM_VSMEM
|
||||
|
||||
elif inst.type == JUMP and as_line[1] == BRANCH:
|
||||
next = watchlist.jump(as_line)
|
||||
if next is None or next == 0:
|
||||
print("Jump to unknown location!", as_line)
|
||||
break
|
||||
elif inst.type == NEXT and as_line[1] == BRANCH:
|
||||
pass
|
||||
else:
|
||||
matched = False
|
||||
if watchlist.try_match_swapped(i, line, lineincrement):
|
||||
temp = insts[i]
|
||||
insts[i] = insts[i + 1]
|
||||
insts[i + 1] = temp
|
||||
next = line
|
||||
else:
|
||||
hasWait = "s_wait" in as_line[0]
|
||||
if hasWait or "_load_" in as_line[0]:
|
||||
if skipped_immed > 0 and hasWait:
|
||||
matched = True
|
||||
skipped_immed -= 1
|
||||
elif 's_waitcnt' in as_line[0] and 'scratch_' not in as_line[0]:
|
||||
print('WARNING: Parsing terminated at:', as_line)
|
||||
break
|
||||
|
||||
if matched or as_line[1] != DONT_KNOW:
|
||||
if matched:
|
||||
inst.asmline = reverse_map[line]
|
||||
result.append(inst)
|
||||
i += 1
|
||||
num_failed_stitches = 0
|
||||
elif not bGFX9 and inst.type == IMMED and line != next and as_line[1] != SKIP:
|
||||
skipped_immed += 1
|
||||
inst.asmline = reverse_map[line]
|
||||
result.append(inst)
|
||||
if 's_barrier' in as_line[0]:
|
||||
next = line + lineincrement
|
||||
i += 1
|
||||
else:
|
||||
num_failed_stitches += 1
|
||||
|
||||
maxline = max(reverse_map[line], maxline)
|
||||
line = next
|
||||
|
||||
N = max(N, 1)
|
||||
if i != N and (insts[i].type == WAVE_ENDED or i == N-1):
|
||||
print('Warning - Wave ended.')
|
||||
elif i < N:
|
||||
print('Warning - Stitching rate: '+str(i * 100 / N)+'% matched', i, ' of ', N)
|
||||
try:
|
||||
print(line, code[line])
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
while line < len(code):
|
||||
if "s_endpgm" in code[line]:
|
||||
mem_unroll.append(
|
||||
[reverse_map[line], SMEM_INST + VLMEM_INST + VSMEM_INST + FLAT_INST]
|
||||
)
|
||||
break
|
||||
line += 1
|
||||
print('Success: Parsed', i, 'tokens')
|
||||
|
||||
return result, loopCount, mem_unroll, flight_count, maxline, len(result), pcskip
|
||||
+270
@@ -0,0 +1,270 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
|
||||
if sys.version_info[0] < 3:
|
||||
raise Exception("Must be using Python 3")
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import socket
|
||||
from pathlib import Path
|
||||
from collections import defaultdict
|
||||
import http.server
|
||||
import socketserver
|
||||
import socket
|
||||
import asyncio
|
||||
import websockets
|
||||
from multiprocessing import Process, Manager
|
||||
import numpy as np
|
||||
from http import HTTPStatus
|
||||
from io import BytesIO
|
||||
from drawing import Readable, GeneratePIC
|
||||
from copy import deepcopy
|
||||
from shutil import copy2
|
||||
from glob import glob
|
||||
|
||||
JSON_GLOBAL_DICTIONARY = {}
|
||||
|
||||
def get_ip():
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
s.settimeout(0)
|
||||
try:
|
||||
hostname = socket.gethostname()
|
||||
IPAddr = socket.gethostbyname(hostname)
|
||||
s.connect(({IPAddr}, 1))
|
||||
except Exception:
|
||||
IPAddr = "127.0.0.1"
|
||||
finally:
|
||||
return IPAddr
|
||||
|
||||
|
||||
IPAddr = get_ip()
|
||||
PORT, WebSocketPort = 8000, 18000
|
||||
SP = "\u00A0"
|
||||
|
||||
|
||||
def get_top_n(code):
|
||||
TOP_N = 10
|
||||
top_n = sorted(deepcopy(code), key=lambda x: x[-1], reverse=True)[:TOP_N]
|
||||
return [
|
||||
(line_num, hitc, 0, run_time) for _, _, _, _, line_num, _, hitc, run_time in top_n
|
||||
]
|
||||
|
||||
|
||||
def wave_info(df, id):
|
||||
dic = {
|
||||
"Issue": df["issued_ins"][id],
|
||||
"Valu": df["valu_ins"][id],
|
||||
"Valu_stall": df["valu_stalls"][id],
|
||||
"Salu": df["salu_ins"][id],
|
||||
"Salu_stall": df["salu_stalls"][id],
|
||||
"Vmem": df["vmem_ins"][id],
|
||||
"Vmem_stall": df["vmem_stalls"][id],
|
||||
"Smem": df["smem_ins"][id],
|
||||
"Smem_stall": df["smem_stalls"][id],
|
||||
"Flat": df["flat_ins"][id],
|
||||
"Flat_stall": df["flat_stalls"][id],
|
||||
"Lds": df["lds_ins"][id],
|
||||
"Lds_stall": df["lds_stalls"][id],
|
||||
"Br": df["br_ins"][id],
|
||||
"Br_stall": df["br_stalls"][id],
|
||||
}
|
||||
dic["Issue_stall"] = int(np.sum([dic[key] for key in dic.keys() if "_STALL" in key]))
|
||||
return dic
|
||||
|
||||
|
||||
def extract_data(df, se_number):
|
||||
if len(df["id"]) == 0 or len(df["instructions"]) == 0 or len(df["timeline"]) == 0:
|
||||
return None
|
||||
|
||||
wave_filenames = []
|
||||
flight_count = []
|
||||
wave_slot_count = [
|
||||
{df["wave_slot"][wave_id]: 0 for wave_id in df["id"]} for k in range(4)
|
||||
]
|
||||
|
||||
print("Number of waves:", len(df["id"]))
|
||||
allwaves_maxline = 0
|
||||
|
||||
for wave_id in df["id"]:
|
||||
stitched, loopCount, mem_unroll, count, maxline, num_insts = df["instructions"][
|
||||
wave_id
|
||||
]
|
||||
timeline = df["timeline"][wave_id]
|
||||
|
||||
if len(stitched) == 0 or len(timeline) == 0:
|
||||
continue
|
||||
|
||||
allwaves_maxline = max(allwaves_maxline, maxline)
|
||||
flight_count.append(count)
|
||||
|
||||
wave_entry = {
|
||||
"id": int(df["id"][wave_id]),
|
||||
"simd": int(df["simd"][wave_id]),
|
||||
"slot": int(df["wave_slot"][wave_id]),
|
||||
"begin": int(df["begin_time"][wave_id]),
|
||||
"end": int(df["end_time"][wave_id]),
|
||||
"info": wave_info(df, wave_id),
|
||||
"instructions": stitched,
|
||||
"timeline": timeline,
|
||||
"waitcnt": mem_unroll,
|
||||
}
|
||||
data_obj = {
|
||||
"name": "SE".format(se_number),
|
||||
"duration": sum(dur for (_, dur) in timeline),
|
||||
"wave": wave_entry,
|
||||
"loop_count": loopCount,
|
||||
"top_n": [],
|
||||
"num_stitched": len(stitched),
|
||||
"num_insts": num_insts,
|
||||
"websocket_port": WebSocketPort,
|
||||
"generation_time": time.ctime(),
|
||||
}
|
||||
|
||||
simd_id = df["simd"][wave_id]
|
||||
slot_id = df["wave_slot"][wave_id]
|
||||
slot_count = wave_slot_count[simd_id][slot_id]
|
||||
wave_slot_count[simd_id][slot_id] += 1
|
||||
|
||||
OUT = (
|
||||
"se"
|
||||
+ str(se_number)
|
||||
+ "_sm"
|
||||
+ str(simd_id)
|
||||
+ "_sl"
|
||||
+ str(slot_id)
|
||||
+ "_wv"
|
||||
+ str(slot_count)
|
||||
+ ".json"
|
||||
)
|
||||
JSON_GLOBAL_DICTIONARY[OUT] = Readable(data_obj)
|
||||
wave_filenames.append((OUT, df["begin_time"][wave_id], df["end_time"][wave_id]))
|
||||
|
||||
data_obj = {
|
||||
"name": "SE".format(se_number),
|
||||
"websocket_port": WebSocketPort,
|
||||
"generation_time": time.ctime(),
|
||||
}
|
||||
se_filename = None
|
||||
if len(wave_filenames) > 0:
|
||||
se_filename = "se" + str(se_number) + "_info.json"
|
||||
JSON_GLOBAL_DICTIONARY[se_filename] = Readable(data_obj)
|
||||
|
||||
return flight_count, wave_filenames, se_filename, allwaves_maxline
|
||||
|
||||
|
||||
def call_picture_callback(return_dict, drawinfo):
|
||||
response, imagebytes = GeneratePIC(drawinfo)
|
||||
return_dict["graph_options.json"] = response
|
||||
for k, v in imagebytes.items():
|
||||
return_dict[k] = v
|
||||
|
||||
for n, m in enumerate(drawinfo["TIMELINES"]):
|
||||
return_dict["wstates" + str(n) + ".json"] = Readable(
|
||||
{"data": [int(n) for n in list(np.asarray(m))]}
|
||||
)
|
||||
for n, e in enumerate(drawinfo["EVENTS"]):
|
||||
return_dict["se" + str(n) + "_perfcounter.json"] = Readable(
|
||||
{"data": [v.toTuple() for v in e]}
|
||||
)
|
||||
|
||||
|
||||
def view_trace(
|
||||
code,
|
||||
dbnames,
|
||||
att_filenames,
|
||||
se_time_begin,
|
||||
gfxv,
|
||||
drawinfo,
|
||||
trace_instance_name
|
||||
):
|
||||
global JSON_GLOBAL_DICTIONARY
|
||||
pic_thread = None
|
||||
|
||||
manager = Manager()
|
||||
return_dict = manager.dict()
|
||||
occ_dict = {str(k): drawinfo["OCCUPANCY"][k] for k in range(len(drawinfo["OCCUPANCY"]))}
|
||||
occ_dict['dispatches'] = {}
|
||||
for id, name in drawinfo['DispatchNames'].items():
|
||||
occ_dict['dispatches'][id] = name
|
||||
occ_dict['names'] = drawinfo['ShaderNames']
|
||||
|
||||
JSON_GLOBAL_DICTIONARY["occupancy.json"] = Readable(occ_dict)
|
||||
pic_thread = Process(target=call_picture_callback, args=(return_dict, drawinfo))
|
||||
pic_thread.start()
|
||||
|
||||
att_filenames = [Path(f).name for f in att_filenames]
|
||||
se_numbers = [int(a.split("_se")[1].split(".att")[0]) for a in att_filenames]
|
||||
flight_count = []
|
||||
simd_wave_filenames = {}
|
||||
se_filenames = []
|
||||
|
||||
allse_maxline = 0
|
||||
for se_number, dbname in zip(se_numbers, dbnames):
|
||||
if len(dbname["id"]) == 0:
|
||||
continue
|
||||
|
||||
count, wv_filenames, se_filename, maxline = extract_data(dbname, se_number)
|
||||
if se_filename is None:
|
||||
continue
|
||||
allse_maxline = max(allse_maxline, maxline)
|
||||
se_filenames.append(se_filename)
|
||||
|
||||
if count is not None:
|
||||
flight_count.append(count)
|
||||
simd_wave_filenames[se_number] = wv_filenames
|
||||
|
||||
code_sel = [c[:-3]+c[-2:] for c in code]
|
||||
JSON_GLOBAL_DICTIONARY['code.json'] = Readable({"code": code_sel, "top_n": get_top_n(code_sel)})
|
||||
|
||||
for key in simd_wave_filenames.keys():
|
||||
wv_array = [
|
||||
[
|
||||
int(s[0].split("_sm")[1].split("_sl")[0]),
|
||||
int(s[0].split("_sl")[1].split("_wv")[0]),
|
||||
int(s[0].split("_wv")[1].split(".")[0]),
|
||||
s,
|
||||
]
|
||||
for s in simd_wave_filenames[key]
|
||||
]
|
||||
|
||||
wv_dict = {}
|
||||
for wv in wv_array:
|
||||
try:
|
||||
wv_dict[wv[0]][wv[1]][wv[2]] = wv[3]
|
||||
except:
|
||||
try:
|
||||
wv_dict[wv[0]][wv[1]] = {wv[2]: wv[3]}
|
||||
except:
|
||||
try:
|
||||
wv_dict[wv[0]] = {wv[1]: {wv[2]: wv[3]}}
|
||||
except:
|
||||
pass
|
||||
|
||||
simd_wave_filenames[key] = wv_dict
|
||||
|
||||
JSON_GLOBAL_DICTIONARY["filenames.json"] = Readable(
|
||||
{
|
||||
"wave_filenames": simd_wave_filenames,
|
||||
"se_filenames": se_filenames,
|
||||
"global_begin_time": int(se_time_begin),
|
||||
"gfxv": gfxv,
|
||||
}
|
||||
)
|
||||
|
||||
if pic_thread is not None:
|
||||
pic_thread.join()
|
||||
for k, v in return_dict.items():
|
||||
JSON_GLOBAL_DICTIONARY[k] = v
|
||||
|
||||
os.makedirs(trace_instance_name + "_ui/", exist_ok=True)
|
||||
JSON_GLOBAL_DICTIONARY["live.json"] = Readable({"live": 0})
|
||||
|
||||
ui_dir_files = glob(os.path.join(os.path.abspath(os.path.dirname(__file__)), "ui")+"/*")
|
||||
for f in ui_dir_files:
|
||||
copy2(f, trace_instance_name + "_ui/")
|
||||
|
||||
for k, v in JSON_GLOBAL_DICTIONARY.items():
|
||||
with open(os.path.join(trace_instance_name+"_ui", k), "w" if ".json" in k else "wb") as f:
|
||||
f.write(v.read())
|
||||
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
|
||||
if sys.version_info[0] < 3:
|
||||
raise Exception("Must be using Python 3")
|
||||
|
||||
import http.server
|
||||
import socketserver
|
||||
import socket
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
class NoCacheHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
|
||||
def end_headers(self):
|
||||
self.send_my_headers()
|
||||
http.server.SimpleHTTPRequestHandler.end_headers(self)
|
||||
|
||||
def send_my_headers(self):
|
||||
self.send_header("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||
self.send_header("Pragma", "no-cache")
|
||||
self.send_header("Expires", "0")
|
||||
|
||||
def do_GET(self):
|
||||
if ".png?" in self.path:
|
||||
self.path = self.path.split(".png?")[0] + ".png"
|
||||
|
||||
http.server.SimpleHTTPRequestHandler.do_GET(self)
|
||||
|
||||
|
||||
class RocTCPServer(socketserver.TCPServer):
|
||||
def server_bind(self):
|
||||
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
self.socket.bind(self.server_address)
|
||||
|
||||
|
||||
def run_server():
|
||||
Handler = NoCacheHTTPRequestHandler
|
||||
os.chdir(os.path.join(os.path.dirname(os.path.abspath(__file__)), "."))
|
||||
try:
|
||||
with RocTCPServer((IPAddr, PORT), Handler) as httpd:
|
||||
httpd.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
|
||||
def get_ip():
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
s.settimeout(0)
|
||||
try:
|
||||
hostname = socket.gethostname()
|
||||
IPAddr = socket.gethostbyname(hostname)
|
||||
s.connect(({IPAddr}, 1))
|
||||
except Exception:
|
||||
IPAddr = "127.0.0.1"
|
||||
finally:
|
||||
return IPAddr
|
||||
|
||||
|
||||
IPAddr = get_ip()
|
||||
PORT = 8000
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
PORT = int(sys.argv[1])
|
||||
print("serving at port: {0}".format(PORT))
|
||||
|
||||
try:
|
||||
run_server()
|
||||
except KeyboardInterrupt:
|
||||
print("Exitting.")
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,132 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="240"
|
||||
height="26"
|
||||
viewBox="0 0 68.974872 7.4722778"
|
||||
version="1.1"
|
||||
id="svg5"
|
||||
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
|
||||
sodipodi:docname="logo.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview7"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
showgrid="false"
|
||||
inkscape:zoom="5.1659481"
|
||||
inkscape:cx="147.89154"
|
||||
inkscape:cy="75.10722"
|
||||
inkscape:window-width="3840"
|
||||
inkscape:window-height="2066"
|
||||
inkscape:window-x="-11"
|
||||
inkscape:window-y="-11"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1" />
|
||||
<defs
|
||||
id="defs2" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<rect
|
||||
style="fill:#217844;stroke:#00000f;stroke-width:0.0610255;stroke-miterlimit:1"
|
||||
id="rect864"
|
||||
width="37.346996"
|
||||
height="6.0453367"
|
||||
x="26.672634"
|
||||
y="0.91105449" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.35px;font-family:Calibri;-inkscape-font-specification:Calibri;fill:#165016;stroke:#00000f;stroke-width:0.0539999;stroke-miterlimit:1;stroke-dasharray:none"
|
||||
x="82.180176"
|
||||
y="95.625328"
|
||||
id="text3303"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3301"
|
||||
style="fill:#165016;stroke-width:0.054"
|
||||
x="82.180176"
|
||||
y="95.625328" /></text>
|
||||
<!-- <g
|
||||
id="g5730"
|
||||
transform="matrix(0.57568164,0,0,0.53998363,-62.239348,-52.859622)">
|
||||
<g
|
||||
aria-label="AMD"
|
||||
id="text954"
|
||||
style="font-size:6.35px;font-family:Calibri;-inkscape-font-specification:Calibri;fill:#000080;stroke:#00000f;stroke-width:0.0539999;stroke-miterlimit:1"
|
||||
transform="translate(27.503837,13.598552)">
|
||||
<path
|
||||
d="m 89.87666,89.436026 q 0.04779,0.05463 0.06348,0.09511 0.01746,0.03646 0.0022,0.06566 -0.01528,0.0292 -0.06797,0.05628 -0.0498,0.02596 -0.139412,0.0607 -0.08962,0.03475 -0.146787,0.05026 -0.05316,0.01729 -0.08543,0.01982 -0.0305,-0.0015 -0.05168,-0.01322 -0.01829,-0.01286 -0.03994,-0.0344 l -0.673965,-0.786215 -1.555301,0.60303 0.04216,1.017881 q 0.0014,0.02938 -0.0052,0.05522 -0.0077,0.02295 -0.03103,0.04861 -0.02154,0.02165 -0.07133,0.04761 -0.0469,0.02484 -0.127848,0.05622 -0.08384,0.0325 -0.142126,0.04513 -0.05428,0.01439 -0.08525,0.0031 -0.02808,-0.01239 -0.04265,-0.04997 -0.0128,-0.04159 -0.01433,-0.11416 l -0.09264,-3.961315 q -7.66e-4,-0.03628 0.0087,-0.06324 0.01239,-0.02808 0.0426,-0.0531 0.0331,-0.02613 0.08691,-0.05032 0.05558,-0.0282 0.142303,-0.06183 0.09251,-0.03587 0.155459,-0.05362 0.06183,-0.02065 0.103895,-0.02366 0.04206,-0.003 0.07127,0.01227 0.02808,0.01239 0.05198,0.03971 z m -2.799476,-2.442912 -0.0029,0.0011 0.07829,2.114582 1.300903,-0.504394 z"
|
||||
style="stroke-width:0.054"
|
||||
id="path4984" />
|
||||
<path
|
||||
d="m 94.99296,89.362526 q -2.68e-4,0.0248 -0.01287,0.04327 -0.0126,0.01847 -0.04374,0.03054 -0.0311,0.009 -0.08078,0.01463 -0.04661,0.0088 -0.124117,0.008 -0.07131,-7.69e-4 -0.123916,-0.01064 -0.04954,-0.0067 -0.08044,-0.01637 -0.02777,-0.0127 -0.03997,-0.03144 -0.0122,-0.01874 -0.01193,-0.04354 l 0.03766,-3.494157 -0.0062,-6.7e-5 -1.467119,3.494252 q -0.0095,0.0216 -0.0283,0.0369 -0.01567,0.01533 -0.04991,0.02737 -0.0311,0.009 -0.07764,0.01157 -0.04347,0.0057 -0.105481,0.0051 -0.06511,-7.02e-4 -0.111547,-0.0074 -0.04644,-0.0067 -0.07734,-0.01634 -0.03087,-0.01274 -0.04931,-0.02844 -0.01533,-0.01567 -0.02133,-0.03434 l -1.329447,-3.524398 -0.0031,-3.4e-5 -0.03767,3.494158 q -2.67e-4,0.0248 -0.01287,0.04327 -0.0126,0.01847 -0.04374,0.03054 -0.0311,0.009 -0.08078,0.01463 -0.04971,0.0088 -0.127216,0.0079 -0.07441,-8.02e-4 -0.123916,-0.01064 -0.04954,-0.0067 -0.08044,-0.01637 -0.02777,-0.0127 -0.03997,-0.03144 -0.0091,-0.0187 -0.0088,-0.04351 l 0.03977,-3.689482 q 0.0014,-0.130217 0.07022,-0.18529 0.06881,-0.05507 0.152521,-0.05417 l 0.322442,0.0035 q 0.09921,0.0011 0.173422,0.02047 0.07421,0.0194 0.129582,0.06031 0.05537,0.04091 0.09191,0.103316 0.03654,0.06241 0.06354,0.146421 l 1.131337,2.917613 0.0155,1.67e-4 1.240374,-2.882745 q 0.03511,-0.09265 0.07611,-0.157319 0.04101,-0.06467 0.08791,-0.101377 0.05004,-0.03977 0.109116,-0.05464 0.05911,-0.01797 0.136618,-0.01713 l 0.337944,0.0036 q 0.04651,5.01e-4 0.08665,0.01644 0.04327,0.01287 0.07084,0.04417 0.0307,0.02824 0.04881,0.07494 0.01813,0.04361 0.01743,0.108714 z"
|
||||
style="stroke-width:0.054"
|
||||
id="path4986" />
|
||||
<path
|
||||
d="m 99.688647,89.106481 q -0.202131,0.480082 -0.476607,0.788407 -0.273273,0.305468 -0.610318,0.442789 -0.332985,0.135666 -0.72523,0.10845 -0.392245,-0.02722 -0.866611,-0.22694 l -0.78299,-0.329666 q -0.06573,-0.02767 -0.107689,-0.0958 -0.0379,-0.06979 0.009,-0.181233 l 1.388446,-3.297704 q 0.04692,-0.111447 0.122123,-0.130248 0.07926,-0.02046 0.144986,0.0072 l 0.837285,0.352526 q 0.480082,0.202131 0.758628,0.467433 0.279749,0.262444 0.403985,0.590616 0.128297,0.326518 0.103035,0.706129 -0.0224,0.380814 -0.198064,0.798028 z m -0.525653,-0.197768 q 0.126331,-0.300051 0.160317,-0.58852 0.03519,-0.291326 -0.04543,-0.547307 -0.07776,-0.254777 -0.276143,-0.462778 -0.198382,-0.208 -0.581304,-0.369224 l -0.500085,-0.210553 -1.211581,2.877633 0.505801,0.212959 q 0.354346,0.149191 0.634542,0.162873 0.280196,0.01368 0.520083,-0.100626 0.239887,-0.114308 0.435404,-0.354952 0.199578,-0.242298 0.358396,-0.619505 z"
|
||||
style="stroke-width:0.054"
|
||||
id="path4988" />
|
||||
</g>
|
||||
<g
|
||||
aria-label="Research"
|
||||
id="text3463"
|
||||
style="font-size:6.35px;font-family:Calibri;-inkscape-font-specification:Calibri;fill:#165016;stroke:#00000f;stroke-width:0.0539999;stroke-miterlimit:1"
|
||||
transform="translate(28.562171,13.598552)">
|
||||
<path
|
||||
d="m 83.86747,96.36485 q 0,0.02481 -0.0093,0.04341 -0.0093,0.0186 -0.04031,0.03101 -0.031,0.0124 -0.08682,0.0186 -0.05581,0.0062 -0.148828,0.0062 -0.08062,0 -0.133325,-0.0062 -0.04961,-0.0062 -0.08062,-0.0186 -0.031,-0.0155 -0.04961,-0.04031 -0.0155,-0.0248 -0.0279,-0.06201 l -0.36897,-0.945679 q -0.06511,-0.161231 -0.136426,-0.294556 -0.06821,-0.136426 -0.164331,-0.232544 -0.09612,-0.09922 -0.226343,-0.151928 -0.130224,-0.05581 -0.313159,-0.05581 h -0.356567 v 1.708423 q 0,0.02481 -0.0155,0.04341 -0.0124,0.0186 -0.04341,0.03101 -0.02791,0.0093 -0.08062,0.0155 -0.04961,0.0093 -0.127124,0.0093 -0.07752,0 -0.130225,-0.0093 -0.04961,-0.0062 -0.08062,-0.0155 -0.03101,-0.0124 -0.04341,-0.03101 -0.0124,-0.0186 -0.0124,-0.04341 v -3.714502 q 0,-0.120923 0.06201,-0.167432 0.06511,-0.04961 0.136426,-0.04961 h 0.852661 q 0.151928,0 0.251147,0.0093 0.10232,0.0062 0.182935,0.0155 0.232544,0.04031 0.409277,0.127124 0.179834,0.08682 0.300757,0.220141 0.120923,0.133325 0.179834,0.306958 0.06201,0.170532 0.06201,0.378272 0,0.201538 -0.05581,0.362768 -0.05271,0.15813 -0.155029,0.282154 -0.10232,0.120922 -0.244947,0.210839 -0.142626,0.08992 -0.31936,0.151929 0.09922,0.04341 0.179834,0.111621 0.08061,0.06511 0.148828,0.15813 0.07131,0.09302 0.133325,0.213941 0.06201,0.120922 0.124024,0.272851 l 0.359668,0.883667 q 0.04341,0.111621 0.05581,0.15813 0.0124,0.04341 0.0124,0.06821 z m -0.803052,-2.818433 q 0,-0.235644 -0.10542,-0.396875 -0.10542,-0.164331 -0.353467,-0.235644 -0.07751,-0.0217 -0.176733,-0.03101 -0.09612,-0.0093 -0.254248,-0.0093 h -0.449585 v 1.351856 h 0.520898 q 0.21084,0 0.362769,-0.04961 0.155029,-0.05271 0.257348,-0.142627 0.10232,-0.09302 0.148829,-0.217041 0.04961,-0.124023 0.04961,-0.269751 z"
|
||||
style="stroke-width:0.054"
|
||||
id="path4967" />
|
||||
<path
|
||||
d="m 86.850233,94.851764 q 0,0.120923 -0.06201,0.173633 -0.05891,0.04961 -0.136426,0.04961 H 84.82245 q 0,0.232544 0.04651,0.418579 0.04651,0.186035 0.155029,0.319361 0.108521,0.133325 0.282154,0.204638 0.173633,0.07131 0.42478,0.07131 0.198437,0 0.353467,-0.03101 0.155029,-0.03411 0.26665,-0.07441 0.114722,-0.04031 0.186035,-0.07131 0.07441,-0.03411 0.111621,-0.03411 0.0217,0 0.03721,0.0124 0.0186,0.0093 0.02791,0.03101 0.0093,0.0217 0.0124,0.06201 0.0062,0.03721 0.0062,0.09302 0,0.04031 -0.0031,0.07131 -0.0031,0.02791 -0.0093,0.05271 -0.0031,0.0217 -0.0155,0.04031 -0.0093,0.0186 -0.0279,0.03721 -0.0155,0.0155 -0.09922,0.05581 -0.08372,0.03721 -0.217041,0.07441 -0.133325,0.03721 -0.310059,0.06511 -0.173633,0.03101 -0.37207,0.03101 -0.344165,0 -0.604614,-0.09612 -0.257349,-0.09612 -0.434082,-0.285254 -0.176734,-0.189136 -0.266651,-0.474389 -0.08992,-0.285254 -0.08992,-0.663526 0,-0.359668 0.09302,-0.644922 0.09302,-0.288354 0.26665,-0.486792 0.176734,-0.201538 0.424781,-0.306958 0.248046,-0.10852 0.555004,-0.10852 0.328663,0 0.558106,0.10542 0.232544,0.10542 0.381372,0.285254 0.148828,0.176733 0.217041,0.418579 0.07131,0.238745 0.07131,0.511596 z m -0.514697,-0.151929 q 0.0093,-0.403076 -0.179834,-0.632519 -0.186035,-0.229444 -0.555005,-0.229444 -0.189135,0 -0.331762,0.07131 -0.142627,0.07131 -0.238745,0.189136 -0.09612,0.117822 -0.148829,0.275952 -0.05271,0.155029 -0.05891,0.325561 z"
|
||||
style="stroke-width:0.054"
|
||||
id="path4969" />
|
||||
<path
|
||||
d="m 89.380312,95.608307 q 0,0.21394 -0.08062,0.381372 -0.07751,0.167432 -0.223242,0.282153 -0.145727,0.114722 -0.347265,0.173633 -0.201539,0.05891 -0.443384,0.05891 -0.148828,0 -0.285254,-0.0248 -0.133325,-0.02171 -0.241846,-0.05581 -0.10542,-0.03721 -0.179834,-0.07441 -0.07441,-0.04031 -0.10852,-0.07131 -0.03411,-0.031 -0.04961,-0.08682 -0.0155,-0.05581 -0.0155,-0.151929 0,-0.05891 0.0062,-0.09922 0.0062,-0.04031 0.0155,-0.06511 0.0093,-0.0248 0.02481,-0.03411 0.0186,-0.0124 0.04031,-0.0124 0.03411,0 0.09922,0.04341 0.06821,0.04031 0.164332,0.08992 0.09922,0.04961 0.232543,0.09302 0.133326,0.04031 0.306958,0.04031 0.130225,0 0.235645,-0.02791 0.10542,-0.02791 0.182935,-0.08062 0.07751,-0.05581 0.117822,-0.139526 0.04341,-0.08372 0.04341,-0.198437 0,-0.117823 -0.06201,-0.198438 -0.05891,-0.08061 -0.15813,-0.142627 -0.09922,-0.06201 -0.223242,-0.10852 -0.124023,-0.04961 -0.257348,-0.10232 -0.130225,-0.05271 -0.257349,-0.117822 -0.124023,-0.06821 -0.223242,-0.164331 -0.09922,-0.09612 -0.161231,-0.229443 -0.05891,-0.133326 -0.05891,-0.319361 0,-0.164331 0.06201,-0.313159 0.06511,-0.151929 0.192236,-0.26355 0.127124,-0.114721 0.31626,-0.182934 0.192236,-0.06821 0.446484,-0.06821 0.111621,0 0.223243,0.0186 0.111621,0.0186 0.201538,0.04651 0.08992,0.0279 0.151928,0.06201 0.06511,0.03101 0.09612,0.05581 0.03411,0.02481 0.04341,0.04341 0.0124,0.0186 0.0155,0.04341 0.0062,0.0217 0.0093,0.05581 0.0062,0.03411 0.0062,0.08372 0,0.05271 -0.0062,0.09302 -0.0031,0.03721 -0.0155,0.06201 -0.0093,0.02481 -0.0248,0.03721 -0.0155,0.0093 -0.03411,0.0093 -0.02791,0 -0.08062,-0.03411 -0.05271,-0.03411 -0.136426,-0.07131 -0.08371,-0.04031 -0.198437,-0.07441 -0.111621,-0.03411 -0.257349,-0.03411 -0.130224,0 -0.229443,0.03101 -0.09922,0.0279 -0.164331,0.08371 -0.06201,0.05271 -0.09612,0.127124 -0.03101,0.07442 -0.03101,0.161231 0,0.120923 0.06201,0.204639 0.06201,0.08061 0.16123,0.142627 0.09922,0.06201 0.226343,0.111621 0.127124,0.04961 0.257348,0.102319 0.133326,0.05271 0.26045,0.117822 0.130224,0.06511 0.229443,0.15813 0.09922,0.09302 0.15813,0.223242 0.06201,0.130225 0.06201,0.310059 z"
|
||||
style="stroke-width:0.054"
|
||||
id="path4971" />
|
||||
<path
|
||||
d="m 92.4933,94.851764 q 0,0.120923 -0.06201,0.173633 -0.05891,0.04961 -0.136426,0.04961 h -1.829346 q 0,0.232544 0.04651,0.418579 0.04651,0.186035 0.15503,0.319361 0.10852,0.133325 0.282153,0.204638 0.173633,0.07131 0.42478,0.07131 0.198438,0 0.353467,-0.03101 0.155029,-0.03411 0.26665,-0.07441 0.114722,-0.04031 0.186035,-0.07131 0.07442,-0.03411 0.111622,-0.03411 0.0217,0 0.03721,0.0124 0.0186,0.0093 0.02791,0.03101 0.0093,0.0217 0.0124,0.06201 0.0062,0.03721 0.0062,0.09302 0,0.04031 -0.0031,0.07131 -0.0031,0.02791 -0.0093,0.05271 -0.0031,0.0217 -0.0155,0.04031 -0.0093,0.0186 -0.02791,0.03721 -0.0155,0.0155 -0.09922,0.05581 -0.08372,0.03721 -0.217041,0.07441 -0.133325,0.03721 -0.310059,0.06511 -0.173632,0.03101 -0.37207,0.03101 -0.344165,0 -0.604614,-0.09612 -0.257349,-0.09612 -0.434082,-0.285254 -0.176734,-0.189136 -0.26665,-0.474389 -0.08992,-0.285254 -0.08992,-0.663526 0,-0.359668 0.09302,-0.644922 0.09302,-0.288354 0.26665,-0.486792 0.176734,-0.201538 0.424781,-0.306958 0.248047,-0.10852 0.555005,-0.10852 0.328662,0 0.558105,0.10542 0.232544,0.10542 0.381372,0.285254 0.148828,0.176733 0.217041,0.418579 0.07131,0.238745 0.07131,0.511596 z m -0.514698,-0.151929 q 0.0093,-0.403076 -0.179834,-0.632519 -0.186035,-0.229444 -0.555004,-0.229444 -0.189136,0 -0.331763,0.07131 -0.142627,0.07131 -0.238745,0.189136 -0.09612,0.117822 -0.148828,0.275952 -0.05271,0.155029 -0.05891,0.325561 z"
|
||||
style="stroke-width:0.054"
|
||||
id="path4973" />
|
||||
<path
|
||||
d="m 95.373744,96.371051 q 0,0.03721 -0.02481,0.05581 -0.0248,0.0186 -0.06821,0.02791 -0.04341,0.0093 -0.127124,0.0093 -0.08062,0 -0.130225,-0.0093 -0.04651,-0.0093 -0.06821,-0.02791 -0.0217,-0.0186 -0.0217,-0.05581 v -0.279053 q -0.182935,0.195337 -0.409277,0.303858 -0.223243,0.10852 -0.47439,0.10852 -0.220142,0 -0.399976,-0.05891 -0.176733,-0.05581 -0.303857,-0.164331 -0.124023,-0.10852 -0.195337,-0.26665 -0.06821,-0.15813 -0.06821,-0.359668 0,-0.235645 0.09612,-0.409278 0.09612,-0.173632 0.275952,-0.288354 0.179834,-0.114722 0.440284,-0.170532 0.260449,-0.05891 0.58601,-0.05891 h 0.384473 v -0.217042 q 0,-0.16123 -0.03411,-0.285253 -0.03411,-0.124024 -0.111621,-0.204639 -0.07442,-0.08372 -0.195337,-0.124024 -0.120923,-0.04341 -0.297657,-0.04341 -0.189135,0 -0.341064,0.04651 -0.148828,0.04341 -0.26355,0.09922 -0.111621,0.05271 -0.189136,0.09922 -0.07441,0.04341 -0.111621,0.04341 -0.0248,0 -0.04341,-0.0124 -0.0186,-0.0124 -0.03411,-0.03721 -0.0124,-0.0248 -0.0186,-0.06201 -0.0062,-0.04031 -0.0062,-0.08682 0,-0.07751 0.0093,-0.120923 0.0124,-0.04651 0.05271,-0.08682 0.04341,-0.04031 0.145727,-0.09302 0.10232,-0.05581 0.235645,-0.09922 0.133325,-0.04651 0.291455,-0.07441 0.15813,-0.03101 0.31936,-0.03101 0.300757,0 0.511597,0.06821 0.21084,0.06821 0.341064,0.201538 0.130225,0.130224 0.189136,0.325561 0.05891,0.195337 0.05891,0.455786 z M 94.865248,95.09671 h -0.437183 q -0.210839,0 -0.365869,0.03721 -0.155029,0.03411 -0.257348,0.10542 -0.10232,0.06821 -0.151929,0.167432 -0.04651,0.09612 -0.04651,0.223242 0,0.217041 0.136426,0.347266 0.139526,0.127124 0.387573,0.127124 0.201538,0 0.372071,-0.10232 0.173632,-0.102319 0.362768,-0.313159 z"
|
||||
style="stroke-width:0.054"
|
||||
id="path4975" />
|
||||
<path
|
||||
d="m 97.94413,93.760358 q 0,0.06821 -0.0031,0.114721 -0.0031,0.04651 -0.0124,0.07441 -0.0093,0.02481 -0.02481,0.04031 -0.0124,0.0124 -0.03721,0.0124 -0.02481,0 -0.06201,-0.0124 -0.03411,-0.0155 -0.08061,-0.02791 -0.04341,-0.0155 -0.09922,-0.0279 -0.05581,-0.0124 -0.120922,-0.0124 -0.07751,0 -0.151929,0.03101 -0.07441,0.03101 -0.15813,0.102319 -0.08061,0.07131 -0.170532,0.189136 -0.08992,0.117822 -0.198438,0.288355 v 1.835546 q 0,0.02481 -0.0124,0.04341 -0.0124,0.0155 -0.04031,0.0279 -0.02791,0.0124 -0.07751,0.0186 -0.04961,0.0062 -0.127124,0.0062 -0.07441,0 -0.124024,-0.0062 -0.04961,-0.0062 -0.08061,-0.0186 -0.02791,-0.0124 -0.04031,-0.0279 -0.0093,-0.0186 -0.0093,-0.04341 v -2.790527 q 0,-0.02481 0.0093,-0.04031 0.0093,-0.0186 0.03721,-0.03101 0.02791,-0.0155 0.07131,-0.0186 0.04341,-0.0062 0.114721,-0.0062 0.06821,0 0.111622,0.0062 0.04651,0.0031 0.07131,0.0186 0.02481,0.0124 0.03411,0.03101 0.0124,0.0155 0.0124,0.04031 V 93.9836 q 0.114722,-0.167432 0.21394,-0.272852 0.10232,-0.10542 0.192237,-0.164331 0.08992,-0.06201 0.176733,-0.08372 0.08992,-0.0248 0.179834,-0.0248 0.04031,0 0.08992,0.0062 0.05271,0.0031 0.10852,0.0155 0.05581,0.0124 0.09922,0.0279 0.04651,0.0155 0.06511,0.03101 0.0186,0.0155 0.0248,0.03101 0.0062,0.0124 0.0093,0.03411 0.0062,0.0217 0.0062,0.06511 0.0031,0.04031 0.0031,0.111621 z"
|
||||
style="stroke-width:0.054"
|
||||
id="path4977" />
|
||||
<path
|
||||
d="m 100.4401,95.94317 q 0,0.05271 -0.003,0.09302 -0.003,0.03721 -0.0124,0.06511 -0.006,0.02481 -0.0186,0.04651 -0.009,0.0186 -0.0496,0.05891 -0.0372,0.03721 -0.13023,0.09612 -0.093,0.05581 -0.21084,0.102319 -0.11472,0.04341 -0.251146,0.07131 -0.136426,0.0279 -0.282153,0.0279 -0.300757,0 -0.533301,-0.09922 -0.232544,-0.09922 -0.390674,-0.288354 -0.155029,-0.192236 -0.238745,-0.468188 -0.08061,-0.279053 -0.08061,-0.641822 0,-0.412378 0.09922,-0.706933 0.10232,-0.297657 0.275952,-0.486792 0.176734,-0.189136 0.412378,-0.279053 0.238746,-0.09302 0.514698,-0.09302 0.133325,0 0.257348,0.02481 0.127124,0.02481 0.232543,0.06511 0.10542,0.04031 0.18604,0.09302 0.0837,0.05271 0.12092,0.08992 0.0372,0.03721 0.0496,0.05891 0.0155,0.0217 0.0248,0.05271 0.009,0.02791 0.0124,0.06511 0.003,0.03721 0.003,0.09302 0,0.120923 -0.0279,0.170532 -0.0279,0.04651 -0.0682,0.04651 -0.0465,0 -0.10852,-0.04961 -0.0589,-0.05271 -0.15193,-0.114722 -0.09302,-0.06201 -0.226344,-0.111621 -0.130225,-0.05271 -0.310058,-0.05271 -0.36897,0 -0.567408,0.285254 -0.195337,0.282153 -0.195337,0.821655 0,0.269751 0.04961,0.47439 0.05271,0.201538 0.151928,0.337963 0.09922,0.136426 0.241846,0.204639 0.145728,0.06511 0.331763,0.06511 0.176733,0 0.310058,-0.05581 0.133326,-0.05581 0.229442,-0.120923 0.0992,-0.06821 0.16434,-0.120923 0.0682,-0.05581 0.10542,-0.05581 0.0217,0 0.0372,0.0124 0.0155,0.0124 0.0248,0.04341 0.0124,0.02791 0.0155,0.07441 0.006,0.04341 0.006,0.10542 z"
|
||||
style="stroke-width:0.054"
|
||||
id="path4979" />
|
||||
<path
|
||||
d="m 103.52208,96.36795 q 0,0.02481 -0.0124,0.04341 -0.0124,0.0155 -0.0403,0.0279 -0.0279,0.0124 -0.0775,0.0186 -0.0496,0.0062 -0.12402,0.0062 -0.0775,0 -0.12713,-0.0062 -0.0496,-0.0062 -0.0775,-0.0186 -0.0279,-0.0124 -0.0403,-0.0279 -0.0124,-0.0186 -0.0124,-0.04341 v -1.634008 q 0,-0.238745 -0.0372,-0.384473 -0.0372,-0.145728 -0.10852,-0.251147 -0.0713,-0.10542 -0.18603,-0.161231 -0.11163,-0.05581 -0.26045,-0.05581 -0.19224,0 -0.38448,0.136425 -0.19223,0.136426 -0.40307,0.399976 v 1.950268 q 0,0.02481 -0.0124,0.04341 -0.0124,0.0155 -0.0403,0.0279 -0.0279,0.0124 -0.0775,0.0186 -0.0496,0.0062 -0.12712,0.0062 -0.0744,0 -0.12402,-0.0062 -0.0496,-0.0062 -0.0806,-0.0186 -0.0279,-0.0124 -0.0403,-0.0279 -0.009,-0.0186 -0.009,-0.04341 v -4.142382 q 0,-0.02481 0.009,-0.04341 0.0124,-0.0186 0.0403,-0.031 0.031,-0.0124 0.0806,-0.0186 0.0496,-0.0062 0.12402,-0.0062 0.0775,0 0.12712,0.0062 0.0496,0.0062 0.0775,0.0186 0.0279,0.0124 0.0403,0.031 0.0124,0.0186 0.0124,0.04341 v 1.671215 q 0.22014,-0.232543 0.44338,-0.344165 0.22324,-0.114721 0.44959,-0.114721 0.27905,0 0.46818,0.09612 0.19224,0.09302 0.31006,0.251147 0.11783,0.15813 0.16743,0.372071 0.0527,0.21084 0.0527,0.511596 z"
|
||||
style="stroke-width:0.054"
|
||||
id="path4981" />
|
||||
</g>
|
||||
</g> -->
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.1311px;font-family:Calibri;-inkscape-font-specification:Calibri;fill:#f9f9f9;stroke:#00000f;stroke-width:0.0586557;stroke-miterlimit:1"
|
||||
x="27.642879"
|
||||
y="5.8916035"
|
||||
id="text1590"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan1588"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.1311px;font-family:Calibri;-inkscape-font-specification:Calibri;fill:#f9f9f9;stroke-width:0.0586558"
|
||||
x="27.642879"
|
||||
y="5.8916035">MI Trace View</tspan></text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 20 KiB |
@@ -0,0 +1,137 @@
|
||||
h2,h3,h4 {
|
||||
text-align: center;
|
||||
font-family: Calibri, Candara, Optima, Arial, 'Trebuchet MS', sans-serif;
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
margin: 20px 5px;
|
||||
/*font-family: Calibri, Candara, Segoe, "Segoe UI", Optima, Arial, sans-serif; font-size: 15px; font-style: normal; font-variant: normal; */
|
||||
font-family: 'Courier New', monospace; font-size: 15px; font-style: normal;
|
||||
}
|
||||
|
||||
a {
|
||||
color: royalblue;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
div#flexbox {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
div#flexbox > div {
|
||||
flex: 50%;
|
||||
}
|
||||
|
||||
div#minimap {
|
||||
position: absolute;
|
||||
width: 350px;
|
||||
top: 340px;
|
||||
display: flex;
|
||||
justify-content: right;
|
||||
right: 5px;
|
||||
}
|
||||
|
||||
div#wave, div#cu_wave {
|
||||
overflow:scroll;
|
||||
overflow-y:hidden;
|
||||
}
|
||||
|
||||
div#ma_code {
|
||||
height: calc(100vh - 180px);
|
||||
overflow: auto;
|
||||
overflow-x: hidden;
|
||||
display: block;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
nav {
|
||||
padding-right: 8px;
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
background: #FFFDE3;
|
||||
z-index: 5;
|
||||
margin-left: 10px;
|
||||
width:330px;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
background-color: lightgray;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.clickable {
|
||||
cursor: pointer;
|
||||
font-weight: 180;
|
||||
}
|
||||
|
||||
ul li {
|
||||
border-bottom: 1px dotted black;
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
display: none;
|
||||
position: relative;
|
||||
left: 10px;
|
||||
z-index: 100;
|
||||
background: #333;
|
||||
border: 1px solid #c0c0c0;
|
||||
opacity: 0.8;
|
||||
color: white;
|
||||
}
|
||||
li:hover .tooltip {
|
||||
display: inline-block; /*block;*/
|
||||
}
|
||||
.loop {
|
||||
margin-left: 30px;
|
||||
background: lightseagreen;
|
||||
}
|
||||
|
||||
.btn {
|
||||
border: 1px solid black;
|
||||
background-color: #D7D7D7;
|
||||
color: black;
|
||||
padding: 3px 4px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
border-style: ridge;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
color: blue;
|
||||
}
|
||||
|
||||
.dropbtn {
|
||||
border: 2px solid black;
|
||||
background-color: #D7D7D7;
|
||||
color: black;
|
||||
padding: 3px 4px;
|
||||
font-size: 15px;
|
||||
cursor: pointer;
|
||||
border-style: ridge;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.dropbtn:hover, .dropbtn:focus {
|
||||
color: blue;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.dropdown-content {
|
||||
display: none;
|
||||
position: absolute;
|
||||
background-color: #e0e0f0;
|
||||
min-width: 10px;
|
||||
box-shadow: 0px 8px 16px 0px rgba(0,0,0.1,0.1);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.dropdown-content a:hover {background-color: #ddd;}
|
||||
.show {display:inline-flex;}
|
||||
Reference in New Issue
Block a user