Нет описания

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. // Copyright 2014 Google Inc. All Rights Reserved.
  2. //
  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. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package driver
  15. import (
  16. "fmt"
  17. "io"
  18. "net/http"
  19. "net/url"
  20. "os"
  21. "path/filepath"
  22. "strconv"
  23. "sync"
  24. "time"
  25. "github.com/google/pprof/internal/measurement"
  26. "github.com/google/pprof/internal/plugin"
  27. "github.com/google/pprof/profile"
  28. )
  29. // fetchProfiles fetches and symbolizes the profiles specified by s.
  30. // It will merge all the profiles it is able to retrieve, even if
  31. // there are some failures. It will return an error if it is unable to
  32. // fetch any profiles.
  33. func fetchProfiles(s *source, o *plugin.Options) (*profile.Profile, error) {
  34. if err := setTmpDir(o.UI); err != nil {
  35. return nil, err
  36. }
  37. p, msrcs, save, err := concurrentGrab(s, o.Fetch, o.Obj, o.UI)
  38. if err != nil {
  39. return nil, err
  40. }
  41. // Symbolize the merged profile.
  42. if err := o.Sym.Symbolize(s.Symbolize, msrcs, p); err != nil {
  43. return nil, err
  44. }
  45. p.RemoveUninteresting()
  46. // Save a copy of the merged profile if there is at least one remote source.
  47. if save {
  48. prefix := "pprof."
  49. if len(p.Mapping) > 0 && p.Mapping[0].File != "" {
  50. prefix += filepath.Base(p.Mapping[0].File) + "."
  51. }
  52. for _, s := range p.SampleType {
  53. prefix += s.Type + "."
  54. }
  55. dir := os.Getenv("PPROF_TMPDIR")
  56. tempFile, err := newTempFile(dir, prefix, ".pb.gz")
  57. if err == nil {
  58. if err = p.Write(tempFile); err == nil {
  59. o.UI.PrintErr("Saved profile in ", tempFile.Name())
  60. }
  61. }
  62. if err != nil {
  63. o.UI.PrintErr("Could not save profile: ", err)
  64. }
  65. }
  66. if err := p.CheckValid(); err != nil {
  67. return nil, err
  68. }
  69. return p, nil
  70. }
  71. // concurrentGrab fetches multiple profiles concurrently
  72. func concurrentGrab(s *source, fetch plugin.Fetcher, obj plugin.ObjTool, ui plugin.UI) (*profile.Profile, plugin.MappingSources, bool, error) {
  73. wg := sync.WaitGroup{}
  74. numprofs := len(s.Sources) + len(s.Base)
  75. profs := make([]*profile.Profile, numprofs)
  76. msrcs := make([]plugin.MappingSources, numprofs)
  77. remote := make([]bool, numprofs)
  78. errs := make([]error, numprofs)
  79. for i, source := range s.Sources {
  80. wg.Add(1)
  81. go func(i int, src string) {
  82. defer wg.Done()
  83. profs[i], msrcs[i], remote[i], errs[i] = grabProfile(s, src, 1, fetch, obj, ui)
  84. }(i, source)
  85. }
  86. for i, source := range s.Base {
  87. wg.Add(1)
  88. go func(i int, src string) {
  89. defer wg.Done()
  90. profs[i], msrcs[i], remote[i], errs[i] = grabProfile(s, src, -1, fetch, obj, ui)
  91. }(i+len(s.Sources), source)
  92. }
  93. wg.Wait()
  94. var save bool
  95. var numFailed = 0
  96. for i, src := range s.Sources {
  97. if errs[i] != nil {
  98. ui.PrintErr(src + ": " + errs[i].Error())
  99. numFailed++
  100. }
  101. save = save || remote[i]
  102. }
  103. for i, src := range s.Base {
  104. b := i + len(s.Sources)
  105. if errs[b] != nil {
  106. ui.PrintErr(src + ": " + errs[b].Error())
  107. numFailed++
  108. }
  109. save = save || remote[b]
  110. }
  111. if numFailed == numprofs {
  112. return nil, nil, false, fmt.Errorf("failed to fetch any profiles")
  113. }
  114. if numFailed > 0 {
  115. ui.PrintErr(fmt.Sprintf("fetched %d profiles out of %d", numprofs-numFailed, numprofs))
  116. }
  117. scaled := make([]*profile.Profile, 0, numprofs)
  118. for _, p := range profs {
  119. if p != nil {
  120. scaled = append(scaled, p)
  121. }
  122. }
  123. // Merge profiles.
  124. if err := measurement.ScaleProfiles(scaled); err != nil {
  125. return nil, nil, false, err
  126. }
  127. p, err := profile.Merge(scaled)
  128. if err != nil {
  129. return nil, nil, false, err
  130. }
  131. // Combine mapping sources.
  132. msrc := make(plugin.MappingSources)
  133. for _, ms := range msrcs {
  134. for m, s := range ms {
  135. msrc[m] = append(msrc[m], s...)
  136. }
  137. }
  138. return p, msrc, save, nil
  139. }
  140. // setTmpDir sets the PPROF_TMPDIR environment variable with a new
  141. // temp directory, if not already set.
  142. func setTmpDir(ui plugin.UI) error {
  143. if profileDir := os.Getenv("PPROF_TMPDIR"); profileDir != "" {
  144. return nil
  145. }
  146. for _, tmpDir := range []string{os.Getenv("HOME") + "/pprof", "/tmp"} {
  147. if err := os.MkdirAll(tmpDir, 0755); err != nil {
  148. ui.PrintErr("Could not use temp dir ", tmpDir, ": ", err.Error())
  149. continue
  150. }
  151. os.Setenv("PPROF_TMPDIR", tmpDir)
  152. return nil
  153. }
  154. return fmt.Errorf("failed to identify temp dir")
  155. }
  156. // grabProfile fetches a profile. Returns the profile, sources for the
  157. // profile mappings, a bool indicating if the profile was fetched
  158. // remotely, and an error.
  159. func grabProfile(s *source, source string, scale float64, fetcher plugin.Fetcher, obj plugin.ObjTool, ui plugin.UI) (p *profile.Profile, msrc plugin.MappingSources, remote bool, err error) {
  160. var src string
  161. duration, timeout := time.Duration(s.Seconds)*time.Second, time.Duration(s.Timeout)*time.Second
  162. if fetcher != nil {
  163. p, src, err = fetcher.Fetch(source, duration, timeout)
  164. if err != nil {
  165. return
  166. }
  167. }
  168. if err != nil || p == nil {
  169. // Fetch the profile over HTTP or from a file.
  170. p, src, err = fetch(source, duration, timeout, ui)
  171. if err != nil {
  172. return
  173. }
  174. }
  175. if err = p.CheckValid(); err != nil {
  176. return
  177. }
  178. // Apply local changes to the profile.
  179. p.Scale(scale)
  180. // Update the binary locations from command line and paths.
  181. locateBinaries(p, s, obj, ui)
  182. // Collect the source URL for all mappings.
  183. if src != "" {
  184. msrc = collectMappingSources(p, src)
  185. remote = true
  186. }
  187. return
  188. }
  189. // collectMappingSources saves the mapping sources of a profile.
  190. func collectMappingSources(p *profile.Profile, source string) plugin.MappingSources {
  191. ms := plugin.MappingSources{}
  192. for _, m := range p.Mapping {
  193. src := struct {
  194. Source string
  195. Start uint64
  196. }{
  197. source, m.Start,
  198. }
  199. if key := m.BuildID; key != "" {
  200. ms[key] = append(ms[key], src)
  201. }
  202. if key := m.File; key != "" {
  203. ms[key] = append(ms[key], src)
  204. }
  205. }
  206. return ms
  207. }
  208. // locateBinaries searches for binary files listed in the profile and, if found,
  209. // updates the profile accordingly.
  210. func locateBinaries(p *profile.Profile, s *source, obj plugin.ObjTool, ui plugin.UI) {
  211. // Construct search path to examine
  212. searchPath := os.Getenv("PPROF_BINARY_PATH")
  213. if searchPath == "" {
  214. // Use $HOME/pprof/binaries as default directory for local symbolization binaries
  215. searchPath = filepath.Join(os.Getenv("HOME"), "pprof", "binaries")
  216. }
  217. mapping:
  218. for i, m := range p.Mapping {
  219. var baseName string
  220. // Replace executable filename/buildID with the overrides from source.
  221. // Assumes the executable is the first Mapping entry.
  222. if i == 0 {
  223. if s.ExecName != "" {
  224. m.File = s.ExecName
  225. }
  226. if s.BuildID != "" {
  227. m.BuildID = s.BuildID
  228. }
  229. }
  230. if m.File != "" {
  231. baseName = filepath.Base(m.File)
  232. }
  233. for _, path := range filepath.SplitList(searchPath) {
  234. var fileNames []string
  235. if m.BuildID != "" {
  236. fileNames = []string{filepath.Join(path, m.BuildID, baseName)}
  237. if matches, err := filepath.Glob(filepath.Join(path, m.BuildID, "*")); err == nil {
  238. fileNames = append(fileNames, matches...)
  239. }
  240. }
  241. if baseName != "" {
  242. fileNames = append(fileNames, filepath.Join(path, baseName))
  243. }
  244. for _, name := range fileNames {
  245. if f, err := obj.Open(name, m.Start, m.Limit, m.Offset); err == nil {
  246. defer f.Close()
  247. fileBuildID := f.BuildID()
  248. if m.BuildID != "" && m.BuildID != fileBuildID {
  249. ui.PrintErr("Ignoring local file " + name + ": build-id mismatch (" + m.BuildID + " != " + fileBuildID + ")")
  250. } else {
  251. m.File = name
  252. continue mapping
  253. }
  254. }
  255. }
  256. }
  257. }
  258. }
  259. // fetch fetches a profile from source, within the timeout specified,
  260. // producing messages through the ui. It returns the profile and the
  261. // url of the actual source of the profile for remote profiles.
  262. func fetch(source string, duration, timeout time.Duration, ui plugin.UI) (p *profile.Profile, src string, err error) {
  263. var f io.ReadCloser
  264. if sourceURL, timeout := adjustURL(source, duration, timeout); sourceURL != "" {
  265. ui.Print("Fetching profile over HTTP from " + sourceURL)
  266. if duration > 0 {
  267. ui.Print(fmt.Sprintf("Please wait... (%v)", duration))
  268. }
  269. f, err = fetchURL(sourceURL, timeout)
  270. src = sourceURL
  271. } else {
  272. f, err = os.Open(source)
  273. }
  274. if err == nil {
  275. defer f.Close()
  276. p, err = profile.Parse(f)
  277. }
  278. return
  279. }
  280. // fetchURL fetches a profile from a URL using HTTP.
  281. func fetchURL(source string, timeout time.Duration) (io.ReadCloser, error) {
  282. resp, err := httpGet(source, timeout)
  283. if err != nil {
  284. return nil, fmt.Errorf("http fetch %s: %v", source, err)
  285. }
  286. if resp.StatusCode != http.StatusOK {
  287. return nil, fmt.Errorf("server response: %s", resp.Status)
  288. }
  289. return resp.Body, nil
  290. }
  291. // adjustURL validates if a profile source is a URL and returns an
  292. // cleaned up URL and the timeout to use for retrieval over HTTP.
  293. // If the source cannot be recognized as a URL it returns an empty string.
  294. func adjustURL(source string, duration, timeout time.Duration) (string, time.Duration) {
  295. u, err := url.Parse(source)
  296. if err != nil || (u.Host == "" && u.Scheme != "" && u.Scheme != "file") {
  297. // Try adding http:// to catch sources of the form hostname:port/path.
  298. // url.Parse treats "hostname" as the scheme.
  299. u, err = url.Parse("http://" + source)
  300. }
  301. if err != nil || u.Host == "" {
  302. return "", 0
  303. }
  304. // Apply duration/timeout overrides to URL.
  305. values := u.Query()
  306. if duration > 0 {
  307. values.Set("seconds", fmt.Sprint(int(duration.Seconds())))
  308. } else {
  309. if urlSeconds := values.Get("seconds"); urlSeconds != "" {
  310. if us, err := strconv.ParseInt(urlSeconds, 10, 32); err == nil {
  311. duration = time.Duration(us) * time.Second
  312. }
  313. }
  314. }
  315. if timeout <= 0 {
  316. if duration > 0 {
  317. timeout = duration + duration/2
  318. } else {
  319. timeout = 60 * time.Second
  320. }
  321. }
  322. u.RawQuery = values.Encode()
  323. return u.String(), timeout
  324. }
  325. // httpGet is a wrapper around http.Get; it is defined as a variable
  326. // so it can be redefined during for testing.
  327. var httpGet = func(url string, timeout time.Duration) (*http.Response, error) {
  328. client := &http.Client{
  329. Transport: &http.Transport{
  330. ResponseHeaderTimeout: timeout + 5*time.Second,
  331. },
  332. }
  333. return client.Get(url)
  334. }