1 module tagion.testbench.dart.insert_remove_stress;
2 // Default import list for bdd
3 import std.algorithm;
4 import std.datetime.stopwatch;
5 import std.file : mkdirRecurse;
6 import std.format;
7 import std.random;
8 import std.range;
9 import std.stdio;
10 import std.typecons : Tuple;
11 import tagion.basic.Types : Buffer;
12 import tagion.behaviour;
13 import tagion.hibon.Document;
14 import tagion.hibon.HiBONJSON;
15 import tagion.testbench.tools.Environment;
16 
17 // dart
18 import std.digest;
19 import tagion.basic.basic : forceRemove;
20 import tagion.crypto.SecureInterfaceNet : HashNet, SecureNet;
21 import tagion.dart.DART : DART;
22 import tagion.dart.DARTBasic;
23 import tagion.dart.DARTFakeNet;
24 import tagion.dart.DARTFile : DARTFile;
25 import tagion.dart.Recorder : Archive, RecordFactory;
26 import tagion.testbench.dart.dart_helper_functions;
27 import tagion.testbench.dart.dartinfo;
28 
29 enum feature = Feature(
30             "insert random stress test",
31             [
32             "This test uses dartfakenet to randomly add and remove archives in the same recorder."
33             ]);
34 
35 alias FeatureContext = Tuple!(
36         AddRemoveAndReadTheResult, "AddRemoveAndReadTheResult",
37         FeatureGroup*, "result"
38 );
39 
40 @safe @Scenario("add remove and read the result",
41         [])
42 class AddRemoveAndReadTheResult {
43 
44     DartInfo info;
45     DART db1;
46     RandomArchives[] random_archives;
47     Mt19937 gen;
48     auto insert_watch = StopWatch(AutoStart.no);
49     auto read_watch = StopWatch(AutoStart.no);
50     uint number_of_seeds;
51     uint number_of_rounds;
52     uint number_of_samples;
53 
54     uint operations;
55 
56     this(DartInfo info, const uint seed, const uint number_of_seeds, const uint number_of_rounds, const uint number_of_samples) {
57         check(number_of_samples < number_of_seeds, "number of samples must be smaller than number of seeds");
58         this.info = info;
59         gen = Mt19937(seed);
60         this.number_of_rounds = number_of_rounds;
61         this.number_of_samples = number_of_samples;
62         this.number_of_seeds = number_of_seeds;
63     }
64 
65     @Given("i have a dartfile")
66     Document dartfile() {
67         mkdirRecurse(info.module_path);
68         // create the dartfile
69         info.dartfilename.forceRemove;
70         DART.create(info.dartfilename, info.net);
71 
72         Exception dart_exception;
73         db1 = new DART(info.net, info.dartfilename, dart_exception);
74         check(dart_exception is null, format("Failed to open DART %s", dart_exception.msg));
75         return result_ok;
76     }
77 
78     @Given("i have an array of randomarchives")
79     Document randomarchives() {
80 
81         // first we generate the array of random archives
82 
83         random_archives = gen.take(number_of_seeds).map!(s => RandomArchives(s)).array;
84 
85         return result_ok;
86     }
87 
88     @When("i select n amount of elements in the randomarchives and add them to the dart and flip their bool. And count the number of instructions.")
89     Document instructions() {
90 
91         foreach (i; 0 .. number_of_rounds) {
92             writefln("running %s", i);
93             auto rnd = MinstdRand0(gen.front);
94             gen.popFront;
95             auto sample_numbers = iota(random_archives.length).randomSample(number_of_samples, rnd).array;
96             auto recorder = db1.recorder();
97 
98             foreach (sample_number; sample_numbers) {
99                 auto docs = random_archives[sample_number].values.map!(a => DARTFakeNet.fake_doc(a));
100 
101                 if (!random_archives[sample_number].in_dart) {
102                     recorder.insert(docs, Archive.Type.ADD);
103                 }
104                 else {
105 
106                     recorder.insert(docs, Archive.Type.REMOVE);
107                 }
108                 operations += docs.walkLength;
109 
110                 random_archives[sample_number].in_dart = !random_archives[sample_number].in_dart;
111             }
112 
113             insert_watch.start();
114             db1.modify(recorder);
115             insert_watch.stop();
116         }
117 
118         return result_ok;
119     }
120 
121     @Then("i read all the elements.")
122     Document elements() {
123 
124         auto fingerprints = random_archives
125             .filter!(s => s.in_dart == true)
126             .map!(d => d.values)
127             .join
128             .map!(a => DARTFakeNet.fake_doc(a))
129             .map!(a => info.net.dartIndex(a))
130             .array;
131 
132         writefln("###");
133         read_watch.start();
134         auto read_recorder = db1.loads(fingerprints, Archive.Type.NONE);
135         read_watch.stop();
136         writeln(read_recorder[].walkLength);
137 
138         check(read_recorder[].walkLength == fingerprints.length, "the length of the read archives is not the same as the expected");
139 
140         auto expected_read_docs = random_archives
141             .filter!(s => s.in_dart == true)
142             .map!(d => d.values)
143             .join
144             .map!(a => DARTFakeNet.fake_doc(a))
145             .array;
146 
147         auto expected_recorder = db1.recorder();
148         expected_recorder.insert(expected_read_docs);
149 
150         check(equal(expected_recorder[].map!(a => a.filed), read_recorder[].map!(a => a.filed)), "data not the same");
151 
152         const long insert_time = insert_watch.peek.total!"msecs";
153         const long read_time = read_watch.peek.total!"msecs";
154 
155         writefln("Total number of operations: %d", operations + fingerprints.length);
156         writefln("ADD and REMOVE operations: %d. pr. sec: %s", operations, operations / double(insert_time) * 1000);
157         writefln("READ operations: %s. pr.sec %s", fingerprints.length, fingerprints.length / double(read_time) * 1000);
158 
159         // the total time
160         writefln("Total operations pr. sec: %1.f", (operations + fingerprints.length) / double(insert_time + read_time) * 1000);
161         db1.close;
162         return result_ok;
163     }
164 
165 }