github.com/Axway/agent-sdk@v1.1.101/pkg/transaction/metric/reportcache.go (about) 1 package metric 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io" 8 "os" 9 "path" 10 "path/filepath" 11 "sort" 12 "strconv" 13 "sync" 14 "time" 15 16 "github.com/Axway/agent-sdk/pkg/agent" 17 "github.com/Axway/agent-sdk/pkg/cache" 18 "github.com/Axway/agent-sdk/pkg/jobs" 19 "github.com/Axway/agent-sdk/pkg/traceability" 20 "github.com/Axway/agent-sdk/pkg/util/log" 21 "github.com/gorhill/cronexpr" 22 ) 23 24 const ( 25 eventsKey = "lighthouse_events" 26 lastPublishTimestampKey = "timestamp" 27 offlineCacheFileName = "agent-report-working.json" 28 offlineReportSuffix = "usage_report.json" 29 offlineReportDateFormat = "2006_01_02" 30 qaOfflineReportDateFormat = "2006_01_02_15_04" 31 ) 32 33 type currentTimeFunc func() time.Time 34 35 type usageReportCache struct { 36 jobs.Job 37 logger log.FieldLogger 38 cacheFilePath string 39 reportCache cache.Cache 40 reportCacheLock sync.Mutex 41 isInitialized bool 42 offlineReportDateFormat string 43 currTimeFunc currentTimeFunc 44 } 45 46 func newReportCache() *usageReportCache { 47 reportManager := &usageReportCache{ 48 logger: log.NewFieldLogger().WithPackage("metric").WithComponent("usageReportCache"), 49 cacheFilePath: traceability.GetCacheDirPath() + "/" + offlineCacheFileName, 50 reportCacheLock: sync.Mutex{}, 51 reportCache: cache.New(), 52 isInitialized: false, 53 offlineReportDateFormat: offlineReportDateFormat, 54 currTimeFunc: time.Now, 55 } 56 if agent.GetCentralConfig().GetUsageReportingConfig().UsingQAVars() { 57 reportManager.offlineReportDateFormat = qaOfflineReportDateFormat 58 } 59 60 reportManager.initialize() 61 return reportManager 62 } 63 64 func (c *usageReportCache) initialize() { 65 reportCache := cache.Load(c.cacheFilePath) 66 c.reportCache = reportCache 67 c.isInitialized = true 68 } 69 70 // getEvents - gets the events from the cache, lock before calling this 71 func (c *usageReportCache) getEvents() UsageEvent { 72 var savedLighthouseEvents UsageEvent 73 74 savedEventString, err := c.reportCache.Get(eventsKey) 75 if err != nil { 76 return UsageEvent{Report: map[string]UsageReport{}} 77 } 78 79 err = json.Unmarshal([]byte(savedEventString.(string)), &savedLighthouseEvents) 80 if err != nil { 81 return UsageEvent{Report: map[string]UsageReport{}} 82 } 83 return savedLighthouseEvents 84 } 85 86 // loadEvents - locks the cache before getting the events 87 func (c *usageReportCache) loadEvents() UsageEvent { 88 if !agent.GetCentralConfig().GetUsageReportingConfig().CanPublish() { 89 return UsageEvent{Report: map[string]UsageReport{}} 90 } 91 c.reportCacheLock.Lock() 92 defer c.reportCacheLock.Unlock() 93 94 return c.getEvents() 95 } 96 97 // setEvents - sets the events in the cache and saves the cache to the disk, lock the cache before calling this 98 func (c *usageReportCache) setEvents(lighthouseEvent UsageEvent) { 99 eventBytes, err := json.Marshal(lighthouseEvent) 100 if err != nil { 101 return 102 } 103 c.reportCache.Set(eventsKey, string(eventBytes)) 104 c.reportCache.Save(c.cacheFilePath) 105 } 106 107 // updateEvents - locks the cache before setting the new light house events in the cache 108 func (c *usageReportCache) updateEvents(lighthouseEvent UsageEvent) { 109 if !c.isInitialized || !agent.GetCentralConfig().GetUsageReportingConfig().CanPublish() { 110 return 111 } 112 113 c.reportCacheLock.Lock() 114 defer c.reportCacheLock.Unlock() 115 116 c.setEvents(lighthouseEvent) 117 } 118 119 func (c *usageReportCache) setLastPublishTimestamp(lastPublishTimestamp time.Time) { 120 c.reportCache.Set(lastPublishTimestampKey, lastPublishTimestamp) 121 c.reportCache.Save(c.cacheFilePath) 122 } 123 124 func (c *usageReportCache) getLastPublishTimestamp() time.Time { 125 c.reportCacheLock.Lock() 126 defer c.reportCacheLock.Unlock() 127 128 lastPublishTime, err := parseTimeFromCache(c.reportCache, lastPublishTimestampKey) 129 if err != nil { 130 return time.Time{} 131 } 132 133 return lastPublishTime 134 } 135 136 func (c *usageReportCache) generateReportPath(timestamp ISO8601Time, index int) string { 137 format := "%s_%s" 138 if index != 0 { 139 format = "%s_" + strconv.Itoa(index) + "_%s" 140 } 141 return path.Join(traceability.GetReportsDirPath(), fmt.Sprintf(format, time.Time(timestamp).Format(c.offlineReportDateFormat), offlineReportSuffix)) 142 } 143 144 // validateReport - copies usage events setting all usages to 0 for any missing time interval 145 func (c *usageReportCache) validateReport(savedEvents UsageEvent) UsageEvent { 146 reportDuration := time.Duration(savedEvents.Granularity * int(time.Millisecond)) 147 148 // order all the keys, this will be used to find any missing times 149 orderedKeys := make([]string, 0, len(savedEvents.Report)) 150 for k := range savedEvents.Report { 151 orderedKeys = append(orderedKeys, k) 152 } 153 sort.Strings(orderedKeys) 154 155 // create an empty report to insert when necessary 156 emptyReport := UsageReport{ 157 Product: savedEvents.Report[orderedKeys[0]].Product, 158 Usage: make(map[string]int64), 159 Meta: savedEvents.Report[orderedKeys[0]].Meta, 160 } 161 for usage := range savedEvents.Report[orderedKeys[0]].Usage { 162 emptyReport.Usage[usage] = 0 163 } 164 165 curDate, _ := time.Parse(ISO8601, orderedKeys[0]) 166 lastDate, _ := time.Parse(ISO8601, orderedKeys[len(orderedKeys)-1]) 167 for curDate.Before(lastDate) { 168 curDateString := curDate.Format(ISO8601) 169 if _, exists := savedEvents.Report[curDateString]; !exists { 170 savedEvents.Report[curDateString] = emptyReport 171 } 172 curDate = curDate.Add(reportDuration) 173 } 174 return savedEvents 175 } 176 177 // addReport - adds a new report to the cache 178 func (c *usageReportCache) addReport(event UsageEvent) error { 179 // Open and load the existing usage file 180 savedEvents := c.loadEvents() 181 182 for key, report := range event.Report { 183 savedEvents.Report[key] = report 184 } 185 // Put all reports into the new event 186 event.Report = savedEvents.Report 187 188 // Update the cache 189 c.updateEvents(event) 190 191 return nil 192 } 193 194 // saveReport - creates a new file with the latest cached events then clears all reports from the cache, lock outside of this 195 func (c *usageReportCache) saveReport() error { 196 c.reportCacheLock.Lock() 197 defer c.reportCacheLock.Unlock() 198 savedEvents := c.getEvents() 199 200 // no reports yet, skip creating the event 201 if len(savedEvents.Report) == 0 { 202 return nil 203 } 204 savedEvents = c.validateReport(savedEvents) 205 206 // create the path to save the file 207 outputFilePath := "" 208 i := 0 209 fileExists := true 210 for fileExists { 211 outputFilePath = c.generateReportPath(savedEvents.Timestamp, i) 212 _, err := os.Stat(outputFilePath) 213 i++ 214 fileExists = !os.IsNotExist(err) 215 } 216 217 // create the new file to save the events 218 file, err := os.Create(filepath.Clean(outputFilePath)) 219 if err != nil { 220 return err 221 } 222 223 // marshal the event into json bytes 224 cacheBytes, err := json.Marshal(savedEvents) 225 if err != nil { 226 file.Close() 227 return err 228 } 229 230 // save the bytes and close the file 231 _, err = io.Copy(file, bytes.NewReader(cacheBytes)) 232 file.Close() 233 if err != nil { 234 return err 235 } 236 237 // clear out all reports 238 savedEvents.Report = make(map[string]UsageReport) 239 c.setEvents(savedEvents) 240 return nil 241 } 242 243 // sendReport - creates a new report with the latest cached events then clears all reports from the cache, lock outside of this 244 func (c *usageReportCache) sendReport(publishFunc func(event UsageEvent) error) error { 245 c.reportCacheLock.Lock() 246 defer c.reportCacheLock.Unlock() 247 savedEvents := c.getEvents() 248 249 // no reports yet, skip creating the event 250 if len(savedEvents.Report) == 0 { 251 return nil 252 } 253 savedEvents = c.validateReport(savedEvents) 254 if err := publishFunc(savedEvents); err != nil { 255 c.logger.Error("could not publish usage, will send at next scheduled publishing") 256 return nil 257 } 258 259 // update the publish time 260 lastPublishTime := time.Now() 261 c.setLastPublishTimestamp(lastPublishTime) 262 263 savedEvents.Report = make(map[string]UsageReport) 264 c.setEvents(savedEvents) 265 return nil 266 } 267 268 func (c *usageReportCache) shouldPublish(schedule string) bool { 269 currentTime := c.currTimeFunc() 270 lastPublishTimestamp := c.getLastPublishTimestamp() 271 272 // if the last publish was made more than a day ago, publish 273 elapsedTimeSinceLastPublish := currentTime.Sub(lastPublishTimestamp) 274 if lastPublishTimestamp.IsZero() || elapsedTimeSinceLastPublish >= 24*time.Hour { 275 return true 276 } 277 278 cronSchedule, err := cronexpr.Parse(schedule) 279 if err != nil { 280 return false 281 } 282 // publish if last scheduled time is past 283 nextPublishTime := cronSchedule.Next(lastPublishTimestamp) 284 if nextPublishTime.Before(currentTime) { 285 return true 286 } 287 288 return false 289 }