initial commit of afs
This commit is contained in:
parent
8a76a7f84a
commit
bcf0b8df1c
|
@ -0,0 +1,278 @@
|
|||
|
||||
# Overview
|
||||
|
||||
This program, `afs.py`, allows you to experiment with the cache consistency
|
||||
behavior of the Andrew File System (AFS). The program generates random client
|
||||
traces (of file opens, reads, writes, and closes), enabling the user to see if
|
||||
they can predict what values end up in various files.
|
||||
|
||||
Here is an example run:
|
||||
|
||||
```sh
|
||||
prompt> ./afs.py -C 2 -n 1 -s 12
|
||||
|
||||
Server c0 c1
|
||||
file:a contains:0
|
||||
open:a [fd:0]
|
||||
write:0 value? -> 1
|
||||
close:0
|
||||
open:a [fd:0]
|
||||
read:0 -> value?
|
||||
close:0
|
||||
file:a contains:?
|
||||
prompt>
|
||||
```
|
||||
|
||||
The trace is fairly simple to read. On the left is the server, and each column
|
||||
shows actions being taken on each of two clients (use `-C <clients>` to specify
|
||||
a different number). Each client generates one random action (`-n 1`), which is
|
||||
either the open/read/close of a file or the open/write/close of a file. The
|
||||
contents of a file, for simplicity, is always just a single number.
|
||||
|
||||
To generate different traces, use `-s` (for a random seed), as always. Here we
|
||||
set it to 12 to get this specific trace.
|
||||
|
||||
In the trace, the server shows the initial contents of all the files in the
|
||||
system:
|
||||
|
||||
```sh
|
||||
file:a contains:0
|
||||
```
|
||||
|
||||
As you can see in this trace, there is just one file (a) and it contains the
|
||||
value 0.
|
||||
|
||||
Time increases downwards, and what is next is client 0 (c0) opening the file
|
||||
'a' (which returns a file descriptor, 0 in this case), writing to that
|
||||
descriptor, and then closing the file.
|
||||
|
||||
Immediately you see the first question posed to you:
|
||||
|
||||
```sh
|
||||
write:0 value? -> 1
|
||||
```
|
||||
|
||||
When writing to descriptor 0, you are overwriting an existing value with the
|
||||
new value of 1. What was that old value? (pretty easy in this case: 0).
|
||||
|
||||
Then client 1 begins doing some work (c1). It opens the file, reads it, and
|
||||
closes it. Again, we have a question to answer:
|
||||
|
||||
```sh
|
||||
read:0 -> value?
|
||||
```
|
||||
|
||||
When reading from this file, what value should client 1 see? Again, given AFS
|
||||
consistency, the answer is straightforward: 1 (the value placed in the file
|
||||
when c0 closed the file and updated the server).
|
||||
|
||||
The final question in the trace is the final value of the file on the server:
|
||||
|
||||
```sh
|
||||
file:a contains:?
|
||||
```
|
||||
|
||||
Again, the answer here is easy: 1 (as generated by c0).
|
||||
|
||||
To see if you have answered these questions correctly, run with the `-c` flag
|
||||
(or `--compute`), as follows:
|
||||
|
||||
```sh
|
||||
prompt> ./afs.py -C 2 -n 1 -s 12 -c
|
||||
|
||||
Server c0 c1
|
||||
file:a contains:0
|
||||
open:a [fd:0]
|
||||
write:0 0 -> 1
|
||||
close:0
|
||||
open:a [fd:0]
|
||||
read:0 -> 1
|
||||
close:0
|
||||
file:a contains:1
|
||||
prompt>
|
||||
```
|
||||
|
||||
From this trace, you can see that all the question marks have been filled in
|
||||
with answers.
|
||||
|
||||
More detail is available on what has happened too, with the '-d' ('--detail')
|
||||
flag. Here is an example that shows when each client issued a get or put of a
|
||||
file to the server:
|
||||
|
||||
```sh
|
||||
prompt> ./afs.py -C 2 -n 1 -s 12 -c -d 1
|
||||
|
||||
Server c0 c1
|
||||
file:a contains:0
|
||||
open:a [fd:0]
|
||||
getfile:a c:c0 [0]
|
||||
|
||||
write:0 0 -> 1
|
||||
|
||||
close:0
|
||||
putfile:a c:c0 [1]
|
||||
|
||||
open:a [fd:0]
|
||||
getfile:a c:c1 [1]
|
||||
|
||||
read:0 -> 1
|
||||
|
||||
close:0
|
||||
|
||||
file:a contains:1
|
||||
prompt>
|
||||
```
|
||||
|
||||
You can show more with higher levels of detail, including cache invalidations,
|
||||
the exact client cache state after each step, and extra diagnostic
|
||||
information. We'll show these in one more example below.
|
||||
|
||||
Random client actions are useful to generate new problems and try to solve
|
||||
them; however, in some cases it is useful to control exactly what each client
|
||||
does in order to see specific AFS behaviors. To do this, you can use the `-A`
|
||||
and `-S` flags (either together or in tandem).
|
||||
|
||||
The `-S` flag lets you control the exact schedule of client actions. Assume our
|
||||
example above. Let's say we wish to run client 1 in entirety first; to achieve
|
||||
this end, we simply run the following:
|
||||
|
||||
```sh
|
||||
prompt> ./afs.py -C 2 -n 1 -s 12 -S 111000
|
||||
|
||||
Server c0 c1
|
||||
file:a contains:0
|
||||
open:a [fd:0]
|
||||
read:0 -> value?
|
||||
close:0
|
||||
open:a [fd:0]
|
||||
write:0 value? -> 1
|
||||
close:0
|
||||
file:a contains:?
|
||||
prompt>
|
||||
```
|
||||
|
||||
The -S flag here is passed "111000" which means "run client 1, then client 1,
|
||||
then 1 again, then 0, 0, 0, and then repeat (if need be)". The result in this
|
||||
case is client 1 reading file a before client 1 writes it.
|
||||
|
||||
The `-A` flag gives exact control over which actions the clients take. Here is
|
||||
an example:
|
||||
|
||||
```sh
|
||||
prompt> ./afs.py -s 12 -S 011100 -A oa1:r1:c1,oa1:w1:c1
|
||||
|
||||
Server c0 c1
|
||||
file:a contains:0
|
||||
open:a [fd:1]
|
||||
open:a [fd:1]
|
||||
write:1 value? -> 1
|
||||
close:1
|
||||
read:1 -> value?
|
||||
close:1
|
||||
file:a contains:?
|
||||
prompt>
|
||||
```
|
||||
|
||||
In this example, we have specified the following via `-A oa1:r1:c1,oa1:w1:c1`.
|
||||
The list splits each clients actions by a comma; thus, client 0 should do
|
||||
whatever `oa1:r1:c1` indicates, whereas client 1 should do whatever the string
|
||||
`oa1:w1:c1` indicates. Parsing each command string is straightforward: `oa1`
|
||||
means open file 'a' and assign it file descriptor 1; `r1` or `w1` means read
|
||||
or write file descriptor 1; `c1` means close file descriptor 1.
|
||||
|
||||
So what value will the read on client 0 return?
|
||||
|
||||
We can also see the cache state, callbacks, and invalidations with a few extra
|
||||
flags (`-d 7`):
|
||||
|
||||
```sh
|
||||
prompt> ./afs.py -s 12 -S 011100 -A oa1:r1:c1,oa1:w1:c1 -c -d 7
|
||||
|
||||
Server c0 c1
|
||||
file:a contains:0
|
||||
open:a [fd:1]
|
||||
getfile:a c:c0 [0]
|
||||
[a: 0 (v=1,d=0,r=1)]
|
||||
|
||||
open:a [fd:1]
|
||||
getfile:a c:c1 [0]
|
||||
[a: 0 (v=1,d=0,r=1)]
|
||||
|
||||
write:1 0 -> 1
|
||||
[a: 1 (v=1,d=1,r=1)]
|
||||
|
||||
close:1
|
||||
putfile:a c:c1 [1]
|
||||
callback: c:c0 file:a
|
||||
invalidate a
|
||||
[a: 0 (v=0,d=0,r=1)]
|
||||
[a: 1 (v=1,d=0,r=0)]
|
||||
|
||||
read:1 -> 0
|
||||
[a: 0 (v=0,d=0,r=1)]
|
||||
|
||||
close:1
|
||||
|
||||
file:a contains:1
|
||||
prompt>
|
||||
```
|
||||
|
||||
From this trace, we can see what happens when client 1 closes the (modified)
|
||||
file. At that point, c1 puts the file to the server. The server knows that c0
|
||||
has the file cached, and thus sends an invalidation to c0. However, c0 already
|
||||
has the file open; as a result, the cache keeps the old contents until the
|
||||
file is closed.
|
||||
|
||||
You can see this in tracking the cache contents throughout the trace
|
||||
(available with the correct `-d` flag, in particular any value which
|
||||
sets the 3rd least significant bit to 1, such as `-d 4, -d 5, -d 6, -d
|
||||
7`, etc.). When client 0 opens the file, you see the following cache
|
||||
state after the open is finished:
|
||||
|
||||
```sh
|
||||
[a: 0 (v=1,d=0,r=1)]
|
||||
```
|
||||
|
||||
This means file 'a' is in the cache with value '0', and has three bits of
|
||||
state associated with it: v (valid), d (dirty), and r (reference count). The
|
||||
valid bit tracks whether the contents are valid; it is now, because the cache
|
||||
has not been invalidated by a callback (yet). The dirty bit changes when the
|
||||
file has been written to and must be flushed back to the server when
|
||||
closed. Finally, the reference count tracks how many times the file has been
|
||||
opened (but not yet closed); this is used to ensure the client gets the old
|
||||
value of the file until it's been closed by all readers and then re-opened.
|
||||
|
||||
The full list of options is available here:
|
||||
|
||||
```sh
|
||||
Options:
|
||||
-h, --help show this help message and exit
|
||||
-s SEED, --seed=SEED the random seed
|
||||
-C NUMCLIENTS, --clients=NUMCLIENTS
|
||||
number of clients
|
||||
-n NUMSTEPS, --numsteps=NUMSTEPS
|
||||
ops each client will do
|
||||
-f NUMFILES, --numfiles=NUMFILES
|
||||
number of files in server
|
||||
-r READRATIO, --readratio=READRATIO
|
||||
ratio of reads/writes
|
||||
-A ACTIONS, --actions=ACTIONS
|
||||
client actions exactly specified, e.g.,
|
||||
oa1:r1:c1,oa1:w1:c1 specifies two clients; each opens
|
||||
the file a, client 0 reads it whereas client 1 writes
|
||||
it, and then each closes it
|
||||
-S SCHEDULE, --schedule=SCHEDULE
|
||||
exact schedule to run; 01 alternates round robin
|
||||
between clients 0 and 1. Left unspecified leads to
|
||||
random scheduling
|
||||
-p, --printstats print extra stats
|
||||
-c, --compute compute answers for me
|
||||
-d DETAIL, --detail=DETAIL
|
||||
detail level when giving answers (1:server
|
||||
actions,2:invalidations,4:client cache,8:extra
|
||||
labels); OR together for multiple
|
||||
```
|
||||
|
||||
Read the AFS chapter, and answer the questions at the back, or just explore
|
||||
this simulator more on your own to increase your understanding of AFS.
|
||||
|
|
@ -0,0 +1,616 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
from __future__ import print_function
|
||||
import random
|
||||
from optparse import OptionParser
|
||||
import string
|
||||
|
||||
# to make Python2 and Python3 act the same -- how dumb
|
||||
def random_seed(seed):
|
||||
try:
|
||||
random.seed(seed, version=1)
|
||||
except:
|
||||
random.seed(seed)
|
||||
return
|
||||
|
||||
def tprint(str):
|
||||
print(str)
|
||||
|
||||
def dprint(str):
|
||||
return
|
||||
|
||||
def dospace(howmuch):
|
||||
for i in range(howmuch + 1):
|
||||
print('%28s' % ' ', end='')
|
||||
|
||||
# given list, pick random element and return it
|
||||
def pickrand(tlist):
|
||||
n = int(random.random() * len(tlist))
|
||||
p = tlist[n]
|
||||
return p
|
||||
|
||||
# given number, conclude if nth bit is set
|
||||
def isset(num, index):
|
||||
mask = 1 << index
|
||||
return (num & mask) > 0
|
||||
|
||||
# useful instead of assert
|
||||
def zassert(cond, str):
|
||||
if cond == False:
|
||||
print('ABORT::', str)
|
||||
exit(1)
|
||||
|
||||
#
|
||||
# Which files are used in the simulation
|
||||
#
|
||||
# Not representing a realistic piece of anything
|
||||
# but rather just for convenience when generating
|
||||
# random traces ...
|
||||
#
|
||||
# Files are named 'a', 'b', etc. for ease of use
|
||||
# Could probably add a numeric aspect to allow
|
||||
# for more than 26 files but who cares
|
||||
#
|
||||
|
||||
class files:
|
||||
def __init__(self, numfiles):
|
||||
self.numfiles = numfiles
|
||||
self.value = 0
|
||||
self.filelist = list(string.ascii_lowercase)[0:numfiles]
|
||||
|
||||
def getfiles(self):
|
||||
return self.filelist
|
||||
|
||||
def getvalue(self):
|
||||
rc = self.value
|
||||
self.value += 1
|
||||
return rc
|
||||
|
||||
#
|
||||
# Models the actions of the AFS server
|
||||
#
|
||||
# The only real interactions are get/put
|
||||
# get() causes the server to track which files cache what;
|
||||
# put() may cause callbacks to invalidate client caches
|
||||
#
|
||||
class server:
|
||||
def __init__(self, files, solve, detail):
|
||||
self.files = files
|
||||
self.solve = solve
|
||||
self.detail = detail
|
||||
|
||||
flist = self.files.getfiles()
|
||||
self.contents = {}
|
||||
for f in flist:
|
||||
v = self.files.getvalue()
|
||||
self.contents[f] = v
|
||||
self.getcnt, self.putcnt = 0, 0
|
||||
|
||||
def stats(self):
|
||||
print('Server -- Gets:%d Puts:%d' % (self.getcnt, self.putcnt))
|
||||
|
||||
def filestats(self, printcontents):
|
||||
for fname in self.contents:
|
||||
if printcontents:
|
||||
print('file:%s contains:%d' % (fname, self.contents[fname]))
|
||||
else:
|
||||
print('file:%s contains:?' % fname)
|
||||
|
||||
|
||||
def setclients(self, clients):
|
||||
# need list of clients
|
||||
self.clients = clients
|
||||
|
||||
# per client callback list
|
||||
self.cache = {}
|
||||
for c in self.clients:
|
||||
self.cache[c.getname()] = []
|
||||
|
||||
def get(self, client, fname):
|
||||
zassert(fname in self.contents, 'server:get() -- file:%s not found on server' % fname)
|
||||
self.getcnt += 1
|
||||
if self.solve and isset(self.detail, 0):
|
||||
print('getfile:%s c:%s [%d]' % (fname, client, self.contents[fname]))
|
||||
if fname not in self.cache[client]:
|
||||
self.cache[client].append(fname)
|
||||
# dprint(' -> List for client %s' % client, ' is ', self.cache[client])
|
||||
return self.contents[fname]
|
||||
|
||||
def put(self, client, fname, value):
|
||||
zassert(fname in self.contents, 'server:put() -- file:%s not found on server' % fname)
|
||||
self.putcnt += 1
|
||||
self.contents[fname] = value
|
||||
if self.solve and isset(self.detail, 0):
|
||||
print('putfile:%s c:%s [%s]' % (fname, client, self.contents[fname]))
|
||||
# scan others for callback
|
||||
for c in self.clients:
|
||||
cname = c.getname()
|
||||
if fname in self.cache[cname] and cname != client:
|
||||
if self.solve and isset(self.detail, 1):
|
||||
print('callback: c:%s file:%s' % (cname, fname))
|
||||
c.invalidate(fname)
|
||||
# XXX - this is not right ...
|
||||
# self.cache[cname].remove(fname)
|
||||
|
||||
#
|
||||
# Per-client file descriptors
|
||||
#
|
||||
# Would be useful if the simulation allowed more
|
||||
# than one active file open() at a time; it kind
|
||||
# of does but this isn't really utilized
|
||||
#
|
||||
class filedesc:
|
||||
def __init__(self, max=1024):
|
||||
self.max = max
|
||||
self.fd = {}
|
||||
for i in range(self.max):
|
||||
self.fd[i] = ''
|
||||
|
||||
def alloc(self, fname, sfd=-1):
|
||||
if sfd != -1:
|
||||
zassert(self.fd[sfd] == '', 'filedesc:alloc() -- fd:%d already in use, cannot allocate' % sfd)
|
||||
self.fd[sfd] = fname
|
||||
return sfd
|
||||
else:
|
||||
for i in range(self.max):
|
||||
if self.fd[i] == '':
|
||||
self.fd[i] = fname
|
||||
return i
|
||||
return -1
|
||||
|
||||
def lookup(self, sfd):
|
||||
zassert(i >= 0 and i < self.max, 'filedesc:lookup() -- file descriptor out of valid range (%d not between 0 and %d)' % (sfd, self.max))
|
||||
zassert(self.fd[sfd] != '', 'filedesc:lookup() -- fd:%d not in use, cannot lookup' % sfd)
|
||||
return self.fd[sfd]
|
||||
|
||||
def free(self, i):
|
||||
zassert(i >= 0 and i < self.max, 'filedesc:free() -- file descriptor out of valid range (%d not between 0 and %d)' % (sfd, self.max))
|
||||
zassert(self.fd[sfd] != '', 'filedesc:free() -- fd:%d not in use, cannot free' % sfd)
|
||||
self.fd[i] = ''
|
||||
|
||||
#
|
||||
# The client cache
|
||||
#
|
||||
# Just models what files are cached.
|
||||
# When a file is opened, its contents are fetched
|
||||
# from the server and put in the cache. At that point,
|
||||
# the cache contents are VALID, DIRTY/NOT (depending
|
||||
# on whether this is for reading or writing), and the
|
||||
# REFERENCE COUNT is set to 1. If multiple open's take
|
||||
# place on this file, REFERENCE COUNT will be updated
|
||||
# accordingly. VALID gets set to 0 if the cache is
|
||||
# invalidated by a callback; however, the contents
|
||||
# still might be used by a given client if the file
|
||||
# is already open. Note that a callback does NOT
|
||||
# prevent a client from overwriting an already opened file.
|
||||
#
|
||||
class cache:
|
||||
def __init__(self, name, num, solve, detail):
|
||||
self.name = name
|
||||
self.num = num
|
||||
self.solve = solve
|
||||
self.detail = detail
|
||||
|
||||
self.cache = {}
|
||||
|
||||
self.hitcnt = 0
|
||||
self.misscnt = 0
|
||||
self.invalidcnt = 0
|
||||
|
||||
def stats(self):
|
||||
print(' Cache -- Hits:%d Misses:%d Invalidates:%d' % (self.hitcnt, self.misscnt, self.invalidcnt))
|
||||
|
||||
def put(self, fname, data, dirty, refcnt):
|
||||
self.cache[fname] = dict(data=data, dirty=dirty, refcnt=refcnt, valid=True)
|
||||
|
||||
def update(self, fname, data):
|
||||
self.cache[fname] = dict(data=data, dirty=True, refcnt=self.cache[fname]['refcnt'], valid=self.cache[fname]['valid'])
|
||||
|
||||
def invalidate(self, fname):
|
||||
dospace(self.num)
|
||||
print('invalidate file:%s' % fname, 'cache:', self.cache)
|
||||
# zassert(fname in self.cache, 'cache:invalidate() -- cannot invalidate file not in cache (%s)' % fname)
|
||||
if fname not in self.cache:
|
||||
return
|
||||
self.invalidcnt += 1
|
||||
self.cache[fname] = dict(data=self.cache[fname]['data'], dirty=self.cache[fname]['dirty'],
|
||||
refcnt=self.cache[fname]['refcnt'], valid=False)
|
||||
if self.solve and isset(self.detail, 1):
|
||||
dospace(self.num)
|
||||
if isset(self.detail,3):
|
||||
print('%2s invalidate %s' % (self.name, fname))
|
||||
else:
|
||||
print('invalidate %s' % (fname))
|
||||
self.printstate(self.num)
|
||||
|
||||
def checkvalid(self, fname):
|
||||
zassert(fname in self.cache, 'cache:checkvalid() -- cannot checkvalid on file not in cache (%s)' % fname)
|
||||
if self.cache[fname]['valid'] == False and self.cache[fname]['refcnt'] == 0:
|
||||
del self.cache[fname]
|
||||
|
||||
def printstate(self, fname):
|
||||
for fname in self.cache:
|
||||
data = self.cache[fname]['data']
|
||||
dirty = self.cache[fname]['dirty']
|
||||
refcnt = self.cache[fname]['refcnt']
|
||||
valid = self.cache[fname]['valid']
|
||||
if valid == True:
|
||||
validPrint = 1
|
||||
else:
|
||||
validPrint = 0
|
||||
if dirty == True:
|
||||
dirtyPrint = 1
|
||||
else:
|
||||
dirtyPrint = 0
|
||||
|
||||
if self.solve and isset(self.detail, 2):
|
||||
dospace(self.num)
|
||||
if isset(self.detail, 3):
|
||||
print('%s [%s:%2d (v=%d,d=%d,r=%d)]' % (self.name, fname, data, validPrint, dirtyPrint, refcnt))
|
||||
else:
|
||||
print('[%s:%2d (v=%d,d=%d,r=%d)]' % (fname, data, validPrint, dirtyPrint, refcnt))
|
||||
|
||||
def checkget(self, fname):
|
||||
if fname in self.cache:
|
||||
self.cache[fname] = dict(data=self.cache[fname]['data'], dirty=self.cache[fname]['dirty'],
|
||||
refcnt=self.cache[fname]['refcnt'], valid=self.cache[fname]['valid'])
|
||||
self.hitcnt += 1
|
||||
return (True, self.cache[fname])
|
||||
self.misscnt += 1
|
||||
return (False, -1)
|
||||
|
||||
def get(self, fname):
|
||||
assert(fname in self.cache)
|
||||
return (True, self.cache[fname])
|
||||
|
||||
def incref(self, fname):
|
||||
assert(fname in self.cache)
|
||||
self.cache[fname] = dict(data=self.cache[fname]['data'], dirty=self.cache[fname]['dirty'],
|
||||
refcnt=self.cache[fname]['refcnt'] + 1, valid=self.cache[fname]['valid'])
|
||||
|
||||
def decref(self, fname):
|
||||
assert(fname in self.cache)
|
||||
self.cache[fname] = dict(data=self.cache[fname]['data'], dirty=self.cache[fname]['dirty'],
|
||||
refcnt=self.cache[fname]['refcnt'] - 1, valid=self.cache[fname]['valid'])
|
||||
|
||||
def setdirty(self, fname, dirty):
|
||||
assert(fname in self.cache)
|
||||
self.cache[fname] = dict(data=self.cache[fname]['data'], dirty=dirty,
|
||||
refcnt=self.cache[fname]['refcnt'], valid=self.cache[fname]['valid'])
|
||||
|
||||
def setclean(self, fname):
|
||||
assert(fname in self.cache)
|
||||
self.cache[fname] = dict(data=self.cache[fname]['data'], dirty=False,
|
||||
refcnt=self.cache[fname]['refcnt'], valid=self.cache[fname]['valid'])
|
||||
|
||||
def isdirty(self, fname):
|
||||
assert(fname in self.cache)
|
||||
return (self.cache[fname]['dirty'] == True)
|
||||
|
||||
def setvalid(self, fname):
|
||||
assert(fname in self.cache)
|
||||
self.cache[fname] = dict(data=self.cache[fname]['data'], dirty=self.cache[fname]['dirty'],
|
||||
refcnt=self.cache[fname]['refcnt'], valid=True)
|
||||
|
||||
|
||||
# actions
|
||||
MICRO_OPEN = 1
|
||||
MICRO_READ = 2
|
||||
MICRO_WRITE = 3
|
||||
MICRO_CLOSE = 4
|
||||
|
||||
def op2name(op):
|
||||
if op == MICRO_OPEN:
|
||||
return 'MICRO_OPEN'
|
||||
elif op == MICRO_READ:
|
||||
return 'MICRO_READ'
|
||||
elif op == MICRO_WRITE:
|
||||
return 'MICRO_WRITE'
|
||||
elif op == MICRO_CLOSE:
|
||||
return 'MICRO_CLOSE'
|
||||
else:
|
||||
abort('error: bad op -> ' + op)
|
||||
|
||||
#
|
||||
# Client class
|
||||
#
|
||||
# Models the behavior of each client in the system.
|
||||
#
|
||||
#
|
||||
#
|
||||
class client:
|
||||
def __init__(self, name, cid, server, files, bias, numsteps, actions, solve, detail):
|
||||
self.name = name # readable name of client
|
||||
self.cid = cid # client ID
|
||||
self.server = server # server object
|
||||
self.files = files # files object
|
||||
self.bias = bias # bias
|
||||
self.actions = actions # schedule exactly?
|
||||
self.solve = solve # show answers?
|
||||
self.detail = detail # how much of an answer to show
|
||||
|
||||
# cache
|
||||
self.cache = cache(self.name, self.cid, self.solve, self.detail)
|
||||
|
||||
# file desc
|
||||
self.fd = filedesc()
|
||||
|
||||
# stats
|
||||
self.readcnt = 0
|
||||
self.writecnt = 0
|
||||
|
||||
# init actions
|
||||
self.done = False # track state
|
||||
self.acnt = 0 # this is used when running
|
||||
self.acts = [] # this just tracks the opcodes
|
||||
|
||||
if self.actions == '':
|
||||
# in case with no specific actions, generate one...
|
||||
for i in range(numsteps):
|
||||
fname = pickrand(self.files.getfiles())
|
||||
r = random.random()
|
||||
fd = self.fd.alloc(fname)
|
||||
zassert(fd >= 0, 'client:init() -- ran out of file descriptors, sorry!')
|
||||
if r < self.bias[0]:
|
||||
# FILE_READ
|
||||
self.acts.append((MICRO_OPEN, fname, fd))
|
||||
self.acts.append((MICRO_READ, fd))
|
||||
self.acts.append((MICRO_CLOSE, fd))
|
||||
else:
|
||||
# FILE_WRITE
|
||||
self.acts.append((MICRO_OPEN, fname, fd))
|
||||
self.acts.append((MICRO_WRITE, fd))
|
||||
self.acts.append((MICRO_CLOSE, fd))
|
||||
else:
|
||||
# in this case, unpack actions and make it happen
|
||||
# should look like this: "oa1:r1:c1" (open 'a' for reading with file desc 1, read from fd:1, close fd:1)
|
||||
# yes the file descriptor and file name are redundant for read/write and close
|
||||
for a in self.actions.split(':'):
|
||||
act = a[0]
|
||||
if act == 'o':
|
||||
zassert(len(a) == 3, 'client:init() -- malformed open action (%s) should be oa1 or something like that' % a)
|
||||
fname, fd = a[1], int(a[2])
|
||||
self.fd.alloc(fname, fd)
|
||||
assert(fd >= 0)
|
||||
self.acts.append((MICRO_OPEN, fname, fd))
|
||||
elif act == 'r':
|
||||
zassert(len(a) == 2, 'client:init() -- malformed read action (%s) should be r1 or something like that' % a)
|
||||
fd = int(a[1])
|
||||
self.acts.append((MICRO_READ, fd))
|
||||
elif act == 'w':
|
||||
zassert(len(a) == 2, 'client:init() -- malformed write action (%s) should be w1 or something like that' % a)
|
||||
fd = int(a[1])
|
||||
self.acts.append((MICRO_WRITE, fd))
|
||||
elif act == 'c':
|
||||
zassert(len(a) == 2, 'client:init() -- malformed close action (%s) should be c1 or something like that' % a)
|
||||
fd = int(a[1])
|
||||
self.acts.append((MICRO_CLOSE, fd))
|
||||
else:
|
||||
print('Unrecognized command: %s (from %s)' % (act, a))
|
||||
exit(1)
|
||||
print(self.acts)
|
||||
return
|
||||
|
||||
def getname(self):
|
||||
return self.name
|
||||
|
||||
def stats(self):
|
||||
print('%s -- Reads:%d Writes:%d' % (self.name, self.readcnt, self.writecnt))
|
||||
self.cache.stats()
|
||||
|
||||
def getfile(self, fname):
|
||||
(in_cache, item) = self.cache.checkget(fname)
|
||||
if in_cache == True and item['valid'] == 1:
|
||||
dprint(' -> CLIENT %s:: HAS LOCAL COPY of %s' % (self.name, fname))
|
||||
# self.cache.setdirty(fname, dirty)
|
||||
else:
|
||||
data = self.server.get(self.name, fname)
|
||||
self.cache.put(fname, data, False, 0)
|
||||
self.cache.incref(fname)
|
||||
return
|
||||
|
||||
def putfile(self, fname, value):
|
||||
self.server.put(self.name, fname, value)
|
||||
self.cache.setclean(fname)
|
||||
self.cache.setvalid(fname)
|
||||
return
|
||||
|
||||
def invalidate(self, fname):
|
||||
self.cache.invalidate(fname)
|
||||
return
|
||||
|
||||
def step(self, space):
|
||||
if self.done == True:
|
||||
return -1
|
||||
if self.acnt == len(self.acts):
|
||||
self.done = True
|
||||
return 0
|
||||
|
||||
# now figure out what to do and do it
|
||||
# action, fname, fd = self.acts[self.acnt]
|
||||
action = self.acts[self.acnt][0]
|
||||
|
||||
# print ''
|
||||
# print '*************************'
|
||||
# print '%s ACTION -> %s' % (self.name, op2name(action))
|
||||
# print '*************************'
|
||||
|
||||
# first, do spacing for command (below)
|
||||
dospace(space)
|
||||
|
||||
if isset(self.detail, 3) == True:
|
||||
print(self.name, end=' ')
|
||||
|
||||
# now handle the action
|
||||
if action == MICRO_OPEN:
|
||||
fname, fd = self.acts[self.acnt][1], self.acts[self.acnt][2]
|
||||
tprint('open:%s [fd:%d]' % (fname, fd))
|
||||
# self.getfile(fname, dirty=False)
|
||||
self.getfile(fname)
|
||||
elif action == MICRO_READ:
|
||||
fd = self.acts[self.acnt][1]
|
||||
fname = self.fd.lookup(fd)
|
||||
self.readcnt += 1
|
||||
in_cache, contents = self.cache.get(fname)
|
||||
assert(in_cache == True)
|
||||
if self.solve:
|
||||
tprint('read:%d -> %d' % (fd, contents['data']))
|
||||
else:
|
||||
tprint('read:%d -> value?' % (fd))
|
||||
elif action == MICRO_WRITE:
|
||||
fd = self.acts[self.acnt][1]
|
||||
fname = self.fd.lookup(fd)
|
||||
self.writecnt += 1
|
||||
in_cache, contents = self.cache.get(fname)
|
||||
assert(in_cache == True)
|
||||
v = self.files.getvalue()
|
||||
self.cache.update(fname, v)
|
||||
if self.solve:
|
||||
tprint('write:%d %d -> %d' % (fd, contents['data'], v))
|
||||
else:
|
||||
tprint('write:%d value? -> %d' % (fd, v))
|
||||
elif action == MICRO_CLOSE:
|
||||
fd = self.acts[self.acnt][1]
|
||||
fname = self.fd.lookup(fd)
|
||||
in_cache, contents = self.cache.get(fname)
|
||||
assert(in_cache == True)
|
||||
tprint('close:%d' % (fd))
|
||||
if self.cache.isdirty(fname):
|
||||
self.putfile(fname, contents['data'])
|
||||
self.cache.decref(fname)
|
||||
self.cache.checkvalid(fname)
|
||||
|
||||
# useful to see
|
||||
self.cache.printstate(self.name)
|
||||
|
||||
if self.solve and self.detail > 0:
|
||||
print('')
|
||||
|
||||
# return that there is more left to do
|
||||
self.acnt += 1
|
||||
return 1
|
||||
|
||||
|
||||
#
|
||||
# main program
|
||||
#
|
||||
parser = OptionParser()
|
||||
parser.add_option('-s', '--seed', default=0, help='the random seed', action='store', type='int', dest='seed')
|
||||
parser.add_option('-C', '--clients', default=2, help='number of clients', action='store', type='int', dest='numclients')
|
||||
parser.add_option('-n', '--numsteps', default=2, help='ops each client will do', action='store', type='int', dest='numsteps')
|
||||
parser.add_option('-f', '--numfiles', default=1, help='number of files in server', action='store', type='int', dest='numfiles')
|
||||
parser.add_option('-r', '--readratio', default=0.5, help='ratio of reads/writes', action='store', type='float', dest='readratio')
|
||||
parser.add_option('-A', '--actions', default='', help='client actions exactly specified, e.g., oa1:r1:c1,oa1:w1:c1 specifies two clients; each opens the file a, client 0 reads it whereas client 1 writes it, and then each closes it', action='store', type='string', dest='actions')
|
||||
parser.add_option('-S', '--schedule', default='', help='exact schedule to run; 01 alternates round robin between clients 0 and 1. Left unspecified leads to random scheduling', action='store', type='string', dest='schedule')
|
||||
parser.add_option('-p', '--printstats', default=False, help='print extra stats', action='store_true', dest='printstats')
|
||||
parser.add_option('-c', '--compute', default=False, help='compute answers for me', action='store_true', dest='solve')
|
||||
parser.add_option('-d', '--detail', default=0, help='detail level when giving answers (1:server actions,2:invalidations,4:client cache,8:extra labels); OR together for multiple', action='store', type='int', dest='detail')
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
print('ARG seed', options.seed)
|
||||
print('ARG numclients', options.numclients)
|
||||
print('ARG numsteps', options.numsteps)
|
||||
print('ARG numfiles', options.numfiles)
|
||||
print('ARG readratio', options.readratio)
|
||||
print('ARG actions', options.actions)
|
||||
print('ARG schedule', options.schedule)
|
||||
print('ARG detail', options.detail)
|
||||
print('')
|
||||
|
||||
seed = int(options.seed)
|
||||
numclients = int(options.numclients)
|
||||
numsteps = int(options.numsteps)
|
||||
numfiles = int(options.numfiles)
|
||||
readratio = float(options.readratio)
|
||||
actions = options.actions
|
||||
schedule = options.schedule
|
||||
printstats = options.printstats
|
||||
solve = options.solve
|
||||
detail = options.detail
|
||||
|
||||
# with specific schedule, files are all specified by a single letter in specific actions list
|
||||
# but we ignore this for now...
|
||||
|
||||
zassert(numfiles > 0 and numfiles <= 26, 'main: can only simulate 26 or fewer files, sorry')
|
||||
zassert(readratio >= 0.0 and readratio <= 1.0, 'main: read ratio must be between 0 and 1 inclusive')
|
||||
|
||||
# start it
|
||||
random_seed(seed)
|
||||
|
||||
# files in server to begin with
|
||||
f = files(numfiles)
|
||||
|
||||
# make server
|
||||
s = server(f, solve, detail)
|
||||
|
||||
clients = []
|
||||
|
||||
if actions != '':
|
||||
# if specific actions are specified, figure some stuff out now
|
||||
# e.g., oa1:ra1:ca1,oa1:ra1:ca1 which is list of 0's actions, then 1's, then...
|
||||
cactions = actions.split(',')
|
||||
if numclients != len(cactions):
|
||||
numclients = len(cactions)
|
||||
i = 0
|
||||
for clist in cactions:
|
||||
clients.append(client('c%d' % i, i, s, f, [], len(clist), clist, solve, detail))
|
||||
i += 1
|
||||
else:
|
||||
# else, make random clients
|
||||
for i in range(numclients):
|
||||
clients.append(client('c%d' % i, i, s, f, [readratio, 1.0], numsteps, '', solve, detail))
|
||||
|
||||
# tell server about these clients
|
||||
s.setclients(clients)
|
||||
|
||||
# init print out for clients
|
||||
print('%12s' % 'Server', '%12s' % ' ', end=' ')
|
||||
for c in clients:
|
||||
print('%13s' % c.getname(), '%13s' % ' ', end=' ')
|
||||
print('')
|
||||
|
||||
# main loop
|
||||
#
|
||||
# over time, pick a random client
|
||||
# have it do one thing, show what happens
|
||||
# move on to next and so forth
|
||||
|
||||
s.filestats(True)
|
||||
|
||||
# for use with specific schedule
|
||||
schedcurr = 0
|
||||
|
||||
# check for legal schedule (must include all clients)
|
||||
if schedule != '':
|
||||
for i in range(len(clients)):
|
||||
cnt = 0
|
||||
for j in range(len(schedule)):
|
||||
curr = schedule[j]
|
||||
if int(curr) == i:
|
||||
cnt += 1
|
||||
zassert(cnt != 0, 'main: client %d not in schedule:%s, which would never terminate' % (i, schedule))
|
||||
|
||||
# RUN the schedule (either random or specified by user)
|
||||
numrunning = len(clients)
|
||||
while numrunning > 0:
|
||||
if schedule == '':
|
||||
c = pickrand(clients)
|
||||
else:
|
||||
idx = int(schedule[schedcurr])
|
||||
# print 'SCHEDULE DEBUG:: schedule:', schedule, 'schedcurr', schedcurr, 'index', idx
|
||||
c = clients[idx]
|
||||
schedcurr += 1
|
||||
if schedcurr == len(schedule):
|
||||
schedcurr = 0
|
||||
rc = c.step(clients.index(c))
|
||||
if rc == 0:
|
||||
numrunning -= 1
|
||||
|
||||
|
||||
s.filestats(solve)
|
||||
|
||||
if printstats:
|
||||
s.stats()
|
||||
for c in clients:
|
||||
c.stats()
|
||||
|
Loading…
Reference in New Issue