Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 146 additions & 41 deletions modules/performance-statistics-ext/report/js/systemViewTab.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,45 +18,117 @@
const sysViewSearchNodesSelect = $('#sysViewSearchNodes');
const searchViewsSelect = $('#searchViews');

function generateColumns(viewName, nodeId) {
const keys = [];
function getSystemViewMeta(viewName) {
return `data/system-views/${viewName}/meta.json`;
}

if (nodeId === "total")
keys.push("viewNodeId");

const hasSchema = Object.values(REPORT_DATA['systemView']).some(nodeData => {
if (nodeData[viewName]) {
keys.push(...nodeData[viewName]['schema']);
return true;
}
return false;
function getSystemViewChunk(viewName, nodeId, chunkIdx) {
const chunkName = String(chunkIdx).padStart(6, '0');
return `data/system-views/${viewName}/${nodeId}/chunk-${chunkName}.json`;
}

function loadSystemViewMeta(viewName) {
return $.getJSON(getSystemViewMeta(viewName)).then(meta => {
meta.nodeOrder = Object.keys(meta.nodes).sort();
meta.totalCount = meta.nodeOrder.reduce((sum, nodeId) => sum + Number(meta.nodes[nodeId]), 0);
meta.totalSegments = buildTotalSegments(meta);
return meta;
});
}

function loadSystemViewChunk(viewName, nodeId, chunkIdx) {
return $.getJSON(getSystemViewChunk(viewName, nodeId, chunkIdx)).then(chunk => {
const rows = (chunk && Array.isArray(chunk.rows)) ? chunk.rows : [];

return rows;
});
}

function buildTotalSegments(meta) {
let offset = 0;

return meta.nodeOrder.map(nodeId => {
const count = Number(meta.nodes[nodeId]);
const start = offset;
const end = offset + count;

offset = end;

return { nodeId, start, end };
}).filter(segment => segment.end > segment.start);
}

function fetchRowsForRange(viewName, nodeId, meta, start, end) {
if (end <= start)
return Promise.resolve([]);

const chunkSize = meta.chunkSize;
const startChunk = Math.floor(start / chunkSize);
const endChunk = Math.floor((end - 1) / chunkSize);
const chunkRequests = [];

for (let idx = startChunk; idx <= endChunk; idx++)
chunkRequests.push(loadSystemViewChunk(viewName, nodeId, idx));

return Promise.all(chunkRequests).then(chunks => {
const rows = [];

if (!hasSchema)
return null;
chunks.forEach((chunkRows, index) => {
const chunkIdx = startChunk + index;
const chunkStart = chunkIdx * chunkSize;
const sliceStart = Math.max(start, chunkStart) - chunkStart;
const sliceEnd = Math.min(end, chunkStart + chunkRows.length) - chunkStart;

return keys.map((key, index) => ({
field: index,
title: key,
sortable: true
}));
if (sliceEnd > sliceStart)
rows.push(...chunkRows.slice(sliceStart, sliceEnd));
});

return rows;
});
}

function generateRows(viewName, nodeId) {
if (nodeId !== "total") {
const view = REPORT_DATA['systemView'][nodeId][viewName];
if (view)
return view['data'];
function fetchTotalRows(viewName, meta, start, end) {
if (end <= start)
return Promise.resolve([]);

const requests = [];

meta.totalSegments.forEach(segment => {
if (end <= segment.start || start >= segment.end)
return;

const localStart = Math.max(start, segment.start) - segment.start;
const localEnd = Math.min(end, segment.end) - segment.start;

requests.push({ nodeId: segment.nodeId, localStart, localEnd });
});

return Promise.all(requests.map(req => {
return fetchRowsForRange(viewName, req.nodeId, meta, req.localStart, req.localEnd)
.then(rows => rows.map(row => [req.nodeId, ...row]));
})).then(chunks => chunks.flat());
}

function generateColumns(meta, nodeId) {
if (!meta || !Array.isArray(meta.columns) || meta.columns.length === 0)
return [];
}

return Object.entries(REPORT_DATA['systemView']).flatMap(([nodeId, nodeData]) => {
if (!nodeData[viewName])
return [];
const columns = [];

if (nodeId === "total")
columns.push({ field: 0, title: "nodeId", sortable: false });

return nodeData[viewName]['data'].map(row => [nodeId, ...row]);
meta.columns.forEach((key, index) => {
const field = nodeId === "total" ? index + 1 : index;

columns.push({
field: field,
title: key,
sortable: false
});
});

return columns;
}

function drawSystemViewsTable() {
Expand All @@ -66,22 +138,55 @@ function drawSystemViewsTable() {
const nodeId = sysViewSearchNodesSelect.val();
const viewName = searchViewsSelect.val();

const columns = generateColumns(viewName, nodeId);
const rows = generateRows(viewName, nodeId);
loadSystemViewMeta(viewName).then(viewMeta => {
const columns = generateColumns(viewMeta, nodeId);
const table = getOrCreateSystemViewTable(div);
const totalRows = nodeId === "total" ? viewMeta.totalCount : viewMeta.nodes[nodeId];

$(table).bootstrapTable({
formatNoMatches: () => `The "${viewName}" system view is empty on node "${nodeId}".`,
pagination: true,
sidePagination: "server",
search: false,
columns: columns,
sortOrder: 'desc',
ajax: function (params) {
const limit = params.data.limit;
const offset = params.data.offset;
const end = Math.min(offset + limit, totalRows);

if (totalRows === 0) {
params.success({total: 0, rows: []});
return;
}

const loader = nodeId === "total"
? fetchTotalRows(viewName, viewMeta, offset, end)
: fetchRowsForRange(viewName, nodeId, viewMeta, offset, end);

loader.then(rows => {
console.log('[systemView] Loaded rows', {viewName, nodeId, count: rows.length});
params.success({total: totalRows, rows: rows});
}).catch(err => {
console.error('[systemView] Failed to load rows', viewName, nodeId, err);
params.error(err);
});
}
});
});
}

const table = document.createElement('table');
function getOrCreateSystemViewTable(div) {
let table = document.getElementById('systemViewTable');

table.id = 'systemViewTable';
div.appendChild(table);
if (!table) {
div.innerHTML = "";
table = document.createElement('table');
table.id = 'systemViewTable';
div.appendChild(table);
}

$(table).bootstrapTable({
formatNoMatches: () => `The "${viewName}" system view is empty on node "${nodeId}".`,
pagination: true,
search: true,
columns: columns,
data: rows,
sortOrder: 'desc'
});
return table;
}

buildSelectNodesSystemView(sysViewSearchNodesSelect, drawSystemViewsTable);
Expand Down
7 changes: 2 additions & 5 deletions modules/performance-statistics-ext/report/js/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,17 +89,14 @@ function buildSelectNodes(el, onSelect) {
function buildSelectNodesSystemView(el, onSelect) {
el.append('<option data-content="<b>All nodes</b>" value="total"/>');

Object.keys(REPORT_DATA['systemView']).forEach(nodeId =>
el.append('<option data-content="' + nodeId + '" value="' + nodeId + '"/>'));
REPORT_DATA.systemViewMeta.nodes.forEach(nodeId => el.append('<option data-content="' + nodeId + '" value="' + nodeId + '"/>'));

el.on('changed.bs.select', onSelect);
}

/** Builds bootstrap-select for system views. */
function buildSelectSystemViews(el, onSelect) {
const views = new Set(Object.values(REPORT_DATA['systemView']).flatMap(nodeData => Object.keys(nodeData)));

views.forEach(view => el.append('<option data-content="' + view + '" value="' + view + '"/>'));
REPORT_DATA.systemViewMeta.views.forEach(view => el.append('<option data-content="' + view + '" value="' + view + '"/>'));

el.on('changed.bs.select', onSelect);
}
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ private static void createReport(String filesDir, String resDir) throws Exceptio
new TransactionsHandler(),
new ComputeHandler(),
new ClusterInfoHandler(),
new SystemViewHandler()
new SystemViewHandler(new File(resDir, "data/system-views").toPath())
};

new FilePerformanceStatisticsReader(handlers).read(Collections.singletonList(new File(filesDir)));
Expand Down
Loading