// 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 javaCPUProfile( []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, 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 parseJavaProfile( []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, 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 parseJavaHeader( string,  []byte,  *Profile) ([]byte, error) {
	 := bytes.IndexByte(, byte('\n'))
	for  != -1 {
		 := string(bytes.TrimSpace([0:]))
		if  != "" {
			 := attributeRx.FindStringSubmatch()
			if  == nil {
				// 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 parseJavaSamples( 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()
			if  == nil {
				// 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 parseJavaLocations( []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", , )
		}
		 := []
		if  == nil {
			// Unused/unseen
			continue
		}
		var ,  string
		var  int64

		if  := javaLocationFileLineRx.FindStringSubmatch([2]); len() == 4 {
			// Found a line of the form: "function (file:line)"
			,  = [1], [2]
			if ,  := strconv.ParseInt([3], 10, 64);  == nil &&  > 0 {
				 = 
			}
		} else if  := javaLocationPathRx.FindStringSubmatch([2]); len() == 3 {
			// 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"
		} else {
			// 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
}