diff --git a/common/utils/T/tracer/logger/Makefile b/common/utils/T/tracer/logger/Makefile
index 18f70e0bc56b303f1ed36d2fae81a12740687bfd..e5391008380ccb32410dcc92a3a0788bd3ad8a45 100644
--- a/common/utils/T/tracer/logger/Makefile
+++ b/common/utils/T/tracer/logger/Makefile
@@ -1,7 +1,7 @@
 CC=gcc
 CFLAGS=-Wall -g -pthread -I..
 
-OBJS=logger.o textlog.o framelog.o ttilog.o timelog.o
+OBJS=logger.o textlog.o framelog.o ttilog.o timelog.o ticklog.o
 
 logger.a: $(OBJS)
 	ar cr logger.a $(OBJS)
diff --git a/common/utils/T/tracer/logger/logger.h b/common/utils/T/tracer/logger/logger.h
index ec04e60819029520074055fc648d6c1497d8c7bf..2c26f8dcb2aa9462f52f4bc7acfb53710417c4b7 100644
--- a/common/utils/T/tracer/logger/logger.h
+++ b/common/utils/T/tracer/logger/logger.h
@@ -11,6 +11,8 @@ logger *new_ttilog(void *event_handler, void *database,
     char *event_name, char *frame_varname, char *subframe_varname,
     char *data_varname, int convert_to_dB);
 logger *new_timelog(void *event_handler, void *database, char *event_name);
+logger *new_ticklog(void *event_handler, void *database,
+    char *event_name, char *frame_name, char *subframe_name);
 
 void framelog_set_skip(logger *_this, int skip_delay);
 
diff --git a/common/utils/T/tracer/logger/ticklog.c b/common/utils/T/tracer/logger/ticklog.c
new file mode 100644
index 0000000000000000000000000000000000000000..b2462aabb642b7682216c48ec3b06e1e73e73164
--- /dev/null
+++ b/common/utils/T/tracer/logger/ticklog.c
@@ -0,0 +1,80 @@
+#include "logger.h"
+#include "logger_defs.h"
+#include "event.h"
+#include "database.h"
+#include "handler.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+struct ticklog {
+  struct logger common;
+  void *database;
+  int frame_arg;
+  int subframe_arg;
+};
+
+static void _event(void *p, event e)
+{
+  struct ticklog *l = p;
+  int i;
+  int frame;
+  int subframe;
+
+  frame = e.e[l->frame_arg].i;
+  subframe = e.e[l->subframe_arg].i;
+
+  for (i = 0; i < l->common.vsize; i++)
+    l->common.v[i]->append(l->common.v[i], e.sending_time, frame, subframe);
+}
+
+logger *new_ticklog(event_handler *h, void *database,
+    char *event_name, char *frame_varname, char *subframe_varname)
+{
+  struct ticklog *ret;
+  int event_id;
+  database_event_format f;
+  int i;
+
+  ret = calloc(1, sizeof(struct ticklog)); if (ret == NULL) abort();
+
+  ret->common.event_name = strdup(event_name);
+  if (ret->common.event_name == NULL) abort();
+  ret->database = database;
+
+  event_id = event_id_from_name(database, event_name);
+
+  ret->common.handler_id = register_handler_function(h,event_id,_event,ret);
+
+  f = get_format(database, event_id);
+
+  /* look for frame and subframe args */
+  ret->frame_arg = -1;
+  ret->subframe_arg = -1;
+  for (i = 0; i < f.count; i++) {
+    if (!strcmp(f.name[i], frame_varname)) ret->frame_arg = i;
+    if (!strcmp(f.name[i], subframe_varname)) ret->subframe_arg = i;
+  }
+  if (ret->frame_arg == -1) {
+    printf("%s:%d: frame argument '%s' not found in event '%s'\n",
+        __FILE__, __LINE__, frame_varname, event_name);
+    abort();
+  }
+  if (ret->subframe_arg == -1) {
+    printf("%s:%d: subframe argument '%s' not found in event '%s'\n",
+        __FILE__, __LINE__, subframe_varname, event_name);
+    abort();
+  }
+  if (strcmp(f.type[ret->frame_arg], "int") != 0) {
+    printf("%s:%d: argument '%s' has wrong type (should be 'int')\n",
+        __FILE__, __LINE__, frame_varname);
+    abort();
+  }
+  if (strcmp(f.type[ret->subframe_arg], "int") != 0) {
+    printf("%s:%d: argument '%s' has wrong type (should be 'int')\n",
+        __FILE__, __LINE__, subframe_varname);
+    abort();
+  }
+
+  return ret;
+}
diff --git a/common/utils/T/tracer/view/Makefile b/common/utils/T/tracer/view/Makefile
index 51b5114a0ede49d44a87e3df9d684417b9a7fe91..32b267ec24d60c25bf1650e5b60250579c6be871 100644
--- a/common/utils/T/tracer/view/Makefile
+++ b/common/utils/T/tracer/view/Makefile
@@ -1,7 +1,7 @@
 CC=gcc
-CFLAGS=-Wall -g -pthread -I..
+CFLAGS=-Wall -g -pthread -I.. -I../logger
 
-OBJS=stdout.o textlist.o xy.o tti.o time.o
+OBJS=stdout.o textlist.o xy.o tti.o time.o ticktime.o
 
 view.a: $(OBJS)
 	ar cr view.a $(OBJS)
diff --git a/common/utils/T/tracer/view/ticktime.c b/common/utils/T/tracer/view/ticktime.c
new file mode 100644
index 0000000000000000000000000000000000000000..90ded4dd431ab0775c3209c30f0e37be8c79f9dd
--- /dev/null
+++ b/common/utils/T/tracer/view/ticktime.c
@@ -0,0 +1,410 @@
+#include "view.h"
+#include "../utils.h"
+#include "logger.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <pthread.h>
+#include <string.h>
+
+/* TODO: some code is identical/almost identical to time.c, merge/factorize */
+
+/****************************************************************************/
+/*                               tick timeview                              */
+/****************************************************************************/
+
+struct plot {
+  struct timespec *tick;
+  int ticksize;
+  int tickmaxsize;
+  int tickstart;
+  int line;
+  int color;
+};
+
+struct ticktime {
+  view common;
+  gui *g;
+  widget *w;
+  float refresh_rate;
+  pthread_mutex_t lock;
+  struct plot *p;
+  int psize;
+  double pixel_length;        /* unit: nanosecond (maximum 1 hour/pixel) */
+  struct timespec latest_time;
+  struct timespec start_time;
+  int autoscroll;
+  struct timespec tick_latest_time;
+  int tick_latest_frame;
+  int tick_latest_subframe;
+  void *clock_ticktime;      /* data for the clock tick, see below */
+};
+
+/* TODO: put that function somewhere else (utils.c) */
+static struct timespec time_add(struct timespec a, struct timespec b)
+{
+  struct timespec ret;
+  ret.tv_sec = a.tv_sec + b.tv_sec;
+  ret.tv_nsec = a.tv_nsec + b.tv_nsec;
+  if (ret.tv_nsec > 1000000000) {
+    ret.tv_sec++;
+    ret.tv_nsec -= 1000000000;
+  }
+  return ret;
+}
+
+/* TODO: put that function somewhere else (utils.c) */
+static struct timespec time_sub(struct timespec a, struct timespec b)
+{
+  struct timespec ret;
+  if (a.tv_nsec < b.tv_nsec) {
+    ret.tv_nsec = (int64_t)a.tv_nsec - (int64_t)b.tv_nsec + 1000000000;
+    ret.tv_sec = a.tv_sec - b.tv_sec - 1;
+  } else {
+    ret.tv_nsec = a.tv_nsec - b.tv_nsec;
+    ret.tv_sec = a.tv_sec - b.tv_sec;
+  }
+  return ret;
+}
+
+/* TODO: put that function somewhere else (utils.c) */
+static struct timespec nano_to_time(int64_t n)
+{
+  struct timespec ret;
+  ret.tv_sec = n / 1000000000;
+  ret.tv_nsec = n % 1000000000;
+  return ret;
+}
+
+/* TODO: put that function somewhere else (utils.c) */
+static int time_cmp(struct timespec a, struct timespec b)
+{
+  if (a.tv_sec < b.tv_sec) return -1;
+  if (a.tv_sec > b.tv_sec) return 1;
+  if (a.tv_nsec < b.tv_nsec) return -1;
+  if (a.tv_nsec > b.tv_nsec) return 1;
+  return 0;
+}
+
+static int interval_empty(struct ticktime *this, int sub,
+    struct timespec start, struct timespec end)
+{
+  int a, b, mid;
+  int i;
+
+  if (this->p[sub].ticksize == 0) return 1;
+
+  /* look for a tick larger than start and smaller than end */
+  a = 0;
+  b = this->p[sub].ticksize - 1;
+  while (b >= a) {
+    mid = (a+b) / 2;
+    i = (this->p[sub].tickstart + mid) % this->p[sub].ticksize;
+    if (time_cmp(this->p[sub].tick[i], start) < 0) a = mid + 1;
+    else if (time_cmp(this->p[sub].tick[i], end) > 0) b = mid - 1;
+    else return 0;
+  }
+  return 1;
+}
+
+static void *ticktime_thread(void *_this)
+{
+  struct ticktime *this = _this;
+  int width;
+  int l;
+  int i;
+  struct timespec tstart;
+  struct timespec tnext;
+  struct plot *p;
+  int64_t pixel_length;
+
+  while (1) {
+    if (pthread_mutex_lock(&this->lock)) abort();
+
+    timeline_get_width(this->g, this->w, &width);
+    timeline_clear_silent(this->g, this->w);
+
+    /* TODO: optimize? */
+
+    /* use rounded pixel_length */
+    pixel_length = this->pixel_length;
+
+    if (this->autoscroll) {
+      tnext = time_add(this->latest_time,
+          (struct timespec){tv_sec:0,tv_nsec:1});
+      tstart = time_sub(tnext, nano_to_time(pixel_length * width));
+      this->start_time = tstart;
+    } else {
+      tstart = this->start_time;
+      tnext = time_add(tstart, nano_to_time(pixel_length * width));
+    }
+
+    for (l = 0; l < this->psize; l++) {
+      for (i = 0; i < width; i++) {
+        struct timespec tick_start, tick_end;
+        tick_start = time_add(tstart, nano_to_time(pixel_length * i));
+        tick_end = time_add(tick_start, nano_to_time(pixel_length-1));
+        if (interval_empty(this, l, tick_start, tick_end))
+          continue;
+        p = &this->p[l];
+        /* TODO: only one call */
+        timeline_add_points_silent(this->g, this->w, p->line, p->color, &i, 1);
+      }
+    }
+
+    widget_dirty(this->g, this->w);
+
+    if (pthread_mutex_unlock(&this->lock)) abort();
+    sleepms(1000 / this->refresh_rate);
+  }
+
+  return 0;
+}
+
+static void scroll(void *private, gui *g,
+    char *notification, widget *w, void *notification_data)
+{
+  struct ticktime *this = private;
+  int *d = notification_data;
+  int x = d[0];
+  int key_modifiers = d[2];
+  double mul = 1.2;
+  double pixel_length;
+  int64_t old_px_len_rounded;
+  struct timespec t;
+  int scroll_px;
+  int width;
+
+  if (pthread_mutex_lock(&this->lock)) abort();
+
+  old_px_len_rounded = this->pixel_length;
+
+  /* scroll if control+wheel, zoom otherwise */
+
+  if (key_modifiers & KEY_CONTROL) {
+    timeline_get_width(this->g, this->w, &width);
+    if (width < 2) width = 2;
+    scroll_px = 100;
+    if (scroll_px > width - 1) scroll_px = width - 1;
+    if (!strcmp(notification, "scrolldown"))
+      this->start_time = time_add(this->start_time,
+          nano_to_time(scroll_px * old_px_len_rounded));
+    else
+      this->start_time = time_sub(this->start_time,
+          nano_to_time(scroll_px * old_px_len_rounded));
+    goto end;
+  }
+
+  if (!strcmp(notification, "scrollup")) mul = 1 / mul;
+
+again:
+  pixel_length = this->pixel_length * mul;
+  if (pixel_length < 1) pixel_length = 1;
+  if (pixel_length > (double)3600 * 1000000000)
+    pixel_length = (double)3600 * 1000000000;
+
+  this->pixel_length = pixel_length;
+
+  /* due to rounding, we may need to zoom by more than 1.2 with
+   * very close lookup, otherwise the user zooming command won't
+   * be visible (say length is 2.7, zoom in, new length is 2.25,
+   * and rounding is 2, same value, no change, no feedback to user => bad)
+   * TODO: make all this cleaner
+   */
+  if (pixel_length != 1 && pixel_length != (double)3600 * 1000000000 &&
+      (int64_t)pixel_length == old_px_len_rounded)
+    goto again;
+
+  t = time_add(this->start_time, nano_to_time(x * old_px_len_rounded));
+  this->start_time = time_sub(t, nano_to_time(x * (int64_t)pixel_length));
+
+end:
+  if (pthread_mutex_unlock(&this->lock)) abort();
+}
+
+static void click(void *private, gui *g,
+    char *notification, widget *w, void *notification_data)
+{
+  struct ticktime *this = private;
+  int *d = notification_data;
+  int button = *d;
+
+  if (button == 3) this->autoscroll = 0;
+  if (button == 1) this->autoscroll = 1;
+}
+
+view *new_view_ticktime(float refresh_rate, gui *g, widget *w)
+{
+  struct ticktime *ret = calloc(1, sizeof(struct ticktime));
+  if (ret == NULL) abort();
+
+  ret->refresh_rate = refresh_rate;
+  ret->g = g;
+  ret->w = w;
+
+  ret->p = NULL;
+  ret->psize = 0;
+
+  ret->autoscroll = 1;
+
+  ret->tick_latest_time.tv_sec = 1;
+
+  /* default pixel length: 10ms */
+  ret->pixel_length = 10 * 1000000;
+
+  register_notifier(g, "scrollup", w, scroll, ret);
+  register_notifier(g, "scrolldown", w, scroll, ret);
+  register_notifier(g, "click", w, click, ret);
+
+  if (pthread_mutex_init(&ret->lock, NULL)) abort();
+
+  new_thread(ticktime_thread, ret);
+
+  return (view *)ret;
+}
+
+/****************************************************************************/
+/*                          subticktimeview                                 */
+/****************************************************************************/
+
+struct subticktime {
+  view common;
+  struct ticktime *parent;
+  int line;
+  int color;
+  int subview;
+};
+
+static void append(view *_this, struct timespec t, int frame, int subframe)
+{
+  struct subticktime *this = (struct subticktime *)_this;
+  struct ticktime    *ticktime = this->parent;
+  struct plot        *p = &ticktime->p[this->subview];
+  int                i;
+  struct timespec    swap;
+  int64_t            diff;
+
+  if (pthread_mutex_lock(&ticktime->lock)) abort();
+
+  /* get time with respect to latest known tick time */
+  diff = (frame*10 + subframe) -
+      (ticktime->tick_latest_frame*10 + ticktime->tick_latest_subframe);
+  if (diff > 1024*10/2) diff -= 1024*10;
+  else if (diff < -1024*10/2) diff += 1024*10;
+  if (diff < 0)
+    t = time_sub(ticktime->tick_latest_time, nano_to_time(-diff * 1000000));
+  else
+    t = time_add(ticktime->tick_latest_time, nano_to_time(diff * 1000000));
+
+  if (p->ticksize < p->tickmaxsize) {
+    p->tick[p->ticksize] = t;
+    p->ticksize++;
+  } else {
+    p->tick[p->tickstart] = t;
+    p->tickstart = (p->tickstart + 1) % p->ticksize;
+  }
+
+  /* due to adjustment of the time, array may not be ordered anymore */
+  for (i = p->ticksize-2; i >= 0; i--) {
+    int prev = (p->tickstart + i) % p->ticksize;
+    int cur = (prev + 1) % p->ticksize;
+    if (time_cmp(p->tick[prev], p->tick[cur]) <= 0) break;
+    swap = p->tick[prev];
+    p->tick[prev] = p->tick[cur];
+    p->tick[cur] = swap;
+  }
+
+  if (time_cmp(ticktime->latest_time, t) < 0)
+    ticktime->latest_time = t;
+
+  if (pthread_mutex_unlock(&ticktime->lock)) abort();
+}
+
+view *new_subview_ticktime(view *_time, int line, int color, int size)
+{
+  struct ticktime *ticktime = (struct ticktime *)_time;
+  struct subticktime *ret = calloc(1, sizeof(struct subticktime));
+  if (ret == NULL) abort();
+
+  ret->common.append = (void (*)(view *, ...))append;
+
+  if (pthread_mutex_lock(&ticktime->lock)) abort();
+
+  ret->parent = ticktime;
+  ret->line = line;
+  ret->color = color;
+  ret->subview = ticktime->psize;
+
+  ticktime->p = realloc(ticktime->p,
+      (ticktime->psize + 1) * sizeof(struct plot));
+  if (ticktime->p == NULL) abort();
+  ticktime->p[ticktime->psize].tick = calloc(size, sizeof(struct timespec));
+  if (ticktime->p[ticktime->psize].tick == NULL) abort();
+  ticktime->p[ticktime->psize].ticksize = 0;
+  ticktime->p[ticktime->psize].tickmaxsize = size;
+  ticktime->p[ticktime->psize].tickstart = 0;
+  ticktime->p[ticktime->psize].line = line;
+  ticktime->p[ticktime->psize].color = color;
+
+  ticktime->psize++;
+
+  if (pthread_mutex_unlock(&ticktime->lock)) abort();
+
+  return (view *)ret;
+}
+
+/****************************************************************************/
+/*                               clock tick                                 */
+/****************************************************************************/
+
+struct clock_ticktime {
+  view common;
+  struct ticktime *parent;
+};
+
+static void clock_append(view *_this, struct timespec t,
+    int frame, int subframe)
+{
+  struct clock_ticktime *this = (struct clock_ticktime *)_this;
+  struct ticktime *tt = this->parent;
+  int64_t diff;
+
+  if (subframe == 10) { subframe = 0; frame = (frame + 1) % 1024; }
+
+  if (pthread_mutex_lock(&tt->lock)) abort();
+
+  /* get time relative to latest known tick time */
+  /* In normal conditions diff is 1 but if the user pauses reception of events
+   * it may be anything. Let's take only positive values.
+   */
+  diff = (frame*10 + subframe) -
+      (tt->tick_latest_frame*10 + tt->tick_latest_subframe);
+  if (diff < 0) diff += 1024*10;
+  tt->tick_latest_time = time_add(tt->tick_latest_time,
+      nano_to_time(diff * 1000000));
+  tt->tick_latest_frame = frame;
+  tt->tick_latest_subframe = subframe;
+
+  if (time_cmp(tt->latest_time, tt->tick_latest_time) < 0)
+    tt->latest_time = tt->tick_latest_time;
+
+  if (pthread_mutex_unlock(&tt->lock)) abort();
+}
+
+void ticktime_set_tick(view *_ticktime, void *logger)
+{
+  struct ticktime *ticktime = (struct ticktime *)_ticktime;
+  struct clock_ticktime *n;
+
+  if (pthread_mutex_lock(&ticktime->lock)) abort();
+
+  free(ticktime->clock_ticktime);
+  n = ticktime->clock_ticktime = calloc(1, sizeof(struct clock_ticktime));
+  if (n == NULL) abort();
+
+  n->common.append = (void (*)(view *, ...))clock_append;
+  n->parent = ticktime;
+
+  logger_add_view(logger, (view *)n);
+
+  if (pthread_mutex_unlock(&ticktime->lock)) abort();
+}
diff --git a/common/utils/T/tracer/view/view.h b/common/utils/T/tracer/view/view.h
index 4b91291b1c3ffa6e2d7621d54f0e0727fe1801eb..cbc5e21b73d95fb3b15524977663a6054846fc5f 100644
--- a/common/utils/T/tracer/view/view.h
+++ b/common/utils/T/tracer/view/view.h
@@ -20,5 +20,8 @@ view *new_view_tti(float refresh_rate, gui *g, widget *w,
 view *new_view_time(int number_of_seconds, float refresh_rate,
     gui *g, widget *w);
 view *new_subview_time(view *time, int line, int color, int size);
+view *new_view_ticktime(float refresh_rate, gui *g, widget *w);
+view *new_subview_ticktime(view *ticktime, int line, int color, int size);
+void ticktime_set_tick(view *ticktime, void *logger);
 
 #endif /* _VIEW_H_ */