No Description

profile.go 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569
  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 profile provides a representation of profile.proto and
  15. // methods to encode/decode profiles in this format.
  16. package profile
  17. import (
  18. "bytes"
  19. "compress/gzip"
  20. "fmt"
  21. "io"
  22. "io/ioutil"
  23. "regexp"
  24. "strings"
  25. "time"
  26. )
  27. // Profile is an in-memory representation of profile.proto.
  28. type Profile struct {
  29. SampleType []*ValueType
  30. DefaultSampleType string
  31. Sample []*Sample
  32. Mapping []*Mapping
  33. Location []*Location
  34. Function []*Function
  35. Comments []string
  36. DropFrames string
  37. KeepFrames string
  38. TimeNanos int64
  39. DurationNanos int64
  40. PeriodType *ValueType
  41. Period int64
  42. commentX []int64
  43. dropFramesX int64
  44. keepFramesX int64
  45. stringTable []string
  46. defaultSampleTypeX int64
  47. }
  48. // ValueType corresponds to Profile.ValueType
  49. type ValueType struct {
  50. Type string // cpu, wall, inuse_space, etc
  51. Unit string // seconds, nanoseconds, bytes, etc
  52. typeX int64
  53. unitX int64
  54. }
  55. // Sample corresponds to Profile.Sample
  56. type Sample struct {
  57. Location []*Location
  58. Value []int64
  59. Label map[string][]string
  60. NumLabel map[string][]int64
  61. locationIDX []uint64
  62. labelX []label
  63. }
  64. // label corresponds to Profile.Label
  65. type label struct {
  66. keyX int64
  67. // Exactly one of the two following values must be set
  68. strX int64
  69. numX int64 // Integer value for this label
  70. }
  71. // Mapping corresponds to Profile.Mapping
  72. type Mapping struct {
  73. ID uint64
  74. Start uint64
  75. Limit uint64
  76. Offset uint64
  77. File string
  78. BuildID string
  79. HasFunctions bool
  80. HasFilenames bool
  81. HasLineNumbers bool
  82. HasInlineFrames bool
  83. fileX int64
  84. buildIDX int64
  85. }
  86. // Location corresponds to Profile.Location
  87. type Location struct {
  88. ID uint64
  89. Mapping *Mapping
  90. Address uint64
  91. Line []Line
  92. mappingIDX uint64
  93. }
  94. // Line corresponds to Profile.Line
  95. type Line struct {
  96. Function *Function
  97. Line int64
  98. functionIDX uint64
  99. }
  100. // Function corresponds to Profile.Function
  101. type Function struct {
  102. ID uint64
  103. Name string
  104. SystemName string
  105. Filename string
  106. StartLine int64
  107. nameX int64
  108. systemNameX int64
  109. filenameX int64
  110. }
  111. // Parse parses a profile and checks for its validity. The input
  112. // may be a gzip-compressed encoded protobuf or one of many legacy
  113. // profile formats which may be unsupported in the future.
  114. func Parse(r io.Reader) (*Profile, error) {
  115. data, err := ioutil.ReadAll(r)
  116. if err != nil {
  117. return nil, err
  118. }
  119. return ParseData(data)
  120. }
  121. // ParseData parses a profile from a buffer and checks for its
  122. // validity.
  123. func ParseData(data []byte) (*Profile, error) {
  124. var p *Profile
  125. var err error
  126. if len(data) >= 2 && data[0] == 0x1f && data[1] == 0x8b {
  127. gz, err := gzip.NewReader(bytes.NewBuffer(data))
  128. if err == nil {
  129. data, err = ioutil.ReadAll(gz)
  130. }
  131. if err != nil {
  132. return nil, fmt.Errorf("decompressing profile: %v", err)
  133. }
  134. }
  135. if p, err = ParseUncompressed(data); err != nil {
  136. if p, err = parseLegacy(data); err != nil {
  137. return nil, fmt.Errorf("parsing profile: %v", err)
  138. }
  139. }
  140. if err := p.CheckValid(); err != nil {
  141. return nil, fmt.Errorf("malformed profile: %v", err)
  142. }
  143. return p, nil
  144. }
  145. var errUnrecognized = fmt.Errorf("unrecognized profile format")
  146. var errMalformed = fmt.Errorf("malformed profile format")
  147. func parseLegacy(data []byte) (*Profile, error) {
  148. parsers := []func([]byte) (*Profile, error){
  149. parseCPU,
  150. parseHeap,
  151. parseGoCount, // goroutine, threadcreate
  152. parseThread,
  153. parseContention,
  154. parseJavaProfile,
  155. }
  156. for _, parser := range parsers {
  157. p, err := parser(data)
  158. if err == nil {
  159. p.addLegacyFrameInfo()
  160. return p, nil
  161. }
  162. if err != errUnrecognized {
  163. return nil, err
  164. }
  165. }
  166. return nil, errUnrecognized
  167. }
  168. // ParseUncompressed parses an uncompressed protobuf into a profile.
  169. func ParseUncompressed(data []byte) (*Profile, error) {
  170. p := &Profile{}
  171. if err := unmarshal(data, p); err != nil {
  172. return nil, err
  173. }
  174. if err := p.postDecode(); err != nil {
  175. return nil, err
  176. }
  177. return p, nil
  178. }
  179. var libRx = regexp.MustCompile(`([.]so$|[.]so[._][0-9]+)`)
  180. // massageMappings applies heuristic-based changes to the profile
  181. // mappings to account for quirks of some environments.
  182. func (p *Profile) massageMappings() {
  183. // Merge adjacent regions with matching names, checking that the offsets match
  184. if len(p.Mapping) > 1 {
  185. mappings := []*Mapping{p.Mapping[0]}
  186. for _, m := range p.Mapping[1:] {
  187. lm := mappings[len(mappings)-1]
  188. if offset := lm.Offset + (lm.Limit - lm.Start); lm.Limit == m.Start &&
  189. offset == m.Offset &&
  190. (lm.File == m.File || lm.File == "") {
  191. lm.File = m.File
  192. lm.Limit = m.Limit
  193. if lm.BuildID == "" {
  194. lm.BuildID = m.BuildID
  195. }
  196. p.updateLocationMapping(m, lm)
  197. continue
  198. }
  199. mappings = append(mappings, m)
  200. }
  201. p.Mapping = mappings
  202. }
  203. // Use heuristics to identify main binary and move it to the top of the list of mappings
  204. for i, m := range p.Mapping {
  205. file := strings.TrimSpace(strings.Replace(m.File, "(deleted)", "", -1))
  206. if len(file) == 0 {
  207. continue
  208. }
  209. if len(libRx.FindStringSubmatch(file)) > 0 {
  210. continue
  211. }
  212. if strings.HasPrefix(file, "[") {
  213. continue
  214. }
  215. // Swap what we guess is main to position 0.
  216. p.Mapping[0], p.Mapping[i] = p.Mapping[i], p.Mapping[0]
  217. break
  218. }
  219. // Keep the mapping IDs neatly sorted
  220. for i, m := range p.Mapping {
  221. m.ID = uint64(i + 1)
  222. }
  223. }
  224. func (p *Profile) updateLocationMapping(from, to *Mapping) {
  225. for _, l := range p.Location {
  226. if l.Mapping == from {
  227. l.Mapping = to
  228. }
  229. }
  230. }
  231. // Write writes the profile as a gzip-compressed marshaled protobuf.
  232. func (p *Profile) Write(w io.Writer) error {
  233. p.preEncode()
  234. b := marshal(p)
  235. zw := gzip.NewWriter(w)
  236. defer zw.Close()
  237. _, err := zw.Write(b)
  238. return err
  239. }
  240. // WriteUncompressed writes the profile as a marshaled protobuf.
  241. func (p *Profile) WriteUncompressed(w io.Writer) error {
  242. p.preEncode()
  243. b := marshal(p)
  244. _, err := w.Write(b)
  245. return err
  246. }
  247. // CheckValid tests whether the profile is valid. Checks include, but are
  248. // not limited to:
  249. // - len(Profile.Sample[n].value) == len(Profile.value_unit)
  250. // - Sample.id has a corresponding Profile.Location
  251. func (p *Profile) CheckValid() error {
  252. // Check that sample values are consistent
  253. sampleLen := len(p.SampleType)
  254. if sampleLen == 0 && len(p.Sample) != 0 {
  255. return fmt.Errorf("missing sample type information")
  256. }
  257. for _, s := range p.Sample {
  258. if len(s.Value) != sampleLen {
  259. return fmt.Errorf("mismatch: sample has: %d values vs. %d types", len(s.Value), len(p.SampleType))
  260. }
  261. }
  262. // Check that all mappings/locations/functions are in the tables
  263. // Check that there are no duplicate ids
  264. mappings := make(map[uint64]*Mapping, len(p.Mapping))
  265. for _, m := range p.Mapping {
  266. if m.ID == 0 {
  267. return fmt.Errorf("found mapping with reserved ID=0")
  268. }
  269. if mappings[m.ID] != nil {
  270. return fmt.Errorf("multiple mappings with same id: %d", m.ID)
  271. }
  272. mappings[m.ID] = m
  273. }
  274. functions := make(map[uint64]*Function, len(p.Function))
  275. for _, f := range p.Function {
  276. if f.ID == 0 {
  277. return fmt.Errorf("found function with reserved ID=0")
  278. }
  279. if functions[f.ID] != nil {
  280. return fmt.Errorf("multiple functions with same id: %d", f.ID)
  281. }
  282. functions[f.ID] = f
  283. }
  284. locations := make(map[uint64]*Location, len(p.Location))
  285. for _, l := range p.Location {
  286. if l.ID == 0 {
  287. return fmt.Errorf("found location with reserved id=0")
  288. }
  289. if locations[l.ID] != nil {
  290. return fmt.Errorf("multiple locations with same id: %d", l.ID)
  291. }
  292. locations[l.ID] = l
  293. if m := l.Mapping; m != nil {
  294. if m.ID == 0 || mappings[m.ID] != m {
  295. return fmt.Errorf("inconsistent mapping %p: %d", m, m.ID)
  296. }
  297. }
  298. for _, ln := range l.Line {
  299. if f := ln.Function; f != nil {
  300. if f.ID == 0 || functions[f.ID] != f {
  301. return fmt.Errorf("inconsistent function %p: %d", f, f.ID)
  302. }
  303. }
  304. }
  305. }
  306. return nil
  307. }
  308. // Aggregate merges the locations in the profile into equivalence
  309. // classes preserving the request attributes. It also updates the
  310. // samples to point to the merged locations.
  311. func (p *Profile) Aggregate(inlineFrame, function, filename, linenumber, address bool) error {
  312. for _, m := range p.Mapping {
  313. m.HasInlineFrames = m.HasInlineFrames && inlineFrame
  314. m.HasFunctions = m.HasFunctions && function
  315. m.HasFilenames = m.HasFilenames && filename
  316. m.HasLineNumbers = m.HasLineNumbers && linenumber
  317. }
  318. // Aggregate functions
  319. if !function || !filename {
  320. for _, f := range p.Function {
  321. if !function {
  322. f.Name = ""
  323. f.SystemName = ""
  324. }
  325. if !filename {
  326. f.Filename = ""
  327. }
  328. }
  329. }
  330. // Aggregate locations
  331. if !inlineFrame || !address || !linenumber {
  332. for _, l := range p.Location {
  333. if !inlineFrame && len(l.Line) > 1 {
  334. l.Line = l.Line[len(l.Line)-1:]
  335. }
  336. if !linenumber {
  337. for i := range l.Line {
  338. l.Line[i].Line = 0
  339. }
  340. }
  341. if !address {
  342. l.Address = 0
  343. }
  344. }
  345. }
  346. return p.CheckValid()
  347. }
  348. // Print dumps a text representation of a profile. Intended mainly
  349. // for debugging purposes.
  350. func (p *Profile) String() string {
  351. ss := make([]string, 0, len(p.Sample)+len(p.Mapping)+len(p.Location))
  352. if pt := p.PeriodType; pt != nil {
  353. ss = append(ss, fmt.Sprintf("PeriodType: %s %s", pt.Type, pt.Unit))
  354. }
  355. ss = append(ss, fmt.Sprintf("Period: %d", p.Period))
  356. if p.TimeNanos != 0 {
  357. ss = append(ss, fmt.Sprintf("Time: %v", time.Unix(0, p.TimeNanos)))
  358. }
  359. if p.DurationNanos != 0 {
  360. ss = append(ss, fmt.Sprintf("Duration: %.4v", time.Duration(p.DurationNanos)))
  361. }
  362. ss = append(ss, "Samples:")
  363. var sh1 string
  364. for _, s := range p.SampleType {
  365. dflt := ""
  366. if s.Type == p.DefaultSampleType {
  367. dflt = "[dflt]"
  368. }
  369. sh1 = sh1 + fmt.Sprintf("%s/%s%s ", s.Type, s.Unit, dflt)
  370. }
  371. ss = append(ss, strings.TrimSpace(sh1))
  372. for _, s := range p.Sample {
  373. var sv string
  374. for _, v := range s.Value {
  375. sv = fmt.Sprintf("%s %10d", sv, v)
  376. }
  377. sv = sv + ": "
  378. for _, l := range s.Location {
  379. sv = sv + fmt.Sprintf("%d ", l.ID)
  380. }
  381. ss = append(ss, sv)
  382. const labelHeader = " "
  383. if len(s.Label) > 0 {
  384. ls := labelHeader
  385. for k, v := range s.Label {
  386. ls = ls + fmt.Sprintf("%s:%v ", k, v)
  387. }
  388. ss = append(ss, ls)
  389. }
  390. if len(s.NumLabel) > 0 {
  391. ls := labelHeader
  392. for k, v := range s.NumLabel {
  393. ls = ls + fmt.Sprintf("%s:%v ", k, v)
  394. }
  395. ss = append(ss, ls)
  396. }
  397. }
  398. ss = append(ss, "Locations")
  399. for _, l := range p.Location {
  400. locStr := fmt.Sprintf("%6d: %#x ", l.ID, l.Address)
  401. if m := l.Mapping; m != nil {
  402. locStr = locStr + fmt.Sprintf("M=%d ", m.ID)
  403. }
  404. if len(l.Line) == 0 {
  405. ss = append(ss, locStr)
  406. }
  407. for li := range l.Line {
  408. lnStr := "??"
  409. if fn := l.Line[li].Function; fn != nil {
  410. lnStr = fmt.Sprintf("%s %s:%d s=%d",
  411. fn.Name,
  412. fn.Filename,
  413. l.Line[li].Line,
  414. fn.StartLine)
  415. if fn.Name != fn.SystemName {
  416. lnStr = lnStr + "(" + fn.SystemName + ")"
  417. }
  418. }
  419. ss = append(ss, locStr+lnStr)
  420. // Do not print location details past the first line
  421. locStr = " "
  422. }
  423. }
  424. ss = append(ss, "Mappings")
  425. for _, m := range p.Mapping {
  426. bits := ""
  427. if m.HasFunctions {
  428. bits = bits + "[FN]"
  429. }
  430. if m.HasFilenames {
  431. bits = bits + "[FL]"
  432. }
  433. if m.HasLineNumbers {
  434. bits = bits + "[LN]"
  435. }
  436. if m.HasInlineFrames {
  437. bits = bits + "[IN]"
  438. }
  439. ss = append(ss, fmt.Sprintf("%d: %#x/%#x/%#x %s %s %s",
  440. m.ID,
  441. m.Start, m.Limit, m.Offset,
  442. m.File,
  443. m.BuildID,
  444. bits))
  445. }
  446. return strings.Join(ss, "\n") + "\n"
  447. }
  448. // Scale multiplies all sample values in a profile by a constant.
  449. func (p *Profile) Scale(ratio float64) {
  450. if ratio == 1 {
  451. return
  452. }
  453. ratios := make([]float64, len(p.SampleType))
  454. for i := range p.SampleType {
  455. ratios[i] = ratio
  456. }
  457. p.ScaleN(ratios)
  458. }
  459. // ScaleN multiplies each sample values in a sample by a different amount.
  460. func (p *Profile) ScaleN(ratios []float64) error {
  461. if len(p.SampleType) != len(ratios) {
  462. return fmt.Errorf("mismatched scale ratios, got %d, want %d", len(ratios), len(p.SampleType))
  463. }
  464. allOnes := true
  465. for _, r := range ratios {
  466. if r != 1 {
  467. allOnes = false
  468. break
  469. }
  470. }
  471. if allOnes {
  472. return nil
  473. }
  474. for _, s := range p.Sample {
  475. for i, v := range s.Value {
  476. if ratios[i] != 1 {
  477. s.Value[i] = int64(float64(v) * ratios[i])
  478. }
  479. }
  480. }
  481. return nil
  482. }
  483. // HasFunctions determines if all locations in this profile have
  484. // symbolized function information.
  485. func (p *Profile) HasFunctions() bool {
  486. for _, l := range p.Location {
  487. if l.Mapping != nil && !l.Mapping.HasFunctions {
  488. return false
  489. }
  490. }
  491. return true
  492. }
  493. // HasFileLines determines if all locations in this profile have
  494. // symbolized file and line number information.
  495. func (p *Profile) HasFileLines() bool {
  496. for _, l := range p.Location {
  497. if l.Mapping != nil && (!l.Mapping.HasFilenames || !l.Mapping.HasLineNumbers) {
  498. return false
  499. }
  500. }
  501. return true
  502. }
  503. // Copy makes a fully independent copy of a profile.
  504. func (p *Profile) Copy() *Profile {
  505. p.preEncode()
  506. b := marshal(p)
  507. pp := &Profile{}
  508. if err := unmarshal(b, pp); err != nil {
  509. panic(err)
  510. }
  511. if err := pp.postDecode(); err != nil {
  512. panic(err)
  513. }
  514. return pp
  515. }