QuizGenerator 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- QuizGenerator/README.md +5 -0
- QuizGenerator/__init__.py +27 -0
- QuizGenerator/__main__.py +7 -0
- QuizGenerator/canvas/__init__.py +13 -0
- QuizGenerator/canvas/canvas_interface.py +622 -0
- QuizGenerator/canvas/classes.py +235 -0
- QuizGenerator/constants.py +149 -0
- QuizGenerator/contentast.py +1809 -0
- QuizGenerator/generate.py +362 -0
- QuizGenerator/logging.yaml +55 -0
- QuizGenerator/misc.py +480 -0
- QuizGenerator/mixins.py +539 -0
- QuizGenerator/performance.py +202 -0
- QuizGenerator/premade_questions/__init__.py +0 -0
- QuizGenerator/premade_questions/basic.py +103 -0
- QuizGenerator/premade_questions/cst334/__init__.py +1 -0
- QuizGenerator/premade_questions/cst334/languages.py +395 -0
- QuizGenerator/premade_questions/cst334/math_questions.py +297 -0
- QuizGenerator/premade_questions/cst334/memory_questions.py +1398 -0
- QuizGenerator/premade_questions/cst334/ostep13_vsfs.py +572 -0
- QuizGenerator/premade_questions/cst334/persistence_questions.py +396 -0
- QuizGenerator/premade_questions/cst334/process.py +649 -0
- QuizGenerator/premade_questions/cst463/__init__.py +0 -0
- QuizGenerator/premade_questions/cst463/gradient_descent/__init__.py +3 -0
- QuizGenerator/premade_questions/cst463/gradient_descent/gradient_calculation.py +369 -0
- QuizGenerator/premade_questions/cst463/gradient_descent/gradient_descent_questions.py +305 -0
- QuizGenerator/premade_questions/cst463/gradient_descent/loss_calculations.py +650 -0
- QuizGenerator/premade_questions/cst463/gradient_descent/misc.py +73 -0
- QuizGenerator/premade_questions/cst463/math_and_data/__init__.py +2 -0
- QuizGenerator/premade_questions/cst463/math_and_data/matrix_questions.py +631 -0
- QuizGenerator/premade_questions/cst463/math_and_data/vector_questions.py +534 -0
- QuizGenerator/premade_questions/cst463/neural-network-basics/__init__.py +6 -0
- QuizGenerator/premade_questions/cst463/neural-network-basics/neural_network_questions.py +1264 -0
- QuizGenerator/premade_questions/cst463/tensorflow-intro/__init__.py +6 -0
- QuizGenerator/premade_questions/cst463/tensorflow-intro/tensorflow_questions.py +936 -0
- QuizGenerator/qrcode_generator.py +293 -0
- QuizGenerator/question.py +657 -0
- QuizGenerator/quiz.py +468 -0
- QuizGenerator/typst_utils.py +113 -0
- quizgenerator-0.1.0.dist-info/METADATA +263 -0
- quizgenerator-0.1.0.dist-info/RECORD +44 -0
- quizgenerator-0.1.0.dist-info/WHEEL +4 -0
- quizgenerator-0.1.0.dist-info/entry_points.txt +2 -0
- quizgenerator-0.1.0.dist-info/licenses/LICENSE +674 -0
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
#!env python
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import abc
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
from QuizGenerator.question import Question, Answer, QuestionRegistry
|
|
8
|
+
from QuizGenerator.contentast import ContentAST
|
|
9
|
+
from QuizGenerator.mixins import TableQuestionMixin, BodyTemplatesMixin
|
|
10
|
+
|
|
11
|
+
log = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class IOQuestion(Question, abc.ABC):
|
|
15
|
+
def __init__(self, *args, **kwargs):
|
|
16
|
+
kwargs["topic"] = kwargs.get("topic", Question.Topic.IO)
|
|
17
|
+
super().__init__(*args, **kwargs)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@QuestionRegistry.register()
|
|
21
|
+
class HardDriveAccessTime(IOQuestion, TableQuestionMixin, BodyTemplatesMixin):
|
|
22
|
+
|
|
23
|
+
def refresh(self, *args, **kwargs):
|
|
24
|
+
super().refresh(*args, **kwargs)
|
|
25
|
+
|
|
26
|
+
self.hard_drive_rotation_speed = 100 * self.rng.randint(36, 150) # e.g. 3600rpm to 15000rpm
|
|
27
|
+
self.seek_delay = float(round(self.rng.randrange(3, 20), 2))
|
|
28
|
+
self.transfer_rate = self.rng.randint(50, 300)
|
|
29
|
+
self.number_of_reads = self.rng.randint(1, 20)
|
|
30
|
+
self.size_of_reads = self.rng.randint(1, 10)
|
|
31
|
+
|
|
32
|
+
self.rotational_delay = (1 / self.hard_drive_rotation_speed) * (60 / 1) * (1000 / 1) * (1/2)
|
|
33
|
+
self.access_delay = self.rotational_delay + self.seek_delay
|
|
34
|
+
self.transfer_delay = 1000 * (self.size_of_reads * self.number_of_reads) / 1024 / self.transfer_rate
|
|
35
|
+
self.disk_access_delay = self.access_delay * self.number_of_reads + self.transfer_delay
|
|
36
|
+
|
|
37
|
+
self.answers.update({
|
|
38
|
+
"answer__rotational_delay": Answer.float_value(
|
|
39
|
+
"answer__rotational_delay",
|
|
40
|
+
self.rotational_delay
|
|
41
|
+
),
|
|
42
|
+
"answer__access_delay": Answer.float_value(
|
|
43
|
+
"answer__access_delay",
|
|
44
|
+
self.access_delay
|
|
45
|
+
),
|
|
46
|
+
"answer__transfer_delay": Answer.float_value(
|
|
47
|
+
"answer__transfer_delay",
|
|
48
|
+
self.transfer_delay
|
|
49
|
+
),
|
|
50
|
+
"answer__disk_access_delay": Answer.float_value(
|
|
51
|
+
"answer__disk_access_delay",
|
|
52
|
+
self.disk_access_delay
|
|
53
|
+
),
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
def get_body(self, *args, **kwargs) -> ContentAST.Section:
|
|
57
|
+
# Create parameter info table using mixin
|
|
58
|
+
parameter_info = {
|
|
59
|
+
"Hard Drive Rotation Speed": f"{self.hard_drive_rotation_speed}RPM",
|
|
60
|
+
"Seek Delay": f"{self.seek_delay}ms",
|
|
61
|
+
"Transfer Rate": f"{self.transfer_rate}MB/s",
|
|
62
|
+
"Number of Reads": f"{self.number_of_reads}",
|
|
63
|
+
"Size of Reads": f"{self.size_of_reads}KB"
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
parameter_table = self.create_info_table(parameter_info)
|
|
67
|
+
|
|
68
|
+
# Create answer table with multiple rows using mixin
|
|
69
|
+
answer_rows = [
|
|
70
|
+
{"Variable": "Rotational Delay", "Value": "answer__rotational_delay"},
|
|
71
|
+
{"Variable": "Access Delay", "Value": "answer__access_delay"},
|
|
72
|
+
{"Variable": "Transfer Delay", "Value": "answer__transfer_delay"},
|
|
73
|
+
{"Variable": "Total Disk Access Delay", "Value": "answer__disk_access_delay"}
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
answer_table = self.create_answer_table(
|
|
77
|
+
headers=["Variable", "Value"],
|
|
78
|
+
data_rows=answer_rows,
|
|
79
|
+
answer_columns=["Value"]
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# Use mixin to create complete body with both tables
|
|
83
|
+
intro_text = "Given the information below, please calculate the following values."
|
|
84
|
+
|
|
85
|
+
instructions = (
|
|
86
|
+
f"Make sure your answers are rounded to {Answer.DEFAULT_ROUNDING_DIGITS} decimal points "
|
|
87
|
+
f"(even if they are whole numbers), and do so after you finish all your calculations! "
|
|
88
|
+
f"(i.e. don't use your rounded answers to calculate your overall answer)"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
body = self.create_parameter_calculation_body(
|
|
92
|
+
intro_text=intro_text,
|
|
93
|
+
parameter_table=parameter_table,
|
|
94
|
+
answer_table=answer_table,
|
|
95
|
+
additional_instructions=instructions
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
return body
|
|
99
|
+
|
|
100
|
+
def get_explanation(self) -> ContentAST.Section:
|
|
101
|
+
explanation = ContentAST.Section()
|
|
102
|
+
|
|
103
|
+
explanation.add_element(
|
|
104
|
+
ContentAST.Paragraph([
|
|
105
|
+
"To calculate the total disk access time (or \"delay\"), "
|
|
106
|
+
"we should first calculate each of the individual parts.",
|
|
107
|
+
r"Since we know that $t_{total} = (\text{# of reads}) \cdot t_{access} + t_{transfer}$"
|
|
108
|
+
r"we therefore need to calculate $t_{access}$ and $t_{transfer}$, where "
|
|
109
|
+
r"$t_{access} = t_{rotation} + t_{seek}$.",
|
|
110
|
+
])
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
explanation.add_elements([
|
|
114
|
+
ContentAST.Paragraph(["Starting with the rotation delay, we calculate:"]),
|
|
115
|
+
ContentAST.Equation(
|
|
116
|
+
"t_{rotation} = "
|
|
117
|
+
+ f"\\frac{{1 minute}}{{{self.hard_drive_rotation_speed}revolutions}}"
|
|
118
|
+
+ r"\cdot \frac{60 seconds}{1 minute} \cdot \frac{1000 ms}{1 second} \cdot \frac{1 revolution}{2} = "
|
|
119
|
+
+ f"{self.rotational_delay:0.2f}ms",
|
|
120
|
+
)
|
|
121
|
+
])
|
|
122
|
+
|
|
123
|
+
explanation.add_elements([
|
|
124
|
+
ContentAST.Paragraph([
|
|
125
|
+
"Now we can calculate:",
|
|
126
|
+
]),
|
|
127
|
+
ContentAST.Equation(
|
|
128
|
+
f"t_{{access}} "
|
|
129
|
+
f"= t_{{rotation}} + t_{{seek}} "
|
|
130
|
+
f"= {self.rotational_delay:0.2f}ms + {self.seek_delay:0.2f}ms = {self.access_delay:0.2f}ms"
|
|
131
|
+
)
|
|
132
|
+
])
|
|
133
|
+
|
|
134
|
+
explanation.add_elements([
|
|
135
|
+
ContentAST.Paragraph([r"Next we need to calculate our transfer delay, $t_{transfer}$, which we do as:"]),
|
|
136
|
+
ContentAST.Equation(
|
|
137
|
+
f"t_{{transfer}} "
|
|
138
|
+
f"= \\frac{{{self.number_of_reads} \\cdot {self.size_of_reads}KB}}{{1}} \\cdot \\frac{{1MB}}{{1024KB}} "
|
|
139
|
+
f"\\cdot \\frac{{1 second}}{{{self.transfer_rate}MB}} \\cdot \\frac{{1000ms}}{{1second}} "
|
|
140
|
+
f"= {self.transfer_delay:0.2}ms"
|
|
141
|
+
)
|
|
142
|
+
])
|
|
143
|
+
|
|
144
|
+
explanation.add_elements([
|
|
145
|
+
ContentAST.Paragraph(["Putting these together we get:"]),
|
|
146
|
+
ContentAST.Equation(
|
|
147
|
+
f"t_{{total}} "
|
|
148
|
+
f"= \\text{{(# reads)}} \\cdot t_{{access}} + t_{{transfer}} "
|
|
149
|
+
f"= {self.number_of_reads} \\cdot {self.access_delay:0.2f} + {self.transfer_delay:0.2f} "
|
|
150
|
+
f"= {self.disk_access_delay:0.2f}ms")
|
|
151
|
+
])
|
|
152
|
+
return explanation
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@QuestionRegistry.register()
|
|
156
|
+
class INodeAccesses(IOQuestion, TableQuestionMixin, BodyTemplatesMixin):
|
|
157
|
+
|
|
158
|
+
def refresh(self, *args, **kwargs):
|
|
159
|
+
super().refresh(*args, **kwargs)
|
|
160
|
+
|
|
161
|
+
# Calculating this first to use blocksize as an even multiple of it
|
|
162
|
+
self.inode_size = 2**self.rng.randint(6, 10)
|
|
163
|
+
|
|
164
|
+
self.block_size = self.inode_size * self.rng.randint(8, 20)
|
|
165
|
+
self.inode_number = self.rng.randint(0, 256)
|
|
166
|
+
self.inode_start_location = self.block_size * self.rng.randint(2, 5)
|
|
167
|
+
|
|
168
|
+
self.inode_address = self.inode_start_location + self.inode_number * self.inode_size
|
|
169
|
+
self.inode_block = self.inode_address // self.block_size
|
|
170
|
+
self.inode_address_in_block = self.inode_address % self.block_size
|
|
171
|
+
self.inode_index_in_block = int(self.inode_address_in_block / self.inode_size)
|
|
172
|
+
|
|
173
|
+
self.answers.update({
|
|
174
|
+
"answer__inode_address": Answer.integer("answer__inode_address", self.inode_address),
|
|
175
|
+
"answer__inode_block": Answer.integer("answer__inode_block", self.inode_block),
|
|
176
|
+
"answer__inode_address_in_block": Answer.integer("answer__inode_address_in_block", self.inode_address_in_block),
|
|
177
|
+
"answer__inode_index_in_block": Answer.integer("answer__inode_index_in_block", self.inode_index_in_block),
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
def get_body(self) -> ContentAST.Section:
|
|
181
|
+
# Create parameter info table using mixin
|
|
182
|
+
parameter_info = {
|
|
183
|
+
"Block Size": f"{self.block_size} Bytes",
|
|
184
|
+
"Inode Number": f"{self.inode_number}",
|
|
185
|
+
"Inode Start Location": f"{self.inode_start_location} Bytes",
|
|
186
|
+
"Inode size": f"{self.inode_size} Bytes"
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
parameter_table = self.create_info_table(parameter_info)
|
|
190
|
+
|
|
191
|
+
# Create answer table with multiple rows using mixin
|
|
192
|
+
answer_rows = [
|
|
193
|
+
{"Variable": "Inode address", "Value": "answer__inode_address"},
|
|
194
|
+
{"Variable": "Block containing inode", "Value": "answer__inode_block"},
|
|
195
|
+
{"Variable": "Inode address (offset) within block", "Value": "answer__inode_address_in_block"},
|
|
196
|
+
{"Variable": "Inode index within block", "Value": "answer__inode_index_in_block"}
|
|
197
|
+
]
|
|
198
|
+
|
|
199
|
+
answer_table = self.create_answer_table(
|
|
200
|
+
headers=["Variable", "Value"],
|
|
201
|
+
data_rows=answer_rows,
|
|
202
|
+
answer_columns=["Value"]
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
# Use mixin to create complete body with both tables
|
|
206
|
+
intro_text = "Given the information below, please calculate the following values."
|
|
207
|
+
|
|
208
|
+
instructions = (
|
|
209
|
+
"(hint: they should all be round numbers). "
|
|
210
|
+
"Remember, demonstrating you know the equations and what goes into them is generally sufficient."
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
body = self.create_parameter_calculation_body(
|
|
214
|
+
intro_text=intro_text,
|
|
215
|
+
parameter_table=parameter_table,
|
|
216
|
+
answer_table=answer_table,
|
|
217
|
+
additional_instructions=instructions
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
return body
|
|
221
|
+
|
|
222
|
+
def get_explanation(self) -> ContentAST.Section:
|
|
223
|
+
explanation = ContentAST.Section()
|
|
224
|
+
|
|
225
|
+
explanation.add_element(
|
|
226
|
+
ContentAST.Paragraph([
|
|
227
|
+
"If we are given an inode number, there are a few steps that we need to take to load the actual inode. "
|
|
228
|
+
"These consist of determining the address of the inode, which block would contain it, "
|
|
229
|
+
"and then its address within the block.",
|
|
230
|
+
"To find the inode address, we calculate:",
|
|
231
|
+
])
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
explanation.add_element(
|
|
235
|
+
ContentAST.Equation.make_block_equation__multiline_equals(
|
|
236
|
+
r"(\text{Inode address})",
|
|
237
|
+
[
|
|
238
|
+
r"(\text{Inode Start Location}) + (\text{inode #}) \cdot (\text{inode size})",
|
|
239
|
+
f"{self.inode_start_location} + {self.inode_number} \\cdot {self.inode_size}",
|
|
240
|
+
f"{self.inode_address}"
|
|
241
|
+
])
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
explanation.add_element(
|
|
245
|
+
ContentAST.Paragraph([
|
|
246
|
+
"Next, we us this to figure out what block the inode is in. "
|
|
247
|
+
"We do this directly so we know what block to load, "
|
|
248
|
+
"thus minimizing the number of loads we have to make.",
|
|
249
|
+
])
|
|
250
|
+
)
|
|
251
|
+
explanation.add_element(ContentAST.Equation.make_block_equation__multiline_equals(
|
|
252
|
+
r"\text{Block containing inode}",
|
|
253
|
+
[
|
|
254
|
+
r"(\text{Inode address}) \mathbin{//} (\text{block size})",
|
|
255
|
+
f"{self.inode_address} \\mathbin{{//}} {self.block_size}",
|
|
256
|
+
f"{self.inode_block}"
|
|
257
|
+
]
|
|
258
|
+
))
|
|
259
|
+
|
|
260
|
+
explanation.add_element(
|
|
261
|
+
ContentAST.Paragraph([
|
|
262
|
+
"When we load this block, we now have in our system memory "
|
|
263
|
+
"(remember, blocks on the hard drive are effectively useless to us until they're in main memory!), "
|
|
264
|
+
"the inode, so next we need to figure out where it is within that block."
|
|
265
|
+
"This means that we'll need to find the offset into this block. "
|
|
266
|
+
"We'll calculate this both as the offset in bytes, and also in number of inodes, "
|
|
267
|
+
"since we can use array indexing.",
|
|
268
|
+
])
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
explanation.add_element(ContentAST.Equation.make_block_equation__multiline_equals(
|
|
272
|
+
r"\text{offset within block}",
|
|
273
|
+
[
|
|
274
|
+
r"(\text{Inode address}) \bmod (\text{block size})",
|
|
275
|
+
f"{self.inode_address} \\bmod {self.block_size}",
|
|
276
|
+
f"{self.inode_address_in_block}"
|
|
277
|
+
]
|
|
278
|
+
))
|
|
279
|
+
|
|
280
|
+
explanation.add_element(
|
|
281
|
+
ContentAST.Text("Remember that `mod` is the same as `%`, the modulo operation.")
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
explanation.add_element(ContentAST.Paragraph(["and"]))
|
|
285
|
+
|
|
286
|
+
explanation.add_element(ContentAST.Equation.make_block_equation__multiline_equals(
|
|
287
|
+
r"\text{index within block}",
|
|
288
|
+
[
|
|
289
|
+
r"\dfrac{\text{offset within block}}{\text{inode size}}",
|
|
290
|
+
f"\\dfrac{{{self.inode_address_in_block}}}{{{self.inode_size}}}",
|
|
291
|
+
f"{self.inode_index_in_block}"
|
|
292
|
+
]
|
|
293
|
+
))
|
|
294
|
+
|
|
295
|
+
return explanation
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
@QuestionRegistry.register()
|
|
299
|
+
class VSFS_states(IOQuestion):
|
|
300
|
+
|
|
301
|
+
from .ostep13_vsfs import fs as vsfs
|
|
302
|
+
|
|
303
|
+
def __init__(self, *args, **kwargs):
|
|
304
|
+
super().__init__(*args, **kwargs)
|
|
305
|
+
self.answer_kind = Answer.AnswerKind.MULTIPLE_DROPDOWN
|
|
306
|
+
|
|
307
|
+
self.num_steps = kwargs.get("num_steps", 10)
|
|
308
|
+
|
|
309
|
+
def refresh(self, *args, **kwargs):
|
|
310
|
+
super().refresh(*args, **kwargs)
|
|
311
|
+
|
|
312
|
+
fs = self.vsfs(4, 4, self.rng)
|
|
313
|
+
operations = fs.run_for_steps(self.num_steps)
|
|
314
|
+
|
|
315
|
+
self.start_state = operations[-1]["start_state"]
|
|
316
|
+
self.end_state = operations[-1]["end_state"]
|
|
317
|
+
|
|
318
|
+
wrong_answers = list(filter(
|
|
319
|
+
lambda o: o != operations[-1]["cmd"],
|
|
320
|
+
map(
|
|
321
|
+
lambda o: o["cmd"],
|
|
322
|
+
operations
|
|
323
|
+
)
|
|
324
|
+
))
|
|
325
|
+
self.rng.shuffle(wrong_answers)
|
|
326
|
+
|
|
327
|
+
self.answers["answer__cmd"] = Answer(
|
|
328
|
+
"answer__cmd",
|
|
329
|
+
f"{operations[-1]['cmd']}",
|
|
330
|
+
kind=Answer.AnswerKind.MULTIPLE_DROPDOWN,
|
|
331
|
+
correct=True,
|
|
332
|
+
baffles=list(set([op['cmd'] for op in operations[:-1] if op != operations[-1]['cmd']]))
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
def get_body(self) -> ContentAST.Section:
|
|
336
|
+
body = ContentAST.Section()
|
|
337
|
+
|
|
338
|
+
body.add_element(ContentAST.Paragraph(["What operation happens between these two states?"]))
|
|
339
|
+
|
|
340
|
+
body.add_element(
|
|
341
|
+
ContentAST.Code(
|
|
342
|
+
self.start_state,
|
|
343
|
+
make_small=True
|
|
344
|
+
)
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
body.add_element(
|
|
348
|
+
ContentAST.AnswerBlock(
|
|
349
|
+
ContentAST.Answer(
|
|
350
|
+
self.answers["answer__cmd"],
|
|
351
|
+
label="Command"
|
|
352
|
+
)
|
|
353
|
+
)
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
body.add_element(
|
|
357
|
+
ContentAST.Code(
|
|
358
|
+
self.end_state,
|
|
359
|
+
make_small=True
|
|
360
|
+
)
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
return body
|
|
364
|
+
|
|
365
|
+
def get_explanation(self) -> ContentAST.Section:
|
|
366
|
+
explanation = ContentAST.Section()
|
|
367
|
+
|
|
368
|
+
explanation.add_element(
|
|
369
|
+
ContentAST.Paragraph([
|
|
370
|
+
"These questions are based on the VSFS simulator that our book mentions. "
|
|
371
|
+
"We will be discussing the interpretation of this in class, but you can also find information "
|
|
372
|
+
"<a href=\"https://github.com/chyyuu/os_tutorial_lab/blob/master/ostep/ostep13-vsfs.md\">here</a>, "
|
|
373
|
+
"as well as simulator code. Please note that the code uses python 2.",
|
|
374
|
+
"",
|
|
375
|
+
"In general, I recommend looking for differences between the two outputs. Recommended steps would be:",
|
|
376
|
+
"<ol>"
|
|
377
|
+
|
|
378
|
+
"<li> Check to see if there are differences between the bitmaps "
|
|
379
|
+
"that could indicate a file/directroy were created or removed.</li>",
|
|
380
|
+
|
|
381
|
+
"<li>Check the listed inodes to see if any entries have changed. "
|
|
382
|
+
"This might be a new entry entirely or a reference count changing. "
|
|
383
|
+
"If the references increased then this was likely a link or creation, "
|
|
384
|
+
"and if it decreased then it is likely an unlink.</li>",
|
|
385
|
+
|
|
386
|
+
"<li>Look at the data blocks to see if a new entry has "
|
|
387
|
+
"been added to a directory or a new block has been mapped.</li>",
|
|
388
|
+
|
|
389
|
+
"</ol>",
|
|
390
|
+
"These steps can usually help you quickly identify "
|
|
391
|
+
"what has occured in the simulation and key you in to the right answer."
|
|
392
|
+
])
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
return explanation
|
|
396
|
+
|