mirror of
https://github.com/intel/compute-runtime.git
synced 2025-12-29 17:13:29 +08:00
feature: dump graph visualisation
Related-To: NEO-15377 Signed-off-by: Naklicki, Mateusz <mateusz.naklicki@intel.com>
This commit is contained in:
committed by
Compute-Runtime-Automation
parent
b5ea7274b8
commit
86d270633c
@@ -196,7 +196,21 @@ ze_result_t ZE_APICALL zeGraphIsEmptyExp(ze_graph_handle_t hGraph) {
|
||||
}
|
||||
|
||||
ze_result_t ZE_APICALL zeGraphDumpContentsExp(ze_graph_handle_t hGraph, const char *filePath, void *pNext) {
|
||||
return ZE_RESULT_ERROR_UNSUPPORTED_FEATURE;
|
||||
if (nullptr != pNext) {
|
||||
return ZE_RESULT_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
auto graph = L0::Graph::fromHandle(hGraph);
|
||||
if (nullptr == graph) {
|
||||
return ZE_RESULT_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (nullptr == filePath) {
|
||||
return ZE_RESULT_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
L0::GraphDotExporter exporter{};
|
||||
return exporter.exportToFile(*graph, filePath);
|
||||
}
|
||||
|
||||
} // namespace L0
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
#include "zello_compile.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
|
||||
#define ENABLE_GRAPH_DUMP true
|
||||
|
||||
using zeGraphCreateExpFP = ze_result_t(ZE_APICALL *)(ze_context_handle_t context, ze_graph_handle_t *phGraph, void *pNext);
|
||||
using zeCommandListBeginGraphCaptureExpFP = ze_result_t(ZE_APICALL *)(ze_command_list_handle_t hCommandList, void *pNext);
|
||||
@@ -48,6 +48,21 @@ struct GraphApi {
|
||||
}
|
||||
};
|
||||
|
||||
void dumpGraphToDotIfEnabled(const GraphApi &graphApi, ze_graph_handle_t virtualGraph, const std::string &testName) {
|
||||
if (!ENABLE_GRAPH_DUMP) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string filename = testName + "_graph.gv";
|
||||
ze_result_t dumpResult = graphApi.graphDumpContents(virtualGraph, filename.c_str(), nullptr);
|
||||
|
||||
if (dumpResult == ZE_RESULT_SUCCESS) {
|
||||
std::cout << "Graph dumped to " << filename << std::endl;
|
||||
} else {
|
||||
std::cerr << "Failed to dump graph for test " << testName << " (result: " << std::hex << dumpResult << ")" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
GraphApi loadGraphApi(ze_driver_handle_t driver) {
|
||||
GraphApi ret;
|
||||
zeDriverGetExtensionFunctionAddress(driver, "zeGraphCreateExp", reinterpret_cast<void **>(&ret.graphCreate));
|
||||
@@ -130,6 +145,8 @@ void testAppendMemoryCopy(ze_driver_handle_t driver, ze_context_handle_t &contex
|
||||
std::cerr << "stackBuffer == " << static_cast<void *>(stackBuffer) << std::endl;
|
||||
}
|
||||
|
||||
dumpGraphToDotIfEnabled(graphApi, virtualGraph, __func__);
|
||||
|
||||
delete[] heapBuffer;
|
||||
SUCCESS_OR_TERMINATE(zeMemFree(context, zeBuffer));
|
||||
|
||||
@@ -225,6 +242,8 @@ void testMultiGraph(ze_driver_handle_t driver, ze_context_handle_t &context, ze_
|
||||
std::cerr << "stackBuffer == " << static_cast<void *>(stackBuffer) << std::endl;
|
||||
}
|
||||
|
||||
dumpGraphToDotIfEnabled(graphApi, virtualGraph, __func__);
|
||||
|
||||
delete[] heapBuffer;
|
||||
SUCCESS_OR_TERMINATE(zeMemFree(context, zeBuffer));
|
||||
|
||||
@@ -388,6 +407,8 @@ void testAppendLaunchKernel(ze_driver_handle_t driver, ze_context_handle_t &cont
|
||||
std::cerr << "outputData == " << static_cast<void *>(outputData.get()) << std::endl;
|
||||
}
|
||||
|
||||
dumpGraphToDotIfEnabled(graphApi, virtualGraph, __func__);
|
||||
|
||||
// Cleanup
|
||||
SUCCESS_OR_TERMINATE(zeMemFree(context, dstBuffer));
|
||||
SUCCESS_OR_TERMINATE(zeMemFree(context, interimBuffer));
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
target_sources(${TARGET_NAME} PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test_graph.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test_graph_exporter.cpp
|
||||
)
|
||||
|
||||
target_include_directories(${TARGET_NAME}
|
||||
|
||||
@@ -328,14 +328,6 @@ TEST(GraphTestDebugApis, GivenNonEmptyGraphWhenGraphIsEmptyIsCalledThenErrorIsRe
|
||||
EXPECT_EQ(ZE_RESULT_QUERY_FALSE, ::zeGraphIsEmptyExp(&srcGraph));
|
||||
}
|
||||
|
||||
TEST(GraphTestDebugApis, WhenGraphDumpContentsIsCalledThenReturnUnsupportedFeature) {
|
||||
GraphsCleanupGuard graphCleanup;
|
||||
Mock<Context> ctx;
|
||||
L0::Graph srcGraph(&ctx, true);
|
||||
auto err = ::zeGraphDumpContentsExp(&srcGraph, "dump", nullptr);
|
||||
EXPECT_EQ(ZE_RESULT_ERROR_UNSUPPORTED_FEATURE, err);
|
||||
}
|
||||
|
||||
TEST(GraphTestApiSubmit, GivenNonNullPNextThenGraphAppendReturnsError) {
|
||||
GraphsCleanupGuard graphCleanup;
|
||||
Mock<Context> ctx;
|
||||
|
||||
@@ -0,0 +1,535 @@
|
||||
/*
|
||||
* 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<Context> 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> event;
|
||||
Mock<CommandList> cmdlist;
|
||||
|
||||
testGraph.capture<CaptureApi::zeCommandListAppendBarrier>(&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> event;
|
||||
Mock<CommandList> cmdlist;
|
||||
|
||||
testGraph.capture<CaptureApi::zeCommandListAppendBarrier>(&cmdlist, &event, 0U, nullptr);
|
||||
testGraph.capture<CaptureApi::zeCommandListAppendMemoryCopy>(&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> event;
|
||||
Mock<CommandList> cmdlist;
|
||||
|
||||
testGraph.capture<CaptureApi::zeCommandListAppendBarrier>(&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> event;
|
||||
Mock<CommandList> cmdlist;
|
||||
|
||||
testGraph.capture<CaptureApi::zeCommandListAppendBarrier>(&cmdlist, &event, 0U, nullptr);
|
||||
testGraph.capture<CaptureApi::zeCommandListAppendMemoryCopy>(&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> event;
|
||||
Mock<CommandList> cmdlist;
|
||||
|
||||
testGraph.capture<CaptureApi::zeCommandListAppendBarrier>(&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> event;
|
||||
Mock<CommandList> cmdlist;
|
||||
|
||||
testGraph.capture<CaptureApi::zeCommandListAppendBarrier>(&cmdlist, &event, 0U, nullptr);
|
||||
testGraph.capture<CaptureApi::zeCommandListAppendMemoryCopy>(&cmdlist, nullptr, nullptr, 0U, nullptr, 0U, nullptr);
|
||||
testGraph.capture<CaptureApi::zeCommandListAppendSignalEvent>(&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<Event> forkEvent;
|
||||
Mock<Event> joinEvent;
|
||||
Mock<CommandList> mainCmdList;
|
||||
Mock<CommandList> subCmdList;
|
||||
|
||||
Graph *testGraphPtr = &testGraph;
|
||||
captureCommand<CaptureApi::zeCommandListAppendBarrier>(mainCmdList, testGraphPtr, &mainCmdList, &forkEvent, 0U, nullptr);
|
||||
|
||||
Graph *subGraph = nullptr;
|
||||
testGraph.forkTo(subCmdList, subGraph, forkEvent);
|
||||
ASSERT_NE(subGraph, nullptr);
|
||||
|
||||
captureCommand<CaptureApi::zeCommandListAppendMemoryCopy>(subCmdList, subGraph, &subCmdList, nullptr, nullptr, 0U, nullptr, 0U, nullptr);
|
||||
captureCommand<CaptureApi::zeCommandListAppendBarrier>(subCmdList, subGraph, &subCmdList, &joinEvent, 0U, nullptr);
|
||||
|
||||
testGraph.tryJoinOnNextCommand(subCmdList, joinEvent);
|
||||
captureCommand<CaptureApi::zeCommandListAppendBarrier>(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<Event> forkEvent1;
|
||||
Mock<Event> forkEvent2;
|
||||
Mock<Event> joinEvent1;
|
||||
Mock<Event> joinEvent2;
|
||||
Mock<CommandList> mainCmdList;
|
||||
Mock<CommandList> subCmdList1;
|
||||
Mock<CommandList> subCmdList2;
|
||||
|
||||
Graph *testGraphPtr = &testGraph;
|
||||
captureCommand<CaptureApi::zeCommandListAppendBarrier>(mainCmdList, testGraphPtr, &mainCmdList, &forkEvent1, 0U, nullptr);
|
||||
|
||||
Graph *subGraph1 = nullptr;
|
||||
testGraph.forkTo(subCmdList1, subGraph1, forkEvent1);
|
||||
ASSERT_NE(subGraph1, nullptr);
|
||||
|
||||
captureCommand<CaptureApi::zeCommandListAppendMemoryCopy>(subCmdList1, subGraph1, &subCmdList1, nullptr, nullptr, 0U, nullptr, 0U, nullptr);
|
||||
|
||||
Graph *subGraph2 = nullptr;
|
||||
captureCommand<CaptureApi::zeCommandListAppendBarrier>(subCmdList1, subGraph1, &subCmdList1, &forkEvent2, 0U, nullptr);
|
||||
subGraph1->forkTo(subCmdList2, subGraph2, forkEvent2);
|
||||
ASSERT_NE(subGraph2, nullptr);
|
||||
|
||||
captureCommand<CaptureApi::zeCommandListAppendBarrier>(subCmdList2, subGraph2, &subCmdList2, &joinEvent2, 0U, nullptr);
|
||||
|
||||
subGraph1->tryJoinOnNextCommand(subCmdList2, joinEvent2);
|
||||
captureCommand<CaptureApi::zeCommandListAppendBarrier>(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<Event> forkEvent1;
|
||||
Mock<Event> forkEvent2;
|
||||
Mock<Event> joinEvent1;
|
||||
Mock<Event> joinEvent2;
|
||||
Mock<CommandList> mainCmdList;
|
||||
Mock<CommandList> subCmdList1;
|
||||
Mock<CommandList> subCmdList2;
|
||||
|
||||
Graph *testGraphPtr = &testGraph;
|
||||
captureCommand<CaptureApi::zeCommandListAppendBarrier>(mainCmdList, testGraphPtr, &mainCmdList, &forkEvent1, 0U, nullptr);
|
||||
|
||||
Graph *subGraph1 = nullptr;
|
||||
testGraph.forkTo(subCmdList1, subGraph1, forkEvent1);
|
||||
ASSERT_NE(subGraph1, nullptr);
|
||||
|
||||
captureCommand<CaptureApi::zeCommandListAppendMemoryCopy>(subCmdList1, subGraph1, &subCmdList1, nullptr, nullptr, 0U, nullptr, 0U, nullptr);
|
||||
captureCommand<CaptureApi::zeCommandListAppendBarrier>(subCmdList1, subGraph1, &subCmdList1, &joinEvent1, 0U, nullptr);
|
||||
|
||||
testGraph.tryJoinOnNextCommand(subCmdList1, joinEvent1);
|
||||
captureCommand<CaptureApi::zeCommandListAppendBarrier>(mainCmdList, testGraphPtr, &mainCmdList, &forkEvent2, 0U, nullptr);
|
||||
|
||||
Graph *subGraph2 = nullptr;
|
||||
testGraph.forkTo(subCmdList2, subGraph2, forkEvent2);
|
||||
ASSERT_NE(subGraph2, nullptr);
|
||||
|
||||
captureCommand<CaptureApi::zeCommandListAppendMemoryCopy>(subCmdList2, subGraph2, &subCmdList2, nullptr, nullptr, 0U, nullptr, 0U, nullptr);
|
||||
captureCommand<CaptureApi::zeCommandListAppendBarrier>(subCmdList2, subGraph2, &subCmdList2, &joinEvent2, 0U, nullptr);
|
||||
|
||||
testGraph.tryJoinOnNextCommand(subCmdList2, joinEvent2);
|
||||
captureCommand<CaptureApi::zeCommandListAppendBarrier>(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<Graph *, 16> 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<Event> forkEvent;
|
||||
Mock<Event> joinEvent;
|
||||
Mock<CommandList> mainCmdList;
|
||||
Mock<CommandList> subCmdList;
|
||||
|
||||
Graph *testGraphPtr = &testGraph;
|
||||
captureCommand<CaptureApi::zeCommandListAppendBarrier>(mainCmdList, testGraphPtr, &mainCmdList, &forkEvent, 0U, nullptr);
|
||||
|
||||
Graph *subGraph = nullptr;
|
||||
testGraph.forkTo(subCmdList, subGraph, forkEvent);
|
||||
ASSERT_NE(subGraph, nullptr);
|
||||
|
||||
testGraph.tryJoinOnNextCommand(subCmdList, joinEvent);
|
||||
captureCommand<CaptureApi::zeCommandListAppendBarrier>(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<Graph *, 16> subGraphs;
|
||||
Mock<CommandList> fakeCmdList;
|
||||
auto index = exporter.findSubgraphIndexByCommandList(subGraphs, &fakeCmdList);
|
||||
|
||||
EXPECT_FALSE(index.has_value());
|
||||
}
|
||||
|
||||
TEST_F(GraphDotExporterTest, GivenGraphWithEmptySubgraphWhenWriteForkJoinEdgesThenNoEdges) {
|
||||
Graph testGraph{&ctx, true};
|
||||
Mock<Event> forkEvent;
|
||||
Mock<Event> joinEvent;
|
||||
Mock<CommandList> mainCmdList;
|
||||
Mock<CommandList> subCmdList;
|
||||
|
||||
Graph *testGraphPtr = &testGraph;
|
||||
captureCommand<CaptureApi::zeCommandListAppendBarrier>(mainCmdList, testGraphPtr, &mainCmdList, &forkEvent, 0U, nullptr);
|
||||
|
||||
Graph *subGraph = nullptr;
|
||||
testGraph.forkTo(subCmdList, subGraph, forkEvent);
|
||||
ASSERT_NE(subGraph, nullptr);
|
||||
|
||||
testGraph.tryJoinOnNextCommand(subCmdList, joinEvent);
|
||||
captureCommand<CaptureApi::zeCommandListAppendBarrier>(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<Event> forkEvent;
|
||||
Mock<CommandList> mainCmdList;
|
||||
Mock<CommandList> subCmdList;
|
||||
|
||||
Graph *testGraphPtr = &testGraph;
|
||||
captureCommand<CaptureApi::zeCommandListAppendBarrier>(mainCmdList, testGraphPtr, &mainCmdList, &forkEvent, 0U, nullptr);
|
||||
|
||||
Graph *subGraph = nullptr;
|
||||
testGraph.forkTo(subCmdList, subGraph, forkEvent);
|
||||
ASSERT_NE(subGraph, nullptr);
|
||||
|
||||
captureCommand<CaptureApi::zeCommandListAppendMemoryCopy>(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<Event> joinEvent;
|
||||
testGraph.tryJoinOnNextCommand(subCmdList, joinEvent);
|
||||
captureCommand<CaptureApi::zeCommandListAppendBarrier>(mainCmdList, testGraphPtr, &mainCmdList, &forkEvent, 0U, nullptr);
|
||||
testGraph.stopCapturing();
|
||||
}
|
||||
|
||||
TEST_F(GraphDotExporterTest, GivenGraphWithEmptyUnjoinedSubgraphWhenWriteUnjoinedForkEdgesThenOutputIsEmpty) {
|
||||
Graph testGraph{&ctx, true};
|
||||
Mock<Event> forkEvent;
|
||||
Mock<CommandList> mainCmdList;
|
||||
Mock<CommandList> subCmdList;
|
||||
|
||||
Graph *testGraphPtr = &testGraph;
|
||||
captureCommand<CaptureApi::zeCommandListAppendBarrier>(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<Event> joinEvent;
|
||||
testGraph.tryJoinOnNextCommand(subCmdList, joinEvent);
|
||||
captureCommand<CaptureApi::zeCommandListAppendBarrier>(mainCmdList, testGraphPtr, &mainCmdList, &forkEvent, 0U, nullptr);
|
||||
testGraph.stopCapturing();
|
||||
}
|
||||
|
||||
class GraphDotExporterFileTest : public GraphDotExporterTest {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
GraphDotExporterTest::SetUp();
|
||||
|
||||
fopenBackup = std::make_unique<VariableBackup<decltype(NEO::IoFunctions::fopenPtr)>>(&NEO::IoFunctions::fopenPtr, NEO::IoFunctions::mockFopen);
|
||||
fwriteBackup = std::make_unique<VariableBackup<decltype(NEO::IoFunctions::fwritePtr)>>(&NEO::IoFunctions::fwritePtr, NEO::IoFunctions::mockFwrite);
|
||||
fcloseBackup = std::make_unique<VariableBackup<decltype(NEO::IoFunctions::fclosePtr)>>(&NEO::IoFunctions::fclosePtr, NEO::IoFunctions::mockFclose);
|
||||
|
||||
mockFopenReturnedBackup = std::make_unique<VariableBackup<FILE *>>(&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<VariableBackup<size_t>>(&NEO::IoFunctions::mockFwriteReturn, expectedContent.size());
|
||||
|
||||
if (expectedContent.size() > 0) {
|
||||
buffer = std::make_unique<char[]>(expectedContent.size() + 1);
|
||||
memset(buffer.get(), 0, expectedContent.size() + 1);
|
||||
mockFwriteBufferBackup = std::make_unique<VariableBackup<char *>>(&NEO::IoFunctions::mockFwriteBuffer, buffer.get());
|
||||
}
|
||||
}
|
||||
|
||||
void setupFailedOpen() {
|
||||
*mockFopenReturnedBackup = static_cast<FILE *>(nullptr);
|
||||
}
|
||||
|
||||
void setupFailedWrite() {
|
||||
mockFwriteReturnBackup = std::make_unique<VariableBackup<size_t>>(&NEO::IoFunctions::mockFwriteReturn, static_cast<size_t>(0));
|
||||
}
|
||||
|
||||
std::string getWrittenContent() const {
|
||||
return buffer ? std::string(buffer.get()) : std::string{};
|
||||
}
|
||||
|
||||
std::unique_ptr<VariableBackup<decltype(NEO::IoFunctions::fopenPtr)>> fopenBackup;
|
||||
std::unique_ptr<VariableBackup<decltype(NEO::IoFunctions::fwritePtr)>> fwriteBackup;
|
||||
std::unique_ptr<VariableBackup<decltype(NEO::IoFunctions::fclosePtr)>> fcloseBackup;
|
||||
std::unique_ptr<VariableBackup<FILE *>> mockFopenReturnedBackup;
|
||||
std::unique_ptr<VariableBackup<size_t>> mockFwriteReturnBackup;
|
||||
std::unique_ptr<VariableBackup<char *>> mockFwriteBufferBackup;
|
||||
std::unique_ptr<char[]> 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
|
||||
@@ -7,11 +7,17 @@
|
||||
|
||||
#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 <optional>
|
||||
#include <sstream>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace L0 {
|
||||
|
||||
Graph::~Graph() {
|
||||
@@ -394,4 +400,256 @@ 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<size_t>(level + 1) * 2, ' ');
|
||||
dot << indent << "// Command nodes:\n";
|
||||
|
||||
const auto &commands = graph.getCapturedCommands();
|
||||
for (CapturedCommandId cmdId = 0; cmdId < static_cast<uint32_t>(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<size_t>(level + 1) * 2, ' ');
|
||||
|
||||
const auto &commands = graph.getCapturedCommands();
|
||||
dot << indent << "// Sequential edges:\n";
|
||||
|
||||
for (CapturedCommandId cmdId = 1; cmdId < static_cast<uint32_t>(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<size_t>(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<uint32_t>(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<size_t>(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<uint32_t> GraphDotExporter::findSubgraphIndex(const StackVec<Graph *, 16> &subGraphs, const Graph *targetGraph) const {
|
||||
for (uint32_t i = 0; i < static_cast<uint32_t>(subGraphs.size()); ++i) {
|
||||
if (subGraphs[i] == targetGraph) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<uint32_t> GraphDotExporter::findSubgraphIndexByCommandList(const StackVec<Graph *, 16> &subGraphs, const L0::CommandList *cmdList) const {
|
||||
for (uint32_t i = 0; i < static_cast<uint32_t>(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<size_t>(level + 1) * 2, ' ');
|
||||
dot << indent << "// Subgraphs:\n";
|
||||
|
||||
for (uint32_t subgraphId = 0; subgraphId < static_cast<uint32_t>(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<CaptureApi>(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<CaptureApi>(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<std::string> colors = {
|
||||
"grey90", // Level 1
|
||||
"grey80", // Level 2
|
||||
"grey70", // Level 3
|
||||
"grey60", // Level 4
|
||||
"grey50" // Level 5+
|
||||
};
|
||||
|
||||
size_t colorIndex = static_cast<size_t>(level) - 1;
|
||||
if (colorIndex >= colors.size()) {
|
||||
colorIndex = colors.size() - 1;
|
||||
}
|
||||
|
||||
return colors[colorIndex];
|
||||
}
|
||||
|
||||
} // namespace L0
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <unordered_map>
|
||||
#include <variant>
|
||||
@@ -51,6 +52,22 @@ using ClosureVariants = std::variant<
|
||||
using CapturedCommand = ClosureVariants;
|
||||
|
||||
using CapturedCommandId = uint32_t;
|
||||
|
||||
struct Graph;
|
||||
|
||||
struct ForkInfo {
|
||||
CapturedCommandId forkCommandId = 0;
|
||||
ze_event_handle_t forkEvent = nullptr;
|
||||
};
|
||||
|
||||
struct ForkJoinInfo {
|
||||
CapturedCommandId forkCommandId = 0;
|
||||
CapturedCommandId joinCommandId = 0;
|
||||
ze_event_handle_t forkEvent = nullptr;
|
||||
ze_event_handle_t joinEvent = nullptr;
|
||||
Graph *forkDestiny = nullptr;
|
||||
};
|
||||
|
||||
struct Graph : _ze_graph_handle_t {
|
||||
Graph(L0::Context *ctx, bool preallocated) : ctx(ctx), preallocated(preallocated) {
|
||||
commands.reserve(16);
|
||||
@@ -85,10 +102,22 @@ struct Graph : _ze_graph_handle_t {
|
||||
return ZE_RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
const std::vector<CapturedCommand> &getCapturedCommands() {
|
||||
const std::vector<CapturedCommand> &getCapturedCommands() const {
|
||||
return commands;
|
||||
}
|
||||
|
||||
const StackVec<Graph *, 16> &getSubgraphs() const {
|
||||
return subGraphs;
|
||||
}
|
||||
|
||||
const std::unordered_map<CapturedCommandId, ForkJoinInfo> &getJoinedForks() const {
|
||||
return joinedForks;
|
||||
}
|
||||
|
||||
const std::unordered_map<L0::CommandList *, ForkInfo> &getUnjoinedForks() const {
|
||||
return unjoinedForks;
|
||||
}
|
||||
|
||||
Graph *getJoinedForkTarget(CapturedCommandId cmdId) {
|
||||
auto it = joinedForks.find(cmdId);
|
||||
if (joinedForks.end() == it) {
|
||||
@@ -165,21 +194,7 @@ struct Graph : _ze_graph_handle_t {
|
||||
bool wasCapturingStopped = false;
|
||||
|
||||
std::unordered_map<L0::Event *, CapturedCommandId> recordedSignals;
|
||||
|
||||
struct ForkInfo {
|
||||
CapturedCommandId forkCommandId = 0;
|
||||
ze_event_handle_t forkEvent = nullptr;
|
||||
};
|
||||
|
||||
std::unordered_map<L0::CommandList *, ForkInfo> unjoinedForks;
|
||||
|
||||
struct ForkJoinInfo {
|
||||
CapturedCommandId forkCommandId = 0;
|
||||
CapturedCommandId joinCommandId = 0;
|
||||
ze_event_handle_t forkEvent = nullptr;
|
||||
ze_event_handle_t joinEvent = nullptr;
|
||||
Graph *forkDestiny = nullptr;
|
||||
};
|
||||
std::unordered_map<CapturedCommandId, ForkJoinInfo> joinedForks;
|
||||
};
|
||||
|
||||
@@ -272,6 +287,31 @@ 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<uint32_t> findSubgraphIndex(const StackVec<Graph *, 16> &subGraphs, const Graph *targetGraph) const;
|
||||
std::optional<uint32_t> findSubgraphIndexByCommandList(const StackVec<Graph *, 16> &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<CaptureApi::X>) <= maxVariantSize, #X " is too big for common variant. Please export some of its state to ClosureExternalStorage");
|
||||
|
||||
@@ -30,7 +30,7 @@ using fseekFuncPtr = int (*)(FILE *, long int, int);
|
||||
using ftellFuncPtr = long int (*)(FILE *);
|
||||
using rewindFuncPtr = decltype(&rewind);
|
||||
using freadFuncPtr = size_t (*)(void *, size_t, size_t, FILE *);
|
||||
using fwriteFuncPtr = decltype(&fwrite);
|
||||
using fwriteFuncPtr = size_t (*)(const void *, size_t, size_t, FILE *);
|
||||
using fflushFuncPtr = decltype(&fflush);
|
||||
using mkdirFuncPtr = int (*)(const char *);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user