* feat(backend): try a new approach for logs formatting (http) * feat(backend): added logger module * feat(backend): added project/session info to /i endpoint * feat(backend): found a solution for correct caller information * feat(backend): finished logs for http handlers * feat(backend): finished logs for mobile http handlers * feat(backend): finished ender * feat(backend): finished assets * feat(backend): finished heuristics * feat(backend): finished image-storage * feat(backend): finished sink * feat(backend): finished storage * feat(backend): formatted logs in all services * feat(backend): finished foss part * feat(backend): added missed foss part * feat(backend): fixed panic in memory manager and sink service * feat(backend): connectors
110 lines
2.4 KiB
Go
110 lines
2.4 KiB
Go
package memory
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"runtime"
|
|
"sync"
|
|
"time"
|
|
|
|
"openreplay/backend/pkg/logger"
|
|
)
|
|
|
|
type Manager interface {
|
|
HasFreeMemory() bool
|
|
}
|
|
|
|
type managerImpl struct {
|
|
log logger.Logger
|
|
mutex *sync.RWMutex
|
|
current uint64
|
|
maximum uint64
|
|
threshold uint64
|
|
allocated uint64
|
|
total uint64
|
|
}
|
|
|
|
func NewManager(log logger.Logger, maximumMemory, thresholdValue uint64) (Manager, error) {
|
|
ctx := context.Background()
|
|
if maximumMemory < 1 {
|
|
log.Info(ctx, "maximumMemory is not defined, try to parse memory limit from system")
|
|
memLimit, err := parseMemoryLimit()
|
|
if err != nil {
|
|
log.Error(ctx, "can't parse system memory limit, err: ", err)
|
|
} else {
|
|
log.Info(ctx, "memory limit is defined: %d MiB", memLimit)
|
|
}
|
|
if memLimit > 0 {
|
|
maximumMemory = uint64(memLimit)
|
|
}
|
|
}
|
|
if thresholdValue > 100 {
|
|
return nil, errors.New("threshold must be less than 100")
|
|
}
|
|
m := &managerImpl{
|
|
log: log,
|
|
mutex: &sync.RWMutex{},
|
|
threshold: thresholdValue,
|
|
maximum: maximumMemory,
|
|
current: 0,
|
|
}
|
|
go m.worker()
|
|
return m, nil
|
|
}
|
|
|
|
func (m *managerImpl) currentUsage() uint64 {
|
|
m.mutex.RLock()
|
|
defer m.mutex.RUnlock()
|
|
return m.current
|
|
}
|
|
|
|
func (m *managerImpl) calcMemoryUsage() {
|
|
var rtm runtime.MemStats
|
|
runtime.ReadMemStats(&rtm)
|
|
allocated := rtm.Alloc / 1024 / 1024
|
|
total := rtm.Sys / 1024 / 1024
|
|
if allocated > m.maximum && m.HasFreeMemory() {
|
|
m.log.Warn(context.Background(), "memory consumption is greater than maximum memory, current: %d, maximum: %d", allocated, m.maximum)
|
|
}
|
|
current := uint64(float64(allocated*100) / float64(m.maximum))
|
|
m.mutex.Lock()
|
|
m.current = current
|
|
m.allocated = allocated
|
|
m.total = total
|
|
m.mutex.Unlock()
|
|
}
|
|
|
|
func (m *managerImpl) printStat() {
|
|
m.log.Info(context.Background(), "current memory consumption: %d, allocated: %d, maximum: %d, current usage: %d, threshold: %d",
|
|
m.total, m.allocated, m.maximum, m.current, m.threshold)
|
|
}
|
|
|
|
func (m *managerImpl) worker() {
|
|
// If maximum memory is not defined, then we don't need to check memory usage
|
|
if m.maximum == 0 {
|
|
m.current = m.threshold
|
|
return
|
|
}
|
|
|
|
// First memory usage calculation
|
|
m.calcMemoryUsage()
|
|
m.printStat()
|
|
|
|
// Logs and update ticks
|
|
updateTick := time.Tick(time.Second)
|
|
logsTick := time.Tick(time.Minute)
|
|
|
|
// Start worker
|
|
for {
|
|
select {
|
|
case <-updateTick:
|
|
m.calcMemoryUsage()
|
|
case <-logsTick:
|
|
m.printStat()
|
|
}
|
|
}
|
|
}
|
|
|
|
func (m *managerImpl) HasFreeMemory() bool {
|
|
return m.currentUsage() <= m.threshold
|
|
}
|