github.com/Axway/agent-sdk@v1.1.101/pkg/transaction/metric/cachestorage.go (about) 1 package metric 2 3 import ( 4 "encoding/json" 5 "os" 6 "os/signal" 7 "strings" 8 "sync" 9 "syscall" 10 "time" 11 12 "github.com/Axway/agent-sdk/pkg/agent" 13 "github.com/Axway/agent-sdk/pkg/cache" 14 "github.com/Axway/agent-sdk/pkg/traceability" 15 "github.com/Axway/agent-sdk/pkg/util" 16 metrics "github.com/rcrowley/go-metrics" 17 ) 18 19 const ( 20 appUsagePrefix = "app_usage." 21 cacheFileName = "agent-usagemetric.json" 22 metricKeyPrefix = "metric." 23 metricStartTimeKey = "metric_start_time" 24 usageStartTimeKey = "usage_start_time" 25 usageCountKey = "usage_count" 26 volumeKey = "usage_volume" 27 ) 28 29 type storageCache interface { 30 initialize() 31 updateUsage(usageCount int) 32 updateVolume(bytes int64) 33 updateAppUsage(usageCount int, appID string) 34 updateMetric(apiStatusMetric metrics.Histogram, metric *APIMetric) 35 removeMetric(metric *APIMetric) 36 save() 37 } 38 39 type cacheStorage struct { 40 cacheFilePath string 41 oldCacheFilePath string 42 collector *collector 43 storage cache.Cache 44 storageLock sync.Mutex 45 isInitialized bool 46 } 47 48 func newStorageCache(collector *collector) storageCache { 49 storageCache := &cacheStorage{ 50 cacheFilePath: traceability.GetCacheDirPath() + "/" + cacheFileName, 51 oldCacheFilePath: traceability.GetDataDirPath() + "/" + cacheFileName, 52 collector: collector, 53 storageLock: sync.Mutex{}, 54 storage: cache.New(), 55 isInitialized: false, 56 } 57 58 return storageCache 59 } 60 61 func (c *cacheStorage) moveCacheFile() { 62 // to remove for next major release 63 _, err := os.Stat(c.oldCacheFilePath) 64 if os.IsNotExist(err) { 65 return 66 } 67 // file exists, move it over 68 os.Rename(c.oldCacheFilePath, c.cacheFilePath) 69 } 70 71 func (c *cacheStorage) initialize() { 72 c.moveCacheFile() // to remove for next major release 73 storageCache := cache.Load(c.cacheFilePath) 74 c.loadUsage(storageCache) 75 c.loadMetrics(storageCache) 76 77 // Not a job as the loop requires signal processing 78 if !c.isInitialized && util.IsNotTest() { 79 go c.storeCacheJob() 80 } 81 c.storage = storageCache 82 c.isInitialized = true 83 } 84 85 func (c *cacheStorage) loadUsage(storageCache cache.Cache) { 86 // update the collector usage start time 87 usageStartTime, err := parseTimeFromCache(storageCache, usageStartTimeKey) 88 if err == nil && !agent.GetCentralConfig().GetUsageReportingConfig().IsOfflineMode() { 89 // do not load this start time when offline 90 c.collector.usageStartTime = usageStartTime 91 } 92 // update the collector metric start time 93 metricStartTime, err := parseTimeFromCache(storageCache, metricStartTimeKey) 94 if err == nil && !agent.GetCentralConfig().GetUsageReportingConfig().IsOfflineMode() { 95 // do not load this start time when offline 96 c.collector.metricStartTime = metricStartTime 97 } 98 99 // update transaction counter in registry. 100 usageCount, err := storageCache.Get(usageCountKey) 101 if err == nil { 102 // un-marshalling the cache defaults the serialization of numeric values to float64 103 c.collector.updateUsage(int64(usageCount.(float64))) 104 } 105 106 // update transaction volume in registry. 107 usageVolume, err := storageCache.Get(volumeKey) 108 if err == nil { 109 // un-marshalling the cache defaults the serialization of numeric values to float64 110 c.collector.updateVolume(int64(usageVolume.(float64))) 111 } 112 } 113 114 func (c *cacheStorage) updateUsage(usageCount int) { 115 if !c.isInitialized || !agent.GetCentralConfig().GetUsageReportingConfig().CanPublish() { 116 return 117 } 118 119 c.storageLock.Lock() 120 defer c.storageLock.Unlock() 121 c.storage.Set(usageStartTimeKey, c.collector.usageStartTime) 122 c.storage.Set(metricStartTimeKey, c.collector.metricStartTime) 123 c.storage.Set(usageCountKey, usageCount) 124 } 125 126 func (c *cacheStorage) updateVolume(bytes int64) { 127 if !c.isInitialized || !agent.GetCentralConfig().IsAxwayManaged() || 128 !agent.GetCentralConfig().GetUsageReportingConfig().CanPublish() { 129 // NOT initialized or NOT axway managed or can NOT publish usage 130 return 131 } 132 133 c.storageLock.Lock() 134 defer c.storageLock.Unlock() 135 c.storage.Set(volumeKey, bytes) 136 } 137 138 func (c *cacheStorage) updateAppUsage(usageCount int, appID string) { 139 if !c.isInitialized || !agent.GetCentralConfig().GetUsageReportingConfig().CanPublish() { 140 return 141 } 142 143 c.storageLock.Lock() 144 defer c.storageLock.Unlock() 145 c.storage.Set(appUsagePrefix+appID, usageCount) 146 } 147 148 func (c *cacheStorage) loadMetrics(storageCache cache.Cache) { 149 cacheKeys := storageCache.GetKeys() 150 for _, cacheKey := range cacheKeys { 151 if strings.Contains(cacheKey, metricKeyPrefix) { 152 if agent.GetCentralConfig().GetUsageReportingConfig().IsOfflineMode() { 153 // delete metrics from cache in offline mode 154 storageCache.Delete(cacheKey) 155 continue 156 } 157 cacheItem, _ := storageCache.Get(cacheKey) 158 159 buffer, _ := json.Marshal(cacheItem) 160 var cm cachedMetric 161 json.Unmarshal(buffer, &cm) 162 163 var metric *APIMetric 164 for _, duration := range cm.Values { 165 metricDetail := Detail{ 166 APIDetails: cm.API, 167 AppDetails: cm.App, 168 StatusCode: cm.StatusCode, 169 Duration: duration, 170 } 171 metric = c.collector.createOrUpdateMetric(metricDetail) 172 } 173 174 newKey := c.getKey(metric) 175 if newKey != cacheKey { 176 c.storageLock.Lock() 177 storageCache.Delete(cacheKey) 178 c.storageLock.Unlock() 179 } 180 storageCache.Set(newKey, cm) 181 if metric != nil { 182 metric.StartTime = cm.StartTime 183 } 184 } 185 } 186 } 187 188 func (c *cacheStorage) updateMetric(histogram metrics.Histogram, metric *APIMetric) { 189 if !c.isInitialized { 190 return 191 } 192 193 c.storageLock.Lock() 194 defer c.storageLock.Unlock() 195 196 cachedMetric := cachedMetric{ 197 Subscription: metric.Subscription, 198 App: metric.App, 199 Product: metric.Product, 200 AssetResource: metric.AssetResource, 201 ProductPlan: metric.ProductPlan, 202 Quota: metric.Quota, 203 API: metric.API, 204 StatusCode: metric.StatusCode, 205 Count: histogram.Count(), 206 Values: histogram.Sample().Values(), 207 StartTime: metric.StartTime, 208 } 209 210 c.storage.Set(c.getKey(metric), cachedMetric) 211 } 212 213 func (c *cacheStorage) removeMetric(metric *APIMetric) { 214 if !c.isInitialized { 215 return 216 } 217 c.storageLock.Lock() 218 defer c.storageLock.Unlock() 219 220 c.storage.Delete(c.getKey(metric)) 221 } 222 223 func (c *cacheStorage) getKey(metric *APIMetric) string { 224 return metricKeyPrefix + 225 metric.Subscription.ID + "." + 226 metric.App.ID + "." + 227 metric.API.ID + "." + 228 metric.StatusCode 229 } 230 231 func (c *cacheStorage) save() { 232 if !c.isInitialized { 233 return 234 } 235 236 c.storageLock.Lock() 237 defer c.storageLock.Unlock() 238 239 c.storage.Save(c.cacheFilePath) 240 } 241 242 func (c *cacheStorage) storeCacheJob() { 243 cachetimeTicker := time.NewTicker(5 * time.Second) 244 signals := make(chan os.Signal, 1) 245 signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP) 246 for { 247 select { 248 case <-cachetimeTicker.C: 249 c.save() 250 case <-signals: 251 c.save() 252 return 253 } 254 } 255 } 256 257 func parseTimeFromCache(storage cache.Cache, key string) (time.Time, error) { 258 resultTime := now() 259 item, err := storage.Get(key) 260 if err != nil { 261 return now(), err 262 } 263 cachedTimeStr, ok := item.(string) 264 if ok { 265 resultTime, _ = time.Parse(time.RFC3339, cachedTimeStr) 266 } else { 267 cachedTime, ok := item.(time.Time) 268 if ok { 269 resultTime = cachedTime 270 } 271 } 272 return resultTime, nil 273 }