mirror of
https://github.com/EchoVault/SugarDB.git
synced 2025-10-04 23:52:42 +08:00
222 lines
5.1 KiB
Go
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
|
|
}
|