-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathWorkload.scala
148 lines (134 loc) · 5.03 KB
/
Workload.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package frankenpaxos
import scala.util.Random
import scala.util.parsing.combinator.RegexParsers
// Clients send commands to a state machine replication protocol. The type of
// commands that the clients send depends on the state machine. For example, a
// key-value store state machine expects very different commands from a
// register state machine.
//
// Even for a given state machine, the kinds of commands that a client can send
// can vary. For example, key-value store clients can run a very contended
// workload (i.e. lots of gets and sets to the same key) or a very uncontended
// workload.
//
// A Workload represents a particular workload for a particular state machine.
// Typically, a client will instantiate a Workload object and repeatedly invoke
// get to get a state machine command to issue to the state machine replication
// protocol.
trait Workload {
def get(): Array[Byte]
}
// The Noop, AppendLog, and Register state machine take arbitrary strings
// (technically, Array[Byte]). StringWorkload produces strings with sizes drawn
// from a normal distribution.
class StringWorkload(sizeMean: Int, sizeStd: Int) extends Workload {
override def toString(): String =
s"StringWorkload(sizeMean=$sizeMean, sizeStd=$sizeStd)"
override def get(): Array[Byte] = {
val size =
Math.max(0, (Random.nextGaussian() * sizeStd + sizeMean).round.toInt)
Array.fill(size)(0)
}
}
// A UniformSingleKeyWorkload consists of `numKeys` keys. We flip a coin to
// decide whether to get or set and then choose a key uniformly at random. If
// we decide to set, we create a random string with size drawn from a normal
// distribution.
class UniformSingleKeyWorkload(
numKeys: Int,
sizeMean: Int,
sizeStd: Int
) extends Workload {
override def toString(): String =
s"UniformSingleKeyWorkload(" +
s"numKeys=$numKeys, sizeMean=$sizeMean, sizeStd=$sizeStd)"
override def get(): Array[Byte] = {
val key = Random.nextInt(numKeys).toString()
val command = if (Random.nextBoolean()) {
statemachine
.KeyValueStoreInput()
.withGetRequest(statemachine.GetRequest(key = Seq(key)))
} else {
val size =
Math.max(0, (Random.nextGaussian() * sizeStd + sizeMean).round.toInt)
val value = Random.nextString(size)
statemachine
.KeyValueStoreInput()
.withSetRequest(
statemachine.SetRequest(
keyValue = Seq(statemachine.SetKeyValuePair(key, value))
)
)
}
command.toByteArray
}
}
// A BernoulliSingleKeyWorkload sets key `x` with likelihood p and gets key `y`
// with likelihood 1 - p. Thus, p is the conflict rate.
class BernoulliSingleKeyWorkload(
conflictRate: Float,
sizeMean: Int,
sizeStd: Int
) extends Workload {
override def toString(): String =
s"BernoulliSingleKeyWorkload(" +
s"conflictRate=$conflictRate, sizeMean=$sizeMean, sizeStd=$sizeStd)"
override def get(): Array[Byte] = {
val command = if (Random.nextFloat() <= conflictRate) {
var size = (Random.nextGaussian() * sizeStd + sizeMean).round.toInt
size = Math.max(0, size)
val value = Random.nextString(size)
statemachine
.KeyValueStoreInput()
.withSetRequest(
statemachine.SetRequest(
keyValue = Seq(statemachine.SetKeyValuePair("x", value))
)
)
} else {
statemachine
.KeyValueStoreInput()
.withGetRequest(statemachine.GetRequest(key = Seq("y")))
}
command.toByteArray
}
}
object Workload {
def fromProto(proto: WorkloadProto): Workload = {
import WorkloadProto.Value
proto.value match {
case Value.StringWorkload(w) => fromProto(w)
case Value.UniformSingleKeyWorkload(w) => fromProto(w)
case Value.BernoulliSingleKeyWorkload(w) => fromProto(w)
case Value.Empty =>
throw new IllegalArgumentException("Empty WorkloadProto encountered.")
}
}
def fromProto(w: StringWorkloadProto): StringWorkload = {
new StringWorkload(sizeMean = w.sizeMean, sizeStd = w.sizeStd)
}
def fromProto(w: UniformSingleKeyWorkloadProto): UniformSingleKeyWorkload = {
new UniformSingleKeyWorkload(numKeys = w.numKeys,
sizeMean = w.sizeMean,
sizeStd = w.sizeStd)
}
def fromProto(
w: BernoulliSingleKeyWorkloadProto
): BernoulliSingleKeyWorkload = {
new BernoulliSingleKeyWorkload(conflictRate = w.conflictRate,
sizeMean = w.sizeMean,
sizeStd = w.sizeStd)
}
def fromFile(filename: String): Workload = {
val source = scala.io.Source.fromFile(filename)
try {
fromProto(WorkloadProto.fromAscii(source.mkString))
} finally {
source.close()
}
}
// Specifying a workload on the command line is a bit tricky since every
// workload is parameterized by a number of variables. Instead of trying to
// do something fancy with flags, we specify workloads using a proto.
implicit val read: scopt.Read[Workload] = scopt.Read.reads(fromFile)
}