Açıklama Yok

driver_test.go 30KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133
  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. }
  251. func (testFlags) ExtraUsage() string { return "" }
  252. func (f testFlags) Bool(s string, d bool, c string) *bool {
  253. if b, ok := f.bools[s]; ok {
  254. return &b
  255. }
  256. return &d
  257. }
  258. func (f testFlags) Int(s string, d int, c string) *int {
  259. if i, ok := f.ints[s]; ok {
  260. return &i
  261. }
  262. return &d
  263. }
  264. func (f testFlags) Float64(s string, d float64, c string) *float64 {
  265. if g, ok := f.floats[s]; ok {
  266. return &g
  267. }
  268. return &d
  269. }
  270. func (f testFlags) String(s, d, c string) *string {
  271. if t, ok := f.strings[s]; ok {
  272. return &t
  273. }
  274. return &d
  275. }
  276. func (f testFlags) BoolVar(p *bool, s string, d bool, c string) {
  277. if b, ok := f.bools[s]; ok {
  278. *p = b
  279. } else {
  280. *p = d
  281. }
  282. }
  283. func (f testFlags) IntVar(p *int, s string, d int, c string) {
  284. if i, ok := f.ints[s]; ok {
  285. *p = i
  286. } else {
  287. *p = d
  288. }
  289. }
  290. func (f testFlags) Float64Var(p *float64, s string, d float64, c string) {
  291. if g, ok := f.floats[s]; ok {
  292. *p = g
  293. } else {
  294. *p = d
  295. }
  296. }
  297. func (f testFlags) StringVar(p *string, s, d, c string) {
  298. if t, ok := f.strings[s]; ok {
  299. *p = t
  300. } else {
  301. *p = d
  302. }
  303. }
  304. func (f testFlags) StringList(s, d, c string) *[]*string {
  305. return &[]*string{}
  306. }
  307. func (f testFlags) Parse(func()) []string {
  308. return f.args
  309. }
  310. func baseFlags() testFlags {
  311. return testFlags{
  312. bools: map[string]bool{
  313. "proto": true,
  314. "trim": true,
  315. "compact_labels": true,
  316. },
  317. ints: map[string]int{
  318. "nodecount": 20,
  319. },
  320. floats: map[string]float64{
  321. "nodefraction": 0.05,
  322. "edgefraction": 0.01,
  323. "divide_by": 1.0,
  324. },
  325. strings: map[string]string{
  326. "unit": "minimum",
  327. },
  328. }
  329. }
  330. const testStart = 0x1000
  331. const testOffset = 0x5000
  332. type testFetcher struct{}
  333. func (testFetcher) Fetch(s string, d, t time.Duration) (*profile.Profile, string, error) {
  334. var p *profile.Profile
  335. switch s {
  336. case "cpu", "unknown":
  337. p = cpuProfile()
  338. case "cpusmall":
  339. p = cpuProfileSmall()
  340. case "heap":
  341. p = heapProfile()
  342. case "heap_alloc":
  343. p = heapProfile()
  344. p.SampleType = []*profile.ValueType{
  345. {Type: "alloc_objects", Unit: "count"},
  346. {Type: "alloc_space", Unit: "bytes"},
  347. }
  348. case "contention":
  349. p = contentionProfile()
  350. case "symbolz":
  351. p = symzProfile()
  352. default:
  353. return nil, "", fmt.Errorf("unexpected source: %s", s)
  354. }
  355. return p, testSourceURL(8000) + s, nil
  356. }
  357. type testSymbolizer struct{}
  358. func (testSymbolizer) Symbolize(_ string, _ plugin.MappingSources, _ *profile.Profile) error {
  359. return nil
  360. }
  361. type testSymbolizeDemangler struct{}
  362. func (testSymbolizeDemangler) Symbolize(_ string, _ plugin.MappingSources, p *profile.Profile) error {
  363. for _, fn := range p.Function {
  364. if fn.Name == "" || fn.SystemName == fn.Name {
  365. fn.Name = fakeDemangler(fn.SystemName)
  366. }
  367. }
  368. return nil
  369. }
  370. func testFetchSymbols(source, post string) ([]byte, error) {
  371. var buf bytes.Buffer
  372. switch source {
  373. case testSourceURL(8000) + "symbolz":
  374. for _, address := range strings.Split(post, "+") {
  375. a, _ := strconv.ParseInt(address, 0, 64)
  376. fmt.Fprintf(&buf, "%v\t", address)
  377. if a-testStart > testOffset {
  378. fmt.Fprintf(&buf, "wrong_source_%v_", address)
  379. continue
  380. }
  381. fmt.Fprintf(&buf, "%#x\n", a-testStart)
  382. }
  383. return buf.Bytes(), nil
  384. case testSourceURL(8001) + "symbolz":
  385. for _, address := range strings.Split(post, "+") {
  386. a, _ := strconv.ParseInt(address, 0, 64)
  387. fmt.Fprintf(&buf, "%v\t", address)
  388. if a-testStart < testOffset {
  389. fmt.Fprintf(&buf, "wrong_source_%v_", address)
  390. continue
  391. }
  392. fmt.Fprintf(&buf, "%#x\n", a-testStart-testOffset)
  393. }
  394. return buf.Bytes(), nil
  395. default:
  396. return nil, fmt.Errorf("unexpected source: %s", source)
  397. }
  398. }
  399. type testSymbolzSymbolizer struct{}
  400. func (testSymbolzSymbolizer) Symbolize(variables string, sources plugin.MappingSources, p *profile.Profile) error {
  401. return symbolz.Symbolize(sources, testFetchSymbols, p, nil)
  402. }
  403. func fakeDemangler(name string) string {
  404. switch name {
  405. case "mangled1000":
  406. return "line1000"
  407. case "mangled2000":
  408. return "line2000"
  409. case "mangled2001":
  410. return "line2001"
  411. case "mangled3000":
  412. return "line3000"
  413. case "mangled3001":
  414. return "line3001"
  415. case "mangled3002":
  416. return "line3002"
  417. case "mangledNEW":
  418. return "operator new"
  419. case "mangledMALLOC":
  420. return "malloc"
  421. default:
  422. return name
  423. }
  424. }
  425. func cpuProfile() *profile.Profile {
  426. var cpuM = []*profile.Mapping{
  427. {
  428. ID: 1,
  429. Start: 0x1000,
  430. Limit: 0x4000,
  431. File: "/path/to/testbinary",
  432. HasFunctions: true,
  433. HasFilenames: true,
  434. HasLineNumbers: true,
  435. HasInlineFrames: true,
  436. },
  437. }
  438. var cpuF = []*profile.Function{
  439. {ID: 1, Name: "mangled1000", SystemName: "mangled1000", Filename: "testdata/file1000.src"},
  440. {ID: 2, Name: "mangled2000", SystemName: "mangled2000", Filename: "testdata/file2000.src"},
  441. {ID: 3, Name: "mangled2001", SystemName: "mangled2001", Filename: "testdata/file2000.src"},
  442. {ID: 4, Name: "mangled3000", SystemName: "mangled3000", Filename: "testdata/file3000.src"},
  443. {ID: 5, Name: "mangled3001", SystemName: "mangled3001", Filename: "testdata/file3000.src"},
  444. {ID: 6, Name: "mangled3002", SystemName: "mangled3002", Filename: "testdata/file3000.src"},
  445. }
  446. var cpuL = []*profile.Location{
  447. {
  448. ID: 1000,
  449. Mapping: cpuM[0],
  450. Address: 0x1000,
  451. Line: []profile.Line{
  452. {Function: cpuF[0], Line: 1},
  453. },
  454. },
  455. {
  456. ID: 2000,
  457. Mapping: cpuM[0],
  458. Address: 0x2000,
  459. Line: []profile.Line{
  460. {Function: cpuF[2], Line: 9},
  461. {Function: cpuF[1], Line: 4},
  462. },
  463. },
  464. {
  465. ID: 3000,
  466. Mapping: cpuM[0],
  467. Address: 0x3000,
  468. Line: []profile.Line{
  469. {Function: cpuF[5], Line: 2},
  470. {Function: cpuF[4], Line: 5},
  471. {Function: cpuF[3], Line: 6},
  472. },
  473. },
  474. {
  475. ID: 3001,
  476. Mapping: cpuM[0],
  477. Address: 0x3001,
  478. Line: []profile.Line{
  479. {Function: cpuF[4], Line: 8},
  480. {Function: cpuF[3], Line: 9},
  481. },
  482. },
  483. {
  484. ID: 3002,
  485. Mapping: cpuM[0],
  486. Address: 0x3002,
  487. Line: []profile.Line{
  488. {Function: cpuF[5], Line: 5},
  489. {Function: cpuF[3], Line: 9},
  490. },
  491. },
  492. }
  493. return &profile.Profile{
  494. PeriodType: &profile.ValueType{Type: "cpu", Unit: "milliseconds"},
  495. Period: 1,
  496. DurationNanos: 10e9,
  497. SampleType: []*profile.ValueType{
  498. {Type: "samples", Unit: "count"},
  499. {Type: "cpu", Unit: "milliseconds"},
  500. },
  501. Sample: []*profile.Sample{
  502. {
  503. Location: []*profile.Location{cpuL[0], cpuL[1], cpuL[2]},
  504. Value: []int64{1000, 1000},
  505. Label: map[string][]string{
  506. "key1": {"tag1"},
  507. "key2": {"tag1"},
  508. },
  509. },
  510. {
  511. Location: []*profile.Location{cpuL[0], cpuL[3]},
  512. Value: []int64{100, 100},
  513. Label: map[string][]string{
  514. "key1": {"tag2"},
  515. "key3": {"tag2"},
  516. },
  517. },
  518. {
  519. Location: []*profile.Location{cpuL[1], cpuL[4]},
  520. Value: []int64{10, 10},
  521. Label: map[string][]string{
  522. "key1": {"tag3"},
  523. "key2": {"tag2"},
  524. },
  525. },
  526. {
  527. Location: []*profile.Location{cpuL[2]},
  528. Value: []int64{10, 10},
  529. Label: map[string][]string{
  530. "key1": {"tag4"},
  531. "key2": {"tag1"},
  532. },
  533. },
  534. },
  535. Location: cpuL,
  536. Function: cpuF,
  537. Mapping: cpuM,
  538. }
  539. }
  540. func cpuProfileSmall() *profile.Profile {
  541. var cpuM = []*profile.Mapping{
  542. {
  543. ID: 1,
  544. Start: 0x1000,
  545. Limit: 0x4000,
  546. File: "/path/to/testbinary",
  547. HasFunctions: true,
  548. HasFilenames: true,
  549. HasLineNumbers: true,
  550. HasInlineFrames: true,
  551. },
  552. }
  553. var cpuL = []*profile.Location{
  554. {
  555. ID: 1000,
  556. Mapping: cpuM[0],
  557. Address: 0x1000,
  558. },
  559. {
  560. ID: 2000,
  561. Mapping: cpuM[0],
  562. Address: 0x2000,
  563. },
  564. {
  565. ID: 3000,
  566. Mapping: cpuM[0],
  567. Address: 0x3000,
  568. },
  569. {
  570. ID: 4000,
  571. Mapping: cpuM[0],
  572. Address: 0x4000,
  573. },
  574. {
  575. ID: 5000,
  576. Mapping: cpuM[0],
  577. Address: 0x5000,
  578. },
  579. }
  580. return &profile.Profile{
  581. PeriodType: &profile.ValueType{Type: "cpu", Unit: "milliseconds"},
  582. Period: 1,
  583. DurationNanos: 10e9,
  584. SampleType: []*profile.ValueType{
  585. {Type: "samples", Unit: "count"},
  586. {Type: "cpu", Unit: "milliseconds"},
  587. },
  588. Sample: []*profile.Sample{
  589. {
  590. Location: []*profile.Location{cpuL[0], cpuL[1], cpuL[2]},
  591. Value: []int64{1000, 1000},
  592. },
  593. {
  594. Location: []*profile.Location{cpuL[3], cpuL[1], cpuL[4]},
  595. Value: []int64{1000, 1000},
  596. },
  597. {
  598. Location: []*profile.Location{cpuL[2]},
  599. Value: []int64{1000, 1000},
  600. },
  601. {
  602. Location: []*profile.Location{cpuL[4]},
  603. Value: []int64{1000, 1000},
  604. },
  605. },
  606. Location: cpuL,
  607. Function: nil,
  608. Mapping: cpuM,
  609. }
  610. }
  611. func heapProfile() *profile.Profile {
  612. var heapM = []*profile.Mapping{
  613. {
  614. ID: 1,
  615. BuildID: "buildid",
  616. Start: 0x1000,
  617. Limit: 0x4000,
  618. HasFunctions: true,
  619. HasFilenames: true,
  620. HasLineNumbers: true,
  621. HasInlineFrames: true,
  622. },
  623. }
  624. var heapF = []*profile.Function{
  625. {ID: 1, Name: "pruneme", SystemName: "pruneme", Filename: "prune.h"},
  626. {ID: 2, Name: "mangled1000", SystemName: "mangled1000", Filename: "testdata/file1000.src"},
  627. {ID: 3, Name: "mangled2000", SystemName: "mangled2000", Filename: "testdata/file2000.src"},
  628. {ID: 4, Name: "mangled2001", SystemName: "mangled2001", Filename: "testdata/file2000.src"},
  629. {ID: 5, Name: "mangled3000", SystemName: "mangled3000", Filename: "testdata/file3000.src"},
  630. {ID: 6, Name: "mangled3001", SystemName: "mangled3001", Filename: "testdata/file3000.src"},
  631. {ID: 7, Name: "mangled3002", SystemName: "mangled3002", Filename: "testdata/file3000.src"},
  632. {ID: 8, Name: "mangledMALLOC", SystemName: "mangledMALLOC", Filename: "malloc.h"},
  633. {ID: 9, Name: "mangledNEW", SystemName: "mangledNEW", Filename: "new.h"},
  634. }
  635. var heapL = []*profile.Location{
  636. {
  637. ID: 1000,
  638. Mapping: heapM[0],
  639. Address: 0x1000,
  640. Line: []profile.Line{
  641. {Function: heapF[0], Line: 100},
  642. {Function: heapF[7], Line: 100},
  643. {Function: heapF[1], Line: 1},
  644. },
  645. },
  646. {
  647. ID: 2000,
  648. Mapping: heapM[0],
  649. Address: 0x2000,
  650. Line: []profile.Line{
  651. {Function: heapF[8], Line: 100},
  652. {Function: heapF[3], Line: 2},
  653. {Function: heapF[2], Line: 3},
  654. },
  655. },
  656. {
  657. ID: 3000,
  658. Mapping: heapM[0],
  659. Address: 0x3000,
  660. Line: []profile.Line{
  661. {Function: heapF[8], Line: 100},
  662. {Function: heapF[6], Line: 3},
  663. {Function: heapF[5], Line: 2},
  664. {Function: heapF[4], Line: 4},
  665. },
  666. },
  667. {
  668. ID: 3001,
  669. Mapping: heapM[0],
  670. Address: 0x3001,
  671. Line: []profile.Line{
  672. {Function: heapF[0], Line: 100},
  673. {Function: heapF[8], Line: 100},
  674. {Function: heapF[5], Line: 2},
  675. {Function: heapF[4], Line: 4},
  676. },
  677. },
  678. {
  679. ID: 3002,
  680. Mapping: heapM[0],
  681. Address: 0x3002,
  682. Line: []profile.Line{
  683. {Function: heapF[6], Line: 3},
  684. {Function: heapF[4], Line: 4},
  685. },
  686. },
  687. }
  688. return &profile.Profile{
  689. Comments: []string{"comment", "#hidden comment"},
  690. PeriodType: &profile.ValueType{Type: "allocations", Unit: "bytes"},
  691. Period: 524288,
  692. SampleType: []*profile.ValueType{
  693. {Type: "inuse_objects", Unit: "count"},
  694. {Type: "inuse_space", Unit: "bytes"},
  695. },
  696. Sample: []*profile.Sample{
  697. {
  698. Location: []*profile.Location{heapL[0], heapL[1], heapL[2]},
  699. Value: []int64{10, 1024000},
  700. NumLabel: map[string][]int64{
  701. "bytes": {102400},
  702. },
  703. },
  704. {
  705. Location: []*profile.Location{heapL[0], heapL[3]},
  706. Value: []int64{20, 4096000},
  707. NumLabel: map[string][]int64{
  708. "bytes": {204800},
  709. },
  710. },
  711. {
  712. Location: []*profile.Location{heapL[1], heapL[4]},
  713. Value: []int64{40, 65536000},
  714. NumLabel: map[string][]int64{
  715. "bytes": {1638400},
  716. },
  717. },
  718. {
  719. Location: []*profile.Location{heapL[2]},
  720. Value: []int64{80, 32768000},
  721. NumLabel: map[string][]int64{
  722. "bytes": {409600},
  723. },
  724. },
  725. },
  726. DropFrames: ".*operator new.*|malloc",
  727. Location: heapL,
  728. Function: heapF,
  729. Mapping: heapM,
  730. }
  731. }
  732. func contentionProfile() *profile.Profile {
  733. var contentionM = []*profile.Mapping{
  734. {
  735. ID: 1,
  736. BuildID: "buildid-contention",
  737. Start: 0x1000,
  738. Limit: 0x4000,
  739. HasFunctions: true,
  740. HasFilenames: true,
  741. HasLineNumbers: true,
  742. HasInlineFrames: true,
  743. },
  744. }
  745. var contentionF = []*profile.Function{
  746. {ID: 1, Name: "mangled1000", SystemName: "mangled1000", Filename: "testdata/file1000.src"},
  747. {ID: 2, Name: "mangled2000", SystemName: "mangled2000", Filename: "testdata/file2000.src"},
  748. {ID: 3, Name: "mangled2001", SystemName: "mangled2001", Filename: "testdata/file2000.src"},
  749. {ID: 4, Name: "mangled3000", SystemName: "mangled3000", Filename: "testdata/file3000.src"},
  750. {ID: 5, Name: "mangled3001", SystemName: "mangled3001", Filename: "testdata/file3000.src"},
  751. {ID: 6, Name: "mangled3002", SystemName: "mangled3002", Filename: "testdata/file3000.src"},
  752. }
  753. var contentionL = []*profile.Location{
  754. {
  755. ID: 1000,
  756. Mapping: contentionM[0],
  757. Address: 0x1000,
  758. Line: []profile.Line{
  759. {Function: contentionF[0], Line: 1},
  760. },
  761. },
  762. {
  763. ID: 2000,
  764. Mapping: contentionM[0],
  765. Address: 0x2000,
  766. Line: []profile.Line{
  767. {Function: contentionF[2], Line: 2},
  768. {Function: contentionF[1], Line: 3},
  769. },
  770. },
  771. {
  772. ID: 3000,
  773. Mapping: contentionM[0],
  774. Address: 0x3000,
  775. Line: []profile.Line{
  776. {Function: contentionF[5], Line: 2},
  777. {Function: contentionF[4], Line: 3},
  778. {Function: contentionF[3], Line: 5},
  779. },
  780. },
  781. {
  782. ID: 3001,
  783. Mapping: contentionM[0],
  784. Address: 0x3001,
  785. Line: []profile.Line{
  786. {Function: contentionF[4], Line: 3},
  787. {Function: contentionF[3], Line: 5},
  788. },
  789. },
  790. {
  791. ID: 3002,
  792. Mapping: contentionM[0],
  793. Address: 0x3002,
  794. Line: []profile.Line{
  795. {Function: contentionF[5], Line: 4},
  796. {Function: contentionF[3], Line: 3},
  797. },
  798. },
  799. }
  800. return &profile.Profile{
  801. PeriodType: &profile.ValueType{Type: "contentions", Unit: "count"},
  802. Period: 524288,
  803. SampleType: []*profile.ValueType{
  804. {Type: "contentions", Unit: "count"},
  805. {Type: "delay", Unit: "nanoseconds"},
  806. },
  807. Sample: []*profile.Sample{
  808. {
  809. Location: []*profile.Location{contentionL[0], contentionL[1], contentionL[2]},
  810. Value: []int64{10, 10240000},
  811. },
  812. {
  813. Location: []*profile.Location{contentionL[0], contentionL[3]},
  814. Value: []int64{20, 40960000},
  815. },
  816. {
  817. Location: []*profile.Location{contentionL[1], contentionL[4]},
  818. Value: []int64{40, 65536000},
  819. },
  820. {
  821. Location: []*profile.Location{contentionL[2]},
  822. Value: []int64{80, 32768000},
  823. },
  824. },
  825. Location: contentionL,
  826. Function: contentionF,
  827. Mapping: contentionM,
  828. Comments: []string{"Comment #1", "Comment #2"},
  829. }
  830. }
  831. func symzProfile() *profile.Profile {
  832. var symzM = []*profile.Mapping{
  833. {
  834. ID: 1,
  835. Start: testStart,
  836. Limit: 0x4000,
  837. File: "/path/to/testbinary",
  838. },
  839. }
  840. var symzL = []*profile.Location{
  841. {ID: 1, Mapping: symzM[0], Address: testStart},
  842. {ID: 2, Mapping: symzM[0], Address: testStart + 0x1000},
  843. {ID: 3, Mapping: symzM[0], Address: testStart + 0x2000},
  844. }
  845. return &profile.Profile{
  846. PeriodType: &profile.ValueType{Type: "cpu", Unit: "milliseconds"},
  847. Period: 1,
  848. DurationNanos: 10e9,
  849. SampleType: []*profile.ValueType{
  850. {Type: "samples", Unit: "count"},
  851. {Type: "cpu", Unit: "milliseconds"},
  852. },
  853. Sample: []*profile.Sample{
  854. {
  855. Location: []*profile.Location{symzL[0], symzL[1], symzL[2]},
  856. Value: []int64{1, 1},
  857. },
  858. },
  859. Location: symzL,
  860. Mapping: symzM,
  861. }
  862. }
  863. var autoCompleteTests = []struct {
  864. in string
  865. out string
  866. }{
  867. {"", ""},
  868. {"xyz", "xyz"}, // no match
  869. {"dis", "disasm"}, // single match
  870. {"t", "t"}, // many matches
  871. {"top abc", "top abc"}, // no function name match
  872. {"top mangledM", "top mangledMALLOC"}, // single function name match
  873. {"top cmd cmd mangledM", "top cmd cmd mangledMALLOC"},
  874. {"top mangled", "top mangled"}, // many function name matches
  875. {"cmd mangledM", "cmd mangledM"}, // invalid command
  876. {"top mangledM cmd", "top mangledM cmd"}, // cursor misplaced
  877. {"top edMA", "top mangledMALLOC"}, // single infix function name match
  878. {"top -mangledM", "top -mangledMALLOC"}, // ignore sign handled
  879. {"lin", "lines"}, // single variable match
  880. {"EdGeF", "edgefraction"}, // single capitalized match
  881. {"help dis", "help disasm"}, // help command match
  882. {"help relative_perc", "help relative_percentages"}, // help variable match
  883. {"help coMpa", "help compact_labels"}, // help variable capitalized match
  884. }
  885. func TestAutoComplete(t *testing.T) {
  886. complete := newCompleter(functionNames(heapProfile()))
  887. for _, test := range autoCompleteTests {
  888. if out := complete(test.in); out != test.out {
  889. t.Errorf("autoComplete(%s) = %s; want %s", test.in, out, test.out)
  890. }
  891. }
  892. }
  893. func TestTagFilter(t *testing.T) {
  894. var tagFilterTests = []struct {
  895. name, value string
  896. tags map[string][]string
  897. want bool
  898. }{
  899. {"test1", "tag2", map[string][]string{"value1": {"tag1", "tag2"}}, true},
  900. {"test2", "tag3", map[string][]string{"value1": {"tag1", "tag2"}}, false},
  901. {"test3", "tag1,tag3", map[string][]string{"value1": {"tag1", "tag2"}, "value2": {"tag3"}}, true},
  902. {"test4", "t..[12],t..3", map[string][]string{"value1": {"tag1", "tag2"}, "value2": {"tag3"}}, true},
  903. {"test5", "tag2,tag3", map[string][]string{"value1": {"tag1", "tag2"}}, false},
  904. }
  905. for _, test := range tagFilterTests {
  906. filter, err := compileTagFilter(test.name, test.value, &proftest.TestUI{T: t}, nil)
  907. if err != nil {
  908. t.Errorf("tagFilter %s:%v", test.name, err)
  909. continue
  910. }
  911. s := profile.Sample{
  912. Label: test.tags,
  913. }
  914. if got := filter(&s); got != test.want {
  915. t.Errorf("tagFilter %s: got %v, want %v", test.name, got, test.want)
  916. }
  917. }
  918. }
  919. type testSymbolzMergeFetcher struct{}
  920. func (testSymbolzMergeFetcher) Fetch(s string, d, t time.Duration) (*profile.Profile, string, error) {
  921. var p *profile.Profile
  922. switch s {
  923. case testSourceURL(8000) + "symbolz":
  924. p = symzProfile()
  925. case testSourceURL(8001) + "symbolz":
  926. p = symzProfile()
  927. p.Mapping[0].Start += testOffset
  928. p.Mapping[0].Limit += testOffset
  929. for i := range p.Location {
  930. p.Location[i].Address += testOffset
  931. }
  932. default:
  933. return nil, "", fmt.Errorf("unexpected source: %s", s)
  934. }
  935. return p, s, nil
  936. }
  937. func TestSymbolzAfterMerge(t *testing.T) {
  938. baseVars := pprofVariables
  939. pprofVariables = baseVars.makeCopy()
  940. defer func() { pprofVariables = baseVars }()
  941. f := baseFlags()
  942. f.args = []string{
  943. testSourceURL(8000) + "symbolz",
  944. testSourceURL(8001) + "symbolz",
  945. }
  946. o := setDefaults(nil)
  947. o.Flagset = f
  948. o.Obj = new(mockObjTool)
  949. src, cmd, err := parseFlags(o)
  950. if err != nil {
  951. t.Fatalf("parseFlags: %v", err)
  952. }
  953. if len(cmd) != 1 || cmd[0] != "proto" {
  954. t.Fatalf("parseFlags returned command %v, want [proto]", cmd)
  955. }
  956. o.Fetch = testSymbolzMergeFetcher{}
  957. o.Sym = testSymbolzSymbolizer{}
  958. p, err := fetchProfiles(src, o)
  959. if err != nil {
  960. t.Fatalf("fetchProfiles: %v", err)
  961. }
  962. if len(p.Location) != 3 {
  963. t.Errorf("Got %d locations after merge, want %d", len(p.Location), 3)
  964. }
  965. for i, l := range p.Location {
  966. if len(l.Line) != 1 {
  967. t.Errorf("Number of lines for symbolz %#x in iteration %d, got %d, want %d", l.Address, i, len(l.Line), 1)
  968. continue
  969. }
  970. address := l.Address - l.Mapping.Start
  971. if got, want := l.Line[0].Function.Name, fmt.Sprintf("%#x", address); got != want {
  972. t.Errorf("symbolz %#x, got %s, want %s", address, got, want)
  973. }
  974. }
  975. }
  976. type mockObjTool struct{}
  977. func (*mockObjTool) Open(file string, start, limit, offset uint64) (plugin.ObjFile, error) {
  978. return &mockFile{file, "abcdef", 0}, nil
  979. }
  980. func (m *mockObjTool) Disasm(file string, start, end uint64) ([]plugin.Inst, error) {
  981. switch start {
  982. case 0x1000:
  983. return []plugin.Inst{
  984. {Addr: 0x1000, Text: "instruction one", File: "file1000.src", Line: 1},
  985. {Addr: 0x1001, Text: "instruction two", File: "file1000.src", Line: 1},
  986. {Addr: 0x1002, Text: "instruction three", File: "file1000.src", Line: 2},
  987. {Addr: 0x1003, Text: "instruction four", File: "file1000.src", Line: 1},
  988. }, nil
  989. case 0x3000:
  990. return []plugin.Inst{
  991. {Addr: 0x3000, Text: "instruction one"},
  992. {Addr: 0x3001, Text: "instruction two"},
  993. {Addr: 0x3002, Text: "instruction three"},
  994. {Addr: 0x3003, Text: "instruction four"},
  995. {Addr: 0x3004, Text: "instruction five"},
  996. }, nil
  997. }
  998. return nil, fmt.Errorf("unimplemented")
  999. }
  1000. type mockFile struct {
  1001. name, buildID string
  1002. base uint64
  1003. }
  1004. // Name returns the underlyinf file name, if available
  1005. func (m *mockFile) Name() string {
  1006. return m.name
  1007. }
  1008. // Base returns the base address to use when looking up symbols in the file.
  1009. func (m *mockFile) Base() uint64 {
  1010. return m.base
  1011. }
  1012. // BuildID returns the GNU build ID of the file, or an empty string.
  1013. func (m *mockFile) BuildID() string {
  1014. return m.buildID
  1015. }
  1016. // SourceLine reports the source line information for a given
  1017. // address in the file. Due to inlining, the source line information
  1018. // is in general a list of positions representing a call stack,
  1019. // with the leaf function first.
  1020. func (*mockFile) SourceLine(addr uint64) ([]plugin.Frame, error) {
  1021. return nil, fmt.Errorf("unimplemented")
  1022. }
  1023. // Symbols returns a list of symbols in the object file.
  1024. // If r is not nil, Symbols restricts the list to symbols
  1025. // with names matching the regular expression.
  1026. // If addr is not zero, Symbols restricts the list to symbols
  1027. // containing that address.
  1028. func (m *mockFile) Symbols(r *regexp.Regexp, addr uint64) ([]*plugin.Sym, error) {
  1029. switch r.String() {
  1030. case "line[13]":
  1031. return []*plugin.Sym{
  1032. {[]string{"line1000"}, m.name, 0x1000, 0x1003},
  1033. {[]string{"line3000"}, m.name, 0x3000, 0x3004},
  1034. }, nil
  1035. }
  1036. return nil, fmt.Errorf("unimplemented")
  1037. }
  1038. // Close closes the file, releasing associated resources.
  1039. func (*mockFile) Close() error {
  1040. return nil
  1041. }