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 }