providers-metrics / index.html
victor's picture
victor HF Staff
Update index.html
169e643 verified
raw
history blame
23.6 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Provider Inference Metrics Dashboard</title>
<script src='https://cdn.plot.ly/plotly-latest.min.js'></script>
<style>
:root {
--bg-color: #f8f9fa;
--card-bg-color: #ffffff;
--text-color: #212529;
--muted-text-color: #6c757d;
--border-color: #dee2e6;
--shadow-color: rgba(0, 0, 0, 0.05);
--primary-color: #0d6efd;
--plot-colorway: ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf']; /* Plotly default */
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
margin: 0;
background-color: var(--bg-color);
color: var(--text-color);
line-height: 1.5;
}
.container {
max-width: 1600px;
margin: 20px auto;
padding: 0 20px;
}
h1 {
text-align: center;
color: var(--text-color);
margin-bottom: 30px;
font-weight: 500;
}
.kpi-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.kpi-card {
background-color: var(--card-bg-color);
padding: 20px;
border-radius: 8px;
box-shadow: 0 4px 8px var(--shadow-color);
text-align: center;
border: 1px solid var(--border-color);
}
.kpi-card h3 {
margin-top: 0;
margin-bottom: 10px;
font-size: 1rem;
color: var(--muted-text-color);
font-weight: 400;
}
.kpi-card .value {
font-size: 1.8rem;
font-weight: 600;
color: var(--text-color);
}
.kpi-card .unit {
font-size: 0.9rem;
color: var(--muted-text-color);
margin-left: 5px;
}
.dashboard-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(450px, 1fr)); /* Responsive grid */
gap: 25px; /* Space between plots */
}
.plot-container {
background-color: var(--card-bg-color);
padding: 20px;
border-radius: 8px;
box-shadow: 0 4px 8px var(--shadow-color);
border: 1px solid var(--border-color);
min-height: 450px; /* Ensure plots have some height */
display: flex; /* For centering loading/error inside */
justify-content: center;
align-items: center;
}
/* Style Plotly chart container */
.plot-container .plotly {
width: 100%;
height: 100%;
}
#loading, #error {
grid-column: 1 / -1; /* Span full width if grid is active */
text-align: center;
font-size: 1.2em;
padding: 40px;
color: var(--muted-text-color);
}
#error {
color: #dc3545; /* Bootstrap danger color */
font-weight: 500;
background-color: #f8d7da;
border: 1px solid #f5c2c7;
border-radius: 8px;
}
footer {
text-align: center;
margin-top: 40px;
padding: 20px;
font-size: 0.9em;
color: var(--muted-text-color);
border-top: 1px solid var(--border-color);
}
footer a {
color: var(--primary-color);
text-decoration: none;
}
footer a:hover {
text-decoration: underline;
}
</style>
</head>
<body>
<div class="container">
<h1>Provider Inference Metrics Dashboard</h1>
<div id="loading">Loading data... Please wait.</div>
<div id="error" style="display: none;"></div>
<!-- KPI Section -->
<div class="kpi-container" style="display: none;">
<div class="kpi-card">
<h3>Total Requests</h3>
<div class="value" id="kpi-total-requests">--</div>
</div>
<div class="kpi-card">
<h3>Success Rate</h3>
<div class="value" id="kpi-success-rate">--<span class="unit">%</span></div>
</div>
<div class="kpi-card">
<h3>Avg. Latency (Overall)</h3>
<div class="value" id="kpi-avg-latency">--<span class="unit">ms</span></div>
</div>
<div class="kpi-card">
<h3>Fastest Provider (Median)</h3>
<div class="value" id="kpi-fastest-provider">--</div>
</div>
</div>
<!-- Dashboard Plots -->
<div class="dashboard-container" style="display: none;">
<div id="plotLatencyProvider" class="plot-container"></div>
<div id="plotReliabilityProvider" class="plot-container"></div>
<div id="plotLatencyModel" class="plot-container"></div>
<div id="plotErrorTypesProvider" class="plot-container"></div>
<div id="plotLatencyHeatmap" class="plot-container"></div>
<!-- Add more divs here for additional plots -->
</div>
<footer id="footer" style="display: none;">
Data fetched from: <a id="data-source-url" href="#" target="_blank">Hugging Face Datasets</a><br>
Last updated: <span id="last-updated"></span>
</footer>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const apiUrl = "https://datasets-server.huggingface.co/rows?dataset=victor%2Fproviders-metrics&config=default&split=train&offset=0&length=100"; // Fetch 1000 rows
const loadingDiv = document.getElementById('loading');
const errorDiv = document.getElementById('error');
const kpiContainer = document.querySelector('.kpi-container');
const dashboardContainer = document.querySelector('.dashboard-container');
const footer = document.getElementById('footer');
const dataSourceUrlElement = document.getElementById('data-source-url');
const lastUpdatedElement = document.getElementById('last-updated');
dataSourceUrlElement.href = apiUrl; // Set link href
// Plotly layout defaults
const baseLayout = {
margin: { l: 60, r: 30, b: 100, t: 60, pad: 4 }, // Default margins
legend: { bgcolor: 'rgba(255,255,255,0.5)', bordercolor: '#ccc', borderwidth: 1 },
colorway: ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf'], // Plotly default color sequence
paper_bgcolor: 'rgba(0,0,0,0)', // Transparent background for plot area
plot_bgcolor: 'rgba(0,0,0,0)', // Transparent background for plotting area
font: {
family: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
color: '#212529'
},
title: {
font: { size: 16, weight: '500' },
x: 0.05, // Title alignment
xanchor: 'left'
},
xaxis: {
gridcolor: '#e9ecef', // Lighter grid lines
linecolor: '#adb5bd',
automargin: true,
tickfont: { size: 10 }
},
yaxis: {
gridcolor: '#e9ecef',
linecolor: '#adb5bd',
automargin: true,
tickfont: { size: 10 }
}
};
// Helper function to deep merge layout options
function mergeLayout(customLayout) {
// Simple deep merge for nested objects like margin, font, title
let layout = JSON.parse(JSON.stringify(baseLayout)); // Deep copy base
for (const key in customLayout) {
if (typeof customLayout[key] === 'object' && customLayout[key] !== null && !Array.isArray(customLayout[key]) && layout[key]) {
Object.assign(layout[key], customLayout[key]);
} else {
layout[key] = customLayout[key];
}
}
return layout;
}
// Helper function to calculate median
function calculateMedian(arr) {
if (!arr || arr.length === 0) return null;
const sortedArr = [...arr].sort((a, b) => a - b);
const mid = Math.floor(sortedArr.length / 2);
return sortedArr.length % 2 !== 0 ? sortedArr[mid] : (sortedArr[mid - 1] + sortedArr[mid]) / 2;
}
fetch(apiUrl)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status} ${response.statusText}`);
}
return response.json();
})
.then(data => {
loadingDiv.style.display = 'none'; // Hide loading message
kpiContainer.style.display = 'grid'; // Show KPIs
dashboardContainer.style.display = 'grid'; // Show dashboard plots
footer.style.display = 'block'; // Show footer
// Extract the actual row data
const rows = data.rows.map(item => item.row);
console.log(`Fetched ${rows.length} rows.`);
lastUpdatedElement.textContent = new Date().toLocaleString();
// --- Calculate and Display KPIs ---
calculateAndDisplayKPIs(rows);
// --- Data Processing and Plotting ---
createLatencyByProviderPlot(rows);
createReliabilityByProviderPlot(rows);
createLatencyByModelPlot(rows);
createErrorTypesByProviderPlot(rows);
createLatencyHeatmap(rows);
})
.catch(error => {
console.error('Error fetching or processing data:', error);
loadingDiv.style.display = 'none';
errorDiv.textContent = `Error loading data: ${error.message}. Please check the console for details. Is the dataset server reachable?`;
errorDiv.style.display = 'block';
});
// --- KPI Calculation Function ---
function calculateAndDisplayKPIs(rows) {
const totalRequests = rows.length;
const successfulRequests = rows.filter(r => r.response_status_code === 200).length;
const successRate = totalRequests > 0 ? ((successfulRequests / totalRequests) * 100).toFixed(1) : 0;
const validLatencies = rows
.map(r => r.duration_ms)
.filter(d => d !== null && d >= 0);
const avgLatency = validLatencies.length > 0
? (validLatencies.reduce((a, b) => a + b, 0) / validLatencies.length).toFixed(0)
: 0;
// Calculate median latency per provider to find the fastest
const latencyByProvider = {};
rows.forEach(row => {
if (row.duration_ms !== null && row.duration_ms >= 0) {
if (!latencyByProvider[row.provider_name]) {
latencyByProvider[row.provider_name] = [];
}
latencyByProvider[row.provider_name].push(row.duration_ms);
}
});
let fastestProvider = '--';
let minMedianLatency = Infinity;
for (const provider in latencyByProvider) {
const median = calculateMedian(latencyByProvider[provider]);
if (median !== null && median < minMedianLatency) {
minMedianLatency = median;
fastestProvider = provider;
}
}
document.getElementById('kpi-total-requests').textContent = totalRequests;
document.getElementById('kpi-success-rate').innerHTML = `${successRate}<span class="unit">%</span>`;
document.getElementById('kpi-avg-latency').innerHTML = `${avgLatency}<span class="unit">ms</span>`;
document.getElementById('kpi-fastest-provider').textContent = fastestProvider;
}
// --- Plotting Functions ---
function createLatencyByProviderPlot(rows) {
const dataByProvider = {};
rows.forEach(row => {
if (!dataByProvider[row.provider_name]) {
dataByProvider[row.provider_name] = [];
}
if (row.duration_ms !== null && row.duration_ms >= 0) {
dataByProvider[row.provider_name].push(row.duration_ms);
}
});
const plotData = Object.keys(dataByProvider).sort().map(provider => ({ // Sort providers alphabetically
y: dataByProvider[provider],
type: 'box',
name: provider,
boxpoints: 'Outliers',
marker: { size: 4 }
}));
const layout = mergeLayout({
title: { text: 'Latency Distribution by Provider' },
yaxis: { title: 'Duration (ms)', type: 'log', autorange: true },
xaxis: { title: 'Provider', tickangle: -30 },
margin: { b: 120 } // More bottom margin for angled labels
});
Plotly.newPlot('plotLatencyProvider', plotData, layout, {responsive: true});
}
function createReliabilityByProviderPlot(rows) {
const statusCountsByProvider = {};
const allProviders = new Set();
const allStatusCodes = new Set();
rows.forEach(row => {
const provider = row.provider_name;
const status = row.response_status_code ?? 'Unknown'; // Handle null status
allProviders.add(provider);
allStatusCodes.add(status);
if (!statusCountsByProvider[provider]) {
statusCountsByProvider[provider] = {};
}
if (!statusCountsByProvider[provider][status]) {
statusCountsByProvider[provider][status] = 0;
}
statusCountsByProvider[provider][status]++;
});
const sortedProviders = Array.from(allProviders).sort();
// Sort status codes: 200 first, then numerically, then 'Unknown'
const sortedStatusCodes = Array.from(allStatusCodes).sort((a, b) => {
if (a === 200) return -1;
if (b === 200) return 1;
if (a === 'Unknown') return 1;
if (b === 'Unknown') return -1;
return a - b;
});
const plotData = sortedStatusCodes.map(status => {
return {
x: sortedProviders,
y: sortedProviders.map(provider => statusCountsByProvider[provider]?.[status] || 0),
name: `Status ${status}`,
type: 'bar',
hovertemplate: `Provider: %{x}<br>Status: ${status}<br>Count: %{y}<extra></extra>`
};
});
const layout = mergeLayout({
title: { text: 'Request Status Codes by Provider' },
barmode: 'stack',
xaxis: { title: 'Provider', tickangle: -30 },
yaxis: { title: 'Number of Requests', autorange: true },
margin: { b: 120 }
});
Plotly.newPlot('plotReliabilityProvider', plotData, layout, {responsive: true});
}
function createLatencyByModelPlot(rows) {
const dataByModel = {};
rows.forEach(row => {
const model = row.model_id;
if (!dataByModel[model]) {
dataByModel[model] = [];
}
if (row.duration_ms !== null && row.duration_ms >= 0) {
dataByModel[model].push(row.duration_ms);
}
});
const plotData = Object.keys(dataByModel).sort().map(model => ({ // Sort models
y: dataByModel[model],
type: 'box',
name: model,
boxpoints: 'Outliers',
marker: { size: 4 }
}));
const layout = mergeLayout({
title: { text: 'Latency Distribution by Model' },
yaxis: { title: 'Duration (ms)', type: 'log', autorange: true },
xaxis: {
title: 'Model ID',
tickangle: -30 // Angle labels if they overlap
},
margin: { b: 180 } // More bottom margin for potentially long/angled labels
});
Plotly.newPlot('plotLatencyModel', plotData, layout, {responsive: true});
}
function createErrorTypesByProviderPlot(rows) {
const errorCountsByProvider = {};
const allProviders = new Set();
const allErrorCodes = new Set();
rows.forEach(row => {
if (row.response_status_code !== 200 && row.response_status_code !== null) { // Only errors
const provider = row.provider_name;
const status = row.response_status_code;
allProviders.add(provider);
allErrorCodes.add(status);
if (!errorCountsByProvider[provider]) {
errorCountsByProvider[provider] = {};
}
if (!errorCountsByProvider[provider][status]) {
errorCountsByProvider[provider][status] = 0;
}
errorCountsByProvider[provider][status]++;
}
});
const sortedProviders = Array.from(allProviders).sort();
const sortedErrorCodes = Array.from(allErrorCodes).sort((a, b) => a - b);
const plotData = sortedErrorCodes.map(status => {
return {
x: sortedProviders,
y: sortedProviders.map(provider => errorCountsByProvider[provider]?.[status] || 0),
name: `Error ${status}`,
type: 'bar',
hovertemplate: `Provider: %{x}<br>Error: ${status}<br>Count: %{y}<extra></extra>`
};
});
const layout = mergeLayout({
title: { text: 'Error Types by Provider (Non-200 Status)' },
barmode: 'group', // Group bars side-by-side for comparison
xaxis: { title: 'Provider', tickangle: -30 },
yaxis: { title: 'Number of Errors', autorange: true },
margin: { b: 120 }
});
Plotly.newPlot('plotErrorTypesProvider', plotData, layout, {responsive: true});
}
function createLatencyHeatmap(rows) {
const latencyData = {}; // { provider: { model: { sum: 0, count: 0 } } }
const allProviders = new Set();
const allModels = new Set();
rows.forEach(row => {
if (row.duration_ms !== null && row.duration_ms >= 0) {
const provider = row.provider_name;
const model = row.model_id;
allProviders.add(provider);
allModels.add(model);
if (!latencyData[provider]) latencyData[provider] = {};
if (!latencyData[provider][model]) latencyData[provider][model] = { sum: 0, count: 0 };
latencyData[provider][model].sum += row.duration_ms;
latencyData[provider][model].count++;
}
});
const sortedProviders = Array.from(allProviders).sort();
const sortedModels = Array.from(allModels).sort();
const zValues = sortedModels.map(model => {
return sortedProviders.map(provider => {
const data = latencyData[provider]?.[model];
const avg = data && data.count > 0 ? data.sum / data.count : null;
return avg;
});
});
const hoverText = sortedModels.map(model => {
return sortedProviders.map(provider => {
const data = latencyData[provider]?.[model];
const avg = data && data.count > 0 ? (data.sum / data.count).toFixed(0) : 'N/A';
const count = data?.count || 0;
return `Model: ${model}<br>Provider: ${provider}<br>Avg Latency: ${avg} ms<br>Requests: ${count}<extra></extra>`;
});
});
const plotData = [{
z: zValues,
x: sortedProviders,
y: sortedModels,
type: 'heatmap',
hoverongaps: false,
colorscale: 'Viridis',
reversescale: true, // Often makes sense for latency (lower is better -> brighter)
colorbar: { title: 'Avg Latency (ms)', titleside: 'right', thickness: 15 },
xgap: 2, // Add gaps between cells
ygap: 2,
hovertemplate: hoverText // Custom hover text
}];
const layout = mergeLayout({
title: { text: 'Average Latency (ms) - Model vs. Provider' },
xaxis: { title: '', side: 'top', tickangle: -30 }, // Move x-axis labels to top, angle
yaxis: { title: '', autorange: 'reversed' }, // Reverse y-axis to match typical matrix layout
margin: { l: 280, r: 50, b: 50, t: 120 } // Adjust margins significantly for labels
});
Plotly.newPlot('plotLatencyHeatmap', plotData, layout, {responsive: true});
}
});
</script>
</body>
</html>