| Abhay Kumar | a2ae599 | 2025-11-10 14:02:24 +0000 | [diff] [blame^] | 1 | /* |
| 2 | * |
| 3 | * Copyright 2018 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 channelz |
| 20 | |
| 21 | import ( |
| 22 | "fmt" |
| 23 | "sync" |
| 24 | "sync/atomic" |
| 25 | "time" |
| 26 | |
| 27 | "google.golang.org/grpc/grpclog" |
| 28 | ) |
| 29 | |
| 30 | const ( |
| 31 | defaultMaxTraceEntry int32 = 30 |
| 32 | ) |
| 33 | |
| 34 | var maxTraceEntry = defaultMaxTraceEntry |
| 35 | |
| 36 | // SetMaxTraceEntry sets maximum number of trace entries per entity (i.e. |
| 37 | // channel/subchannel). Setting it to 0 will disable channel tracing. |
| 38 | func SetMaxTraceEntry(i int32) { |
| 39 | atomic.StoreInt32(&maxTraceEntry, i) |
| 40 | } |
| 41 | |
| 42 | // ResetMaxTraceEntryToDefault resets the maximum number of trace entries per |
| 43 | // entity to default. |
| 44 | func ResetMaxTraceEntryToDefault() { |
| 45 | atomic.StoreInt32(&maxTraceEntry, defaultMaxTraceEntry) |
| 46 | } |
| 47 | |
| 48 | func getMaxTraceEntry() int { |
| 49 | i := atomic.LoadInt32(&maxTraceEntry) |
| 50 | return int(i) |
| 51 | } |
| 52 | |
| 53 | // traceEvent is an internal representation of a single trace event |
| 54 | type traceEvent struct { |
| 55 | // Desc is a simple description of the trace event. |
| 56 | Desc string |
| 57 | // Severity states the severity of this trace event. |
| 58 | Severity Severity |
| 59 | // Timestamp is the event time. |
| 60 | Timestamp time.Time |
| 61 | // RefID is the id of the entity that gets referenced in the event. RefID is 0 if no other entity is |
| 62 | // involved in this event. |
| 63 | // e.g. SubChannel (id: 4[]) Created. --> RefID = 4, RefName = "" (inside []) |
| 64 | RefID int64 |
| 65 | // RefName is the reference name for the entity that gets referenced in the event. |
| 66 | RefName string |
| 67 | // RefType indicates the referenced entity type, i.e Channel or SubChannel. |
| 68 | RefType RefChannelType |
| 69 | } |
| 70 | |
| 71 | // TraceEvent is what the caller of AddTraceEvent should provide to describe the |
| 72 | // event to be added to the channel trace. |
| 73 | // |
| 74 | // The Parent field is optional. It is used for an event that will be recorded |
| 75 | // in the entity's parent trace. |
| 76 | type TraceEvent struct { |
| 77 | Desc string |
| 78 | Severity Severity |
| 79 | Parent *TraceEvent |
| 80 | } |
| 81 | |
| 82 | // ChannelTrace provides tracing information for a channel. |
| 83 | // It tracks various events and metadata related to the channel's lifecycle |
| 84 | // and operations. |
| 85 | type ChannelTrace struct { |
| 86 | cm *channelMap |
| 87 | clearCalled bool |
| 88 | // The time when the trace was created. |
| 89 | CreationTime time.Time |
| 90 | // A counter for the number of events recorded in the |
| 91 | // trace. |
| 92 | EventNum int64 |
| 93 | mu sync.Mutex |
| 94 | // A slice of traceEvent pointers representing the events recorded for |
| 95 | // this channel. |
| 96 | Events []*traceEvent |
| 97 | } |
| 98 | |
| 99 | func (c *ChannelTrace) copy() *ChannelTrace { |
| 100 | return &ChannelTrace{ |
| 101 | CreationTime: c.CreationTime, |
| 102 | EventNum: c.EventNum, |
| 103 | Events: append(([]*traceEvent)(nil), c.Events...), |
| 104 | } |
| 105 | } |
| 106 | |
| 107 | func (c *ChannelTrace) append(e *traceEvent) { |
| 108 | c.mu.Lock() |
| 109 | if len(c.Events) == getMaxTraceEntry() { |
| 110 | del := c.Events[0] |
| 111 | c.Events = c.Events[1:] |
| 112 | if del.RefID != 0 { |
| 113 | // start recursive cleanup in a goroutine to not block the call originated from grpc. |
| 114 | go func() { |
| 115 | // need to acquire c.cm.mu lock to call the unlocked attemptCleanup func. |
| 116 | c.cm.mu.Lock() |
| 117 | c.cm.decrTraceRefCount(del.RefID) |
| 118 | c.cm.mu.Unlock() |
| 119 | }() |
| 120 | } |
| 121 | } |
| 122 | e.Timestamp = time.Now() |
| 123 | c.Events = append(c.Events, e) |
| 124 | c.EventNum++ |
| 125 | c.mu.Unlock() |
| 126 | } |
| 127 | |
| 128 | func (c *ChannelTrace) clear() { |
| 129 | if c.clearCalled { |
| 130 | return |
| 131 | } |
| 132 | c.clearCalled = true |
| 133 | c.mu.Lock() |
| 134 | for _, e := range c.Events { |
| 135 | if e.RefID != 0 { |
| 136 | // caller should have already held the c.cm.mu lock. |
| 137 | c.cm.decrTraceRefCount(e.RefID) |
| 138 | } |
| 139 | } |
| 140 | c.mu.Unlock() |
| 141 | } |
| 142 | |
| 143 | // Severity is the severity level of a trace event. |
| 144 | // The canonical enumeration of all valid values is here: |
| 145 | // https://github.com/grpc/grpc-proto/blob/9b13d199cc0d4703c7ea26c9c330ba695866eb23/grpc/channelz/v1/channelz.proto#L126. |
| 146 | type Severity int |
| 147 | |
| 148 | const ( |
| 149 | // CtUnknown indicates unknown severity of a trace event. |
| 150 | CtUnknown Severity = iota |
| 151 | // CtInfo indicates info level severity of a trace event. |
| 152 | CtInfo |
| 153 | // CtWarning indicates warning level severity of a trace event. |
| 154 | CtWarning |
| 155 | // CtError indicates error level severity of a trace event. |
| 156 | CtError |
| 157 | ) |
| 158 | |
| 159 | // RefChannelType is the type of the entity being referenced in a trace event. |
| 160 | type RefChannelType int |
| 161 | |
| 162 | const ( |
| 163 | // RefUnknown indicates an unknown entity type, the zero value for this type. |
| 164 | RefUnknown RefChannelType = iota |
| 165 | // RefChannel indicates the referenced entity is a Channel. |
| 166 | RefChannel |
| 167 | // RefSubChannel indicates the referenced entity is a SubChannel. |
| 168 | RefSubChannel |
| 169 | // RefServer indicates the referenced entity is a Server. |
| 170 | RefServer |
| 171 | // RefListenSocket indicates the referenced entity is a ListenSocket. |
| 172 | RefListenSocket |
| 173 | // RefNormalSocket indicates the referenced entity is a NormalSocket. |
| 174 | RefNormalSocket |
| 175 | ) |
| 176 | |
| 177 | var refChannelTypeToString = map[RefChannelType]string{ |
| 178 | RefUnknown: "Unknown", |
| 179 | RefChannel: "Channel", |
| 180 | RefSubChannel: "SubChannel", |
| 181 | RefServer: "Server", |
| 182 | RefListenSocket: "ListenSocket", |
| 183 | RefNormalSocket: "NormalSocket", |
| 184 | } |
| 185 | |
| 186 | // String returns a string representation of the RefChannelType |
| 187 | func (r RefChannelType) String() string { |
| 188 | return refChannelTypeToString[r] |
| 189 | } |
| 190 | |
| 191 | // AddTraceEvent adds trace related to the entity with specified id, using the |
| 192 | // provided TraceEventDesc. |
| 193 | // |
| 194 | // If channelz is not turned ON, this will simply log the event descriptions. |
| 195 | func AddTraceEvent(l grpclog.DepthLoggerV2, e Entity, depth int, desc *TraceEvent) { |
| 196 | // Log only the trace description associated with the bottom most entity. |
| 197 | d := fmt.Sprintf("[%s] %s", e, desc.Desc) |
| 198 | switch desc.Severity { |
| 199 | case CtUnknown, CtInfo: |
| 200 | l.InfoDepth(depth+1, d) |
| 201 | case CtWarning: |
| 202 | l.WarningDepth(depth+1, d) |
| 203 | case CtError: |
| 204 | l.ErrorDepth(depth+1, d) |
| 205 | } |
| 206 | |
| 207 | if getMaxTraceEntry() == 0 { |
| 208 | return |
| 209 | } |
| 210 | if IsOn() { |
| 211 | db.traceEvent(e.id(), desc) |
| 212 | } |
| 213 | } |