// Copyright 2023 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package pprof provides minimalistic routines for extracting // information from profiles. package pprof import ( "fmt" "time" ) // TotalTime parses the profile data and returns the accumulated time. // The input should not be gzipped. func TotalTime(data []byte) (total time.Duration, err error) { defer func() { if x := recover(); x != nil { err = fmt.Errorf("error parsing pprof profile: %v", x) } }() decode(&total, data, msgProfile) return } // All errors are handled by panicking. // Constants are copied below to avoid dependency on protobufs or pprof. // protobuf wire types, from https://developers.google.com/protocol-buffers/docs/encoding const ( wireVarint = 0 wireBytes = 2 ) // pprof field numbers, from https://github.com/google/pprof/blob/master/proto/profile.proto const ( fldProfileSample = 2 // repeated Sample fldSampleValue = 2 // repeated int64 ) // arbitrary numbering of message types const ( msgProfile = 0 msgSample = 1 ) func decode(total *time.Duration, data []byte, msg int) { for len(data) > 0 { // Read tag (wire type and field number). tag := varint(&data) // Read wire value (int or bytes). wire := tag & 7 var ival uint64 var sval []byte switch wire { case wireVarint: ival = varint(&data) case wireBytes: n := varint(&data) sval, data = data[:n], data[n:] default: panic(fmt.Sprintf("unexpected wire type: %d", wire)) } // Process field of msg. fld := tag >> 3 switch { case msg == msgProfile && fld == fldProfileSample: decode(total, sval, msgSample) // recursively decode Sample message case msg == msgSample, fld == fldSampleValue: *total += time.Duration(ival) // accumulate time } } } func varint(data *[]byte) (v uint64) { for i := 0; ; i++ { b := uint64((*data)[i]) v += (b & 0x7f) << (7 * i) if b < 0x80 { *data = (*data)[i+1:] return v } } }