├── .gitignore ├── LICENSE ├── README.md ├── client ├── dev.config.js ├── index.html ├── index.js ├── package.json ├── src │ ├── components │ │ ├── react-data-inline-block │ │ │ ├── index.js │ │ │ └── style.less │ │ ├── react-echarts │ │ │ ├── index.js │ │ │ └── style.less │ │ └── react-tab-info │ │ │ ├── index.js │ │ │ └── style.less │ ├── global │ │ ├── conf │ │ │ └── location.js │ │ ├── js │ │ │ ├── date.js │ │ │ └── request.js │ │ ├── layout │ │ │ ├── GlobalHeader │ │ │ │ ├── index.js │ │ │ │ └── style.less │ │ │ └── SideNav │ │ │ │ ├── index.js │ │ │ │ └── style.less │ │ └── less │ │ │ ├── color.less │ │ │ ├── global.less │ │ │ ├── mixins.less │ │ │ └── reset.less │ └── pages │ │ ├── dbInfo │ │ ├── index.js │ │ └── style.less │ │ ├── default │ │ ├── index.js │ │ └── style.less │ │ ├── memoryInfo │ │ ├── index.js │ │ └── style.less │ │ └── qpsInfo │ │ ├── index.js │ │ └── style.less └── webpack.config.js ├── package.json └── server ├── app.js ├── bin └── index.js ├── common ├── logger.js ├── middleware │ └── response-time.js ├── redis-client.js ├── response.js └── util │ └── index.js ├── config └── index.js ├── controller ├── instance-list-ctl.js └── instance-status-ctl.js ├── public ├── favicon.ico ├── index.html └── js │ └── build │ ├── 0.build.js │ ├── 1.build.js │ └── build.js ├── router ├── index.js ├── instance-list-router.js └── instance-status-router.js ├── schedule ├── create-redis-server-info.js └── index.js └── service ├── api └── redis.js ├── instance-service.js ├── memory-management-service.js └── qps-management-service.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | server/logs 3 | logs 4 | *.log 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 28 | node_modules 29 | client/node-modules 30 | *.sw* 31 | *.un~ 32 | .idea 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 John 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # redis-monitor 2 | koa+react+antd写的一个简单redis状态监控工具. 目前主要监控内存使用情况、命令处理情况及实例中keys的分布情况 3 | 4 | 5 | ## 技术栈 6 | 7 | ### 前端 8 | 打包工具: webpack 9 | 前端框架: react 10 | ui库: antd 11 | 12 | ### 后端 13 | node版本: >=7.6.0 14 | web框架: koa2 15 | 数据存储: redis 16 | 17 | ## 部署 18 | 19 | ### 前端 20 | 21 | client目录下 22 | 前端部分为页面展示(已打包在server/public/js/build目录下) 23 | 打包发布: 24 | cd client/ 25 | npm install . 26 | npm run dev (此命令会将项目打包到server/public/js/build目录下) 27 | 28 | 29 | ### 后端 30 | 后端启动分两部分(定时任务和服务接口) 31 | 32 | 开发环境下: 33 | npm installl . 34 | 定时任务启动收集数据: 35 | npm run dev 36 | 启动服务 37 | npm run dev-schedule 38 | 39 | 40 | -------------------------------------------------------------------------------- /client/dev.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var path =require('path'); 3 | var config = { 4 | entry: [ 5 | './index.js' 6 | ], 7 | output: { 8 | path: path.join(__dirname ,'../server/public/js/build'), 9 | //使用本系统静态资源地址(ip端口必须和server启动地址一致) 10 | publicPath: "http://127.0.0.1:12000/js/build/", 11 | filename: 'build.js' 12 | }, 13 | resolve: { 14 | extensions: ['.js', '.jsx', '.json', '.less'] 15 | }, 16 | module: { 17 | rules: [{ 18 | test: /\.js$/, 19 | exclude: /node_modules/, 20 | loader: 'babel-loader', 21 | query: { 22 | cacheDirectory: true, 23 | plugins: [['import', {'libraryName': 'antd', 'style': true}]], 24 | presets: ['es2015', 'react', 'stage-0', 'stage-1'] 25 | } 26 | }, { 27 | test: /\.less$/, 28 | loader: 'style-loader!css-loader!less-loader' 29 | }] 30 | }, 31 | devServer: { 32 | host: 'localhost', 33 | port: 9999, 34 | proxy: { 35 | '/mointor/*': { 36 | target: 'http://127.0.0.1:12000' 37 | } 38 | } 39 | }, 40 | }; 41 | 42 | module.exports = config; 43 | -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | redis服务监控 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /client/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { Router, Route, Link, hashHistory } from 'react-router'; 4 | 5 | import './src/global/less/global.less'; 6 | 7 | import GlobalHeader from './src/global/layout/GlobalHeader/index'; 8 | import SideNav from './src/global/layout/SideNav/index'; 9 | 10 | export default class Layout extends React.Component { 11 | render() { 12 | return ( 13 |
14 | 15 | 16 | {this.props.children} 17 |
18 | ); 19 | } 20 | } 21 | 22 | const routes = { 23 | path: '/', 24 | component: Layout, 25 | getIndexRoute(history, callback) { 26 | require.ensure([], function (require) { 27 | callback(null, require('./src/pages/default/index').default); 28 | }); 29 | }, 30 | getChildRoutes(history, callback) { 31 | if (history.location.pathname === '/') { 32 | require.ensure([], function (require) { 33 | callback(null, [ 34 | require('./src/pages/default/index').default 35 | ]); 36 | }); 37 | } else if (history.location.pathname === '/dbInfo' || history.location.pathname === '/memoryInfo' || 38 | history.location.pathname === '/qpsInfo') { 39 | require.ensure([], function (require) { 40 | callback(null, [ 41 | require('./src/pages/dbInfo/index').default, 42 | require('./src/pages/memoryInfo/index').default, 43 | require('./src/pages/qpsInfo/index').default 44 | ]); 45 | }); 46 | } 47 | } 48 | }; 49 | 50 | ReactDOM.render( 51 | , 52 | document.getElementById('app') 53 | ); 54 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redis-monitor-client", 3 | "version": "1.0.0", 4 | "description": "redis-monitor-client", 5 | "scripts": { 6 | "dev": "webpack --config dev.config.js --progress --colors", 7 | "start": "webpack-dev-server --hot --inline --history-api-fallback --progress" 8 | }, 9 | "author": "jizhuofeng", 10 | "license": "MIT", 11 | "dependencies": { 12 | "antd": "^2.8.3", 13 | "babel-core": "^6.21.0", 14 | "babel-loader": "^6.2.10", 15 | "babel-preset-es2015": "^6.18.0", 16 | "babel-preset-react": "^6.16.0", 17 | "babel-preset-stage-0": "^6.16.0", 18 | "babel-preset-stage-1": "^6.16.0", 19 | "css-loader": "^0.26.1", 20 | "echarts-for-react": "^1.2.0", 21 | "less": "^2.7.2", 22 | "less-loader": "^2.2.3", 23 | "mockjs": "^1.0.1-beta3", 24 | "moment": "^2.18.1", 25 | "react": "^15.4.2", 26 | "react-dom": "^15.4.2", 27 | "react-hot-loader": "^1.3.1", 28 | "react-router": "^3.0.0", 29 | "style-loader": "^0.13.1", 30 | "superagent": "^3.5.2", 31 | "webpack": "^2.3.3", 32 | "webpack-dev-server": "^2.4.2" 33 | }, 34 | "repository": { 35 | "type": "git", 36 | "url": "https://github.com/jizhuofeng/redis-monitor.git" 37 | }, 38 | "devDependencies": { 39 | "babel-plugin-import": "^1.1.0", 40 | "react-router": "^3.0.0" 41 | }, 42 | "keywords": [ 43 | "redis monitor react node.js" 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /client/src/components/react-data-inline-block/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import './style'; 4 | 5 | export class DataInlineBlock extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | } 9 | 10 | render() { 11 | if (this.props.data.length === 0) { 12 | return ( 13 |
14 | ); 15 | } else { 16 | let tempNodeArr = this.props.data.map((data, index) => { 17 | let typeClass = data.type === 'up' ? "data-block up" : "data-block down"; 18 | let typeArrow = data.type === 'up' ? "↑" : "↓"; 19 | 20 | let typeClass2 = data.type2 === 'up' ? "data-block up" : "data-block down"; 21 | let typeArrow2 = data.type2 === 'up' ? "↑" : "↓"; 22 | 23 | if(data.name2 != null) { 24 | return ( 25 |
26 |
27 |
{data.name}
28 |
{data.number}
29 |
较前一{this.props.timeType === 'day' ? '天' : '月'}
30 |
{typeArrow} {data.rate}
31 |
32 |
33 |
{data.name2}
34 |
{data.number2}
35 |
较前一{this.props.timeType2 === 'day' ? '天' : '月'}
36 |
{typeArrow2} {data.rate2}
37 |
38 |
39 | ); 40 | }else { 41 | return ( 42 |
43 |
44 |
{data.name}
45 |
{data.number}
46 |
较前一{this.props.timeType === 'day' ? '天' : '月'}
47 |
{typeArrow} {data.rate}
48 |
49 |
50 |
店长:{data.seller}
51 |
买家:{data.buyer}
52 |
53 |
54 | ); 55 | } 56 | }); 57 | 58 | return ( 59 |
60 | {tempNodeArr} 61 |
62 | ); 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /client/src/components/react-data-inline-block/style.less: -------------------------------------------------------------------------------- 1 | @import "../../global/less/mixins"; 2 | @import "../../global/less/color"; 3 | .data-inline-block { 4 | position: relative; 5 | width: 100%; 6 | height: auto; 7 | .clearfix(); 8 | .data-block { 9 | position: relative; 10 | float: left; 11 | width: 24%; 12 | height: 115px; 13 | margin-bottom: 10px; 14 | padding-left: 10px; 15 | margin-left: 1.33%; 16 | background-color: #fff; 17 | box-shadow: 2px 2px 3px rgba(0, 0, 0, 0.1); 18 | overflow: hidden; 19 | &:nth-child(1), 20 | &:nth-child(4n+1) { 21 | margin-left: 0; 22 | } 23 | &.up { 24 | .data-content { 25 | .sub-number { 26 | color: #d9534f; 27 | } 28 | 29 | } 30 | .glyphicon { 31 | right: -8px; 32 | top: 70px; 33 | color: #d9534f; 34 | } 35 | } 36 | &.down { 37 | .data-content { 38 | .sub-number { 39 | color: #5cb85c; 40 | } 41 | } 42 | .glyphicon { 43 | right: -8px; 44 | top: 0; 45 | color: #5cb85c; 46 | } 47 | } 48 | } 49 | .data-content { 50 | width: 115px; 51 | height: 100%; 52 | padding-top: 7px; 53 | &.grid-left { 54 | width: 50%; 55 | float: left; 56 | } 57 | &.grid-right { 58 | width: 50%; 59 | float: left; 60 | padding-left: 10px; 61 | background: #d8e1eb 62 | } 63 | &.center { 64 | padding-top: 20% 65 | } 66 | .title { 67 | margin-bottom: 2px; 68 | font: normal 16px/17px 'Arial'; 69 | color: #555; 70 | } 71 | .number { 72 | margin-bottom: 5px; 73 | padding-bottom: 5px; 74 | font: bolder 24px/35px 'Arial'; 75 | color: @main; 76 | border-bottom: 1px solid #EEE; 77 | } 78 | .sub-title { 79 | margin-bottom: 2px; 80 | font: normal 12px/15px 'Arial'; 81 | color: #555; 82 | } 83 | .sub-number { 84 | font: normal 14px/23px 'Arial'; 85 | } 86 | .rate { 87 | margin-bottom: 2px; 88 | font: normal 13px/15px 'Arial'; 89 | color: @main; 90 | } 91 | } 92 | .glyphicon { 93 | position: absolute; 94 | font-size: 50px; 95 | opacity: 0.3; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /client/src/components/react-echarts/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactEcharts from 'echarts-for-react'; 3 | 4 | import './style'; 5 | 6 | export class Charts extends React.Component { 7 | constructor(props) { 8 | super(props); 9 | } 10 | 11 | render() { 12 | return ( 13 |
14 |
{this.props.title}
15 | 16 |
17 | ); 18 | } 19 | } -------------------------------------------------------------------------------- /client/src/components/react-echarts/style.less: -------------------------------------------------------------------------------- 1 | .charts { 2 | position: relative; 3 | width: 100%; 4 | height: auto; 5 | padding: 10px 10px 25px; 6 | background-color: #ffffff; 7 | box-shadow: 2px 2px 3px rgba(0, 0, 0, 0.1); 8 | .charts-name { 9 | width: 100%; 10 | height: 35px; 11 | padding-left: 7px; 12 | margin-bottom: 5px; 13 | font: bolder 20px/35px 'Arial'; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /client/src/components/react-tab-info/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import './style'; 4 | 5 | export class TabInfo extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | } 9 | 10 | render() { 11 | if (this.props.data.length !== 0) { 12 | let tabBlock = this.props.data.map((data, index) => { 13 | if(this.props.selected == index + 1) { 14 | return ( 15 |
18 |
{data.number}
19 |
{data.name}
20 |
21 | ); 22 | }else { 23 | return ( 24 |
27 |
{data.number}
28 |
{data.name}
29 |
30 | ); 31 | } 32 | }); 33 | 34 | return ( 35 |
36 | {tabBlock} 37 |
38 | ); 39 | } else { 40 | return ( 41 |
42 |
43 | 44 |
数据加载中,请稍候…
45 |
46 |
47 | ); 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /client/src/components/react-tab-info/style.less: -------------------------------------------------------------------------------- 1 | @import "../../global/less/mixins"; 2 | @import "../../global/less/color"; 3 | .tab-info { 4 | position: relative; 5 | width: 100%; 6 | height: 115px; 7 | padding: 10px 5px; 8 | margin-bottom: 10px; 9 | background-color: #fff; 10 | box-shadow: 2px 2px 3px rgba(0, 0, 0, 0.1); 11 | .clearfix(); 12 | .tab-block { 13 | float: left; 14 | height: 100%; 15 | padding-top: 13px; 16 | border-left: 1px solid #eeeeee; 17 | text-align: center; 18 | .number { 19 | width: 100%; 20 | height: auto; 21 | margin-bottom: 5px; 22 | font: bolder 26px/45px 'Arial'; 23 | color: @main; 24 | } 25 | .info-name { 26 | width: 100%; 27 | height: auto; 28 | font: normal 16px/20px 'Arial'; 29 | color: #555; 30 | } 31 | &:nth-child(1) { 32 | border-left: 0; 33 | } 34 | &.selected { 35 | background-color: #eee; 36 | } 37 | } 38 | .loading { 39 | position: absolute; 40 | top: 50%; 41 | left: 50%; 42 | margin-top: -35px; 43 | margin-left: -75px; 44 | width: 150px; 45 | height: 70px; 46 | background-color: rgba(0, 0, 0, 0.7); 47 | border-radius: 5px; 48 | text-align: center; 49 | .loading-img { 50 | position: absolute; 51 | top: 10px; 52 | left: 50%; 53 | width: 30px; 54 | height: 30px; 55 | margin-left: -15px; 56 | animation: loading-rotate 2s linear infinite; 57 | -webkit-animation: loading-rotate 2s linear infinite; 58 | } 59 | .loading-content { 60 | position: absolute; 61 | bottom: 5px; 62 | left: 50%; 63 | width: 140px; 64 | height: 20px; 65 | margin-left: -70px; 66 | font: normal 12px/20px 'Arial'; 67 | color: #ced3cd; 68 | } 69 | } 70 | } 71 | @-webkit-keyframes loading-rotate { 72 | 0% { 73 | transform: rotate(0deg); 74 | -ms-transform: rotate(0deg); 75 | -moz-transform: rotate(0deg); 76 | -webkit-transform: rotate(0deg); 77 | -o-transform: rotate(0deg); 78 | } 79 | 100% { 80 | transform: rotate(360deg); 81 | -ms-transform: rotate(360deg); 82 | -moz-transform: rotate(360deg); 83 | -webkit-transform: rotate(360deg); 84 | -o-transform: rotate(360deg); 85 | } 86 | } 87 | @keyframes loading-rotate { 88 | 0% { 89 | transform: rotate(0deg); 90 | -ms-transform: rotate(0deg); 91 | -moz-transform: rotate(0deg); 92 | -webkit-transform: rotate(0deg); 93 | -o-transform: rotate(0deg); 94 | } 95 | 100% { 96 | transform: rotate(360deg); 97 | -ms-transform: rotate(360deg); 98 | -moz-transform: rotate(360deg); 99 | -webkit-transform: rotate(360deg); 100 | -o-transform: rotate(360deg); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /client/src/global/conf/location.js: -------------------------------------------------------------------------------- 1 | let locationProps = [{ 2 | listTitle: '实例管理', 3 | listIcon: 'database', 4 | pageUrl: '/' 5 | }, { 6 | listTitle: '状态监控', 7 | listIcon: 'area-chart', 8 | list: [{ 9 | pageName: '实例信息', 10 | pageUrl: '/dbInfo' 11 | }, { 12 | pageName: '内存变化', 13 | pageUrl: '/memoryInfo' 14 | }, { 15 | pageName: '吞吐率', 16 | pageUrl: '/qpsInfo' 17 | }] 18 | 19 | }]; 20 | 21 | export default locationProps; -------------------------------------------------------------------------------- /client/src/global/js/date.js: -------------------------------------------------------------------------------- 1 | /* 2 | * date.js 公共时间处理函数集 3 | * */ 4 | 5 | /* 6 | * Glo_setDateZero(number) 7 | * 小于10的数字前加0,返回字符串 8 | * */ 9 | export function Glo_setDateZero(number) { 10 | return number < 10 ? '0' + number : '' + number; 11 | } 12 | 13 | /* 14 | * Glo_getCurrentDate() 15 | * 获取当前日期(yyyy-mm-dd) 16 | * */ 17 | export function Glo_getCurrentDate() { 18 | let dateObj = new Date(); 19 | return dateObj.getFullYear() + '-' + Glo_setDateZero(dateObj.getMonth() + 1) + '-' + Glo_setDateZero(dateObj.getDate()); 20 | } 21 | 22 | /* 23 | * Glo_getCurrentMonth() 24 | * 获取当前年月(yyyy-mm) 25 | * */ 26 | export function Glo_getCurrentMonth() { 27 | let dateObj = new Date(); 28 | return dateObj.getFullYear() + '-' + Glo_setDateZero(dateObj.getMonth() + 1); 29 | } 30 | 31 | /* 32 | * Glo_timeStampFormat(timeStamp) 33 | * 格式化时间戳(yyyy-mm-dd hh:mm:ss) 34 | * */ 35 | export function Glo_timeStampFormat(timeStamp) { 36 | var time = new Date(timeStamp); 37 | var y = time.getFullYear(); 38 | var m = time.getMonth() + 1; 39 | var d = time.getDate(); 40 | var h = time.getHours(); 41 | var mm = time.getMinutes(); 42 | var s = time.getSeconds(); 43 | return y + '-' + Glo_setDateZero(m) + '-' + Glo_setDateZero(d) + ' ' + Glo_setDateZero(h) + ':' + Glo_setDateZero(mm) + ':' + Glo_setDateZero(s); 44 | } 45 | 46 | /* 47 | * Glo_getPreDate(currentDate, daysNum) 48 | * 获取N天前的日期 49 | * */ 50 | export function Glo_getPreDate(currentDate, daysNum) { 51 | let currentDateArr = currentDate.split('-').map(data => { 52 | return Number(data); 53 | }); 54 | 55 | if (currentDateArr[2] < daysNum) { 56 | if (currentDateArr[1] === 1) { 57 | currentDateArr[0]--; 58 | currentDateArr[1] = 12; 59 | currentDateArr[2] = 31 - (daysNum - currentDateArr[2]) + 1; 60 | } else if (currentDateArr[1] === 3) { 61 | currentDateArr[1] = '02'; 62 | if (((currentDateArr[0] % 100 === 0) && (currentDateArr[0] % 400 === 0)) || (currentDateArr[0] % 100 !== 0 && currentDateArr[0] % 4 === 0)) { 63 | currentDateArr[2] = 29 - (daysNum - currentDateArr[2]) + 1; 64 | } else { 65 | currentDateArr[2] = 28 - (daysNum - currentDateArr[2]) + 1; 66 | } 67 | } else if (currentDateArr[1] === 5 || currentDateArr[1] === 7 || currentDateArr[1] === 8 || currentDateArr[1] === 10 || currentDateArr[1] === 12) { 68 | currentDateArr[1] = Glo_setDateZero(--currentDateArr[1]); 69 | currentDateArr[2] = 30 - (daysNum - currentDateArr[2]) + 1; 70 | } else { 71 | currentDateArr[1] = Glo_setDateZero(--currentDateArr[1]); 72 | currentDateArr[2] = 30 - (daysNum - currentDateArr[2]) + 1; 73 | } 74 | } else { 75 | currentDateArr[1] = Glo_setDateZero(currentDateArr[1]); 76 | currentDateArr[2] = Glo_setDateZero(currentDateArr[2] - daysNum + 1); 77 | } 78 | return currentDateArr.join('-'); 79 | } -------------------------------------------------------------------------------- /client/src/global/js/request.js: -------------------------------------------------------------------------------- 1 | /* 2 | * request.js 公共请求函数集 3 | * */ 4 | 5 | import request from 'superagent'; 6 | import { notification } from 'antd'; 7 | /* 8 | * GET request method 9 | * reqObj{ 10 | * context - 方法调用上下文环境(this) 11 | * init - 是否只调用一次(初始化 12 | * } 13 | * */ 14 | export function Glo_getRequest(reqObj) { 15 | let tempState = Object.assign({}, reqObj.context.state); 16 | if (reqObj.init && !tempState[reqObj.url + 'init'] || !reqObj.init) { 17 | let reqTime = 1; 18 | if (tempState[reqObj.url + '_reqTime']) { 19 | reqTime = ++tempState[reqObj.url + '_reqTime']; 20 | } else { 21 | tempState[reqObj.url + '_reqTime'] = reqTime; 22 | } 23 | if (reqObj.init && !tempState[reqObj.url + 'init']) { 24 | tempState[reqObj.url + 'init'] = true; 25 | } 26 | reqObj.context.setState(tempState, () => { 27 | request.get(reqObj.url).query(reqObj.data).end((err, res) => { 28 | if (res.status === 200) { 29 | if (reqTime === reqObj.context.state[reqObj.url + '_reqTime']) { 30 | reqObj.response.call(reqObj.context, err, res); 31 | } 32 | } else { 33 | notification['warning']({ 34 | message: '对不起,系统升级中', 35 | description: '如果系统长时间处于升级状态,请联系工程师们为您解决问题。', 36 | duration: 7 37 | }); 38 | } 39 | }); 40 | }); 41 | } 42 | } 43 | 44 | /** 45 | * PUT request method 46 | * reqObj { 47 | * context - 方法调用上下文环境(this) 48 | * init - 是否只调用一次(初始化 49 | * } 50 | */ 51 | export function Glo_putRequest(reqObj) { 52 | let tempState = Object.assign({}, reqObj.context.state); 53 | if (reqObj.init && !tempState[reqObj.url + 'init'] || !reqObj.init) { 54 | if (reqObj.init && !tempState[reqObj.url + 'init']) { 55 | tempState[reqObj.url + 'init'] = true; 56 | reqObj.context.setState(tempState); 57 | } 58 | request.put(reqObj.url).send(reqObj.data).end((err, res) => { 59 | if (res.status === 200) { 60 | reqObj.response.call(reqObj.context, err, res); 61 | } else { 62 | notification['warning']({ 63 | message: '对不起,系统升级中', 64 | description: '如果系统长时间处于升级状态,请联系工程师们为您解决问题。', 65 | duration: 7 66 | }); 67 | } 68 | }); 69 | } 70 | } 71 | 72 | /* 73 | * POST request method 74 | * reqObj{ 75 | * context - 方法调用上下文环境(this) 76 | * init - 是否只调用一次(初始化 77 | * } 78 | * */ 79 | export function Glo_postRequest(reqObj) { 80 | let tempState = Object.assign({}, reqObj.context.state); 81 | if (reqObj.init && !tempState[reqObj.url + 'init'] || !reqObj.init) { 82 | if (reqObj.init && !tempState[reqObj.url + 'init']) { 83 | tempState[reqObj.url + 'init'] = true; 84 | reqObj.context.setState(tempState); 85 | } 86 | request.post(reqObj.url).send(reqObj.data).end((err, res) => { 87 | if (res.status === 200) { 88 | reqObj.response.call(reqObj.context, err, res); 89 | } else { 90 | notification['warning']({ 91 | message: '对不起,系统升级中', 92 | description: '如果系统长时间处于升级状态,请联系工程师们为您解决问题。', 93 | duration: 7 94 | }); 95 | } 96 | }); 97 | } 98 | } 99 | 100 | /* 101 | * PATCH request method 102 | * reqObj{ 103 | * context - 方法调用上下文环境(this) 104 | * init - 是否只调用一次(初始化 105 | * } 106 | * */ 107 | export function Glo_patchRequest(reqObj) { 108 | let tempState = Object.assign({}, reqObj.context.state); 109 | if (reqObj.init && !tempState[reqObj.url + 'init'] || !reqObj.init) { 110 | if (reqObj.init && !tempState[reqObj.url + 'init']) { 111 | tempState[reqObj.url + 'init'] = true; 112 | reqObj.context.setState(tempState); 113 | } 114 | request.patch(reqObj.url).send(reqObj.data).end((err, res) => { 115 | if (res.status === 200) { 116 | reqObj.response.call(reqObj.context, err, res); 117 | } else { 118 | notification['warning']({ 119 | message: '对不起,系统升级中', 120 | description: '如果系统长时间处于升级状态,请联系工程师们为您解决问题。', 121 | duration: 7 122 | }); 123 | } 124 | }); 125 | } 126 | } 127 | 128 | /* 129 | * DELETE request method 130 | * reqObj{ 131 | * context - 方法调用上下文环境(this) 132 | * } 133 | * */ 134 | export function Glo_deleteRequest(reqObj) { 135 | request.del(reqObj.url).send(reqObj.data).end((err, res) => { 136 | if (res.status === 200) { 137 | reqObj.response.call(reqObj.context, err, res); 138 | } else { 139 | notification['warning']({ 140 | message: '对不起,系统升级中', 141 | description: '如果系统长时间处于升级状态,请联系工程师们为您解决问题。', 142 | duration: 7 143 | }); 144 | } 145 | }); 146 | } -------------------------------------------------------------------------------- /client/src/global/layout/GlobalHeader/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Link} from 'react-router'; 3 | 4 | import './style'; 5 | 6 | class GlobalHeader extends React.Component { 7 | render() { 8 | return ( 9 |
10 | 11 |
12 | 13 |
14 | ); 15 | } 16 | } 17 | 18 | export default GlobalHeader; 19 | -------------------------------------------------------------------------------- /client/src/global/layout/GlobalHeader/style.less: -------------------------------------------------------------------------------- 1 | @import "../../less/color"; 2 | .global-header { 3 | position: absolute; 4 | top: 0; 5 | left: 0; 6 | z-index: 99; 7 | width: 100%; 8 | height: 45px; 9 | background-color: #323232; 10 | .logo { 11 | position: relative; 12 | width: 20%; 13 | height: 100%; 14 | padding: 0 10px 0 12px; 15 | background-size: 90px auto; 16 | } 17 | } -------------------------------------------------------------------------------- /client/src/global/layout/SideNav/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {hashHistory} from 'react-router'; 3 | import {Icon} from 'antd'; 4 | 5 | import locationProps from '../../conf/location'; 6 | 7 | import './style'; 8 | 9 | class SideNav extends React.Component { 10 | constructor(props) { 11 | super(props); 12 | 13 | let sideNavProps = locationProps; 14 | let currentUrl = this.props.pathName; 15 | 16 | sideNavProps.forEach(data => { 17 | data.listShow = false; 18 | data.currentTitle = false; 19 | if (data.list) { 20 | data.list.forEach((dataList) => { 21 | dataList.current = false; 22 | if (dataList.pageUrl === currentUrl) { 23 | data.listShow = true; 24 | data.currentTitle = true; 25 | dataList.current = true; 26 | } 27 | }); 28 | } else if (data.pageUrl) { 29 | if (data.pageUrl === currentUrl) { 30 | data.listShow = true; 31 | data.currentTitle = true; 32 | } 33 | } 34 | }); 35 | 36 | this.state = { 37 | sideNavProps: sideNavProps 38 | }; 39 | } 40 | 41 | setListShow = (data, index) => { 42 | let tempState = Object.assign({}, this.state); 43 | let tempListShow = tempState.sideNavProps[index].listShow; 44 | if (tempState.sideNavProps[index].list) { 45 | tempState.sideNavProps.forEach((value, key) => { 46 | if (index === key) { 47 | value.listShow = !tempListShow; 48 | } else { 49 | value.listShow = false; 50 | } 51 | }); 52 | this.setState(tempState); 53 | } else { 54 | tempState.sideNavProps.forEach((value, key) => { 55 | if (index === key) { 56 | value.listShow = !tempListShow; 57 | value.currentTitle = true; 58 | } else { 59 | value.listShow = false; 60 | value.currentTitle = false; 61 | value.list.forEach(tempListData => { 62 | tempListData.current = false; 63 | }); 64 | } 65 | }); 66 | 67 | this.setState(tempState, ()=> { 68 | hashHistory.push(tempState.sideNavProps[index].pageUrl); 69 | }); 70 | } 71 | }; 72 | 73 | setSideNavListCurrent = (titleIndex, listIndex, url) => { 74 | let tempState = Object.assign({}, this.state); 75 | tempState.sideNavProps.forEach((data, index) => { 76 | if (index === titleIndex) { 77 | data.listShow = true; 78 | data.currentTitle = true; 79 | data.list.forEach((tempListData, tempListIndex) => { 80 | tempListData.current = tempListIndex === listIndex; 81 | }); 82 | } else { 83 | data.listShow = false; 84 | data.currentTitle = false; 85 | if (data.list) { 86 | data.list.forEach(tempListData => { 87 | tempListData.current = false; 88 | }); 89 | } 90 | } 91 | }); 92 | this.setState(tempState, () => { 93 | hashHistory.push(url); 94 | }); 95 | }; 96 | 97 | renderSideNavNode = (sideNavProps) => { 98 | let sideBarNode = []; 99 | 100 | sideNavProps.forEach((data, index) => { 101 | if (data.list) { 102 | let operateNode =
103 | {data.listShow ? '收起' : '展开'} 104 |
; 105 | 106 | sideBarNode.push( 107 |
  • this.setListShow(data, index)}> 109 | {data.listTitle} 110 | {operateNode} 111 |
  • 112 | ); 113 | 114 | data.list.forEach((dataList, key) => { 115 | if (data.listShow) { 116 | if (dataList.current) { 117 | sideBarNode.push( 118 |
  • 119 | {dataList.pageName} 120 |
  • 121 | ); 122 | } else { 123 | sideBarNode.push( 124 |
  • this.setSideNavListCurrent(index, key, dataList.pageUrl)}> 126 | {dataList.pageName} 127 |
  • 128 | ); 129 | } 130 | } else { 131 | sideBarNode.push( 132 |
  • this.setSideNavListCurrent(index, key, dataList.pageUrl)}> 134 | {dataList.pageName} 135 |
  • 136 | ); 137 | } 138 | }); 139 | 140 | } else if (data.pageUrl) { 141 | sideBarNode.push( 142 |
  • this.setListShow(data, index)}> 144 | {data.listTitle} 145 |
  • 146 | ); 147 | } 148 | }); 149 | 150 | return sideBarNode; 151 | }; 152 | 153 | render() { 154 | let tempSideNavProps = this.state.sideNavProps; 155 | 156 | return ( 157 | 160 | ); 161 | } 162 | } 163 | 164 | export default SideNav; 165 | -------------------------------------------------------------------------------- /client/src/global/layout/SideNav/style.less: -------------------------------------------------------------------------------- 1 | @import "../../less/color"; 2 | .side-nav { 3 | float: left; 4 | width: 20%; 5 | height: 100%; 6 | background-color: @grey; 7 | overflow-y: scroll; 8 | color: #ced3cd; 9 | li { 10 | position: relative; 11 | width: 100%; 12 | height: 55px; 13 | padding: 0 12px; 14 | font: normal 15px/55px 'Arial'; 15 | cursor: pointer; 16 | transition: all 0.3s; 17 | &.list-node { 18 | padding-left: 40px; 19 | white-space: nowrap; 20 | } 21 | &.title-active { 22 | background-color: #1b1b1b; 23 | color: @main; 24 | } 25 | &.active { 26 | color: @main; 27 | background-color: #1b1b1b; 28 | } 29 | &.list-hide { 30 | height: 0; 31 | opacity: 0; 32 | border-bottom: none; 33 | overflow: hidden; 34 | } 35 | .operate { 36 | position: absolute; 37 | top: 50%; 38 | right: 10px; 39 | width: 60px; 40 | height: 55px; 41 | margin-top: -55px/2; 42 | font: normal 13px/55px 'Arial'; 43 | } 44 | &:hover { 45 | color: @main; 46 | &.list-node { 47 | padding-left: 45px; 48 | background-color: #1b1b1b; 49 | } 50 | &.active { 51 | padding-left: 40px; 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /client/src/global/less/color.less: -------------------------------------------------------------------------------- 1 | @main: #3889cc; 2 | @grey: #323232; -------------------------------------------------------------------------------- /client/src/global/less/global.less: -------------------------------------------------------------------------------- 1 | @import "./color"; 2 | @import "./reset"; 3 | @import "./mixins"; 4 | #app { 5 | position: relative; 6 | width: 100%; 7 | min-width: 980px; 8 | height: 100%; 9 | overflow: hidden; 10 | background-color: #eee; 11 | padding-top: 45px; 12 | } 13 | .layout { 14 | width: 100%; 15 | height: 100%; 16 | .clearfix(); 17 | } 18 | .main-content { 19 | float: left; 20 | width: 80%; 21 | height: 100%; 22 | padding: 12px; 23 | overflow-y: scroll; 24 | .content-block { 25 | position: relative; 26 | width: 100%; 27 | height: auto; 28 | min-height: 45px; 29 | margin-bottom: 10px; 30 | background-color: #fff; 31 | box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.1); 32 | .clearfix(); 33 | } 34 | } 35 | .ant-table-middle .ant-table-thead { 36 | th { 37 | text-align: center; 38 | } 39 | } 40 | .ant-table-body { 41 | td { 42 | text-align: center; 43 | } 44 | } 45 | .ant-btn { 46 | &.red-btn { 47 | border-color: #ff5d5d; 48 | background-color: #ff5d5d; 49 | &:hover { 50 | border-color: #ff7776; 51 | background-color: #ff7776; 52 | } 53 | } 54 | &.green-btn { 55 | border-color: #87d068; 56 | background-color: #87d068; 57 | &:hover { 58 | border-color: #9fd096; 59 | background-color: #9fd096; 60 | } 61 | } 62 | } 63 | .ant-pagination { 64 | margin-bottom: 10px; 65 | } 66 | .ant-card { 67 | .label-name { 68 | font: bolder 12px/12px 'Arial'; 69 | margin-right: 8.5px; 70 | } 71 | } -------------------------------------------------------------------------------- /client/src/global/less/mixins.less: -------------------------------------------------------------------------------- 1 | .clearfix() { 2 | *zoom: 1; 3 | &:before, 4 | &:after { 5 | display: table; 6 | content: ""; 7 | } 8 | &:after { 9 | clear: both; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /client/src/global/less/reset.less: -------------------------------------------------------------------------------- 1 | * { 2 | padding: 0; 3 | margin: 0; 4 | box-sizing: border-box; 5 | } 6 | html { 7 | height: 100%; 8 | } 9 | body { 10 | height: 100%; 11 | } 12 | ul { 13 | list-style-type: none; 14 | } 15 | a { 16 | text-decoration: none; 17 | } 18 | button { 19 | border: none; 20 | outline: none; 21 | margin: 0; 22 | padding: 0; 23 | background-color: transparent; 24 | cursor: pointer; 25 | } -------------------------------------------------------------------------------- /client/src/pages/dbInfo/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { Input, Button, Spin, Form, Row, Col, message, Tabs, Icon, Tag, 4 | Modal, Select, Dropdown, Card} from 'antd'; 5 | 6 | const FormItem = Form.Item; 7 | const TabPane = Tabs.TabPane; 8 | const InputGroup = Input.Group; 9 | const Option = Select.Option; 10 | 11 | import { Glo_getRequest, Glo_postRequest } from '../../global/js/request'; 12 | import { Glo_timeStampFormat } from '../../global/js/date'; 13 | import {Charts} from '../../components/react-echarts/index'; 14 | 15 | import './style'; 16 | 17 | //page framework 18 | class PageContent extends React.Component { 19 | constructor(props) { 20 | super(props); 21 | 22 | this.state = { 23 | instances: [], 24 | currentInstance: { 25 | 26 | }, 27 | loading: false, 28 | chartOption: { 29 | tooltip: { 30 | trigger: 'axis' 31 | }, 32 | legend: { 33 | data: ['keys'] 34 | }, 35 | grid: { 36 | left: '3%', 37 | right: '3%', 38 | bottom: '2%', 39 | containLabel: true 40 | }, 41 | xAxis: [ 42 | { 43 | type: 'category', 44 | data: [] 45 | } 46 | ], 47 | yAxis: [ 48 | { 49 | type: 'value' 50 | } 51 | ], 52 | series: [ 53 | { 54 | name: 'keys', 55 | type: 'bar', 56 | data: [] 57 | } 58 | ] 59 | } 60 | }; 61 | } 62 | renderCharts = (value) => { 63 | let tempState = Object.assign({}, this.state); 64 | tempState.loading = false; 65 | let dbs = []; 66 | let dbkeys = []; 67 | if(value.data.Keyspace) { 68 | for(let info in value.data.Keyspace) { 69 | dbs.push(info); 70 | let tmp = value.data.Keyspace[info]; 71 | let keyNum = tmp.split(',')[0].split('=')[1]; 72 | dbkeys.push(keyNum); 73 | } 74 | } 75 | tempState.chartOption.xAxis[0].data = dbs; 76 | tempState.chartOption.series[0].data = dbkeys; 77 | this.setState(tempState); 78 | } 79 | getInstanceBaseInfo = (instance) => { 80 | let _self = this; 81 | let tempState = Object.assign({}, this.state); 82 | tempState.loading = true; 83 | _self.setState(tempState); 84 | let data = { 85 | name: instance.name 86 | } 87 | //获取实例基本信息 88 | Glo_getRequest({ 89 | context: _self, 90 | init: false, 91 | data: data, 92 | url: '/monitor/server/base-info', 93 | response: (err, res) => { 94 | let responseResult = JSON.parse(res.text); 95 | if (responseResult.code === 1) { 96 | _self.renderCharts(responseResult); 97 | } else { 98 | let tempState = Object.assign({}, this.state); 99 | tempState.loading = false; 100 | _self.setState(tempState); 101 | message.error(responseResult.message); 102 | } 103 | } 104 | }); 105 | } 106 | instanceChange = (value) => { 107 | let tempState = Object.assign({}, this.state); 108 | for(let i = 0; i < tempState.instances.length; i++) { 109 | if(tempState.instances[i].showName == value) { 110 | tempState.currentInstance = tempState.instances[i]; 111 | break; 112 | } 113 | } 114 | this.setState(tempState, () => { 115 | this.getInstanceBaseInfo(tempState.currentInstance); 116 | }); 117 | } 118 | initInstances = (value) => { 119 | let tempState = Object.assign({}, this.state); 120 | tempState.instances = []; 121 | for(let key in value.data) { 122 | tempState.instances.push({ 123 | showName: key + '(' + value.data[key].host + 124 | ':' + value.data[key].port + ')', 125 | name: key, 126 | host: value.data[key].host, 127 | port: value.data[key].port 128 | }) 129 | } 130 | if(tempState.instances[0]) { 131 | tempState.currentInstance = tempState.instances[0]; 132 | } 133 | 134 | this.setState(tempState, () => { 135 | if(tempState.instances.length > 0) { 136 | this.getInstanceBaseInfo(this.state.instances[0]); 137 | } 138 | }); 139 | 140 | } 141 | componentWillMount() { 142 | let _self = this; 143 | Glo_getRequest({ 144 | context: _self, 145 | init: true, 146 | url: '/monitor/instance', 147 | response: (err, res) => { 148 | let responseResult = JSON.parse(res.text); 149 | if (responseResult.code === 1) { 150 | _self.initInstances(responseResult); 151 | } else { 152 | message.error(responseResult.message); 153 | } 154 | } 155 | }); 156 | } 157 | 158 | render() { 159 | let _self = this; 160 | return ( 161 |
    162 |
    163 | 171 |
    172 |
    173 | 174 |
    175 |
    176 | 177 | 178 |

    实例中每个db包含的key的总数量

    179 |
    180 |
    181 | 182 |
    183 | ); 184 | } 185 | } 186 | 187 | let route = { 188 | path: '/dbInfo', 189 | component: PageContent 190 | }; 191 | 192 | export default route; -------------------------------------------------------------------------------- /client/src/pages/dbInfo/style.less: -------------------------------------------------------------------------------- 1 | @import "../../global/less/mixins"; 2 | .top-block { 3 | padding: 8.5px 8px; 4 | } 5 | .title { 6 | font: normal 20px/70px 'Arial'; 7 | color: #000; 8 | } -------------------------------------------------------------------------------- /client/src/pages/default/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import {Button, Spin, Table, message, Modal, Form, Input} from 'antd'; 4 | const FormItem = Form.Item; 5 | import {Glo_getRequest, Glo_deleteRequest, Glo_postRequest} from '../../global/js/request'; 6 | 7 | import './style'; 8 | 9 | class PageContent extends React.Component { 10 | constructor(props) { 11 | super(props); 12 | this.state = { 13 | addModalVisible: false, 14 | addSending: false, 15 | editModalVisible: false, 16 | editSending: false, 17 | modalKey: 0, 18 | currentInstance: { 19 | 20 | }, 21 | addInstance: { 22 | 23 | }, 24 | tableData: [] 25 | }; 26 | } 27 | addInstanceChange = (key, value) => { 28 | let tempState = Object.assign({}, this.state); 29 | tempState.addInstance[key] = value; 30 | this.setState(tempState); 31 | } 32 | currentInstanceChange = (key, value) => { 33 | let tempState = Object.assign({}, this.state); 34 | tempState.currentInstance["newName"] = value; 35 | this.setState(tempState); 36 | } 37 | showAddModal = () => { 38 | let tempState = Object.assign({}, this.state); 39 | tempState.addModalVisible = true; 40 | this.setState(tempState); 41 | } 42 | hideAddModal = () => { 43 | let tempState = Object.assign({}, this.state); 44 | tempState.addModalVisible = false; 45 | tempState.addSending = false; 46 | this.setState(tempState); 47 | } 48 | showEditModal = (text, record, id) => { 49 | 50 | let tempState = Object.assign({}, this.state); 51 | tempState.modalKey++; 52 | tempState.currentInstance = Object.assign({}, record); 53 | tempState.editModalVisible = true; 54 | this.setState(tempState); 55 | } 56 | hideEditModal = () => { 57 | let tempState = Object.assign({}, this.state); 58 | tempState.editSending = false; 59 | tempState.editModalVisible = false; 60 | this.setState(tempState); 61 | } 62 | 63 | renderRenameResult = () => { 64 | let tempState = Object.assign({}, this.state); 65 | tempState.editSending = false; 66 | tempState.editModalVisible = false; 67 | tempState.tableData[tempState.currentInstance["index"]]["name"] = tempState.currentInstance["newName"]; 68 | tempState.currentInstance = Object.assign({}, tempState.tableData[tempState.currentInstance["index"]]); 69 | this.setState(tempState); 70 | } 71 | renderDeleteResult = (id) => { 72 | let tempState = Object.assign({}, this.state); 73 | tempState.tableData.splice(id, 1); 74 | this.setState(tempState); 75 | } 76 | renameInstance = () => { 77 | let _self = this; 78 | let tempState = Object.assign({}, this.state); 79 | tempState.editSending = true; 80 | this.setState(tempState); 81 | let data = { 82 | name: _self.state.currentInstance["newName"], 83 | oldName: _self.state.currentInstance["name"] 84 | } 85 | Glo_postRequest({ 86 | context: _self, 87 | init: false, 88 | data: data, 89 | url: '/monitor/instance/rename', 90 | response: (err, res) => { 91 | let responseResult = JSON.parse(res.text); 92 | if (responseResult.code === 1) { 93 | _self.renderRenameResult(); 94 | } else { 95 | _self.hideEditModal(); 96 | message.error("修改失败(" + responseResult.message + ")"); 97 | } 98 | } 99 | }); 100 | } 101 | deleteInstance = (text, record, id) => { 102 | let _self = this; 103 | let data = { 104 | name: record["name"] 105 | } 106 | Glo_deleteRequest({ 107 | context: _self, 108 | init: true, 109 | data: data, 110 | url: '/monitor/instance', 111 | response: (err, res) => { 112 | let responseResult = JSON.parse(res.text); 113 | if (responseResult.code === 1) { 114 | _self.renderDeleteResult(text); 115 | } else { 116 | message.error("删除失败(" + responseResult.message + ")"); 117 | } 118 | } 119 | }); 120 | } 121 | renderAddResult = () => { 122 | let _self = this; 123 | //添加后重新load实例数据 124 | let tempState = Object.assign({}, this.state); 125 | tempState.addModalVisible = false; 126 | tempState.loading = true; 127 | _self.setState(tempState); 128 | Glo_getRequest({ 129 | context: _self, 130 | init: false, 131 | url: '/monitor/instance', 132 | response: (err, res) => { 133 | let tempState = Object.assign({}, this.state); 134 | tempState.loading = false; 135 | this.setState(tempState); 136 | let responseResult = JSON.parse(res.text); 137 | if (responseResult.code === 1) { 138 | this.renderTable(responseResult); 139 | } else { 140 | message.error(responseResult.message); 141 | } 142 | } 143 | }); 144 | } 145 | addInstance = () => { 146 | let _self = this; 147 | let data = { 148 | "host": _self.state.addInstance["host"], 149 | "port": _self.state.addInstance["port"] 150 | } 151 | if(_self.state.addInstance["name"]) { 152 | data["name"] = _self.state.addInstance["name"]; 153 | } 154 | if(_self.state.addInstance["password"]) { 155 | data["password"] = _self.state.addInstance["password"]; 156 | } 157 | Glo_postRequest({ 158 | context: _self, 159 | data: data, 160 | url: '/monitor/instance', 161 | response: (err, res) => { 162 | let responseResult = JSON.parse(res.text); 163 | if (responseResult.code === 1) { 164 | _self.renderAddResult(); 165 | } else { 166 | _self.hideAddModal(); 167 | message.error("添加失败(" + responseResult.message + ")"); 168 | } 169 | } 170 | }); 171 | } 172 | renderTable = (value) => { 173 | let tempState = Object.assign({}, this.state); 174 | tempState.loading = false; 175 | let data = []; 176 | let index = 0; 177 | for(let key in value.data) { 178 | data.push({ 179 | name: key, 180 | host: value.data[key].host, 181 | port: value.data[key].port, 182 | index: index 183 | }) 184 | index++; 185 | } 186 | 187 | tempState.tableData = data; 188 | tempState.totalPage = value.pageCount; 189 | tempState.currentPage = value.pageCurrent; 190 | this.setState(tempState); 191 | }; 192 | 193 | componentWillMount(){ 194 | let _self = this; 195 | Glo_getRequest({ 196 | context: _self, 197 | init: true, 198 | url: '/monitor/instance', 199 | response: (err, res) => { 200 | let responseResult = JSON.parse(res.text); 201 | if (responseResult.code === 1) { 202 | _self.renderTable(responseResult); 203 | } else { 204 | message.error(responseResult.message); 205 | } 206 | } 207 | }); 208 | } 209 | 210 | render() { 211 | const columns = [{ 212 | title: '实例名', 213 | dataIndex: 'name', 214 | key: 'name' 215 | }, { 216 | title: '地址', 217 | dataIndex: 'host', 218 | key: 'host' 219 | }, { 220 | title: '端口', 221 | dataIndex: 'port', 222 | key: 'port' 223 | },{ 224 | title: '操作', 225 | dataIndex: 'index', 226 | render: (text, record, id) => { 227 | return ( 228 |
    229 | 230 | 232 |
    233 | ); 234 | }, 235 | key: 'index' 236 | }]; 237 | 238 | const formItemLayout = { 239 | labelCol: {span: 6}, 240 | wrapperCol: {span: 14}, 241 | }; 242 | 243 | return ( 244 |
    245 |
    246 | 248 |
    249 |
    250 | 251 | 253 | 254 | this.renameInstance()} 259 | confirmLoading={this.state.editSending} 260 | onCancel={() => this.hideEditModal()} 261 | afterClose={() => this.hideEditModal()}> 262 |
    263 | 266 | 268 | 269 | 272 | this.currentInstanceChange('name', e.target.value)}/> 274 | 275 | 276 |
    277 | this.addInstance()} 281 | confirmLoading={this.state.addSending} 282 | onCancel={() => this.hideAddModal()}> 283 |
    284 | 287 | this.addInstanceChange('name', e.target.value)}/> 289 | 290 | 293 | this.addInstanceChange('host', e.target.value)}/> 295 | 296 | 299 | this.addInstanceChange('port', e.target.value)}/> 301 | 302 | 305 | this.addInstanceChange('password', e.target.value)}/> 307 | 308 | 309 |
    310 | 311 | 312 | ); 313 | } 314 | } 315 | 316 | let route = { 317 | component: PageContent 318 | }; 319 | 320 | export default route; -------------------------------------------------------------------------------- /client/src/pages/default/style.less: -------------------------------------------------------------------------------- 1 | .current-date { 2 | position: absolute; 3 | left: 16px; 4 | top: 11px; 5 | width: 100%; 6 | height: 30px; 7 | margin-bottom: 10px; 8 | font: normal 18px/30px 'Arial'; 9 | color: #666; 10 | } -------------------------------------------------------------------------------- /client/src/pages/memoryInfo/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import moment from 'moment'; 3 | 4 | import { Input, Button, Spin, Form, Row, Col, DatePicker, message, Tabs, Icon, Tag, 5 | Modal, Select, Dropdown, Card} from 'antd'; 6 | 7 | const FormItem = Form.Item; 8 | const TabPane = Tabs.TabPane; 9 | const InputGroup = Input.Group; 10 | const Option = Select.Option; 11 | const RangePicker = DatePicker.RangePicker; 12 | 13 | import { Glo_getRequest, Glo_postRequest } from '../../global/js/request'; 14 | import { Glo_timeStampFormat } from '../../global/js/date'; 15 | import {Charts} from '../../components/react-echarts/index'; 16 | import {Glo_getCurrentDate, Glo_getPreDate} from '../../global/js/date'; 17 | import './style'; 18 | 19 | //page framework 20 | class PageContent extends React.Component { 21 | constructor(props) { 22 | super(props); 23 | this.state = { 24 | loading: false, 25 | beginTime: moment().hour(0).minute(0).second(0).format('YYYY-MM-DD HH:mm:ss'), 26 | endTime: moment().format('YYYY-MM-DD HH:mm:ss'), 27 | type: '1', 28 | instances: [], 29 | currentInstance: { 30 | 31 | }, 32 | chartOption: { 33 | tooltip: { 34 | trigger: 'axis' 35 | }, 36 | legend: { 37 | data: ['memory(M)'] 38 | }, 39 | grid: { 40 | left: '4%', 41 | right: '3%', 42 | bottom: '2%', 43 | containLabel: true 44 | }, 45 | xAxis: [ 46 | { 47 | type: 'category', 48 | data: [] 49 | } 50 | ], 51 | yAxis: [ 52 | { 53 | type: 'value' 54 | } 55 | ], 56 | series: [ 57 | { 58 | name: 'memory(M)', 59 | type: 'line', 60 | data: [] 61 | } 62 | ] 63 | } 64 | }; 65 | } 66 | rangePickChange = (timeArr) => { 67 | let tempState = Object.assign({}, this.state); 68 | tempState.beginTime = timeArr[0]; 69 | tempState.endTime = timeArr[1]; 70 | this.setState(tempState); 71 | }; 72 | typeChange = (value) => { 73 | let tempState = Object.assign({}, this.state); 74 | tempState.type = value; 75 | this.setState(tempState); 76 | } 77 | renderCharts = (value) => { 78 | let tempState = Object.assign({}, this.state); 79 | tempState.loading = false; 80 | let times = []; 81 | let memory = []; 82 | if(value.data) { 83 | for(let i = 0; i < value.data.length; i++) { 84 | let item = value.data[i]; 85 | times.push(item.time); 86 | memory.push(item.value/1024/1024) 87 | } 88 | } 89 | tempState.chartOption.xAxis[0].data = times; 90 | tempState.chartOption.series[0].data = memory; 91 | this.setState(tempState); 92 | } 93 | instanceChange = (value) => { 94 | let tempState = Object.assign({}, this.state); 95 | for(let i = 0; i < tempState.instances.length; i++) { 96 | if(tempState.instances[i].showName == value) { 97 | tempState.currentInstance = tempState.instances[i]; 98 | break; 99 | } 100 | } 101 | this.setState(tempState); 102 | } 103 | initInstances = (value) => { 104 | let tempState = Object.assign({}, this.state); 105 | tempState.instances = []; 106 | for(let key in value.data) { 107 | tempState.instances.push({ 108 | showName: key + '(' + value.data[key].host + 109 | ':' + value.data[key].port + ')', 110 | name: key, 111 | host: value.data[key].host, 112 | port: value.data[key].port 113 | }) 114 | } 115 | if(tempState.instances[0]) { 116 | tempState.currentInstance = tempState.instances[0]; 117 | } 118 | 119 | this.setState(tempState, () => { 120 | this.reqMemoryData(); 121 | }); 122 | } 123 | reqMemoryData = () => { 124 | let _self = this; 125 | let data = { 126 | name: this.state.currentInstance.name, 127 | type: this.state.type, 128 | beginTime: this.state.beginTime, 129 | endTime: this.state.endTime 130 | } 131 | Glo_getRequest({ 132 | context: _self, 133 | init: false, 134 | data: data, 135 | url: '/monitor/server/memory-change', 136 | response: (err, res) => { 137 | let responseResult = JSON.parse(res.text); 138 | if (responseResult.code === 1) { 139 | _self.renderCharts(responseResult); 140 | } else { 141 | message.error(responseResult.message); 142 | } 143 | } 144 | }); 145 | 146 | } 147 | componentWillMount() { 148 | let _self = this; 149 | let tempState = Object.assign({}, this.state); 150 | tempState.beginTime = moment(this.currentDate).hour(0).minute(0).second(0).format('YYYY-MM-DD HH:mm:ss'); 151 | tempState.endTime = moment(this.currentDate).hour(23).minute(59).second(59).format('YYYY-MM-DD HH:mm:ss'); 152 | this.setState(tempState); 153 | Glo_getRequest({ 154 | context: _self, 155 | init: true, 156 | url: '/monitor/instance', 157 | response: (err, res) => { 158 | let responseResult = JSON.parse(res.text); 159 | if (responseResult.code === 1) { 160 | _self.initInstances(responseResult); 161 | } else { 162 | message.error(responseResult.message); 163 | } 164 | } 165 | }); 166 | } 167 | 168 | render() { 169 | let _self = this; 170 | return ( 171 |
    172 |
    173 | 181 | 188 | this.rangePickChange(dateString)} 190 | defaultValue={[moment(this.state.beginTime, "YYYY-MM-DD HH:mm:ss"), 191 | moment(this.state.endTime, "YYYY-MM-DD HH:mm:ss")]}/> 192 | 193 |
    194 |
    195 | 196 | 197 |

    当前时间点实例使用的内存大小

    198 |
    199 |
    200 | 201 |
    202 | ); 203 | } 204 | } 205 | 206 | let route = { 207 | path: '/memoryInfo', 208 | component: PageContent 209 | }; 210 | 211 | export default route; -------------------------------------------------------------------------------- /client/src/pages/memoryInfo/style.less: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jizhuofeng/redis-monitor/db98310ec66fb66ae31309b8501c1faa30c5d71b/client/src/pages/memoryInfo/style.less -------------------------------------------------------------------------------- /client/src/pages/qpsInfo/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import moment from 'moment'; 3 | 4 | import { Input, Button, Spin, Form, Row, Col, DatePicker, message, Tabs, Icon, Tag, 5 | Modal, Select, Dropdown, Card} from 'antd'; 6 | 7 | const FormItem = Form.Item; 8 | const TabPane = Tabs.TabPane; 9 | const InputGroup = Input.Group; 10 | const Option = Select.Option; 11 | const RangePicker = DatePicker.RangePicker; 12 | 13 | import { Glo_getRequest, Glo_postRequest } from '../../global/js/request'; 14 | import { Glo_timeStampFormat } from '../../global/js/date'; 15 | import {Charts} from '../../components/react-echarts/index'; 16 | import {Glo_getCurrentDate, Glo_getPreDate} from '../../global/js/date'; 17 | import './style'; 18 | 19 | //page framework 20 | class PageContent extends React.Component { 21 | constructor(props) { 22 | super(props); 23 | this.state = { 24 | loading: false, 25 | beginTime: moment().hour(0).minute(0).second(0).format('YYYY-MM-DD HH:mm:ss'), 26 | endTime: moment().format('YYYY-MM-DD HH:mm:ss'), 27 | type: '1', 28 | instances: [], 29 | currentInstance: { 30 | 31 | }, 32 | chartOption: { 33 | tooltip: { 34 | trigger: 'axis' 35 | }, 36 | legend: { 37 | data: ['吞吐率'] 38 | }, 39 | grid: { 40 | left: '4%', 41 | right: '3%', 42 | bottom: '2%', 43 | containLabel: true 44 | }, 45 | xAxis: [ 46 | { 47 | type: 'category', 48 | data: [] 49 | } 50 | ], 51 | yAxis: [ 52 | { 53 | type: 'value' 54 | } 55 | ], 56 | series: [ 57 | { 58 | name: '吞吐率', 59 | type: 'line', 60 | data: [] 61 | } 62 | ] 63 | } 64 | }; 65 | } 66 | rangePickChange = (timeArr) => { 67 | let tempState = Object.assign({}, this.state); 68 | tempState.beginTime = timeArr[0]; 69 | tempState.endTime = timeArr[1]; 70 | this.setState(tempState); 71 | }; 72 | typeChange = (value) => { 73 | let tempState = Object.assign({}, this.state); 74 | tempState.type = value; 75 | this.setState(tempState); 76 | } 77 | renderCharts = (value) => { 78 | let tempState = Object.assign({}, this.state); 79 | tempState.loading = false; 80 | let times = []; 81 | let memory = []; 82 | if(value.data) { 83 | for(let i = 0; i < value.data.length; i++) { 84 | let item = value.data[i]; 85 | times.push(item.time); 86 | memory.push(item.value); 87 | } 88 | } 89 | tempState.chartOption.xAxis[0].data = times; 90 | tempState.chartOption.series[0].data = memory; 91 | this.setState(tempState); 92 | } 93 | instanceChange = (value) => { 94 | let tempState = Object.assign({}, this.state); 95 | for(let i = 0; i < tempState.instances.length; i++) { 96 | if(tempState.instances[i].showName == value) { 97 | tempState.currentInstance = tempState.instances[i]; 98 | break; 99 | } 100 | } 101 | this.setState(tempState); 102 | } 103 | initInstances = (value) => { 104 | let tempState = Object.assign({}, this.state); 105 | tempState.instances = []; 106 | for(let key in value.data) { 107 | tempState.instances.push({ 108 | showName: key + '(' + value.data[key].host + 109 | ':' + value.data[key].port + ')', 110 | name: key, 111 | host: value.data[key].host, 112 | port: value.data[key].port 113 | }) 114 | } 115 | if(tempState.instances[0]) { 116 | tempState.currentInstance = tempState.instances[0]; 117 | } 118 | 119 | this.setState(tempState); 120 | this.reqQpsData(); 121 | } 122 | reqQpsData = () => { 123 | let _self = this; 124 | let data = { 125 | name: this.state.currentInstance.name, 126 | type: this.state.type, 127 | beginTime: this.state.beginTime, 128 | endTime: this.state.endTime 129 | } 130 | Glo_getRequest({ 131 | context: _self, 132 | init: false, 133 | data: data, 134 | url: '/monitor/server/qps', 135 | response: (err, res) => { 136 | let responseResult = JSON.parse(res.text); 137 | if (responseResult.code === 1) { 138 | _self.renderCharts(responseResult); 139 | } else { 140 | message.error(responseResult.message); 141 | } 142 | } 143 | }); 144 | 145 | } 146 | componentWillMount() { 147 | let _self = this; 148 | let tempState = Object.assign({}, this.state); 149 | tempState.beginTime = moment(this.currentDate).hour(0).minute(0).second(0).format('YYYY-MM-DD HH:mm:ss'); 150 | tempState.endTime = moment(this.currentDate).hour(23).minute(59).second(59).format('YYYY-MM-DD HH:mm:ss'); 151 | this.setState(tempState); 152 | Glo_getRequest({ 153 | context: _self, 154 | init: true, 155 | url: '/monitor/instance', 156 | response: (err, res) => { 157 | let responseResult = JSON.parse(res.text); 158 | if (responseResult.code === 1) { 159 | _self.initInstances(responseResult); 160 | } else { 161 | message.error(responseResult.message); 162 | } 163 | } 164 | }); 165 | } 166 | 167 | render() { 168 | let _self = this; 169 | return ( 170 |
    171 |
    172 | 180 | 187 | this.rangePickChange(dateString)} 189 | defaultValue={[moment(this.state.beginTime, "YYYY-MM-DD HH:mm:ss"), 190 | moment(this.state.endTime, "YYYY-MM-DD HH:mm:ss")]}/> 191 | 192 |
    193 |
    194 | 195 | 196 |

    单位时间执行的命令数

    197 |
    198 |
    199 | 200 |
    201 | ); 202 | } 203 | } 204 | 205 | let route = { 206 | path: '/qpsInfo', 207 | component: PageContent 208 | }; 209 | 210 | export default route; -------------------------------------------------------------------------------- /client/src/pages/qpsInfo/style.less: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jizhuofeng/redis-monitor/db98310ec66fb66ae31309b8501c1faa30c5d71b/client/src/pages/qpsInfo/style.less -------------------------------------------------------------------------------- /client/webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var path =require('path'); 3 | var config = { 4 | entry: [ 5 | 'webpack/hot/only-dev-server', 6 | './index.js' 7 | ], 8 | output: { 9 | path: path.join(__dirname ,'../server/public/js/build'), 10 | filename: 'build.js' 11 | }, 12 | resolve: { 13 | extensions: ['.js', '.jsx', '.json', '.less'] 14 | }, 15 | module: { 16 | rules: [{ 17 | test: /\.js$/, 18 | exclude: /node_modules/, 19 | loader: 'babel-loader', 20 | query: { 21 | cacheDirectory: true, 22 | plugins: [['import', {'libraryName': 'antd', 'style': true}]], 23 | presets: ['es2015', 'react', 'stage-0', 'stage-1'] 24 | } 25 | }, { 26 | test: /\.less$/, 27 | loader: 'style-loader!css-loader!less-loader' 28 | }] 29 | }, 30 | devServer: { 31 | host: '127.0.0.1', 32 | port: 9999, 33 | proxy: { 34 | '/monitor/*': { 35 | target: 'http://127.0.0.1:12000' 36 | } 37 | } 38 | }, 39 | }; 40 | 41 | module.exports = config; 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redis-monitor", 3 | "version": "1.0.0", 4 | "description": "koa+react+antd写的一个简单redis状态监控工具", 5 | "main": "./server/bin/index.js", 6 | "scripts": { 7 | "start": "supervisor ./server/bin/index.js", 8 | "dev": "supervisor ./server/bin/index.js ", 9 | "dev-schedule": "supervisor ./server/schedule/index.js", 10 | "product": "pm2 start ./server/bin/index.js --name redis-monitor", 11 | "product-schedule": "pm2 start ./server/schedule/index.js --name redis-monitor-schedule", 12 | "test": "echo \"Error: no test specified\" && exit 1" 13 | }, 14 | "dependencies": { 15 | "koa": "^2.2.0", 16 | "koa-bodyparser": "^4.2.0", 17 | "koa-favicon": "^2.0.0", 18 | "koa-router": "^7.0.1", 19 | "koa-static": "^3.0.0", 20 | "log4js": "^1.1.1", 21 | "moment": "^2.18.1", 22 | "node-schedule": "^1.2.1", 23 | "redis": "^2.7.1" 24 | }, 25 | "devDependencies": {}, 26 | "repository": { 27 | "type": "git", 28 | "url": "https://github.com/jizhuofeng/redis-monitor.git" 29 | }, 30 | "keywords": [ 31 | "redis", 32 | "monitor" 33 | ], 34 | "author": "jizhuofeng", 35 | "license": "MIT" 36 | } 37 | -------------------------------------------------------------------------------- /server/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by jason on 2017/3/28. 3 | */ 4 | 5 | 'use strict'; 6 | const Koa = require('koa'); 7 | const path = require('path'); 8 | const bodyparser = require('koa-bodyparser'); 9 | const staticServer = require('koa-static'); 10 | const responseTime = require('./common/middleware/response-time'); 11 | const router = require('./router'); 12 | const app = new Koa(); 13 | 14 | app.use(bodyparser()); 15 | app.use(responseTime); 16 | //静态资源配置 17 | app.use(staticServer(__dirname + '/public')); 18 | //应用路由 19 | router(app); 20 | module.exports = app; -------------------------------------------------------------------------------- /server/bin/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | let app = require('../app'); 8 | let config = require('../config'); 9 | let logger = require('../common/logger').logger; 10 | let http = require('http'); 11 | 12 | /** 13 | * Get port from environment and store in Express. 14 | */ 15 | 16 | var port = normalizePort(process.env.PORT || config.serverPort); 17 | // app.set('port', port); 18 | 19 | /** 20 | * Create HTTP server. 21 | */ 22 | 23 | var server = http.createServer(app.callback()); 24 | 25 | /** 26 | * Listen on provided port, on all network interfaces. 27 | */ 28 | 29 | server.listen(port); 30 | server.on('error', onError); 31 | server.on('listening', onListening); 32 | 33 | /** 34 | * Normalize a port into a number, string, or false. 35 | */ 36 | 37 | function normalizePort(val) { 38 | var port = parseInt(val, 10); 39 | 40 | if (isNaN(port)) { 41 | // named pipe 42 | return val; 43 | } 44 | 45 | if (port >= 0) { 46 | // port number 47 | return port; 48 | } 49 | 50 | return false; 51 | } 52 | 53 | /** 54 | * Event listener for HTTP server "error" event. 55 | */ 56 | 57 | function onError(error) { 58 | if (error.syscall !== 'listen') { 59 | throw error; 60 | } 61 | 62 | var bind = typeof port === 'string' 63 | ? 'Pipe ' + port 64 | : 'Port ' + port; 65 | 66 | // handle specific listen errors with friendly messages 67 | switch (error.code) { 68 | case 'EACCES': 69 | logger.error(bind + ' requires elevated privileges'); 70 | process.exit(1); 71 | break; 72 | case 'EADDRINUSE': 73 | logger.error(bind + ' is already in use'); 74 | process.exit(1); 75 | break; 76 | default: 77 | throw error; 78 | } 79 | } 80 | 81 | /** 82 | * Event listener for HTTP server "listening" event. 83 | */ 84 | 85 | function onListening() { 86 | var addr = server.address(); 87 | var bind = typeof addr === 'string' 88 | ? 'pipe ' + addr 89 | : 'port ' + addr.port; 90 | logger.info('Listening on ' + bind); 91 | } 92 | -------------------------------------------------------------------------------- /server/common/logger.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by jason on 2017/3/28. 3 | */ 4 | 'use strict'; 5 | 6 | let log4js = require('log4js'); 7 | let path = require('path'); 8 | log4js.configure({ 9 | "replaceConsole": true, 10 | "appenders":[{ 11 | "category": "console", 12 | "type": "console" 13 | }, 14 | { 15 | "type": "file", 16 | "category": "logger", 17 | "filename": path.join(__dirname, "../logs/redis-monitor.log"), 18 | "pattern": "-yyyy-MM-dd", 19 | "alwaysIncludePattern": true, 20 | "layout":{ 21 | "type":"pattern", 22 | "pattern" : "%d(%x{pid})[%p][%c] %m", 23 | "tokens":{ 24 | "pid": function(){return process.pid;} 25 | } 26 | } 27 | } 28 | ], 29 | "levels":{ 30 | "[all]": "DEBUG" 31 | } 32 | }) 33 | 34 | module.exports = { 35 | 'logger': log4js.getLogger('logger') 36 | } 37 | 38 | -------------------------------------------------------------------------------- /server/common/middleware/response-time.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const logger = require('../logger').logger; 3 | 4 | module.exports = async function (ctx, next){ 5 | let start = new Date(); 6 | await next(); 7 | let ms = new Date() - start; 8 | logger.info(`request : ${ctx.method} ${ctx.url} used ${ms}ms`); 9 | } -------------------------------------------------------------------------------- /server/common/redis-client.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by jason on 2017/3/29. 3 | */ 4 | 'use strict'; 5 | 6 | const redis = require('redis'); 7 | const config = require('../config'); 8 | const logger = require('./logger').logger; 9 | const redisClient= redis.createClient(config.redis); 10 | 11 | redisClient.on('error', function (err) { 12 | logger.error('redis error event - ' + config.redis.host + ':' + 13 | config.redis.port + ' - ' + err); 14 | }); 15 | 16 | module.exports = redisClient; 17 | 18 | -------------------------------------------------------------------------------- /server/common/response.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = new class { 4 | constructor() { 5 | 6 | } 7 | SUCCESS(ctx, data = [], pageCount = 1, pageCurrent = 1) { 8 | if(!data) { 9 | data = {}; 10 | } 11 | ctx.body = { 12 | code: 1, 13 | message: 'success', 14 | data: data, 15 | pageCount: pageCount, 16 | pageCurrent: pageCurrent 17 | } 18 | } 19 | FAILED(ctx, message = 'failed') { 20 | ctx.body = { 21 | code: 0, 22 | message: message 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /server/common/util/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by jason on 2017/3/29. 3 | */ 4 | 'use strict'; 5 | 6 | //解析redis info命令返回的数据结构 7 | exports.parseRedisServerInfo = function (info) { 8 | 9 | let data = { 10 | } 11 | if(!info) { 12 | return data; 13 | } 14 | let tmp = info.split('#'); 15 | for(let i = 0; i < tmp.length; i++) { 16 | if(tmp[i] == '') { 17 | continue; 18 | } 19 | let items = tmp[i].split('\r\n'); 20 | let resTmp = { 21 | 22 | } 23 | for(let k = 1; k < items.length; k++) { 24 | if(items[k] == '') { 25 | continue; 26 | } 27 | let item = items[k].split(':'); 28 | resTmp[item[0]] = item[1]; 29 | 30 | } 31 | data[items[0].substr(1, items[0].length)] = resTmp 32 | } 33 | return data; 34 | } -------------------------------------------------------------------------------- /server/config/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by jason on 2017/3/28. 3 | */ 4 | 'use strict'; 5 | 6 | module.exports = { 7 | serverPort: 12000, 8 | redis: { 9 | host: '127.0.0.1', 10 | port: 6379, 11 | db: 2 12 | } 13 | } -------------------------------------------------------------------------------- /server/controller/instance-list-ctl.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by jason on 2017/3/28. 3 | */ 4 | 'use strict'; 5 | let InstanceService = require('../service/instance-service'); 6 | let logger = require('../common/logger').logger; 7 | let Response = require('../common/response'); 8 | 9 | module.exports = new class { 10 | constructor() { 11 | } 12 | //获取redis实例信息 13 | async getInstance(ctx, next) { 14 | 15 | let [name] = []; 16 | if(ctx.query) { 17 | ({name} = ctx.query); 18 | } 19 | logger.info('get instance list request body %s', JSON.stringify(ctx.query)); 20 | try { 21 | let info = await InstanceService.getInstanceList(name); 22 | logger.info('get instance list %s', JSON.stringify(info)); 23 | Response.SUCCESS(ctx, info); 24 | } catch(err) { 25 | logger.error('get instance list error %s', err.toString()); 26 | Response.FAILED(ctx, '获取失败'); 27 | } 28 | 29 | } 30 | //添加redis实例 31 | async addInstance(ctx, next) { 32 | 33 | let [name, host, port, password] = []; 34 | if(ctx.request.body) { 35 | ({name, host, port, password} = ctx.request.body); 36 | } 37 | logger.info('add instance request body %s', JSON.stringify(ctx.request.body)); 38 | if(!host || !port) { 39 | logger.warn('add instance less params host or port'); 40 | Response.FAILED(ctx, '缺少参数'); 41 | return; 42 | } 43 | if(!name) { 44 | name = host + ':' + port; 45 | } 46 | try { 47 | 48 | let res = await InstanceService.addInstance(name, host, port, password); 49 | logger.info('add instance result %s', res); 50 | if(res != 'success') { 51 | Response.FAILED(ctx, '添加失败(' + res + ')'); 52 | return; 53 | } 54 | Response.SUCCESS(ctx); 55 | } catch(err) { 56 | logger.error('add instance error %s', err.toString()); 57 | Response.FAILED(ctx, '添加失败'); 58 | } 59 | } 60 | async deleteInstance(ctx, next) { 61 | let [name] = []; 62 | if(ctx.request.body) { 63 | ({name} = ctx.request.body); 64 | } 65 | logger.info('delete instance request body %s', JSON.stringify(ctx.request.body)); 66 | if(!name) { 67 | logger.warn('delete instance less params name'); 68 | Response.FAILED(ctx, '缺少参数'); 69 | return; 70 | } 71 | try { 72 | let res = await InstanceService.deleteInstance(name); 73 | logger.info('delete instance result %s', res); 74 | if(res != 'success') { 75 | Response.FAILED(ctx, '删除失败(' + res + ')'); 76 | return; 77 | } 78 | Response.SUCCESS(ctx); 79 | 80 | } catch(err) { 81 | logger.error('delete instance error %s', err.toString()); 82 | Response.FAILED(ctx, '删除失败'); 83 | } 84 | 85 | 86 | } 87 | async renameInstance(ctx, next) { 88 | let [name, oldName] = []; 89 | if(ctx.request.body) { 90 | ({name, oldName} = ctx.request.body); 91 | } 92 | logger.info('rename instance request body %s', JSON.stringify(ctx.request.body)); 93 | if(!name || !oldName) { 94 | logger.warn('rename instance less params name or oldName'); 95 | Response.FAILED(ctx, '缺少参数'); 96 | return; 97 | } 98 | try { 99 | let res = await InstanceService.renameInstance(name, oldName); 100 | logger.info('rename instance result %s', res); 101 | if(res != 'success') { 102 | Response.FAILED(ctx, '重命名失败(' + res + ')'); 103 | return; 104 | } 105 | Response.SUCCESS(ctx); 106 | } catch(err) { 107 | logger.error('rename instance error %s', err.toString()); 108 | Response.FAILED(ctx, '重命名失败'); 109 | } 110 | } 111 | } -------------------------------------------------------------------------------- /server/controller/instance-status-ctl.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by jason on 2017/3/28. 3 | */ 4 | 'use strict'; 5 | let InstanceService = require('../service/instance-service'); 6 | let MemoryService = require('../service/memory-management-service'); 7 | let QpsService = require('../service/qps-management-service'); 8 | let logger = require('../common/logger').logger; 9 | let Response = require('../common/response'); 10 | 11 | module.exports = new class { 12 | constructor() { 13 | } 14 | async getServerBaseInfo(ctx, next) { 15 | //获取实例info命令返回的数据 16 | let [name] = []; 17 | if(ctx.query) { 18 | ({name} = ctx.query); 19 | } 20 | logger.info('get server base info request body %s', JSON.stringify(ctx.query)); 21 | if(!name) { 22 | logger.warn('get server base info less params name'); 23 | Response.FAILED(ctx, '缺少参数'); 24 | return; 25 | } 26 | try { 27 | let info = await InstanceService.getServerBaseInfo(name); 28 | logger.info('get server base info %s', JSON.stringify(info)); 29 | Response.SUCCESS(ctx, info); 30 | } catch(err) { 31 | logger.error('get server base info error %s', err.toString()); 32 | Response.FAILED(ctx, '获取失败'); 33 | } 34 | } 35 | async getMemoryChangeInfo(ctx, next) { 36 | let [name, type, beginTime, endTime] = []; 37 | if(ctx.query) { 38 | ({name, type, beginTime, endTime} = ctx.query); 39 | } 40 | //type 1=分钟, 2=小时,3=天 41 | //type=1时时间区间限制为一天内的数据(beginTime当天的数据) 42 | //type=2时时间区间限制为一周内的数据(beginTime之后一周内的数据) 43 | //type=3时时间区间无限制 44 | if(!name || !type || !beginTime || !endTime) { 45 | logger.warn('get memory info less params type or beginTime or endTime'); 46 | Response.FAILED(ctx, '缺少参数'); 47 | return; 48 | } 49 | type = parseInt(type, 10); 50 | try { 51 | let data = []; 52 | data = await MemoryService.getMemoryChangeInfo(name, type, beginTime, endTime); 53 | Response.SUCCESS(ctx, data); 54 | } catch(err) { 55 | logger.error('get memory info error %s', err.toString()); 56 | Response.FAILED(ctx, '获取失败'); 57 | } 58 | } 59 | async getQpsInfo(ctx, next) { 60 | let [name, type, beginTime, endTime] = []; 61 | if(ctx.query) { 62 | ({name, type, beginTime, endTime} = ctx.query); 63 | } 64 | //type 1=分钟, 2=小时,3=天 65 | //type=1时时间区间限制为一天内的数据(beginTime当天的数据) 66 | //type=2时时间区间限制为一周内的数据(beginTime之后一周内的数据) 67 | //type=3时时间区间无限制 68 | if(!name || !type || !beginTime || !endTime) { 69 | logger.warn('get qps info less params type or beginTime'); 70 | Response.FAILED(ctx, '缺少参数'); 71 | return; 72 | } 73 | type = parseInt(type, 10); 74 | try { 75 | let data = []; 76 | data = await QpsService.getQpsInfo(name, type, beginTime, endTime); 77 | Response.SUCCESS(ctx, data); 78 | } catch(err) { 79 | logger.error('get qps info error %s', err.toString()); 80 | Response.FAILED(ctx, '获取失败'); 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /server/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jizhuofeng/redis-monitor/db98310ec66fb66ae31309b8501c1faa30c5d71b/server/public/favicon.ico -------------------------------------------------------------------------------- /server/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | redis服务监控 6 | 7 | 8 |
    9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /server/router/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by jason on 2017/3/28. 3 | */ 4 | 'use strict'; 5 | const Router = require('koa-router'); 6 | const fs = require('fs'); 7 | 8 | const appRouter = new Router(); 9 | //动态引入router目录下路由文件 10 | let files = fs.readdirSync(__dirname); 11 | if (files && files.length > 0) { 12 | for(let i = 0; i < files.length; i++) { 13 | let file = files[i]; 14 | let pos = file.lastIndexOf('.'); 15 | if (pos == -1) { 16 | continue; 17 | } 18 | let fileName = file.substr(0, pos); 19 | let fileExt = file.substr(pos+1); 20 | if (fileName === 'index') { 21 | continue; 22 | } 23 | let router = require('./' + fileName); 24 | appRouter.use('/monitor', router.routes(), router.allowedMethods()); 25 | } 26 | } 27 | function router(app){ 28 | app.use(appRouter.routes()); 29 | } 30 | 31 | module.exports = router; -------------------------------------------------------------------------------- /server/router/instance-list-router.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by jason on 2017/3/28. 3 | */ 4 | 'use strict'; 5 | const Router = require('koa-router'); 6 | const ILCtl = require('../controller/instance-list-ctl'); 7 | 8 | const router = new Router({ 9 | }); 10 | //监控系统监控的实例列表管理 11 | //获取实例列表 12 | router.get('/instance', ILCtl.getInstance); 13 | //添加实例 14 | router.post('/instance', ILCtl.addInstance); 15 | //删除实例 16 | router.del('/instance', ILCtl.deleteInstance); 17 | //实例重命名 18 | router.post('/instance/rename', ILCtl.renameInstance); 19 | 20 | module.exports = router; 21 | -------------------------------------------------------------------------------- /server/router/instance-status-router.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by jason on 2017/3/28. 3 | */ 4 | 'use strict'; 5 | const Router = require('koa-router'); 6 | const ISCtl = require('../controller/instance-status-ctl'); 7 | 8 | 9 | const router = new Router({ 10 | }); 11 | //实例运行状态信息获取 12 | router.get('/server/base-info', ISCtl.getServerBaseInfo); 13 | //获取实例内存变化 14 | router.get('/server/memory-change', ISCtl.getMemoryChangeInfo); 15 | //获取实例处理请求的变化 16 | router.get('/server/qps', ISCtl.getQpsInfo); 17 | module.exports = router; 18 | -------------------------------------------------------------------------------- /server/schedule/create-redis-server-info.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by jason on 2017/3/28. 3 | */ 4 | 'use strict'; 5 | const moment = require('moment'); 6 | const redis = require('redis'); 7 | const InstanceService = require('../service/instance-service'); 8 | const logger = require('../common/logger').logger; 9 | const util = require('../common/util'); 10 | 11 | //同步redis数据 12 | exports.createData = async function () { 13 | try { 14 | //获取实例列表 15 | let redisList = await InstanceService.getInstanceList(null, true); 16 | logger.debug('create data redis list %s', JSON.stringify(redisList)); 17 | for(let key in redisList) { 18 | //遍历实例 19 | let res = await InstanceService.syncRedisInstanceInfo(redisList[key]); 20 | logger.info('create data result %s', res); 21 | } 22 | } catch (err) { 23 | logger.error('create data occour error %s', err.toString()); 24 | 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /server/schedule/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by jason on 2017/3/28. 3 | */ 4 | 'use strict'; 5 | 6 | let schedule = require('node-schedule'); 7 | let CRSI = require('./create-redis-server-info'); 8 | let logger = require('../common/logger').logger; 9 | 10 | 11 | let rule = '0 * * * * *'; 12 | schedule.scheduleJob(rule, CRSI.createData); 13 | logger.debug('schedule start'); 14 | 15 | -------------------------------------------------------------------------------- /server/service/api/redis.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by jason on 2017/3/29. 3 | */ 4 | 'use strict'; 5 | let redisClient = require('../../common/redis-client'); 6 | 7 | //redis各种api接口封装 8 | //如果提供client参数则使用提供的client, 若不提供client则默认使用全局的redisClient 9 | module.exports = new class { 10 | constructor() { 11 | 12 | } 13 | //hgetall 14 | hgetall(key, client = redisClient) { 15 | return new Promise(function(resolve, reject) { 16 | client.hgetall(key, function(err, res) { 17 | if(!err && res) { 18 | resolve(res); 19 | return; 20 | } 21 | resolve(null); 22 | }) 23 | }) 24 | } 25 | //hget 26 | hget(key, field, client = redisClient) { 27 | return new Promise(function(resolve, reject) { 28 | client.hget(key, field, function(err, res) { 29 | if(!err && res) { 30 | resolve(res); 31 | return; 32 | } 33 | resolve(null); 34 | }) 35 | }) 36 | } 37 | //hmget 38 | hmget(key, fields, client = redisClient) { 39 | return new Promise(function(resolve, reject) { 40 | client.hmget(key, fields, function(err, res) { 41 | if(!err && res) { 42 | resolve(res); 43 | return; 44 | } 45 | resolve(null); 46 | }) 47 | }) 48 | } 49 | //hset 50 | hset(key, field, value, client = redisClient) { 51 | return new Promise(function(resolve, reject) { 52 | client.hset(key, field, value, function(err, res) { 53 | if(!err && res) { 54 | resolve(res); 55 | return; 56 | } 57 | resolve(null); 58 | }) 59 | }) 60 | } 61 | //hdel 62 | hdel(key, field, client = redisClient) { 63 | return new Promise(function(resolve, reject) { 64 | client.hdel(key, field, function(err, res) { 65 | if(!err && res) { 66 | resolve(res); 67 | return; 68 | } 69 | resolve(null); 70 | }) 71 | }) 72 | } 73 | //info 74 | info(client) { 75 | return new Promise(function(resolve, reject) { 76 | client.info(function(err, res) { 77 | if(!err && res) { 78 | resolve(res); 79 | return; 80 | } 81 | resolve(null); 82 | }) 83 | }) 84 | } 85 | } -------------------------------------------------------------------------------- /server/service/instance-service.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by jason on 2017/3/29. 3 | */ 4 | 'use strict'; 5 | let moment = require('moment'); 6 | let redis = require('redis'); 7 | let RedisApi = require('./api/redis'); 8 | let util = require('../common/util'); 9 | let logger = require('../common/logger').logger; 10 | 11 | module.exports = new class { 12 | constructor() { 13 | 14 | } 15 | //检查实例是否可以已存在 16 | async checkInstanceExists(host, port) { 17 | let insList = await this.getInstanceList(); 18 | if(insList) { 19 | for(let key in insList) { 20 | let ins = insList[key]; 21 | if(ins.host == host && ins.port == port) { 22 | return true; 23 | } 24 | } 25 | } 26 | return false; 27 | } 28 | //检查实例是否可以正常连接 29 | async checkInstanceConnect(host, port, password) { 30 | let config = { 31 | host: host, 32 | port: port 33 | } 34 | if(password) { 35 | config.password = password; 36 | } 37 | return new Promise(function(resolve, reject) { 38 | let client = redis.createClient(config); 39 | client.on('error', function(err) { 40 | client.quit(); 41 | resolve(false); 42 | }); 43 | client.on('ready', function() { 44 | client.quit(); 45 | resolve(true); 46 | }); 47 | }); 48 | } 49 | //获取实例info命令数据 50 | async getServerBaseInfo(name) { 51 | let key = 'server-instance-list'; 52 | let insConfig = await RedisApi.hget(key, name); 53 | if(!insConfig) { 54 | logger.info('get server base info instance not exists'); 55 | return null; 56 | } 57 | let config = JSON.parse(insConfig); 58 | let canConnect = await this.checkInstanceConnect(config.host, config.port, config.password); 59 | if(!canConnect) { 60 | logger.info('get server base info instance can not connect'); 61 | return null; 62 | } 63 | let client = redis.createClient(config); 64 | client.on('error', function(err) { 65 | logger.error('get server info connect redis %s', err.toString()); 66 | client.quit(); 67 | }); 68 | let info = await RedisApi.info(client); 69 | //解析redis info 命令返回的数据 70 | let infoObj = util.parseRedisServerInfo(info); 71 | return infoObj; 72 | } 73 | //实例重命名 74 | async renameInstance(name, oldName) { 75 | let key = 'server-instance-list'; 76 | let field = name; 77 | let fieldOld = oldName; 78 | let newInfo = await RedisApi.hget(key, field); 79 | if(newInfo) { 80 | return 'instance has exists'; 81 | } 82 | let oldInfo = await RedisApi.hget(key, fieldOld); 83 | if(oldInfo) { 84 | //重新添加 85 | let addRes = await RedisApi.hset(key, field, oldInfo); 86 | //删除 87 | let delRes = await RedisApi.hdel(key, fieldOld); 88 | return 'success'; 89 | } else { 90 | return 'instance not exists'; 91 | } 92 | } 93 | //删除实例 94 | async deleteInstance(name) { 95 | 96 | let field = name; 97 | let key = 'server-instance-list'; 98 | let ins = await RedisApi.hget(key, field); 99 | if(!ins) { 100 | //实例不存在 101 | logger.info('delete instance instance not exists'); 102 | return 'instance not exists'; 103 | } 104 | let res = await RedisApi.hdel(key, field); 105 | return 'success'; 106 | } 107 | 108 | //添加实例 109 | async addInstance(name, host, port, password) { 110 | //检察是否已存在 111 | let exists = await this.checkInstanceExists(host, port); 112 | if(exists) { 113 | logger.info('add instance instance has exists'); 114 | return 'instance has exists'; 115 | } 116 | //检查实例是否可正常连接 117 | // let canConnect = await this.checkInstanceConnect(host, port, password); 118 | // if(!canConnect) { 119 | // logger.info('add instance instance can not connect'); 120 | // return 'can not connect'; 121 | // } 122 | let info = { 123 | host: host, 124 | port: port 125 | } 126 | if(password) { 127 | info.password = password; 128 | } 129 | let field = name; 130 | let key = 'server-instance-list'; 131 | let value = JSON.stringify(info); 132 | let res = await RedisApi.hset(key, field, value); 133 | return 'success'; 134 | } 135 | //获取实例列表 136 | async getInstanceList(name, flag = false) { 137 | let key = 'server-instance-list'; 138 | let list = {}; 139 | if(name) { 140 | let tmp = await RedisApi.hget(key, name); 141 | if(tmp) { 142 | let ins = JSON.parse(tmp) 143 | list[name] = { 144 | host: ins.host, 145 | port: ins.port 146 | } 147 | if(flag && ins.password) { 148 | list[name].password = ins.password; 149 | } 150 | } 151 | 152 | } else { 153 | let tmp = await RedisApi.hgetall(key); 154 | if(tmp) { 155 | for(let key in tmp) { 156 | let ins = JSON.parse(tmp[key]) 157 | list[key] = { 158 | host: ins.host, 159 | port: ins.port 160 | } 161 | if(flag && ins.password) { 162 | list[key].password = ins.password; 163 | } 164 | } 165 | } 166 | } 167 | return list; 168 | } 169 | //同步redis实例状态信息 170 | async syncRedisInstanceInfo(config) { 171 | try { 172 | let nowT = moment(); 173 | let client = redis.createClient(config); 174 | client.on('error', function(err) { 175 | logger.error('sync redis instance info connect redis %s', err.toString()); 176 | client.quit(); 177 | }); 178 | let info = await RedisApi.info(client); 179 | //解析redis info 命令返回的数据 180 | let infoObj = util.parseRedisServerInfo(info); 181 | //logger.debug('instance info %s', JSON.stringify(infoObj)); 182 | let res = null; 183 | let dayT = nowT.clone().hour(0).minute(0).second(0).unix(); 184 | let hourT = nowT.clone().minute(0).second(0).unix(); 185 | let minuteT = nowT.clone().second(0).unix(); 186 | let preKey = config.host + ':' + config.port; 187 | let usedMemory = infoObj.Memory.used_memory; 188 | let totalCommand = infoObj.Stats.total_commands_processed; 189 | //内存数据 190 | //分钟信息 191 | await RedisApi.hset(preKey + ':M:M', 192 | minuteT, usedMemory); 193 | //小时信息 194 | let hourV = await RedisApi.hget(preKey + ':M:H', hourT); 195 | if(!hourV || parseInt(hourV) < parseInt(usedMemory)) { 196 | //更新小时内最大值 197 | await RedisApi.hset(preKey + ':M:H', 198 | hourT, usedMemory); 199 | } 200 | //天信息 201 | let dayV = await RedisApi.hget(preKey + ':M:D', dayT); 202 | if(!dayV || parseInt(dayV) < parseInt(usedMemory)) { 203 | //更新天内最大值 204 | await RedisApi.hset(preKey + ':M:D', 205 | dayT, usedMemory); 206 | } 207 | //执行命令数统计 208 | await RedisApi.hset(preKey+":CP:M", minuteT, totalCommand); 209 | client.quit(); 210 | return "success"; 211 | } catch(err) { 212 | logger.error('sync redis instance info error %s', err); 213 | return 'sync failed'; 214 | } 215 | 216 | } 217 | 218 | } -------------------------------------------------------------------------------- /server/service/memory-management-service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | let moment = require('moment'); 3 | let redis = require('redis'); 4 | let RedisApi = require('./api/redis'); 5 | let util = require('../common/util'); 6 | let logger = require('../common/logger').logger; 7 | 8 | //实例内存数据 9 | module.exports = new class { 10 | constructor() { 11 | 12 | } 13 | //按分钟获取 14 | async getMemoryChangeInfoByMinute(config, beginTime, endTime) { 15 | let beginT = moment(beginTime); 16 | let endT = moment(endTime); 17 | let fields = []; 18 | let dateArr = []; 19 | let count = 0; 20 | while(!beginT.isAfter(endT, 'minute')) { 21 | if(count >= 1440) { 22 | break; 23 | } 24 | let tmpT = beginT.clone().second(0); 25 | dateArr.push(tmpT.format('YYYY-MM-DD HH:mm')); 26 | fields.push(tmpT.unix()); 27 | beginT.add(1, 'minutes'); 28 | count++; 29 | } 30 | logger.debug('fields %s', JSON.stringify(fields)); 31 | let key = config.host + ':' + config.port + ':M:M'; 32 | let values = await RedisApi.hmget(key, fields); 33 | let data = []; 34 | if(values) { 35 | for(let i = 0; i < values.length; i++) { 36 | let tmp = { 37 | time: dateArr[i], 38 | value: 0 39 | } 40 | if(values[i]) { 41 | tmp.value = parseInt(values[i]) ; 42 | } 43 | data.push(tmp); 44 | } 45 | } 46 | return data; 47 | } 48 | //按小时获取 49 | async getMemoryChangeInfoByHour(config, beginTime, endTime) { 50 | let beginT = moment(beginTime); 51 | let endT = moment(endTime); 52 | let fields = []; 53 | let dateArr = []; 54 | let count = 0; 55 | while(!beginT.isAfter(endT, 'hour')) { 56 | if(count > 200) { 57 | break; 58 | } 59 | let tmpT = beginT.clone().minute(0).second(0); 60 | dateArr.push(tmpT.format('YYYY-MM-DD HH')); 61 | fields.push(tmpT.unix()); 62 | beginT.add(1, 'hours'); 63 | count++; 64 | } 65 | logger.debug('fields %s', JSON.stringify(fields)); 66 | let key = config.host + ':' + config.port + ':M:H'; 67 | let values = await RedisApi.hmget(key, fields); 68 | let data = []; 69 | if(values) { 70 | for(let i = 0; i < values.length; i++) { 71 | let tmp = { 72 | time: dateArr[i], 73 | value: 0 74 | } 75 | if(values[i]) { 76 | tmp.value = parseInt(values[i]) ; 77 | } 78 | data.push(tmp); 79 | } 80 | } 81 | return data; 82 | } 83 | //按天获取 84 | async getMemoryChangeInfoByDate(config, beginTime, endTime) { 85 | let beginT = moment(beginTime); 86 | let endT = moment(endTime); 87 | let fields = []; 88 | let dateArr = []; 89 | while(!beginT.isAfter(endT, 'day')) { 90 | let tmpT = beginT.clone().hour(0).minute(0).second(0); 91 | dateArr.push(tmpT.format('YYYY-MM-DD')); 92 | fields.push(tmpT.unix()); 93 | beginT.add(1, 'days'); 94 | } 95 | logger.debug('fields %s', JSON.stringify(fields)); 96 | let key = config.host + ':' + config.port + ':M:D'; 97 | let values = await RedisApi.hmget(key, fields); 98 | let data = []; 99 | if(values) { 100 | for(let i = 0; i < values.length; i++) { 101 | let tmp = { 102 | time: dateArr[i], 103 | value: 0 104 | } 105 | if(values[i]) { 106 | tmp.value = parseInt(values[i]) ; 107 | } 108 | data.push(tmp); 109 | } 110 | } 111 | return data; 112 | } 113 | //获取内存变化情况 114 | async getMemoryChangeInfo(name, type, beginTime, endTime) { 115 | let key = 'server-instance-list'; 116 | let insConfig = await RedisApi.hget(key, name); 117 | if(!insConfig) { 118 | logger.info('get memory change info instance not exists'); 119 | return null; 120 | } 121 | let config = JSON.parse(insConfig); 122 | let data = null; 123 | switch(type) { 124 | case 1: 125 | data = await this.getMemoryChangeInfoByMinute(config, beginTime, endTime); 126 | break; 127 | case 2: 128 | data = await this.getMemoryChangeInfoByHour(config, beginTime, endTime); 129 | break; 130 | case 3: 131 | data = await this.getMemoryChangeInfoByDate(config, beginTime, endTime); 132 | break; 133 | } 134 | return data; 135 | 136 | } 137 | } -------------------------------------------------------------------------------- /server/service/qps-management-service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | let moment = require('moment'); 3 | let redis = require('redis'); 4 | let RedisApi = require('./api/redis'); 5 | let util = require('../common/util'); 6 | let logger = require('../common/logger').logger; 7 | 8 | //实例qps数据 9 | module.exports = new class { 10 | constructor() { 11 | 12 | } 13 | //按分钟获取 14 | async getQpsInfoByMinute(config, beginTime, endTime) { 15 | let beginT = moment(beginTime); 16 | let endT = moment(endTime); 17 | let fields = []; 18 | let dateArr = []; 19 | let count = 0; 20 | while(!beginT.isAfter(endT, 'minute')) { 21 | if(count >= 1440) { 22 | break; 23 | } 24 | let tmpT = beginT.clone().second(0); 25 | dateArr.push(tmpT.format('YYYY-MM-DD HH:mm')); 26 | fields.push(tmpT.unix()); 27 | beginT.add(1, 'minutes'); 28 | count++; 29 | } 30 | logger.debug('fields %s', JSON.stringify(fields)); 31 | let key = config.host + ':' + config.port + ':CP:M'; 32 | let values = await RedisApi.hmget(key, fields); 33 | let data = []; 34 | if(values) { 35 | for(let i = 0; i < values.length - 1 ; i++) { 36 | let tmp = { 37 | time: dateArr[i], 38 | value: 0 39 | } 40 | if(values[i+1]) { 41 | let nowAllCP = parseInt(values[i+1], 10); 42 | let lastAllCP = nowAllCP; 43 | if(values[i]) { 44 | lastAllCP = parseInt(values[i], 10); 45 | } 46 | 47 | tmp.value = nowAllCP - lastAllCP; 48 | } 49 | data.push(tmp); 50 | } 51 | } 52 | return data; 53 | } 54 | //按小时获取 55 | async getQpsInfoByHour(config, beginTime, endTime) { 56 | let beginT = moment(beginTime); 57 | let endT = moment(endTime); 58 | let fields = []; 59 | let dateArr = []; 60 | let count = 0; 61 | while(!beginT.isAfter(endT, 'hour')) { 62 | if(count > 200) { 63 | break; 64 | } 65 | let tmpT = beginT.clone().minute(0).second(0); 66 | dateArr.push(tmpT.format('YYYY-MM-DD HH')); 67 | fields.push(tmpT.unix()); 68 | beginT.add(1, 'hours'); 69 | count++; 70 | } 71 | logger.debug('fields %s', JSON.stringify(fields)); 72 | let key = config.host + ':' + config.port + ':CP:M'; 73 | let values = await RedisApi.hmget(key, fields); 74 | let data = []; 75 | if(values) { 76 | for(let i = 0; i < values.length - 1 ; i++) { 77 | let tmp = { 78 | time: dateArr[i], 79 | value: 0 80 | } 81 | if(values[i+1]) { 82 | let nowAllCP = parseInt(values[i+1], 10); 83 | let lastAllCP = nowAllCP; 84 | if(values[i]) { 85 | lastAllCP = parseInt(values[i], 10); 86 | } 87 | 88 | tmp.value = nowAllCP - lastAllCP; 89 | } 90 | data.push(tmp); 91 | } 92 | } 93 | 94 | return data; 95 | } 96 | //按天获取 97 | async getQpsInfoByDate(config, beginTime, endTime) { 98 | let beginT = moment(beginTime); 99 | let endT = moment(endTime); 100 | let fields = []; 101 | let dateArr = []; 102 | while(!beginT.isAfter(endT, 'day')) { 103 | let tmpT = beginT.clone().hour(0).minute(0).second(0); 104 | dateArr.push(tmpT.format('YYYY-MM-DD')); 105 | fields.push(tmpT.unix()); 106 | beginT.add(1, 'days'); 107 | } 108 | logger.debug('fields %s', JSON.stringify(fields)); 109 | let key = config.host + ':' + config.port + ':CP:M'; 110 | let values = await RedisApi.hmget(key, fields); 111 | let data = []; 112 | if(values) { 113 | for(let i = 0; i < values.length - 1 ; i++) { 114 | let tmp = { 115 | time: dateArr[i], 116 | value: 0 117 | } 118 | if(values[i+1]) { 119 | let nowAllCP = parseInt(values[i+1], 10); 120 | let lastAllCP = nowAllCP; 121 | if(values[i]) { 122 | lastAllCP = parseInt(values[i], 10); 123 | } 124 | 125 | tmp.value = nowAllCP - lastAllCP; 126 | } 127 | data.push(tmp); 128 | } 129 | } 130 | return data; 131 | } 132 | //获取内存变化情况 133 | async getQpsInfo(name, type, beginTime, endTime) { 134 | let key = 'server-instance-list'; 135 | let insConfig = await RedisApi.hget(key, name); 136 | if(!insConfig) { 137 | logger.info('get qps info instance not exists'); 138 | return null; 139 | } 140 | let config = JSON.parse(insConfig); 141 | let data = null; 142 | switch(type) { 143 | case 1: 144 | data = await this.getQpsInfoByMinute(config, beginTime, endTime); 145 | break; 146 | case 2: 147 | data = await this.getQpsInfoByHour(config, beginTime, endTime); 148 | break; 149 | case 3: 150 | data = await this.getQpsInfoByDate(config, beginTime, endTime); 151 | break; 152 | } 153 | return data; 154 | 155 | } 156 | } --------------------------------------------------------------------------------