async function queriesPlanner({
comb = defaultCombinations,
runFn,
sleepTime = 1000,
attributes = [],
log = [],
storageClient,
collectionName = "queryPlanner",
dbName = "benchmark-all",
datasetsAccessor,
customDataFlag = false,
defaultExpName = "defaultExperiment"
} = {}) {
let useCustomCombinations = Inputs.toggle({
label: "Custom Query combinations"
});
let experimentName = Inputs.text({
label: "Experiment Name",
value: defaultExpName
});
let customCombinations;
let noOfExperiments = Inputs.range([0, 100], {
step: 1,
label: "Repeat experiment",
value: 1
});
let queryCounter = { success: 0, failed: 0 };
let datasets_ = datasetsAccessor.getNames();
let defaultDatasets = [datasets_[2]];
console.log({ defaultDatasets });
if (customDataFlag) {
defaultDatasets[0] = "custom-data";
}
let selectDatasets = Inputs.checkbox(datasets_, {
label: "Benchmark datasets",
value: defaultDatasets
});
let generateQueriesBtn = Inputs.button("Create Experiment");
let status = html`<details style="max-width: 600px; background: #fffced; box-sizing: border-box; padding: 10px 20px;"><summary style="font-weight: bold; cursor: pointer; outline: none;">Log</details>`;
let target = html`<div style='display:flex;flex-direction:column; gap: 15px;'>${experimentName} ${selectDatasets} ${useCustomCombinations} ${noOfExperiments} ${generateQueriesBtn} ${status}</div>`;
target.log = log;
let statusLog = html``;
let queries_ = [];
let statusLock = false;
let queriesStatus = html``;
target.queries = {};
//Toggle custom combinations
useCustomCombinations.addEventListener("input", (event) => {
if (event.target.type != "checkbox") {
return;
}
if (useCustomCombinations.value) {
//Disable at start
customCombinations = htl.html`<div style='display:flex;flex-direction:column'> ${Inputs.textarea(
{
placeholder: `Ex : ["1C-RV", "1C-@V", "1C-NV", "1Q-@V", "1Q-NV", "1Q-RV", "1Q-RV+1C-@V"]`
}
)} <div> For more information on query combinations please check <a href='https://observablehq.com/@kasivisu4/queries-generator?collection=@kasivisu4/benchmark-tools'>here</a></div>`;
useCustomCombinations.appendChild(customCombinations);
} else {
customCombinations.remove();
}
});
async function runFn({ noOfExperiments, comb, datasets }) {
console.log({ comb });
log.push({ msg: `Query Planner: Started`, level: 1 });
log.push({
msg: `Query Planner: Running for ${JSON.stringify(datasets)}`,
level: 1
});
log.push({
msg: `Query Planner: Combinations ${JSON.stringify(comb)}`,
level: 1
});
for (let dataset of datasets) {
try {
log.push({ msg: `Query Planner: Running for ${dataset}`, level: 1 });
let res = datasetsAccessor;
await res.selectDataset(dataset)();
let data = res.datasets[dataset];
log.push({
msg: `Query Planner : Fetched ${dataset} with recordcount ${
data.length
}. The record count Provided is ${datasetsAccessor.getRecordCount()}`,
level: 1
});
let queries = queriesGenerator(data, {
queryCombinations: comb,
replicaCount: noOfExperiments,
attributes
});
log.push({
msg: `Query Planner: Generated ${
queries?.length || 0
} queries for ${dataset}`,
level: 1
});
log.push({
msg: `Query Planner: Storing ${
queries?.length || 0
} queries for ${dataset} in ${storageClient?.name}`,
level: 1
});
for (let query of queries) {
let comb = Object.keys(query)[0];
let q = Object.fromEntries(query[comb]);
let res = {
comb,
query: q,
dataset,
experimentName: experimentName.value,
queryId: queryCounter.success + 1
};
let response;
if (storageClient) {
response = await storageClient({
db: dbName,
collection: collectionName,
operation: "insert",
data: [res]
});
if (response?.acknowledged) {
queryCounter.success += 1;
} else {
queryCounter.failed += 1;
log.push({
msg: `Query Planner: Storing failed due to ${response} `,
level: 0
});
}
} else {
queries_.push(res);
queryCounter.success += 1;
}
}
log.push({
msg: `Query Planner: Storing Completed ${
queries?.length || 0
} queries for ${dataset} in ${
storageClient ? storageClient : "local"
}`,
level: 1
});
//Store the queries in case of no storage client
if (!storageClient) {
target.queries[experimentName.value] = queries_;
}
} catch (err) {
log.push({
msg: `Query Planner: Failed for ${dataset} with error ${err} `,
level: 0
});
console.error(
`Query Planner: Failed for ${dataset} with error ${err} `
);
}
}
log.push({
msg: `Query Planner: Execution Completed | Success ${queryCounter.success} | Failed ${queryCounter.failed}`,
level: 1
});
}
async function updateStatus() {
while (statusLock) {
//Queries
queriesStatus?.remove();
queriesStatus = htl.html`Running... | Generated ${queryCounter.success} | Failed ${queryCounter.failed}`;
target.appendChild(queriesStatus);
// Log
statusLog?.remove();
statusLog = html`<div style='font-size:15px'>${[
...log.filter((d) => d.level < 2).slice(-10)
]
.reverse()
.map((d, i) => (d.msg.length > 50 ? d.msg.slice(0, 50) + "..." : d.msg))
.join("<br>")}</div>`;
status.appendChild(statusLog);
await new Promise((d) => setTimeout(d, sleepTime));
}
}
updateStatus();
generateQueriesBtn.addEventListener("click", async () => {
generateQueriesBtn.childNodes[0].disabled = true;
statusLock = true;
updateStatus();
await runFn({
noOfExperiments: noOfExperiments.value,
comb: useCustomCombinations.value
? JSON.parse(customCombinations.querySelector("form").value)
: comb,
datasets: selectDatasets.value
});
target.log = log;
//Update the log and status before lock
updateStatus();
await new Promise((d) => setTimeout(d, sleepTime));
statusLock = false;
generateQueriesBtn.childNodes[0].disabled = false;
//Queries
queriesStatus?.remove();
queriesStatus = htl.html`Query Planner Execution Completed | Success ${queryCounter.success} | Failed ${queryCounter.failed}`;
target.appendChild(queriesStatus);
target.dispatchEvent(new Event("input", { bubbles: true }));
});
return target;
}