暂无描述

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