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 }