/
goref.go
232 lines (196 loc) · 5.04 KB
/
goref.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
package goref
import (
"sync"
"time"
)
// TODO tracking execution time might cause performance issues (e.g. in virtualized environments gettimeofday() might be slow)
// if that turns out to be the case, deactivate Data.TotalNsec
// data -- internal GoRef data structure
type data struct {
// currently active invocations
active int32
// number of finished invocations
count int64
// time spent in those invocations (in nanoseconds)
nsec int64
}
// event types (for internal communication):
const (
// stop the goroutine handling this GoRef instance
evStop = iota
// resets this GoRef instance
evReset = iota
// Takes a snapshot and sends it to snapshotChannel
evSnapshot = iota
// increments a ref counter
evRef = iota
// decrements a ref counter (and updates the total count + time)
evDeref = iota
)
type event struct {
typ int
key string
nsec int64
}
// GoRef -- A simple, go-style key-based reference counter that can be used for profiling your application (main class)
type GoRef struct {
name string
parent *GoRef
data map[string]*data
_children map[string]*GoRef
childLock sync.Mutex
evChannel chan event
snapshotChannel chan Snapshot
}
func (g *GoRef) do(evType int, key string, nsec int64) {
g.evChannel <- event{
typ: evType,
key: key,
nsec: nsec,
}
}
// get -- Get the Data object for the specified key (or create it) - thread safe
func (g *GoRef) get(key string) *data {
rc, ok := g.data[key]
if !ok {
rc = &data{}
g.data[key] = rc
}
return rc
}
// Ref -- References an instance of 'key'
func (g *GoRef) Ref(key string) *Instance {
g.do(evRef, key, 0)
return &Instance{
parent: g,
key: key,
startTime: time.Now(),
}
}
func (g *GoRef) run() {
for msg := range g.evChannel {
//log.Print("~~goref: ", msg)
switch msg.typ {
case evRef:
g.get(msg.key).active++
break
case evDeref:
d := g.get(msg.key)
d.active--
d.count++
d.nsec += msg.nsec
break
case evSnapshot:
g.takeSnapshot()
break
case evReset:
g.data = map[string]*data{}
break
case evStop:
return // TODO stop this GoRef instance safely
default:
panic("unsupported GoRef event type")
}
}
}
// GetChild -- Gets (or creates) a specific child instance (recursively)
func (g *GoRef) GetChild(path ...string) *GoRef {
if len(path) == 0 {
return g
}
firstSegment := path[0]
var child *GoRef
{ // keep the lock as short as possible
g.childLock.Lock()
defer g.childLock.Unlock()
var ok bool
child, ok = g._children[firstSegment]
if !ok {
// create a new child transparently
child = newGoRef(firstSegment, g)
g._children[firstSegment] = child
}
}
return child.GetChild(path[1:]...)
}
// GetChildren -- Creates a point-in-time copy of this GoRef instance's children
func (g *GoRef) GetChildren() map[string]*GoRef {
g.childLock.Lock()
defer g.childLock.Unlock()
// simply copy all entries
var rc = make(map[string]*GoRef, len(g._children))
for name, child := range g._children {
rc[name] = child
}
return rc
}
// GetParent -- Get the parent of this GoRef instance (will return nil for root instances)
func (g *GoRef) GetParent() *GoRef {
// g.parent is immutable -> no locking necessary
return g.parent
}
// GetPath -- Get this GoRef instance's path (i.e. its parents' and its own name)
//
// Root instances have empty names, all the others have the name you give them
// when creating them with GetChild().
//
// To get a single string path, you can use strings.Join()
//
// ```go
// strings.Join(g.GetPath(), "/")
// ```
func (g *GoRef) GetPath() []string {
var rc []string
// this method needs no thread safety mechanisms.
// A GoRef's name and parent are immutable.
if g.parent != nil {
rc = append(g.parent.GetPath(), g.name)
}
return rc
}
// GetSnapshot -- Creates and returns a deep copy of the current state (including child instance states)
func (g *GoRef) GetSnapshot() Snapshot {
g.do(evSnapshot, "", 0)
// get child snapshots while we wait
children := g.GetChildren()
childData := make(map[string]Snapshot, len(children))
for name, child := range children {
childData[name] = child.GetSnapshot()
}
rc := <-g.snapshotChannel
rc.Children = childData
return rc
}
// takeSnapshot -- internal (-> thread-unsafe) method taking a deep copy of the current state and sending it to snapshotChannel
func (g *GoRef) takeSnapshot() {
// copy entries
data := make(map[string]Data, len(g.data))
for key, d := range g.data {
data[key] = newData(d)
}
// send Snapshot
g.snapshotChannel <- Snapshot{
Data: data,
Ts: time.Now(),
}
}
// Reset -- Resets this GoRef instance to its initial state
func (g *GoRef) Reset() {
g.do(evReset, "", 0)
}
// NewGoRef -- Construct a new root-level GoRef instance
func NewGoRef() *GoRef {
return newGoRef("", nil)
}
func newGoRef(name string, parent *GoRef) *GoRef {
rc := &GoRef{
name: name,
parent: parent,
data: map[string]*data{},
_children: map[string]*GoRef{},
evChannel: make(chan event, 100),
snapshotChannel: make(chan Snapshot, 5),
}
go rc.run()
return rc
}