2025-11-10 17:32:18 +08:00
|
|
|
// Package diagram provide diagram data structure and operation
|
|
|
|
|
package diagram
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
2026-07-15 17:33:55 +08:00
|
|
|
"fmt"
|
2026-07-30 15:32:22 +08:00
|
|
|
"sort"
|
2026-07-15 17:33:55 +08:00
|
|
|
"strconv"
|
2025-11-10 17:32:18 +08:00
|
|
|
|
|
|
|
|
"github.com/redis/go-redis/v9"
|
|
|
|
|
)
|
|
|
|
|
|
2025-11-11 17:37:06 +08:00
|
|
|
// RedisClient define struct to accessing redis data that does not require the use of distributed locks
|
2025-11-10 17:32:18 +08:00
|
|
|
type RedisClient struct {
|
|
|
|
|
Client *redis.Client
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-15 17:33:55 +08:00
|
|
|
// QueryLatestMeasurementValue returns the score whose member contains the
|
|
|
|
|
// greatest numeric timestamp. Measurement ZSets currently store timestamp in
|
|
|
|
|
// member and measurement value in score.
|
|
|
|
|
func (rc *RedisClient) QueryLatestMeasurementValue(ctx context.Context, key string) (float64, error) {
|
2026-07-30 15:32:22 +08:00
|
|
|
values, err := rc.QueryLatestMeasurementValues(ctx, key, 1)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return 0, err
|
|
|
|
|
}
|
|
|
|
|
return values[0], nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// QueryLatestMeasurementValues returns up to size scores ordered by their
|
|
|
|
|
// numeric member timestamps from newest to oldest.
|
|
|
|
|
func (rc *RedisClient) QueryLatestMeasurementValues(ctx context.Context, key string, size int) ([]float64, error) {
|
2026-07-15 17:33:55 +08:00
|
|
|
if rc.Client == nil {
|
2026-07-30 15:32:22 +08:00
|
|
|
return nil, fmt.Errorf("redis client is not initialized")
|
|
|
|
|
}
|
|
|
|
|
if size <= 0 {
|
|
|
|
|
return nil, fmt.Errorf("measurement window size must be greater than 0, got %d", size)
|
2026-07-15 17:33:55 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
members, err := rc.Client.ZRangeWithScores(ctx, key, 0, -1).Result()
|
|
|
|
|
if err != nil {
|
2026-07-30 15:32:22 +08:00
|
|
|
return nil, err
|
2026-07-15 17:33:55 +08:00
|
|
|
}
|
2026-07-30 15:32:22 +08:00
|
|
|
return latestMeasurementValues(members, key, size)
|
2026-07-15 17:33:55 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func latestMeasurementValue(members []redis.Z, key string) (float64, error) {
|
2026-07-30 15:32:22 +08:00
|
|
|
values, err := latestMeasurementValues(members, key, 1)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return 0, err
|
|
|
|
|
}
|
|
|
|
|
return values[0], nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func latestMeasurementValues(members []redis.Z, key string, size int) ([]float64, error) {
|
|
|
|
|
if size <= 0 {
|
|
|
|
|
return nil, fmt.Errorf("measurement window size must be greater than 0, got %d", size)
|
|
|
|
|
}
|
2026-07-15 17:33:55 +08:00
|
|
|
if len(members) == 0 {
|
2026-07-30 15:32:22 +08:00
|
|
|
return nil, fmt.Errorf("real-time measurement value not found for key %q", key)
|
2026-07-15 17:33:55 +08:00
|
|
|
}
|
|
|
|
|
|
2026-07-30 15:32:22 +08:00
|
|
|
type timestampedValue struct {
|
|
|
|
|
timestamp int64
|
|
|
|
|
value float64
|
|
|
|
|
}
|
|
|
|
|
values := make([]timestampedValue, 0, len(members))
|
2026-07-15 17:33:55 +08:00
|
|
|
for _, member := range members {
|
|
|
|
|
timestamp, err := strconv.ParseInt(fmt.Sprint(member.Member), 10, 64)
|
|
|
|
|
if err != nil {
|
|
|
|
|
continue
|
|
|
|
|
}
|
2026-07-30 15:32:22 +08:00
|
|
|
values = append(values, timestampedValue{timestamp: timestamp, value: member.Score})
|
|
|
|
|
}
|
|
|
|
|
if len(values) == 0 {
|
|
|
|
|
return nil, fmt.Errorf("real-time measurement timestamps are invalid for key %q", key)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sort.Slice(values, func(i, j int) bool {
|
|
|
|
|
return values[i].timestamp > values[j].timestamp
|
|
|
|
|
})
|
|
|
|
|
if size > len(values) {
|
|
|
|
|
size = len(values)
|
2026-07-15 17:33:55 +08:00
|
|
|
}
|
2026-07-30 15:32:22 +08:00
|
|
|
result := make([]float64, size)
|
|
|
|
|
for index := range size {
|
|
|
|
|
result[index] = values[index].value
|
2026-07-15 17:33:55 +08:00
|
|
|
}
|
2026-07-30 15:32:22 +08:00
|
|
|
return result, nil
|
2026-07-15 17:33:55 +08:00
|
|
|
}
|
|
|
|
|
|
2025-11-10 17:32:18 +08:00
|
|
|
// NewRedisClient define func of new redis client instance
|
|
|
|
|
func NewRedisClient() *RedisClient {
|
|
|
|
|
return &RedisClient{
|
|
|
|
|
Client: GetRedisClientInstance(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-27 17:41:17 +08:00
|
|
|
// QueryByZRange define func to query real time data from redis zset
|
|
|
|
|
func (rc *RedisClient) QueryByZRange(ctx context.Context, key string, size int64) ([]redis.Z, error) {
|
2025-11-10 17:32:18 +08:00
|
|
|
client := rc.Client
|
|
|
|
|
args := redis.ZRangeArgs{
|
2025-11-25 16:13:55 +08:00
|
|
|
Key: key,
|
|
|
|
|
Start: 0,
|
|
|
|
|
Stop: size,
|
|
|
|
|
ByScore: false,
|
|
|
|
|
ByLex: false,
|
|
|
|
|
Rev: false,
|
|
|
|
|
Offset: 0,
|
|
|
|
|
Count: 0,
|
2025-11-10 17:32:18 +08:00
|
|
|
}
|
2025-11-11 11:50:25 +08:00
|
|
|
return client.ZRangeArgsWithScores(ctx, args).Result()
|
2025-11-10 17:32:18 +08:00
|
|
|
}
|