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  }