Class ::nsmcp::BoundedLogScanner (public)
::nx::Class ::nsmcp::BoundedLogScanner
Defined in /usr/local/ns/tcl/nsmcp/log-01-scanner.tcl
- Testcases:
- No testcase defined.
Source code: :property cacheDirectory:required :property literals:required :property {channel {}} :property {limit {}} :property {blockBytes:integer 4194304} :property {native:boolean true} :property {nativeThreshold:integer 32} :variable keys {} :variable overlap 0 :variable buffer {} :variable bufferFrom 0 :variable bufferEnd 0 :variable windowCeiling -1 :variable windowHits 0 :variable readBytes 65536 :variable nativePrepared false :variable temporaryChannel {} :variable temporaryPath {} :method init {} { if {[llength ${:literals}]<1 || [llength ${:literals}]>20} {error "scanner requires 1..20 literals"} if {${:blockBytes}<1 || ${:blockBytes}>8388608 || ${:nativeThreshold}<1} {error "invalid scanner buffer limits"} foreach literal ${:literals} { set bytes [encoding convertto utf-8 $literal] set length [string length $bytes] if {$length<1 || $length>4096 || [regexp {[\x00\r\n]} $literal]} {error "scanner literals must be 1..4096 UTF-8 bytes without CR/LF/NUL"} lappend :keys $bytes set :overlap [expr {max(${:overlap},$length-1)}] } set :blockBytes [expr {max(${:blockBytes},${:overlap}+1)}] set :readBytes [expr {min(${:blockBytes},65536)}] if {${:channel} ne "" && (![string is entier -strict ${:limit}] || ${:limit}<0)} {error "scanner requires a fixed nonnegative byte limit"} } :public method destroy {} { if {[info exists :temporaryChannel] && ${:temporaryChannel} ne ""} {catch {close ${:temporaryChannel}}} if {[info exists :temporaryPath] && ${:temporaryPath} ne ""} {catch {file delete -- ${:temporaryPath}}} next } # A caller can supply an already bounded, record-aligned byte chunk instead # of a source channel. No source paths or raw bodies enter persistent state. :public method load {bytes} { if {${:channel} ne ""} {error "load requires an in-memory scanner"} if {[string length $bytes]>${:blockBytes}} {error "scanner chunk exceeds its byte bound"} :setBuffer $bytes 0 [string length $bytes] set :limit ${:bufferEnd} } :method setBuffer {bytes from ceiling} { set :buffer $bytes set :bufferFrom $from set :bufferEnd [expr {$from+[string length $bytes]}] set :windowCeiling $ceiling set :windowHits 0 set :nativePrepared false } :method prepareNative {} { if {${:nativePrepared}} {return} if {${:temporaryChannel} eq ""} { set :temporaryChannel [file tempfile :temporaryPath [file join ${:cacheDirectory} bounded-scan-]] fconfigure ${:temporaryChannel} -translation binary -buffering none } else { ns_ftruncate ${:temporaryChannel} 0 } seek ${:temporaryChannel} 0 puts -nonewline ${:temporaryChannel} ${:buffer} set :nativePrepared true } :method first {position ceiling} { set relative [expr {$position-${:bufferFrom}}] set available [expr {$ceiling-${:bufferFrom}}] set found -1 # Native descriptor searching is safe only on this bounded private file. # Dense windows switch to C string searching in the cached byte buffer, # avoiding one descriptor seek/read per adjacent candidate. if {${:native} && [llength ${:keys}]==1 && ${:windowHits}<${:nativeThreshold} && [info commands ns_fseekchars] ne ""} { :prepareNative seek ${:temporaryChannel} $relative set candidate [ns_fseekchars ${:temporaryChannel} [lindex ${:literals} 0]] if {$candidate>=0 && $candidate+[string length [lindex ${:keys} 0]]<=$available} {set found $candidate} } else { set buffer ${:buffer} foreach key ${:keys} { set candidate [string first $key $buffer $relative] if {$candidate>=0 && $candidate+[string length $key]<=$available && ($found<0 || $candidate<$found)} {set found $candidate} } } if {$found>=0} {return [expr {${:bufferFrom}+$found}]} return -1 } # Return the earliest raw match fully contained in [from, ceiling). The # ceiling can shrink between calls; buffered bytes beyond it never qualify. :public method find {from {ceiling {}}} { if {$ceiling eq ""} {set ceiling ${:limit}} if {![string is entier -strict $from] || ![string is entier -strict $ceiling] || $from<0 || $ceiling<$from || $ceiling>${:limit}} {error "invalid scanner byte interval"} set position $from while {$position<$ceiling} { if {${:channel} eq ""} { set found [:first $position $ceiling] if {$found>=0} {incr :windowHits} return $found } if {$position<${:bufferFrom} || $position>=${:bufferEnd} || ${:windowCeiling}!=$ceiling} { seek ${:channel} $position set expected [expr {min(${:readBytes},$ceiling-$position)}] set bytes [read ${:channel} $expected] if {[string length $bytes]<$expected} {error "source ended before its fixed scanner bound"} :setBuffer $bytes $position $ceiling } set found [:first $position [expr {min($ceiling,${:bufferEnd})}]] # A short batch key near the end cannot win before a longer key # beginning earlier but crossing this boundary has been checked. if {$found>=0 && (${:bufferEnd}>=$ceiling || $found<${:bufferEnd}-${:overlap})} {incr :windowHits; return $found} if {${:bufferEnd}>=$ceiling} {return -1} if {${:windowHits}==0} { set :readBytes [expr {min(${:blockBytes},2*${:readBytes})}] } else { set :readBytes [expr {min(${:blockBytes},65536)}] } set position [expr {max($from,${:bufferEnd}-${:overlap})}] # Refill even though the overlap is inside the previous buffer. set :windowCeiling -1 } return -1 } # In-memory physical-line candidates for access logs. A caller can request # sequential lines when normalization makes a raw literal prefilter unsafe. :public method nextLine {position {filter true}} { if {${:channel} ne ""} {error "nextLine requires an in-memory scanner"} if {$position<0 || $position>${:limit}} {error "invalid line cursor"} if {$position==${:limit}} {return {}} if {$filter} { set found [:find $position] if {$found<0} {return {}} set start [expr {[string last \n ${:buffer} [expr {$found-1}]]+1}] } else {set start $position} set end [string first \n ${:buffer} $start] set terminated [expr {$end>=0}] if {!$terminated} {set end ${:limit}} return [dict create start $start end [expr {$end+($terminated ? 1 : 0)}] raw [string range ${:buffer} $start [expr {$end-1}]] terminated $terminated] }XQL Not present: Generic, PostgreSQL, Oracle
![[i]](/resources/acs-subsite/ZoomIn16.gif)