changeset 320:c68714dee9f2 trim

centralise all need for new approach to cdb index shrinking in one place
author Henry S. Thompson <ht@inf.ed.ac.uk>
date Tue, 20 Jan 2026 19:46:48 +0000
parents 439cbfd6cbda
children cf50a54b8b53
files lib/python/cc/lmh/cdb_one.py lib/python/cc/lmh/shrink.py lib/python/cc/lmh/shrink_key.py lib/python/cc/lmh/warc2cdb.py
diffstat 4 files changed, 64 insertions(+), 81 deletions(-) [+]
line wrap: on
line diff
--- a/lib/python/cc/lmh/cdb_one.py	Fri Jan 16 17:12:33 2026 +0000
+++ b/lib/python/cc/lmh/cdb_one.py	Tue Jan 20 19:46:48 2026 +0000
@@ -12,6 +12,7 @@
 '''
 
 import cython, typing, timeit, re, sys, array
+import shrink_key
 
 from db import CCdb
 
@@ -55,16 +56,7 @@
     N += 1
     seg = int(segb)
     if (seg >= S and seg < E):
-      # See shrink.py l. 27ff for this shrinkage
-      for i in range(dpl):
-        if wdate[i] != DP[i]:
-          break
-      else:
-        i+=1
-      if i > 0:
-        nd = wdate[i:]
-      else:
-        nd = DP
+      nd = shrink_key.shrink_date(wdate, DP)
       if kind == 'robotstxt':
         nd += seg
       if not uri.startswith(b'http'):
--- a/lib/python/cc/lmh/shrink.py	Fri Jan 16 17:12:33 2026 +0000
+++ b/lib/python/cc/lmh/shrink.py	Tue Jan 20 19:46:48 2026 +0000
@@ -1,4 +1,5 @@
 #!/usr/bin/env python3
+# cython: profile=False, language_level=3str
 '''Shrink cdb_input-style files after the fact
    Each record is encoded as +klen,dlen:key->data
 
@@ -6,6 +7,8 @@
 '''
 import sys, io
 
+import shrink_key
+
 ifn = sys.argv.pop(1)
 dp = sys.argv.pop(1).encode('ascii')
 ofn = sys.argv.pop(1)
@@ -24,26 +27,13 @@
     kl=int(os1)
     ll=int(os2)
     #print(od,dp,dpl,file=sys.stderr)
-    for i in range(dpl):
-      if od[i] != dp[i]:
-        break
-    else:
-      i+=1
-    if i > 0:
-      nd = od[i:]
-    else:
-      nd = od
-    #print(i,od,nd,file=sys.stderr)
     ol = oe[-ll:]
-    nli = int(ol)
-    bad = (nli < 0 or nli > 2147483647)
-    of.write(b'+%d,%d'%(kl-(len(od)-len(nd)),len(ol) if bad else 4))
+    (nd,nlb)=shrink_key.shrink_date(od,dp,ol)
+    #print(i,od,nd,'%x'%nlb,file=sys.stderr)
+    of.write(b'+%d,%d'%(kl-(len(od)-len(nd)),len(nlb)))
     of.write(b':')
     of.write(nd)
     of.write(b':')
-    of.write(oe[:-ll])
-    if bad:
-      of.write(ol)
-    else:
-      of.write(int.to_bytes(nli,4))
+    of.write(oe[:-ll]) # includes the ->
+    of.write(nlb)
     of.write(b'\n')
--- a/lib/python/cc/lmh/shrink_key.py	Fri Jan 16 17:12:33 2026 +0000
+++ b/lib/python/cc/lmh/shrink_key.py	Tue Jan 20 19:46:48 2026 +0000
@@ -15,24 +15,10 @@
 
 C_PAT: typing.Pattern[bytes] = re.compile(b'[^ ]* ([^ ]*) .*{"url": "(http[^"]*).*"filename": "[^"]*[.]([0-9][0-9]?)/(warc|robotstxt|crawldiagnostics)/')
 
-def shrink(entry: bytes, dp: bytes) -> bytes:
-  m: typing.Match[bytes] | None
-
+def shrink_date(wdate: bytes, dp: bytes, lmb: bytes) -> tuple[bytes,bytes]:
   dpl: int = len(dp)
-  uri: bytes
-  wdate: bytes
-  kind: bytes
-  segb: bytes
-  ts: array.array
-  seg: int
-  res: int
   i: int
-  nd: bytes
-  if (m:=C_PAT.match(entry)):
-    (wdate, uri, segb, kind) = m.groups()
-  else:
-    raise ValueError(l)
-  seg = int(segb)
+  lm: int
   for i in range(dpl):
     if wdate[i] != dp[i]:
       break
@@ -42,16 +28,36 @@
     nd = wdate[i:]
   else:
     nd = dp
-  if kind == 'robotstxt':
-    nd += seg
-  if not uri.startswith(b'http'):
-    raise ValueError(uri)
+  lm = int(lmb)
+  if (lm >= 0 and lm <= 2147483647):
+    lmb=int.to_bytes(lm,4)
   #print(nd,uri[4:],file=sys.stderr)
-  return nd+uri[4:]
+  return (nd,lmb)
 
 if __name__ == "__main__":
+  m: typing.Match[bytes] | None
   cdx_in: typing.BinaryIO = sys.stdin.buffer
   l: bytes
   DP: bytes = sys.argv[1].encode('ASCII')
+  uri: bytes
+  wdate: bytes
+  kind: bytes
+  segb: bytes
+  ts: array.array
+  seg: int
+  res: int
+  nd: bytes
+  nv: bytes
   for l in cdx_in:
-    sys.stdout.buffer.write(shrink(l,DP))
+    if (m:=C_PAT.match(l)):
+      (wdate, uri, segb, kind) = m.groups()
+    else:
+      raise ValueError(l)
+    (nd,nv) = shrink_date(wdate,DP,'0')
+    seg = int(segb)
+    if kind == 'robotstxt':
+      nd += seg
+    if not uri.startswith(b'http'):
+      raise ValueError(uri)
+    sys.stdout.buffer.write(nd+uri[4:])
+    sys.stdout.buffer.write(b'\n')
--- a/lib/python/cc/lmh/warc2cdb.py	Fri Jan 16 17:12:33 2026 +0000
+++ b/lib/python/cc/lmh/warc2cdb.py	Tue Jan 20 19:46:48 2026 +0000
@@ -1,13 +1,18 @@
 #!/usr/bin/env python3
+# cython: profile=False, language_level=3str
 '''Produce cdb_input-style files from warc responses with lmh header value
 
-   Usage: warc2cdb.py CC-date segment output-dir warc_file_type warc_file_range lm_shrink_prefix cdate_shrink_prefix '''
+  Usage: warc2cdb.py CC-date segment output-dir warc_file_type warc_file_range  cdate_base
+   warc_file_type is warc|robotstxt|crawldiagnostics
+   warc_file_range is (literally) '???' or from,to
+   cdate_base is the most likely complete (14-digit) warc-crawl-date'''
 
 import re, warc, sys, glob, codecs, os.path
 import cython, typing
 import email.utils
 from urllib.parse import quote
 import subprocess
+import shrink_key
 
 TUPAT: typing.Pattern[bytes] = re.compile(b'^WARC-Target-URI: (.*?)\r?$',re.MULTILINE)
 DPAT: typing.Pattern[bytes] = re.compile(b'^WARC-Date: (.*?)\r?$',re.MULTILINE)
@@ -23,22 +28,24 @@
 
 URI: bytes
 DATE: bytes
+LM: bytes
 
 WIN: int = 0
 LOSE: int = 0
 N: int = 0
 UERRS: int = 0
 NON_HTTP: int = 0
-NON_MONTH: int = 0
-NON_ERA: int = 0
 
-def _u_esc(c):
+C_BASE: bytes
+C_BASE_L: int
+
+def _u_esc(c: int) -> str:
   if c<65536:
     return '\\u%04X'%c
   else:
     return '\\U%08X'%c
 
-def java_unicode_encode(ude):
+def java_unicode_encode(ude: Type[UnicodeDecodeError]) -> tuple[str,int]:
   '''like backslashreplace but use uppercase and \\ u00NN instead of \\ xnn'''
   return (''.join(_u_esc(ord(c)) for c in ude.object[ude.start:ude.end]),
           ude.end)
@@ -47,8 +54,8 @@
 
 def LMHline(wtype: int, buf: memoryview, part: int) -> None:
   global TUPAT, DPAT, LMPAT, FFPAT, DTAB, DDEL, OUT, WIN, LOSE, NON_HTTP, NON_MONTH
-  global N, UERRS, SEG, R_T, LM_ERA, LM_ERA_L, C_MONTH, C_MONTH_L, NON_ERA
-  global DATE, URI
+  global N, UERRS, SEG, R_T, C_BASE, C_BASE_L
+  global DATE, URI, LM
   m: typing.Match[cython.bytes] | None
   mm: typing.Match[cython.bytes] | None
   lmi: cython.bytes
@@ -72,11 +79,6 @@
       try:
         try:
           lmi = b'%d'%int(email.utils.parsedate_to_datetime(dateTime.decode('utf8')).timestamp())
-          if LM_ERA:
-            if lmi.startswith(LM_ERA): # save 2 bytes in ~80% of cases
-              lmi=b'0'+lmi[LM_ERA_L:]
-            else:
-              NON_ERA += 1
         except OverflowError:
           lmi = b'32535215999'
       except (TypeError,IndexError,ValueError) as e:
@@ -84,11 +86,7 @@
         LOSE += 1
         return
       DATE=(DATE.translate(DTAB,DDEL))
-      if C_MONTH:
-        if DATE.startswith(C_MONTH):
-          DATE=DATE[C_MONTH_L:]
-        else:
-          NON_MONTH += 1
+      (DATE,LM) = shrink_key.shrink_date(DATE,C_BASE,lmi)
       WIN += 1
       try:
         URI.decode('ascii')
@@ -101,7 +99,7 @@
         URI=URI[4:]
       else:
         NON_HTTP += 1
-      l: int = len(lmi)
+      l: int = len(LM)
       kl: int = (len(DATE)+len(URI)+(len(SEG) if R_T else 0))
       OUT.write(b'+')
       OUT.write(b'%d'%kl)
@@ -113,28 +111,25 @@
         OUT.write(SEG)
       OUT.write(URI)
       OUT.write(b'->')
-      OUT.write(lmi)
+      OUT.write(LM)
       OUT.write(b'\n')
 
-def main(CCdate, segment, outdir, subdir = 'warc', fpat = None, era = '', month = '' ):
+def main(CCdate: str, segment: str, outdir: str, subdir: str, fpat: str, dp: str ):
   global OUT, N, WIN, LOSE, UERRS, SEG, R_T
-  global NON_HTTP, C_MONTH, C_MONTH_L, NON_MONTH, LM_ERA, LM_ERA_L, NON_ERA
+  global NON_HTTP, C_BASE, C_BASE_L
 
   SEG = segment.encode('utf8')
   R_T = (subdir == 'robotstxt')
-  LM_ERA = era.encode('utf8')
-  LM_ERA_L = len(era)
-  C_MONTH = month.encode('utf8')
-  C_MONTH_L = len(month)
-  infile_pat='bash -c "ls $CCC/CC-MAIN-%s/*.%s/orig/%s/*00%s.warc.gz | sort -k8"'%(
-    CCdate, segment, subdir, ("???" if fpat is None else (
-      (("{%s..%s}"%tuple(fpat.split(','))) if ',' in fpat else fpat))))
-  
+  C_BASE = dp.encode('ascii')
+  C_BASE_L = len(dp)
+  if fpat != '???':
+    fpat = ("{%s..%s}"%tuple(fpat.split(','))) if ',' in fpat else fpat
+  infile_pat='bash -c "ls $CCC/CC-MAIN-%s/*.%s/orig/%s/*00%s.warc.gz | sort -k8"'%(CCdate, segment, subdir, fpat)
   with open((outfile_name:="%s/%s/%s/lmh.cdb_in"%(outdir, segment, subdir)),'wb') as OUT:
     for infile_name in subprocess.run(infile_pat, shell=True,
                                    stdout=subprocess.PIPE).stdout.decode('utf8').split():
       print(infile_name,file=sys.stderr)
-      WIN = LOSE = N = UERRS = NON_HTTP = NON_MONTH = NON_ERA = 0
+      WIN = LOSE = N = UERRS = NON_HTTP = 0
       if subdir in ['warc','robotstxt']:
         warc.warc(infile_name,LMHline,[warc.RESP],parts=3)
       elif subdir == 'crawldiagnostics':
@@ -142,7 +137,7 @@
       else:
         print('bogus type %s'%subdir,file=sys.stderr)
         exit(1)
-      print('%d LM headers, %d win, %d lose, %d non-ASCII URIs, %d non-close, %d dodgy schemes, %d dodgy WARC dates'%(N,WIN,LOSE,UERRS,NON_ERA,NON_HTTP,NON_MONTH),
+      print('%d LM headers, %d win, %d lose, %d non-ASCII URIs, %d dodgy schemes'%(N,WIN,LOSE,UERRS,NON_HTTP),
                                                                  file=sys.stderr)
     OUT.write(b'\n')