| /* |
| * Copyright 2019 Dgraph Labs, Inc. and Contributors |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package sim |
| |
| import ( |
| "bufio" |
| "errors" |
| "fmt" |
| "io" |
| "math/rand" |
| "strconv" |
| "strings" |
| "time" |
| ) |
| |
| var ( |
| // ErrDone is returned when the underlying file has ran out of lines. |
| ErrDone = errors.New("no more values in the Simulator") |
| // ErrBadLine is returned when the trace file line is unrecognizable to |
| // the Parser. |
| ErrBadLine = errors.New("bad line for trace format") |
| ) |
| |
| // Simulator is the central type of the `sim` package. It is a function |
| // returning a key from some source (composed from the other functions in this |
| // package, either generated or parsed). You can use these Simulators to |
| // approximate access distributions. |
| type Simulator func() (uint64, error) |
| |
| // NewZipfian creates a Simulator returning numbers following a Zipfian [1] |
| // distribution infinitely. Zipfian distributions are useful for simulating real |
| // workloads. |
| // |
| // [1]: https://en.wikipedia.org/wiki/Zipf%27s_law |
| func NewZipfian(s, v float64, n uint64) Simulator { |
| z := rand.NewZipf(rand.New(rand.NewSource(time.Now().UnixNano())), s, v, n) |
| return func() (uint64, error) { |
| return z.Uint64(), nil |
| } |
| } |
| |
| // NewUniform creates a Simulator returning uniformly distributed [1] (random) |
| // numbers [0, max) infinitely. |
| // |
| // [1]: https://en.wikipedia.org/wiki/Uniform_distribution_(continuous) |
| func NewUniform(max uint64) Simulator { |
| m := int64(max) |
| r := rand.New(rand.NewSource(time.Now().UnixNano())) |
| return func() (uint64, error) { |
| return uint64(r.Int63n(m)), nil |
| } |
| } |
| |
| // Parser is used as a parameter to NewReader so we can create Simulators from |
| // varying trace file formats easily. |
| type Parser func(string, error) ([]uint64, error) |
| |
| // NewReader creates a Simulator from two components: the Parser, which is a |
| // filetype specific function for parsing lines, and the file itself, which will |
| // be read from. |
| // |
| // When every line in the file has been read, ErrDone will be returned. For some |
| // trace formats (LIRS) there is one item per line. For others (ARC) there is a |
| // range of items on each line. Thus, the true number of items in each file |
| // is hard to determine, so it's up to the user to handle ErrDone accordingly. |
| func NewReader(parser Parser, file io.Reader) Simulator { |
| b := bufio.NewReader(file) |
| s := make([]uint64, 0) |
| i := -1 |
| var err error |
| return func() (uint64, error) { |
| // only parse a new line when we've run out of items |
| if i++; i == len(s) { |
| // parse sequence from line |
| if s, err = parser(b.ReadString('\n')); err != nil { |
| s = []uint64{0} |
| } |
| i = 0 |
| } |
| return s[i], err |
| } |
| } |
| |
| // ParseLIRS takes a single line of input from a LIRS trace file as described in |
| // multiple papers [1] and returns a slice containing one number. A nice |
| // collection of LIRS trace files can be found in Ben Manes' repo [2]. |
| // |
| // [1]: https://en.wikipedia.org/wiki/LIRS_caching_algorithm |
| // [2]: https://git.io/fj9gU |
| func ParseLIRS(line string, err error) ([]uint64, error) { |
| if line = strings.TrimSpace(line); line != "" { |
| // example: "1\r\n" |
| key, err := strconv.ParseUint(line, 10, 64) |
| return []uint64{key}, err |
| } |
| return nil, ErrDone |
| } |
| |
| // ParseARC takes a single line of input from an ARC trace file as described in |
| // "ARC: a self-tuning, low overhead replacement cache" [1] by Nimrod Megiddo |
| // and Dharmendra S. Modha [1] and returns a sequence of numbers generated from |
| // the line and any error. For use with NewReader. |
| // |
| // [1]: https://scinapse.io/papers/1860107648 |
| func ParseARC(line string, err error) ([]uint64, error) { |
| if line != "" { |
| // example: "0 5 0 0\n" |
| // |
| // - first block: starting number in sequence |
| // - second block: number of items in sequence |
| // - third block: ignore |
| // - fourth block: global line number (not used) |
| cols := strings.Fields(line) |
| if len(cols) != 4 { |
| return nil, ErrBadLine |
| } |
| start, err := strconv.ParseUint(cols[0], 10, 64) |
| if err != nil { |
| return nil, err |
| } |
| count, err := strconv.ParseUint(cols[1], 10, 64) |
| if err != nil { |
| return nil, err |
| } |
| // populate sequence from start to start + count |
| seq := make([]uint64, count) |
| for i := range seq { |
| seq[i] = start + uint64(i) |
| } |
| return seq, nil |
| } |
| return nil, ErrDone |
| } |
| |
| // Collection evaluates the Simulator size times and saves each item to the |
| // returned slice. |
| func Collection(simulator Simulator, size uint64) []uint64 { |
| collection := make([]uint64, size) |
| for i := range collection { |
| collection[i], _ = simulator() |
| } |
| return collection |
| } |
| |
| // StringCollection evaluates the Simulator size times and saves each item to |
| // the returned slice, after converting it to a string. |
| func StringCollection(simulator Simulator, size uint64) []string { |
| collection := make([]string, size) |
| for i := range collection { |
| n, _ := simulator() |
| collection[i] = fmt.Sprintf("%d", n) |
| } |
| return collection |
| } |