Files
landing_page/qdrant-landing/themes/qdrant/static/js/benchmarks.js
T
treanandAndrey Vasnetsov 4513883b53 New page for benchmarks with example plots (#70)
* new page for benchmarks with example plots

* wip: cahrt

* add precision selector

* upd

* stats file

* select data for the table

* added a benchmark table

* upd table visualization

* upd bench graph

* benchmarks list layout

* upd benchmark layout + add text

* upd

* upd wording

* typo

* Benchmarks page single page layout (#74)

* all benchmarks in one page

* add faq page

* move how-to-contribute in faq section

* upds

Co-authored-by: Andrey Vasnetsov <andrey@vasnetsov.com>
2022-08-23 14:10:57 +02:00

358 lines
8.1 KiB
JavaScript

/**
*
* Data processing functions
*
*/
let engineToColor = {
redis: '#5961FF',
milvus: '#1493cc',
weaviate: '#01cc26',
qdrant: '#bc1439',
elastic: '#f9b110',
}
let lowerIsBetterMap = {
"rps": false,
"mean_precisions": false,
"mean_time": true,
"p95_time": true,
"p99_time": true,
"upload_time": true,
"total_time": true,
"total_upload_time": true,
}
const normalizedTitles = {
total_time: 'Total Time (s)',
engine_name: 'Engine',
dataset_name: 'Dataset',
rps: 'RPS',
mean_precisions: 'Precision',
total_upload_time: 'Upload + Index Time (s)',
upload_time: 'Upload Time (s)',
mean_time: 'Latency (s)',
p95_time: 'P95 (s)',
p99_time: 'P99 (s)'
}
function extractUniqueVals(data, key) {
let vals = {};
for (const row of data) {
vals[row[key]] = 1;
}
return [...Object.keys(vals)]
}
function getDatasetsList(data) {
return extractUniqueVals(data, 'dataset_name');
}
function getParallelOptions(data) {
return extractUniqueVals(data, 'parallel');
}
function filterData(data, conditions) {
let filtered = [];
for (const row of data) {
let match = true;
for (const key in conditions) {
if (row[key] !== conditions[key]) {
match = false;
break;
}
}
if (match) {
filtered.push(row);
}
}
return filtered;
}
function filterBestPoints(data, lowerIsBetter) {
let filtered = [];
// sort by highest x
data.sort((a, b) => b.x - a.x);
// keep only point with increasing y value
let bestValue = data[0].y;
filtered.push(data[0]);
for (const point of data) {
let isCurrentBest = point.y > bestValue;
if (lowerIsBetter) {
isCurrentBest = point.y < bestValue;
}
if (isCurrentBest) {
filtered.push(point);
bestValue = point.y;
}
}
return filtered;
}
function getPrecisionVsValue(data, key) {
let vals = [];
for (const row of data) {
vals.push({
x: row['mean_precisions'],
y: row[key],
...row
});
}
// sort by x
vals.sort((a, b) => a.x - b.x);
vals = filterBestPoints(vals, lowerIsBetterMap[key])
return vals;
}
function getPlotDataForEngine(data, engine, key) {
let filtered = filterData(data, {"engine_name": engine});
return {
label: engine,
data: getPrecisionVsValue(filtered, key),
parsing: false,
borderColor: engineToColor[engine],
cubicInterpolationMode: 'monotone',
};
}
function getPlotData(data, key) {
let engines = extractUniqueVals(data, "engine_name");
let plotData = [];
for (const engine of engines) {
plotData.push(getPlotDataForEngine(data, engine, key));
}
return plotData;
}
function selectByPrecision(data, minPrecision) {
let filtered = [];
for (const row of data) {
if (row.mean_precisions >= minPrecision) {
filtered.push(row);
}
}
return filtered;
}
function selectDataForTable(data, sortKey) {
let lowerIsBetter = lowerIsBetterMap[sortKey];
let engines = {}
// Select best value per engine
for (const row of data) {
let engine = row.engine_name;
if (!engines[engine]) {
engines[engine] = row;
} else {
let current = engines[engine];
// If engine alredy present, check if current value is better
if (lowerIsBetter) {
if (row[sortKey] < current[sortKey]) {
engines[engine] = row;
}
} else {
if (row[sortKey] > current[sortKey]) {
engines[engine] = row;
}
}
}
}
// get value from engines
let vals = Object.values(engines);
// sort by sortKey
if (lowerIsBetter) {
vals.sort((a, b) => a[sortKey] - b[sortKey]);
} else {
vals.sort((a, b) => b[sortKey] - a[sortKey]);
}
return vals;
}
/**
*
*
* Interface functions
*
*
*/
function updataDropdown(selector, options) {
while (selector.firstChild) {
selector.removeChild(selector.firstChild);
}
for (let i = 0; i < options.length; i++) {
let option = document.createElement("option");
option.value = options[i];
option.text = options[i];
selector.appendChild(option);
}
}
function getSelectedValue(selector) {
return selector.options[selector.selectedIndex].value;
}
function getRadioButtonValue(elements) {
for (let i = 0; i < elements.length; i++) {
if (elements[i].checked) {
return elements[i].value;
}
}
}
function renderPlot(chart, data) {
chart.data.datasets = data;
chart.update();
}
function getSelectedData(chartId) {
let data = window.datasets[chartId];
let datasetSelector = document.getElementById("datasets-selector-" + chartId);
let parallelSelector = document.getElementById("parallel-selector-" + chartId);
let selectedDataset = getSelectedValue(datasetSelector);
let selectedParallel = getSelectedValue(parallelSelector);
let parallelsInt = parseInt(selectedParallel);
let selectedData = filterData(data, {
"dataset_name": selectedDataset,
"parallel": parallelsInt
});
return selectedData;
}
function updateLine(chartId, x) {
let chart = window.charts[chartId];
chart.data.datasets.pop();
drawLine(chart, x);
let plotValueSelector = document.getElementsByName("plot-value-" + chartId);
let selectedPlotValue = getRadioButtonValue(plotValueSelector);
let selectedData = getSelectedData(chartId);
let filteredByPrecision = selectByPrecision(selectedData, x);
let tableData = selectDataForTable(filteredByPrecision, selectedPlotValue);
renderTable(tableData, chartId, selectedPlotValue);
}
function drawLine(chart, x) {
chart.data.datasets.push({
type: 'bar',
label: "Precision cutoff",
maxBarThickness: 2,
backgroundColor: "#ff000033",
parsing: false,
data: [
{x: x, y: chart.scales.y.max, setup_name: "cutoff"}
],
});
chart.update();
}
function renderSelected(chartId) {
let chart = window.charts[chartId];
let plotValueSelector = document.getElementsByName("plot-value-" + chartId);
let precisionSelector = document.getElementById("precision-selector-" + chartId);
let selectedData = getSelectedData(chartId);
let selectedPlotValue = getRadioButtonValue(plotValueSelector);
chart.options.scales.y.title.text = selectedPlotValue;
let plotData = getPlotData(selectedData, selectedPlotValue);
renderPlot(chart, plotData);
let precisionRange = [chart.scales.x.min, chart.scales.x.max];
precisionSelector.min = precisionRange[0];
precisionSelector.max = precisionRange[1];
precisionSelector.step = (precisionRange[1] - precisionRange[0]) / 100;
let precisionValue = precisionRange[0] + (precisionRange[1] - precisionRange[0]) / 2;
precisionSelector.value = precisionValue;
drawLine(chart, precisionValue);
let filteredByPrecision = selectByPrecision(selectedData, precisionValue);
let tableData = selectDataForTable(filteredByPrecision, selectedPlotValue);
renderTable(tableData, chartId, selectedPlotValue);
}
const renderTable = function (tableData, chartId, selectedPlotValue) {
if (!tableData[0]) {
return;
}
let titles = Object.keys(tableData[0]);
const titleElements = titles.map(title => {
let normTitle = title;
if (normalizedTitles.hasOwnProperty(title)) {
normTitle = normalizedTitles[title];
}
return `<th scope="col">${normTitle}</th>`;
});
const rows = tableData.map(obj => {
const row = Object.values(obj).map((value, i) => {
if (typeof value === 'object') {
value = JSON.stringify(value)
}
// check for float point
if (typeof value === 'number' && Math.trunc(value) !== value) {
value = value.toFixed(20).match(/^-?\d*\.?0*\d{0,2}/)[0];
}
if (titles[i] === selectedPlotValue) {
return `<th scope="row">${value}</th>`;
}
return `<td>${value}</td>`;
});
return `<tr>${row.join('')}</tr>`;
});
const table = document.createElement('table');
table.classList.add('table', 'table-striped', 'table-responsive', 'table-sm');
table.innerHTML = `<thead><tr>${titleElements.join('')}</tr></thead><tbody>${rows.join('')}</tbody>`;
if (document.getElementById('table-' + chartId).querySelector('.table')) {
document.getElementById('table-' + chartId).querySelector('.table').remove();
}
document.getElementById('table-' + chartId).append(table)
}