Making ATT work with Profilerv2

Change-Id: Ic9334aa80e40faaaf5c1a79ba37dbe52e8d31253
This commit is contained in:
gobhardw
2023-02-07 13:06:02 +05:30
committed by Ammar ELWazir
parent 6dda141e4b
commit 03c305dbd4
28 changed files with 3783 additions and 24 deletions
+56
View File
@@ -0,0 +1,56 @@
# ###############################################################################
# # 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.
# ###############################################################################
# Building att plugin library
file(GLOB ROCMTOOLS_UTIL_SRC_FILES ${PROJECT_SOURCE_DIR}/src/utils/helper.cpp)
file(GLOB FILE_SOURCES att.cpp)
add_library(att_plugin SHARED ${FILE_SOURCES} ${ROCMTOOLS_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})
target_compile_definitions(att_plugin
PRIVATE HIP_PROF_HIP_API_STRING=1 __HIP_PLATFORM_HCC__=1)
target_include_directories(att_plugin PRIVATE ${PROJECT_SOURCE_DIR}/inc ${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_TARGET} systemd hsa-runtime64::hsa-runtime64 stdc++fs)
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(t.db att/t.db 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/trace.json att/ui/trace.json COPYONLY)
install(DIRECTORY
${CMAKE_CURRENT_BINARY_DIR}/att
DESTINATION ${CMAKE_INSTALL_LIBEXECDIR}/rocprofiler
USE_SOURCE_PERMISSIONS
COMPONENT runtime)
+189
View File
@@ -0,0 +1,189 @@
/* 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 <experimental/filesystem>
#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 "rocprofiler.h"
#include "rocprofiler_plugin.h"
#include "../utils.h"
namespace {
class att_plugin_t {
public:
att_plugin_t() {}
std::mutex writing_lock;
bool is_valid_{true};
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_; }
void FlushATTRecord(const rocprofiler_record_att_tracer_t* att_tracer_record,
rocprofiler_session_id_t session_id, rocprofiler_buffer_id_t buffer_id) {
std::lock_guard<std::mutex> lock(writing_lock);
if (!att_tracer_record) {
printf("No att data buffer received\n");
return;
}
size_t name_length;
CHECK_ROCMTOOLS(rocprofiler_query_kernel_info_size(ROCPROFILER_KERNEL_NAME,
att_tracer_record->kernel_id, &name_length));
const char* kernel_name_c = static_cast<const char*>(malloc(name_length * sizeof(char)));
CHECK_ROCMTOOLS(rocprofiler_query_kernel_info(ROCPROFILER_KERNEL_NAME,
att_tracer_record->kernel_id, &kernel_name_c));
std::string name_demangled = rocmtools::truncate_name(rocmtools::cxx_demangle(kernel_name_c));
// Get the number of shader engine traces
int se_num = att_tracer_record->shader_engine_data_count;
// Find if this filename already exists. If so, increment vname.
int file_iteration = -1;
bool bIncrementVersion = true;
while(bIncrementVersion) {
file_iteration += 1;
std::string fss = name_demangled+"_v"+std::to_string(file_iteration);
bIncrementVersion = att_file_exists(fss + "_kernel.txt");
}
std::string fname = name_demangled+"_v"+std::to_string(file_iteration)+"_kernel.txt";
std::ofstream(fname.c_str()) << name_demangled << ": " << kernel_name_c << '\n';
// iterate over each shader engine att trace
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);
rocprofiler_record_se_att_data_t* se_att_trace = &att_tracer_record->shader_engine_data[i];
uint32_t size = se_att_trace->buffer_size;
const char* data_buffer_ptr = reinterpret_cast<char*>(se_att_trace->buffer_ptr);
// dump data in binary format
std::ostringstream oss;
oss << name_demangled << "_v" << file_iteration << "_se" << i << ".att";
std::ofstream out(oss.str().c_str(), std::ios::binary);
if (out.is_open()) {
out.write((char*)data_buffer_ptr, size);
out.close();
} else {
std::cerr << "\t" << __FUNCTION__ << " Failed to open file: " << oss.str().c_str() << '\n';
}
}
}
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 0;
switch (begin->kind) {
case ROCPROFILER_PROFILER_RECORD:
case ROCPROFILER_TRACER_RECORD:
case ROCPROFILER_PC_SAMPLING_RECORD:
case ROCPROFILER_SPM_RECORD:
printf("Invalid record Kind: %d", 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;
}
}
rocprofiler_next_record(begin, &begin, session_id, buffer_id);
}
return 0;
}
private:
};
att_plugin_t* att_plugin = nullptr;
} // namespace
ROCPROFILER_EXPORT int rocprofiler_plugin_initialize(uint32_t rocprofiler_major_version,
uint32_t rocprofiler_minor_version) {
if (rocprofiler_major_version != ROCPROFILER_VERSION_MAJOR ||
rocprofiler_minor_version < ROCPROFILER_VERSION_MINOR)
return -1;
if (att_plugin != nullptr) return -1;
att_plugin = new att_plugin_t();
if (att_plugin->IsValid()) return 0;
// The plugin failed to initialied, destroy it and return an error.
delete att_plugin;
att_plugin = nullptr;
return -1;
}
ROCPROFILER_EXPORT void rocprofiler_plugin_finalize() {
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) {
if (!att_plugin || !att_plugin->IsValid()) return -1;
return att_plugin->WriteBufferRecords(begin, end, session_id, buffer_id);
}
ROCPROFILER_EXPORT int rocprofiler_plugin_write_record(rocprofiler_record_tracer_t record,
rocprofiler_session_id_t session_id) {
if (!att_plugin || !att_plugin->IsValid()) return -1;
if (record.header.id.handle == 0) return 0;
return 0;
}
+523
View File
@@ -0,0 +1,523 @@
#!/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 struct import *
from ctypes import *
import ctypes
from copy import deepcopy
from trace_view import view_trace
import sys
import glob
import numpy as np
import matplotlib.pyplot as plt
import json
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),
('value', ctypes.c_int),
('to_line', ctypes.c_int),
('index', ctypes.c_int),
('line_num', ctypes.c_int)]
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 Wave(ctypes.Structure):
_fields_ = [
('simd', ctypes.c_uint64),
('wave_id', ctypes.c_uint64),
('begin_time', ctypes.c_uint64), # Begin and end cycle
('end_time', ctypes.c_uint64),
# total VMEM/FLAT/LDS/SMEM instructions issued
# total issued memory instructions
('num_mem_instrs', ctypes.c_uint64),
# total issued instructions (compute + memory)
('num_issued_instrs', ctypes.c_uint64),
('num_valu_instrs', ctypes.c_uint64),
('num_valu_stalls', ctypes.c_uint64),
# VMEM Pipeline: instrs and stalls
('num_vmem_instrs', ctypes.c_uint64),
('num_vmem_stalls', ctypes.c_uint64),
# FLAT instrs and stalls
('num_flat_instrs', ctypes.c_uint64),
('num_flat_stalls', ctypes.c_uint64),
# LDS instr and stalls
('num_lds_instrs', ctypes.c_uint64),
('num_lds_stalls', ctypes.c_uint64),
# SCA instrs stalls
('num_salu_instrs', ctypes.c_uint64),
('num_smem_instrs', ctypes.c_uint64),
('num_salu_stalls', ctypes.c_uint64),
('num_smem_stalls', ctypes.c_uint64),
# Branch
('num_branch_instrs', ctypes.c_uint64),
('num_branch_taken_instrs', ctypes.c_uint64),
('num_branch_stalls', ctypes.c_uint64),
('timeline_string', ctypes.c_char_p),
('instructions_string', ctypes.c_char_p)]
class ReturnInfo(ctypes.Structure):
_fields_ = [('num_waves', ctypes.c_uint64),
('wavedata', POINTER(Wave)),
('num_events', ctypes.c_uint64),
('perfevents', POINTER(PerfEvent))]
try: # For build dir
path_to_parser = os.path.abspath('/usr/lib/hsa-amd-aqlprofile/librocprofv2_att.so')
SO = CDLL(path_to_parser)
except: # For installed dir
path_to_parser = os.path.abspath('/usr/local/lib/hsa-amd-aqlprofile/librocprofv2_att.so')
SO = CDLL(path_to_parser)
SO.AnalyseBinary.restype = ReturnInfo
SO.AnalyseBinary.argtypes = [ctypes.c_char_p, ctypes.c_int, ctypes.c_bool]
SO.wrapped_parse_binary.argtypes = [ctypes.c_char_p, ctypes.c_char_p]
SO.wrapped_parse_binary.restype = ReturnAssemblyInfo
def parse_binary(filename, kernel=None):
if kernel is None or kernel == '':
kernel = ctypes.c_char_p(0)
print('Parsing all kernels')
else:
with open(glob.glob(kernel)[0], 'r') as file:
kernel = file.readlines()
print('Parsing kernel:', kernel[0].split(': ')[0])
kernel = kernel[0].split(': ')[1].split('.kd')[0]
kernel = str(kernel).encode('utf-8')
info = SO.wrapped_parse_binary(str(filename).encode('utf-8'), kernel)
code = []
for k in range(info.code_len):
code_entry = info.code[k]
# copy string memory from C++
line = deepcopy(code_entry.line.decode("utf-8"))
loc = deepcopy(code_entry.loc.decode("utf-8"))
# Transform empty entries back to python's None
to_line = int(code_entry.to_line) if (code_entry.to_line >= 0) else None
loc = loc if len(loc) > 0 else None
code.append((line, int(code_entry.value), to_line, loc,
int(code_entry.index), int(code_entry.line_num)))
jumps = {}
for k in range(info.jumps_len):
jumps[info.jumps[k].key] = info.jumps[k].value
return code, jumps
def getWaves(filename, target_cu, verbose):
info = SO.AnalyseBinary(filename.encode('utf-8'), target_cu, verbose)
waves = [info.wavedata[k] for k in range(info.num_waves)]
events = [deepcopy(info.perfevents[k]) for k in range(info.num_events)]
for wave in waves:
wave.timeline = deepcopy(wave.timeline_string.decode("utf-8"))
wave.instructions = deepcopy(wave.instructions_string.decode("utf-8"))
return waves, events
def persist(output_ui, trace_file, SIMD):
trace = Path(trace_file).name
simds, waves = [], []
begin_time, end_time, timeline, instructions = [], [], [], []
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:
simds.append(wave.simd)
waves.append(wave.wave_id)
begin_time.append(wave.begin_time)
end_time.append(wave.end_time)
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)
instructions.append(wave.instructions)
#df = pd.DataFrame({
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,
}#)
#[print(d) for c, d in df.iterrows()]; quit()
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 Copy_Files(output_ui):
curpath = os.path.dirname(os.path.abspath(__file__))
outpath = output_ui+'/ui/'
os.makedirs(outpath, exist_ok=True)
os.system('cp '+curpath+'/ui/* '+outpath)
def get_delta_time(events):
for begin in range(len(events)):
tg_cu = events[begin].cu
for e in range(begin+1,len(events)):
if events[e].cu == tg_cu:
return events[e].time-events[begin].time
return 1
def num_cus(EVENTS):
cus = 0
for events in EVENTS:
for e in events:
cus = max(cus, e.cu)
return cus+1
def draw_wave_metrics(selections, normalize):
global PIC_SAVE_FOLDER
global EVENTS
global EVENT_NAMES
#event_names = ['Busy CUs', 'Occupancy', 'Eligible waves', 'Waves waiting']
with open(PIC_SAVE_FOLDER+'counters.json', 'w') as f:
f.write(json.dumps({"counters": EVENT_NAMES}))
plt.figure(figsize=(15,3))
delta_time = int(0.5+np.mean([get_delta_time(events) for events in EVENTS]))
maxtime = np.max([np.max([e.time for e in events]) for events in EVENTS])+1
event_timeline = np.zeros((16, maxtime), dtype=np.int32)
print('Delta:', delta_time)
print('Max_cycles:', maxtime)
kernsize = 2*(delta_time//8)+1
trim = max(maxtime//5000,1)
cycles = 4*np.arange(maxtime)[::trim]
kernel = np.asarray([np.exp(-abs(k/kernsize)**2) for k in range(-kernsize*3,kernsize*3+1)])
kernel /= np.sum(kernel)*len(EVENTS)*delta_time#*5.12 # SEslots/100%
for events in EVENTS:
for e in range(len(events)-1):
bk = events[e].bank*4
start = events[e].time
end = start+delta_time
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_time] += \
np.asarray(events[-1].toTuple()[1:5])[:, None]
event_timeline = [np.convolve(e, kernel)[3*kernsize:-3*kernsize] for e in event_timeline]
if normalize:
event_timeline = [100*e/max(e.max(), 1E-5) for e in event_timeline]
#event_timeline[0] = np.clip(event_timeline[0]*8.5, 0, 100) #CC rate
colors = ['blue', 'green', 'gray', 'red', 'orange', 'cyan', 'black', 'darkviolet',
'yellow', 'darkred', 'pink', 'lime', 'gold', 'tan', 'aqua', 'olive']
[plt.plot(cycles, e[::trim], '-', 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.subplots_adjust(left=0.05, right=1, top=1, bottom=0.07)
plt.savefig(PIC_SAVE_FOLDER+'timeline.png', dpi=150)
#plt.show()
def draw_wave_states(selections, normalize):
global TIMELINES
global PIC_SAVE_FOLDER
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,3))
maxtime = max([np.max((TIMELINES[k]!=0)*np.arange(0,TIMELINES[k].size)) for k in plot_indices])
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)
kernsize = maxtime//120+3
trim = max(maxtime//5000,1)
cycles = np.arange(timelines[0].size)[::trim]
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][::trim] for time in timelines]
with open(PIC_SAVE_FOLDER+'counters.json', 'w') as f:
f.write(json.dumps({"counters": STATES}))
[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.ylim(-1)
plt.xlim(-maxtime//200, maxtime+maxtime//200)
plt.subplots_adjust(left=0.05, right=1, top=1, bottom=0.07)
plt.savefig(PIC_SAVE_FOLDER+'timeline.png', dpi=150)
def GeneratePIC(selections=[True for k in range(4)], normalize=True):
if len(EVENTS) > 0 and np.sum([len(e) for e in EVENTS]) > 32:
draw_wave_metrics(selections, normalize)
else:
draw_wave_states(selections, normalize)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("assembly_code", help="Path of the assembly code")
parser.add_argument("--trace_file", help="Filter for trace files", default=None, type=str)
parser.add_argument("-o", "--output_ui", help="Output Folder", default='/dev/shm/attplugin/')
parser.add_argument("-k", "--att_kernel", help="Kernel file", type=str, default='*_kernel.txt')
parser.add_argument("-w", "--wave_id", help="wave id")
parser.add_argument("-p", "--ports", help="Server and websocket ports, default: 8000,18000")
parser.add_argument("--target_cu", help="Collected target CU id{0-15}", type=int, default=None)
parser.add_argument("-v", "--verbose", action="store_true")
parser.add_argument("-g", "--genasm",
help="Generate post-processed asm file at this path", type=str, default="")
args = parser.parse_args()
global EVENT_NAMES
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)]
elif args.target_cu is None and 'att: TARGET_CU' in line:
args.target_cu = int(clean(line))
print('Target CU set to:', args.target_cu)
for line in lines:
if 'PERFCOUNTER=' in line:
EVENT_NAMES += [clean(line).split('SQ_')[1].lower()]
if args.target_cu is None:
args.target_cu = 1
# Assembly parsing
path = Path(args.assembly_code)
if not path.is_file():
print("Invalid assembly_code('{0}')!".format(args.assembly_code))
sys.exit(1)
att_kernel = glob.glob(args.att_kernel)
if len(att_kernel) == 0:
print('Could not find att output kernel:', args.att_kernel)
exit(1)
elif len(att_kernel) > 1:
print('Found multiple kernel matching given filters:')
for n, k in enumerate(att_kernel):
print('\t', n, '->', k)
bValid = False
while bValid == False:
try:
args.att_kernel = att_kernel[int(input("Please select number: "))]
bValid = True
except KeyboardInterrupt:
exit(0)
except:
print('Invalid option.')
else:
args.att_kernel = att_kernel[0]
print('Att kernel:', args.att_kernel)
code, jumps = parse_binary(args.assembly_code, args.att_kernel)
# Trace Parsing
if args.trace_file is None:
filenames = glob.glob(args.att_kernel.split('_kernel.txt')[0]+'*.att')
assert(len(filenames) > 0)
else:
filenames = glob.glob(args.trace_file)
print('Trace filenames:', filenames)
Copy_Files(args.output_ui)
DBFILES = []
global TIMELINES
global EVENTS
TIMELINES = [np.zeros(int(1E4),dtype=np.int32) for k in range(5)]
EVENTS = []
for name in filenames:
SIMD, perfevents = getWaves(name, args.target_cu, args.verbose)
EVENTS.append(perfevents)
DBFILES.append( persist(args.output_ui, name, SIMD) )
for wave in SIMD:
time_acc = 0
tuples1 = wave.timeline.split('(')
tuples2 = [t.split(')')[0].split(',') for t in tuples1 if t != '']
tuples3 = [(int(t[0]),int(t[1])) for t in tuples2]
for state in tuples3:
if state[1] > 1E7:
print('Warning: Time limit reached for ',state[0], state[1])
break
if time_acc+state[1] > TIMELINES[state[0]].size:
TIMELINES[state[0]] = np.hstack([
TIMELINES[state[0]],
np.zeros_like(TIMELINES[state[0]])
])
TIMELINES[state[0]][time_acc:time_acc+state[1]] += 1
time_acc += state[1]
if args.genasm and len(args.genasm) > 0:
flight_count = view_trace(args, 0, code, jumps, DBFILES, filenames, True, None)
with open(args.assembly_code, 'r') as file:
lines = file.readlines()
assembly_code = {l+1.0: lines[l][:-1] for l in range(len(lines))}
assembly_code = insert_waitcnt(flight_count, assembly_code)
with open(args.genasm, 'w') as file:
keys = sorted(assembly_code.keys())
for k in keys:
file.write(assembly_code[k]+'\n')
else:
global PIC_SAVE_FOLDER
PIC_SAVE_FOLDER = args.output_ui+"/ui/"
view_trace(args, 0, code, jumps, DBFILES, filenames, False, GeneratePIC)
+465
View File
@@ -0,0 +1,465 @@
#!/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 struct import *
from collections import defaultdict
import json
import time
import webbrowser
import http.server
import socketserver
import socket
import asyncio
import websockets
from multiprocessing import *
from copy import deepcopy
PORT, WebSocketPort = 8000, 18000
SP = '\u00A0'
RS_TRACE_DEBUG = "RS_TRACE_DEBUG" in os.environ
if RS_TRACE_DEBUG:
LOG = open('./att_viewer.log', 'w')
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()
def debug_log(msg, last=False):
if RS_TRACE_DEBUG:
LOG.write(msg)
if last:
LOG.close()
def try_match_swapped(insts, code, i, line):
return insts[i+1][1] == code[line][1] and insts[i][1] == code[line+1][1]
def stitch(insts, code, jumps):
result, i, line, loopCount, N = [], 0, 0, defaultdict(int), len(insts)
SMEM_INST = []
VMEM_INST = []
FLAT_INST = []
NUM_SMEM = 0
NUM_VMEM = 0
NUM_FLAT = 0
mem_unroll = []
flight_count = []
ordering = 0 # 0 for in order, 1 for ongoing flats and 2 for SMEM
while i < N:
inst = insts[i]
if line >= len(code):
break
as_line = code[line]
if inst[1] == as_line[1]:
if line in jumps:
loopCount[line-1] += 1 # label is the previous line
matched, next = True, line + 1
num_inflight = NUM_FLAT + NUM_SMEM + NUM_VMEM
if inst[1] == 1 or inst[1] == 5: # SMEM, LDS
ordering = 2 if inst[1] == 1 else ordering
SMEM_INST.append([line, num_inflight])
NUM_SMEM += 1
elif inst[1] == 3 or (inst[1] == 4 and 'global_' in as_line[0]): # VMEM R/W
VMEM_INST.append([line, num_inflight])
NUM_VMEM += 1
elif inst[1] == 4: # FLAT
ordering = max(ordering, 1)
FLAT_INST.append([line, num_inflight])
NUM_FLAT += 1
elif inst[1] == 9 and 'waitcnt' in as_line[0]:
if 'lgkmcnt' in as_line[0]:
wait_N = int(as_line[0].split('lgkmcnt(')[1].split(')')[0])
flight_count.append([as_line[-1], num_inflight, wait_N])
if wait_N == 0:
ordering = 0
if ordering == 0:
offset = len(SMEM_INST)-wait_N
mem_unroll.append( [line, SMEM_INST[:offset]+FLAT_INST] )
SMEM_INST = SMEM_INST[offset:]
FLAT_INST = []
NUM_FLAT = 0
NUM_SMEM = 0
else:
NUM_SMEM = min(max(wait_N-NUM_FLAT, 0), NUM_SMEM)
NUM_FLAT = min(max(wait_N-NUM_SMEM, 0), NUM_FLAT)
if 'vmcnt' in as_line[0]:
wait_N = int(as_line[0].split('vmcnt(')[1].split(')')[0])
flight_count.append([as_line[-1], num_inflight, wait_N])
if wait_N == 0 and ordering != 2:
ordering = 0
if ordering == 0:
offset = len(VMEM_INST)-wait_N
mem_unroll.append( [line, VMEM_INST[:offset]+FLAT_INST] )
VMEM_INST = VMEM_INST[offset:]
FLAT_INST = []
NUM_FLAT = 0
NUM_VMEM = 0
else:
NUM_VMEM = min(max(wait_N-NUM_FLAT, 0), NUM_VMEM)
NUM_FLAT = min(max(wait_N-NUM_VMEM, 0), NUM_FLAT)
elif inst[1] == 7 and as_line[1] == 10: # jump
matched, next = True, as_line[2]
elif inst[1] == 8 and as_line[1] == 10: # next
matched, next = True, line + 1
else:
# instructions with almost same timestamp swapped
# if i+1 < N and line+1 < len(code) and inst[0] == insts[i+1][0]:
matched = False
next = line + 1
if i+1 < N and line+1 < len(code):
if try_match_swapped(insts, code, i, line):
#print('Swap:', code[line])
#print('For:', code[line+1])
#result += (*(insts[i+1]), line),
#result += (*inst, line+1),
#i, next = i+2, line+2
temp = insts[i]
insts[i] = insts[i+1]
insts[i+1] = temp
next = line
#else:
# print('Could not parse tokens:', insts[i], as_line)
if matched:
new_res = inst + (line,)
result.append(new_res)
i += 1
line = next
N = max(N, 1)
if len(result) != N:
print('Warning - Stitching rate: {'+str(len(result) * 100 / N)+'% matched')
return result, loopCount, mem_unroll, flight_count
def extract_tuple(content, num):
vals = content.split(',')
assert (len(vals) == num)
last_val = vals[-1][:-1] if vals[-1].endswith(')') else vals[-1]
vals = [vals[0][1:]] + vals[1:-1] + [last_val]
return tuple(int(val) for val in vals)
def get_top_n(stitched):
TOP_N = 10
by_line_num = defaultdict(lambda: [0, 0, 0])
for (_, _, s2i, run_time, line_num) in stitched:
entry = by_line_num[line_num]
entry[0] += 1
entry[1] += s2i
entry[2] += run_time
top_n = sorted(
[(line_num, v[0], v[1], v[2])
for (line_num, v) in by_line_num.items()],
key=lambda x: x[2] + x[3],
reverse=True)
return top_n[:TOP_N]
def rjust_html(s, n):
s = str(s)
return SP * (n-len(s)) + s if len(s) < n else s
def rjust_html_format(msg, n1, inst, n2, n3, stall):
return str(rjust_html(msg,n1)) + str(rjust_html(inst,n2)) + str(SP*n3) + str(stall)
def wave_info(df, id):
issued_ins, mem_ins = df['issued_ins'][id], df['mem_ins'][id]
valu_ins, valu_stalls = df['valu_ins'][id], df['valu_stalls'][id]
salu_ins, salu_stalls = df['salu_ins'][id], df['salu_stalls'][id]
vmem_ins, vmem_stalls = df['vmem_ins'][id], df['vmem_stalls'][id]
smem_ins, smem_stalls = df['smem_ins'][id], df['smem_stalls'][id]
flat_ins, flat_stalls = df['flat_ins'][id], df['flat_stalls'][id]
lds_ins, lds_stalls = df['lds_ins'][id], df['lds_stalls'][id]
br_ins, br_stalls = df['br_ins'][id], df['br_stalls'][id]
return 'Issued:' + str(rjust_html(issued_ins,8)) + str(SP*2) + 'Mem:' + str(mem_ins) \
+ "-" * 26 + rjust_html_format("VALU:",6,valu_ins,8,4,valu_stalls) \
+ rjust_html_format("SALU:",6,salu_ins,8,4,salu_stalls) \
+ rjust_html_format("VMEM:",6,vmem_ins,8,4,vmem_stalls) \
+ rjust_html_format("SMEM:",6,smem_ins,8,4,smem_stalls) \
+ rjust_html_format("FLAT:",6,flat_ins,8,4,flat_stalls) \
+ rjust_html_format("LDS:",6,lds_ins,8,4,lds_stalls) \
+ rjust_html_format("BR:",6,br_ins,8,4,br_stalls)
def extract_waves(waves):
result, slot2seq = [], {}
for id in waves['id']:
row = {key: waves[key][id] for key in waves.keys()}
insts, timeline = [], []
for x in row['instructions'].split('),'):
if len(x) > 0:
insts.append(extract_tuple(x, 4))
for x in row['timeline'].split('),'):
if len(x) > 0:
timeline.append(extract_tuple(x, 2))
# aggregate per wave slot
if (row['simd'], row['wave_slot']) in slot2seq:
slot = result[slot2seq[(row['simd'], row['wave_slot'])]]
last_end_time = slot[2][-1][-1]
slot[2] += (row['id'], row['begin_time'], row['end_time']),
slot[3] += insts
# filler between waves
slot[4] += (0, row['begin_time'] - last_end_time),
slot[4] += timeline
else:
slot2seq[row['simd'], row['wave_slot']] = len(result)
result.append([row['simd'], row['wave_slot'],
[(row['id'], row['begin_time'], row['end_time'])],
insts,
timeline])
return result
def extract_data(df, output_ui, se_number, code, jumps):
if len(df['id']) == 0 or len(df['instructions']) == 0 or len(df['timeline']) == 0:
return None
cu_waves = extract_waves(df)
all_filenames = []
flight_count = []
for wave_id in df['id']:
insts, timeline = [], []
if len(df['instructions'][wave_id]) == 0 or len(df['timeline'][wave_id]) == 0:
continue
for x in df['instructions'][wave_id].split('),'):
insts.append(extract_tuple(x, 4))
for x in df['timeline'][wave_id].split('),'):
timeline.append(extract_tuple(x, 2))
stitched, loopCount, mem_unroll, count = stitch(insts, code, jumps)
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,
"code": code,
"waitcnt": mem_unroll
}
data_obj = {
"name": 'SE'.format(se_number),
"kernel": code[0][0],
"duration": sum(dur for (_, dur) in timeline),
"wave": wave_entry,
"simd_waves": [],
"cu_waves": cu_waves,
"loop_count": loopCount,
"top_n": get_top_n(stitched),
"websocket_port": WebSocketPort,
"generation_time": time.ctime()
}
if len(data_obj["cu_waves"]) == 0:
continue
OUT = output_ui+'/ui/se'+str(se_number)+'_sm'+str(df['simd'][wave_id])+\
'_wv'+str(df['wave_slot'][wave_id])+'.json'
with open(OUT, 'w') as f:
f.write(json.dumps(data_obj))
all_filenames.append(OUT.split('/')[-1])
return flight_count, all_filenames
def open_browser():
time.sleep(0.1)
webbrowser.open_new_tab('http://{0}:{1}'.format(IPAddr, PORT))
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):
global PICTURE_CALLBACK
if 'timeline.png?' in self.path:
selections = [int(s)!=0 for s in self.path.split('timeline.png?')[1]]
print('Sel:', selections)
PICTURE_CALLBACK(selections[1:], selections[0])
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():
global RS_HOME
Handler = NoCacheHTTPRequestHandler
os.chdir(RS_HOME+'/ui')
try:
with RocTCPServer((IPAddr, PORT), Handler) as httpd:
httpd.serve_forever()
except KeyboardInterrupt:
pass
def fix_space(line):
line = line.replace(' ', SP)
line = line.replace('\t', SP*4)
return line
def WebSocketserver(websocket, path):
data = websocket.recv()
print(354, data)
cpp, ln, _ = data.split(':')
ln = int(ln)
HL, EMP = 'highlight', ''
content = None
print("loading...")
try:
f = open(cpp, 'r', errors='replace')
content = ''.join('<li class=\"line_'+str(i)+
str(HL if i==ln else EMP)+'">'+str(i).ljust(5)+fix_space(l)+'</li>'
for i, l in enumerate(f.readlines(), 1))
except FileNotFoundError:
content = cpp + ' not found!'
websocket.send(content)
def run_websocket():
start_server = websockets.serve(WebSocketserver, IPAddr, WebSocketPort)
try:
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()
except KeyboardInterrupt:
pass
def assign_ports(ports):
ps = [int(port) for port in ports.split(',')]
if ps[0] <= 5000 or ps[1] <= 5000:
print('Need to have port values > 5000')
sys.exit(1)
elif ps[0] == ps[1]:
print('Can not use the same port for both web server and websocket server: '+ps[0])
sys.exit(1)
global IPAddr, PORT, WebSocketPort
PORT, WebSocketPort = ps[0], ps[1]
def view_trace(args, wait, code, jumps, dbnames, att_filenames, bReturnLoc, pic_callback):
global PICTURE_CALLBACK
PICTURE_CALLBACK = pic_callback
pic_thread = Process(target=pic_callback)
pic_thread.start()
assert(len(dbnames) > 0)
global RS_HOME
output_ui = args.output_ui
RS_HOME = output_ui
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 = {}
for se_number, dbname in zip(se_numbers, dbnames):
if len(dbname['id']) == 0:
continue
count, wv_filenames = extract_data(dbname, output_ui, se_number, code, jumps)
if count is not None:
flight_count.append(count)
simd_wave_filenames[se_number] = wv_filenames
if bReturnLoc:
return flight_count
for key in simd_wave_filenames.keys():
wv_array = [[
int(s.split('_sm')[1].split('_wv')[0]),
int(s.split('_wv')[1][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]
except:
try:
wv_dict[wv[0]] = {wv[1]: wv[2]}
except:
exit(-1)
simd_wave_filenames[key] = wv_dict
with open(output_ui+'/ui/filenames.json', 'w') as f:
f.write(json.dumps({"filenames": simd_wave_filenames}))
if args.ports:
assign_ports(args.ports)
print('serving at ports: {0},{1}'.format(PORT, WebSocketPort))
if wait == 0:
try:
PROCS = [Process(target=run_server),
Process(target=open_browser),
Process(target=run_websocket)]
if pic_thread is not None:
pic_thread.join()
for p in PROCS:
p.start()
for p in PROCS:
p.join()
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

+106
View File
@@ -0,0 +1,106 @@
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;
}