Thursday, 15 September 2016

Recording & displaying terminal sessions with ttyrec, ttyplay, tty2gif & ttygif

ttyrec is a utility for recording terminal activity which can be replayed at a later date using ttyplay.
It's a derivative of the script command but is different in the sense that holds terminal activity & timing information in the same file.

ttyrec utility is available here.
I was not able to run it out of the box & had to apply the patch supplied by the developer.
Although the patch appears to be for RHEL 5 but I was able to make it work on CentOS 6 system.

[root@centdb /]# tar xvf ttyrec-1.0.8.tar.gz
ttyrec-1.0.8/
ttyrec-1.0.8/ttyrec.c
ttyrec-1.0.8/ttyplay.c
ttyrec-1.0.8/ttyrec.h
ttyrec-1.0.8/io.c
ttyrec-1.0.8/io.h
ttyrec-1.0.8/ttytime.c
ttyrec-1.0.8/README
ttyrec-1.0.8/Makefile
ttyrec-1.0.8/ttyrec.1
ttyrec-1.0.8/ttyplay.1
ttyrec-1.0.8/ttytime.1
[root@centdb /]# cd ttyrec-1.0.8/
[root@centdb ttyrec-1.0.8]# ls
io.c  io.h  Makefile  README  ttyplay.1  ttyplay.c  ttyrec.1  ttyrec.c  ttyrec.h  ttytime.1  ttytime.c

The contents of the patch ttyrec-1.0.8.RHEL5.patch script are as follows:

--- original/Makefile   2006-06-11 10:52:50.000000000 -0500
+++ new/Makefile        2012-04-23 18:34:05.000000000 -0500
@@ -1,5 +1,5 @@
 CC = gcc
-CFLAGS = -O2
+CFLAGS = -O2 -DHAVE_openpty
 VERSION = 1.0.8

 TARGET = ttyrec ttyplay ttytime
@@ -10,7 +10,7 @@ DIST =        ttyrec.c ttyplay.c ttyrec.h io.c
 all: $(TARGET)

 ttyrec: ttyrec.o io.o
-       $(CC) $(CFLAGS) -o ttyrec ttyrec.o io.o
+       $(CC) $(CFLAGS) -o ttyrec ttyrec.o io.o -lutil

 ttyplay: ttyplay.o io.o
        $(CC) $(CFLAGS) -o ttyplay ttyplay.o io.o
diff -rupN original/ttyrec.c new/ttyrec.c
--- original/ttyrec.c   2006-06-11 10:52:50.000000000 -0500
+++ new/ttyrec.c        2012-04-23 18:26:41.000000000 -0500
@@ -71,7 +71,9 @@
 #define _(FOO) FOO

 #ifdef HAVE_openpty
-#include <libutil.h>
+/* #include <libutil.h> */
+#include <pty.h>  /* for openpty and forkpty */
+#include <utmp.h> /* for login_tty */
 #endif

 #if defined(SVR4) && !defined(CDEL)


 Run the following command to apply the patch:

[root@centdb ttyrec-1.0.8]# patch -i ttyrec-1.0.8.RHEL5.patch
patching file Makefile
patching file ttyrec.c

Then run 'make'

[root@centdb ttyrec-1.0.8]# make
gcc -O2 -DHAVE_openpty   -c -o ttyrec.o ttyrec.c
gcc -O2 -DHAVE_openpty   -c -o io.o io.c
gcc -O2 -DHAVE_openpty -o ttyrec ttyrec.o io.o -lutil
gcc -O2 -DHAVE_openpty   -c -o ttyplay.o ttyplay.c
gcc -O2 -DHAVE_openpty -o ttyplay ttyplay.o io.o
gcc -O2 -DHAVE_openpty   -c -o ttytime.o ttytime.c
gcc -O2 -DHAVE_openpty -o ttytime ttytime.o io.o
[root@centdb ttyrec-1.0.8]# ls
io.c  io.o      README   ttyplay.1  ttyplay.o  ttyrec.1                  ttyrec.c  ttyrec.o  ttytime.1  ttytime.o
io.h  Makefile  ttyplay  ttyplay.c  ttyrec     ttyrec-1.0.8.RHEL5.patch  ttyrec.h  ttytime   ttytime.c
[root@centdb ttyrec-1.0.8]#

With this ttyrec is ready for use.
By default ttyrec records sessions in a file called ttyrecord in the current working directory.
For example:

[root@centdb ttyrec-1.0.8]# ./ttyrec
[root@centdb ttyrec-1.0.8]# uname -a;date;uptime
Linux centdb 2.6.32-573.el6.x86_64 #1 SMP Thu Jul 23 15:44:03 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux
Wed Sep 14 03:40:08 PDT 2016
 03:40:08 up 18 min,  2 users,  load average: 0.00, 0.02, 0.07
[root@centdb ttyrec-1.0.8]# exit[root@centdb ttyrec-1.0.8]#
[root@centdb ttyrec-1.0.8]# ls -l ttyrecord
-rw-r--r-- 1 root root 683 Sep 14 03:40 ttyrecord
[root@centdb ttyrec-1.0.8]#


To play this recording type:

[root@centdb ttyrec-1.0.8]# ./ttyplay ttyrecord
[root@centdb ttyrec-1.0.8]# uname -a;date;uptime
Linux centdb 2.6.32-573.el6.x86_64 #1 SMP Thu Jul 23 15:44:03 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux
Wed Sep 14 03:40:08 PDT 2016
 03:40:08 up 18 min,  2 users,  load average: 0.00, 0.02, 0.07
[root@centdb ttyrec-1.0.8]# exit[root@centdb ttyrec-1.0.8]#

Now we get to convert our terminal recording we'll convert it to a gif that can be played in any modern web browser.

We'll be using a python script called tty2gif.py available here.

This script requires python-opster & ImageMagick packages to work.

To install opster, I used the tar archive:

[root@centdb /]# tar zxvf opster-4.1.tar.gz
opster-4.1/
opster-4.1/MANIFEST.in
opster-4.1/opster.py
opster-4.1/PKG-INFO
opster-4.1/README.rst
opster-4.1/setup.cfg
opster-4.1/setup.py
[root@centdb /]# cd opster-4.1/

[root@centdb opster-4.1]# python setup.py install
running install
running build
running build_py
creating build
creating build/lib
copying opster.py -> build/lib
running install_lib
copying build/lib/opster.py -> /usr/lib/python2.6/site-packages
byte-compiling /usr/lib/python2.6/site-packages/opster.py to opster.pyc
running install_egg_info
Writing /usr/lib/python2.6/site-packages/opster-4.1-py2.6.egg-info
[root@centdb opster-4.1]#


[root@centdb /]# rpm -qa | grep -i image
ImageMagick-c++-6.7.2.7-2.el6.x86_64
ImageMagick-6.7.2.7-2.el6.x86_64

To start the conversion type the following command:

./tty2gif.py typing ttyrecord

The starnge thing was that I wasn't able to get it to run when I tried using a X Windows client.

[root@centdb /]# ./tty2gif.py typing ttyrecord

[root@centdb ttyrec-1.0.8]# import: unable to open X server `192.168.10.152:0.0' @ error/import.c/ImportImageCommand/368.
uimport: unable to open X server `192.168.10.152:0.0' @ error/import.c/ImportImageCommand/368.
nimport: unable to open X server `192.168.10.152:0.0' @ error/import.c/ImportImageCommand/368.

The only way I could get it to work was when I ran the command in the system's console with direct GUI.

When the script completes it'll generate a lot of gif files in the directory where the script was run.

To convert them to a single gif we will use ImageMagick. The command is :

convert -delay 10 -loop 0 step*.gif -delay 500 $(ls -1 step*.gif | tail -1) -layers Optimize final.gif

The time taken to convert the recording will depend on how long the recording was.
I found the process to be CPU intensive as my VM with one vCPU hit 90% during the conversion.

Here's the demo:




In addition to tty2dif, ttygif is another tool that we can use to convert a ttyrec recording to a gif.
It can be downloaded from github here.

I downloaded the package & unzipped it:

[root@centdb /]# unzip ttygif-master.zip
Archive:  ttygif-master.zip
0b5c5f1631f424b3526f379897f4d34f7aadd9e6
   creating: ttygif-master/
  inflating: ttygif-master/.gitignore
  inflating: ttygif-master/LICENSE
  inflating: ttygif-master/Makefile
  inflating: ttygif-master/README.md
  inflating: ttygif-master/io.c
  inflating: ttygif-master/io.h
  inflating: ttygif-master/string_builder.c
  inflating: ttygif-master/string_builder.h
  inflating: ttygif-master/ttygif.c
  inflating: ttygif-master/ttygif.png
  inflating: ttygif-master/ttygif.rb
  inflating: ttygif-master/ttyrec.h
  inflating: ttygif-master/utils.c
  inflating: ttygif-master/utils.h


To install run:

  [root@centdb ttygif-master]# make
gcc -O2 -Wall -DOS_LINUX   -c -o ttygif.o ttygif.c
gcc -O2 -Wall -DOS_LINUX   -c -o io.o io.c
gcc -O2 -Wall -DOS_LINUX   -c -o string_builder.o string_builder.c
gcc -O2 -Wall -DOS_LINUX   -c -o utils.o utils.c
gcc -O2 -Wall -DOS_LINUX -o ttygif ttygif.o io.o string_builder.o utils.o
[root@centdb ttygif-master]# make install
cp ttygif /usr/local/bin/ttygif
[root@centdb ttygif-master]#

To convert a ttyerc recording to a gif type the following:

[root@centdb ttygif-master]# ./ttygif myrec 

[root@centdb ttyrec-1.0.8]# echo "this will start the recording & save the file myrec in your CWD"
this will start the recording & save the file myrec in your CWD
[root@centdb ttyrec-1.0.8]# echo "to stop the recording type exit"
to stop the recording type exit
[root@centdb ttyrec-1.0.8]# exitCreating Animated GIF ... this can take a while
Created: tty.gif in the current directory!
[root@centdb ttygif-master]# ^C

This creates a single gif & we do not have to use ImageMagick to combine multiple gifs.

[root@centdb ttygif-master]# ls -l tty.gif
-rw-r--r-- 1 root root 34608 Sep 14 22:13 tty.gif
[root@centdb ttygif-master]#

Here is the file tty.gif:



No comments:

Post a Comment

Using capture groups in grep in Linux

Introduction Let me start by saying that this article isn't about capture groups in grep per se. What we are going to do here with gr...