Sin descripción

driver_test.go 30KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137
  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. "bytes"
  17. "flag"
  18. "fmt"
  19. "io/ioutil"
  20. "net"
  21. _ "net/http/pprof"
  22. "os"
  23. "regexp"
  24. "runtime"
  25. "strconv"
  26. "strings"
  27. "testing"
  28. "time"
  29. "github.com/google/pprof/internal/plugin"
  30. "github.com/google/pprof/internal/proftest"
  31. "github.com/google/pprof/internal/symbolz"
  32. "github.com/google/pprof/profile"
  33. )
  34. var updateFlag = flag.Bool("update", false, "Update the golden files")
  35. func TestParse(t *testing.T) {
  36. // Override weblist command to collect output in buffer
  37. pprofCommands["weblist"].postProcess = nil
  38. // Our mockObjTool.Open will always return success, causing
  39. // driver.locateBinaries to "find" the binaries below in a non-existant
  40. // directory. As a workaround, point the search path to the fake
  41. // directory containing out fake binaries.
  42. savePath := os.Getenv("PPROF_BINARY_PATH")
  43. os.Setenv("PPROF_BINARY_PATH", "/path/to")
  44. defer os.Setenv("PPROF_BINARY_PATH", savePath)
  45. testcase := []struct {
  46. flags, source string
  47. }{
  48. {"text,functions,flat", "cpu"},
  49. {"tree,addresses,flat,nodecount=4", "cpusmall"},
  50. {"text,functions,flat,nodecount=5,call_tree", "unknown"},
  51. {"text,alloc_objects,flat", "heap_alloc"},
  52. {"text,files,flat", "heap"},
  53. {"text,files,flat,focus=[12]00,taghide=[X3]00", "heap"},
  54. {"text,inuse_objects,flat", "heap"},
  55. {"text,lines,cum,hide=line[X3]0", "cpu"},
  56. {"text,lines,cum,show=[12]00", "cpu"},
  57. {"text,lines,cum,hide=line[X3]0,focus=[12]00", "cpu"},
  58. {"topproto,lines,cum,hide=mangled[X3]0", "cpu"},
  59. {"tree,lines,cum,focus=[24]00", "heap"},
  60. {"tree,relative_percentages,cum,focus=[24]00", "heap"},
  61. {"callgrind", "cpu"},
  62. {"callgrind,call_tree", "cpu"},
  63. {"callgrind", "heap"},
  64. {"dot,functions,flat", "cpu"},
  65. {"dot,functions,flat,call_tree", "cpu"},
  66. {"dot,lines,flat,focus=[12]00", "heap"},
  67. {"dot,addresses,flat,ignore=[X3]002,focus=[X1]000", "contention"},
  68. {"dot,files,cum", "contention"},
  69. {"comments", "cpu"},
  70. {"comments", "heap"},
  71. {"tags", "cpu"},
  72. {"tags,tagignore=tag[13],tagfocus=key[12]", "cpu"},
  73. {"tags", "heap"},
  74. {"tags,unit=bytes", "heap"},
  75. {"traces", "cpu"},
  76. {"dot,alloc_space,flat,focus=[234]00", "heap_alloc"},
  77. {"dot,alloc_space,flat,tagshow=[2]00", "heap_alloc"},
  78. {"dot,alloc_space,flat,hide=line.*1?23?", "heap_alloc"},
  79. {"dot,inuse_space,flat,tagfocus=1mb:2gb", "heap"},
  80. {"dot,inuse_space,flat,tagfocus=30kb:,tagignore=1mb:2mb", "heap"},
  81. {"disasm=line[13],addresses,flat", "cpu"},
  82. {"peek=line.*01", "cpu"},
  83. {"weblist=line[13],addresses,flat", "cpu"},
  84. }
  85. baseVars := pprofVariables
  86. defer func() { pprofVariables = baseVars }()
  87. for _, tc := range testcase {
  88. // Reset the pprof variables before processing
  89. pprofVariables = baseVars.makeCopy()
  90. f := baseFlags()
  91. f.args = []string{tc.source}
  92. flags := strings.Split(tc.flags, ",")
  93. // Skip the output format in the first flag, to output to a proto
  94. addFlags(&f, flags[1:])
  95. // Encode profile into a protobuf and decode it again.
  96. protoTempFile, err := ioutil.TempFile("", "profile_proto")
  97. if err != nil {
  98. t.Errorf("cannot create tempfile: %v", err)
  99. }
  100. defer os.Remove(protoTempFile.Name())
  101. defer protoTempFile.Close()
  102. f.strings["output"] = protoTempFile.Name()
  103. if flags[0] == "topproto" {
  104. f.bools["proto"] = false
  105. f.bools["topproto"] = true
  106. }
  107. // First pprof invocation to save the profile into a profile.proto.
  108. o1 := setDefaults(nil)
  109. o1.Flagset = f
  110. o1.Fetch = testFetcher{}
  111. o1.Sym = testSymbolizer{}
  112. if err := PProf(o1); err != nil {
  113. t.Errorf("%s %q: %v", tc.source, tc.flags, err)
  114. continue
  115. }
  116. // Reset the pprof variables after the proto invocation
  117. pprofVariables = baseVars.makeCopy()
  118. // Read the profile from the encoded protobuf
  119. outputTempFile, err := ioutil.TempFile("", "profile_output")
  120. if err != nil {
  121. t.Errorf("cannot create tempfile: %v", err)
  122. }
  123. defer os.Remove(outputTempFile.Name())
  124. defer outputTempFile.Close()
  125. f.strings["output"] = outputTempFile.Name()
  126. f.args = []string{protoTempFile.Name()}
  127. var solution string
  128. // Apply the flags for the second pprof run, and identify name of
  129. // the file containing expected results
  130. if flags[0] == "topproto" {
  131. solution = solutionFilename(tc.source, &f)
  132. delete(f.bools, "topproto")
  133. f.bools["text"] = true
  134. } else {
  135. delete(f.bools, "proto")
  136. addFlags(&f, flags[:1])
  137. solution = solutionFilename(tc.source, &f)
  138. }
  139. // Second pprof invocation to read the profile from profile.proto
  140. // and generate a report.
  141. o2 := setDefaults(nil)
  142. o2.Flagset = f
  143. o2.Sym = testSymbolizeDemangler{}
  144. o2.Obj = new(mockObjTool)
  145. if err := PProf(o2); err != nil {
  146. t.Errorf("%s: %v", tc.source, err)
  147. }
  148. b, err := ioutil.ReadFile(outputTempFile.Name())
  149. if err != nil {
  150. t.Errorf("Failed to read profile %s: %v", outputTempFile.Name(), err)
  151. }
  152. // Read data file with expected solution
  153. solution = "testdata/" + solution
  154. sbuf, err := ioutil.ReadFile(solution)
  155. if err != nil {
  156. t.Errorf("reading solution file %s: %v", solution, err)
  157. continue
  158. }
  159. if runtime.GOOS == "windows" {
  160. sbuf = bytes.Replace(sbuf, []byte("testdata/"), []byte("testdata\\"), -1)
  161. sbuf = bytes.Replace(sbuf, []byte("/path/to/"), []byte("\\path\\to\\"), -1)
  162. }
  163. if flags[0] == "svg" {
  164. b = removeScripts(b)
  165. sbuf = removeScripts(sbuf)
  166. }
  167. if string(b) != string(sbuf) {
  168. t.Errorf("diff %s %s", solution, tc.source)
  169. d, err := proftest.Diff(sbuf, b)
  170. if err != nil {
  171. t.Fatalf("diff %s %v", solution, err)
  172. }
  173. t.Errorf("%s\n%s\n", solution, d)
  174. if *updateFlag {
  175. err := ioutil.WriteFile(solution, b, 0644)
  176. if err != nil {
  177. t.Errorf("failed to update the solution file %q: %v", solution, err)
  178. }
  179. }
  180. }
  181. }
  182. }
  183. // removeScripts removes <script > .. </script> pairs from its input
  184. func removeScripts(in []byte) []byte {
  185. beginMarker := []byte("<script")
  186. endMarker := []byte("</script>")
  187. if begin := bytes.Index(in, beginMarker); begin > 0 {
  188. if end := bytes.Index(in[begin:], endMarker); end > 0 {
  189. in = append(in[:begin], removeScripts(in[begin+end+len(endMarker):])...)
  190. }
  191. }
  192. return in
  193. }
  194. // addFlags parses flag descriptions and adds them to the testFlags
  195. func addFlags(f *testFlags, flags []string) {
  196. for _, flag := range flags {
  197. fields := strings.SplitN(flag, "=", 2)
  198. switch len(fields) {
  199. case 1:
  200. f.bools[fields[0]] = true
  201. case 2:
  202. if i, err := strconv.Atoi(fields[1]); err == nil {
  203. f.ints[fields[0]] = i
  204. } else {
  205. f.strings[fields[0]] = fields[1]
  206. }
  207. }
  208. }
  209. }
  210. func testSourceURL(port int) string {
  211. return fmt.Sprintf("http://%s/", net.JoinHostPort(testSourceAddress, strconv.Itoa(port)))
  212. }
  213. // solutionFilename returns the name of the solution file for the test
  214. func solutionFilename(source string, f *testFlags) string {
  215. name := []string{"pprof", strings.TrimPrefix(source, testSourceURL(8000))}
  216. name = addString(name, f, []string{"flat", "cum"})
  217. name = addString(name, f, []string{"functions", "files", "lines", "addresses"})
  218. name = addString(name, f, []string{"inuse_space", "inuse_objects", "alloc_space", "alloc_objects"})
  219. name = addString(name, f, []string{"relative_percentages"})
  220. name = addString(name, f, []string{"seconds"})
  221. name = addString(name, f, []string{"call_tree"})
  222. name = addString(name, f, []string{"text", "tree", "callgrind", "dot", "svg", "tags", "dot", "traces", "disasm", "peek", "weblist", "topproto", "comments"})
  223. if f.strings["focus"] != "" || f.strings["tagfocus"] != "" {
  224. name = append(name, "focus")
  225. }
  226. if f.strings["ignore"] != "" || f.strings["tagignore"] != "" {
  227. name = append(name, "ignore")
  228. }
  229. name = addString(name, f, []string{"hide", "show"})
  230. if f.strings["unit"] != "minimum" {
  231. name = addString(name, f, []string{"unit"})
  232. }
  233. return strings.Join(name, ".")
  234. }
  235. func addString(name []string, f *testFlags, components []string) []string {
  236. for _, c := range components {
  237. if f.bools[c] || f.strings[c] != "" || f.ints[c] != 0 {
  238. return append(name, c)
  239. }
  240. }
  241. return name
  242. }
  243. // testFlags implements the plugin.FlagSet interface.
  244. type testFlags struct {
  245. bools map[string]bool
  246. ints map[string]int
  247. floats map[string]float64
  248. strings map[string]string
  249. args []string
  250. stringLists map[string][]*string
  251. }
  252. func (testFlags) ExtraUsage() string { return "" }
  253. func (f testFlags) Bool(s string, d bool, c string) *bool {
  254. if b, ok := f.bools[s]; ok {
  255. return &b
  256. }
  257. return &d
  258. }
  259. func (f testFlags) Int(s string, d int, c string) *int {
  260. if i, ok := f.ints[s]; ok {
  261. return &i
  262. }
  263. return &d
  264. }
  265. func (f testFlags) Float64(s string, d float64, c string) *float64 {
  266. if g, ok := f.floats[s]; ok {
  267. return &g
  268. }
  269. return &d
  270. }
  271. func (f testFlags) String(s, d, c string) *string {
  272. if t, ok := f.strings[s]; ok {
  273. return &t
  274. }
  275. return &d
  276. }
  277. func (f testFlags) BoolVar(p *bool, s string, d bool, c string) {
  278. if b, ok := f.bools[s]; ok {
  279. *p = b
  280. } else {
  281. *p = d
  282. }
  283. }
  284. func (f testFlags) IntVar(p *int, s string, d int, c string) {
  285. if i, ok := f.ints[s]; ok {
  286. *p = i
  287. } else {
  288. *p = d
  289. }
  290. }
  291. func (f testFlags) Float64Var(p *float64, s string, d float64, c string) {
  292. if g, ok := f.floats[s]; ok {
  293. *p = g
  294. } else {
  295. *p = d
  296. }
  297. }
  298. func (f testFlags) StringVar(p *string, s, d, c string) {
  299. if t, ok := f.strings[s]; ok {
  300. *p = t
  301. } else {
  302. *p = d
  303. }
  304. }
  305. func (f testFlags) StringList(s, d, c string) *[]*string {
  306. if t, ok := f.stringLists[s]; ok {
  307. return &t
  308. }
  309. return &[]*string{}
  310. }
  311. func (f testFlags) Parse(func()) []string {
  312. return f.args
  313. }
  314. func baseFlags() testFlags {
  315. return testFlags{
  316. bools: map[string]bool{
  317. "proto": true,
  318. "trim": true,
  319. "compact_labels": true,
  320. },
  321. ints: map[string]int{
  322. "nodecount": 20,
  323. },
  324. floats: map[string]float64{
  325. "nodefraction": 0.05,
  326. "edgefraction": 0.01,
  327. "divide_by": 1.0,
  328. },
  329. strings: map[string]string{
  330. "unit": "minimum",
  331. },
  332. }
  333. }
  334. const testStart = 0x1000
  335. const testOffset = 0x5000
  336. type testFetcher struct{}
  337. func (testFetcher) Fetch(s string, d, t time.Duration) (*profile.Profile, string, error) {
  338. var p *profile.Profile
  339. switch s {
  340. case "cpu", "unknown":
  341. p = cpuProfile()
  342. case "cpusmall":
  343. p = cpuProfileSmall()
  344. case "heap":
  345. p = heapProfile()
  346. case "heap_alloc":
  347. p = heapProfile()
  348. p.SampleType = []*profile.ValueType{
  349. {Type: "alloc_objects", Unit: "count"},
  350. {Type: "alloc_space", Unit: "bytes"},
  351. }
  352. case "contention":
  353. p = contentionProfile()
  354. case "symbolz":
  355. p = symzProfile()
  356. default:
  357. return nil, "", fmt.Errorf("unexpected source: %s", s)
  358. }
  359. return p, testSourceURL(8000) + s, nil
  360. }
  361. type testSymbolizer struct{}
  362. func (testSymbolizer) Symbolize(_ string, _ plugin.MappingSources, _ *profile.Profile) error {
  363. return nil
  364. }
  365. type testSymbolizeDemangler struct{}
  366. func (testSymbolizeDemangler) Symbolize(_ string, _ plugin.MappingSources, p *profile.Profile) error {
  367. for _, fn := range p.Function {
  368. if fn.Name == "" || fn.SystemName == fn.Name {
  369. fn.Name = fakeDemangler(fn.SystemName)
  370. }
  371. }
  372. return nil
  373. }
  374. func testFetchSymbols(source, post string) ([]byte, error) {
  375. var buf bytes.Buffer
  376. switch source {
  377. case testSourceURL(8000) + "symbolz":
  378. for _, address := range strings.Split(post, "+") {
  379. a, _ := strconv.ParseInt(address, 0, 64)
  380. fmt.Fprintf(&buf, "%v\t", address)
  381. if a-testStart > testOffset {
  382. fmt.Fprintf(&buf, "wrong_source_%v_", address)
  383. continue
  384. }
  385. fmt.Fprintf(&buf, "%#x\n", a-testStart)
  386. }
  387. return buf.Bytes(), nil
  388. case testSourceURL(8001) + "symbolz":
  389. for _, address := range strings.Split(post, "+") {
  390. a, _ := strconv.ParseInt(address, 0, 64)
  391. fmt.Fprintf(&buf, "%v\t", address)
  392. if a-testStart < testOffset {
  393. fmt.Fprintf(&buf, "wrong_source_%v_", address)
  394. continue
  395. }
  396. fmt.Fprintf(&buf, "%#x\n", a-testStart-testOffset)
  397. }
  398. return buf.Bytes(), nil
  399. default:
  400. return nil, fmt.Errorf("unexpected source: %s", source)
  401. }
  402. }
  403. type testSymbolzSymbolizer struct{}
  404. func (testSymbolzSymbolizer) Symbolize(variables string, sources plugin.MappingSources, p *profile.Profile) error {
  405. return symbolz.Symbolize(sources, testFetchSymbols, p, nil)
  406. }
  407. func fakeDemangler(name string) string {
  408. switch name {
  409. case "mangled1000":
  410. return "line1000"
  411. case "mangled2000":
  412. return "line2000"
  413. case "mangled2001":
  414. return "line2001"
  415. case "mangled3000":
  416. return "line3000"
  417. case "mangled3001":
  418. return "line3001"
  419. case "mangled3002":
  420. return "line3002"
  421. case "mangledNEW":
  422. return "operator new"
  423. case "mangledMALLOC":
  424. return "malloc"
  425. default:
  426. return name
  427. }
  428. }
  429. func cpuProfile() *profile.Profile {
  430. var cpuM = []*profile.Mapping{
  431. {
  432. ID: 1,
  433. Start: 0x1000,
  434. Limit: 0x4000,
  435. File: "/path/to/testbinary",
  436. HasFunctions: true,
  437. HasFilenames: true,
  438. HasLineNumbers: true,
  439. HasInlineFrames: true,
  440. },
  441. }
  442. var cpuF = []*profile.Function{
  443. {ID: 1, Name: "mangled1000", SystemName: "mangled1000", Filename: "testdata/file1000.src"},
  444. {ID: 2, Name: "mangled2000", SystemName: "mangled2000", Filename: "testdata/file2000.src"},
  445. {ID: 3, Name: "mangled2001", SystemName: "mangled2001", Filename: "testdata/file2000.src"},
  446. {ID: 4, Name: "mangled3000", SystemName: "mangled3000", Filename: "testdata/file3000.src"},
  447. {ID: 5, Name: "mangled3001", SystemName: "mangled3001", Filename: "testdata/file3000.src"},
  448. {ID: 6, Name: "mangled3002", SystemName: "mangled3002", Filename: "testdata/file3000.src"},
  449. }
  450. var cpuL = []*profile.Location{
  451. {
  452. ID: 1000,
  453. Mapping: cpuM[0],
  454. Address: 0x1000,
  455. Line: []profile.Line{
  456. {Function: cpuF[0], Line: 1},
  457. },
  458. },
  459. {
  460. ID: 2000,
  461. Mapping: cpuM[0],
  462. Address: 0x2000,
  463. Line: []profile.Line{
  464. {Function: cpuF[2], Line: 9},
  465. {Function: cpuF[1], Line: 4},
  466. },
  467. },
  468. {
  469. ID: 3000,
  470. Mapping: cpuM[0],
  471. Address: 0x3000,
  472. Line: []profile.Line{
  473. {Function: cpuF[5], Line: 2},
  474. {Function: cpuF[4], Line: 5},
  475. {Function: cpuF[3], Line: 6},
  476. },
  477. },
  478. {
  479. ID: 3001,
  480. Mapping: cpuM[0],
  481. Address: 0x3001,
  482. Line: []profile.Line{
  483. {Function: cpuF[4], Line: 8},
  484. {Function: cpuF[3], Line: 9},
  485. },
  486. },
  487. {
  488. ID: 3002,
  489. Mapping: cpuM[0],
  490. Address: 0x3002,
  491. Line: []profile.Line{
  492. {Function: cpuF[5], Line: 5},
  493. {Function: cpuF[3], Line: 9},
  494. },
  495. },
  496. }
  497. return &profile.Profile{
  498. PeriodType: &profile.ValueType{Type: "cpu", Unit: "milliseconds"},
  499. Period: 1,
  500. DurationNanos: 10e9,
  501. SampleType: []*profile.ValueType{
  502. {Type: "samples", Unit: "count"},
  503. {Type: "cpu", Unit: "milliseconds"},
  504. },
  505. Sample: []*profile.Sample{
  506. {
  507. Location: []*profile.Location{cpuL[0], cpuL[1], cpuL[2]},
  508. Value: []int64{1000, 1000},
  509. Label: map[string][]string{
  510. "key1": {"tag1"},
  511. "key2": {"tag1"},
  512. },
  513. },
  514. {
  515. Location: []*profile.Location{cpuL[0], cpuL[3]},
  516. Value: []int64{100, 100},
  517. Label: map[string][]string{
  518. "key1": {"tag2"},
  519. "key3": {"tag2"},
  520. },
  521. },
  522. {
  523. Location: []*profile.Location{cpuL[1], cpuL[4]},
  524. Value: []int64{10, 10},
  525. Label: map[string][]string{
  526. "key1": {"tag3"},
  527. "key2": {"tag2"},
  528. },
  529. },
  530. {
  531. Location: []*profile.Location{cpuL[2]},
  532. Value: []int64{10, 10},
  533. Label: map[string][]string{
  534. "key1": {"tag4"},
  535. "key2": {"tag1"},
  536. },
  537. },
  538. },
  539. Location: cpuL,
  540. Function: cpuF,
  541. Mapping: cpuM,
  542. }
  543. }
  544. func cpuProfileSmall() *profile.Profile {
  545. var cpuM = []*profile.Mapping{
  546. {
  547. ID: 1,
  548. Start: 0x1000,
  549. Limit: 0x4000,
  550. File: "/path/to/testbinary",
  551. HasFunctions: true,
  552. HasFilenames: true,
  553. HasLineNumbers: true,
  554. HasInlineFrames: true,
  555. },
  556. }
  557. var cpuL = []*profile.Location{
  558. {
  559. ID: 1000,
  560. Mapping: cpuM[0],
  561. Address: 0x1000,
  562. },
  563. {
  564. ID: 2000,
  565. Mapping: cpuM[0],
  566. Address: 0x2000,
  567. },
  568. {
  569. ID: 3000,
  570. Mapping: cpuM[0],
  571. Address: 0x3000,
  572. },
  573. {
  574. ID: 4000,
  575. Mapping: cpuM[0],
  576. Address: 0x4000,
  577. },
  578. {
  579. ID: 5000,
  580. Mapping: cpuM[0],
  581. Address: 0x5000,
  582. },
  583. }
  584. return &profile.Profile{
  585. PeriodType: &profile.ValueType{Type: "cpu", Unit: "milliseconds"},
  586. Period: 1,
  587. DurationNanos: 10e9,
  588. SampleType: []*profile.ValueType{
  589. {Type: "samples", Unit: "count"},
  590. {Type: "cpu", Unit: "milliseconds"},
  591. },
  592. Sample: []*profile.Sample{
  593. {
  594. Location: []*profile.Location{cpuL[0], cpuL[1], cpuL[2]},
  595. Value: []int64{1000, 1000},
  596. },
  597. {
  598. Location: []*profile.Location{cpuL[3], cpuL[1], cpuL[4]},
  599. Value: []int64{1000, 1000},
  600. },
  601. {
  602. Location: []*profile.Location{cpuL[2]},
  603. Value: []int64{1000, 1000},
  604. },
  605. {
  606. Location: []*profile.Location{cpuL[4]},
  607. Value: []int64{1000, 1000},
  608. },
  609. },
  610. Location: cpuL,
  611. Function: nil,
  612. Mapping: cpuM,
  613. }
  614. }
  615. func heapProfile() *profile.Profile {
  616. var heapM = []*profile.Mapping{
  617. {
  618. ID: 1,
  619. BuildID: "buildid",
  620. Start: 0x1000,
  621. Limit: 0x4000,
  622. HasFunctions: true,
  623. HasFilenames: true,
  624. HasLineNumbers: true,
  625. HasInlineFrames: true,
  626. },
  627. }
  628. var heapF = []*profile.Function{
  629. {ID: 1, Name: "pruneme", SystemName: "pruneme", Filename: "prune.h"},
  630. {ID: 2, Name: "mangled1000", SystemName: "mangled1000", Filename: "testdata/file1000.src"},
  631. {ID: 3, Name: "mangled2000", SystemName: "mangled2000", Filename: "testdata/file2000.src"},
  632. {ID: 4, Name: "mangled2001", SystemName: "mangled2001", Filename: "testdata/file2000.src"},
  633. {ID: 5, Name: "mangled3000", SystemName: "mangled3000", Filename: "testdata/file3000.src"},
  634. {ID: 6, Name: "mangled3001", SystemName: "mangled3001", Filename: "testdata/file3000.src"},
  635. {ID: 7, Name: "mangled3002", SystemName: "mangled3002", Filename: "testdata/file3000.src"},
  636. {ID: 8, Name: "mangledMALLOC", SystemName: "mangledMALLOC", Filename: "malloc.h"},
  637. {ID: 9, Name: "mangledNEW", SystemName: "mangledNEW", Filename: "new.h"},
  638. }
  639. var heapL = []*profile.Location{
  640. {
  641. ID: 1000,
  642. Mapping: heapM[0],
  643. Address: 0x1000,
  644. Line: []profile.Line{
  645. {Function: heapF[0], Line: 100},
  646. {Function: heapF[7], Line: 100},
  647. {Function: heapF[1], Line: 1},
  648. },
  649. },
  650. {
  651. ID: 2000,
  652. Mapping: heapM[0],
  653. Address: 0x2000,
  654. Line: []profile.Line{
  655. {Function: heapF[8], Line: 100},
  656. {Function: heapF[3], Line: 2},
  657. {Function: heapF[2], Line: 3},
  658. },
  659. },
  660. {
  661. ID: 3000,
  662. Mapping: heapM[0],
  663. Address: 0x3000,
  664. Line: []profile.Line{
  665. {Function: heapF[8], Line: 100},
  666. {Function: heapF[6], Line: 3},
  667. {Function: heapF[5], Line: 2},
  668. {Function: heapF[4], Line: 4},
  669. },
  670. },
  671. {
  672. ID: 3001,
  673. Mapping: heapM[0],
  674. Address: 0x3001,
  675. Line: []profile.Line{
  676. {Function: heapF[0], Line: 100},
  677. {Function: heapF[8], Line: 100},
  678. {Function: heapF[5], Line: 2},
  679. {Function: heapF[4], Line: 4},
  680. },
  681. },
  682. {
  683. ID: 3002,
  684. Mapping: heapM[0],
  685. Address: 0x3002,
  686. Line: []profile.Line{
  687. {Function: heapF[6], Line: 3},
  688. {Function: heapF[4], Line: 4},
  689. },
  690. },
  691. }
  692. return &profile.Profile{
  693. Comments: []string{"comment", "#hidden comment"},
  694. PeriodType: &profile.ValueType{Type: "allocations", Unit: "bytes"},
  695. Period: 524288,
  696. SampleType: []*profile.ValueType{
  697. {Type: "inuse_objects", Unit: "count"},
  698. {Type: "inuse_space", Unit: "bytes"},
  699. },
  700. Sample: []*profile.Sample{
  701. {
  702. Location: []*profile.Location{heapL[0], heapL[1], heapL[2]},
  703. Value: []int64{10, 1024000},
  704. NumLabel: map[string][]int64{
  705. "bytes": {102400},
  706. },
  707. },
  708. {
  709. Location: []*profile.Location{heapL[0], heapL[3]},
  710. Value: []int64{20, 4096000},
  711. NumLabel: map[string][]int64{
  712. "bytes": {204800},
  713. },
  714. },
  715. {
  716. Location: []*profile.Location{heapL[1], heapL[4]},
  717. Value: []int64{40, 65536000},
  718. NumLabel: map[string][]int64{
  719. "bytes": {1638400},
  720. },
  721. },
  722. {
  723. Location: []*profile.Location{heapL[2]},
  724. Value: []int64{80, 32768000},
  725. NumLabel: map[string][]int64{
  726. "bytes": {409600},
  727. },
  728. },
  729. },
  730. DropFrames: ".*operator new.*|malloc",
  731. Location: heapL,
  732. Function: heapF,
  733. Mapping: heapM,
  734. }
  735. }
  736. func contentionProfile() *profile.Profile {
  737. var contentionM = []*profile.Mapping{
  738. {
  739. ID: 1,
  740. BuildID: "buildid-contention",
  741. Start: 0x1000,
  742. Limit: 0x4000,
  743. HasFunctions: true,
  744. HasFilenames: true,
  745. HasLineNumbers: true,
  746. HasInlineFrames: true,
  747. },
  748. }
  749. var contentionF = []*profile.Function{
  750. {ID: 1, Name: "mangled1000", SystemName: "mangled1000", Filename: "testdata/file1000.src"},
  751. {ID: 2, Name: "mangled2000", SystemName: "mangled2000", Filename: "testdata/file2000.src"},
  752. {ID: 3, Name: "mangled2001", SystemName: "mangled2001", Filename: "testdata/file2000.src"},
  753. {ID: 4, Name: "mangled3000", SystemName: "mangled3000", Filename: "testdata/file3000.src"},
  754. {ID: 5, Name: "mangled3001", SystemName: "mangled3001", Filename: "testdata/file3000.src"},
  755. {ID: 6, Name: "mangled3002", SystemName: "mangled3002", Filename: "testdata/file3000.src"},
  756. }
  757. var contentionL = []*profile.Location{
  758. {
  759. ID: 1000,
  760. Mapping: contentionM[0],
  761. Address: 0x1000,
  762. Line: []profile.Line{
  763. {Function: contentionF[0], Line: 1},
  764. },
  765. },
  766. {
  767. ID: 2000,
  768. Mapping: contentionM[0],
  769. Address: 0x2000,
  770. Line: []profile.Line{
  771. {Function: contentionF[2], Line: 2},
  772. {Function: contentionF[1], Line: 3},
  773. },
  774. },
  775. {
  776. ID: 3000,
  777. Mapping: contentionM[0],
  778. Address: 0x3000,
  779. Line: []profile.Line{
  780. {Function: contentionF[5], Line: 2},
  781. {Function: contentionF[4], Line: 3},
  782. {Function: contentionF[3], Line: 5},
  783. },
  784. },
  785. {
  786. ID: 3001,
  787. Mapping: contentionM[0],
  788. Address: 0x3001,
  789. Line: []profile.Line{
  790. {Function: contentionF[4], Line: 3},
  791. {Function: contentionF[3], Line: 5},
  792. },
  793. },
  794. {
  795. ID: 3002,
  796. Mapping: contentionM[0],
  797. Address: 0x3002,
  798. Line: []profile.Line{
  799. {Function: contentionF[5], Line: 4},
  800. {Function: contentionF[3], Line: 3},
  801. },
  802. },
  803. }
  804. return &profile.Profile{
  805. PeriodType: &profile.ValueType{Type: "contentions", Unit: "count"},
  806. Period: 524288,
  807. SampleType: []*profile.ValueType{
  808. {Type: "contentions", Unit: "count"},
  809. {Type: "delay", Unit: "nanoseconds"},
  810. },
  811. Sample: []*profile.Sample{
  812. {
  813. Location: []*profile.Location{contentionL[0], contentionL[1], contentionL[2]},
  814. Value: []int64{10, 10240000},
  815. },
  816. {
  817. Location: []*profile.Location{contentionL[0], contentionL[3]},
  818. Value: []int64{20, 40960000},
  819. },
  820. {
  821. Location: []*profile.Location{contentionL[1], contentionL[4]},
  822. Value: []int64{40, 65536000},
  823. },
  824. {
  825. Location: []*profile.Location{contentionL[2]},
  826. Value: []int64{80, 32768000},
  827. },
  828. },
  829. Location: contentionL,
  830. Function: contentionF,
  831. Mapping: contentionM,
  832. Comments: []string{"Comment #1", "Comment #2"},
  833. }
  834. }
  835. func symzProfile() *profile.Profile {
  836. var symzM = []*profile.Mapping{
  837. {
  838. ID: 1,
  839. Start: testStart,
  840. Limit: 0x4000,
  841. File: "/path/to/testbinary",
  842. },
  843. }
  844. var symzL = []*profile.Location{
  845. {ID: 1, Mapping: symzM[0], Address: testStart},
  846. {ID: 2, Mapping: symzM[0], Address: testStart + 0x1000},
  847. {ID: 3, Mapping: symzM[0], Address: testStart + 0x2000},
  848. }
  849. return &profile.Profile{
  850. PeriodType: &profile.ValueType{Type: "cpu", Unit: "milliseconds"},
  851. Period: 1,
  852. DurationNanos: 10e9,
  853. SampleType: []*profile.ValueType{
  854. {Type: "samples", Unit: "count"},
  855. {Type: "cpu", Unit: "milliseconds"},
  856. },
  857. Sample: []*profile.Sample{
  858. {
  859. Location: []*profile.Location{symzL[0], symzL[1], symzL[2]},
  860. Value: []int64{1, 1},
  861. },
  862. },
  863. Location: symzL,
  864. Mapping: symzM,
  865. }
  866. }
  867. var autoCompleteTests = []struct {
  868. in string
  869. out string
  870. }{
  871. {"", ""},
  872. {"xyz", "xyz"}, // no match
  873. {"dis", "disasm"}, // single match
  874. {"t", "t"}, // many matches
  875. {"top abc", "top abc"}, // no function name match
  876. {"top mangledM", "top mangledMALLOC"}, // single function name match
  877. {"top cmd cmd mangledM", "top cmd cmd mangledMALLOC"},
  878. {"top mangled", "top mangled"}, // many function name matches
  879. {"cmd mangledM", "cmd mangledM"}, // invalid command
  880. {"top mangledM cmd", "top mangledM cmd"}, // cursor misplaced
  881. {"top edMA", "top mangledMALLOC"}, // single infix function name match
  882. {"top -mangledM", "top -mangledMALLOC"}, // ignore sign handled
  883. {"lin", "lines"}, // single variable match
  884. {"EdGeF", "edgefraction"}, // single capitalized match
  885. {"help dis", "help disasm"}, // help command match
  886. {"help relative_perc", "help relative_percentages"}, // help variable match
  887. {"help coMpa", "help compact_labels"}, // help variable capitalized match
  888. }
  889. func TestAutoComplete(t *testing.T) {
  890. complete := newCompleter(functionNames(heapProfile()))
  891. for _, test := range autoCompleteTests {
  892. if out := complete(test.in); out != test.out {
  893. t.Errorf("autoComplete(%s) = %s; want %s", test.in, out, test.out)
  894. }
  895. }
  896. }
  897. func TestTagFilter(t *testing.T) {
  898. var tagFilterTests = []struct {
  899. name, value string
  900. tags map[string][]string
  901. want bool
  902. }{
  903. {"test1", "tag2", map[string][]string{"value1": {"tag1", "tag2"}}, true},
  904. {"test2", "tag3", map[string][]string{"value1": {"tag1", "tag2"}}, false},
  905. {"test3", "tag1,tag3", map[string][]string{"value1": {"tag1", "tag2"}, "value2": {"tag3"}}, true},
  906. {"test4", "t..[12],t..3", map[string][]string{"value1": {"tag1", "tag2"}, "value2": {"tag3"}}, true},
  907. {"test5", "tag2,tag3", map[string][]string{"value1": {"tag1", "tag2"}}, false},
  908. }
  909. for _, test := range tagFilterTests {
  910. filter, err := compileTagFilter(test.name, test.value, &proftest.TestUI{T: t}, nil)
  911. if err != nil {
  912. t.Errorf("tagFilter %s:%v", test.name, err)
  913. continue
  914. }
  915. s := profile.Sample{
  916. Label: test.tags,
  917. }
  918. if got := filter(&s); got != test.want {
  919. t.Errorf("tagFilter %s: got %v, want %v", test.name, got, test.want)
  920. }
  921. }
  922. }
  923. type testSymbolzMergeFetcher struct{}
  924. func (testSymbolzMergeFetcher) Fetch(s string, d, t time.Duration) (*profile.Profile, string, error) {
  925. var p *profile.Profile
  926. switch s {
  927. case testSourceURL(8000) + "symbolz":
  928. p = symzProfile()
  929. case testSourceURL(8001) + "symbolz":
  930. p = symzProfile()
  931. p.Mapping[0].Start += testOffset
  932. p.Mapping[0].Limit += testOffset
  933. for i := range p.Location {
  934. p.Location[i].Address += testOffset
  935. }
  936. default:
  937. return nil, "", fmt.Errorf("unexpected source: %s", s)
  938. }
  939. return p, s, nil
  940. }
  941. func TestSymbolzAfterMerge(t *testing.T) {
  942. baseVars := pprofVariables
  943. pprofVariables = baseVars.makeCopy()
  944. defer func() { pprofVariables = baseVars }()
  945. f := baseFlags()
  946. f.args = []string{
  947. testSourceURL(8000) + "symbolz",
  948. testSourceURL(8001) + "symbolz",
  949. }
  950. o := setDefaults(nil)
  951. o.Flagset = f
  952. o.Obj = new(mockObjTool)
  953. src, cmd, err := parseFlags(o)
  954. if err != nil {
  955. t.Fatalf("parseFlags: %v", err)
  956. }
  957. if len(cmd) != 1 || cmd[0] != "proto" {
  958. t.Fatalf("parseFlags returned command %v, want [proto]", cmd)
  959. }
  960. o.Fetch = testSymbolzMergeFetcher{}
  961. o.Sym = testSymbolzSymbolizer{}
  962. p, err := fetchProfiles(src, o)
  963. if err != nil {
  964. t.Fatalf("fetchProfiles: %v", err)
  965. }
  966. if len(p.Location) != 3 {
  967. t.Errorf("Got %d locations after merge, want %d", len(p.Location), 3)
  968. }
  969. for i, l := range p.Location {
  970. if len(l.Line) != 1 {
  971. t.Errorf("Number of lines for symbolz %#x in iteration %d, got %d, want %d", l.Address, i, len(l.Line), 1)
  972. continue
  973. }
  974. address := l.Address - l.Mapping.Start
  975. if got, want := l.Line[0].Function.Name, fmt.Sprintf("%#x", address); got != want {
  976. t.Errorf("symbolz %#x, got %s, want %s", address, got, want)
  977. }
  978. }
  979. }
  980. type mockObjTool struct{}
  981. func (*mockObjTool) Open(file string, start, limit, offset uint64) (plugin.ObjFile, error) {
  982. return &mockFile{file, "abcdef", 0}, nil
  983. }
  984. func (m *mockObjTool) Disasm(file string, start, end uint64) ([]plugin.Inst, error) {
  985. switch start {
  986. case 0x1000:
  987. return []plugin.Inst{
  988. {Addr: 0x1000, Text: "instruction one", File: "file1000.src", Line: 1},
  989. {Addr: 0x1001, Text: "instruction two", File: "file1000.src", Line: 1},
  990. {Addr: 0x1002, Text: "instruction three", File: "file1000.src", Line: 2},
  991. {Addr: 0x1003, Text: "instruction four", File: "file1000.src", Line: 1},
  992. }, nil
  993. case 0x3000:
  994. return []plugin.Inst{
  995. {Addr: 0x3000, Text: "instruction one"},
  996. {Addr: 0x3001, Text: "instruction two"},
  997. {Addr: 0x3002, Text: "instruction three"},
  998. {Addr: 0x3003, Text: "instruction four"},
  999. {Addr: 0x3004, Text: "instruction five"},
  1000. }, nil
  1001. }
  1002. return nil, fmt.Errorf("unimplemented")
  1003. }
  1004. type mockFile struct {
  1005. name, buildID string
  1006. base uint64
  1007. }
  1008. // Name returns the underlyinf file name, if available
  1009. func (m *mockFile) Name() string {
  1010. return m.name
  1011. }
  1012. // Base returns the base address to use when looking up symbols in the file.
  1013. func (m *mockFile) Base() uint64 {
  1014. return m.base
  1015. }
  1016. // BuildID returns the GNU build ID of the file, or an empty string.
  1017. func (m *mockFile) BuildID() string {
  1018. return m.buildID
  1019. }
  1020. // SourceLine reports the source line information for a given
  1021. // address in the file. Due to inlining, the source line information
  1022. // is in general a list of positions representing a call stack,
  1023. // with the leaf function first.
  1024. func (*mockFile) SourceLine(addr uint64) ([]plugin.Frame, error) {
  1025. return nil, fmt.Errorf("unimplemented")
  1026. }
  1027. // Symbols returns a list of symbols in the object file.
  1028. // If r is not nil, Symbols restricts the list to symbols
  1029. // with names matching the regular expression.
  1030. // If addr is not zero, Symbols restricts the list to symbols
  1031. // containing that address.
  1032. func (m *mockFile) Symbols(r *regexp.Regexp, addr uint64) ([]*plugin.Sym, error) {
  1033. switch r.String() {
  1034. case "line[13]":
  1035. return []*plugin.Sym{
  1036. {[]string{"line1000"}, m.name, 0x1000, 0x1003},
  1037. {[]string{"line3000"}, m.name, 0x3000, 0x3004},
  1038. }, nil
  1039. }
  1040. return nil, fmt.Errorf("unimplemented")
  1041. }
  1042. // Close closes the file, releasing associated resources.
  1043. func (*mockFile) Close() error {
  1044. return nil
  1045. }