diff --git a/source/lib/python/rocpd/libpyrocpd.cpp b/source/lib/python/rocpd/libpyrocpd.cpp index 0f62325724..8bdc2b8cdc 100644 --- a/source/lib/python/rocpd/libpyrocpd.cpp +++ b/source/lib/python/rocpd/libpyrocpd.cpp @@ -676,7 +676,7 @@ PYBIND11_MODULE(libpyrocpd, pyrocpd) auto sqlgen_otf2 = common::simple_timer{ fmt::format("OTF2 generation from {} SQL database(s)", data.size())}; - uint16_t _process_counter = 0; + uint16_t _process_counter = 1; for(auto obj : {data.connection}) { auto* conn = rocpd::interop::get_connection(std::move(obj)); diff --git a/source/lib/python/rocpd/source/otf2.cpp b/source/lib/python/rocpd/source/otf2.cpp index ed84dfb82e..17293a742d 100644 --- a/source/lib/python/rocpd/source/otf2.cpp +++ b/source/lib/python/rocpd/source/otf2.cpp @@ -139,6 +139,9 @@ archive_t* archive = nullptr; auto flush_callbacks = OTF2_FlushCallbacks{pre_flush, post_flush}; OTF2_GlobalDefWriter* global_def_writer = nullptr; // shared between data bases (processes) +hash_map_t hash_data = {}; // shared definition dictionary between data bases and nodes +auto existing_hash = std::unordered_set{}; + enum rocprofiler_location_type_t { ROCPROFILER_AGENT_NO_TYPE = 0, @@ -299,6 +302,16 @@ add_event(std::string_view name, ROCP_FATAL << "otf2::add_event phase is not enter or exit"; } +void +add_write_string(size_t _hash, std::string_view _name_strv) +{ + if(_hash > 0 && existing_hash.count(_hash) == 0) + { + OTF2_CHECK(OTF2_GlobalDefWriter_WriteString(global_def_writer, _hash, _name_strv.data())); + existing_hash.emplace(_hash); + } +}; + void setup(const rocprofiler::tool::output_config& cfg, uint64_t min_start, uint64_t max_fini) { @@ -345,28 +358,14 @@ setup(const rocprofiler::tool::output_config& cfg, uint64_t min_start, uint64_t OTF2_CHECK(OTF2_GlobalDefWriter_WriteString(global_def_writer, 0, "")); - auto add_write_string = [](size_t _hash, std::string_view _name_strv) { - static auto _existing = std::unordered_set{}; - if(_hash > 0 && _existing.count(_hash) == 0) - { - OTF2_CHECK( - OTF2_GlobalDefWriter_WriteString(global_def_writer, _hash, _name_strv.data())); - _existing.emplace(_hash); - } - }; - - auto add_write_string_val = [&add_write_string](std::string_view _name_v) { - auto _hash_v = get_hash_id(_name_v); - add_write_string(_hash_v, _name_v); - return _hash_v; - }; - //(must be shared between processes) - auto _attr_name = std::string_view{"category"}; - auto _attr_desc = std::string_view{"tracing category"}; + auto _attr_name = std::string_view{"category"}; + auto _attr_name_hash = get_hash_id(_attr_name); + add_write_string(_attr_name_hash, _attr_name); - auto _attr_name_hash = add_write_string_val(_attr_name); - auto _attr_desc_hash = add_write_string_val(_attr_desc); + auto _attr_desc = std::string_view{"tracing category"}; + auto _attr_desc_hash = get_hash_id(_attr_desc); + add_write_string(_attr_desc_hash, _attr_desc); OTF2_CHECK(OTF2_GlobalDefWriter_WriteAttribute( global_def_writer, 0, _attr_name_hash, _attr_desc_hash, OTF2_TYPE_STRING)); @@ -428,17 +427,16 @@ write_otf2(const OTF2Session& otf2_session, const tool::generator& memory_copy_gen, const tool::generator& memory_allocation_gen) { - const uint64_t _no_agent_handle = 0; - // std::numeric_limits::max() - 1; - const auto& ocfg = otf2_session.config; - - auto _app_ts = rocprofiler::tool::timestamps_t{process.start, process.fini}; + const auto& ocfg = otf2_session.config; + auto _app_ts = rocprofiler::tool::timestamps_t{process.start, process.fini}; auto thread_event_info = std::map{}; auto agent_memcpy_info = std::map>{}; // tid -> agent_handle ->evt auto agent_memalloc_info = std::map>{}; // // tid -> agent_handle ->evt + auto mem_dealloc_info = std::map{}; // // tid -> evt + auto mem_unknown_info = std::map{}; // // tid -> evt auto agent_dispatch_info = std::map>>{}; // tid -> agent_handle @@ -469,8 +467,6 @@ write_otf2(const OTF2Session& otf2_session, thread_event_info.emplace(itr.tid, _evt_info); } - auto _hash_data = hash_map_t{}; - struct evt_data { rocprofiler_callback_phase_t phase = ROCPROFILER_CALLBACK_PHASE_NONE; @@ -505,8 +501,8 @@ write_otf2(const OTF2Session& otf2_session, for(const auto& itr : api_gen.get(ditr)) { std::string _name = itr.name; - _hash_data.emplace(get_hash_id(_name), - region_info{_name, OTF2_REGION_ROLE_FUNCTION, OTF2_PARADIGM_HIP}); + hash_data.emplace(get_hash_id(_name), + region_info{_name, OTF2_REGION_ROLE_FUNCTION, OTF2_PARADIGM_HIP}); auto& _evt_info = thread_event_info.at(itr.tid); _evt_info.event_count += 1; @@ -534,7 +530,7 @@ write_otf2(const OTF2Session& otf2_session, for(const auto& itr : memory_copy_gen.get(ditr)) { std::string _name = itr.name; - _hash_data.emplace( + hash_data.emplace( get_hash_id(_name), region_info{_name, OTF2_REGION_ROLE_DATA_TRANSFER, OTF2_PARADIGM_HIP}); @@ -583,7 +579,7 @@ write_otf2(const OTF2Session& otf2_session, if(itr.type == "ALLOC") { - _hash_data.emplace( + hash_data.emplace( get_hash_id(_alloc_operation), region_info{_alloc_operation, OTF2_REGION_ROLE_ALLOCATE, OTF2_PARADIGM_HIP}); @@ -615,18 +611,15 @@ write_otf2(const OTF2Session& otf2_session, } else if(itr.type == "FREE") // { - _hash_data.emplace( + hash_data.emplace( get_hash_id(_alloc_operation), region_info{_alloc_operation, OTF2_REGION_ROLE_DEALLOCATE, OTF2_PARADIGM_HIP}); + auto _evt_info = event_info{ + location_base{process.pid, itr.tid, ROCPROFILER_AGENT_MEMORY_DEALLOC_TYPE}}; - auto _evt_info = event_info{location_base{ - process.pid, itr.tid, _no_agent_handle, ROCPROFILER_AGENT_MEMORY_DEALLOC_TYPE}}; _evt_info.name = fmt::format("Thread {}, Memory Deallocate (Free)", itr.tid); - - agent_memalloc_info[itr.tid].emplace(_no_agent_handle, _evt_info); - _evt_info.event_count += 1; - + mem_dealloc_info.emplace(itr.tid, _evt_info); _data.emplace_back(evt_data{ROCPROFILER_CALLBACK_PHASE_ENTER, _alloc_operation, _evt_info.get_location(), @@ -643,7 +636,7 @@ write_otf2(const OTF2Session& otf2_session, auto _evt_info = event_info{location_base{process.pid, itr.tid}}; _evt_info.name = fmt::format("Thread {}, Memory Operation UNK", itr.tid); _evt_info.event_count += 1; - agent_memalloc_info[itr.tid].emplace(_no_agent_handle, _evt_info); + mem_unknown_info.emplace(itr.tid, _evt_info); _data.emplace_back(evt_data{ROCPROFILER_CALLBACK_PHASE_ENTER, _alloc_operation, _evt_info.get_location(), @@ -663,8 +656,8 @@ write_otf2(const OTF2Session& otf2_session, { auto _name = fmt::format( "{}", (ocfg.kernel_rename && !itr.region.empty()) ? itr.region : itr.name); - _hash_data.emplace(get_hash_id(_name), - region_info{_name, OTF2_REGION_ROLE_FUNCTION, OTF2_PARADIGM_HIP}); + hash_data.emplace(get_hash_id(_name), + region_info{_name, OTF2_REGION_ROLE_FUNCTION, OTF2_PARADIGM_HIP}); const auto* _perfetto_name = rocprofiler::sdk::perfetto_category< rocprofiler::sdk::category::kernel_dispatch>::name; @@ -738,16 +731,13 @@ write_otf2(const OTF2Session& otf2_session, } OTF2_CHECK(OTF2_Archive_CloseDefFiles(archive)); - for(const auto& itr : _hash_data) + for(const auto& itr : hash_data) { - if(itr.first != 0) + if(itr.first != 0 && existing_hash.count(itr.first) == 0) + { OTF2_CHECK(OTF2_GlobalDefWriter_WriteString( global_def_writer, itr.first, itr.second.name.c_str())); - } - for(const auto& itr : _hash_data) - { - if(itr.first != 0) OTF2_CHECK(OTF2_GlobalDefWriter_WriteRegion(global_def_writer, itr.first, itr.first, @@ -759,16 +749,11 @@ write_otf2(const OTF2Session& otf2_session, 0, 0, 0)); - } - auto add_write_string = [](size_t _hash, std::string_view _name) { - static auto _existing = std::unordered_set{}; - if(_hash > 0 && _existing.count(_hash) == 0) - { - OTF2_CHECK(OTF2_GlobalDefWriter_WriteString(global_def_writer, _hash, _name.data())); - _existing.emplace(_hash); + // Add to the list of processed definitions + existing_hash.emplace(itr.first); } - }; + } for(const auto& itr : _attr_str) add_write_string(itr.first, itr.second); @@ -873,11 +858,40 @@ write_otf2(const OTF2Session& otf2_session, _hash, OTF2_LOCATION_TYPE_ACCELERATOR_STREAM, 2 * evt.event_count, // # events - agent_handle // location group - )); + agent_handle) // location group + ); } } + // Mem-free events + for(auto& [tid, evt] : mem_dealloc_info) + { + auto _hash = get_hash_id(evt.name); + + add_write_string(_hash, evt.name); + OTF2_CHECK(OTF2_GlobalDefWriter_WriteLocation(global_def_writer, + evt.id(), // id + _hash, + OTF2_LOCATION_TYPE_UNKNOWN, + 2 * evt.event_count, + tree_node_id // location group + )); + } + + // Mem-unknown events + for(auto& [tid, evt] : mem_unknown_info) + { + auto _hash = get_hash_id(evt.name); + + add_write_string(_hash, evt.name); + OTF2_CHECK(OTF2_GlobalDefWriter_WriteLocation(global_def_writer, + evt.id(), // id + _hash, + OTF2_LOCATION_TYPE_UNKNOWN, + 2 * evt.event_count, + tree_node_id)); + } + // Dispatch Events for(auto& [tid, itr] : agent_dispatch_info) { diff --git a/tests/pytest-packages/pytest_utils/otf2_reader.py b/tests/pytest-packages/pytest_utils/otf2_reader.py index 9fdef6175d..40b658670f 100644 --- a/tests/pytest-packages/pytest_utils/otf2_reader.py +++ b/tests/pytest-packages/pytest_utils/otf2_reader.py @@ -72,11 +72,13 @@ def __str__(self): class OTF2Reader: - """Read in perfetto protobuf output""" + """Read in otf2 output""" def __init__(self, filename): self.filename = filename if isinstance(filename, (list, tuple)) else [filename] + # returns the the map *reader -> [data_frame], where *reader is created per input otf2 file, + # and each data_frame in [data_frame] corresponds to a rocpd data base (indexed by tree_node) which contributed to the otf2 file def read(self): def _read_trace(trace_name): trace = otf2.reader.Reader(trace_name) @@ -150,20 +152,21 @@ def _read_trace(trace_name): f"Modified length ({_mlen}) != Expected length({_elen}) for {event} at {location}" ) - data = { - "system_tree_node": [], - "location_group": [], - "location": [], - "region": [], - "attributes": [], - "depth": [], - "name": [], - "category": [], - "start_ts": [], - "end_ts": [], - } - + process_dfs = [] for tree, lgitr in call_stack.items(): + data = { + "system_tree_node": [], + "location_group": [], + "location": [], + "region": [], + "attributes": [], + "depth": [], + "name": [], + "category": [], + "start_ts": [], + "end_ts": [], + } + for group, gitr in lgitr.items(): for loc, ritr in gitr.items(): for region in ritr: @@ -177,21 +180,20 @@ def _read_trace(trace_name): data["name"] += [region.name] data["start_ts"] += [region.enter_nsec] data["end_ts"] += [region.leave_nsec] + process_dfs += [pd.DataFrame.from_dict(data)] - return (trace, pd.DataFrame.from_dict(data)) + return (trace, process_dfs) - readers = [] - df = pd.DataFrame() + ret_val = {} for itr in self.filename: - _reader, _df = _read_trace(itr) - readers += [_reader] - df = pd.concat([df, _df]) + _reader, _process_dfs = _read_trace(itr) + ret_val[_reader] = _process_dfs - return (df, readers) + return ret_val def read_trace(filename): - data = OTF2Reader(filename).read()[0] + data = list(OTF2Reader(filename).read().values())[0][0] print(f"\nDATA:\n{data}") diff --git a/tests/pytest-packages/tests/rocprofv3.py b/tests/pytest-packages/tests/rocprofv3.py index 48f018eb0d..493c987aa1 100644 --- a/tests/pytest-packages/tests/rocprofv3.py +++ b/tests/pytest-packages/tests/rocprofv3.py @@ -21,6 +21,7 @@ # SOFTWARE. from __future__ import absolute_import +import re def test_perfetto_data( @@ -98,7 +99,9 @@ def test_perfetto_data( def test_otf2_data( - otf2_data, json_data, categories=("hip", "hsa", "marker", "kernel", "memory_copy") + otf2_data, + json_data, + categories=("hip", "hsa", "kernel", "memory_allocation", "memory_copy"), ): def get_operation_name(kind_id, op_id): return json_data["rocprofiler-sdk-tool"]["strings"]["buffer_records"][kind_id][ @@ -146,6 +149,57 @@ def roctx_mark_filter(val): _json_data = [itr for itr in _json_data if roctx_mark_filter(itr) is not None] + if json_category == "memory_allocation": + _json_data_alloc = [itr for itr in _json_data if itr["operation"] == 1] + _json_data_free = [itr for itr in _json_data if itr["operation"] == 3] + _otf2_data_alloc = _otf2_data.loc[ + _otf2_data["name"] == "MEMORY_ALLOCATION_ALLOCATE" + ] + _otf2_data_free = _otf2_data.loc[ + _otf2_data["name"] == "MEMORY_ALLOCATION_FREE" + ] + assert len(_otf2_data_alloc) == len( + _json_data_alloc + ), f"memory_allocation allocate otf2 ({len(_otf2_data_alloc)}):\n\t{_otf2_data_alloc} \n json ({len(_json_data_alloc)}):\n\t{_json_data_alloc}" + assert len(_otf2_data_free) == len( + _json_data_free + ), f"memory_allocation free otf2 ({len(_otf2_data_free)}):\n\t{_otf2_data_free} \n json ({len(_json_data_free)}):\n\t{_json_data_free}" + + # problematic timing of free, not seen on visualisation + # select the first thread where free is reported, order by start time json and otf2 2 data for this thread and compare time stamps one by one + # testing max two threads + max_thr = 2 + for ind_thr in range(max_thr): + if len(_otf2_data_free) > ind_thr: + loc_name = _otf2_data_free.iloc[ind_thr]["location"].name + thread_id = int(re.match(r"Thread (\d+)", loc_name).group(1)) + # hope this is ordered by ts + _json_data_free_inthread = [ + itr for itr in _json_data_free if itr["thread_id"] == thread_id + ] + _otf2_data_free_inthread = [] + i = 0 + for i in range(len(_otf2_data_free)): + _loc_name = _otf2_data_free.iloc[i]["location"].name + _thread_id = int(re.match(r"Thread (\d+)", _loc_name).group(1)) + if _thread_id == thread_id: + _otf2_data_free_inthread.append(_otf2_data_free.iloc[i]) + assert len(_otf2_data_free_inthread) == len( + _json_data_free_inthread + ), f"thread {thread_id}, memory_allocation free otf2 ({len(_otf2_data_free_inthread)}):\n\t{_otf2_data_free_inthread} \n json ({len(_json_data_free_inthread)}):\n\t{_json_data_free_inthread}" + i = 0 + for i in range(len(_otf2_data_free_inthread)): + _otf2_s_ts = _otf2_data_free_inthread[i]["start_ts"] + _otf2_e_ts = _otf2_data_free_inthread[i]["end_ts"] + _json_s_ts = _json_data_free_inthread[i]["start_timestamp"] + _json_e_ts = _json_data_free_inthread[i]["end_timestamp"] + assert ( + _otf2_s_ts == _json_s_ts + ), f"memory_allocation free otf2 record {i} start timestamp ({_otf2_s_ts}):\n\t{_otf2_data_free_inthread[i]} \n json start ts({_json_e_ts}):\n\t{_json_data_free_inthread[i]}" + assert ( + _otf2_e_ts == _json_e_ts + ), f"memory_allocation free otf2 record {i} end timestamp ({_otf2_e_ts}):\n\t{_otf2_data_free_inthread[i]} \n json end ts({_json_e_ts}):\n\t{_json_data_free_inthread[i]}" + assert len(_otf2_data) == len( _json_data ), f"{otf2_category} ({len(_otf2_data)}):\n\t{_otf2_data}\n{json_category} ({len(_json_data)}):\n\t{_json_data}" diff --git a/tests/rocprofv3/collection-period/CMakeLists.txt b/tests/rocprofv3/collection-period/CMakeLists.txt index b09aba2312..5d22ac4e7c 100644 --- a/tests/rocprofv3/collection-period/CMakeLists.txt +++ b/tests/rocprofv3/collection-period/CMakeLists.txt @@ -23,12 +23,11 @@ set(collection-period-env "${PRELOAD_ENV}" ROCPROF_COLLECTION_PERIOD_TESTING=tru rocprofiler_configure_pytest_files(CONFIG pytest.ini COPY validate.py conftest.py) -########################################################################################## +# ######################################################################################## # -# Command line input +# Command line input # -########################################################################################## - +# ######################################################################################## add_test( NAME rocprofv3-test-collection-period-execute COMMAND diff --git a/tests/rocprofv3/collection-period/conftest.py b/tests/rocprofv3/collection-period/conftest.py index 4b5626e9e4..1ca1f15953 100644 --- a/tests/rocprofv3/collection-period/conftest.py +++ b/tests/rocprofv3/collection-period/conftest.py @@ -95,4 +95,5 @@ def otf2_data(request): filename = request.config.getoption("--otf2-input") if not os.path.exists(filename): raise FileExistsError(f"{filename} does not exist") - return OTF2Reader(filename).read()[0] + # the first and only frame in the first and only collection of frames + return list(OTF2Reader(filename).read().values())[0][0] diff --git a/tests/rocprofv3/kernel-rename/conftest.py b/tests/rocprofv3/kernel-rename/conftest.py index c0a8d934ca..ad6598fb1e 100644 --- a/tests/rocprofv3/kernel-rename/conftest.py +++ b/tests/rocprofv3/kernel-rename/conftest.py @@ -68,4 +68,4 @@ def otf2_data(request): filename = request.config.getoption("--otf2-input") if not os.path.exists(filename): raise FileExistsError(f"{filename} does not exist") - return OTF2Reader(filename).read()[0] + return list(OTF2Reader(filename).read().values())[0][0] diff --git a/tests/rocprofv3/memory-allocation/conftest.py b/tests/rocprofv3/memory-allocation/conftest.py index 6007ecc41d..cb89faf2de 100644 --- a/tests/rocprofv3/memory-allocation/conftest.py +++ b/tests/rocprofv3/memory-allocation/conftest.py @@ -59,4 +59,4 @@ def otf2_data(request): filename = request.config.getoption("--otf2-input") if not os.path.exists(filename): raise FileExistsError(f"{filename} does not exist") - return OTF2Reader(filename).read()[0] + return list(OTF2Reader(filename).read().values())[0][0] diff --git a/tests/rocprofv3/rocdecode-trace/conftest.py b/tests/rocprofv3/rocdecode-trace/conftest.py index 5c14f4d9cd..a928ac0072 100644 --- a/tests/rocprofv3/rocdecode-trace/conftest.py +++ b/tests/rocprofv3/rocdecode-trace/conftest.py @@ -69,7 +69,7 @@ def otf2_data(request): filename = request.config.getoption("--otf2-input") if not os.path.isfile(filename): return pytest.skip("rocdecode tracing unavailable") - return OTF2Reader(filename).read()[0] + return list(OTF2Reader(filename).read().values())[0][0] @pytest.fixture diff --git a/tests/rocprofv3/rocjpeg-trace/conftest.py b/tests/rocprofv3/rocjpeg-trace/conftest.py index 090ba671fc..5aecd9db64 100644 --- a/tests/rocprofv3/rocjpeg-trace/conftest.py +++ b/tests/rocprofv3/rocjpeg-trace/conftest.py @@ -91,7 +91,7 @@ def otf2_data(request): filename = request.config.getoption("--otf2-input") if not os.path.isfile(filename): return pytest.skip("rocjpeg tracing unavailable") - return OTF2Reader(filename).read()[0] + return list(OTF2Reader(filename).read().values())[0][0] @pytest.fixture diff --git a/tests/rocprofv3/rocpd/CMakeLists.txt b/tests/rocprofv3/rocpd/CMakeLists.txt index 1bafc1730c..a02eb85a68 100644 --- a/tests/rocprofv3/rocpd/CMakeLists.txt +++ b/tests/rocprofv3/rocpd/CMakeLists.txt @@ -38,8 +38,7 @@ endif() # # generate rocpd database and the old-way outputs csv, otf2, perfetto to compare # -######################################################################################### - +# ######################################################################################## add_test( NAME rocprofv3-test-rocpd-execute COMMAND @@ -85,12 +84,11 @@ set_tests_properties( FIXTURES_SETUP rocprofv3-test-rocpd-multiproc) -######################################################################################### +# ######################################################################################## # # OTF2 generation # -######################################################################################### - +# ######################################################################################## add_test( NAME rocprofv3-test-rocpd-otf2-generation COMMAND @@ -135,15 +133,16 @@ set_tests_properties( "${ROCPROFILER_DEFAULT_FAIL_REGEX}" DISABLED "${MULTIPROC_IS_DISABLED}" + FIXTURES_SETUP + rocprofv3-test-rocpd-generation-multiproc FIXTURES_REQUIRED rocprofv3-test-rocpd-multiproc) -######################################################################################### +# ######################################################################################## # # perfetto generate # -######################################################################################### - +# ######################################################################################## add_test( NAME rocprofv3-test-rocpd-perfetto-generation COMMAND @@ -189,15 +188,16 @@ set_tests_properties( "${ROCPROFILER_DEFAULT_FAIL_REGEX}" DISABLED "${MULTIPROC_IS_DISABLED}" + FIXTURES_SETUP + rocprofv3-test-rocpd-generation-multiproc FIXTURES_REQUIRED rocprofv3-test-rocpd-multiproc) -######################################################################################### +# ######################################################################################## # # CSV generate # -######################################################################################### - +# ######################################################################################## add_test( NAME rocprofv3-test-rocpd-csv-generation COMMAND @@ -222,11 +222,11 @@ set_tests_properties( FIXTURES_REQUIRED rocprofv3-test-rocpd) -######################################################################################### +# ######################################################################################## # # Validation # -######################################################################################### +# ######################################################################################## set(VALIDATION_DEPENDS rocprofv3-test-rocpd-perfetto-generation rocprofv3-test-rocpd-csv-generation @@ -254,3 +254,32 @@ set_tests_properties( "${ROCPROFILER_DEFAULT_FAIL_REGEX}" FIXTURES_REQUIRED rocprofv3-test-rocpd-generation) + +set(MULTIPROC_VALIDATION_DEPENDS rocprofv3-test-rocpd-perfetto-generation-multiproc + rocprofv3-test-rocpd-otf2-generation-multiproc) + +add_test( + NAME rocprofv3-test-rocpd-validation-multiproc + COMMAND + ${Python3_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/validate.py --skip + test_csv_data test_perfetto_data test_rocpd_data --json-input + ${CMAKE_CURRENT_BINARY_DIR}/rocpd-input-data-multiproc/out_mp_0_results.json + ${CMAKE_CURRENT_BINARY_DIR}/rocpd-input-data-multiproc/out_mp_1_results.json + --otf2-input + ${CMAKE_CURRENT_BINARY_DIR}/rocpd-output-data-multiproc/rocpd-mp-output-test_results.otf2 + ) + +set_tests_properties( + rocprofv3-test-rocpd-validation-multiproc + PROPERTIES TIMEOUT + 45 + LABELS + "integration-tests;rocpd" + ENVIRONMENT + "${rocprofv3-rocpd-env}" + FAIL_REGULAR_EXPRESSION + "${ROCPROFILER_DEFAULT_FAIL_REGEX}" + DEPENDS + "${MULTIPROC_VALIDATION_DEPENDS}" + FIXTURES_REQUIRED + rocprofv3-test-rocpd-generation-multiproc) diff --git a/tests/rocprofv3/rocpd/conftest.py b/tests/rocprofv3/rocpd/conftest.py index 73faaed82e..2a1e439f32 100644 --- a/tests/rocprofv3/rocpd/conftest.py +++ b/tests/rocprofv3/rocpd/conftest.py @@ -35,9 +35,17 @@ def pytest_addoption(parser): + parser.addoption( + "--skip", + action="store", + nargs="+", + help="list of outputy type to exclude from testing", + ) + parser.addoption( "--json-input", action="store", + nargs="+", help="Path to JSON file.", ) parser.addoption( @@ -62,11 +70,17 @@ def pytest_addoption(parser): pd.set_option(f"display.max_{itr}", None) +# returns a list of json structures: a json structure per db/process @pytest.fixture def json_data(request): - filename = request.config.getoption("--json-input") - with open(filename, "r") as inp: - return dotdict(collapse_dict_list(json.load(inp))) + filenames = request.config.getoption("--json-input") + ret_val = [] + for filename in filenames: + if not os.path.exists(filename): + raise FileExistsError(f"{filename} does not exist") + with open(filename, "r") as inp: + ret_val.append(dotdict(collapse_dict_list(json.load(inp)))) + return ret_val @pytest.fixture @@ -75,12 +89,13 @@ def pftrace_data(request): return PerfettoReader(filename).read()[0] +# returns a list of data frames of otf2 data coming from several processes, written in a single file with index 0: a data frame per process @pytest.fixture def otf2_data(request): filename = request.config.getoption("--otf2-input") if not os.path.exists(filename): raise FileExistsError(f"{filename} does not exist") - return OTF2Reader(filename).read()[0] + return OTF2Reader(filename).read() @pytest.fixture @@ -133,3 +148,14 @@ def process_current_domain(_name, _list): process_current_domain(current_name, current_list) return domains + + +def pytest_collection_modifyitems(config, items): + tests_to_skip = config.getoption("--skip") + if not tests_to_skip: + # --skip not given in cli, therefore move on + return + skip_listed = pytest.mark.skip(reason="included in --skiplist") + for item in items: + if item.name in tests_to_skip: + item.add_marker(skip_listed) diff --git a/tests/rocprofv3/rocpd/validate.py b/tests/rocprofv3/rocpd/validate.py index a47c56d538..f30dabca3b 100644 --- a/tests/rocprofv3/rocpd/validate.py +++ b/tests/rocprofv3/rocpd/validate.py @@ -26,24 +26,36 @@ import pytest +# todo: MP make pftrace data a list and remove json[0] def test_perfetto_data(pftrace_data, json_data): import rocprofiler_sdk.tests.rocprofv3 as rocprofv3 rocprofv3.test_perfetto_data( - pftrace_data, - json_data, + pftrace_data, # not a list + json_data[0], ("hip", "marker", "kernel", "memory_copy"), ) +# the approach works when each rocpd data base and each json file contains data for 1 process def test_otf2_data(otf2_data, json_data): import rocprofiler_sdk.tests.rocprofv3 as rocprofv3 - rocprofv3.test_otf2_data( - otf2_data, - json_data, - ("hip", "marker", "kernel", "memory_copy", "memory_allocation"), - ) + j = 0 # counting json structures in linear list json_data + for reader, data_frames in otf2_data.items(): + print(f"\n Otf2 file reader: {reader}\n") + assert len(data_frames) == len( + json_data + ), f"# {len(data_frames)} of otf2 data collections (a collection per process), differs from the # {len(json_data)} of json data collections" + + for _df in data_frames: + print(f"\n json index {j}\n") + rocprofv3.test_otf2_data( + _df, + json_data[j], + ("hip", "marker", "kernel", "memory_copy", "memory_allocation"), + ) + j += 1 if __name__ == "__main__": diff --git a/tests/rocprofv3/roctracer-roctx/conftest.py b/tests/rocprofv3/roctracer-roctx/conftest.py index 35715ac33e..a75f115f61 100644 --- a/tests/rocprofv3/roctracer-roctx/conftest.py +++ b/tests/rocprofv3/roctracer-roctx/conftest.py @@ -68,4 +68,4 @@ def otf2_data(request): filename = request.config.getoption("--otf2-input") if not os.path.exists(filename): raise FileExistsError(f"{filename} does not exist") - return OTF2Reader(filename).read()[0] + return list(OTF2Reader(filename).read().values())[0][0] diff --git a/tests/rocprofv3/summary/conftest.py b/tests/rocprofv3/summary/conftest.py index 73faaed82e..b90d578079 100644 --- a/tests/rocprofv3/summary/conftest.py +++ b/tests/rocprofv3/summary/conftest.py @@ -80,7 +80,7 @@ def otf2_data(request): filename = request.config.getoption("--otf2-input") if not os.path.exists(filename): raise FileExistsError(f"{filename} does not exist") - return OTF2Reader(filename).read()[0] + return list(OTF2Reader(filename).read().values())[0][0] @pytest.fixture diff --git a/tests/rocprofv3/tracing-hip-in-libraries/conftest.py b/tests/rocprofv3/tracing-hip-in-libraries/conftest.py index 37e01d8d17..fb8690955a 100644 --- a/tests/rocprofv3/tracing-hip-in-libraries/conftest.py +++ b/tests/rocprofv3/tracing-hip-in-libraries/conftest.py @@ -242,4 +242,4 @@ def pftrace_data(request): @pytest.fixture def otf2_data(request): filename = request.config.getoption("--otf2-input") - return OTF2Reader(filename).read()[0] + return list(OTF2Reader(filename).read().values())[0][0]