// 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) 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)< 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 := os.ReadFile(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 }