1// Copyright (c) 2016 Couchbase, Inc. 2// Licensed under the Apache License, Version 2.0 (the "License"); 3// you may not use this file except in compliance with the 4// License. You may obtain a copy of the License at 5// http://www.apache.org/licenses/LICENSE-2.0 6// Unless required by applicable law or agreed to in writing, 7// software distributed under the License is distributed on an "AS 8// IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 9// express or implied. See the License for the specific language 10// governing permissions and limitations under the License. 11 12// Package trace provides a ring buffer utility to trace events. 13package trace 14 15import ( 16 "bytes" 17 "fmt" 18 "sync" 19) 20 21// A Msg is a trace message, which might be repeated. 22type Msg struct { 23 Title string 24 Body []byte `json:"Body,omitempty"` 25 26 // Repeats will be >1 when there was a "run" of consolidated, 27 // repeated trace messages. 28 Repeats uint64 29} 30 31// A RingBuffer provides a ring buffer to capture trace messages, 32// along with an optional consolidation func that can merge similar, 33// consecutive trace messages. 34type RingBuffer struct { 35 consolidateFunc MsgConsolidateFunc 36 37 m sync.Mutex // Protects the fields that follow. 38 39 next int // Index in msgs where next entry will be written. 40 msgs []Msg 41} 42 43// MsgConsolidateFunc is the func signature of an optional merge 44// function, allowing for similar trace messages to be consolidated. 45// For example, instead of using 216 individual slots in the ring 46// buffer, in order to save space, the consolidation func can arrange 47// for just a single entry of "216 repeated mutations" to be used 48// instead. 49// 50// The next and prev Msg parameters may be modified by the 51// consolidate func. The consolidate func should return nil if it 52// performed a consolidation and doesn't want a new entry written. 53type MsgConsolidateFunc func(next *Msg, prev *Msg) *Msg 54 55// ConsolidateByTitle implements the MsgConsolidateFunc signature 56// by consolidating trace message when their titles are the same. 57func ConsolidateByTitle(next *Msg, prev *Msg) *Msg { 58 if prev == nil || prev.Title != next.Title { 59 return next 60 } 61 62 prev.Repeats++ 63 return nil 64} 65 66// NewRingBuffer returns a RingBuffer initialized with the 67// given capacity and optional consolidateFunc. 68func NewRingBuffer( 69 capacity int, 70 consolidateFunc MsgConsolidateFunc) *RingBuffer { 71 return &RingBuffer{ 72 consolidateFunc: consolidateFunc, 73 next: 0, 74 msgs: make([]Msg, capacity), 75 } 76} 77 78// Add appens a trace message to the ring buffer, consolidating trace 79// messages based on the optional consolidation function. 80func (trb *RingBuffer) Add(title string, body []byte) { 81 if len(trb.msgs) <= 0 { 82 return 83 } 84 85 msg := &Msg{ 86 Title: title, 87 Body: body, 88 Repeats: 1, 89 } 90 91 trb.m.Lock() 92 93 if trb.consolidateFunc != nil { 94 msg = trb.consolidateFunc(msg, trb.lastUNLOCKED()) 95 if msg == nil { 96 trb.m.Unlock() 97 98 return 99 } 100 } 101 102 trb.msgs[trb.next] = *msg 103 104 trb.next++ 105 if trb.next >= len(trb.msgs) { 106 trb.next = 0 107 } 108 109 trb.m.Unlock() 110} 111 112// Cap returns the capacity of the ring buffer. 113func (trb *RingBuffer) Cap() int { 114 return len(trb.msgs) 115} 116 117// Last returns the last trace in the ring buffer. 118func (trb *RingBuffer) Last() *Msg { 119 trb.m.Lock() 120 last := trb.lastUNLOCKED() 121 trb.m.Unlock() 122 return last 123} 124 125func (trb *RingBuffer) lastUNLOCKED() *Msg { 126 if len(trb.msgs) <= 0 { 127 return nil 128 } 129 last := trb.next - 1 130 if last < 0 { 131 last = len(trb.msgs) - 1 132 } 133 return &trb.msgs[last] 134} 135 136// Msgs returns a copy of all the trace messages, as an array with the 137// oldest trace message first. 138func (trb *RingBuffer) Msgs() []Msg { 139 rv := make([]Msg, 0, len(trb.msgs)) 140 141 trb.m.Lock() 142 143 i := trb.next 144 for { 145 if trb.msgs[i].Title != "" { 146 rv = append(rv, trb.msgs[i]) 147 } 148 149 i++ 150 if i >= len(trb.msgs) { 151 i = 0 152 } 153 154 if i == trb.next { // We've returned to the beginning. 155 break 156 } 157 } 158 159 trb.m.Unlock() 160 161 return rv 162} 163 164// MsgsToString formats a []Msg into a pretty string. 165// lineSep is usually something like "\n". 166// linePrefix is usually something like " ". 167func MsgsToString(msgs []Msg, lineSep, linePrefix string) string { 168 linePrefixRest := lineSep + linePrefix 169 170 var buf bytes.Buffer 171 172 for i := range msgs { 173 msg := &msgs[i] 174 175 body := "" 176 bodySep := "" 177 if msg.Body != nil { 178 body = string(msg.Body) 179 bodySep = " " 180 } 181 182 linePrefixCur := "" 183 if i > 0 { 184 linePrefixCur = linePrefixRest 185 } 186 187 if msg.Repeats > 1 { 188 fmt.Fprintf(&buf, "%s%s (%dx)%s%s", 189 linePrefixCur, msg.Title, msg.Repeats, bodySep, body) 190 } else { 191 fmt.Fprintf(&buf, "%s%s%s%s", 192 linePrefixCur, msg.Title, bodySep, body) 193 } 194 } 195 196 return buf.String() 197} 198