Commit ee63c939 authored by tammam.alsoleman's avatar tammam.alsoleman

edit to show the execution time

parent eb5ae0d6
...@@ -50,7 +50,7 @@ public class SearchClient { ...@@ -50,7 +50,7 @@ public class SearchClient {
if (stubs.isEmpty()) { if (stubs.isEmpty()) {
return Collections.emptyList(); return Collections.emptyList();
} }
long startTime = System.currentTimeMillis();
// Phase 1: Aggregate Global Counts // Phase 1: Aggregate Global Counts
Map<String, Integer> globalTermCounts = new HashMap<>(); Map<String, Integer> globalTermCounts = new HashMap<>();
int filesPerWorker = (int) Math.ceil((double) allFiles.size() / stubs.size()); int filesPerWorker = (int) Math.ceil((double) allFiles.size() / stubs.size());
...@@ -101,7 +101,8 @@ public class SearchClient { ...@@ -101,7 +101,8 @@ public class SearchClient {
} catch (Exception e) { System.err.println("Worker " + address + " Phase 2 error"); } } catch (Exception e) { System.err.println("Worker " + address + " Phase 2 error"); }
currentFileIndex += count; currentFileIndex += count;
} }
long endTime = System.currentTimeMillis();
System.out.println(">>> Distributed Search took: " + (endTime - startTime) + " ms");
// --- return the sorted list --- // --- return the sorted list ---
finalResults.sort((a, b) -> Double.compare(b.getScore(), a.getScore())); finalResults.sort((a, b) -> Double.compare(b.getScore(), a.getScore()));
return finalResults; return finalResults;
......
...@@ -3,15 +3,14 @@ ...@@ -3,15 +3,14 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Distributed Engine | Smart Search</title> <title>Distributed Engine | Performance Dashboard</title>
<!-- External Resources: FontAwesome for icons and Inter Font for modern typography --> <!-- UI Enhancement: FontAwesome & Inter Font -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&display=swap" rel="stylesheet">
<style> <style>
:root { :root {
/* Professional Navy Palette */
--primary-dark: #0f172a; --primary-dark: #0f172a;
--primary-navy: #1e3a8a; --primary-navy: #1e3a8a;
--accent-blue: #3b82f6; --accent-blue: #3b82f6;
...@@ -19,6 +18,7 @@ ...@@ -19,6 +18,7 @@
--text-main: #1e293b; --text-main: #1e293b;
--text-muted: #64748b; --text-muted: #64748b;
--card-border: #e2e8f0; --card-border: #e2e8f0;
--success-green: #059669;
} }
body { body {
...@@ -27,7 +27,6 @@ ...@@ -27,7 +27,6 @@
display: flex; flex-direction: column; align-items: center; min-height: 100vh; display: flex; flex-direction: column; align-items: center; min-height: 100vh;
} }
/* Header with Navy Gradient */
header { header {
width: 100%; width: 100%;
background: linear-gradient(135deg, var(--primary-dark) 0%, var(--primary-navy) 100%); background: linear-gradient(135deg, var(--primary-dark) 0%, var(--primary-navy) 100%);
...@@ -38,7 +37,6 @@ ...@@ -38,7 +37,6 @@
header h1 { margin: 0; font-size: 2.6rem; font-weight: 700; letter-spacing: -0.5px; } header h1 { margin: 0; font-size: 2.6rem; font-weight: 700; letter-spacing: -0.5px; }
header p { opacity: 0.8; font-weight: 300; margin-top: 8px; font-size: 1.1rem; } header p { opacity: 0.8; font-weight: 300; margin-top: 8px; font-size: 1.1rem; }
/* Main Container */
.main-container { .main-container {
width: 90%; max-width: 850px; background: white; width: 90%; max-width: 850px; background: white;
border-radius: 12px; padding: 40px; box-shadow: 0 20px 25px -5px rgba(0,0,0,0.05); border-radius: 12px; padding: 40px; box-shadow: 0 20px 25px -5px rgba(0,0,0,0.05);
...@@ -52,10 +50,7 @@ ...@@ -52,10 +50,7 @@
font-size: 1rem; transition: all 0.3s; outline: none; font-size: 1rem; transition: all 0.3s; outline: none;
} }
input[type="text"]:focus { input[type="text"]:focus { border-color: var(--accent-blue); box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.1); }
border-color: var(--accent-blue);
box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.1);
}
button#searchBtn { button#searchBtn {
padding: 0 35px; background: var(--primary-navy); color: white; padding: 0 35px; background: var(--primary-navy); color: white;
...@@ -65,66 +60,42 @@ ...@@ -65,66 +60,42 @@
button#searchBtn:hover { background: var(--primary-dark); transform: translateY(-1px); } button#searchBtn:hover { background: var(--primary-dark); transform: translateY(-1px); }
/* Search History Chips */ /* History Tags */
.history-container { .history-container { display: flex; align-items: center; gap: 10px; margin-bottom: 25px; font-size: 0.85rem; color: var(--text-muted); flex-wrap: wrap; }
display: flex; align-items: center; gap: 10px; margin-bottom: 25px; .history-chip { background: #f1f5f9; color: var(--primary-navy); padding: 4px 12px; border-radius: 20px; cursor: pointer; border: 1px solid #e2e8f0; }
font-size: 0.85rem; color: var(--text-muted); flex-wrap: wrap; .history-chip:hover { background: var(--primary-navy); color: white; }
}
.history-chip {
background: #f1f5f9; color: var(--primary-navy); padding: 4px 12px;
border-radius: 20px; cursor: pointer; transition: all 0.2s;
border: 1px solid #e2e8f0; font-weight: 500;
}
.history-chip:hover { background: var(--primary-navy); color: white; border-color: var(--primary-navy); }
.clear-history { color: #ef4444; cursor: pointer; font-size: 0.75rem; text-decoration: underline; margin-left: 5px; } /* Stats & Benchmark Section */
/* Stats Bar */
.stats-bar { .stats-bar {
display: flex; justify-content: space-between; padding: 14px 20px; display: flex; justify-content: space-between; align-items: center; padding: 14px 20px;
background: #f8fafc; border-radius: 6px; margin-bottom: 25px; background: #f8fafc; border-radius: 6px; margin-bottom: 25px;
font-size: 0.9rem; color: var(--text-muted); display: none; font-size: 0.9rem; color: var(--text-muted); display: none;
border-left: 4px solid var(--primary-navy); border-left: 4px solid var(--primary-navy);
} }
/* Results List & Cards */ .log-btn { background: #e2e8f0; border: none; padding: 5px 10px; border-radius: 4px; font-size: 0.75rem; cursor: pointer; color: var(--primary-navy); font-weight: 600; }
.results-list { display: flex; flex-direction: column; gap: 12px; } .log-btn:hover { background: #cbd5e1; }
.result-card {
padding: 18px; border: 1px solid var(--card-border); border-radius: 10px;
transition: all 0.3s; display: flex; align-items: center; gap: 18px;
animation: slideUp 0.4s ease forwards;
}
.result-card:hover {
border-color: var(--accent-blue); background-color: #fcfdfe; transform: translateX(5px);
}
.icon-box { .perf-log-box {
background: #eff6ff; color: var(--primary-navy); padding: 12px; margin-top: 30px; padding: 20px; background: #fafafa; border-radius: 8px; border: 1px dashed #cbd5e1; text-align: left; display: none;
border-radius: 8px; font-size: 1.3rem; border: 1px solid #dbeafe;
} }
.perf-log-box h3 { margin-top: 0; font-size: 1rem; color: var(--primary-navy); }
.perf-table { width: 100%; border-collapse: collapse; font-size: 0.85rem; margin-top: 10px; }
.perf-table th { text-align: left; padding: 8px; border-bottom: 2px solid #e2e8f0; color: var(--text-muted); }
.perf-table td { padding: 8px; border-bottom: 1px solid #f1f5f9; }
.res-content { flex: 1; } /* Results Styling */
.res-title { font-weight: 700; color: var(--primary-dark); font-size: 1.05rem; margin-bottom: 4px; } .results-list { display: flex; flex-direction: column; gap: 12px; }
.res-meta { font-size: 0.85rem; color: var(--text-muted); display: flex; gap: 15px; } .result-card { padding: 18px; border: 1px solid var(--card-border); border-radius: 10px; display: flex; align-items: center; gap: 18px; animation: slideUp 0.4s ease forwards; }
.res-score { color: #059669; font-weight: 700; } .icon-box { background: #eff6ff; color: var(--primary-navy); padding: 12px; border-radius: 8px; font-size: 1.3rem; }
.res-title { font-weight: 700; color: var(--primary-dark); }
.res-score { color: var(--success-green); font-weight: 700; }
/* Pagination Controls */
.pagination { display: flex; justify-content: center; align-items: center; gap: 12px; margin-top: 30px; display: none; } .pagination { display: flex; justify-content: center; align-items: center; gap: 12px; margin-top: 30px; display: none; }
.nav-btn { background: white; border: 1px solid var(--card-border); padding: 10px 22px; border-radius: 6px; cursor: pointer; font-weight: 600; color: var(--primary-navy); }
.nav-btn {
background: white; border: 1px solid var(--card-border); padding: 10px 22px;
border-radius: 6px; cursor: pointer; font-weight: 600; color: var(--primary-navy); transition: all 0.2s;
}
.nav-btn:hover:not(:disabled) { background: var(--primary-navy); color: white; } .nav-btn:hover:not(:disabled) { background: var(--primary-navy); color: white; }
.nav-btn:disabled { opacity: 0.4; cursor: not-allowed; }
/* Animation Keyframes */
@keyframes slideUp { from { opacity: 0; transform: translateY(15px); } to { opacity: 1; transform: translateY(0); } } @keyframes slideUp { from { opacity: 0; transform: translateY(15px); } to { opacity: 1; transform: translateY(0); } }
.loader { display: none; border: 3px solid #f3f3f3; border-top: 3px solid var(--primary-navy); border-radius: 50%; width: 25px; height: 25px; animation: spin 1s linear infinite; margin: 20px auto; } .loader { display: none; border: 3px solid #f3f3f3; border-top: 3px solid var(--primary-navy); border-radius: 50%; width: 25px; height: 25px; animation: spin 1s linear infinite; margin: 20px auto; }
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
</style> </style>
...@@ -133,66 +104,76 @@ ...@@ -133,66 +104,76 @@
<header> <header>
<h1><i class="fas fa-server"></i> ClusterSearch</h1> <h1><i class="fas fa-server"></i> ClusterSearch</h1>
<p>Intelligent Distributed TF-IDF Text Retrieval System</p> <p>Performance-driven Distributed TF-IDF Retrieval System</p>
</header> </header>
<div class="main-container"> <div class="main-container">
<!-- Search Input Section --> <!-- Input wrapper -->
<div class="search-wrapper"> <div class="search-wrapper">
<input type="text" id="query" placeholder="Type keywords to search (e.g., windows drivers)..." onkeypress="if(event.key === 'Enter') newSearch()"> <input type="text" id="query" placeholder="Enter keywords to evaluate performance..." onkeypress="if(event.key === 'Enter') newSearch()">
<button id="searchBtn" onclick="newSearch()"> <button id="searchBtn" onclick="newSearch()">
<i class="fas fa-bolt"></i> Search <i class="fas fa-bolt"></i> Search
</button> </button>
</div> </div>
<!-- Search History Tags --> <!-- Search History -->
<div class="history-container" id="historyBox"> <div class="history-container" id="historyBox">
<span>Recent searches:</span> <span>Recent:</span>
<div id="historyList" style="display: flex; gap: 8px;"></div> <div id="historyList" style="display: flex; gap: 8px;"></div>
<span class="clear-history" onclick="clearHistory()">[Clear]</span> <span style="color:#ef4444; cursor:pointer; font-size:0.7rem;" onclick="clearHistory()">[Clear]</span>
</div> </div>
<!-- Search Statistics Bar --> <!-- Metrics Bar -->
<div class="stats-bar" id="statsBar"> <div class="stats-bar" id="statsBar">
<span><i class="fas fa-check-circle"></i> Results: <strong id="totalRes" style="color:var(--primary-dark)">0</strong></span> <div><i class="fas fa-check-circle"></i> Results: <strong id="totalRes" style="color:var(--primary-dark)">0</strong></div>
<span><i class="fas fa-tachometer-alt"></i> Cluster Latency: <strong id="timeTaken" style="color:var(--primary-dark)">0</strong> ms</span> <div><i class="fas fa-tachometer-alt"></i> Latency: <strong id="timeTaken" style="color:var(--primary-dark)">0</strong> ms</div>
<button class="log-btn" onclick="addPerformanceToLog()"><i class="fas fa-save"></i> Log Run</button>
</div> </div>
<!-- Feedback Area (Loading / Results / Errors) -->
<div class="loader" id="loader"></div> <div class="loader" id="loader"></div>
<div class="results-list" id="results"></div> <div class="results-list" id="results"></div>
<!-- Navigation Area (Next / Previous Page) -->
<div class="pagination" id="pagination"> <div class="pagination" id="pagination">
<button class="nav-btn" id="prevBtn" onclick="changePage(-1)">Previous</button> <button class="nav-btn" id="prevBtn" onclick="changePage(-1)">Previous</button>
<span id="pageInfo" style="font-weight: 600; color: var(--text-muted);">Page 1</span> <span id="pageInfo">Page 1</span>
<button class="nav-btn" id="nextBtn" onclick="changePage(1)">Next</button> <button class="nav-btn" id="nextBtn" onclick="changePage(1)">Next</button>
</div> </div>
<!-- Performance Comparison Log (The Benchmarking Feature) -->
<div class="perf-log-box" id="perfLogBox">
<h3><i class="fas fa-chart-bar"></i> Performance Benchmark Log</h3>
<p style="font-size: 0.75rem; color:gray; margin-bottom: 10px;">Use this table to compare search speeds across different node counts.</p>
<table class="perf-table">
<thead>
<tr>
<th>#</th>
<th>Query</th>
<th>Count</th>
<th>Latency</th>
<th>Timestamp</th>
</tr>
</thead>
<tbody id="perfTableBody"></tbody>
</table>
</div>
</div> </div>
<script> <script>
let allResults = []; // Stores the full result set from server let allResults = [];
let currentPage = 1; // Current page tracker let currentPage = 1;
const pageSize = 10; // Items to show per page const pageSize = 10;
let runCounter = 0;
// Initialize UI
document.addEventListener('DOMContentLoaded', renderHistory); document.addEventListener('DOMContentLoaded', renderHistory);
/**
* Resets the search state and triggers a new search
*/
async function newSearch() { async function newSearch() {
const query = document.getElementById('query').value.trim(); const query = document.getElementById('query').value.trim();
if (!query) return; if (!query) return;
saveToHistory(query); saveToHistory(query);
currentPage = 1; currentPage = 1;
await performSearch(query); await performSearch(query);
} }
/**
* Communicates with the Frontend HTTP API to get results from the Leader
*/
async function performSearch(query) { async function performSearch(query) {
const resultsDiv = document.getElementById('results'); const resultsDiv = document.getElementById('results');
const loader = document.getElementById('loader'); const loader = document.getElementById('loader');
...@@ -207,30 +188,24 @@ ...@@ -207,30 +188,24 @@
const startTime = performance.now(); const startTime = performance.now();
try { try {
// Forward query to our FrontendApplication Java server
const response = await fetch(`/api/search?query=${encodeURIComponent(query)}`); const response = await fetch(`/api/search?query=${encodeURIComponent(query)}`);
if (!response.ok) throw new Error("Leader node did not respond."); if (!response.ok) throw new Error("Search failed at Leader node.");
allResults = await response.json(); allResults = await response.json();
const endTime = performance.now(); const endTime = performance.now();
// Update stats
document.getElementById('totalRes').innerText = allResults.length; document.getElementById('totalRes').innerText = allResults.length;
document.getElementById('timeTaken').innerText = (endTime - startTime).toFixed(0); document.getElementById('timeTaken').innerText = (endTime - startTime).toFixed(0);
loader.style.display = "none"; loader.style.display = "none";
statsBar.style.display = "flex"; statsBar.style.display = "flex";
render(); // Draw the current page render();
} catch (e) { } catch (e) {
loader.style.display = "none"; loader.style.display = "none";
resultsDiv.innerHTML = `<div style="text-align:center; color:#dc2626; padding:20px;"> resultsDiv.innerHTML = `<div style="text-align:center; color:#dc2626; padding:20px;">Error: ${e.message}</div>`;
<i class="fas fa-wifi-slash"></i> Error: ${e.message}</div>`;
} }
} }
/**
* Renders a specific page of results (10 at a time)
*/
function render() { function render() {
const resultsDiv = document.getElementById('results'); const resultsDiv = document.getElementById('results');
const start = (currentPage - 1) * pageSize; const start = (currentPage - 1) * pageSize;
...@@ -238,15 +213,13 @@ ...@@ -238,15 +213,13 @@
resultsDiv.innerHTML = ""; resultsDiv.innerHTML = "";
if (allResults.length === 0) { if (allResults.length === 0) {
resultsDiv.innerHTML = "<p style='text-align:center; color:gray;'>No relevant documents found in the cluster.</p>"; resultsDiv.innerHTML = "<p style='text-align:center;'>No matching files.</p>";
return; return;
} }
pageData.forEach((item, idx) => { pageData.forEach((item, idx) => {
// Handle both Protobuf (with _) and standard JSON field names const name = item.documentName_ || item.documentName || "File";
const name = item.documentName_ || item.documentName || "Unknown";
const score = item.score_ || item.score || 0; const score = item.score_ || item.score || 0;
resultsDiv.innerHTML += ` resultsDiv.innerHTML += `
<div class="result-card"> <div class="result-card">
<div class="icon-box"><i class="fas fa-file-invoice"></i></div> <div class="icon-box"><i class="fas fa-file-invoice"></i></div>
...@@ -254,13 +227,12 @@ ...@@ -254,13 +227,12 @@
<div class="res-title">Document #${name}</div> <div class="res-title">Document #${name}</div>
<div class="res-meta"> <div class="res-meta">
<span>Global Rank: <strong>${start + idx + 1}</strong></span> <span>Global Rank: <strong>${start + idx + 1}</strong></span>
<span>Relativity Score: <span class="res-score">${(score * 100).toFixed(4)}%</span></span> <span>Relativity: <span class="res-score">${(score * 100).toFixed(4)}%</span></span>
</div> </div>
</div> </div>
</div>`; </div>`;
}); });
// Update Pagination Controls
document.getElementById('pagination').style.display = "flex"; document.getElementById('pagination').style.display = "flex";
document.getElementById('pageInfo').innerText = `Page ${currentPage} of ${Math.ceil(allResults.length / pageSize)}`; document.getElementById('pageInfo').innerText = `Page ${currentPage} of ${Math.ceil(allResults.length / pageSize)}`;
document.getElementById('prevBtn').disabled = (currentPage === 1); document.getElementById('prevBtn').disabled = (currentPage === 1);
...@@ -268,49 +240,55 @@ ...@@ -268,49 +240,55 @@
} }
/** /**
* Handles search history logic using localStorage * Logic to record the current search performance for benchmarking
*/ */
function addPerformanceToLog() {
const logBox = document.getElementById('perfLogBox');
const tableBody = document.getElementById('perfTableBody');
const query = document.getElementById('query').value;
const count = document.getElementById('totalRes').innerText;
const time = document.getElementById('timeTaken').innerText;
const now = new Date().toLocaleTimeString();
logBox.style.display = "block";
runCounter++;
const row = `<tr>
<td>${runCounter}</td>
<td><strong>${query}</strong></td>
<td>${count} docs</td>
<td style="color:var(--primary-navy); font-weight:700;">${time} ms</td>
<td>${now}</td>
</tr>`;
tableBody.innerHTML = row + tableBody.innerHTML; // Prepend to show newest on top
}
function saveToHistory(query) { function saveToHistory(query) {
let history = JSON.parse(localStorage.getItem('searchHistory') || '[]'); let history = JSON.parse(localStorage.getItem('searchHistory') || '[]');
history = history.filter(item => item !== query); // Remove duplicates history = history.filter(item => item !== query);
history.unshift(query); // Add to front history.unshift(query);
localStorage.setItem('searchHistory', JSON.stringify(history.slice(0, 5))); // Keep last 5 localStorage.setItem('searchHistory', JSON.stringify(history.slice(0, 5)));
renderHistory(); renderHistory();
} }
function renderHistory() { function renderHistory() {
const list = document.getElementById('historyList'); const list = document.getElementById('historyList');
const history = JSON.parse(localStorage.getItem('searchHistory') || '[]'); const history = JSON.parse(localStorage.getItem('searchHistory') || '[]');
list.innerHTML = ""; list.innerHTML = "";
if(history.length === 0) { if(history.length === 0) { document.getElementById('historyBox').style.display = "none"; return; }
document.getElementById('historyBox').style.display = "none";
return;
}
document.getElementById('historyBox').style.display = "flex"; document.getElementById('historyBox').style.display = "flex";
history.forEach(item => { history.forEach(item => {
const chip = document.createElement('span'); const chip = document.createElement('span');
chip.className = 'history-chip'; chip.className = 'history-chip';
chip.innerText = item; chip.innerText = item;
chip.onclick = () => { chip.onclick = () => { document.getElementById('query').value = item; newSearch(); };
document.getElementById('query').value = item;
newSearch();
};
list.appendChild(chip); list.appendChild(chip);
}); });
} }
function clearHistory() { function clearHistory() { localStorage.removeItem('searchHistory'); renderHistory(); }
localStorage.removeItem('searchHistory'); function changePage(step) { currentPage += step; render(); window.scrollTo({top: 0, behavior: 'smooth'}); }
renderHistory();
}
function changePage(step) {
currentPage += step;
render();
window.scrollTo({top: 0, behavior: 'smooth'});
}
</script> </script>
</body> </body>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment