Add 'projects/rocprofiler/' from commit '16ae2e90c6157e98e846d2bccbaaf533ca5e662a'

git-subtree-dir: projects/rocprofiler
git-subtree-mainline: 2a52e3974d
git-subtree-split: 16ae2e90c6
This commit is contained in:
systems-assistant[bot]
2025-07-22 22:52:43 +00:00
386 changed files with 216890 additions and 0 deletions
@@ -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)
+309
View File
@@ -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;
}
+641
View File
@@ -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
View File
@@ -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;
};
+361
View File
@@ -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{};
};
+141
View File
@@ -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)
+574
View File
@@ -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
View File
@@ -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
+132
View File
@@ -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;}