dump.go 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. /*
  2. Copyright 2018 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package garbagecollector
  14. import (
  15. "fmt"
  16. "net/http"
  17. "strings"
  18. "gonum.org/v1/gonum/graph"
  19. "gonum.org/v1/gonum/graph/encoding"
  20. "gonum.org/v1/gonum/graph/encoding/dot"
  21. "gonum.org/v1/gonum/graph/simple"
  22. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  23. "k8s.io/apimachinery/pkg/runtime/schema"
  24. "k8s.io/apimachinery/pkg/types"
  25. utilruntime "k8s.io/apimachinery/pkg/util/runtime"
  26. )
  27. type gonumVertex struct {
  28. uid types.UID
  29. gvk schema.GroupVersionKind
  30. namespace string
  31. name string
  32. missingFromGraph bool
  33. beingDeleted bool
  34. deletingDependents bool
  35. virtual bool
  36. vertexID int64
  37. }
  38. func (v *gonumVertex) ID() int64 {
  39. return v.vertexID
  40. }
  41. func (v *gonumVertex) String() string {
  42. kind := v.gvk.Kind + "." + v.gvk.Version
  43. if len(v.gvk.Group) > 0 {
  44. kind = kind + "." + v.gvk.Group
  45. }
  46. missing := ""
  47. if v.missingFromGraph {
  48. missing = "(missing)"
  49. }
  50. deleting := ""
  51. if v.beingDeleted {
  52. deleting = "(deleting)"
  53. }
  54. deletingDependents := ""
  55. if v.deletingDependents {
  56. deleting = "(deletingDependents)"
  57. }
  58. virtual := ""
  59. if v.virtual {
  60. virtual = "(virtual)"
  61. }
  62. return fmt.Sprintf(`%s/%s[%s]-%v%s%s%s%s`, kind, v.name, v.namespace, v.uid, missing, deleting, deletingDependents, virtual)
  63. }
  64. func (v *gonumVertex) Attributes() []encoding.Attribute {
  65. kubectlString := v.gvk.Kind + "." + v.gvk.Version
  66. if len(v.gvk.Group) > 0 {
  67. kubectlString = kubectlString + "." + v.gvk.Group
  68. }
  69. kubectlString = kubectlString + "/" + v.name
  70. label := fmt.Sprintf(`uid=%v
  71. namespace=%v
  72. %v
  73. `,
  74. v.uid,
  75. v.namespace,
  76. kubectlString,
  77. )
  78. conditionStrings := []string{}
  79. if v.beingDeleted {
  80. conditionStrings = append(conditionStrings, "beingDeleted")
  81. }
  82. if v.deletingDependents {
  83. conditionStrings = append(conditionStrings, "deletingDependents")
  84. }
  85. if v.virtual {
  86. conditionStrings = append(conditionStrings, "virtual")
  87. }
  88. if v.missingFromGraph {
  89. conditionStrings = append(conditionStrings, "missingFromGraph")
  90. }
  91. conditionString := strings.Join(conditionStrings, ",")
  92. if len(conditionString) > 0 {
  93. label = label + conditionString + "\n"
  94. }
  95. return []encoding.Attribute{
  96. {Key: "label", Value: fmt.Sprintf(`"%v"`, label)},
  97. // these place metadata in the correct location, but don't conform to any normal attribute for rendering
  98. {Key: "group", Value: fmt.Sprintf(`"%v"`, v.gvk.Group)},
  99. {Key: "version", Value: fmt.Sprintf(`"%v"`, v.gvk.Version)},
  100. {Key: "kind", Value: fmt.Sprintf(`"%v"`, v.gvk.Kind)},
  101. {Key: "namespace", Value: fmt.Sprintf(`"%v"`, v.namespace)},
  102. {Key: "name", Value: fmt.Sprintf(`"%v"`, v.name)},
  103. {Key: "uid", Value: fmt.Sprintf(`"%v"`, v.uid)},
  104. {Key: "missing", Value: fmt.Sprintf(`"%v"`, v.missingFromGraph)},
  105. {Key: "beingDeleted", Value: fmt.Sprintf(`"%v"`, v.beingDeleted)},
  106. {Key: "deletingDependents", Value: fmt.Sprintf(`"%v"`, v.deletingDependents)},
  107. {Key: "virtual", Value: fmt.Sprintf(`"%v"`, v.virtual)},
  108. }
  109. }
  110. // NewGonumVertex creates a new gonumVertex.
  111. func NewGonumVertex(node *node, nodeID int64) *gonumVertex {
  112. gv, err := schema.ParseGroupVersion(node.identity.APIVersion)
  113. if err != nil {
  114. // this indicates a bad data serialization that should be prevented during storage of the API
  115. utilruntime.HandleError(err)
  116. }
  117. return &gonumVertex{
  118. uid: node.identity.UID,
  119. gvk: gv.WithKind(node.identity.Kind),
  120. namespace: node.identity.Namespace,
  121. name: node.identity.Name,
  122. beingDeleted: node.beingDeleted,
  123. deletingDependents: node.deletingDependents,
  124. virtual: node.virtual,
  125. vertexID: nodeID,
  126. }
  127. }
  128. // NewMissingGonumVertex creates a new gonumVertex.
  129. func NewMissingGonumVertex(ownerRef metav1.OwnerReference, nodeID int64) *gonumVertex {
  130. gv, err := schema.ParseGroupVersion(ownerRef.APIVersion)
  131. if err != nil {
  132. // this indicates a bad data serialization that should be prevented during storage of the API
  133. utilruntime.HandleError(err)
  134. }
  135. return &gonumVertex{
  136. uid: ownerRef.UID,
  137. gvk: gv.WithKind(ownerRef.Kind),
  138. name: ownerRef.Name,
  139. missingFromGraph: true,
  140. vertexID: nodeID,
  141. }
  142. }
  143. func (m *concurrentUIDToNode) ToGonumGraph() graph.Directed {
  144. m.uidToNodeLock.Lock()
  145. defer m.uidToNodeLock.Unlock()
  146. return toGonumGraph(m.uidToNode)
  147. }
  148. func toGonumGraph(uidToNode map[types.UID]*node) graph.Directed {
  149. uidToVertex := map[types.UID]*gonumVertex{}
  150. graphBuilder := simple.NewDirectedGraph()
  151. // add the vertices first, then edges. That avoids having to deal with missing refs.
  152. for _, node := range uidToNode {
  153. // skip adding objects that don't have owner references and aren't referred to.
  154. if len(node.dependents) == 0 && len(node.owners) == 0 {
  155. continue
  156. }
  157. vertex := NewGonumVertex(node, graphBuilder.NewNode().ID())
  158. uidToVertex[node.identity.UID] = vertex
  159. graphBuilder.AddNode(vertex)
  160. }
  161. for _, node := range uidToNode {
  162. currVertex := uidToVertex[node.identity.UID]
  163. for _, ownerRef := range node.owners {
  164. currOwnerVertex, ok := uidToVertex[ownerRef.UID]
  165. if !ok {
  166. currOwnerVertex = NewMissingGonumVertex(ownerRef, graphBuilder.NewNode().ID())
  167. uidToVertex[node.identity.UID] = currOwnerVertex
  168. graphBuilder.AddNode(currOwnerVertex)
  169. }
  170. graphBuilder.SetEdge(simple.Edge{
  171. F: currVertex,
  172. T: currOwnerVertex,
  173. })
  174. }
  175. }
  176. return graphBuilder
  177. }
  178. func (m *concurrentUIDToNode) ToGonumGraphForObj(uids ...types.UID) graph.Directed {
  179. m.uidToNodeLock.Lock()
  180. defer m.uidToNodeLock.Unlock()
  181. return toGonumGraphForObj(m.uidToNode, uids...)
  182. }
  183. func toGonumGraphForObj(uidToNode map[types.UID]*node, uids ...types.UID) graph.Directed {
  184. uidsToCheck := append([]types.UID{}, uids...)
  185. interestingNodes := map[types.UID]*node{}
  186. // build the set of nodes to inspect first, then use the normal construction on the subset
  187. for i := 0; i < len(uidsToCheck); i++ {
  188. uid := uidsToCheck[i]
  189. // if we've already been observed, there was a bug, but skip it so we don't loop forever
  190. if _, ok := interestingNodes[uid]; ok {
  191. continue
  192. }
  193. node, ok := uidToNode[uid]
  194. // if there is no node for the UID, skip over it. We may add it to the list multiple times
  195. // but we won't loop forever and hopefully the condition doesn't happen very often
  196. if !ok {
  197. continue
  198. }
  199. interestingNodes[node.identity.UID] = node
  200. for _, ownerRef := range node.owners {
  201. // if we've already inspected this UID, don't add it to be inspected again
  202. if _, ok := interestingNodes[ownerRef.UID]; ok {
  203. continue
  204. }
  205. uidsToCheck = append(uidsToCheck, ownerRef.UID)
  206. }
  207. for dependent := range node.dependents {
  208. // if we've already inspected this UID, don't add it to be inspected again
  209. if _, ok := interestingNodes[dependent.identity.UID]; ok {
  210. continue
  211. }
  212. uidsToCheck = append(uidsToCheck, dependent.identity.UID)
  213. }
  214. }
  215. return toGonumGraph(interestingNodes)
  216. }
  217. // NewDebugHandler creates a new debugHTTPHandler.
  218. func NewDebugHandler(controller *GarbageCollector) http.Handler {
  219. return &debugHTTPHandler{controller: controller}
  220. }
  221. type debugHTTPHandler struct {
  222. controller *GarbageCollector
  223. }
  224. func (h *debugHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
  225. if req.URL.Path != "/graph" {
  226. http.Error(w, "", http.StatusNotFound)
  227. return
  228. }
  229. var graph graph.Directed
  230. if uidStrings := req.URL.Query()["uid"]; len(uidStrings) > 0 {
  231. uids := []types.UID{}
  232. for _, uidString := range uidStrings {
  233. uids = append(uids, types.UID(uidString))
  234. }
  235. graph = h.controller.dependencyGraphBuilder.uidToNode.ToGonumGraphForObj(uids...)
  236. } else {
  237. graph = h.controller.dependencyGraphBuilder.uidToNode.ToGonumGraph()
  238. }
  239. data, err := dot.Marshal(graph, "full", "", " ")
  240. if err != nil {
  241. http.Error(w, err.Error(), http.StatusInternalServerError)
  242. return
  243. }
  244. w.Header().Set("Content-Type", "text/vnd.graphviz")
  245. w.Header().Set("X-Content-Type-Options", "nosniff")
  246. w.Write(data)
  247. w.WriteHeader(http.StatusOK)
  248. }