github.com/pingcap/chaos@v0.0.0-20190710112158-c86faf4b3719/pkg/history/history.go (about) 1 package history 2 3 import ( 4 "bufio" 5 "encoding/json" 6 "fmt" 7 "os" 8 "path" 9 "sort" 10 "sync" 11 12 "github.com/pingcap/chaos/pkg/core" 13 ) 14 15 // opRecord is similar to core.Operation, but it stores data in json.RawMessage 16 // instead of interface{} in order to marshal into bytes. 17 type opRecord struct { 18 Action string `json:"action"` 19 Proc int64 `json:"proc"` 20 Data json.RawMessage `json:"data"` 21 } 22 23 // TODO: different operation for initial state and final state. 24 const dumpOperation = "dump" 25 26 // Recorder records operation history. 27 type Recorder struct { 28 sync.Mutex 29 f *os.File 30 } 31 32 // NewRecorder creates a recorder to log the history to the file. 33 func NewRecorder(name string) (*Recorder, error) { 34 os.MkdirAll(path.Dir(name), 0755) 35 36 f, err := os.OpenFile(name, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) 37 if err != nil { 38 return nil, err 39 } 40 41 return &Recorder{f: f}, nil 42 } 43 44 // Close closes the recorder. 45 func (r *Recorder) Close() { 46 r.f.Close() 47 } 48 49 // RecordState records the request. 50 func (r *Recorder) RecordState(state interface{}) error { 51 return r.record(0, dumpOperation, state) 52 } 53 54 // RecordRequest records the request. 55 func (r *Recorder) RecordRequest(proc int64, op interface{}) error { 56 return r.record(proc, core.InvokeOperation, op) 57 } 58 59 // RecordResponse records the response. 60 func (r *Recorder) RecordResponse(proc int64, op interface{}) error { 61 return r.record(proc, core.ReturnOperation, op) 62 } 63 64 func (r *Recorder) record(proc int64, action string, op interface{}) error { 65 // Marshal the op to json in order to store it in a history file. 66 data, err := json.Marshal(op) 67 if err != nil { 68 return err 69 } 70 71 v := opRecord{ 72 Action: action, 73 Proc: proc, 74 Data: json.RawMessage(data), 75 } 76 77 data, err = json.Marshal(v) 78 if err != nil { 79 return err 80 } 81 82 r.Lock() 83 defer r.Unlock() 84 85 if _, err = r.f.Write(data); err != nil { 86 return err 87 } 88 89 if _, err = r.f.WriteString("\n"); err != nil { 90 return err 91 } 92 93 return nil 94 } 95 96 // RecordParser is to parses the operation data. 97 // It must be thread-safe. 98 type RecordParser interface { 99 // OnRequest parses an operation data to model's input. 100 OnRequest(data json.RawMessage) (interface{}, error) 101 // OnResponse parses an operation data to model's output. 102 // Return nil means the operation has an infinite end time. 103 // E.g, we meet timeout for a operation. 104 OnResponse(data json.RawMessage) (interface{}, error) 105 // If we have some infinite operations, we should return a 106 // noop response to complete the operation. 107 OnNoopResponse() interface{} 108 // OnState parses model state json data to model's state 109 OnState(state json.RawMessage) (interface{}, error) 110 } 111 112 // ReadHistory reads operations and a model state from a history file. 113 func ReadHistory(historyFile string, p RecordParser) ([]core.Operation, interface{}, error) { 114 f, err := os.Open(historyFile) 115 if err != nil { 116 return nil, nil, err 117 } 118 defer f.Close() 119 120 var state interface{} 121 ops := make([]core.Operation, 0, 1024) 122 scanner := bufio.NewScanner(f) 123 for scanner.Scan() { 124 var record opRecord 125 if err = json.Unmarshal(scanner.Bytes(), &record); err != nil { 126 return nil, nil, err 127 } 128 129 var data interface{} 130 if record.Action == core.InvokeOperation { 131 if data, err = p.OnRequest(record.Data); err != nil { 132 return nil, nil, err 133 } 134 } else if record.Action == core.ReturnOperation { 135 if data, err = p.OnResponse(record.Data); err != nil { 136 return nil, nil, err 137 } 138 } else { 139 if state, err = p.OnState(record.Data); err != nil { 140 return nil, nil, err 141 } 142 // A dumped state is not an operation. 143 continue 144 } 145 146 op := core.Operation{ 147 Action: record.Action, 148 Proc: record.Proc, 149 Data: data, 150 } 151 ops = append(ops, op) 152 } 153 154 if err = scanner.Err(); err != nil { 155 return nil, nil, err 156 } 157 158 return ops, state, nil 159 } 160 161 // int64Slice attaches the methods of Interface to []int, sorting in increasing order. 162 type int64Slice []int64 163 164 func (p int64Slice) Len() int { return len(p) } 165 func (p int64Slice) Less(i, j int) bool { return p[i] < p[j] } 166 func (p int64Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } 167 168 // CompleteOperations completes the history of operation. 169 func CompleteOperations(ops []core.Operation, p RecordParser) ([]core.Operation, error) { 170 procID := map[int64]struct{}{} 171 compOps := make([]core.Operation, 0, len(ops)) 172 for _, op := range ops { 173 if op.Action == core.InvokeOperation { 174 if _, ok := procID[op.Proc]; ok { 175 return nil, fmt.Errorf("missing return, op: %v", op) 176 } 177 procID[op.Proc] = struct{}{} 178 compOps = append(compOps, op) 179 } else { 180 if _, ok := procID[op.Proc]; !ok { 181 return nil, fmt.Errorf("missing invoke, op: %v", op) 182 } 183 if op.Data == nil { 184 continue 185 } 186 delete(procID, op.Proc) 187 compOps = append(compOps, op) 188 } 189 } 190 191 // To get a determined complete history of operations, we sort procIDs. 192 var keys []int64 193 for k := range procID { 194 keys = append(keys, k) 195 } 196 sort.Sort(int64Slice(keys)) 197 198 for _, proc := range keys { 199 op := core.Operation{ 200 Action: core.ReturnOperation, 201 Proc: proc, 202 Data: p.OnNoopResponse(), 203 } 204 compOps = append(compOps, op) 205 } 206 207 return compOps, nil 208 }