perf: display command cost time in log list table
perf: beautify color of redis type tag
This commit is contained in:
parent
76679496c6
commit
ab4c78c3d7
|
@ -17,10 +17,10 @@ import (
|
|||
)
|
||||
|
||||
type cmdHistoryItem struct {
|
||||
timestamp int64
|
||||
Time string `json:"time"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Server string `json:"server"`
|
||||
Cmd string `json:"cmd"`
|
||||
Cost int64 `json:"cost"`
|
||||
}
|
||||
|
||||
type connectionService struct {
|
||||
|
@ -260,17 +260,17 @@ func (c *connectionService) getRedisClient(connName string, db int) (*redis.Clie
|
|||
ReadTimeout: time.Duration(selConn.ExecTimeout) * time.Second,
|
||||
WriteTimeout: time.Duration(selConn.ExecTimeout) * time.Second,
|
||||
})
|
||||
rdb.AddHook(redis2.NewHook(connName, func(cmd string) {
|
||||
rdb.AddHook(redis2.NewHook(connName, func(cmd string, cost int64) {
|
||||
now := time.Now()
|
||||
last := strings.LastIndex(cmd, ":")
|
||||
if last != -1 {
|
||||
cmd = cmd[:last]
|
||||
}
|
||||
//last := strings.LastIndex(cmd, ":")
|
||||
//if last != -1 {
|
||||
// cmd = cmd[:last]
|
||||
//}
|
||||
c.cmdHistory = append(c.cmdHistory, cmdHistoryItem{
|
||||
timestamp: now.UnixMilli(),
|
||||
Time: now.Format("2006-01-02 15:04:05"),
|
||||
Timestamp: now.UnixMilli(),
|
||||
Server: connName,
|
||||
Cmd: cmd,
|
||||
Cost: cost,
|
||||
})
|
||||
}))
|
||||
|
||||
|
@ -287,7 +287,7 @@ func (c *connectionService) getRedisClient(connName string, db int) (*redis.Clie
|
|||
}
|
||||
|
||||
if db >= 0 {
|
||||
if err := rdb.Do(ctx, "SELECT", strconv.Itoa(db)).Err(); err != nil {
|
||||
if err := rdb.Do(ctx, "select", strconv.Itoa(db)).Err(); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,12 +2,15 @@ package redis
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"log"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type execCallback func(string)
|
||||
type execCallback func(string, int64)
|
||||
|
||||
type LogHook struct {
|
||||
name string
|
||||
|
@ -21,28 +24,93 @@ func NewHook(name string, cmdExec execCallback) *LogHook {
|
|||
}
|
||||
}
|
||||
|
||||
func appendArg(b []byte, v interface{}) []byte {
|
||||
switch v := v.(type) {
|
||||
case nil:
|
||||
return append(b, "<nil>"...)
|
||||
case string:
|
||||
return append(b, []byte(v)...)
|
||||
case []byte:
|
||||
return append(b, v...)
|
||||
case int:
|
||||
return strconv.AppendInt(b, int64(v), 10)
|
||||
case int8:
|
||||
return strconv.AppendInt(b, int64(v), 10)
|
||||
case int16:
|
||||
return strconv.AppendInt(b, int64(v), 10)
|
||||
case int32:
|
||||
return strconv.AppendInt(b, int64(v), 10)
|
||||
case int64:
|
||||
return strconv.AppendInt(b, v, 10)
|
||||
case uint:
|
||||
return strconv.AppendUint(b, uint64(v), 10)
|
||||
case uint8:
|
||||
return strconv.AppendUint(b, uint64(v), 10)
|
||||
case uint16:
|
||||
return strconv.AppendUint(b, uint64(v), 10)
|
||||
case uint32:
|
||||
return strconv.AppendUint(b, uint64(v), 10)
|
||||
case uint64:
|
||||
return strconv.AppendUint(b, v, 10)
|
||||
case float32:
|
||||
return strconv.AppendFloat(b, float64(v), 'f', -1, 64)
|
||||
case float64:
|
||||
return strconv.AppendFloat(b, v, 'f', -1, 64)
|
||||
case bool:
|
||||
if v {
|
||||
return append(b, "true"...)
|
||||
}
|
||||
return append(b, "false"...)
|
||||
case time.Time:
|
||||
return v.AppendFormat(b, time.RFC3339Nano)
|
||||
default:
|
||||
return append(b, fmt.Sprint(v)...)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *LogHook) DialHook(next redis.DialHook) redis.DialHook {
|
||||
return func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
return next(ctx, network, addr)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *LogHook) ProcessHook(next redis.ProcessHook) redis.ProcessHook {
|
||||
return func(ctx context.Context, cmd redis.Cmder) error {
|
||||
log.Println(cmd)
|
||||
t := time.Now()
|
||||
err := next(ctx, cmd)
|
||||
if l.cmdExec != nil {
|
||||
l.cmdExec(cmd.String())
|
||||
b := make([]byte, 0, 64)
|
||||
for i, arg := range cmd.Args() {
|
||||
if i > 0 {
|
||||
b = append(b, ' ')
|
||||
}
|
||||
return next(ctx, cmd)
|
||||
b = appendArg(b, arg)
|
||||
}
|
||||
l.cmdExec(string(b), time.Since(t).Milliseconds())
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func (l *LogHook) ProcessPipelineHook(next redis.ProcessPipelineHook) redis.ProcessPipelineHook {
|
||||
return func(ctx context.Context, cmds []redis.Cmder) error {
|
||||
t := time.Now()
|
||||
err := next(ctx, cmds)
|
||||
cost := time.Since(t).Milliseconds()
|
||||
for _, cmd := range cmds {
|
||||
log.Println("pipeline: ", cmd)
|
||||
if l.cmdExec != nil {
|
||||
l.cmdExec(cmd.String())
|
||||
b := make([]byte, 0, 64)
|
||||
for i, arg := range cmd.Args() {
|
||||
if i > 0 {
|
||||
b = append(b, ' ')
|
||||
}
|
||||
b = appendArg(b, arg)
|
||||
}
|
||||
l.cmdExec(string(b), cost)
|
||||
}
|
||||
}
|
||||
return next(ctx, cmds)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
"name": "frontend",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"dayjs": "^1.11.9",
|
||||
"highlight.js": "^11.8.0",
|
||||
"lodash": "^4.17.21",
|
||||
"pinia": "^2.1.4",
|
||||
|
@ -963,6 +964,11 @@
|
|||
"date-fns": ">=2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dayjs": {
|
||||
"version": "1.11.9",
|
||||
"resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.9.tgz",
|
||||
"integrity": "sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA=="
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz",
|
||||
|
@ -2694,6 +2700,11 @@
|
|||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"dayjs": {
|
||||
"version": "1.11.9",
|
||||
"resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.9.tgz",
|
||||
"integrity": "sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA=="
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz",
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"dayjs": "^1.11.9",
|
||||
"highlight.js": "^11.8.0",
|
||||
"lodash": "^4.17.21",
|
||||
"pinia": "^2.1.4",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import { typesColor, validType } from '../../consts/support_redis_type.js'
|
||||
import { typesBgColor, typesColor, validType } from '../../consts/support_redis_type.js'
|
||||
|
||||
const props = defineProps({
|
||||
type: {
|
||||
|
@ -10,22 +10,23 @@ const props = defineProps({
|
|||
},
|
||||
default: 'STRING',
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: 'white',
|
||||
},
|
||||
bordered: Boolean,
|
||||
size: String,
|
||||
})
|
||||
|
||||
const backgroundColor = computed(() => {
|
||||
const fontColor = computed(() => {
|
||||
return typesColor[props.type]
|
||||
})
|
||||
|
||||
const backgroundColor = computed(() => {
|
||||
return typesBgColor[props.type]
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-tag
|
||||
:bordered="false"
|
||||
:color="{ color: backgroundColor, textColor: props.color }"
|
||||
:bordered="props.bordered"
|
||||
:color="{ color: backgroundColor, borderColor: fontColor, textColor: fontColor }"
|
||||
:size="props.size"
|
||||
:class="[props.size === 'small' ? 'redis-type-tag-small' : 'redis-type-tag']"
|
||||
strong
|
||||
|
|
|
@ -5,6 +5,7 @@ import Refresh from '../icons/Refresh.vue'
|
|||
import useConnectionStore from '../../stores/connections.js'
|
||||
import { map, uniqBy } from 'lodash'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
const connectionStore = useConnectionStore()
|
||||
const i18n = useI18n()
|
||||
|
@ -75,10 +76,15 @@ onActivated(() => {
|
|||
:columns="[
|
||||
{
|
||||
title: $t('exec_time'),
|
||||
key: 'time',
|
||||
key: 'timestamp',
|
||||
defaultSortOrder: 'ascend',
|
||||
sorter: 'default',
|
||||
width: 180,
|
||||
align: 'center',
|
||||
titleAlign: 'center',
|
||||
render({ timestamp }, index) {
|
||||
return dayjs(timestamp).locale('zh-cn').format('YYYY-MM-DD hh:mm:ss')
|
||||
},
|
||||
},
|
||||
{
|
||||
title: $t('server'),
|
||||
|
@ -88,16 +94,35 @@ onActivated(() => {
|
|||
return value === '' || row.server === value.toString()
|
||||
},
|
||||
width: 150,
|
||||
align: 'center',
|
||||
titleAlign: 'center',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: $t('cmd'),
|
||||
key: 'cmd',
|
||||
titleAlign: 'center',
|
||||
filterOptionValue: data.keyword,
|
||||
resizable: true,
|
||||
filter(value, row) {
|
||||
return value === '' || !!~row.cmd.indexOf(value.toString())
|
||||
},
|
||||
},
|
||||
{
|
||||
title: $t('cost_time'),
|
||||
key: 'cost',
|
||||
width: 100,
|
||||
align: 'center',
|
||||
titleAlign: 'center',
|
||||
render({ cost }, index) {
|
||||
const ms = dayjs.duration(cost).asMilliseconds()
|
||||
if (ms < 1000) {
|
||||
return `${ms} ms`
|
||||
} else {
|
||||
return `${Math.floor(ms / 1000)} s`
|
||||
}
|
||||
},
|
||||
},
|
||||
]"
|
||||
:data="data.history"
|
||||
flex-height
|
||||
|
|
|
@ -20,7 +20,7 @@ import ToggleServer from '../icons/ToggleServer.vue'
|
|||
import Unlink from '../icons/Unlink.vue'
|
||||
import Filter from '../icons/Filter.vue'
|
||||
import Close from '../icons/Close.vue'
|
||||
import { typesColor } from '../../consts/support_redis_type.js'
|
||||
import { typesBgColor, typesColor } from '../../consts/support_redis_type.js'
|
||||
|
||||
const props = defineProps({
|
||||
server: String,
|
||||
|
@ -299,7 +299,10 @@ const renderSuffix = ({ option }) => {
|
|||
size: 'small',
|
||||
closable: true,
|
||||
bordered: false,
|
||||
color: { color: typesColor[typeFilter], textColor: 'white' },
|
||||
color: {
|
||||
color: typesBgColor[typeFilter],
|
||||
textColor: typesColor[typeFilter],
|
||||
},
|
||||
onClose: () => {
|
||||
// remove type filter
|
||||
connectionStore.setKeyFilter(server, db, matchPattern)
|
||||
|
|
|
@ -7,11 +7,19 @@ export const types = {
|
|||
}
|
||||
|
||||
export const typesColor = {
|
||||
[types.STRING]: '#5A96E3',
|
||||
[types.HASH]: '#9575DE',
|
||||
[types.LIST]: '#7A9D54',
|
||||
[types.SET]: '#F3AA60',
|
||||
[types.ZSET]: '#FF6666',
|
||||
[types.STRING]: '#8256DC',
|
||||
[types.HASH]: '#2983ED',
|
||||
[types.LIST]: '#26A15E',
|
||||
[types.SET]: '#EE9F33',
|
||||
[types.ZSET]: '#CE3352',
|
||||
}
|
||||
|
||||
export const typesBgColor = {
|
||||
[types.STRING]: '#F2EDFB',
|
||||
[types.HASH]: '#E4F0FC',
|
||||
[types.LIST]: '#E3F3EB',
|
||||
[types.SET]: '#FDF1DF',
|
||||
[types.ZSET]: '#FAEAED',
|
||||
}
|
||||
|
||||
// export const typesName = Object.fromEntries(Object.entries(types).map(([key, value]) => [key, value.name]))
|
||||
|
|
|
@ -153,5 +153,6 @@
|
|||
"filter_server": "Filter Server",
|
||||
"filter_keyword": "Filter Keyword",
|
||||
"exec_time": "Exec Time",
|
||||
"cmd": "Command"
|
||||
"cmd": "Command",
|
||||
"cost_time": "Cost"
|
||||
}
|
||||
|
|
|
@ -155,5 +155,6 @@
|
|||
"filter_server": "筛选服务器",
|
||||
"filter_keyword": "筛选关键字",
|
||||
"exec_time": "执行时间",
|
||||
"cmd": "命令"
|
||||
"cmd": "命令",
|
||||
"cost_time": "耗时"
|
||||
}
|
||||
|
|
|
@ -4,6 +4,12 @@ import { createI18n } from 'vue-i18n'
|
|||
import App from './App.vue'
|
||||
import { lang } from './langs'
|
||||
import './style.scss'
|
||||
import dayjs from 'dayjs'
|
||||
import duration from 'dayjs/plugin/duration'
|
||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||
|
||||
dayjs.extend(duration)
|
||||
dayjs.extend(relativeTime)
|
||||
|
||||
const app = createApp(App)
|
||||
app.use(
|
||||
|
|
|
@ -73,6 +73,7 @@ const useConnectionStore = defineStore('connections', {
|
|||
* @property {string} time
|
||||
* @property {string} server
|
||||
* @property {string} cmd
|
||||
* @property {number} cost
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -167,7 +168,7 @@ const useConnectionStore = defineStore('connections', {
|
|||
/**
|
||||
* get connection by name from local profile
|
||||
* @param name
|
||||
* @returns {Promise<{}|null>}
|
||||
* @returns {Promise<ConnectionProfile|null>}
|
||||
*/
|
||||
async getConnectionProfile(name) {
|
||||
try {
|
||||
|
|
Loading…
Reference in New Issue