.\" Automatically generated by Pod::Man 4.14 (Pod::Simple 3.40) .\" .\" Standard preamble: .\" ======================================================================== .de Sp \" Vertical space (when we can't use .PP) .if t .sp .5v .if n .sp .. .de Vb \" Begin verbatim text .ft CW .nf .ne \\$1 .. .de Ve \" End verbatim text .ft R .fi .. .\" Set up some character translations and predefined strings. \*(-- will .\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left .\" double quote, and \*(R" will give a right double quote. \*(C+ will .\" give a nicer C++. Capital omega is used to do unbreakable dashes and .\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff, .\" nothing in troff, for use with C<>. .tr \(*W- .ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p' .ie n \{\ . ds -- \(*W- . ds PI pi . if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch . if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch . ds L" "" . ds R" "" . ds C` "" . ds C' "" 'br\} .el\{\ . ds -- \|\(em\| . ds PI \(*p . ds L" `` . ds R" '' . ds C` . ds C' 'br\} .\" .\" Escape single quotes in literal strings from groff's Unicode transform. .ie \n(.g .ds Aq \(aq .el .ds Aq ' .\" .\" If the F register is >0, we'll generate index entries on stderr for .\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index .\" entries marked with X<> in POD. Of course, you'll have to process the .\" output yourself in some meaningful fashion. .\" .\" Avoid warning from groff about undefined register 'F'. .de IX .. .nr rF 0 .if \n(.g .if rF .nr rF 1 .if (\n(rF:(\n(.g==0)) \{\ . if \nF \{\ . de IX . tm Index:\\$1\t\\n%\t"\\$2" .. . if !\nF==2 \{\ . nr % 0 . nr F 2 . \} . \} .\} .rr rF .\" .\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2). .\" Fear. Run. Save yourself. No user-serviceable parts. . \" fudge factors for nroff and troff .if n \{\ . ds #H 0 . ds #V .8m . ds #F .3m . ds #[ \f1 . ds #] \fP .\} .if t \{\ . ds #H ((1u-(\\\\n(.fu%2u))*.13m) . ds #V .6m . ds #F 0 . ds #[ \& . ds #] \& .\} . \" simple accents for nroff and troff .if n \{\ . ds ' \& . ds ` \& . ds ^ \& . ds , \& . ds ~ ~ . ds / .\} .if t \{\ . ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u" . ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u' . ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u' . ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u' . ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u' . ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u' .\} . \" troff and (daisy-wheel) nroff accents .ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V' .ds 8 \h'\*(#H'\(*b\h'-\*(#H' .ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#] .ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H' .ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u' .ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#] .ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#] .ds ae a\h'-(\w'a'u*4/10)'e .ds Ae A\h'-(\w'A'u*4/10)'E . \" corrections for vroff .if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u' .if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u' . \" for low resolution devices (crt and lpr) .if \n(.H>23 .if \n(.V>19 \ \{\ . ds : e . ds 8 ss . ds o a . ds d- d\h'-1'\(ga . ds D- D\h'-1'\(hy . ds th \o'bp' . ds Th \o'LP' . ds ae ae . ds Ae AE .\} .rm #[ #] #H #V #F C .\" ======================================================================== .\" .IX Title "VNL-FILTER 1" .TH VNL-FILTER 1 "2020-12-09" "" "vnlog" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l .nh .SH "NAME" vnl\-filter \- filters vnlogs to select particular rows, fields .SH "SYNOPSIS" .IX Header "SYNOPSIS" .Vb 1 \& $ cat run.vnl \& \& # time x y z temperature \& 3 1 2.3 4.8 30 \& 4 1.1 2.2 4.7 31 \& 6 1 2.0 4.0 35 \& 7 1 1.6 3.1 42 \& \& \& $ = 35\*(Aq | vnl\-align \& \& # time x y z temperature \& 6 1 2.0 4.0 35 \& 7 1 1.6 3.1 42 \& \& \& \& $ 10\*(Aq .Ve .PP would select only those rows whose \f(CW\*(C`size\*(C'\fR column contains a value > 10. See the detailed description of matches expressions below for more detail. .SS "Context lines" .IX Subsection "Context lines" \&\f(CW\*(C`vnl\-filter\*(C'\fR supports the context output options (\f(CW\*(C`\-A\*(C'\fR, \f(CW\*(C`\-B\*(C'\fR and \f(CW\*(C`\-C\*(C'\fR) exactly like the \f(CW\*(C`grep\*(C'\fR tool. I.e to print out all rows whose \f(CW\*(C`size\*(C'\fR column contains a value > 10 \fIbut also\fR include the 3 rows immediately before \&\fIand\fR after such matching rows, do this: .PP .Vb 1 \& vnl\-filter \-C3 \*(Aqsize > 10\*(Aq .Ve .PP \&\f(CW\*(C`\-B\*(C'\fR reports the rows \fIbefore\fR matching ones and \f(CW\*(C`\-A\*(C'\fR the rows \fIafter\fR matching ones. \f(CW\*(C`\-C\*(C'\fR reports both. Note that this applies \fIonly\fR to \fImatches\fR expressions: records skipped because they fail \f(CW\*(C`\-\-has\*(C'\fR or \f(CW\*(C`\-\-skipempty\*(C'\fR are \&\fInot\fR included in contextual output. .SS "Backend choice" .IX Subsection "Backend choice" By default, the parsing of arguments and the legend happens in perl, which then constructs a simple awk script, and invokes \f(CW\*(C`mawk\*(C'\fR to actually read the data and to process it. This is done because awk is lighter weight and runs faster, which is important because our data sets could be quite large. We default to \&\f(CW\*(C`mawk\*(C'\fR specifically, since this is a simpler implementation than \f(CW\*(C`gawk\*(C'\fR, and runs much faster. If for whatever reason we want to do everything with perl, this can be requested with the \f(CW\*(C`\-\-perl\*(C'\fR option. .SS "Special functions" .IX Subsection "Special functions" For convenience we support several special functions in any expression passed on to awk or perl (named expressions, matches expressions, \f(CW\*(C`\-\-eval\*(C'\fR strings). These generally maintain some internal state, and vnl-filter makes sure that this state is consistent. Note that these are evaluated \fIafter\fR \&\f(CW\*(C`\-\-skipcomments\*(C'\fR and \f(CW\*(C`\-\-has\*(C'\fR. So any record skipped because of a \f(CW\*(C`\-\-has\*(C'\fR expression, for instance, will \fInot\fR be considered in \f(CW\*(C`prev()\*(C'\fR, \f(CW\*(C`diff()\*(C'\fR and so on. .IP "\(bu" 4 \&\f(CWrel(x)\fR returns value of \f(CW\*(C`x\*(C'\fR relative to the first value of \f(CW\*(C`x\*(C'\fR. For instance we might want to see the time or position relative to the start, not relative to some absolute beginning. Example: .Sp .Vb 1 \& $ cat tst.vnl \& \& # time x \& 100 200 \& 101 212 \& 102 209 \& \& \& $ ) # read each line \& { \& next unless matches; # skip non\-matching lines \& eval expression; # evaluate the arbitrary expression \& } .Ve .SS "\-\-function|\-\-sub" .IX Subsection "--function|--sub" Evaluates the given expression as a function that can be used in other expressions. This is most useful when you want to print something that can't trivially be written as a simple expression. For instance: .PP .Vb 5 \& $ cat tst.vnl \& # s \& 1\-2 \& 3\-4 \& 5\-6 \& \& $ < tst.vnl \& vnl\-filter \-\-function \*(Aqbefore(x) { sub("\-.*","",x); return x }\*(Aq \e \& \-\-function \*(Aqafter(x) { sub(".*\-","",x); return x }\*(Aq \e \& \-p \*(Aqb=before(s),a=after(s)\*(Aq \& # b a \& 1 2 \& 3 4 \& 5 6 .Ve .PP See the \s-1CAVEATS\s0 section below if you're doing something sufficiently-complicated where you need this. .SS "\-\-[no]skipempty" .IX Subsection "--[no]skipempty" Do [not] skip records where all fields are blank. By default we \fIdo\fR skip all empty records; to include them, pass \f(CW\*(C`\-\-noskipempty\*(C'\fR .SS "\-\-skipcomments" .IX Subsection "--skipcomments" Don't output non-legend comments .SS "\-\-perl" .IX Subsection "--perl" By default all procesing is performed by \f(CW\*(C`mawk\*(C'\fR, but if for whatever reason we want perl instead, pass \f(CW\*(C`\-\-perl\*(C'\fR. Both modes work, but \f(CW\*(C`mawk\*(C'\fR is noticeably faster. \f(CW\*(C`\-\-perl\*(C'\fR could be useful because it is more powerful, which could be important since a number of things pass commandline strings directly to the underlying language (named expressions, matches expressions, \f(CW\*(C`\-\-eval\*(C'\fR strings). Note that while variables in perl use sigils, column references should \fInot\fR use sigils. To print the sum of all values in column \f(CW\*(C`a\*(C'\fR you'd do this in awk .PP .Vb 1 \& vnl\-filter \-\-eval \*(Aq{suma += a} END {print suma}\*(Aq .Ve .PP and this in perl .PP .Vb 1 \& vnl\-filter \-\-perl \-\-eval \*(Aq{$suma += a} END {say $suma}\*(Aq .Ve .PP The perl strings are evaluated without \f(CW\*(C`use strict\*(C'\fR or \f(CW\*(C`use warnings\*(C'\fR so I didn't have to declare \f(CW$suma\fR in the example. .PP With \f(CW\*(C`\-\-perl\*(C'\fR, empty strings (\f(CW\*(C`\-\*(C'\fR in the vnlog file) are converted to \&\f(CW\*(C`undef\*(C'\fR. .SS "\-\-dumpexprs" .IX Subsection "--dumpexprs" Used for debugging. This spits out all the final awk (or perl) program we run for the given commandline options and given input. This is the final program, with the column references resolved to numeric indices, so one can figure out what went wrong. .SS "\-\-unbuffered" .IX Subsection "--unbuffered" Flushes each line after each print. This makes sure each line is output as soon as it is available, which is crucial for realtime output and streaming plots. .SS "\-\-stream" .IX Subsection "--stream" Synonym for \f(CW\*(C`\-\-unbuffered\*(C'\fR .SH "CAVEATS" .IX Header "CAVEATS" This tool is very lax in its input validation (on purpose). As a result, columns with names like \f(CW%CPU\fR and \f(CW\*(C`TIME+\*(C'\fR do work (i.e. you can more or less feed in output from \f(CW\*(C`top \-b\*(C'\fR). The downside is that shooting yourself in the foot is possible. This tradeoff is currently tuned to be very permissive, which works well for my use cases. I'd be interested in hearing other people's experiences. Potential pitfalls/unexpected behaviors: .IP "\(bu" 4 All column names are replaced in all eval strings without regard to context. The earlier example that reports the sum of values in a column: \f(CW\*(C`vnl\-filter \-\-eval \&\*(Aq{suma += a} END {print suma}\*(Aq\*(C'\fR will work fine if we \fIdo\fR have a column named \&\f(CW\*(C`a\*(C'\fR and do \fInot\fR have a column named \f(CW\*(C`suma\*(C'\fR. But will not do the right thing if any of those are violated. For instance, if a column \f(CW\*(C`a\*(C'\fR doesn't exist, then \&\f(CW\*(C`awk\*(C'\fR would see \f(CW\*(C`suma += a\*(C'\fR instead of something like \f(CW\*(C`suma += $5\*(C'\fR. \f(CW\*(C`a\*(C'\fR would be an uninitialized variable, which evaluates to 0, so the full \&\f(CW\*(C`vnl\-filter\*(C'\fR command would not fail, but would print 0 instead. It's the user's responsibility to make sure we're talking about the right columns. The focus here was one-liners so hopefully nobody has so many columns, they can't keep track of all of them in their head. I don't see any way to resolve this without seriously impacting the scope of the tool, so I'm leaving this alone. .IP "\(bu" 4 It is natural to use vnlog as a database. You can run queries with something like .Sp .Vb 1 \& vnl\-filter \*(Aqkey == 5\*(Aq .Ve .Sp This works. But unlike a real database this is clearly a linear lookup. With large data files, this would be significantly slower than the logarithmic searches provided by a real database. The meaning of \*(L"large\*(R" and \*(L"significant\*(R" varies, and you should test it. In my experience vnlog \*(L"databases\*(R" scale surprisingly well. But at some point, importing your data to something like sqlite is well worth it. .IP "\(bu" 4 When substituting column names I match \fIeither\fR a word-nonword transition (\f(CW\*(C`\eb\*(C'\fR) \fIor\fR a whitespace-nonword transition. The word boundaries is what would be used 99% of the time. But the keys may have special characters in them, which don't work with \f(CW\*(C`\eb\*(C'\fR. This means that whitespace becomes important: \f(CW\*(C`1+%CPU\*(C'\fR will not be parsed as expected, which is correct since \f(CW\*(C`+%CPU\*(C'\fR is also a valid field name. But \f(CW\*(C`1+ %CPU\*(C'\fR will be parsed correctly, so if you have weird field names, put the whitespace into your expressions. It'll make them more readable anyway. .IP "\(bu" 4 Strings passed to \f(CW\*(C`\-p\*(C'\fR are split on \f(CW\*(C`,\*(C'\fR \fIexcept\fR if the \f(CW\*(C`,\*(C'\fR is inside balanced \f(CW\*(C`()\*(C'\fR. This makes it possible to say things like \f(CW\*(C`vnl\-filter \-\-function \&\*(Aqf(a,b) { ... }\*(Aq \-p \*(Aqc=f(a,b)\*(Aq\*(C'\fR. This is probably the right behavior, although some questionable looking field names become potentially impossible: \f(CW\*(C`f(a\*(C'\fR and \&\f(CW\*(C`b)\*(C'\fR \fIcould\fR otherwise be legal field names, but you're probably asking for trouble if you do that. .IP "\(bu" 4 Currently there're two modes: a pick/print mode and an \f(CW\*(C`\-\-eval\*(C'\fR mode. Then there's also \f(CW\*(C`\-\-function\*(C'\fR, which adds bits of \f(CW\*(C`\-\-eval\*(C'\fR to the pick/print mode, but it feels maybe insufficient. I don't yet have strong feelings about what this should become. Comments welcome .SH "REPOSITORY" .IX Header "REPOSITORY" https://github.com/dkogan/vnlog/ .SH "AUTHOR" .IX Header "AUTHOR" Dima Kogan \f(CW\*(C`\*(C'\fR .SH "LICENSE AND COPYRIGHT" .IX Header "LICENSE AND COPYRIGHT" Copyright 2016\-2017 California Institute of Technology .PP Copyright 2017\-2019 Dima Kogan \f(CW\*(C`\*(C'\fR .PP This library is free software; you can redistribute it and/or modify it under the terms of the \s-1GNU\s0 Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.