Skip to content

Instantly share code, notes, and snippets.

@netj
Created July 27, 2012 07:52
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save netj/3186715 to your computer and use it in GitHub Desktop.
Save netj/3186715 to your computer and use it in GitHub Desktop.
Expand all Hadoop MapReduce Task logs — Checking task logs from Hadoop MapReduce web UI is a pain. This bookmarklet saves a lot of clicks! Simply run this once from any page showing a table of tasks, and you'll see every task log on the same page.
/* Created with YouScript Bookmarklet Editor
* http://netj.github.com/youscript/
*/
/*
Checking task logs from Hadoop MapReduce web UI is a pain. This bookmarklet saves a lot of clicks! Simply run this once from any page showing a table of tasks, and you'll see every task log on the same page. You can specify the amount of log to tail, and this will refresh every 30s. To circumvent web security, you need to run node-jsonp-proxy on your machine, available at: https://github.com/clintandrewhall/node-jsonp-proxy
*/
var topRowsToExpand = localStorage.expandTaskLogs_topRowsToExpand || 100;
var tasklogFilter = localStorage.expandTaskLogs_tasklogFilter || "syslog";
var offsetKB = localStorage.expandTaskLogs_offsetKB || 1;
var updateIntervalSecs = localStorage.expandTaskLogs_updateIntervalSecs || 30;
var jsonpProxyURL = localStorage.expandTaskLogs_jsonpProxyURL || "http://localhost:8001/?url=$url&format=text&jsonp=?";
topRowsToExpand = prompt("How many rows do you want to expand from the top?\n You can click on individual rows to toggle them later.", topRowsToExpand);
if (topRowsToExpand) {
localStorage.expandTaskLogs_topRowsToExpand = topRowsToExpand = parseInt(topRowsToExpand);
function showError() {
if (window.updateLogsT != null) window.updateLogsT = clearTimeout(window.updateLogsT);
console.error.apply(console, arguments);
if (window.shownError) return;
alert("You may need to run a JSON-P proxy on your machine!\n" +
"Install it by running:\n\n" +
" git clone https://github.com/clintandrewhall/node-jsonp-proxy.git\n" +
" cd node-jsonp-proxy\n" +
" node server.js\n" +
"\n");
window.shownError = true;
}
function getPage(url, next) {
var t = setTimeout(showError, Math.max(30, 0.9*updateIntervalSecs)*1000);
if (url)
return $.ajax({
url: jsonpProxyURL.replace("$url", encodeURIComponent(url)),
dataType: "jsonp",
success: function(data, status) { t = clearTimeout(t); return next(data, status); },
error: showError
});
else
console.error("no url");
}
function textMatch(re) { return function(){ return $(this).text().match(re); }; }
function updateLogs() {
if (window.updateLogsT != null) window.updateLogsT = clearTimeout(window.updateLogsT);
var $table = $("table").first();
var isViewingTaskListPage = $table.find("a").filter(textMatch(/^Last 4KB$/)).length == 0;
var expandRow;
if ($("#expandLogsUI").length == 0) {
$("<style>")
.html("#expandLogsUI { background-color: #F9F862; border: 1px solid #999; border-radius: 10px; box-shadow: 0px 5px 10px #333; padding: 10px; width: 700px; position: fixed; top: 10px; right: 10px; opacity: 0.25; }" +
"#expandLogsUI:hover { opacity: 1; }" +
"#expandLogsUI + table { margin-top: 20px; }" +
"#expandLogsUI span { margin: auto 10px; }" +
"#expandLogsUI input, #expandLogsUI button, #expandLogsUI select { font-size: inherit; }" +
"tr { background-color: #888; cursor: s-resize; }" +
"tr.expanded { background-color: initial; cursor: n-resize; }" +
"tr.tasklog { background-color: initial; cursor: initial; }" +
".tasklog .status { color: #f30; }" +
"")
.appendTo("head");
$("<div>").attr("id", "expandLogsUI")
.append($("<span>").append(
$("<select>").attr("id", "tasklogFilter")
.append($("<option>").text("stdout"))
.append($("<option>").text("stderr"))
.append($("<option>").text("syslog"))
.val(tasklogFilter)
.change(updateLogs)
.before(
$("<label>").attr("for", "tasklogFilter")
.text("Expand ")
)
)
)
.append($("<span>").append(
$("<input>").attr("id", "offsetKB")
.attr("size", 5)
.val(offsetKB)
.change(updateLogs)
.before(
$("<label>").attr("for", "offsetKB")
.text("tailing ")
)
.after("KB")
)
)
.append($("<span>").append(
$("<input>").attr("id", "updateIntervalSecs")
.attr("size", 5)
.val(updateIntervalSecs)
.change(updateLogs)
.before(
$("<label>").attr("for", "updateIntervalSecs")
.text("updating every ")
)
.after("secs")
)
)
.append($("<span>").append(
$("<button>").attr("id", "updateLogs")
.text("Refresh")
.click(updateLogs)
)
)
.append("<hr>")
.append($("<div>").css("font-size", "75%").append(
$("<input>").attr("id", "jsonpProxyURL")
.attr("size", 64)
.val(jsonpProxyURL)
.change(updateLogs)
.before(
$("<label>").attr("for", "jsonpProxyURL")
.text("JSON-P Proxy URL: ")
)
)
)
.append($("<div>").css("font-size", "50%")
.append("You need to run something like ")
.append(
$("<a>").attr("href", "https://github.com/clintandrewhall/node-jsonp-proxy")
.text("node-jsonp-proxy")
)
.append(" or ")
.append(
$("<a>").attr("href", "https://gist.github.com/3364319")
.text("jsonp-proxy.py")
)
.append(" to bypass same-origin policy and access all logs from your browser.")
)
.insertBefore($table);
var rowsToExpand = $table.find("tbody:nth(0) > tr");
if (isViewingTaskListPage)
rowsToExpand = rowsToExpand.filter(function(){return $(this).find("td:nth(0) a").length;});
rowsToExpand.click(function(event){
var row = $(this);
row.toggleClass("expanded");
if (row.hasClass("expanded")) {
expandRow.apply(this);
} else {
if (row.next().hasClass("tasklog"))
row.next().remove();
}
});
if (topRowsToExpand)
rowsToExpand = rowsToExpand.filter(":lt("+topRowsToExpand+")");
rowsToExpand.addClass("expanded");
}
var numRemainingRowsToUpdate = $table.find("tr.expanded").length;
localStorage.expandTaskLogs_jsonpProxyURL = jsonpProxyURL = $("#jsonpProxyURL").val();
localStorage.expandTaskLogs_tasklogFilter = tasklogFilter = $("#tasklogFilter").val();
localStorage.expandTaskLogs_offsetKB = offsetKB = parseFloat($("#offsetKB").val()) || null;
localStorage.expandTaskLogs_updateIntervalSecs = updateIntervalSecs = parseFloat($("#updateIntervalSecs").val()) || null;
if (isViewingTaskListPage) {
expandRow = function(row) {
var taskRow = $(this);
var taskLink = taskRow.find("a").first();
var taskId = taskLink.text();
getPage(taskLink[0].href, function(taskPage){
var taskAttemptRow = $("table:first > tbody > tr:last", taskPage);
loadTaskLogAfter(taskRow, taskAttemptRow, taskId);
});
};
getPage(location.href, function(thisPage) {
var newTaskLinks = $("table a", thisPage);
$table.find("tr.expanded").each(function() {
var taskRow = $(this);
var taskLink = taskRow.find("a").first();
var taskId = taskLink.text();
taskRow.contents().remove();
taskRow.append(
newTaskLinks
.filter(textMatch(taskId))
.closest("tr").contents()
);
});
$table.find("tr.expanded").each(expandRow);
});
} else {
expandRow = function() {
var taskAttemptRow = $(this);
loadTaskLogAfter(taskAttemptRow, taskAttemptRow);
}
$table.find("tr.expanded").each(expandRow);
}
function loadTaskLogAfter(anchorRow, taskAttemptRow, taskId) {
var taskAttemptTable = taskAttemptRow.closest("table");
var tasklogLink = $("a", taskAttemptRow)
.filter(function(){ return $(this).text() == "Last 4KB"; })
.first();
var tasklogURLOrig;
var tasklogURL = tasklogURLOrig = tasklogLink.attr("href");
if (tasklogURL == null) return;
if (offsetKB == null)
tasklogURL = tasklogURLOrig.replace("start=-4097", "all=true");
else
tasklogURL = tasklogURLOrig.replace("start=-4097", "start=-"+(Math.round(offsetKB*1024)+1));
tasklogURL += "&filter="+tasklogFilter+"&plaintext=true";
var taskAttemptHeaderColumns = $("thead tr, tr:nth(0)", taskAttemptTable).first().find("td");
var taskAttemptIdIndex = taskAttemptHeaderColumns
.filter(textMatch(/Task Attempts|Task Id/i))
.index();
var taskAttemptMachineIndex = taskAttemptHeaderColumns
.filter(textMatch(/Machine|Host/i))
.index();
var taskAttemptId = taskAttemptRow.find("td:nth("+taskAttemptIdIndex+")").text();
var taskTrackerLink = taskAttemptRow.find("td:nth("+taskAttemptMachineIndex+")").contents().clone();
taskId = taskId || taskAttemptId;
console.log(taskId, taskAttemptId, taskTrackerLink.text(), tasklogURL);
var tasklogId = "tasklog"+taskId.replace(/[^A-Za-z0-9_-]/g, "_");
if ($("#"+tasklogId).length == 0) {
var numCols = anchorRow.find("td").length;
anchorRow.after("<tr class='tasklog'><td colspan='"+numCols+"' id='"+tasklogId+"' class='tasklog'> " +
"<a><b class='title'></b></a> " +
"<small class='tasktracker'></small> " +
"<small class='timestamp'></small> " +
"<b class='status'></b>" +
"<pre></pre></td></tr>");
}
var o = $("#"+tasklogId);
o.find(".tasktracker").contents().remove();
o.find(".tasktracker").append(taskTrackerLink);
o.find(".title")
.text((offsetKB != null ? "Last "+offsetKB+"KB of " : "Entire ") +
tasklogFilter +" of "+ taskAttemptId)
.attr("title", taskId)
.closest("a")
.attr("href", tasklogURLOrig.replace("start=-4097", "&filter="+tasklogFilter));
o.find(".status").text("(Loading...)");
getPage(tasklogURL, function(tasklogText) {
o.find(".status").text("");
o.find(".timestamp").text(new Date().toLocaleString());
o.find("pre").text(tasklogText);
if (--numRemainingRowsToUpdate == 0) {
if (updateIntervalSecs)
window.updateLogsT = setTimeout(updateLogs, updateIntervalSecs*1000);
}
});
}
};
if (window.$) updateLogs();
else {
var s = document.createElement("script");
s.src = "http://code.jquery.com/jquery-1.8.3.min.js";
document.body.appendChild(s);
s.onload = updateLogs;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment