gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. // Copyright 2022 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package process
  4. import (
  5. "fmt"
  6. "io"
  7. "runtime/pprof"
  8. "sort"
  9. "time"
  10. "code.gitea.io/gitea/modules/gtprof"
  11. "github.com/google/pprof/profile"
  12. )
  13. // StackEntry is an entry on a stacktrace
  14. type StackEntry struct {
  15. Function string
  16. File string
  17. Line int
  18. }
  19. // Label represents a pprof label assigned to goroutine stack
  20. type Label struct {
  21. Name string
  22. Value string
  23. }
  24. // Stack is a stacktrace relating to a goroutine. (Multiple goroutines may have the same stacktrace)
  25. type Stack struct {
  26. Count int64 // Number of goroutines with this stack trace
  27. Description string
  28. Labels []*Label `json:",omitempty"`
  29. Entry []*StackEntry `json:",omitempty"`
  30. }
  31. // A Process is a combined representation of a Process and a Stacktrace for the goroutines associated with it
  32. type Process struct {
  33. PID IDType
  34. ParentPID IDType
  35. Description string
  36. Start time.Time
  37. Type string
  38. Children []*Process `json:",omitempty"`
  39. Stacks []*Stack `json:",omitempty"`
  40. }
  41. // Processes gets the processes in a thread safe manner
  42. func (pm *Manager) Processes(flat, noSystem bool) ([]*Process, int) {
  43. pm.mutex.Lock()
  44. processCount := len(pm.processMap)
  45. processes := make([]*Process, 0, len(pm.processMap))
  46. if flat {
  47. for _, process := range pm.processMap {
  48. if noSystem && process.Type == SystemProcessType {
  49. continue
  50. }
  51. processes = append(processes, process.toProcess())
  52. }
  53. } else {
  54. // We need our own processMap
  55. processMap := map[IDType]*Process{}
  56. for _, internalProcess := range pm.processMap {
  57. process, ok := processMap[internalProcess.PID]
  58. if !ok {
  59. process = internalProcess.toProcess()
  60. processMap[process.PID] = process
  61. }
  62. // Check its parent
  63. if process.ParentPID == "" {
  64. processes = append(processes, process)
  65. continue
  66. }
  67. internalParentProcess, ok := pm.processMap[internalProcess.ParentPID]
  68. if ok {
  69. parentProcess, ok := processMap[process.ParentPID]
  70. if !ok {
  71. parentProcess = internalParentProcess.toProcess()
  72. processMap[parentProcess.PID] = parentProcess
  73. }
  74. parentProcess.Children = append(parentProcess.Children, process)
  75. continue
  76. }
  77. processes = append(processes, process)
  78. }
  79. }
  80. pm.mutex.Unlock()
  81. if !flat && noSystem {
  82. for i := 0; i < len(processes); i++ {
  83. process := processes[i]
  84. if process.Type != SystemProcessType {
  85. continue
  86. }
  87. processes[len(processes)-1], processes[i] = processes[i], processes[len(processes)-1]
  88. processes = append(processes[:len(processes)-1], process.Children...)
  89. i--
  90. }
  91. }
  92. // Sort by process' start time. Oldest process appears first.
  93. sort.Slice(processes, func(i, j int) bool {
  94. left, right := processes[i], processes[j]
  95. return left.Start.Before(right.Start)
  96. })
  97. return processes, processCount
  98. }
  99. // ProcessStacktraces gets the processes and stacktraces in a thread safe manner
  100. func (pm *Manager) ProcessStacktraces(flat, noSystem bool) ([]*Process, int, int64, error) {
  101. var stacks *profile.Profile
  102. var err error
  103. // We cannot use the pm.ProcessMap here because we will release the mutex ...
  104. processMap := map[IDType]*Process{}
  105. var processCount int
  106. // Lock the manager
  107. pm.mutex.Lock()
  108. processCount = len(pm.processMap)
  109. // Add a defer to unlock in case there is a panic
  110. unlocked := false
  111. defer func() {
  112. if !unlocked {
  113. pm.mutex.Unlock()
  114. }
  115. }()
  116. processes := make([]*Process, 0, len(pm.processMap))
  117. if flat {
  118. for _, internalProcess := range pm.processMap {
  119. process := internalProcess.toProcess()
  120. processMap[process.PID] = process
  121. if noSystem && internalProcess.Type == SystemProcessType {
  122. continue
  123. }
  124. processes = append(processes, process)
  125. }
  126. } else {
  127. for _, internalProcess := range pm.processMap {
  128. process, ok := processMap[internalProcess.PID]
  129. if !ok {
  130. process = internalProcess.toProcess()
  131. processMap[process.PID] = process
  132. }
  133. // Check its parent
  134. if process.ParentPID == "" {
  135. processes = append(processes, process)
  136. continue
  137. }
  138. internalParentProcess, ok := pm.processMap[internalProcess.ParentPID]
  139. if ok {
  140. parentProcess, ok := processMap[process.ParentPID]
  141. if !ok {
  142. parentProcess = internalParentProcess.toProcess()
  143. processMap[parentProcess.PID] = parentProcess
  144. }
  145. parentProcess.Children = append(parentProcess.Children, process)
  146. continue
  147. }
  148. processes = append(processes, process)
  149. }
  150. }
  151. // Now from within the lock we need to get the goroutines.
  152. // Why? If we release the lock then between between filling the above map and getting
  153. // the stacktraces another process could be created which would then look like a dead process below
  154. reader, writer := io.Pipe()
  155. defer reader.Close()
  156. go func() {
  157. err := pprof.Lookup("goroutine").WriteTo(writer, 0)
  158. _ = writer.CloseWithError(err)
  159. }()
  160. stacks, err = profile.Parse(reader)
  161. if err != nil {
  162. return nil, 0, 0, err
  163. }
  164. // Unlock the mutex
  165. pm.mutex.Unlock()
  166. unlocked = true
  167. goroutineCount := int64(0)
  168. // Now walk through the "Sample" slice in the goroutines stack
  169. for _, sample := range stacks.Sample {
  170. // In the "goroutine" pprof profile each sample represents one or more goroutines
  171. // with the same labels and stacktraces.
  172. // We will represent each goroutine by a `Stack`
  173. stack := &Stack{}
  174. // Add the non-process associated labels from the goroutine sample to the Stack
  175. for name, value := range sample.Label {
  176. if name == gtprof.LabelProcessDescription || name == gtprof.LabelPid || (!flat && name == gtprof.LabelPpid) || name == gtprof.LabelProcessType {
  177. continue
  178. }
  179. // Labels from the "goroutine" pprof profile only have one value.
  180. // This is because the underlying representation is a map[string]string
  181. if len(value) != 1 {
  182. // Unexpected...
  183. return nil, 0, 0, fmt.Errorf("label: %s in goroutine stack with unexpected number of values: %v", name, value)
  184. }
  185. stack.Labels = append(stack.Labels, &Label{Name: name, Value: value[0]})
  186. }
  187. // The number of goroutines that this sample represents is the `stack.Value[0]`
  188. stack.Count = sample.Value[0]
  189. goroutineCount += stack.Count
  190. // Now we want to associate this Stack with a Process.
  191. var process *Process
  192. // Try to get the PID from the goroutine labels
  193. if pidvalue, ok := sample.Label[gtprof.LabelPid]; ok && len(pidvalue) == 1 {
  194. pid := IDType(pidvalue[0])
  195. // Now try to get the process from our map
  196. process, ok = processMap[pid]
  197. if !ok && pid != "" {
  198. // This means that no process has been found in the process map - but there was a process PID
  199. // Therefore this goroutine belongs to a dead process and it has escaped control of the process as it
  200. // should have died with the process context cancellation.
  201. // We need to create a dead process holder for this process and label it appropriately
  202. // get the parent PID
  203. ppid := IDType("")
  204. if value, ok := sample.Label[gtprof.LabelPpid]; ok && len(value) == 1 {
  205. ppid = IDType(value[0])
  206. }
  207. // format the description
  208. description := "(dead process)"
  209. if value, ok := sample.Label[gtprof.LabelProcessDescription]; ok && len(value) == 1 {
  210. description = value[0] + " " + description
  211. }
  212. // override the type of the process to "code" but add the old type as a label on the first stack
  213. ptype := NoneProcessType
  214. if value, ok := sample.Label[gtprof.LabelProcessType]; ok && len(value) == 1 {
  215. stack.Labels = append(stack.Labels, &Label{Name: gtprof.LabelProcessType, Value: value[0]})
  216. }
  217. process = &Process{
  218. PID: pid,
  219. ParentPID: ppid,
  220. Description: description,
  221. Type: ptype,
  222. }
  223. // Now add the dead process back to the map and tree so we don't go back through this again.
  224. processMap[process.PID] = process
  225. added := false
  226. if process.ParentPID != "" && !flat {
  227. if parent, ok := processMap[process.ParentPID]; ok {
  228. parent.Children = append(parent.Children, process)
  229. added = true
  230. }
  231. }
  232. if !added {
  233. processes = append(processes, process)
  234. }
  235. }
  236. }
  237. if process == nil {
  238. // This means that the sample we're looking has no PID label
  239. var ok bool
  240. process, ok = processMap[""]
  241. if !ok {
  242. // this is the first time we've come acrross an unassociated goroutine so create a "process" to hold them
  243. process = &Process{
  244. Description: "(unassociated)",
  245. Type: NoneProcessType,
  246. }
  247. processMap[process.PID] = process
  248. processes = append(processes, process)
  249. }
  250. }
  251. // The sample.Location represents a stack trace for this goroutine,
  252. // however each Location can represent multiple lines (mostly due to inlining)
  253. // so we need to walk the lines too
  254. for _, location := range sample.Location {
  255. for _, line := range location.Line {
  256. entry := &StackEntry{
  257. Function: line.Function.Name,
  258. File: line.Function.Filename,
  259. Line: int(line.Line),
  260. }
  261. stack.Entry = append(stack.Entry, entry)
  262. }
  263. }
  264. // Now we need a short-descriptive name to call the stack trace if when it is folded and
  265. // assuming the stack trace has some lines we'll choose the bottom of the stack (i.e. the
  266. // initial function that started the stack trace.) The top of the stack is unlikely to
  267. // be very helpful as a lot of the time it will be runtime.select or some other call into
  268. // a std library.
  269. stack.Description = "(unknown)"
  270. if len(stack.Entry) > 0 {
  271. stack.Description = stack.Entry[len(stack.Entry)-1].Function
  272. }
  273. process.Stacks = append(process.Stacks, stack)
  274. }
  275. // restrict to not show system processes
  276. if noSystem {
  277. for i := 0; i < len(processes); i++ {
  278. process := processes[i]
  279. if process.Type != SystemProcessType && process.Type != NoneProcessType {
  280. continue
  281. }
  282. processes[len(processes)-1], processes[i] = processes[i], processes[len(processes)-1]
  283. processes = append(processes[:len(processes)-1], process.Children...)
  284. i--
  285. }
  286. }
  287. // Now finally re-sort the processes. Newest process appears first
  288. after := func(processes []*Process) func(i, j int) bool {
  289. return func(i, j int) bool {
  290. left, right := processes[i], processes[j]
  291. return left.Start.After(right.Start)
  292. }
  293. }
  294. sort.Slice(processes, after(processes))
  295. if !flat {
  296. var sortChildren func(process *Process)
  297. sortChildren = func(process *Process) {
  298. sort.Slice(process.Children, after(process.Children))
  299. for _, child := range process.Children {
  300. sortChildren(child)
  301. }
  302. }
  303. }
  304. return processes, processCount, goroutineCount, err
  305. }