golang.org/toolchain@v0.0.1-go1.9rc2.windows-amd64/src/cmd/vendor/github.com/google/pprof/profile/legacy_java_profile.go (about)

     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: value},
   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  				s.NumLabel = map[string][]int64{"bytes": []int64{s.Value[1] / s.Value[0]}}
   216  				s.Value[0], s.Value[1] = scaleHeapSample(s.Value[0], s.Value[1], javaHeapzSamplingRate)
   217  			case "contention":
   218  				if period := p.Period; period != 0 {
   219  					s.Value[0] = s.Value[0] * p.Period
   220  					s.Value[1] = s.Value[1] * p.Period
   221  				}
   222  			}
   223  			p.Sample = append(p.Sample, s)
   224  		}
   225  		// Grab next line.
   226  		b = b[nextNewLine+1:]
   227  		nextNewLine = bytes.IndexByte(b, byte('\n'))
   228  	}
   229  	return b, locs, nil
   230  }
   231  
   232  // parseJavaLocations parses the location information in a java
   233  // profile and populates the Locations in a profile. It uses the
   234  // location addresses from the profile as both the ID of each
   235  // location.
   236  func parseJavaLocations(b []byte, locs map[uint64]*Location, p *Profile) error {
   237  	r := bytes.NewBuffer(b)
   238  	fns := make(map[string]*Function)
   239  	for {
   240  		line, err := r.ReadString('\n')
   241  		if err != nil {
   242  			if err != io.EOF {
   243  				return err
   244  			}
   245  			if line == "" {
   246  				break
   247  			}
   248  		}
   249  
   250  		if line = strings.TrimSpace(line); line == "" {
   251  			continue
   252  		}
   253  
   254  		jloc := javaLocationRx.FindStringSubmatch(line)
   255  		if len(jloc) != 3 {
   256  			continue
   257  		}
   258  		addr, err := strconv.ParseUint(jloc[1], 16, 64)
   259  		if err != nil {
   260  			return fmt.Errorf("parsing sample %s: %v", line, err)
   261  		}
   262  		loc := locs[addr]
   263  		if loc == nil {
   264  			// Unused/unseen
   265  			continue
   266  		}
   267  		var lineFunc, lineFile string
   268  		var lineNo int64
   269  
   270  		if fileLine := javaLocationFileLineRx.FindStringSubmatch(jloc[2]); len(fileLine) == 4 {
   271  			// Found a line of the form: "function (file:line)"
   272  			lineFunc, lineFile = fileLine[1], fileLine[2]
   273  			if n, err := strconv.ParseInt(fileLine[3], 10, 64); err == nil && n > 0 {
   274  				lineNo = n
   275  			}
   276  		} else if filePath := javaLocationPathRx.FindStringSubmatch(jloc[2]); len(filePath) == 3 {
   277  			// If there's not a file:line, it's a shared library path.
   278  			// The path isn't interesting, so just give the .so.
   279  			lineFunc, lineFile = filePath[1], filepath.Base(filePath[2])
   280  		} else if strings.Contains(jloc[2], "generated stub/JIT") {
   281  			lineFunc = "STUB"
   282  		} else {
   283  			// Treat whole line as the function name. This is used by the
   284  			// java agent for internal states such as "GC" or "VM".
   285  			lineFunc = jloc[2]
   286  		}
   287  		fn := fns[lineFunc]
   288  
   289  		if fn == nil {
   290  			fn = &Function{
   291  				Name:       lineFunc,
   292  				SystemName: lineFunc,
   293  				Filename:   lineFile,
   294  			}
   295  			fns[lineFunc] = fn
   296  			p.Function = append(p.Function, fn)
   297  		}
   298  		loc.Line = []Line{
   299  			{
   300  				Function: fn,
   301  				Line:     lineNo,
   302  			},
   303  		}
   304  		loc.Address = 0
   305  	}
   306  
   307  	p.remapLocationIDs()
   308  	p.remapFunctionIDs()
   309  	p.remapMappingIDs()
   310  
   311  	return nil
   312  }