Skip to content
This repository was archived by the owner on Jul 19, 2023. It is now read-only.

Commit ef392e8

Browse files
authored
Fix panic in usagestats (#706)
1 parent acc6163 commit ef392e8

File tree

1 file changed

+137
-66
lines changed

1 file changed

+137
-66
lines changed

pkg/usagestats/stats.go

Lines changed: 137 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ var (
2828
statsPrefix = "github.com/grafana/phlare/"
2929
targetKey = "target"
3030
editionKey = "edition"
31+
32+
createLock sync.RWMutex
3133
)
3234

3335
// Report is the JSON object sent to the stats server
@@ -156,40 +158,61 @@ func memstats() interface{} {
156158
// NewFloat returns a new Float stats object.
157159
// If a Float stats object with the same name already exists it is returned.
158160
func NewFloat(name string) *expvar.Float {
159-
existing := expvar.Get(statsPrefix + name)
160-
if existing != nil {
161-
if f, ok := existing.(*expvar.Float); ok {
162-
return f
163-
}
164-
panic(fmt.Sprintf("%v is set to a non-float value", name))
165-
}
166-
return expvar.NewFloat(statsPrefix + name)
161+
return createOrRetrieveExpvar(
162+
func() (*expvar.Float, error) { // check
163+
existing := expvar.Get(statsPrefix + name)
164+
if existing != nil {
165+
if f, ok := existing.(*expvar.Float); ok {
166+
return f, nil
167+
}
168+
return nil, fmt.Errorf("%v is set to a non-float value", name)
169+
}
170+
return nil, nil
171+
},
172+
func() *expvar.Float { // create
173+
return expvar.NewFloat(statsPrefix + name)
174+
},
175+
)
167176
}
168177

169178
// NewInt returns a new Int stats object.
170179
// If an Int stats object object with the same name already exists it is returned.
171180
func NewInt(name string) *expvar.Int {
172-
existing := expvar.Get(statsPrefix + name)
173-
if existing != nil {
174-
if i, ok := existing.(*expvar.Int); ok {
175-
return i
176-
}
177-
panic(fmt.Sprintf("%v is set to a non-int value", name))
178-
}
179-
return expvar.NewInt(statsPrefix + name)
181+
return createOrRetrieveExpvar(
182+
func() (*expvar.Int, error) { // check
183+
existing := expvar.Get(statsPrefix + name)
184+
if existing != nil {
185+
if i, ok := existing.(*expvar.Int); ok {
186+
return i, nil
187+
}
188+
return nil, fmt.Errorf("%v is set to a non-int value", name)
189+
}
190+
return nil, nil
191+
},
192+
func() *expvar.Int { // create
193+
return expvar.NewInt(statsPrefix + name)
194+
},
195+
)
180196
}
181197

182198
// NewString returns a new String stats object.
183199
// If a String stats object with the same name already exists it is returned.
184200
func NewString(name string) *expvar.String {
185-
existing := expvar.Get(statsPrefix + name)
186-
if existing != nil {
187-
if s, ok := existing.(*expvar.String); ok {
188-
return s
189-
}
190-
panic(fmt.Sprintf("%v is set to a non-string value", name))
191-
}
192-
return expvar.NewString(statsPrefix + name)
201+
return createOrRetrieveExpvar(
202+
func() (*expvar.String, error) { // check
203+
existing := expvar.Get(statsPrefix + name)
204+
if existing != nil {
205+
if s, ok := existing.(*expvar.String); ok {
206+
return s, nil
207+
}
208+
return nil, fmt.Errorf("%v is set to a non-string value", name)
209+
}
210+
return nil, nil
211+
},
212+
func() *expvar.String { // create
213+
return expvar.NewString(statsPrefix + name)
214+
},
215+
)
193216
}
194217

195218
// Target sets the target name. This can be set multiple times.
@@ -225,23 +248,31 @@ type Statistics struct {
225248
// - stdvar
226249
// If a Statistics object with the same name already exists it is returned.
227250
func NewStatistics(name string) *Statistics {
228-
s := &Statistics{
229-
min: atomic.NewFloat64(math.Inf(0)),
230-
max: atomic.NewFloat64(math.Inf(-1)),
231-
count: atomic.NewInt64(0),
232-
avg: atomic.NewFloat64(0),
233-
mean: atomic.NewFloat64(0),
234-
value: atomic.NewFloat64(0),
235-
}
236-
existing := expvar.Get(statsPrefix + name)
237-
if existing != nil {
238-
if s, ok := existing.(*Statistics); ok {
251+
return createOrRetrieveExpvar(
252+
func() (*Statistics, error) { // check
253+
254+
existing := expvar.Get(statsPrefix + name)
255+
if existing != nil {
256+
if s, ok := existing.(*Statistics); ok {
257+
return s, nil
258+
}
259+
return nil, fmt.Errorf("%v is set to a non-Statistics value", name)
260+
}
261+
return nil, nil
262+
},
263+
func() *Statistics { // create
264+
s := &Statistics{
265+
min: atomic.NewFloat64(math.Inf(0)),
266+
max: atomic.NewFloat64(math.Inf(-1)),
267+
count: atomic.NewInt64(0),
268+
avg: atomic.NewFloat64(0),
269+
mean: atomic.NewFloat64(0),
270+
value: atomic.NewFloat64(0),
271+
}
272+
expvar.Publish(statsPrefix+name, s)
239273
return s
240-
}
241-
panic(fmt.Sprintf("%v is set to a non-Statistics value", name))
242-
}
243-
expvar.Publish(statsPrefix+name, s)
244-
return s
274+
},
275+
)
245276
}
246277

247278
func (s *Statistics) String() string {
@@ -316,23 +347,56 @@ type Counter struct {
316347
resetTime time.Time
317348
}
318349

350+
func createOrRetrieveExpvar[K any](check func() (*K, error), create func() *K) *K {
351+
// check if string exists holding read lock
352+
createLock.RLock()
353+
s, err := check()
354+
createLock.RUnlock()
355+
if err != nil {
356+
panic(err.Error())
357+
}
358+
if s != nil {
359+
return s
360+
}
361+
362+
// acquire write lock and check again and create if still missing
363+
createLock.Lock()
364+
defer createLock.Unlock()
365+
s, err = check()
366+
if err != nil {
367+
panic(err.Error())
368+
}
369+
if s != nil {
370+
return s
371+
}
372+
373+
return create()
374+
}
375+
319376
// NewCounter returns a new Counter stats object.
320377
// If a Counter stats object with the same name already exists it is returned.
321378
func NewCounter(name string) *Counter {
322-
existing := expvar.Get(statsPrefix + name)
323-
if existing != nil {
324-
if c, ok := existing.(*Counter); ok {
379+
return createOrRetrieveExpvar(
380+
func() (*Counter, error) { // check
381+
existing := expvar.Get(statsPrefix + name)
382+
if existing != nil {
383+
if c, ok := existing.(*Counter); ok {
384+
return c, nil
385+
}
386+
return nil, fmt.Errorf("%v is set to a non-Counter value", name)
387+
}
388+
return nil, nil
389+
},
390+
func() *Counter { // create
391+
c := &Counter{
392+
total: atomic.NewInt64(0),
393+
rate: atomic.NewFloat64(0),
394+
resetTime: time.Now(),
395+
}
396+
expvar.Publish(statsPrefix+name, c)
325397
return c
326-
}
327-
panic(fmt.Sprintf("%v is set to a non-Counter value", name))
328-
}
329-
c := &Counter{
330-
total: atomic.NewInt64(0),
331-
rate: atomic.NewFloat64(0),
332-
resetTime: time.Now(),
333-
}
334-
expvar.Publish(statsPrefix+name, c)
335-
return c
398+
},
399+
)
336400
}
337401

338402
func (c *Counter) updateRate() {
@@ -371,19 +435,26 @@ type WordCounter struct {
371435
// The WordCounter object is thread-safe and counts the number of words recorded.
372436
// If a WordCounter stats object with the same name already exists it is returned.
373437
func NewWordCounter(name string) *WordCounter {
374-
c := &WordCounter{
375-
count: atomic.NewInt64(0),
376-
words: sync.Map{},
377-
}
378-
existing := expvar.Get(statsPrefix + name)
379-
if existing != nil {
380-
if w, ok := existing.(*WordCounter); ok {
381-
return w
382-
}
383-
panic(fmt.Sprintf("%v is set to a non-WordCounter value", name))
384-
}
385-
expvar.Publish(statsPrefix+name, c)
386-
return c
438+
return createOrRetrieveExpvar(
439+
func() (*WordCounter, error) { // check
440+
existing := expvar.Get(statsPrefix + name)
441+
if existing != nil {
442+
if w, ok := existing.(*WordCounter); ok {
443+
return w, nil
444+
}
445+
return nil, fmt.Errorf("%v is set to a non-WordCounter value", name)
446+
}
447+
return nil, nil
448+
},
449+
func() *WordCounter { // create
450+
c := &WordCounter{
451+
count: atomic.NewInt64(0),
452+
words: sync.Map{},
453+
}
454+
expvar.Publish(statsPrefix+name, c)
455+
return c
456+
},
457+
)
387458
}
388459

389460
func (w *WordCounter) Add(word string) {

0 commit comments

Comments
 (0)