Spaces:
Sleeping
Sleeping
| // _ _ | |
| // __ _____ __ ___ ___ __ _| |_ ___ | |
| // \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \ | |
| // \ V V / __/ (_| |\ V /| | (_| | || __/ | |
| // \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___| | |
| // | |
| // Copyright © 2016 - 2024 Weaviate B.V. All rights reserved. | |
| // | |
| // CONTACT: [email protected] | |
| // | |
| package memwatch | |
| import ( | |
| "fmt" | |
| "runtime/metrics" | |
| "sync" | |
| ) | |
| const ( | |
| B = 1 | |
| KiB = 1 << (10 * iota) // 2^10 | |
| MiB = 1 << (10 * iota) // 2^20 | |
| GiB = 1 << (10 * iota) // 2^30 | |
| TiB = 1 << (10 * iota) // 2^40 | |
| ) | |
| var ErrNotEnoughMemory = fmt.Errorf("not enough memory") | |
| // Monitor allows making statements about the memory ratio used by the application | |
| type Monitor struct { | |
| metricsReader metricsReader | |
| limitSetter limitSetter | |
| maxRatio float64 | |
| // state | |
| mu sync.Mutex | |
| limit int64 | |
| used int64 | |
| } | |
| // Refresh retrieves the current memory stats from the runtime and stores them | |
| // in the local cache | |
| func (m *Monitor) Refresh() { | |
| m.obtainCurrentUsage() | |
| m.updateLimit() | |
| } | |
| // we have no intentions of ever modifying the limit, but SetMemoryLimit with a | |
| // negative value is the only way to read the limit from the runtime | |
| type limitSetter func(size int64) int64 | |
| // NewMonitor creates a [Monitor] with the given metrics reader and target | |
| // ratio | |
| // | |
| // Typically this would be called with LiveHeapReader and | |
| // debug.SetMemoryLimit | |
| func NewMonitor(metricsReader metricsReader, limitSetter limitSetter, | |
| maxRatio float64, | |
| ) *Monitor { | |
| return &Monitor{ | |
| metricsReader: metricsReader, | |
| limitSetter: limitSetter, | |
| maxRatio: maxRatio, | |
| } | |
| } | |
| func (m *Monitor) CheckAlloc(sizeInBytes int64) error { | |
| m.mu.Lock() | |
| defer m.mu.Unlock() | |
| if float64(m.used+sizeInBytes)/float64(m.limit) > m.maxRatio { | |
| return ErrNotEnoughMemory | |
| } | |
| return nil | |
| } | |
| func (m *Monitor) Ratio() float64 { | |
| m.mu.Lock() | |
| defer m.mu.Unlock() | |
| return float64(m.used) / float64(m.limit) | |
| } | |
| // obtainCurrentUsage obtains the most recent live heap from runtime/metrics | |
| func (m *Monitor) obtainCurrentUsage() { | |
| m.setUsed(m.metricsReader()) | |
| } | |
| func LiveHeapReader() int64 { | |
| const liveHeapBytesMetric = "/gc/heap/live:bytes" | |
| sample := make([]metrics.Sample, 1) | |
| sample[0].Name = liveHeapBytesMetric | |
| metrics.Read(sample) | |
| if sample[0].Value.Kind() == metrics.KindBad { | |
| panic(fmt.Sprintf("metric %q no longer supported", liveHeapBytesMetric)) | |
| } | |
| return int64(sample[0].Value.Uint64()) | |
| } | |
| // setUsed is a thread-safe way way to set the current usage | |
| func (m *Monitor) setUsed(used int64) { | |
| m.mu.Lock() | |
| defer m.mu.Unlock() | |
| m.used = used | |
| } | |
| func (m *Monitor) updateLimit() { | |
| m.mu.Lock() | |
| defer m.mu.Unlock() | |
| // setting a negative limit is the only way to obtain the current limit | |
| m.limit = m.limitSetter(-1) | |
| } | |
| type metricsReader func() int64 | |