go.ligato.io/vpp-agent/v3@v3.5.0/plugins/kvscheduler/txn_record.go (about)

     1  // Copyright (c) 2018 Cisco and/or its affiliates.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at:
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package kvscheduler
    16  
    17  import (
    18  	"fmt"
    19  	"runtime/trace"
    20  	"sort"
    21  	"strconv"
    22  	"strings"
    23  	"time"
    24  
    25  	kvs "go.ligato.io/vpp-agent/v3/plugins/kvscheduler/api"
    26  	"go.ligato.io/vpp-agent/v3/plugins/kvscheduler/internal/graph"
    27  	"go.ligato.io/vpp-agent/v3/plugins/kvscheduler/internal/utils"
    28  	"go.ligato.io/vpp-agent/v3/proto/ligato/kvscheduler"
    29  )
    30  
    31  // GetTransactionHistory returns history of transactions started within the specified
    32  // time window, or the full recorded history if the timestamps are zero values.
    33  func (s *Scheduler) GetTransactionHistory(since, until time.Time) (history kvs.RecordedTxns) {
    34  	s.historyLock.Lock()
    35  	defer s.historyLock.Unlock()
    36  
    37  	if !since.IsZero() && !until.IsZero() && until.Before(since) {
    38  		// invalid time window
    39  		return
    40  	}
    41  
    42  	lastBefore := -1
    43  	firstAfter := len(s.txnHistory)
    44  
    45  	if !since.IsZero() {
    46  		for ; lastBefore+1 < len(s.txnHistory); lastBefore++ {
    47  			if !s.txnHistory[lastBefore+1].Start.Before(since) {
    48  				break
    49  			}
    50  		}
    51  	}
    52  
    53  	if !until.IsZero() {
    54  		for ; firstAfter > 0; firstAfter-- {
    55  			if !s.txnHistory[firstAfter-1].Start.After(until) {
    56  				break
    57  			}
    58  		}
    59  	}
    60  
    61  	return s.txnHistory[lastBefore+1 : firstAfter]
    62  }
    63  
    64  // GetRecordedTransaction returns record of a transaction referenced by the sequence number.
    65  func (s *Scheduler) GetRecordedTransaction(SeqNum uint64) (txn *kvs.RecordedTxn) {
    66  	s.historyLock.Lock()
    67  	defer s.historyLock.Unlock()
    68  
    69  	for _, txn := range s.txnHistory {
    70  		if txn.SeqNum == SeqNum {
    71  			return txn
    72  		}
    73  	}
    74  	return nil
    75  }
    76  
    77  // preRecordTxnOp prepares txn operation record - fills attributes that we can even
    78  // before executing the operation.
    79  func (s *Scheduler) preRecordTxnOp(args *applyValueArgs, node graph.Node) *kvs.RecordedTxnOp {
    80  	var prevValue *utils.RecordedProtoMessage
    81  	if getNodeState(node) != kvscheduler.ValueState_REMOVED {
    82  		prevValue = utils.RecordProtoMessage(node.GetValue())
    83  	}
    84  	_, prevErr := getNodeError(node)
    85  	var prevErrMsg string
    86  	if prevErr != nil {
    87  		prevErrMsg = prevErr.Error()
    88  	}
    89  	return &kvs.RecordedTxnOp{
    90  		Key:        args.kv.key,
    91  		PrevValue:  prevValue,
    92  		NewValue:   utils.RecordProtoMessage(args.kv.value),
    93  		PrevState:  getNodeState(node),
    94  		PrevErr:    prevErr,
    95  		PrevErrMsg: prevErrMsg,
    96  		IsDerived:  args.isDerived,
    97  		IsProperty: args.isDerived && s.registry.GetDescriptorForKey(args.kv.key) == nil,
    98  		IsRevert:   args.kv.isRevert,
    99  		IsRetry:    args.isRetry,
   100  		IsRecreate: args.recreating != nil && args.recreating.Has(args.kv.key),
   101  	}
   102  }
   103  
   104  // preRecordTransaction logs transaction arguments + plan before execution to
   105  // persist some information in case there is a crash during execution.
   106  func (s *Scheduler) preRecordTransaction(txn *transaction, planned kvs.RecordedTxnOps,
   107  	skippedSimulation bool) *kvs.RecordedTxn {
   108  	defer trace.StartRegion(txn.ctx, "preRecordTransaction").End()
   109  	defer trackTransactionMethod("preRecordTransaction")()
   110  
   111  	// allocate new transaction record
   112  	record := &kvs.RecordedTxn{
   113  		PreRecord:      true,
   114  		WithSimulation: !skippedSimulation,
   115  		SeqNum:         txn.seqNum,
   116  		TxnType:        txn.txnType,
   117  		Planned:        planned,
   118  	}
   119  	if txn.txnType == kvs.NBTransaction {
   120  		record.ResyncType = txn.nb.resyncType
   121  		record.Description = txn.nb.description
   122  	}
   123  	if txn.txnType == kvs.RetryFailedOps {
   124  		record.RetryForTxn = txn.retry.txnSeqNum
   125  		record.RetryAttempt = txn.retry.attempt
   126  	}
   127  
   128  	// record values sorted alphabetically by keys
   129  	if txn.txnType != kvs.NBTransaction || txn.nb.resyncType != kvs.DownstreamResync {
   130  		for _, kv := range txn.values {
   131  			record.Values = append(record.Values, kvs.RecordedKVPair{
   132  				Key:    kv.key,
   133  				Value:  utils.RecordProtoMessage(kv.value),
   134  				Origin: kv.origin,
   135  			})
   136  		}
   137  		sort.Slice(record.Values, func(i, j int) bool {
   138  			return record.Values[i].Key < record.Values[j].Key
   139  		})
   140  	}
   141  
   142  	// if enabled, print txn summary
   143  	if s.config.PrintTxnSummary {
   144  		// build header for the log
   145  		txnInfo := ""
   146  		if txn.txnType == kvs.NBTransaction && txn.nb.resyncType != kvs.NotResync {
   147  			txnInfo = txn.nb.resyncType.String()
   148  		} else if txn.txnType == kvs.RetryFailedOps && txn.retry != nil {
   149  			txnInfo = fmt.Sprintf("retrying TX #%d (attempt %d)", txn.retry.txnSeqNum, txn.retry.attempt)
   150  		}
   151  		msg := fmt.Sprintf("#%d - %s", record.SeqNum, txn.txnType.String())
   152  		n := 115 - len(msg)
   153  		var buf strings.Builder
   154  		buf.WriteString("+======================================================================================================================+\n")
   155  		buf.WriteString(fmt.Sprintf("| %s %"+strconv.Itoa(n)+"s |\n", msg, txnInfo))
   156  		buf.WriteString("+======================================================================================================================+\n")
   157  		buf.WriteString(record.StringWithOpts(false, false, 2))
   158  		fmt.Println(buf.String())
   159  	}
   160  
   161  	return record
   162  }
   163  
   164  // recordTransaction records the finalized transaction (log + in-memory).
   165  func (s *Scheduler) recordTransaction(txn *transaction, txnRecord *kvs.RecordedTxn, executed kvs.RecordedTxnOps, start, stop time.Time) {
   166  	defer trace.StartRegion(txn.ctx, "recordTransaction").End()
   167  	defer trackTransactionMethod("recordTransaction")()
   168  
   169  	txnRecord.PreRecord = false
   170  	txnRecord.Start = start
   171  	txnRecord.Stop = stop
   172  	txnRecord.Executed = executed
   173  
   174  	if s.config.PrintTxnSummary {
   175  		txnType := txn.txnType.String()
   176  		msg := fmt.Sprintf("#%d - %s", txnRecord.SeqNum, txnType)
   177  		elapsed := stop.Sub(start)
   178  		msg2 := fmt.Sprintf("took %v", elapsed.Round(time.Microsecond*100))
   179  
   180  		var buf strings.Builder
   181  		buf.WriteString("o----------------------------------------------------------------------------------------------------------------------o\n")
   182  		buf.WriteString(txnRecord.StringWithOpts(true, false, 2))
   183  		buf.WriteString("x----------------------------------------------------------------------------------------------------------------------x\n")
   184  		buf.WriteString(fmt.Sprintf("| %s %"+fmt.Sprint(115-len(msg))+"s |\n", msg, msg2))
   185  		buf.WriteString("x----------------------------------------------------------------------------------------------------------------------x\n")
   186  		fmt.Println(buf.String())
   187  	}
   188  
   189  	// add transaction record into the history
   190  	if s.config.RecordTransactionHistory {
   191  		s.historyLock.Lock()
   192  		s.txnHistory = append(s.txnHistory, txnRecord)
   193  		s.historyLock.Unlock()
   194  	}
   195  }
   196  
   197  // transactionHistoryTrimming runs in a separate go routine and periodically removes
   198  // transaction records too old to keep (by the configuration).
   199  func (s *Scheduler) transactionHistoryTrimming() {
   200  	defer s.wg.Done()
   201  
   202  	for {
   203  		select {
   204  		case <-s.ctx.Done():
   205  			return
   206  		case <-time.After(txnHistoryTrimmingPeriod):
   207  			s.historyLock.Lock()
   208  			now := time.Now()
   209  			ageLimit := time.Duration(s.config.TransactionHistoryAgeLimit) * time.Minute
   210  			initPeriod := time.Duration(s.config.PermanentlyRecordedInitPeriod) * time.Minute
   211  			var i, j int // i = first after init period, j = first after init period to keep
   212  			for i = 0; i < len(s.txnHistory); i++ {
   213  				sinceStart := s.txnHistory[i].Start.Sub(s.startTime)
   214  				if sinceStart > initPeriod {
   215  					break
   216  				}
   217  			}
   218  			for j = i; j < len(s.txnHistory); j++ {
   219  				elapsed := now.Sub(s.txnHistory[j].Stop)
   220  				if elapsed <= ageLimit {
   221  					break
   222  				}
   223  			}
   224  			if j > i {
   225  				copy(s.txnHistory[i:], s.txnHistory[j:])
   226  				newLen := len(s.txnHistory) - (j - i)
   227  				for k := newLen; k < len(s.txnHistory); k++ {
   228  					s.txnHistory[k] = nil
   229  				}
   230  				s.txnHistory = s.txnHistory[:newLen]
   231  			}
   232  			s.historyLock.Unlock()
   233  		}
   234  	}
   235  }