github.com/GuanceCloud/cliutils@v1.1.21/pipeline/offload/offload.go (about)

     1  // Unless explicitly stated otherwise all files in this repository are licensed
     2  // under the MIT License.
     3  // This product includes software developed at Guance Cloud (https://www.guance.com/).
     4  // Copyright 2021-present Guance, Inc.
     5  
     6  package offload
     7  
     8  import (
     9  	"context"
    10  	"fmt"
    11  	"time"
    12  
    13  	"github.com/GuanceCloud/cliutils/logger"
    14  	"github.com/GuanceCloud/cliutils/point"
    15  )
    16  
    17  const (
    18  	maxCustomer   = 16
    19  	chanSize      = maxCustomer
    20  	ptsBuf        = 128
    21  	flushInterval = time.Second * 15
    22  )
    23  
    24  var l = logger.DefaultSLogger("pl-offload")
    25  
    26  func InitLog() {
    27  	l = logger.SLogger("pl-offload")
    28  }
    29  
    30  type OffloadConfig struct {
    31  	Receiver  string   `toml:"receiver"`
    32  	Addresses []string `toml:"addresses"`
    33  }
    34  
    35  type Receiver interface {
    36  	// thread safety
    37  	Send(s uint64, cat point.Category, data []*point.Point) error
    38  }
    39  
    40  type dataChan struct {
    41  	unknownCategory,
    42  	dynamicDWCategory,
    43  	metric,
    44  	metricDeprecated,
    45  	network,
    46  	keyEvent,
    47  	object,
    48  	customObject,
    49  	logging,
    50  	tracing,
    51  	rum,
    52  	security,
    53  	profiling chan []*point.Point
    54  }
    55  
    56  func newDataChan() *dataChan {
    57  	newChan := func() chan []*point.Point {
    58  		return make(chan []*point.Point, chanSize)
    59  	}
    60  
    61  	return &dataChan{
    62  		unknownCategory:   newChan(),
    63  		dynamicDWCategory: newChan(),
    64  		metric:            newChan(),
    65  		metricDeprecated:  newChan(),
    66  		network:           newChan(),
    67  		keyEvent:          newChan(),
    68  		object:            newChan(),
    69  		customObject:      newChan(),
    70  		logging:           newChan(),
    71  		tracing:           newChan(),
    72  		rum:               newChan(),
    73  		security:          newChan(),
    74  		profiling:         newChan(),
    75  	}
    76  }
    77  
    78  type OffloadWorker struct {
    79  	ch       *dataChan
    80  	stopChan chan struct{}
    81  
    82  	sender Receiver
    83  }
    84  
    85  func NewOffloader(cfg *OffloadConfig) (*OffloadWorker, error) {
    86  	if cfg == nil || len(cfg.Receiver) == 0 {
    87  		return nil, fmt.Errorf("no config")
    88  	}
    89  
    90  	wrk := &OffloadWorker{
    91  		ch:       newDataChan(),
    92  		stopChan: make(chan struct{}),
    93  	}
    94  
    95  	switch cfg.Receiver {
    96  	case DKRcv:
    97  		if s, err := NewDKRecver(cfg.Addresses); err != nil {
    98  			return nil, err
    99  		} else {
   100  			wrk.sender = s
   101  		}
   102  	default:
   103  		return nil, fmt.Errorf("unsupported receiver")
   104  	}
   105  
   106  	return wrk, nil
   107  }
   108  
   109  func (offload *OffloadWorker) Customer(ctx context.Context, cat point.Category) error {
   110  	flushTicker := time.NewTicker(flushInterval)
   111  	var ch chan []*point.Point
   112  
   113  	switch cat { //nolint:exhaustive
   114  	case point.Logging:
   115  		ch = offload.ch.logging
   116  	default:
   117  		return fmt.Errorf("unsupported category")
   118  	}
   119  
   120  	ptsCache := make([]*point.Point, 0, ptsBuf)
   121  
   122  	var lbID uint64 = 0 // taking modulus to achieve load balancing
   123  
   124  	for {
   125  		select {
   126  		case pts := <-ch:
   127  			ptsCache, lbID = offload.sendOrCache(lbID, cat, ptsCache, pts)
   128  
   129  		case <-flushTicker.C:
   130  			if len(ptsCache) > 0 {
   131  				if err := offload.sender.Send(lbID, cat, ptsCache); err != nil {
   132  					l.Errorf("offload send failed: %w", err)
   133  				}
   134  				ptsCache = make([]*point.Point, 0, ptsBuf)
   135  				lbID++
   136  			}
   137  		case <-offload.stopChan:
   138  			if err := offload.sender.Send(lbID, cat, ptsCache); err != nil {
   139  				l.Errorf("offload send failed: %w", err)
   140  			}
   141  			return nil
   142  
   143  		case <-ctx.Done():
   144  			if err := offload.sender.Send(lbID, cat, ptsCache); err != nil {
   145  				l.Errorf("offload send failed: %w", err)
   146  			}
   147  			return nil
   148  		}
   149  	}
   150  }
   151  
   152  func (offload *OffloadWorker) sendOrCache(s uint64, cat point.Category, cache []*point.Point, ptsInput []*point.Point) ([]*point.Point, uint64) {
   153  	diff := ptsBuf - len(cache)
   154  	switch {
   155  	case diff > len(ptsInput):
   156  		// append
   157  		cache = append(cache, ptsInput...)
   158  
   159  	case diff == len(ptsInput):
   160  		// append and send
   161  		cache = append(cache, ptsInput...)
   162  		if err := offload.sender.Send(s, cat, cache); err != nil {
   163  			l.Errorf("offload send failed: %w", err)
   164  		}
   165  		// new slice
   166  		cache = make([]*point.Point, 0, ptsBuf)
   167  		s++
   168  
   169  	case diff < len(ptsInput):
   170  		cache = append(cache, ptsInput[:diff]...)
   171  		if err := offload.sender.Send(s, cat, cache); err != nil {
   172  			l.Errorf("offload send failed: %w", err)
   173  		}
   174  		cache = make([]*point.Point, 0, ptsBuf)
   175  
   176  		ptsInput = ptsInput[diff:]
   177  		for i := 0; i < len(ptsInput)/ptsBuf; i++ {
   178  			cache = append(cache, ptsInput[i*ptsBuf:(i+1)*ptsBuf]...)
   179  			if err := offload.sender.Send(s, cat, cache); err != nil {
   180  				l.Errorf("offload send failed: %w", err)
   181  			}
   182  			cache = make([]*point.Point, 0, ptsBuf)
   183  		}
   184  
   185  		if i := len(ptsInput) % ptsBuf; i > 0 {
   186  			cache = append(cache, ptsInput[len(ptsInput)-i:]...)
   187  		}
   188  		s++
   189  	}
   190  	return cache, s
   191  }
   192  
   193  func (offload *OffloadWorker) Send(cat point.Category, pts []*point.Point) error {
   194  	if cat != point.Logging {
   195  		return fmt.Errorf("unsupported category")
   196  	}
   197  
   198  	if offload.ch == nil || offload.ch.logging == nil {
   199  		return fmt.Errorf("logging data chan not ready")
   200  	}
   201  
   202  	switch cat { //nolint:exhaustive
   203  	case point.Logging:
   204  		offload.ch.logging <- pts
   205  	default:
   206  	}
   207  
   208  	return nil
   209  }
   210  
   211  func (offload *OffloadWorker) Stop() {
   212  	if offload.stopChan != nil {
   213  		close(offload.stopChan)
   214  	}
   215  }