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

     1  // (c) Copyright IBM Corp. 2021
     2  // (c) Copyright Instana Inc. 2020
     3  
     4  package instana
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"os"
    10  	"os/user"
    11  	"strconv"
    12  	"sync"
    13  	"time"
    14  
    15  	"github.com/instana/go-sensor/acceptor"
    16  	"github.com/instana/go-sensor/process"
    17  )
    18  
    19  // ErrAgentNotReady is an error returned for an attempt to communicate with an agent before the client
    20  // announcement process is done
    21  var ErrAgentNotReady = errors.New("agent not ready")
    22  
    23  type containerSnapshot struct {
    24  	ID    string
    25  	Type  string
    26  	Image string
    27  }
    28  
    29  type serverlessSnapshot struct {
    30  	EntityID  string
    31  	Host      string
    32  	PID       int
    33  	Container containerSnapshot
    34  	StartedAt time.Time
    35  	Zone      string
    36  	Tags      map[string]interface{}
    37  }
    38  
    39  func newProcessPluginPayload(snapshot serverlessSnapshot, prevStats, currentStats processStats) acceptor.PluginPayload {
    40  	var currUser, currGroup string
    41  	if u, err := user.Current(); err == nil {
    42  		currUser = u.Username
    43  
    44  		if g, err := user.LookupGroupId(u.Gid); err == nil {
    45  			currGroup = g.Name
    46  		}
    47  	}
    48  
    49  	env := getProcessEnv()
    50  	for k := range env {
    51  		if k == "INSTANA_AGENT_KEY" {
    52  			continue
    53  		}
    54  
    55  		if sensor.options.Tracer.Secrets.Match(k) {
    56  			env[k] = "<redacted>"
    57  		}
    58  	}
    59  
    60  	return acceptor.NewProcessPluginPayload(strconv.Itoa(snapshot.PID), acceptor.ProcessData{
    61  		PID:           snapshot.PID,
    62  		Exec:          os.Args[0],
    63  		Args:          os.Args[1:],
    64  		Env:           env,
    65  		User:          currUser,
    66  		Group:         currGroup,
    67  		ContainerID:   snapshot.Container.ID,
    68  		ContainerType: snapshot.Container.Type,
    69  		Start:         snapshot.StartedAt.UnixNano() / int64(time.Millisecond),
    70  		HostName:      snapshot.Host,
    71  		HostPID:       snapshot.PID,
    72  		CPU:           acceptor.NewProcessCPUStatsDelta(prevStats.CPU, currentStats.CPU, currentStats.Tick-prevStats.Tick),
    73  		Memory:        acceptor.NewProcessMemoryStatsUpdate(prevStats.Memory, currentStats.Memory),
    74  		OpenFiles:     acceptor.NewProcessOpenFilesStatsUpdate(prevStats.Limits, currentStats.Limits),
    75  	})
    76  }
    77  
    78  type processStats struct {
    79  	Tick   int
    80  	CPU    process.CPUStats
    81  	Memory process.MemStats
    82  	Limits process.ResourceLimits
    83  }
    84  
    85  type processStatsCollector struct {
    86  	logger LeveledLogger
    87  	mu     sync.RWMutex
    88  	stats  processStats
    89  }
    90  
    91  func (c *processStatsCollector) Run(ctx context.Context, collectionInterval time.Duration) {
    92  	timer := time.NewTicker(collectionInterval)
    93  	defer timer.Stop()
    94  
    95  	for {
    96  		select {
    97  		case <-timer.C:
    98  			fetchCtx, cancel := context.WithTimeout(ctx, collectionInterval)
    99  			c.fetchStats(fetchCtx)
   100  			cancel()
   101  		case <-ctx.Done():
   102  			return
   103  		}
   104  	}
   105  }
   106  
   107  func (c *processStatsCollector) Collect() processStats {
   108  	c.mu.RLock()
   109  	defer c.mu.RUnlock()
   110  
   111  	return c.stats
   112  }
   113  
   114  func (c *processStatsCollector) fetchStats(ctx context.Context) {
   115  	stats := c.Collect()
   116  
   117  	var wg sync.WaitGroup
   118  	wg.Add(3)
   119  
   120  	done := make(chan struct{})
   121  	go func() {
   122  		wg.Wait()
   123  		close(done)
   124  	}()
   125  
   126  	go func() {
   127  		defer wg.Done()
   128  
   129  		st, tick, err := process.Stats().CPU()
   130  		if err != nil {
   131  			c.logger.Debug("failed to read process CPU stats, skipping: ", err)
   132  			return
   133  		}
   134  
   135  		stats.CPU, stats.Tick = st, tick
   136  	}()
   137  
   138  	go func() {
   139  		defer wg.Done()
   140  
   141  		st, err := process.Stats().Memory()
   142  		if err != nil {
   143  			c.logger.Debug("failed to read process memory stats, skipping: ", err)
   144  			return
   145  		}
   146  
   147  		stats.Memory = st
   148  	}()
   149  
   150  	go func() {
   151  		defer wg.Done()
   152  
   153  		st, err := process.Stats().Limits()
   154  		if err != nil {
   155  			c.logger.Debug("failed to read process open files stats, skipping: ", err)
   156  			return
   157  		}
   158  
   159  		stats.Limits = st
   160  	}()
   161  
   162  	select {
   163  	case <-done:
   164  		break
   165  	case <-ctx.Done():
   166  		c.logger.Debug("failed to obtain process stats (timed out)")
   167  		return // context has been cancelled, skip this update
   168  	}
   169  
   170  	c.mu.Lock()
   171  	c.stats = stats
   172  	defer c.mu.Unlock()
   173  }