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  }