|
28 | 28 | statsPrefix = "github.com/grafana/phlare/" |
29 | 29 | targetKey = "target" |
30 | 30 | editionKey = "edition" |
| 31 | + |
| 32 | + createLock sync.RWMutex |
31 | 33 | ) |
32 | 34 |
|
33 | 35 | // Report is the JSON object sent to the stats server |
@@ -156,40 +158,61 @@ func memstats() interface{} { |
156 | 158 | // NewFloat returns a new Float stats object. |
157 | 159 | // If a Float stats object with the same name already exists it is returned. |
158 | 160 | 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 | + ) |
167 | 176 | } |
168 | 177 |
|
169 | 178 | // NewInt returns a new Int stats object. |
170 | 179 | // If an Int stats object object with the same name already exists it is returned. |
171 | 180 | 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 | + ) |
180 | 196 | } |
181 | 197 |
|
182 | 198 | // NewString returns a new String stats object. |
183 | 199 | // If a String stats object with the same name already exists it is returned. |
184 | 200 | 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 | + ) |
193 | 216 | } |
194 | 217 |
|
195 | 218 | // Target sets the target name. This can be set multiple times. |
@@ -225,23 +248,31 @@ type Statistics struct { |
225 | 248 | // - stdvar |
226 | 249 | // If a Statistics object with the same name already exists it is returned. |
227 | 250 | 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) |
239 | 273 | 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 | + ) |
245 | 276 | } |
246 | 277 |
|
247 | 278 | func (s *Statistics) String() string { |
@@ -316,23 +347,56 @@ type Counter struct { |
316 | 347 | resetTime time.Time |
317 | 348 | } |
318 | 349 |
|
| 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 | + |
319 | 376 | // NewCounter returns a new Counter stats object. |
320 | 377 | // If a Counter stats object with the same name already exists it is returned. |
321 | 378 | 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) |
325 | 397 | 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 | + ) |
336 | 400 | } |
337 | 401 |
|
338 | 402 | func (c *Counter) updateRate() { |
@@ -371,19 +435,26 @@ type WordCounter struct { |
371 | 435 | // The WordCounter object is thread-safe and counts the number of words recorded. |
372 | 436 | // If a WordCounter stats object with the same name already exists it is returned. |
373 | 437 | 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 | + ) |
387 | 458 | } |
388 | 459 |
|
389 | 460 | func (w *WordCounter) Add(word string) { |
|
0 commit comments