Нет описания

driver_test.go 38KB

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