Aucune description

driver_test.go 29KB

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