暫無描述

profile.go 13KB

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