package dbdata import ( "container/list" "errors" "fmt" "strconv" "sync" "time" "github.com/bjdgyc/anylink/base" ) const ( LayoutTimeFormat = "2006-01-02 15:04:05" LayoutTimeFormatMin = "2006-01-02 15:04" RealTimeMaxSize = 120 // 实时数据最大保存条数 ) type StatsInfo struct { RealtimeData map[string]*list.List Actions []string Scopes []string mux sync.Mutex } type ScopeDetail struct { sTime time.Time eTime time.Time minutes int fsTime string feTime string } var StatsInfoIns *StatsInfo func init() { StatsInfoIns = &StatsInfo{ Actions: []string{"online", "network", "cpu", "mem"}, Scopes: []string{"rt", "1h", "24h", "3d", "7d", "30d"}, RealtimeData: make(map[string]*list.List), } for _, v := range StatsInfoIns.Actions { StatsInfoIns.RealtimeData[v] = list.New() } } // 校验统计类型值 func (s *StatsInfo) ValidAction(action string) bool { for _, item := range s.Actions { if item == action { return true } } return false } // 校验日期范围值 func (s *StatsInfo) ValidScope(scope string) bool { for _, item := range s.Scopes { if item == scope { return true } } return false } // 设置实时统计数据 func (s *StatsInfo) SetRealTime(action string, val interface{}) { s.mux.Lock() defer s.mux.Unlock() if s.RealtimeData[action].Len() >= RealTimeMaxSize { ele := s.RealtimeData[action].Front() s.RealtimeData[action].Remove(ele) } s.RealtimeData[action].PushBack(val) } // 获取实时统计数据 func (s *StatsInfo) GetRealTime(action string) (res []interface{}) { s.mux.Lock() defer s.mux.Unlock() for e := s.RealtimeData[action].Front(); e != nil; e = e.Next() { res = append(res, e.Value) } return } // 保存数据至数据库 func (s *StatsInfo) SaveStatsInfo(so StatsOnline, sn StatsNetwork, sc StatsCpu, sm StatsMem) { if so.Num != 0 { _ = Add(so) } if sn.Up != 0 || sn.Down != 0 { _ = Add(sn) } if sc.Percent != 0 { _ = Add(sc) } if sm.Percent != 0 { _ = Add(sm) } } // 获取统计数据 func (s *StatsInfo) GetData(action string, scope string) (res []interface{}, err error) { if scope == "rt" { return s.GetRealTime(action), nil } statsMaps := make(map[string]interface{}) currSec := fmt.Sprintf("%02d", time.Now().Second()) // 获取时间段数据 sd := s.getScopeDetail(scope) timeList := s.getTimeList(sd) res = make([]interface{}, len(timeList)) // 获取数据库查询条件 where := s.getStatsWhere(sd) if where == "" { return nil, errors.New("不支持的数据库类型: " + base.Cfg.DbType) } // 查询数据表 switch action { case "online": statsRes := []StatsOnline{} FindWhere(&statsRes, 0, 0, where, sd.fsTime, sd.feTime) for _, v := range statsRes { t := v.CreatedAt.Format(LayoutTimeFormatMin) statsMaps[t] = v } case "network": statsRes := []StatsNetwork{} FindWhere(&statsRes, 0, 0, where, sd.fsTime, sd.feTime) for _, v := range statsRes { t := v.CreatedAt.Format(LayoutTimeFormatMin) statsMaps[t] = v } case "cpu": statsRes := []StatsCpu{} FindWhere(&statsRes, 0, 0, where, sd.fsTime, sd.feTime) for _, v := range statsRes { t := v.CreatedAt.Format(LayoutTimeFormatMin) statsMaps[t] = v } case "mem": statsRes := []StatsMem{} FindWhere(&statsRes, 0, 0, where, sd.fsTime, sd.feTime) for _, v := range statsRes { t := v.CreatedAt.Format(LayoutTimeFormatMin) statsMaps[t] = v } } // 整合数据 for i, v := range timeList { if mv, ok := statsMaps[v]; ok { res[i] = mv continue } t, _ := time.ParseInLocation(LayoutTimeFormat, v+":"+currSec, time.Local) switch action { case "online": res[i] = StatsOnline{CreatedAt: t} case "network": res[i] = StatsNetwork{CreatedAt: t} case "cpu": res[i] = StatsCpu{CreatedAt: t} case "mem": res[i] = StatsMem{CreatedAt: t} } } return } // 获取日期范围的明细值 func (s *StatsInfo) getScopeDetail(scope string) (sd *ScopeDetail) { sd = &ScopeDetail{} t := time.Now() sd.eTime = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), 59, t.Nanosecond(), time.Local) sd.minutes = 0 switch scope { case "1h": sd.sTime = sd.eTime.Add(-time.Minute * 60) sd.minutes = 1 case "24h": sd.sTime = sd.eTime.AddDate(0, 0, -1) sd.minutes = 5 case "7d": sd.sTime = sd.eTime.AddDate(0, 0, -7) sd.minutes = 30 case "30d": sd.sTime = sd.eTime.AddDate(0, 0, -30) sd.minutes = 150 } if sd.minutes != 0 { sd.sTime = sd.sTime.Add(-time.Minute * time.Duration(sd.minutes)) } sd.fsTime = sd.sTime.Format(LayoutTimeFormat) sd.feTime = sd.eTime.Format(LayoutTimeFormat) return } // 针对日期范围进行拆解 func (s *StatsInfo) getTimeList(sd *ScopeDetail) []string { subSec := int64(60 * sd.minutes) count := (sd.eTime.Unix()-sd.sTime.Unix())/subSec - 1 eTime := sd.eTime.Unix() - subSec timeLists := make([]string, count) for i := count - 1; i >= 0; i-- { timeLists[i] = time.Unix(eTime, 0).Format(LayoutTimeFormatMin) eTime = eTime - subSec } return timeLists } // 获取where条件 func (s *StatsInfo) getStatsWhere(sd *ScopeDetail) (where string) { where = "created_at BETWEEN ? AND ?" min := strconv.Itoa(sd.minutes) switch base.Cfg.DbType { case "mysql": where += " AND floor(TIMESTAMPDIFF(SECOND, created_at, '" + sd.feTime + "') / 60) % " + min + " = 0" case "sqlite3": where += " AND CAST(ROUND((JULIANDAY('" + sd.feTime + "') - JULIANDAY(created_at)) * 86400) / 60 as integer) % " + min + " = 0" case "postgres": where += " AND floor((EXTRACT(EPOCH FROM " + sd.feTime + ") - EXTRACT(EPOCH FROM created_at)) / 60) % " + min + " = 0" default: where = "" } return } func (s *StatsInfo) ClearStatsInfo(action string, ts string) (affected int64, err error) { switch action { case "online": affected, err = xdb.Where("created_at < '" + ts + "'").Delete(&StatsOnline{}) case "network": affected, err = xdb.Where("created_at < '" + ts + "'").Delete(&StatsNetwork{}) case "cpu": affected, err = xdb.Where("created_at < '" + ts + "'").Delete(&StatsCpu{}) case "mem": affected, err = xdb.Where("created_at < '" + ts + "'").Delete(&StatsMem{}) } return affected, err }