// Copyright (c) 2019 Uber Technologies, Inc.//// Permission is hereby granted, free of charge, to any person obtaining a copy// of this software and associated documentation files (the "Software"), to deal// in the Software without restriction, including without limitation the rights// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell// copies of the Software, and to permit persons to whom the Software is// furnished to do so, subject to the following conditions://// The above copyright notice and this permission notice shall be included in// all copies or substantial portions of the Software.//// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN// THE SOFTWARE.package dotimport ()// ErrorType of a constructor or group is updated when they fail to build.typeErrorTypeintconst ( noError ErrorType = iota rootCause transitiveFailure)// CtorID is a unique numeric identifier for constructors.typeCtorIDuintptr// Ctor encodes a constructor provided to the container for the DOT graph.typeCtorstruct { Name string Package string File string Line int ID CtorID Params []*Param GroupParams []*Group Results []*Result ErrorType ErrorType}// removeParam deletes the dependency on the provided result's nodeKey.// This is used to prune links to results of deleted constructors.func ( *Ctor) ( nodeKey) {var []*Paramfor , := range .Params {if != .nodeKey() { = append(, ) } } .Params = }type nodeKey struct { t reflect.Type name string group string}// Node is a single node in a graph and is embedded into Params and Results.typeNodestruct { Type reflect.Type Name string Group string}func ( *Node) () nodeKey {returnnodeKey{t: .Type, name: .Name, group: .Group}}// Param is a parameter node in the graph. Parameters are the input to constructors.typeParamstruct { *Node Optional bool}// Result is a result node in the graph. Results are the output of constructors.typeResultstruct { *Node// GroupIndex is added to differentiate grouped values from one another. // Since grouped values have the same type and group, their Node / string // representations are the same so we need indices to uniquely identify // the values. GroupIndex int}// Group is a group node in the graph. Group represents an fx value group.typeGroupstruct {// Type is the type of values in the group. Type reflect.Type Name string Results []*Result ErrorType ErrorType}func ( *Group) () nodeKey {returnnodeKey{t: .Type, group: .Name}}// TODO(rhang): Avoid linear search to discover group results that should be pruned.func ( *Group) ( *Result) {var []*Resultfor , := range .Results {if .GroupIndex != .GroupIndex { = append(, ) } } .Results = }// Graph is the DOT-format graph in a Container.typeGraphstruct { Ctors []*Ctor ctorMap map[CtorID]*Ctor Groups []*Group groupMap map[nodeKey]*Group consumers map[nodeKey][]*Ctor Failed *FailedNodes}// FailedNodes is the nodes that failed in the graph.typeFailedNodesstruct {// RootCauses is a list of the point of failures. They are the root causes // of failed invokes and can be either missing types (not provided) or // error types (error providing). RootCauses []*Result// TransitiveFailures is the list of nodes that failed to build due to // missing/failed dependencies. TransitiveFailures []*Result// ctors is a collection of failed constructors IDs that are populated as the graph is // traversed for errors. ctors map[CtorID]struct{}// Groups is a collection of failed groupKeys that is populated as the graph is traversed // for errors. groups map[nodeKey]struct{}}// NewGraph creates an empty graph.func () *Graph {return &Graph{ctorMap: make(map[CtorID]*Ctor),groupMap: make(map[nodeKey]*Group),consumers: make(map[nodeKey][]*Ctor),Failed: &FailedNodes{ctors: make(map[CtorID]struct{}),groups: make(map[nodeKey]struct{}), }, }}// NewGroup creates a new group with information in the groupKey.func ( nodeKey) *Group {return &Group{Type: .t,Name: .group, }}// AddCtor adds the constructor with paramList and resultList into the graph.func ( *Graph) ( *Ctor, []*Param, []*Result) {var ( []*Param []*Group )// Loop through the paramList to separate them into regular params and // grouped params. For grouped params, we use getGroup to find the actual // group.for , := range {if .Group == "" {// Not a value group. = append(, )continue } := nodeKey{t: .Type.Elem(), group: .Group} := .getGroup() = append(, ) }for , := range {// If the result is a grouped value, we want to update its GroupIndex // and add it to the Group.if .Group != "" { .addToGroup(, .ID) } } .Params = .GroupParams = .Results = // Track which constructors consume a parameter.for , := range { := .nodeKey() .consumers[] = append(.consumers[], ) } .Ctors = append(.Ctors, ) .ctorMap[.ID] = }func ( *Graph) ( *Result, bool) {if { .addRootCause() } else { .addTransitiveFailure() }}// AddMissingNodes adds missing nodes to the list of failed Results in the graph.func ( *Graph) ( []*Result) {// The failure(s) are root causes if there are no other failures. := len(.Failed.RootCauses) == 0for , := range { .failNode(, ) }}// FailNodes adds results to the list of failed Results in the graph, and// updates the state of the constructor with the given id accordingly.func ( *Graph) ( []*Result, CtorID) {// This failure is the root cause if there are no other failures. := len(.Failed.RootCauses) == 0 .Failed.ctors[] = struct{}{}for , := range { .failNode(, ) }if , := .ctorMap[]; {if { .ErrorType = rootCause } else { .ErrorType = transitiveFailure } }}// FailGroupNodes finds and adds the failed grouped nodes to the list of failed// Results in the graph, and updates the state of the group and constructor// with the given id accordingly.func ( *Graph) ( string, reflect.Type, CtorID) {// This failure is the root cause if there are no other failures. := len(.Failed.RootCauses) == 0 := nodeKey{t: , group: } := .getGroup()// If the ctor does not exist it cannot be failed.if , := .ctorMap[]; ! {return }// Track which constructors and groups have failed. .Failed.ctors[] = struct{}{} .Failed.groups[] = struct{}{}for , := range .ctorMap[].Results {if .Type == && .Group == { .failNode(, ) } }if , := .ctorMap[]; {if { .ErrorType = rootCause .ErrorType = rootCause } else { .ErrorType = transitiveFailure .ErrorType = transitiveFailure } }}// getGroup finds the group by nodeKey from the graph. If it is not available,// a new group is created and returned.func ( *Graph) ( nodeKey) *Group { , := .groupMap[]if ! { = NewGroup() .groupMap[] = .Groups = append(.Groups, ) }return}// addToGroup adds a newly provided grouped result to the appropriate group.func ( *Graph) ( *Result, CtorID) { := nodeKey{t: .Type, group: .Group} := .getGroup() .GroupIndex = len(.Results) .Results = append(.Results, )}// PruneSuccess removes elements from the graph that do not have failed results.// Removing elements that do not have failing results makes the graph easier to debug,// since non-failing nodes and edges can clutter the graph and don't help the user debug.func ( *Graph) () { .pruneCtors(.Failed.ctors) .pruneGroups(.Failed.groups)}// pruneCtors removes constructors from the graph that do not have failing Results.func ( *Graph) ( map[CtorID]struct{}) {var []*Ctorfor , := range .Ctors {if , := [.ID]; { = append(, )continue }// If a constructor is deleted, the constructor's stale result references need to // be removed from that result's Group and/or consuming constructor. .pruneCtorParams(, .consumers) .pruneGroupResults(, .groupMap)delete(.ctorMap, .ID) } .Ctors = }// pruneGroups removes groups from the graph that do not have failing results.func ( *Graph) ( map[nodeKey]struct{}) {var []*Groupfor , := range .Groups { := .nodeKey()if , := []; { = append(, )continue }delete(.groupMap, ) } .Groups = .pruneCtorGroupParams(.groupMap)}// pruneCtorParams removes results of the constructor argument that are still referenced in the// Params of constructors that consume those results. If the results in the constructor are found// in the params of a consuming constructor that result should be removed.func ( *Graph) ( *Ctor, map[nodeKey][]*Ctor) {for , := range .Results {for , := range [.nodeKey()] { .removeParam(.nodeKey()) } }}// pruneCtorGroupParams removes constructor results that are still referenced in the GroupParams of// constructors that consume those results.func ( *Graph) ( map[nodeKey]*Group) {for , := range .Ctors {var []*Groupfor , := range .GroupParams { := .nodeKey()if , := []; { = append(, ) } } .GroupParams = }}// pruneGroupResults removes results of the constructor argument that are still referenced in// the Group object that contains that result. If a group no longer exists references to that// should should be removed.func ( *Graph) ( *Ctor, map[nodeKey]*Group) {for , := range .Results { := .nodeKey()if .group == "" {continue } , := []if { .removeResult() } }}// String implements fmt.Stringer for Param.func ( *Param) () string {if .Name != "" {returnfmt.Sprintf("%v[name=%v]", .Type.String(), .Name) }return .Type.String()}// String implements fmt.Stringer for Result.func ( *Result) () string {switch {case .Name != "":returnfmt.Sprintf("%v[name=%v]", .Type.String(), .Name)case .Group != "":returnfmt.Sprintf("%v[group=%v]%v", .Type.String(), .Group, .GroupIndex)default:return .Type.String() }}// String implements fmt.Stringer for Group.func ( *Group) () string {returnfmt.Sprintf("[type=%v group=%v]", .Type.String(), .Name)}// Attributes composes and returns a string of the Result node's attributes.func ( *Result) () string {switch {case .Name != "":returnfmt.Sprintf(`label=<%v<BR /><FONT POINT-SIZE="10">Name: %v</FONT>>`, .Type, .Name)case .Group != "":returnfmt.Sprintf(`label=<%v<BR /><FONT POINT-SIZE="10">Group: %v</FONT>>`, .Type, .Group)default:returnfmt.Sprintf(`label=<%v>`, .Type) }}// Attributes composes and returns a string of the Group node's attributes.func ( *Group) () string { := fmt.Sprintf(`shape=diamond label=<%v<BR /><FONT POINT-SIZE="10">Group: %v</FONT>>`, .Type, .Name)if .ErrorType != noError { += " color=" + .ErrorType.Color() }return}// Color returns the color representation of each ErrorType.func ( ErrorType) () string {switch {caserootCause:return"red"casetransitiveFailure:return"orange"default:return"black" }}func ( *Graph) ( *Result) { .Failed.RootCauses = append(.Failed.RootCauses, )}func ( *Graph) ( *Result) { .Failed.TransitiveFailures = append(.Failed.TransitiveFailures, )}
The pages are generated with Goldsv0.8.2. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.