#!/usr/bin/ruby # vim: et sw=2 VER="(985514d)" ME="links.rb: " $mrg_fields=["vlan","vpi","vci","comment"] $chk_fields=["speed","vlan","vpi","vci"] $lst_fields=["vlan","vpi","vci"] $str_fields=["speed","comment"] $agr_fields=["pc","lag"] $int_fields=[] $check=false $hdrs=false $sub=false $incl=[] $excl=[] $skip=[] $expr=[] flags="" class Object def _str(p=nil) return to_s if !p or self.class!=Float return "%.#{p}f" % self end end def in_list(s,list) for i in list if i.match(s) return true end end return false end def regexfix(str) tmp=[] for t in str.scan(/(_)|(\|)|([^_\|]*)/).flatten.compact tmp.push(t) if not t.empty? end res="" while t=tmp.shift case t when("_") then res+=(tmp.empty? or tmp[0]=="|") ? "([ .]+|$)" : "[ .]+" when("|") then res+="|" else res+="#{t}" end end return res end def in_fields(t,fields) fields.length.times do |i| return i if fields[i]==t end return nil end def scomp(x,y) return 0 if x.empty? and y.empty? return -1 if x.empty? return 1 if y.empty? xa=x.scan(/([0-9]+)|([^0-9]+)/) ya=y.scan(/([0-9]+)|([^0-9]+)/) r=nil xi=xa.shift yi=ya.shift while xi and yi if xi[0] and yi[0] xv=xi[0].to_i yv=yi[0].to_i if(xv==yv) r=0 xi=xa.shift yi=ya.shift next end r=(xv>yv) ? 1 : -1 break end if xi[1] and yi[1] xv=xi[1] yv=yi[1] if(xv==yv) r=0 xi=xa.shift yi=ya.shift next end r=(xv<=>yv) break end break end return (x<=>y) if !r if r==0 r=-1 if !xi and yi r=1 if xi and !yi end return r end class Expr FN0=["count","show"] FN1=["count","sum","min","max","avg","add","sub","icnt","show","pfxl","pfxc"] FN2=["sumif"] FNS=FN1+FN2 AGG=["count","sum","sumif","min","max","avg"] SAG=["add","sub"] OPS=["+","-","*","/"] CMP=["=","==","!=","<","<=",">",">="] LOG=["|","&","^","?","!"] ALL=OPS+CMP+LOG OPL={ "|"=>1,"&"=>1,"^"=>1, "="=>2,"=="=>2,"!="=>2,"<"=>2,"<="=>2,">"=>2,">="=>2, "+"=>3,"-"=>3,"*"=>4,"/"=>4, "?"=>0,"!"=>9 } Ops=Struct.new :ops, :op, :ac attr_reader :fld, :aggr def initialize(arr) @aggr=0 # 1:AGG, 2:SAG, 4:vaggr @show=false @temp=false @fld=nil @str=arr.join @ops=[] tmp=[] tmp[0]=Ops.new([],nil,0) i=0 if arr.length>1 and arr[1]==":" t=arr.shift if m=/^#(.*)/.match(t) @fld=m[1] @temp=true else @fld=t end arr.shift end for s in arr if s=="," or s==")" while i>0 and tmp[i].ac==0 tmp[i].ops.push(tmp[i].op) if tmp[i].op tmp[i-1].ops+=tmp[i].ops i-=1 end if tmp[i].op=="?" if tmp[i].ac!=-3 $stderr.puts ME+"syntax error" exit end tmp[i].ops.push(tmp[i].op) tmp[i-1].ops+=tmp[i].ops i-=1 end end if s==":" while i>0 and tmp[i].ac==0 tmp[i].ops.push(tmp[i].op) if tmp[i].op tmp[i-1].ops+=tmp[i].ops i-=1 end if tmp[i].op!="?" or tmp[i].ac!=-2 $stderr.puts ME+"syntax error" exit end tmp[i].ac=-3 next end if s=="?" if tmp[i].op=="?" $stderr.puts ME+"syntax error" exit end if tmp[i].op tmp[i].ops.push(tmp[i].op) tmp[i].op=nil end tmp[i+1]=Ops.new([],s,-2) i+=1 next end if s=="()" if !tmp[i].op or not FNS.include?(tmp[i].op) $stderr.puts ME+"syntax error" exit end if not FN0.include?(tmp[i].op) $stderr.puts ME+"function needs argument" exit end tmp[i].ops.push("1",tmp[i].op) tmp[i].op=nil next end if s=="," if i<1 or tmp[i-1].ac<2 $stderr.puts ME+"syntax error" exit end tmp[i].ops.push(tmp[i].op) if tmp[i].op tmp[i].op=nil tmp[i-1].ac-=1 next end if s=="(" tmp[i+1]=Ops.new([],nil,-1) i+=1 next end if s==")" if tmp[i].ac!=-1 $stderr.puts ME+"parantheses mismatch" exit end tmp[i].ops.push(tmp[i].op) if tmp[i].op tmp[i-1].ops+=tmp[i].ops i-=1 next if tmp[i].ac<1 if tmp[i].ac>1 $stderr.puts ME+"missing argument" exit end if i<1 tmp[i].ops.push(tmp[i].op) if tmp[i].op tmp[i].op=nil tmp[i].ac=0 next end tmp[i].ops.push(tmp[i].op) if tmp[i].op tmp[i-1].ops+=tmp[i].ops i-=1 next end if s=="!" if !tmp[i].op and tmp[i].ac==0 tmp[i].op=s next end tmp[i+1]=Ops.new([],s,0) i+=1 next end if FNS.include?(s) @aggr|=1 if AGG.include?(s) @aggr|=2 if SAG.include?(s) if ("%b" % @aggr).count("1")>1 $stderr.puts ME+"cannot mix aggregation functions" exit end @show=true if s=="show" c=0 c=1 if FN1.include?(s) c=2 if FN2.include?(s) if !tmp[i].op and tmp[i].ac==0 tmp[i].op=s tmp[i].ac=c next end tmp[i+1]=Ops.new([],s,c) i+=1 next end if ALL.include?(s) if !tmp[i].op tmp[i].op=s next end if OPL[s]>OPL[tmp[i].op] tmp[i+1]=Ops.new([tmp[i].ops.pop],s,0) i+=1 next end if OPL[s]==OPL[tmp[i].op] tmp[i].ops.push(tmp[i].op) tmp[i].op=s next end while i>0 and tmp[i].ac==0 tmp[i].ops.push(tmp[i].op) if tmp[i].op tmp[i-1].ops+=tmp[i].ops i-=1 end tmp[i].ops.push(tmp[i].op) tmp[i].op=s next end tmp[i].ops.push(s) if tmp[i].op=="!" tmp[i].ops.push(tmp[i].op) tmp[i-1].ops+=tmp[i].ops i-=1 end end if tmp[i].ac==-1 $stderr.puts ME+"parantheses mismatch" exit end while i>1 and tmp[i].ac==0 tmp[i].ops.push(tmp[i].op) if tmp[i].op tmp[i-1].ops+=tmp[i].ops i-=1 end if tmp[i].op=="?" tmp[i].ops.push(tmp[i].op) tmp[i-1].ops+=tmp[i].ops i-=1 end if i>1 $stderr.puts ME+"stack error" exit end if i==1 tmp[i].ops.push(tmp[i].op) if tmp[i].op tmp[i-1].ops+=tmp[i].ops end @ops=tmp[0].ops @ops.push(tmp[0].op) if tmp[0].op end def self.parse(str) expr=[] arr=[] pc=0 for s in str.scan(/!=|==|<=|>=|\(\)|[\:\,\=\?\<\>\+\-\*\/\!\|\&\^\(\)]|"[^"]*"|[@#a-zA-Z 0-9\.]*/) next if s.empty? if s=="," and pc==0 e=Expr.new(arr) e.chk_vaggr(expr) expr<v) ? r : v if r s.push(v) ar.push(v) next end if o=="avg" if s.length<1 $stderr.puts ME+"expr: avg(): need argument" exit end a=s.pop if !a $stderr.puts ME+"expr: avg(): invalid argument" exit end v=a.to_f rs=av.shift rc=av.shift if rs and rc rs+=v rc+=1 v=rs/rc else rs=v rc=1 end s.push(v) ar.push(rs,rc) next end if o=="add" if s.length<1 $stderr.puts ME+"expr: add(): need argument" exit end a=s.pop if !a $stderr.puts ME+"expr: add(): invalid argument" exit end v=a.to_f r=av.shift r=0.0 if !r v=(bl) ? r+v : 0.0 s.push(v) ar.push(v) next end if o=="sub" if s.length<1 $stderr.puts ME+"expr: sub(): need argument" exit end a=s.pop if !a $stderr.puts ME+"expr: sub(): invalid argument" exit end v=a.to_f r=av.shift r=0.0 if !r v=(bl) ? r-v : v s.push(v) ar.push(v) next end if o=="icnt" if s.length<1 $stderr.puts ME+"expr: icnt(): need argument" exit end a=s.pop if !a $stderr.puts ME+"expr: icnt(): invalid argument" exit end v=a.split(/[, ]+/).compact.length s.push(v) next end if o=="show" if s.length<1 $stderr.puts ME+"expr: show(): need argument" exit end a=s.pop if !a s.push(0) next end if a.class==String a=(a.empty? or a=="-") ? 0 : 1 else a=a.to_i end v=(a==0) ? 0 : 1 s.push(v) next end if o=="pfxl" if s.length<1 $stderr.puts ME+"expr: pfxl(): need argument" exit end a=s.pop if !a or a.class!=String $stderr.puts ME+"expr: pfxl(): invalid argument" next end d=(a.include?(":")) ? 128 : 32 t=a.split("/") if t.length!=2 s.push(d) next end v=t[1].to_i s.push(v) next end if o=="pfxc" if s.length<1 $stderr.puts ME+"expr: pfxc(): need argument" exit end a=s.pop if !a or a.class!=String $stderr.puts ME+"expr: pfxc(): invalid argument" next end if a.include?(":") s.push(0) next end t=a.split("/") if t.length==2 v=32-t[1].to_i s.push(1<" b=s.pop.to_f a=s.pop.to_f v=(a>b) ? 1 : 0 s.push(v) next end if o==">=" b=s.pop.to_f a=s.pop.to_f v=(a>=b) ? 1 : 0 s.push(v) next end if /^"/.match(o) o=o.delete('"') s.push(o) next end if /^[0-9.]+$/.match(o) if o.include?(".") o=o.to_f else o=o.to_i end s.push(o) next end t=o.downcase if m=/^@(.*)/.match(t) if bl o="" if !o=vars["@"+m[1]] else o="" if !o=fld[m[1]] vars["@"+m[1]]=o end elsif fld.has_key?(t) o="" if !o=fld[t] elsif vars.has_key?(t) o="" if !o=vars[t] else $stderr.puts ME+"expr: unknown var: "+o exit end s.push(o) end if not av.empty? $stderr.puts ME+"syntax error" exit end av.replace(ar) v=s.pop vars[@fld]=v if @fld return v end def name return @fld if @fld return @str end def temp? return @temp end def aggr?(*ba) for b in ba return true if (@aggr&b)==b end return false end def show? return @show end def chk_vaggr(expr) vars=[] for o in @ops next if FNS.include?(o) or ALL.include?(o) vars<=u[0].to_i and xi<=u[1].to_i f=true break end end next f end a.sort! { |x,y| scomp(x,y) } v=a.join(",") @attr[n][fld]=v return end if $str_fields.include?(fld) v=(v) ? v+"; "+t : t @attr[n][fld]=v return end @attr[n][fld]=t end def cattr(fld) v=@attr[1][fld] if $str_fields.include?(fld) t=@attr[2][fld] return v if !t return t if !v return v if t==v return v+"; "+t end return v end def iattr(fld,t=nil) if t @attr[1][fld]=t return end v=@attr[1][fld] return v if v return 0 end def [] k @attr[1][k] end def has_key?(k) return true if @attr[1].has_key?(k) return @flds.include?(k) end def merge(fld) 1.upto(2) do |n| for f in $mrg_fields next if !v=fld.attr(f,n) attr(f,n,v) end end for f in $int_fields next if !v=fld[f] @attr[1][f]+=v end end def check(fld) v1=@attr[1][fld] v2=@attr[2][fld] return false if !v1 or !v2 if $lst_fields.include?(fld) if v1.start_with?("*") or v2.start_with?("*") attr(fld,1,v2) return false end if v1.start_with?("+") or v2.start_with?("+") attr(fld,1,v2) return false end end return (v1==v2) ? false : true end end class Node attr_accessor :idx, :nm def initialize(nm,ip,tp,loc=nil) @nm=nm @ip=ip @tp=tp @loc=loc end def name if @loc return @nm+' "'+@loc+'"' end return @nm end def type @tp end def color return ["#d8d8d8","#d8d8d8"] if !@tp return case @tp when("Gateway") then ["#ef715a","#b0de8f"] when("Router") then ["#ef715a","#ef715a"] when("Switch L2") then ["#9eb0ff","#9eb0ff"] when("Switch L3") then ["#9eb0ff","#ef715a"] when("Access Switch") then ["#9c9e9c","#9c9e9c"] when("Access Point") then ["#9ee0d0","#9ee0d0"] when("Accces Server") then ["#9ecc7d","#9ecc7d"] else ["#d8d8d8","#d8d8d8"] end end end class Link alias _dup dup attr_reader :n1, :n2, :if1, :if2, :flds, :av attr_accessor :ml, :we, :wecnt, :link def self.get_link(n1,n2,if1,if2) for l in $links return l if l.n1==n1 and l.n2==n2 and l.if1==if1 and l.if2==if2 return l if l.n1==n2 and l.n2==n1 and l.if1==if2 and l.if2==if1 end return nil end def self.chk_links # sublinks i=0 while i<$links.length li=$links[i] j=i+1 for e in $expr li.exec(e) if not e.aggr?(4) end while j<$links.length lj=$links[j] if lj.link==li li.ml=true for e in $expr v=lj.exec(e,li.av) if not e.aggr?(4) li.flds.iattr(e.fld,v) if e.aggr?(2) end $links.delete_at(j) next end j+=1 end i+=1 end # portchannels i=0 while i<$links.length li=$links[i] j=i+1 while j<$links.length lj=$links[j] pj=nil for f in $agr_fields break if pj=lj.flds[f] end ok=false ok=true if pj and lj.n1==li.n1 and lj.n2==li.n2 and pj==li.if1 if ok li.we+=lj.we $links.delete_at(j) next end j+=1 end i+=1 end # multilinks i=0 while i<$links.length li=$links[i] j=i+1 while j<$links.length lj=$links[j] ok=false ok=true if lj.n1==li.n1 and lj.n2==li.n2 ok=true if lj.n1==li.n2 and lj.n2==li.n1 if ok li.ml=true li.we+=lj.we li.wecnt+=1 li.flds.merge(lj.flds) $links.delete_at(j) next end j+=1 end i+=1 end end def initialize(n1,n2,if1,if2,we,flds) @n1,@n2=n1,n2 @if1,@if2=if1,if2 @ml=false @we=we @wecnt=1 @flds=Fields.new(flds) @link=nil @av={} @vars={} end def dup(nm,ifn) obj=_dup obj.send :fix,nm,ifn return obj end def speed(w=nil) return @we/@wecnt if !w @we=w if @we==0.0 or w<@we end def attr(attr,n,val=nil) return @flds.attr(attr,n,val) if val return @flds.attr(attr,n) end def iattr(attr,val=nil) return @flds.iattr(attr,val) end def cattr(attr) v=@flds.cattr(attr) return (v) ? "#{v.dump}" : '""' end def sattr(attr) v=@flds.cattr(attr) return (v) ? "#{v.dump}" : '""' end def sattr1(attr) v=@flds.attr(attr,1) return (v) ? "#{v.dump}" : '""' end def chk_fields() for f in $chk_fields if @flds.check(f) $stderr.printf ME+"Warning: %s mismatch: %s:%s <-> %s:%s\n",f,@n1.nm,@if1,@n2.nm,@if2 end end end def exec(e,av=nil) if av bl=true else bl=false av=@av end av[e]=[] if !av[e] v=e.exec(@flds,av[e],$vars,bl).to_f @flds.iattr(e.fld,v) return v end private def fix(nm,ifn) @n2=nm @if2=ifn end end def get_node_type(s) return case s when(/^gw-/i) then "Gateway" when(/^rt-/i) then "Router" when(/^sw-/i) then "Switch L2" when(/^ap-/i) then "Access Point" when(/^as-/i) then "Access Server" when(/^swr-/i) then "Switch L3" when(/^sw3-/i) then "Switch L3" when(/^swa-/i) then "Access Switch" when(/^C1[78]/i) then "Router" when(/^C28/i) then "Router" when(/^C29/i) then "Switch L2" when(/^C3[5678]/i) then "Switch L3" when(/^C45/i) then "Switch L3" when(/^C6[58]/i) then "Switch L3" when(/^C7[23]/i) then "Router" when(/^C76/i) then "Switch L3" when(/^Quanta-/i) then "Switch L3" when(/^Wbx-/i) then "Switch L3" when(/^S3048-/i) then "Switch L3" else nil end end def get_node_type2(s) return case s when(/^GW/i) then "Gateway" when(/^RT/i) then "Router" when(/^SW/i) then "Switch L2" when(/^AP/i) then "Access Point" when(/^AS/i) then "Access Server" when(/^SW[R3]/i) then "Switch L3" when(/^SWA/i) then "Access Switch" else nil end end def _grp_expand(wet,w,str) return if !str or !w tmp=str.split(",") for t in tmp if not t.include?("-") wet[t]< 1, "G" => 1000, "T" => 1000000} return w=tmp[1].to_f*t[tmp[2]] end w=case s when("E") then 10.0 when("FE") then 100.0 when("GE") then 1000.0 when("10GE") then 10000.0 when("25GE") then 25000.0 when("40GE") then 40000.0 when("100GE") then 100000.0 when("E1") then 2.0 when("E2") then 8.4 when("E3") then 34.4 when("E4") then 139.3 when("E5") then 564.9 when("T1") then 1.5 when("T1C") then 3.1 when("T2") then 6.3 when("T3") then 44.7 when("T4") then 274.1 when("T5") then 400.3 when("DS1") then 1.5 when("DS1C") then 3.1 when("DS2") then 6.3 when("DS3") then 44.7 when("DS4") then 274.1 when("DS5") then 400.3 when("STM-0") then 51.8 when("STM-1") then 155.5 when("STM-4") then 622.0 when("STM-16") then 2488.3 when("STM-64") then 9953.2 when("STM-256") then 39813.1 when("STM-1024") then 159252.4 when("OC-1") then 51.8 when("OC-3") then 155.5 when("OC-12") then 622.0 when("OC-24") then 1244.1 when("OC-48") then 2488.3 when("OC-192") then 9953.2 when("OC-768") then 39813.1 when("OC-3072") then 159252.4 else s.to_f end return w end def mk_wet(str) m=/\<(.*)\>/.match(str) return nil if !m wet={} if not m[1].include?(":") w=get_speed(m[1]) wet["*"]=w return wet end tmp=m[1].split(" ") spd={} for t in tmp m=/(.*):(.*)/.match(t) next if !m m[2].split(",").each do |g| spd[g]=get_speed(m[1]) end end wet["*"]=spd["*"] if spd["*"] if m=/\|[^ ]*\|/.match(str) gs=m[0].split("|") gs.shift idx=1 for g in gs si=idx.to_s _grp_expand(wet,spd[si],g) idx+=1 end end return wet end def port_speed(ifn,wet=nil) w=case ifn when(/^Et[0-9]/) then 10.0 when(/^Fa[0-9]/) then 100.0 when(/^Gi[0-9]/) then 1000.0 when(/^Te[0-9]/) then 10000.0 else nil end return w if w return 0.0 if !wet return wet[ifn] if wet[ifn] return wet["*"] if wet["*"] return 0.0 end def load_file(buf,fn) dir=File.dirname(fn) begin f=File.open(fn,"r") rescue $stderr.puts ME+"unable to open file" exit end for l in f l.chop! next if l.empty? next if /^! /.match(l) buf.push(l) end f.close end def usage me=ME.delete(":") puts "Usage: "+me+"[-n] [-I ] [-X ] [-S ] ..." puts " -n - just validate (aka dry-run)" puts " -I - include lines matching RegEx " puts " -X - exclude lines matching RegEx " puts " -S - skip blocks matching RegEx " puts " -e - do simple math expr on every record" exit end i=0 while i=ARGV.length for l in $stdin l.chop! next if l.empty? next if /^! /.match(l) next if /^<[^>]*>$/.match(l) buf.push(l) end else while i]*>$/.match(l) buf.push(l) end else load_file(buf,fn) end end end $nodes={} $links=[] for s in buf if tmp=/^# +([^ ]*)/.match(s) nm=tmp[1] m=/\[([^ ]*)\]/.match(s) ip=(m) ? m[1] : nil m=/"([^"]*)"/.match(s) loc=(m) ? m[1] : nil m=/\{(.*)\}/.match(s) type=(m) ? m[1] : nil tp=(type) ? get_node_type2(type) : get_node_type(nm) next if $nodes[nm] next if in_list(nm,$skip) $nodes[nm]=Node.new(nm,ip,tp,loc) end end inbl=igbl=false hdr=fld=wet=nil fields=ft=nil n1=n2=nil if1=if2=nil l=bl=nil vlan=nil lif=nil tmp=nil a=nil ll=[] for s in buf if inbl if /^--+/.match(s) inbl=igbl=false hdr=fld=wet=nil idx=ln=nil fields=ft=nil n1=n2=nil if1=if2=nil l=bl=nil vlan=nil lif=nil next end next if igbl next if !n1 tmp=s.split(/\t+/) next if !tmp[1] if tmp[0].empty? next if !l c=tmp.length next if c<2 tc=/\t+/.match(s)[0].length i=ft.index(tc) next if !i or i<1 if i==1 ll<1 f=fields[i] t=tmp[j] if t.start_with?("!") l.attr("comment",a,t) break end i+=1 j+=1 c-=1 l.attr(f,a,t) end next end if l and not ll.empty? for t in ll if /:/.match(t) t1=t.split(/:/) nm=t1[0] if2=t1[1] else nm=t if2="NULL" end if !n2=$nodes[nm] next if $hdrs next if not $incl.empty? and not in_list(nm,$incl) next if in_list(nm,$excl) n2=Node.new(nm,nil,get_node_type(nm)) $nodes[nm]=n2 end $links<0 and (we1==0 or we2
EOF f.close fork { exec("start /tmp/links.htm") }