Нема описа

driver_test.go 33KB

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