api/ui/debug/js/batch.js (350 lines of code) (raw):
// Copyright (c) 2017-2018 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
if (!String.prototype.format) {
String.prototype.format = function () {
var args = [].slice.call(arguments);
return this.replace(/(\{\d+\})/g, function (a) {
return args[+(a.substr(1, a.length - 2)) || 0];
});
};
}
const maxRowsPerPage = 100;
const pagesToShow = 10;
const maxValueLength = 100;
var isFactTable = true;
var ownedShards = [];
var currentBatchID = 0;
function Iterator(column, numRows) {
this.values = column.values;
this.counts = column.counts;
if (!this.values || !this.counts.length || !this.counts || !this.counts.length) {
this.values = [null];
this.counts = [numRows];
}
this.currentRow = 0;
this.currentIndex = 0;
}
Iterator.prototype.shouldSkip = function () {
return this.currentRow !== 0 && this.currentRow !== this.counts[this.currentIndex - 1];
};
Iterator.prototype.advance = function () {
this.currentRow++;
if (this.currentRow === this.counts[this.currentIndex]) {
this.currentIndex++;
}
};
Iterator.prototype.read = function () {
return {
value: this.values[this.currentIndex] === 'undefined' ? 'NULL' : this.values[this.currentIndex],
count: this.currentIndex === 0 ? this.counts[this.currentIndex] : this.counts[this.currentIndex] - this.counts[this.currentIndex - 1]
}
};
function renderTables(tables) {
var tableSelect = $('#table-select');
tables.forEach(function (table, id) {
if (id == 0) {
listShards(function (shards) {
ownedShards = shards;
getTableSchema(table);
});
}
tableSelect.append('<option value' + '=' + id + '>' + table + '</option>');
});
tableSelect.change(function () {
var currentTableName = $(this).find(":selected").text();
getTableSchema(currentTableName);
});
}
function renderShards(shards) {
var shardSelect = $('#shard-select');
shardSelect.empty();
shards.forEach(function (shard, id) {
shardSelect.append('<option value' + '=' + id + '>' + shard + '</option>');
});
}
function getBatchSize(shard, id) {
var batch = shard.liveStore.batches[id];
if (shard.liveStore.lastReadRecord.batchID == id) {
return shard.liveStore.lastReadRecord.index;
}
return batch.capacity;
}
function renderShard(shard) {
console.log(shard);
var batchTableBody = $('#batch-table-body');
batchTableBody.empty();
Object.keys(shard.liveStore.batches).forEach(function (id) {
var batchSize = getBatchSize(shard, id);
batchTableBody.append('<tr id="batch-tr-{0}" class="clickable" onclick="loadBatchTable({1}, {2})"><td>{3}</td><td>{4}</td></tr>'.format(id, id, batchSize, id, batchSize));
});
Object.keys(shard.archiveStore.currentVersion.batches).forEach(function (id) {
var batch = shard.archiveStore.currentVersion.batches[id];
var date = new Date(parseInt(id, 10) * 86400000)
var idStr = '{0} {1}-{2}-{3}'.format(id, date.getUTCFullYear(), date.getUTCMonth() + 1, date.getUTCDate());
batchTableBody.append('<tr id="batch-tr-{0}" class="clickable" onclick="loadBatchTable({1}, {2})"><td nowrap>{3}</td><td>{4}</td></tr>'.format(id, id, batch.size, idStr, batch.size));
});
var numLiveRecords = shard.liveStore.lastReadRecord.index + shard.liveStore.batchSize * Object.keys(shard.liveStore.batches).filter(function (id) {
return id != shard.liveStore.lastReadRecord.batchID;
}).length;
var primaryKeyView = $('#primary-key-view');
primaryKeyView.empty();
var primaryKeyStats = $('<table></table>');
primaryKeyStats.append('<tr><td><b>Primary Key: </b></td></tr>');
primaryKeyStats.append('<tr><td>' +
' <label for="primary-key-lookup-input">Primary Key Lookup: </label></td><td><input class="small-input" id="primary-key-lookup-input" type="text"/></td></tr>')
primaryKeyStats.append('<tr><td>Allocated Memory (MB):</td><td>{0}</td></tr>'.format(shard.liveStore.primaryKey.allocatedBytes / 1024 / 1024));
primaryKeyStats.append('<tr><td>Percentage:</td><td>{0}% ({1} / {2})</td></tr>'.format(numLiveRecords * 100 / shard.liveStore.primaryKey.capacity, numLiveRecords, shard.liveStore.primaryKey.capacity));
primaryKeyStats.append('<tr><td>Cutoff:</td><td>{0}</td></tr>'.format(new Date(shard.liveStore.primaryKey.eventTimeCutoff * 1000).toLocaleString()));
primaryKeyView.append(primaryKeyStats);
var pkLookupInput = $('#primary-key-lookup-input')
pkLookupInput.keyup(function (e) {
if (e.keyCode === 13) {
var uuid = $('#primary-key-lookup-input').val();
var currentTableName = $('#table-select').find(":selected").text();
var currentShardID = $('#shard-select').find(":selected").text();
$.ajax({
url: '/dbg/{0}/{1}/primary-keys?key={2}'.format(currentTableName, currentShardID, uuid),
dataType: 'json',
success: function (recordID) {
var batchID = recordID.batchID;
var row = recordID.index;
var batchSize = getBatchSize(shard, batchID);
jumpToRowOfBatch(row, batchID, batchSize, true);
},
error: function (xhr) {
alert(xhr.responseText);
}
});
}
});
}
function getPolygonUrl(polygonStr) {
var pointStrs = polygonStr.replace("Polygon", "").split(",");
var sumLat = 0;
var sumLong = 0;
var path = "";
for (var i in pointStrs) {
var pointStr = pointStrs[i];
var point = pointStr.replace(/[\(\)]/g, "").split("+");
var lat = parseFloat(point[1]);
var long = parseFloat(point[0]);
path += '|{0},{1}'.format(lat, long);
sumLat += lat;
sumLong += long;
}
var avgLat = sumLat / pointStrs.length;
var avgLong = sumLong / pointStrs.length;
return 'http://maps.googleapis.com/maps/api/staticmap?center={0},{1}&size=2000x2000&zoom=10&sensor=false&path=color:0xff0000ff|weight:5{2}'.format(avgLat, avgLong, path);
}
function renderBatch(highlightRowNumber) {
return function (json) {
var header = document.getElementById('table-header');
var html = '<th>Row Number</th>';
for (var i = 0; i < json.columns.length; i++) {
if (json.deleted !== null && json.deleted.includes(i)) {
html += '<th class="deleted-column data-column">' + json.columns[i] + '<br>' + json.types[i] + '</th>';
} else {
html += '<th class="data-column">' + json.columns[i] + '<br>' + json.types[i] + '</th>';
}
}
header.innerHTML = html;
var iterators = json.columns.map(function (columnName, id) {
if (id < json.vectors.length) {
return new Iterator(json.vectors[id], json.numRows)
;
} else {
return new Iterator({}, json.numRows);
}
});
var body = document.getElementById('table-data');
html = '';
for (var r = 0; r < json.numRows; r++) {
var rowCls = "";
if (r == highlightRowNumber) {
rowCls = "highlight";
}
html += '<tr class="{0}"><td class="data-column">'.format(rowCls) + (r + json.startRow) + '</td>';
for (var c = 0; c < iterators.length; c++) {
if (!iterators[c].shouldSkip()) {
var valueCount = iterators[c].read();
var ts = '';
if (c == 0 && json.types[0] == 'Uint32') {
ts = '<br>';
ts += new Date(valueCount.value * 1000).toLocaleString();
}
var fullValue = '' + valueCount.value;
if (fullValue.length > maxValueLength) {
html += '<td class="data-column short-value-column" data-value="{0}" rowspan="{1}">{2}</td>'.format(fullValue, valueCount.count, fullValue.substr(0, maxValueLength) + ts);
} else {
html += '<td class="data-column" rowspan="{0}">{1}</td>'.format(valueCount.count, fullValue + ts);
}
}
iterators[c].advance();
}
html += '</tr>';
}
body.innerHTML = html;
// add popup for shortened value
var shortColumns = document.getElementsByClassName("short-value-column");
for (var j = 0; j < shortColumns.length; j++) {
shortColumns[j].addEventListener("click", function () {
var fullValue = (this).getAttribute("data-value");
if (fullValue.startsWith("Polygon")) {
var url = getPolygonUrl(fullValue);
var win = window.open(url, '_blank');
win.focus();
} else {
alert(fullValue);
}
});
}
// populate column select
renderColumns(json.columns);
};
}
function jumpToRowOfBatch(rowNumber, batchID, batchSize, highlight) {
var prevBatchID = currentBatchID
currentBatchID = batchID;
if (currentBatchID != prevBatchID) {
var prevElem = $("#batch-tr-{0}".format(prevBatchID))
if (prevElem.length && prevElem.hasClass("highlight")) {
prevElem.removeClass("highlight");
}
var curElem = $("#batch-tr-{0}".format(currentBatchID))
if (curElem.length && !curElem.hasClass("highlight")) {
curElem.addClass("highlight");
}
}
var maxPage = Math.floor(batchSize / maxRowsPerPage) + 1;
var startPage = Math.max(0, Math.floor(rowNumber / maxRowsPerPage));
setPages(startPage, startPage + pagesToShow, maxPage);
var highlightRowNumber = rowNumber % maxRowsPerPage;
if (!highlight) {
highlightRowNumber = -1;
}
loadBatch(startPage * maxRowsPerPage, maxRowsPerPage, highlightRowNumber);
}
function loadBatchTable(batchID, batchSize) {
$('#row-jump').show();
var rowNumberInput = $('#row-number-input');
rowNumberInput.focus();
rowNumberInput.keyup(function (e) {
if (e.keyCode === 13) {
var rowNumber = $('#row-number-input').val();
jumpToRowOfBatch(rowNumber, batchID, batchSize, false);
}
});
jumpToRowOfBatch(0, batchID, batchSize, false);
}
function setPages(startPage, endPage, maxPage) {
var pagination = $('#batch-pagination');
pagination.empty();
pagination.append('<a href="#" onclick="setPages({0},{1},{2})">«</a>'.format(Math.max(startPage - pagesToShow, 0), Math.max(pagesToShow, startPage), maxPage));
for (var i = startPage; i < Math.min(endPage, maxPage); i++) {
pagination.append('<a href="#" onclick="loadBatch({0},{1},{2})">{3}</a>'.format(i * maxRowsPerPage, maxRowsPerPage, -1, i + 1));
}
pagination.append('<a href="#" onclick="setPages({0},{1},{2})">»</a>'.format(endPage, endPage + pagesToShow, maxPage));
}
function renderColumns(columns) {
var columnSelect = $('#column-select');
columnSelect.empty();
columns.forEach(function (column, id) {
columnSelect.append('<option value' + '=' + id + '>' + column + '</option>');
});
}
function loadBatch(startRow, numRows, highlightRowNumber) {
var currentTableName = $('#table-select').find(":selected").text();
var currentShardID = $('#shard-select').find(":selected").text();
$.getJSON(
'/dbg/{0}/{1}/batches/{2}?startRow={3}&numRows={4}'.format(currentTableName, currentShardID, currentBatchID, startRow, numRows),
{},
renderBatch(highlightRowNumber)
);
}
function listBatches() {
var currentTableName = $('#table-select').find(":selected").text();
var currentShardID = $('#shard-select').find(":selected").text();
$.getJSON(
'/dbg/{0}/{1}'.format(currentTableName, currentShardID),
{},
renderShard
);
}
function listTables() {
$.getJSON(
'/schema/tables',
{},
renderTables
);
}
function listShards(call) {
$.getJSON(
'/dbg/shards',
{},
call,
);
}
function getTableSchema(table) {
$.getJSON(
'/schema/tables/{0}'.format(table),
{},
function (schema) {
isFactTable = schema.isFactTable;
var columns = schema.columns.map(function (column) {
return column.name;
});
renderColumns(columns);
if (isFactTable) {
renderShards(ownedShards);
} else {
renderShards([0]);
}
},
);
}
function loadVectorParty(batchID, columnName) {
var currentTableName = $('#table-select').find(":selected").text();
var currentShardID = $('#shard-select').find(":selected").text();
$.ajax({
url: '/dbg/{0}/{1}/batches/{2}/vector-parties/{3}'.format(currentTableName, currentShardID, batchID, columnName)
}).done(listBatches);
}
$(document).ready(function () {
$('#row-jump').hide();
initShardPicker();
initBatchLoader();
listTables();
});
function initShardPicker() {
var shardPicker = $('#shard-pick');
shardPicker.append('<label for="table-select">Table: </label>');
shardPicker.append('<select id="table-select"></select>');
shardPicker.append('<label for="shard-select">Shard: </label>');
shardPicker.append('<select id="shard-select"></select>');
var shardPickButton = $('<button class="medium-button">Submit</button>');
shardPickButton.click(function (event) {
listBatches();
});
shardPicker.append(shardPickButton);
}
function initBatchLoader() {
var batchInput = $('<input class="small-input" id="time-picker"/>').datetimepicker();
var columnSelect = $('<select id="column-select"/>');
var vpLoadButton = $('<button class="medium-button">Load</button>').click(function () {
var time = batchInput.val();
if (!time) {
alert("No batch id specified!");
return
}
var batchID = Math.floor(new Date(time).getTime() / 1000 / 86400);
var column = $('#column-select').find(":selected").text();
if (!column) {
alert("no column selected!");
return
}
loadVectorParty(batchID, column);
});
var batchLoad = $('#batch-load');
batchLoad.append();
batchLoad.append('<label for="time-picker">BatchID: </label>');
batchLoad.append(batchInput);
batchLoad.append('<label for="column-select">Column: </label>');
batchLoad.append(columnSelect);
batchLoad.append(vpLoadButton);
}