-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstats.py
More file actions
55 lines (45 loc) · 1.9 KB
/
Copy pathstats.py
File metadata and controls
55 lines (45 loc) · 1.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
"""
stats.py — CrawlGraph
======================
Thread-safe crawl statistics.
Every counter is guarded by a single Lock. We use one lock for all fields
rather than a lock per field because the snapshot() read must see a
consistent view of all counters at the same instant — using separate locks
would allow a thread to read pages_crawled after one increment but
links_found before another, producing a misleading snapshot.
"""
import threading
import time
class CrawlStats:
def __init__(self) -> None:
self._lock = threading.Lock()
self._start: float = time.monotonic()
self.pages_crawled: int = 0
self.links_found: int = 0
self.errors: int = 0
# ── Mutators (each acquires the lock for a short, bounded region) ────────
def try_inc_pages(self, limit: int) -> int | None:
"""Increment pages_crawled only if doing so stays within limit."""
with self._lock:
if self.pages_crawled >= limit:
return None
self.pages_crawled += 1
return self.pages_crawled
def inc_links(self, n: int = 1) -> None:
with self._lock:
self.links_found += n
def inc_errors(self) -> None:
with self._lock:
self.errors += 1
# ── Snapshot (consistent read of all fields) ─────────────────────────────
def snapshot(self) -> dict:
"""Return a consistent read of all metrics under a single lock hold."""
with self._lock:
elapsed = max(time.monotonic() - self._start, 1e-9)
return {
"pages_crawled": self.pages_crawled,
"links_found": self.links_found,
"errors": self.errors,
"elapsed_s": round(elapsed, 1),
"pages_per_sec": round(self.pages_crawled / elapsed, 2),
}