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