From 737f81c49155716027d6f0f79455628613e30271 Mon Sep 17 00:00:00 2001 From: "Naklicki, Mateusz" Date: Wed, 27 Aug 2025 09:43:26 +0000 Subject: [PATCH] feature: include API calls parameters in graph visualisation - add logic for extracting parameters from closures - move dump-related types to specified graph_export file - extract test fixtures to header file for reuse - rename test_graph_exporter.cpp -> test_graph_export.cpp Related-To: NEO-15377 Signed-off-by: Naklicki, Mateusz --- level_zero/CMakeLists.txt | 1 + .../driver_experimental/public/zex_graph.cpp | 1 + .../unit_tests/experimental/CMakeLists.txt | 4 +- .../experimental/test_graph_export.cpp | 904 ++++++++++++++++++ .../experimental/test_graph_export.h | 161 ++++ .../experimental/test_graph_exporter.cpp | 535 ----------- .../experimental/source/graph/CMakeLists.txt | 3 + .../graph/captured_apis/graph_captured_apis.h | 31 + .../source/graph/definitions/graph_export.inl | 6 + .../experimental/source/graph/graph.cpp | 256 ----- level_zero/experimental/source/graph/graph.h | 31 +- .../source/graph/graph_export.cpp | 719 ++++++++++++++ .../experimental/source/graph/graph_export.h | 54 ++ 13 files changed, 1887 insertions(+), 819 deletions(-) create mode 100644 level_zero/core/test/unit_tests/experimental/test_graph_export.cpp create mode 100644 level_zero/core/test/unit_tests/experimental/test_graph_export.h delete mode 100644 level_zero/core/test/unit_tests/experimental/test_graph_exporter.cpp create mode 100644 level_zero/experimental/source/graph/definitions/graph_export.inl create mode 100644 level_zero/experimental/source/graph/graph_export.cpp create mode 100644 level_zero/experimental/source/graph/graph_export.h diff --git a/level_zero/CMakeLists.txt b/level_zero/CMakeLists.txt index 99f8cf9772..95a8bc666d 100644 --- a/level_zero/CMakeLists.txt +++ b/level_zero/CMakeLists.txt @@ -217,6 +217,7 @@ if(BUILD_WITH_L0) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/core/source/cmdlist/definitions${BRANCH_DIR_SUFFIX}) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/core/source/driver/definitions${BRANCH_DIR_SUFFIX}) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/experimental/source/graph/captured_apis${BRANCH_DIR_SUFFIX}) + include_directories(${CMAKE_CURRENT_SOURCE_DIR}/experimental/source/graph/definitions${BRANCH_DIR_SUFFIX}) # Create our shared library/DLL configure_file(ze_intel_gpu_version.h.in ${NEO_BUILD_DIR}/ze_intel_gpu_version.h) diff --git a/level_zero/api/driver_experimental/public/zex_graph.cpp b/level_zero/api/driver_experimental/public/zex_graph.cpp index 0246fb85e2..c7152463bd 100644 --- a/level_zero/api/driver_experimental/public/zex_graph.cpp +++ b/level_zero/api/driver_experimental/public/zex_graph.cpp @@ -10,6 +10,7 @@ #include "level_zero/core/source/cmdlist/cmdlist.h" #include "level_zero/core/source/context/context.h" #include "level_zero/experimental/source/graph/graph.h" +#include "level_zero/experimental/source/graph/graph_export.h" namespace L0 { diff --git a/level_zero/core/test/unit_tests/experimental/CMakeLists.txt b/level_zero/core/test/unit_tests/experimental/CMakeLists.txt index 089ffa12dd..4570cdaa98 100644 --- a/level_zero/core/test/unit_tests/experimental/CMakeLists.txt +++ b/level_zero/core/test/unit_tests/experimental/CMakeLists.txt @@ -6,8 +6,10 @@ target_sources(${TARGET_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt + ${CMAKE_CURRENT_SOURCE_DIR}/test_graph.h ${CMAKE_CURRENT_SOURCE_DIR}/test_graph.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/test_graph_exporter.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_graph_export.h + ${CMAKE_CURRENT_SOURCE_DIR}/test_graph_export.cpp ) target_include_directories(${TARGET_NAME} diff --git a/level_zero/core/test/unit_tests/experimental/test_graph_export.cpp b/level_zero/core/test/unit_tests/experimental/test_graph_export.cpp new file mode 100644 index 0000000000..5d76fea17e --- /dev/null +++ b/level_zero/core/test/unit_tests/experimental/test_graph_export.cpp @@ -0,0 +1,904 @@ +/* + * Copyright (C) 2025 Intel Corporation + * + * SPDX-License-Identifier: MIT + * + */ + +#include "level_zero/core/test/unit_tests/experimental/test_graph_export.h" + +#include "shared/test/common/mocks/mock_io_functions.h" + +#include "level_zero/experimental/source/graph/captured_apis/graph_captured_apis.h" +#include "level_zero/experimental/source/graph/graph_export.h" + +#include "gtest/gtest.h" + +using namespace NEO; + +namespace L0 { +namespace ult { + +TEST_F(GraphDotExporterTest, GivenNullFilePathWhenExportToFileThenReturnsInvalidArgument) { + Graph testGraph{&ctx, true}; + auto result = exporter.exportToFile(testGraph, nullptr); + EXPECT_EQ(ZE_RESULT_ERROR_INVALID_ARGUMENT, result); +} + +TEST_F(GraphDotExporterTest, GivenEmptyGraphWhenExportToStringThenContainsDigraphHeader) { + Graph testGraph{&ctx, true}; + + std::string dot = exporter.exportToString(testGraph); + EXPECT_NE(dot.find("digraph \"graph\" {"), std::string::npos); + EXPECT_NE(dot.find("rankdir=TB;"), std::string::npos); + EXPECT_NE(dot.find("nodesep=1;"), std::string::npos); + EXPECT_NE(dot.find("ranksep=1;"), std::string::npos); + EXPECT_NE(dot.find("node [shape=box, style=filled];"), std::string::npos); + EXPECT_NE(dot.find("edge [color=black];"), std::string::npos); + EXPECT_NE(dot.find('}'), std::string::npos); +} + +TEST_F(GraphDotExporterTest, GivenGraphWithSingleCommandWhenExportToStringThenContainsCommandNode) { + Graph testGraph{&ctx, true}; + Mock event; + Mock cmdlist; + + testGraph.capture(&cmdlist, &event, 0U, nullptr); + testGraph.stopCapturing(); + + std::string dot = exporter.exportToString(testGraph); + EXPECT_NE(dot.find("zeCommandListAppendBarrier"), std::string::npos); + EXPECT_NE(dot.find("L0_S0_C0"), std::string::npos); +} + +TEST_F(GraphDotExporterTest, GivenGraphWithMultipleCommandsWhenExportToStringThenContainsSequentialEdges) { + Graph testGraph{&ctx, true}; + Mock event; + Mock cmdlist; + + testGraph.capture(&cmdlist, &event, 0U, nullptr); + testGraph.capture(&cmdlist, nullptr, nullptr, 0U, nullptr, 0U, nullptr); + testGraph.stopCapturing(); + + std::string dot = exporter.exportToString(testGraph); + EXPECT_NE(dot.find("L0_S0_C0 -> L0_S0_C1"), std::string::npos); +} + +TEST_F(GraphDotExporterTest, WhenWriteHeaderThenGeneratesValidDotHeader) { + Graph testGraph{&ctx, true}; + + std::ostringstream dot; + exporter.writeHeader(dot); + std::string header = dot.str(); + + EXPECT_NE(header.find("digraph \"graph\" {"), std::string::npos); + EXPECT_NE(header.find("rankdir=TB;"), std::string::npos); + EXPECT_NE(header.find("nodesep=1;"), std::string::npos); + EXPECT_NE(header.find("ranksep=1;"), std::string::npos); + EXPECT_NE(header.find("node [shape=box, style=filled];"), std::string::npos); + EXPECT_NE(header.find("edge [color=black];"), std::string::npos); +} + +TEST_F(GraphDotExporterTest, GivenGraphWithCommandWhenWriteNodesThenGeneratesNodeDefinitions) { + Graph testGraph{&ctx, true}; + Mock event; + Mock cmdlist; + + testGraph.capture(&cmdlist, &event, 0U, nullptr); + testGraph.stopCapturing(); + + std::ostringstream dot; + exporter.writeNodes(dot, testGraph, 0, 0); + std::string output = dot.str(); + + EXPECT_NE(output.find("L0_S0_C0"), std::string::npos); + EXPECT_NE(output.find("zeCommandListAppendBarrier"), std::string::npos); +} + +TEST_F(GraphDotExporterTest, GivenGraphWithMultipleCommandsWhenWriteEdgesThenGeneratesSequentialEdges) { + Graph testGraph{&ctx, true}; + Mock event; + Mock cmdlist; + + testGraph.capture(&cmdlist, &event, 0U, nullptr); + testGraph.capture(&cmdlist, nullptr, nullptr, 0U, nullptr, 0U, nullptr); + testGraph.stopCapturing(); + + std::ostringstream dot; + exporter.writeEdges(dot, testGraph, 0, 0); + std::string output = dot.str(); + + EXPECT_NE(output.find("L0_S0_C0 -> L0_S0_C1"), std::string::npos); +} + +TEST_F(GraphDotExporterTest, GivenGraphWithCommandWhenGetCommandNodeLabelThenReturnsCorrectLabel) { + Graph testGraph{&ctx, true}; + Mock event; + Mock cmdlist; + + testGraph.capture(&cmdlist, &event, 0U, nullptr); + testGraph.stopCapturing(); + + std::string label = exporter.getCommandNodeLabel(testGraph, 0, ""); + EXPECT_NE(label.find("zeCommandListAppendBarrier"), std::string::npos); +} + +TEST_F(GraphDotExporterTest, GivenDifferentCommandTypesWhenGetCommandNodeAttributesThenReturnsCorrectColors) { + Graph testGraph{&ctx, true}; + Mock event; + Mock cmdlist; + + testGraph.capture(&cmdlist, &event, 0U, nullptr); + testGraph.capture(&cmdlist, nullptr, nullptr, 0U, nullptr, 0U, nullptr); + testGraph.capture(&cmdlist, &event); + testGraph.capture(&cmdlist, nullptr, nullptr, nullptr, 0U, nullptr); + testGraph.capture(&cmdlist, nullptr, nullptr, 0U, nullptr); + + testGraph.stopCapturing(); + + EXPECT_EQ(exporter.getCommandNodeAttributes(testGraph, 0), ", fillcolor=orange"); + EXPECT_EQ(exporter.getCommandNodeAttributes(testGraph, 1), ", fillcolor=lightblue"); + EXPECT_EQ(exporter.getCommandNodeAttributes(testGraph, 2), ", fillcolor=yellow"); + EXPECT_EQ(exporter.getCommandNodeAttributes(testGraph, 3), ", fillcolor=lightgreen"); + EXPECT_EQ(exporter.getCommandNodeAttributes(testGraph, 4), ", fillcolor=pink"); +} + +TEST_F(GraphDotExporterTest, WhenGenerateNodeIdThenReturnsCorrectFormat) { + EXPECT_EQ(exporter.generateNodeId(0, 0, 0), "L0_S0_C0"); + EXPECT_EQ(exporter.generateNodeId(1, 2, 3), "L1_S2_C3"); + EXPECT_EQ(exporter.generateNodeId(10, 20, 30), "L10_S20_C30"); +} + +TEST_F(GraphDotExporterTest, WhenGenerateSubgraphIdThenReturnsCorrectFormat) { + EXPECT_EQ(exporter.generateSubgraphId(0, 0), "L0_S0"); + EXPECT_EQ(exporter.generateSubgraphId(1, 2), "L1_S2"); + EXPECT_EQ(exporter.generateSubgraphId(10, 20), "L10_S20"); +} + +TEST_F(GraphDotExporterTest, WhenGetSubgraphFillColorThenReturnsCorrectColors) { + EXPECT_EQ(exporter.getSubgraphFillColor(1), "grey90"); + EXPECT_EQ(exporter.getSubgraphFillColor(2), "grey80"); + EXPECT_EQ(exporter.getSubgraphFillColor(3), "grey70"); + EXPECT_EQ(exporter.getSubgraphFillColor(4), "grey60"); + EXPECT_EQ(exporter.getSubgraphFillColor(5), "grey50"); +} + +TEST_F(GraphDotExporterTest, GivenDeepLevelWhenGetSubgraphFillColorThenReturnsDeepestColor) { + EXPECT_EQ(exporter.getSubgraphFillColor(6), "grey50"); + EXPECT_EQ(exporter.getSubgraphFillColor(10), "grey50"); + EXPECT_EQ(exporter.getSubgraphFillColor(100), "grey50"); +} + +TEST_F(GraphDotExporterTest, GivenGraphWithoutSubgraphsWhenWriteSubgraphsThenGeneratesNoOutput) { + Graph testGraph{&ctx, true}; + std::ostringstream dot; + exporter.writeSubgraphs(dot, testGraph, 0); + std::string output = dot.str(); + + EXPECT_TRUE(output.empty()); +} + +TEST_F(GraphDotExporterTest, GivenGraphWithSubgraphsWhenWriteSubgraphsThenGeneratesSubgraphStructure) { + Graph testGraph{&ctx, true}; + Mock forkEvent; + Mock joinEvent; + Mock mainCmdList; + Mock subCmdList; + + Graph *testGraphPtr = &testGraph; + captureCommand(mainCmdList, testGraphPtr, &mainCmdList, &forkEvent, 0U, nullptr); + + Graph *subGraph = nullptr; + testGraph.forkTo(subCmdList, subGraph, forkEvent); + ASSERT_NE(subGraph, nullptr); + + captureCommand(subCmdList, subGraph, &subCmdList, nullptr, nullptr, 0U, nullptr, 0U, nullptr); + captureCommand(subCmdList, subGraph, &subCmdList, &joinEvent, 0U, nullptr); + + testGraph.tryJoinOnNextCommand(subCmdList, joinEvent); + captureCommand(mainCmdList, testGraphPtr, &mainCmdList, nullptr, 0U, nullptr); + testGraph.stopCapturing(); + + std::ostringstream dot; + exporter.writeSubgraphs(dot, testGraph, 0); + std::string output = dot.str(); + + EXPECT_NE(output.find("// Subgraphs:"), std::string::npos); + EXPECT_NE(output.find("subgraph cluster_L1_S0"), std::string::npos); + EXPECT_NE(output.find("label=\"Subgraph 1-0\""), std::string::npos); + EXPECT_NE(output.find("style=filled"), std::string::npos); + EXPECT_NE(output.find("fillcolor=grey90"), std::string::npos); +} + +TEST_F(GraphDotExporterTest, GivenGraphWithNestedSubgraphsWhenWriteSubgraphsThenGeneratesNestedStructure) { + Graph testGraph{&ctx, true}; + Mock forkEvent1; + Mock forkEvent2; + Mock joinEvent1; + Mock joinEvent2; + Mock mainCmdList; + Mock subCmdList1; + Mock subCmdList2; + + Graph *testGraphPtr = &testGraph; + captureCommand(mainCmdList, testGraphPtr, &mainCmdList, &forkEvent1, 0U, nullptr); + + Graph *subGraph1 = nullptr; + testGraph.forkTo(subCmdList1, subGraph1, forkEvent1); + ASSERT_NE(subGraph1, nullptr); + + captureCommand(subCmdList1, subGraph1, &subCmdList1, nullptr, nullptr, 0U, nullptr, 0U, nullptr); + + Graph *subGraph2 = nullptr; + captureCommand(subCmdList1, subGraph1, &subCmdList1, &forkEvent2, 0U, nullptr); + subGraph1->forkTo(subCmdList2, subGraph2, forkEvent2); + ASSERT_NE(subGraph2, nullptr); + + captureCommand(subCmdList2, subGraph2, &subCmdList2, &joinEvent2, 0U, nullptr); + + subGraph1->tryJoinOnNextCommand(subCmdList2, joinEvent2); + captureCommand(subCmdList1, subGraph1, &subCmdList1, &joinEvent1, 0U, nullptr); + + testGraph.tryJoinOnNextCommand(subCmdList1, joinEvent1); + testGraph.stopCapturing(); + + std::ostringstream dot; + exporter.writeSubgraphs(dot, testGraph, 0); + std::string output = dot.str(); + + EXPECT_NE(output.find("subgraph cluster_L1_S0"), std::string::npos); + EXPECT_NE(output.find("subgraph cluster_L2_S0"), std::string::npos); + EXPECT_NE(output.find("fillcolor=grey90"), std::string::npos); + EXPECT_NE(output.find("fillcolor=grey80"), std::string::npos); +} + +TEST_F(GraphDotExporterTest, GivenGraphWithAdjacentSubgraphsWhenWriteSubgraphsThenGeneratesMultipleSubgraphs) { + Graph testGraph{&ctx, true}; + Mock forkEvent1; + Mock forkEvent2; + Mock joinEvent1; + Mock joinEvent2; + Mock mainCmdList; + Mock subCmdList1; + Mock subCmdList2; + + Graph *testGraphPtr = &testGraph; + captureCommand(mainCmdList, testGraphPtr, &mainCmdList, &forkEvent1, 0U, nullptr); + + Graph *subGraph1 = nullptr; + testGraph.forkTo(subCmdList1, subGraph1, forkEvent1); + ASSERT_NE(subGraph1, nullptr); + + captureCommand(subCmdList1, subGraph1, &subCmdList1, nullptr, nullptr, 0U, nullptr, 0U, nullptr); + captureCommand(subCmdList1, subGraph1, &subCmdList1, &joinEvent1, 0U, nullptr); + + testGraph.tryJoinOnNextCommand(subCmdList1, joinEvent1); + captureCommand(mainCmdList, testGraphPtr, &mainCmdList, &forkEvent2, 0U, nullptr); + + Graph *subGraph2 = nullptr; + testGraph.forkTo(subCmdList2, subGraph2, forkEvent2); + ASSERT_NE(subGraph2, nullptr); + + captureCommand(subCmdList2, subGraph2, &subCmdList2, nullptr, nullptr, 0U, nullptr, 0U, nullptr); + captureCommand(subCmdList2, subGraph2, &subCmdList2, &joinEvent2, 0U, nullptr); + + testGraph.tryJoinOnNextCommand(subCmdList2, joinEvent2); + captureCommand(mainCmdList, testGraphPtr, &mainCmdList, nullptr, 0U, nullptr); + testGraph.stopCapturing(); + + std::ostringstream dot; + exporter.writeSubgraphs(dot, testGraph, 0); + std::string output = dot.str(); + + EXPECT_NE(output.find("// Subgraphs:"), std::string::npos); + EXPECT_NE(output.find("subgraph cluster_L1_S0"), std::string::npos); + EXPECT_NE(output.find("subgraph cluster_L1_S1"), std::string::npos); + EXPECT_NE(output.find("label=\"Subgraph 1-0\""), std::string::npos); + EXPECT_NE(output.find("label=\"Subgraph 1-1\""), std::string::npos); +} + +TEST_F(GraphDotExporterTest, WhenFindSubgraphIndexOnEmptyGraphThenReturnsNullopt) { + const StackVec subGraphs; + Graph fakeSubgraph{&ctx, true}; + auto index = exporter.findSubgraphIndex(subGraphs, &fakeSubgraph); + + EXPECT_FALSE(index.has_value()); +} + +TEST_F(GraphDotExporterTest, WhenFindSubgraphIndexWithInvalidSubgraphThenReturnsNullopt) { + Graph testGraph{&ctx, true}; + Mock forkEvent; + Mock joinEvent; + Mock mainCmdList; + Mock subCmdList; + + Graph *testGraphPtr = &testGraph; + captureCommand(mainCmdList, testGraphPtr, &mainCmdList, &forkEvent, 0U, nullptr); + + Graph *subGraph = nullptr; + testGraph.forkTo(subCmdList, subGraph, forkEvent); + ASSERT_NE(subGraph, nullptr); + + testGraph.tryJoinOnNextCommand(subCmdList, joinEvent); + captureCommand(mainCmdList, testGraphPtr, &mainCmdList, nullptr, 0U, nullptr); + testGraph.stopCapturing(); + + const auto &subGraphs = testGraph.getSubgraphs(); + Graph fakeSubgraph{&ctx, true}; + auto index = exporter.findSubgraphIndex(subGraphs, &fakeSubgraph); + + EXPECT_FALSE(index.has_value()); +} + +TEST_F(GraphDotExporterTest, WhenFindSubgraphIndexWithValidGraphThenReturnsCorrectIndex) { + Graph testGraph{&ctx, true}; + Mock forkEvent; + Mock joinEvent; + Mock mainCmdList; + Mock subCmdList; + + Graph *testGraphPtr = &testGraph; + captureCommand(mainCmdList, testGraphPtr, &mainCmdList, &forkEvent, 0U, nullptr); + + Graph *subGraph = nullptr; + testGraph.forkTo(subCmdList, subGraph, forkEvent); + ASSERT_NE(subGraph, nullptr); + + testGraph.tryJoinOnNextCommand(subCmdList, joinEvent); + captureCommand(mainCmdList, testGraphPtr, &mainCmdList, nullptr, 0U, nullptr); + testGraph.stopCapturing(); + + const auto &subGraphs = testGraph.getSubgraphs(); + auto index = exporter.findSubgraphIndex(subGraphs, subGraph); + + ASSERT_TRUE(index.has_value()); + EXPECT_EQ(index.value(), 0U); +} + +TEST_F(GraphDotExporterTest, WhenFindSubgraphIndexByCommandListOnEmptyGraphThenReturnsNullopt) { + const StackVec subGraphs; + Mock fakeCmdList; + auto index = exporter.findSubgraphIndexByCommandList(subGraphs, &fakeCmdList); + + EXPECT_FALSE(index.has_value()); +} + +TEST_F(GraphDotExporterTest, WhenFindSubgraphIndexByCommandListWithInvalidCommandListThenReturnsNullopt) { + Graph testGraph{&ctx, true}; + Mock forkEvent; + Mock joinEvent; + Mock mainCmdList; + Mock subCmdList; + + Graph *testGraphPtr = &testGraph; + captureCommand(mainCmdList, testGraphPtr, &mainCmdList, &forkEvent, 0U, nullptr); + + Graph *subGraph = nullptr; + testGraph.forkTo(subCmdList, subGraph, forkEvent); + ASSERT_NE(subGraph, nullptr); + + testGraph.tryJoinOnNextCommand(subCmdList, joinEvent); + captureCommand(mainCmdList, testGraphPtr, &mainCmdList, nullptr, 0U, nullptr); + testGraph.stopCapturing(); + + const auto &subGraphs = testGraph.getSubgraphs(); + Mock fakeCmdList; + auto index = exporter.findSubgraphIndexByCommandList(subGraphs, &fakeCmdList); + + EXPECT_FALSE(index.has_value()); +} + +TEST_F(GraphDotExporterTest, WhenFindSubgraphIndexByCommandListWithValidCommandListThenReturnsCorrectIndex) { + Graph testGraph{&ctx, true}; + Mock forkEvent; + Mock joinEvent; + Mock mainCmdList; + Mock subCmdList; + + Graph *testGraphPtr = &testGraph; + captureCommand(mainCmdList, testGraphPtr, &mainCmdList, &forkEvent, 0U, nullptr); + + Graph *subGraph = nullptr; + testGraph.forkTo(subCmdList, subGraph, forkEvent); + ASSERT_NE(subGraph, nullptr); + + testGraph.tryJoinOnNextCommand(subCmdList, joinEvent); + captureCommand(mainCmdList, testGraphPtr, &mainCmdList, nullptr, 0U, nullptr); + testGraph.stopCapturing(); + + const auto &subGraphs = testGraph.getSubgraphs(); + auto index = exporter.findSubgraphIndexByCommandList(subGraphs, &subCmdList); + + ASSERT_TRUE(index.has_value()); + EXPECT_EQ(index.value(), 0U); +} + +TEST_F(GraphDotExporterTest, GivenGraphWithEmptySubgraphWhenWriteForkJoinEdgesThenNoEdges) { + Graph testGraph{&ctx, true}; + Mock forkEvent; + Mock joinEvent; + Mock mainCmdList; + Mock subCmdList; + + Graph *testGraphPtr = &testGraph; + captureCommand(mainCmdList, testGraphPtr, &mainCmdList, &forkEvent, 0U, nullptr); + + Graph *subGraph = nullptr; + testGraph.forkTo(subCmdList, subGraph, forkEvent); + ASSERT_NE(subGraph, nullptr); + + testGraph.tryJoinOnNextCommand(subCmdList, joinEvent); + captureCommand(mainCmdList, testGraphPtr, &mainCmdList, nullptr, 0U, nullptr); + testGraph.stopCapturing(); + + std::ostringstream dot; + exporter.writeForkJoinEdges(dot, testGraph, 0, 0); + std::string output = dot.str(); + + EXPECT_EQ(output.find("->"), std::string::npos); +} + +TEST_F(GraphDotExporterTest, GivenGraphWithUnjoinedForksWhenWriteUnjoinedForkEdgesThenGeneratesUnjoinedEdges) { + Graph testGraph{&ctx, true}; + Mock forkEvent; + Mock mainCmdList; + Mock subCmdList; + + Graph *testGraphPtr = &testGraph; + captureCommand(mainCmdList, testGraphPtr, &mainCmdList, &forkEvent, 0U, nullptr); + + Graph *subGraph = nullptr; + testGraph.forkTo(subCmdList, subGraph, forkEvent); + ASSERT_NE(subGraph, nullptr); + + captureCommand(subCmdList, subGraph, &subCmdList, nullptr, nullptr, 0U, nullptr, 0U, nullptr); + + std::ostringstream dot; + exporter.writeUnjoinedForkEdges(dot, testGraph, 0, 0); + std::string output = dot.str(); + + EXPECT_NE(output.find("// Unjoined forks:"), std::string::npos); + EXPECT_NE(output.find("L0_S0_C0 -> L1_S0_C0 [color=red, label=\"unjoined fork\"];"), std::string::npos); + + // Prevent double free with unjoined forks + Mock joinEvent; + testGraph.tryJoinOnNextCommand(subCmdList, joinEvent); + captureCommand(mainCmdList, testGraphPtr, &mainCmdList, &forkEvent, 0U, nullptr); + testGraph.stopCapturing(); +} + +TEST_F(GraphDotExporterTest, GivenGraphWithEmptyUnjoinedSubgraphWhenWriteUnjoinedForkEdgesThenOutputIsEmpty) { + Graph testGraph{&ctx, true}; + Mock forkEvent; + Mock mainCmdList; + Mock subCmdList; + + Graph *testGraphPtr = &testGraph; + captureCommand(mainCmdList, testGraphPtr, &mainCmdList, &forkEvent, 0U, nullptr); + + Graph *subGraph = nullptr; + testGraph.forkTo(subCmdList, subGraph, forkEvent); + ASSERT_NE(subGraph, nullptr); + + std::ostringstream dot; + exporter.writeUnjoinedForkEdges(dot, testGraph, 0, 0); + std::string output = dot.str(); + + EXPECT_EQ(output.find("L0_S0_C0 -> L1_S0_C0 [color=red, label=\"unjoined fork\"];"), std::string::npos); + + // Prevent double free with unjoined forks + Mock joinEvent; + testGraph.tryJoinOnNextCommand(subCmdList, joinEvent); + captureCommand(mainCmdList, testGraphPtr, &mainCmdList, &forkEvent, 0U, nullptr); + testGraph.stopCapturing(); +} + +TEST_F(GraphDotExporterFileTest, GivenEmptyGraphWhenExportToFileThenWritesValidDotContent) { + Graph testGraph{&ctx, true}; + setupSuccessfulWrite(testGraph); + + auto result = exporter.exportToFile(testGraph, testFilePath.c_str()); + EXPECT_EQ(ZE_RESULT_SUCCESS, result); + + std::string writtenContent = getWrittenContent(); + EXPECT_NE(writtenContent.find("digraph \"graph\" {"), std::string::npos); + EXPECT_NE(writtenContent.find('}'), std::string::npos); + + EXPECT_EQ(mockFopenCalledBefore + 1, NEO::IoFunctions::mockFopenCalled); + EXPECT_EQ(mockFwriteCalledBefore + 1, NEO::IoFunctions::mockFwriteCalled); + EXPECT_EQ(mockFcloseCalledBefore + 1, NEO::IoFunctions::mockFcloseCalled); +} + +TEST_F(GraphDotExporterFileTest, GivenFailedFileOpenWhenExportToFileThenReturnsUnknownError) { + Graph testGraph{&ctx, true}; + setupFailedOpen(); + + auto result = exporter.exportToFile(testGraph, testFilePath.c_str()); + EXPECT_EQ(ZE_RESULT_ERROR_UNKNOWN, result); + + EXPECT_EQ(mockFopenCalledBefore + 1, NEO::IoFunctions::mockFopenCalled); + EXPECT_EQ(mockFwriteCalledBefore, NEO::IoFunctions::mockFwriteCalled); +} + +TEST_F(GraphDotExporterFileTest, GivenFailedFileWriteWhenExportToFileThenReturnsUnknownError) { + Graph testGraph{&ctx, true}; + setupFailedWrite(); + + auto result = exporter.exportToFile(testGraph, testFilePath.c_str()); + EXPECT_EQ(ZE_RESULT_ERROR_UNKNOWN, result); + + EXPECT_EQ(mockFopenCalledBefore + 1, NEO::IoFunctions::mockFopenCalled); + EXPECT_EQ(mockFwriteCalledBefore + 1, NEO::IoFunctions::mockFwriteCalled); + EXPECT_EQ(mockFcloseCalledBefore + 1, NEO::IoFunctions::mockFcloseCalled); +} + +TEST(GraphDumpHelperTest, GivenNullptrAndPtrWhenFormatPointerIsCalledThenReturnsFormattedString) { + EXPECT_EQ(GraphDumpHelper::formatPointer(nullptr), "0x0"); + const void *ptr = reinterpret_cast(0x1234); + EXPECT_EQ(GraphDumpHelper::formatPointer(ptr), "0x1234"); +} + +TEST(GraphDumpHelperTest, GivenGroupCountWhenFormatGroupCountIsCalledThenReturnsFormattedString) { + ze_group_count_t groupCount{1, 2, 3}; + EXPECT_EQ(GraphDumpHelper::formatGroupCount(groupCount), "1x2x3"); +} + +DEFINE_APIARGS_FIELDS(zeCommandListAppendWriteGlobalTimestamp, "hCommandList", "dstptr", "hSignalEvent", "numWaitEvents", "phWaitEvents", "phWaitEvents[0]"); +DEFINE_APIARGS_FIELDS(zeCommandListAppendBarrier, "hCommandList", "hSignalEvent", "numWaitEvents", "phWaitEvents", "phWaitEvents[0]"); +DEFINE_APIARGS_FIELDS(zeCommandListAppendMemoryRangesBarrier, "hCommandList", "numRanges", "pRangeSizes", "pRanges", "hSignalEvent", "numWaitEvents", "phWaitEvents", "phWaitEvents[0]"); +DEFINE_APIARGS_FIELDS(zeCommandListAppendMemoryCopy, "hCommandList", "dstptr", "srcptr", "size", "hSignalEvent", "numWaitEvents", "phWaitEvents", "phWaitEvents[0]"); +DEFINE_APIARGS_FIELDS(zeCommandListAppendMemoryFill, "hCommandList", "ptr", "pattern", "patternSize", "size", "hSignalEvent", "numWaitEvents", "phWaitEvents", "phWaitEvents[0]"); +DEFINE_APIARGS_FIELDS(zeCommandListAppendMemoryCopyRegion, "hCommandList", "dstptr", "dstRegion", "dstPitch", "dstSlicePitch", "srcptr", "srcRegion", "srcPitch", "srcSlicePitch", "hSignalEvent", "numWaitEvents", "phWaitEvents", "phWaitEvents[0]"); +DEFINE_APIARGS_FIELDS(zeCommandListAppendMemoryCopyFromContext, "hCommandList", "dstptr", "hContextSrc", "srcptr", "size", "hSignalEvent", "numWaitEvents", "phWaitEvents", "phWaitEvents[0]"); +DEFINE_APIARGS_FIELDS(zeCommandListAppendImageCopy, "hCommandList", "hDstImage", "hSrcImage", "hSignalEvent", "numWaitEvents", "phWaitEvents", "phWaitEvents[0]"); +DEFINE_APIARGS_FIELDS(zeCommandListAppendImageCopyRegion, "hCommandList", "hDstImage", "hSrcImage", "pDstRegion", "pSrcRegion", "hSignalEvent", "numWaitEvents", "phWaitEvents", "phWaitEvents[0]"); +DEFINE_APIARGS_FIELDS(zeCommandListAppendImageCopyToMemory, "hCommandList", "dstptr", "hSrcImage", "pSrcRegion", "hSignalEvent", "numWaitEvents", "phWaitEvents", "phWaitEvents[0]"); +DEFINE_APIARGS_FIELDS(zeCommandListAppendImageCopyFromMemory, "hCommandList", "hDstImage", "srcptr", "pDstRegion", "hSignalEvent", "numWaitEvents", "phWaitEvents", "phWaitEvents[0]"); +DEFINE_APIARGS_FIELDS(zeCommandListAppendMemoryPrefetch, "hCommandList", "ptr", "size"); +DEFINE_APIARGS_FIELDS(zeCommandListAppendMemAdvise, "hCommandList", "hDevice", "ptr", "size", "advice"); +DEFINE_APIARGS_FIELDS(zeCommandListAppendSignalEvent, "hCommandList", "hEvent"); +DEFINE_APIARGS_FIELDS(zeCommandListAppendWaitOnEvents, "hCommandList", "numEvents", "phEvents", "phEvents[0]"); +DEFINE_APIARGS_FIELDS(zeCommandListAppendEventReset, "hCommandList", "hEvent"); +DEFINE_APIARGS_FIELDS(zeCommandListAppendQueryKernelTimestamps, "hCommandList", "numEvents", "phEvents", "phEvents[0]", "dstptr", "pOffsets", "hSignalEvent", "numWaitEvents", "phWaitEvents", "phWaitEvents[0]"); +DEFINE_APIARGS_FIELDS(zeCommandListAppendLaunchKernel, "hCommandList", "hKernel", "launchFuncArgs", "hSignalEvent", "numWaitEvents", "phWaitEvents", "phWaitEvents[0]"); +DEFINE_APIARGS_FIELDS(zeCommandListAppendLaunchCooperativeKernel, "hCommandList", "hKernel", "launchFuncArgs", "hSignalEvent", "numWaitEvents", "phWaitEvents", "phWaitEvents[0]"); +DEFINE_APIARGS_FIELDS(zeCommandListAppendLaunchKernelIndirect, "hCommandList", "hKernel", "pLaunchArgumentsBuffer", "hSignalEvent", "numWaitEvents", "phWaitEvents", "phWaitEvents[0]"); +DEFINE_APIARGS_FIELDS(zeCommandListAppendLaunchKernelWithParameters, "hCommandList", "hKernel", "pGroupCounts", "pNext", "hSignalEvent", "numWaitEvents", "phWaitEvents", "phWaitEvents[0]"); +DEFINE_APIARGS_FIELDS(zeCommandListAppendLaunchMultipleKernelsIndirect, "hCommandList", "numKernels", "phKernels", "pCountBuffer", "pLaunchArgumentsBuffer", "hSignalEvent", "numWaitEvents", "phWaitEvents", "phWaitEvents[0]"); +DEFINE_APIARGS_FIELDS(zeCommandListAppendSignalExternalSemaphoreExt, "hCommandList", "numSemaphores", "phSemaphores", "phSemaphores[0]", "signalParams", "hSignalEvent", "numWaitEvents", "phWaitEvents", "phWaitEvents[0]"); +DEFINE_APIARGS_FIELDS(zeCommandListAppendWaitExternalSemaphoreExt, "hCommandList", "numSemaphores", "phSemaphores", "phSemaphores[0]", "waitParams", "hSignalEvent", "numWaitEvents", "phWaitEvents", "phWaitEvents[0]"); +DEFINE_APIARGS_FIELDS(zeCommandListAppendImageCopyToMemoryExt, "hCommandList", "dstptr", "hSrcImage", "pSrcRegion", "destRowPitch", "destSlicePitch", "hSignalEvent", "numWaitEvents", "phWaitEvents", "phWaitEvents[0]"); +DEFINE_APIARGS_FIELDS(zeCommandListAppendImageCopyFromMemoryExt, "hCommandList", "hDstImage", "srcptr", "pDstRegion", "srcRowPitch", "srcSlicePitch", "hSignalEvent", "numWaitEvents", "phWaitEvents", "phWaitEvents[0]"); + +TEST_F(ExtractParametersTestFixture, zeCommandListAppendWriteGlobalTimestamp) { + Closure::ApiArgs args{}; + args.numWaitEvents = 1; + args.phWaitEvents = dummyEvents; + args.hSignalEvent = dummyEvents[0]; + expectAllApiArgsPresent(args); +} + +TEST_F(ExtractParametersTestFixture, zeCommandListAppendBarrier) { + Closure::ApiArgs args{}; + args.numWaitEvents = 1; + args.phWaitEvents = dummyEvents; + args.hSignalEvent = dummyEvents[0]; + expectAllApiArgsPresent(args); +} + +TEST_F(ExtractParametersTestFixture, zeCommandListAppendMemoryRangesBarrier) { + Closure::ApiArgs args{}; + args.numWaitEvents = 1; + args.phWaitEvents = dummyEvents; + args.hSignalEvent = dummyEvents[0]; + args.numRanges = 1; + args.pRangeSizes = dummyRangeSizes; + args.pRanges = dummyRanges; + expectAllApiArgsPresent(args); +} + +TEST_F(ExtractParametersTestFixture, zeCommandListAppendMemoryCopy) { + Closure::ApiArgs args{}; + args.numWaitEvents = 1; + args.phWaitEvents = dummyEvents; + args.hSignalEvent = dummyEvents[0]; + expectAllApiArgsPresent(args); +} + +TEST_F(ExtractParametersTestFixture, zeCommandListAppendMemoryFill) { + Closure::ApiArgs args{}; + args.numWaitEvents = 1; + args.phWaitEvents = dummyEvents; + args.hSignalEvent = dummyEvents[0]; + expectAllApiArgsPresent(args); +} + +TEST_F(ExtractParametersTestFixture, zeCommandListAppendMemoryCopyRegion) { + Closure::ApiArgs args{}; + args.numWaitEvents = 1; + args.phWaitEvents = dummyEvents; + args.hSignalEvent = dummyEvents[0]; + expectAllApiArgsPresent(args); +} + +TEST_F(ExtractParametersTestFixture, zeCommandListAppendMemoryCopyFromContext) { + Closure::ApiArgs args{}; + args.numWaitEvents = 1; + args.phWaitEvents = dummyEvents; + args.hSignalEvent = dummyEvents[0]; + expectAllApiArgsPresent(args); +} + +TEST_F(ExtractParametersTestFixture, zeCommandListAppendImageCopy) { + Closure::ApiArgs args{}; + args.numWaitEvents = 1; + args.phWaitEvents = dummyEvents; + args.hSignalEvent = dummyEvents[0]; + expectAllApiArgsPresent(args); +} + +TEST_F(ExtractParametersTestFixture, zeCommandListAppendImageCopyRegion) { + Closure::ApiArgs args{}; + args.numWaitEvents = 1; + args.phWaitEvents = dummyEvents; + args.hSignalEvent = dummyEvents[0]; + args.hDstImage = reinterpret_cast(0x1); + args.hSrcImage = reinterpret_cast(0x2); + args.pDstRegion = &dummyRegion; + args.pSrcRegion = &dummyRegion; + expectAllApiArgsPresent(args); +} + +TEST_F(ExtractParametersTestFixture, zeCommandListAppendImageCopyToMemory) { + Closure::ApiArgs args{}; + args.numWaitEvents = 1; + args.phWaitEvents = dummyEvents; + args.hSignalEvent = dummyEvents[0]; + args.pSrcRegion = &dummyRegion; + expectAllApiArgsPresent(args); +} + +TEST_F(ExtractParametersTestFixture, zeCommandListAppendImageCopyFromMemory) { + Closure::ApiArgs args{}; + args.numWaitEvents = 1; + args.phWaitEvents = dummyEvents; + args.hSignalEvent = dummyEvents[0]; + args.pDstRegion = &dummyRegion; + expectAllApiArgsPresent(args); +} + +TEST_F(ExtractParametersTestFixture, zeCommandListAppendMemoryPrefetch) { + Closure::ApiArgs args{}; + expectAllApiArgsPresent(args); +} + +TEST_F(ExtractParametersTestFixture, zeCommandListAppendMemAdvise) { + Closure::ApiArgs args{}; + expectAllApiArgsPresent(args); +} + +TEST_F(ExtractParametersTestFixture, zeCommandListAppendSignalEvent) { + Closure::ApiArgs args{}; + expectAllApiArgsPresent(args); +} + +TEST_F(ExtractParametersTestFixture, zeCommandListAppendWaitOnEvents) { + Closure::ApiArgs args{}; + args.numEvents = 1; + args.phEvents = dummyEvents; + expectAllApiArgsPresent(args); +} + +TEST_F(ExtractParametersTestFixture, zeCommandListAppendEventReset) { + Closure::ApiArgs args{}; + expectAllApiArgsPresent(args); +} + +TEST_F(ExtractParametersTestFixture, zeCommandListAppendQueryKernelTimestamps) { + Closure::ApiArgs args{}; + args.numEvents = 1; + args.phEvents = dummyEvents; + args.numWaitEvents = 1; + args.phWaitEvents = dummyEvents; + args.hSignalEvent = dummyEvents[0]; + args.pOffsets = dummyOffsets; + expectAllApiArgsPresent(args); +} + +TEST_F(ExtractParametersTestFixture, zeCommandListAppendLaunchKernel) { + Closure::ApiArgs args{}; + args.numWaitEvents = 1; + args.phWaitEvents = dummyEvents; + args.hSignalEvent = dummyEvents[0]; + args.kernelHandle = &kernel; + args.launchKernelArgs = &dummyLaunchArgs; + expectAllApiArgsPresent(args); +} + +TEST_F(ExtractParametersTestFixture, zeCommandListAppendLaunchCooperativeKernel) { + Closure::ApiArgs args{}; + args.numWaitEvents = 1; + args.phWaitEvents = dummyEvents; + args.hSignalEvent = dummyEvents[0]; + args.kernelHandle = &kernel; + args.launchKernelArgs = &dummyLaunchArgs; + expectAllApiArgsPresent(args); +} + +TEST_F(ExtractParametersTestFixture, zeCommandListAppendLaunchKernelIndirect) { + Closure::ApiArgs args{}; + args.numWaitEvents = 1; + args.phWaitEvents = dummyEvents; + args.hSignalEvent = dummyEvents[0]; + args.kernelHandle = &kernel; + args.launchArgsBuffer = &dummyLaunchArgs; + expectAllApiArgsPresent(args); +} + +TEST_F(ExtractParametersTestFixture, zeCommandListAppendLaunchKernelWithParameters) { + // currently skipped as zeCommandListAppendLaunchKernelWithParameters is not supported yet + Closure::ApiArgs args{nullptr}; + args.numWaitEvents = 1; + args.phWaitEvents = dummyEvents; + args.hSignalEvent = dummyEvents[0]; + expectAllApiArgsPresent(args); +} + +TEST_F(ExtractParametersTestFixture, zeCommandListAppendLaunchMultipleKernelsIndirect) { + Closure::ApiArgs args{}; + args.numWaitEvents = 1; + args.phWaitEvents = dummyEvents; + args.hSignalEvent = dummyEvents[0]; + args.numKernels = 1; + args.phKernels = dummyKernels; + args.pCountBuffer = dummyCountBuffer; + args.launchArgsBuffer = &dummyLaunchArgs; + expectAllApiArgsPresent(args); +} + +TEST_F(ExtractParametersTestFixture, zeCommandListAppendSignalExternalSemaphoreExt) { + Closure::ApiArgs args{}; + args.numWaitEvents = 1; + args.phWaitEvents = dummyEvents; + args.hSignalEvent = dummyEvents[0]; + args.numSemaphores = 1; + args.phSemaphores = dummySemaphores; + args.signalParams = &dummySignalParams; + expectAllApiArgsPresent(args); +} + +TEST_F(ExtractParametersTestFixture, zeCommandListAppendWaitExternalSemaphoreExt) { + Closure::ApiArgs args{}; + args.numWaitEvents = 1; + args.phWaitEvents = dummyEvents; + args.hSignalEvent = dummyEvents[0]; + args.numSemaphores = 1; + args.phSemaphores = dummySemaphores; + args.waitParams = &dummyWaitParams; + expectAllApiArgsPresent(args); +} + +TEST_F(ExtractParametersTestFixture, zeCommandListAppendImageCopyToMemoryExt) { + Closure::ApiArgs args{}; + args.numWaitEvents = 1; + args.phWaitEvents = dummyEvents; + args.hSignalEvent = dummyEvents[0]; + args.pSrcRegion = &dummyRegion; + expectAllApiArgsPresent(args); +} + +TEST_F(ExtractParametersTestFixture, zeCommandListAppendImageCopyFromMemoryExt) { + Closure::ApiArgs args{}; + args.numWaitEvents = 1; + args.phWaitEvents = dummyEvents; + args.hSignalEvent = dummyEvents[0]; + args.pDstRegion = &dummyRegion; + expectAllApiArgsPresent(args); +} + +TEST_F(ExtractParametersTestFixture, GivenMultipleWaitEventsWhenExtractParametersIsCalledThenParametersAreExtractedCorrectly) { + ze_event_handle_t multipleWaitEvents[2] = { + reinterpret_cast(0x100), + reinterpret_cast(0x200)}; + + Closure::ApiArgs args{}; + args.numWaitEvents = 2; + args.phWaitEvents = multipleWaitEvents; + args.hSignalEvent = dummyEvents[0]; + Closure closure(args, storage); + auto params = GraphDumpHelper::extractParameters(closure, storage); + + EXPECT_TRUE(hasParam(params, "phWaitEvents")); + EXPECT_TRUE(hasParam(params, "phWaitEvents[0]")); + EXPECT_TRUE(hasParam(params, "phWaitEvents[1]")); + + std::string waitEventStr1 = GraphDumpHelper::formatPointer(multipleWaitEvents[0]); + std::string waitEventStr2 = GraphDumpHelper::formatPointer(multipleWaitEvents[1]); + EXPECT_EQ(getParamValue(params, "phWaitEvents[0]"), waitEventStr1); + EXPECT_EQ(getParamValue(params, "phWaitEvents[1]"), waitEventStr2); +} + +TEST_F(ExtractParametersTestFixture, GivenMultipleEventsWhenExtractParametersIsCalledThenParametersAreExtractedCorrectly) { + constexpr uintptr_t firstEventAddress = 0x400; + constexpr uintptr_t secondEventAddress = 0x500; + ze_event_handle_t multipleEvents[2] = { + reinterpret_cast(firstEventAddress), + reinterpret_cast(secondEventAddress)}; + + Closure::ApiArgs args{}; + args.numEvents = 2; + args.phEvents = multipleEvents; + + Closure closure(args, storage); + auto params = GraphDumpHelper::extractParameters(closure, storage); + + EXPECT_TRUE(hasParam(params, "phEvents")); + EXPECT_TRUE(hasParam(params, "phEvents[0]")); + EXPECT_TRUE(hasParam(params, "phEvents[1]")); + + std::string eventStr1 = GraphDumpHelper::formatPointer(multipleEvents[0]); + std::string eventStr2 = GraphDumpHelper::formatPointer(multipleEvents[1]); + EXPECT_EQ(getParamValue(params, "phEvents[0]"), eventStr1); + EXPECT_EQ(getParamValue(params, "phEvents[1]"), eventStr2); +} + +TEST_F(ExtractParametersTestFixture, GivenMultipleSemaphoresWhenExtractParametersIsCalledThenParametersAreExtractedCorrectly) { + constexpr uintptr_t firstSemaphoreAddress = 0x600; + constexpr uintptr_t secondSemaphoreAddress = 0x700; + ze_external_semaphore_ext_handle_t multipleSemaphores[2] = { + reinterpret_cast(firstSemaphoreAddress), + reinterpret_cast(secondSemaphoreAddress)}; + + Closure::ApiArgs args{}; + args.numSemaphores = 2; + args.phSemaphores = multipleSemaphores; + args.signalParams = &dummySignalParams; + + Closure closure(args, storage); + auto params = GraphDumpHelper::extractParameters(closure, storage); + + EXPECT_TRUE(hasParam(params, "phSemaphores")); + EXPECT_TRUE(hasParam(params, "phSemaphores[0]")); + EXPECT_TRUE(hasParam(params, "phSemaphores[1]")); + + std::string semaphoreStr1 = GraphDumpHelper::formatPointer(multipleSemaphores[0]); + std::string semaphoreStr2 = GraphDumpHelper::formatPointer(multipleSemaphores[1]); + EXPECT_EQ(getParamValue(params, "phSemaphores[0]"), semaphoreStr1); + EXPECT_EQ(getParamValue(params, "phSemaphores[1]"), semaphoreStr2); +} + +TEST_F(ExtractParametersTestFixture, GivenZeroEventsWhenExtractParametersIsCalledThenNoIndividualEntriesArePresent) { + Closure::ApiArgs args{}; + args.numEvents = 0; + args.phEvents = nullptr; + + Closure closure(args, storage); + auto params = GraphDumpHelper::extractParameters(closure, storage); + + EXPECT_TRUE(hasParam(params, "hCommandList")); + EXPECT_FALSE(hasParam(params, "numEvents")); + EXPECT_FALSE(hasParam(params, "phEvents")); + EXPECT_FALSE(hasParam(params, "phEvents[0]")); +} + +TEST_F(ExtractParametersTestFixture, GivenNoOptionalRegionParametersWhenExtractParametersIsCalledThenNoRegionEntriesArePresent) { + Closure::ApiArgs args{}; + Closure closure(args, storage); + auto params = GraphDumpHelper::extractParameters(closure, storage); + + EXPECT_TRUE(hasParam(params, "hCommandList")); + EXPECT_FALSE(hasParam(params, "pSrcRegion")); + EXPECT_FALSE(hasParam(params, "pDstRegion")); +} + +TEST_F(ExtractParametersTestFixture, GivenNoOptionalOffsetsParameterWhenExtractParametersIsCalledThenNoOffsetsEntryIsPresent) { + Closure::ApiArgs args{}; + Closure closure(args, storage); + auto params = GraphDumpHelper::extractParameters(closure, storage); + + EXPECT_TRUE(hasParam(params, "hCommandList")); + EXPECT_FALSE(hasParam(params, "pOffsets")); +} + +} // namespace ult +} // namespace L0 diff --git a/level_zero/core/test/unit_tests/experimental/test_graph_export.h b/level_zero/core/test/unit_tests/experimental/test_graph_export.h new file mode 100644 index 0000000000..abe4dc42a0 --- /dev/null +++ b/level_zero/core/test/unit_tests/experimental/test_graph_export.h @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2025 Intel Corporation + * + * SPDX-License-Identifier: MIT + * + */ + +#include "shared/test/common/helpers/variable_backup.h" +#include "shared/test/common/mocks/mock_io_functions.h" + +#include "level_zero/core/test/unit_tests/experimental/test_graph.h" +#include "level_zero/experimental/source/graph/captured_apis/graph_captured_apis.h" +#include "level_zero/experimental/source/graph/graph_export.h" + +#include "gtest/gtest.h" + +using namespace NEO; + +namespace L0 { +namespace ult { + +class MockGraphDotExporter : public GraphDotExporter { + public: + using GraphDotExporter::exportToString; + using GraphDotExporter::findSubgraphIndex; + using GraphDotExporter::findSubgraphIndexByCommandList; + using GraphDotExporter::generateNodeId; + using GraphDotExporter::generateSubgraphId; + using GraphDotExporter::getCommandNodeAttributes; + using GraphDotExporter::getCommandNodeLabel; + using GraphDotExporter::getSubgraphFillColor; + using GraphDotExporter::writeEdges; + using GraphDotExporter::writeForkJoinEdges; + using GraphDotExporter::writeHeader; + using GraphDotExporter::writeNodes; + using GraphDotExporter::writeSubgraphs; + using GraphDotExporter::writeUnjoinedForkEdges; +}; + +class GraphDotExporterTest : public ::testing::Test { + protected: + GraphsCleanupGuard graphCleanup; + Mock ctx; + MockGraphDotExporter exporter; + const std::string testFilePath = "test_graph_export.gv"; +}; + +class GraphDotExporterFileTest : public GraphDotExporterTest { + protected: + void SetUp() override { + GraphDotExporterTest::SetUp(); + + fopenBackup = std::make_unique>(&NEO::IoFunctions::fopenPtr, NEO::IoFunctions::mockFopen); + fwriteBackup = std::make_unique>(&NEO::IoFunctions::fwritePtr, NEO::IoFunctions::mockFwrite); + fcloseBackup = std::make_unique>(&NEO::IoFunctions::fclosePtr, NEO::IoFunctions::mockFclose); + + mockFopenReturnedBackup = std::make_unique>(&IoFunctions::mockFopenReturned); + + mockFopenCalledBefore = NEO::IoFunctions::mockFopenCalled; + mockFwriteCalledBefore = NEO::IoFunctions::mockFwriteCalled; + mockFcloseCalledBefore = NEO::IoFunctions::mockFcloseCalled; + } + + void setupSuccessfulWrite(Graph &testGraph) { + std::string expectedContent = exporter.exportToString(testGraph); + ASSERT_NE(expectedContent.size(), 0U); + + mockFwriteReturnBackup = std::make_unique>(&NEO::IoFunctions::mockFwriteReturn, expectedContent.size()); + + if (expectedContent.size() > 0) { + buffer = std::make_unique(expectedContent.size() + 1); + memset(buffer.get(), 0, expectedContent.size() + 1); + mockFwriteBufferBackup = std::make_unique>(&NEO::IoFunctions::mockFwriteBuffer, buffer.get()); + } + } + + void setupFailedOpen() { + *mockFopenReturnedBackup = static_cast(nullptr); + } + + void setupFailedWrite() { + mockFwriteReturnBackup = std::make_unique>(&NEO::IoFunctions::mockFwriteReturn, static_cast(0)); + } + + std::string getWrittenContent() const { + return buffer ? std::string(buffer.get()) : std::string{}; + } + + std::unique_ptr> fopenBackup; + std::unique_ptr> fwriteBackup; + std::unique_ptr> fcloseBackup; + std::unique_ptr> mockFopenReturnedBackup; + std::unique_ptr> mockFwriteReturnBackup; + std::unique_ptr> mockFwriteBufferBackup; + std::unique_ptr buffer; + + uint32_t mockFopenCalledBefore; + uint32_t mockFwriteCalledBefore; + uint32_t mockFcloseCalledBefore; +}; + +#define DEFINE_APIARGS_FIELDS(Api, ...) \ + template <> \ + std::set ExtractParametersTestFixture::getApiArgsFieldNames::ApiArgs>() { \ + return {__VA_ARGS__}; \ + } + +class ExtractParametersTestFixture : public ::testing::Test { + protected: + ClosureExternalStorage storage; + + ze_event_handle_t dummyEvents[1] = {reinterpret_cast(0x1000)}; + ze_image_region_t dummyRegion = {}; + size_t dummyRangeSizes[1] = {1024}; + const void *dummyRanges[1] = {reinterpret_cast(0x1000)}; + uint64_t dummyOffsets[1] = {0}; + ze_external_semaphore_ext_handle_t dummySemaphores[1] = {reinterpret_cast(0x1)}; + ze_external_semaphore_signal_params_ext_t dummySignalParams = {}; + ze_external_semaphore_wait_params_ext_t dummyWaitParams = {}; + uint32_t dummyCountBuffer[1] = {1}; + ze_group_count_t dummyLaunchArgs = {1, 1, 1}; + Mock kernel; + ze_kernel_handle_t dummyKernels[1]; + + void SetUp() override { + dummyKernels[0] = &kernel; + } + + template + void expectAllApiArgsPresent(const ApiArgsT &args) { + Closure closure(args, storage); + auto params = GraphDumpHelper::extractParameters(closure, storage); + if (!closure.isSupported) { + GTEST_SKIP(); + } + + std::set paramNames; + for (const auto ¶m : params) { + paramNames.insert(param.first); + } + std::set expectedFields = getApiArgsFieldNames(); + EXPECT_EQ(paramNames, expectedFields); + } + + template + std::set getApiArgsFieldNames() { return {}; }; + + bool hasParam(const std::vector> ¶ms, const std::string &name) { + return std::find_if(params.begin(), params.end(), + [&name](const auto ¶m) { return param.first == name; }) != params.end(); + } + + std::string getParamValue(const std::vector> ¶ms, const std::string &name) { + auto iter = std::find_if(params.begin(), params.end(), + [&name](const auto ¶m) { return param.first == name; }); + return iter != params.end() ? iter->second : std::string{}; + } +}; + +} // namespace ult +} // namespace L0 diff --git a/level_zero/core/test/unit_tests/experimental/test_graph_exporter.cpp b/level_zero/core/test/unit_tests/experimental/test_graph_exporter.cpp deleted file mode 100644 index 6a98dbdfae..0000000000 --- a/level_zero/core/test/unit_tests/experimental/test_graph_exporter.cpp +++ /dev/null @@ -1,535 +0,0 @@ -/* - * Copyright (C) 2025 Intel Corporation - * - * SPDX-License-Identifier: MIT - * - */ - -#include "shared/test/common/helpers/variable_backup.h" -#include "shared/test/common/mocks/mock_io_functions.h" - -#include "level_zero/core/test/unit_tests/experimental/test_graph.h" - -#include "gtest/gtest.h" - -using namespace NEO; - -namespace L0 { -namespace ult { - -class MockGraphDotExporter : public GraphDotExporter { - public: - using GraphDotExporter::exportToString; - using GraphDotExporter::findSubgraphIndex; - using GraphDotExporter::findSubgraphIndexByCommandList; - using GraphDotExporter::generateNodeId; - using GraphDotExporter::generateSubgraphId; - using GraphDotExporter::getCommandNodeAttributes; - using GraphDotExporter::getCommandNodeLabel; - using GraphDotExporter::getSubgraphFillColor; - using GraphDotExporter::writeEdges; - using GraphDotExporter::writeForkJoinEdges; - using GraphDotExporter::writeHeader; - using GraphDotExporter::writeNodes; - using GraphDotExporter::writeSubgraphs; - using GraphDotExporter::writeUnjoinedForkEdges; -}; - -class GraphDotExporterTest : public ::testing::Test { - protected: - GraphsCleanupGuard graphCleanup; - Mock ctx; - MockGraphDotExporter exporter; - const std::string testFilePath = "test_graph_export.gv"; -}; - -TEST_F(GraphDotExporterTest, GivenNullFilePathWhenExportToFileThenReturnsInvalidArgument) { - Graph testGraph{&ctx, true}; - auto result = exporter.exportToFile(testGraph, nullptr); - EXPECT_EQ(ZE_RESULT_ERROR_INVALID_ARGUMENT, result); -} - -TEST_F(GraphDotExporterTest, GivenEmptyGraphWhenExportToStringThenContainsDigraphHeader) { - Graph testGraph{&ctx, true}; - - std::string dot = exporter.exportToString(testGraph); - EXPECT_NE(dot.find("digraph \"graph\" {"), std::string::npos); - EXPECT_NE(dot.find("rankdir=TB;"), std::string::npos); - EXPECT_NE(dot.find("nodesep=1;"), std::string::npos); - EXPECT_NE(dot.find("ranksep=1;"), std::string::npos); - EXPECT_NE(dot.find("node [shape=box, style=filled];"), std::string::npos); - EXPECT_NE(dot.find("edge [color=black];"), std::string::npos); - EXPECT_NE(dot.find('}'), std::string::npos); -} - -TEST_F(GraphDotExporterTest, GivenGraphWithSingleCommandWhenExportToStringThenContainsCommandNode) { - Graph testGraph{&ctx, true}; - Mock event; - Mock cmdlist; - - testGraph.capture(&cmdlist, &event, 0U, nullptr); - testGraph.stopCapturing(); - - std::string dot = exporter.exportToString(testGraph); - EXPECT_NE(dot.find("zeCommandListAppendBarrier"), std::string::npos); - EXPECT_NE(dot.find("L0_S0_C0"), std::string::npos); -} - -TEST_F(GraphDotExporterTest, GivenGraphWithMultipleCommandsWhenExportToStringThenContainsSequentialEdges) { - Graph testGraph{&ctx, true}; - Mock event; - Mock cmdlist; - - testGraph.capture(&cmdlist, &event, 0U, nullptr); - testGraph.capture(&cmdlist, nullptr, nullptr, 0U, nullptr, 0U, nullptr); - testGraph.stopCapturing(); - - std::string dot = exporter.exportToString(testGraph); - EXPECT_NE(dot.find("L0_S0_C0 -> L0_S0_C1"), std::string::npos); -} - -TEST_F(GraphDotExporterTest, WhenWriteHeaderThenGeneratesValidDotHeader) { - Graph testGraph{&ctx, true}; - - std::ostringstream dot; - exporter.writeHeader(dot); - std::string header = dot.str(); - - EXPECT_NE(header.find("digraph \"graph\" {"), std::string::npos); - EXPECT_NE(header.find("rankdir=TB;"), std::string::npos); - EXPECT_NE(header.find("nodesep=1;"), std::string::npos); - EXPECT_NE(header.find("ranksep=1;"), std::string::npos); - EXPECT_NE(header.find("node [shape=box, style=filled];"), std::string::npos); - EXPECT_NE(header.find("edge [color=black];"), std::string::npos); -} - -TEST_F(GraphDotExporterTest, GivenGraphWithCommandWhenWriteNodesThenGeneratesNodeDefinitions) { - Graph testGraph{&ctx, true}; - Mock event; - Mock cmdlist; - - testGraph.capture(&cmdlist, &event, 0U, nullptr); - testGraph.stopCapturing(); - - std::ostringstream dot; - exporter.writeNodes(dot, testGraph, 0, 0); - std::string output = dot.str(); - - EXPECT_NE(output.find("L0_S0_C0"), std::string::npos); - EXPECT_NE(output.find("zeCommandListAppendBarrier"), std::string::npos); -} - -TEST_F(GraphDotExporterTest, GivenGraphWithMultipleCommandsWhenWriteEdgesThenGeneratesSequentialEdges) { - Graph testGraph{&ctx, true}; - Mock event; - Mock cmdlist; - - testGraph.capture(&cmdlist, &event, 0U, nullptr); - testGraph.capture(&cmdlist, nullptr, nullptr, 0U, nullptr, 0U, nullptr); - testGraph.stopCapturing(); - - std::ostringstream dot; - exporter.writeEdges(dot, testGraph, 0, 0); - std::string output = dot.str(); - - EXPECT_NE(output.find("L0_S0_C0 -> L0_S0_C1"), std::string::npos); -} - -TEST_F(GraphDotExporterTest, GivenGraphWithCommandWhenGetCommandNodeLabelThenReturnsCorrectLabel) { - Graph testGraph{&ctx, true}; - Mock event; - Mock cmdlist; - - testGraph.capture(&cmdlist, &event, 0U, nullptr); - testGraph.stopCapturing(); - - std::string label = exporter.getCommandNodeLabel(testGraph, 0); - EXPECT_EQ(label, "zeCommandListAppendBarrier"); -} - -TEST_F(GraphDotExporterTest, GivenDifferentCommandTypesWhenGetCommandNodeAttributesThenReturnsCorrectColors) { - Graph testGraph{&ctx, true}; - Mock event; - Mock cmdlist; - - testGraph.capture(&cmdlist, &event, 0U, nullptr); - testGraph.capture(&cmdlist, nullptr, nullptr, 0U, nullptr, 0U, nullptr); - testGraph.capture(&cmdlist, &event); - - testGraph.stopCapturing(); - - EXPECT_EQ(exporter.getCommandNodeAttributes(testGraph, 0), ", fillcolor=orange"); - EXPECT_EQ(exporter.getCommandNodeAttributes(testGraph, 1), ", fillcolor=lightblue"); - EXPECT_EQ(exporter.getCommandNodeAttributes(testGraph, 2), ", fillcolor=yellow"); -} - -TEST_F(GraphDotExporterTest, WhenGenerateNodeIdThenReturnsCorrectFormat) { - EXPECT_EQ(exporter.generateNodeId(0, 0, 0), "L0_S0_C0"); - EXPECT_EQ(exporter.generateNodeId(1, 2, 3), "L1_S2_C3"); - EXPECT_EQ(exporter.generateNodeId(10, 20, 30), "L10_S20_C30"); -} - -TEST_F(GraphDotExporterTest, WhenGenerateSubgraphIdThenReturnsCorrectFormat) { - EXPECT_EQ(exporter.generateSubgraphId(0, 0), "L0_S0"); - EXPECT_EQ(exporter.generateSubgraphId(1, 2), "L1_S2"); - EXPECT_EQ(exporter.generateSubgraphId(10, 20), "L10_S20"); -} - -TEST_F(GraphDotExporterTest, WhenGetSubgraphFillColorThenReturnsCorrectColors) { - EXPECT_EQ(exporter.getSubgraphFillColor(1), "grey90"); - EXPECT_EQ(exporter.getSubgraphFillColor(2), "grey80"); - EXPECT_EQ(exporter.getSubgraphFillColor(3), "grey70"); - EXPECT_EQ(exporter.getSubgraphFillColor(4), "grey60"); - EXPECT_EQ(exporter.getSubgraphFillColor(5), "grey50"); -} - -TEST_F(GraphDotExporterTest, GivenDeepLevelWhenGetSubgraphFillColorThenReturnsDeepestColor) { - EXPECT_EQ(exporter.getSubgraphFillColor(6), "grey50"); - EXPECT_EQ(exporter.getSubgraphFillColor(10), "grey50"); - EXPECT_EQ(exporter.getSubgraphFillColor(100), "grey50"); -} - -TEST_F(GraphDotExporterTest, GivenGraphWithoutSubgraphsWhenWriteSubgraphsThenGeneratesNoOutput) { - Graph testGraph{&ctx, true}; - std::ostringstream dot; - exporter.writeSubgraphs(dot, testGraph, 0); - std::string output = dot.str(); - - EXPECT_TRUE(output.empty()); -} - -TEST_F(GraphDotExporterTest, GivenGraphWithSubgraphsWhenWriteSubgraphsThenGeneratesSubgraphStructure) { - Graph testGraph{&ctx, true}; - Mock forkEvent; - Mock joinEvent; - Mock mainCmdList; - Mock subCmdList; - - Graph *testGraphPtr = &testGraph; - captureCommand(mainCmdList, testGraphPtr, &mainCmdList, &forkEvent, 0U, nullptr); - - Graph *subGraph = nullptr; - testGraph.forkTo(subCmdList, subGraph, forkEvent); - ASSERT_NE(subGraph, nullptr); - - captureCommand(subCmdList, subGraph, &subCmdList, nullptr, nullptr, 0U, nullptr, 0U, nullptr); - captureCommand(subCmdList, subGraph, &subCmdList, &joinEvent, 0U, nullptr); - - testGraph.tryJoinOnNextCommand(subCmdList, joinEvent); - captureCommand(mainCmdList, testGraphPtr, &mainCmdList, nullptr, 0U, nullptr); - testGraph.stopCapturing(); - - std::ostringstream dot; - exporter.writeSubgraphs(dot, testGraph, 0); - std::string output = dot.str(); - - EXPECT_NE(output.find("// Subgraphs:"), std::string::npos); - EXPECT_NE(output.find("subgraph cluster_L1_S0"), std::string::npos); - EXPECT_NE(output.find("label=\"Subgraph 1-0\""), std::string::npos); - EXPECT_NE(output.find("style=filled"), std::string::npos); - EXPECT_NE(output.find("fillcolor=grey90"), std::string::npos); -} - -TEST_F(GraphDotExporterTest, GivenGraphWithNestedSubgraphsWhenWriteSubgraphsThenGeneratesNestedStructure) { - Graph testGraph{&ctx, true}; - Mock forkEvent1; - Mock forkEvent2; - Mock joinEvent1; - Mock joinEvent2; - Mock mainCmdList; - Mock subCmdList1; - Mock subCmdList2; - - Graph *testGraphPtr = &testGraph; - captureCommand(mainCmdList, testGraphPtr, &mainCmdList, &forkEvent1, 0U, nullptr); - - Graph *subGraph1 = nullptr; - testGraph.forkTo(subCmdList1, subGraph1, forkEvent1); - ASSERT_NE(subGraph1, nullptr); - - captureCommand(subCmdList1, subGraph1, &subCmdList1, nullptr, nullptr, 0U, nullptr, 0U, nullptr); - - Graph *subGraph2 = nullptr; - captureCommand(subCmdList1, subGraph1, &subCmdList1, &forkEvent2, 0U, nullptr); - subGraph1->forkTo(subCmdList2, subGraph2, forkEvent2); - ASSERT_NE(subGraph2, nullptr); - - captureCommand(subCmdList2, subGraph2, &subCmdList2, &joinEvent2, 0U, nullptr); - - subGraph1->tryJoinOnNextCommand(subCmdList2, joinEvent2); - captureCommand(subCmdList1, subGraph1, &subCmdList1, &joinEvent1, 0U, nullptr); - - testGraph.tryJoinOnNextCommand(subCmdList1, joinEvent1); - testGraph.stopCapturing(); - - std::ostringstream dot; - exporter.writeSubgraphs(dot, testGraph, 0); - std::string output = dot.str(); - - EXPECT_NE(output.find("subgraph cluster_L1_S0"), std::string::npos); - EXPECT_NE(output.find("subgraph cluster_L2_S0"), std::string::npos); - EXPECT_NE(output.find("fillcolor=grey90"), std::string::npos); - EXPECT_NE(output.find("fillcolor=grey80"), std::string::npos); -} - -TEST_F(GraphDotExporterTest, GivenGraphWithAdjacentSubgraphsWhenWriteSubgraphsThenGeneratesMultipleSubgraphs) { - Graph testGraph{&ctx, true}; - Mock forkEvent1; - Mock forkEvent2; - Mock joinEvent1; - Mock joinEvent2; - Mock mainCmdList; - Mock subCmdList1; - Mock subCmdList2; - - Graph *testGraphPtr = &testGraph; - captureCommand(mainCmdList, testGraphPtr, &mainCmdList, &forkEvent1, 0U, nullptr); - - Graph *subGraph1 = nullptr; - testGraph.forkTo(subCmdList1, subGraph1, forkEvent1); - ASSERT_NE(subGraph1, nullptr); - - captureCommand(subCmdList1, subGraph1, &subCmdList1, nullptr, nullptr, 0U, nullptr, 0U, nullptr); - captureCommand(subCmdList1, subGraph1, &subCmdList1, &joinEvent1, 0U, nullptr); - - testGraph.tryJoinOnNextCommand(subCmdList1, joinEvent1); - captureCommand(mainCmdList, testGraphPtr, &mainCmdList, &forkEvent2, 0U, nullptr); - - Graph *subGraph2 = nullptr; - testGraph.forkTo(subCmdList2, subGraph2, forkEvent2); - ASSERT_NE(subGraph2, nullptr); - - captureCommand(subCmdList2, subGraph2, &subCmdList2, nullptr, nullptr, 0U, nullptr, 0U, nullptr); - captureCommand(subCmdList2, subGraph2, &subCmdList2, &joinEvent2, 0U, nullptr); - - testGraph.tryJoinOnNextCommand(subCmdList2, joinEvent2); - captureCommand(mainCmdList, testGraphPtr, &mainCmdList, nullptr, 0U, nullptr); - testGraph.stopCapturing(); - - std::ostringstream dot; - exporter.writeSubgraphs(dot, testGraph, 0); - std::string output = dot.str(); - - EXPECT_NE(output.find("// Subgraphs:"), std::string::npos); - EXPECT_NE(output.find("subgraph cluster_L1_S0"), std::string::npos); - EXPECT_NE(output.find("subgraph cluster_L1_S1"), std::string::npos); - EXPECT_NE(output.find("label=\"Subgraph 1-0\""), std::string::npos); - EXPECT_NE(output.find("label=\"Subgraph 1-1\""), std::string::npos); -} - -TEST_F(GraphDotExporterTest, WhenFindSubgraphIndexWithInvalidSubgraphThenReturnsNullopt) { - const StackVec subGraphs; - Graph fakeSubgraph{&ctx, true}; - auto index = exporter.findSubgraphIndex(subGraphs, &fakeSubgraph); - - EXPECT_FALSE(index.has_value()); -} - -TEST_F(GraphDotExporterTest, WhenFindSubgraphIndexWithValidGraphThenReturnsCorrectIndex) { - Graph testGraph{&ctx, true}; - Mock forkEvent; - Mock joinEvent; - Mock mainCmdList; - Mock subCmdList; - - Graph *testGraphPtr = &testGraph; - captureCommand(mainCmdList, testGraphPtr, &mainCmdList, &forkEvent, 0U, nullptr); - - Graph *subGraph = nullptr; - testGraph.forkTo(subCmdList, subGraph, forkEvent); - ASSERT_NE(subGraph, nullptr); - - testGraph.tryJoinOnNextCommand(subCmdList, joinEvent); - captureCommand(mainCmdList, testGraphPtr, &mainCmdList, nullptr, 0U, nullptr); - testGraph.stopCapturing(); - - const auto &subGraphs = testGraph.getSubgraphs(); - auto index = exporter.findSubgraphIndex(subGraphs, subGraph); - - ASSERT_TRUE(index.has_value()); - EXPECT_EQ(index.value(), 0U); -} - -TEST_F(GraphDotExporterTest, WhenFindSubgraphIndexByCommandListWithInvalidCommandListThenReturnsNullopt) { - const StackVec subGraphs; - Mock fakeCmdList; - auto index = exporter.findSubgraphIndexByCommandList(subGraphs, &fakeCmdList); - - EXPECT_FALSE(index.has_value()); -} - -TEST_F(GraphDotExporterTest, GivenGraphWithEmptySubgraphWhenWriteForkJoinEdgesThenNoEdges) { - Graph testGraph{&ctx, true}; - Mock forkEvent; - Mock joinEvent; - Mock mainCmdList; - Mock subCmdList; - - Graph *testGraphPtr = &testGraph; - captureCommand(mainCmdList, testGraphPtr, &mainCmdList, &forkEvent, 0U, nullptr); - - Graph *subGraph = nullptr; - testGraph.forkTo(subCmdList, subGraph, forkEvent); - ASSERT_NE(subGraph, nullptr); - - testGraph.tryJoinOnNextCommand(subCmdList, joinEvent); - captureCommand(mainCmdList, testGraphPtr, &mainCmdList, nullptr, 0U, nullptr); - testGraph.stopCapturing(); - - std::ostringstream dot; - exporter.writeForkJoinEdges(dot, testGraph, 0, 0); - std::string output = dot.str(); - - EXPECT_EQ(output.find("->"), std::string::npos); -} - -TEST_F(GraphDotExporterTest, GivenGraphWithUnjoinedForksWhenWriteUnjoinedForkEdgesThenGeneratesUnjoinedEdges) { - Graph testGraph{&ctx, true}; - Mock forkEvent; - Mock mainCmdList; - Mock subCmdList; - - Graph *testGraphPtr = &testGraph; - captureCommand(mainCmdList, testGraphPtr, &mainCmdList, &forkEvent, 0U, nullptr); - - Graph *subGraph = nullptr; - testGraph.forkTo(subCmdList, subGraph, forkEvent); - ASSERT_NE(subGraph, nullptr); - - captureCommand(subCmdList, subGraph, &subCmdList, nullptr, nullptr, 0U, nullptr, 0U, nullptr); - - std::ostringstream dot; - exporter.writeUnjoinedForkEdges(dot, testGraph, 0, 0); - std::string output = dot.str(); - - EXPECT_NE(output.find("// Unjoined forks:"), std::string::npos); - EXPECT_NE(output.find("L0_S0_C0 -> L1_S0_C0 [color=red, label=\"unjoined fork\"];"), std::string::npos); - - // Prevent double free with unjoined forks - Mock joinEvent; - testGraph.tryJoinOnNextCommand(subCmdList, joinEvent); - captureCommand(mainCmdList, testGraphPtr, &mainCmdList, &forkEvent, 0U, nullptr); - testGraph.stopCapturing(); -} - -TEST_F(GraphDotExporterTest, GivenGraphWithEmptyUnjoinedSubgraphWhenWriteUnjoinedForkEdgesThenOutputIsEmpty) { - Graph testGraph{&ctx, true}; - Mock forkEvent; - Mock mainCmdList; - Mock subCmdList; - - Graph *testGraphPtr = &testGraph; - captureCommand(mainCmdList, testGraphPtr, &mainCmdList, &forkEvent, 0U, nullptr); - - Graph *subGraph = nullptr; - testGraph.forkTo(subCmdList, subGraph, forkEvent); - ASSERT_NE(subGraph, nullptr); - - std::ostringstream dot; - exporter.writeUnjoinedForkEdges(dot, testGraph, 0, 0); - std::string output = dot.str(); - - EXPECT_EQ(output.find("L0_S0_C0 -> L1_S0_C0 [color=red, label=\"unjoined fork\"];"), std::string::npos); - - // Prevent double free with unjoined forks - Mock joinEvent; - testGraph.tryJoinOnNextCommand(subCmdList, joinEvent); - captureCommand(mainCmdList, testGraphPtr, &mainCmdList, &forkEvent, 0U, nullptr); - testGraph.stopCapturing(); -} - -class GraphDotExporterFileTest : public GraphDotExporterTest { - protected: - void SetUp() override { - GraphDotExporterTest::SetUp(); - - fopenBackup = std::make_unique>(&NEO::IoFunctions::fopenPtr, NEO::IoFunctions::mockFopen); - fwriteBackup = std::make_unique>(&NEO::IoFunctions::fwritePtr, NEO::IoFunctions::mockFwrite); - fcloseBackup = std::make_unique>(&NEO::IoFunctions::fclosePtr, NEO::IoFunctions::mockFclose); - - mockFopenReturnedBackup = std::make_unique>(&IoFunctions::mockFopenReturned); - - mockFopenCalledBefore = NEO::IoFunctions::mockFopenCalled; - mockFwriteCalledBefore = NEO::IoFunctions::mockFwriteCalled; - mockFcloseCalledBefore = NEO::IoFunctions::mockFcloseCalled; - } - - void setupSuccessfulWrite(Graph &testGraph) { - std::string expectedContent = exporter.exportToString(testGraph); - ASSERT_NE(expectedContent.size(), 0U); - - mockFwriteReturnBackup = std::make_unique>(&NEO::IoFunctions::mockFwriteReturn, expectedContent.size()); - - if (expectedContent.size() > 0) { - buffer = std::make_unique(expectedContent.size() + 1); - memset(buffer.get(), 0, expectedContent.size() + 1); - mockFwriteBufferBackup = std::make_unique>(&NEO::IoFunctions::mockFwriteBuffer, buffer.get()); - } - } - - void setupFailedOpen() { - *mockFopenReturnedBackup = static_cast(nullptr); - } - - void setupFailedWrite() { - mockFwriteReturnBackup = std::make_unique>(&NEO::IoFunctions::mockFwriteReturn, static_cast(0)); - } - - std::string getWrittenContent() const { - return buffer ? std::string(buffer.get()) : std::string{}; - } - - std::unique_ptr> fopenBackup; - std::unique_ptr> fwriteBackup; - std::unique_ptr> fcloseBackup; - std::unique_ptr> mockFopenReturnedBackup; - std::unique_ptr> mockFwriteReturnBackup; - std::unique_ptr> mockFwriteBufferBackup; - std::unique_ptr buffer; - - uint32_t mockFopenCalledBefore; - uint32_t mockFwriteCalledBefore; - uint32_t mockFcloseCalledBefore; -}; - -TEST_F(GraphDotExporterFileTest, GivenEmptyGraphWhenExportToFileThenWritesValidDotContent) { - Graph testGraph{&ctx, true}; - setupSuccessfulWrite(testGraph); - - auto result = exporter.exportToFile(testGraph, testFilePath.c_str()); - EXPECT_EQ(ZE_RESULT_SUCCESS, result); - - std::string writtenContent = getWrittenContent(); - EXPECT_NE(writtenContent.find("digraph \"graph\" {"), std::string::npos); - EXPECT_NE(writtenContent.find('}'), std::string::npos); - - EXPECT_EQ(mockFopenCalledBefore + 1, NEO::IoFunctions::mockFopenCalled); - EXPECT_EQ(mockFwriteCalledBefore + 1, NEO::IoFunctions::mockFwriteCalled); - EXPECT_EQ(mockFcloseCalledBefore + 1, NEO::IoFunctions::mockFcloseCalled); -} - -TEST_F(GraphDotExporterFileTest, GivenFailedFileOpenWhenExportToFileThenReturnsUnknownError) { - Graph testGraph{&ctx, true}; - setupFailedOpen(); - - auto result = exporter.exportToFile(testGraph, testFilePath.c_str()); - EXPECT_EQ(ZE_RESULT_ERROR_UNKNOWN, result); - - EXPECT_EQ(mockFopenCalledBefore + 1, NEO::IoFunctions::mockFopenCalled); - EXPECT_EQ(mockFwriteCalledBefore, NEO::IoFunctions::mockFwriteCalled); -} - -TEST_F(GraphDotExporterFileTest, GivenFailedFileWriteWhenExportToFileThenReturnsUnknownError) { - Graph testGraph{&ctx, true}; - setupFailedWrite(); - - auto result = exporter.exportToFile(testGraph, testFilePath.c_str()); - EXPECT_EQ(ZE_RESULT_ERROR_UNKNOWN, result); - - EXPECT_EQ(mockFopenCalledBefore + 1, NEO::IoFunctions::mockFopenCalled); - EXPECT_EQ(mockFwriteCalledBefore + 1, NEO::IoFunctions::mockFwriteCalled); - EXPECT_EQ(mockFcloseCalledBefore + 1, NEO::IoFunctions::mockFcloseCalled); -} - -} // namespace ult -} // namespace L0 diff --git a/level_zero/experimental/source/graph/CMakeLists.txt b/level_zero/experimental/source/graph/CMakeLists.txt index 45b9a4b686..f78510ce1b 100644 --- a/level_zero/experimental/source/graph/CMakeLists.txt +++ b/level_zero/experimental/source/graph/CMakeLists.txt @@ -9,6 +9,9 @@ target_sources(${L0_STATIC_LIB_NAME} ${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt ${CMAKE_CURRENT_SOURCE_DIR}/graph.cpp ${CMAKE_CURRENT_SOURCE_DIR}/graph.h + ${CMAKE_CURRENT_SOURCE_DIR}/graph_export.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/graph_export.h + ${CMAKE_CURRENT_SOURCE_DIR}/definitions${BRANCH_DIR_SUFFIX}graph_export.inl ) add_subdirectories() diff --git a/level_zero/experimental/source/graph/captured_apis/graph_captured_apis.h b/level_zero/experimental/source/graph/captured_apis/graph_captured_apis.h index 28c566177a..52795bc789 100644 --- a/level_zero/experimental/source/graph/captured_apis/graph_captured_apis.h +++ b/level_zero/experimental/source/graph/captured_apis/graph_captured_apis.h @@ -7,9 +7,16 @@ #pragma once +#include "shared/source/helpers/string.h" #include "shared/source/utilities/stackvec.h" +#include "level_zero/core/source/kernel/kernel_mutable_state.h" +#include "level_zero/ze_api.h" + #include +#include +#include +#include namespace L0 { @@ -108,6 +115,13 @@ struct ClosureExternalStorage { return waitEvents.data() + id; } + const ze_event_handle_t *getEventsList(EventsListId id) const { + if (invalidEventsListId == id) { + return nullptr; + } + return waitEvents.data() + id; + } + KernelMutableState *getKernelMutableState(KernelStateId id) { if (invalidKernelStateId == id) { return nullptr; @@ -845,4 +859,21 @@ struct Closure { ze_result_t instantiateTo(CommandList &executionTarget, ClosureExternalStorage &externalStorage) const; }; +namespace GraphDumpHelper { + +template +std::vector> extractParameters(const Closure &closure, const ClosureExternalStorage &storage) { + return {}; +} + +#define RR_CAPTURED_API(X) \ + template <> \ + std::vector> extractParameters( \ + const Closure &closure, const ClosureExternalStorage &storage); + +RR_CAPTURED_APIS() + +#undef RR_CAPTURED_API + +} // namespace GraphDumpHelper } // namespace L0 diff --git a/level_zero/experimental/source/graph/definitions/graph_export.inl b/level_zero/experimental/source/graph/definitions/graph_export.inl new file mode 100644 index 0000000000..196735787e --- /dev/null +++ b/level_zero/experimental/source/graph/definitions/graph_export.inl @@ -0,0 +1,6 @@ +/* + * Copyright (C) 2025 Intel Corporation + * + * SPDX-License-Identifier: MIT + * + */ diff --git a/level_zero/experimental/source/graph/graph.cpp b/level_zero/experimental/source/graph/graph.cpp index 887d1aa05e..76753606df 100644 --- a/level_zero/experimental/source/graph/graph.cpp +++ b/level_zero/experimental/source/graph/graph.cpp @@ -7,15 +7,11 @@ #include "level_zero/experimental/source/graph/graph.h" -#include "shared/source/utilities/io_functions.h" - #include "level_zero/core/source/cmdlist/cmdlist.h" #include "level_zero/core/source/context/context.h" #include "level_zero/core/source/event/event.h" #include "level_zero/core/source/kernel/kernel_imp.h" -#include -#include #include namespace L0 { @@ -463,256 +459,4 @@ void recordHandleSignalEventFromPreviousCommand(L0::CommandList &srcCmdList, Gra captureTarget.registerSignallingEventFromPreviousCommand(*L0::Event::fromHandle(event)); } -ze_result_t GraphDotExporter::exportToFile(const Graph &graph, const char *filePath) const { - if (nullptr == filePath) { - return ZE_RESULT_ERROR_INVALID_ARGUMENT; - } - - FILE *file = NEO::IoFunctions::fopenPtr(filePath, "w"); - if (nullptr == file) { - return ZE_RESULT_ERROR_UNKNOWN; - } - - std::string dotContent = exportToString(graph); - size_t bytesWritten = NEO::IoFunctions::fwritePtr(dotContent.c_str(), 1, dotContent.size(), file); - NEO::IoFunctions::fclosePtr(file); - - if (bytesWritten != dotContent.size()) { - return ZE_RESULT_ERROR_UNKNOWN; - } - - return ZE_RESULT_SUCCESS; -} - -std::string GraphDotExporter::exportToString(const Graph &graph) const { - std::ostringstream dot; - - writeHeader(dot); - writeNodes(dot, graph, 0, 0); - writeEdges(dot, graph, 0, 0); - writeSubgraphs(dot, graph, 0); - - dot << "}\n"; - return dot.str(); -} - -void GraphDotExporter::writeHeader(std::ostringstream &dot) const { - dot << "digraph \"graph\" {\n"; - dot << " rankdir=TB;\n"; - dot << " nodesep=1;\n"; - dot << " ranksep=1;\n"; - dot << " node [shape=box, style=filled];\n"; - dot << " edge [color=black];\n\n"; -} - -void GraphDotExporter::writeNodes(std::ostringstream &dot, const Graph &graph, uint32_t level, uint32_t subgraphId) const { - const std::string indent(static_cast(level + 1) * 2, ' '); - dot << indent << "// Command nodes:\n"; - - const auto &commands = graph.getCapturedCommands(); - for (CapturedCommandId cmdId = 0; cmdId < static_cast(commands.size()); ++cmdId) { - const std::string nodeId = generateNodeId(level, subgraphId, cmdId); - const std::string label = getCommandNodeLabel(graph, cmdId); - const std::string attributes = getCommandNodeAttributes(graph, cmdId); - - dot << indent << nodeId << " [label=\"" << label << "\"" << attributes << "];\n"; - } - dot << "\n"; -} - -void GraphDotExporter::writeEdges(std::ostringstream &dot, const Graph &graph, uint32_t level, uint32_t subgraphId) const { - writeSequentialEdges(dot, graph, level, subgraphId); - writeForkJoinEdges(dot, graph, level, subgraphId); - writeUnjoinedForkEdges(dot, graph, level, subgraphId); - - dot << "\n"; -} - -void GraphDotExporter::writeSequentialEdges(std::ostringstream &dot, const Graph &graph, uint32_t level, uint32_t subgraphId) const { - const std::string indent(static_cast(level + 1) * 2, ' '); - - const auto &commands = graph.getCapturedCommands(); - dot << indent << "// Sequential edges:\n"; - - for (CapturedCommandId cmdId = 1; cmdId < static_cast(commands.size()); ++cmdId) { - const std::string fromNode = generateNodeId(level, subgraphId, cmdId - 1); - const std::string toNode = generateNodeId(level, subgraphId, cmdId); - dot << indent << fromNode << " -> " << toNode << ";\n"; - } -} - -void GraphDotExporter::writeForkJoinEdges(std::ostringstream &dot, const Graph &graph, uint32_t level, uint32_t subgraphId) const { - const std::string indent(static_cast(level + 1) * 2, ' '); - - const auto &joinedForks = graph.getJoinedForks(); - const auto &subGraphs = graph.getSubgraphs(); - - dot << "\n" - << indent << "// Fork/Join edges:\n"; - - for (const auto &[forkCmdId, forkJoinInfo] : joinedForks) { - const auto subgraphIndex = findSubgraphIndex(subGraphs, forkJoinInfo.forkDestiny); - if (subgraphIndex && !forkJoinInfo.forkDestiny->getCapturedCommands().empty()) { - const auto &subgraphCommands = forkJoinInfo.forkDestiny->getCapturedCommands(); - const std::string forkNode = generateNodeId(level, subgraphId, forkJoinInfo.forkCommandId); - const std::string subgraphFirstNode = generateNodeId(level + 1, *subgraphIndex, 0); - const std::string subgraphLastNode = generateNodeId(level + 1, *subgraphIndex, static_cast(subgraphCommands.size()) - 1); - const std::string joinNode = generateNodeId(level, subgraphId, forkJoinInfo.joinCommandId); - - dot << indent << forkNode << " -> " << subgraphFirstNode << ";\n"; - dot << indent << subgraphLastNode << " -> " << joinNode << ";\n"; - } - } -} - -void GraphDotExporter::writeUnjoinedForkEdges(std::ostringstream &dot, const Graph &graph, uint32_t level, uint32_t subgraphId) const { - const std::string indent(static_cast(level + 1) * 2, ' '); - - const auto &unjoinedForks = graph.getUnjoinedForks(); - const auto &subGraphs = graph.getSubgraphs(); - - dot << "\n" - << indent << "// Unjoined forks:\n"; - - for (const auto &[cmdList, forkInfo] : unjoinedForks) { - const auto subgraphIndex = findSubgraphIndexByCommandList(subGraphs, cmdList); - if (subgraphIndex && !subGraphs[*subgraphIndex]->getCapturedCommands().empty()) { - const std::string forkNode = generateNodeId(level, subgraphId, forkInfo.forkCommandId); - const std::string subgraphFirstNode = generateNodeId(level + 1, *subgraphIndex, 0); - dot << indent << forkNode << " -> " << subgraphFirstNode << " [color=red, label=\"unjoined fork\"];\n"; - } - } -} - -std::optional GraphDotExporter::findSubgraphIndex(const StackVec &subGraphs, const Graph *targetGraph) const { - for (uint32_t i = 0; i < static_cast(subGraphs.size()); ++i) { - if (subGraphs[i] == targetGraph) { - return i; - } - } - return std::nullopt; -} - -std::optional GraphDotExporter::findSubgraphIndexByCommandList(const StackVec &subGraphs, const L0::CommandList *cmdList) const { - for (uint32_t i = 0; i < static_cast(subGraphs.size()); ++i) { - if (subGraphs[i]->getExecutionTarget() == cmdList) { - return i; - } - } - return std::nullopt; -} - -void GraphDotExporter::writeSubgraphs(std::ostringstream &dot, const Graph &graph, uint32_t level) const { - const auto &subGraphs = graph.getSubgraphs(); - if (subGraphs.empty()) { - return; - } - - const std::string indent(static_cast(level + 1) * 2, ' '); - dot << indent << "// Subgraphs:\n"; - - for (uint32_t subgraphId = 0; subgraphId < static_cast(subGraphs.size()); ++subgraphId) { - const std::string clusterName = "cluster_" + generateSubgraphId(level + 1, subgraphId); - - dot << indent << "subgraph " << clusterName << " {\n"; - dot << indent << " label=\"Subgraph " << (level + 1) << "-" << subgraphId << "\";\n"; - dot << indent << " style=filled;\n"; - dot << indent << " fillcolor=" << getSubgraphFillColor(level + 1) << ";\n\n"; - - writeNodes(dot, *subGraphs[subgraphId], level + 1, subgraphId); - writeEdges(dot, *subGraphs[subgraphId], level + 1, subgraphId); - writeSubgraphs(dot, *subGraphs[subgraphId], level + 1); - - dot << indent << " }\n\n"; - } -} - -std::string GraphDotExporter::getCommandNodeLabel(const Graph &graph, CapturedCommandId cmdId) const { - const auto &commands = graph.getCapturedCommands(); - const auto &cmd = commands[cmdId]; - - std::string baseLabel; - switch (static_cast(cmd.index())) { -#define RR_CAPTURED_API(X) \ - case CaptureApi::X: \ - baseLabel = #X; \ - break; - - RR_CAPTURED_APIS() -#undef RR_CAPTURED_API - - default: - baseLabel = "Unknown"; - break; - } - - return baseLabel; -} - -std::string GraphDotExporter::getCommandNodeAttributes(const Graph &graph, CapturedCommandId cmdId) const { - const auto &commands = graph.getCapturedCommands(); - const auto &cmd = commands[cmdId]; - - switch (static_cast(cmd.index())) { - case CaptureApi::zeCommandListAppendMemoryCopy: - case CaptureApi::zeCommandListAppendMemoryCopyRegion: - case CaptureApi::zeCommandListAppendMemoryCopyFromContext: - case CaptureApi::zeCommandListAppendMemoryFill: - return ", fillcolor=lightblue"; - - case CaptureApi::zeCommandListAppendBarrier: - case CaptureApi::zeCommandListAppendMemoryRangesBarrier: - return ", fillcolor=orange"; - - case CaptureApi::zeCommandListAppendSignalEvent: - case CaptureApi::zeCommandListAppendWaitOnEvents: - case CaptureApi::zeCommandListAppendEventReset: - return ", fillcolor=yellow"; - - case CaptureApi::zeCommandListAppendImageCopy: - case CaptureApi::zeCommandListAppendImageCopyRegion: - case CaptureApi::zeCommandListAppendImageCopyToMemory: - case CaptureApi::zeCommandListAppendImageCopyFromMemory: - case CaptureApi::zeCommandListAppendImageCopyToMemoryExt: - case CaptureApi::zeCommandListAppendImageCopyFromMemoryExt: - return ", fillcolor=lightgreen"; - - case CaptureApi::zeCommandListAppendWriteGlobalTimestamp: - case CaptureApi::zeCommandListAppendQueryKernelTimestamps: - return ", fillcolor=pink"; - - default: - return ", fillcolor=aliceblue"; - } -} - -std::string GraphDotExporter::generateNodeId(uint32_t level, uint32_t subgraphId, CapturedCommandId cmdId) const { - std::ostringstream oss; - oss << "L" << level << "_S" << subgraphId << "_C" << cmdId; - return oss.str(); -} - -std::string GraphDotExporter::generateSubgraphId(uint32_t level, uint32_t subgraphId) const { - std::ostringstream oss; - oss << "L" << level << "_S" << subgraphId; - return oss.str(); -} - -std::string GraphDotExporter::getSubgraphFillColor(uint32_t level) const { - const std::vector colors = { - "grey90", // Level 1 - "grey80", // Level 2 - "grey70", // Level 3 - "grey60", // Level 4 - "grey50" // Level 5+ - }; - - size_t colorIndex = static_cast(level) - 1; - if (colorIndex >= colors.size()) { - colorIndex = colors.size() - 1; - } - - return colors[colorIndex]; -} - } // namespace L0 \ No newline at end of file diff --git a/level_zero/experimental/source/graph/graph.h b/level_zero/experimental/source/graph/graph.h index 2a6cfe9d22..d50e111293 100644 --- a/level_zero/experimental/source/graph/graph.h +++ b/level_zero/experimental/source/graph/graph.h @@ -7,7 +7,6 @@ #pragma once -#include "shared/source/helpers/string.h" #include "shared/source/utilities/stackvec.h" #include "level_zero/core/source/kernel/kernel_mutable_state.h" @@ -17,7 +16,6 @@ #include #include -#include #include #include #include @@ -178,6 +176,10 @@ struct Graph : _ze_graph_handle_t { return externalStorage; } + const ClosureExternalStorage &getExternalStorage() const { + return externalStorage; + } + protected: void unregisterSignallingEvents(); @@ -288,31 +290,6 @@ struct ExecutableGraph : _ze_executable_graph_handle_t { GraphSubmissionChain submissionChain; }; -class GraphDotExporter { - public: - ze_result_t exportToFile(const Graph &graph, const char *filePath) const; - - protected: - std::string exportToString(const Graph &graph) const; - - void writeHeader(std::ostringstream &dot) const; - void writeNodes(std::ostringstream &dot, const Graph &graph, uint32_t level, uint32_t subgraphId) const; - void writeSubgraphs(std::ostringstream &dot, const Graph &graph, uint32_t level) const; - void writeEdges(std::ostringstream &dot, const Graph &graph, uint32_t level, uint32_t subgraphId) const; - void writeSequentialEdges(std::ostringstream &dot, const Graph &graph, uint32_t level, uint32_t subgraphId) const; - void writeForkJoinEdges(std::ostringstream &dot, const Graph &graph, uint32_t level, uint32_t subgraphId) const; - void writeUnjoinedForkEdges(std::ostringstream &dot, const Graph &graph, uint32_t level, uint32_t subgraphId) const; - - std::optional findSubgraphIndex(const StackVec &subGraphs, const Graph *targetGraph) const; - std::optional findSubgraphIndexByCommandList(const StackVec &subGraphs, const L0::CommandList *cmdList) const; - - std::string getCommandNodeLabel(const Graph &graph, CapturedCommandId cmdId) const; - std::string getCommandNodeAttributes(const Graph &graph, CapturedCommandId cmdId) const; - std::string generateNodeId(uint32_t level, uint32_t subgraphId, CapturedCommandId cmdId) const; - std::string generateSubgraphId(uint32_t level, uint32_t subgraphId) const; - std::string getSubgraphFillColor(uint32_t level) const; -}; - constexpr size_t maxVariantSize = 2 * 64; #define RR_CAPTURED_API(X) \ static_assert(sizeof(Closure) <= maxVariantSize, #X " is too big for common variant. Please export some of its state to ClosureExternalStorage"); diff --git a/level_zero/experimental/source/graph/graph_export.cpp b/level_zero/experimental/source/graph/graph_export.cpp new file mode 100644 index 0000000000..3115032725 --- /dev/null +++ b/level_zero/experimental/source/graph/graph_export.cpp @@ -0,0 +1,719 @@ +/* + * Copyright (C) 2025 Intel Corporation + * + * SPDX-License-Identifier: MIT + * + */ + +#include "level_zero/experimental/source/graph/graph_export.h" + +#include "shared/source/utilities/io_functions.h" + +#include "level_zero/core/source/cmdlist/cmdlist.h" +#include "level_zero/core/source/event/event.h" +#include "level_zero/experimental/source/graph/graph.h" + +#include "graph_export.inl" + +#include +#include + +namespace L0 { + +ze_result_t GraphDotExporter::exportToFile(const Graph &graph, const char *filePath) const { + if (nullptr == filePath) { + return ZE_RESULT_ERROR_INVALID_ARGUMENT; + } + + FILE *file = NEO::IoFunctions::fopenPtr(filePath, "w"); + if (nullptr == file) { + return ZE_RESULT_ERROR_UNKNOWN; + } + + std::string dotContent = exportToString(graph); + size_t bytesWritten = NEO::IoFunctions::fwritePtr(dotContent.c_str(), 1, dotContent.size(), file); + NEO::IoFunctions::fclosePtr(file); + + if (bytesWritten != dotContent.size()) { + return ZE_RESULT_ERROR_UNKNOWN; + } + + return ZE_RESULT_SUCCESS; +} + +std::string GraphDotExporter::exportToString(const Graph &graph) const { + std::ostringstream dot; + + writeHeader(dot); + writeNodes(dot, graph, 0, 0); + writeEdges(dot, graph, 0, 0); + writeSubgraphs(dot, graph, 0); + + dot << "}\n"; + return dot.str(); +} + +void GraphDotExporter::writeHeader(std::ostringstream &dot) const { + dot << "digraph \"graph\" {\n"; + dot << " rankdir=TB;\n"; + dot << " nodesep=1;\n"; + dot << " ranksep=1;\n"; + dot << " node [shape=box, style=filled];\n"; + dot << " edge [color=black];\n\n"; +} + +void GraphDotExporter::writeNodes(std::ostringstream &dot, const Graph &graph, uint32_t level, uint32_t subgraphId) const { + const std::string indent(static_cast(level + 1) * 2, ' '); + dot << indent << "// Command nodes:\n"; + + const auto &commands = graph.getCapturedCommands(); + for (CapturedCommandId cmdId = 0; cmdId < static_cast(commands.size()); ++cmdId) { + const std::string nodeId = generateNodeId(level, subgraphId, cmdId); + const std::string label = getCommandNodeLabel(graph, cmdId, indent); + const std::string attributes = getCommandNodeAttributes(graph, cmdId); + + dot << indent << nodeId << " [label=" << label << attributes << "];\n"; + } + dot << "\n"; +} + +void GraphDotExporter::writeEdges(std::ostringstream &dot, const Graph &graph, uint32_t level, uint32_t subgraphId) const { + writeSequentialEdges(dot, graph, level, subgraphId); + writeForkJoinEdges(dot, graph, level, subgraphId); + writeUnjoinedForkEdges(dot, graph, level, subgraphId); + + dot << "\n"; +} + +void GraphDotExporter::writeSequentialEdges(std::ostringstream &dot, const Graph &graph, uint32_t level, uint32_t subgraphId) const { + const std::string indent(static_cast(level + 1) * 2, ' '); + + const auto &commands = graph.getCapturedCommands(); + dot << indent << "// Sequential edges:\n"; + + for (CapturedCommandId cmdId = 1; cmdId < static_cast(commands.size()); ++cmdId) { + const std::string fromNode = generateNodeId(level, subgraphId, cmdId - 1); + const std::string toNode = generateNodeId(level, subgraphId, cmdId); + dot << indent << fromNode << " -> " << toNode << ";\n"; + } +} + +void GraphDotExporter::writeForkJoinEdges(std::ostringstream &dot, const Graph &graph, uint32_t level, uint32_t subgraphId) const { + const std::string indent(static_cast(level + 1) * 2, ' '); + + const auto &joinedForks = graph.getJoinedForks(); + const auto &subGraphs = graph.getSubgraphs(); + + dot << "\n" + << indent << "// Fork/Join edges:\n"; + + for (const auto &[forkCmdId, forkJoinInfo] : joinedForks) { + const auto subgraphIndex = findSubgraphIndex(subGraphs, forkJoinInfo.forkDestiny); + if (subgraphIndex && !forkJoinInfo.forkDestiny->getCapturedCommands().empty()) { + const auto &subgraphCommands = forkJoinInfo.forkDestiny->getCapturedCommands(); + const std::string forkNode = generateNodeId(level, subgraphId, forkJoinInfo.forkCommandId); + const std::string subgraphFirstNode = generateNodeId(level + 1, *subgraphIndex, 0); + const std::string subgraphLastNode = generateNodeId(level + 1, *subgraphIndex, static_cast(subgraphCommands.size()) - 1); + const std::string joinNode = generateNodeId(level, subgraphId, forkJoinInfo.joinCommandId); + + dot << indent << forkNode << " -> " << subgraphFirstNode << ";\n"; + dot << indent << subgraphLastNode << " -> " << joinNode << ";\n"; + } + } +} + +void GraphDotExporter::writeUnjoinedForkEdges(std::ostringstream &dot, const Graph &graph, uint32_t level, uint32_t subgraphId) const { + const std::string indent(static_cast(level + 1) * 2, ' '); + + const auto &unjoinedForks = graph.getUnjoinedForks(); + const auto &subGraphs = graph.getSubgraphs(); + + dot << "\n" + << indent << "// Unjoined forks:\n"; + + for (const auto &[cmdList, forkInfo] : unjoinedForks) { + const auto subgraphIndex = findSubgraphIndexByCommandList(subGraphs, cmdList); + if (subgraphIndex && !subGraphs[*subgraphIndex]->getCapturedCommands().empty()) { + const std::string forkNode = generateNodeId(level, subgraphId, forkInfo.forkCommandId); + const std::string subgraphFirstNode = generateNodeId(level + 1, *subgraphIndex, 0); + dot << indent << forkNode << " -> " << subgraphFirstNode << " [color=red, label=\"unjoined fork\"];\n"; + } + } +} + +std::optional GraphDotExporter::findSubgraphIndex(std::span subGraphs, const Graph *targetGraph) const { + for (uint32_t i = 0; i < static_cast(subGraphs.size()); ++i) { + if (subGraphs[i] == targetGraph) { + return i; + } + } + return std::nullopt; +} + +std::optional GraphDotExporter::findSubgraphIndexByCommandList(std::span subGraphs, const L0::CommandList *cmdList) const { + for (uint32_t i = 0; i < static_cast(subGraphs.size()); ++i) { + if (subGraphs[i]->getExecutionTarget() == cmdList) { + return i; + } + } + return std::nullopt; +} + +void GraphDotExporter::writeSubgraphs(std::ostringstream &dot, const Graph &graph, uint32_t level) const { + const auto &subGraphs = graph.getSubgraphs(); + if (subGraphs.empty()) { + return; + } + + const std::string indent(static_cast(level + 1) * 2, ' '); + dot << indent << "// Subgraphs:\n"; + + for (uint32_t subgraphId = 0; subgraphId < static_cast(subGraphs.size()); ++subgraphId) { + const std::string clusterName = "cluster_" + generateSubgraphId(level + 1, subgraphId); + + dot << indent << "subgraph " << clusterName << " {\n"; + dot << indent << " label=\"Subgraph " << (level + 1) << "-" << subgraphId << "\";\n"; + dot << indent << " style=filled;\n"; + dot << indent << " fillcolor=" << getSubgraphFillColor(level + 1) << ";\n\n"; + + writeNodes(dot, *subGraphs[subgraphId], level + 1, subgraphId); + writeEdges(dot, *subGraphs[subgraphId], level + 1, subgraphId); + writeSubgraphs(dot, *subGraphs[subgraphId], level + 1); + + dot << indent << " }\n\n"; + } +} + +std::string GraphDotExporter::getCommandNodeLabel(const Graph &graph, CapturedCommandId cmdId, const std::string &indent) const { + const auto &commands = graph.getCapturedCommands(); + const auto &cmd = commands[cmdId]; + + std::string baseLabel; + std::vector> params; + + switch (static_cast(cmd.index())) { +#define RR_CAPTURED_API(X) \ + case CaptureApi::X: \ + baseLabel = #X; \ + params = GraphDumpHelper::extractParameters(std::get(CaptureApi::X)>(cmd), graph.getExternalStorage()); \ + break; + + RR_CAPTURED_APIS() +#undef RR_CAPTURED_API + + default: + baseLabel = "Unknown"; + break; + } + + if (params.empty()) { + return "\"" + baseLabel + "\""; + } + + std::ostringstream label; + label << "\n"; + label << indent << "<" + << "\n"; + label << indent << " \n"; + + for (const auto ¶m : params) { + label << indent << " "; + label << ""; + label << "\n"; + } + + label << indent << "
" << baseLabel << "
" << param.first << "" << param.second << "
" + << ">, shape=plain"; + return label.str(); +} + +std::string GraphDotExporter::getCommandNodeAttributes(const Graph &graph, CapturedCommandId cmdId) const { + const auto &commands = graph.getCapturedCommands(); + const auto &cmd = commands[cmdId]; + + switch (static_cast(cmd.index())) { + case CaptureApi::zeCommandListAppendMemoryCopy: + case CaptureApi::zeCommandListAppendMemoryCopyRegion: + case CaptureApi::zeCommandListAppendMemoryCopyFromContext: + case CaptureApi::zeCommandListAppendMemoryFill: + return ", fillcolor=lightblue"; + + case CaptureApi::zeCommandListAppendBarrier: + case CaptureApi::zeCommandListAppendMemoryRangesBarrier: + return ", fillcolor=orange"; + + case CaptureApi::zeCommandListAppendSignalEvent: + case CaptureApi::zeCommandListAppendWaitOnEvents: + case CaptureApi::zeCommandListAppendEventReset: + return ", fillcolor=yellow"; + + case CaptureApi::zeCommandListAppendImageCopy: + case CaptureApi::zeCommandListAppendImageCopyRegion: + case CaptureApi::zeCommandListAppendImageCopyToMemory: + case CaptureApi::zeCommandListAppendImageCopyFromMemory: + case CaptureApi::zeCommandListAppendImageCopyToMemoryExt: + case CaptureApi::zeCommandListAppendImageCopyFromMemoryExt: + return ", fillcolor=lightgreen"; + + case CaptureApi::zeCommandListAppendWriteGlobalTimestamp: + case CaptureApi::zeCommandListAppendQueryKernelTimestamps: + return ", fillcolor=pink"; + + default: + return ", fillcolor=aliceblue"; + } +} + +std::string GraphDotExporter::generateNodeId(uint32_t level, uint32_t subgraphId, CapturedCommandId cmdId) const { + std::ostringstream oss; + oss << "L" << level << "_S" << subgraphId << "_C" << cmdId; + return oss.str(); +} + +std::string GraphDotExporter::generateSubgraphId(uint32_t level, uint32_t subgraphId) const { + std::ostringstream oss; + oss << "L" << level << "_S" << subgraphId; + return oss.str(); +} + +std::string GraphDotExporter::getSubgraphFillColor(uint32_t level) const { + const std::vector colors = { + "grey90", // Level 1 + "grey80", // Level 2 + "grey70", // Level 3 + "grey60", // Level 4 + "grey50" // Level 5+ + }; + + size_t colorIndex = static_cast(level) - 1; + if (colorIndex >= colors.size()) { + colorIndex = colors.size() - 1; + } + + return colors[colorIndex]; +} + +namespace GraphDumpHelper { + +std::string formatPointer(const void *ptr) { + std::ostringstream addr; + addr << "0x" << std::hex << std::uppercase << reinterpret_cast(ptr); + return addr.str(); +} + +std::string formatGroupCount(const ze_group_count_t &groupCount) { + return std::to_string(groupCount.groupCountX) + "x" + + std::to_string(groupCount.groupCountY) + "x" + + std::to_string(groupCount.groupCountZ); +} + +template +std::vector> createBaseParams(const ApiArgsT &apiArgs) { + return {{"hCommandList", formatPointer(apiArgs.hCommandList)}}; +} + +template +void addCommonEventParameters(std::vector> ¶ms, + const Closure &closure, + const ClosureExternalStorage &storage) { + if constexpr (HasHSignalEvent) { + if (closure.apiArgs.hSignalEvent != nullptr) { + params.emplace_back("hSignalEvent", formatPointer(closure.apiArgs.hSignalEvent)); + } + } + + if constexpr (requires { closure.indirectArgs.waitEvents; }) { + auto *events = storage.getEventsList(closure.indirectArgs.waitEvents); + if constexpr (HasPhWaitEvents) { + if (closure.apiArgs.numWaitEvents > 0) { + params.emplace_back("numWaitEvents", std::to_string(closure.apiArgs.numWaitEvents)); + params.emplace_back("phWaitEvents", formatPointer(static_cast(events))); + for (uint32_t i = 0; i < closure.apiArgs.numWaitEvents; ++i) { + std::string eventKey = "phWaitEvents[" + std::to_string(i) + "]"; + params.emplace_back(eventKey, formatPointer(events[i])); + } + } + } + if constexpr (HasPhEvents) { + if (closure.apiArgs.numEvents > 0) { + params.emplace_back("numEvents", std::to_string(closure.apiArgs.numEvents)); + params.emplace_back("phEvents", formatPointer(static_cast(events))); + for (uint32_t i = 0; i < closure.apiArgs.numEvents; ++i) { + std::string eventKey = "phEvents[" + std::to_string(i) + "]"; + params.emplace_back(eventKey, formatPointer(events[i])); + } + } + } + } +} + +template +void addOptionalRegionParameters(std::vector> ¶ms, const ApiArgsT &apiArgs) { + if constexpr (requires { apiArgs.pDstRegion; }) { + if (apiArgs.pDstRegion != nullptr) { + params.emplace_back("pDstRegion", formatPointer(apiArgs.pDstRegion)); + } + } + if constexpr (requires { apiArgs.pSrcRegion; }) { + if (apiArgs.pSrcRegion != nullptr) { + params.emplace_back("pSrcRegion", formatPointer(apiArgs.pSrcRegion)); + } + } +} + +template <> +std::vector> extractParameters( + const Closure &closure, const ClosureExternalStorage &storage) { + + auto params = createBaseParams(closure.apiArgs); + params.emplace_back("dstptr", formatPointer(closure.apiArgs.dstptr)); + params.emplace_back("srcptr", formatPointer(closure.apiArgs.srcptr)); + params.emplace_back("size", std::to_string(closure.apiArgs.size)); + + addCommonEventParameters(params, closure, storage); + + return params; +} + +template <> +std::vector> extractParameters( + const Closure &closure, const ClosureExternalStorage &storage) { + + auto params = createBaseParams(closure.apiArgs); + params.emplace_back("ptr", formatPointer(closure.apiArgs.ptr)); + params.emplace_back("pattern", formatPointer(closure.indirectArgs.pattern.data())); + params.emplace_back("patternSize", std::to_string(closure.indirectArgs.pattern.size())); + params.emplace_back("size", std::to_string(closure.apiArgs.size)); + + addCommonEventParameters(params, closure, storage); + + return params; +} + +template <> +std::vector> extractParameters( + const Closure &closure, const ClosureExternalStorage &storage) { + + auto params = createBaseParams(closure.apiArgs); + params.emplace_back("hKernel", formatPointer(closure.apiArgs.kernelHandle)); + params.emplace_back("launchFuncArgs", formatGroupCount(closure.indirectArgs.launchKernelArgs)); + + addCommonEventParameters(params, closure, storage); + + return params; +} + +template <> +std::vector> extractParameters( + const Closure &closure, const ClosureExternalStorage &storage) { + + auto params = createBaseParams(closure.apiArgs); + addCommonEventParameters(params, closure, storage); + + return params; +} + +template <> +std::vector> extractParameters( + const Closure &closure, const ClosureExternalStorage &storage) { + + auto params = createBaseParams(closure.apiArgs); + addCommonEventParameters(params, closure, storage); + + return params; +} + +template <> +std::vector> extractParameters( + const Closure &closure, const ClosureExternalStorage &storage) { + + auto params = createBaseParams(closure.apiArgs); + params.emplace_back("dstptr", formatPointer(closure.apiArgs.dstptr)); + + addCommonEventParameters(params, closure, storage); + + return params; +} + +template <> +std::vector> extractParameters( + const Closure &closure, const ClosureExternalStorage &storage) { + + auto params = createBaseParams(closure.apiArgs); + params.emplace_back("numRanges", std::to_string(closure.indirectArgs.rangeSizes.size())); + params.emplace_back("pRangeSizes", formatPointer(closure.indirectArgs.rangeSizes.data())); + params.emplace_back("pRanges", formatPointer(static_cast(closure.indirectArgs.ranges.data()))); + + addCommonEventParameters(params, closure, storage); + + return params; +} + +template <> +std::vector> extractParameters( + const Closure &closure, const ClosureExternalStorage &storage) { + + auto params = createBaseParams(closure.apiArgs); + params.emplace_back("dstptr", formatPointer(closure.apiArgs.dstptr)); + params.emplace_back("dstRegion", formatPointer(closure.apiArgs.dstRegion)); + params.emplace_back("dstPitch", std::to_string(closure.apiArgs.dstPitch)); + params.emplace_back("dstSlicePitch", std::to_string(closure.apiArgs.dstSlicePitch)); + params.emplace_back("srcptr", formatPointer(closure.apiArgs.srcptr)); + params.emplace_back("srcRegion", formatPointer(closure.apiArgs.srcRegion)); + params.emplace_back("srcPitch", std::to_string(closure.apiArgs.srcPitch)); + params.emplace_back("srcSlicePitch", std::to_string(closure.apiArgs.srcSlicePitch)); + + addCommonEventParameters(params, closure, storage); + + return params; +} + +template <> +std::vector> extractParameters( + const Closure &closure, const ClosureExternalStorage &storage) { + + auto params = createBaseParams(closure.apiArgs); + params.emplace_back("dstptr", formatPointer(closure.apiArgs.dstptr)); + params.emplace_back("hContextSrc", formatPointer(closure.apiArgs.hContextSrc)); + params.emplace_back("srcptr", formatPointer(closure.apiArgs.srcptr)); + params.emplace_back("size", std::to_string(closure.apiArgs.size)); + + addCommonEventParameters(params, closure, storage); + + return params; +} + +template <> +std::vector> extractParameters( + const Closure &closure, const ClosureExternalStorage &storage) { + + auto params = createBaseParams(closure.apiArgs); + params.emplace_back("hDstImage", formatPointer(closure.apiArgs.hDstImage)); + params.emplace_back("hSrcImage", formatPointer(closure.apiArgs.hSrcImage)); + + addCommonEventParameters(params, closure, storage); + + return params; +} + +template <> +std::vector> extractParameters( + const Closure &closure, const ClosureExternalStorage &storage) { + + auto params = createBaseParams(closure.apiArgs); + params.emplace_back("hDstImage", formatPointer(closure.apiArgs.hDstImage)); + params.emplace_back("hSrcImage", formatPointer(closure.apiArgs.hSrcImage)); + + addOptionalRegionParameters(params, closure.apiArgs); + addCommonEventParameters(params, closure, storage); + + return params; +} + +template <> +std::vector> extractParameters( + const Closure &closure, const ClosureExternalStorage &storage) { + + auto params = createBaseParams(closure.apiArgs); + params.emplace_back("dstptr", formatPointer(closure.apiArgs.dstptr)); + params.emplace_back("hSrcImage", formatPointer(closure.apiArgs.hSrcImage)); + + addOptionalRegionParameters(params, closure.apiArgs); + addCommonEventParameters(params, closure, storage); + + return params; +} + +template <> +std::vector> extractParameters( + const Closure &closure, const ClosureExternalStorage &storage) { + + auto params = createBaseParams(closure.apiArgs); + params.emplace_back("hDstImage", formatPointer(closure.apiArgs.hDstImage)); + params.emplace_back("srcptr", formatPointer(closure.apiArgs.srcptr)); + + addOptionalRegionParameters(params, closure.apiArgs); + addCommonEventParameters(params, closure, storage); + + return params; +} + +template <> +std::vector> extractParameters( + const Closure &closure, const ClosureExternalStorage &storage) { + + auto params = createBaseParams(closure.apiArgs); + params.emplace_back("ptr", formatPointer(closure.apiArgs.ptr)); + params.emplace_back("size", std::to_string(closure.apiArgs.size)); + + return params; +} + +template <> +std::vector> extractParameters( + const Closure &closure, const ClosureExternalStorage &storage) { + + auto params = createBaseParams(closure.apiArgs); + params.emplace_back("hDevice", formatPointer(closure.apiArgs.hDevice)); + params.emplace_back("ptr", formatPointer(closure.apiArgs.ptr)); + params.emplace_back("size", std::to_string(closure.apiArgs.size)); + params.emplace_back("advice", std::to_string(static_cast(closure.apiArgs.advice))); + + return params; +} + +template <> +std::vector> extractParameters( + const Closure &closure, const ClosureExternalStorage &storage) { + + auto params = createBaseParams(closure.apiArgs); + params.emplace_back("hEvent", formatPointer(closure.apiArgs.hEvent)); + + return params; +} + +template <> +std::vector> extractParameters( + const Closure &closure, const ClosureExternalStorage &storage) { + + auto params = createBaseParams(closure.apiArgs); + params.emplace_back("hEvent", formatPointer(closure.apiArgs.hEvent)); + + return params; +} + +template <> +std::vector> extractParameters( + const Closure &closure, const ClosureExternalStorage &storage) { + + auto params = createBaseParams(closure.apiArgs); + params.emplace_back("dstptr", formatPointer(closure.apiArgs.dstptr)); + + if (closure.apiArgs.pOffsets != nullptr) { + params.emplace_back("pOffsets", formatPointer(closure.apiArgs.pOffsets)); + } + + addCommonEventParameters(params, closure, storage); + + return params; +} + +template <> +std::vector> extractParameters( + const Closure &closure, const ClosureExternalStorage &storage) { + + auto params = createBaseParams(closure.apiArgs); + params.emplace_back("hKernel", formatPointer(closure.apiArgs.kernelHandle)); + params.emplace_back("launchFuncArgs", formatGroupCount(closure.indirectArgs.launchKernelArgs)); + + addCommonEventParameters(params, closure, storage); + + return params; +} + +template <> +std::vector> extractParameters( + const Closure &closure, const ClosureExternalStorage &storage) { + + auto params = createBaseParams(closure.apiArgs); + params.emplace_back("hKernel", formatPointer(closure.apiArgs.kernelHandle)); + params.emplace_back("pLaunchArgumentsBuffer", formatPointer(closure.apiArgs.launchArgsBuffer)); + + addCommonEventParameters(params, closure, storage); + + return params; +} + +template <> +std::vector> extractParameters( + const Closure &closure, const ClosureExternalStorage &storage) { + // does not have closure specialization yet + return {}; +} + +template <> +std::vector> extractParameters( + const Closure &closure, const ClosureExternalStorage &storage) { + + auto params = createBaseParams(closure.apiArgs); + params.emplace_back("numKernels", std::to_string(closure.apiArgs.numKernels)); + params.emplace_back("phKernels", formatPointer(static_cast(closure.apiArgs.phKernels))); + params.emplace_back("pCountBuffer", formatPointer(closure.apiArgs.pCountBuffer)); + params.emplace_back("pLaunchArgumentsBuffer", formatPointer(closure.apiArgs.launchArgsBuffer)); + + addCommonEventParameters(params, closure, storage); + + return params; +} + +template <> +std::vector> extractParameters( + const Closure &closure, const ClosureExternalStorage &storage) { + + auto params = createBaseParams(closure.apiArgs); + params.emplace_back("numSemaphores", std::to_string(closure.apiArgs.numSemaphores)); + params.emplace_back("phSemaphores", formatPointer(static_cast(closure.apiArgs.phSemaphores))); + for (uint32_t i = 0; i < closure.apiArgs.numSemaphores; ++i) { + std::string semaphoreKey = "phSemaphores[" + std::to_string(i) + "]"; + params.emplace_back(semaphoreKey, formatPointer(closure.indirectArgs.semaphores[i])); + } + params.emplace_back("signalParams", formatPointer(&closure.indirectArgs.signalParams)); + + addCommonEventParameters(params, closure, storage); + + return params; +} + +template <> +std::vector> extractParameters( + const Closure &closure, const ClosureExternalStorage &storage) { + + auto params = createBaseParams(closure.apiArgs); + params.emplace_back("numSemaphores", std::to_string(closure.apiArgs.numSemaphores)); + params.emplace_back("phSemaphores", formatPointer(static_cast(closure.apiArgs.phSemaphores))); + for (uint32_t i = 0; i < closure.apiArgs.numSemaphores; ++i) { + std::string semaphoreKey = "phSemaphores[" + std::to_string(i) + "]"; + params.emplace_back(semaphoreKey, formatPointer(closure.indirectArgs.semaphores[i])); + } + params.emplace_back("waitParams", formatPointer(&closure.indirectArgs.waitParams)); + + addCommonEventParameters(params, closure, storage); + + return params; +} + +template <> +std::vector> extractParameters( + const Closure &closure, const ClosureExternalStorage &storage) { + + auto params = createBaseParams(closure.apiArgs); + params.emplace_back("dstptr", formatPointer(closure.apiArgs.dstptr)); + params.emplace_back("hSrcImage", formatPointer(closure.apiArgs.hSrcImage)); + params.emplace_back("destRowPitch", std::to_string(closure.apiArgs.destRowPitch)); + params.emplace_back("destSlicePitch", std::to_string(closure.apiArgs.destSlicePitch)); + + addOptionalRegionParameters(params, closure.apiArgs); + addCommonEventParameters(params, closure, storage); + + return params; +} + +template <> +std::vector> extractParameters( + const Closure &closure, const ClosureExternalStorage &storage) { + + auto params = createBaseParams(closure.apiArgs); + params.emplace_back("hDstImage", formatPointer(closure.apiArgs.hDstImage)); + params.emplace_back("srcptr", formatPointer(closure.apiArgs.srcptr)); + params.emplace_back("srcRowPitch", std::to_string(closure.apiArgs.srcRowPitch)); + params.emplace_back("srcSlicePitch", std::to_string(closure.apiArgs.srcSlicePitch)); + + addOptionalRegionParameters(params, closure.apiArgs); + addCommonEventParameters(params, closure, storage); + + return params; +} + +} // namespace GraphDumpHelper + +} // namespace L0 diff --git a/level_zero/experimental/source/graph/graph_export.h b/level_zero/experimental/source/graph/graph_export.h new file mode 100644 index 0000000000..25543460a4 --- /dev/null +++ b/level_zero/experimental/source/graph/graph_export.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2025 Intel Corporation + * + * SPDX-License-Identifier: MIT + * + */ + +#pragma once + +#include "level_zero/ze_api.h" + +#include "graph.h" + +#include +#include +#include + +namespace L0 { + +struct CommandList; + +class GraphDotExporter { + public: + ze_result_t exportToFile(const Graph &graph, const char *filePath) const; + + protected: + std::string exportToString(const Graph &graph) const; + + void writeHeader(std::ostringstream &dot) const; + void writeNodes(std::ostringstream &dot, const Graph &graph, uint32_t level, uint32_t subgraphId) const; + void writeSubgraphs(std::ostringstream &dot, const Graph &graph, uint32_t level) const; + void writeEdges(std::ostringstream &dot, const Graph &graph, uint32_t level, uint32_t subgraphId) const; + void writeSequentialEdges(std::ostringstream &dot, const Graph &graph, uint32_t level, uint32_t subgraphId) const; + void writeForkJoinEdges(std::ostringstream &dot, const Graph &graph, uint32_t level, uint32_t subgraphId) const; + void writeUnjoinedForkEdges(std::ostringstream &dot, const Graph &graph, uint32_t level, uint32_t subgraphId) const; + + std::optional findSubgraphIndex(std::span subGraphs, const Graph *targetGraph) const; + std::optional findSubgraphIndexByCommandList(std::span subGraphs, const L0::CommandList *cmdList) const; + + std::string getCommandNodeLabel(const Graph &graph, CapturedCommandId cmdId, const std::string &indent) const; + std::string getCommandNodeAttributes(const Graph &graph, CapturedCommandId cmdId) const; + std::string generateNodeId(uint32_t level, uint32_t subgraphId, CapturedCommandId cmdId) const; + std::string generateSubgraphId(uint32_t level, uint32_t subgraphId) const; + std::string getSubgraphFillColor(uint32_t level) const; +}; + +namespace GraphDumpHelper { + +std::string formatPointer(const void *ptr); +std::string formatGroupCount(const ze_group_count_t &groupCount); + +} // namespace GraphDumpHelper + +} // namespace L0