123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281 |
- package statistic
- import (
- "path/filepath"
- "strings"
- "sync"
- "time"
- "github.com/microcosm-cc/bluemonday"
- "imuslab.com/zoraxy/mod/database"
- )
- /*
- Statistic Package
- This packet is designed to collection information
- and store them for future analysis
- */
- // Faststat, a interval summary for all collected data and avoid
- // looping through every data everytime a overview is needed
- type DailySummary struct {
- TotalRequest int64 //Total request of the day
- ErrorRequest int64 //Invalid request of the day, including error or not found
- ValidRequest int64 //Valid request of the day
- //Type counters
- ForwardTypes *sync.Map //Map that hold the forward types
- RequestOrigin *sync.Map //Map that hold [country ISO code]: visitor counter
- RequestClientIp *sync.Map //Map that hold all unique request IPs
- Referer *sync.Map //Map that store where the user was refered from
- UserAgent *sync.Map //Map that store the useragent of the request
- RequestURL *sync.Map //Request URL of the request object
- }
- type RequestInfo struct {
- IpAddr string //IP address of the downstream request
- RequestOriginalCountryISOCode string //ISO code of the country where the request originated
- Succ bool //If the request is successful and resp generated by upstream instead of Zoraxy (except static web server)
- StatusCode int //HTTP status code of the request
- ForwardType string //Forward type of the request, usually the proxy type (e.g. host-http, subdomain-websocket or vdir-http or any of the combination)
- Referer string //Referer of the downstream request
- UserAgent string //UserAgent of the downstream request
- RequestURL string //Request URL
- Target string //Target domain or hostname
- }
- type CollectorOption struct {
- Database *database.Database
- }
- type Collector struct {
- rtdataStopChan chan bool
- DailySummary *DailySummary
- Option *CollectorOption
- }
- func NewStatisticCollector(option CollectorOption) (*Collector, error) {
- option.Database.NewTable("stats")
- //Create the collector object
- thisCollector := Collector{
- DailySummary: NewDailySummary(),
- Option: &option,
- }
- //Load the stat if exists for today
- //This will exists if the program was forcefully restarted
- year, month, day := time.Now().Date()
- summary := thisCollector.LoadSummaryOfDay(year, month, day)
- if summary != nil {
- thisCollector.DailySummary = summary
- }
- //Schedule the realtime statistic clearing at midnight everyday
- rtstatStopChan := thisCollector.ScheduleResetRealtimeStats()
- thisCollector.rtdataStopChan = rtstatStopChan
- return &thisCollector, nil
- }
- // Write the current in-memory summary to database file
- func (c *Collector) SaveSummaryOfDay() {
- //When it is called in 0:00am, make sure it is stored as yesterday key
- t := time.Now().Add(-30 * time.Second)
- summaryKey := t.Format("2006_01_02")
- saveData := DailySummaryToExport(*c.DailySummary)
- c.Option.Database.Write("stats", summaryKey, saveData)
- }
- // Get the daily summary up until now
- func (c *Collector) GetCurrentDailySummary() *DailySummary {
- return c.DailySummary
- }
- // Load the summary of a day given
- func (c *Collector) LoadSummaryOfDay(year int, month time.Month, day int) *DailySummary {
- date := time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.Local)
- summaryKey := date.Format("2006_01_02")
- targetSummaryExport := DailySummaryExport{}
- c.Option.Database.Read("stats", summaryKey, &targetSummaryExport)
- targetSummary := DailySummaryExportToSummary(targetSummaryExport)
- return &targetSummary
- }
- // Reset today summary, for debug or restoring injections
- func (c *Collector) ResetSummaryOfDay() {
- c.DailySummary = NewDailySummary()
- }
- // This function gives the current slot in the 288- 5 minutes interval of the day
- func (c *Collector) GetCurrentRealtimeStatIntervalId() int {
- now := time.Now()
- startOfDay := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local).Unix()
- secondsSinceStartOfDay := now.Unix() - startOfDay
- interval := secondsSinceStartOfDay / (5 * 60)
- return int(interval)
- }
- func (c *Collector) Close() {
- //Stop the ticker
- c.rtdataStopChan <- true
- //Write the buffered data into database
- c.SaveSummaryOfDay()
- }
- // Main function to record all the inbound traffics
- // Note that this function run in go routine and might have concurrent R/W issue
- // Please make sure there is no racing paramters in this function
- func (c *Collector) RecordRequest(ri RequestInfo) {
- go func() {
- c.DailySummary.TotalRequest++
- if ri.Succ {
- c.DailySummary.ValidRequest++
- } else {
- c.DailySummary.ErrorRequest++
- }
- //Store the request info into correct types of maps
- ft, ok := c.DailySummary.ForwardTypes.Load(ri.ForwardType)
- if !ok {
- c.DailySummary.ForwardTypes.Store(ri.ForwardType, 1)
- } else {
- c.DailySummary.ForwardTypes.Store(ri.ForwardType, ft.(int)+1)
- }
- originISO := strings.ToLower(ri.RequestOriginalCountryISOCode)
- fo, ok := c.DailySummary.RequestOrigin.Load(originISO)
- if !ok {
- c.DailySummary.RequestOrigin.Store(originISO, 1)
- } else {
- c.DailySummary.RequestOrigin.Store(originISO, fo.(int)+1)
- }
- //Filter out CF forwarded requests
- if strings.Contains(ri.IpAddr, ",") {
- ips := strings.Split(strings.TrimSpace(ri.IpAddr), ",")
- if len(ips) >= 1 && IsValidIPAddress(strings.TrimPrefix(ips[0], "[")) {
- //Example when forwarded from CF: 158.250.160.114,109.21.249.211
- //Or IPv6 [15c4:cbb4:cc98:4291:ffc1:3a46:06a1:51a7],109.21.249.211
- ri.IpAddr = ips[0]
- }
- }
- fi, ok := c.DailySummary.RequestClientIp.Load(ri.IpAddr)
- if !ok {
- c.DailySummary.RequestClientIp.Store(ri.IpAddr, 1)
- } else {
- c.DailySummary.RequestClientIp.Store(ri.IpAddr, fi.(int)+1)
- }
- //Record the referer
- p := bluemonday.StripTagsPolicy()
- filteredReferer := p.Sanitize(
- ri.Referer,
- )
- rf, ok := c.DailySummary.Referer.Load(filteredReferer)
- if !ok {
- c.DailySummary.Referer.Store(filteredReferer, 1)
- } else {
- c.DailySummary.Referer.Store(filteredReferer, rf.(int)+1)
- }
- //Record the UserAgent
- ua, ok := c.DailySummary.UserAgent.Load(ri.UserAgent)
- if !ok {
- c.DailySummary.UserAgent.Store(ri.UserAgent, 1)
- } else {
- c.DailySummary.UserAgent.Store(ri.UserAgent, ua.(int)+1)
- }
- //Record request URL, if it is a page
- ext := filepath.Ext(ri.RequestURL)
- if ext != "" && !isWebPageExtension(ext) {
- return
- }
- ru, ok := c.DailySummary.RequestURL.Load(ri.RequestURL)
- if !ok {
- c.DailySummary.RequestURL.Store(ri.RequestURL, 1)
- } else {
- c.DailySummary.RequestURL.Store(ri.RequestURL, ru.(int)+1)
- }
- }()
- //ADD MORE HERE IF NEEDED
- }
- // nightly task
- func (c *Collector) ScheduleResetRealtimeStats() chan bool {
- doneCh := make(chan bool)
- go func() {
- defer close(doneCh)
- for {
- // calculate duration until next midnight
- now := time.Now()
- // Get midnight of the next day in the local time zone
- midnight := time.Date(now.Year(), now.Month(), now.Day()+1, 0, 0, 0, 0, now.Location())
- // Calculate the duration until midnight
- duration := midnight.Sub(now)
- select {
- case <-time.After(duration):
- // store daily summary to database and reset summary
- c.SaveSummaryOfDay()
- c.DailySummary = NewDailySummary()
- case <-doneCh:
- // stop the routine
- return
- }
- }
- }()
- return doneCh
- }
- func NewDailySummary() *DailySummary {
- return &DailySummary{
- TotalRequest: 0,
- ErrorRequest: 0,
- ValidRequest: 0,
- ForwardTypes: &sync.Map{},
- RequestOrigin: &sync.Map{},
- RequestClientIp: &sync.Map{},
- Referer: &sync.Map{},
- UserAgent: &sync.Map{},
- RequestURL: &sync.Map{},
- }
- }
- func PrintDailySummary(summary *DailySummary) {
- summary.ForwardTypes.Range(func(key, value interface{}) bool {
- println(key.(string), value.(int))
- return true
- })
- summary.RequestOrigin.Range(func(key, value interface{}) bool {
- println(key.(string), value.(int))
- return true
- })
- summary.RequestClientIp.Range(func(key, value interface{}) bool {
- println(key.(string), value.(int))
- return true
- })
- summary.Referer.Range(func(key, value interface{}) bool {
- println(key.(string), value.(int))
- return true
- })
- summary.UserAgent.Range(func(key, value interface{}) bool {
- println(key.(string), value.(int))
- return true
- })
- summary.RequestURL.Range(func(key, value interface{}) bool {
- println(key.(string), value.(int))
- return true
- })
- }
|