blob: f663f0e2f4cd3151e5b8797ff6af662e2ba99c82 [file] [log] [blame] [edit]
/*
* 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
}