uptime.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. package uptime
  2. import (
  3. "encoding/json"
  4. "log"
  5. "net/http"
  6. "time"
  7. "imuslab.com/zoraxy/mod/utils"
  8. )
  9. type Record struct {
  10. Timestamp int64
  11. ID string
  12. Name string
  13. URL string
  14. Protocol string
  15. Online bool
  16. StatusCode int
  17. Latency int64
  18. }
  19. type Target struct {
  20. ID string
  21. Name string
  22. URL string
  23. Protocol string
  24. }
  25. type Config struct {
  26. Targets []*Target
  27. Interval int
  28. MaxRecordsStore int
  29. }
  30. type Monitor struct {
  31. Config *Config
  32. OnlineStatusLog map[string][]*Record
  33. }
  34. // Default configs
  35. var exampleTarget = Target{
  36. ID: "example",
  37. Name: "Example",
  38. URL: "example.com",
  39. Protocol: "https",
  40. }
  41. // Create a new uptime monitor
  42. func NewUptimeMonitor(config *Config) (*Monitor, error) {
  43. //Create new monitor object
  44. thisMonitor := Monitor{
  45. Config: config,
  46. OnlineStatusLog: map[string][]*Record{},
  47. }
  48. //Start the endpoint listener
  49. ticker := time.NewTicker(time.Duration(config.Interval) * time.Second)
  50. done := make(chan bool)
  51. //Start the uptime check once first before entering loop
  52. thisMonitor.ExecuteUptimeCheck()
  53. go func() {
  54. for {
  55. select {
  56. case <-done:
  57. return
  58. case t := <-ticker.C:
  59. log.Println("Uptime updated - ", t.Unix())
  60. thisMonitor.ExecuteUptimeCheck()
  61. }
  62. }
  63. }()
  64. return &thisMonitor, nil
  65. }
  66. func (m *Monitor) ExecuteUptimeCheck() {
  67. for _, target := range m.Config.Targets {
  68. //For each target to check online, do the following
  69. var thisRecord Record
  70. if target.Protocol == "http" || target.Protocol == "https" {
  71. online, laterncy, statusCode := getWebsiteStatusWithLatency(target.URL)
  72. thisRecord = Record{
  73. Timestamp: time.Now().Unix(),
  74. ID: target.ID,
  75. Name: target.Name,
  76. URL: target.URL,
  77. Protocol: target.Protocol,
  78. Online: online,
  79. StatusCode: statusCode,
  80. Latency: laterncy,
  81. }
  82. //fmt.Println(thisRecord)
  83. } else {
  84. log.Println("Unknown protocol: " + target.Protocol + ". Skipping")
  85. continue
  86. }
  87. thisRecords, ok := m.OnlineStatusLog[target.ID]
  88. if !ok {
  89. //First record. Create the array
  90. m.OnlineStatusLog[target.ID] = []*Record{&thisRecord}
  91. } else {
  92. //Append to the previous record
  93. thisRecords = append(thisRecords, &thisRecord)
  94. //Check if the record is longer than the logged record. If yes, clear out the old records
  95. if len(thisRecords) > m.Config.MaxRecordsStore {
  96. thisRecords = thisRecords[1:]
  97. }
  98. m.OnlineStatusLog[target.ID] = thisRecords
  99. }
  100. }
  101. //TODO: Write results to db
  102. }
  103. func (m *Monitor) AddTargetToMonitor(target *Target) {
  104. // Add target to Config
  105. m.Config.Targets = append(m.Config.Targets, target)
  106. // Add target to OnlineStatusLog
  107. m.OnlineStatusLog[target.ID] = []*Record{}
  108. }
  109. func (m *Monitor) RemoveTargetFromMonitor(targetId string) {
  110. // Remove target from Config
  111. for i, target := range m.Config.Targets {
  112. if target.ID == targetId {
  113. m.Config.Targets = append(m.Config.Targets[:i], m.Config.Targets[i+1:]...)
  114. break
  115. }
  116. }
  117. // Remove target from OnlineStatusLog
  118. delete(m.OnlineStatusLog, targetId)
  119. }
  120. // Scan the config target. If a target exists in m.OnlineStatusLog no longer
  121. // exists in m.Monitor.Config.Targets, it remove it from the log as well.
  122. func (m *Monitor) CleanRecords() {
  123. // Create a set of IDs for all targets in the config
  124. targetIDs := make(map[string]bool)
  125. for _, target := range m.Config.Targets {
  126. targetIDs[target.ID] = true
  127. }
  128. // Iterate over all log entries and remove any that have a target ID that
  129. // is not in the set of current target IDs
  130. newStatusLog := m.OnlineStatusLog
  131. for id, _ := range m.OnlineStatusLog {
  132. _, idExistsInTargets := targetIDs[id]
  133. if !idExistsInTargets {
  134. delete(newStatusLog, id)
  135. }
  136. }
  137. m.OnlineStatusLog = newStatusLog
  138. }
  139. /*
  140. Web Interface Handler
  141. */
  142. func (m *Monitor) HandleUptimeLogRead(w http.ResponseWriter, r *http.Request) {
  143. id, _ := utils.GetPara(r, "id")
  144. if id == "" {
  145. js, _ := json.Marshal(m.OnlineStatusLog)
  146. w.Header().Set("Content-Type", "application/json")
  147. w.Write(js)
  148. } else {
  149. //Check if that id exists
  150. log, ok := m.OnlineStatusLog[id]
  151. if !ok {
  152. http.NotFound(w, r)
  153. return
  154. }
  155. js, _ := json.MarshalIndent(log, "", " ")
  156. w.Header().Set("Content-Type", "application/json")
  157. w.Write(js)
  158. }
  159. }
  160. /*
  161. Utilities
  162. */
  163. // Get website stauts with latency given URL, return is conn succ and its latency and status code
  164. func getWebsiteStatusWithLatency(url string) (bool, int64, int) {
  165. start := time.Now().UnixNano() / int64(time.Millisecond)
  166. statusCode, err := getWebsiteStatus(url)
  167. end := time.Now().UnixNano() / int64(time.Millisecond)
  168. if err != nil {
  169. log.Println(err.Error())
  170. return false, 0, 0
  171. } else {
  172. diff := end - start
  173. succ := false
  174. if statusCode >= 200 && statusCode < 300 {
  175. //OK
  176. succ = true
  177. } else if statusCode >= 300 && statusCode < 400 {
  178. //Redirection code
  179. succ = true
  180. } else {
  181. succ = false
  182. }
  183. return succ, diff, statusCode
  184. }
  185. }
  186. func getWebsiteStatus(url string) (int, error) {
  187. resp, err := http.Get(url)
  188. if err != nil {
  189. return 0, err
  190. }
  191. status_code := resp.StatusCode
  192. resp.Body.Close()
  193. return status_code, nil
  194. }