Brak opisu

profile.go 14KB

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