暫無描述

fetch.go 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498
  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. "encoding/base64"
  17. "fmt"
  18. "io"
  19. "math/rand"
  20. "net/http"
  21. "net/url"
  22. "os"
  23. "os/exec"
  24. "path/filepath"
  25. "strconv"
  26. "sync"
  27. "time"
  28. "github.com/google/pprof/internal/measurement"
  29. "github.com/google/pprof/internal/plugin"
  30. "github.com/google/pprof/profile"
  31. )
  32. // fetchProfiles fetches and symbolizes the profiles specified by s.
  33. // It will merge all the profiles it is able to retrieve, even if
  34. // there are some failures. It will return an error if it is unable to
  35. // fetch any profiles.
  36. func fetchProfiles(s *source, o *plugin.Options) (*profile.Profile, error) {
  37. if err := setTmpDir(o.UI); err != nil {
  38. return nil, err
  39. }
  40. sources := make([]profileSource, 0, len(s.Sources)+len(s.Base))
  41. for _, src := range s.Sources {
  42. sources = append(sources, profileSource{
  43. addr: src,
  44. source: s,
  45. scale: 1,
  46. })
  47. }
  48. for _, src := range s.Base {
  49. sources = append(sources, profileSource{
  50. addr: src,
  51. source: s,
  52. scale: -1,
  53. })
  54. }
  55. p, msrcs, save, cnt, err := chunkedGrab(sources, o.Fetch, o.Obj, o.UI)
  56. if err != nil {
  57. return nil, err
  58. }
  59. if cnt == 0 {
  60. return nil, fmt.Errorf("failed to fetch any profiles")
  61. }
  62. if want, got := len(sources), cnt; want != got {
  63. o.UI.PrintErr(fmt.Sprintf("fetched %d profiles out of %d", got, want))
  64. }
  65. // Symbolize the merged profile.
  66. if err := o.Sym.Symbolize(s.Symbolize, msrcs, p); err != nil {
  67. return nil, err
  68. }
  69. p.RemoveUninteresting()
  70. // Save a copy of the merged profile if there is at least one remote source.
  71. if save {
  72. prefix := "pprof."
  73. if len(p.Mapping) > 0 && p.Mapping[0].File != "" {
  74. prefix += filepath.Base(p.Mapping[0].File) + "."
  75. }
  76. for _, s := range p.SampleType {
  77. prefix += s.Type + "."
  78. }
  79. dir := os.Getenv("PPROF_TMPDIR")
  80. tempFile, err := newTempFile(dir, prefix, ".pb.gz")
  81. if err == nil {
  82. if err = p.Write(tempFile); err == nil {
  83. o.UI.PrintErr("Saved profile in ", tempFile.Name())
  84. }
  85. }
  86. if err != nil {
  87. o.UI.PrintErr("Could not save profile: ", err)
  88. }
  89. }
  90. if err := p.CheckValid(); err != nil {
  91. return nil, err
  92. }
  93. return p, nil
  94. }
  95. // chunkedGrab fetches the profiles described in source and merges them into
  96. // a single profile. It fetches a chunk of profiles concurrently, with a maximum
  97. // chunk size to limit its memory usage.
  98. func chunkedGrab(sources []profileSource, fetch plugin.Fetcher, obj plugin.ObjTool, ui plugin.UI) (*profile.Profile, plugin.MappingSources, bool, int, error) {
  99. const chunkSize = 64
  100. var p *profile.Profile
  101. var msrc plugin.MappingSources
  102. var save bool
  103. var count int
  104. for start := 0; start < len(sources); start += chunkSize {
  105. end := start + chunkSize
  106. if end > len(sources) {
  107. end = len(sources)
  108. }
  109. chunkP, chunkMsrc, chunkSave, chunkCount, chunkErr := concurrentGrab(sources[start:end], fetch, obj, ui)
  110. switch {
  111. case chunkErr != nil:
  112. return nil, nil, false, 0, chunkErr
  113. case chunkP == nil:
  114. continue
  115. case p == nil:
  116. p, msrc, save, count = chunkP, chunkMsrc, chunkSave, chunkCount
  117. default:
  118. p, msrc, chunkErr = combineProfiles([]*profile.Profile{p, chunkP}, []plugin.MappingSources{msrc, chunkMsrc})
  119. if chunkErr != nil {
  120. return nil, nil, false, 0, chunkErr
  121. }
  122. if chunkSave {
  123. save = true
  124. }
  125. count += chunkCount
  126. }
  127. }
  128. return p, msrc, save, count, nil
  129. }
  130. // concurrentGrab fetches multiple profiles concurrently
  131. func concurrentGrab(sources []profileSource, fetch plugin.Fetcher, obj plugin.ObjTool, ui plugin.UI) (*profile.Profile, plugin.MappingSources, bool, int, error) {
  132. wg := sync.WaitGroup{}
  133. wg.Add(len(sources))
  134. for i := range sources {
  135. go func(s *profileSource) {
  136. defer wg.Done()
  137. s.p, s.msrc, s.remote, s.err = grabProfile(s.source, s.addr, s.scale, fetch, obj, ui)
  138. }(&sources[i])
  139. }
  140. wg.Wait()
  141. var save bool
  142. profiles := make([]*profile.Profile, 0, len(sources))
  143. msrcs := make([]plugin.MappingSources, 0, len(sources))
  144. for i := range sources {
  145. s := &sources[i]
  146. if err := s.err; err != nil {
  147. ui.PrintErr(s.addr + ": " + err.Error())
  148. continue
  149. }
  150. save = save || s.remote
  151. profiles = append(profiles, s.p)
  152. msrcs = append(msrcs, s.msrc)
  153. *s = profileSource{}
  154. }
  155. if len(profiles) == 0 {
  156. return nil, nil, false, 0, nil
  157. }
  158. p, msrc, err := combineProfiles(profiles, msrcs)
  159. if err != nil {
  160. return nil, nil, false, 0, err
  161. }
  162. return p, msrc, save, len(profiles), nil
  163. }
  164. func combineProfiles(profiles []*profile.Profile, msrcs []plugin.MappingSources) (*profile.Profile, plugin.MappingSources, error) {
  165. // Merge profiles.
  166. if err := measurement.ScaleProfiles(profiles); err != nil {
  167. return nil, nil, err
  168. }
  169. p, err := profile.Merge(profiles)
  170. if err != nil {
  171. return nil, nil, err
  172. }
  173. // Combine mapping sources.
  174. msrc := make(plugin.MappingSources)
  175. for _, ms := range msrcs {
  176. for m, s := range ms {
  177. msrc[m] = append(msrc[m], s...)
  178. }
  179. }
  180. return p, msrc, nil
  181. }
  182. type profileSource struct {
  183. addr string
  184. source *source
  185. scale float64
  186. p *profile.Profile
  187. msrc plugin.MappingSources
  188. remote bool
  189. err error
  190. }
  191. // setTmpDir sets the PPROF_TMPDIR environment variable with a new
  192. // temp directory, if not already set.
  193. func setTmpDir(ui plugin.UI) error {
  194. if profileDir := os.Getenv("PPROF_TMPDIR"); profileDir != "" {
  195. return nil
  196. }
  197. for _, tmpDir := range []string{os.Getenv("HOME") + "/pprof", "/tmp"} {
  198. if err := os.MkdirAll(tmpDir, 0755); err != nil {
  199. ui.PrintErr("Could not use temp dir ", tmpDir, ": ", err.Error())
  200. continue
  201. }
  202. os.Setenv("PPROF_TMPDIR", tmpDir)
  203. return nil
  204. }
  205. return fmt.Errorf("failed to identify temp dir")
  206. }
  207. // grabProfile fetches a profile. Returns the profile, sources for the
  208. // profile mappings, a bool indicating if the profile was fetched
  209. // remotely, and an error.
  210. 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) {
  211. var src string
  212. duration, timeout := time.Duration(s.Seconds)*time.Second, time.Duration(s.Timeout)*time.Second
  213. if fetcher != nil {
  214. p, src, err = fetcher.Fetch(source, duration, timeout)
  215. if err != nil {
  216. return
  217. }
  218. }
  219. if err != nil || p == nil {
  220. // Fetch the profile over HTTP or from a file.
  221. p, src, err = fetch(source, duration, timeout, ui)
  222. if err != nil {
  223. return
  224. }
  225. }
  226. if err = p.CheckValid(); err != nil {
  227. return
  228. }
  229. // Apply local changes to the profile.
  230. p.Scale(scale)
  231. // Update the binary locations from command line and paths.
  232. locateBinaries(p, s, obj, ui)
  233. // Collect the source URL for all mappings.
  234. if src != "" {
  235. msrc = collectMappingSources(p, src)
  236. remote = true
  237. }
  238. return
  239. }
  240. // collectMappingSources saves the mapping sources of a profile.
  241. func collectMappingSources(p *profile.Profile, source string) plugin.MappingSources {
  242. ms := plugin.MappingSources{}
  243. for _, m := range p.Mapping {
  244. src := struct {
  245. Source string
  246. Start uint64
  247. }{
  248. source, m.Start,
  249. }
  250. key := m.BuildID
  251. if key == "" {
  252. key = m.File
  253. }
  254. if key == "" {
  255. // If there is no build id or source file, use the source as the
  256. // mapping file. This will enable remote symbolization for this
  257. // mapping, in particular for Go profiles on the legacy format.
  258. m.File = source
  259. key = source
  260. }
  261. ms[key] = append(ms[key], src)
  262. }
  263. return ms
  264. }
  265. // locateBinaries searches for binary files listed in the profile and, if found,
  266. // updates the profile accordingly.
  267. func locateBinaries(p *profile.Profile, s *source, obj plugin.ObjTool, ui plugin.UI) {
  268. // Construct search path to examine
  269. searchPath := os.Getenv("PPROF_BINARY_PATH")
  270. if searchPath == "" {
  271. // Use $HOME/pprof/binaries as default directory for local symbolization binaries
  272. searchPath = filepath.Join(os.Getenv("HOME"), "pprof", "binaries")
  273. }
  274. mapping:
  275. for i, m := range p.Mapping {
  276. var baseName string
  277. // Replace executable filename/buildID with the overrides from source.
  278. // Assumes the executable is the first Mapping entry.
  279. if i == 0 {
  280. if s.ExecName != "" {
  281. m.File = s.ExecName
  282. }
  283. if s.BuildID != "" {
  284. m.BuildID = s.BuildID
  285. }
  286. }
  287. if m.File != "" {
  288. baseName = filepath.Base(m.File)
  289. }
  290. for _, path := range filepath.SplitList(searchPath) {
  291. var fileNames []string
  292. if m.BuildID != "" {
  293. fileNames = []string{filepath.Join(path, m.BuildID, baseName)}
  294. if matches, err := filepath.Glob(filepath.Join(path, m.BuildID, "*")); err == nil {
  295. fileNames = append(fileNames, matches...)
  296. }
  297. }
  298. if baseName != "" {
  299. fileNames = append(fileNames, filepath.Join(path, baseName))
  300. }
  301. for _, name := range fileNames {
  302. if f, err := obj.Open(name, m.Start, m.Limit, m.Offset); err == nil {
  303. defer f.Close()
  304. fileBuildID := f.BuildID()
  305. if m.BuildID != "" && m.BuildID != fileBuildID {
  306. ui.PrintErr("Ignoring local file " + name + ": build-id mismatch (" + m.BuildID + " != " + fileBuildID + ")")
  307. } else {
  308. m.File = name
  309. continue mapping
  310. }
  311. }
  312. }
  313. }
  314. }
  315. }
  316. // fetch fetches a profile from source, within the timeout specified,
  317. // producing messages through the ui. It returns the profile and the
  318. // url of the actual source of the profile for remote profiles.
  319. func fetch(source string, duration, timeout time.Duration, ui plugin.UI) (p *profile.Profile, src string, err error) {
  320. var f io.ReadCloser
  321. if sourceURL, timeout := adjustURL(source, duration, timeout); sourceURL != "" {
  322. ui.Print("Fetching profile over HTTP from " + sourceURL)
  323. if duration > 0 {
  324. ui.Print(fmt.Sprintf("Please wait... (%v)", duration))
  325. }
  326. f, err = fetchURL(sourceURL, timeout)
  327. src = sourceURL
  328. } else {
  329. f, err = profileProtoReader(source)
  330. }
  331. if err == nil {
  332. defer f.Close()
  333. p, err = profile.Parse(f)
  334. }
  335. return
  336. }
  337. // fetchURL fetches a profile from a URL using HTTP.
  338. func fetchURL(source string, timeout time.Duration) (io.ReadCloser, error) {
  339. resp, err := httpGet(source, timeout)
  340. if err != nil {
  341. return nil, fmt.Errorf("http fetch %s: %v", source, err)
  342. }
  343. if resp.StatusCode != http.StatusOK {
  344. return nil, fmt.Errorf("server response: %s", resp.Status)
  345. }
  346. return resp.Body, nil
  347. }
  348. // profileProtoReader takes a path, and using heuristics, will try to convert
  349. // the file to profile.proto format. It returns a ReadCloser to the
  350. // profile.proto data; however, if the file contents were unknown or conversion
  351. // failed, it may still not be a valid profile.proto.
  352. func profileProtoReader(path string) (io.ReadCloser, error) {
  353. sourceFile, openErr := os.Open(path)
  354. if openErr != nil {
  355. return nil, openErr
  356. }
  357. // If the file is the output of a perf record command, it should begin
  358. // with the string PERFILE2.
  359. perfHeader := []byte("PERFILE2")
  360. actualHeader := make([]byte, len(perfHeader))
  361. _, readErr := sourceFile.Read(actualHeader)
  362. _, seekErr := sourceFile.Seek(0, 0)
  363. if seekErr != nil {
  364. return nil, seekErr
  365. } else if readErr != nil && readErr != io.EOF{
  366. return nil, readErr
  367. } else if string(actualHeader) == string(perfHeader) {
  368. sourceFile.Close()
  369. profileFile, convertErr := convertPerfData(path)
  370. if convertErr != nil {
  371. return nil, convertErr
  372. }
  373. return os.Open(profileFile)
  374. }
  375. return sourceFile, nil
  376. }
  377. // convertPerfData converts the file at path which should be in perf.data format
  378. // using the perf_to_profile tool. It prints the stderr and stdout of
  379. // perf_to_profile to the stderr and stdout of this process, then returns the
  380. // path to a file containing the profile.proto formatted data.
  381. func convertPerfData(perfPath string) (string, error) {
  382. randomBytes := make([]byte, 32)
  383. _, randErr := rand.Read(randomBytes)
  384. if randErr != nil {
  385. return "", randErr
  386. }
  387. randomFileName := "/tmp/pprof_" +
  388. base64.StdEncoding.EncodeToString(randomBytes)
  389. cmd := exec.Command("perf_to_profile", perfPath, randomFileName)
  390. stdout, stdoutErr := cmd.StdoutPipe()
  391. if stdoutErr != nil {
  392. return "", stdoutErr
  393. }
  394. go func() {
  395. io.Copy(os.Stdout, stdout)
  396. }()
  397. stderr, stderrErr := cmd.StderrPipe()
  398. if stderrErr != nil {
  399. return "", stderrErr
  400. }
  401. go func() {
  402. io.Copy(os.Stderr, stderr)
  403. }()
  404. if err := cmd.Run(); err != nil {
  405. return "", err
  406. }
  407. return randomFileName, nil
  408. }
  409. // adjustURL validates if a profile source is a URL and returns an
  410. // cleaned up URL and the timeout to use for retrieval over HTTP.
  411. // If the source cannot be recognized as a URL it returns an empty string.
  412. func adjustURL(source string, duration, timeout time.Duration) (string, time.Duration) {
  413. u, err := url.Parse(source)
  414. if err != nil || (u.Host == "" && u.Scheme != "" && u.Scheme != "file") {
  415. // Try adding http:// to catch sources of the form hostname:port/path.
  416. // url.Parse treats "hostname" as the scheme.
  417. u, err = url.Parse("http://" + source)
  418. }
  419. if err != nil || u.Host == "" {
  420. return "", 0
  421. }
  422. // Apply duration/timeout overrides to URL.
  423. values := u.Query()
  424. if duration > 0 {
  425. values.Set("seconds", fmt.Sprint(int(duration.Seconds())))
  426. } else {
  427. if urlSeconds := values.Get("seconds"); urlSeconds != "" {
  428. if us, err := strconv.ParseInt(urlSeconds, 10, 32); err == nil {
  429. duration = time.Duration(us) * time.Second
  430. }
  431. }
  432. }
  433. if timeout <= 0 {
  434. if duration > 0 {
  435. timeout = duration + duration/2
  436. } else {
  437. timeout = 60 * time.Second
  438. }
  439. }
  440. u.RawQuery = values.Encode()
  441. return u.String(), timeout
  442. }
  443. // httpGet is a wrapper around http.Get; it is defined as a variable
  444. // so it can be redefined during for testing.
  445. var httpGet = func(url string, timeout time.Duration) (*http.Response, error) {
  446. client := &http.Client{
  447. Transport: &http.Transport{
  448. ResponseHeaderTimeout: timeout + 5*time.Second,
  449. },
  450. }
  451. return client.Get(url)
  452. }