github.com/benz9527/xboot@v0.0.0-20240504061247-c23f15593274/observability/stats.go (about)

     1  package observability
     2  
     3  import (
     4  	"context"
     5  	"go.opentelemetry.io/otel/attribute"
     6  	"os"
     7  	"runtime"
     8  	"strings"
     9  	"sync"
    10  	"time"
    11  
    12  	"github.com/samber/lo"
    13  	"github.com/shirou/gopsutil/v3/process"
    14  	otelruntime "go.opentelemetry.io/contrib/instrumentation/runtime"
    15  	"go.opentelemetry.io/otel"
    16  	"go.opentelemetry.io/otel/metric"
    17  )
    18  
    19  var (
    20  	once sync.Once
    21  )
    22  
    23  type appStats struct {
    24  	ctx              context.Context
    25  	shutdownCallback func(ctx context.Context) error
    26  	goroutines       metric.Int64ObservableUpDownCounter
    27  	processes        metric.Int64ObservableUpDownCounter
    28  	memories         metric.Int64ObservableUpDownCounter
    29  	cpuUsages        metric.Float64ObservableGauge
    30  	cpuTimes         metric.Float64ObservableGauge
    31  	moreRuntimeInfo  bool
    32  }
    33  
    34  func (stats *appStats) waitForShutdown() {
    35  	if stats == nil || stats.shutdownCallback == nil {
    36  		return
    37  	}
    38  	go func() {
    39  		select {
    40  		case <-stats.ctx.Done():
    41  			_ = stats.shutdownCallback(context.Background())
    42  		}
    43  	}()
    44  }
    45  
    46  func InitAppStats(ctx context.Context, name string) {
    47  	once.Do(func() {
    48  		builder := &strings.Builder{}
    49  		builder.WriteString("xboot/app")
    50  		if len(strings.TrimSpace(name)) > 0 {
    51  			builder.Write([]byte("/"))
    52  			builder.WriteString(name)
    53  		} else {
    54  			builder.Write([]byte("/"))
    55  			builder.WriteString("default")
    56  		}
    57  		name = builder.String()
    58  		stats := &appStats{
    59  			ctx: ctx,
    60  			goroutines: lo.Must[metric.Int64ObservableUpDownCounter](otel.Meter(
    61  				name,
    62  				metric.WithInstrumentationVersion(otelruntime.Version()),
    63  			).Int64ObservableUpDownCounter(
    64  				"app.runtime.goroutines",
    65  				metric.WithDescription(`The application runtime goroutines' info.`),
    66  				metric.WithInt64Callback(func(ctx context.Context, ob metric.Int64Observer) error {
    67  					gNum := runtime.NumGoroutine()
    68  					ob.Observe(int64(gNum))
    69  					return nil
    70  				}),
    71  			)),
    72  			processes: lo.Must[metric.Int64ObservableUpDownCounter](otel.Meter(
    73  				name,
    74  				metric.WithInstrumentationVersion(otelruntime.Version()),
    75  			).Int64ObservableUpDownCounter(
    76  				"app.runtime.processes",
    77  				metric.WithDescription(`The application runtime processes' info.`),
    78  				metric.WithInt64Callback(func(ctx context.Context, ob metric.Int64Observer) error {
    79  					procs := runtime.GOMAXPROCS(0)
    80  					ob.Observe(int64(procs))
    81  					return nil
    82  				}),
    83  			)),
    84  		}
    85  		_, stats.moreRuntimeInfo = os.LookupEnv("AppMoreRuntimeInfo")
    86  		if stats.moreRuntimeInfo {
    87  			pid := os.Getpid()
    88  			proc, err := process.NewProcess(int32(pid))
    89  			if err != nil {
    90  				panic(err)
    91  			}
    92  			stats.memories = lo.Must[metric.Int64ObservableUpDownCounter](otel.Meter(
    93  				name,
    94  				metric.WithInstrumentationVersion(otelruntime.Version()),
    95  			).Int64ObservableUpDownCounter(
    96  				"app.runtime.mem.allocated",
    97  				metric.WithDescription(`The application runtime allocated memory's info.`),
    98  				metric.WithUnit("KB"),
    99  				metric.WithInt64Callback(func(ctx context.Context, ob metric.Int64Observer) error {
   100  					memStats := &runtime.MemStats{}
   101  					runtime.ReadMemStats(memStats)
   102  					ob.Observe(int64(memStats.Alloc >> 10))
   103  					return nil
   104  				}),
   105  			))
   106  			stats.cpuUsages = lo.Must[metric.Float64ObservableGauge](otel.Meter(
   107  				name,
   108  				metric.WithInstrumentationVersion(otelruntime.Version()),
   109  			).Float64ObservableGauge(
   110  				"app.runtime.cpu.usage",
   111  				metric.WithDescription(`The application main process runtime CPU usage percent info.`),
   112  				metric.WithUnit("%"),
   113  				metric.WithFloat64Callback(func(ctx context.Context, ob metric.Float64Observer) error {
   114  					percent, err := proc.CPUPercent()
   115  					if err != nil {
   116  						return err
   117  					}
   118  					times, err := proc.Times()
   119  					if err != nil {
   120  						panic(err)
   121  					}
   122  					set := attribute.NewSet(attribute.Float64("milliseconds", (times.User+times.System)/float64(time.Millisecond)))
   123  					ob.Observe(percent, metric.WithAttributeSet(set))
   124  					return nil
   125  				}),
   126  			))
   127  			stats.cpuTimes = lo.Must[metric.Float64ObservableGauge](otel.Meter(
   128  				name,
   129  				metric.WithInstrumentationVersion(otelruntime.Version()),
   130  			).Float64ObservableGauge(
   131  				"app.runtime.cpu.times",
   132  				metric.WithDescription(`The application main process runtime CPU times info.`),
   133  				metric.WithUnit("ms"),
   134  				metric.WithFloat64Callback(func(ctx context.Context, ob metric.Float64Observer) error {
   135  					times, err := proc.Times()
   136  					if err != nil {
   137  						panic(err)
   138  					}
   139  					cpuTimes := (times.User + times.System) / float64(time.Millisecond)
   140  					ob.Observe(cpuTimes)
   141  					return nil
   142  				}),
   143  			))
   144  		}
   145  		_ = otelruntime.Start()
   146  		stats.waitForShutdown()
   147  	})
   148  }