Merge pull request #55 from appleboy/stat

Fix #54 Support success or failure of notification counts information.
This commit is contained in:
Bo-Yi Wu 2016-04-14 21:42:40 -05:00
commit 5483769d14
11 changed files with 222 additions and 15 deletions

View File

@ -19,6 +19,7 @@ A push notification server using [Gin](https://github.com/gin-gonic/gin) framewo
* Support zero downtime restarts for go servers using [endless](https://github.com/fvbock/endless). * Support zero downtime restarts for go servers using [endless](https://github.com/fvbock/endless).
* Support [HTTP/2](https://http2.github.io/) or HTTP/1.1 protocol. * Support [HTTP/2](https://http2.github.io/) or HTTP/1.1 protocol.
* Support notification queue and multiple workers. * Support notification queue and multiple workers.
* Support `/api/stat/app` show notification success and failure counts.
See the [YAML config example](config/config.yml): See the [YAML config example](config/config.yml):
@ -35,7 +36,8 @@ core:
api: api:
push_uri: "/api/push" push_uri: "/api/push"
stat_go_uri: "/api/status" stat_go_uri: "/api/stat/go"
stat_app_uri: "/api/stat/app"
android: android:
enabled: true enabled: true
@ -101,19 +103,79 @@ Please make sure your [config.yml](config/config.yml) exist. Default port is `80
$ gorush -c config.yml $ gorush -c config.yml
``` ```
Test status of api server using [httpie](https://github.com/jkbrzt/httpie) tool: Get go status of api server using [httpie](https://github.com/jkbrzt/httpie) tool:
```bash ```bash
$ http -v --verify=no --json GET https://localhost:8088/api/status $ http -v --verify=no --json GET https://localhost:8088/api/stat/go
``` ```
## Web API ## Web API
gorush support the following API. Gorush support the following API.
* **GET** `/api/status` Golang cpu, memory, gc, etc information. Thanks for [golang-stats-api-handler](https://github.com/fukata/golang-stats-api-handler). * **GET** `/api/stat/go` Golang cpu, memory, gc, etc information. Thanks for [golang-stats-api-handler](https://github.com/fukata/golang-stats-api-handler).
* **GET** `/api/stat/app` show notification success and failure counts.
* **POST** `/api/push` push ios and android notifications. * **POST** `/api/push` push ios and android notifications.
### GET /api/stat/go
Golang cpu, memory, gc, etc information. Response with `200` http status code.
```json
{
time: 1460686815848046600,
go_version: "go1.6.1",
go_os: "darwin",
go_arch: "amd64",
cpu_num: 4,
goroutine_num: 15,
gomaxprocs: 4,
cgo_call_num: 1,
memory_alloc: 7455192,
memory_total_alloc: 8935464,
memory_sys: 12560632,
memory_lookups: 17,
memory_mallocs: 31426,
memory_frees: 11772,
memory_stack: 524288,
heap_alloc: 7455192,
heap_sys: 8912896,
heap_idle: 909312,
heap_inuse: 8003584,
heap_released: 0,
heap_objects: 19654,
gc_next: 9754725,
gc_last: 1460686815762559700,
gc_num: 2,
gc_per_second: 0,
gc_pause_per_second: 0,
gc_pause: [
0.326576,
0.227096
]
}
```
### GET /api/stat/app
Get success or failure of notification counts information.
```json
{
queue_max: 8192,
queue_usage: 0,
total_count: 77,
ios: {
push_success: 19,
push_error: 38
},
android: {
push_success: 10,
push_error: 10
}
}
```
### POST /api/push ### POST /api/push
Simple send iOS notification example, the `platform` value is `1`: Simple send iOS notification example, the `platform` value is `1`:
@ -356,7 +418,7 @@ $ docker run -name gorush -p 80:8088 appleboy/gorush
Testing your gorush server. Testing your gorush server.
```bash ```bash
$ http -v --verify=no --json GET http://your.docker.host/api/status $ http -v --verify=no --json GET http://your.docker.host/api/stat/go
``` ```
![statue screenshot](screenshot/status.png) ![statue screenshot](screenshot/status.png)

View File

@ -10,7 +10,8 @@ core:
api: api:
push_uri: "/api/push" push_uri: "/api/push"
stat_go_uri: "/api/status" stat_go_uri: "/api/stat/go"
stat_app_uri: "/api/stat/app"
android: android:
enabled: true enabled: true

View File

@ -77,6 +77,7 @@ func main() {
Message: *message, Message: *message,
} }
gorush.InitAppStatus()
gorush.PushToAndroid(req) gorush.PushToAndroid(req)
return return
@ -103,6 +104,7 @@ func main() {
Message: *message, Message: *message,
} }
gorush.InitAppStatus()
gorush.InitAPNSClient() gorush.InitAPNSClient()
gorush.PushToIOS(req) gorush.PushToIOS(req)
@ -113,6 +115,7 @@ func main() {
gorush.LogError.Fatal(err) gorush.LogError.Fatal(err)
} }
gorush.InitAppStatus()
gorush.InitAPNSClient() gorush.InitAPNSClient()
gorush.InitWorkers(gorush.PushConf.Core.WorkerNum, gorush.PushConf.Core.QueueNum) gorush.InitWorkers(gorush.PushConf.Core.WorkerNum, gorush.PushConf.Core.QueueNum)
gorush.RunHTTPServer() gorush.RunHTTPServer()

View File

@ -31,6 +31,7 @@ type SectionCore struct {
type SectionAPI struct { type SectionAPI struct {
PushURI string `yaml:"push_uri"` PushURI string `yaml:"push_uri"`
StatGoURI string `yaml:"stat_go_uri"` StatGoURI string `yaml:"stat_go_uri"`
StatAppURI string `yaml:"stat_app_uri"`
} }
// SectionAndroid is sub seciont of config. // SectionAndroid is sub seciont of config.
@ -72,7 +73,8 @@ func BuildDefaultPushConf() ConfYaml {
// Api // Api
conf.API.PushURI = "/api/push" conf.API.PushURI = "/api/push"
conf.API.StatGoURI = "/api/status" conf.API.StatGoURI = "/api/stat/go"
conf.API.StatAppURI = "/api/stat/app"
// Android // Android
conf.Android.Enabled = false conf.Android.Enabled = false

View File

@ -18,5 +18,6 @@ var (
// LogAccess is log server request log // LogAccess is log server request log
LogAccess *logrus.Logger LogAccess *logrus.Logger
// LogError is log server error log // LogError is log server error log
LogError *logrus.Logger LogError *logrus.Logger
RushStatus StatusApp
) )

View File

@ -158,9 +158,11 @@ func queueNotification(req RequestPush) int {
} }
QueueNotification <- notification QueueNotification <- notification
count++ count += len(notification.Tokens)
} }
addTotalCount(int64(count))
return count return count
} }
@ -276,6 +278,7 @@ func PushToIOS(req PushNotification) bool {
// apns server error // apns server error
LogPush(FailedPush, token, req, err) LogPush(FailedPush, token, req, err)
isError = true isError = true
addIosError(1)
continue continue
} }
@ -283,11 +286,13 @@ func PushToIOS(req PushNotification) bool {
// error message: // error message:
// ref: https://github.com/sideshow/apns2/blob/master/response.go#L14-L65 // ref: https://github.com/sideshow/apns2/blob/master/response.go#L14-L65
LogPush(FailedPush, token, req, errors.New(res.Reason)) LogPush(FailedPush, token, req, errors.New(res.Reason))
addIosError(1)
continue continue
} }
if res.Sent() { if res.Sent() {
LogPush(SucceededPush, token, req, nil) LogPush(SucceededPush, token, req, nil)
addIosSuccess(1)
} }
} }
@ -380,6 +385,8 @@ func PushToAndroid(req PushNotification) bool {
} }
LogAccess.Debug(fmt.Sprintf("Android Success count: %d, Failure count: %d", res.Success, res.Failure)) LogAccess.Debug(fmt.Sprintf("Android Success count: %d, Failure count: %d", res.Success, res.Failure))
addAndroidSuccess(int64(res.Success))
addAndroidError(int64(res.Failure))
for k, result := range res.Results { for k, result := range res.Results {
if result.Error != "" { if result.Error != "" {

View File

@ -355,7 +355,7 @@ func TestSenMultipleNotifications(t *testing.T) {
} }
count := queueNotification(req) count := queueNotification(req)
assert.Equal(t, 2, count) assert.Equal(t, 3, count)
} }
func TestDisabledAndroidNotifications(t *testing.T) { func TestDisabledAndroidNotifications(t *testing.T) {
@ -421,7 +421,7 @@ func TestDisabledIosNotifications(t *testing.T) {
} }
count := queueNotification(req) count := queueNotification(req)
assert.Equal(t, 1, count) assert.Equal(t, 2, count)
} }
func TestMissingIosCertificate(t *testing.T) { func TestMissingIosCertificate(t *testing.T) {

View File

@ -68,6 +68,7 @@ func routerEngine() *gin.Engine {
r.Use(LogMiddleware()) r.Use(LogMiddleware())
r.GET(PushConf.API.StatGoURI, api.StatusHandler) r.GET(PushConf.API.StatGoURI, api.StatusHandler)
r.GET(PushConf.API.StatAppURI, appStatusHandler)
r.POST(PushConf.API.PushURI, pushHandler) r.POST(PushConf.API.PushURI, pushHandler)
r.GET("/", rootHandler) r.GET("/", rootHandler)

View File

@ -36,7 +36,7 @@ func TestRunNormalServer(t *testing.T) {
time.Sleep(5 * time.Millisecond) time.Sleep(5 * time.Millisecond)
assert.Error(t, RunHTTPServer()) assert.Error(t, RunHTTPServer())
gofight.TestRequest(t, "http://localhost:8088/api/status") gofight.TestRequest(t, "http://localhost:8088/api/stat/go")
} }
func TestRunTLSServer(t *testing.T) { func TestRunTLSServer(t *testing.T) {
@ -76,12 +76,12 @@ func TestRootHandler(t *testing.T) {
}) })
} }
func TestAPIStatusHandler(t *testing.T) { func TestAPIStatusGoHandler(t *testing.T) {
initTest() initTest()
r := gofight.New() r := gofight.New()
r.GET("/api/status"). r.GET("/api/stat/go").
Run(routerEngine(), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) { Run(routerEngine(), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) {
data := []byte(r.Body.String()) data := []byte(r.Body.String())
@ -92,6 +92,17 @@ func TestAPIStatusHandler(t *testing.T) {
}) })
} }
func TestAPIStatusAppHandler(t *testing.T) {
initTest()
r := gofight.New()
r.GET("/api/stat/app").
Run(routerEngine(), func(r gofight.HTTPResponse, rq gofight.HTTPRequest) {
assert.Equal(t, http.StatusOK, r.Code)
})
}
func TestMissingNotificationsParameter(t *testing.T) { func TestMissingNotificationsParameter(t *testing.T) {
initTest() initTest()

67
gorush/status.go Normal file
View File

@ -0,0 +1,67 @@
package gorush
import (
"github.com/gin-gonic/gin"
"net/http"
"sync/atomic"
)
type StatusApp struct {
QueueMax int `json:"queue_max"`
QueueUsage int `json:"queue_usage"`
TotalCount int64 `json:"total_count"`
Ios IosStatus `json:"ios"`
Android AndroidStatus `json:"android"`
}
type AndroidStatus struct {
PushSuccess int64 `json:"push_success"`
PushError int64 `json:"push_error"`
}
type IosStatus struct {
PushSuccess int64 `json:"push_success"`
PushError int64 `json:"push_error"`
}
func InitAppStatus() {
RushStatus.TotalCount = 0
RushStatus.Ios.PushSuccess = 0
RushStatus.Ios.PushError = 0
RushStatus.Android.PushSuccess = 0
RushStatus.Android.PushError = 0
}
func addTotalCount(count int64) {
atomic.AddInt64(&RushStatus.TotalCount, count)
}
func addIosSuccess(count int64) {
atomic.AddInt64(&RushStatus.Ios.PushSuccess, count)
}
func addIosError(count int64) {
atomic.AddInt64(&RushStatus.Ios.PushError, count)
}
func addAndroidSuccess(count int64) {
atomic.AddInt64(&RushStatus.Android.PushSuccess, count)
}
func addAndroidError(count int64) {
atomic.AddInt64(&RushStatus.Android.PushError, count)
}
func appStatusHandler(c *gin.Context) {
result := StatusApp{}
result.QueueMax = cap(QueueNotification)
result.QueueUsage = len(QueueNotification)
result.TotalCount = atomic.LoadInt64(&RushStatus.TotalCount)
result.Ios.PushSuccess = atomic.LoadInt64(&RushStatus.Ios.PushSuccess)
result.Ios.PushError = atomic.LoadInt64(&RushStatus.Ios.PushError)
result.Android.PushSuccess = atomic.LoadInt64(&RushStatus.Android.PushSuccess)
result.Android.PushError = atomic.LoadInt64(&RushStatus.Android.PushError)
c.JSON(http.StatusOK, result)
}

52
gorush/status_test.go Normal file
View File

@ -0,0 +1,52 @@
package gorush
import (
"github.com/stretchr/testify/assert"
"sync/atomic"
"testing"
)
func TestAddTotalCount(t *testing.T) {
InitAppStatus()
addTotalCount(1000)
val := atomic.LoadInt64(&RushStatus.TotalCount)
assert.Equal(t, int64(1000), val)
}
func TestAddIosSuccess(t *testing.T) {
InitAppStatus()
addIosSuccess(1000)
val := atomic.LoadInt64(&RushStatus.Ios.PushSuccess)
assert.Equal(t, int64(1000), val)
}
func TestAddIosError(t *testing.T) {
InitAppStatus()
addIosError(1000)
val := atomic.LoadInt64(&RushStatus.Ios.PushError)
assert.Equal(t, int64(1000), val)
}
func TestAndroidSuccess(t *testing.T) {
InitAppStatus()
addAndroidSuccess(1000)
val := atomic.LoadInt64(&RushStatus.Android.PushSuccess)
assert.Equal(t, int64(1000), val)
}
func TestAddAndroidError(t *testing.T) {
InitAppStatus()
addAndroidError(1000)
val := atomic.LoadInt64(&RushStatus.Android.PushError)
assert.Equal(t, int64(1000), val)
}