diff --git a/modules/performance-statistics-ext/report/js/systemViewTab.js b/modules/performance-statistics-ext/report/js/systemViewTab.js index 20ba62803..a18b330d8 100644 --- a/modules/performance-statistics-ext/report/js/systemViewTab.js +++ b/modules/performance-statistics-ext/report/js/systemViewTab.js @@ -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() { @@ -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); diff --git a/modules/performance-statistics-ext/report/js/utils.js b/modules/performance-statistics-ext/report/js/utils.js index 0e0f20b3d..7d0fdb421 100644 --- a/modules/performance-statistics-ext/report/js/utils.js +++ b/modules/performance-statistics-ext/report/js/utils.js @@ -89,17 +89,14 @@ function buildSelectNodes(el, onSelect) { function buildSelectNodesSystemView(el, onSelect) { el.append(''); - Object.keys(REPORT_DATA['systemView']).forEach(nodeId => - el.append('')); + REPORT_DATA.systemViewMeta.nodes.forEach(nodeId => el.append('')); 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('')); + REPORT_DATA.systemViewMeta.views.forEach(view => el.append('')); el.on('changed.bs.select', onSelect); } diff --git a/modules/performance-statistics-ext/src/main/java/org/apache/ignite/internal/performancestatistics/PerformanceStatisticsReportBuilder.java b/modules/performance-statistics-ext/src/main/java/org/apache/ignite/internal/performancestatistics/PerformanceStatisticsReportBuilder.java index fdff05d4f..7fc56d10a 100644 --- a/modules/performance-statistics-ext/src/main/java/org/apache/ignite/internal/performancestatistics/PerformanceStatisticsReportBuilder.java +++ b/modules/performance-statistics-ext/src/main/java/org/apache/ignite/internal/performancestatistics/PerformanceStatisticsReportBuilder.java @@ -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))); diff --git a/modules/performance-statistics-ext/src/main/java/org/apache/ignite/internal/performancestatistics/handlers/SystemViewHandler.java b/modules/performance-statistics-ext/src/main/java/org/apache/ignite/internal/performancestatistics/handlers/SystemViewHandler.java index d51cf953e..c649058bf 100644 --- a/modules/performance-statistics-ext/src/main/java/org/apache/ignite/internal/performancestatistics/handlers/SystemViewHandler.java +++ b/modules/performance-statistics-ext/src/main/java/org/apache/ignite/internal/performancestatistics/handlers/SystemViewHandler.java @@ -17,10 +17,20 @@ package org.apache.ignite.internal.performancestatistics.handlers; +import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.TreeMap; import java.util.UUID; +import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; @@ -28,58 +38,217 @@ import static org.apache.ignite.internal.performancestatistics.util.Utils.MAPPER; /** - * Builds JSON with system view tables. - *
- * Example: - *
- * {
- * $nodeId: {
- * $systemViewTable:
- * schema: [ $column1, $column2 ],
- * data:[
- * [ $value1, $value2 ],
- * [ $value3, $value4 ]
- * ]
- * }
- * }
- *
+ * Writes system view tables into chunked JSON files.
*/
public class SystemViewHandler implements IgnitePerformanceStatisticsHandler {
+ /** Number of rows per chunk. */
+ private static final int CHUNK_SIZE = 2_000;
+
+ /** Base directory for system view data. */
+ private final Path baseDir;
+
+ /** Per-view state in traversal order. */
+ private final Map