diff --git a/tests/.gitignore b/tests/.gitignore
index ba74bfbb913d915e7e0a59efc8e1344f81638065..817c704ffab2d8c4b0d9b2a90f928b1828d6331d 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -20,4 +20,5 @@ foo
 simple_archiver
 simple_injector
 simple_group_injector
+simple_telemetry_listener
 coverage
\ No newline at end of file
diff --git a/tests/Makefile.am b/tests/Makefile.am
index b40b04224f18d190c7aaa772a1f8e3b5867e39f8..3a9fb1590f5055c0a86389c6ad289581ec961247 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -115,6 +115,7 @@ check_PROGRAMS = check_version check_init check_uuid \
 		 simple_interlock_client_2 \
 		 simple_injector \
 		 simple_archiver \
+		 simple_telemetry_listener \
 		 check_events \
 		 coverage
 
diff --git a/tests/simple_telemetry_listener.c b/tests/simple_telemetry_listener.c
new file mode 100644
index 0000000000000000000000000000000000000000..795e48691a81b8bf5a13723bf7175a28b06a0708
--- /dev/null
+++ b/tests/simple_telemetry_listener.c
@@ -0,0 +1,170 @@
+/* Connect to pool and subscribe to all JOIN and all CDO ID creating
+ * events. Log them. */
+/*
+ * Copyright (C) 2020 Cray Computer GmbH
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its
+ *    contributors may be used to endorse or promote products derived from
+ *    this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/* FIXME: set signal handler to flush log and stop cleanly */
+
+
+#include "maestro.h"
+#include "maestro/i_statistics.h"
+
+#include <unistd.h>
+
+const mstro_nanosec_t  MAX_WAIT = ((mstro_nanosec_t)15)*1000*1000*1000; /* 10s */
+int
+main(int argc, char ** argv)
+{
+  /* FIXME: parse arguments */
+  char *component_name = argv[0];
+  
+  mstro_status s=mstro_init(NULL, component_name, 0);
+
+  /* wildcard selector: any CDO */
+  mstro_cdo_selector selector=NULL;
+  s=mstro_cdo_selector_create(
+      NULL, NULL,
+      "(has .maestro.core.cdo.name)",
+      &selector);
+  
+
+  /* Subscribe to DECLARE and SEAL.
+   *
+   * That allows us to log app-id/name/local-id and later
+   * app-id/local-id/cdo-id
+   */
+  mstro_subscription cdo_subscription=NULL;
+  s = mstro_subscribe(selector,
+                      MSTRO_POOL_EVENT_DECLARE |MSTRO_POOL_EVENT_SEAL,
+                      MSTRO_SUBSCRIPTION_OPTS_DEFAULT,
+                      &cdo_subscription);
+  s=mstro_cdo_selector_dispose(selector);
+  
+
+  /* Subscribe to JOIN and LEAVE.
+   *
+   * That allows us to log component names and implement a termination
+   * criterion like "if component X has left, terminate telemetry
+   * listener" */
+  mstro_subscription join_leave_subscription=NULL;
+  s=mstro_subscribe(
+      NULL,
+      ( MSTRO_POOL_EVENT_APP_JOIN | MSTRO_POOL_EVENT_APP_LEAVE),
+      MSTRO_SUBSCRIPTION_OPTS_DEFAULT,
+      &join_leave_subscription);
+
+  
+  bool done = false;
+
+  mstro_nanosec_t starttime = mstro_clock();
+
+  /* instead of a busy loop we could use event wait sets -- but we
+   * don't have them yet */
+  while(!done) {
+    mstro_pool_event e=NULL;
+    /* poll join/leave */
+    s=mstro_subscription_poll(join_leave_subscription, &e);
+    
+    if(e!=NULL) {
+      fprintf(stdout, "JOIN/LEAVE event(s)\n");
+      mstro_pool_event tmp=e;
+      /* handle all */
+      while(tmp) {
+        switch(tmp->kind) {
+          case MSTRO_POOL_EVENT_APP_JOIN:
+            fprintf(stdout, "Noticed JOIN event of app %s\n",
+                    tmp->join.component_name);
+            break;
+          case MSTRO_POOL_EVENT_APP_LEAVE:
+            fprintf(stdout, "Noticed LEAVE event of app %" PRIappid "\n",
+                    tmp->leave.appid);
+            break;
+          default:
+            fprintf(stderr, "Unexpected event %d\n", tmp->kind);
+        }
+        fflush(stdout);
+        tmp=tmp->next;
+      }
+
+      /* acknowledge all */
+      s=mstro_subscription_ack(join_leave_subscription, e);
+      /* dispose all */
+      s=mstro_pool_event_dispose(e);
+    } else {
+      s=mstro_subscription_poll(cdo_subscription, &e);
+      if(e!=NULL) {
+        mstro_pool_event tmp=e;
+        /* handle all */
+        while(tmp) {
+          switch(tmp->kind) {
+            case MSTRO_POOL_EVENT_DECLARE:
+            case MSTRO_POOL_EVENT_SEAL:
+              fprintf(stdout, "CDO event %s\n",
+                      mstro_pool_event_description(tmp->kind));
+              break;
+            default:
+              fprintf(stderr, "Unexpected CDO event %d\n", tmp->kind);
+          }
+          tmp=tmp->next;
+        }
+        /* acknowledge all */
+        s=mstro_subscription_ack(cdo_subscription, e);
+        /* dispose all */
+        s=mstro_pool_event_dispose(e);
+      } else {
+        sleep(1); /* core will queue up events for us, no need to do
+                   * intensive busy-loop */
+      }
+    }
+    
+    if(mstro_clock()>starttime+MAX_WAIT) {
+      fprintf(stderr, "Waited %" PRIu64 "s and still not done\n",
+              MAX_WAIT/(1000*1000*1000));
+      done=true;
+    }
+    if(done)
+      break; /* from WHILE */
+             
+  }
+  s= mstro_subscription_dispose(cdo_subscription);
+  s= mstro_subscription_dispose(join_leave_subscription);
+  
+
+BAILOUT:
+  ;
+  mstro_status s1=mstro_finalize();
+
+  if(s1!=MSTRO_OK || s!=MSTRO_OK)
+    return EXIT_FAILURE;
+  else
+    return EXIT_SUCCESS;
+}
+