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

     1  // (c) Copyright IBM Corp. 2021
     2  // (c) Copyright Instana Inc. 2020
     3  
     4  package internal
     5  
     6  import (
     7  	"bytes"
     8  	"strconv"
     9  	"sync"
    10  	"time"
    11  )
    12  
    13  // Supported profile runtimes
    14  const (
    15  	RuntimeGolang = "golang"
    16  )
    17  
    18  // Supported profile categories
    19  const (
    20  	CategoryCPU    = "cpu"
    21  	CategoryMemory = "memory"
    22  	CategoryTime   = "time"
    23  )
    24  
    25  // Supported profile types
    26  const (
    27  	TypeCPUUsage         = "cpu-usage"
    28  	TypeMemoryAllocation = "memory-allocations"
    29  	TypeBlockingCalls    = "blocking-calls"
    30  )
    31  
    32  // Human-readable measurement units
    33  const (
    34  	UnitSample      = "sample"
    35  	UnitMillisecond = "millisecond"
    36  	UnitMicrosecond = "microsecond"
    37  	UnitNanosecond  = "nanosecond"
    38  	UnitByte        = "byte"
    39  	UnitKilobyte    = "kilobyte"
    40  	UnitPercent     = "percent"
    41  )
    42  
    43  // AgentProfile is a presenter type used to serialize a collected profile
    44  // to JSON format supported by Instana profile sensor
    45  type AgentProfile struct {
    46  	ID        string          `json:"id"`
    47  	Runtime   string          `json:"runtime"`
    48  	Category  string          `json:"category"`
    49  	Type      string          `json:"type"`
    50  	Unit      string          `json:"unit"`
    51  	Roots     []AgentCallSite `json:"roots"`
    52  	Duration  int64           `json:"duration"`
    53  	Timespan  int64           `json:"timespan"`
    54  	Timestamp int64           `json:"timestamp"`
    55  }
    56  
    57  // NewAgentProfile creates a new profile payload for the host agent
    58  func NewAgentProfile(p *Profile) AgentProfile {
    59  	callSites := make([]AgentCallSite, 0, len(p.Roots))
    60  	for _, root := range p.Roots {
    61  		callSites = append(callSites, NewAgentCallSite(root))
    62  	}
    63  
    64  	return AgentProfile{
    65  		ID:        p.ID,
    66  		Runtime:   p.Runtime,
    67  		Category:  p.Category,
    68  		Type:      p.Type,
    69  		Unit:      p.Unit,
    70  		Roots:     callSites,
    71  		Duration:  p.Duration,
    72  		Timespan:  p.Timespan,
    73  		Timestamp: p.Timestamp,
    74  	}
    75  }
    76  
    77  // Profile holds the gathered profiling data
    78  type Profile struct {
    79  	ID        string
    80  	Runtime   string
    81  	Category  string
    82  	Type      string
    83  	Unit      string
    84  	Roots     []*CallSite
    85  	Duration  int64
    86  	Timespan  int64
    87  	Timestamp int64
    88  }
    89  
    90  // NewProfile inititalizes a new profile
    91  func NewProfile(category string, typ string, unit string, roots []*CallSite, duration int64, timespan int64) *Profile {
    92  	return &Profile{
    93  		ID:        GenerateUUID(),
    94  		Runtime:   RuntimeGolang,
    95  		Category:  category,
    96  		Type:      typ,
    97  		Unit:      unit,
    98  		Roots:     roots,
    99  		Duration:  duration / int64(time.Millisecond),
   100  		Timespan:  timespan * 1000,
   101  		Timestamp: time.Now().Unix() * 1000,
   102  	}
   103  }
   104  
   105  // AgentCallSite is a presenter type used to serialize a call site
   106  // to JSON format supported by Instana profile sensor
   107  type AgentCallSite struct {
   108  	MethodName  string          `json:"method_name"`
   109  	FileName    string          `json:"file_name"`
   110  	FileLine    int64           `json:"file_line"`
   111  	Measurement float64         `json:"measurement"`
   112  	NumSamples  int64           `json:"num_samples"`
   113  	Children    []AgentCallSite `json:"children"`
   114  }
   115  
   116  // NewAgentCallSite initializes a new call site payload for the host agent
   117  func NewAgentCallSite(cs *CallSite) AgentCallSite {
   118  	children := make([]AgentCallSite, 0, len(cs.children))
   119  	for _, child := range cs.children {
   120  		children = append(children, NewAgentCallSite(child))
   121  	}
   122  
   123  	m, ns := cs.Measurement()
   124  
   125  	return AgentCallSite{
   126  		MethodName:  cs.MethodName,
   127  		FileName:    cs.FileName,
   128  		FileLine:    cs.FileLine,
   129  		Measurement: m,
   130  		NumSamples:  ns,
   131  		Children:    children,
   132  	}
   133  }
   134  
   135  // CallSite represents a recorded method call
   136  type CallSite struct {
   137  	MethodName  string
   138  	FileName    string
   139  	FileLine    int64
   140  	Metadata    map[string]string
   141  	measurement float64
   142  	numSamples  int64
   143  	children    map[string]*CallSite
   144  	updateLock  *sync.RWMutex
   145  }
   146  
   147  // NewCallSite initializes a new CallSite
   148  func NewCallSite(methodName string, fileName string, fileLine int64) *CallSite {
   149  	cn := &CallSite{
   150  		MethodName: methodName,
   151  		FileName:   fileName,
   152  		FileLine:   fileLine,
   153  		children:   make(map[string]*CallSite),
   154  		updateLock: &sync.RWMutex{},
   155  	}
   156  
   157  	return cn
   158  }
   159  
   160  // FindOrAddChild adds a new subcall to a call tree. It returns the existing record the subcall already present
   161  func (cs *CallSite) FindOrAddChild(methodName, fileName string, fileLine int64) *CallSite {
   162  	child := cs.findChild(methodName, fileName, fileLine)
   163  	if child == nil {
   164  		child = NewCallSite(methodName, fileName, fileLine)
   165  		cs.addChild(child)
   166  	}
   167  
   168  	return child
   169  }
   170  
   171  // Increment increases the sampled measurement while adding up the number of samples used
   172  func (cs *CallSite) Increment(value float64, numSamples int64) {
   173  	cs.measurement += value
   174  	cs.numSamples += numSamples
   175  }
   176  
   177  // Measurement returns the sampled measurement along with the number of samples
   178  func (cs *CallSite) Measurement() (value float64, numSamples int64) {
   179  	return cs.measurement, cs.numSamples
   180  }
   181  
   182  func (cs *CallSite) findChild(methodName, fileName string, fileLine int64) *CallSite {
   183  	cs.updateLock.RLock()
   184  	defer cs.updateLock.RUnlock()
   185  
   186  	if child, exists := cs.children[createKey(methodName, fileName, fileLine)]; exists {
   187  		return child
   188  	}
   189  
   190  	return nil
   191  }
   192  
   193  func (cs *CallSite) addChild(child *CallSite) {
   194  	cs.updateLock.Lock()
   195  	defer cs.updateLock.Unlock()
   196  
   197  	cs.children[createKey(child.MethodName, child.FileName, child.FileLine)] = child
   198  }
   199  
   200  func createKey(methodName, fileName string, fileLine int64) string {
   201  	var b bytes.Buffer
   202  
   203  	b.WriteString(methodName)
   204  	b.WriteString(" (")
   205  	b.WriteString(fileName)
   206  	b.WriteString(":")
   207  	b.WriteString(strconv.FormatInt(fileLine, 10))
   208  	b.WriteString(")")
   209  
   210  	return b.String()
   211  }