Skip to content

Commit

Permalink
Graph Visualization: Added hierarchies for commands and subcommands
Browse files Browse the repository at this point in the history
based on CommandBuffer and QueueSubmit. Added flag to get the graph
visualization in formats: 'pbtxt' (tensorboard) or 'dot' (graphviz).

Subcommand Name: To get the name for subcommands, it is used
the initialization commands (no-real commands).
  • Loading branch information
elviscapiaq committed Jan 9, 2019
1 parent 128ea1c commit 9b7d33d
Show file tree
Hide file tree
Showing 12 changed files with 210 additions and 57 deletions.
18 changes: 16 additions & 2 deletions cmd/gapit/create_graph_visualization.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"flag"
"github.com/google/gapid/core/app"
"github.com/google/gapid/core/log"
"github.com/google/gapid/gapis/service"
"os"
)

Expand All @@ -38,6 +39,19 @@ func (verb *createGraphVisualizationVerb) Run(ctx context.Context, flags flag.Fl
app.Usage(ctx, "Exactly one gfx trace file expected, got %d", flags.NArg())
return nil
}
if verb.Format == "" {
app.Usage(ctx, "specify an output format with --format <format> (supported formats: pbtxt and dot)")
return nil
}
var format service.GraphFormat
if verb.Format == "pbtxt" {
format = service.GraphFormat_PBTXT
} else if verb.Format == "dot" {
format = service.GraphFormat_DOT
} else {
app.Usage(ctx, "invalid format (supported formats: pbtxt and dot)")
return nil
}

client, capture, err := getGapisAndLoadCapture(ctx, verb.Gapis, GapirFlags{}, flags.Arg(0), CaptureFileFlags{})
if err != nil {
Expand All @@ -47,14 +61,14 @@ func (verb *createGraphVisualizationVerb) Run(ctx context.Context, flags flag.Fl

log.I(ctx, "Creating graph visualization file from capture id: %s", capture.ID)

graphVisualization, err := client.GetGraphVisualization(ctx, capture)
graphVisualization, err := client.GetGraphVisualization(ctx, capture, format)
if err != nil {
return log.Errf(ctx, err, "GetGraphVisualization(%v)", capture)
}

filePath := verb.Out
if filePath == "" {
filePath = "graph_visualization.dot"
filePath = "graph_visualization." + verb.Format
}

file, err := os.Create(filePath)
Expand Down
5 changes: 3 additions & 2 deletions cmd/gapit/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,8 @@ type (
}

CreateGraphVisualizationFlags struct {
Gapis GapisFlags
Out string `help:"path to save graph visualization"`
Gapis GapisFlags
Out string `help:"path to save graph visualization"`
Format string `help:""output format of the graph: 'pbtxt' (Tensorboard) or 'dot' (Graphviz)""`
}
)
25 changes: 23 additions & 2 deletions gapis/api/graph_visualization.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,29 @@ func (h *Hierarchy) IncreaseIDByOne(level int) {
h.LevelsID[level-1]++
}

type HierarchyNames struct {
BeginNameToLevel map[string]int
EndNameToLevel map[string]int
NameOfLevels []string
}

func (hierarchyNames *HierarchyNames) GetName(level int) string {
return hierarchyNames.NameOfLevels[level-1]
}

func (hierarchyNames *HierarchyNames) PushBack(beginName, endName, name string) {
size := len(hierarchyNames.NameOfLevels) + 1
hierarchyNames.BeginNameToLevel[beginName] = size
hierarchyNames.EndNameToLevel[endName] = size
hierarchyNames.NameOfLevels = append(hierarchyNames.NameOfLevels, name)
}

type GraphVisualizationAPI interface {
GetCommandLabel(currentHierarchy *Hierarchy, command Cmd) string
GetGraphVisualizationBuilder() GraphVisualizationBuilder
}

type GraphVisualizationBuilder interface {
GetCommandLabel(command Cmd, commandNodeId uint64) string

GetSubCommandLabel(index SubCmdIdx) string
GetSubCommandLabel(index SubCmdIdx, commandName string, subCommandName string) string
}
112 changes: 81 additions & 31 deletions gapis/api/vulkan/graph_visualization.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (

// Interface compliance test
var (
_ = api.GraphVisualizationBuilder(&labelForVulkanCommands{})
_ = api.GraphVisualizationAPI(API{})
)

Expand All @@ -37,53 +38,103 @@ const (
)

var (
beginCommands = map[string]int{
VK_BEGIN_COMMAND_BUFFER: 1,
VK_CMD_BEGIN_RENDER_PASS: 2,
VK_CMD_NEXT_SUBPASS: 3,
}
listOfCommandNames = []string{
VK_COMMAND_BUFFER,
VK_RENDER_PASS,
VK_SUBPASS,
}
endCommands = map[string]int{
VK_END_COMMAND_BUFFER: 1,
VK_CMD_END_RENDER_PASS: 2,
VK_CMD_NEXT_SUBPASS: 3,
}
commandHierarchyNames = getCommandHierarchyNames()
subCommandHierarchyNames = getSubCommandHierarchyNames()
)

type labelForVulkanCommands struct {
labelToHierarchy map[string]*api.Hierarchy
subCommandIndexNameToHierarchyLabel map[string]string
}

func getCommandHierarchyNames() *api.HierarchyNames {
commandHierarchyNames := &api.HierarchyNames{BeginNameToLevel: map[string]int{}, EndNameToLevel: map[string]int{},
NameOfLevels: []string{}}

commandHierarchyNames.PushBack(VK_BEGIN_COMMAND_BUFFER, VK_END_COMMAND_BUFFER, VK_COMMAND_BUFFER)
commandHierarchyNames.PushBack(VK_CMD_BEGIN_RENDER_PASS, VK_CMD_END_RENDER_PASS, VK_RENDER_PASS)
commandHierarchyNames.PushBack(VK_CMD_NEXT_SUBPASS, VK_CMD_NEXT_SUBPASS, VK_SUBPASS)
return commandHierarchyNames
}

func getSubCommandHierarchyNames() *api.HierarchyNames {
subCommandHierarchyNames := &api.HierarchyNames{BeginNameToLevel: map[string]int{}, EndNameToLevel: map[string]int{},
NameOfLevels: []string{}}

subCommandHierarchyNames.PushBack(VK_CMD_BEGIN_RENDER_PASS, VK_CMD_END_RENDER_PASS, VK_RENDER_PASS)
subCommandHierarchyNames.PushBack(VK_CMD_NEXT_SUBPASS, VK_CMD_NEXT_SUBPASS, VK_SUBPASS)
return subCommandHierarchyNames
}

func getCommandBuffer(command api.Cmd) string {
parameters := command.CmdParams()
for _, parameter := range parameters {
if parameter.Name == COMMAND_BUFFER {
commandBuffer := parameter.Name + fmt.Sprintf("%d", parameter.Get()) + "/"
commandBuffer := fmt.Sprintf("%s_%d", parameter.Name, parameter.Get())
return commandBuffer
}
}
return ""
}

func (API) GetCommandLabel(hierarchy *api.Hierarchy, command api.Cmd) string {
func (builder *labelForVulkanCommands) GetCommandLabel(command api.Cmd, commandNodeId uint64) string {
commandName := command.CmdName()
label := ""
if commandBuffer := getCommandBuffer(command); commandBuffer != "" {
if _, ok := builder.labelToHierarchy[commandBuffer]; !ok {
builder.labelToHierarchy[commandBuffer] = &api.Hierarchy{}
}
hierarchy := builder.labelToHierarchy[commandBuffer]
label += commandBuffer + "/"
label += getLabelFromHierarchy(commandName, commandHierarchyNames, hierarchy)
label += fmt.Sprintf("%s_%d", commandName, commandNodeId)
} else {
label += fmt.Sprintf("%s_%d", commandName, commandNodeId)
}
return label
}

func (builder *labelForVulkanCommands) GetSubCommandLabel(index api.SubCmdIdx, commandName, subCommandName string) string {
label := commandName
subCommandIndexName := commandName
for i := 1; i < len(index); i++ {
subCommandIndexName += fmt.Sprintf("/%d", index[i])
if i+1 < len(index) {
if hierarchyLabel, ok := builder.subCommandIndexNameToHierarchyLabel[subCommandIndexName]; ok {
label += "/" + hierarchyLabel
} else {
label += fmt.Sprintf("/%d", index[i])
}
}
}
if _, ok := builder.labelToHierarchy[label]; !ok {
builder.labelToHierarchy[label] = &api.Hierarchy{}
}
hierarchy := builder.labelToHierarchy[label]
labelFromHierarchy := getLabelFromHierarchy(subCommandName, subCommandHierarchyNames, hierarchy)
labelFromHierarchy += fmt.Sprintf("%s_%d", subCommandName, index[len(index)-1])
builder.subCommandIndexNameToHierarchyLabel[subCommandIndexName] = labelFromHierarchy

label += "/" + labelFromHierarchy
return label
}

func getLabelFromHierarchy(name string, hierarchyNames *api.HierarchyNames, hierarchy *api.Hierarchy) string {
isEndCommand := false
if level, ok := beginCommands[commandName]; ok {
if level, ok := hierarchyNames.BeginNameToLevel[name]; ok {
hierarchy.PushBackToResize(level + 1)
hierarchy.IncreaseIDByOne(level)
} else {
if level, ok := endCommands[commandName]; ok {
hierarchy.PopBackToResize(level + 1)
isEndCommand = true
}
} else if level, ok := hierarchyNames.EndNameToLevel[name]; ok {
hierarchy.PopBackToResize(level + 1)
isEndCommand = true
}

label := getCommandBuffer(command)
label := ""
for level := 1; level < hierarchy.GetSize(); level++ {
label += fmt.Sprintf("%s%d/", listOfCommandNames[level-1], hierarchy.GetID(level))
label += fmt.Sprintf("%d_%s/", hierarchy.GetID(level), hierarchyNames.GetName(level))
}

if level, ok := beginCommands[commandName]; ok && commandName == VK_CMD_BEGIN_RENDER_PASS {
if level, ok := hierarchyNames.BeginNameToLevel[name]; ok && name == VK_CMD_BEGIN_RENDER_PASS {
levelForSubpass := level + 1
hierarchy.PushBackToResize(levelForSubpass + 1)
hierarchy.IncreaseIDByOne(levelForSubpass)
Expand All @@ -94,10 +145,9 @@ func (API) GetCommandLabel(hierarchy *api.Hierarchy, command api.Cmd) string {
return label
}

func (API) GetSubCommandLabel(index api.SubCmdIdx) string {
label := ""
for i := 1; i < len(index); i++ {
label += fmt.Sprintf("/%d", index[i])
func (API) GetGraphVisualizationBuilder() api.GraphVisualizationBuilder {
return &labelForVulkanCommands{
labelToHierarchy: map[string]*api.Hierarchy{},
subCommandIndexNameToHierarchyLabel: map[string]string{},
}
return label
}
3 changes: 2 additions & 1 deletion gapis/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -485,9 +485,10 @@ func (c *client) GetTimestamps(ctx context.Context, capture *path.Capture, devic
return res, nil
}

func (c *client) GetGraphVisualization(ctx context.Context, capture *path.Capture) ([]byte, error) {
func (c *client) GetGraphVisualization(ctx context.Context, capture *path.Capture, format service.GraphFormat) ([]byte, error) {
res, err := c.client.GetGraphVisualization(ctx, &service.GraphVisualizationRequest{
Capture: capture,
Format: format,
})
if err != nil {
return []byte{}, err
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ go_library(
deps = [
"//gapis/api:go_default_library",
"//gapis/resolve/dependencygraph2:go_default_library",
"//gapis/service:go_default_library",
"//gapis/service/path:go_default_library",
],
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package graph_visualization

import (
"fmt"
"sort"
"strconv"
)

Expand All @@ -32,6 +33,7 @@ type node struct {
label string
name string
attributes string
isReal bool
}

type edge struct {
Expand Down Expand Up @@ -176,14 +178,26 @@ func (g *graph) removeNodePreservingEdges(idNode int) {
g.removeNodeById(idNode)
}

type nodeSorter []*node

func (input nodeSorter) Len() int {
return len(input)
}
func (input nodeSorter) Swap(i, j int) {
input[i], input[j] = input[j], input[i]
}
func (input nodeSorter) Less(i, j int) bool {
return input[i].id < input[j].id
}

func (g *graph) traverseGraph(currentNode *node, visitTime, minVisitTime, idInStronglyConnectedComponents, visitedNodesId *[]int, currentId, currentTime *int) {
*visitedNodesId = append(*visitedNodesId, currentNode.id)
(*visitTime)[currentNode.id] = *currentTime
(*minVisitTime)[currentNode.id] = *currentTime
(*currentTime)++

for idNeighbour := range currentNode.outNeighbourIdToEdgeId {
neighbour := g.nodeIdToNode[idNeighbour]
for neighbourId := range currentNode.outNeighbourIdToEdgeId {
neighbour := g.nodeIdToNode[neighbourId]
if (*visitTime)[neighbour.id] == NO_VISITED {
g.traverseGraph(neighbour, visitTime, minVisitTime, idInStronglyConnectedComponents, visitedNodesId, currentId, currentTime)
}
Expand Down Expand Up @@ -232,8 +246,8 @@ func (g *graph) makeStronglyConnectedComponentsByCommandTypeId() {
}

for _, currentNode := range g.nodeIdToNode {
for idNeighbour := range currentNode.outNeighbourIdToEdgeId {
neighbour := g.nodeIdToNode[idNeighbour]
for neighbourId := range currentNode.outNeighbourIdToEdgeId {
neighbour := g.nodeIdToNode[neighbourId]
newGraph.addEdgeBetweenNodesById(currentNode.commandTypeId, neighbour.commandTypeId)
}
}
Expand Down Expand Up @@ -271,13 +285,31 @@ func (g *graph) getGraphInDotFormat() string {
}

func (g *graph) getGraphInPbtxtFormat() string {
output := ""
nodes := []*node{}
for _, currentNode := range g.nodeIdToNode {
nodes = append(nodes, currentNode)
}
sort.Sort(nodeSorter(nodes))

output := ""
for _, currentNode := range nodes {
if !currentNode.isReal {
continue
}
lines := "node {\n"
lines += "name: \"" + currentNode.label + "\"\n"
lines += "op: \"" + currentNode.label + "\"\n"
for idNeighbour := range currentNode.inNeighbourIdToEdgeId {
neighbour := g.nodeIdToNode[idNeighbour]

neighbours := []*node{}
for neighbourId := range currentNode.inNeighbourIdToEdgeId {
neighbours = append(neighbours, g.nodeIdToNode[neighbourId])
}
sort.Sort(nodeSorter(neighbours))

for _, neighbour := range neighbours {
if !neighbour.isReal {
continue
}
lines += "input: \"" + neighbour.label + "\"\n"
}
lines += "attr {\n"
Expand Down
Loading

0 comments on commit 9b7d33d

Please sign in to comment.