diff --git a/lib/App/feedgnuplot.pm b/lib/App/feedgnuplot.pm new file mode 100755 index 0000000..f0005fe --- /dev/null +++ b/lib/App/feedgnuplot.pm @@ -0,0 +1,650 @@ +package App::feedgnuplot; + +our $VERSION = 1.26; + +=head1 NAME + +feedgnuplot - General purpose pipe-oriented plotting tool + +=head1 SYNOPSIS + +Simple plotting of piped data: + + $ seq 5 | awk '{print 2*$1, $1*$1}' + 2 1 + 4 4 + 6 9 + 8 16 + 10 25 + + $ seq 5 | awk '{print 2*$1, $1*$1}' | + feedgnuplot --lines --points --legend 0 "data 0" --title "Test plot" --y2 1 + +Simple real-time plotting example: plot how much data is received on the wlan0 +network interface in bytes/second (uses bash, awk and Linux): + + $ while true; do sleep 1; cat /proc/net/dev; done | + gawk '/wlan0/ {if(b) {print $2-b; fflush()} b=$2}' | + feedgnuplot --lines --stream --xlen 10 --ylabel 'Bytes/sec' --xlabel seconds + +=head1 DESCRIPTION + +This is a flexible, command-line-oriented frontend to Gnuplot. It creates +plots from data coming in on STDIN or given in a filename passed on the +commandline. Various data representations are supported, as is hardcopy +output and streaming display of live data. A simple example: + + $ seq 5 | awk '{print 2*$1, $1*$1}' | feedgnuplot + +You should see a plot with two curves. The C command generates some data to +plot and the C reads it in from STDIN and generates the plot. The +C invocation is just an example; more interesting things would be plotted +in normal usage. No commandline-options are required for the most basic +plotting. Input parsing is flexible; every line need not have the same number of +points. New curves will be created as needed. + +The most commonly used functionality of gnuplot is supported directly by the +script. Anything not directly supported can still be done with the +C<--extracmds> and C<--curvestyle> options. Arbitrary gnuplot commands can be +passed in with C<--extracmds>. For example, to turn off the grid, pass in +C<--extracmds 'unset grid'>. As many of these options as needed can be passed +in. To add arbitrary curve styles, use C<--curvestyle curveID extrastyle>. Pass +these more than once to affect more than one curve. To apply an extra style to +I the curves that lack an explicit C<--curvestyle>, pass in +C<--curvestyleall extrastyle>. + +=head2 Data formats + +By default, each value present in the incoming data represents a distinct data +point, as demonstrated in the original example above (we had 10 numbers in the +input and 10 points in the plot). If requested, the script supports more +sophisticated interpretation of input data + +=head3 Domain selection + +If C<--domain> is passed in, the first value on each line of input is +interpreted as the I-value for the rest of the data on that line. Without +C<--domain> the I-value is the line number, and the first value on a line is +a plain data point like the others. Default is C<--nodomain>. Thus the original +example above produces 2 curves, with B<1,2,3,4,5> as the I-values. If we run +the same command with --domain: + + $ seq 5 | awk '{print 2*$1, $1*$1}' | feedgnuplot --domain + +we get only 1 curve, with B<2,4,6,8,10> as the I-values. As many points as +desired can appear on a single line, but all points on a line are associated +with the I-value at the start of that line. + +=head3 Curve indexing + +By default, each column represents a separate curve. This is fine unless sparse +data is to be plotted. With the C<--dataid> option, each point is represented by +2 values: a string identifying the curve, and the value itself. If we add +C<--dataid> to the original example: + + $ seq 5 | awk '{print 2*$1, $1*$1}' | feedgnuplot --dataid --autolegend + +we get 5 different curves with one point in each. The first column, as produced +by C, is B<2,4,6,8,10>. These are interpreted as the IDs of the curves to +be plotted. The C<--autolegend> option adds a legend using the given IDs to +label the curves. The IDs need not be numbers; generic strings are accepted. As +many points as desired can appear on a single line. C<--domain> can be used in +conjunction with C<--dataid>. + +=head3 Multi-value style support + +Depending on how gnuplot is plotting the data, more than one value may be needed +to represent a single point. For example, the script has support to plot all the +data with C<--circles>. This requires a radius to be specified for each point in +addition to the position of the point. Thus, when plotting with C<--circles>, 2 +numbers are read for each data point instead of 1. A similar situation exists +with C<--colormap> where each point contains the position I the +color. There are other gnuplot styles that require more data (such as error +bars), but none of these are directly supported by the script. They can still be +used, though, by specifying the specific style with C<--curvestyle>, and +specifying how many extra values are needed for each point with +C<--extraValuesPerPoint extra>. C<--extraValuesPerPoint> is ONLY needed for the +styles not explicitly supported; supported styles set that variable +automatically. + +=head3 3D data + +To plot 3D data, pass in C<--3d>. C<--domain> MUST be given when plotting 3D +data to avoid domain ambiguity. If 3D data is being plotted, there are by +definition 2 domain values instead of one (I as a function of I and I +instead of I as a function of I). Thus the first 2 values on each line are +interpreted as the domain instead of just 1. The rest of the processing happens +the same way as before. + +=head3 Time/date data + +If the input data domain is a time/date, this can be interpreted with +C<--timefmt>. This option takes a single argument: the format to use to parse +the data. The format is documented in 'set timefmt' in gnuplot, although the +common flags that C understands are generally supported. The backslash +sequences in the format are I supported, so if you want a tab, put in a tab +instead of \t. Whitespace in the format I supported. When this flag is +given, some other options act a little bit differently: + +=over + +=item + +C<--xlen> is an I in seconds + +=item + +C<--xmin> and C<--xmax> I use the format passed in to C<--timefmt> + +=back + +Using this option changes both the way the input is parsed I the way the +x-axis tics are labelled. Gnuplot tries to be intelligent in this labelling, but +it doesn't always to what the user wants. The labelling can be controlled with +the gnuplot C command, which takes the same type of format string as +C<--timefmt>. Example: + + $ sar 1 -1 | + awk '$1 ~ /..:..:../ && $8 ~/^[0-9\.]*$/ {print $1,$8; fflush()}' | + feedgnuplot --stream --domain + --lines --timefmt '%H:%M:%S' + --extracmds 'set format x "%H:%M:%S"' + +This plots the 'idle' CPU consumption against time. + +Note that while gnuplot supports the time/date on any axis, I +currently supports it I as the x-axis domain. This may change in the +future. + +=head2 Real-time streaming data + +To plot real-time data, pass in the C<--stream [refreshperiod]> option. Data +will then be plotted as it is received. The plot will be updated every +C seconds. If the period isn't specified, a 1Hz refresh rate is +used. To refresh at specific intervals indicated by the data, set the +refreshperiod to 0 or to 'trigger'. The plot will then I be refreshed when +a data line 'replot' is received. This 'replot' command works in both triggered +and timed modes, but in triggered mode, it's the only way to replot. Look in +L for more information. + +To plot only the most recent data (instead of I the data), C<--xlen +windowsize> can be given. This will create an constantly-updating, scrolling +view of the recent past. C should be replaced by the desired length +of the domain window to plot, in domain units (passed-in values if C<--domain> +or line numbers otherwise). If the domain is a time/date via C<--timefmt>, then +C is and I in seconds. + +=head3 Special data commands + +If we are reading streaming data, the input stream can contain special commands +in addition to the raw data. Feedgnuplot looks for these at the start of every +input line. If a command is detected, the rest of the line is discarded. These +commands are + +=over + +=item C + +This command refreshes the plot right now, instead of waiting for the next +refresh time indicated by the timer. This command works in addition to the timed +refresh, as indicated by C<--stream [refreshperiod]>. + +=item C + +This command clears out the current data in the plot. The plotting process +continues, however, to any data following the C. + +=item C + +This command causes feedgnuplot to exit. + +=back + +=head2 Hardcopy output + +The script is able to produce hardcopy output with C<--hardcopy outputfile>. The +output type can be inferred from the filename, if B<.ps>, B<.eps>, B<.pdf>, +B<.svg> or B<.png> is requested. If any other file type is requested, +C<--terminal> I be passed in to tell gnuplot how to make the plot. + +=head2 Self-plotting data files + +This script can be used to enable self-plotting data files. There are 2 ways of +doing this: with a shebang (#!) or with inline perl data. + +=head3 Self-plotting data with a #! + +A self-plotting, executable data file C is formatted as + + $ cat data + #!/usr/bin/feedgnuplot --lines --points + 2 1 + 4 4 + 6 9 + 8 16 + 10 25 + 12 36 + 14 49 + 16 64 + 18 81 + 20 100 + 22 121 + 24 144 + 26 169 + 28 196 + 30 225 + +This is the shebang (#!) line followed by the data, formatted as before. The +data file can be plotted simply with + + $ ./data + +The caveats here are that on Linux the whole #! line is limited to 127 charaters +and that the full path to feedgnuplot must be given. The 127 character limit is +a serious limitation, but this can likely be resolved with a kernel patch. I +have only tried on Linux 2.6. + +=head3 Self-plotting data with perl inline data + +Perl supports storing data and code in the same file. This can also be used to +create self-plotting files: + + $ cat plotdata.pl + #!/usr/bin/perl + use strict; + use warnings; + + open PLOT, "| feedgnuplot --lines --points" or die "Couldn't open plotting pipe"; + while( ) + { + my @xy = split; + print PLOT "@xy\n"; + } + __DATA__ + 2 1 + 4 4 + 6 9 + 8 16 + 10 25 + 12 36 + 14 49 + 16 64 + 18 81 + 20 100 + 22 121 + 24 144 + 26 169 + 28 196 + 30 225 + +This is especially useful if the logged data is not in a format directly +supported by feedgnuplot. Raw data can be stored after the __DATA__ directive, +with a small perl script to manipulate the data into a useable format and send +it to the plotter. + +=head1 ARGUMENTS + +=over + +=item + +--[no]domain + +If enabled, the first element of each line is the domain variable. If not, the +point index is used + +=item + +--[no]dataid + +If enabled, each data point is preceded by the ID of the data set that point +corresponds to. This ID is interpreted as a string, NOT as just a number. If not +enabled, the order of the point is used. + +As an example, if line 3 of the input is "0 9 1 20" then + +=over + +=item + +'--nodomain --nodataid' would parse the 4 numbers as points in 4 different +curves at x=3 + +=item + +'--domain --nodataid' would parse the 4 numbers as points in 3 different +curves at x=0. Here, 0 is the x-variable and 9,1,20 are the data values + +=item + +'--nodomain --dataid' would parse the 4 numbers as points in 2 different +curves at x=3. Here 0 and 1 are the data IDs and 9 and 20 are the +data values + +=item + +'--domain --dataid' would parse the 4 numbers as a single point at +x=0. Here 9 is the data ID and 1 is the data value. 20 is an extra +value, so it is ignored. If another value followed 20, we'd get another +point in curve ID 20 + +=back + +=item + +--[no]3d + +Do [not] plot in 3D. This only makes sense with --domain. Each domain here is an +(x,y) tuple + +=item + +--timefmt [format] + +Interpret the X data as a time/date, parsed with the given format + +=item + +--colormap + +Show a colormapped xy plot. Requires extra data for the color. zmin/zmax can be +used to set the extents of the colors. Automatically increments +C<--extraValuesPerPoint> + +=item + +--stream [period] + +Plot the data as it comes in, in realtime. If period is given, replot every +period seconds. If no period is given, replot at 1Hz. If the period is given as +0 or 'trigger', replot I when the incoming data dictates this. See the +L section of the man page. + +=item + +--[no]lines + +Do [not] draw lines to connect consecutive points + +=item + +--[no]points + +Do [not] draw points + +=item + +--circles + +Plot with circles. This requires a radius be specified for each point. +Automatically increments C<--extraValuesPerPoint>). C supported for 3d +plots. + +=item + +--title xxx + +Set the title of the plot + +=item + +--legend curveID legend + +Set the label for a curve plot. Use this option multiple times for multiple +curves. With --dataid, curveID is the ID. Otherwise, it's the index of the +curve, starting at 0 + +=item + +--autolegend + +Use the curve IDs for the legend. Titles given with --legend override these + +=item + +--xlen xxx + +When using --stream, sets the size of the x-window to plot. Omit this or set it +to 0 to plot ALL the data. Does not make sense with 3d plots. Implies +--monotonic + +=item + +--xmin/xmax/ymin/ymax/y2min/y2max/zmin/zmax xxx + +Set the range for the given axis. These x-axis bounds are ignored in a streaming +plot. The y2-axis bound do not apply in 3d plots. The z-axis bounds apply +I to 3d plots or colormaps. + +=item + +--xlabel/ylabel/y2label/zlabel xxx + +Label the given axis. The y2-axis label does not apply to 3d plots while the +z-axis label applies I to 3d plots. + +=item + +--y2 xxx + +Plot the data specified by this curve ID on the y2 axis. Without --dataid, the +ID is just an ordered 0-based index. Does not apply to 3d plots. Can be passed +multiple times, or passed a comma-separated list. By default the y2-axis curves +look the same as the y-axis ones. I.e. the viewer of the resulting plot has to +be told which is which via an axes label, legend, etc. Prior to version 1.25 of +feedgnuplot the curves plotted on the y2 axis were drawn with a thicker line. +This is no longer the case, but that behavior can be brought back by passing +something like + + --y2 curveid --curvestyle curveid 'linewidth 3' + +=item + +--histogram curveID + + +Set up a this specific curve to plot a histogram. The bin width is given with +the --binwidth option (assumed 1.0 if omitted). --histogram does NOT touch the +drawing style. It is often desired to plot these with boxes, and this MUST be +explicitly requested with --curvestyleall 'with boxes'. This works with --domain +and/or --stream, but in those cases the x-value is used ONLY to cull old data +because of --xlen or --monotonic. I.e. the x-values are NOT drawn in any way. +Can be passed multiple times, or passed a comma- separated list + +=item + +--binwidth width + +The width of bins when making histograms. This setting applies to ALL histograms +in the plot. Defaults to 1.0 if not given. + +=item + +--histstyle style + +Normally, histograms are generated with the 'smooth freq' gnuplot style. +--histstyle can be used to select different 'smooth' settings. Allowed are +'unique', 'cumulative' and 'cnormal'. 'unique' indicates whether a bin has at +least one item in it: instead of counting the items, it'll always report 0 or 1. +'cumulative' is the integral of the "normal" histogram. 'cnormal' is like +'cumulative', but rescaled to end up at 1.0. + +=item + +--curvestyle curveID + +style Additional styles per curve. With --dataid, curveID is the ID. Otherwise, +it's the index of the curve, starting at 0. Use this option multiple times for +multiple curves. --curvestylall does NOT apply to curves that have a +--curvestyle + +=item + +--curvestyleall xxx + +Additional styles for all curves that have no --curvestyle + +=item + +--extracmds xxx + +Additional commands. These could contain extra global styles for instance. Can +be passed multiple times. + +=item + +--square + +Plot data with aspect ratio 1. For 3D plots, this controls the aspect ratio for +all 3 axes + +=item + +--square_xy + +For 3D plots, set square aspect ratio for ONLY the x,y axes + +=item + +--hardcopy xxx + +If not streaming, output to a file specified here. Format inferred from +filename, unless specified by --terminal + +=item + +--terminal xxx + +String passed to 'set terminal'. No attempts are made to validate this. +--hardcopy sets this to some sensible defaults if --hardcopy is given .png, +.pdf, .ps, .eps or .svg. If any other file type is desired, use both --hardcopy +and --terminal + +=item + +--maxcurves xxx + +The maximum allowed number of curves. This is 100 by default, but can be reset +with this option. This exists purely to prevent perl from allocating all of the +system's memory when reading bogus data + +=item + +--monotonic + +If --domain is given, checks to make sure that the x- coordinate in the input +data is monotonically increasing. If a given x-variable is in the past, all data +currently cached for this curve is purged. Without --monotonic, all data is +kept. Does not make sense with 3d plots. No --monotonic by default. The data is +replotted before being purged + +=item + +--extraValuesPerPoint + +xxx How many extra values are given for each data point. Normally this is 0, and +does not need to be specified, but sometimes we want extra data, like for colors +or point sizes or error bars, etc. feedgnuplot options that require this +(colormap, circles) automatically set it. This option is ONLY needed if unknown +styles are used, with --curvestyleall for instance + +=item + +--dump + +Instead of printing to gnuplot, print to STDOUT. Very useful for debugging. It +is possible to send the output produced this way to gnuplot directly. + +=item + +--exit + +Terminate the feedgnuplot process after passing data to gnuplot. The window will +persist but will not be interactive. Without this option feedgnuplot keeps +running and must be killed by the user. Note that this option works only with +later versions of gnuplot and only with some gnuplot terminals. + +=item + +--geometry + +If using X11, specifies the size, position of the plot window + +=item + +--version + +Print the version and exit + +=back + +=head1 RECIPES + +=head2 Basic plotting of piped data + + $ seq 5 | awk '{print 2*$1, $1*$1}' + 2 1 + 4 4 + 6 9 + 8 16 + 10 25 + + $ seq 5 | awk '{print 2*$1, $1*$1}' | + feedgnuplot --lines --points --legend 0 "data 0" --title "Test plot" --y2 1 + +=head2 Realtime plot of network throughput + +Looks at wlan0 on Linux. + + $ while true; do sleep 1; cat /proc/net/dev; done | + gawk '/wlan0/ {if(b) {print $2-b; fflush()} b=$2}' | + feedgnuplot --lines --stream --xlen 10 --ylabel 'Bytes/sec' --xlabel seconds + +=head2 Realtime plot of battery charge in respect to time + +Uses the result of the C command. + + $ while true; do acpi; sleep 15; done | + perl -nE 'BEGIN{ $| = 1; } /([0-9]*)%/; say join(" ", time(), $1);' | + feedgnuplot --stream --ymin 0 --ymax 100 --lines --domain --xlabel 'Time' --timefmt '%s' --ylabel "Battery charge (%)" + +=head2 Realtime plot of temperatures in an IBM Thinkpad + +Uses C, which reports temperatures at various locations +in a Thinkpad. + + $ while true; do cat /proc/acpi/ibm/thermal | awk '{$1=""; print}' ; sleep 1; done | + feedgnuplot --stream --xlen 100 --lines --autolegend --ymax 100 --ymin 20 --ylabel 'Temperature (deg C)' + +=head2 Plotting a histogram of file sizes in a directory + + $ ls -l | awk '{print $5/1e6}' | + feedgnuplot --histogram 0 --curvestyleall 'with boxes' --ymin 0 --xlabel 'File size (MB)' --ylabel Frequency + +=head1 ACKNOWLEDGEMENT + +This program is originally based on the driveGnuPlots.pl script from +Thanassis Tsiodras. It is available from his site at +L + +=head1 REPOSITORY + +L + +=head1 AUTHOR + +Dima Kogan, C<< >> + +=head1 LICENSE AND COPYRIGHT + +Copyright 2011-2012 Dima Kogan. + +This program is free software; you can redistribute it and/or modify it +under the terms of either: the GNU General Public License as published +by the Free Software Foundation; or the Artistic License. + +See http://dev.perl.org/licenses/ for more information. + +=cut + +1; diff --git a/t/happy_cpantesters.t b/t/happy_cpantesters.t new file mode 100644 index 0000000..8f7414c --- /dev/null +++ b/t/happy_cpantesters.t @@ -0,0 +1,13 @@ +#!perl + +use strict; +use Test::More tests => 1; + +BEGIN{ + require_ok 'App::feedgnuplot'; + +} + +diag("App::feedgnuplot/$App::feedgnuplot::VERSION"); + +__DATA__