BTreeTestTool.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Taft, December 7, 1983 10:42 am
Russ Atkinson (RRA) June 25, 1985 11:35:30 am PDT
DIRECTORY
BasicTime USING [GetClockPulses, MicrosecondsToPulses, Pulses],
BTree USING [GetState, SetUpdateInProgress, Tree, Validate],
BTreeTestOps,
BTreeVM,
Buttons USING [Button, ButtonProc, Create, ReLabel],
Containers USING [Container, Create],
Convert USING [Error, IntFromRope, RopeFromCard, RopeFromReal],
IO USING [PutFR],
Labels USING [Create, Label, Set],
Menus USING [AppendMenuEntry, CreateEntry, CreateMenu, Menu, MenuProc],
MessageWindow USING [Append, Blink],
Process USING [Detach, GetCurrent],
Real USING [Float],
Rope USING [Concat, Length, ROPE, Substr],
VFonts USING [CharWidth, StringWidth],
ViewerClasses USING [Viewer, ViewerClassRec],
ViewerEvents USING [EventProc, RegisterEventProc],
ViewerOps USING [AddProp, FetchProp, SetMenu],
ViewerTools USING [GetContents, MakeNewTextViewer, SetSelection];
BTreeTestTool: CEDAR PROGRAM
IMPORTS BasicTime, BTree, BTreeTestOps, BTreeVM, Buttons, Containers, Convert, IO, Labels, Menus, MessageWindow, Process, Real, Rope, VFonts, ViewerEvents, ViewerOps, ViewerTools = {
entryHeight: CARDINAL = 15; -- how tall to make each line of items
entryVSpace: CARDINAL = 8;  -- vertical leading space between lines
entryHSpace: CARDINAL = 8;  -- horizontal space between items in a line
Handle: TYPE = REF BTreeTestToolRec;
BTreeTestToolRec: TYPE = RECORD [ -- the data for a particular tool instance
outer: Containers.Container ← NIL, -- handle for the enclosing container
height: CARDINAL ← 0, -- height measured from the top of the container
idleMenu, activeMenu: Menus.Menu,
parV: ParametersViewer, -- viewer containing user input parameters
resV: ResultsViewer, -- viewer containing test results
params: Params, -- parameters for setting up BTree
results: Results, -- record of result values
tree: BTree.Tree ← NIL,
storage: BTreeVM.Handle ← NIL,
initTree: BOOLEANTRUE,
process: PROCESSNIL,
pleaseStop, pleaseDestroy: BOOLEANFALSE];
MakeBTreeTestTool: PROC = {
handle: Handle ← NEW[BTreeTestToolRec];
handle.idleMenu ← Menus.CreateMenu[];
Menus.AppendMenuEntry[
menu: handle.idleMenu,
entry: Menus.CreateEntry[
name: "Start",
proc: StartCommand,
clientData: handle]
];
Menus.AppendMenuEntry[
menu: handle.idleMenu,
entry: Menus.CreateEntry[
name: "InitTree",
proc: InitTreeCommand,
clientData: handle]
];
Menus.AppendMenuEntry[
menu: handle.idleMenu,
entry: Menus.CreateEntry[
name: "ResetStats",
proc: ResetStatsCommand,
clientData: handle]
];
handle.activeMenu ← Menus.CreateMenu[];
Menus.AppendMenuEntry[
menu: handle.activeMenu,
entry: Menus.CreateEntry[
name: "Stop",
proc: StopCommand,
clientData: handle]
];
handle.outer ← Containers.Create[[
name: "BTreeTest",
iconic: FALSE,
column: left,
menu: handle.idleMenu,
scrollable: TRUE ]];
MakeParametersViewer[handle];
MakeResultsViewer[handle];
ViewerOps.AddProp[viewer: handle.outer, prop: $handle, val: handle];
[] ← ViewerEvents.RegisterEventProc[proc: DestroyEvent, event: destroy, filter: handle.outer, before: TRUE];
};
DestroyEvent: ViewerEvents.EventProc --[viewer: ViewerClasses.Viewer, event: ViewerEvent, before: BOOL] RETURNS[abort: BOOLFALSE]-- = {
handle: Handle = NARROW[ViewerOps.FetchProp[viewer: viewer, prop: $handle]];
handle.pleaseStop ← handle.pleaseDestroy ← TRUE;
IF handle.process=NIL THEN TRUSTED { Process.Detach[FORK BTreeTestOps.Destroy[]] };
};
ParametersViewer: TYPE = RECORD [
pageSizeButton: Buttons.Button ← NIL, pageSize: ViewerClasses.Viewer ← NIL,
cacheSizeButton: Buttons.Button ← NIL, cacheSize: ViewerClasses.Viewer ← NIL,
maxTreeSizeButton: Buttons.Button ← NIL, maxTreeSize: ViewerClasses.Viewer ← NIL,
longUpdate: Buttons.Button ← NIL, validateEveryUpdate: Buttons.Button ← NIL];
MakeParametersViewer: PROC [handle: Handle] = {
OPEN par: handle.parV;
MakePromptButton: PROC [name, initialValue: Rope.ROPE] RETURNS [button: Buttons.Button, viewer: ViewerClasses.Viewer] = {
button ← Buttons.Create[
info: [
name: name,
wx: xPos,
wy: handle.height,
default the width so that it will be computed for us
wh: entryHeight, -- specify rather than defaulting so line is uniform
parent: handle.outer,
border: FALSE ],
proc: Prompt];
viewer ← ViewerTools.MakeNewTextViewer[ [
parent: handle.outer,
wx: button.wx + button.ww + entryHSpace,
wy: handle.height+2,
ww: 7*VFonts.CharWidth['0],
wh: entryHeight,
data: initialValue, -- initial contents
scrollable: FALSE,
border: FALSE]];
ViewerOps.AddProp[viewer: button, prop: $value, val: viewer];
xPos ← viewer.wx + viewer.ww + entryHSpace;
};
MakeBoolButton: PROC [name: Rope.ROPE, initialValue: BOOLEAN] RETURNS [button: Buttons.Button] = {
button ← Buttons.Create[
info: [
name: Rope.Concat[name, IF initialValue THEN ": Yes" ELSE ": No "],
wx: xPos,
wy: handle.height,
default the width so that it will be computed for us
wh: entryHeight, -- specify rather than defaulting so line is uniform
parent: handle.outer,
border: FALSE ],
proc: BoolButton];
ViewerOps.AddProp[viewer: button, prop: $value, val: NEW [BOOLEAN ← initialValue]];
xPos ← button.wx + button.ww + entryHSpace;
};
xPos: CARDINAL ← 0;
handle.height ← handle.height + entryVSpace; -- space down from the top of the viewer
[button: par.pageSizeButton, viewer: par.pageSize] ← MakePromptButton[name: "DiskPages/BTreePage:", initialValue: "1"];
[button: par.cacheSizeButton, viewer: par.cacheSize] ← MakePromptButton[name: "CacheSize:", initialValue: "20"];
[button: par.maxTreeSizeButton, viewer: par.maxTreeSize] ← MakePromptButton[name: "MaxTreeEntries:", initialValue: "10000"];
xPos ← 0;
handle.height ← handle.height + entryHeight + entryVSpace;
par.longUpdate ← MakeBoolButton[name: "LongUpdate", initialValue: FALSE];
par.validateEveryUpdate ← MakeBoolButton[name: "ValidateEveryUpdate", initialValue: FALSE];
handle.height ← handle.height + entryHeight + entryVSpace; -- interline spacing
};
Prompt: Buttons.ButtonProc = {
force the selection into the user input field
viewer: ViewerClasses.Viewer ← NARROW[ViewerOps.FetchProp[viewer: NARROW[parent], prop: $value]];
ViewerTools.SetSelection[viewer]; -- force the selection
};
BoolButton: Buttons.ButtonProc = {
button: Buttons.Button = NARROW[parent];
value: REF BOOLEAN = NARROW[ViewerOps.FetchProp[viewer: button, prop: $value]];
name: Rope.ROPE = Rope.Substr[base: button.name, len: Rope.Length[button.name]-3];
value^ ← ~value^;
Buttons.ReLabel[button: button, newName: Rope.Concat[name, IF value^ THEN "Yes" ELSE "No "]];
};
Params: TYPE = RECORD [
cacheSize: BTreeVM.CacheSize ← 20,
filePagesPerPage: BTreeVM.FilePagesPerPage ← 1,
maxEntries: BTreeTestOps.KeyIndex ← 10000,
longUpdate, validateEveryUpdate: BOOLEANFALSE];
ParamError: ERROR [message: Rope.ROPE] = CODE;
ParseParams: PROC [handle: Handle] RETURNS [params: Params] = {
OPEN pv: handle.parV;
IntervalFromRope: PROC [r: Rope.ROPE, min, max: INT] RETURNS [result: INT] =
{
result ← Convert.IntFromRope[r ! Convert.Error => GOTO notNumber];
IF result NOT IN [min..max] THEN ERROR ParamError[IO.PutFR["\"%g\" is out of bounds; must be in the range [%g..%g]", [rope[r]], [integer[min]], [integer[max]]]];
EXITS
notNumber => ERROR ParamError[IO.PutFR["\"%g\" is not a number", [rope[r]]]];
};
params.cacheSize ← IntervalFromRope[r: ViewerTools.GetContents[pv.cacheSize], min: FIRST[BTreeVM.CacheSize], max: LAST[BTreeVM.CacheSize]];
params.filePagesPerPage ← IntervalFromRope[r: ViewerTools.GetContents[pv.pageSize], min: FIRST[BTreeVM.FilePagesPerPage], max: LAST[BTreeVM.FilePagesPerPage]];
params.maxEntries ← IntervalFromRope[r: ViewerTools.GetContents[pv.maxTreeSize], min: 100, max: LAST[BTreeTestOps.KeyIndex]];
params.longUpdate ← NARROW[ViewerOps.FetchProp[viewer: pv.longUpdate, prop: $value], REF BOOLEAN]^;
params.validateEveryUpdate ← NARROW[ViewerOps.FetchProp[viewer: pv.validateEveryUpdate, prop: $value], REF BOOLEAN]^;
};
ResultsViewer: TYPE = RECORD [
treePages, treeLevels, treeEntries: Labels.Label,
operations: ARRAY Operation OF Labels.Label,
totalOperations: Labels.Label,
msPerOp: ARRAY Operation OF Labels.Label,
cacheRefs, hitPercent, reads, writes, writesPerUpdate: Labels.Label,
elapsed, percentRW, msPerReadWrite: Labels.Label];
Operation: TYPE = BTreeTestOps.Operation;
MakeResultsViewer: PROC [handle: Handle] = {
OPEN res: handle.resV;
MakeLabel: PROC [value: Rope.ROPE, width: CARDINAL ← 0] RETURNS [label: Labels.Label] = {
label ← Labels.Create[ [
name: value,
wx: xPos,
wy: handle.height,
ww: IF width#0 THEN width ELSE VFonts.StringWidth[value],
wh: entryHeight,
parent: handle.outer,
border: FALSE]];
xPos ← label.wx + label.ww + entryHSpace;
};
charWidth: CARDINAL = VFonts.CharWidth['0];
itemWidth: CARDINAL = 12*charWidth;
xPos: CARDINAL ← 0;
handle.height ← handle.height + entryVSpace;
[] ← MakeLabel["Tree size: Pages:"];
res.treePages ← MakeLabel["0", 6*charWidth];
[] ← MakeLabel["Levels:"];
res.treeLevels ← MakeLabel["0", 4*charWidth];
[] ← MakeLabel["Entries:"];
res.treeEntries ← MakeLabel["0", 8*charWidth];
handle.height ← handle.height + entryHeight + entryVSpace;
xPos ← itemWidth + entryHSpace;
[] ← MakeLabel["Lookup", itemWidth];
[] ← MakeLabel["Enumerate", itemWidth];
[] ← MakeLabel["Insert", itemWidth];
[] ← MakeLabel["Delete", itemWidth];
[] ← MakeLabel["Replace", itemWidth];
[] ← MakeLabel["Total", itemWidth];
xPos ← 0;
handle.height ← handle.height + entryHeight + entryVSpace;
[] ← MakeLabel["Operations", itemWidth];
FOR op: Operation IN Operation DO
res.operations[op] ← MakeLabel["0", itemWidth];
ENDLOOP;
res.totalOperations ← MakeLabel["0", itemWidth];
xPos ← 0;
handle.height ← handle.height + entryHeight;
[] ← MakeLabel["ms/op", itemWidth];
FOR op: Operation IN Operation DO
res.msPerOp[op] ← MakeLabel["0", itemWidth];
ENDLOOP;
handle.height ← handle.height + entryHeight + entryVSpace;
xPos ← 0;
[] ← MakeLabel["CacheRefs: "];
res.cacheRefs ← MakeLabel["0", 9*charWidth];
[] ← MakeLabel["Hit%: "];
res.hitPercent ← MakeLabel["0", 5*charWidth];
handle.height ← handle.height + entryHeight + entryVSpace;
xPos ← 0;
[] ← MakeLabel["Storage Reads: "];
res.reads ← MakeLabel["0", 9*charWidth];
[] ← MakeLabel["Writes: "];
res.writes ← MakeLabel["0", 9*charWidth];
[] ← MakeLabel["Writes/update: "];
res.writesPerUpdate ← MakeLabel["0", 7*charWidth];
handle.height ← handle.height + entryHeight + entryVSpace;
xPos ← 0;
[] ← MakeLabel["Total elapsed time (sec): "];
res.elapsed ← MakeLabel["0", 9*charWidth];
[] ← MakeLabel["% R+W time: "];
res.percentRW ← MakeLabel["0", 5*charWidth];
[] ← MakeLabel["ms/(R or W): "];
res.msPerReadWrite ← MakeLabel["0", 5*charWidth];
handle.height ← handle.height + entryHeight + entryVSpace;
};
Results: TYPE = RECORD [
counts: ARRAY Operation OF LONG CARDINALALL [0],
times: ARRAY Operation OF BasicTime.Pulses ← ALL [0],
treePages, treeLevels: CARDINAL ← 0,
treeEntries: LONG CARDINAL ← 0,
hits, misses, reads, writes: LONG CARDINAL ← 0,
cumReadWriteTime: BasicTime.Pulses ← 0];
DisplayResults: PROC [handle: Handle] = {
OPEN rv: handle.resV, res: handle.results;
totalOperations: LONG CARDINAL ← 0;
totalTime: BasicTime.Pulses ← 0;
millisecondsPerPulse: REAL = 1000.0 / Real.Float[BasicTime.MicrosecondsToPulses[1000000]];
Labels.Set[rv.treePages, Convert.RopeFromCard[res.treePages]];
Labels.Set[rv.treeLevels, Convert.RopeFromCard[res.treeLevels]];
Labels.Set[rv.treeEntries, Convert.RopeFromCard[res.treeEntries]];
FOR op: Operation IN Operation DO
Labels.Set[rv.operations[op], Convert.RopeFromCard[res.counts[op]]];
totalOperations ← totalOperations+res.counts[op];
totalTime ← totalTime+res.times[op];
ENDLOOP;
Labels.Set[rv.totalOperations, Convert.RopeFromCard[totalOperations]];
FOR op: Operation IN Operation DO
msPerOp: REAL = (Real.Float[res.times[op]] / Real.Float[MAX[res.counts[op], 1]]) * millisecondsPerPulse;
Labels.Set[rv.msPerOp[op], Convert.RopeFromReal[from: msPerOp, precision: 3]];
ENDLOOP;
Labels.Set[rv.cacheRefs, Convert.RopeFromCard[res.hits+res.misses]];
Labels.Set[rv.hitPercent, Convert.RopeFromReal[from: Real.Float[res.hits]*100.0 / Real.Float[MAX[res.hits+res.misses, 1]], precision: 3]];
Labels.Set[rv.reads, Convert.RopeFromCard[res.reads]];
Labels.Set[rv.writes, Convert.RopeFromCard[res.writes]];
Labels.Set[rv.writesPerUpdate, Convert.RopeFromReal[from: Real.Float[res.writes] / Real.Float[MAX[res.counts[insert]+res.counts[delete]+res.counts[replace], 1]], precision: 3]];
Labels.Set[rv.elapsed, Convert.RopeFromReal[from: Real.Float[totalTime] * millisecondsPerPulse/1000.0, precision: 3]];
Labels.Set[rv.percentRW, Convert.RopeFromReal[from: Real.Float[res.cumReadWriteTime]*100.0 / Real.Float[MAX[totalTime, 1]], precision: 3]];
Labels.Set[rv.msPerReadWrite, Convert.RopeFromReal[from: (Real.Float[res.cumReadWriteTime] / Real.Float[MAX[res.reads+res.writes, 1]]) * millisecondsPerPulse, precision: 3]];
};
StartCommand: Menus.MenuProc = {
handle: Handle = NARROW [clientData];
params: Params ← ParseParams[handle
! ParamError => {
MessageWindow.Append[message: message, clearFirst: TRUE];
GOTO paramError }];
IF ~handle.initTree AND params#handle.params THEN {
IF params.filePagesPerPage#handle.params.filePagesPerPage OR params.maxEntries#handle.params.maxEntries THEN {
MessageWindow.Append[message: "Parameters changed; you must InitTree first", clearFirst: TRUE];
GOTO paramError;
};
handle.tree ← NIL;
handle.storage ← NIL;
};
handle.params ← params;
IF handle.tree=NIL THEN
[tree: handle.tree, storage: handle.storage] ← BTreeTestOps.Create[cacheSize: params.cacheSize, initialize: handle.initTree, filePagesPerPage: params.filePagesPerPage, maxEntries: params.maxEntries];
handle.initTree ← FALSE;
IF params.longUpdate THEN handle.tree.SetUpdateInProgress[TRUE];
handle.pleaseStop ← FALSE;
handle.process ← FORK TestProc[handle];
ViewerOps.SetMenu[viewer: handle.outer, menu: handle.activeMenu];
EXITS
paramError => MessageWindow.Blink[];
};
StopCommand: Menus.MenuProc = {
handle: Handle = NARROW [clientData];
handle.pleaseStop ← TRUE;
TRUSTED { JOIN handle.process };
handle.process ← NIL;
ViewerOps.SetMenu[viewer: handle.outer, menu: handle.idleMenu];
};
InitTreeCommand: Menus.MenuProc = {
handle: Handle = NARROW [clientData];
handle.tree ← NIL;
handle.storage ← NIL;
handle.initTree ← TRUE;
handle.results ← [];
DisplayResults[handle];
};
ResetStatsCommand: Menus.MenuProc = {
handle: Handle = NARROW [clientData];
handle.tree ← NIL;
handle.storage ← NIL;
handle.results ← [];
DisplayResults[handle];
};
TestProc: PROC [handle: Handle] = {
OPEN res: handle.results;
ENABLE UNWIND =>
IF ~handle.pleaseStop THEN {
ViewerOps.SetMenu[viewer: handle.outer, menu: handle.idleMenu];
TRUSTED { Process.Detach[Process.GetCurrent[]] };
};
lastUpdateTime: BasicTime.Pulses ← BasicTime.GetClockPulses[];
updateInterval: LONG CARDINAL = 3*BasicTime.MicrosecondsToPulses[1000000];
UNTIL handle.pleaseStop DO
operation: BTreeTestOps.Operation;
k: BTreeTestOps.KeyIndex;
then: BasicTime.Pulses ← BasicTime.GetClockPulses[];
now: BasicTime.Pulses;
[operation: operation, key: k] ← BTreeTestOps.PerformRandomOperation[];
now ← BasicTime.GetClockPulses[];
res.times[operation] ← res.times[operation] + (now - then);
res.counts[operation] ← res.counts[operation]+(IF operation=validate THEN handle.tree.GetState[].entryCount ELSE 1);
IF handle.params.validateEveryUpdate THEN {
myCount: LONG CARDINAL = BTreeTestOps.GetEntryCount[];
then ← BasicTime.GetClockPulses[];
handle.tree.Validate[];
now ← BasicTime.GetClockPulses[];
res.times[validate] ← res.times[validate] + (now - then);
res.counts[validate] ← res.counts[validate] + myCount;
IF handle.tree.GetState[].entryCount # myCount THEN ERROR; -- entry count disagrees. Call BTreeTestImpl.FindMissingEntries to find the missing ones.
};
IF now-lastUpdateTime > updateInterval OR handle.pleaseStop THEN {
[entryCount: res.treeEntries, greatestPage: res.treePages, depth: res.treeLevels] ← handle.tree.GetState[];
[hits: res.hits, misses: res.misses, reads: res.reads, writes: res.writes, cumReadWriteTime: res.cumReadWriteTime] ← handle.storage.GetStats[];
DisplayResults[handle];
lastUpdateTime ← now;
};
ENDLOOP;
IF handle.params.longUpdate THEN handle.tree.SetUpdateInProgress[FALSE];
IF handle.pleaseDestroy THEN {
BTreeTestOps.Destroy[];
TRUSTED { Process.Detach[Process.GetCurrent[]] };
};
};
Initialization
MakeBTreeTestTool[];
}.