diff --git a/fetch.py b/fetch.py index fc5439f..32af3e2 100644 --- a/fetch.py +++ b/fetch.py @@ -1,6 +1,7 @@ import re, random, time, string import json import threading +from collections import OrderedDict import rocksock import network_stats from http2 import RsHttp, _parse_url @@ -9,9 +10,11 @@ from misc import _log, tor_proxy_url config = None -# Cache for is_usable_proxy() - avoids repeated validation of same proxy strings -_proxy_valid_cache = {} +# LRU cache for is_usable_proxy() - avoids repeated validation of same proxy strings +# Uses OrderedDict to maintain insertion order; oldest entries evicted when full +_proxy_valid_cache = OrderedDict() _proxy_valid_cache_max = 10000 +_proxy_valid_cache_lock = threading.Lock() class FetchSession(object): @@ -603,16 +606,20 @@ def is_usable_proxy(proxy): - Invalid port (0, >65535) - Private/reserved ranges - Results are cached to avoid repeated validation of the same proxy strings. + Results are cached using LRU eviction to avoid repeated validation. """ - # Check cache first - if proxy in _proxy_valid_cache: - return _proxy_valid_cache[proxy] + with _proxy_valid_cache_lock: + if proxy in _proxy_valid_cache: + # Move to end (most recently used) + _proxy_valid_cache.move_to_end(proxy) + return _proxy_valid_cache[proxy] result = _validate_proxy(proxy) - # Cache result (with size limit) - if len(_proxy_valid_cache) < _proxy_valid_cache_max: + with _proxy_valid_cache_lock: + # Evict oldest entries if at capacity + while len(_proxy_valid_cache) >= _proxy_valid_cache_max: + _proxy_valid_cache.popitem(last=False) _proxy_valid_cache[proxy] = result return result diff --git a/static/dashboard.js b/static/dashboard.js index 2a44846..4e87ddd 100644 --- a/static/dashboard.js +++ b/static/dashboard.js @@ -9,6 +9,17 @@ var uplotCharts = {}; // Chart.js instances (persistent) var chartJsInstances = {}; +// Get currently active tab ID +function getActiveTab() { + var activeBtn = document.querySelector('.tab-btn.active'); + return activeBtn ? activeBtn.dataset.tab : 'overview'; +} + +// Check if a specific tab is currently active +function isTabActive(tabId) { + return getActiveTab() === tabId; +} + // Tab switching function initTabs() { $$('.tab-btn').forEach(function(btn) { @@ -331,10 +342,15 @@ function update(d) { // System $('uptime').textContent = fmtTime(d.uptime_seconds); - // Charts - renderLineChart('rateChart', d.rate_history, '#58a6ff', d.peak_rate * 1.1); - renderLineChart('srChart', d.success_rate_history, '#3fb950', 100); - renderHistogram('latencyHisto', d.latency_histogram); + // Charts - only render for active tabs to reduce CPU usage + var activeTab = getActiveTab(); + if (activeTab === 'overview' || activeTab === 'perf') { + renderLineChart('rateChart', d.rate_history, '#58a6ff', d.peak_rate * 1.1); + renderLineChart('srChart', d.success_rate_history, '#3fb950', 100); + } + if (activeTab === 'perf') { + renderHistogram('latencyHisto', d.latency_histogram); + } // Protocol breakdown var ps = d.proto_stats || {}; @@ -347,9 +363,9 @@ function update(d) { rateEl.className = 'proto-rate ' + (s.success_rate < 20 ? 'tag-err' : s.success_rate < 50 ? 'tag-warn' : 'tag-ok'); }); - // Results pie (Chart.js doughnut) + // Results pie (Chart.js doughnut) - only on overview tab var passed = d.passed || 0, failed = d.failed || 0, total = passed + failed; - if (total > 0) { + if (activeTab === 'overview' && total > 0) { renderDoughnutChart('resultsPie', ['Passed', 'Failed'], [passed, failed], ['#3fb950', '#f85149']); } $('passedLeg').textContent = fmt(passed); @@ -357,13 +373,15 @@ function update(d) { $('failedLeg').textContent = fmt(failed); $('failedPct').textContent = pct(failed, total) + '%'; - // Failures breakdown (Chart.js doughnut) + // Failures breakdown (Chart.js doughnut) - only on overview tab var fhtml = '', failColors = ['#f85149','#db6d28','#d29922','#58a6ff','#a371f7','#39c5cf','#db61a2','#7d8590']; if (d.failures && Object.keys(d.failures).length > 0) { var cats = Object.keys(d.failures).sort(function(a,b) { return d.failures[b] - d.failures[a]; }); - var failVals = cats.map(function(c) { return d.failures[c]; }); - var failCols = cats.map(function(_, i) { return failColors[i % failColors.length]; }); - renderDoughnutChart('failPie', cats, failVals, failCols); + if (activeTab === 'overview') { + var failVals = cats.map(function(c) { return d.failures[c]; }); + var failCols = cats.map(function(_, i) { return failColors[i % failColors.length]; }); + renderDoughnutChart('failPie', cats, failVals, failCols); + } cats.forEach(function(cat, i) { var n = d.failures[cat], col = failColors[i % failColors.length]; fhtml += '