Browse Source

added files

Spiros Ioannou 9 years ago
parent
commit
52ada10ea3
6 changed files with 608 additions and 0 deletions
  1. 15 0
      Makefile
  2. 43 0
      README
  3. BIN
      runcached
  4. 355 0
      runcached.c
  5. 123 0
      runcached.py
  6. 72 0
      runcached.sh

+ 15 - 0
Makefile

@@ -0,0 +1,15 @@
+#
+CC=gcc
+CCarm=arm-linux-gnueabi-gcc
+CFLAGS=-O2 -g
+CFLAGS=-O2 
+LDFLAGS=-lm
+
+runcached: runcached.c
+	$(CC) $(CFLAGS) -O runcached.c -o $@ $(LDFLAGS)
+
+arm: runcached.c
+	$(CCarm) $(CFLAGS) -O runcached.c -o $@ $(LDFLAGS)
+
+clean: 
+	rm -f runcached core

+ 43 - 0
README

@@ -0,0 +1,43 @@
+runcached (Run Cached)
+------------------------------------------------------------------------------
+Execute commands while caching their output for subsequent calls. 
+Command output will be cached for <cacheperiod> seconds and "replayed" for 
+any subsequent calls. Original exit status will also be emulated.
+
+After cacheperiod has expired, command will be re-executed and a new result 
+will be cached. 
+Caching depends on command and arguments executed and the 
+path of the runcached executable. Cache results are stored in /tmp
+
+You can use runcached to run resource-expensive commands multiple times, 
+parsing different parts of its output each time. Those commands will be
+run only once for each cacheperiod. 
+
+Implementation is provided in 3 languages, python, C, BASH.
+
+
+Usage:
+======
+Python:
+runcached.py [-c cacheperiod] <command to execute with args>
+
+C:
+runcached [-c cacheperiod] <command to execute with args>
+
+Bash
+runcached.sh  <command to execute with args>
+
+
+
+Example:
+========
+
+1) Run the date command. Produce a new "date" every 5 seconds.
+runcached.py -c 5 date
+
+2) Query multiple parameters of mysql without re-running the query, and making 
+sure all of them correspond to the same time.
+
+UserParameter=mysql.globalstatus[*],/usr/local/bin/runcached.py -c 20 /usr/bin/mysql -ANe \"show global status\"|egrep '$1\b'|awk '{print $ 2}'
+
+

BIN
runcached


+ 355 - 0
runcached.c

@@ -0,0 +1,355 @@
+/* 
+ * runcached
+ * Execute commands while caching their output for subsequent calls. 
+ * Command output will be cached for $cacheperiod and replayed for subsequent calls
+ *
+ * Author Spiros Ioannou sivann <at> inaccess.com
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <math.h>
+
+
+int cacheperiod=27; //seconds
+char cachedir[512];
+int maxwaitprev=5;  //seconds to wait for previous same command to finish before quiting
+int minrand=0;      //random seconds to wait before running cmd
+int maxrand=0;
+int argskip=1;
+char pidfile[128];
+
+char * str2md5str( char[]);
+void cleanup(void);
+int runit(char **,char *,char *,char *,char *);
+
+int main(int argc, char **argv) {
+    int i,j,count;
+    char cmd[1024];
+    char buf[512];
+    char * cmdmd5;
+    FILE *fp;
+    struct stat st;
+    time_t diffsec;
+
+    char cmddatafile[128];
+    char cmdexitcode[128];
+    char cmdfile[128];
+
+    if (argc<2 || (argc==2 && !strcmp(argv[1],"-c"))) {
+        fprintf(stderr,"Usage: %s [-c cacheperiod] <command to execute with args>\n",argv[0]);
+        exit(1);
+    }
+
+    if (!strcmp(argv[1],"-c")) {
+        cacheperiod=atoi(argv[2]);
+        argskip=3;
+    }
+
+    strcpy(cachedir,"/tmp");
+
+    for (j=0,cmd[0]=0,i=0+argskip;i<argc;i++) {
+        j+=strlen(argv[i]);
+        if (j+1>sizeof(cachedir)) {
+            fprintf(stderr,"argument list too long\n");
+            exit(2);
+        }
+        strcat(cmd,argv[i]);
+        strcat(cmd," ");
+    }
+
+    cmd[strlen(cmd)-1]=0;
+
+    cmdmd5=str2md5str(cmd);
+
+    if (maxrand-minrand) {
+        srand(time(NULL));
+        sleep(rand()%(maxrand+1)+minrand);
+    }
+
+    snprintf(pidfile,127,"%s/%s-runcached.pid",cachedir,cmdmd5);
+    snprintf(cmddatafile,127,"%s/%s.data",cachedir,cmdmd5);
+    snprintf(cmdexitcode,127,"%s/%s.exitcode",cachedir,cmdmd5);
+    snprintf(cmdfile,127,"%s/%s.cmd",cachedir,cmdmd5);
+
+    atexit(cleanup);
+
+    // don't run the same command in parallel, wait the previous one to finish
+    count=maxwaitprev;
+    while (isfile(pidfile)) {
+        sleep(1);
+        count-=1;
+        if (count == 0) {
+            fprintf(stderr,"timeout waiting for previous %s to finish\n" , cmd);
+            exit (1);
+        }
+    }
+
+    //write pid file
+    fp = fopen(pidfile,"w");
+	if (!fp) {
+		perror(pidfile);
+		exit(2);
+	}
+    fprintf(fp,"%ld",(long)getpid());
+    fclose(fp);
+
+    //if not cached before, run it
+    if (!isfile(cmddatafile)) {
+        runit(argv,cmd,cmddatafile,cmdexitcode,cmdfile) ;
+    }
+
+    //if too old, re-run it
+    if (stat(cmddatafile, &st) == -1) {
+        perror("stat");
+        exit(EXIT_FAILURE);
+    }
+
+    diffsec=time(0)-(time_t)st.st_mtim.tv_sec;
+
+    if (diffsec > cacheperiod) {
+        runit(argv,cmd,cmddatafile,cmdexitcode,cmdfile) ;
+    }
+
+    fp = fopen(cmddatafile,"r");
+	if (!fp) {
+		perror(cmddatafile);
+		exit(1);
+	}
+    while (fgets(buf, sizeof(buf), fp) != NULL)
+        printf("%s", buf);
+    fclose(fp);
+
+    exit(0);
+
+} //main
+
+int isfile(char *path) {
+    return (access( path, F_OK ) != -1 ) ;
+}
+
+void cleanup(void) {
+    //if ( access( pidfile, F_OK ) != -1 ) {
+	printf("Cleanup\n");
+    if (isfile(pidfile)) {
+        unlink(pidfile);
+    }
+}
+
+int runit(char **argv,char * cmd,char * cmddatafile,char * cmdexitcode,char * cmdfile) {
+    int out_old, out_new;
+    int err_old, err_new;
+    int exitcode;
+    char buf[1024];
+
+    FILE *pfp, *fp;
+
+
+    fflush(stdout);
+    fflush(stderr);
+
+    //redirect stdout and stderr to file
+    out_old = dup(1);
+    out_new = open(cmddatafile,O_WRONLY|O_CREAT|O_TRUNC,S_IROTH|S_IRUSR|S_IRGRP|S_IWUSR);
+    dup2(out_new, 1);
+    close(out_new);
+
+    err_old = dup(2);
+    err_new = open(cmddatafile,O_WRONLY|O_CREAT|O_APPEND,S_IROTH|S_IRUSR|S_IRGRP|S_IWUSR);
+    dup2(err_new, 2);
+    close(err_new);
+
+    /* run command with arguments: */
+    /*
+    argv+=argskip;
+    if (execvp(*argv, argv)<0) {
+        perror("execvp");
+        exit (errno);
+    }
+    */
+
+    pfp = popen(cmd, "r");
+    if (pfp == NULL) {
+        perror("popen");
+        exit(errno);
+    }
+
+    while (fgets(buf, sizeof(buf), pfp) != NULL)
+        printf("%s", buf);
+
+    exitcode=pclose(pfp);
+
+    fp = fopen(cmdexitcode,"w");
+	if (!fp) {
+		perror(cmdexitcode);
+	}
+	else {
+		fprintf(fp,"%d",exitcode);
+		fclose(fp);
+	}
+
+    fp = fopen(cmdfile,"w");
+	if (!fp) {
+		perror(cmdfile);
+	}
+	else {
+		fprintf(fp,"%s",cmd);
+		fclose(fp);
+	}
+
+    //redirect stdout and stderr back to where it was
+    fflush(stdout);
+    dup2(out_old, 1);
+    close(out_old);
+
+    fflush(stderr);
+    dup2(err_old, 2);
+    close(err_old);
+}
+
+
+ 
+typedef union uwb {
+    unsigned w;
+    unsigned char b[4];
+} WBunion;
+ 
+typedef unsigned Digest[4];
+ 
+unsigned f0( unsigned abcd[] ){
+    return ( abcd[1] & abcd[2]) | (~abcd[1] & abcd[3]);}
+ 
+unsigned f1( unsigned abcd[] ){
+    return ( abcd[3] & abcd[1]) | (~abcd[3] & abcd[2]);}
+ 
+unsigned f2( unsigned abcd[] ){
+    return  abcd[1] ^ abcd[2] ^ abcd[3];}
+ 
+unsigned f3( unsigned abcd[] ){
+    return abcd[2] ^ (abcd[1] |~ abcd[3]);}
+ 
+typedef unsigned (*DgstFctn)(unsigned a[]);
+ 
+unsigned *calcKs( unsigned *k)
+{
+    double s, pwr;
+    int i;
+ 
+    pwr = pow( 2, 32);
+    for (i=0; i<64; i++) {
+        s = fabs(sin(1+i));
+        k[i] = (unsigned)( s * pwr );
+    }
+    return k;
+}
+ 
+// ROtate v Left by amt bits
+unsigned rol( unsigned v, short amt )
+{
+    unsigned  msk1 = (1<<amt) -1;
+    return ((v>>(32-amt)) & msk1) | ((v<<amt) & ~msk1);
+}
+ 
+unsigned *md5( const char *msg, int mlen) 
+{
+    static Digest h0 = { 0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476 };
+//    static Digest h0 = { 0x01234567, 0x89ABCDEF, 0xFEDCBA98, 0x76543210 };
+    static DgstFctn ff[] = { &f0, &f1, &f2, &f3 };
+    static short M[] = { 1, 5, 3, 7 };
+    static short O[] = { 0, 1, 5, 0 };
+    static short rot0[] = { 7,12,17,22};
+    static short rot1[] = { 5, 9,14,20};
+    static short rot2[] = { 4,11,16,23};
+    static short rot3[] = { 6,10,15,21};
+    static short *rots[] = {rot0, rot1, rot2, rot3 };
+    static unsigned kspace[64];
+    static unsigned *k;
+ 
+    static Digest h;
+    Digest abcd;
+    DgstFctn fctn;
+    short m, o, g;
+    unsigned f;
+    short *rotn;
+    union {
+        unsigned w[16];
+        char     b[64];
+    }mm;
+    int os = 0;
+    int grp, grps, q, p;
+    unsigned char *msg2;
+ 
+    if (k==NULL) k= calcKs(kspace);
+ 
+    for (q=0; q<4; q++) h[q] = h0[q];   // initialize
+ 
+    {
+        grps  = 1 + (mlen+8)/64;
+        msg2 = malloc( 64*grps);
+        memcpy( msg2, msg, mlen);
+        msg2[mlen] = (unsigned char)0x80;  
+        q = mlen + 1;
+        while (q < 64*grps){ msg2[q] = 0; q++ ; }
+        {
+//            unsigned char t;
+            WBunion u;
+            u.w = 8*mlen;
+//            t = u.b[0]; u.b[0] = u.b[3]; u.b[3] = t;
+//            t = u.b[1]; u.b[1] = u.b[2]; u.b[2] = t;
+            q -= 8;
+            memcpy(msg2+q, &u.w, 4 );
+        }
+    }
+ 
+    for (grp=0; grp<grps; grp++)
+    {
+        memcpy( mm.b, msg2+os, 64);
+        for(q=0;q<4;q++) abcd[q] = h[q];
+        for (p = 0; p<4; p++) {
+            fctn = ff[p];
+            rotn = rots[p];
+            m = M[p]; o= O[p];
+            for (q=0; q<16; q++) {
+                g = (m*q + o) % 16;
+                f = abcd[1] + rol( abcd[0]+ fctn(abcd) + k[q+16*p] + mm.w[g], rotn[q%4]);
+ 
+                abcd[0] = abcd[3];
+                abcd[3] = abcd[2];
+                abcd[2] = abcd[1];
+                abcd[1] = f;
+            }
+        }
+        for (p=0; p<4; p++)
+            h[p] += abcd[p];
+        os += 64;
+    }
+    return h;
+}    
+ 
+char * str2md5str( char msg[] )
+{
+    int j,k;
+    unsigned *d = md5(msg, strlen(msg));
+    WBunion u;
+    char s[16];
+
+    char * md5str;
+    md5str=malloc(34);
+ 
+    for (j=0;j<4; j++){
+        u.w = d[j];
+        for (k=0;k<4;k++)  {
+            sprintf(s,"%02x",u.b[k]);
+            strcat(md5str,s);
+        }
+    }
+ 
+    return md5str;
+}

+ 123 - 0
runcached.py

@@ -0,0 +1,123 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# runcached
+# Execute commands while caching their output for subsequent calls. 
+# Command output will be cached for $cacheperiod and replayed for subsequent calls
+#
+# Author Spiros Ioannou sivann <at> inaccess.com
+#
+
+cacheperiod=27 #seconds
+cachedir="/tmp"
+maxwaitprev=5	#seconds to wait for previous same command to finish before quiting
+minrand=0		#random seconds to wait before running cmd
+maxrand=0
+
+import os
+import sys
+import errno
+import subprocess
+import time
+import hashlib
+import random
+import atexit
+import syslog
+
+
+argskip=1
+
+if (len(sys.argv) < 2) or (len(sys.argv) == 2 and sys.argv[1] == '-c'):
+	sys.exit('Usage: %s [-c cacheperiod] <command to execute with args>' % sys.argv[0])
+
+if sys.argv[1] == '-c':
+	cacheperiod=int(sys.argv[2])
+	argskip=3
+
+cmd=" ".join(sys.argv[argskip:])
+
+#hash of executed command w/args
+m = hashlib.md5()
+m.update(cmd)
+cmdmd5=m.hexdigest()
+
+
+#random sleep to avoid racing condition of creating the pid on the same time
+if maxrand-minrand > 0:
+	time.sleep(random.randrange(minrand,maxrand))
+
+pidfile=cachedir+os.sep+cmdmd5+'-runcached.pid'
+cmddatafile=cachedir+os.sep+cmdmd5+'.data'
+cmdexitcode=cachedir+os.sep+cmdmd5+'.exitcode'
+cmdfile=cachedir+os.sep+cmdmd5+'.cmd'
+
+
+
+def cleanup():
+	if os.path.isfile(pidfile):
+		os.remove(pidfile)
+
+
+def myexcepthook(exctype, value, traceback):
+	#if exctype == KeyboardInterrupt:
+	#	 print "Handler code goes here"
+	cleanup()
+	syslog.syslog(syslog.LOG_ERR, value.__str__())
+	_old_excepthook(exctype, value, traceback)
+
+def runit(cmd,cmddatafile,cmdexitcode,cmdfile):
+	f_stdout=open(cmddatafile, 'w')
+	#f_stderr=open('stderr.txt', 'w')
+	f_stderr=f_stdout
+	p = subprocess.Popen(cmd, stdout=f_stdout, stderr=f_stderr,shell=True)
+	p.communicate()
+	f_stdout.close()
+	exitcode=p.returncode
+	with open(cmdfile, 'w') as f:
+		f.write(cmd)
+	with open(cmdexitcode, 'w') as f:
+		f.write(str(exitcode))
+
+def file_get_contents(filename):
+	with open(filename) as f:
+		return f.read()
+
+
+#install cleanup hook
+_old_excepthook = sys.excepthook
+sys.excepthook = myexcepthook
+atexit.register(cleanup)
+
+#don't run the same command in parallel, wait the previous one to finish
+count=maxwaitprev
+while os.path.isfile(pidfile):
+#remove stale pid if left there without a running process
+	prevpid=file_get_contents(pidfile).strip()
+	if not os.path.exists("/proc/"+prevpid):
+		os.remove(pidfile)
+	time.sleep(1)
+	count-=1
+	if count == 0:
+		sys.stderr.write("timeout waiting for '%s' to finish. (pid: %s)\n" % (cmd,pidfile))
+		sys.exit (1)
+
+#write pidfile
+mypid=os.getpid()
+with open(pidfile, 'w') as f:
+	f.write(str(mypid))
+
+#if not cached before, run it
+if not os.path.isfile(cmddatafile):
+	runit(cmd,cmddatafile,cmdexitcode,cmdfile)
+
+#if too old, re-run it
+currtime=int(time.time())
+lastrun=int(os.path.getmtime(cmddatafile))
+diffsec=currtime - lastrun
+if (diffsec > cacheperiod):
+	runit(cmd,cmddatafile,cmdexitcode,cmdfile)
+
+with open(cmddatafile, 'r') as f:
+	sys.stdout.write(f.read())
+
+cleanup()

+ 72 - 0
runcached.sh

@@ -0,0 +1,72 @@
+#!/bin/bash
+
+# Run an expensive command as frequently as you want through this. 
+# Command output will be kept for $cacheperiod and replayed for subsequent calls
+# Possible usage: query multiple EMC parameters with zabbix in random order, 
+#                by running the EMC interfacing command just once, transparrently to zabbix.
+# sivann 2012
+# Thu Jun 14 13:11:28 EEST 2012
+####################################################33
+
+cacheperiod=60 #seconds
+savedir="/tmp"
+####################################################33
+
+
+
+
+for arg in "$@"
+do
+		cmd=( "${cmd[@]}" "$arg" )
+        let "i+=1"
+done
+
+cmdmd5=`echo -n "${cmd[@]}" |md5sum|awk '{print $1}'`
+
+#random sleep to avoid racing condition of creating the pid on the same time
+sleep .$[ ( $RANDOM % 10 ) + 1 ]s
+
+#don't run the same command in parallel, wait for it to finish the 1st time
+count=15
+while [ -f /tmp/${cmdmd5}-runcached.pid ]; do
+	sleep 2
+	count=`expr $count -1`
+	if [ $count -eq 0 ]; then
+		echo "timeout waiting for runcached.pid to be removed"
+		exit -1
+	fi
+done
+
+echo $$ >/tmp/${cmdmd5}-runcached.pid
+
+
+cachedir="/tmp"
+cmddatafile="${cachedir}/${cmdmd5}.data"
+cmdexitcode="${cachedir}/${cmdmd5}.exitcode"
+cmdfile="${cachedir}/${cmdmd5}.cmd"
+##########
+
+function runit {
+	"${cmd[@]}" 2>&1 | tee $cmddatafile 1>/dev/null 2>&1
+	exitcode=${PIPESTATUS[0]}
+	echo $exitcode > $cmdexitcode
+	echo  "${cmd[@]}" > $cmdfile
+}
+
+
+if [ ! -f "$cmddatafile"  ] ; then  runit ; fi
+
+lastrun=`stat -c %Y $cmddatafile`
+currtime=`date +%s`
+diffsec=$(( (currtime - lastrun) ))
+
+if [ "$diffsec"  -ge "$cacheperiod" ] ; then
+	runit 
+fi
+
+cat $cmddatafile
+
+/bin/rm /tmp/${cmdmd5}-runcached.pid
+
+exit `cat $cmdexitcode`
+