Skip to content

Commit

Permalink
chore: add more information about the system
Browse files Browse the repository at this point in the history
  • Loading branch information
ervitis committed Dec 7, 2020
1 parent 830868f commit e1cf970
Show file tree
Hide file tree
Showing 7 changed files with 259 additions and 22 deletions.
4 changes: 2 additions & 2 deletions examples/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (

func checkPort() gohealthchecker.Healthfunc {
return func() (code int, e error) {
conn, err := net.Dial("tcp", ":8185")
conn, err := net.Dial("tcp", ":8085")
if err != nil {
return http.StatusInternalServerError, err
}
Expand All @@ -21,7 +21,7 @@ func checkPort() gohealthchecker.Healthfunc {
}

func checkGithub() gohealthchecker.Healthfunc {
const myUrl = "https://api.github.com/usrs/ervitis"
const myUrl = "https://api.github.com/users/ervitis"

return func() (code int, e error) {
req, err := http.NewRequest(http.MethodGet, myUrl, nil)
Expand Down
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@ module github.com/ervitis/gohealthchecker

go 1.13

require github.com/gorilla/mux v1.7.3
require (
github.com/ervitis/goprocinfo v1.0.0
github.com/gorilla/mux v1.8.0
)
6 changes: 4 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/ervitis/goprocinfo v1.0.0 h1:QKty3U/By/lWz+yJu1zLQw9DpEmF/1cJVmr9wxyeVo0=
github.com/ervitis/goprocinfo v1.0.0/go.mod h1:uY2dRmcKAzEjtgvQ+NBj7RK4EBIK/oDtp2ydUNRa0Ds=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
84 changes: 70 additions & 14 deletions healthchecker.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ package gohealthchecker

import (
"encoding/json"
"fmt"
"github.com/gorilla/mux"
"net/http"
"os"
"reflect"
"runtime"
"strings"
"sync"
"time"
)

type (
Expand All @@ -21,9 +24,10 @@ type (
Service string `json:"service"`
}

responseError struct {
Info []info `json:"info"`
Code int `json:"code"`
response struct {
Info []info `json:"info,omitempty"`
Code int `json:"code"`
SystemInfo SystemInformationResponse `json:"systemInformation"`
}

errInfo struct {
Expand All @@ -32,6 +36,22 @@ type (
service string
}

// SystemInformationResponse body response struct data
SystemInformationResponse struct {
ProcessStatus string `json:"processStatus"`
ProcessActive bool `json:"processActive"`
Pid uint64 `json:"pid"`
StartTime string `json:"startTime"`
Memory struct {
Total uint64 `json:"total"`
Free uint64 `json:"free"`
Available uint64 `json:"available"`
} `json:"memory"`
IpAddress string `json:"ipAddress"`
RuntimeVersion string `json:"runtimeVersion"`
CanAcceptWork bool `json:"canAcceptWork"`
}

fnNode struct {
next *fnNode
e *errInfo
Expand All @@ -41,12 +61,12 @@ type (

// Healthchecker type
Healthchecker struct {
mtx sync.Mutex
fns *fnNode
statusOk int
statusKo int
nErrors uint
// TODO add logger
mtx sync.Mutex
fns *fnNode
statusOk int
statusKo int
nErrors uint
systemInfo *SystemInformation
}

// Healthfunc is used to define the health check functions
Expand All @@ -68,7 +88,7 @@ type (
// NewHealthchecker Constructor
// Pass the http statuses when it's ok or ko to tell if your microservice is not ready
func NewHealthchecker(statusOk, statusKo int) *Healthchecker {
return &Healthchecker{statusKo: statusKo, statusOk: statusOk, nErrors: 0}
return &Healthchecker{statusKo: statusKo, statusOk: statusOk, nErrors: 0, systemInfo: &SystemInformation{startTime: time.Now()}}
}

func (h *Healthchecker) executeHealthChecker() {
Expand All @@ -92,6 +112,37 @@ func (h *Healthchecker) executeHealthChecker() {
}
c = c.next
}

// do check system
h.recoverFromPanic()
if err := h.systemInfo.GetSystemInfo(); err != nil {
h.nErrors++
panic(err.Error())
}
}

func (h *Healthchecker) responseSystemFactory() *response {
r := &response{}
r.SystemInfo.ProcessActive = h.systemInfo.processActive
r.SystemInfo.ProcessStatus = h.systemInfo.processStatus
r.SystemInfo.Memory.Available = h.systemInfo.memory.available
r.SystemInfo.Memory.Total = h.systemInfo.memory.total
r.SystemInfo.Memory.Free = h.systemInfo.memory.free
r.SystemInfo.CanAcceptWork = h.systemInfo.canAcceptWork
r.SystemInfo.Pid = h.systemInfo.pid
r.SystemInfo.IpAddress = h.systemInfo.ipAddress.ipAddress
r.SystemInfo.RuntimeVersion = h.systemInfo.runtimeVersion
r.SystemInfo.StartTime = h.systemInfo.startTime.Format(time.RFC3339)

return r
}

func (h *Healthchecker) recoverFromPanic() {
defer func() {
if r := recover(); r != nil {
_, _ = fmt.Fprintf(os.Stdout, "panic at %v", time.Now().Format(time.RFC3339))
}
}()
}

func toString(ts []string) string {
Expand Down Expand Up @@ -123,27 +174,32 @@ func (h *Healthchecker) clearError() {
}()
}

func (a *apiHealthCheck) healthCheckerHandler(w http.ResponseWriter, r *http.Request) {
func (a *apiHealthCheck) healthCheckerHandler(w http.ResponseWriter, _ *http.Request) {
a.healthchecker.executeHealthChecker()
resp := a.healthchecker.responseSystemFactory()

if a.healthchecker.nErrors == 0 {
resp.Code = a.healthchecker.statusOk
b, _ := json.Marshal(resp)
w.WriteHeader(a.healthchecker.statusOk)
_, _ = w.Write(b)
return
}

responseError := &responseError{Code: a.healthchecker.statusKo}
resp.Code = a.healthchecker.statusKo

c := a.healthchecker.fns
for c != nil {
if c.e != nil {
info := &info{Code: c.e.code, Message: c.e.message, Service: c.e.service}
responseError.Info = append(responseError.Info, *info)
resp.Info = append(resp.Info, *info)
}

c = c.next
}
a.healthchecker.clearError()

b, err := json.Marshal(responseError)
b, err := json.Marshal(resp)
if err != nil {
panic(err)
}
Expand Down
37 changes: 34 additions & 3 deletions healthchecker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,10 +165,10 @@ func TestHealthchecker_ActivateHealthCheck(t *testing.T) {

r := h.ActivateHealthCheck("/healthtest")

mux := http.NewServeMux()
mux.Handle("/healthtest", r)
muxRouter := http.NewServeMux()
muxRouter.Handle("/healthtest", r)

srv := httptest.NewUnstartedServer(mux)
srv := httptest.NewUnstartedServer(muxRouter)

l, _ := net.Listen("tcp", "localhost:8082")
srv.Listener = l
Expand Down Expand Up @@ -236,3 +236,34 @@ func TestHealthchecker_CustomFunctionName(t *testing.T) {
t.Error("error setting custom function name")
}
}

func TestHealthChecker_ReturnsOnlySystemCheck(t *testing.T) {
h := NewHealthchecker(http.StatusOK, http.StatusInternalServerError)

r := h.ActivateHealthCheck("/healthtest")

muxRouter := http.NewServeMux()
muxRouter.Handle("/healthtest", r)

srv := httptest.NewUnstartedServer(muxRouter)

l, _ := net.Listen("tcp", "localhost:8082")
srv.Listener = l

srv.Start()
defer srv.Close()

w := httptest.NewRecorder()
r.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "http://localhost:8082/healthtest", nil))

if w.Code != http.StatusOK {
t.Error("code is not status ok")
}

var body map[string]interface{}
_ = json.Unmarshal(w.Body.Bytes(), &body)

if _, ok := body["systemInformation"]; !ok {
t.Error("we should see the systemInformation data inside the body")
}
}
113 changes: 113 additions & 0 deletions helper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package gohealthchecker

import (
"fmt"
"github.com/ervitis/goprocinfo/linux"
"net"
"os"
"runtime"
"sync"
"time"
)

const (
Running string = "R"
Sleeping = "S"
Waiting = "D"
Zombie = "Z"
Stopped = "T"
Traced = "t"
Dead = "X"
)

type (
ipAddressInfo struct {
ipAddress string
}

mappedProcessesStatus map[string]bool

memoryData struct {
total uint64
free uint64
available uint64
}

SystemInformation struct {
mappedProcessStatus mappedProcessesStatus
processStatus string
processActive bool
pid uint64
startTime time.Time
memory *memoryData
ipAddress *ipAddressInfo
runtimeVersion string
canAcceptWork bool

mtx sync.Mutex
}
)

// GetIpAddress returns the ip address of the machine
// it returns an IpAddressInfo object which stores the IpAddress
// we are using the net.Dial function to get the origin of the connection to the host we want to connect
func (info *SystemInformation) getIpAddress() *ipAddressInfo {
conn, _ := net.Dial("udp", "1.1.1.1:80")
defer conn.Close()

return &ipAddressInfo{ipAddress: conn.LocalAddr().(*net.UDPAddr).IP.String()}
}

// GetRuntimeVersion returns the Golang version which was compiled
func (info *SystemInformation) getRuntimeVersion() string {
return runtime.Version()
}

func (info *SystemInformation) initMappedStatus() {
if info.mappedProcessStatus != nil {
return
}

info.mtx.Lock()
defer info.mtx.Unlock()
info.mappedProcessStatus = make(mappedProcessesStatus)
info.mappedProcessStatus[Running] = true
info.mappedProcessStatus[Sleeping] = true
info.mappedProcessStatus[Traced] = false
info.mappedProcessStatus[Waiting] = false
info.mappedProcessStatus[Zombie] = false
info.mappedProcessStatus[Stopped] = false
info.mappedProcessStatus[Dead] = false
}

// GetSystemInfo initialize the data of the current process running
// it returns an error if it cannot locate the *nix files to get the data
func (info *SystemInformation) GetSystemInfo() error {
info.initMappedStatus()

info.mtx.Lock()
defer info.mtx.Unlock()

processStatus, err := linux.ReadProcessStat(fmt.Sprintf("/proc/%d/stat", os.Getpid()))
if err != nil {
return err
}

memInfo, err := linux.ReadMemInfo("/proc/meminfo")
if err != nil {
return err
}

info.pid = processStatus.Pid
info.processActive = info.mappedProcessStatus[processStatus.State]
info.processStatus = processStatus.State
info.memory = &memoryData{
total: memInfo.MemTotal,
free: memInfo.MemFree,
available: memInfo.MemAvailable,
}
info.runtimeVersion = info.getRuntimeVersion()
info.ipAddress = info.getIpAddress()
info.canAcceptWork = info.processActive && (info.memory.available > 0)
return nil
}
32 changes: 32 additions & 0 deletions helper_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package gohealthchecker

import "testing"

func TestHelperGetHostIpAddress(t *testing.T) {
info := &SystemInformation{}
myIp := info.getIpAddress()

if myIp.ipAddress == "" {
t.Errorf("ip address should not be empty")
}

if myIp.ipAddress == "127.0.0.1" || myIp.ipAddress == "0.0.0.0" {
t.Errorf("ip address should not be localhost")
}
}

func TestHelperGetRuntimeVersion(t *testing.T) {
info := &SystemInformation{}
version := info.getRuntimeVersion()

if version == "" {
t.Errorf("version should not be empty")
}
}

func TestGetStatusSystem(t *testing.T) {
info := &SystemInformation{}
if err := info.GetSystemInfo(); err != nil {
t.Errorf(err.Error())
}
}

0 comments on commit e1cf970

Please sign in to comment.