...

Source file src/pkg/cmd/vendor/github.com/google/pprof/profile/legacy_java_profile.go

     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	
    15	// This file implements parsers to convert java legacy profiles into
    16	// the profile.proto format.
    17	
    18	package profile
    19	
    20	import (
    21		"bytes"
    22		"fmt"
    23		"io"
    24		"path/filepath"
    25		"regexp"
    26		"strconv"
    27		"strings"
    28	)
    29	
    30	var (
    31		attributeRx            = regexp.MustCompile(`([\w ]+)=([\w ]+)`)
    32		javaSampleRx           = regexp.MustCompile(` *(\d+) +(\d+) +@ +([ x0-9a-f]*)`)
    33		javaLocationRx         = regexp.MustCompile(`^\s*0x([[:xdigit:]]+)\s+(.*)\s*$`)
    34		javaLocationFileLineRx = regexp.MustCompile(`^(.*)\s+\((.+):(-?[[:digit:]]+)\)$`)
    35		javaLocationPathRx     = regexp.MustCompile(`^(.*)\s+\((.*)\)$`)
    36	)
    37	
    38	// javaCPUProfile returns a new Profile from profilez data.
    39	// b is the profile bytes after the header, period is the profiling
    40	// period, and parse is a function to parse 8-byte chunks from the
    41	// profile in its native endianness.
    42	func javaCPUProfile(b []byte, period int64, parse func(b []byte) (uint64, []byte)) (*Profile, error) {
    43		p := &Profile{
    44			Period:     period * 1000,
    45			PeriodType: &ValueType{Type: "cpu", Unit: "nanoseconds"},
    46			SampleType: []*ValueType{{Type: "samples", Unit: "count"}, {Type: "cpu", Unit: "nanoseconds"}},
    47		}
    48		var err error
    49		var locs map[uint64]*Location
    50		if b, locs, err = parseCPUSamples(b, parse, false, p); err != nil {
    51			return nil, err
    52		}
    53	
    54		if err = parseJavaLocations(b, locs, p); err != nil {
    55			return nil, err
    56		}
    57	
    58		// Strip out addresses for better merge.
    59		if err = p.Aggregate(true, true, true, true, false); err != nil {
    60			return nil, err
    61		}
    62	
    63		return p, nil
    64	}
    65	
    66	// parseJavaProfile returns a new profile from heapz or contentionz
    67	// data. b is the profile bytes after the header.
    68	func parseJavaProfile(b []byte) (*Profile, error) {
    69		h := bytes.SplitAfterN(b, []byte("\n"), 2)
    70		if len(h) < 2 {
    71			return nil, errUnrecognized
    72		}
    73	
    74		p := &Profile{
    75			PeriodType: &ValueType{},
    76		}
    77		header := string(bytes.TrimSpace(h[0]))
    78	
    79		var err error
    80		var pType string
    81		switch header {
    82		case "--- heapz 1 ---":
    83			pType = "heap"
    84		case "--- contentionz 1 ---":
    85			pType = "contention"
    86		default:
    87			return nil, errUnrecognized
    88		}
    89	
    90		if b, err = parseJavaHeader(pType, h[1], p); err != nil {
    91			return nil, err
    92		}
    93		var locs map[uint64]*Location
    94		if b, locs, err = parseJavaSamples(pType, b, p); err != nil {
    95			return nil, err
    96		}
    97		if err = parseJavaLocations(b, locs, p); err != nil {
    98			return nil, err
    99		}
   100	
   101		// Strip out addresses for better merge.
   102		if err = p.Aggregate(true, true, true, true, false); err != nil {
   103			return nil, err
   104		}
   105	
   106		return p, nil
   107	}
   108	
   109	// parseJavaHeader parses the attribute section on a java profile and
   110	// populates a profile. Returns the remainder of the buffer after all
   111	// attributes.
   112	func parseJavaHeader(pType string, b []byte, p *Profile) ([]byte, error) {
   113		nextNewLine := bytes.IndexByte(b, byte('\n'))
   114		for nextNewLine != -1 {
   115			line := string(bytes.TrimSpace(b[0:nextNewLine]))
   116			if line != "" {
   117				h := attributeRx.FindStringSubmatch(line)
   118				if h == nil {
   119					// Not a valid attribute, exit.
   120					return b, nil
   121				}
   122	
   123				attribute, value := strings.TrimSpace(h[1]), strings.TrimSpace(h[2])
   124				var err error
   125				switch pType + "/" + attribute {
   126				case "heap/format", "cpu/format", "contention/format":
   127					if value != "java" {
   128						return nil, errUnrecognized
   129					}
   130				case "heap/resolution":
   131					p.SampleType = []*ValueType{
   132						{Type: "inuse_objects", Unit: "count"},
   133						{Type: "inuse_space", Unit: value},
   134					}
   135				case "contention/resolution":
   136					p.SampleType = []*ValueType{
   137						{Type: "contentions", Unit: "count"},
   138						{Type: "delay", Unit: value},
   139					}
   140				case "contention/sampling period":
   141					p.PeriodType = &ValueType{
   142						Type: "contentions", Unit: "count",
   143					}
   144					if p.Period, err = strconv.ParseInt(value, 0, 64); err != nil {
   145						return nil, fmt.Errorf("failed to parse attribute %s: %v", line, err)
   146					}
   147				case "contention/ms since reset":
   148					millis, err := strconv.ParseInt(value, 0, 64)
   149					if err != nil {
   150						return nil, fmt.Errorf("failed to parse attribute %s: %v", line, err)
   151					}
   152					p.DurationNanos = millis * 1000 * 1000
   153				default:
   154					return nil, errUnrecognized
   155				}
   156			}
   157			// Grab next line.
   158			b = b[nextNewLine+1:]
   159			nextNewLine = bytes.IndexByte(b, byte('\n'))
   160		}
   161		return b, nil
   162	}
   163	
   164	// parseJavaSamples parses the samples from a java profile and
   165	// populates the Samples in a profile. Returns the remainder of the
   166	// buffer after the samples.
   167	func parseJavaSamples(pType string, b []byte, p *Profile) ([]byte, map[uint64]*Location, error) {
   168		nextNewLine := bytes.IndexByte(b, byte('\n'))
   169		locs := make(map[uint64]*Location)
   170		for nextNewLine != -1 {
   171			line := string(bytes.TrimSpace(b[0:nextNewLine]))
   172			if line != "" {
   173				sample := javaSampleRx.FindStringSubmatch(line)
   174				if sample == nil {
   175					// Not a valid sample, exit.
   176					return b, locs, nil
   177				}
   178	
   179				// Java profiles have data/fields inverted compared to other
   180				// profile types.
   181				var err error
   182				value1, value2, value3 := sample[2], sample[1], sample[3]
   183				addrs, err := parseHexAddresses(value3)
   184				if err != nil {
   185					return nil, nil, fmt.Errorf("malformed sample: %s: %v", line, err)
   186				}
   187	
   188				var sloc []*Location
   189				for _, addr := range addrs {
   190					loc := locs[addr]
   191					if locs[addr] == nil {
   192						loc = &Location{
   193							Address: addr,
   194						}
   195						p.Location = append(p.Location, loc)
   196						locs[addr] = loc
   197					}
   198					sloc = append(sloc, loc)
   199				}
   200				s := &Sample{
   201					Value:    make([]int64, 2),
   202					Location: sloc,
   203				}
   204	
   205				if s.Value[0], err = strconv.ParseInt(value1, 0, 64); err != nil {
   206					return nil, nil, fmt.Errorf("parsing sample %s: %v", line, err)
   207				}
   208				if s.Value[1], err = strconv.ParseInt(value2, 0, 64); err != nil {
   209					return nil, nil, fmt.Errorf("parsing sample %s: %v", line, err)
   210				}
   211	
   212				switch pType {
   213				case "heap":
   214					const javaHeapzSamplingRate = 524288 // 512K
   215					if s.Value[0] == 0 {
   216						return nil, nil, fmt.Errorf("parsing sample %s: second value must be non-zero", line)
   217					}
   218					s.NumLabel = map[string][]int64{"bytes": {s.Value[1] / s.Value[0]}}
   219					s.Value[0], s.Value[1] = scaleHeapSample(s.Value[0], s.Value[1], javaHeapzSamplingRate)
   220				case "contention":
   221					if period := p.Period; period != 0 {
   222						s.Value[0] = s.Value[0] * p.Period
   223						s.Value[1] = s.Value[1] * p.Period
   224					}
   225				}
   226				p.Sample = append(p.Sample, s)
   227			}
   228			// Grab next line.
   229			b = b[nextNewLine+1:]
   230			nextNewLine = bytes.IndexByte(b, byte('\n'))
   231		}
   232		return b, locs, nil
   233	}
   234	
   235	// parseJavaLocations parses the location information in a java
   236	// profile and populates the Locations in a profile. It uses the
   237	// location addresses from the profile as both the ID of each
   238	// location.
   239	func parseJavaLocations(b []byte, locs map[uint64]*Location, p *Profile) error {
   240		r := bytes.NewBuffer(b)
   241		fns := make(map[string]*Function)
   242		for {
   243			line, err := r.ReadString('\n')
   244			if err != nil {
   245				if err != io.EOF {
   246					return err
   247				}
   248				if line == "" {
   249					break
   250				}
   251			}
   252	
   253			if line = strings.TrimSpace(line); line == "" {
   254				continue
   255			}
   256	
   257			jloc := javaLocationRx.FindStringSubmatch(line)
   258			if len(jloc) != 3 {
   259				continue
   260			}
   261			addr, err := strconv.ParseUint(jloc[1], 16, 64)
   262			if err != nil {
   263				return fmt.Errorf("parsing sample %s: %v", line, err)
   264			}
   265			loc := locs[addr]
   266			if loc == nil {
   267				// Unused/unseen
   268				continue
   269			}
   270			var lineFunc, lineFile string
   271			var lineNo int64
   272	
   273			if fileLine := javaLocationFileLineRx.FindStringSubmatch(jloc[2]); len(fileLine) == 4 {
   274				// Found a line of the form: "function (file:line)"
   275				lineFunc, lineFile = fileLine[1], fileLine[2]
   276				if n, err := strconv.ParseInt(fileLine[3], 10, 64); err == nil && n > 0 {
   277					lineNo = n
   278				}
   279			} else if filePath := javaLocationPathRx.FindStringSubmatch(jloc[2]); len(filePath) == 3 {
   280				// If there's not a file:line, it's a shared library path.
   281				// The path isn't interesting, so just give the .so.
   282				lineFunc, lineFile = filePath[1], filepath.Base(filePath[2])
   283			} else if strings.Contains(jloc[2], "generated stub/JIT") {
   284				lineFunc = "STUB"
   285			} else {
   286				// Treat whole line as the function name. This is used by the
   287				// java agent for internal states such as "GC" or "VM".
   288				lineFunc = jloc[2]
   289			}
   290			fn := fns[lineFunc]
   291	
   292			if fn == nil {
   293				fn = &Function{
   294					Name:       lineFunc,
   295					SystemName: lineFunc,
   296					Filename:   lineFile,
   297				}
   298				fns[lineFunc] = fn
   299				p.Function = append(p.Function, fn)
   300			}
   301			loc.Line = []Line{
   302				{
   303					Function: fn,
   304					Line:     lineNo,
   305				},
   306			}
   307			loc.Address = 0
   308		}
   309	
   310		p.remapLocationIDs()
   311		p.remapFunctionIDs()
   312		p.remapMappingIDs()
   313	
   314		return nil
   315	}
   316	

View as plain text