Copyright 2014 Google Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
This file implements parsers to convert java legacy profiles into the profile.proto format.

package profile

import (
	
	
	
	
	
	
	
)

var (
	attributeRx            = regexp.MustCompile(`([\w ]+)=([\w ]+)`)
	javaSampleRx           = regexp.MustCompile(` *(\d+) +(\d+) +@ +([ x0-9a-f]*)`)
	javaLocationRx         = regexp.MustCompile(`^\s*0x([[:xdigit:]]+)\s+(.*)\s*$`)
	javaLocationFileLineRx = regexp.MustCompile(`^(.*)\s+\((.+):(-?[[:digit:]]+)\)$`)
	javaLocationPathRx     = regexp.MustCompile(`^(.*)\s+\((.*)\)$`)
)
javaCPUProfile returns a new Profile from profilez data. b is the profile bytes after the header, period is the profiling period, and parse is a function to parse 8-byte chunks from the profile in its native endianness.
func ( []byte,  int64,  func( []byte) (uint64, []byte)) (*Profile, error) {
	 := &Profile{
		Period:      * 1000,
		PeriodType: &ValueType{Type: "cpu", Unit: "nanoseconds"},
		SampleType: []*ValueType{{Type: "samples", Unit: "count"}, {Type: "cpu", Unit: "nanoseconds"}},
	}
	var  error
	var  map[uint64]*Location
	if , ,  = parseCPUSamples(, , false, );  != nil {
		return nil, 
	}

	if  = parseJavaLocations(, , );  != nil {
		return nil, 
	}
Strip out addresses for better merge.
	if  = .Aggregate(true, true, true, true, false);  != nil {
		return nil, 
	}

	return , nil
}
parseJavaProfile returns a new profile from heapz or contentionz data. b is the profile bytes after the header.
func ( []byte) (*Profile, error) {
	 := bytes.SplitAfterN(, []byte("\n"), 2)
	if len() < 2 {
		return nil, errUnrecognized
	}

	 := &Profile{
		PeriodType: &ValueType{},
	}
	 := string(bytes.TrimSpace([0]))

	var  error
	var  string
	switch  {
	case "--- heapz 1 ---":
		 = "heap"
	case "--- contentionz 1 ---":
		 = "contention"
	default:
		return nil, errUnrecognized
	}

	if ,  = parseJavaHeader(, [1], );  != nil {
		return nil, 
	}
	var  map[uint64]*Location
	if , ,  = parseJavaSamples(, , );  != nil {
		return nil, 
	}
	if  = parseJavaLocations(, , );  != nil {
		return nil, 
	}
Strip out addresses for better merge.
	if  = .Aggregate(true, true, true, true, false);  != nil {
		return nil, 
	}

	return , nil
}
parseJavaHeader parses the attribute section on a java profile and populates a profile. Returns the remainder of the buffer after all attributes.
func ( string,  []byte,  *Profile) ([]byte, error) {
	 := bytes.IndexByte(, byte('\n'))
	for  != -1 {
		 := string(bytes.TrimSpace([0:]))
		if  != "" {
			 := attributeRx.FindStringSubmatch()
Not a valid attribute, exit.
				return , nil
			}

			,  := strings.TrimSpace([1]), strings.TrimSpace([2])
			var  error
			switch  + "/" +  {
			case "heap/format", "cpu/format", "contention/format":
				if  != "java" {
					return nil, errUnrecognized
				}
			case "heap/resolution":
				.SampleType = []*ValueType{
					{Type: "inuse_objects", Unit: "count"},
					{Type: "inuse_space", Unit: },
				}
			case "contention/resolution":
				.SampleType = []*ValueType{
					{Type: "contentions", Unit: "count"},
					{Type: "delay", Unit: },
				}
			case "contention/sampling period":
				.PeriodType = &ValueType{
					Type: "contentions", Unit: "count",
				}
				if .Period,  = strconv.ParseInt(, 0, 64);  != nil {
					return nil, fmt.Errorf("failed to parse attribute %s: %v", , )
				}
			case "contention/ms since reset":
				,  := strconv.ParseInt(, 0, 64)
				if  != nil {
					return nil, fmt.Errorf("failed to parse attribute %s: %v", , )
				}
				.DurationNanos =  * 1000 * 1000
			default:
				return nil, errUnrecognized
			}
Grab next line.
		 = [+1:]
		 = bytes.IndexByte(, byte('\n'))
	}
	return , nil
}
parseJavaSamples parses the samples from a java profile and populates the Samples in a profile. Returns the remainder of the buffer after the samples.
func ( string,  []byte,  *Profile) ([]byte, map[uint64]*Location, error) {
	 := bytes.IndexByte(, byte('\n'))
	 := make(map[uint64]*Location)
	for  != -1 {
		 := string(bytes.TrimSpace([0:]))
		if  != "" {
			 := javaSampleRx.FindStringSubmatch()
Not a valid sample, exit.
				return , , nil
			}
Java profiles have data/fields inverted compared to other profile types.
			var  error
			, ,  := [2], [1], [3]
			,  := parseHexAddresses()
			if  != nil {
				return nil, nil, fmt.Errorf("malformed sample: %s: %v", , )
			}

			var  []*Location
			for ,  := range  {
				 := []
				if [] == nil {
					 = &Location{
						Address: ,
					}
					.Location = append(.Location, )
					[] = 
				}
				 = append(, )
			}
			 := &Sample{
				Value:    make([]int64, 2),
				Location: ,
			}

			if .Value[0],  = strconv.ParseInt(, 0, 64);  != nil {
				return nil, nil, fmt.Errorf("parsing sample %s: %v", , )
			}
			if .Value[1],  = strconv.ParseInt(, 0, 64);  != nil {
				return nil, nil, fmt.Errorf("parsing sample %s: %v", , )
			}

			switch  {
			case "heap":
				const  = 524288 // 512K
				if .Value[0] == 0 {
					return nil, nil, fmt.Errorf("parsing sample %s: second value must be non-zero", )
				}
				.NumLabel = map[string][]int64{"bytes": {.Value[1] / .Value[0]}}
				.Value[0], .Value[1] = scaleHeapSample(.Value[0], .Value[1], )
			case "contention":
				if  := .Period;  != 0 {
					.Value[0] = .Value[0] * .Period
					.Value[1] = .Value[1] * .Period
				}
			}
			.Sample = append(.Sample, )
Grab next line.
		 = [+1:]
		 = bytes.IndexByte(, byte('\n'))
	}
	return , , nil
}
parseJavaLocations parses the location information in a java profile and populates the Locations in a profile. It uses the location addresses from the profile as both the ID of each location.
func ( []byte,  map[uint64]*Location,  *Profile) error {
	 := bytes.NewBuffer()
	 := make(map[string]*Function)
	for {
		,  := .ReadString('\n')
		if  != nil {
			if  != io.EOF {
				return 
			}
			if  == "" {
				break
			}
		}

		if  = strings.TrimSpace();  == "" {
			continue
		}

		 := javaLocationRx.FindStringSubmatch()
		if len() != 3 {
			continue
		}
		,  := strconv.ParseUint([1], 16, 64)
		if  != nil {
			return fmt.Errorf("parsing sample %s: %v", , )
		}
		 := []
Unused/unseen
			continue
		}
		var ,  string
		var  int64

Found a line of the form: "function (file:line)"
			,  = [1], [2]
			if ,  := strconv.ParseInt([3], 10, 64);  == nil &&  > 0 {
				 = 
			}
If there's not a file:line, it's a shared library path. The path isn't interesting, so just give the .so.
			,  = [1], filepath.Base([2])
		} else if strings.Contains([2], "generated stub/JIT") {
			 = "STUB"
Treat whole line as the function name. This is used by the java agent for internal states such as "GC" or "VM".
			 = [2]
		}
		 := []

		if  == nil {
			 = &Function{
				Name:       ,
				SystemName: ,
				Filename:   ,
			}
			[] = 
			.Function = append(.Function, )
		}
		.Line = []Line{
			{
				Function: ,
				Line:     ,
			},
		}
		.Address = 0
	}

	.remapLocationIDs()
	.remapFunctionIDs()
	.remapMappingIDs()

	return nil