Showing
10 changed files
with
404 additions
and
2 deletions
@@ -50,4 +50,11 @@ app.use(mount('/', webApp)); | @@ -50,4 +50,11 @@ app.use(mount('/', webApp)); | ||
50 | 50 | ||
51 | server.listen(port, () => { | 51 | server.listen(port, () => { |
52 | console.log(`app started in ${port}`); | 52 | console.log(`app started in ${port}`); |
53 | -}); | ||
53 | + | ||
54 | + // process.on('uncaughtException', function(err) { | ||
55 | + // console.error('Caught exception:', err.stack) | ||
56 | + // }); | ||
57 | + // process.on('unhandledRejection', function(reason, p) { | ||
58 | + // console.error('Unhandled Rejection at: Promise ', p, ' reason: ', reason.stack) | ||
59 | + // }); | ||
60 | +}); |
apps/web/actions/profile.js
0 → 100644
1 | +'use strict'; | ||
2 | +const Router = require('koa-router'); | ||
3 | +const SqlBuilder = require('../utils/sql-builder'); | ||
4 | +const request = require('superagent'); | ||
5 | +const _ = require('lodash'); | ||
6 | + | ||
7 | +const r = new Router; | ||
8 | + | ||
9 | +const TABLE = { | ||
10 | + DURATION: 'api-duration', | ||
11 | + REPORT: 'error-report' | ||
12 | +}; | ||
13 | + | ||
14 | +const profile_sql = { | ||
15 | + duration() { | ||
16 | + return SqlBuilder.of(TABLE.DURATION); | ||
17 | + }, | ||
18 | + error() { | ||
19 | + return SqlBuilder.of(TABLE.REPORT); | ||
20 | + } | ||
21 | +}; | ||
22 | + | ||
23 | +async function exec(sql) { | ||
24 | + console.log('influx sql =>', sql); | ||
25 | + return request.get('http://influxd.yoho.cn/query') | ||
26 | + .query({ | ||
27 | + q: sql, | ||
28 | + db: 'web-apm' | ||
29 | + }).then(({body: result}) => { | ||
30 | + const series = _.get(result, 'results[0].series[0]', {}); | ||
31 | + const col = _.get(series, 'columns', []); | ||
32 | + const values = _.get(series, "values", []); | ||
33 | + | ||
34 | + return values.map(v => { | ||
35 | + return _.zipObject(col, v) | ||
36 | + }) | ||
37 | + }); | ||
38 | +} | ||
39 | + | ||
40 | +const profile_service = { | ||
41 | + async time() { | ||
42 | + const model = profile_sql.duration().select('*'); | ||
43 | + const rows = await exec(model.toSql()); | ||
44 | + return {times: rows} | ||
45 | + }, | ||
46 | + | ||
47 | + async error() { | ||
48 | + const model = profile_sql.error().select('*'); | ||
49 | + const rows = await exec(model.toSql()); | ||
50 | + return {errors: rows}; | ||
51 | + } | ||
52 | +}; | ||
53 | + | ||
54 | +const profile_controller = { | ||
55 | + async time_report(ctx) { | ||
56 | + const result = await profile_service.time(); | ||
57 | + await ctx.render('action/profile_time', result); | ||
58 | + }, | ||
59 | + async error_report(ctx) { | ||
60 | + const result = await profile_service.error(); | ||
61 | + await ctx.render('action/profile_error', result); | ||
62 | + } | ||
63 | +}; | ||
64 | + | ||
65 | +r.get('/time', profile_controller.time_report); | ||
66 | +r.get('/error', profile_controller.error_report); | ||
67 | + | ||
68 | +module.exports = r; |
@@ -21,6 +21,7 @@ const api = require('./actions/api'); | @@ -21,6 +21,7 @@ const api = require('./actions/api'); | ||
21 | const abuseProtection = require('./actions/abuse_protection'); | 21 | const abuseProtection = require('./actions/abuse_protection'); |
22 | const crawler = require('./actions/crawler'); | 22 | const crawler = require('./actions/crawler'); |
23 | const seo = require('./actions/seo'); | 23 | const seo = require('./actions/seo'); |
24 | +const profile = require('./actions/profile'); | ||
24 | const checkcode = require('./actions/checkcode').router; | 25 | const checkcode = require('./actions/checkcode').router; |
25 | const noAuth = new Router(); | 26 | const noAuth = new Router(); |
26 | const base = new Router(); | 27 | const base = new Router(); |
@@ -55,6 +56,7 @@ module.exports = function(app) { | @@ -55,6 +56,7 @@ module.exports = function(app) { | ||
55 | base.use('/degrade', degrade.routes(), degrade.allowedMethods()); | 56 | base.use('/degrade', degrade.routes(), degrade.allowedMethods()); |
56 | base.use('/deploys', deploy.routes(), deploy.allowedMethods()); | 57 | base.use('/deploys', deploy.routes(), deploy.allowedMethods()); |
57 | base.use('/keywords', keywords.routes(), keywords.allowedMethods()); | 58 | base.use('/keywords', keywords.routes(), keywords.allowedMethods()); |
59 | + base.use('/profile', profile.routes(), profile.allowedMethods()); | ||
58 | 60 | ||
59 | const white = crawler('/crawler/ip_whitelists', '/crawler/ua_whitelists', '白名单', false); | 61 | const white = crawler('/crawler/ip_whitelists', '/crawler/ua_whitelists', '白名单', false); |
60 | const black = crawler('/crawler/ip_blacklists', '/crawler/ua_blacklists', '黑名单', true); | 62 | const black = crawler('/crawler/ip_blacklists', '/crawler/ua_blacklists', '黑名单', true); |
apps/web/utils/sql-builder.js
0 → 100644
1 | +class SqlBuilder { | ||
2 | + constructor(table) { | ||
3 | + this._select = []; | ||
4 | + this._from = table; | ||
5 | + this._where = []; | ||
6 | + this._orWhere = []; | ||
7 | + this._limit = 10; | ||
8 | + this._offset = 0; | ||
9 | + this._orderBy = null; | ||
10 | + } | ||
11 | + | ||
12 | + static of(table) { | ||
13 | + return new SqlBuilder(table) | ||
14 | + } | ||
15 | + | ||
16 | + // placeholer is {} | ||
17 | + // select * from where time={} | ||
18 | + static raw(sql, ...args) { | ||
19 | + return args.reduce((t, a) => { | ||
20 | + return t.replace('{}', a) | ||
21 | + }, sql.slice(0)) | ||
22 | + } | ||
23 | + | ||
24 | + select(...args) { | ||
25 | + this._select.push(...args); | ||
26 | + return this; | ||
27 | + } | ||
28 | + | ||
29 | + where(field, op, value) { | ||
30 | + this._where.push({field, op, value}); | ||
31 | + return this; | ||
32 | + } | ||
33 | + | ||
34 | + orWhere(field, op, value) { | ||
35 | + this._orWhere.push({field, op, value}); | ||
36 | + return this; | ||
37 | + } | ||
38 | + | ||
39 | + from(table) { | ||
40 | + this._from = table; | ||
41 | + return this; | ||
42 | + } | ||
43 | + | ||
44 | + limit(limit) { | ||
45 | + this._limit = limit; | ||
46 | + return this; | ||
47 | + } | ||
48 | + | ||
49 | + offset(offset) { | ||
50 | + this._offset = offset; | ||
51 | + return this; | ||
52 | + } | ||
53 | + | ||
54 | + // order = desc or asc | ||
55 | + orderBy(order = 'DESC') { | ||
56 | + this._orderBy = order; | ||
57 | + return this; | ||
58 | + } | ||
59 | + | ||
60 | + toSql() { | ||
61 | + return this.toString(); | ||
62 | + } | ||
63 | + | ||
64 | + toString() { | ||
65 | + let sql = `SELECT ${this._select.join(',')} FROM "${this._from}" `; | ||
66 | + | ||
67 | + let andWhere = this._where.map(({field, op, value}) => `${field}${op}${value}`).join(' AND '); | ||
68 | + let orWhere = this._orWhere.map(({field, op, value}) => `${field}${op}${value}`).join(' OR '); | ||
69 | + | ||
70 | + if (andWhere) { | ||
71 | + sql += `WHERE ${andWhere} `; | ||
72 | + } | ||
73 | + | ||
74 | + if (andWhere && orWhere) { | ||
75 | + sql += `AND (${orWhere}) `; | ||
76 | + } else { | ||
77 | + if (orWhere) { | ||
78 | + sql += `WHERE ${orWhere}` | ||
79 | + } | ||
80 | + } | ||
81 | + | ||
82 | + if (this._orderBy) { | ||
83 | + sql += `ORDER BY time ${this._orderBy.order} ` | ||
84 | + } | ||
85 | + | ||
86 | + sql += `LIMIT ${this._limit} `; | ||
87 | + sql += `OFFSET ${this._offset} `; | ||
88 | + | ||
89 | + return sql; | ||
90 | + } | ||
91 | +} | ||
92 | + | ||
93 | +module.exports = SqlBuilder; |
apps/web/views/action/profile_error.hbs
0 → 100644
1 | +<div class="pageheader"> | ||
2 | + <div class="media"> | ||
3 | + <div class="pageicon pull-left"> | ||
4 | + <i class="fa fa-th-list"></i> | ||
5 | + </div> | ||
6 | + <div class="media-body"> | ||
7 | + <ul class="breadcrumb"> | ||
8 | + <li><a href=""><i class="glyphicon glyphicon-home"></i></a></li> | ||
9 | + <li><a href="">性能监控</a></li> | ||
10 | + <li>错误分析</li> | ||
11 | + </ul> | ||
12 | + <h4>错误分析</h4> | ||
13 | + </div> | ||
14 | + </div> | ||
15 | + <!-- media --> | ||
16 | +</div> | ||
17 | +<!-- pageheader --> | ||
18 | + | ||
19 | +<div class="contentpanel page-servers"> | ||
20 | + <div class="panel panel-primary-head"> | ||
21 | + <!--<div class="panel-heading">--> | ||
22 | + <!--<div class="pull-right">--> | ||
23 | + <!--<a id="new-page" href="/servers/new" class="btn btn-success btn-rounded"><i class="glyphicon glyphicon-plus"></i> 新增服务器</a>--> | ||
24 | + <!--</div>--> | ||
25 | + <!--<h4 class="panel-title">服务器设置</h4>--> | ||
26 | + <!--<p>配置服务器连接方式、可以通过标签区分</p>--> | ||
27 | + | ||
28 | + <!--</div>--> | ||
29 | + <!-- panel-heading --> | ||
30 | + | ||
31 | + <table id="table-servers" class="table table-striped table-bordered responsive"> | ||
32 | + <thead class=""> | ||
33 | + <tr> | ||
34 | + <th>时间</th> | ||
35 | + <th>应用</th> | ||
36 | + <th>类型</th> | ||
37 | + <th>父请求ID</th> | ||
38 | + <th>请求ID</th> | ||
39 | + <td>用户ID</td> | ||
40 | + <td>访客ID</td> | ||
41 | + <td>错误状态码</td> | ||
42 | + <td>错误信息</td> | ||
43 | + <td>出错文件</td> | ||
44 | + <td>行号</td> | ||
45 | + <td>列数</td> | ||
46 | + <td>错误堆栈</td> | ||
47 | + </tr> | ||
48 | + </thead> | ||
49 | + | ||
50 | + <tbody> | ||
51 | + {{#each errors}} | ||
52 | + <tr> | ||
53 | + <td>{{time}}</td> | ||
54 | + <td>{{app}}</td> | ||
55 | + <td>{{type}}</td> | ||
56 | + <td>{{preqID}}</td> | ||
57 | + <td>{{reqID}}</td> | ||
58 | + <td>{{uid}}</td> | ||
59 | + <td>{{udid}}</td> | ||
60 | + <td>{{code}}</td> | ||
61 | + <td>{{message}}</td> | ||
62 | + <td>{{script}}</td> | ||
63 | + <td>{{line}}</td> | ||
64 | + <td>{{column}}</td> | ||
65 | + <td>{{stack}}</td> | ||
66 | + </tr> | ||
67 | + {{/each}} | ||
68 | + </tbody> | ||
69 | + </table> | ||
70 | + </div> | ||
71 | + <!-- panel --> | ||
72 | +</div> | ||
73 | + | ||
74 | + | ||
75 | +<script> | ||
76 | + $(document).off().on('ready pjax:end', function() { | ||
77 | + | ||
78 | + console.log('page: servers'); | ||
79 | + | ||
80 | + // $("#table-servers").on('draw.dt', function() { | ||
81 | + // eventBind(); | ||
82 | + // }); | ||
83 | + | ||
84 | + function eventBind() { | ||
85 | + $('.server-del').off().on('click', function(){ | ||
86 | + var id = $(this).parent().data('id'); | ||
87 | + $.post('/servers/del', {id: id}, function(ret){ | ||
88 | + if(ret.code == 200){ | ||
89 | + var i = layer.alert('操作成功', function(){ | ||
90 | + layer.close(i); | ||
91 | + location.href = location.href; | ||
92 | + }); | ||
93 | + } | ||
94 | + }); | ||
95 | + }); | ||
96 | + | ||
97 | + $('.server-edit').off().on('click', function(){ | ||
98 | + var id = $(this).parent().data('id'); | ||
99 | + location.href = '/servers/edit?id=' + id; | ||
100 | + }); | ||
101 | + } | ||
102 | + | ||
103 | + eventBind(); | ||
104 | + | ||
105 | + $("#table-servers").DataTable({ | ||
106 | + pageLength: 20, | ||
107 | + retrieve: true, | ||
108 | + responsive: true, | ||
109 | + searching: false | ||
110 | + }); | ||
111 | + | ||
112 | + }); | ||
113 | + | ||
114 | +</script> |
apps/web/views/action/profile_time.hbs
0 → 100644
1 | +<div class="pageheader"> | ||
2 | + <div class="media"> | ||
3 | + <div class="pageicon pull-left"> | ||
4 | + <i class="fa fa-th-list"></i> | ||
5 | + </div> | ||
6 | + <div class="media-body"> | ||
7 | + <ul class="breadcrumb"> | ||
8 | + <li><a href=""><i class="glyphicon glyphicon-home"></i></a></li> | ||
9 | + <li><a href="">性能监控</a></li> | ||
10 | + <li>耗时分析</li> | ||
11 | + </ul> | ||
12 | + <h4>耗时分析</h4> | ||
13 | + </div> | ||
14 | + </div> | ||
15 | + <!-- media --> | ||
16 | +</div> | ||
17 | +<!-- pageheader --> | ||
18 | + | ||
19 | +<div class="contentpanel page-servers"> | ||
20 | + <div class="panel panel-primary-head"> | ||
21 | + <!--<div class="panel-heading">--> | ||
22 | + <!--<div class="pull-right">--> | ||
23 | + <!--<a id="new-page" href="/servers/new" class="btn btn-success btn-rounded"><i class="glyphicon glyphicon-plus"></i> 新增服务器</a>--> | ||
24 | + <!--</div>--> | ||
25 | + <!--<h4 class="panel-title">服务器设置</h4>--> | ||
26 | + <!--<p>配置服务器连接方式、可以通过标签区分</p>--> | ||
27 | + | ||
28 | + <!--</div>--> | ||
29 | + <!-- panel-heading --> | ||
30 | + | ||
31 | + <table id="table-servers" class="table table-striped table-bordered responsive"> | ||
32 | + <thead class=""> | ||
33 | + <tr> | ||
34 | + <th>时间</th> | ||
35 | + <th>应用</th> | ||
36 | + <th>类型</th> | ||
37 | + <th>父请求ID</th> | ||
38 | + <th>请求ID</th> | ||
39 | + <td>用户ID</td> | ||
40 | + <td>访客ID</td> | ||
41 | + <td>接口名称</td> | ||
42 | + <td>请求路由</td> | ||
43 | + <td>耗时</td> | ||
44 | + </tr> | ||
45 | + </thead> | ||
46 | + | ||
47 | + <tbody> | ||
48 | + {{#each times}} | ||
49 | + <tr> | ||
50 | + <td>{{time}}</td> | ||
51 | + <td>{{app}}</td> | ||
52 | + <td>{{type}}</td> | ||
53 | + <td>{{preqID}}</td> | ||
54 | + <td>{{reqID}}</td> | ||
55 | + <td>{{uid}}</td> | ||
56 | + <td>{{udid}}</td> | ||
57 | + <td>{{api}}</td> | ||
58 | + <td>{{route}}</td> | ||
59 | + <td>{{duration}}</td> | ||
60 | + </tr> | ||
61 | + {{/each}} | ||
62 | + </tbody> | ||
63 | + </table> | ||
64 | + </div> | ||
65 | + <!-- panel --> | ||
66 | +</div> | ||
67 | + | ||
68 | + | ||
69 | +<script> | ||
70 | + $(document).off().on('ready pjax:end', function() { | ||
71 | + | ||
72 | + console.log('page: servers'); | ||
73 | + | ||
74 | + // $("#table-servers").on('draw.dt', function() { | ||
75 | + // eventBind(); | ||
76 | + // }); | ||
77 | + | ||
78 | + function eventBind() { | ||
79 | + $('.server-del').off().on('click', function(){ | ||
80 | + var id = $(this).parent().data('id'); | ||
81 | + $.post('/servers/del', {id: id}, function(ret){ | ||
82 | + if(ret.code == 200){ | ||
83 | + var i = layer.alert('操作成功', function(){ | ||
84 | + layer.close(i); | ||
85 | + location.href = location.href; | ||
86 | + }); | ||
87 | + } | ||
88 | + }); | ||
89 | + }); | ||
90 | + | ||
91 | + $('.server-edit').off().on('click', function(){ | ||
92 | + var id = $(this).parent().data('id'); | ||
93 | + location.href = '/servers/edit?id=' + id; | ||
94 | + }); | ||
95 | + } | ||
96 | + | ||
97 | + eventBind(); | ||
98 | + | ||
99 | + $("#table-servers").DataTable({ | ||
100 | + pageLength: 20, | ||
101 | + retrieve: true, | ||
102 | + responsive: true, | ||
103 | + searching: false | ||
104 | + }); | ||
105 | + | ||
106 | + }); | ||
107 | + | ||
108 | +</script> |
@@ -74,6 +74,13 @@ | @@ -74,6 +74,13 @@ | ||
74 | <li><a href="/seo/tdk"><span>TDK管理</span></a></li> | 74 | <li><a href="/seo/tdk"><span>TDK管理</span></a></li> |
75 | </ul> | 75 | </ul> |
76 | </li> | 76 | </li> |
77 | + | ||
78 | + <li class="parent"><a><i class="fa fa-list"></i> <span>性能分析</span></a> | ||
79 | + <ul class="children"> | ||
80 | + <li><a href="/profile/time"> <span>耗时</span></a></li> | ||
81 | + <li><a href="/profile/error"> <span>错误</span></a></li> | ||
82 | + </ul> | ||
83 | + </li> | ||
77 | {{/if}} | 84 | {{/if}} |
78 | </ul> | 85 | </ul> |
79 | 86 |
@@ -64,6 +64,7 @@ | @@ -64,6 +64,7 @@ | ||
64 | "shelljs": "^0.7.0", | 64 | "shelljs": "^0.7.0", |
65 | "socket.io": "^1.4.6", | 65 | "socket.io": "^1.4.6", |
66 | "ssh2": "^0.5.4", | 66 | "ssh2": "^0.5.4", |
67 | + "superagent": "^3.6.0", | ||
67 | "tar": "^2.2.1", | 68 | "tar": "^2.2.1", |
68 | "utility": "^1.8.0" | 69 | "utility": "^1.8.0" |
69 | }, | 70 | }, |
This diff could not be displayed because it is too large.
-
Please register or login to post a comment