github.com/ddev/ddev@v1.23.2-0.20240519125000-d824ffe36ff3/pkg/amplitude/storages/delayed_transmission_event_storage.go (about)

     1  package storages
     2  
     3  import (
     4  	"encoding/gob"
     5  	"os"
     6  	"sync"
     7  	"time"
     8  
     9  	"github.com/amplitude/analytics-go/amplitude/types"
    10  )
    11  
    12  // NewDelayedTransmissionEventStorage initializes a new EventStorage of type delayedTransmissionEventStorage.
    13  func NewDelayedTransmissionEventStorage(
    14  	logger types.Logger,
    15  	capacity int,
    16  	interval time.Duration,
    17  	fileName string,
    18  ) types.EventStorage {
    19  	return &delayedTransmissionEventStorage{
    20  		logger:   logger,
    21  		fileName: fileName,
    22  		loaded:   false,
    23  		capacity: capacity,
    24  		interval: interval,
    25  	}
    26  }
    27  
    28  type delayedTransmissionEventStorage struct {
    29  	logger   types.Logger
    30  	fileName string
    31  	loaded   bool
    32  	capacity int
    33  	interval time.Duration
    34  	cache    eventCache
    35  
    36  	mu sync.RWMutex
    37  }
    38  
    39  // eventCache is the structure used for the cache file.
    40  type eventCache struct {
    41  	LastSubmittedAt time.Time
    42  	Events          []*types.StorageEvent
    43  }
    44  
    45  // PushNew writes a new event to the cache.
    46  func (s *delayedTransmissionEventStorage) PushNew(event *types.StorageEvent) {
    47  	s.push(false, event)
    48  }
    49  
    50  // ReturnBack is used to return back previously pulled events to the cache.
    51  func (s *delayedTransmissionEventStorage) ReturnBack(events ...*types.StorageEvent) {
    52  	s.push(true, events...)
    53  }
    54  
    55  // push prepends or appends events to the cache.
    56  func (s *delayedTransmissionEventStorage) push(prepend bool, events ...*types.StorageEvent) {
    57  	if len(events) == 0 {
    58  		return
    59  	}
    60  
    61  	s.mu.Lock()
    62  	defer s.mu.Unlock()
    63  
    64  	err := s.readCache()
    65  	if err != nil {
    66  		s.logger.Errorf("Error '%s', while reading event cache.", err)
    67  	}
    68  
    69  	prependIndex := 0
    70  
    71  	for _, event := range events {
    72  		s.addNonRetriedEvent(event, prepend, &prependIndex)
    73  	}
    74  
    75  	err = s.writeCache()
    76  	if err != nil {
    77  		s.logger.Errorf("Error '%s', while writing event cache.", err)
    78  	}
    79  
    80  	s.logger.Debugf("Pushed %d events to the cache.", len(events))
    81  }
    82  
    83  // Pull returns a chunk of events and removes returned events from the cache.
    84  func (s *delayedTransmissionEventStorage) Pull(count int, before time.Time) []*types.StorageEvent {
    85  	s.mu.Lock()
    86  	defer s.mu.Unlock()
    87  
    88  	err := s.readCache()
    89  	if err != nil {
    90  		s.logger.Errorf("Error '%s', while reading event cache.", err)
    91  	}
    92  
    93  	// Early return if capacity and interval hasn't reached.
    94  	if !s.cache.LastSubmittedAt.Add(s.interval).Before(before) && !(len(s.cache.Events) >= s.capacity) {
    95  		return make([]*types.StorageEvent, 0)
    96  	}
    97  
    98  	defer func() {
    99  		err = s.writeCache()
   100  		if err != nil {
   101  			s.logger.Errorf("Error '%s', while writing event cache.", err)
   102  		}
   103  	}()
   104  
   105  	s.cache.LastSubmittedAt = time.Now()
   106  
   107  	if len(s.cache.Events) >= count {
   108  		events := make([]*types.StorageEvent, count)
   109  		copy(events, s.cache.Events)
   110  		copy(s.cache.Events, s.cache.Events[count:])
   111  
   112  		// Remove copied items from cache.
   113  		s.cache.Events = s.cache.Events[:len(s.cache.Events)-count]
   114  
   115  		s.logger.Debugf("Pulled %d events from the cache.", len(events))
   116  
   117  		return events
   118  	}
   119  
   120  	events := make([]*types.StorageEvent, len(s.cache.Events), count)
   121  	copy(events, s.cache.Events)
   122  
   123  	// Clear the cache
   124  	s.cache.Events = s.cache.Events[:0]
   125  
   126  	s.logger.Debugf("Pulled %d events from the cache.", len(events))
   127  
   128  	return events
   129  }
   130  
   131  // Count returns the number of events ready for transmission.
   132  func (s *delayedTransmissionEventStorage) Count(before time.Time) int {
   133  	s.mu.RLock()
   134  	defer s.mu.RUnlock()
   135  
   136  	err := s.readCache()
   137  	if err != nil {
   138  		s.logger.Errorf("Error '%s', while reading event cache.", err)
   139  	}
   140  
   141  	if s.cache.LastSubmittedAt.Add(s.interval).Before(before) || len(s.cache.Events) >= s.capacity {
   142  		return len(s.cache.Events)
   143  	}
   144  
   145  	return 0
   146  }
   147  
   148  // addNonRetriedEvent adds the events to the internal memory cache.
   149  func (s *delayedTransmissionEventStorage) addNonRetriedEvent(event *types.StorageEvent, prepend bool, prependIndex *int) {
   150  	if prepend {
   151  		s.cache.Events = append(s.cache.Events, nil)
   152  		copy(s.cache.Events[*prependIndex+1:], s.cache.Events[*prependIndex:])
   153  		s.cache.Events[*prependIndex] = event
   154  		*prependIndex++
   155  	} else {
   156  		s.cache.Events = append(s.cache.Events, event)
   157  	}
   158  }
   159  
   160  // readCache reads the events from the cache file.
   161  func (s *delayedTransmissionEventStorage) readCache() error {
   162  	// The cache is read once per run time, early exit if already loaded.
   163  	if s.loaded {
   164  		return nil
   165  	}
   166  
   167  	file, err := os.Open(s.fileName)
   168  	// If the file does not exist, early exit.
   169  	if err != nil {
   170  		s.loaded = true
   171  
   172  		return nil
   173  	}
   174  
   175  	defer file.Close()
   176  
   177  	stat, err := file.Stat()
   178  	if err != nil || stat.Size() == 0 {
   179  		s.loaded = true
   180  		s.logger.Infof("Event cache is empty")
   181  
   182  		return nil
   183  	}
   184  
   185  	decoder := gob.NewDecoder(file)
   186  	err = decoder.Decode(&s.cache)
   187  
   188  	// If the file was properly read, mark the cache as loaded.
   189  	if err == nil {
   190  		s.loaded = true
   191  
   192  		s.logger.Debugf("Read %d events from the cache.", len(s.cache.Events))
   193  	}
   194  
   195  	return err
   196  }
   197  
   198  // writeCache writes the events to the cache file.
   199  func (s *delayedTransmissionEventStorage) writeCache() error {
   200  	file, err := os.Create(s.fileName)
   201  	if err != nil {
   202  		return err
   203  	}
   204  
   205  	defer file.Close()
   206  
   207  	encoder := gob.NewEncoder(file)
   208  	err = encoder.Encode(&s.cache)
   209  
   210  	if err == nil {
   211  		s.logger.Debugf("Wrote %d events to the cache.", len(s.cache.Events))
   212  	}
   213  
   214  	return err
   215  }