github.com/instana/go-sensor@v1.62.2-0.20240520081010-4919868049e1/autoprofile/internal/allocation_sampler.go (about)

     1  // (c) Copyright IBM Corp. 2021
     2  // (c) Copyright Instana Inc. 2020
     3  
     4  package internal
     5  
     6  import (
     7  	"bytes"
     8  	"errors"
     9  	"runtime/pprof"
    10  
    11  	"github.com/instana/go-sensor/autoprofile/internal/pprof/profile"
    12  )
    13  
    14  // AllocationSampler collects information about the number of memory allocations
    15  type AllocationSampler struct{}
    16  
    17  // NewAllocationSampler initializes a new allocation sampler
    18  func NewAllocationSampler() *AllocationSampler {
    19  	return &AllocationSampler{}
    20  }
    21  
    22  // Reset is a no-op for allocation sampler
    23  func (as *AllocationSampler) Reset() {}
    24  
    25  // Start is a no-op for allocation sampler
    26  func (as *AllocationSampler) Start() error { return nil }
    27  
    28  // Stop is a no-op for allocation sampler
    29  func (as *AllocationSampler) Stop() error { return nil }
    30  
    31  // Profile retrieves the head profile and converts it to the profile.Profile
    32  func (as *AllocationSampler) Profile(duration int64, timespan int64) (*Profile, error) {
    33  	hp, err := as.readHeapProfile()
    34  	if err != nil {
    35  		return nil, err
    36  	}
    37  
    38  	if hp == nil {
    39  		return nil, errors.New("no profile returned")
    40  	}
    41  
    42  	top, err := as.createAllocationCallGraph(hp)
    43  	if err != nil {
    44  		return nil, err
    45  	}
    46  
    47  	roots := make([]*CallSite, 0)
    48  	for _, child := range top.children {
    49  		roots = append(roots, child)
    50  	}
    51  
    52  	return NewProfile(CategoryMemory, TypeMemoryAllocation, UnitByte, roots, duration, timespan), nil
    53  }
    54  
    55  func (as *AllocationSampler) createAllocationCallGraph(p *profile.Profile) (*CallSite, error) {
    56  	// find "inuse_space" type index
    57  	inuseSpaceTypeIndex := -1
    58  	for i, s := range p.SampleType {
    59  		if s.Type == "inuse_space" {
    60  			inuseSpaceTypeIndex = i
    61  			break
    62  		}
    63  	}
    64  
    65  	// find "inuse_objects" type index
    66  	inuseObjectsTypeIndex := -1
    67  	for i, s := range p.SampleType {
    68  		if s.Type == "inuse_objects" {
    69  			inuseObjectsTypeIndex = i
    70  			break
    71  		}
    72  	}
    73  
    74  	if inuseSpaceTypeIndex == -1 || inuseObjectsTypeIndex == -1 {
    75  		return nil, errors.New("unrecognized profile data")
    76  	}
    77  
    78  	// build call graph
    79  	top := NewCallSite("", "", 0)
    80  
    81  	for _, s := range p.Sample {
    82  		if shouldSkipStack(s) {
    83  			continue
    84  		}
    85  
    86  		value := s.Value[inuseSpaceTypeIndex]
    87  		if value == 0 {
    88  			continue
    89  		}
    90  
    91  		count := s.Value[inuseObjectsTypeIndex]
    92  		current := top
    93  		for i := len(s.Location) - 1; i >= 0; i-- {
    94  			l := s.Location[i]
    95  			funcName, fileName, fileLine := readFuncInfo(l)
    96  
    97  			current = current.FindOrAddChild(funcName, fileName, fileLine)
    98  		}
    99  
   100  		current.Increment(float64(value), int64(count))
   101  	}
   102  
   103  	return top, nil
   104  }
   105  
   106  func (as *AllocationSampler) readHeapProfile() (*profile.Profile, error) {
   107  	buf := bytes.NewBuffer(nil)
   108  	if err := pprof.WriteHeapProfile(buf); err != nil {
   109  		return nil, err
   110  	}
   111  
   112  	p, err := profile.Parse(buf)
   113  	if err != nil {
   114  		return nil, err
   115  	}
   116  
   117  	if err := symbolizeProfile(p); err != nil {
   118  		return nil, err
   119  	}
   120  
   121  	if err := p.CheckValid(); err != nil {
   122  		return nil, err
   123  	}
   124  
   125  	return p, nil
   126  }