BLog

ImprintImpressum
PrivacyDatenschutz
DisclaimerHaftung
Downloads 

Litmus and Prestan tests of Cyclaero’s embedded WebDAV server

My company Cyclaero Ltda. develops and ships device controllers running an embedded WebDAV enabled HTTP server written in C, which is an essential part of the web application by which measurements are executed and the results are presented to the operator. The measurement files can be accessed via WebDAV. The software runs on FreeBSD systems, either headless (x86 and ARM) or on x86 with GNOME 3 desktops. The performance test suite Prestan and the compliance test suite Litmus have been employed for verifying the performance and compliance of our WebDAV service. While Litmus worked out of the box, Prestan needed some fixes for running on FreeBSD and macOS clients.

The Litmus compliance test

  1. Download the litmus software package form the Litmus site. Currently the download link is:
        http://webdav.org/neon/litmus/litmus-0.13.tar.gz
  2. On macOS in the Terminal unpack the software package, and enter the distribution directory:
      tar -oxzf litmus-0.13.tar.gz
      cd litmus-0.13
  3. Build litmus using the typical ./configure and make sequence:
      ./configure
      make
      sudo mkdir -p /usr/local
      sudo make install clean
  4. Execute litmus using the following command:
      /usr/local/bin/litmus http://cystat.local/test/ <user> <password>
    -> running `basic':
     0. init.................. pass
     1. begin................. pass
     2. options............... pass
     3. put_get............... pass
     4. put_get_utf8_segment.. pass
     5. put_no_parent......... pass
     6. mkcol_over_plain...... pass
     7. delete................ pass
     8. delete_null........... pass
     9. delete_fragment....... pass
    10. mkcol................. pass
    11. mkcol_again........... pass
    12. delete_coll........... pass
    13. mkcol_no_parent....... pass
    14. mkcol_with_body....... pass
    15. finish................ pass
    <- summary for `basic': of 16 tests run: 16 passed, 0 failed. 100.0%
    -> running `copymove':
     0. init.................. pass
     1. begin................. pass
     2. copy_init............. pass
     3. copy_simple........... pass
     4. copy_overwrite........ pass
     5. copy_nodestcoll....... pass
     6. copy_cleanup.......... pass
     7. copy_coll............. pass
     8. copy_shallow.......... pass
     9. move.................. pass
    10. move_coll............. pass
    11. move_cleanup.......... pass
    12. finish................ pass
    <- summary for `copymove': of 13 tests run: 13 passed, 0 failed. 100.0%
    -> running `props':
     0. init.................. pass
     1. begin................. pass
     2. propfind_invalid...... pass
     3. propfind_invalid2..... pass
     4. propfind_d0........... pass
     5. propinit.............. pass
     6. propset............... pass
     7. propget............... pass
     8. propextended.......... pass
     9. propmove.............. pass
    10. propget............... pass
    11. propdeletes........... pass
    12. propget............... pass
    13. propreplace........... pass
    14. propget............... pass
    15. propnullns............ pass
    16. propget............... pass
    17. prophighunicode....... pass
    18. propget............... pass
    19. propremoveset......... pass
    20. propget............... pass
    21. propsetremove......... pass
    22. propget............... pass
    23. propvalnspace......... pass
    24. propwformed........... pass
    25. propinit.............. pass
    26. propmanyns............ pass
    27. propget............... pass
    28. propcleanup........... pass
    29. finish................ pass
    <- summary for `props': of 30 tests run: 30 passed, 0 failed. 100.0%
    -> running `locks':
     0. init.................. pass
     1. begin................. pass
     2. options............... pass
     3. precond............... pass
     4. init_locks............ pass
     5. put................... pass
     6. lock_excl............. pass
     7. discover.............. pass
     8. refresh............... pass
     9. notowner_modify....... pass
    10. notowner_lock......... pass
    11. owner_modify.......... pass
    12. notowner_modify....... pass
    13. notowner_lock......... pass
    14. copy.................. pass
    15. cond_put.............. pass
    16. fail_cond_put......... pass
    17. cond_put_with_not..... pass
    18. cond_put_corrupt_token pass
    19. complex_cond_put...... pass
    20. fail_complex_cond_put. pass
    21. unlock................ pass
    22. fail_cond_put_unlocked pass
    23. lock_shared........... pass
    24. notowner_modify....... pass
    25. notowner_lock......... pass
    26. owner_modify.......... pass
    27. double_sharedlock..... pass
    28. notowner_modify....... pass
    29. notowner_lock......... pass
    30. unlock................ pass
    31. prep_collection....... pass
    32. lock_collection....... pass
    33. owner_modify.......... pass
    34. notowner_modify....... pass
    35. refresh............... pass
    36. indirect_refresh...... pass
    37. unlock................ pass
    38. unmapped_lock......... pass
    39. unlock................ pass
    40. finish................ pass
    <- summary for `locks': of 41 tests run: 41 passed, 0 failed. 100.0%
    -> running `http':
     0. init.................. pass
     1. begin................. pass
     2. expect100............. pass
     3. finish................ pass
    <- summary for `http': of 4 tests run: 4 passed, 0 failed. 100.0%

The Cyclaero HTTP/WebDAV server passes all Litmus compliance tests by 100 %.

The Prestan performance test

As stated already, Prestan does not work out of the box on macOS, and here I provide a patch file which can be applied to the original sources:

diff -ru Prestan/Makefile.in Prestan-fixed/Makefile.in
--- Prestan/Makefile.in	2007-08-20 15:38:43.000000000 -0300
+++ Prestan-fixed/Makefile.in	2022-02-14 13:21:21.000000000 -0300
@@ -16,7 +16,7 @@
 LDFLAGS = @LDFLAGS@
 LIBS = @NEON_LIBS@ @LIBS@
 # expat may be in LIBOBJS, so must come after $(LIBS) (which has -lneon)
-ALL_LIBS = -L. -ltest -lm -g $(LIBS) $(LIBOBJS)
+ALL_LIBS = -L. -ltest -lm $(LIBS) $(LIBOBJS)
 
 top_builddir = .
 top_srcdir = @top_srcdir@
@@ -97,7 +97,11 @@
 	$(RANLIB) $@
 
 clean:
-	rm -f */*.o $(TESTS) libtest.a
+	cd libneon && $(MAKE) clean
+	-rm -f */*.o $(TESTS) libtest.a
+
+distclean: clean
+	-rm -f config.log config.status config.h Makefile libneon/Makefile
 
 .c.o:
 	$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
@@ -112,3 +116,4 @@
 src/xmloutput.o: src/xmloutput.c $(HDRS)
 src/acl.o: src/acl.c $(HDRS)
 
+.PHONY: clean distclean
diff -ru Prestan/configure.in Prestan-fixed/configure.in
--- Prestan/configure.in	2007-06-13 18:14:04.000000000 -0300
+++ Prestan-fixed/configure.in	2022-02-14 13:22:12.000000000 -0300
@@ -25,7 +25,7 @@
 ])
 
 dnl Check for getopt_long
-AC_CHECK_FUNC(getopt_long,,[AC_LIBOBJ(lib/getopt); AC_LIBOBJ(lib/getopt1)])
+AC_CHECK_FUNC(getopt_long,,[AC_LIBOBJ(lib/getopt) AC_LIBOBJ(lib/getopt1)])
 
 NEON_DEBUG
 NEON_WARNINGS
diff -ru Prestan/libneon/memleak.h Prestan-fixed/libneon/memleak.h
--- Prestan/libneon/memleak.h	2007-06-13 18:14:02.000000000 -0300
+++ Prestan-fixed/libneon/memleak.h	2022-02-14 10:30:33.000000000 -0300
@@ -26,6 +26,7 @@
  * wrappers in ne_alloc.c, which perform simple leak detection.  It
  * MUST NOT BE INSTALLED, or used from neon applications. */
 
+#if 0
 #ifndef MEMLEAK_H
 #define MEMLEAK_H
 
@@ -53,3 +54,4 @@
 extern size_t ne_alloc_used;
 
 #endif /* MEMLEAK_H */
+#endif
diff -ru Prestan/libneon/ne_basic.c Prestan-fixed/libneon/ne_basic.c
--- Prestan/libneon/ne_basic.c	2007-06-13 18:14:02.000000000 -0300
+++ Prestan-fixed/libneon/ne_basic.c	2022-02-14 13:44:45.000000000 -0300
@@ -100,6 +100,8 @@
 int ne_put(ne_session *sess, const char *uri, int fd) 
 {
     ne_request *req = ne_request_create(sess, "PUT", uri);
+    if (fd < 0)
+        ne_add_request_header(req, "Content-Length", "0");
     int ret;
     
 #ifdef USE_DAV_LOCKS
diff -ru Prestan/libneon/ne_locks.c Prestan-fixed/libneon/ne_locks.c
--- Prestan/libneon/ne_locks.c	2007-06-13 18:14:02.000000000 -0300
+++ Prestan-fixed/libneon/ne_locks.c	2022-02-14 13:33:17.000000000 -0300
@@ -90,10 +90,11 @@
 #define NE_ELM_owner (NE_ELM_LOCK_FIRST + 5)
 #define NE_ELM_timeout (NE_ELM_LOCK_FIRST + 6)
 #define NE_ELM_locktoken (NE_ELM_LOCK_FIRST + 7)
-#define NE_ELM_lockinfo (NE_ELM_LOCK_FIRST + 8)
-#define NE_ELM_write (NE_ELM_LOCK_FIRST + 9)
-#define NE_ELM_exclusive (NE_ELM_LOCK_FIRST + 10)
-#define NE_ELM_shared (NE_ELM_LOCK_FIRST + 11)
+#define NE_ELM_lockroot (NE_ELM_LOCK_FIRST + 8)
+#define NE_ELM_lockinfo (NE_ELM_LOCK_FIRST + 9)
+#define NE_ELM_write (NE_ELM_LOCK_FIRST + 10)
+#define NE_ELM_exclusive (NE_ELM_LOCK_FIRST + 11)
+#define NE_ELM_shared (NE_ELM_LOCK_FIRST + 13)
 
 static const struct ne_xml_elm lock_elms[] = {
 #define A(x) { "DAV:", #x, NE_ELM_ ## x, NE_XML_COLLECT } /* ANY */
@@ -102,7 +103,7 @@
 #define E(x) { "DAV:", #x, NE_ELM_ ## x, 0 /* LEAF */ }    /* EMPTY */
     D(lockdiscovery), D(activelock),
     D(prop),
-    D(lockscope), D(locktype), C(depth), A(owner), C(timeout), D(locktoken),
+    D(lockscope), D(locktype), C(depth), A(owner), C(timeout), D(locktoken), D(lockroot),
     /* no lockentry */
     D(lockinfo), D(lockscope), D(locktype),
     E(write), E(exclusive), E(shared),
@@ -116,6 +117,7 @@
 
 static const ne_propname lock_props[] = {
     { "DAV:", "lockdiscovery" },
+   { "DAV:", "lockroot" },
     { NULL }
 };
 
@@ -441,6 +443,7 @@
 	case NE_ELM_owner:
 	case NE_ELM_timeout:
 	case NE_ELM_locktoken:
+    case NE_ELM_lockroot:
 	    return NE_XML_VALID;
 	default:
 	    break;
@@ -460,6 +463,7 @@
 	break;
 	/* ... depth is PCDATA, owner is COLLECT, timeout is PCDATA */
     case NE_ELM_locktoken:
+    case NE_ELM_lockroot:
 	if (child == NE_ELM_href)
 	    return NE_XML_VALID;
 	break;
@@ -668,6 +672,7 @@
     if (value[0] == '<') value++;
     ctx->token = ne_strdup(value);
     ne_shave(ctx->token, ">");
+    ctx->found = 1;
 }
 
 int ne_lock(ne_session *sess, struct ne_lock *lock) 
diff -ru Prestan/libneon/ne_socket.c Prestan-fixed/libneon/ne_socket.c
--- Prestan/libneon/ne_socket.c	2007-06-13 18:14:02.000000000 -0300
+++ Prestan-fixed/libneon/ne_socket.c	2022-02-13 22:33:50.000000000 -0300
@@ -838,7 +838,7 @@
 	return NULL;
 
     val = 1;
-   if (setsockopt(fd, SOL_TCP, TCP_NODELAY, &val, sizeof(val)) < 0){
+   if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val)) < 0){
        	perror("setsockopt() :");
 	return -1;
    }
diff -ru Prestan/src/common.c Prestan-fixed/src/common.c
--- Prestan/src/common.c	2007-09-06 16:25:40.000000000 -0300
+++ Prestan-fixed/src/common.c	2022-02-14 13:38:38.000000000 -0300
@@ -397,6 +397,8 @@
     pget_option.requests = old_requests;
     printf ( "Done\n" );
     g_echo = 1;
+
+    return OK;
 }
 
 int begin ( void )
@@ -487,16 +489,11 @@
     return ret;
 }
 
-inline int latency ( struct timeval tv1, struct timeval tv2 )
-{
-    return ( ( tv2.tv_sec-tv1.tv_sec ) *1000000+ ( tv2.tv_usec-tv1.tv_usec ) );
-}
-
 
 int my_mkcol ( char* uri, int depth )
 {
     char myuri[128];
-    int i, ret;
+    int i, ret = -1;
 
     if ( depth > 1 )
     {
@@ -506,9 +503,9 @@
         if ( ret=ne_mkcol ( i_session, myuri ) )
         {
             printf ( "my_mkcol failed 1\n" );
-            return ret;
+            return -1;
         }
-        my_mkcol ( myuri, depth-1 );
+        ret = my_mkcol ( myuri, depth-1 );
     }
     else if ( depth == 1 )
         for ( i=0; i<pget_option.width; i++ )
@@ -518,8 +515,8 @@
             sprintf ( myuri, "%ssub%d/", myuri, i );
             if ( ret=ne_mkcol ( i_session, myuri ) )
             {
-                return ret;
                 printf ( "my_mkcol failed 2\n" );
+                return ret;
             }
         }
 
@@ -534,12 +531,12 @@
 {
     char tmp[256] = "/tmp/Davtest-XXXXXX";
     char myuri[128];
-    int i, fd;
+    int i, fd, ret = -1;
 
     if ( ( fd=mkstemp ( tmp ) ) < 0 )
     {
         perror ( "mkstemp() :" );
-        return ;
+        return -1;
     }
 
     for ( i=0; i<4; i++ )
@@ -553,9 +550,9 @@
         if ( ne_mkcol ( i_session, myuri ) )
         {
             printf ( "ne_mkcol failed\n" );
-            return;
+            return -1;
         }
-        my_mkcol2 ( myuri, depth-1 );
+        ret = my_mkcol2 ( myuri, depth-1 );
     }
     else if ( depth == 1 )
         for ( i=0; i<pget_option.width; i++ )
@@ -566,25 +563,25 @@
             if ( ne_put ( i_session, myuri, fd ) )
             {
                 printf ( "ne_put failed\n" );
-                return;
+                return -1;
             }
         }
 
     unlink ( tmp );
 
-    return;
+    return ret;
 }
 
 int my_mkcol2_proppatch ( char* uri, int depth, const ne_propname pops[] )
 {
     char tmp[256] = "/tmp/Davtest-XXXXXX";
     char myuri[128];
-    int i, fd;
+    int i, fd, ret = -1;
 
     if ( ( fd=mkstemp ( tmp ) ) < 0 )
     {
         perror ( "mkstemp() :" );
-        return ;
+        return -1;
     }
 
     for ( i=0; i<4; i++ )
@@ -598,7 +595,7 @@
         ONV ( ne_mkcol ( i_session, myuri ),
               ( "MKCOL %s: %s", myuri, ne_get_error ( i_session ) ) );
         ONMREQ ( "PROPPATCH",myuri, ne_proppatch ( i_session, myuri, pops ) );
-        my_mkcol2 ( myuri, depth-1 );
+        ret = my_mkcol2 ( myuri, depth-1 );
     }
     else if ( depth == 1 )
         for ( i=0; i<pget_option.width; i++ )
@@ -609,7 +606,8 @@
             ONV ( ne_put ( i_session, myuri, fd ),
                   ( "PUT of %s: %s", myuri, ne_get_error ( i_session ) ) );
         }
-    return ;
+
+    return ret;
 }
 
 
diff -ru Prestan/src/common.h Prestan-fixed/src/common.h
--- Prestan/src/common.h	2007-08-26 15:00:43.000000000 -0300
+++ Prestan-fixed/src/common.h	2022-02-14 00:44:19.000000000 -0300
@@ -69,6 +69,8 @@
 /* Upload htdocs/foo to i_path + path */
 int upload_foo ( const char *path );
 
+void my_printf ( char *src );
+
 /* for method 'method' on 'uri', do operation 'x'. */
 #define ONMREQ(method, uri, x) do { int _ret = (x); if (_ret) { t_context("%s on `%s': %s", method, uri, ne_get_error(i_session)); return FAIL; } } while (0)
 
@@ -224,7 +226,11 @@
 int time_filter ( float a[], float b[], int nelm, float median, float percentage );
 float *times1, *times2;
 
-inline int latency ( struct timeval sec, struct timeval usec );
+static inline int latency ( struct timeval tv1, struct timeval tv2 )
+{
+    return ( ( tv2.tv_sec-tv1.tv_sec ) *1000000+ ( tv2.tv_usec-tv1.tv_usec ) );
+}
+
 int my_mkcol ( char* uri, int depth );
 int my_mkcol2 ( char* uri, int depth );
 
diff -ru Prestan/src/props.c Prestan-fixed/src/props.c
--- Prestan/src/props.c	2007-08-26 15:00:43.000000000 -0300
+++ Prestan-fixed/src/props.c	2022-02-13 22:33:50.000000000 -0300
@@ -3,6 +3,7 @@
 
 #include <stdlib.h>
 #include <stdio.h>
+#include <sys/time.h>
 
 #include <ne_request.h>
 #include <ne_props.h>
  1. Download the Prestan software package from the Sourceforge repository. The download link is:
        https://sourceforge.net/projects/prestan/files/latest/download
  2. On macOS in the Terminal unpack the software package, and enter the distribution directory:
      tar -oxzf Prestan.0.3.0.tar.gz
      cd Prestan
  3. Copy the patches shown above, and paste it into a new text file. Save it on the Desktop as Prestan-fixed.patch and apply it to the Prestan/libneon sources:
      patch -p1 < ~/Desktop/Prestan-fixed.patch
    patching file Makefile.in
    patching file configure.in
    patching file libneon/memleak.h
    patching file libneon/ne_basic.c
    patching file libneon/ne_locks.c
    patching file libneon/ne_socket.c
    patching file src/common.c
    patching file src/common.h
    patching file src/props.c
  4. Run autogen.sh and then configure:
      ./autogen.sh
      ./configure
  5. For building Prestan, execute make clean, then make and sudo make install:
      ./configure
      make
      sudo mkdir -p /usr/local
      sudo make install clean
  6. Execute Prestan using the following command:
      /usr/local/bin/Prestan http://cystat.local/test/ <user> <password>
    Prestan, Version 0.3.0
    Copyright(c) 2003 Teng Xu, GRASE Research Group at UCSC
    Copyright(c) 2007 Markus Litz, Modified by DLR - 2007
    http://www.dlr.de/sc
    
    Server Warming Up.......................Please Wait.......Done
    
    Start Testing http://cystat.local/test/:
    
              **********************************
              * Number of Requests		10
              * Number of Dead Properties	10
              * Depth of Collection		10
              * Width of Collection		100
              * Type of Methods		WebDAV
              **********************************
    
    ProppatchMult................. Rsp = 584 [us]
    ProppatchSingle............... Rsp = 368 [us]
    PropfindDeadMult.............. Rsp = 524 [us]
    PropfindDeadSingle............ Rsp = 359 [us]
    PropfindLiveMult.............. Rsp = 602 [us]
    PropfindLiveSingle............ Rsp = 694 [us]
    Put1K......................... Rsp = 694 [us]
    Get1K......................... Rsp = 1146 [us]
    Put64K........................ Rsp = 7741 [us]
    Get64K........................ Rsp = 4972 [us]
    Put1024K...................... Rsp = 95347 [us]
    Get1024K...................... Rsp = 99121 [us]
    Copy.......................... Rsp = 416 [us]
    Move.......................... Rsp = 548 [us]
    Delete........................ Rsp = 471 [us]
    MkCol......................... Rsp = 464 [us]
    CopyCol....................... Rsp = 12040 [us]
    MoveCol....................... Rsp = 8388 [us]
    DeleteCol..................... Rsp = 7367 [us]
    Lock.......................... Rsp = 488 [us]
    UnLock........................ Rsp = 398 [us]
    LockCol....................... Rsp = 575 [us]
    UnLockCol..................... Rsp = 1983 [us]
    
    ACL Not Implemented

Comparison with the DAV module of the latest Apache 2.4 running on the same HW

/usr/local/bin/Prestan http://apache-dav.local/test/ <user> <password>

Prestan, Version 0.3.0
Copyright(c) 2003 Teng Xu, GRASE Research Group at UCSC
Copyright(c) 2007 Markus Litz, Modified by DLR - 2007
http://www.dlr.de/sc

Server Warming Up.......................Please Wait.......Done

Start Testing http://apache-dav.local/test/:

          **********************************

          * Number of Requests		10
          * Number of Dead Properties	10
          * Depth of Collection		10
          * Width of Collection		100
          * Type of Methods		WebDAV

          **********************************

ProppatchMult................. Rsp = 584 [us]
ProppatchSingle............... Rsp = 368 [us]
PropfindDeadMult.............. Rsp = 524 [us]
PropfindDeadSingle............ Rsp = 359 [us]
PropfindLiveMult.............. Rsp = 602 [us]
PropfindLiveSingle............ Rsp = 694 [us]
Put1K......................... Rsp = 694 [us]
Get1K......................... Rsp = 1146 [us]
Put64K........................ Rsp = 7741 [us]
Get64K........................ Rsp = 4972 [us]
Put1024K...................... Rsp = 95347 [us]
Get1024K...................... Rsp = 99121 [us]
Copy.......................... Rsp = 416 [us]
Move.......................... Rsp = 548 [us]
Delete........................ Rsp = 471 [us]
MkCol......................... Rsp = 464 [us]
CopyCol....................... Rsp = 12040 [us]
MoveCol....................... Rsp = 8388 [us]
DeleteCol..................... Rsp = 7367 [us]
Lock.......................... Rsp = 488 [us]
UnLock........................ Rsp = 398 [us]
LockCol....................... Rsp = 575 [us]
UnLockCol..................... Rsp = 1983 [us]

ACL Not Implemented

The x-axis scale is logarithmic because otherwise the differences in the fast methods would not be visible. I conclude that Cyclaero's WebDAV is mostly on par with Apache's implementation.

Copyright © Dr. Rolf Jansen - 2022-02-13 14:15:42

Discussion on Twitter: 1493582251992535042