Files
SugarDB/internal/utils.go
2024-03-26 14:00:18 +08:00

222 lines
5.1 KiB
Go

// Copyright 2024 Kelvin Clement Mwinuka
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package internal
import (
"bufio"
"bytes"
"errors"
"fmt"
"github.com/echovault/echovault/pkg/utils"
"io"
"log"
"math/big"
"net"
"runtime"
"slices"
"strconv"
"strings"
"time"
"github.com/sethvargo/go-retry"
"github.com/tidwall/resp"
)
func AdaptType(s string) interface{} {
// Adapt the type of the parameter to string, float64 or int
n, _, err := big.ParseFloat(s, 10, 256, big.RoundingMode(big.Exact))
if err != nil {
return s
}
if n.IsInt() {
i, _ := n.Int64()
return int(i)
}
f, _ := n.Float64()
return f
}
func Decode(raw []byte) ([]string, error) {
reader := resp.NewReader(bytes.NewReader(raw))
value, _, err := reader.ReadValue()
if err != nil {
return nil, err
}
var res []string
for i := 0; i < len(value.Array()); i++ {
res = append(res, value.Array()[i].String())
}
return res, nil
}
func ReadMessage(r io.Reader) ([]byte, error) {
reader := bufio.NewReader(r)
var res []byte
chunk := make([]byte, 8192)
for {
n, err := reader.Read(chunk)
if err != nil && errors.Is(err, io.EOF) {
break
}
if err != nil {
return nil, err
}
res = append(res, chunk...)
if n < len(chunk) {
break
}
clear(chunk)
}
return bytes.Trim(res, "\x00"), nil
}
func RetryBackoff(b retry.Backoff, maxRetries uint64, jitter, cappedDuration, maxDuration time.Duration) retry.Backoff {
backoff := b
if maxRetries > 0 {
backoff = retry.WithMaxRetries(maxRetries, backoff)
}
if jitter > 0 {
backoff = retry.WithJitter(jitter, backoff)
}
if cappedDuration > 0 {
backoff = retry.WithCappedDuration(cappedDuration, backoff)
}
if maxDuration > 0 {
backoff = retry.WithMaxDuration(maxDuration, backoff)
}
return backoff
}
func GetIPAddress() (string, error) {
conn, err := net.Dial("udp", "8.8.8.8:80")
if err != nil {
return "", err
}
defer func() {
if err = conn.Close(); err != nil {
log.Println(err)
}
}()
localAddr := strings.Split(conn.LocalAddr().String(), ":")[0]
return localAddr, nil
}
func GetSubCommand(command utils.Command, cmd []string) interface{} {
if len(command.SubCommands) == 0 || len(cmd) < 2 {
return nil
}
for _, subCommand := range command.SubCommands {
if strings.EqualFold(subCommand.Command, cmd[1]) {
return subCommand
}
}
return nil
}
func IsWriteCommand(command utils.Command, subCommand utils.SubCommand) bool {
return slices.Contains(append(command.Categories, subCommand.Categories...), utils.WriteCategory)
}
func AbsInt(n int) int {
if n < 0 {
return -n
}
return n
}
// ParseMemory returns an integer representing the bytes in the memory string
func ParseMemory(memory string) (uint64, error) {
// Parse memory strings such as "100mb", "16gb"
memString := memory[0 : len(memory)-2]
bytesInt, err := strconv.ParseInt(memString, 10, 64)
if err != nil {
return 0, err
}
memUnit := strings.ToLower(memory[len(memory)-2:])
switch memUnit {
case "kb":
bytesInt *= 1024
case "mb":
bytesInt *= 1024 * 1024
case "gb":
bytesInt *= 1024 * 1024 * 1024
case "tb":
bytesInt *= 1024 * 1024 * 1024 * 1024
case "pb":
bytesInt *= 1024 * 1024 * 1024 * 1024 * 1024
default:
return 0, fmt.Errorf("memory unit %s not supported, use (kb, mb, gb, tb, pb) ", memUnit)
}
return uint64(bytesInt), nil
}
// IsMaxMemoryExceeded checks whether we have exceeded the current maximum memory limit.
func IsMaxMemoryExceeded(maxMemory uint64) bool {
if maxMemory == 0 {
return false
}
var memStats runtime.MemStats
runtime.ReadMemStats(&memStats)
// If we're currently using less than the configured max memory, return false.
if memStats.HeapInuse < maxMemory {
return false
}
// If we're currently using more than max memory, force a garbage collection before we start deleting keys.
// This measure is to prevent deleting keys that may be important when some memory can be reclaimed
// by just collecting garbage.
runtime.GC()
runtime.ReadMemStats(&memStats)
// Return true when whe are above or equal to max memory.
return memStats.HeapInuse >= maxMemory
}
// FilterExpiredKeys filters out keys that are already expired, so they are not persisted.
func FilterExpiredKeys(state map[string]utils.KeyData) map[string]utils.KeyData {
var keysToDelete []string
for k, v := range state {
// Skip keys with no expiry time.
if v.ExpireAt == (time.Time{}) {
continue
}
// If the key is already expired, mark it for deletion.
if v.ExpireAt.Before(time.Now()) {
keysToDelete = append(keysToDelete, k)
}
}
for _, key := range keysToDelete {
delete(state, key)
}
return state
}