github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/grpc/profiling/service/service.go (about) 1 /* 2 * 3 * Copyright 2019 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19 // Package service defines methods to register a gRPC client/service for a 20 // profiling service that is exposed in the same server. This service can be 21 // queried by a client to remotely manage the gRPC profiling behaviour of an 22 // application. 23 // 24 // Experimental 25 // 26 // Notice: This package is EXPERIMENTAL and may be changed or removed in a 27 // later release. 28 package service 29 30 import ( 31 "context" 32 "errors" 33 "sync" 34 35 grpc "github.com/hxx258456/ccgo/grpc" 36 "github.com/hxx258456/ccgo/grpc/grpclog" 37 "github.com/hxx258456/ccgo/grpc/internal/profiling" 38 ppb "github.com/hxx258456/ccgo/grpc/profiling/proto" 39 ) 40 41 var logger = grpclog.Component("profiling") 42 43 // ProfilingConfig defines configuration options for the Init method. 44 type ProfilingConfig struct { 45 // Setting this to true will enable profiling. 46 Enabled bool 47 48 // Profiling uses a circular buffer (ring buffer) to store statistics for 49 // only the last few RPCs so that profiling stats do not grow unbounded. This 50 // parameter defines the upper limit on the number of RPCs for which 51 // statistics should be stored at any given time. An average RPC requires 52 // approximately 2-3 KiB of memory for profiling-related statistics, so 53 // choose an appropriate number based on the amount of memory you can afford. 54 StreamStatsSize uint32 55 56 // To expose the profiling service and its methods, a *grpc.Server must be 57 // provided. 58 Server *grpc.Server 59 } 60 61 var errorNilServer = errors.New("profiling: no grpc.Server provided") 62 63 // Init takes a *ProfilingConfig to initialize profiling (turned on/off 64 // depending on the value set in pc.Enabled) and register the profiling service 65 // in the server provided in pc.Server. 66 func Init(pc *ProfilingConfig) error { 67 if pc.Server == nil { 68 return errorNilServer 69 } 70 71 if err := profiling.InitStats(pc.StreamStatsSize); err != nil { 72 return err 73 } 74 75 ppb.RegisterProfilingServer(pc.Server, getProfilingServerInstance()) 76 77 // Do this last after everything has been initialized and allocated. 78 profiling.Enable(pc.Enabled) 79 80 return nil 81 } 82 83 type profilingServer struct { 84 ppb.UnimplementedProfilingServer 85 drainMutex sync.Mutex 86 } 87 88 var profilingServerInstance *profilingServer 89 var profilingServerOnce sync.Once 90 91 // getProfilingServerInstance creates and returns a singleton instance of 92 // profilingServer. Only one instance of profilingServer is created to use a 93 // shared mutex across all profilingServer instances. 94 func getProfilingServerInstance() *profilingServer { 95 profilingServerOnce.Do(func() { 96 profilingServerInstance = &profilingServer{} 97 }) 98 99 return profilingServerInstance 100 } 101 102 func (s *profilingServer) Enable(ctx context.Context, req *ppb.EnableRequest) (*ppb.EnableResponse, error) { 103 if req.Enabled { 104 logger.Infof("profilingServer: Enable: enabling profiling") 105 } else { 106 logger.Infof("profilingServer: Enable: disabling profiling") 107 } 108 profiling.Enable(req.Enabled) 109 110 return &ppb.EnableResponse{}, nil 111 } 112 113 func timerToProtoTimer(timer *profiling.Timer) *ppb.Timer { 114 return &ppb.Timer{ 115 Tags: timer.Tags, 116 BeginSec: timer.Begin.Unix(), 117 BeginNsec: int32(timer.Begin.Nanosecond()), 118 EndSec: timer.End.Unix(), 119 EndNsec: int32(timer.End.Nanosecond()), 120 GoId: timer.GoID, 121 } 122 } 123 124 func statToProtoStat(stat *profiling.Stat) *ppb.Stat { 125 protoStat := &ppb.Stat{ 126 Tags: stat.Tags, 127 Timers: make([]*ppb.Timer, 0, len(stat.Timers)), 128 Metadata: stat.Metadata, 129 } 130 for _, t := range stat.Timers { 131 protoStat.Timers = append(protoStat.Timers, timerToProtoTimer(t)) 132 } 133 return protoStat 134 } 135 136 func (s *profilingServer) GetStreamStats(ctx context.Context, req *ppb.GetStreamStatsRequest) (*ppb.GetStreamStatsResponse, error) { 137 // Since the drain operation is destructive, only one client request should 138 // be served at a time. 139 logger.Infof("profilingServer: GetStreamStats: processing request") 140 s.drainMutex.Lock() 141 results := profiling.StreamStats.Drain() 142 s.drainMutex.Unlock() 143 144 logger.Infof("profilingServer: GetStreamStats: returning %v records", len(results)) 145 streamStats := make([]*ppb.Stat, 0) 146 for _, stat := range results { 147 streamStats = append(streamStats, statToProtoStat(stat.(*profiling.Stat))) 148 } 149 return &ppb.GetStreamStatsResponse{StreamStats: streamStats}, nil 150 }