Commit 0839b0e8 authored by jingbo.wang's avatar jingbo.wang

页面显示 & 发送邮件 的api 完成

parent 6f581e80
...@@ -4,6 +4,7 @@ go 1.12 ...@@ -4,6 +4,7 @@ go 1.12
require ( require (
github.com/Shopify/sarama v1.23.1 github.com/Shopify/sarama v1.23.1
github.com/buaazp/fasthttprouter v0.1.1
github.com/influxdata/influxdb v1.7.9 github.com/influxdata/influxdb v1.7.9
github.com/mkevac/debugcharts v0.0.0-20180124214838-d3203a8fa926 github.com/mkevac/debugcharts v0.0.0-20180124214838-d3203a8fa926
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 // indirect github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 // indirect
......
...@@ -2,18 +2,19 @@ package main ...@@ -2,18 +2,19 @@ package main
import ( import (
"fmt" "fmt"
api_server "git.quantgroup.cn/DevOps/enoch/pkg/api-server"
"git.quantgroup.cn/DevOps/enoch/pkg/dao" "git.quantgroup.cn/DevOps/enoch/pkg/dao"
"git.quantgroup.cn/DevOps/enoch/pkg/global" "git.quantgroup.cn/DevOps/enoch/pkg/global"
"git.quantgroup.cn/DevOps/enoch/pkg/glog" "git.quantgroup.cn/DevOps/enoch/pkg/glog"
"git.quantgroup.cn/DevOps/enoch/pkg/points" "git.quantgroup.cn/DevOps/enoch/pkg/points"
report_form "git.quantgroup.cn/DevOps/enoch/pkg/report-form"
"github.com/Shopify/sarama" "github.com/Shopify/sarama"
_ "github.com/mkevac/debugcharts" _ "github.com/mkevac/debugcharts"
"github.com/valyala/fasthttp"
"net/http" "net/http"
_ "net/http/pprof" _ "net/http/pprof"
"os" "os"
"os/signal" "os/signal"
"runtime/debug"
"strconv"
"strings" "strings"
"syscall" "syscall"
"time" "time"
...@@ -111,32 +112,24 @@ func main() { ...@@ -111,32 +112,24 @@ func main() {
dao.DbInit() dao.DbInit()
//健康状态报表 //健康状态报表
// report_form.RegularReport(global.ReportFormDir) report_form.RegularReport(global.ReportFormDir)
//TODO 告警策略 //TODO 告警策略
} }
//对外api //对外api
runApiServer() api_server.ListenAndServe()
//处理消息(阻塞) //处理消息(阻塞)
handlerKafkaMsg() handlerKafkaMsg()
} }
func runApiServer() { func init() {
http.HandleFunc("/tech/health/check", func(writer http.ResponseWriter, request *http.Request) { api_server.HandlerFunc(api_server.GET, "/tech/health/check", healthCheck)
writer.WriteHeader(http.StatusOK) api_server.HandlerFunc(api_server.HEAD, "/tech/health/check", healthCheck)
}) }
go func() { func healthCheck(ctx *fasthttp.RequestCtx) {
defer func() { ctx.Response.SetStatusCode(fasthttp.StatusOK)
if err := recover(); err != nil { ctx.Response.SetBodyString("ok")
glog.Error(err, debug.Stack())
}
}()
err := http.ListenAndServe(":"+strconv.Itoa(global.HttpPort), nil)
if err != nil {
glog.Error(err)
}
}()
} }
package api_server
import (
"fmt"
"git.quantgroup.cn/DevOps/enoch/pkg/global"
"git.quantgroup.cn/DevOps/enoch/pkg/glog"
"github.com/buaazp/fasthttprouter"
"github.com/valyala/fasthttp"
"github.com/vrg0/go-common/logger"
"github.com/vrg0/go-common/util"
"runtime"
"strconv"
"strings"
"sync"
"time"
)
var (
router = fasthttprouter.New()
once = new(sync.Once)
)
const (
GET = "GET"
PUT = "PUT"
DELETE = "DELETE"
POST = "POST"
PATCH = "PATCH"
HEAD = "HEAD"
OPTIONS = "OPTIONS"
)
//跨域
func wrap(handler fasthttp.RequestHandler) fasthttp.RequestHandler {
return func(ctx *fasthttp.RequestCtx) {
handler(ctx)
ctx.Response.Header.Add("Access-Control-Allow-Origin", "*")
}
}
//注册函数
//HandlerFunc只能在init()中执行
func HandlerFunc(method string, path string, handle fasthttp.RequestHandler) {
if pc, _, _, ok := runtime.Caller(1); ok && strings.Contains(runtime.FuncForPC(pc).Name(), ".init") {
router.Handle(method, path, handle)
} else {
glog.Error("HandlerFunc must be execute in init()")
}
}
//程序对外API的http-server
//启动服务
func ListenAndServe() {
once.Do(listenAndServe)
}
func listenAndServe() {
//handlerFunc 发生 panic 后返回的内容
router.PanicHandler = func(ctx *fasthttp.RequestCtx, i interface{}) {
rtn := "panic: " + ctx.String() + " "
switch x := i.(type) {
case error:
rtn += x.Error()
case string:
rtn += x
default:
rtn += "未识别的panic类型"
}
rtn += "\n"
rtn += ctx.Request.String()
glog.Error(rtn)
ctx.Response.SetStatusCode(500)
ctx.Response.SetBodyString(rtn)
}
//启动http server
go func() {
//server panic后自动重启
defer func() {
if err := recover(); err != nil {
errStr := fmt.Sprint("api fasthttp panic:", err)
glog.Error(errStr)
time.Sleep(time.Second * 1)
listenAndServe()
}
}()
//创建http-server对象
l := global.Logger.GetStandardLogger()
l.SetPrefix("_API_SERVER_ ")
hook := logger.NewHookWriter(l.Writer())
hook.AddHookFunc(func(data []byte) bool {
body := util.BytesString(data)
if strings.Contains(body, "error") {
}
return true
})
l.SetOutput(hook)
server := fasthttp.Server{
Logger: l,
Handler: wrap(router.Handler),
}
//启动http-server
firstRunFlag := true
for {
if firstRunFlag {
firstRunFlag = false
} else {
time.Sleep(time.Second * 1) //二次重启延时1秒
}
if err := server.ListenAndServe("0.0.0.0:" + strconv.Itoa(global.HttpPort)); err != nil {
glog.Error("api fasthttp err", err)
}
}
}()
//等待http-server启动成功
time.Sleep(time.Millisecond * 100)
}
package api_server
import (
"github.com/valyala/fasthttp"
"strings"
"testing"
)
func init() {
//正确执行的测试场景
HandlerFunc(GET, "/hello", func(ctx *fasthttp.RequestCtx) {
ctx.Response.SetBodyString("hello!")
})
//panic测试场景
HandlerFunc(GET, "/panic", func(ctx *fasthttp.RequestCtx) {
panic("panic_test")
})
}
//测试panic回调函数是否好用
func TestHandlerFuncPanic(t *testing.T) {
ListenAndServe()
if _, body, err := fasthttp.Get(nil, "http://127.0.0.1:5555/panic"); err != nil {
t.Error(err)
} else if !strings.Contains(string(body), "panic_test") {
t.Error("recover panic fatal")
}
}
//测试http-server是否启动
func TestListenAndServe(t *testing.T) {
ListenAndServe()
if _, _, err := fasthttp.Get(nil, "http://127.0.0.1:5555/hello"); err != nil {
t.Error(err)
}
}
//不在init()中执行会报panic
func TestHandlerFunc(t *testing.T) {
defer func() {
if err := recover(); err != nil {
if errStr, ok := err.(string); !ok || errStr != "HandlerFunc must be execute in init()" {
t.Error("HandlerFunc Err")
}
}
}()
HandlerFunc(GET, "/test", func(ctx *fasthttp.RequestCtx) {
ctx.Response.SetBodyString("test!")
})
}
package report_form
import (
"encoding/json"
"fmt"
"git.quantgroup.cn/DevOps/enoch/pkg/api-server"
"git.quantgroup.cn/DevOps/enoch/pkg/email"
"git.quantgroup.cn/DevOps/enoch/pkg/global"
"github.com/valyala/fasthttp"
"github.com/vrg0/go-common/util"
"io/ioutil"
)
func show(ctx *fasthttp.RequestCtx) {
long := ctx.UserValue("long").(string)
y := ctx.UserValue("y").(string)
m := ctx.UserValue("m").(string)
d := ctx.UserValue("d").(string)
name := ctx.UserValue("name").(string)
if long != "day" && long != "week" {
ctx.Response.SetStatusCode(fasthttp.StatusNotFound)
ctx.Response.SetBodyString("page not found")
return
}
if len(m) == 1 {
m = "0" + m
}
if len(d) == 1 {
d = "0" + d
}
date := fmt.Sprintf("%s-%s-%s", y, m, d)
filePath := global.ReportFormDir + "/" + date + "/" + long + "/" + name + ".html"
if !util.Exists(filePath) {
ctx.Response.SetStatusCode(fasthttp.StatusBadRequest)
ctx.Response.SetBodyString("超出查询范围")
return
}
body, err := ioutil.ReadFile(filePath)
if err != nil {
ctx.Response.SetStatusCode(fasthttp.StatusBadRequest)
ctx.Response.SetBodyString("读取文件失败")
}
ctx.Response.Header.SetContentType("text/html")
ctx.Response.SetStatusCode(fasthttp.StatusOK)
ctx.Response.SetBody(body)
}
func sendEmail(ctx *fasthttp.RequestCtx) {
long := ctx.UserValue("long").(string)
y := ctx.UserValue("y").(string)
m := ctx.UserValue("m").(string)
d := ctx.UserValue("d").(string)
name := ctx.UserValue("name").(string)
if long != "day" && long != "week" {
ctx.Response.SetStatusCode(fasthttp.StatusNotFound)
ctx.Response.SetBodyString("page not found")
return
}
if len(m) == 1 {
m = "0" + m
}
if len(d) == 1 {
d = "0" + d
}
date := fmt.Sprintf("%s-%s-%s", y, m, d)
filePath := global.ReportFormDir + "/" + date + "/" + long + "/" + name + ".html"
if !util.Exists(filePath) {
ctx.Response.SetStatusCode(fasthttp.StatusBadRequest)
ctx.Response.SetBodyString("超出查询范围")
return
}
body, err := ioutil.ReadFile(filePath)
if err != nil {
ctx.Response.SetStatusCode(fasthttp.StatusBadRequest)
ctx.Response.SetBodyString("读取文件失败")
return
}
emailList := make([]string, 0)
if err := json.Unmarshal(ctx.Request.Body(), &emailList); err != nil {
ctx.Response.SetStatusCode(fasthttp.StatusBadRequest)
ctx.Response.SetBodyString("can not unmarshal body")
return
}
email.SendEmail(fmt.Sprintf("服务健康状态表:%s_%s_%s", name, date, long), util.BytesString(body), emailList...)
}
func init() {
//显示报表
api_server.HandlerFunc(api_server.GET, "/show/:long/:y/:m/:d/:name", show)
//发送邮件:周
api_server.HandlerFunc(api_server.POST, "/send-email/:long/:y/:m/:d/:name", sendEmail)
}
...@@ -36,17 +36,42 @@ func hrn(n int) string { ...@@ -36,17 +36,42 @@ func hrn(n int) string {
return fmt.Sprintf("%.3f%s", r, pre) return fmt.Sprintf("%.3f%s", r, pre)
} }
//TODO 分表
func (sm *ServiceMap) ServiceReportForm() map[string]string {
rtn := make(map[string]string)
//
return rtn
}
//获取指定path的访问量
func (sm *ServiceMap) GetPathCount(serviceName string, path string) int {
s, ok := sm.serviceMap[serviceName]
if !ok {
return 0
}
pm := s.GetPathMap()
p, ok := pm[path]
if !ok {
return 0
}
return p.GetCount()
}
//总表 //总表
func (sm *ServiceMap) ServiceMapReportForm() string { func (sm *ServiceMap) ServiceMapReportForm() string {
rtn := new(strings.Builder) rtn := new(strings.Builder)
var t *Table = nil var t *Table = nil
//表头 //表头
rtn.WriteString("<div><pre>\n") rtn.WriteString("<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" /></head><body><div><pre>\n")
rtn.WriteString(" 服务健康状态总表\n") rtn.WriteString(" 服务健康状态总表\n")
rtn.WriteString(fmt.Sprintf("时间:%s ~ %s\n", rtn.WriteString(fmt.Sprintf("时间:%s ~ %s\n",
sm.startTime.In(cstZone).Format(time.RFC3339), sm.startTime.In(cstZone).Format(timeFormat),
sm.endTime.In(cstZone).Format(time.RFC3339)), sm.endTime.In(cstZone).Format(timeFormat)),
) )
rtn.WriteString(fmt.Sprintf("总访问量:%s 总响应时间:%s 平均响应时间:%s QPS:%.2f\n", rtn.WriteString(fmt.Sprintf("总访问量:%s 总响应时间:%s 平均响应时间:%s QPS:%.2f\n",
hrn(sm.GetCount()), hrn(sm.GetCount()),
...@@ -139,12 +164,13 @@ func (sm *ServiceMap) ServiceMapReportForm() string { ...@@ -139,12 +164,13 @@ func (sm *ServiceMap) ServiceMapReportForm() string {
//响应时间最高的请求排行 //响应时间最高的请求排行
getMaxDurationTracePointList := sm.GetMaxDurationTracePointList() getMaxDurationTracePointList := sm.GetMaxDurationTracePointList()
if len(getMaxDurationTracePointList) != 0 { if len(getMaxDurationTracePointList) != 0 {
t = NewTable("响应时间最高的请求排行", "排名", "响应时间", "服务名称", "接口", "时间戳", "trace_id") t = NewTable("响应时间最高的请求排行", "排名", "响应时间", "服务名称", "接口", "时间戳", "trace_id", "访问量")
for i, tp := range getMaxDurationTracePointList { for i, tp := range getMaxDurationTracePointList {
color := colorBlack color := colorBlack
if tp.Duration > maxDuration { if tp.Duration > maxDuration {
color = colorRed color = colorRed
} }
//访问量
_ = t.AddRecord( _ = t.AddRecord(
color, color,
fmt.Sprintf("No.%d", i+1), fmt.Sprintf("No.%d", i+1),
...@@ -152,7 +178,8 @@ func (sm *ServiceMap) ServiceMapReportForm() string { ...@@ -152,7 +178,8 @@ func (sm *ServiceMap) ServiceMapReportForm() string {
tp.ServiceName, tp.ServiceName,
tp.Path, tp.Path,
tp.Timestamp.In(cstZone).Format(timeFormat), tp.Timestamp.In(cstZone).Format(timeFormat),
tp.TraceId, fmt.Sprintf(`<a href="http://zipkin-3c.xyqb.com/zipkin/traces/%s" target="_blank" rel="noopener noreferrer">%s</a>`, tp.TraceId, tp.TraceId),
hrn(sm.GetPathCount(tp.ServiceName, tp.Path)),
) )
} }
if !t.IsEmpty() { if !t.IsEmpty() {
...@@ -299,7 +326,7 @@ func (sm *ServiceMap) ServiceMapReportForm() string { ...@@ -299,7 +326,7 @@ func (sm *ServiceMap) ServiceMapReportForm() string {
} }
} }
rtn.WriteString("</pre></div>\n") rtn.WriteString("</pre></div></body></html>\n")
return rtn.String() return rtn.String()
} }
......
...@@ -96,6 +96,11 @@ func (s *Service) Name() string { ...@@ -96,6 +96,11 @@ func (s *Service) Name() string {
return s.name return s.name
} }
//获取Path Map
func (s *Service) GetPathMap() map[string]*Path {
return s.pathMap
}
//获取节点列表 //获取节点列表
func (s *Service) getNodeList() []string { func (s *Service) getNodeList() []string {
const sqlGetNodeList = `SHOW TAG VALUES FROM machine_info WITH key = "host" WHERE sys_name = '%s';` const sqlGetNodeList = `SHOW TAG VALUES FROM machine_info WITH key = "host" WHERE sys_name = '%s';`
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment