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 {
|
type cmdHistoryItem struct {
|
||||||
timestamp int64
|
Timestamp int64 `json:"timestamp"`
|
||||||
Time string `json:"time"`
|
|
||||||
Server string `json:"server"`
|
Server string `json:"server"`
|
||||||
Cmd string `json:"cmd"`
|
Cmd string `json:"cmd"`
|
||||||
|
Cost int64 `json:"cost"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type connectionService struct {
|
type connectionService struct {
|
||||||
|
@ -260,17 +260,17 @@ func (c *connectionService) getRedisClient(connName string, db int) (*redis.Clie
|
||||||
ReadTimeout: time.Duration(selConn.ExecTimeout) * time.Second,
|
ReadTimeout: time.Duration(selConn.ExecTimeout) * time.Second,
|
||||||
WriteTimeout: 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()
|
now := time.Now()
|
||||||
last := strings.LastIndex(cmd, ":")
|
//last := strings.LastIndex(cmd, ":")
|
||||||
if last != -1 {
|
//if last != -1 {
|
||||||
cmd = cmd[:last]
|
// cmd = cmd[:last]
|
||||||
}
|
//}
|
||||||
c.cmdHistory = append(c.cmdHistory, cmdHistoryItem{
|
c.cmdHistory = append(c.cmdHistory, cmdHistoryItem{
|
||||||
timestamp: now.UnixMilli(),
|
Timestamp: now.UnixMilli(),
|
||||||
Time: now.Format("2006-01-02 15:04:05"),
|
|
||||||
Server: connName,
|
Server: connName,
|
||||||
Cmd: cmd,
|
Cmd: cmd,
|
||||||
|
Cost: cost,
|
||||||
})
|
})
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
@ -287,7 +287,7 @@ func (c *connectionService) getRedisClient(connName string, db int) (*redis.Clie
|
||||||
}
|
}
|
||||||
|
|
||||||
if db >= 0 {
|
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
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,12 +2,15 @@ package redis
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"github.com/redis/go-redis/v9"
|
"github.com/redis/go-redis/v9"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type execCallback func(string)
|
type execCallback func(string, int64)
|
||||||
|
|
||||||
type LogHook struct {
|
type LogHook struct {
|
||||||
name string
|
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 {
|
func (l *LogHook) DialHook(next redis.DialHook) redis.DialHook {
|
||||||
return func(ctx context.Context, network, addr string) (net.Conn, error) {
|
return func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
return next(ctx, network, addr)
|
return next(ctx, network, addr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *LogHook) ProcessHook(next redis.ProcessHook) redis.ProcessHook {
|
func (l *LogHook) ProcessHook(next redis.ProcessHook) redis.ProcessHook {
|
||||||
return func(ctx context.Context, cmd redis.Cmder) error {
|
return func(ctx context.Context, cmd redis.Cmder) error {
|
||||||
log.Println(cmd)
|
log.Println(cmd)
|
||||||
|
t := time.Now()
|
||||||
|
err := next(ctx, cmd)
|
||||||
if l.cmdExec != nil {
|
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 {
|
func (l *LogHook) ProcessPipelineHook(next redis.ProcessPipelineHook) redis.ProcessPipelineHook {
|
||||||
return func(ctx context.Context, cmds []redis.Cmder) error {
|
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 {
|
for _, cmd := range cmds {
|
||||||
log.Println("pipeline: ", cmd)
|
log.Println("pipeline: ", cmd)
|
||||||
if l.cmdExec != nil {
|
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",
|
"name": "frontend",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"dayjs": "^1.11.9",
|
||||||
"highlight.js": "^11.8.0",
|
"highlight.js": "^11.8.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"pinia": "^2.1.4",
|
"pinia": "^2.1.4",
|
||||||
|
@ -963,6 +964,11 @@
|
||||||
"date-fns": ">=2.0.0"
|
"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": {
|
"node_modules/debug": {
|
||||||
"version": "4.3.4",
|
"version": "4.3.4",
|
||||||
"resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz",
|
"resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz",
|
||||||
|
@ -2694,6 +2700,11 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
|
"dayjs": {
|
||||||
|
"version": "1.11.9",
|
||||||
|
"resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.9.tgz",
|
||||||
|
"integrity": "sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA=="
|
||||||
|
},
|
||||||
"debug": {
|
"debug": {
|
||||||
"version": "4.3.4",
|
"version": "4.3.4",
|
||||||
"resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz",
|
"resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz",
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"dayjs": "^1.11.9",
|
||||||
"highlight.js": "^11.8.0",
|
"highlight.js": "^11.8.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"pinia": "^2.1.4",
|
"pinia": "^2.1.4",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed } from 'vue'
|
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({
|
const props = defineProps({
|
||||||
type: {
|
type: {
|
||||||
|
@ -10,22 +10,23 @@ const props = defineProps({
|
||||||
},
|
},
|
||||||
default: 'STRING',
|
default: 'STRING',
|
||||||
},
|
},
|
||||||
color: {
|
bordered: Boolean,
|
||||||
type: String,
|
|
||||||
default: 'white',
|
|
||||||
},
|
|
||||||
size: String,
|
size: String,
|
||||||
})
|
})
|
||||||
|
|
||||||
const backgroundColor = computed(() => {
|
const fontColor = computed(() => {
|
||||||
return typesColor[props.type]
|
return typesColor[props.type]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const backgroundColor = computed(() => {
|
||||||
|
return typesBgColor[props.type]
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<n-tag
|
<n-tag
|
||||||
:bordered="false"
|
:bordered="props.bordered"
|
||||||
:color="{ color: backgroundColor, textColor: props.color }"
|
:color="{ color: backgroundColor, borderColor: fontColor, textColor: fontColor }"
|
||||||
:size="props.size"
|
:size="props.size"
|
||||||
:class="[props.size === 'small' ? 'redis-type-tag-small' : 'redis-type-tag']"
|
:class="[props.size === 'small' ? 'redis-type-tag-small' : 'redis-type-tag']"
|
||||||
strong
|
strong
|
||||||
|
|
|
@ -5,6 +5,7 @@ import Refresh from '../icons/Refresh.vue'
|
||||||
import useConnectionStore from '../../stores/connections.js'
|
import useConnectionStore from '../../stores/connections.js'
|
||||||
import { map, uniqBy } from 'lodash'
|
import { map, uniqBy } from 'lodash'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
|
||||||
const connectionStore = useConnectionStore()
|
const connectionStore = useConnectionStore()
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
|
@ -75,10 +76,15 @@ onActivated(() => {
|
||||||
:columns="[
|
:columns="[
|
||||||
{
|
{
|
||||||
title: $t('exec_time'),
|
title: $t('exec_time'),
|
||||||
key: 'time',
|
key: 'timestamp',
|
||||||
defaultSortOrder: 'ascend',
|
defaultSortOrder: 'ascend',
|
||||||
sorter: 'default',
|
sorter: 'default',
|
||||||
width: 180,
|
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'),
|
title: $t('server'),
|
||||||
|
@ -88,16 +94,35 @@ onActivated(() => {
|
||||||
return value === '' || row.server === value.toString()
|
return value === '' || row.server === value.toString()
|
||||||
},
|
},
|
||||||
width: 150,
|
width: 150,
|
||||||
|
align: 'center',
|
||||||
|
titleAlign: 'center',
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: $t('cmd'),
|
title: $t('cmd'),
|
||||||
key: 'cmd',
|
key: 'cmd',
|
||||||
|
titleAlign: 'center',
|
||||||
filterOptionValue: data.keyword,
|
filterOptionValue: data.keyword,
|
||||||
|
resizable: true,
|
||||||
filter(value, row) {
|
filter(value, row) {
|
||||||
return value === '' || !!~row.cmd.indexOf(value.toString())
|
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"
|
:data="data.history"
|
||||||
flex-height
|
flex-height
|
||||||
|
|
|
@ -20,7 +20,7 @@ import ToggleServer from '../icons/ToggleServer.vue'
|
||||||
import Unlink from '../icons/Unlink.vue'
|
import Unlink from '../icons/Unlink.vue'
|
||||||
import Filter from '../icons/Filter.vue'
|
import Filter from '../icons/Filter.vue'
|
||||||
import Close from '../icons/Close.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({
|
const props = defineProps({
|
||||||
server: String,
|
server: String,
|
||||||
|
@ -299,7 +299,10 @@ const renderSuffix = ({ option }) => {
|
||||||
size: 'small',
|
size: 'small',
|
||||||
closable: true,
|
closable: true,
|
||||||
bordered: false,
|
bordered: false,
|
||||||
color: { color: typesColor[typeFilter], textColor: 'white' },
|
color: {
|
||||||
|
color: typesBgColor[typeFilter],
|
||||||
|
textColor: typesColor[typeFilter],
|
||||||
|
},
|
||||||
onClose: () => {
|
onClose: () => {
|
||||||
// remove type filter
|
// remove type filter
|
||||||
connectionStore.setKeyFilter(server, db, matchPattern)
|
connectionStore.setKeyFilter(server, db, matchPattern)
|
||||||
|
|
|
@ -7,11 +7,19 @@ export const types = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const typesColor = {
|
export const typesColor = {
|
||||||
[types.STRING]: '#5A96E3',
|
[types.STRING]: '#8256DC',
|
||||||
[types.HASH]: '#9575DE',
|
[types.HASH]: '#2983ED',
|
||||||
[types.LIST]: '#7A9D54',
|
[types.LIST]: '#26A15E',
|
||||||
[types.SET]: '#F3AA60',
|
[types.SET]: '#EE9F33',
|
||||||
[types.ZSET]: '#FF6666',
|
[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]))
|
// export const typesName = Object.fromEntries(Object.entries(types).map(([key, value]) => [key, value.name]))
|
||||||
|
|
|
@ -153,5 +153,6 @@
|
||||||
"filter_server": "Filter Server",
|
"filter_server": "Filter Server",
|
||||||
"filter_keyword": "Filter Keyword",
|
"filter_keyword": "Filter Keyword",
|
||||||
"exec_time": "Exec Time",
|
"exec_time": "Exec Time",
|
||||||
"cmd": "Command"
|
"cmd": "Command",
|
||||||
|
"cost_time": "Cost"
|
||||||
}
|
}
|
||||||
|
|
|
@ -155,5 +155,6 @@
|
||||||
"filter_server": "筛选服务器",
|
"filter_server": "筛选服务器",
|
||||||
"filter_keyword": "筛选关键字",
|
"filter_keyword": "筛选关键字",
|
||||||
"exec_time": "执行时间",
|
"exec_time": "执行时间",
|
||||||
"cmd": "命令"
|
"cmd": "命令",
|
||||||
|
"cost_time": "耗时"
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,12 @@ import { createI18n } from 'vue-i18n'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import { lang } from './langs'
|
import { lang } from './langs'
|
||||||
import './style.scss'
|
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)
|
const app = createApp(App)
|
||||||
app.use(
|
app.use(
|
||||||
|
|
|
@ -73,6 +73,7 @@ const useConnectionStore = defineStore('connections', {
|
||||||
* @property {string} time
|
* @property {string} time
|
||||||
* @property {string} server
|
* @property {string} server
|
||||||
* @property {string} cmd
|
* @property {string} cmd
|
||||||
|
* @property {number} cost
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -167,7 +168,7 @@ const useConnectionStore = defineStore('connections', {
|
||||||
/**
|
/**
|
||||||
* get connection by name from local profile
|
* get connection by name from local profile
|
||||||
* @param name
|
* @param name
|
||||||
* @returns {Promise<{}|null>}
|
* @returns {Promise<ConnectionProfile|null>}
|
||||||
*/
|
*/
|
||||||
async getConnectionProfile(name) {
|
async getConnectionProfile(name) {
|
||||||
try {
|
try {
|
||||||
|
|
Loading…
Reference in New Issue