| // Copyright 2023 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| // Package internal/counter implements the internals of the public counter package. | |
| // In addition to the public API, this package also includes APIs to parse and | |
| // manage the counter files, needed by the upload package. | |
| package counter | |
| import ( | |
| "fmt" | |
| "os" | |
| "runtime" | |
| "strings" | |
| "sync/atomic" | |
| ) | |
| var ( | |
| // Note: not using internal/godebug, so that internal/godebug can use | |
| // internal/counter. | |
| debugCounter = strings.Contains(os.Getenv("GODEBUG"), "countertrace=1") | |
| CrashOnBugs = false // for testing; if set, exit on fatal log messages | |
| ) | |
| // debugPrintf formats a debug message if GODEBUG=countertrace=1. | |
| func debugPrintf(format string, args ...any) { | |
| if debugCounter { | |
| if len(format) == 0 || format[len(format)-1] != '\n' { | |
| format += "\n" | |
| } | |
| fmt.Fprintf(os.Stderr, "counter: "+format, args...) | |
| } | |
| } | |
| // debugFatalf logs a fatal error if GODEBUG=countertrace=1. | |
| func debugFatalf(format string, args ...any) { | |
| if debugCounter || CrashOnBugs { | |
| if len(format) == 0 || format[len(format)-1] != '\n' { | |
| format += "\n" | |
| } | |
| fmt.Fprintf(os.Stderr, "counter bug: "+format, args...) | |
| os.Exit(1) | |
| } | |
| } | |
| // A Counter is a single named event counter. | |
| // A Counter is safe for use by multiple goroutines simultaneously. | |
| // | |
| // Counters should typically be created using New | |
| // and stored as global variables, like: | |
| // | |
| // package mypackage | |
| // var errorCount = counter.New("mypackage/errors") | |
| // | |
| // (The initialization of errorCount in this example is handled | |
| // entirely by the compiler and linker; this line executes no code | |
| // at program startup.) | |
| // | |
| // Then code can call Add to increment the counter | |
| // each time the corresponding event is observed. | |
| // | |
| // Although it is possible to use New to create | |
| // a Counter each time a particular event needs to be recorded, | |
| // that usage fails to amortize the construction cost over | |
| // multiple calls to Add, so it is more expensive and not recommended. | |
| type Counter struct { | |
| name string | |
| file *file | |
| next atomic.Pointer[Counter] | |
| state counterState | |
| ptr counterPtr | |
| } | |
| func (c *Counter) Name() string { | |
| return c.name | |
| } | |
| type counterPtr struct { | |
| m *mappedFile | |
| count *atomic.Uint64 | |
| } | |
| type counterState struct { | |
| bits atomic.Uint64 | |
| } | |
| func (s *counterState) load() counterStateBits { | |
| return counterStateBits(s.bits.Load()) | |
| } | |
| func (s *counterState) update(old *counterStateBits, new counterStateBits) bool { | |
| if s.bits.CompareAndSwap(uint64(*old), uint64(new)) { | |
| *old = new | |
| return true | |
| } | |
| return false | |
| } | |
| type counterStateBits uint64 | |
| const ( | |
| stateReaders counterStateBits = 1<<30 - 1 | |
| stateLocked counterStateBits = stateReaders | |
| stateHavePtr counterStateBits = 1 << 30 | |
| stateExtraShift = 31 | |
| stateExtra counterStateBits = 1<<64 - 1<<stateExtraShift | |
| ) | |
| func (b counterStateBits) readers() int { return int(b & stateReaders) } | |
| func (b counterStateBits) locked() bool { return b&stateReaders == stateLocked } | |
| func (b counterStateBits) havePtr() bool { return b&stateHavePtr != 0 } | |
| func (b counterStateBits) extra() uint64 { return uint64(b&stateExtra) >> stateExtraShift } | |
| func (b counterStateBits) incReader() counterStateBits { return b + 1 } | |
| func (b counterStateBits) decReader() counterStateBits { return b - 1 } | |
| func (b counterStateBits) setLocked() counterStateBits { return b | stateLocked } | |
| func (b counterStateBits) clearLocked() counterStateBits { return b &^ stateLocked } | |
| func (b counterStateBits) setHavePtr() counterStateBits { return b | stateHavePtr } | |
| func (b counterStateBits) clearHavePtr() counterStateBits { return b &^ stateHavePtr } | |
| func (b counterStateBits) clearExtra() counterStateBits { return b &^ stateExtra } | |
| func (b counterStateBits) addExtra(n uint64) counterStateBits { | |
| const maxExtra = uint64(stateExtra) >> stateExtraShift // 0x1ffffffff | |
| x := b.extra() | |
| if x+n < x || x+n > maxExtra { | |
| x = maxExtra | |
| } else { | |
| x += n | |
| } | |
| return b.clearExtra() | counterStateBits(x)<<stateExtraShift | |
| } | |
| // New returns a counter with the given name. | |
| // New can be called in global initializers and will be compiled down to | |
| // linker-initialized data. That is, calling New to initialize a global | |
| // has no cost at program startup. | |
| func New(name string) *Counter { | |
| // Note: not calling defaultFile.New in order to keep this | |
| // function something the compiler can inline and convert | |
| // into static data initializations, with no init-time footprint. | |
| return &Counter{name: name, file: &defaultFile} | |
| } | |
| // Inc adds 1 to the counter. | |
| func (c *Counter) Inc() { | |
| c.Add(1) | |
| } | |
| // Add adds n to the counter. n cannot be negative, as counts cannot decrease. | |
| func (c *Counter) Add(n int64) { | |
| debugPrintf("Add %q += %d", c.name, n) | |
| if n < 0 { | |
| panic("Counter.Add negative") | |
| } | |
| if n == 0 { | |
| return | |
| } | |
| c.file.register(c) | |
| state := c.state.load() | |
| for ; ; state = c.state.load() { | |
| switch { | |
| case !state.locked() && state.havePtr(): | |
| if !c.state.update(&state, state.incReader()) { | |
| continue | |
| } | |
| // Counter unlocked or counter shared; has an initialized count pointer; acquired shared lock. | |
| if c.ptr.count == nil { | |
| for !c.state.update(&state, state.addExtra(uint64(n))) { | |
| // keep trying - we already took the reader lock | |
| state = c.state.load() | |
| } | |
| debugPrintf("Add %q += %d: nil extra=%d\n", c.name, n, state.extra()) | |
| } else { | |
| sum := c.add(uint64(n)) | |
| debugPrintf("Add %q += %d: count=%d\n", c.name, n, sum) | |
| } | |
| c.releaseReader(state) | |
| return | |
| case state.locked(): | |
| if !c.state.update(&state, state.addExtra(uint64(n))) { | |
| continue | |
| } | |
| debugPrintf("Add %q += %d: locked extra=%d\n", c.name, n, state.extra()) | |
| return | |
| case !state.havePtr(): | |
| if !c.state.update(&state, state.addExtra(uint64(n)).setLocked()) { | |
| continue | |
| } | |
| debugPrintf("Add %q += %d: noptr extra=%d\n", c.name, n, state.extra()) | |
| c.releaseLock(state) | |
| return | |
| } | |
| } | |
| } | |
| func (c *Counter) releaseReader(state counterStateBits) { | |
| for ; ; state = c.state.load() { | |
| // If we are the last reader and havePtr was cleared | |
| // while this batch of readers was using c.ptr, | |
| // it's our job to update c.ptr by upgrading to a full lock | |
| // and letting releaseLock do the work. | |
| // Note: no new reader will attempt to add itself now that havePtr is clear, | |
| // so we are only racing against possible additions to extra. | |
| if state.readers() == 1 && !state.havePtr() { | |
| if !c.state.update(&state, state.setLocked()) { | |
| continue | |
| } | |
| debugPrintf("releaseReader %s: last reader, need ptr\n", c.name) | |
| c.releaseLock(state) | |
| return | |
| } | |
| // Release reader. | |
| if !c.state.update(&state, state.decReader()) { | |
| continue | |
| } | |
| debugPrintf("releaseReader %s: released (%d readers now)\n", c.name, state.readers()) | |
| return | |
| } | |
| } | |
| func (c *Counter) releaseLock(state counterStateBits) { | |
| for ; ; state = c.state.load() { | |
| if !state.havePtr() { | |
| // Set havePtr before updating ptr, | |
| // to avoid race with the next clear of havePtr. | |
| if !c.state.update(&state, state.setHavePtr()) { | |
| continue | |
| } | |
| debugPrintf("releaseLock %s: reset havePtr (extra=%d)\n", c.name, state.extra()) | |
| // Optimization: only bother loading a new pointer | |
| // if we have a value to add to it. | |
| c.ptr = counterPtr{nil, nil} | |
| if state.extra() != 0 { | |
| c.ptr = c.file.lookup(c.name) | |
| debugPrintf("releaseLock %s: ptr=%v\n", c.name, c.ptr) | |
| } | |
| } | |
| if extra := state.extra(); extra != 0 && c.ptr.count != nil { | |
| if !c.state.update(&state, state.clearExtra()) { | |
| continue | |
| } | |
| sum := c.add(extra) | |
| debugPrintf("releaseLock %s: flush extra=%d -> count=%d\n", c.name, extra, sum) | |
| } | |
| // Took care of refreshing ptr and flushing extra. | |
| // Now we can release the lock, unless of course | |
| // another goroutine cleared havePtr or added to extra, | |
| // in which case we go around again. | |
| if !c.state.update(&state, state.clearLocked()) { | |
| continue | |
| } | |
| debugPrintf("releaseLock %s: unlocked\n", c.name) | |
| return | |
| } | |
| } | |
| // add wraps the atomic.Uint64.Add operation to handle integer overflow. | |
| func (c *Counter) add(n uint64) uint64 { | |
| count := c.ptr.count | |
| for { | |
| old := count.Load() | |
| sum := old + n | |
| if sum < old { | |
| sum = ^uint64(0) | |
| } | |
| if count.CompareAndSwap(old, sum) { | |
| runtime.KeepAlive(c.ptr.m) | |
| return sum | |
| } | |
| } | |
| } | |
| func (c *Counter) invalidate() { | |
| for { | |
| state := c.state.load() | |
| if !state.havePtr() { | |
| debugPrintf("invalidate %s: no ptr\n", c.name) | |
| return | |
| } | |
| if c.state.update(&state, state.clearHavePtr()) { | |
| debugPrintf("invalidate %s: cleared havePtr\n", c.name) | |
| return | |
| } | |
| } | |
| } | |
| func (c *Counter) refresh() { | |
| for { | |
| state := c.state.load() | |
| if state.havePtr() || state.readers() > 0 || state.extra() == 0 { | |
| debugPrintf("refresh %s: havePtr=%v readers=%d extra=%d\n", c.name, state.havePtr(), state.readers(), state.extra()) | |
| return | |
| } | |
| if c.state.update(&state, state.setLocked()) { | |
| debugPrintf("refresh %s: locked havePtr=%v readers=%d extra=%d\n", c.name, state.havePtr(), state.readers(), state.extra()) | |
| c.releaseLock(state) | |
| return | |
| } | |
| } | |
| } | |
| // Read reads the given counter. | |
| // This is the implementation of x/telemetry/counter/countertest.ReadCounter. | |
| func Read(c *Counter) (uint64, error) { | |
| if c.file.current.Load() == nil { | |
| return c.state.load().extra(), nil | |
| } | |
| pf, err := readFile(c.file) | |
| if err != nil { | |
| return 0, err | |
| } | |
| v, ok := pf.Count[DecodeStack(c.Name())] | |
| if !ok { | |
| return v, fmt.Errorf("not found:%q", DecodeStack(c.Name())) | |
| } | |
| return v, nil | |
| } | |
| func readFile(f *file) (*File, error) { | |
| if f == nil { | |
| debugPrintf("No file") | |
| return nil, fmt.Errorf("counter is not initialized - was Open called?") | |
| } | |
| // Note: don't call f.rotate here as this will enqueue a follow-up rotation. | |
| f.rotate1() | |
| if f.err != nil { | |
| return nil, fmt.Errorf("failed to rotate mapped file - %v", f.err) | |
| } | |
| current := f.current.Load() | |
| if current == nil { | |
| return nil, fmt.Errorf("counter has no mapped file") | |
| } | |
| name := current.f.Name() | |
| data, err := ReadMapped(name) | |
| if err != nil { | |
| return nil, fmt.Errorf("failed to read from file: %v", err) | |
| } | |
| pf, err := Parse(name, data) | |
| if err != nil { | |
| return nil, fmt.Errorf("failed to parse: %v", err) | |
| } | |
| return pf, nil | |
| } | |
| // ReadFile reads the counters and stack counters from the given file. | |
| // This is the implementation of x/telemetry/counter/countertest.ReadFile. | |
| func ReadFile(name string) (counters, stackCounters map[string]uint64, _ error) { | |
| // TODO: Document the format of the stackCounters names. | |
| data, err := ReadMapped(name) | |
| if err != nil { | |
| return nil, nil, fmt.Errorf("failed to read from file: %v", err) | |
| } | |
| pf, err := Parse(name, data) | |
| if err != nil { | |
| return nil, nil, fmt.Errorf("failed to parse: %v", err) | |
| } | |
| counters = make(map[string]uint64) | |
| stackCounters = make(map[string]uint64) | |
| for k, v := range pf.Count { | |
| if IsStackCounter(k) { | |
| stackCounters[DecodeStack(k)] = v | |
| } else { | |
| counters[k] = v | |
| } | |
| } | |
| return counters, stackCounters, nil | |
| } | |
| // ReadMapped reads the contents of the given file by memory mapping. | |
| // | |
| // This avoids file synchronization issues. | |
| func ReadMapped(name string) ([]byte, error) { | |
| f, err := os.OpenFile(name, os.O_RDWR, 0666) | |
| if err != nil { | |
| return nil, err | |
| } | |
| defer f.Close() | |
| fi, err := f.Stat() | |
| if err != nil { | |
| return nil, err | |
| } | |
| mapping, err := memmap(f) | |
| if err != nil { | |
| return nil, err | |
| } | |
| data := make([]byte, fi.Size()) | |
| copy(data, mapping.Data) | |
| munmap(mapping) | |
| return data, nil | |
| } |