mirror of
https://github.com/hibiken/asynqmon.git
synced 2025-01-19 03:05:53 +08:00
Support redis cluster
- Added `--redis-cluster-nodes` flag - Display cluster information in redis info page
This commit is contained in:
parent
008215566a
commit
ce5c86eea5
91
main.go
91
main.go
@ -10,6 +10,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-redis/redis/v8"
|
"github.com/go-redis/redis/v8"
|
||||||
@ -20,13 +21,14 @@ import (
|
|||||||
|
|
||||||
// Command-line flags
|
// Command-line flags
|
||||||
var (
|
var (
|
||||||
flagPort int
|
flagPort int
|
||||||
flagRedisAddr string
|
flagRedisAddr string
|
||||||
flagRedisDB int
|
flagRedisDB int
|
||||||
flagRedisPassword string
|
flagRedisPassword string
|
||||||
flagRedisTLS string
|
flagRedisTLS string
|
||||||
flagRedisURL string
|
flagRedisURL string
|
||||||
flagRedisInsecureTLS bool
|
flagRedisInsecureTLS bool
|
||||||
|
flagRedisClusterNodes string
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -36,7 +38,8 @@ func init() {
|
|||||||
flag.StringVar(&flagRedisPassword, "redis-password", "", "password to use when connecting to redis server")
|
flag.StringVar(&flagRedisPassword, "redis-password", "", "password to use when connecting to redis server")
|
||||||
flag.StringVar(&flagRedisTLS, "redis-tls", "", "server name for TLS validation used when connecting to redis server")
|
flag.StringVar(&flagRedisTLS, "redis-tls", "", "server name for TLS validation used when connecting to redis server")
|
||||||
flag.StringVar(&flagRedisURL, "redis-url", "", "URL to redis server")
|
flag.StringVar(&flagRedisURL, "redis-url", "", "URL to redis server")
|
||||||
flag.BoolVar(&flagRedisInsecureTLS, "redis-insecure-tls", false, "Disable TLS certificate host checks")
|
flag.BoolVar(&flagRedisInsecureTLS, "redis-insecure-tls", false, "disable TLS certificate host checks")
|
||||||
|
flag.StringVar(&flagRedisClusterNodes, "redis-cluster-nodes", "", "comma separated list of host:port addresses of cluster nodes")
|
||||||
}
|
}
|
||||||
|
|
||||||
// staticFileServer implements the http.Handler interface, so we can use it
|
// staticFileServer implements the http.Handler interface, so we can use it
|
||||||
@ -88,20 +91,26 @@ func (srv *staticFileServer) indexFilePath() string {
|
|||||||
return filepath.Join(srv.staticDirPath, srv.indexFileName)
|
return filepath.Join(srv.staticDirPath, srv.indexFileName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRedisOptionsFromFlags() (*redis.Options, error) {
|
func getRedisOptionsFromFlags() (*redis.UniversalOptions, error) {
|
||||||
var err error
|
var opts redis.UniversalOptions
|
||||||
var opts *redis.Options
|
|
||||||
|
|
||||||
if flagRedisURL != "" {
|
if flagRedisClusterNodes != "" {
|
||||||
opts, err = redis.ParseURL(flagRedisURL)
|
opts.Addrs = strings.Split(flagRedisClusterNodes, ",")
|
||||||
if err != nil {
|
opts.Password = flagRedisPassword
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
opts = &redis.Options{
|
if flagRedisURL != "" {
|
||||||
Addr: flagRedisAddr,
|
res, err := redis.ParseURL(flagRedisURL)
|
||||||
DB: flagRedisDB,
|
if err != nil {
|
||||||
Password: flagRedisPassword,
|
return nil, err
|
||||||
|
}
|
||||||
|
opts.Addrs = append(opts.Addrs, res.Addr)
|
||||||
|
opts.DB = res.DB
|
||||||
|
opts.Password = res.Password
|
||||||
|
|
||||||
|
} else {
|
||||||
|
opts.Addrs = []string{flagRedisAddr}
|
||||||
|
opts.DB = flagRedisDB
|
||||||
|
opts.Password = flagRedisPassword
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,7 +123,7 @@ func getRedisOptionsFromFlags() (*redis.Options, error) {
|
|||||||
}
|
}
|
||||||
opts.TLSConfig.InsecureSkipVerify = true
|
opts.TLSConfig.InsecureSkipVerify = true
|
||||||
}
|
}
|
||||||
return opts, nil
|
return &opts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:embed ui/build/*
|
//go:embed ui/build/*
|
||||||
@ -128,16 +137,34 @@ func main() {
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
inspector := asynq.NewInspector(asynq.RedisClientOpt{
|
useRedisCluster := flagRedisClusterNodes != ""
|
||||||
Addr: opts.Addr,
|
|
||||||
DB: opts.DB,
|
var redisConnOpt asynq.RedisConnOpt
|
||||||
Password: opts.Password,
|
if useRedisCluster {
|
||||||
TLSConfig: opts.TLSConfig,
|
redisConnOpt = asynq.RedisClusterClientOpt{
|
||||||
})
|
Addrs: opts.Addrs,
|
||||||
|
Password: opts.Password,
|
||||||
|
TLSConfig: opts.TLSConfig,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
redisConnOpt = asynq.RedisClientOpt{
|
||||||
|
Addr: opts.Addrs[0],
|
||||||
|
DB: opts.DB,
|
||||||
|
Password: opts.Password,
|
||||||
|
TLSConfig: opts.TLSConfig,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inspector := asynq.NewInspector(redisConnOpt)
|
||||||
defer inspector.Close()
|
defer inspector.Close()
|
||||||
|
|
||||||
rdb := redis.NewClient(opts)
|
var redisClient redis.UniversalClient
|
||||||
defer rdb.Close()
|
if useRedisCluster {
|
||||||
|
redisClient = redis.NewClusterClient(opts.Cluster())
|
||||||
|
} else {
|
||||||
|
redisClient = redis.NewClient(opts.Simple())
|
||||||
|
}
|
||||||
|
defer redisClient.Close()
|
||||||
|
|
||||||
router := mux.NewRouter()
|
router := mux.NewRouter()
|
||||||
router.Use(loggingMiddleware)
|
router.Use(loggingMiddleware)
|
||||||
@ -207,7 +234,11 @@ func main() {
|
|||||||
api.HandleFunc("/scheduler_entries/{entry_id}/enqueue_events", newListSchedulerEnqueueEventsHandlerFunc(inspector)).Methods("GET")
|
api.HandleFunc("/scheduler_entries/{entry_id}/enqueue_events", newListSchedulerEnqueueEventsHandlerFunc(inspector)).Methods("GET")
|
||||||
|
|
||||||
// Redis info endpoint.
|
// Redis info endpoint.
|
||||||
api.HandleFunc("/redis_info", newRedisInfoHandlerFunc(rdb)).Methods("GET")
|
if useRedisCluster {
|
||||||
|
api.HandleFunc("/redis_info", newRedisClusterInfoHandlerFunc(redisClient.(*redis.ClusterClient), inspector)).Methods("GET")
|
||||||
|
} else {
|
||||||
|
api.HandleFunc("/redis_info", newRedisInfoHandlerFunc(redisClient.(*redis.Client))).Methods("GET")
|
||||||
|
}
|
||||||
|
|
||||||
fs := &staticFileServer{
|
fs := &staticFileServer{
|
||||||
contents: staticContents,
|
contents: staticContents,
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/go-redis/redis/v8"
|
"github.com/go-redis/redis/v8"
|
||||||
|
"github.com/hibiken/asynq"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ****************************************************************************
|
// ****************************************************************************
|
||||||
@ -18,21 +19,86 @@ type RedisInfoResponse struct {
|
|||||||
Addr string `json:"address"`
|
Addr string `json:"address"`
|
||||||
Info map[string]string `json:"info"`
|
Info map[string]string `json:"info"`
|
||||||
RawInfo string `json:"raw_info"`
|
RawInfo string `json:"raw_info"`
|
||||||
|
Cluster bool `json:"cluster"`
|
||||||
|
|
||||||
|
// Following fields are only set when connected to redis cluster.
|
||||||
|
RawClusterNodes string `json:"raw_cluster_nodes"`
|
||||||
|
QueueLocations []*QueueLocationInfo `json:"queue_locations"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func newRedisInfoHandlerFunc(rdb *redis.Client) http.HandlerFunc {
|
type QueueLocationInfo struct {
|
||||||
|
Queue string `json:"queue"` // queue name
|
||||||
|
KeySlot int64 `json:"keyslot"` // cluster key slot for the queue
|
||||||
|
Nodes []string `json:"nodes"` // list of cluster node addresses
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRedisInfoHandlerFunc(client *redis.Client) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
res, err := rdb.Info(ctx).Result()
|
res, err := client.Info(ctx).Result()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
info := parseRedisInfo(res)
|
info := parseRedisInfo(res)
|
||||||
resp := RedisInfoResponse{
|
resp := RedisInfoResponse{
|
||||||
Addr: rdb.Options().Addr,
|
Addr: client.Options().Addr,
|
||||||
Info: info,
|
Info: info,
|
||||||
RawInfo: res,
|
RawInfo: res,
|
||||||
|
Cluster: false,
|
||||||
|
}
|
||||||
|
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRedisClusterInfoHandlerFunc(client *redis.ClusterClient, inspector *asynq.Inspector) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := context.Background()
|
||||||
|
rawClusterInfo, err := client.ClusterInfo(ctx).Result()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
info := parseRedisInfo(rawClusterInfo)
|
||||||
|
rawClusterNodes, err := client.ClusterNodes(ctx).Result()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
queues, err := inspector.Queues()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var queueLocations []*QueueLocationInfo
|
||||||
|
for _, qname := range queues {
|
||||||
|
q := QueueLocationInfo{Queue: qname}
|
||||||
|
q.KeySlot, err = inspector.ClusterKeySlot(qname)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
nodes, err := inspector.ClusterNodes(qname)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, n := range nodes {
|
||||||
|
q.Nodes = append(q.Nodes, n.Addr)
|
||||||
|
}
|
||||||
|
queueLocations = append(queueLocations, &q)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := RedisInfoResponse{
|
||||||
|
Addr: strings.Join(client.Options().Addrs, ","),
|
||||||
|
Info: info,
|
||||||
|
RawInfo: rawClusterInfo,
|
||||||
|
Cluster: true,
|
||||||
|
RawClusterNodes: rawClusterNodes,
|
||||||
|
QueueLocations: queueLocations,
|
||||||
}
|
}
|
||||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
@ -80,6 +80,18 @@ export interface RedisInfoResponse {
|
|||||||
address: string;
|
address: string;
|
||||||
info: RedisInfo;
|
info: RedisInfo;
|
||||||
raw_info: string;
|
raw_info: string;
|
||||||
|
cluster: boolean;
|
||||||
|
|
||||||
|
// following fields are set only when cluster=true
|
||||||
|
raw_cluster_nodes: string;
|
||||||
|
queue_locations: QueueLocation[] | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Describes location of a queue in cluster.
|
||||||
|
export interface QueueLocation {
|
||||||
|
queue: string; // queue name
|
||||||
|
keyslot: number; // cluster keyslot
|
||||||
|
nodes: string[]; // node addresses
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return value from redis INFO command.
|
// Return value from redis INFO command.
|
||||||
|
48
ui/src/components/QueueLocationTable.tsx
Normal file
48
ui/src/components/QueueLocationTable.tsx
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { makeStyles } from "@material-ui/core/styles";
|
||||||
|
import Table from "@material-ui/core/Table";
|
||||||
|
import TableBody from "@material-ui/core/TableBody";
|
||||||
|
import TableCell from "@material-ui/core/TableCell";
|
||||||
|
import TableContainer from "@material-ui/core/TableContainer";
|
||||||
|
import TableHead from "@material-ui/core/TableHead";
|
||||||
|
import TableRow from "@material-ui/core/TableRow";
|
||||||
|
import { QueueLocation } from "../api";
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme) => ({
|
||||||
|
table: {
|
||||||
|
minWidth: 650,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
queueLocations: QueueLocation[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function QueueLocationTable(props: Props) {
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableContainer>
|
||||||
|
<Table className={classes.table} aria-label="queue location table">
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>Queue</TableCell>
|
||||||
|
<TableCell>KeySlot</TableCell>
|
||||||
|
<TableCell>Node Addresses</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{props.queueLocations.map((loc) => (
|
||||||
|
<TableRow key={loc.queue}>
|
||||||
|
<TableCell component="th" scope="row">
|
||||||
|
{loc.queue}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>{loc.keyslot}</TableCell>
|
||||||
|
<TableCell>{loc.nodes.join(", ")}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
);
|
||||||
|
}
|
@ -4,7 +4,7 @@ import {
|
|||||||
GET_REDIS_INFO_SUCCESS,
|
GET_REDIS_INFO_SUCCESS,
|
||||||
RedisInfoActionTypes,
|
RedisInfoActionTypes,
|
||||||
} from "../actions/redisInfoActions";
|
} from "../actions/redisInfoActions";
|
||||||
import { RedisInfo } from "../api";
|
import { QueueLocation, RedisInfo } from "../api";
|
||||||
|
|
||||||
interface RedisInfoState {
|
interface RedisInfoState {
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
@ -12,6 +12,9 @@ interface RedisInfoState {
|
|||||||
address: string;
|
address: string;
|
||||||
data: RedisInfo | null;
|
data: RedisInfo | null;
|
||||||
rawData: string | null;
|
rawData: string | null;
|
||||||
|
cluster: boolean;
|
||||||
|
rawClusterNodes: string | null;
|
||||||
|
queueLocations: QueueLocation[] | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: RedisInfoState = {
|
const initialState: RedisInfoState = {
|
||||||
@ -20,6 +23,9 @@ const initialState: RedisInfoState = {
|
|||||||
address: "",
|
address: "",
|
||||||
data: null,
|
data: null,
|
||||||
rawData: null,
|
rawData: null,
|
||||||
|
cluster: false,
|
||||||
|
rawClusterNodes: null,
|
||||||
|
queueLocations: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function redisInfoReducer(
|
export default function redisInfoReducer(
|
||||||
@ -47,6 +53,9 @@ export default function redisInfoReducer(
|
|||||||
address: action.payload.address,
|
address: action.payload.address,
|
||||||
data: action.payload.info,
|
data: action.payload.info,
|
||||||
rawData: action.payload.raw_info,
|
rawData: action.payload.raw_info,
|
||||||
|
cluster: action.payload.cluster,
|
||||||
|
rawClusterNodes: action.payload.raw_cluster_nodes,
|
||||||
|
queueLocations: action.payload.queue_locations,
|
||||||
};
|
};
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -13,6 +13,9 @@ import { getRedisInfoAsync } from "../actions/redisInfoActions";
|
|||||||
import { usePolling } from "../hooks";
|
import { usePolling } from "../hooks";
|
||||||
import { AppState } from "../store";
|
import { AppState } from "../store";
|
||||||
import { timeAgoUnix } from "../utils";
|
import { timeAgoUnix } from "../utils";
|
||||||
|
import { RedisInfo } from "../api";
|
||||||
|
import QueueLocationTable from "../components/QueueLocationTable";
|
||||||
|
import Link from "@material-ui/core/Link";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
container: {
|
container: {
|
||||||
@ -28,6 +31,9 @@ function mapStateToProps(state: AppState) {
|
|||||||
redisInfo: state.redis.data,
|
redisInfo: state.redis.data,
|
||||||
redisAddress: state.redis.address,
|
redisAddress: state.redis.address,
|
||||||
redisInfoRaw: state.redis.rawData,
|
redisInfoRaw: state.redis.rawData,
|
||||||
|
redisClusterEnabled: state.redis.cluster,
|
||||||
|
redisClusterNodesRaw: state.redis.rawClusterNodes,
|
||||||
|
queueLocations: state.redis.queueLocations,
|
||||||
pollInterval: state.settings.pollInterval,
|
pollInterval: state.settings.pollInterval,
|
||||||
themePreference: state.settings.themePreference,
|
themePreference: state.settings.themePreference,
|
||||||
};
|
};
|
||||||
@ -38,7 +44,15 @@ type Props = ConnectedProps<typeof connector>;
|
|||||||
|
|
||||||
function RedisInfoView(props: Props) {
|
function RedisInfoView(props: Props) {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const { pollInterval, getRedisInfoAsync, redisInfo, redisInfoRaw } = props;
|
const {
|
||||||
|
pollInterval,
|
||||||
|
getRedisInfoAsync,
|
||||||
|
redisInfo,
|
||||||
|
redisInfoRaw,
|
||||||
|
redisClusterEnabled,
|
||||||
|
redisClusterNodesRaw,
|
||||||
|
queueLocations,
|
||||||
|
} = props;
|
||||||
usePolling(getRedisInfoAsync, pollInterval);
|
usePolling(getRedisInfoAsync, pollInterval);
|
||||||
|
|
||||||
// Metrics to show
|
// Metrics to show
|
||||||
@ -56,101 +70,60 @@ function RedisInfoView(props: Props) {
|
|||||||
<>
|
<>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Typography variant="h5" color="textPrimary">
|
<Typography variant="h5" color="textPrimary">
|
||||||
Redis Info
|
{redisClusterEnabled ? "Redis Cluster Info" : "Redis Info"}
|
||||||
</Typography>
|
|
||||||
<Typography variant="subtitle1" color="textSecondary">
|
|
||||||
Connected to: {props.redisAddress}
|
|
||||||
</Typography>
|
</Typography>
|
||||||
|
{!redisClusterEnabled && (
|
||||||
|
<Typography variant="subtitle1" color="textSecondary">
|
||||||
|
Connected to: {props.redisAddress}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
</Grid>
|
</Grid>
|
||||||
{redisInfo !== null && (
|
{queueLocations && queueLocations.length > 0 && (
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Typography variant="h6" color="textSecondary">
|
||||||
|
Queue Location in Cluster
|
||||||
|
</Typography>
|
||||||
|
<QueueLocationTable queueLocations={queueLocations} />
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
{redisClusterNodesRaw && (
|
||||||
<>
|
<>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Typography variant="h6" color="textSecondary">
|
<Typography variant="h6" color="textSecondary">
|
||||||
Server
|
<Link
|
||||||
|
href="https://redis.io/commands/cluster-nodes"
|
||||||
|
target="_"
|
||||||
|
>
|
||||||
|
CLUSTER NODES
|
||||||
|
</Link>{" "}
|
||||||
|
Command Output
|
||||||
</Typography>
|
</Typography>
|
||||||
|
<SyntaxHighlighter language="yaml">
|
||||||
|
{redisClusterNodesRaw}
|
||||||
|
</SyntaxHighlighter>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={3}>
|
|
||||||
<MetricCard
|
|
||||||
title="Version"
|
|
||||||
content={redisInfo.redis_version}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
<Grid item xs={3}>
|
|
||||||
<MetricCard
|
|
||||||
title="Uptime"
|
|
||||||
content={`${redisInfo.uptime_in_days} days`}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
<Grid item xs={6} />
|
|
||||||
<Grid item xs={12}>
|
|
||||||
<Typography variant="h6" color="textSecondary">
|
|
||||||
Memory
|
|
||||||
</Typography>
|
|
||||||
</Grid>
|
|
||||||
<Grid item xs={3}>
|
|
||||||
<MetricCard
|
|
||||||
title="Used Memory"
|
|
||||||
content={redisInfo.used_memory_human}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
<Grid item xs={3}>
|
|
||||||
<MetricCard
|
|
||||||
title="Peak Memory Used"
|
|
||||||
content={redisInfo.used_memory_peak_human}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
<Grid item xs={3}>
|
|
||||||
<MetricCard
|
|
||||||
title="Memory Fragmentation Ratio"
|
|
||||||
content={redisInfo.mem_fragmentation_ratio}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
<Grid item xs={3} />
|
|
||||||
<Grid item xs={12}>
|
|
||||||
<Typography variant="h6" color="textSecondary">
|
|
||||||
Connections
|
|
||||||
</Typography>
|
|
||||||
</Grid>
|
|
||||||
<Grid item xs={3}>
|
|
||||||
<MetricCard
|
|
||||||
title="Connected Clients"
|
|
||||||
content={redisInfo.connected_clients}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
<Grid item xs={3}>
|
|
||||||
<MetricCard
|
|
||||||
title="Connected Replicas"
|
|
||||||
content={redisInfo.connected_slaves}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
<Grid item xs={6} />
|
|
||||||
<Grid item xs={12}>
|
|
||||||
<Typography variant="h6" color="textSecondary">
|
|
||||||
Persistence
|
|
||||||
</Typography>
|
|
||||||
</Grid>
|
|
||||||
<Grid item xs={3}>
|
|
||||||
<MetricCard
|
|
||||||
title="Last Save to Disk"
|
|
||||||
content={timeAgoUnix(
|
|
||||||
parseInt(redisInfo.rdb_last_save_time)
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
<Grid item xs={3}>
|
|
||||||
<MetricCard
|
|
||||||
title="Number of Changes Since Last Dump"
|
|
||||||
content={redisInfo.rdb_changes_since_last_save}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
<Grid item xs={6} />
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{redisInfoRaw !== null && (
|
{redisInfo && !redisClusterEnabled && (
|
||||||
|
<RedisMetricCards redisInfo={redisInfo} />
|
||||||
|
)}
|
||||||
|
{redisInfoRaw && (
|
||||||
<>
|
<>
|
||||||
<Grid item xs={6}>
|
<Grid item xs={6}>
|
||||||
<Typography variant="h6" color="textSecondary">
|
<Typography variant="h6" color="textSecondary">
|
||||||
INFO Command Output
|
{redisClusterEnabled ? (
|
||||||
|
<Link
|
||||||
|
href="https://redis.io/commands/cluster-info"
|
||||||
|
target="_"
|
||||||
|
>
|
||||||
|
CLUSTER INFO
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<Link href="https://redis.io/commands/info" target="_">
|
||||||
|
INFO
|
||||||
|
</Link>
|
||||||
|
)}{" "}
|
||||||
|
Command Output
|
||||||
</Typography>
|
</Typography>
|
||||||
<SyntaxHighlighter language="yaml">
|
<SyntaxHighlighter language="yaml">
|
||||||
{redisInfoRaw}
|
{redisInfoRaw}
|
||||||
@ -173,6 +146,86 @@ function RedisInfoView(props: Props) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function RedisMetricCards(props: { redisInfo: RedisInfo }) {
|
||||||
|
const { redisInfo } = props;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Typography variant="h6" color="textSecondary">
|
||||||
|
Server
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={3}>
|
||||||
|
<MetricCard title="Version" content={redisInfo.redis_version} />
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={3}>
|
||||||
|
<MetricCard
|
||||||
|
title="Uptime"
|
||||||
|
content={`${redisInfo.uptime_in_days} days`}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={6} />
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Typography variant="h6" color="textSecondary">
|
||||||
|
Memory
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={3}>
|
||||||
|
<MetricCard title="Used Memory" content={redisInfo.used_memory_human} />
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={3}>
|
||||||
|
<MetricCard
|
||||||
|
title="Peak Memory Used"
|
||||||
|
content={redisInfo.used_memory_peak_human}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={3}>
|
||||||
|
<MetricCard
|
||||||
|
title="Memory Fragmentation Ratio"
|
||||||
|
content={redisInfo.mem_fragmentation_ratio}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={3} />
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Typography variant="h6" color="textSecondary">
|
||||||
|
Connections
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={3}>
|
||||||
|
<MetricCard
|
||||||
|
title="Connected Clients"
|
||||||
|
content={redisInfo.connected_clients}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={3}>
|
||||||
|
<MetricCard
|
||||||
|
title="Connected Replicas"
|
||||||
|
content={redisInfo.connected_slaves}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={6} />
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Typography variant="h6" color="textSecondary">
|
||||||
|
Persistence
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={3}>
|
||||||
|
<MetricCard
|
||||||
|
title="Last Save to Disk"
|
||||||
|
content={timeAgoUnix(parseInt(redisInfo.rdb_last_save_time))}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={3}>
|
||||||
|
<MetricCard
|
||||||
|
title="Number of Changes Since Last Dump"
|
||||||
|
content={redisInfo.rdb_changes_since_last_save}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={6} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
interface MetricCardProps {
|
interface MetricCardProps {
|
||||||
title: string;
|
title: string;
|
||||||
content: string;
|
content: string;
|
||||||
|
Loading…
Reference in New Issue
Block a user