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 }