kernel-sharkqt: Add streams to GUI

This patch modifies the GUI code in order to make it compatible
with the new version of the C API.

Signed-off-by: Yordan Karadzhov <ykaradzhov@vmware.com>
diff --git a/kernel-shark-qt/src/CMakeLists.txt b/kernel-shark-qt/src/CMakeLists.txt
index fc3d371..5b7f998 100644
--- a/kernel-shark-qt/src/CMakeLists.txt
+++ b/kernel-shark-qt/src/CMakeLists.txt
@@ -28,69 +28,69 @@
 
 endif (OPENGL_FOUND AND GLUT_FOUND)
 
-# if (Qt5Widgets_FOUND AND Qt5Network_FOUND)
+if (Qt5Widgets_FOUND AND Qt5Network_FOUND)
+
+    message(STATUS "libkshark-gui")
+    set (ks-guiLib_hdr  KsUtils.hpp
+                        KsModels.hpp
+                        KsGLWidget.hpp
+                        KsDualMarker.hpp
+                        KsWidgetsLib.hpp
+                        KsTraceGraph.hpp
+                        KsTraceViewer.hpp
+                        KsMainWindow.hpp
+                        KsCaptureDialog.hpp
+                        KsQuickContextMenu.hpp
+                        KsAdvFilteringDialog.hpp)
 # 
-#     message(STATUS "libkshark-gui")
-#     set (ks-guiLib_hdr  KsUtils.hpp
-#                         KsModels.hpp
-#                         KsGLWidget.hpp
-#                         KsDualMarker.hpp
-#                         KsWidgetsLib.hpp
-#                         KsTraceGraph.hpp
-#                         KsTraceViewer.hpp
-#                         KsMainWindow.hpp
-#                         KsCaptureDialog.hpp
-#                         KsQuickContextMenu.hpp
-#                         KsAdvFilteringDialog.hpp)
+    QT5_WRAP_CPP(ks-guiLib_hdr_moc ${ks-guiLib_hdr})
+
+    add_library(kshark-gui  SHARED  ${ks-guiLib_hdr_moc}    KsUtils.cpp
+                                                            KsModels.cpp
+                                                            KsSession.cpp
+                                                            KsGLWidget.cpp
+                                                            KsDualMarker.cpp
+                                                            KsWidgetsLib.cpp
+                                                            KsTraceGraph.cpp
+                                                            KsTraceViewer.cpp
+                                                            KsMainWindow.cpp
+                                                            KsCaptureDialog.cpp
+                                                            KsQuickContextMenu.cpp
+                                                            KsAdvFilteringDialog.cpp)
 # 
-#     QT5_WRAP_CPP(ks-guiLib_hdr_moc ${ks-guiLib_hdr})
-# 
-#     add_library(kshark-gui  SHARED  ${ks-guiLib_hdr_moc}    KsUtils.cpp
-#                                                             KsModels.cpp
-#                                                             KsSession.cpp
-#                                                             KsGLWidget.cpp
-#                                                             KsDualMarker.cpp
-#                                                             KsWidgetsLib.cpp
-#                                                             KsTraceGraph.cpp
-#                                                             KsTraceViewer.cpp
-#                                                             KsMainWindow.cpp
-#                                                             KsCaptureDialog.cpp
-#                                                             KsQuickContextMenu.cpp
-#                                                             KsAdvFilteringDialog.cpp)
-# 
-#     target_link_libraries(kshark-gui kshark-plot
-#                                      ${CMAKE_DL_LIBS}
-#                                      ${TRACEEVENT_LIBRARY}
-#                                      ${TRACECMD_LIBRARY}
-#                                      Qt5::Widgets
-#                                      Qt5::Network)
-# 
-#     set_target_properties(kshark-gui PROPERTIES  SUFFIX ".so.${KS_VERSION_STRING}")
-# 
-#     message(STATUS "kernelshark")
-#     add_executable(kernelshark          kernelshark.cpp)
-#     target_link_libraries(kernelshark   kshark-gui)
-# 
-#     message(STATUS "kshark-record")
-#     add_executable(kshark-record        kshark-record.cpp)
-#     target_link_libraries(kshark-record kshark-gui)
-# 
-#     install(TARGETS kernelshark kshark-record kshark kshark-plot kshark-gui
-#             RUNTIME DESTINATION /usr/local/bin/
-#             LIBRARY DESTINATION /usr/local/lib/kshark/)
-# 
-#     install(FILES "${KS_DIR}/kernelshark.desktop"
-#             DESTINATION /usr/share/applications/)
-# 
-#     install(FILES "${KS_DIR}/org.freedesktop.kshark-record.policy"
-#             DESTINATION /usr/share/polkit-1/actions/)
-# 
-#     install(PROGRAMS "${KS_DIR}/bin/kshark-su-record"
-#             DESTINATION /usr/local/bin/)
-# 
-# endif (Qt5Widgets_FOUND AND Qt5Network_FOUND)
-# 
-# add_subdirectory(plugins)
+    target_link_libraries(kshark-gui kshark-plot
+                                     ${CMAKE_DL_LIBS}
+                                     ${TRACEEVENT_LIBRARY}
+                                     ${TRACECMD_LIBRARY}
+                                     Qt5::Widgets
+                                     Qt5::Network)
+
+    set_target_properties(kshark-gui PROPERTIES  SUFFIX ".so.${KS_VERSION_STRING}")
+
+    message(STATUS "kernelshark")
+    add_executable(kernelshark          kernelshark.cpp)
+    target_link_libraries(kernelshark   kshark-gui)
+
+    message(STATUS "kshark-record")
+    add_executable(kshark-record        kshark-record.cpp)
+    target_link_libraries(kshark-record kshark-gui)
+
+    install(TARGETS kernelshark kshark-record kshark kshark-plot kshark-gui
+            RUNTIME DESTINATION /usr/local/bin/
+            LIBRARY DESTINATION /usr/local/lib/kshark/)
+
+    install(FILES "${KS_DIR}/kernelshark.desktop"
+            DESTINATION /usr/share/applications/)
+
+    install(FILES "${KS_DIR}/org.freedesktop.kshark-record.policy"
+            DESTINATION /usr/share/polkit-1/actions/)
+
+    install(PROGRAMS "${KS_DIR}/bin/kshark-su-record"
+            DESTINATION /usr/local/bin/)
+
+endif (Qt5Widgets_FOUND AND Qt5Network_FOUND)
+
+add_subdirectory(plugins)
 
 find_program(DO_AS_ROOT pkexec)
 
diff --git a/kernel-shark-qt/src/plugins/kvm_events.c b/kernel-shark-qt/src/plugins/kvm_events.c
new file mode 100644
index 0000000..9382b53
--- /dev/null
+++ b/kernel-shark-qt/src/plugins/kvm_events.c
@@ -0,0 +1,205 @@
+/* SPDX-License-Identifier: LGPL-2.1 */
+
+/*
+ * Copyright (C) 2017 VMware Inc, Yordan Karadzhov <y.karadz@gmail.com>
+ */
+
+/**
+ *  @file    rename_sched_events.c
+ *  @brief   A plugin to deal with renamed threads.
+ */
+
+#ifndef _KS_PLUGIN_SHED_RENAME_H
+#define _KS_PLUGIN_SHED_RENAME_H
+
+// KernelShark
+#include "libkshark.h"
+#include "libkshark-model.h"
+
+/** Structure representing a plugin-specific context. */
+struct plugin_rename_context {
+	/** Stream identifier of the Monitor data. */
+	uint8_t		monitor_stream_id;
+
+	/** Pointer to the kvm_entry_event object. */
+	struct tep_event_format	*kvm_entry_event;
+
+	/** Pointer to the sched_switch_next_field format descriptor. */
+	struct tep_format_field	*kvm_vcpu_id_field;
+
+	
+};
+
+/** Plugin context instances. */
+static struct plugin_rename_context *
+plugin_context_handler[KS_MAX_NUM_STREAMS] = {NULL};
+
+static void plugin_close(int sd)
+{
+	free(plugin_context_handler[sd]);
+	plugin_context_handler[sd] = NULL;
+}
+
+// static void free_plugin_context()
+// {
+// 	int i;
+// 
+// 	for (i = 0; i < KS_MAX_NUM_STREAMS; ++i)
+// 		plugin_close(i);
+// }
+
+static bool
+plugin_update_stream_context(struct kshark_context *kshark_ctx, int sd)
+{
+	struct plugin_rename_context *plugin_ctx;
+	struct tep_event_format *event;
+	struct kshark_data_stream *stream;
+
+// 	printf("#### plugin_update_stream_context %i %p: %p %p\n", sd, plugin_context_handler, plugin_context_handler[0], plugin_context_handler[1]);
+
+	stream = kshark_get_data_stream(kshark_ctx, sd);
+	if (!stream)
+		return false;
+
+	plugin_ctx = malloc(sizeof(*plugin_ctx));
+	printf("plugin_update_stream_context %i  %p\n", sd, plugin_ctx);
+	plugin_ctx->handle = stream->handle;
+	plugin_ctx->pevent = stream->pevent;
+
+	event = tep_find_event_by_name(plugin_ctx->pevent,
+				       "sched", "sched_switch");
+	if (!event)
+		return false;
+
+	plugin_ctx->sched_switch_event = event;
+	plugin_ctx->sched_switch_next_field =
+		tep_find_any_field(event, "next_pid");
+
+	plugin_ctx->sched_switch_comm_field =
+		tep_find_field(event, "next_comm");
+
+	plugin_ctx->done = false;
+	plugin_context_handler[sd] = plugin_ctx;
+	printf("plugin_update_stream_context %i done %p: %p %p\n", sd, plugin_context_handler, plugin_context_handler[0], plugin_context_handler[1]);
+	return true;
+}
+
+static bool plugin_update_context(struct kshark_context *kshark_ctx)
+{
+	int *stream_ids, i;
+
+	stream_ids = kshark_all_streams(kshark_ctx);
+	for (i = 0; i < kshark_ctx->n_streams; ++i) {
+		if (!plugin_update_stream_context(kshark_ctx, stream_ids[i]))
+			goto fail;
+	}
+
+	return true;
+
+ fail:
+	free_plugin_context();
+
+	return false;
+}
+
+static void plugin_kvm_action(struct kshark_context *kshark_ctx,
+			      struct tep_record *rec,
+			      struct kshark_entry *entry)
+{}
+
+static int plugin_get_next_pid(struct tep_record *record, int sd)
+{
+	unsigned long long val;
+	struct plugin_rename_context *plugin_ctx =
+		plugin_context_handler[sd];
+
+	tep_read_number_field(plugin_ctx->sched_switch_next_field,
+			      record->data, &val);
+	return val;
+}
+
+static bool plugin_sched_switch_match_pid(struct kshark_context *kshark_ctx,
+					  struct kshark_entry *e,
+					  int sd, int pid)
+{
+	struct plugin_rename_context *plugin_ctx =
+		plugin_context_handler[e->stream_id];
+	struct tep_record *record = NULL;
+	int switch_pid;
+
+	if (plugin_ctx->sched_switch_event &&
+	    e->stream_id == sd &&
+	    e->event_id == plugin_ctx->sched_switch_event->id) {
+		if (e->event_id == KS_EVENT_OVERFLOW)
+			return false;
+
+		record = kshark_read_at(kshark_ctx, sd, e->offset);
+		if (!record) {
+			printf("%i %i %lu\n", sd, e->event_id, e->offset);
+			return false;
+		}
+
+		switch_pid = plugin_get_next_pid(record, sd);
+		free(record);
+
+		if (switch_pid == pid)
+			return true;
+	}
+
+	return false;
+}
+
+static void kvm_draw_nop(struct kshark_cpp_argv *argv,
+			 int sd, int pid, int draw_action)
+{}
+
+static int plugin_rename_sched_init(struct kshark_context *kshark_ctx, int sd)
+{
+	struct plugin_rename_context *plugin_ctx;
+
+	if (!plugin_update_stream_context(kshark_ctx, sd))
+		return 0;
+
+	plugin_ctx = plugin_context_handler[sd];
+	kshark_register_event_handler(&kshark_ctx->event_handlers,
+				      plugin_ctx->sched_switch_event->id,
+				      sd,
+				      plugin_nop,
+				      plugin_rename);
+
+	return kshark_ctx->n_streams;
+}
+
+static int plugin_rename_sched_close(struct kshark_context *kshark_ctx, int sd)
+{
+	struct plugin_rename_context *plugin_ctx;
+	plugin_ctx = plugin_context_handler[sd];
+	if (!plugin_ctx)
+		return 0;
+
+	kshark_unregister_event_handler(&kshark_ctx->event_handlers,
+					plugin_ctx->sched_switch_event->id,
+					sd,
+					plugin_nop,
+					plugin_rename);
+
+	plugin_close(sd);
+
+	return kshark_ctx->n_streams;
+}
+
+/** Load this plugin. */
+int KSHARK_PLUGIN_INITIALIZER(struct kshark_context *kshark_ctx, int sd)
+{
+	printf("--> rename init %i\n", sd);
+	return plugin_rename_sched_init(kshark_ctx, sd);
+}
+
+/** Unload this plugin. */
+int KSHARK_PLUGIN_DEINITIALIZER(struct kshark_context *kshark_ctx, int sd)
+{
+	printf("<-- rename close %i\n", sd);
+	return plugin_rename_sched_close(kshark_ctx, sd);
+}
+
+#endif
diff --git a/kernel-shark-qt/src/plugins/rename_sched_events.c b/kernel-shark-qt/src/plugins/rename_sched_events.c
index e8a2825..6e15dbb 100644
--- a/kernel-shark-qt/src/plugins/rename_sched_events.c
+++ b/kernel-shark-qt/src/plugins/rename_sched_events.c
@@ -17,7 +17,7 @@
 #include "libkshark-model.h"
 
 /** Structure representing a plugin-specific context. */
-struct plugin_sched_context {
+struct plugin_rename_context {
 	/** Input handle for the trace data file. */
 	struct tracecmd_input	*handle;
 
@@ -37,24 +37,43 @@
 	bool done;
 };
 
-/** Plugin context instance. */
-static struct plugin_sched_context *plugin_sched_context_handler = NULL;
+/** Plugin context instances. */
+static struct plugin_rename_context *
+plugin_context_handler[KS_MAX_NUM_STREAMS] = {NULL};
 
-static bool plugin_sched_update_context(struct kshark_context *kshark_ctx)
+static void plugin_close(int sd)
 {
-	struct plugin_sched_context *plugin_ctx;
+	free(plugin_context_handler[sd]);
+	plugin_context_handler[sd] = NULL;
+}
+
+// static void free_plugin_context()
+// {
+// 	int i;
+// 
+// 	for (i = 0; i < KS_MAX_NUM_STREAMS; ++i)
+// 		plugin_close(i);
+// }
+
+static bool
+plugin_update_stream_context(struct kshark_context *kshark_ctx, int sd)
+{
+	struct plugin_rename_context *plugin_ctx;
 	struct tep_event_format *event;
+	struct kshark_data_stream *stream;
 
-	if (!plugin_sched_context_handler) {
-		plugin_sched_context_handler =
-			malloc(sizeof(*plugin_sched_context_handler));
-	}
+// 	printf("#### plugin_update_stream_context %i %p: %p %p\n", sd, plugin_context_handler, plugin_context_handler[0], plugin_context_handler[1]);
 
-	plugin_ctx = plugin_sched_context_handler;
-	plugin_ctx->handle = kshark_ctx->handle;
-	plugin_ctx->pevent = kshark_ctx->pevent;
+	stream = kshark_get_data_stream(kshark_ctx, sd);
+	if (!stream)
+		return false;
 
-	event = tep_find_event_by_name(plugin_ctx->pevent, 
+	plugin_ctx = malloc(sizeof(*plugin_ctx));
+	printf("plugin_update_stream_context %i  %p\n", sd, plugin_ctx);
+	plugin_ctx->handle = stream->handle;
+	plugin_ctx->pevent = stream->pevent;
+
+	event = tep_find_event_by_name(plugin_ctx->pevent,
 				       "sched", "sched_switch");
 	if (!event)
 		return false;
@@ -67,20 +86,39 @@
 		tep_find_field(event, "next_comm");
 
 	plugin_ctx->done = false;
-
+	plugin_context_handler[sd] = plugin_ctx;
+	printf("plugin_update_stream_context %i done %p: %p %p\n", sd, plugin_context_handler, plugin_context_handler[0], plugin_context_handler[1]);
 	return true;
 }
 
+// static bool plugin_update_context(struct kshark_context *kshark_ctx)
+// {
+// 	int *stream_ids, i;
+// 
+// 	stream_ids = kshark_all_streams(kshark_ctx);
+// 	for (i = 0; i < kshark_ctx->n_streams; ++i) {
+// 		if (!plugin_update_stream_context(kshark_ctx, stream_ids[i]))
+// 			goto fail;
+// 	}
+// 
+// 	return true;
+// 
+//  fail:
+// 	free_plugin_context();
+// 
+// 	return false;
+// }
+
 static void plugin_nop(struct kshark_context *kshark_ctx,
 		       struct tep_record *rec,
 		       struct kshark_entry *entry)
 {}
 
-static int plugin_get_next_pid(struct tep_record *record)
+static int plugin_get_next_pid(struct tep_record *record, int sd)
 {
 	unsigned long long val;
-	struct plugin_sched_context *plugin_ctx =
-		plugin_sched_context_handler;
+	struct plugin_rename_context *plugin_ctx =
+		plugin_context_handler[sd];
 
 	tep_read_number_field(plugin_ctx->sched_switch_next_field,
 			      record->data, &val);
@@ -89,18 +127,26 @@
 
 static bool plugin_sched_switch_match_pid(struct kshark_context *kshark_ctx,
 					  struct kshark_entry *e,
-					  int pid)
+					  int sd, int pid)
 {
-	struct plugin_sched_context *plugin_ctx =
-		plugin_sched_context_handler;
+	struct plugin_rename_context *plugin_ctx =
+		plugin_context_handler[e->stream_id];
 	struct tep_record *record = NULL;
 	int switch_pid;
 
 	if (plugin_ctx->sched_switch_event &&
+	    e->stream_id == sd &&
 	    e->event_id == plugin_ctx->sched_switch_event->id) {
-		record = kshark_read_at(kshark_ctx, e->offset);
+		if (e->event_id == KS_EVENT_OVERFLOW)
+			return false;
 
-		switch_pid = plugin_get_next_pid(record);
+		record = kshark_read_at(kshark_ctx, sd, e->offset);
+		if (!record) {
+			printf("%i %i %lu\n", sd, e->event_id, e->offset);
+			return false;
+		}
+
+		switch_pid = plugin_get_next_pid(record, sd);
 		free(record);
 
 		if (switch_pid == pid)
@@ -111,97 +157,104 @@
 }
 
 static void plugin_rename(struct kshark_cpp_argv *argv,
-			  int pid, int draw_action)
+			  int sd, int pid, int draw_action)
 {
-	struct plugin_sched_context *plugin_ctx =
-		plugin_sched_context_handler;
+	struct plugin_rename_context *plugin_ctx;
 	struct kshark_context *kshark_ctx;
 	const struct kshark_entry *entry;
 	struct kshark_entry_request req;
 	struct tep_record *record;
-	int *pids, n_tasks, r;
+	int *stream_ids, *pids, n_tasks, i, r;
 	const char *comm;
 	ssize_t index;
 
-	if (plugin_ctx->done)
-		return;
-
 	req.first = argv->histo->data_size - 1;
 	req.n = argv->histo->data_size;
 	req.cond = plugin_sched_switch_match_pid;
 	req.vis_only = false;
 
+	printf("@@@ plugin_rename\n");
 	kshark_ctx = NULL;
-	kshark_instance(&kshark_ctx);
-	n_tasks = kshark_get_task_pids(kshark_ctx, &pids);
-	for (r = 0; r < n_tasks; ++r) {
-		req.val = pids[r];
-		entry = kshark_get_entry_back(&req, argv->histo->data, &index);
-		if (!entry)
+	if (!kshark_instance(&kshark_ctx))
+		return;
+
+	stream_ids = kshark_all_streams(kshark_ctx);
+	for (i = 0; i < kshark_ctx->n_streams; ++i) {
+		sd = stream_ids[i];
+		plugin_ctx = plugin_context_handler[sd];
+		if (plugin_ctx->done)
 			continue;
 
-		record = kshark_read_at(kshark_ctx, entry->offset);
-		comm = record->data +
-			       plugin_ctx->sched_switch_comm_field->offset;
+		req.sd = sd;
+		n_tasks = kshark_get_task_pids(kshark_ctx, sd, &pids);
+		for (r = 0; r < n_tasks; ++r) {
+			req.val = pids[r];
+			entry = kshark_get_entry_back(&req, argv->histo->data, &index);
+			if (!entry)
+				continue;
 
-		printf("%li task: %s  pid: %i\n", index, comm, pids[r]);
+// 			record = kshark_read_at(kshark_ctx, sd, entry->offset);
+// 			comm = record->data +
+// 			       plugin_ctx->sched_switch_comm_field->offset;
+
+			printf("%li task: %s  pid: %i\n", index, comm, pids[r]);
+		}
+
+		plugin_ctx->done = true;
+		free(pids);
 	}
-
-	plugin_ctx->done = true;
-
-	free(pids);
 }
 
-
-static int plugin_rename_sched_init(struct kshark_context *kshark_ctx)
+static int plugin_rename_sched_init(struct kshark_context *kshark_ctx, int sd)
 {
-	struct plugin_sched_context *plugin_ctx;
+	struct plugin_rename_context *plugin_ctx;
 
-	if (!plugin_sched_update_context(kshark_ctx)) {
-		free(plugin_sched_context_handler);
-		plugin_sched_context_handler = NULL;
+	if (!plugin_update_stream_context(kshark_ctx, sd))
 		return 0;
-	}
 
-	plugin_ctx = plugin_sched_context_handler;
+	plugin_ctx = plugin_context_handler[sd];
 	kshark_register_event_handler(&kshark_ctx->event_handlers,
 				      plugin_ctx->sched_switch_event->id,
+				      sd,
 				      plugin_nop,
 				      plugin_rename);
 
-	return 1;
+// 	printf("--> rename init %i  %p %p\n", sd, plugin_context_handler[0], plugin_context_handler[1]);
+	return kshark_ctx->n_streams;
 }
 
-static int plugin_rename_sched_close(struct kshark_context *kshark_ctx)
+static int plugin_rename_sched_close(struct kshark_context *kshark_ctx, int sd)
 {
-	struct plugin_sched_context *plugin_ctx;
-
-	if (!plugin_sched_context_handler)
+	struct plugin_rename_context *plugin_ctx;
+// 	printf("--> rename close %i %p %p %p\n", sd, plugin_context_handler, plugin_context_handler[0], plugin_context_handler[1]);
+	plugin_ctx = plugin_context_handler[sd];
+	if (!plugin_ctx)
 		return 0;
 
-	plugin_ctx = plugin_sched_context_handler;
-
 	kshark_unregister_event_handler(&kshark_ctx->event_handlers,
 					plugin_ctx->sched_switch_event->id,
+					sd,
 					plugin_nop,
 					plugin_rename);
 
-	free(plugin_ctx);
-	plugin_sched_context_handler = NULL;
+// 	printf("<-- rename close %i  %p\n", sd, plugin_ctx);
+	plugin_close(sd);
 
-	return 1;
+	return kshark_ctx->n_streams;
 }
 
 /** Load this plugin. */
-int KSHARK_PLUGIN_INITIALIZER(struct kshark_context *kshark_ctx)
+int KSHARK_PLUGIN_INITIALIZER(struct kshark_context *kshark_ctx, int sd)
 {
-	return plugin_rename_sched_init(kshark_ctx);
+	printf("--> rename init %i\n", sd);
+	return plugin_rename_sched_init(kshark_ctx, sd);
 }
 
 /** Unload this plugin. */
-int KSHARK_PLUGIN_DEINITIALIZER(struct kshark_context *kshark_ctx)
+int KSHARK_PLUGIN_DEINITIALIZER(struct kshark_context *kshark_ctx, int sd)
 {
-	return plugin_rename_sched_close(kshark_ctx);
+	printf("<-- rename close %i\n", sd);
+	return plugin_rename_sched_close(kshark_ctx, sd);
 }
 
 #endif
diff --git a/kernel-shark/examples/CMakeLists.txt b/kernel-shark/examples/CMakeLists.txt
index 1af98f6..88fe3a5 100644
--- a/kernel-shark/examples/CMakeLists.txt
+++ b/kernel-shark/examples/CMakeLists.txt
@@ -28,6 +28,6 @@
 add_executable(mload          multiload.c)
 target_link_libraries(mload   kshark)
 
-message(STATUS "multiplot")
-add_executable(mplot          multiplot.cpp)
-target_link_libraries(mplot   kshark-plot)
+# message(STATUS "multiplot")
+# add_executable(mplot          multiplot.cpp)
+# target_link_libraries(mplot   kshark-plot)
diff --git a/kernel-shark/examples/datafilter.c b/kernel-shark/examples/datafilter.c
index 2c81fd3..af6c301 100644
--- a/kernel-shark/examples/datafilter.c
+++ b/kernel-shark/examples/datafilter.c
@@ -64,7 +64,7 @@
 	 * filterd entris in text format.
 	 */
 	kshark_ctx->filter_mask = KS_TEXT_VIEW_FILTER_MASK;
-	kshark_filter_entries(kshark_ctx, sd, data, n_rows);
+	kshark_filter_stream_entries(kshark_ctx, sd, data, n_rows);
 
 	/* Print to the screen the first 10 visible entries. */
 	count = 0;
@@ -94,7 +94,7 @@
 					     event->id);
 	}
 
-	kshark_filter_entries(kshark_ctx, sd, data, n_rows);
+	kshark_filter_stream_entries(kshark_ctx, sd, data, n_rows);
 
 	/* Print to the screen the first 10 visible entries. */
 	count = 0;
diff --git a/kernel-shark/examples/datahisto.c b/kernel-shark/examples/datahisto.c
index c4b8bb5..ebafd4a 100644
--- a/kernel-shark/examples/datahisto.c
+++ b/kernel-shark/examples/datahisto.c
@@ -26,22 +26,22 @@
 	printf("bin %i {\n", bin);
 	if (strcmp(type, "cpu") == 0) {
 		e_front = ksmodel_get_entry_front(histo, bin, true,
-						  kshark_match_cpu, sd, val,
+						  kshark_match_cpu, sd, &val,
 						  NULL,
 						  &i_front);
 
 		e_back = ksmodel_get_entry_back(histo, bin, true,
-						kshark_match_cpu, sd, val,
+						kshark_match_cpu, sd, &val,
 						NULL,
 						&i_back);
 	} else if (strcmp(type, "task") == 0) {
 		e_front = ksmodel_get_entry_front(histo, bin, true,
-						  kshark_match_pid, sd, val,
+						  kshark_match_pid, sd, &val,
 						  NULL,
 						  &i_front);
 
 		e_back = ksmodel_get_entry_back(histo, bin, true,
-						kshark_match_pid, sd, val,
+						kshark_match_pid, sd, &val,
 						NULL,
 						&i_back);
 	} else {
diff --git a/kernel-shark/examples/multiplot.cpp b/kernel-shark/examples/multiplot.cpp
index f166aa5..c782f4a 100644
--- a/kernel-shark/examples/multiplot.cpp
+++ b/kernel-shark/examples/multiplot.cpp
@@ -51,7 +51,6 @@
 		stream = kshark_get_data_stream(kshark_ctx, sd);
 		nCPUs = tep_get_cpus(stream->pevent);
 		for (int cpu = 0; cpu < nCPUs; ++cpu) {
-// 			graph = new KsPlot::Graph(&histos[d], &colors);
 			graph = new KsPlot::Graph(&histo_m, &pidColors,
 							    &pidColors);
 
diff --git a/kernel-shark/src/CMakeLists.txt b/kernel-shark/src/CMakeLists.txt
index faf323f..b7dbd7e 100644
--- a/kernel-shark/src/CMakeLists.txt
+++ b/kernel-shark/src/CMakeLists.txt
@@ -28,71 +28,71 @@
 
 endif (OPENGL_FOUND AND GLUT_FOUND)
 
-#if (Qt5Widgets_FOUND AND Qt5Network_FOUND)
-#
-#    message(STATUS "libkshark-gui")
-#    set (ks-guiLib_hdr  KsUtils.hpp
-#                        KsModels.hpp
-#                        KsGLWidget.hpp
-#                        KsSearchFSM.hpp
-#                        KsDualMarker.hpp
-#                        KsWidgetsLib.hpp
-#                        KsTraceGraph.hpp
-#                        KsTraceViewer.hpp
-#                        KsMainWindow.hpp
-#                        KsCaptureDialog.hpp
-#                        KsQuickContextMenu.hpp
-#                        KsAdvFilteringDialog.hpp)
-#
-#    QT5_WRAP_CPP(ks-guiLib_hdr_moc ${ks-guiLib_hdr})
-#
-#    add_library(kshark-gui  SHARED  ${ks-guiLib_hdr_moc}    KsUtils.cpp
-#                                                            KsModels.cpp
-#                                                            KsSession.cpp
-#                                                            KsGLWidget.cpp
-#                                                            KsSearchFSM.cpp
-#                                                            KsDualMarker.cpp
-#                                                            KsWidgetsLib.cpp
-#                                                            KsTraceGraph.cpp
-#                                                            KsTraceViewer.cpp
-#                                                            KsMainWindow.cpp
-#                                                            KsCaptureDialog.cpp
-#                                                            KsQuickContextMenu.cpp
-#                                                            KsAdvFilteringDialog.cpp)
-#
-#    target_link_libraries(kshark-gui kshark-plot
-#                                     ${CMAKE_DL_LIBS}
-#                                     ${TRACEEVENT_LIBRARY}
-#                                     ${TRACECMD_LIBRARY}
-#                                     Qt5::Widgets
-#                                     Qt5::Network)
-#
-#    set_target_properties(kshark-gui PROPERTIES  SUFFIX ".so.${KS_VERSION_STRING}")
-#
-#    message(STATUS "kernelshark")
-#    add_executable(kernelshark          kernelshark.cpp)
-#    target_link_libraries(kernelshark   kshark-gui)
-#
-#    message(STATUS "kshark-record")
-#    add_executable(kshark-record        kshark-record.cpp)
-#    target_link_libraries(kshark-record kshark-gui)
-#
-#    install(TARGETS kernelshark kshark-record kshark kshark-plot kshark-gui
-#            RUNTIME DESTINATION ${_INSTALL_PREFIX}/bin/
-#            LIBRARY DESTINATION ${_INSTALL_PREFIX}/lib/kshark/)
-#
-#    install(FILES "${KS_DIR}/kernelshark.desktop"
-#            DESTINATION /usr/share/applications/)
-#
-#    install(FILES "${KS_DIR}/org.freedesktop.kshark-record.policy"
-#            DESTINATION /usr/share/polkit-1/actions/)
-#
-#    install(PROGRAMS "${KS_DIR}/bin/kshark-su-record"
-#            DESTINATION ${_INSTALL_PREFIX}/bin/)
-#
-#endif (Qt5Widgets_FOUND AND Qt5Network_FOUND)
-#
-#add_subdirectory(plugins)
+if (Qt5Widgets_FOUND AND Qt5Network_FOUND)
+
+    message(STATUS "libkshark-gui")
+    set (ks-guiLib_hdr  KsUtils.hpp
+                        KsModels.hpp
+                        KsGLWidget.hpp
+                        KsSearchFSM.hpp
+                        KsDualMarker.hpp
+                        KsWidgetsLib.hpp
+                        KsTraceGraph.hpp
+                        KsTraceViewer.hpp
+                        KsMainWindow.hpp
+                        KsCaptureDialog.hpp
+                        KsQuickContextMenu.hpp
+                        KsAdvFilteringDialog.hpp)
+
+    QT5_WRAP_CPP(ks-guiLib_hdr_moc ${ks-guiLib_hdr})
+
+    add_library(kshark-gui  SHARED  ${ks-guiLib_hdr_moc}    KsUtils.cpp
+                                                            KsModels.cpp
+                                                            KsSession.cpp
+                                                            KsGLWidget.cpp
+                                                            KsSearchFSM.cpp
+                                                            KsDualMarker.cpp
+                                                            KsWidgetsLib.cpp
+                                                            KsTraceGraph.cpp
+                                                            KsTraceViewer.cpp
+                                                            KsMainWindow.cpp
+                                                            KsCaptureDialog.cpp
+                                                            KsQuickContextMenu.cpp
+                                                            KsAdvFilteringDialog.cpp)
+
+    target_link_libraries(kshark-gui kshark-plot
+                                     ${CMAKE_DL_LIBS}
+                                     ${TRACEEVENT_LIBRARY}
+                                     ${TRACECMD_LIBRARY}
+                                     Qt5::Widgets
+                                     Qt5::Network)
+
+    set_target_properties(kshark-gui PROPERTIES  SUFFIX ".so.${KS_VERSION_STRING}")
+
+    message(STATUS "kernelshark")
+    add_executable(kernelshark          kernelshark.cpp)
+    target_link_libraries(kernelshark   kshark-gui)
+
+    message(STATUS "kshark-record")
+    add_executable(kshark-record        kshark-record.cpp)
+    target_link_libraries(kshark-record kshark-gui)
+
+    install(TARGETS kernelshark kshark-record kshark kshark-plot kshark-gui
+            RUNTIME DESTINATION ${_INSTALL_PREFIX}/bin/
+            LIBRARY DESTINATION ${_INSTALL_PREFIX}/lib/kshark/)
+
+    install(FILES "${KS_DIR}/kernelshark.desktop"
+            DESTINATION /usr/share/applications/)
+
+    install(FILES "${KS_DIR}/org.freedesktop.kshark-record.policy"
+            DESTINATION /usr/share/polkit-1/actions/)
+
+    install(PROGRAMS "${KS_DIR}/bin/kshark-su-record"
+            DESTINATION ${_INSTALL_PREFIX}/bin/)
+
+endif (Qt5Widgets_FOUND AND Qt5Network_FOUND)
+
+add_subdirectory(plugins)
 
 find_program(DO_AS_ROOT pkexec)
 
diff --git a/kernel-shark/src/KsAdvFilteringDialog.cpp b/kernel-shark/src/KsAdvFilteringDialog.cpp
index 1edf68b..f657efe 100644
--- a/kernel-shark/src/KsAdvFilteringDialog.cpp
+++ b/kernel-shark/src/KsAdvFilteringDialog.cpp
@@ -24,6 +24,7 @@
   _sysEvLabel("System/Event: ", &_condToolBar1),
   _opsLabel("Operator: ", this),
   _fieldLabel("Field: ", this),
+  _streamComboBox(&_condToolBar1),
   _systemComboBox(&_condToolBar1),
   _eventComboBox(&_condToolBar1),
   _opsComboBox(&_condToolBar2),
@@ -71,13 +72,14 @@
 
 	lamAddLine();
 
-	_getFilters(kshark_ctx);
+	_getFilters();
 
 	if (_filters.count()) {
-		_makeFilterTable(kshark_ctx);
+		_makeFilterTable();
 		lamAddLine();
 	}
 
+	_condToolBar1.addWidget(&_streamComboBox);
 	_condToolBar1.addWidget(&_sysEvLabel);
 	_condToolBar1.addWidget(&_systemComboBox);
 	_condToolBar1.addWidget(&_eventComboBox);
@@ -86,13 +88,16 @@
 	 * Using the old Signal-Slot syntax because QComboBox::currentIndexChanged
 	 * has overloads.
 	 */
+	connect(&_streamComboBox,	SIGNAL(currentIndexChanged(const QString&)),
+		this,			SLOT(_streamChanged(const QString&)));
+
 	connect(&_systemComboBox,	SIGNAL(currentIndexChanged(const QString&)),
 		this,			SLOT(_systemChanged(const QString&)));
 
 	connect(&_eventComboBox,	SIGNAL(currentIndexChanged(const QString&)),
 		this,			SLOT(_eventChanged(const QString&)));
 
-	_setSystemCombo(kshark_ctx);
+	_setSystemCombo();
 
 	_condToolBar1.addSeparator();
 	_condToolBar1.addWidget(&_insertEvtButton);
@@ -149,15 +154,15 @@
 		this,			&QWidget::close);
 }
 
-void KsAdvFilteringDialog::_setSystemCombo(struct kshark_context *kshark_ctx)
+void KsAdvFilteringDialog::_setSystemCombo()
 {
 	QStringList sysList;
 	tep_event **events;
 	int i(0), nEvts(0);
 
-	if (kshark_ctx->pevent) {
-		nEvts = tep_get_events_count(kshark_ctx->pevent);
-		events = tep_list_events(kshark_ctx->pevent,
+	if (_stream->pevent) {
+		nEvts = tep_get_events_count(_stream->pevent);
+		events = tep_list_events(_stream->pevent,
 					 TEP_EVENT_SORT_SYSTEM);
 	}
 
@@ -202,15 +207,15 @@
 	return OpsList;
 }
 
-void KsAdvFilteringDialog::_getFilters(struct kshark_context *kshark_ctx)
+void KsAdvFilteringDialog::_getFilters()
 {
 	tep_event **events;
 	char *str;
 
-	events = tep_list_events(kshark_ctx->pevent, TEP_EVENT_SORT_SYSTEM);
+	events = tep_list_events(_stream->pevent, TEP_EVENT_SORT_SYSTEM);
 
 	for (int i = 0; events[i]; i++) {
-		str = tep_filter_make_string(kshark_ctx->advanced_event_filter,
+		str = tep_filter_make_string(_stream->advanced_event_filter,
 					     events[i]->id);
 		if (!str)
 			continue;
@@ -223,7 +228,7 @@
 	}
 }
 
-void KsAdvFilteringDialog::_makeFilterTable(struct kshark_context *kshark_ctx)
+void KsAdvFilteringDialog::_makeFilterTable()
 {
 	QMapIterator<int, QString> f(_filters);
 	QTableWidgetItem *i1, *i2, *i3;
@@ -272,19 +277,20 @@
 	}
 }
 
+void KsAdvFilteringDialog::_streamChanged(const QString &sysName)
+{
+	
+}
+
 void KsAdvFilteringDialog::_systemChanged(const QString &sysName)
 {
-	kshark_context *kshark_ctx(NULL);
 	QStringList evtsList;
 	tep_event **events;
 	int i, nEvts;
 
 	_eventComboBox.clear();
-	if (!kshark_instance(&kshark_ctx) || !kshark_ctx->pevent)
-		return;
-
-	nEvts = tep_get_events_count(kshark_ctx->pevent);
-	events = tep_list_events(kshark_ctx->pevent, TEP_EVENT_SORT_SYSTEM);
+	nEvts = tep_get_events_count(_stream->pevent);
+	events = tep_list_events(_stream->pevent, TEP_EVENT_SORT_SYSTEM);
 
 	for (i = 0; i < nEvts; ++i) {
 		if (sysName == events[i]->system)
@@ -317,17 +323,13 @@
 void KsAdvFilteringDialog::_eventChanged(const QString &evtName)
 {
 	QString sysName = _systemComboBox.currentText();
-	kshark_context *kshark_ctx(NULL);
 	QStringList fieldList;
 	tep_event **events;
 	int nEvts;
 
 	_fieldComboBox.clear();
-	if (!kshark_instance(&kshark_ctx) || !kshark_ctx->pevent)
-		return;
-
-	nEvts = tep_get_events_count(kshark_ctx->pevent);
-	events = tep_list_events(kshark_ctx->pevent, TEP_EVENT_SORT_SYSTEM);
+	nEvts = tep_get_events_count(_stream->pevent);
+	events = tep_list_events(_stream->pevent, TEP_EVENT_SORT_SYSTEM);
 
 	for (int i = 0; i < nEvts; ++i) {
 		if (evtName == events[i]->name &&
@@ -383,19 +385,15 @@
 void KsAdvFilteringDialog::_applyPress()
 {
 	QMapIterator<int, QString> f(_filters);
-	kshark_context *kshark_ctx(NULL);
 	const char *text;
 	tep_errno ret;
 	char *filter;
 	int i(0);
 
-	if (!kshark_instance(&kshark_ctx))
-		return;
-
 	while (f.hasNext()) {
 		f.next();
 		if (_table->_cb[i]->checkState() == Qt::Checked) {
-			tep_filter_remove_event(kshark_ctx->advanced_event_filter,
+			tep_filter_remove_event(_stream->advanced_event_filter,
 						f.key());
 		}
 		++i;
@@ -419,14 +417,14 @@
 	filter = (char*) malloc(strlen(text) + 1);
 	strcpy(filter, text);
 
-	ret = tep_filter_add_filter_str(kshark_ctx->advanced_event_filter,
-					   filter);
+	ret = tep_filter_add_filter_str(_stream->advanced_event_filter,
+					filter);
 
 	if (ret < 0) {
 		char error_str[200];
 
-		tep_strerror(kshark_ctx->pevent, ret, error_str,
-						       sizeof(error_str));
+		tep_strerror(_stream->pevent, ret, error_str,
+						   sizeof(error_str));
 
 		fprintf(stderr, "filter failed due to: %s\n", error_str);
 		free(filter);
diff --git a/kernel-shark/src/KsAdvFilteringDialog.hpp b/kernel-shark/src/KsAdvFilteringDialog.hpp
index 2a534d0..647c0e9 100644
--- a/kernel-shark/src/KsAdvFilteringDialog.hpp
+++ b/kernel-shark/src/KsAdvFilteringDialog.hpp
@@ -32,6 +32,8 @@
 	void dataReload();
 
 private:
+	kshark_data_stream 	*_stream;
+
 	int 			_noHelpHeight;
 
 	QMap<int, QString>	_filters;
@@ -46,7 +48,7 @@
 
 	QLabel		_descrLabel, _sysEvLabel, _opsLabel, _fieldLabel;
 
-	QComboBox	_systemComboBox, _eventComboBox;
+	QComboBox	_streamComboBox, _systemComboBox, _eventComboBox;
 
 	QComboBox	_opsComboBox, _fieldComboBox;
 
@@ -74,15 +76,17 @@
 
 	QStringList _operators();
 
-	void _getFilters(struct kshark_context *kshark_ctx);
+	void _getFilters();
 
-	void _makeFilterTable(struct kshark_context *kshark_ctx);
+	void _makeFilterTable();
 
 	QStringList _getEventFormatFields(struct tep_event *event);
 
-	void _setSystemCombo(struct kshark_context *kshark_ctx);
+	void _setSystemCombo();
 
 private slots:
+	void _streamChanged(const QString&);
+
 	void _systemChanged(const QString&);
 
 	void _eventChanged(const QString&);
diff --git a/kernel-shark/src/KsCaptureDialog.hpp b/kernel-shark/src/KsCaptureDialog.hpp
index d65f475..a0a0cb9 100644
--- a/kernel-shark/src/KsCaptureDialog.hpp
+++ b/kernel-shark/src/KsCaptureDialog.hpp
@@ -16,6 +16,7 @@
 #include <QtWidgets>
 
 // KernelShark
+#include "libkshark.h"
 #include "KsWidgetsLib.hpp"
 
 /**
diff --git a/kernel-shark/src/KsDualMarker.cpp b/kernel-shark/src/KsDualMarker.cpp
index 5dcbaae..8f5cc08 100644
--- a/kernel-shark/src/KsDualMarker.cpp
+++ b/kernel-shark/src/KsDualMarker.cpp
@@ -59,8 +59,8 @@
 {
 	_isSet = false;
 	_bin = -1;
-	_cpu = -1;
-	_task = -1;
+// 	_cpu = -1;
+// 	_task = -1;
 	_pos = 0;
 
 	_mark._visible = false;
@@ -77,13 +77,15 @@
  */
 bool KsGraphMark::set(const KsDataStore &data,
 		      kshark_trace_histo *histo,
-		      size_t pos, int cpuGraph, int taskGraph)
+		      size_t pos, int sd/*,
+		      int cpuGraph, int taskGraph*/)
 {
 	_isSet = true;
 	_pos = pos;
+	_sd = sd;
 	_ts = data.rows()[_pos]->ts;
-	_cpu = cpuGraph;
-	_task = taskGraph;
+// 	_cpu = cpuGraph;
+// 	_task = taskGraph;
 
 	if (_ts > histo->max || _ts < histo->min) {
 		_bin = -1;
@@ -109,7 +111,7 @@
 	if (!_isSet)
 		return false;
 
-	return set(data, histo, this->_pos, this->_cpu, this->_task);
+	return set(data, histo, this->_pos, this->_sd/*, this->_cpu, this->_task*/);
 }
 
 /** Unset the Marker and make it invisible. */
@@ -315,10 +317,10 @@
 				   KsGLWidget *glw)
 {
 	if(_markA.update(data, glw->model()->histo()))
-		glw->setMark(&_markA);
+		glw->setMarkPoints(data, &_markA);
 
 	if(_markB.update(data, glw->model()->histo()))
-		glw->setMark(&_markB);
+		glw->setMarkPoints(data, &_markB);
 
 	updateLabels();
 }
diff --git a/kernel-shark/src/KsDualMarker.hpp b/kernel-shark/src/KsDualMarker.hpp
index 597bddb..1040c8c 100644
--- a/kernel-shark/src/KsDualMarker.hpp
+++ b/kernel-shark/src/KsDualMarker.hpp
@@ -66,9 +66,8 @@
 
 	bool set(const KsDataStore &data,
 		 kshark_trace_histo *histo,
-		 size_t pos,
-		 int cpuGraph,
-		 int taskGraph);
+		 size_t pos, int sd/*,
+		 int cpuGraph, int taskGraph*/);
 
 	bool update(const KsDataStore &data, kshark_trace_histo *histo);
 
@@ -83,18 +82,14 @@
 
 	void remove();
 
-public:
 	/** Is this marker set. */
 	bool		_isSet;
 
 	/** The number of the bin this marker points to. */
 	int		_bin;
 
-	/** The index of the CPU Graph this marker points to. */
-	int		_cpu;
-
-	/** The  index of the Task Graph this marker points to. */
-	int		_task;
+	/** Data stream identifier of the Graph this marker points to. */
+	int		_sd;
 
 	/** The index inside the data array this marker points to. */
 	size_t		_pos;
diff --git a/kernel-shark/src/KsGLWidget.cpp b/kernel-shark/src/KsGLWidget.cpp
index 789514a..f0b1c8f 100644
--- a/kernel-shark/src/KsGLWidget.cpp
+++ b/kernel-shark/src/KsGLWidget.cpp
@@ -22,6 +22,7 @@
 /** Create a default (empty) OpenGL widget. */
 KsGLWidget::KsGLWidget(QWidget *parent)
 : QOpenGLWidget(parent),
+//   _sd(sd),
   _hMargin(20),
   _vMargin(30),
   _vSpacing(20),
@@ -91,12 +92,17 @@
 		_drawAxisX();
 
 	/* Process and draw all graphs by using the built-in logic. */
-	_makeGraphs(_cpuList, _taskList);
-	for (auto const &g: _graphs)
+	_makeGraphs();
+
+	for (auto const &stream: _graphs)
+		for (auto const &g: stream)
+			g->draw(1.5 * _dpr);
+
+	for (auto const &g: _comboGraphs)
 		g->draw(1.5 * _dpr);
 
 	/* Process and draw all plugin-specific shapes. */
-	_makePluginShapes(_cpuList, _taskList);
+	_makePluginShapes();
 	while (!_shapes.empty()) {
 		auto s = _shapes.front();
 		s->draw();
@@ -116,8 +122,8 @@
 /** Reset (empty) the widget. */
 void KsGLWidget::reset()
 {
-	_cpuList = {};
-	_taskList = {};
+	_streamPlots.clear();
+	_streamPlots.clear();
 	_data = nullptr;
 	_model.reset();
 }
@@ -132,7 +138,7 @@
 }
 
 int KsGLWidget::_getLastTask(struct kshark_trace_histo *histo,
-			     int bin, int cpu)
+			     int bin, int sd, int cpu)
 {
 	kshark_context *kshark_ctx(nullptr);
 	kshark_entry_collection *col;
@@ -143,15 +149,17 @@
 
 	col = kshark_find_data_collection(kshark_ctx->collections,
 					  KsUtils::matchCPUVisible,
-					  cpu);
+					  sd, &cpu, 1);
 
 	for (int b = bin; b >= 0; --b) {
-		pid = ksmodel_get_pid_back(histo, b, cpu, false, col, nullptr);
+		pid = ksmodel_get_pid_back(histo, b, sd, cpu,
+					   false, col, nullptr);
 		if (pid >= 0)
 			return pid;
 	}
 
 	return ksmodel_get_pid_back(histo, LOWER_OVERFLOW_BIN,
+					   sd,
 					   cpu,
 					   false,
 					   col,
@@ -159,7 +167,7 @@
 }
 
 int KsGLWidget::_getLastCPU(struct kshark_trace_histo *histo,
-			     int bin, int pid)
+			    int bin, int sd, int pid)
 {
 	kshark_context *kshark_ctx(nullptr);
 	kshark_entry_collection *col;
@@ -170,15 +178,17 @@
 
 	col = kshark_find_data_collection(kshark_ctx->collections,
 					  kshark_match_pid,
-					  pid);
+					  sd, &pid, 1);
 
 	for (int b = bin; b >= 0; --b) {
-		cpu = ksmodel_get_cpu_back(histo, b, pid, false, col, nullptr);
+		cpu = ksmodel_get_cpu_back(histo, b, sd, pid,
+					   false, col, nullptr);
 		if (cpu >= 0)
 			return cpu;
 	}
 
 	return ksmodel_get_cpu_back(histo, LOWER_OVERFLOW_BIN,
+					   sd,
 					   pid,
 					   false,
 					   col,
@@ -189,7 +199,7 @@
 /** Reimplemented event handler used to receive mouse move events. */
 void KsGLWidget::mouseMoveEvent(QMouseEvent *event)
 {
-	int bin, cpu, pid;
+	int bin, sd, cpu, pid;
 	size_t row;
 	bool ret;
 
@@ -197,22 +207,21 @@
 		_rangeBoundStretched(_posInRange(event->pos().x()));
 
 	bin = event->pos().x() - _hMargin;
-	cpu = getPlotCPU(event->pos());
-	pid = getPlotPid(event->pos());
+	getPlotInfo(event->pos(), &sd, &cpu, &pid);
 
-	ret = _find(bin, cpu, pid, 5, false, &row);
+	ret = _find(bin, sd, cpu, pid, 5, false, &row);
 	if (ret) {
 		emit found(row);
 	} else {
 		if (cpu >= 0) {
-			pid = _getLastTask(_model.histo(), bin, cpu);
+			pid = _getLastTask(_model.histo(), bin, sd, cpu);
 		}
 
 		if (pid > 0) {
-			cpu = _getLastCPU(_model.histo(), bin, pid);
+			cpu = _getLastCPU(_model.histo(), bin, sd, pid);
 		}
 
-		emit notFound(ksmodel_bin_ts(_model.histo(), bin), cpu, pid);
+		emit notFound(ksmodel_bin_ts(_model.histo(), bin), sd, cpu, pid);
 	}
 }
 
@@ -325,9 +334,15 @@
  */
 void KsGLWidget::loadData(KsDataStore *data)
 {
+	kshark_context *kshark_ctx(nullptr);
+	QVector<int> plotVec;
 	uint64_t tMin, tMax;
+	int *streamIds, sd;
 	int nCPUs, nBins;
 
+	if (!kshark_instance(&kshark_ctx) || !kshark_ctx->n_streams)
+		return;
+
 	_data = data;
 
 	/*
@@ -335,26 +350,36 @@
 	 * One bin will correspond to one pixel.
 	 */
 	nBins = width() - _hMargin * 2;
-	nCPUs = tep_get_cpus(_data->tep());
-
 	_model.reset();
 
+	if (!_data->size())
+		return;
+
 	/* Now load the entire set of trace data. */
 	tMin = _data->rows()[0]->ts;
 	tMax = _data->rows()[_data->size() - 1]->ts;
 	ksmodel_set_bining(_model.histo(), nBins, tMin, tMax);
 	_model.fill(_data->rows(), _data->size());
 
-	/* Make a default CPU list. All CPUs will be plotted. */
-	_cpuList = {};
-	for (int i = 0; i < nCPUs; ++i)
-		_cpuList.append(i);
+	_streamPlots.clear();
+	/*
+	 * Make a default CPU  Taski lists. All CPUs from all Data streams will
+	 * be plotted. No tasks will be plotted.
+	 */
+	streamIds = kshark_all_streams(kshark_ctx);
+	for (int i = 0; i < kshark_ctx->n_streams; ++i) {
+		sd = streamIds[i];
+		nCPUs = tep_get_cpus(_data->tep(sd));
+		plotVec.clear();
+		for (int i = 0; i < nCPUs; ++i)
+			plotVec.append(i);
 
-	/* Make a default task list. No tasks will be plotted. */
-	_taskList = {};
+		_streamPlots[sd]._cpuList = plotVec;
+		_streamPlots[sd]._taskList = {};
+	}
 
 	loadColors();
-	_makeGraphs(_cpuList, _taskList);
+	_makeGraphs();
 }
 
 /**
@@ -373,85 +398,49 @@
  * Position the graphical elements of the marker according to the current
  * position of the graphs inside the GL widget.
  */
-void KsGLWidget::setMark(KsGraphMark *mark)
+void KsGLWidget::setMarkPoints(const KsDataStore &data, KsGraphMark *mark)
 {
+	const kshark_entry *e = data.rows()[mark->_pos];
+	int sd = e->stream_id;
+
 	mark->_mark.setDPR(_dpr);
 	mark->_mark.setX(mark->_bin + _hMargin);
 	mark->_mark.setY(_vMargin / 2 + 2, height() - _vMargin);
 
-	if (mark->_cpu >= 0) {
-		mark->_mark.setCPUY(_graphs[mark->_cpu]->getBase());
-		mark->_mark.setCPUVisible(true);
-	} else {
-		mark->_mark.setCPUVisible(false);
-	}
+	mark->_mark.setCPUVisible(false);
+	mark->_mark.setTaskVisible(false);
+	mark->_mark.setComboVisible(false);
 
-	if (mark->_task >= 0) {
-		mark->_mark.setTaskY(_graphs[mark->_task]->getBase());
-		mark->_mark.setTaskVisible(true);
-	} else {
-		mark->_mark.setTaskVisible(false);
-	}
-}
-
-/**
- * @brief Check if a given KernelShark entry is ploted.
- *
- * @param e: Input location for the KernelShark entry.
- * @param graphCPU: Output location for index of the CPU graph to which this
- *		    entry belongs. If such a graph does not exist the outputted
- *		    value is "-1".
- * @param graphTask: Output location for index of the Task graph to which this
- *		     entry belongs. If such a graph does not exist the
- *		     outputted value is "-1".
- */
-void KsGLWidget::findGraphIds(const kshark_entry &e,
-			      int *graphCPU,
-			      int *graphTask)
-{
-	int graph(0);
-	bool cpuFound(false), taskFound(false);
-
-	/*
-	 * Loop over all CPU graphs and try to find the one that
-	 * contains the entry.
-	 */
-	for (auto const &c: _cpuList) {
-		if (c == e.cpu) {
-			cpuFound = true;
-			break;
+	for (int i = 0; i < _streamPlots[sd]._cpuList.count(); ++i) {
+		if (_streamPlots[sd]._cpuList[i] == e->cpu) {
+			mark->_mark.setCPUY(_streamPlots[sd]._cpuPlotBase[i]);
+			mark->_mark.setCPUVisible(true);
 		}
-		++graph;
 	}
 
-	if (cpuFound)
-		*graphCPU = graph;
-	else
-		*graphCPU = -1;
-
-	/*
-	 * Loop over all Task graphs and try to find the one that
-	 * contains the entry.
-	 */
-	graph = _cpuList.count();
-	for (auto const &p: _taskList) {
-		if (p == e.pid) {
-			taskFound = true;
-			break;
+	for (int i = 0; i < _streamPlots[sd]._taskList.count(); ++i) {
+		if (_streamPlots[sd]._taskList[i] == e->pid) {
+			mark->_mark.setTaskY(_streamPlots[sd]._taskPlotBase[i]);
+			mark->_mark.setTaskVisible(true);
 		}
-		++graph;
 	}
 
-	if (taskFound)
-		*graphTask = graph;
-	else
-		*graphTask = -1;
+	for (auto const &c: _comboPlots) {
+		if (c._guestStreamId == e->stream_id && c._vcpu == e->cpu) {
+			mark->_mark.setComboY(c._vcpuBase);
+			mark->_mark.setComboVisible(true);
+		} else if (c._hostStreamId == e->stream_id &&
+			   c._hostPid == e->pid) {
+			mark->_mark.setComboY(c._hostBase);
+			mark->_mark.setComboVisible(true);
+		}
+	}
 }
 
 void KsGLWidget::_drawAxisX()
 {
 	KsPlot::Point a0(_hMargin, _vMargin / 4), a1(_hMargin, _vMargin / 2);
-	KsPlot::Point b0(width()/2, _vMargin / 4), b1(width() / 2, _vMargin / 2);
+	KsPlot::Point b0(width() / 2, _vMargin / 4), b1(width() / 2, _vMargin / 2);
 	KsPlot::Point c0(width() - _hMargin, _vMargin / 4),
 			 c1(width() - _hMargin, _vMargin / 2);
 	int lineSize = 2 * _dpr;
@@ -466,42 +455,68 @@
 	KsPlot::drawLine(a0, c0, {}, lineSize);
 }
 
-void KsGLWidget::_makeGraphs(QVector<int> cpuList, QVector<int> taskList)
+void KsGLWidget::_makeGraphs()
 {
+	int base(_vMargin + KS_GRAPH_HEIGHT);
+
 	/* The very first thing to do is to clean up. */
-	for (auto &g: _graphs)
+	for (auto &stream: _graphs) {
+		for (auto &g: stream)
+			delete g;
+		stream.resize(0);
+	}
+
+	for (auto &g: _comboGraphs)
 		delete g;
-	_graphs.resize(0);
+	_comboGraphs.resize(0);
 
 	if (!_data || !_data->size())
 		return;
 
-	auto lamAddGraph = [&](KsPlot::Graph *graph) {
+	auto lamAddGraph = [&](int sd, KsPlot::Graph *graph) {
 		/*
-		* Calculate the base level of the CPU graph inside the widget.
-		* Remember that the "Y" coordinate is inverted.
-		*/
+		 * Calculate the base level of the CPU graph inside the widget.
+		 * Remember that the "Y" coordinate is inverted.
+		 */
 		if (!graph)
 			return;
 
-		int base = _vMargin +
-			   _vSpacing * _graphs.count() +
-			   KS_GRAPH_HEIGHT * (_graphs.count() + 1);
-
 		graph->setBase(base);
-		_graphs.append(graph);
+
+		_graphs[sd].append(graph);
+		base += graph->height() + _vSpacing;
 	};
 
-	/* Create CPU graphs according to the cpuList. */
-	for (auto const &cpu: cpuList)
-		lamAddGraph(_newCPUGraph(cpu));
+	for (auto it = _streamPlots.begin(); it != _streamPlots.end(); ++it) {
+		int sd = it.key();
+		/* Create CPU graphs according to the cpuList. */
+		it.value()._cpuPlotBase = {};
+		for (auto const &cpu: it.value()._cpuList) {
+			it.value()._cpuPlotBase.append(base);
+			lamAddGraph(sd, _newCPUGraph(sd, cpu));
+		}
+		/* Create Task graphs taskList to the taskList. */
+		it.value()._taskPlotBase = {};
+		for (auto const &pid: it.value()._taskList) {
+			it.value()._taskPlotBase.append(base);
+			lamAddGraph(sd, _newTaskGraph(sd, pid));
+		}
+	}
 
-	/* Create Task graphs taskList to the taskList. */
-	for (auto const &pid: taskList)
-		lamAddGraph(_newTaskGraph(pid));
+	for (auto &cp: _comboPlots) {
+		KsPlot::ComboGraph *graph = _newComboGraph(cp._hostStreamId,
+							   cp._hostPid,
+							   cp._guestStreamId,
+							   cp._vcpu);
+		graph->setBase(base);
+		_comboGraphs.append(graph);
+		cp._vcpuBase = base;
+		cp._hostBase = base + graph->height() / 2;
+		base += graph->height() + _vSpacing;
+	}
 }
 
-void KsGLWidget::_makePluginShapes(QVector<int> cpuList, QVector<int> taskList)
+void KsGLWidget::_makePluginShapes()
 {
 	kshark_context *kshark_ctx(nullptr);
 	kshark_event_handler *evt_handlers;
@@ -513,32 +528,37 @@
 	cppArgv._histo = _model.histo();
 	cppArgv._shapes = &_shapes;
 
-	for (int g = 0; g < cpuList.count(); ++g) {
-		cppArgv._graph = _graphs[g];
-		evt_handlers = kshark_ctx->event_handlers;
-		while (evt_handlers) {
-			evt_handlers->draw_func(cppArgv.toC(),
-						cpuList[g],
-						KSHARK_PLUGIN_CPU_DRAW);
+	for (auto it = _streamPlots.constBegin(); it != _streamPlots.constEnd(); ++it) {
+		int sd = it.key();
+		for (int g = 0; g < it.value()._cpuList.count(); ++g) {
+			cppArgv._graph = _graphs[it.key()][g];
+			evt_handlers = kshark_ctx->event_handlers;
+			while (evt_handlers) {
+				evt_handlers->draw_func(cppArgv.toC(),
+							sd,
+							it.value()._cpuList[g],
+							KSHARK_PLUGIN_CPU_DRAW);
 
-			evt_handlers = evt_handlers->next;
+				evt_handlers = evt_handlers->next;
+			}
 		}
-	}
 
-	for (int g = 0; g < taskList.count(); ++g) {
-		cppArgv._graph = _graphs[cpuList.count() + g];
-		evt_handlers = kshark_ctx->event_handlers;
-		while (evt_handlers) {
-			evt_handlers->draw_func(cppArgv.toC(),
-						taskList[g],
-						KSHARK_PLUGIN_TASK_DRAW);
+		for (int g = 0; g < it.value()._taskList.count(); ++g) {
+			cppArgv._graph = _graphs[it.key()][it.value()._cpuList.count() + g];
+			evt_handlers = kshark_ctx->event_handlers;
+			while (evt_handlers) {
+				evt_handlers->draw_func(cppArgv.toC(),
+							sd,
+							it.value()._taskList[g],
+							KSHARK_PLUGIN_TASK_DRAW);
 
-			evt_handlers = evt_handlers->next;
+				evt_handlers = evt_handlers->next;
+			}
 		}
 	}
 }
 
-KsPlot::Graph *KsGLWidget::_newCPUGraph(int cpu)
+KsPlot::Graph *KsGLWidget::_newCPUGraph(int sd, int cpu)
 {
 	/* The CPU graph needs to know only the colors of the tasks. */
 	KsPlot::Graph *graph = new KsPlot::Graph(_model.histo(),
@@ -557,15 +577,15 @@
 
 	col = kshark_find_data_collection(kshark_ctx->collections,
 					  KsUtils::matchCPUVisible,
-					  cpu);
+					  sd, &cpu, 1);
 
 	graph->setDataCollectionPtr(col);
-	graph->fillCPUGraph(cpu);
+	graph->fillCPUGraph(sd, cpu);
 
 	return graph;
 }
 
-KsPlot::Graph *KsGLWidget::_newTaskGraph(int pid)
+KsPlot::Graph *KsGLWidget::_newTaskGraph(int sd, int pid)
 {
 	/*
 	 * The Task graph needs to know the colors of the tasks and the colors
@@ -584,7 +604,8 @@
 	graph->setHeight(KS_GRAPH_HEIGHT);
 
 	col = kshark_find_data_collection(kshark_ctx->collections,
-					  kshark_match_pid, pid);
+					  kshark_match_pid, sd, &pid, 1);
+
 	if (!col) {
 		/*
 		 * If a data collection for this task does not exist,
@@ -593,7 +614,8 @@
 		col = kshark_register_data_collection(kshark_ctx,
 						      _data->rows(),
 						      _data->size(),
-						      kshark_match_pid, pid,
+						      kshark_match_pid,
+						      sd, &pid, 1,
 						      25);
 	}
 
@@ -616,7 +638,54 @@
 	}
 
 	graph->setDataCollectionPtr(col);
-	graph->fillTaskGraph(pid);
+	graph->fillTaskGraph(sd, pid);
+
+	return graph;
+}
+
+KsPlot::ComboGraph *KsGLWidget::_newComboGraph(int sdHost, int pidHost, int sdGuest, int vcpu)
+{
+	/*
+	 * The Combo graph needs to know the colors of the tasks and the colors
+	 * of the CPUs.
+	 */
+	KsPlot::ComboGraph *graph =
+		new KsPlot::ComboGraph(_model.histo(), &_pidColors,
+						       &_cpuColors);
+	kshark_context *kshark_ctx(nullptr);
+	kshark_entry_collection *col;
+
+	if (!kshark_instance(&kshark_ctx))
+		return nullptr;
+
+	graph->setHMargin(_hMargin);
+
+	/* The Combo graph is two times taller than the normal graph. */
+	graph->setHeight(2 * KS_GRAPH_HEIGHT);
+
+	col = kshark_find_data_collection(kshark_ctx->collections,
+					  KsUtils::matchCPUVisible,
+					  sdGuest, &vcpu, 1);
+	graph->setGuestDataCollectionPtr(col);
+
+	col = kshark_find_data_collection(kshark_ctx->collections,
+					  kshark_match_pid,
+					  sdHost, &pidHost, 1);
+	if (!col) {
+		/*
+		 * If a data collection for this task does not exist,
+		 * register a new one.
+		 */
+		col = kshark_register_data_collection(kshark_ctx,
+						      _data->rows(),
+						      _data->size(),
+						      kshark_match_pid,
+						      sdHost, &pidHost, 1,
+						      25);
+	}
+	graph->setHostDataCollectionPtr(col);
+
+	graph->fill(sdHost, pidHost, sdGuest, vcpu);
 
 	return graph;
 }
@@ -636,18 +705,19 @@
 bool KsGLWidget::find(const QPoint &point, int variance, bool joined,
 		      size_t *index)
 {
+	int bin, sd, cpu, pid;
+
 	/*
 	 * Get the bin, pid and cpu numbers.
 	 * Remember that one bin corresponds to one pixel.
 	 */
-	int bin = point.x() - _hMargin;
-	int cpu = getPlotCPU(point);
-	int pid = getPlotPid(point);
+	bin = point.x() - _hMargin;
+	getPlotInfo(point, &sd, &cpu, &pid);
 
-	return _find(bin, cpu, pid, variance, joined, index);
+	return _find(bin, sd, cpu, pid, variance, joined, index);
 }
 
-int KsGLWidget::_getNextCPU(int pid, int bin)
+int KsGLWidget::_getNextCPU(int sd, int pid, int bin)
 {
 	kshark_context *kshark_ctx(nullptr);
 	kshark_entry_collection *col;
@@ -658,12 +728,12 @@
 
 	col = kshark_find_data_collection(kshark_ctx->collections,
 					  kshark_match_pid,
-					  pid);
+					  sd, &pid, 1);
 	if (!col)
 		return KS_EMPTY_BIN;
 
 	for (int i = bin; i < _model.histo()->n_bins; ++i) {
-		cpu = ksmodel_get_cpu_front(_model.histo(), i, pid,
+		cpu = ksmodel_get_cpu_front(_model.histo(), i, sd, pid,
 					    false, col, nullptr);
 		if (cpu >= 0)
 			return cpu;
@@ -672,7 +742,7 @@
 	return KS_EMPTY_BIN;
 }
 
-bool KsGLWidget::_find(int bin, int cpu, int pid,
+bool KsGLWidget::_find(int bin, int sd, int cpu, int pid,
 		       int variance, bool joined, size_t *row)
 {
 	int hSize = _model.histo()->n_bins;
@@ -690,7 +760,7 @@
 	auto lamGetEntryByCPU = [&](int b) {
 		/* Get the first data entry in this bin. */
 		found = ksmodel_first_index_at_cpu(_model.histo(),
-							   b, cpu);
+						   b, sd, cpu);
 		if (found < 0) {
 			/*
 			 * The bin is empty or the entire connect of the bin
@@ -706,7 +776,7 @@
 	auto lamGetEntryByPid = [&](int b) {
 		/* Get the first data entry in this bin. */
 		found = ksmodel_first_index_at_pid(_model.histo(),
-							   b, pid);
+						   b, sd, pid);
 		if (found < 0) {
 			/*
 			 * The bin is empty or the entire connect of the bin
@@ -772,7 +842,7 @@
 		 * for an entry on the next CPU used by this task.
 		 */
 		if (!ret && joined) {
-			cpu = _getNextCPU(pid, bin);
+			cpu = _getNextCPU(sd, pid, bin);
 			ret = lamFindEntryByCPU(bin);
 		}
 
@@ -857,7 +927,6 @@
 	 * Calculate the new range of the histogram. The number of bins will
 	 * stay the same.
 	 */
-
 	min = ksmodel_bin_ts(_model.histo(), binMin);
 	max = ksmodel_bin_ts(_model.histo(), binMax);
 	if (max - min < nBins) {
@@ -911,35 +980,55 @@
 	return posX;
 }
 
-/** Get the CPU Id of the Graph plotted at given position. */
-int KsGLWidget::getPlotCPU(const QPoint &point)
+bool KsGLWidget::getPlotInfo(const QPoint &point, int *sd, int *cpu, int *pid)
 {
-	int cpuId, y = point.y();
+	int base, n;
 
-	if (_cpuList.count() == 0)
-		return -1;
+	*sd = *cpu = *pid = -1;
 
-	cpuId = (y - _vMargin + _vSpacing / 2) / (_vSpacing + KS_GRAPH_HEIGHT);
-	if (cpuId < 0 || cpuId >= _cpuList.count())
-		return -1;
+	for (auto it = _streamPlots.constBegin(); it != _streamPlots.constEnd(); ++it) {
+		n = it.value()._cpuList.count();
+		for (int i = 0; i < n; ++i) {
+			base = it.value()._cpuPlotBase[i];
+			if (base - KS_GRAPH_HEIGHT < point.y() &&
+			    point.y() < base) {
+				*sd = it.key();
+				*cpu = it.value()._cpuList[i];
 
-	return _cpuList[cpuId];
-}
+				return true;
+			}
+		}
 
-/** Get the CPU Id of the Graph plotted at given position. */
-int KsGLWidget::getPlotPid(const QPoint &point)
-{
-	int pidId, y = point.y();
+		n = it.value()._taskList.count();
+		for (int i = 0; i < n; ++i) {
+			base = it.value()._taskPlotBase[i];
+			if (base - KS_GRAPH_HEIGHT < point.y() &&
+			    point.y() < base) {
+				*sd = it.key();
+				*pid = it.value()._taskList[i];
 
-	if (_taskList.count() == 0)
-		return -1;
+				return true;
+			}
+		}
+	}
 
-	pidId = (y - _vMargin -
-		     _cpuList.count()*(KS_GRAPH_HEIGHT + _vSpacing) +
-		     _vSpacing / 2) / (_vSpacing + KS_GRAPH_HEIGHT);
+	for (auto const &cp: _comboPlots) {
+		base = cp._vcpuBase + _vSpacing / 4;
+		if (base - KS_GRAPH_HEIGHT < point.y() && point.y() < base) {
+			*sd = cp._guestStreamId;
+			*cpu = cp._vcpu;
 
-	if (pidId < 0 || pidId >= _taskList.count())
-		return -1;
+			return true;
+		}
 
-	return _taskList[pidId];
+		base = cp._hostBase + _vSpacing / 4;
+		if (base - KS_GRAPH_HEIGHT < point.y() && point.y() < base) {
+			*sd = cp._hostStreamId;
+			*pid = cp._hostPid;
+
+			return true;
+		}
+	}
+
+	return false;
 }
diff --git a/kernel-shark/src/KsGLWidget.hpp b/kernel-shark/src/KsGLWidget.hpp
index 3bcecf9..843aff2 100644
--- a/kernel-shark/src/KsGLWidget.hpp
+++ b/kernel-shark/src/KsGLWidget.hpp
@@ -21,6 +21,31 @@
 #include "KsModels.hpp"
 #include "KsDualMarker.hpp"
 
+struct KsPerStreamPlots {
+	/** CPUs to be plotted. */
+	QVector<int>	_cpuList;
+	QVector<int>	_cpuPlotBase;
+
+	/** Tasks to be plotted. */
+	QVector<int>	_taskList;
+	QVector<int>	_taskPlotBase;
+};
+
+struct KsVirtComboPlot {
+
+	int	_hostStreamId;
+
+	int	_hostPid;
+
+	int	_guestStreamId;
+
+	int	_vcpu;
+
+	int	_hostBase;
+
+	int	_vcpuBase;
+};
+
 /**
  * The KsGLWidget class provides a widget for rendering OpenGL graphics used
  * to plot trace graphs.
@@ -69,23 +94,57 @@
 	KsGraphModel *model() {return &_model;}
 
 	/** Get the number of CPU graphs. */
-	int cpuGraphCount() const {return _cpuList.count();}
+	int cpuGraphCount(int sd) const
+	{
+		auto it = _streamPlots.find(sd);
+		if (it != _streamPlots.end())
+			return it.value()._cpuList.count();
+		return 0;
+	}
 
 	/** Get the number of Task graphs. */
-	int taskGraphCount() const {return _taskList.count();}
+	int taskGraphCount(int sd) const
+	{
+		auto it = _streamPlots.find(sd);
+		if (it != _streamPlots.end())
+			return it.value()._taskList.count();
+		return 0;
+	}
 
 	/** Get the total number of graphs. */
-	int graphCount() const {return _cpuList.count() + _taskList.count();}
+	int graphCount(int sd) const
+	{
+		auto it = _streamPlots.find(sd);
+		if (it != _streamPlots.end())
+			return it.value()._taskList.count() +
+			       it.value()._cpuList.count();
+
+		return 0;
+	}
+
+	int totGraphCount() const
+	{
+		int count(0);
+
+		for (auto const &s: _streamPlots)
+			count += s._taskList.count() +
+				 s._cpuList.count();
+
+		return count;
+	}
+
+	int totComboGraphCount() const {return _comboPlots.count();}
 
 	/** Get the height of the widget. */
 	int height() const
 	{
-		return graphCount() * (KS_GRAPH_HEIGHT + _vSpacing) +
+		return totGraphCount() * (KS_GRAPH_HEIGHT + _vSpacing) +
+		       totComboGraphCount() * (KS_GRAPH_HEIGHT * 2 + _vSpacing) +
 		       _vMargin * 2;
 	}
 
 	/** Get the device pixel ratio. */
-	int dpr() const {return _dpr;}
+	int dpr()	const {return _dpr;}
 
 	/** Get the size of the horizontal margin space. */
 	int hMargin()	const {return _hMargin;}
@@ -96,24 +155,17 @@
 	/** Get the size of the vertical spaceing between the graphs. */
 	int vSpacing()	const {return _vSpacing;}
 
-	void setMark(KsGraphMark *mark);
-
-	void findGraphIds(const kshark_entry &e,
-			  int *graphCPU,
-			  int *graphTask);
+	void setMarkPoints(const KsDataStore &data, KsGraphMark *mark);
 
 	bool find(const QPoint &point, int variance, bool joined,
 		  size_t *index);
 
-	int getPlotCPU(const QPoint &point);
+	bool getPlotInfo(const QPoint &point, int *sd, int *cpu, int *pid);
 
-	int getPlotPid(const QPoint &point);
+	/** CPUs to Tasks (per data stream) be plotted. */
+	QMap<int, KsPerStreamPlots>	_streamPlots;
 
-	/** CPUs to be plotted. */
-	QVector<int>	_cpuList;
-
-	/** Tasks to be plotted. */
-	QVector<int>	_taskList;
+	QVector<KsVirtComboPlot>	_comboPlots;
 
 signals:
 	/**
@@ -126,7 +178,7 @@
 	 * This signal is emitted when the mouse moves but there is no visible
 	 * KernelShark entry under the cursor.
 	 */
-	void notFound(uint64_t ts, int cpu, int pid);
+	void notFound(uint64_t ts, int sd, int cpu, int pid);
 
 	/** This signal is emitted when the Plus key is pressed. */
 	void zoomIn();
@@ -159,7 +211,9 @@
 	void updateView(size_t pos, bool mark);
 
 private:
-	QVector<KsPlot::Graph*>	_graphs;
+	QMap<int, QVector<KsPlot::Graph*>>	_graphs;
+
+	QVector<KsPlot::ComboGraph*>		_comboGraphs;
 
 	KsPlot::PlotObjList	_shapes;
 
@@ -189,13 +243,16 @@
 
 	void _drawAxisX();
 
-	void _makeGraphs(QVector<int> cpuMask, QVector<int> taskMask);
+	void _makeGraphs();
 
-	KsPlot::Graph *_newCPUGraph(int cpu);
+	KsPlot::Graph *_newCPUGraph(int sd, int cpu);
 
-	KsPlot::Graph *_newTaskGraph(int pid);
+	KsPlot::Graph *_newTaskGraph(int sd, int pid);
 
-	void _makePluginShapes(QVector<int> cpuMask, QVector<int> taskMask);
+	KsPlot::ComboGraph *_newComboGraph(int sdHost, int pidHost,
+					   int sdGuest, int vcpu);
+
+	void _makePluginShapes();
 
 	int _posInRange(int x);
 
@@ -207,14 +264,16 @@
 
 	bool _findAndSelect(QMouseEvent *event);
 
-	bool _find(int bin, int cpu, int pid,
+	bool _find(int bin, int sd, int cpu, int pid,
 		   int variance, bool joined, size_t *row);
 
-	int _getNextCPU(int pid, int bin);
+	int _getNextCPU(int sd, int pid, int bin);
 
-	int _getLastTask(struct kshark_trace_histo *histo, int bin, int cpu);
+	int _getLastTask(struct kshark_trace_histo *histo,
+			 int bin, int sd, int cpu);
 
-	int _getLastCPU(struct kshark_trace_histo *histo, int bin, int pid);
+	int _getLastCPU(struct kshark_trace_histo *histo,
+			int sd, int bin, int pid);
 
 	void _deselect();
 };
diff --git a/kernel-shark/src/KsMainWindow.cpp b/kernel-shark/src/KsMainWindow.cpp
index 7afb721..222b8ad 100644
--- a/kernel-shark/src/KsMainWindow.cpp
+++ b/kernel-shark/src/KsMainWindow.cpp
@@ -42,7 +42,8 @@
   _plugins(this),
   _capture(this),
   _captureLocalServer(this),
-  _openAction("Open", this),
+  _openAction("Open Trace File", this),
+  _appendAction("Append Trace File", this),
   _restoreSessionAction("Restore Last Session", this),
   _importSessionAction("Import Session", this),
   _exportSessionAction("Export Sassion", this),
@@ -59,6 +60,7 @@
   _cpuSelectAction("CPUs", this),
   _taskSelectAction("Tasks", this),
   _managePluginsAction("Manage plugins", this),
+  _virtComboSelectAction("Virt. Combos", this),
   _addPluginsAction("Add plugins", this),
   _captureAction("Record", this),
   _colorAction(this),
@@ -169,6 +171,13 @@
 	connect(&_openAction,	&QAction::triggered,
 		this,		&KsMainWindow::_open);
 
+	_appendAction.setIcon(QIcon::fromTheme("document-open"));
+	_appendAction.setShortcut(tr("Ctrl+A"));
+	_appendAction.setStatusTip("Append an existing data file");
+
+	connect(&_appendAction,	&QAction::triggered,
+		this,		&KsMainWindow::_append);
+
 	_restoreSessionAction.setIcon(QIcon::fromTheme("document-open-recent"));
 	connect(&_restoreSessionAction,	&QAction::triggered,
 		this,			&KsMainWindow::_restoreSession);
@@ -227,6 +236,9 @@
 	connect(&_taskSelectAction,	&QAction::triggered,
 		this,			&KsMainWindow::_taskSelect);
 
+	connect(&_virtComboSelectAction,&QAction::triggered,
+		this,			&KsMainWindow::_virtComboSelect);
+
 	/* Tools menu */
 	_managePluginsAction.setShortcut(tr("Ctrl+P"));
 	_managePluginsAction.setIcon(QIcon::fromTheme("preferences-system"));
@@ -290,6 +302,7 @@
 	/* File menu */
 	file = menuBar()->addMenu("File");
 	file->addAction(&_openAction);
+	file->addAction(&_appendAction);
 
 	sessions = file->addMenu("Sessions");
 	sessions->setIcon(QIcon::fromTheme("document-properties"));
@@ -340,6 +353,7 @@
 	plots = menuBar()->addMenu("Plots");
 	plots->addAction(&_cpuSelectAction);
 	plots->addAction(&_taskSelectAction);
+	plots->addAction(&_virtComboSelectAction);
 
 	/* Tools menu */
 	tools = menuBar()->addMenu("Tools");
@@ -368,6 +382,18 @@
 		loadDataFile(fileName);
 }
 
+void KsMainWindow::_append()
+{
+	QString fileName =
+		QFileDialog::getOpenFileName(this,
+					     "Append File",
+					     KS_DIR,
+					     "trace-cmd files (*.dat);;All files (*)");
+
+	if (!fileName.isEmpty())
+		appendDataFile(fileName);
+}
+
 void KsMainWindow::_restoreSession()
 {
 	QString file = KS_CONF_DIR;
@@ -399,9 +425,9 @@
 	if (!kshark_instance(&kshark_ctx))
 		return;
 
-	_session.saveGraphs(*_graph.glPtr());
 	_session.saveVisModel(_graph.glPtr()->model()->histo());
-	_session.saveFilters(kshark_ctx);
+	_session.saveDataStreams(kshark_ctx);
+	_session.saveGraphs(kshark_ctx, _graph);
 	_session.saveDualMarker(&_mState);
 	_session.saveTable(_view);
 	_session.saveColorScheme();
@@ -456,7 +482,6 @@
 void KsMainWindow::_importFilter()
 {
 	kshark_context *kshark_ctx(nullptr);
-	kshark_config_doc *conf;
 	QString fileName;
 
 	if (!kshark_instance(&kshark_ctx))
@@ -468,23 +493,12 @@
 	if (fileName.isEmpty())
 		return;
 
-	conf = kshark_open_config_file(fileName.toStdString().c_str(),
-				       "kshark.config.filter");
-	if (!conf)
-		return;
-
-	kshark_import_all_event_filters(kshark_ctx, conf);
-	kshark_free_config_doc(conf);
-
-	kshark_filter_entries(kshark_ctx, _data.rows(), _data.size());
-	_filterSyncCBoxUpdate(kshark_ctx);
-	emit _data.updateWidgets(&_data);
+	_session.loadFilters(kshark_ctx, fileName, &_data);
 }
 
 void KsMainWindow::_exportFilter()
 {
 	kshark_context *kshark_ctx(nullptr);
-	kshark_config_doc *conf(nullptr);
 	QString fileName;
 
 	if (!kshark_instance(&kshark_ctx))
@@ -504,9 +518,7 @@
 		}
 	}
 
-	kshark_export_all_event_filters(kshark_ctx, &conf);
-	kshark_save_config_file(fileName.toStdString().c_str(), conf);
-	kshark_free_config_doc(conf);
+	_session.saveFilters(kshark_ctx, fileName);
 }
 
 void KsMainWindow::_listFilterSync(int state)
@@ -524,37 +536,72 @@
 void KsMainWindow::_showEvents()
 {
 	kshark_context *kshark_ctx(nullptr);
+	QVector<KsCheckBoxWidget *> cbds;
 	KsCheckBoxWidget *events_cb;
 	KsCheckBoxDialog *dialog;
+	kshark_data_stream *stream;
+	int *streamIds, sd;
 
 	if (!kshark_instance(&kshark_ctx))
 		return;
 
-	events_cb = new KsEventsCheckBoxWidget(_data.tep(), this);
-	dialog = new KsCheckBoxDialog(events_cb, this);
+	streamIds = kshark_all_streams(kshark_ctx);
+	for (int i = 0; i < kshark_ctx->n_streams; ++i) {
+		sd = streamIds[i];
+		stream = kshark_ctx->stream[sd];
+		events_cb = new KsEventsCheckBoxWidget(sd, this);
+		events_cb->setStream(QString(stream->file));
+		cbds.append(events_cb);
 
-	if (!kshark_ctx->show_event_filter ||
-	    !kshark_ctx->show_event_filter->count) {
-		events_cb->setDefault(true);
-	} else {
-		/*
-		 * The event filter contains IDs. Make this visible in the
-		 * CheckBox Widget.
-		 */
-		tep_event **events =
-			tep_list_events(_data.tep(), TEP_EVENT_SORT_SYSTEM);
-		int nEvts = tep_get_events_count(_data.tep());
-		QVector<bool> v(nEvts, false);
+		if (!stream->show_event_filter ||
+		    !stream->show_event_filter->count) {
+		    events_cb->setDefault(true);
+		} else {
+			/*
+			 * The event filter contains IDs. Make this visible in
+			 * the CheckBox Widget.
+			 */
+			tep_event **events = tep_list_events(_data.tep(sd),
+							     TEP_EVENT_SORT_SYSTEM);
+			int nEvts = tep_get_events_count(_data.tep(sd));
+			QVector<bool> v(nEvts, false);
+			for (int i = 0; i < nEvts; ++i) {
+				if (tracecmd_filter_id_find(stream->show_event_filter,
+							    events[i]->id))
+					v[i] = true;
+			}
 
-		for (int i = 0; i < nEvts; ++i) {
-			if (tracecmd_filter_id_find(kshark_ctx->show_event_filter,
-						    events[i]->id))
-				v[i] = true;
+			events_cb->set(v);
 		}
-
-		events_cb->set(v);
 	}
 
+// 	events_cb = new KsEventsCheckBoxWidget(_data.tep(), this);
+// 	dialog = new KsCheckBoxDialog(events_cb, this);
+// 
+// 	if (!kshark_ctx->show_event_filter ||
+// 	    !kshark_ctx->show_event_filter->count) {
+// 		events_cb->setDefault(true);
+// 	} else {
+// 		/*
+// 		 * The event filter contains IDs. Make this visible in the
+// 		 * CheckBox Widget.
+// 		 */
+// 		tep_event_format **events =
+// 			tep_list_events(_data.tep(), TEP_EVENT_SORT_SYSTEM);
+// 		int nEvts = tep_get_events_count(_data.tep());
+// 		QVector<bool> v(nEvts, false);
+// 
+// 		for (int i = 0; i < nEvts; ++i) {
+// 			if (tracecmd_filter_id_find(kshark_ctx->show_event_filter,
+// 						    events[i]->id))
+// 				v[i] = true;
+// 		}
+// 
+// 		events_cb->set(v);
+// 	}
+
+	dialog = new KsCheckBoxDialog(cbds, this);
+
 	connect(dialog,		&KsCheckBoxDialog::apply,
 		&_data,		&KsDataStore::applyPosEventFilter);
 
@@ -564,32 +611,43 @@
 void KsMainWindow::_showTasks()
 {
 	kshark_context *kshark_ctx(nullptr);
+	QVector<KsCheckBoxWidget *> cbds;
+	kshark_data_stream *stream;
 	KsCheckBoxWidget *tasks_cbd;
 	KsCheckBoxDialog *dialog;
+	int *streamIds, sd;
 
 	if (!kshark_instance(&kshark_ctx))
 		return;
 
-	tasks_cbd = new KsTasksCheckBoxWidget(_data.tep(), true, this);
-	dialog = new KsCheckBoxDialog(tasks_cbd, this);
+	streamIds = kshark_all_streams(kshark_ctx);
+	for (int i = 0; i < kshark_ctx->n_streams; ++i) {
+		sd = streamIds[i];
+		stream = kshark_ctx->stream[sd];
+		tasks_cbd = new KsTasksCheckBoxWidget(sd, true, this);
+		tasks_cbd->setStream(QString(stream->file));
+		cbds.append(tasks_cbd);
 
-	if (!kshark_ctx->show_task_filter ||
-	    !kshark_ctx->show_task_filter->count) {
-		tasks_cbd->setDefault(true);
-	} else {
-		QVector<int> pids = KsUtils::getPidList();
-		int nPids = pids.count();
-		QVector<bool> v(nPids, false);
+		if (!stream->show_task_filter ||
+		    !stream->show_task_filter->count) {
+			tasks_cbd->setDefault(true);
+		} else {
+			QVector<int> pids = KsUtils::getPidList(sd);
+			int nPids = pids.count();
+			QVector<bool> v(nPids, false);
 
-		for (int i = 0; i < nPids; ++i) {
-			if (tracecmd_filter_id_find(kshark_ctx->show_task_filter,
-						    pids[i]))
-				v[i] = true;
+			for (int i = 0; i < nPids; ++i) {
+				if (tracecmd_filter_id_find(stream->show_task_filter,
+							    pids[i]))
+					v[i] = true;
+			}
+
+			tasks_cbd->set(v);
 		}
-
-		tasks_cbd->set(v);
 	}
 
+	dialog = new KsCheckBoxDialog(cbds, this);
+
 	connect(dialog,		&KsCheckBoxDialog::apply,
 		&_data,		&KsDataStore::applyPosTaskFilter);
 
@@ -599,32 +657,43 @@
 void KsMainWindow::_hideTasks()
 {
 	kshark_context *kshark_ctx(nullptr);
+	QVector<KsCheckBoxWidget *> cbds;
+	kshark_data_stream *stream;
 	KsCheckBoxWidget *tasks_cbd;
 	KsCheckBoxDialog *dialog;
+	int *streamIds, sd;
 
 	if (!kshark_instance(&kshark_ctx))
 		return;
 
-	tasks_cbd = new KsTasksCheckBoxWidget(_data.tep(), false, this);
-	dialog = new KsCheckBoxDialog(tasks_cbd, this);
+	streamIds = kshark_all_streams(kshark_ctx);
+	for (int i = 0; i < kshark_ctx->n_streams; ++i) {
+		sd = streamIds[i];
+		stream = kshark_ctx->stream[sd];
+		tasks_cbd = new KsTasksCheckBoxWidget(sd, false, this);
+		tasks_cbd->setStream(QString(stream->file));
+		cbds.append(tasks_cbd);
 
-	if (!kshark_ctx->hide_task_filter ||
-	    !kshark_ctx->hide_task_filter->count) {
-		tasks_cbd->setDefault(false);
-	} else {
-		QVector<int> pids = KsUtils::getPidList();
-		int nPids = pids.count();
-		QVector<bool> v(nPids, false);
+		if (!stream->hide_task_filter ||
+		    !stream->hide_task_filter->count) {
+			tasks_cbd->setDefault(false);
+		} else {
+			QVector<int> pids = KsUtils::getPidList(sd);
+			int nPids = pids.count();
+			QVector<bool> v(nPids, false);
 
-		for (int i = 0; i < nPids; ++i) {
-			if (tracecmd_filter_id_find(kshark_ctx->hide_task_filter,
-						    pids[i]))
-				v[i] = true;
+			for (int i = 0; i < nPids; ++i) {
+				if (tracecmd_filter_id_find(stream->hide_task_filter,
+							    pids[i]))
+					v[i] = true;
+			}
+
+			tasks_cbd->set(v);
 		}
-
-		tasks_cbd->set(v);
 	}
 
+	dialog = new KsCheckBoxDialog(cbds, this);
+
 	connect(dialog,		&KsCheckBoxDialog::apply,
 		&_data,		&KsDataStore::applyNegTaskFilter);
 
@@ -634,30 +703,40 @@
 void KsMainWindow::_showCPUs()
 {
 	kshark_context *kshark_ctx(nullptr);
-	KsCheckBoxWidget *cpu_cbd;
+	QVector<KsCheckBoxWidget *> cbds;
+	kshark_data_stream *stream;
+	KsCheckBoxWidget *cpus_cbd;
 	KsCheckBoxDialog *dialog;
+	int *streamIds, sd, nCPUs;
 
 	if (!kshark_instance(&kshark_ctx))
 		return;
 
-	cpu_cbd = new KsCPUCheckBoxWidget(_data.tep(), this);
-	dialog = new KsCheckBoxDialog(cpu_cbd, this);
+	streamIds = kshark_all_streams(kshark_ctx);
+	for (int i = 0; i < kshark_ctx->n_streams; ++i) {
+		sd = streamIds[i];
+		stream = kshark_ctx->stream[sd];
+		cpus_cbd = new KsCPUCheckBoxWidget(sd, this);
+		cpus_cbd->setStream(QString(stream->file));
+		cbds.append(cpus_cbd);
 
-	if (!kshark_ctx->show_cpu_filter ||
-	    !kshark_ctx->show_cpu_filter->count) {
-		cpu_cbd->setDefault(true);
-	} else {
-		int nCPUs = tep_get_cpus(_data.tep());
-		QVector<bool> v(nCPUs, false);
+		nCPUs = tep_get_cpus(_data.tep(sd));
+		if (!stream->show_cpu_filter ||
+		    !stream->show_cpu_filter->count) {
+			cpus_cbd->setDefault(true);
+		} else {
+			QVector<bool> v(nCPUs, false);
+			for (int i = 0; i < nCPUs; ++i) {
+				if (tracecmd_filter_id_find(stream->show_cpu_filter, i))
+					v[i] = true;
+			}
 
-		for (int i = 0; i < nCPUs; ++i) {
-			if (tracecmd_filter_id_find(kshark_ctx->show_cpu_filter, i))
-				v[i] = true;
+			cpus_cbd->set(v);
 		}
-
-		cpu_cbd->set(v);
 	}
 
+	dialog = new KsCheckBoxDialog(cbds, this);
+
 	connect(dialog,		&KsCheckBoxDialog::apply,
 		&_data,		&KsDataStore::applyPosCPUFilter);
 
@@ -667,58 +746,67 @@
 void KsMainWindow::_hideCPUs()
 {
 	kshark_context *kshark_ctx(nullptr);
-	KsCheckBoxWidget *cpu_cbd;
+	QVector<KsCheckBoxWidget *> cbds;
+	kshark_data_stream *stream;
+	KsCheckBoxWidget *cpus_cbd;
 	KsCheckBoxDialog *dialog;
+	int *streamIds, sd, nCPUs;
 
 	if (!kshark_instance(&kshark_ctx))
 		return;
 
-	cpu_cbd = new KsCPUCheckBoxWidget(_data.tep(), this);
-	dialog = new KsCheckBoxDialog(cpu_cbd, this);
+	streamIds = kshark_all_streams(kshark_ctx);
+	for (int i = 0; i < kshark_ctx->n_streams; ++i) {
+		sd = streamIds[i];
+		stream = kshark_ctx->stream[sd];
+		cpus_cbd = new KsCPUCheckBoxWidget(sd, this);
+		cpus_cbd->setStream(QString(stream->file));
+		cbds.append(cpus_cbd);
 
-	if (!kshark_ctx->hide_cpu_filter ||
-	    !kshark_ctx->hide_cpu_filter->count) {
-		cpu_cbd->setDefault(false);
-	} else {
-		int nCPUs = tep_get_cpus(_data.tep());
-		QVector<bool> v(nCPUs, false);
+		nCPUs = tep_get_cpus(_data.tep(sd));
+		if (!stream->hide_cpu_filter ||
+		    !stream->hide_cpu_filter->count) {
+			cpus_cbd->setDefault(false);
+		} else {
+			QVector<bool> v(nCPUs, false);
+			for (int i = 0; i < nCPUs; ++i) {
+				if (tracecmd_filter_id_find(stream->hide_cpu_filter, i))
+					v[i] = true;
+			}
 
-		for (int i = 0; i < nCPUs; ++i) {
-			if (tracecmd_filter_id_find(kshark_ctx->hide_cpu_filter,
-						    i))
-				v[i] = true;
+			cpus_cbd->set(v);
 		}
-
-		cpu_cbd->set(v);
 	}
 
+	dialog = new KsCheckBoxDialog(cbds, this);
+
 	connect(dialog,		&KsCheckBoxDialog::apply,
-		&_data,		&KsDataStore::applyNegCPUFilter);
+		&_graph,	&KsTraceGraph::cpuReDraw);
 
 	dialog->show();
 }
 
 void KsMainWindow::_advancedFiltering()
 {
-	KsAdvFilteringDialog *dialog;
-
-	if (!_data.tep()) {
-		QErrorMessage *em = new QErrorMessage(this);
-		QString text("Unable to open Advanced filtering dialog.");
-
-		text += " Tracing data has to be loaded first.";
-
-		em->showMessage(text, "advancedFiltering");
-		qCritical() << "ERROR: " << text;
-
-		return;
-	}
-
-	dialog = new KsAdvFilteringDialog(this);
-	connect(dialog,		&KsAdvFilteringDialog::dataReload,
-		&_data,		&KsDataStore::reload);
-
-	dialog->show();
+// 	KsAdvFilteringDialog *dialog;
+// 
+// 	if (!_data.tep()) {
+// 		QErrorMessage *em = new QErrorMessage(this);
+// 		QString text("Unable to open Advanced filtering dialog.");
+// 
+// 		text += " Tracing data has to be loaded first.";
+// 
+// 		em->showMessage(text, "advancedFiltering");
+// 		qCritical() << "ERROR: " << text;
+// 
+// 		return;
+// 	}
+// 
+// 	dialog = new KsAdvFilteringDialog(this);
+// 	connect(dialog,		&KsAdvFilteringDialog::dataReload,
+// 		&_data,		&KsDataStore::reload);
+// 
+// 	dialog->show();
 }
 
 void KsMainWindow::_clearFilters()
@@ -728,23 +816,36 @@
 
 void KsMainWindow::_cpuSelect()
 {
-	KsCheckBoxWidget *cpus_cbd = new KsCPUCheckBoxWidget(_data.tep(), this);
-	KsCheckBoxDialog *dialog = new KsCheckBoxDialog(cpus_cbd, this);
+	kshark_context *kshark_ctx(nullptr);
+	QVector<KsCheckBoxWidget *> cbds;
+	KsCheckBoxWidget *cpus_cbd;
+	KsCheckBoxDialog *dialog;
+	int *streamIds, sd, nCPUs;
 
-	if(_data.tep()) {
-		int nCPUs = tep_get_cpus(_data.tep());
-		if (nCPUs == _graph.glPtr()->cpuGraphCount()) {
+	if (!kshark_instance(&kshark_ctx))
+		return;
+
+	streamIds = kshark_all_streams(kshark_ctx);
+	for (int i = 0; i < kshark_ctx->n_streams; ++i) {
+		sd = streamIds[i];
+		cpus_cbd = new KsCPUCheckBoxWidget(sd, this);
+		cpus_cbd->setStream(QString(kshark_ctx->stream[sd]->file));
+		cbds.append(cpus_cbd);
+
+		nCPUs = tep_get_cpus(_data.tep(sd));
+		if (nCPUs == _graph.glPtr()->cpuGraphCount(sd)) {
 			cpus_cbd->setDefault(true);
 		} else {
 			QVector<bool> v(nCPUs, false);
-
-			for (auto const &cpu: _graph.glPtr()->_cpuList)
+			for (auto const &cpu: _graph.glPtr()->_streamPlots[sd]._cpuList)
 				v[cpu] = true;
 
 			cpus_cbd->set(v);
 		}
 	}
 
+	dialog = new KsCheckBoxDialog(cbds, this);
+
 	connect(dialog,		&KsCheckBoxDialog::apply,
 		&_graph,	&KsTraceGraph::cpuReDraw);
 
@@ -753,54 +854,99 @@
 
 void KsMainWindow::_taskSelect()
 {
-	KsCheckBoxWidget *tasks_cbd = new KsTasksCheckBoxWidget(_data.tep(),
-								true,
-								this);
-	KsCheckBoxDialog *dialog = new KsCheckBoxDialog(tasks_cbd, this);
-	QVector<int> pids = KsUtils::getPidList();
-	int nPids = pids.count();
+	kshark_context *kshark_ctx(nullptr);
+	QVector<KsCheckBoxWidget *> cbds;
+	KsCheckBoxWidget *tasks_cbd;
+	KsCheckBoxDialog *dialog;
+	int *streamIds, sd, nPids;
+	QVector<int> pids;
 
-	if (nPids == _graph.glPtr()->taskGraphCount()) {
-		tasks_cbd->setDefault(true);
-	} else {
-		QVector<bool> v(nPids, false);
-		for (int i = 0; i < nPids; ++i) {
-			for (auto const &pid: _graph.glPtr()->_taskList) {
-				if (pids[i] == pid) {
-					v[i] = true;
-					break;
+	if (!kshark_instance(&kshark_ctx))
+		return;
+
+	streamIds = kshark_all_streams(kshark_ctx);
+	for (int i = 0; i < kshark_ctx->n_streams; ++i) {
+		sd = streamIds[i];
+		tasks_cbd = new KsTasksCheckBoxWidget(sd, true, this);
+		tasks_cbd->setStream(QString(kshark_ctx->stream[sd]->file));
+		cbds.append(tasks_cbd);
+
+		pids = KsUtils::getPidList(sd);
+		nPids = pids.count();
+		if (nPids == _graph.glPtr()->taskGraphCount(sd)) {
+			tasks_cbd->setDefault(true);
+		} else {
+			QVector<bool> v(nPids, false);
+			for (int i = 0; i < nPids; ++i) {
+				QVector<int> plots =
+					_graph.glPtr()->_streamPlots[sd]._taskList;
+				for (auto const &pid: plots) {
+					if (pids[i] == pid) {
+						v[i] = true;
+						break;
+					}
 				}
 			}
-		}
 
-		tasks_cbd->set(v);
+			tasks_cbd->set(v);
+		}
 	}
 
+	dialog = new KsCheckBoxDialog(cbds, this);
+
 	connect(dialog,		&KsCheckBoxDialog::apply,
 		&_graph,	&KsTraceGraph::taskReDraw);
 
 	dialog->show();
 }
 
+void KsMainWindow::_virtComboSelect()
+{
+	kshark_context *kshark_ctx(nullptr);
+	KsComboPlotDialog *dialog;
+
+	if (!kshark_instance(&kshark_ctx))
+		return;
+
+	dialog = new KsComboPlotDialog(this);
+
+	connect(dialog,		&KsComboPlotDialog::apply,
+		&_graph,	&KsTraceGraph::comboReDraw);
+
+	dialog->show();
+}
+
 void KsMainWindow::_pluginSelect()
 {
+	kshark_context *kshark_ctx(nullptr);
+	QVector<bool> registeredPlugins;
+	QVector<KsCheckBoxWidget *> cbds;
 	KsCheckBoxWidget *plugin_cbd;
 	KsCheckBoxDialog *dialog;
-	QVector<bool> registeredPlugins;
+	int *streamIds, sd;
 	QStringList plugins;
 
+	if (!kshark_instance(&kshark_ctx))
+		return;
+
 	plugins << _plugins._ksPluginList << _plugins._userPluginList;
 
 	registeredPlugins << _plugins._registeredKsPlugins
 			  << _plugins._registeredUserPlugins;
 
-	plugin_cbd = new KsPluginCheckBoxWidget(plugins, this);
-	plugin_cbd->set(registeredPlugins);
+	streamIds = kshark_all_streams(kshark_ctx);
+	for (int i = 0; i < kshark_ctx->n_streams; ++i) {
+		sd = streamIds[i];
+		plugin_cbd = new KsPluginCheckBoxWidget(sd, plugins, this);
+		plugin_cbd->setStream(QString(kshark_ctx->stream[sd]->file));
+		plugin_cbd->set(registeredPlugins);
+		cbds.append(plugin_cbd);
+	}
 
-	dialog = new KsCheckBoxDialog(plugin_cbd, this);
+	dialog = new KsCheckBoxDialog(cbds, this);
 
 	connect(dialog,		&KsCheckBoxDialog::apply,
-		&_plugins,	&KsPluginManager::updatePlugins);
+		&_plugins,	&KsPluginManager::updatePlugins_hack);
 
 	dialog->show();
 }
@@ -864,14 +1010,14 @@
 
 void KsMainWindow::_aboutInfo()
 {
-	KsMessageDialog *message;
+	KsWidgetsLib::KsMessageDialog *message;
 	QString text;
 
 	text.append(" KernelShark\n\n version: ");
 	text.append(KS_VERSION_STRING);
 	text.append("\n");
 
-	message = new KsMessageDialog(text);
+	message = new KsWidgetsLib::KsMessageDialog(text);
 	message->setWindowTitle("About");
 	message->show();
 }
@@ -882,13 +1028,12 @@
 				  QUrl::TolerantMode));
 }
 
-/** Load trace data for file. */
-void KsMainWindow::loadDataFile(const QString& fileName)
+void KsMainWindow::_load(const QString& fileName, bool append)
 {
-	char buff[FILENAME_MAX];
 	QString pbLabel("Loading    ");
 	bool loadDone = false;
 	struct stat st;
+	double shift;
 	int ret;
 
 	ret = stat(fileName.toStdString().c_str(), &st);
@@ -904,9 +1049,16 @@
 
 	qInfo() << "Loading " << fileName;
 
-	_mState.reset();
-	_view.reset();
-	_graph.reset();
+	if (append) {
+		bool ok;
+		shift = QInputDialog::getDouble(this, tr("Append Trace file"),
+						   tr("Offset [usec]:"), 0,
+						   INT_MIN, INT_MAX, 1, &ok);
+		if (ok)
+			shift *= 1000.;
+		else
+			shift = 0.;
+	}
 
 	if (fileName.size() < 40) {
 		pbLabel += fileName;
@@ -916,14 +1068,29 @@
 	}
 
 	setWindowTitle("Kernel Shark");
-	KsProgressBar pb(pbLabel);
+	KsWidgetsLib::KsProgressBar pb(pbLabel);
 	QApplication::processEvents();
 
-	auto lamLoadJob = [&](KsDataStore *d) {
+	_view.reset();
+	_graph.reset();
+
+	auto lamLoadJob = [&] (KsDataStore *d) {
 		d->loadDataFile(fileName);
 		loadDone = true;
 	};
-	std::thread tload(lamLoadJob, &_data);
+
+	auto lamAppendJob = [&] (KsDataStore *d) {
+		d->appendDataFile(fileName, shift);
+		loadDone = true;
+	};
+
+	std::thread job;
+	if (append) {
+		job = std::thread(lamAppendJob, &_data);
+	} else {
+		job = std::thread(lamLoadJob, &_data);
+	}
+// 	std::thread job(lamLoadJob, &_data);
 
 	for (int i = 0; i < 160; ++i) {
 		/*
@@ -937,7 +1104,7 @@
 		usleep(150000);
 	}
 
-	tload.join();
+	job.join();
 
 	if (!_data.size()) {
 		QString text("File ");
@@ -950,17 +1117,26 @@
 	}
 
 	pb.setValue(165);
-	_view.loadData(&_data);
 
+	_view.loadData(&_data);
 	pb.setValue(180);
+
 	_graph.loadData(&_data);
 	pb.setValue(195);
-	setWindowTitle("Kernel Shark (" + fileName + ")");
+}
 
-	if (realpath(fileName.toStdString().c_str(), buff)) {
-		QString path(buff);
-		_session.saveDataFile(path);
-	}
+/** Load trace data for file. */
+void KsMainWindow::loadDataFile(const QString& fileName)
+{
+	_mState.reset();
+	_load(fileName, false);
+	setWindowTitle("Kernel Shark (" + fileName + ")");
+}
+
+/** Append trace data for file. */
+void KsMainWindow::appendDataFile(const QString& fileName)
+{
+	_load(fileName, true);
 }
 
 void KsMainWindow::_error(const QString &text, const QString &errCode,
@@ -1004,6 +1180,9 @@
 		return;
 	}
 
+	KsWidgetsLib::KsProgressBar pb("Loading session settings ...");
+	pb.setValue(10);
+
 	if (!_session.importFromFile(fileName)) {
 		QString text("Unable to open session description file ");
 
@@ -1015,42 +1194,29 @@
 	}
 
 	_session.loadPlugins(kshark_ctx, &_plugins);
+	pb.setValue(20);
 
-	QString dataFile(_session.getDataFile(kshark_ctx));
-	if (dataFile.isEmpty()) {
-		QString text("Unable to open trace data file for session ");
-
-		text.append(fileName);
-		text.append("\n");
-		_error(text, "loadSessErr1", true, true);
-
-		return;
-	}
-
-	loadDataFile(dataFile);
-	if (!_data.tep()) {
+	_session.loadDataStreams(kshark_ctx, &_data);
+	if (!kshark_ctx->n_streams) {
 		_plugins.unloadAll();
 		return;
 	}
 
-	KsProgressBar pb("Loading session settings ...");
-	pb.setValue(10);
+	_view.loadData(&_data);
+	_graph.loadData(&_data);
 
-	_session.loadGraphs(&_graph);
-	pb.setValue(20);
-
-	_session.loadFilters(kshark_ctx, &_data);
 	_filterSyncCBoxUpdate(kshark_ctx);
-	pb.setValue(130);
+	pb.setValue(110);
 
 	_session.loadSplitterSize(&_splitter);
 	_session.loadMainWindowSize(this);
 	this->show();
-	pb.setValue(140);
+	pb.setValue(120);
 
 	_session.loadDualMarker(&_mState, &_graph);
 	_session.loadVisModel(_graph.glPtr()->model());
 	_mState.updateMarkers(_data, _graph.glPtr());
+	_session.loadGraphs(kshark_ctx, _graph);
 	pb.setValue(170);
 
 	_session.loadTable(&_view);
diff --git a/kernel-shark/src/KsMainWindow.hpp b/kernel-shark/src/KsMainWindow.hpp
index 78cd442..37192df 100644
--- a/kernel-shark/src/KsMainWindow.hpp
+++ b/kernel-shark/src/KsMainWindow.hpp
@@ -35,6 +35,8 @@
 
 	void loadDataFile(const QString &fileName);
 
+	void appendDataFile(const QString &fileName);
+
 	void loadSession(const QString &fileName);
 
 	/**
@@ -97,6 +99,8 @@
 	// File menu.
 	QAction		_openAction;
 
+	QAction		_appendAction;
+
 	QAction		_restoreSessionAction;
 
 	QAction		_importSessionAction;
@@ -129,6 +133,8 @@
 
 	QAction		_taskSelectAction;
 
+	QAction		_virtComboSelectAction;
+
 	// Tools menu.
 	QAction		_managePluginsAction;
 
@@ -151,8 +157,12 @@
 
 	QShortcut        _deselectShortcut;
 
+	void _load(const QString& fileName, bool append);
+
 	void _open();
 
+	void _append();
+
 	void _restoreSession();
 
 	void _importSession();
@@ -185,6 +195,8 @@
 
 	void _taskSelect();
 
+	void _virtComboSelect();
+
 	void _pluginSelect();
 
 	void _pluginAdd();
diff --git a/kernel-shark/src/KsModels.cpp b/kernel-shark/src/KsModels.cpp
index b89fee8..8c6994a 100644
--- a/kernel-shark/src/KsModels.cpp
+++ b/kernel-shark/src/KsModels.cpp
@@ -197,7 +197,7 @@
 : QAbstractTableModel(parent),
   _data(nullptr),
   _nRows(0),
-  _header({"#", "CPU", "Time Stamp", "Task", "PID",
+  _header({" >> ", "#", "CPU", "Time Stamp", "Task", "PID",
 	   "Latency", "Event", "Info"}),
   _markA(KS_NO_ROW_SELECTED),
   _markB(KS_NO_ROW_SELECTED)
@@ -211,14 +211,21 @@
 QVariant KsViewModel::data(const QModelIndex &index, int role) const
 {
 	if (role == Qt::ForegroundRole) {
-		if (index.row() == _markA)
+		if (index.row() == _markA && index.column() != 0)
 			return QVariant::fromValue(QColor(Qt::white));
 
-		if (index.row() == _markB)
+		if (index.row() == _markB && index.column() != 0)
 			return QVariant::fromValue(QColor(Qt::white));
 	}
 
 	if (role == Qt::BackgroundRole) {
+		if (index.column() == TRACE_VIEW_STREAM_ID) {
+			int sd = _data[index.row()]->stream_id;
+			QColor col = KsUtils::getStreamColor(sd);
+
+			return QVariant::fromValue(col);
+		}
+
 		if (index.row() == _markA)
 			return QVariant::fromValue(QColor(_colorMarkA));
 
@@ -238,6 +245,8 @@
 	int pid;
 
 	switch (column) {
+		case TRACE_VIEW_STREAM_ID :
+			return QString("%1").arg(_data[row]->stream_id);
 		case TRACE_VIEW_COL_INDEX :
 			return QString("%1").arg(row);
 
diff --git a/kernel-shark/src/KsModels.hpp b/kernel-shark/src/KsModels.hpp
index 3faaf4a..8e4df35 100644
--- a/kernel-shark/src/KsModels.hpp
+++ b/kernel-shark/src/KsModels.hpp
@@ -93,6 +93,9 @@
 
 	/** Table columns Identifiers. */
 	enum {
+		/** Identifier of the Data stream. */
+		TRACE_VIEW_STREAM_ID,
+
 		/** Identifier of the Index column. */
 		TRACE_VIEW_COL_INDEX,
 
@@ -192,8 +195,12 @@
 	 */
 	int mapRowFromSource(int r) const
 	{
-		/*This works because the row number is shown in column "0". */
-		return this->data(this->index(r, 0)).toInt();
+		/*
+		 * This works because the row number is shown in column
+		 * TRACE_VIEW_COL_INDEX.
+		 */
+		int col = KsViewModel::TRACE_VIEW_COL_INDEX;
+		return this->data(this->index(r, col)).toInt();
 	}
 
 	/** Get the source model. */
diff --git a/kernel-shark/src/KsPlotTools.cpp b/kernel-shark/src/KsPlotTools.cpp
index 4447bea..910979e 100644
--- a/kernel-shark/src/KsPlotTools.cpp
+++ b/kernel-shark/src/KsPlotTools.cpp
@@ -457,6 +457,8 @@
 	_cpu._size = 5.5f;
 	_task._color = Color(0, 255, 0);
 	_task._size = 5.5f;
+	_combo._color = Color(225, 100, 255);
+	_combo._size = 5.5f;
 }
 
 void Mark::_draw(const Color &col, float size) const
@@ -464,6 +466,7 @@
 	drawLine(_a, _b, col, size);
 	_cpu.draw();
 	_task.draw();
+	_combo.draw();
 }
 
 /**
@@ -474,7 +477,7 @@
 void Mark::setDPR(int dpr)
 {
 	_size = 1.5 * dpr;
-	_task._size = _cpu._size = 1.5 + 4.0 * dpr;
+	_task._size = _cpu._size = _combo._size = 1.5 + 4.0 * dpr;
 }
 
 /**
@@ -488,6 +491,7 @@
 	_b.setX(x);
 	_cpu.setX(x);
 	_task.setX(x);
+	_combo.setX(x);
 }
 
 /**
@@ -533,6 +537,26 @@
 }
 
 /**
+ * @brief Set the visiblity of the Mark's Combo point.
+ *
+ * @param v: If True, the Task point will be visible.
+ */
+void Mark::setComboVisible(bool v)
+{
+	_combo._visible = v;
+}
+
+/**
+ * @brief Set the Y coordinates (vertical) of the Mark's Combo points.
+ *
+ * @param yCombo: Y coordinate of the Mark's Task point.
+ */
+void Mark::setComboY(int yCombo)
+{
+	_combo.setY(yCombo);
+}
+
+/**
  * @brief Set the visiblity of the Mark's Task points.
  *
  * @param v: If True, the Task point will be visible.
@@ -577,7 +601,8 @@
   _collectionPtr(nullptr),
   _binColors(nullptr),
   _ensembleColors(nullptr),
-  _zeroSuppress(false)
+  _zeroSuppress(false),
+  _drawBase(true)
 {}
 
 /**
@@ -592,10 +617,12 @@
   _bins(new(std::nothrow) Bin[histo->n_bins]),
   _size(histo->n_bins),
   _hMargin(30),
+  _height(50),
   _collectionPtr(nullptr),
   _binColors(bct),
   _ensembleColors(ect),
-  _zeroSuppress(false)
+  _zeroSuppress(false),
+  _drawBase(true)
 {
 	if (!_bins) {
 		_size = 0;
@@ -1082,7 +1109,6 @@
 							 _collectionPtr,
 							 nullptr);
 			if (pidCpu0 < 0 && pidCpuLOB == pid) {
-
 				/*
 				 * The Task is the last one running on this
 				 * CPU. Set the Pid of the bin. In this case
@@ -1119,11 +1145,13 @@
 	int lastPid(-1), b(0), boxH(_height * .3);
 	Rectangle taskBox;
 
-	/*
-	 * Start by drawing a line between the base points of the first and
-	 * the last bin.
-	 */
-	drawLine(_bins[0]._base, _bins[_size - 1]._base, {}, size);
+	if (_drawBase) {
+		/*
+		 * Start by drawing a line between the base points of the first and
+		 * the last bin.
+		 */
+		drawLine(_bins[0]._base, _bins[_size - 1]._base, {}, size);
+	}
 
 	/* Draw as vartical lines all bins containing data. */
 	for (int i = 0; i < _size; ++i)
@@ -1213,4 +1241,241 @@
 	}
 }
 
+ComboGraph::ComboGraph()
+: _histoPtr(nullptr),
+  _host(),
+  _guest()
+{
+	_init();
+}
+
+ComboGraph::ComboGraph(kshark_trace_histo *histo,
+		       KsPlot::ColorTable *bct,
+		       KsPlot::ColorTable *ect)
+: _histoPtr(histo),
+  _host(histo, bct, ect),
+  _guest(histo, bct, bct)
+{
+	_init();
+}
+
+void ComboGraph::_init()
+{
+	_guest.drawBase(false);
+	_guest.setZeroSuppressed(true);
+}
+
+/**
+ * @brief This function will set the Y (vertical) coordinate of the
+ *	  ComboGraph's base. It is safe to use this function even if the Graph
+ *	  contains data.
+ *
+ * @param b: Y coordinate of the Graph's base in pixels.
+ */
+void ComboGraph::setBase(int b)
+{
+	_guest.setBase(b);
+	_host.setBase(b + _host.height());
+}
+
+/**
+ * @brief Set the vertical size (height) of the ComboGraph.
+ *
+ * @param h: the height of the Graph in pixels.
+ */
+void ComboGraph::setHeight(int h)
+{
+	_host.setHeight(h / 2);
+	_guest.setHeight(h / 2);
+}
+
+void ComboGraph::fill(int sdHost, int pidHost, int sdGuest, int vcpu)
+{
+	kshark_context *kshark_ctx(nullptr);
+	struct kshark_data_stream *stream;
+	tep_event *event;
+
+	_sdHost = sdHost;
+	_pidHost = pidHost;
+	_vcpuEntryId = _vcpuExitId = -1;
+
+	if (!kshark_instance(&kshark_ctx))
+		return;
+
+	stream = kshark_get_data_stream(kshark_ctx, sdHost);
+	if (!stream)
+		return;
+
+	event = tep_find_event_by_name(stream->pevent,
+				       "kvm", "kvm_entry");
+	if (event)
+		_vcpuEntryId = event->id;
+
+	event = tep_find_event_by_name(stream->pevent,
+				       "kvm", "kvm_exit");
+	if (event)
+		_vcpuExitId = event->id;
+
+	_host.fillTaskGraph(sdHost, pidHost);
+	_guest.fillCPUGraph(sdGuest, vcpu);
+}
+
+bool kshark_match_event_id(struct kshark_context *kshark_ctx,
+			   struct kshark_entry *e, int sd, int *values)
+{
+	return e->stream_id == sd &&
+	       e->pid == values[0] &&
+	       e->event_id == values[1];
+}
+
+struct VirtBridge : public Shape {
+
+	Point _entryPointHost, _exitPointHost;
+
+	Point _entryPointGuest, _exitPointGuest;
+
+	void _draw(const Color &col, float size) const;
+};
+
+void VirtBridge::_draw(const Color &col, float size) const
+{
+	drawLine(_entryPointHost, _entryPointGuest, _color, _size);
+	drawLine(_entryPointGuest, _exitPointGuest, _color, _size + 1);
+	drawLine(_exitPointGuest, _exitPointHost, _color, _size);
+}
+
+struct VirtGap : public Shape {
+
+	Point _exitPoint, _entryPoint;
+
+	int _height;
+	void _draw(const Color &col, float size) const;
+};
+
+void VirtGap::_draw(const Color &col, float size) const
+{
+	if (_entryPoint.x() - _exitPoint.x() < 4)
+		return;
+
+	Point p0(_exitPoint.x() + _size, _exitPoint.y());
+	Point p1(_exitPoint.x() + _size, _exitPoint.y() - _height);
+	Point p2(_entryPoint.x() - _size , _entryPoint.y() );
+	Point p3(_entryPoint.x() -_size , _entryPoint.y() - _height);
+
+	Rectangle gap;
+
+	gap.setPoint(0, p0);
+	gap.setPoint(1, p1);
+	gap.setPoint(2, p2);
+	gap.setPoint(3, p3);
+
+	gap._color = {255, 255, 255};
+	gap.setFill(false);
+	gap.draw();
+}
+
+void ComboGraph::_drawBridge()
+{
+	const kshark_entry *entry, *exit;
+	ssize_t indexEntry, indexExit;
+	int values[2] = {_pidHost, -1};
+	VirtBridge bridge;
+	VirtGap gap;
+
+	bridge._entryPointHost = bridge._entryPointGuest
+		= _guest.getBin(0)._base;
+	bridge._size = 2;
+	bridge._visible = false;
+
+	gap._exitPoint = _guest.getBin(0)._base;
+	gap._size = 2;
+	gap._height = 10;
+	gap._visible = false;
+
+	auto lamStartBridg = [&] (int bin) {
+		bridge._entryPointHost = _host.getBin(bin)._base;
+		bridge._entryPointGuest = _guest.getBin(bin)._base;
+		bridge._color = _host.getBin(bin)._color;
+		bridge._visible = true;
+	};
+
+	auto lamCloseBridg = [&] (int bin) {
+		bridge._exitPointGuest = _guest.getBin(bin)._base;
+		bridge._exitPointHost = _host.getBin(bin)._base;
+		bridge._color = _host.getBin(bin)._color;
+		bridge._visible = true;
+		bridge.draw();
+		bridge._visible = false;
+	};
+
+	auto lamStartGap = [&] (int bin) {
+		gap._exitPoint = _guest.getBin(bin)._base;
+		gap._visible = true;
+	};
+
+	auto lamCloseGap = [&] (int bin) {
+		gap._entryPoint = _guest.getBin(bin)._base;
+		gap._visible = true;
+		gap.draw();
+		gap._visible = false;
+	};
+
+	for (int bin = 0; bin < _histoPtr->n_bins; ++bin) {
+		values[1] = _vcpuEntryId;
+		entry = ksmodel_get_entry_back(_histoPtr, bin, true,
+					       kshark_match_event_id,
+					       _sdHost, values,
+					       nullptr, &indexEntry);
+
+		values[1] = _vcpuExitId;
+		exit = ksmodel_get_entry_back(_histoPtr, bin, true,
+					      kshark_match_event_id,
+					      _sdHost, values,
+					      nullptr, &indexExit);
+
+		if (entry && !exit) {
+			lamStartBridg(bin);
+			lamCloseGap(bin);
+		}
+
+		if (exit && !entry) {
+			lamCloseBridg(bin);
+			lamStartGap(bin);
+		}
+
+		if (exit && entry) {
+			if (bridge._visible)
+				lamCloseBridg(bin);
+
+			if (gap._visible)
+				lamCloseGap(bin);
+
+			if (indexEntry > indexExit) {
+				lamStartBridg(bin);
+			} else {
+				lamStartBridg(bin);
+				lamCloseBridg(bin);
+				lamStartGap(bin);
+			}
+		}
+	}
+
+	bridge._exitPointGuest = bridge._exitPointHost =
+		_guest.getBin(_histoPtr->n_bins - 1)._base;
+	bridge.draw();
+}
+
+void ComboGraph::draw(float size)
+{
+	_host.draw();
+	_guest.draw();
+	_drawBridge();
+}
+
+void ComboGraph::setHMargin(int hMargin)
+{
+	_host.setHMargin(hMargin);
+	_guest.setHMargin(hMargin);
+}
+
 }; // KsPlot
diff --git a/kernel-shark/src/KsPlotTools.hpp b/kernel-shark/src/KsPlotTools.hpp
index fe01d45..24b75e8 100644
--- a/kernel-shark/src/KsPlotTools.hpp
+++ b/kernel-shark/src/KsPlotTools.hpp
@@ -318,14 +318,30 @@
 
 	void setY(int yA, int yB);
 
+	int cpuY() const {return _cpu.y();}
+
 	void setCPUY(int yCPU);
 
+	bool cpuIsVisible() const {return _cpu._visible;}
+
 	void setCPUVisible(bool v);
 
+	int taskY() const {return _task.y();}
+
 	void setTaskY(int yTask);
 
+	bool taskIsVisible() const {return _task._visible;}
+
 	void setTaskVisible(bool v);
 
+	int comboY() const {return _combo.y();}
+
+	void setComboY(int yCombo);
+
+	bool comboIsVisible() const {return _combo._visible;}
+
+	void setComboVisible(bool v);
+
 private:
 	void _draw(const Color &col, float size = 1.) const override;
 
@@ -340,6 +356,9 @@
 
 	/** A point indicating the position of the Mark in a Task graph. */
 	Point _task;
+
+	/** A point indicating the position of the Mark in a Combo graph. */
+	Point _combo;
 };
 
 /** This class represents a KernelShark graph's bin. */
@@ -396,7 +415,7 @@
 	 */
 	Graph(const Graph &) = delete;
 
-	/* Disable moving. Same as copying.*/
+	/* Disable moving. Same as copying. */
 	Graph(Graph &&) = delete;
 
 	Graph(kshark_trace_histo *histo, KsPlot::ColorTable *bct,
@@ -437,7 +456,7 @@
 	void setHeight(int h);
 
 	/** @brief Get the vertical size (height) of the Graph. */
-	int getHeight() const {return _height;}
+	int height() const {return _height;}
 
 	void setBinValue(int bin, int val);
 
@@ -468,7 +487,9 @@
 	 */
 	void setZeroSuppressed(bool zs) {_zeroSuppress = zs;}
 
-private:
+	void drawBase(bool b) {_drawBase = b;}
+
+protected:
 	/** Pointer to the model descriptor object. */
 	kshark_trace_histo	*_histoPtr;
 
@@ -498,9 +519,82 @@
 
 	bool			_zeroSuppress;
 
+	bool			_drawBase;
+
 	void _initBins();
 };
 
+class ComboGraph {
+public:
+	ComboGraph();
+
+	/*
+	 * Disable copying. We can enable the Copy Constructor in the future,
+	 * but only if we really need it for some reason.
+	 */
+	ComboGraph(const Graph &) = delete;
+
+	/* Disable moving. Same as copying. */
+	ComboGraph(ComboGraph &&) = delete;
+
+	ComboGraph(kshark_trace_histo *histo, KsPlot::ColorTable *bct,
+					      KsPlot::ColorTable *ect);
+
+	/* Keep this destructor virtual. */
+	virtual ~ComboGraph() {}
+
+	/**
+	 * @brief Provide the Host graph with a Data Collection. This
+	 *	  collection will be used to optimise the processing of the
+	 *	  content of the bins.
+	 *
+	 * @param col: Input location for the data collection descriptor.
+	 */
+	void setHostDataCollectionPtr(kshark_entry_collection *col) {
+		_host.setDataCollectionPtr(col);
+	}
+
+	/**
+	 * @brief Provide the Guest graph with a Data Collection. This
+	 *	  collection will be used to optimise the processing of the
+	 *	  content of the bins.
+	 *
+	 * @param col: Input location for the data collection descriptor.
+	 */
+	void setGuestDataCollectionPtr(kshark_entry_collection *col) {
+		_guest.setDataCollectionPtr(col);
+	}
+
+	void setBase(int b);
+
+	void setHeight(int h);
+
+	/** @brief Get the vertical size (height) of the Combo graph. */
+	int height() const {return _host.height() + _guest.height();}
+
+	void setHMargin(int hMargin);
+
+	void fill(int sdHost, int pidHost, int sdGuest, int vcpu);
+
+	void draw(float size = 1);
+
+private:
+	/** Pointer to the model descriptor object. */
+	kshark_trace_histo	*_histoPtr;
+
+	Graph _host, _guest;
+
+	void _init();
+
+	void _drawBridge();
+
+	int _sdHost;
+
+	int _pidHost;
+
+	int _vcpuEntryId, _vcpuExitId;
+};
+
 }; // KsPlot
 
 #endif  /* _KS_PLOT_TOOLS_H */
diff --git a/kernel-shark/src/KsQuickContextMenu.cpp b/kernel-shark/src/KsQuickContextMenu.cpp
index 6be818b..da20724 100644
--- a/kernel-shark/src/KsQuickContextMenu.cpp
+++ b/kernel-shark/src/KsQuickContextMenu.cpp
@@ -44,8 +44,8 @@
  * @param dm: The State machine of the Dual marker.
  * @param parent: The parent of this widget.
  */
-KsQuickContextMenu::KsQuickContextMenu(KsDataStore *data, size_t row,
-				       KsDualMarkerSM *dm,
+KsQuickContextMenu::KsQuickContextMenu(KsDualMarkerSM *dm,
+				       KsDataStore *data, size_t row,
 				       QWidget *parent)
 : KsQuickMarkerMenu(dm, parent),
   _data(data),
@@ -67,7 +67,7 @@
 	typedef void (KsQuickContextMenu::*mfp)();
 	QString taskName, parentName, descr;
 	KsTraceGraph *graphs;
-	int pid, cpu;
+	int pid, cpu, sd;
 
 	if (!parent || !_data)
 		return;
@@ -75,6 +75,7 @@
 	taskName = kshark_get_task_easy(_data->rows()[_row]);
 	pid = kshark_get_pid_easy(_data->rows()[_row]);
 	cpu = _data->rows()[_row]->cpu;
+	sd = _data->rows()[_row]->stream_id;
 
 	auto lamAddAction = [this, &descr] (QAction *action, mfp mf) {
 		action->setText(descr);
@@ -127,9 +128,6 @@
 	addSection("Pointer plot menu");
 
 	if (parentName == "KsTraceViewer") {
-		descr = QString("Show CPU [%1] only").arg(cpu);
-		lamAddAction(&_showCPUAction, &KsQuickContextMenu::_showCPU);
-
 		descr = "Add [";
 		descr += taskName;
 		descr += "-";
@@ -141,7 +139,7 @@
 
 	if (parentName == "KsTraceGraph" &&
 	    (graphs = dynamic_cast<KsTraceGraph *>(parent))) {
-		if (graphs->glPtr()->_taskList.contains(pid)) {
+		if (graphs->glPtr()->_streamPlots[sd]._taskList.contains(pid)) {
 			descr = "Remove [";
 			descr += taskName;
 			descr += "-";
@@ -159,7 +157,7 @@
 				     &KsQuickContextMenu::_addTaskPlot);
 		}
 
-		if (graphs->glPtr()->_cpuList.contains(cpu)) {
+		if (graphs->glPtr()->_streamPlots[sd]._cpuList.contains(cpu)) {
 			descr = "Remove [CPU ";
 			descr += QString("%1").arg(cpu);
 			descr += "] plot";
@@ -178,62 +176,67 @@
 void KsQuickContextMenu::_hideTask()
 {
 	int pid = kshark_get_pid_easy(_data->rows()[_row]);
+	int sd = _data->rows()[_row]->stream_id;
 	kshark_context *kshark_ctx(nullptr);
 	QVector<int> vec;
 
 	if (!kshark_instance(&kshark_ctx))
 		return;
 
-	vec =_getFilterVector(kshark_ctx->hide_task_filter, pid);
-	_data->applyNegTaskFilter(vec);
+	vec =_getFilterVector(kshark_ctx->stream[sd]->hide_task_filter, pid);
+	_data->applyNegTaskFilter(sd, vec);
 }
 
 void KsQuickContextMenu::_showTask()
 {
 	int pid = kshark_get_pid_easy(_data->rows()[_row]);
-
-	_data->applyPosTaskFilter(QVector<int>(1, pid));
+	int sd = _data->rows()[_row]->stream_id;
+	_data->applyPosTaskFilter(sd, QVector<int>(1, pid));
 }
 
 void KsQuickContextMenu::_hideEvent()
 {
 	int eventId = kshark_get_event_id_easy(_data->rows()[_row]);
+	int sd = _data->rows()[_row]->stream_id;
 	kshark_context *kshark_ctx(nullptr);
 	QVector<int> vec;
 
 	if (!kshark_instance(&kshark_ctx))
 		return;
 
-	vec =_getFilterVector(kshark_ctx->hide_event_filter, eventId);
-	_data->applyNegEventFilter(vec);
+	vec =_getFilterVector(kshark_ctx->stream[sd]->hide_event_filter, eventId);
+	_data->applyNegEventFilter(sd, vec);
 }
 
 void KsQuickContextMenu::_showEvent()
 {
 	int eventId = kshark_get_event_id_easy(_data->rows()[_row]);
+	int sd = _data->rows()[_row]->stream_id;
 
-	_data->applyPosEventFilter(QVector<int>(1, eventId));
+	_data->applyPosEventFilter(sd, QVector<int>(1, eventId));
 }
 
 void KsQuickContextMenu::_showCPU()
 {
 	int cpu = _data->rows()[_row]->cpu;
+	int sd = _data->rows()[_row]->stream_id;
 
-	_data->applyPosCPUFilter(QVector<int>(1, cpu));
+	_data->applyPosCPUFilter(sd, QVector<int>(1, cpu));
 }
 
 void KsQuickContextMenu::_hideCPU()
 {
+	int sd = _data->rows()[_row]->stream_id;
 	kshark_context *kshark_ctx(nullptr);
 	QVector<int> vec;
 
 	if (!kshark_instance(&kshark_ctx))
 		return;
 
-	vec =_getFilterVector(kshark_ctx->hide_cpu_filter,
+	vec =_getFilterVector(kshark_ctx->stream[sd]->hide_cpu_filter,
 			      _data->rows()[_row]->cpu);
 
-	_data->applyNegCPUFilter(vec);
+	_data->applyNegCPUFilter(sd, vec);
 }
 
 QVector<int> KsQuickContextMenu::_getFilterVector(tracecmd_filter_id *filter, int newId)
@@ -248,25 +251,27 @@
 void KsQuickContextMenu::_addTaskPlot()
 {
 	int pid = kshark_get_pid_easy(_data->rows()[_row]);
+	int sd = _data->rows()[_row]->stream_id;
 
-	emit addTaskPlot(pid);
+	emit addTaskPlot(sd, pid);
 }
 
 void KsQuickContextMenu::_addCPUPlot()
 {
-	emit addCPUPlot(_data->rows()[_row]->cpu);
+	emit addCPUPlot(_data->rows()[_row]->stream_id, _data->rows()[_row]->cpu);
 }
 
 void KsQuickContextMenu::_removeTaskPlot()
 {
 	int pid = kshark_get_pid_easy(_data->rows()[_row]);
+	int sd = _data->rows()[_row]->stream_id;
 
-	emit removeTaskPlot(pid);
+	emit removeTaskPlot(sd, pid);
 }
 
 void KsQuickContextMenu::_removeCPUPlot()
 {
-	emit removeCPUPlot(_data->rows()[_row]->cpu);
+	emit removeCPUPlot(_data->rows()[_row]->stream_id, _data->rows()[_row]->cpu);
 }
 
 /**
@@ -275,10 +280,11 @@
  * @param dm: The State machine of the Dual marker.
  * @param parent: The parent of this widget.
  */
-KsRmPlotContextMenu::KsRmPlotContextMenu(KsDualMarkerSM *dm,
+KsRmPlotContextMenu::KsRmPlotContextMenu(KsDualMarkerSM *dm, int sd,
 					 QWidget *parent)
 : KsQuickMarkerMenu(dm, parent),
-  _removePlotAction(this)
+  _removePlotAction(this),
+  _sd(sd)
 {
 	addSection("Plots");
 
@@ -295,9 +301,9 @@
  * @param cpu : CPU Id.
  * @param parent: The parent of this widget.
  */
-KsRmCPUPlotMenu::KsRmCPUPlotMenu(KsDualMarkerSM *dm, int cpu,
+KsRmCPUPlotMenu::KsRmCPUPlotMenu(KsDualMarkerSM *dm, int sd, int cpu,
 				 QWidget *parent)
-: KsRmPlotContextMenu(dm, parent)
+: KsRmPlotContextMenu(dm, sd, parent)
 {
 	_removePlotAction.setText(QString("Remove [CPU %1]").arg(cpu));
 }
@@ -309,9 +315,9 @@
  * @param pid: Process Id.
  * @param parent: The parent of this widget.
  */
-KsRmTaskPlotMenu::KsRmTaskPlotMenu(KsDualMarkerSM *dm, int pid,
+KsRmTaskPlotMenu::KsRmTaskPlotMenu(KsDualMarkerSM *dm, int sd, int pid,
 				   QWidget *parent)
-: KsRmPlotContextMenu(dm, parent)
+: KsRmPlotContextMenu(dm, sd, parent)
 {
 	kshark_context *kshark_ctx(nullptr);
 	QString descr("Remove [ ");
@@ -319,7 +325,7 @@
 	if (!kshark_instance(&kshark_ctx))
 		return;
 
-	descr += tep_data_comm_from_pid(kshark_ctx->pevent, pid);
+	descr += tep_data_comm_from_pid(kshark_ctx->stream[_sd]->pevent, pid);
 	descr += "-";
 	descr += QString("%1").arg(pid);
 	descr += "] plot";
diff --git a/kernel-shark/src/KsQuickContextMenu.hpp b/kernel-shark/src/KsQuickContextMenu.hpp
index df8a65b..5976c17 100644
--- a/kernel-shark/src/KsQuickContextMenu.hpp
+++ b/kernel-shark/src/KsQuickContextMenu.hpp
@@ -29,7 +29,7 @@
 	/** Signal to deselect the active marker. */
 	void deselect();
 
-private:
+protected:
 	KsDualMarkerSM	*_dm;
 
 	QAction _deselectAction;
@@ -45,22 +45,22 @@
 public:
 	KsQuickContextMenu() = delete;
 
-	KsQuickContextMenu(KsDataStore *data, size_t row,
-			   KsDualMarkerSM *dm,
+	KsQuickContextMenu(KsDualMarkerSM *dm,
+			   KsDataStore *data, size_t row,
 			   QWidget *parent = nullptr);
 
 signals:
 	/** Signal to add a task plot. */
-	void addTaskPlot(int);
+	void addTaskPlot(int sd, int pid);
 
 	/** Signal to add a CPU plot. */
-	void addCPUPlot(int);
+	void addCPUPlot(int sd, int pid);
 
 	/** Signal to remove a task plot. */
-	void removeTaskPlot(int);
+	void removeTaskPlot(int sd, int pid);
 
 	/** Signal to remove a CPU plot. */
-	void removeCPUPlot(int);
+	void removeCPUPlot(int sd, int pid);
 
 private:
 	void _hideTask();
@@ -108,6 +108,7 @@
 	QAction _removeTaskPlotAction;
 
 	QAction _clearAllFilters;
+
 };
 
 /**
@@ -118,7 +119,8 @@
 public:
 	KsRmPlotContextMenu() = delete;
 
-	KsRmPlotContextMenu(KsDualMarkerSM *dm, QWidget *parent = nullptr);
+	KsRmPlotContextMenu(KsDualMarkerSM *dm, int sd,
+			    QWidget *parent = nullptr);
 
 signals:
 	/** Signal to remove a plot. */
@@ -127,13 +129,16 @@
 protected:
 	/** Menu action. */
 	QAction _removePlotAction;
+
+	/** . */
+	int _sd;
 };
 
 /**
  * The KsQuickMarkerMenu class provides CPU Plot remove menus.
  */
 struct KsRmCPUPlotMenu : public KsRmPlotContextMenu {
-	KsRmCPUPlotMenu(KsDualMarkerSM *dm, int cpu,
+	KsRmCPUPlotMenu(KsDualMarkerSM *dm, int sd, int cpu,
 			QWidget *parent = nullptr);
 };
 
@@ -141,7 +146,7 @@
  * The KsQuickMarkerMenu class provides Task Plot remove menus.
  */
 struct KsRmTaskPlotMenu : public KsRmPlotContextMenu {
-	KsRmTaskPlotMenu(KsDualMarkerSM *dm, int pid,
+	KsRmTaskPlotMenu(KsDualMarkerSM *dm, int sd, int pid,
 			 QWidget *parent = nullptr);
 };
 
diff --git a/kernel-shark/src/KsSession.cpp b/kernel-shark/src/KsSession.cpp
index 9d86776..0ea3675 100644
--- a/kernel-shark/src/KsSession.cpp
+++ b/kernel-shark/src/KsSession.cpp
@@ -92,29 +92,32 @@
 QString KsSession::getDataFile(kshark_context *kshark_ctx)
 {
 	kshark_config_doc *file = kshark_config_alloc(KS_CONFIG_JSON);
-	const char *file_str;
+	int sd;
 
 	if (!kshark_config_doc_get(_config, "Data", file))
 		return QString();
 
-	file_str = kshark_import_trace_file(kshark_ctx, file);
-	if (file_str)
-		return QString(file_str);
+	sd = kshark_import_trace_file(kshark_ctx, file);
+	if (sd)
+		return QString(kshark_ctx->stream[sd]->file);
 
 	return QString();
 }
 
 /**
- * @brief Save the configuration of the filters.
+ * @brief Save the configuration of the filters to file.
  *
  * @param kshark_ctx: Input location for context pointer.
+ * @param fileName: The name of the file.
  */
-void KsSession::saveFilters(kshark_context *kshark_ctx)
+void KsSession::saveFilters(kshark_context *kshark_ctx,
+			    const QString &fileName)
 {
-	kshark_config_doc *filters =
-		kshark_export_all_filters(kshark_ctx, KS_CONFIG_JSON);
+	kshark_config_doc *conf(nullptr);
 
-	kshark_config_doc_add(_config, "Filters", filters);
+	kshark_export_all_dstreams(kshark_ctx, &conf);
+	kshark_save_config_file(fileName.toStdString().c_str(), conf);
+	kshark_free_config_doc(conf);
 }
 
 /**
@@ -123,25 +126,92 @@
  * @param kshark_ctx: Input location for context pointer.
  * @param data: Input location for KsDataStore object;
  */
-void KsSession::loadFilters(kshark_context *kshark_ctx, KsDataStore *data)
+void KsSession::loadFilters(kshark_context *kshark_ctx,
+			    const QString &fileName,
+			    KsDataStore *data)
 {
-	kshark_config_doc *filters = kshark_config_alloc(KS_CONFIG_JSON);
+	json_object *jconf, *jall_stream, *jstream, *jdata, *jfile, *jfilters;
+	kshark_config_doc *conf =
+		kshark_open_config_file(fileName.toStdString().c_str(),
+				        "kshark.config.filter");
+	int n, sd, *streamIds = kshark_all_streams(kshark_ctx);
+	bool doReload(false);
 
-	if (!kshark_config_doc_get(_config, "Filters", filters))
+	if (!conf)
 		return;
 
-	kshark_import_all_filters(kshark_ctx, filters);
+	if (conf->format != KS_CONFIG_JSON)
+		return;
 
-	if (kshark_ctx->advanced_event_filter->filters)
+	jconf = KS_JSON_CAST(conf->conf_doc);
+
+	if (!json_object_object_get_ex(jconf, "data streams", &jall_stream))
+		return;
+
+	if (json_object_get_type(jall_stream) != json_type_array)
+		return;
+
+	n = json_object_array_length(jall_stream);
+	for (int i = 0; i < kshark_ctx->n_streams; ++i) {
+		sd = streamIds[i];
+		for (int j = 0; j < n; ++j) {
+			char abs_path[FILENAME_MAX];
+			const char *file;
+
+			jstream = json_object_array_get_idx(jall_stream, j);
+			json_object_object_get_ex(jstream, "data", &jdata);
+			json_object_object_get_ex(jdata, "file", &jfile);
+			file = json_object_get_string(jfile);
+
+			if (!realpath(kshark_ctx->stream[sd]->file, abs_path))
+				continue;
+
+			if (strcmp(file, abs_path) == 0) {
+				json_object_object_get_ex(jstream,
+							  "filters",
+							  &jfilters);
+
+				kshark_config_doc *filters =
+					kshark_json_to_conf(jfilters);
+
+				kshark_import_all_filters(kshark_ctx,
+							  sd, filters);
+				kshark_free_config_doc(filters);
+			}
+		}
+
+		if (kshark_ctx->stream[sd]->advanced_event_filter->filters)
+			doReload = true;
+	}
+
+	if (doReload)
 		data->reload();
 	else
-		kshark_filter_entries(kshark_ctx, data->rows(), data->size());
-
-	data->registerCPUCollections();
+		data->update();
 
 	emit data->updateWidgets(data);
 }
 
+void KsSession::saveDataStreams(kshark_context *kshark_ctx)
+{
+	kshark_export_all_dstreams(kshark_ctx, &_config);
+}
+
+void KsSession::loadDataStreams(kshark_context *kshark_ctx, KsDataStore *data)
+{
+	bool ret;
+
+	ret = kshark_import_all_dstreams(kshark_ctx, _config,
+					 data->rows_r(), data->size_r());
+
+	if (!ret) {
+		data->clear();
+		return;
+	}
+
+	data->registerCPUCollections();
+}
+
 /**
  * @brief Save the state of the table.
  *
@@ -152,7 +222,7 @@
 	int64_t r = view.getTopRow();
 
 	topRow->conf_doc = json_object_new_int64(r);
-	kshark_config_doc_add(_config, "ViewTop",topRow);
+	kshark_config_doc_add(_config, "ViewTop", topRow);
 }
 
 /**
@@ -309,10 +379,16 @@
  *
  * @param glw: Input location for the KsGLWidget widget.
  */
-void KsSession::saveGraphs(const KsGLWidget &glw)
+void KsSession::saveGraphs(kshark_context *kshark_ctx,
+			   KsTraceGraph &graphs)
 {
-	_saveCPUPlots(glw._cpuList);
-	_saveTaskPlots(glw._taskList);
+	int *streamIds = kshark_all_streams(kshark_ctx);
+	for (int i = 0; i < kshark_ctx->n_streams; ++i) {
+		_saveCPUPlots(streamIds[i], graphs.glPtr());
+		_saveTaskPlots(streamIds[i], graphs.glPtr());
+	}
+
+	_saveComboPlots(graphs.glPtr());
 }
 
 /**
@@ -320,84 +396,199 @@
  *
  * @param graphs: Input location for the KsTraceGraph widget.
  */
-void KsSession::loadGraphs(KsTraceGraph *graphs)
+void KsSession::loadGraphs(kshark_context *kshark_ctx,
+			   KsTraceGraph &graphs)
 {
-	graphs->cpuReDraw(_getCPUPlots());
-	graphs->taskReDraw(_getTaskPlots());
-}
+	int sd, *streamIds = kshark_all_streams(kshark_ctx);
 
-void KsSession::_saveCPUPlots(const QVector<int> &cpus)
-{
-	kshark_config_doc *cpuPlts = kshark_config_alloc(KS_CONFIG_JSON);
-	json_object *jcpus = json_object_new_array();
-
-	for (int i = 0; i < cpus.count(); ++i) {
-		json_object *jcpu = json_object_new_int(cpus[i]);
-		json_object_array_put_idx(jcpus, i, jcpu);
+	for (int i = 0; i < kshark_ctx->n_streams; ++i) {
+		sd = streamIds[i];
+		graphs.cpuReDraw(sd, _getCPUPlots(sd));
+		graphs.taskReDraw(sd, _getTaskPlots(sd));
 	}
 
-	cpuPlts->conf_doc = jcpus;
-	kshark_config_doc_add(_config, "CPUPlots", cpuPlts);
+	QVector<QVector<int>> combos = _getComboPlots();
+	for (auto const &cp: combos) {
+		graphs.comboReDraw(0, cp);
+	}
 }
 
-QVector<int> KsSession::_getCPUPlots()
+void KsSession::_savePlots(int sd, KsGLWidget *glw, bool cpu)
 {
-	kshark_config_doc *cpuPlts = kshark_config_alloc(KS_CONFIG_JSON);
-	json_object *jcpus;
-	QVector<int> cpus;
-	size_t length;
+	kshark_config_doc *streamsConf = kshark_config_alloc(KS_CONFIG_JSON);
+	json_object *jallStreams, *jstream, *jstreamId, *jplots;
+	QVector<int> plotIds;
+	int nStreams;
 
-	if (!kshark_config_doc_get(_config, "CPUPlots", cpuPlts))
-		return cpus;
+	if (cpu) {
+		plotIds = glw->_streamPlots[sd]._cpuList;
+	} else {
+		plotIds = glw->_streamPlots[sd]._taskList;
+	}
+
+	if (!kshark_config_doc_get(_config, "data streams", streamsConf) ||
+	    streamsConf->format != KS_CONFIG_JSON)
+		return;
+
+	jallStreams = KS_JSON_CAST(streamsConf->conf_doc);
+	if (json_object_get_type(jallStreams) != json_type_array)
+		return;
+
+	nStreams = json_object_array_length(jallStreams);
+	for (int i = 0; i < nStreams; ++i) {
+		jstream = json_object_array_get_idx(jallStreams, i);
+		if (json_object_object_get_ex(jstream, "stream id", &jstreamId) &&
+		    json_object_get_int(jstreamId) == sd)
+			break;
+
+		jstream = nullptr;
+	}
+
+	if (!jstream)
+		return;
+
+	jplots = json_object_new_array();
+	for (int i = 0; i < plotIds.count(); ++i) {
+		json_object *jcpu = json_object_new_int(plotIds[i]);
+		json_object_array_put_idx(jplots, i, jcpu);
+	}
+
+	if (cpu)
+		json_object_object_add(jstream, "CPUPlots", jplots);
+	else
+		json_object_object_add(jstream, "TaskPlots", jplots);
+}
+
+void KsSession::_saveCPUPlots(int sd, KsGLWidget *glw)
+{
+	_savePlots(sd, glw, true);
+}
+
+void KsSession::_saveTaskPlots(int sd, KsGLWidget *glw)
+{
+	_savePlots(sd, glw, false);
+}
+
+void KsSession::_saveComboPlots(KsGLWidget *glw)
+{
+	kshark_config_doc *combos = kshark_config_alloc(KS_CONFIG_JSON);
+	int var, nCombos = glw->_comboPlots.count();
+	json_object *jcombo, *jplots;
+
+	if (!nCombos)
+		return;
+
+	jplots = json_object_new_array();
+	for (int i = 0; i < nCombos; ++i) {
+		jcombo = json_object_new_array();
+
+		var = glw->_comboPlots[i]._hostStreamId;
+		json_object_array_put_idx(jcombo, 0, json_object_new_int(var));
+
+		var = glw->_comboPlots[i]._hostPid;
+		json_object_array_put_idx(jcombo, 1, json_object_new_int(var));
+
+		var = glw->_comboPlots[i]._guestStreamId;
+		json_object_array_put_idx(jcombo, 2, json_object_new_int(var));
+
+		var = glw->_comboPlots[i]._vcpu;
+		json_object_array_put_idx(jcombo, 3, json_object_new_int(var));
+
+		json_object_array_put_idx(jplots, i, jcombo);
+	}
+
+	combos->conf_doc = jplots;
+	kshark_config_doc_add(_config, "ComboPlots", combos);
+}
+
+QVector<int> KsSession::_getPlots(int sd, bool cpu)
+{
+	kshark_config_doc *streamsConf = kshark_config_alloc(KS_CONFIG_JSON);
+	json_object *jallStreams, *jstream, *jstreamId, *jplots;
+	int nStreams, nPlots, id;
+	const char *plotKey;
+	QVector<int> plots;
+
+	if (!kshark_config_doc_get(_config, "data streams", streamsConf) ||
+	    streamsConf->format != KS_CONFIG_JSON)
+		return plots;
+
+	jallStreams = KS_JSON_CAST(streamsConf->conf_doc);
+	if (json_object_get_type(jallStreams) != json_type_array)
+		return plots;
+
+	nStreams = json_object_array_length(jallStreams);
+	for (int i = 0; i < nStreams; ++i) {
+		jstream = json_object_array_get_idx(jallStreams, i);
+		if (json_object_object_get_ex(jstream, "stream id", &jstreamId) &&
+		    json_object_get_int(jstreamId) == sd)
+			break;
+
+		jstream = nullptr;
+	}
+
+	if (!jstream)
+		return plots;
+
+	if (cpu)
+		plotKey = "CPUPlots";
+	else
+		plotKey = "TaskPlots";
+
+	if (!json_object_object_get_ex(jstream, plotKey, &jplots) ||
+	    json_object_get_type(jplots) != json_type_array)
+		return plots;
+
+	nPlots = json_object_array_length(jplots);
+	for (int i = 0; i < nPlots; ++i) {
+		id = json_object_get_int(json_object_array_get_idx(jplots, i));
+		plots.append(id);
+	}
+
+	return plots;
+}
+
+QVector<int> KsSession::_getCPUPlots(int sd)
+{
+	return _getPlots(sd, true);
+}
+
+QVector<int> KsSession::_getTaskPlots(int sd)
+{
+	return _getPlots(sd, false);
+}
+
+QVector<QVector<int>> KsSession::_getComboPlots()
+{
+	kshark_config_doc *combos = kshark_config_alloc(KS_CONFIG_JSON);
+	int nPlots, sdHost, sdGuest, pidHost, vcpu;
+	json_object *jplots, *jcombo;
+	QVector<QVector<int>> vec;
+
+	if (!kshark_config_doc_get(_config, "ComboPlots", combos))
+		return vec;
 
 	if (_config->format == KS_CONFIG_JSON) {
-		jcpus = KS_JSON_CAST(cpuPlts->conf_doc);
-		length = json_object_array_length(jcpus);
-		for (size_t i = 0; i < length; ++i) {
-			int cpu = json_object_get_int(json_object_array_get_idx(jcpus,
-										i));
-			cpus.append(cpu);
+		jplots = KS_JSON_CAST(combos->conf_doc);
+		if (json_object_get_type(jplots) != json_type_array)
+			return vec;
+
+		nPlots = json_object_array_length(jplots);
+		for (int i = 0; i < nPlots; ++i) {
+			jcombo = json_object_array_get_idx(jplots, i);
+			if (json_object_get_type(jcombo) != json_type_array)
+				return vec;
+
+			sdHost = json_object_get_int(json_object_array_get_idx(jcombo, 0));
+			pidHost = json_object_get_int(json_object_array_get_idx(jcombo, 1));
+			sdGuest = json_object_get_int(json_object_array_get_idx(jcombo, 2));
+			vcpu = json_object_get_int(json_object_array_get_idx(jcombo, 3));
+
+			vec.append({sdHost, pidHost, sdGuest, vcpu});
 		}
 	}
 
-	return cpus;
-}
-
-void KsSession::_saveTaskPlots(const QVector<int> &tasks)
-{
-	kshark_config_doc *taskPlts = kshark_config_alloc(KS_CONFIG_JSON);
-	json_object *jtasks = json_object_new_array();
-
-	for (int i = 0; i < tasks.count(); ++i) {
-		json_object *jtask = json_object_new_int(tasks[i]);
-		json_object_array_put_idx(jtasks, i, jtask);
-	}
-
-	taskPlts->conf_doc = jtasks;
-	kshark_config_doc_add(_config, "TaskPlots", taskPlts);
-}
-
-QVector<int> KsSession::_getTaskPlots()
-{
-	kshark_config_doc *taskPlts = kshark_config_alloc(KS_CONFIG_JSON);
-	json_object *jtasks;
-	QVector<int> tasks;
-	size_t length;
-
-	if (!kshark_config_doc_get(_config, "TaskPlots", taskPlts))
-		return tasks;
-
-	if (_config->format == KS_CONFIG_JSON) {
-		jtasks = KS_JSON_CAST(taskPlts->conf_doc);
-		length = json_object_array_length(jtasks);
-		for (size_t i = 0; i < length; ++i) {
-			int pid = json_object_get_int(json_object_array_get_idx(jtasks,
-										i));
-			tasks.append(pid);
-		}
-	}
-
-	return tasks;
+	return vec;
 }
 
 /**
@@ -531,31 +722,31 @@
  */
 void KsSession::savePlugins(const KsPluginManager &pm)
 {
-	struct kshark_config_doc *plugins =
-		kshark_config_new("kshark.config.plugins", KS_CONFIG_JSON);
-	json_object *jplugins = KS_JSON_CAST(plugins->conf_doc);
-	const QVector<bool> &registeredPlugins = pm._registeredKsPlugins;
-	const QStringList &pluginList = pm._ksPluginList;
-	int nPlugins = pluginList.length();
-	json_object *jlist, *jpl;
-	QByteArray array;
-	char* buffer;
-	bool active;
-
-	jlist = json_object_new_array();
-	for (int i = 0; i < nPlugins; ++i) {
-		array = pluginList[i].toLocal8Bit();
-		buffer = array.data();
-		jpl = json_object_new_array();
-		json_object_array_put_idx(jpl, 0, json_object_new_string(buffer));
-
-		active = registeredPlugins[i];
-		json_object_array_put_idx(jpl, 1, json_object_new_boolean(active));
-		json_object_array_put_idx(jlist, i, jpl);
-	}
-
-	json_object_object_add(jplugins, "Plugin List", jlist);
-	kshark_config_doc_add(_config, "Plugins", plugins);
+// 	struct kshark_config_doc *plugins =
+// 		kshark_config_new("kshark.config.plugins", KS_CONFIG_JSON);
+// 	json_object *jplugins = KS_JSON_CAST(plugins->conf_doc);
+// 	const QVector<bool> &registeredPlugins = pm._registeredKsPlugins;
+// 	const QStringList &pluginList = pm._ksPluginList;
+// 	int nPlugins = pluginList.length();
+// 	json_object *jlist, *jpl;
+// 	QByteArray array;
+// 	char* buffer;
+// 	bool active;
+// 
+// 	jlist = json_object_new_array();
+// 	for (int i = 0; i < nPlugins; ++i) {
+// 		array = pluginList[i].toLocal8Bit();
+// 		buffer = array.data();
+// 		jpl = json_object_new_array();
+// 		json_object_array_put_idx(jpl, 0, json_object_new_string(buffer));
+// 
+// 		active = registeredPlugins[i];
+// 		json_object_array_put_idx(jpl, 1, json_object_new_boolean(active));
+// 		json_object_array_put_idx(jlist, i, jpl);
+// 	}
+// 
+// 	json_object_object_add(jplugins, "Plugin List", jlist);
+// 	kshark_config_doc_add(_config, "Plugins", plugins);
 }
 
 /**
@@ -566,35 +757,35 @@
  */
 void KsSession::loadPlugins(kshark_context *kshark_ctx, KsPluginManager *pm)
 {
-	kshark_config_doc *plugins = kshark_config_alloc(KS_CONFIG_JSON);
-	json_object *jplugins, *jlist, *jpl;
-	const char *pluginName;
-	QVector<int> pluginIds;
-	int length, index;
-	bool loaded;
-
-	if (!kshark_config_doc_get(_config, "Plugins", plugins) ||
-	    !kshark_type_check(plugins, "kshark.config.plugins"))
-		return;
-
-	if (plugins->format == KS_CONFIG_JSON) {
-		jplugins = KS_JSON_CAST(plugins->conf_doc);
-		json_object_object_get_ex(jplugins, "Plugin List", &jlist);
-		if (!jlist ||
-	            json_object_get_type(jlist) != json_type_array ||
-		    !json_object_array_length(jlist))
-			return;
-
-		length = json_object_array_length(jlist);
-		for (int i = 0; i < length; ++i) {
-			jpl = json_object_array_get_idx(jlist, i);
-			pluginName = json_object_get_string(json_object_array_get_idx(jpl, 0));
-			index = pm->_ksPluginList.indexOf(pluginName);
-			loaded = json_object_get_boolean(json_object_array_get_idx(jpl, 1));
-			if (index >= 0 && loaded)
-				pluginIds.append(index);
-		}
-	}
-
-	pm->updatePlugins(pluginIds);
+// 	kshark_config_doc *plugins = kshark_config_alloc(KS_CONFIG_JSON);
+// 	json_object *jplugins, *jlist, *jpl;
+// 	const char *pluginName;
+// 	QVector<int> pluginIds;
+// 	int length, index;
+// 	bool loaded;
+// 
+// 	if (!kshark_config_doc_get(_config, "Plugins", plugins) ||
+// 	    !kshark_type_check(plugins, "kshark.config.plugins"))
+// 		return;
+// 
+// 	if (plugins->format == KS_CONFIG_JSON) {
+// 		jplugins = KS_JSON_CAST(plugins->conf_doc);
+// 		json_object_object_get_ex(jplugins, "Plugin List", &jlist);
+// 		if (!jlist ||
+// 	            json_object_get_type(jlist) != json_type_array ||
+// 		    !json_object_array_length(jlist))
+// 			return;
+// 
+// 		length = json_object_array_length(jlist);
+// 		for (int i = 0; i < length; ++i) {
+// 			jpl = json_object_array_get_idx(jlist, i);
+// 			pluginName = json_object_get_string(json_object_array_get_idx(jpl, 0));
+// 			index = pm->_ksPluginList.indexOf(pluginName);
+// 			loaded = json_object_get_boolean(json_object_array_get_idx(jpl, 1));
+// 			if (index >= 0 && loaded)
+// 				pluginIds.append(index);
+// 		}
+// 	}
+// 
+// 	pm->updatePlugins(pluginIds);
 }
diff --git a/kernel-shark/src/KsSession.hpp b/kernel-shark/src/KsSession.hpp
index b07c810..44ac2e1 100644
--- a/kernel-shark/src/KsSession.hpp
+++ b/kernel-shark/src/KsSession.hpp
@@ -49,13 +49,20 @@
 
 	void loadVisModel(KsGraphModel *model);
 
-	void saveGraphs(const KsGLWidget &glw);
+	void saveGraphs(kshark_context *kshark_ctx,
+			KsTraceGraph &graphs);
 
-	void loadGraphs(KsTraceGraph *graphs);
+	void loadGraphs(kshark_context *kshark_ctx,
+			KsTraceGraph &graphs);
 
-	void saveFilters(kshark_context *kshark_ctx);
+	void saveFilters(kshark_context *kshark_ctx, const QString &fileName);
 
-	void loadFilters(kshark_context *kshark_ctx, KsDataStore *data);
+	void loadFilters(kshark_context *kshark_ctx, const QString &fileName,
+						     KsDataStore *data);
+
+	void saveDataStreams(kshark_context *kshark_ctx);
+
+	void loadDataStreams(kshark_context *kshark_ctx, KsDataStore *data);
 
 	void saveMainWindowSize(const QMainWindow &window);
 
@@ -86,13 +93,21 @@
 
 	json_object *_getMarkerJson();
 
-	void _saveCPUPlots(const QVector<int> &cpus);
+	void _savePlots(int sd, KsGLWidget *glw, bool cpu);
 
-	QVector<int> _getCPUPlots();
+	QVector<int> _getPlots(int sd, bool cpu);
 
-	void _saveTaskPlots(const QVector<int> &tasks);
+	void _saveCPUPlots(int sd, KsGLWidget *glw);
 
-	QVector<int> _getTaskPlots();
+	QVector<int> _getCPUPlots(int sd);
+
+	void _saveTaskPlots(int sd, KsGLWidget *glw);
+
+	QVector<int> _getTaskPlots(int sd);
+
+	void _saveComboPlots(KsGLWidget *glw);
+
+	QVector<QVector<int>> _getComboPlots();
 
 	bool _getMarker(const char* name, size_t *pos);
 
diff --git a/kernel-shark/src/KsTraceGraph.cpp b/kernel-shark/src/KsTraceGraph.cpp
index 6087e96..dd3f9bd 100644
--- a/kernel-shark/src/KsTraceGraph.cpp
+++ b/kernel-shark/src/KsTraceGraph.cpp
@@ -271,8 +271,9 @@
 	_keyPressed = false;
 }
 
-void KsTraceGraph::_resetPointer(uint64_t ts, int cpu, int pid)
+void KsTraceGraph::_resetPointer(uint64_t ts, int sd, int cpu, int pid)
 {
+	struct kshark_data_stream *stream;
 	uint64_t sec, usec;
 	QString pointer;
 
@@ -286,7 +287,11 @@
 		if (!kshark_instance(&kshark_ctx))
 			return;
 
-		QString comm(tep_data_comm_from_pid(kshark_ctx->pevent, pid));
+		stream = kshark_get_data_stream(kshark_ctx, sd);
+		if (!stream)
+			return;
+
+		QString comm(tep_data_comm_from_pid(stream->pevent, pid));
 		comm.append("-");
 		comm.append(QString("%1").arg(pid));
 		_labelI1.setText(comm);
@@ -309,7 +314,7 @@
 	QString info(kshark_get_info_easy(e));
 	QString comm(kshark_get_task_easy(e));
 	QString pointer, elidedText;
-	int labelWidth, width;
+	int labelWidth;
 	uint64_t sec, usec;
 
 	kshark_convert_nano(e->ts, &sec, &usec);
@@ -335,16 +340,7 @@
 	 * The Info string is too long and cannot be displayed on the toolbar.
 	 * Try to fit the text in the available space.
 	 */
-	QFontMetrics metrix(_labelI5.font());
-	width = labelWidth - FONT_WIDTH * 3;
-	elidedText = metrix.elidedText(info, Qt::ElideRight, width);
-
-	while(labelWidth < STRING_WIDTH(elidedText) + FONT_WIDTH * 5) {
-		width -= FONT_WIDTH * 3;
-		elidedText = metrix.elidedText(info, Qt::ElideRight, width);
-	}
-
-	_labelI5.setText(elidedText);
+	KsUtils::setElidedText(&_labelI5, info, Qt::ElideRight, labelWidth);
 	_labelI5.setVisible(true);
 	QCoreApplication::processEvents();
 }
@@ -356,55 +352,44 @@
  */
 void KsTraceGraph::markEntry(size_t row)
 {
-	int graph, cpuGrId, taskGrId;
-
-	_glWindow.findGraphIds(*_data->rows()[row], &cpuGrId, &taskGrId);
+	int yPosVis(-1);
 
 	/*
 	 * If a Task graph has been found, this Task graph will be
 	 * visible. If no Task graph has been found, make visible
 	 * the corresponding CPU graph.
 	 */
-	if (taskGrId >= 0)
-		graph = taskGrId;
-	else
-		graph = cpuGrId;
+	if (_mState->activeMarker()._mark.comboIsVisible())
+		yPosVis = _mState->activeMarker()._mark.comboY();
+	else if (_mState->activeMarker()._mark.taskIsVisible())
+		yPosVis = _mState->activeMarker()._mark.taskY();
+	else if (_mState->activeMarker()._mark.cpuIsVisible())
+		yPosVis = _mState->activeMarker()._mark.cpuY();
 
-	_scrollArea.ensureVisible(0,
-				  _legendAxisX.height() +
-				  _glWindow.vMargin() +
-				  KS_GRAPH_HEIGHT / 2 +
-				  graph*(KS_GRAPH_HEIGHT + _glWindow.vSpacing()),
-				  50,
-				  KS_GRAPH_HEIGHT / 2 + _glWindow.vSpacing() / 2);
+	if (yPosVis > 0)
+		_scrollArea.ensureVisible(0, yPosVis);
 
 	_glWindow.model()->jumpTo(_data->rows()[row]->ts);
-	_mState->activeMarker().set(*_data,
-				    _glWindow.model()->histo(),
-				    row, cpuGrId, taskGrId);
+	_mState->activeMarker().set(*_data, _glWindow.model()->histo(),
+				    row, _data->rows()[row]->stream_id);
 
 	_mState->updateMarkers(*_data, &_glWindow);
 }
 
 void KsTraceGraph::_markerReDraw()
 {
-	int cpuGrId, taskGrId;
 	size_t row;
 
 	if (_mState->markerA()._isSet) {
 		row = _mState->markerA()._pos;
-		_glWindow.findGraphIds(*_data->rows()[row], &cpuGrId, &taskGrId);
-		_mState->markerA().set(*_data,
-				       _glWindow.model()->histo(),
-				       row, cpuGrId, taskGrId);
+		_mState->markerA().set(*_data, _glWindow.model()->histo(),
+				       row, _data->rows()[row]->stream_id);
 	}
 
 	if (_mState->markerB()._isSet) {
 		row = _mState->markerB()._pos;
-		_glWindow.findGraphIds(*_data->rows()[row], &cpuGrId, &taskGrId);
-		_mState->markerB().set(*_data,
-				       _glWindow.model()->histo(),
-				       row, cpuGrId, taskGrId);
+		_mState->markerB().set(*_data, _glWindow.model()->histo(),
+				       row, _data->rows()[row]->stream_id);
 	}
 }
 
@@ -413,9 +398,11 @@
  *
  * @param v: CPU ids to be plotted.
  */
-void KsTraceGraph::cpuReDraw(QVector<int> v)
+void KsTraceGraph::cpuReDraw(int sd, QVector<int> v)
 {
-	_glWindow._cpuList = v;
+	if (_glWindow._streamPlots.contains(sd))
+		_glWindow._streamPlots[sd]._cpuList = v;
+
 	_selfUpdate();
 }
 
@@ -424,51 +411,72 @@
  *
  * @param v: Process ids of the tasks to be plotted.
  */
-void KsTraceGraph::taskReDraw(QVector<int> v)
+void KsTraceGraph::taskReDraw(int sd, QVector<int> v)
 {
-	_glWindow._taskList = v;
+	if (_glWindow._streamPlots.contains(sd))
+		_glWindow._streamPlots[sd]._taskList = v;
+
+	_selfUpdate();
+}
+
+void KsTraceGraph::comboReDraw(int sd, QVector<int> v)
+{
+	KsVirtComboPlot combo;
+
+	combo._hostStreamId = v[0];
+	combo._hostPid = v[1];
+	combo._guestStreamId = v[2];
+	combo._vcpu = v[3];
+	combo._hostBase = 0;
+	combo._vcpuBase = 0;
+
+// 	if (_glWindow._comboPlots.contains(combo))
+// 		return;
+
+	_glWindow._comboPlots.append(combo);
+
 	_selfUpdate();
 }
 
 /** Add (and plot) a CPU graph to the existing list of CPU graphs. */
-void KsTraceGraph::addCPUPlot(int cpu)
+void KsTraceGraph::addCPUPlot(int sd, int cpu)
 {
-	if (_glWindow._cpuList.contains(cpu))
+	if (_glWindow._streamPlots[sd]._cpuList.contains(cpu))
 		return;
 
-	_glWindow._cpuList.append(cpu);
-	qSort(_glWindow._cpuList);
+	_glWindow._streamPlots[sd]._cpuList.append(cpu);
+	qSort(_glWindow._streamPlots[sd]._cpuList);
 	_selfUpdate();
 }
 
 /** Add (and plot) a Task graph to the existing list of Task graphs. */
-void KsTraceGraph::addTaskPlot(int pid)
+void KsTraceGraph::addTaskPlot(int sd, int pid)
 {
-	if (_glWindow._taskList.contains(pid))
+	if (_glWindow._streamPlots[sd]._taskList.contains(pid))
 		return;
 
-	_glWindow._taskList.append(pid);
-	qSort(_glWindow._taskList);
+	_glWindow._streamPlots[sd]._taskList.append(pid);
+	qSort(_glWindow._streamPlots[sd]._taskList);
 	_selfUpdate();
 }
 
 /** Remove a CPU graph from the existing list of CPU graphs. */
-void KsTraceGraph::removeCPUPlot(int cpu)
+void KsTraceGraph::removeCPUPlot(int sd, int cpu)
 {
-	if (!_glWindow._cpuList.contains(cpu))
+	if (!_glWindow._streamPlots[sd]._cpuList.contains(cpu))
 		return;
 
-	_glWindow._cpuList.removeAll(cpu);
+	_glWindow._streamPlots[sd]._cpuList.removeAll(cpu);
 	_selfUpdate();
 }
 
 /** Remove a Task graph from the existing list of Task graphs. */
-void KsTraceGraph::removeTaskPlot(int pid)
+void KsTraceGraph::removeTaskPlot(int sd, int pid)
 {
-	if (!_glWindow._taskList.contains(pid))
+	if (!_glWindow._streamPlots[sd]._taskList.contains(pid))
 		return;
 
-	_glWindow._taskList.removeAll(pid);
+	_glWindow._streamPlots[sd]._taskList.removeAll(pid);
 	_selfUpdate();
 }
 
@@ -544,7 +552,7 @@
 {
 	QString graphLegends, graphName;
 	QVBoxLayout *layout;
-	int width = 0;
+	int sd, width = 0;
 
 	if (_legendWindow.layout()) {
 		/*
@@ -565,28 +573,47 @@
 	layout->setAlignment(Qt::AlignTop);
 	layout->addSpacing(_glWindow.vMargin());
 
-	auto lamMakeName = [&]() {
-		QLabel *name = new QLabel(graphName);
-
+	auto lamMaxWidth = [&]() {
 		if (width < STRING_WIDTH(graphName))
 			width = STRING_WIDTH(graphName);
-
-		name->setAlignment(Qt::AlignBottom);
-		name->setStyleSheet("QLabel {background-color : white;}");
-		name->setFixedHeight(KS_GRAPH_HEIGHT);
-		layout->addWidget(name);
 	};
 
-	for (auto const &cpu: _glWindow._cpuList) {
-		graphName = QString("CPU %1").arg(cpu);
-		lamMakeName();
+	auto lamMakeName = [&]() {
+		QVariant color = KsUtils::getStreamColor(sd);
+		QLabel *name = new QLabel(graphName);
+		QString styleSheet =
+			"background-color : " + color.toString() + ";";
+
+		name->setStyleSheet(styleSheet);
+		name->setFixedHeight(KS_GRAPH_HEIGHT);
+		layout->addWidget(name);
+		lamMaxWidth();
+	};
+
+	for (auto it = _glWindow._streamPlots.constBegin();
+	     it != _glWindow._streamPlots.constEnd();
+	     ++it) {
+		sd = it.key();
+		for (auto const &cpu: _glWindow._streamPlots[sd]._cpuList) {
+			graphName = QString(" CPU %1 ").arg(cpu);
+			lamMakeName();
+		}
+
+		for (auto const &pid: _glWindow._streamPlots[sd]._taskList) {
+			graphName =
+				QString(tep_data_comm_from_pid(_data->tep(sd), pid));
+			graphName.append(QString("-%1 ").arg(pid));
+			lamMakeName();
+		}
 	}
 
-	for (auto const &pid: _glWindow._taskList) {
-		graphName = QString(tep_data_comm_from_pid(_data->tep(),
-							   pid));
-		graphName.append(QString("-%1").arg(pid));
-		lamMakeName();
+	for (auto const &p: _glWindow._comboPlots) {
+		graphName = QString(" vCPU %1 \n\nHost-%2").arg(p._vcpu)
+							   .arg(p._hostPid);
+		QLabel *name = new QLabel(graphName);
+		name->setFixedHeight(KS_GRAPH_HEIGHT * 2);
+		layout->addWidget(name);
+		lamMaxWidth();
 	}
 
 	_legendWindow.setLayout(layout);
@@ -718,7 +745,7 @@
 void KsTraceGraph::_onCustomContextMenu(const QPoint &point)
 {
 	KsQuickMarkerMenu *menu(nullptr);
-	int cpu, pid;
+	int sd, cpu, pid;
 	size_t row;
 	bool found;
 
@@ -726,8 +753,8 @@
 	if (found) {
 		/* KernelShark entry has been found under the cursor. */
 		KsQuickContextMenu *entryMenu;
-		menu = entryMenu = new KsQuickContextMenu(_data, row,
-							  _mState, this);
+		menu = entryMenu = new KsQuickContextMenu(_mState, _data, row,
+							  this);
 
 		connect(entryMenu,	&KsQuickContextMenu::addTaskPlot,
 			this,		&KsTraceGraph::addTaskPlot);
@@ -741,34 +768,35 @@
 		connect(entryMenu,	&KsQuickContextMenu::removeCPUPlot,
 			this,		&KsTraceGraph::removeCPUPlot);
 	} else {
-		cpu = _glWindow.getPlotCPU(point);
+		if (!_glWindow.getPlotInfo(point, &sd, &cpu, &pid))
+			return;
+
 		if (cpu >= 0) {
 			/*
 			 * This is a CPU plot, but we do not have an entry
 			 * under the cursor.
 			 */
 			KsRmCPUPlotMenu *rmMenu;
-			menu = rmMenu = new KsRmCPUPlotMenu(_mState, cpu, this);
+			menu = rmMenu = new KsRmCPUPlotMenu(_mState, sd, cpu, this);
 
-			auto lamRmPlot = [&cpu, this] () {
-				removeCPUPlot(cpu);
+			auto lamRmPlot = [&sd, &cpu, this] () {
+				removeCPUPlot(sd, cpu);
 			};
 
 			connect(rmMenu, &KsRmPlotContextMenu::removePlot,
 				lamRmPlot);
 		}
 
-		pid = _glWindow.getPlotPid(point);
 		if (pid >= 0) {
 			/*
 			 * This is a Task plot, but we do not have an entry
 			 * under the cursor.
 			 */
 			KsRmTaskPlotMenu *rmMenu;
-			menu = rmMenu = new KsRmTaskPlotMenu(_mState, pid, this);
+			menu = rmMenu = new KsRmTaskPlotMenu(_mState, sd, pid, this);
 
-			auto lamRmPlot = [&pid, this] () {
-				removeTaskPlot(pid);
+			auto lamRmPlot = [&sd, &pid, this] () {
+				removeTaskPlot(sd, pid);
 			};
 
 			connect(rmMenu, &KsRmPlotContextMenu::removePlot,
diff --git a/kernel-shark/src/KsTraceGraph.hpp b/kernel-shark/src/KsTraceGraph.hpp
index c53258c..b786bc4 100644
--- a/kernel-shark/src/KsTraceGraph.hpp
+++ b/kernel-shark/src/KsTraceGraph.hpp
@@ -52,17 +52,19 @@
 
 	void markEntry(size_t);
 
-	void cpuReDraw(QVector<int>);
+	void cpuReDraw(int sd, QVector<int>cpus);
 
-	void taskReDraw(QVector<int>);
+	void taskReDraw(int sd, QVector<int> pids);
 
-	void addCPUPlot(int);
+	void comboReDraw(int sd, QVector<int> v);
 
-	void addTaskPlot(int);
+	void addCPUPlot(int sd, int cpu);
 
-	void removeCPUPlot(int);
+	void addTaskPlot(int sd, int cpu);
 
-	void removeTaskPlot(int);
+	void removeCPUPlot(int sd, int cpu);
+
+	void removeTaskPlot(int sd, int cpu);
 
 	void update(KsDataStore *data);
 
@@ -81,7 +83,6 @@
 	void deselect();
 
 private:
-
 	void _zoomIn();
 
 	void _zoomOut();
@@ -96,7 +97,7 @@
 
 	void _stopUpdating();
 
-	void _resetPointer(uint64_t ts, int cpu, int pid);
+	void _resetPointer(uint64_t ts, int sd, int cpu, int pid);
 
 	void _setPointerInfo(size_t);
 
diff --git a/kernel-shark/src/KsTraceViewer.cpp b/kernel-shark/src/KsTraceViewer.cpp
index 4e2c93e..1a0e9a2 100644
--- a/kernel-shark/src/KsTraceViewer.cpp
+++ b/kernel-shark/src/KsTraceViewer.cpp
@@ -245,7 +245,8 @@
 		 * of the row number in the source model.
 		 */
 		size_t row = _proxyModel.mapRowFromSource(i.row());
-		KsQuickContextMenu menu(_data, row, _mState, this);
+
+		KsQuickContextMenu menu(_mState, _data, row, this);
 
 		/*
 		 * Note that this slot was connected to the
@@ -457,7 +458,7 @@
 /** Switch the Dual marker. */
 void KsTraceViewer::markSwitch()
 {
-	int row;
+	size_t row;
 
 	/* The state of the Dual marker has changed. Get the new active marker. */
 	DualMarkerState state = _mState->getState();
@@ -488,7 +489,7 @@
 		 * The index in the source model is used to retrieve the value
 		 * of the row number in the proxy model.
 		 */
-		size_t row =_mState->getMarker(state)._pos;
+		row =_mState->getMarker(state)._pos;
 
 		QModelIndex index =
 			_proxyModel.mapFromSource(_model.index(row, 0));
@@ -558,20 +559,29 @@
 
 void KsTraceViewer::_resizeToContents()
 {
-	int rows, columnSize;
+	int col, rows, columnSize;
 
 	_view.setVisible(false);
 	_view.resizeColumnsToContents();
 	_view.setVisible(true);
 
 	/*
-	 * Because of some unknown reason the first column doesn't get
+	 * Because of some unknown reason some of the columns doesn't get
 	 * resized properly by the code above. We will resize this
 	 * column by hand.
 	 */
+	col = KsViewModel::TRACE_VIEW_STREAM_ID;
+	columnSize = STRING_WIDTH(_model.header()[col]) + FONT_WIDTH;
+	_view.setColumnWidth(col, columnSize);
+
+	col = KsViewModel::TRACE_VIEW_COL_CPU;
+	columnSize = STRING_WIDTH(_model.header()[col]) + FONT_WIDTH * 2;
+	_view.setColumnWidth(col, columnSize);
+
+	col = KsViewModel::TRACE_VIEW_COL_INDEX;
 	rows = _model.rowCount({});
 	columnSize = STRING_WIDTH(QString("%1").arg(rows)) + FONT_WIDTH;
-	_view.setColumnWidth(0, columnSize);
+	_view.setColumnWidth(col, columnSize);
 }
 
 //! @cond Doxygen_Suppress
diff --git a/kernel-shark/src/KsTraceViewer.hpp b/kernel-shark/src/KsTraceViewer.hpp
index cf529ba..2176671 100644
--- a/kernel-shark/src/KsTraceViewer.hpp
+++ b/kernel-shark/src/KsTraceViewer.hpp
@@ -82,7 +82,7 @@
 	 * This signal is used to re-emitted the addTaskPlot signal of the
 	 * KsQuickContextMenu.
 	 */
-	void addTaskPlot(int pid);
+	void addTaskPlot(int sd, int pid);
 
 	/**
 	 * This signal is used to re-emitted the deselect signal of the
diff --git a/kernel-shark/src/KsUtils.cpp b/kernel-shark/src/KsUtils.cpp
index 6af0c66..49b8a3b 100644
--- a/kernel-shark/src/KsUtils.cpp
+++ b/kernel-shark/src/KsUtils.cpp
@@ -14,8 +14,12 @@
 
 namespace KsUtils {
 
-/** @brief Get a sorted vector of Task's Pids. */
-QVector<int> getPidList()
+/**
+ * @brief Get a sorteg vector of Task's Pids.
+ *
+ * @param sd: Data stream identifier.
+ */
+QVector<int> getPidList(int sd)
 {
 	kshark_context *kshark_ctx(nullptr);
 	int nTasks, *tempPids;
@@ -24,7 +28,7 @@
 	if (!kshark_instance(&kshark_ctx))
 		return pids;
 
-	nTasks = kshark_get_task_pids(kshark_ctx, &tempPids);
+	nTasks = kshark_get_task_pids(kshark_ctx, sd, &tempPids);
 	for (int r = 0; r < nTasks; ++r) {
 		pids.append(tempPids[r]);
 	}
@@ -125,15 +129,37 @@
  *
  * @param kshark_ctx: Input location for the session context pointer.
  * @param e: kshark_entry to be checked.
+ * @param sd: Data stream identifier.
  * @param cpu: Matching condition value.
  *
  * @returns True if the CPU of the entry matches the value of "cpu" and
  * 	    the entry is visibility in Graph. Otherwise false.
  */
 bool matchCPUVisible(struct kshark_context *kshark_ctx,
-		     struct kshark_entry *e, int cpu)
+		     struct kshark_entry *e, int sd, int *cpu)
 {
-	return (e->cpu == cpu && (e->visible & KS_GRAPH_VIEW_FILTER_MASK));
+	return (e->cpu == *cpu &&
+		e->stream_id == sd &&
+		(e->visible & KS_GRAPH_VIEW_FILTER_MASK));
+}
+
+void setElidedText(QLabel* label, QString text,
+		   enum Qt::TextElideMode mode,
+		   int labelWidth)
+{
+	QFontMetrics metrix(label->font());
+	QString elidedText;
+	int textWidth;
+
+	textWidth = labelWidth - FONT_WIDTH * 3;
+	elidedText = metrix.elidedText(text, Qt::ElideRight, textWidth);
+
+	while(labelWidth < STRING_WIDTH(elidedText) + FONT_WIDTH * 5) {
+		textWidth -= FONT_WIDTH * 3;
+		elidedText = metrix.elidedText(text, mode, textWidth);
+	}
+
+	label->setText(elidedText);
 }
 
 }; // KsUtils
@@ -149,7 +175,6 @@
 /** Create a default (empty) KsDataStore. */
 KsDataStore::KsDataStore(QWidget *parent)
 : QObject(parent),
-  _tep(nullptr),
   _rows(nullptr),
   _dataSize(0)
 {}
@@ -158,34 +183,101 @@
 KsDataStore::~KsDataStore()
 {}
 
-/** Load trace data for file. */
-void KsDataStore::loadDataFile(const QString &file)
+int KsDataStore::_openDataFile(kshark_context *kshark_ctx,
+				const QString &file)
 {
-	kshark_context *kshark_ctx(nullptr);
+	int sd;
 
-	if (!kshark_instance(&kshark_ctx))
-		return;
-
-	clear();
-
-	if (!kshark_open(kshark_ctx, file.toStdString().c_str())) {
-		qCritical() << "ERROR Loading file " << file;
-		return;
+	sd = kshark_open(kshark_ctx, file.toStdString().c_str());
+	if (sd < 0) {
+		qCritical() << "ERROR" << sd << "while loading file " << file;
+		return sd;
 	}
 
-	_tep = kshark_ctx->pevent;
+	kshark_handle_all_plugins(kshark_ctx, sd, KSHARK_PLUGIN_UPDATE);
 
-	if (kshark_ctx->event_handlers == nullptr)
-		kshark_handle_plugins(kshark_ctx, KSHARK_PLUGIN_INIT);
-	else
-		kshark_handle_plugins(kshark_ctx, KSHARK_PLUGIN_UPDATE);
+	return sd;
+}
 
-	_dataSize = kshark_load_data_entries(kshark_ctx, &_rows);
+/** Load trace data for file. */
+int  KsDataStore::loadDataFile(const QString &file)
+{
+	kshark_context *kshark_ctx(nullptr);
+	ssize_t n;
+	int sd;
+
+	if (!kshark_instance(&kshark_ctx))
+		return -EFAULT;
+
+	clear();
+	_unregisterCPUCollections();
+
+	sd = _openDataFile(kshark_ctx, file);
+	n = kshark_load_data_entries(kshark_ctx, sd, &_rows);
+	if (n < 0) {
+		kshark_close(kshark_ctx, sd);
+		return n;
+	}
+
+	_dataSize = n;
+	registerCPUCollections();
+
+	return sd;
+}
+
+/**
+ * @brief Append a trace data file to the data-set that is already loaded.
+ *	  The clock of the new data will be calibrated in order to be
+ *	  compatible with the clock of the prior data.
+ *
+ * @param file: Trace data file, to be append to the already loaded data.
+ * @param calib: Callback function providing the calibration of the clock of
+ *		 the associated data.
+ * @param argv: Array of arguments for the calibration function.
+ */
+int KsDataStore::appendDataFile(const QString &file, int64_t shift)
+{
+	kshark_context *kshark_ctx(nullptr);
+	struct kshark_entry **apndRows = NULL;
+	struct kshark_entry **mergedRows;
+	ssize_t nApnd = 0;
+	int sd;
+
+	if (!kshark_instance(&kshark_ctx))
+		return -EFAULT;
+
+	_unregisterCPUCollections();
+
+	sd = _openDataFile(kshark_ctx, file);
+
+	kshark_ctx->stream[sd]->calib = kshark_offset_calib;
+	kshark_ctx->stream[sd]->calib_array = new int64_t;
+	*(kshark_ctx->stream[sd]->calib_array) = shift;
+	kshark_ctx->stream[sd]->calib_array_size = 1;
+
+	nApnd = kshark_load_data_entries(kshark_ctx, sd, &apndRows);
+	if (nApnd < 0) {
+		kshark_close(kshark_ctx, sd);
+		return nApnd;
+	}
+
+	mergedRows = kshark_data_merge(_rows, _dataSize,
+				       apndRows, nApnd);
+
+	free(_rows);
+	free(apndRows);
+
+	_dataSize += nApnd;
+	_rows = mergedRows;
+
+	registerCPUCollections();
+
+	return sd;
 }
 
 void KsDataStore::_freeData()
 {
-	if (_dataSize) {
+	if (_dataSize > 0) {
 		for (size_t r = 0; r < _dataSize; ++r)
 			free(_rows[r]);
 
@@ -206,8 +298,14 @@
 
 	_freeData();
 
-	_dataSize = kshark_load_data_entries(kshark_ctx, &_rows);
-	_tep = kshark_ctx->pevent;
+	if (kshark_ctx->n_streams == 0)
+		return;
+
+	_unregisterCPUCollections();
+
+	_dataSize = kshark_load_all_data_entries(kshark_ctx, &_rows);
+
+	registerCPUCollections();
 
 	emit updateWidgets(this);
 }
@@ -217,11 +315,26 @@
 {
 	kshark_context *kshark_ctx(nullptr);
 
-	_freeData();
-	_tep = nullptr;
+	if (!kshark_instance(&kshark_ctx))
+		return;
 
-	if (kshark_instance(&kshark_ctx) && kshark_ctx->handle)
-		kshark_close(kshark_ctx);
+	_freeData();
+	kshark_close_all(kshark_ctx);
+}
+
+/** Get the trace event parser for a given data stream. */
+tep_handle *KsDataStore::tep(int sd) const {
+	kshark_context *kshark_ctx(nullptr);
+	kshark_data_stream *stream;
+
+	if (!kshark_instance(&kshark_ctx))
+		return nullptr;
+
+	stream = kshark_get_data_stream(kshark_ctx, sd);
+	if (stream)
+		return stream->pevent;
+
+	return nullptr;
 }
 
 /** Update the visibility of the entries (filter). */
@@ -234,91 +347,107 @@
 
 	_unregisterCPUCollections();
 
-	if (kshark_filter_is_set(kshark_ctx)) {
-		kshark_filter_entries(kshark_ctx, _rows, _dataSize);
-		emit updateWidgets(this);
-	}
+	kshark_filter_all_entries(kshark_ctx, _rows, _dataSize);
 
 	registerCPUCollections();
+
+	emit updateWidgets(this);
 }
 
 /** Register a collection of visible entries for each CPU. */
 void KsDataStore::registerCPUCollections()
 {
+	qInfo() << "@ registerCPUCollections";
 	kshark_context *kshark_ctx(nullptr);
+	int *streamIds, nCPUs, sd;
 
-	if (!kshark_instance(&kshark_ctx) ||
-	    !kshark_filter_is_set(kshark_ctx))
+	if (!kshark_instance(&kshark_ctx))
 		return;
 
-	int nCPUs = tep_get_cpus(_tep);
-	for (int cpu = 0; cpu < nCPUs; ++cpu) {
-		kshark_register_data_collection(kshark_ctx,
-						_rows, _dataSize,
-						KsUtils::matchCPUVisible,
-						cpu,
-						0);
+	streamIds = kshark_all_streams(kshark_ctx);
+	for (int i = 0; i < kshark_ctx->n_streams; ++i) {
+		sd = streamIds[i];
+
+		nCPUs = tep_get_cpus(kshark_ctx->stream[sd]->pevent);
+		for (int cpu = 0; cpu < nCPUs; ++cpu) {
+			kshark_register_data_collection(kshark_ctx,
+							_rows, _dataSize,
+							KsUtils::matchCPUVisible,
+							sd, &cpu, 1,
+							0);
+		}
 	}
+
+	free(streamIds);
 }
 
 void KsDataStore::_unregisterCPUCollections()
 {
 	kshark_context *kshark_ctx(nullptr);
-
+	int *streamIds, nCPUs, sd;
+	qInfo() << "@@ unregisterCPUCollections";
 	if (!kshark_instance(&kshark_ctx))
 		return;
 
-	int nCPUs = tep_get_cpus(_tep);
-	for (int cpu = 0; cpu < nCPUs; ++cpu) {
-		kshark_unregister_data_collection(&kshark_ctx->collections,
-						  KsUtils::matchCPUVisible,
-						  cpu);
+	streamIds = kshark_all_streams(kshark_ctx);
+	for (int i = 0; i < kshark_ctx->n_streams; ++i) {
+		sd = streamIds[i];
+		if (!kshark_filter_is_set(kshark_ctx, sd))
+			continue;
+
+		nCPUs = tep_get_cpus(kshark_ctx->stream[sd]->pevent);
+		for (int cpu = 0; cpu < nCPUs; ++cpu) {
+			kshark_unregister_data_collection(&kshark_ctx->collections,
+							  KsUtils::matchCPUVisible,
+							  sd, &cpu, 1);
+		}
 	}
+
+	free(streamIds);
 }
 
-void KsDataStore::_applyIdFilter(int filterId, QVector<int> vec)
+void KsDataStore::_applyIdFilter(int filterId, QVector<int> vec, int sd)
 {
 	kshark_context *kshark_ctx(nullptr);
-
 	if (!kshark_instance(&kshark_ctx))
 		return;
 
 	switch (filterId) {
 		case KS_SHOW_EVENT_FILTER:
 		case KS_HIDE_EVENT_FILTER:
-			kshark_filter_clear(kshark_ctx, KS_SHOW_EVENT_FILTER);
-			kshark_filter_clear(kshark_ctx, KS_HIDE_EVENT_FILTER);
+			kshark_filter_clear(kshark_ctx, sd, KS_SHOW_EVENT_FILTER);
+			kshark_filter_clear(kshark_ctx, sd, KS_HIDE_EVENT_FILTER);
 			break;
 		case KS_SHOW_TASK_FILTER:
 		case KS_HIDE_TASK_FILTER:
-			kshark_filter_clear(kshark_ctx, KS_SHOW_TASK_FILTER);
-			kshark_filter_clear(kshark_ctx, KS_HIDE_TASK_FILTER);
+			kshark_filter_clear(kshark_ctx, sd, KS_SHOW_TASK_FILTER);
+			kshark_filter_clear(kshark_ctx, sd, KS_HIDE_TASK_FILTER);
 			break;
 		case KS_SHOW_CPU_FILTER:
 		case KS_HIDE_CPU_FILTER:
-			kshark_filter_clear(kshark_ctx, KS_SHOW_CPU_FILTER);
-			kshark_filter_clear(kshark_ctx, KS_HIDE_CPU_FILTER);
+			kshark_filter_clear(kshark_ctx, sd, KS_SHOW_CPU_FILTER);
+			kshark_filter_clear(kshark_ctx, sd, KS_HIDE_CPU_FILTER);
 			break;
 		default:
 			return;
 	}
 
 	for (auto &&val: vec)
-		kshark_filter_add_id(kshark_ctx, filterId, val);
+		kshark_filter_add_id(kshark_ctx, sd, filterId, val);
 
-	if (!_tep)
+	if (!kshark_ctx->n_streams)
 		return;
 
 	_unregisterCPUCollections();
 
 	/*
-	 * If the advanced event filter is set, the data has to be reloaded,
+	 * If the advanced event filter is set the data has to be reloaded,
 	 * because the advanced filter uses tep_records.
 	 */
-	if (kshark_ctx->advanced_event_filter->filters)
+	if (kshark_ctx->stream[sd]->advanced_event_filter->filters)
 		reload();
 	else
-		kshark_filter_entries(kshark_ctx, _rows, _dataSize);
+		kshark_filter_stream_entries(kshark_ctx, sd, _rows, _dataSize);
 
 	registerCPUCollections();
 
@@ -326,61 +455,105 @@
 }
 
 /** Apply Show Task filter. */
-void KsDataStore::applyPosTaskFilter(QVector<int> vec)
+void KsDataStore::applyPosTaskFilter(int sd, QVector<int> vec)
 {
-	_applyIdFilter(KS_SHOW_TASK_FILTER, vec);
+	kshark_context *kshark_ctx(nullptr);
+	int nTasks, *pids;
+
+	if (!kshark_instance(&kshark_ctx))
+		return;
+
+	nTasks = kshark_get_task_pids(kshark_ctx, sd, &pids);
+	free(pids);
+	if (vec.count() == nTasks)
+		return;
+
+	_applyIdFilter(KS_SHOW_TASK_FILTER, vec, sd);
 }
 
 /** Apply Hide Task filter. */
-void KsDataStore::applyNegTaskFilter(QVector<int> vec)
+void KsDataStore::applyNegTaskFilter(int sd, QVector<int> vec)
 {
-	_applyIdFilter(KS_HIDE_TASK_FILTER, vec);
+	if (!vec.count())
+		return;
+
+	_applyIdFilter(KS_HIDE_TASK_FILTER, vec, sd);
 }
 
 /** Apply Show Event filter. */
-void KsDataStore::applyPosEventFilter(QVector<int> vec)
+void KsDataStore::applyPosEventFilter(int sd, QVector<int> vec)
 {
-	_applyIdFilter(KS_SHOW_EVENT_FILTER, vec);
+	_applyIdFilter(KS_SHOW_EVENT_FILTER, vec, sd);
 }
 
 /** Apply Hide Event filter. */
-void KsDataStore::applyNegEventFilter(QVector<int> vec)
+void KsDataStore::applyNegEventFilter(int sd, QVector<int> vec)
 {
-	_applyIdFilter(KS_HIDE_EVENT_FILTER, vec);
+	if (!vec.count())
+		return;
+
+	_applyIdFilter(KS_HIDE_EVENT_FILTER, vec, sd);
 }
 
 /** Apply Show CPU filter. */
-void KsDataStore::applyPosCPUFilter(QVector<int> vec)
+void KsDataStore::applyPosCPUFilter(int sd, QVector<int> vec)
 {
-	_applyIdFilter(KS_SHOW_CPU_FILTER, vec);
+	kshark_context *kshark_ctx(nullptr);
+	kshark_data_stream *stream;
+	int nCPUs;
+
+	if (!kshark_instance(&kshark_ctx))
+		return;
+
+	stream = kshark_get_data_stream(kshark_ctx, sd);
+	if (!stream)
+		return;
+
+	nCPUs = tep_get_cpus(stream->pevent);
+	if (vec.count() == nCPUs)
+		return;
+
+	_applyIdFilter(KS_SHOW_CPU_FILTER, vec, sd);
 }
 
 /** Apply Hide CPU filter. */
-void KsDataStore::applyNegCPUFilter(QVector<int> vec)
+void KsDataStore::applyNegCPUFilter(int sd, QVector<int> vec)
 {
-	_applyIdFilter(KS_HIDE_CPU_FILTER, vec);
+	if (!vec.count())
+		return;
+
+	_applyIdFilter(KS_HIDE_CPU_FILTER, vec, sd);
 }
 
 /** Disable all filters. */
 void KsDataStore::clearAllFilters()
 {
 	kshark_context *kshark_ctx(nullptr);
+	int *streamIds, sd;
 
-	if (!kshark_instance(&kshark_ctx) || !_tep)
+	if (!kshark_instance(&kshark_ctx) || !kshark_ctx->n_streams)
 		return;
 
 	_unregisterCPUCollections();
 
-	kshark_filter_clear(kshark_ctx, KS_SHOW_TASK_FILTER);
-	kshark_filter_clear(kshark_ctx, KS_HIDE_TASK_FILTER);
-	kshark_filter_clear(kshark_ctx, KS_SHOW_EVENT_FILTER);
-	kshark_filter_clear(kshark_ctx, KS_HIDE_EVENT_FILTER);
-	kshark_filter_clear(kshark_ctx, KS_SHOW_CPU_FILTER);
-	kshark_filter_clear(kshark_ctx, KS_HIDE_CPU_FILTER);
+	streamIds = kshark_all_streams(kshark_ctx);
+	for (int i = 0; i < kshark_ctx->n_streams; ++i) {
+		sd = streamIds[i];
 
-	tep_filter_reset(kshark_ctx->advanced_event_filter);
+		kshark_filter_clear(kshark_ctx, sd, KS_SHOW_TASK_FILTER);
+		kshark_filter_clear(kshark_ctx, sd, KS_HIDE_TASK_FILTER);
+		kshark_filter_clear(kshark_ctx, sd, KS_SHOW_EVENT_FILTER);
+		kshark_filter_clear(kshark_ctx, sd, KS_HIDE_EVENT_FILTER);
+		kshark_filter_clear(kshark_ctx, sd, KS_SHOW_CPU_FILTER);
+		kshark_filter_clear(kshark_ctx, sd, KS_HIDE_CPU_FILTER);
+
+		tep_filter_reset(kshark_ctx->stream[sd]->advanced_event_filter);
+	}
+
 	kshark_clear_all_filters(kshark_ctx, _rows, _dataSize);
 
+	free(streamIds);
+
 	emit updateWidgets(this);
 }
 
@@ -400,6 +573,16 @@
 	registerFromList(kshark_ctx);
 }
 
+KsPluginManager::~KsPluginManager()
+{
+	kshark_context *kshark_ctx(nullptr);
+
+	if (!kshark_instance(&kshark_ctx))
+		return;
+
+	unregisterFromList(kshark_ctx);
+}
+
 /** Parse the plugin list declared in the CMake-generated header file. */
 void KsPluginManager::_parsePluginList()
 {
@@ -423,6 +606,7 @@
  */
 void KsPluginManager::registerFromList(kshark_context *kshark_ctx)
 {
+	qInfo() << "registerFromList" << _ksPluginList;
 	auto lamRegBuiltIn = [&kshark_ctx](const QString &plugin)
 	{
 		char *lib;
@@ -433,6 +617,7 @@
 		if (n <= 0)
 			return;
 
+		qInfo() << "reg" << lib;
 		kshark_register_plugin(kshark_ctx, lib);
 		free(lib);
 	};
@@ -443,13 +628,19 @@
 		kshark_register_plugin(kshark_ctx, lib.c_str());
 	};
 
-	_forEachInList(_ksPluginList,
-		       _registeredKsPlugins,
-		       lamRegBuiltIn);
+	for (auto const &p: _ksPluginList)
+		lamRegBuiltIn(p);
 
-	_forEachInList(_userPluginList,
-		       _registeredUserPlugins,
-		       lamRegUser);
+	for (auto const &p: _userPluginList)
+		lamRegUser(p);
+
+// 	_forEachInList(_ksPluginList,
+// 		       _registeredKsPlugins,
+// 		       lamRegBuiltIn);
+
+// 	_forEachInList(_userPluginList,
+// 		       _registeredUserPlugins,
+// 		       lamRegUser);
 }
 
 /**
@@ -468,6 +659,7 @@
 		if (n <= 0)
 			return;
 
+		qInfo() << "u_reg" << lib;
 		kshark_unregister_plugin(kshark_ctx, lib);
 		free(lib);
 	};
@@ -478,13 +670,19 @@
 		kshark_unregister_plugin(kshark_ctx, lib.c_str());
 	};
 
-	_forEachInList(_ksPluginList,
-		       _registeredKsPlugins,
-			lamUregBuiltIn);
+	for (auto const &p: _ksPluginList)
+		lamUregBuiltIn(p);
 
-	_forEachInList(_userPluginList,
-		       _registeredUserPlugins,
-			lamUregUser);
+	for (auto const &p: _userPluginList)
+		lamUregUser(p);
+
+// 	_forEachInList(_ksPluginList,
+// 		       _registeredKsPlugins,
+// 			lamUregBuiltIn);
+
+// 	_forEachInList(_userPluginList,
+// 		       _registeredUserPlugins,
+// 			lamUregUser);
 }
 
 /**
@@ -579,8 +777,8 @@
 			}
 
 			return;
-		} else if  (plugin.contains("/lib/plugin-" +
-			                   _ksPluginList[i], Qt::CaseInsensitive)) {
+		} else if (plugin.contains("/lib/plugin-" + _ksPluginList[i],
+					    Qt::CaseInsensitive)) {
 			/*
 			 * The argument is the name of the library .so file.
 			 */
@@ -623,11 +821,33 @@
 void KsPluginManager::unloadAll()
 {
 	kshark_context *kshark_ctx(nullptr);
+	int *streamIds;
 
 	if (!kshark_instance(&kshark_ctx))
 		return;
 
-	kshark_handle_plugins(kshark_ctx, KSHARK_PLUGIN_CLOSE);
+	streamIds = kshark_all_streams(kshark_ctx);
+	for (int i = 0; i < kshark_ctx->n_streams; ++i)
+		kshark_handle_all_plugins(kshark_ctx, streamIds[i],
+					  KSHARK_PLUGIN_CLOSE);
+
+	unregisterFromList(kshark_ctx);
+
+	kshark_free_plugin_list(kshark_ctx->plugins);
+	kshark_ctx->plugins = nullptr;
+	kshark_free_event_handler_list(kshark_ctx->event_handlers);
+}
+
+/** Unload all plugins. */
+void KsPluginManager::unload(int sd)
+{
+	kshark_context *kshark_ctx(nullptr);
+
+	if (!kshark_instance(&kshark_ctx))
+		return;
+
+	kshark_handle_all_plugins(kshark_ctx, sd, KSHARK_PLUGIN_CLOSE);
+
 	kshark_free_plugin_list(kshark_ctx->plugins);
 	kshark_ctx->plugins = nullptr;
 	kshark_free_event_handler_list(kshark_ctx->event_handlers);
@@ -639,14 +859,68 @@
  *
  * @param pluginIds: The indexes of the plugins to be loaded.
  */
-void KsPluginManager::updatePlugins(QVector<int> pluginIds)
+// void KsPluginManager::updatePlugins(QVector<int> pluginIds)
+// {
+// 	kshark_context *kshark_ctx(nullptr);
+// 
+// 	if (!kshark_instance(&kshark_ctx))
+// 		return;
+// 
+// 	auto lamRegisterPlugins = [&] (QVector<int> ids)
+// 	{
+// 		int nKsPlugins = _registeredKsPlugins.count();
+// 
+// 		/* First clear all registered plugins. */
+// 		for (auto &p: _registeredKsPlugins)
+// 			p = false;
+// 		for (auto &p: _registeredUserPlugins)
+// 			p = false;
+// 
+// 		/* The vector contains the indexes of those to register. */
+// 		for (auto const &p: ids) {
+// 			if (p < nKsPlugins)
+// 				_registeredKsPlugins[p] = true;
+// 			else
+// 				_registeredUserPlugins[p - nKsPlugins] = true;
+// 		}
+// 		registerFromList(kshark_ctx);
+// 	};
+// 
+// 	if (!kshark_ctx->pevent) {
+// 		kshark_free_plugin_list(kshark_ctx->plugins);
+// 		kshark_ctx->plugins = nullptr;
+// 
+// 		/*
+// 		 * No data is loaded. For the moment, just register the
+// 		 * plugins. Handling of the plugins will be done after
+// 		 * we load a data file.
+// 		 */
+// 		lamRegisterPlugins(pluginIds);
+// 		return;
+// 	}
+// 
+// 	/* Clean up all old plugins first. */
+// 	unloadAll();
+// 
+// 	/* Now load. */
+// 	lamRegisterPlugins(pluginIds);
+// 	kshark_handle_plugins(kshark_ctx, KSHARK_PLUGIN_INIT);
+// 
+// 	emit dataReload();
+// }
+/** @brief Update (change) the Plugins.
+ *
+ * @param pluginIds: The indexes of the plugins to be loaded.
+ */
+void KsPluginManager::updatePlugins(int sd, QVector<int> pluginIds)
 {
 	kshark_context *kshark_ctx(nullptr);
+// 	int *streamIds;
 
 	if (!kshark_instance(&kshark_ctx))
 		return;
 
-	auto register_plugins = [&] (QVector<int> ids)
+	auto lamRegisterPlugins = [&] (QVector<int> ids)
 	{
 		int nKsPlugins = _registeredKsPlugins.count();
 
@@ -663,10 +937,10 @@
 			else
 				_registeredUserPlugins[p - nKsPlugins] = true;
 		}
-		registerFromList(kshark_ctx);
+// 		registerFromList(kshark_ctx);
 	};
 
-	if (!kshark_ctx->pevent) {
+	if (!kshark_ctx->n_streams) {
 		kshark_free_plugin_list(kshark_ctx->plugins);
 		kshark_ctx->plugins = nullptr;
 
@@ -675,16 +949,35 @@
 		 * plugins. Handling of the plugins will be done after
 		 * we load a data file.
 		 */
-		register_plugins(pluginIds);
+		lamRegisterPlugins(pluginIds);
 		return;
 	}
 
-	/* Clean up all old plugins first. */
-	unloadAll();
+	/* First clean up all old plugins associated with this data stream. */
+// 	unload(sd);
 
 	/* Now load. */
-	register_plugins(pluginIds);
-	kshark_handle_plugins(kshark_ctx, KSHARK_PLUGIN_INIT);
+	lamRegisterPlugins(pluginIds);
 
-	emit dataReload();
+// 	kshark_handle_plugins(kshark_ctx, sd, KSHARK_PLUGIN_INIT);
+
+// 	streamIds = kshark_all_streams(kshark_ctx);
+// 	for (int i = 0; i < kshark_ctx->n_streams; ++i)
+// 		emit dataReload(streamIds[i]);
+// 	free(streamIds);
+}
+
+void KsPluginManager::updatePlugins_hack(int sd, QVector<int> pluginIds)
+{
+	kshark_context *kshark_ctx(nullptr);
+	struct kshark_plugin_list *plugin;
+
+	qInfo() << "updatePlugins_hack" << sd << pluginIds;
+	if (!kshark_instance(&kshark_ctx))
+		return;
+
+	for (plugin = kshark_ctx->plugins; plugin; plugin = plugin->next)
+		kshark_plugin_add_stream(plugin, sd);
+
+	kshark_handle_all_plugins(kshark_ctx, sd, KSHARK_PLUGIN_UPDATE);
 }
diff --git a/kernel-shark/src/KsUtils.hpp b/kernel-shark/src/KsUtils.hpp
index c8b5e88..bb0d650 100644
--- a/kernel-shark/src/KsUtils.hpp
+++ b/kernel-shark/src/KsUtils.hpp
@@ -82,7 +82,7 @@
 
 namespace KsUtils {
 
-QVector<int> getPidList();
+QVector<int> getPidList(int sd);
 
 QVector<int> getFilterIds(tracecmd_filter_id *filter);
 
@@ -110,7 +110,23 @@
 }
 
 bool matchCPUVisible(struct kshark_context *kshark_ctx,
-			      struct kshark_entry *e, int cpu);
+		     struct kshark_entry *e, int sd, int *cpu);
+
+void setElidedText(QLabel* label, QString text,
+		   enum Qt::TextElideMode mode,
+		   int labelWidth);
+
+
+#define KS_STREAM_COLOR_LIGHTNESS 30
+
+inline QColor getStreamColor(int sd)
+{
+	QColor col(Qt::GlobalColor(Qt::red + sd));
+	col.setAlpha(KS_STREAM_COLOR_LIGHTNESS);
+
+	return col;
+}
+
 }; // KsUtils
 
 /** Identifier of the Dual Marker active state. */
@@ -131,36 +147,41 @@
 
 	~KsDataStore();
 
-	void loadDataFile(const QString &file);
+	int loadDataFile(const QString &file);
+
+	int appendDataFile(const QString &file, int64_t shift);
 
 	void clear();
 
-	/** Get the trace event parser. */
-	tep_handle *tep() const {return _tep;}
+	tep_handle *tep(int sd) const;
 
-	/** Get the trace data array.. */
+	/** Get the trace data array. */
 	struct kshark_entry **rows() const {return _rows;}
 
+	struct kshark_entry ***rows_r() {return &_rows;}
+
 	/** Get the size of the data array. */
 	size_t size() const {return _dataSize;}
 
+	size_t *size_r() {return &_dataSize;}
+
 	void reload();
 
 	void update();
 
 	void registerCPUCollections();
 
-	void applyPosTaskFilter(QVector<int>);
+	void applyPosTaskFilter(int sd, QVector<int> vec);
 
-	void applyNegTaskFilter(QVector<int>);
+	void applyNegTaskFilter(int sd, QVector<int> vec);
 
-	void applyPosEventFilter(QVector<int>);
+	void applyPosEventFilter(int sd, QVector<int> vec);
 
-	void applyNegEventFilter(QVector<int>);
+	void applyNegEventFilter(int sd, QVector<int> vec);
 
-	void applyPosCPUFilter(QVector<int>);
+	void applyPosCPUFilter(int sd, QVector<int> vec);
 
-	void applyNegCPUFilter(QVector<int>);
+	void applyNegCPUFilter(int sd, QVector<int> vec);
 
 	void clearAllFilters();
 
@@ -172,27 +193,30 @@
 	void updateWidgets(KsDataStore *);
 
 private:
-	/** Page event used to parse the page. */
-	tep_handle		*_tep;
-
 	/** Trace data array. */
 	struct kshark_entry	**_rows;
 
 	/** The size of the data array. */
 	size_t			_dataSize;
 
+	int _openDataFile(kshark_context *kshark_ctx, const QString &file);
+
 	void _freeData();
+
 	void _unregisterCPUCollections();
-	void _applyIdFilter(int filterId, QVector<int> vec);
+
+	void _applyIdFilter(int filterId, QVector<int> vec, int sd);
 };
 
-/** A Plugin Manage class. */
+/** A Plugin Manager class. */
 class KsPluginManager : public QObject
 {
 	Q_OBJECT
 public:
 	explicit KsPluginManager(QWidget *parent = nullptr);
 
+	 ~KsPluginManager();
+
 	/** A list of available built-in plugins. */
 	QStringList	_ksPluginList;
 
@@ -214,12 +238,14 @@
 	void addPlugins(const QStringList &fileNames);
 
 	void unloadAll();
+	void unload(int sd);
 
-	void updatePlugins(QVector<int> pluginId);
+	void updatePlugins(int sd, QVector<int> pluginId);
+	void updatePlugins_hack(int sd, QVector<int> pluginIds);
 
 signals:
 	/** This signal is emitted when a plugin is loaded or unloaded. */
-	void dataReload();
+	void dataReload(int sd);
 
 private:
 	void _parsePluginList();
diff --git a/kernel-shark/src/KsWidgetsLib.cpp b/kernel-shark/src/KsWidgetsLib.cpp
index ea02b5e..fe1a22d 100644
--- a/kernel-shark/src/KsWidgetsLib.cpp
+++ b/kernel-shark/src/KsWidgetsLib.cpp
@@ -10,12 +10,13 @@
  */
 
 // KernelShark
-#include "libkshark.h"
-#include "KsUtils.hpp"
 #include "KsCmakeDef.hpp"
 #include "KsPlotTools.hpp"
 #include "KsWidgetsLib.hpp"
 
+namespace KsWidgetsLib
+{
+
 /**
  * @brief Create KsProgressBar.
  *
@@ -76,9 +77,6 @@
 	this->setLayout(&_layout);
 }
 
-namespace KsWidgetsLib
-{
-
 /**
  * @brief Launch a File exists dialog. Use this function to ask the user
  * before overwriting an existing file.
@@ -111,15 +109,19 @@
  * @param name: The name of this widget.
  * @param parent: The parent of this widget.
  */
-KsCheckBoxWidget::KsCheckBoxWidget(const QString &name, QWidget *parent)
+KsCheckBoxWidget::KsCheckBoxWidget(int sd, const QString &name,
+				   QWidget *parent)
 : QWidget(parent),
   _tb(this),
+  _sd(sd),
   _allCb("all", &_tb),
+  _allCbAction(nullptr),
   _cbWidget(this),
   _cbLayout(&_cbWidget),
   _topLayout(this),
+  _stramLabel("", this),
   _name(name),
-  _nameLabel(name + ":  ",&_tb)
+  _nameLabel(name + ":  ", &_tb)
 {
 	setWindowTitle(_name);
 	setMinimumHeight(SCREEN_HEIGHT / 2);
@@ -129,8 +131,11 @@
 
 	_cbWidget.setLayout(&_cbLayout);
 
+	_topLayout.addWidget(&_stramLabel);
+
 	_tb.addWidget(&_nameLabel);
-	_tb.addWidget(&_allCb);
+	_allCbAction = _tb.addWidget(&_allCb);
+
 	_topLayout.addWidget(&_tb);
 
 	_topLayout.addWidget(&_cbWidget);
@@ -138,6 +143,7 @@
 
 	setLayout(&_topLayout);
 	_allCb.setCheckState(Qt::Checked);
+// 	setVisibleCbAll(false);
 }
 
 /**
@@ -218,15 +224,19 @@
  * @param cbw: A KsCheckBoxWidget to be nested in this dialog.
  * @param parent: The parent of this widget.
  */
-KsCheckBoxDialog::KsCheckBoxDialog(KsCheckBoxWidget *cbw, QWidget *parent)
-: QDialog(parent), _checkBoxWidget(cbw),
+KsCheckBoxDialog::KsCheckBoxDialog(QVector<KsCheckBoxWidget *> cbws, QWidget *parent)
+: QDialog(parent), _checkBoxWidgets(cbws),
   _applyButton("Apply", this),
   _cancelButton("Cancel", this)
 {
 	int buttonWidth;
 
-	setWindowTitle(cbw->name());
-	_topLayout.addWidget(_checkBoxWidget);
+	if (!cbws.isEmpty())
+		setWindowTitle(cbws[0]->name());
+
+	for (auto const &w: _checkBoxWidgets)
+		_cbLayout.addWidget(w);
+	_topLayout.addLayout(&_cbLayout);
 
 	buttonWidth = STRING_WIDTH("--Cancel--");
 	_applyButton.setFixedWidth(buttonWidth);
@@ -256,16 +266,170 @@
 
 void KsCheckBoxDialog::_applyPress()
 {
-	QVector<int> vec = _checkBoxWidget->getCheckedIds();
-	emit apply(vec);
+	QVector<int> vec;
 
 	/*
 	 * Disconnect _applyButton. This is done in order to protect
 	 * against multiple clicks.
 	 */
 	disconnect(_applyButtonConnection);
+
+	for (auto const &w: _checkBoxWidgets) {
+		vec = w->getCheckedIds();
+		emit apply(w->sd(), vec);
+	}
 }
 
+KsComboPlotDialog::KsComboPlotDialog(QWidget *parent)
+: _hostStreamLabel("Host data stream:"),
+  _guestStreamLabel("Guest data stream"),
+  _hostStreamComboBox(this),
+  _guestStreamComboBox(this),
+  _vcpuCheckBoxWidget(nullptr),
+  _hostCheckBoxWidget(nullptr),
+  _applyButton("Apply", this),
+  _cancelButton("Cancel", this)
+{
+	kshark_context *kshark_ctx(nullptr);
+	int *streamIds, buttonWidth;
+	int sdHost(0), sdGuest(1);
+	QStringList streamList;
+
+	auto lamAddLine = [&] {
+		QFrame* line = new QFrame();
+
+		line->setFrameShape(QFrame::HLine);
+		line->setFrameShadow(QFrame::Sunken);
+		_topLayout.addWidget(line);
+	};
+
+	setWindowTitle("Combo Plots");
+
+	if (!kshark_instance(&kshark_ctx) ||
+	    kshark_ctx->n_streams < 2)
+		return;
+
+	streamIds = kshark_all_streams(kshark_ctx);
+	_hostStreamComboBox.addItem(QString::number(streamIds[0]));
+	for (int i = 1; i < kshark_ctx->n_streams; ++i) {
+		_hostStreamComboBox.addItem(QString::number(streamIds[i]));
+		_guestStreamComboBox.addItem(QString::number(streamIds[i]));
+	}
+
+	_streamMenuLayout.addWidget(&_hostStreamLabel, 0, 0);
+	_streamMenuLayout.addWidget(&_hostStreamComboBox, 0, 1);
+
+	_streamMenuLayout.addWidget(&_guestStreamLabel, 1, 0);
+	_streamMenuLayout.addWidget(&_guestStreamComboBox, 1, 1);
+
+	_topLayout.addLayout(&_streamMenuLayout);
+
+	lamAddLine();
+
+	_hostCheckBoxWidget = new KsTasksCheckBoxWidget(sdHost, true, this);
+	_hostCheckBoxWidget->setStream(QString(kshark_ctx->stream[sdHost]->file));
+	_hostCheckBoxWidget->setSingleSelection();
+	_hostCheckBoxWidget->setDefault(false);
+
+	_vcpuCheckBoxWidget = new KsCPUCheckBoxWidget(sdGuest, this);
+	_vcpuCheckBoxWidget->setStream(QString(kshark_ctx->stream[sdGuest]->file));
+	_vcpuCheckBoxWidget->setSingleSelection();
+	_vcpuCheckBoxWidget->setDefault(false);
+
+	_cbLayout.addWidget(_hostCheckBoxWidget);
+	_cbLayout.addWidget(_vcpuCheckBoxWidget);
+
+	_topLayout.addLayout(&_cbLayout);
+
+	buttonWidth = STRING_WIDTH("--Cancel--");
+	_applyButton.setFixedWidth(buttonWidth);
+	_cancelButton.setFixedWidth(buttonWidth);
+
+	_buttonLayout.addWidget(&_applyButton);
+	_applyButton.setAutoDefault(false);
+
+	_buttonLayout.addWidget(&_cancelButton);
+	_cancelButton.setAutoDefault(false);
+
+	_buttonLayout.setAlignment(Qt::AlignLeft);
+	_topLayout.addLayout(&_buttonLayout);
+
+	_applyButtonConnection =
+		connect(&_applyButton,	&QPushButton::pressed,
+			this,		&KsComboPlotDialog::_applyPress);
+
+	connect(&_applyButton,	&QPushButton::pressed,
+		this,		&QWidget::close);
+
+	connect(&_cancelButton,	&QPushButton::pressed,
+		this,		&QWidget::close);
+
+	/*
+	 * Using the old Signal-Slot syntax because QComboBox::currentIndexChanged
+	 * has overloads.
+	 */
+	connect(&_hostStreamComboBox,	SIGNAL(currentIndexChanged(const QString &)),
+		this,			SLOT(_hostStreamChanged(const QString &)));
+
+	connect(&_guestStreamComboBox,	SIGNAL(currentIndexChanged(const QString &)),
+		this,			SLOT(_guestStreamChanged(const QString &)));
+
+	setLayout(&_topLayout);
+}
+
+void KsComboPlotDialog::_applyPress()
+{
+	QVector<int> combo(4);
+
+	/*
+	 * Disconnect _applyButton. This is done in order to protect
+	 * against multiple clicks.
+	 */
+	disconnect(_applyButtonConnection);
+
+	combo[0] = _hostStreamComboBox.currentText().toInt();
+	combo[1] = _hostCheckBoxWidget->getCheckedIds()[0];
+	combo[2] = _guestStreamComboBox.currentText().toInt();
+	combo[3] = _vcpuCheckBoxWidget->getCheckedIds()[0];
+
+	emit apply(-1, combo);
+}
+
+void KsComboPlotDialog::_hostStreamChanged(const QString &sdStr)
+{
+	qInfo() << "_hostStreamChanged";
+	kshark_context *kshark_ctx(nullptr);
+	int *streamIds, sdHost;
+	QStringList streamList;
+
+	if (!kshark_instance(&kshark_ctx))
+		return;
+
+	sdHost = sdStr.toInt();
+	_guestStreamComboBox.clear();
+	streamIds = kshark_all_streams(kshark_ctx);
+	for (int i = 0; i < kshark_ctx->n_streams; ++i)
+		if (sdHost != streamIds[i])
+			_guestStreamComboBox.addItem(QString::number(streamIds[i]));
+}
+
+void KsComboPlotDialog::_guestStreamChanged(const QString &sdStr)
+{
+	qInfo() << "_guestStreamChanged";
+	kshark_context *kshark_ctx(nullptr);
+	int sdGuest;
+
+	if (!kshark_instance(&kshark_ctx))
+		return;
+
+	sdGuest = sdStr.toInt();
+
+	delete _vcpuCheckBoxWidget;
+
+	_vcpuCheckBoxWidget = new KsCPUCheckBoxWidget(sdGuest, this);
+	_vcpuCheckBoxWidget->setDefault(false);
+	_cbLayout.addWidget(_vcpuCheckBoxWidget);
+}
 
 /**
  * @brief Create KsCheckBoxTable.
@@ -359,9 +523,9 @@
  * @param name: The name of this widget.
  * @param parent: The parent of this widget.
  */
-KsCheckBoxTableWidget::KsCheckBoxTableWidget(const QString &name,
+KsCheckBoxTableWidget::KsCheckBoxTableWidget(int sd, const QString &name,
 					     QWidget *parent)
-: KsCheckBoxWidget(name, parent),
+: KsCheckBoxWidget(sd, name, parent),
   _table(this)
 {
 	connect(&_table,	&KsCheckBoxTable::changeState,
@@ -514,9 +678,9 @@
  * @param name: The name of this widget.
  * @param parent: The parent of this widget.
  */
-KsCheckBoxTreeWidget::KsCheckBoxTreeWidget(const QString &name,
+KsCheckBoxTreeWidget::KsCheckBoxTreeWidget(int sd, const QString &name,
 					   QWidget *parent)
-: KsCheckBoxWidget(name, parent),
+: KsCheckBoxWidget(sd, name, parent),
   _tree(this)
 {
 	connect(&_tree,	&KsCheckBoxTree::verify,
@@ -614,24 +778,29 @@
 /**
  * @brief Create KsCPUCheckBoxWidget.
  *
- * @param tep: Trace event parseer.
+ * @param sd: Data stream identifier.
  * @param parent: The parent of this widget.
  */
-KsCPUCheckBoxWidget::KsCPUCheckBoxWidget(struct tep_handle *tep,
-					 QWidget *parent)
-: KsCheckBoxTreeWidget("CPUs", parent)
+KsCPUCheckBoxWidget::KsCPUCheckBoxWidget(int sd, QWidget *parent)
+: KsCheckBoxTreeWidget(sd, "CPUs", parent)
 {
 	int nCPUs(0), height(FONT_HEIGHT * 1.5);
+	kshark_context *kshark_ctx(nullptr);
+	kshark_data_stream *stream;
 	KsPlot::ColorTable colors;
 	QString style;
 
+	if (!kshark_instance(&kshark_ctx))
+		return;
+
 	style = QString("QTreeView::item { height: %1 ;}").arg(height);
 	_tree.setStyleSheet(style);
 
 	_initTree();
 
-	if (tep)
-		nCPUs = tep_get_cpus(tep);
+	stream = kshark_get_data_stream(kshark_ctx, sd);
+	if (stream)
+		nCPUs = tep_get_cpus(stream->pevent);
 
 	_id.resize(nCPUs);
 	_cb.resize(nCPUs);
@@ -656,22 +825,107 @@
 /**
  * @brief Create KsEventsCheckBoxWidget.
  *
- * @param tep: Trace event parseer.
+ * @param sd: Data stream identifier.
  * @param parent: The parent of this widget.
  */
-KsEventsCheckBoxWidget::KsEventsCheckBoxWidget(struct tep_handle *tep,
+// KsEventsCheckBoxWidget::KsEventsCheckBoxWidget(int sd, QWidget *parent)
+// : KsCheckBoxTreeWidget(sd, "Events", parent)
+// {
+// 	QTreeWidgetItem *sysItem, *evtItem;
+// 	tep_event_format **events(nullptr);
+// 	kshark_context *kshark_ctx(nullptr);
+// 	kshark_data_stream *stream;
+// 	QString sysName, evtName;
+// 	int nEvts(0), i(0);
+// 
+// 	if (!kshark_instance(&kshark_ctx))
+// 		return;
+// 
+// 	stream = kshark_get_data_stream(kshark_ctx, sd);
+// 	if (stream) {
+// 		nEvts = tep_get_events_count(stream->pevent);
+// 		events = tep_list_events(stream->pevent, TEP_EVENT_SORT_SYSTEM);
+// 	}
+// 
+// 	_initTree();
+// 	_id.resize(nEvts);
+// 	_cb.resize(nEvts);
+// 
+// 	while (i < nEvts) {
+// 		sysName = events[i]->system;
+// 		sysItem = new QTreeWidgetItem;
+// 		sysItem->setText(0, sysName);
+// 		sysItem->setCheckState(0, Qt::Checked);
+// 		_tree.addTopLevelItem(sysItem);
+// 
+// 		while (sysName == events[i]->system) {
+// 			evtName = events[i]->name;
+// 			evtItem = new QTreeWidgetItem;
+// 			evtItem->setText(0, evtName);
+// 			evtItem->setCheckState(0, Qt::Checked);
+// 			evtItem->setFlags(evtItem->flags() |
+// 					  Qt::ItemIsUserCheckable);
+// 
+// 			sysItem->addChild(evtItem);
+// 
+// 			_id[i] = events[i]->id;
+// 			_cb[i] = evtItem;
+// 
+// 			if (++i == nEvts)
+// 				break;
+// 		}
+// 	}
+// 
+// 	_tree.sortItems(0, Qt::AscendingOrder);
+// 	_adjustSize();
+// }
+
+KsEventsCheckBoxWidget::KsEventsCheckBoxWidget(tep_handle *pevent,
 					       QWidget *parent)
-: KsCheckBoxTreeWidget("Events", parent)
+: KsCheckBoxTreeWidget(-1, "Events", parent)
+{
+	tep_event **events;
+	int nEvts;
+
+	if (pevent) {
+		nEvts = tep_get_events_count(pevent);
+		events = tep_list_events(pevent,
+					TEP_EVENT_SORT_SYSTEM);
+		_makeItems(events, nEvts);
+	}
+}
+
+/**
+ * @brief Create KsEventsCheckBoxWidget.
+ *
+ * @param pevent: Page event used to parse the page.
+ * @param parent: The parent of this widget.
+ */
+KsEventsCheckBoxWidget::KsEventsCheckBoxWidget(int sd, QWidget *parent)
+: KsCheckBoxTreeWidget(sd, "Events", parent)
+{
+	kshark_context *kshark_ctx(nullptr);
+	kshark_data_stream *stream;
+	tep_event **events;
+	int nEvts(0);
+
+	if (!kshark_instance(&kshark_ctx))
+		return;
+
+	stream = kshark_get_data_stream(kshark_ctx, sd);
+	if (stream) {
+		nEvts = tep_get_events_count(stream->pevent);
+		events = tep_list_events(stream->pevent,
+					 TEP_EVENT_SORT_SYSTEM);
+		_makeItems(events, nEvts);
+	}
+}
+
+void KsEventsCheckBoxWidget::_makeItems(tep_event **events, int nEvts)
 {
 	QTreeWidgetItem *sysItem, *evtItem;
-	tep_event **events(nullptr);
 	QString sysName, evtName;
-	int nEvts(0), i(0);
-
-	if (tep) {
-		nEvts = tep_get_events_count(tep);
-		events = tep_list_events(tep, TEP_EVENT_SORT_SYSTEM);
-	}
+	int i(0);
 
 	_initTree();
 	_id.resize(nEvts);
@@ -719,16 +973,17 @@
 /**
  * @brief Create KsTasksCheckBoxWidget.
  *
- * @param pevent: Page event used to parse the page.
+ * @param sd: Data stream identifier.
  * @param cond: If True make a "Show Task" widget. Otherwise make "Hide Task".
  * @param parent: The parent of this widget.
  */
-KsTasksCheckBoxWidget::KsTasksCheckBoxWidget(struct tep_handle *pevent,
-					     bool cond, QWidget *parent)
-: KsCheckBoxTableWidget("Tasks", parent)
+KsTasksCheckBoxWidget::KsTasksCheckBoxWidget(int sd, bool cond, QWidget *parent)
+: KsCheckBoxTableWidget(sd, "Tasks", parent),
+  _cond(cond)
 {
 	kshark_context *kshark_ctx(nullptr);
 	QTableWidgetItem *pidItem, *comItem;
+	kshark_data_stream *stream;
 	KsPlot::ColorTable colors;
 	QStringList headers;
 	const char *comm;
@@ -737,12 +992,16 @@
 	if (!kshark_instance(&kshark_ctx))
 		return;
 
+	stream = kshark_get_data_stream(kshark_ctx, sd);
+	if (!stream)
+		return;
+
 	if (_cond)
 		headers << "Show" << "Pid" << "Task";
 	else
 		headers << "Hide" << "Pid" << "Task";
 
-	_id = KsUtils::getPidList();
+	_id = KsUtils::getPidList(sd);
 	nTasks = _id.count();
 	_initTable(headers, nTasks);
 	colors = KsPlot::getTaskColorTable();
@@ -752,7 +1011,7 @@
 		pidItem	= new QTableWidgetItem(tr("%1").arg(pid));
 		_table.setItem(i, 1, pidItem);
 
-		comm = tep_data_comm_from_pid(kshark_ctx->pevent, pid);
+		comm = tep_data_comm_from_pid(stream->pevent, pid);
 		comItem = new QTableWidgetItem(tr(comm));
 
 		pidItem->setBackgroundColor(QColor(colors[pid].r(),
@@ -774,9 +1033,9 @@
  * @param pluginList: A list of plugin names.
  * @param parent: The parent of this widget.
  */
-KsPluginCheckBoxWidget::KsPluginCheckBoxWidget(QStringList pluginList,
+KsPluginCheckBoxWidget::KsPluginCheckBoxWidget(int sd, QStringList pluginList,
 					       QWidget *parent)
-: KsCheckBoxTableWidget("Plugins", parent)
+: KsCheckBoxTableWidget(sd, "Plugins", parent)
 {
 	QTableWidgetItem *nameItem, *infoItem;
 	QStringList headers;
diff --git a/kernel-shark/src/KsWidgetsLib.hpp b/kernel-shark/src/KsWidgetsLib.hpp
index 6f22374..e55e408 100644
--- a/kernel-shark/src/KsWidgetsLib.hpp
+++ b/kernel-shark/src/KsWidgetsLib.hpp
@@ -15,6 +15,13 @@
 // Qt
 #include <QtWidgets>
 
+// KernelShark
+#include "libkshark.h"
+#include "KsUtils.hpp"
+
+namespace KsWidgetsLib
+{
+
 /**
  * The KsProgressBar class provides a visualization of the progress of a
  * running job.
@@ -66,9 +73,6 @@
 /** The width of the KsMessageDialog widget. */
 #define KS_MSG_DIALOG_WIDTH  (SCREEN_WIDTH / 10)
 
-namespace KsWidgetsLib
-{
-
 bool fileExistsDialog(QString fileName);
 
 }; // KsWidgetsLib
@@ -81,7 +85,7 @@
 {
 	Q_OBJECT
 public:
-	KsCheckBoxWidget(const QString &name = "",
+	KsCheckBoxWidget(int sd, const QString &name = "",
 			 QWidget *parent = nullptr);
 
 	/** Get the name of the widget. */
@@ -95,19 +99,51 @@
 		return false;
 	}
 
+	void setVisibleCbAll(bool v) {_allCbAction->setVisible(v);}
+
 	void setDefault(bool);
 
 	void set(QVector<bool> v);
 
 	QVector<int> getCheckedIds();
 
+	/**
+	 * Get the identifier of the Data stream for which the selection
+	 * applies.
+	 */
+	int sd() const {return _sd;}
+
+	void setStream(QString stream)
+	{
+		_streamName = stream;
+		KsUtils::setElidedText(&_stramLabel, _streamName,
+				       Qt::ElideLeft, width());
+		QApplication::processEvents();
+	}
+
+	/**
+	 * Reimplemented event handler used to update the geometry of the widget on
+	 * resize events.
+	 */
+	void resizeEvent(QResizeEvent* event)
+	{
+		KsUtils::setElidedText(&_stramLabel, _streamName,
+				       Qt::ElideLeft, width());
+		QApplication::processEvents();
+	}
+
 private:
 	QToolBar _tb;
 
 protected:
+	/** Identifier of the Data stream for which the selection applies. */
+	int		_sd;
+
 	/** The "all" checkboxe. */
 	QCheckBox	_allCb;
 
+	QAction		*_allCbAction;
+
 	/** A vector of Id numbers coupled to each checkboxe. */
 	QVector<int>	_id;
 
@@ -120,6 +156,12 @@
 	/** The top level layout of this widget. */
 	QVBoxLayout	_topLayout;
 
+	QString		_streamName;
+	/**
+	 * A label to show the name of the Data stream for which the selection
+	 * applies. */
+	QLabel		_stramLabel;
+
 	/** The name of this widget. */
 	QString		_name;
 
@@ -146,20 +188,20 @@
 public:
 	KsCheckBoxDialog() = delete;
 
-	KsCheckBoxDialog(KsCheckBoxWidget *cbw, QWidget *parent = nullptr);
+	KsCheckBoxDialog(QVector<KsCheckBoxWidget *> cbws, QWidget *parent = nullptr);
 
 signals:
 	/** Signal emitted when the "Apply" button is pressed. */
-	void apply(QVector<int>);
+	void apply(int sd, QVector<int>);
 
 private:
 	void _applyPress();
 
 	QVBoxLayout		_topLayout;
 
-	QHBoxLayout		_buttonLayout;
+	QHBoxLayout		_cbLayout, _buttonLayout;
 
-	KsCheckBoxWidget	*_checkBoxWidget;
+	QVector<KsCheckBoxWidget *>	_checkBoxWidgets;
 
 	QPushButton		_applyButton, _cancelButton;
 
@@ -199,9 +241,15 @@
 {
 	Q_OBJECT
 public:
-	KsCheckBoxTableWidget(const QString &name = "",
+	KsCheckBoxTableWidget(int sd, const QString &name = "",
 			      QWidget *parent = nullptr);
 
+	void setSingleSelection()
+	{
+		_table.setSelectionMode(QAbstractItemView::SingleSelection); // !!!!!!!!!!
+		setVisibleCbAll(false);
+	}
+
 protected:
 	void _adjustSize();
 
@@ -260,9 +308,15 @@
 public:
 	KsCheckBoxTreeWidget() = delete;
 
-	KsCheckBoxTreeWidget(const QString &name = "",
+	KsCheckBoxTreeWidget(int sd, const QString &name = "",
 			     QWidget *parent = nullptr);
 
+	void setSingleSelection()
+	{
+		_tree.setSelectionMode(QAbstractItemView::SingleSelection);  // !!!!!!!!!!
+		setVisibleCbAll(false);
+	}
+
 protected:
 	void _adjustSize();
 
@@ -290,6 +344,45 @@
 	void _verify();
 };
 
+class KsComboPlotDialog : public QDialog
+{
+	Q_OBJECT
+public:
+	explicit KsComboPlotDialog(QWidget *parent = nullptr);
+
+signals:
+	/** Signal emitted when the "Apply" button is pressed. */
+	void apply(int sd, QVector<int>);
+
+private:
+	QVBoxLayout			_topLayout;
+
+	QGridLayout			_streamMenuLayout;
+
+	QHBoxLayout			_cbLayout, _buttonLayout;
+
+	QLabel				_hostStreamLabel, _guestStreamLabel;
+
+	QComboBox			_hostStreamComboBox;
+
+	QComboBox			_guestStreamComboBox;
+
+	KsCheckBoxTreeWidget		*_vcpuCheckBoxWidget;
+
+	KsCheckBoxTableWidget		*_hostCheckBoxWidget;
+
+	QPushButton			_applyButton, _cancelButton;
+
+	QMetaObject::Connection		_applyButtonConnection;
+
+	void _applyPress();
+
+private slots:
+	void _hostStreamChanged(const QString&);
+
+	void _guestStreamChanged(const QString&);
+};
+
 /**
  * The KsCPUCheckBoxWidget class provides a widget for selecting CPU plots to
  * show.
@@ -298,8 +391,7 @@
 {
 	KsCPUCheckBoxWidget() = delete;
 
-	KsCPUCheckBoxWidget(struct tep_handle *pe,
-			    QWidget *parent = nullptr);
+	KsCPUCheckBoxWidget(int sd, QWidget *parent = nullptr);
 };
 
 /**
@@ -310,9 +402,7 @@
 {
 	KsTasksCheckBoxWidget() = delete;
 
-	KsTasksCheckBoxWidget(struct tep_handle *pe,
-			      bool cond = true,
-			      QWidget *parent = nullptr);
+	KsTasksCheckBoxWidget(int sd, bool cond, QWidget *parent = nullptr);
 
 private:
 	/**
@@ -330,10 +420,14 @@
 {
 	KsEventsCheckBoxWidget() = delete;
 
-	KsEventsCheckBoxWidget(struct tep_handle *pe,
-			       QWidget *parent = nullptr);
+	KsEventsCheckBoxWidget(int sd, QWidget *parent = nullptr);
+
+	KsEventsCheckBoxWidget(tep_handle *pevent, QWidget *parent = nullptr);
 
 	void removeSystem(QString name);
+
+private:
+	void _makeItems(tep_event **events, int nEvts);
 };
 
 /**
@@ -343,7 +437,7 @@
 {
 	KsPluginCheckBoxWidget() = delete;
 
-	KsPluginCheckBoxWidget(QStringList pluginList,
+	KsPluginCheckBoxWidget(int sd, QStringList pluginList,
 			       QWidget *parent = nullptr);
 };
 
diff --git a/kernel-shark/src/libkshark-collection.c b/kernel-shark/src/libkshark-collection.c
index d2e4674..79e1a6c 100644
--- a/kernel-shark/src/libkshark-collection.c
+++ b/kernel-shark/src/libkshark-collection.c
@@ -75,7 +75,8 @@
 			     size_t n_rows,
 			     matching_condition_func cond,
 			     int sd,
-			     int val,
+			     int *values,
+			     int n_val,
 			     size_t margin)
 {
 	struct kshark_entry_collection *col_ptr = NULL;
@@ -118,7 +119,7 @@
 	}
 
 	for (i = first + margin; i < end; ++i) {
-		if (!cond(kshark_ctx, data[i], sd, val)) {
+		if (!cond(kshark_ctx, data[i], sd, values)) {
 			/*
 			 * The entry is irrelevant for this collection.
 			 * Do nothing.
@@ -148,7 +149,7 @@
 			}
 		} else if (good_data &&
 			   data[i]->next &&
-			   !cond(kshark_ctx, data[i]->next, sd, val)) {
+			   !cond(kshark_ctx, data[i]->next, sd, values)) {
 			/*
 			 * Break the collection here. Add some margin data
 			 * after the data of interest.
@@ -169,7 +170,7 @@
 			 */
 			if (i + margin >= j) {
 				for (;j < i + margin; ++j) {
-					if (cond(kshark_ctx, data[j], sd, val)) {
+					if (cond(kshark_ctx, data[j], sd, values)) {
 						/*
 						 * Good data has been found.
 						 * Continue extending the
@@ -229,7 +230,10 @@
 	}
 
 	col_ptr->cond = cond;
-	col_ptr->val = val;
+	col_ptr->n_val = n_val;
+	col_ptr->stream_id = sd;
+	col_ptr->values = malloc(n_val * sizeof(*col_ptr->values));
+	memcpy(col_ptr->values, values, n_val * sizeof(*col_ptr->values));
 
 	col_ptr->size = resume_count;
 	for (i = 0; i < col_ptr->size; ++i) {
@@ -484,7 +488,7 @@
 							   0,
 							   req_tmp->cond,
 							   req_tmp->sd,
-							   req_tmp->val,
+							   req_tmp->values,
 							   req_tmp->vis_only,
 							   req_tmp->vis_mask);
 
@@ -572,7 +576,7 @@
 							   0,
 							   req_tmp->cond,
 							   req_tmp->sd,
-							   req_tmp->val,
+							   req_tmp->values,
 							   req_tmp->vis_only,
 							   req_tmp->vis_mask);
 
@@ -709,6 +713,17 @@
 	return entry;
 }
 
+static bool val_compare(int *val_a, int *val_b, size_t n_val)
+{
+	size_t i;
+
+	for (i = 0; i < n_val; ++i)
+		if (val_a[i] != val_b[i])
+			return false;
+
+	return true;
+}
+
 /**
  * @brief Search the list of Data collections and find the collection defined
  *	  with a given Matching condition function and value.
@@ -724,13 +739,14 @@
 struct kshark_entry_collection *
 kshark_find_data_collection(struct kshark_entry_collection *col,
 			    matching_condition_func cond,
-			    int sd, int val)
+			    int sd, int *values, size_t n_val)
 {
 	while (col) {
 		if (col->cond == cond &&
-		    col->sd == sd &&
-		    col->val == val)
-			return col;
+		    col->stream_id == sd &&
+		    col->n_val == n_val &&
+		    val_compare(col->values, values, n_val))
+				return col;
 
 		col = col->next;
 	}
@@ -758,6 +774,7 @@
 {
 	free(col->resume_points);
 	free(col->break_points);
+	free(col->values);
 	free(col);
 }
 
@@ -788,7 +805,7 @@
 				size_t n_rows,
 				matching_condition_func cond,
 				int sd,
-				int val,
+				int *values, size_t n_val,
 				size_t margin)
 {
 	struct kshark_entry_collection *col;
@@ -796,7 +813,7 @@
 	col = kshark_add_collection_to_list(kshark_ctx,
 					    &kshark_ctx->collections,
 					    data, n_rows,
-					    cond, sd, val,
+					    cond, sd, values, n_val,
 					    margin);
 
 	return col;
@@ -829,14 +846,15 @@
 			      struct kshark_entry **data,
 			      size_t n_rows,
 			      matching_condition_func cond,
-			      int sd, int val,
+			      int sd, int *values, size_t n_val,
 			      size_t margin)
 {
 	struct kshark_entry_collection *col;
 
 	col = kshark_data_collection_alloc(kshark_ctx, data,
 					   0, n_rows,
-					   cond, sd, val,
+					   cond, sd,
+					   values, n_val,
 					   margin);
 
 	if (col) {
@@ -861,15 +879,16 @@
  */
 void kshark_unregister_data_collection(struct kshark_entry_collection **col,
 				       matching_condition_func cond,
-				       int sd, int val)
+				       int sd, int *values, size_t n_val)
 {
 	struct kshark_entry_collection **last = col;
 	struct kshark_entry_collection *list;
 
 	for (list = *col; list; list = list->next) {
 		if (list->cond == cond &&
-		    list->sd == sd &&
-		    list->val == val) {
+		    list->stream_id == sd &&
+		    list->n_val == n_val &&
+		    val_compare(list->values, values, n_val)) {
 			*last = list->next;
 			kshark_free_data_collection(list);
 			return;
diff --git a/kernel-shark/src/libkshark-configio.c b/kernel-shark/src/libkshark-configio.c
index c93aa05..76c54a8 100644
--- a/kernel-shark/src/libkshark-configio.c
+++ b/kernel-shark/src/libkshark-configio.c
@@ -332,7 +332,7 @@
 	return true;
 
  fail:
-	fprintf(stderr, "Failed to get config. document [%s]>.\n", key);
+	fprintf(stderr, "Failed to get config. document <%s>.\n", key);
 	return false;
 }
 
@@ -896,6 +896,11 @@
 	}
 }
 
+static int compare_ids(const void* a, const void* b)
+{
+    return ( *(int*)a - *(int*)b );
+}
+
 static bool kshark_filter_array_to_json(struct tracecmd_filter_id *filter,
 					const char *filter_name,
 					struct json_object *jobj)
@@ -914,6 +919,8 @@
 	if (!ids)
 		return true;
 
+	qsort(ids, filter->count, sizeof(int), compare_ids);
+
 	/* Create a Json array and fill the Id values into it. */
 	jfilter_data = json_object_new_array();
 	if (!jfilter_data)
@@ -1338,6 +1345,60 @@
 	}
 }
 
+static bool kshark_calib_array_from_json(struct kshark_context *kshark_ctx,
+					 int sd, struct json_object *jobj)
+{
+	json_object *jcalib_argv, *jcalib;
+	int64_t *calib_argv = NULL;
+	int i, calib_length;
+
+	if (!json_object_object_get_ex(jobj, "calib. array", &jcalib_argv) ||
+	    json_object_get_type(jcalib_argv) != json_type_array)
+		return false;
+
+	calib_length = json_object_array_length(jcalib_argv);
+	if (!calib_length)
+		return false;
+
+	calib_argv = calloc(calib_length, sizeof(*calib_argv));
+	for (i = 0; i < calib_length; ++i) {
+		jcalib = json_object_array_get_idx(jcalib_argv, i);
+		calib_argv[i] = json_object_get_int64(jcalib);
+	}
+
+	kshark_ctx->stream[sd]->calib = kshark_offset_calib;
+	kshark_ctx->stream[sd]->calib_array = calib_argv;
+	kshark_ctx->stream[sd]->calib_array_size = calib_length;
+
+	return true;
+}
+
+/**
+ * @brief Load from Configuration document the value of the time calibration
+ *	  constants into a Configuration document.
+ *
+ * @param kshark_ctx: Input location for session context pointer.
+ * @param sd: Data stream identifier.
+ * @param conf: Input location for the kshark_config_doc instance. Currently
+ *		only Json format is supported. If NULL, a new Configuration
+ *		document will be created.
+ *
+ * @returns True on success, otherwise False.
+ */
+bool kshark_import_calib_array(struct kshark_context *kshark_ctx, int sd,
+			       struct kshark_config_doc *conf)
+{
+	switch (conf->format) {
+	case KS_CONFIG_JSON:
+		return kshark_calib_array_from_json(kshark_ctx, sd, conf->conf_doc);
+
+	default:
+		fprintf(stderr, "Document format %d not supported\n",
+			conf->format);
+		return false;
+	}
+}
+
 static bool kshark_calib_array_to_json(struct kshark_context *kshark_ctx,
 				       int sd, struct json_object *jobj)
 {
@@ -1488,12 +1549,12 @@
 	/* Save a filter only if it contains Id values. */
 	ret = true;
 	if (filter_is_set(stream->show_task_filter))
-		ret &= kshark_import_filter_array(stream->show_task_filter,
+		ret &= kshark_export_filter_array(stream->show_task_filter,
 						  KS_SHOW_TASK_FILTER_NAME,
 						  *conf);
 
 	if (filter_is_set(stream->hide_task_filter))
-		ret &= kshark_import_filter_array(stream->hide_task_filter,
+		ret &= kshark_export_filter_array(stream->hide_task_filter,
 						  KS_HIDE_TASK_FILTER_NAME,
 						  *conf);
 
@@ -1760,6 +1821,8 @@
 	return NULL;
 }
 
+
+
 /**
  * @brief Load Data Stream from a Configuration document.
  *
@@ -1775,16 +1838,15 @@
  *	    negative error code.
  */
 int kshark_import_dstream(struct kshark_context *kshark_ctx,
-			  struct kshark_config_doc *conf,
+			  struct kshark_config_doc *conf/*,
 			  struct kshark_entry ***data_rows,
-			  size_t *data_size)
+			  size_t *data_size*/)
 {
 	struct kshark_config_doc *file_conf, *filter_conf;
 	bool ret = false;
 	int sd = -EFAULT;
 
-	*data_size = 0;
-
+// 	*data_size = 0;
 	if (!kshark_type_check(conf, "kshark.config.stream"))
 		return sd;
 
@@ -1796,6 +1858,7 @@
 	    kshark_config_doc_get(conf, "filters", filter_conf)) {
 		sd = kshark_import_trace_file(kshark_ctx, file_conf);
 		if (sd >= 0) {
+			kshark_import_calib_array(kshark_ctx, sd, conf);
 			ret = kshark_import_all_filters(kshark_ctx, sd,
 							filter_conf);
 			if (!ret) {
@@ -1803,8 +1866,11 @@
 				return -EFAULT;
 			}
 
-			*data_size = kshark_load_data_entries(kshark_ctx, sd,
-							      data_rows);
+			kshark_handle_all_plugins(kshark_ctx, sd,
+						  KSHARK_PLUGIN_UPDATE);
+
+// 			*data_size = kshark_load_data_entries(kshark_ctx, sd,
+// 							      data_rows);
 		}
 	}
 
@@ -1827,6 +1893,9 @@
 	for (int i = 0; i < kshark_ctx->n_streams; ++i) {
 		dstream_conf = kshark_export_dstream(kshark_ctx, stream_ids[i],
 						     KS_CONFIG_JSON);
+		if (!dstream_conf)
+			goto fail;
+
 		json_object_array_put_idx(jall_streams, i, dstream_conf->conf_doc);
 
 		/* Free only the kshark_config_doc object. */
@@ -1834,7 +1903,13 @@
 	}
 
 	json_object_object_add(jobj, "data streams", jall_streams);
+
 	return true;
+
+ fail:
+	json_object_put(jall_streams);
+
+	return false;
 }
 
 /**
@@ -1849,24 +1924,26 @@
  * @returns True on success, otherwise False.
  */
 bool kshark_export_all_dstreams(struct kshark_context *kshark_ctx,
-				struct kshark_config_doc *conf)
+				struct kshark_config_doc **conf)
 {
-	switch (conf->format) {
+	if (!*conf)
+		*conf = kshark_filter_config_new(KS_CONFIG_JSON);
+
+	if (!*conf)
+		return false;
+
+	switch ((*conf)->format) {
 	case KS_CONFIG_JSON:
 		return kshark_export_all_dstreams_to_json(kshark_ctx,
-							  conf->conf_doc);
+							  (*conf)->conf_doc);
 
 	default:
 		fprintf(stderr, "Document format %d not supported\n",
-			conf->format);
+			(*conf)->format);
 		return false;
 	}
 }
 
-static void __calib(struct kshark_entry *e, int64_t *atgv) {
-	e->ts += atgv[0];
-}
-
 static bool
 kshark_import_all_dstreams_from_json(struct kshark_context *kshark_ctx,
 				     struct json_object *jobj,
@@ -1874,72 +1951,31 @@
 				     size_t *data_size)
 {
 	struct kshark_config_doc dstream_conf;
-	struct kshark_entry **stream_data_rows = NULL, **merged_data_rows;
-	size_t stream_data_size = 0;
-	json_object *jall_streams, *jstream, *jcalib_argv, *jcalib;
-	int sd, i, j, length, calib_length;
-	int64_t *calib_argv = NULL;
+	json_object *jall_streams, *jstream;
+	int sd, i, length;
 
 	if (!json_object_object_get_ex(jobj, "data streams", &jall_streams) ||
 	    json_object_get_type(jall_streams) != json_type_array)
-		goto fail;
+		return false;
 
 	length = json_object_array_length(jall_streams);
 	if (!length)
-		goto fail;
+		return false;
 
 	dstream_conf.format = KS_CONFIG_JSON;
-	jstream = json_object_array_get_idx(jall_streams, 0);
-	dstream_conf.conf_doc = jstream;
-	kshark_import_dstream(kshark_ctx, &dstream_conf, data_rows, data_size);
-
-	for (i = 1; i < length; ++i) {
+	for (i = 0; i < length; ++i) {
 		jstream = json_object_array_get_idx(jall_streams, i);
-
-		if (!json_object_object_get_ex(jstream, "calib. array", &jcalib_argv) ||
-		    json_object_get_type(jcalib_argv) != json_type_array)
-			goto fail;
-
-		calib_length = json_object_array_length(jcalib_argv);
-		if (!calib_length)
-			goto fail;
-
-		calib_argv = calloc(calib_length, sizeof(*calib_argv));
-		for (j = 0; j < calib_length; ++j) {
-			jcalib = json_object_array_get_idx(jcalib_argv, j);
-			calib_argv[j] = json_object_get_int64(jcalib);
-		}
-
 		dstream_conf.conf_doc = jstream;
 
-		sd = kshark_import_dstream(kshark_ctx, &dstream_conf,
-					   &stream_data_rows, &stream_data_size);
+		sd = kshark_import_dstream(kshark_ctx, &dstream_conf);
 
-		merged_data_rows =
-			kshark_data_merge(*data_rows, *data_size,
-					  stream_data_rows, stream_data_size,
-					  __calib, calib_argv);
-
-		free(stream_data_rows);
-		free(*data_rows);
-
-		kshark_ctx->stream[sd]->calib_array = calib_argv;
-		kshark_ctx->stream[sd]->calib_array_size = calib_length;
-
-		stream_data_rows = *data_rows = NULL;
-
-		*data_rows = merged_data_rows;
-		*data_size += stream_data_size;
+		if (sd < 0)
+			return false;
 	}
 
+	*data_size = kshark_load_all_data_entries(kshark_ctx, data_rows);
+
 	return true;
-
- fail:
-	free(*data_rows);
-	free(calib_argv);
-	*data_size = 0;
-
-	return false;
 }
 
 /**
diff --git a/kernel-shark/src/libkshark-model.c b/kernel-shark/src/libkshark-model.c
index dc59d17..f056745 100644
--- a/kernel-shark/src/libkshark-model.c
+++ b/kernel-shark/src/libkshark-model.c
@@ -478,7 +478,6 @@
 
 	/* Set the new Lower Overflow bin. */
 	ksmodel_set_lower_edge(histo);
-
 	/*
 	 * Copy the the mapping indexes of all overlaping bins starting from
 	 * bin "0" of the new histo. Note that the number of overlaping bins
@@ -778,7 +777,7 @@
 ksmodel_entry_front_request_alloc(struct kshark_trace_histo *histo,
 				  int bin, bool vis_only,
 				  matching_condition_func func,
-				  int sd, int val)
+				  int sd, int *values)
 {
 	size_t first, n;
 
@@ -790,7 +789,7 @@
 	first = ksmodel_first_index_at_bin(histo, bin);
 
 	return kshark_entry_request_alloc(first, n,
-					  func, sd, val,
+					  func, sd, values,
 					  vis_only, KS_GRAPH_VIEW_FILTER_MASK);
 }
 
@@ -798,7 +797,7 @@
 ksmodel_entry_back_request_alloc(struct kshark_trace_histo *histo,
 				 int bin, bool vis_only,
 				 matching_condition_func func,
-				 int sd, int val)
+				 int sd, int *values)
 {
 	size_t first, n;
 
@@ -810,7 +809,7 @@
 	first = ksmodel_last_index_at_bin(histo, bin);
 
 	return kshark_entry_request_alloc(first, n,
-					  func, sd, val,
+					  func, sd, values,
 					  vis_only, KS_GRAPH_VIEW_FILTER_MASK);
 }
 
@@ -903,7 +902,7 @@
 const struct kshark_entry *
 ksmodel_get_entry_front(struct kshark_trace_histo *histo,
 			int bin, bool vis_only,
-			matching_condition_func func, int sd, int val,
+			matching_condition_func func, int sd, int *values,
 			struct kshark_entry_collection *col,
 			ssize_t *index)
 {
@@ -915,7 +914,7 @@
 
 	/* Set the position at the beginning of the bin and go forward. */
 	req = ksmodel_entry_front_request_alloc(histo, bin, vis_only,
-							    func, sd, val);
+							    func, sd, values);
 	if (!req)
 		return NULL;
 
@@ -951,7 +950,7 @@
 const struct kshark_entry *
 ksmodel_get_entry_back(struct kshark_trace_histo *histo,
 		       int bin, bool vis_only,
-		       matching_condition_func func, int sd, int val,
+		       matching_condition_func func, int sd, int *values,
 		       struct kshark_entry_collection *col,
 		       ssize_t *index)
 {
@@ -963,7 +962,7 @@
 
 	/* Set the position at the end of the bin and go backwards. */
 	req = ksmodel_entry_back_request_alloc(histo, bin, vis_only,
-							   func, sd, val);
+							   func, sd, values);
 	if (!req)
 		return NULL;
 
@@ -1022,7 +1021,7 @@
 		return KS_EMPTY_BIN;
 
 	entry = ksmodel_get_entry_front(histo, bin, vis_only,
-					       kshark_match_cpu, sd, cpu,
+					       kshark_match_cpu, sd, &cpu,
 					       col, index);
 
 	return ksmodel_get_entry_pid(entry);
@@ -1056,7 +1055,7 @@
 		return KS_EMPTY_BIN;
 
 	entry = ksmodel_get_entry_back(histo, bin, vis_only,
-					      kshark_match_cpu, sd, cpu,
+					      kshark_match_cpu, sd, &cpu,
 					      col, index);
 
 	return ksmodel_get_entry_pid(entry);
@@ -1106,7 +1105,7 @@
 		return KS_EMPTY_BIN;
 
 	entry = ksmodel_get_entry_front(histo, bin, vis_only,
-					       kshark_match_pid, sd, pid,
+					       kshark_match_pid, sd, &pid,
 					       col,
 					       index);
 	return ksmodel_get_entry_cpu(entry);
@@ -1140,7 +1139,7 @@
 		return KS_EMPTY_BIN;
 
 	entry = ksmodel_get_entry_back(histo, bin, vis_only,
-					      kshark_match_pid, sd, pid,
+					      kshark_match_pid, sd, &pid,
 					      col,
 					      index);
 
@@ -1174,7 +1173,7 @@
 	/* Set the position at the beginning of the bin and go forward. */
 	req = ksmodel_entry_front_request_alloc(histo,
 						bin, true,
-						kshark_match_cpu, sd, cpu);
+						kshark_match_cpu, sd, &cpu);
 	if (!req)
 		return false;
 
@@ -1228,7 +1227,7 @@
 	/* Set the position at the beginning of the bin and go forward. */
 	req = ksmodel_entry_front_request_alloc(histo,
 						bin, true,
-						kshark_match_pid, sd, pid);
+						kshark_match_pid, sd, &pid);
 	if (!req)
 		return false;
 
@@ -1256,15 +1255,15 @@
 }
 
 static bool match_cpu_missed_events(struct kshark_context *kshark_ctx,
-				    struct kshark_entry *e, int sd, int cpu)
+				    struct kshark_entry *e, int sd, int *cpu)
 {
-	return e->event_id == -EOVERFLOW && e->cpu == cpu && e->stream_id == sd;
+	return e->event_id == -EOVERFLOW && e->cpu == *cpu && e->stream_id == sd;
 }
 
 static bool match_pid_missed_events(struct kshark_context *kshark_ctx,
-				    struct kshark_entry *e, int sd, int pid)
+				    struct kshark_entry *e, int sd, int *pid)
 {
-	return e->event_id == -EOVERFLOW && e->pid == pid && e->stream_id == sd;
+	return e->event_id == -EOVERFLOW && e->pid == *pid && e->stream_id == sd;
 }
 
 /**
@@ -1288,7 +1287,7 @@
 			      ssize_t *index)
 {
 	return ksmodel_get_entry_front(histo, bin, true,
-				       match_cpu_missed_events, sd, cpu,
+				       match_cpu_missed_events, sd, &cpu,
 				       col, index);
 }
 
@@ -1313,6 +1312,6 @@
 			       ssize_t *index)
 {
 	return ksmodel_get_entry_front(histo, bin, true,
-				       match_pid_missed_events, sd, pid,
+				       match_pid_missed_events, sd, &pid,
 				       col, index);
 }
diff --git a/kernel-shark/src/libkshark-model.h b/kernel-shark/src/libkshark-model.h
index dd9c6b2..3d81b0e 100644
--- a/kernel-shark/src/libkshark-model.h
+++ b/kernel-shark/src/libkshark-model.h
@@ -110,14 +110,14 @@
 const struct kshark_entry *
 ksmodel_get_entry_front(struct kshark_trace_histo *histo,
 			int bin, bool vis_only,
-			matching_condition_func func, int sd, int val,
+			matching_condition_func func, int sd, int *values,
 			struct kshark_entry_collection *col,
 			ssize_t *index);
 
 const struct kshark_entry *
 ksmodel_get_entry_back(struct kshark_trace_histo *histo,
 		       int bin, bool vis_only,
-		       matching_condition_func func, int sd, int val,
+		       matching_condition_func func, int sd, int *values,
 		       struct kshark_entry_collection *col,
 		       ssize_t *index);
 
diff --git a/kernel-shark/src/libkshark-plugin.c b/kernel-shark/src/libkshark-plugin.c
index b1f31d5..ffa8541 100644
--- a/kernel-shark/src/libkshark-plugin.c
+++ b/kernel-shark/src/libkshark-plugin.c
@@ -150,10 +150,10 @@
  * @param kshark_ctx: Input location for the session context pointer.
  * @param file: The plugin object file to load.
  *
- * @returns Zero on success, or a negative error code on failure.
+ * @returns The plugin object on success, or NULL on failure.
  */
-int kshark_register_plugin(struct kshark_context *kshark_ctx,
-			   const char *file)
+struct kshark_plugin_list *
+kshark_register_plugin(struct kshark_context *kshark_ctx, const char *file)
 {
 	struct kshark_plugin_list *plugin = kshark_ctx->plugins;
 	struct stat st;
@@ -161,7 +161,7 @@
 
 	while (plugin) {
 		if (strcmp(plugin->file, file) == 0)
-			return -EEXIST;
+			return NULL;
 
 		plugin = plugin->next;
 	}
@@ -169,19 +169,21 @@
 	ret = stat(file, &st);
 	if (ret < 0) {
 		fprintf(stderr, "plugin %s not found\n", file);
-		return -ENODEV;
+		return NULL;
 	}
 
 	plugin = calloc(sizeof(struct kshark_plugin_list), 1);
 	if (!plugin) {
 		fprintf(stderr, "failed to allocate memory for plugin\n");
-		return -ENOMEM;
+		return NULL;
 	}
 
+	plugin->streams = NULL;
+
 	if (asprintf(&plugin->file, "%s", file) <= 0) {
 		fprintf(stderr,
 			"failed to allocate memory for plugin file name");
-		return -ENOMEM;
+		return NULL;
 	}
 
 	plugin->handle = dlopen(plugin->file, RTLD_NOW | RTLD_GLOBAL);
@@ -200,7 +202,7 @@
 	plugin->next = kshark_ctx->plugins;
 	kshark_ctx->plugins = plugin;
 
-	return 0;
+	return plugin;
 
  fail:
 	fprintf(stderr, "cannot load plugin '%s'\n%s\n",
@@ -213,7 +215,7 @@
 
 	free(plugin);
 
-	return EFAULT;
+	return NULL;
 }
 
 /**
@@ -261,6 +263,79 @@
 	}
 }
 
+struct kshark_plugin_list *
+kshark_find_plugin(struct kshark_plugin_list *plugins, const char *file)
+{
+	for (; plugins; plugins = plugins->next)
+		if (strcmp(plugins->file, file) == 0)
+			return plugins;
+
+	return NULL;
+}
+
+void kshark_plugin_add_stream(struct kshark_plugin_list *plugin, int sd)
+{
+	struct kshark_stream_list *stream;
+
+	/* First make sure that the Data Stream has not been added already. */
+	for (stream = plugin->streams; stream; stream = stream->next)
+		if (stream->stream_id == sd)
+			return;
+
+	/* Add the stream to the list. */
+	stream = malloc(sizeof(*stream));
+	stream->stream_id = sd;
+	stream->next = plugin->streams;
+	plugin->streams = stream;
+}
+
+void kshark_plugin_remove_stream(struct kshark_plugin_list *plugin, int sd)
+{
+	struct kshark_stream_list **stream;
+
+	for (stream = &plugin->streams; *stream; stream = &(*stream)->next) {
+		if ((*stream)->stream_id == sd) {
+			struct kshark_stream_list *this_stream;
+
+			this_stream = *stream;
+			*stream = this_stream->next;
+			free(this_stream);
+		}
+	}
+}
+
+int kshark_handle_plugin(struct kshark_context *kshark_ctx,
+			 struct kshark_plugin_list *plugin,
+			 int sd,
+			 enum kshark_plugin_actions task_id)
+{
+	struct kshark_stream_list *stream;
+	int handler_count = 0;
+
+	for (stream = plugin->streams; stream; stream = stream->next) {
+		if (stream->stream_id == sd)
+			switch (task_id) {
+			case KSHARK_PLUGIN_INIT:
+				handler_count = plugin->init(kshark_ctx, sd);
+				break;
+
+			case KSHARK_PLUGIN_UPDATE:
+				plugin->close(kshark_ctx, sd);
+				handler_count = plugin->init(kshark_ctx, sd);
+				break;
+
+			case KSHARK_PLUGIN_CLOSE:
+				handler_count = plugin->close(kshark_ctx, sd);
+				break;
+
+			default:
+				return -EINVAL;
+			}
+	}
+
+	return handler_count;
+}
+
 /**
  * @brief Use this function to initialize/update/deinitialize all registered
  *	  plugins.
@@ -271,30 +346,33 @@
  * @returns The number of successful added/removed plugin handlers on success,
  *	    or a negative error code on failure.
  */
-int kshark_handle_plugins(struct kshark_context *kshark_ctx,
-			  enum kshark_plugin_actions task_id)
+int kshark_handle_all_plugins(struct kshark_context *kshark_ctx, int sd,
+			      enum kshark_plugin_actions task_id)
 {
 	struct kshark_plugin_list *plugin;
 	int handler_count = 0;
 
 	for (plugin = kshark_ctx->plugins; plugin; plugin = plugin->next) {
-		switch (task_id) {
-		case KSHARK_PLUGIN_INIT:
-			handler_count += plugin->init(kshark_ctx);
-			break;
+		handler_count +=
+			kshark_handle_plugin(kshark_ctx, plugin, sd, task_id);
 
-		case KSHARK_PLUGIN_UPDATE:
-			plugin->close(kshark_ctx);
-			handler_count += plugin->init(kshark_ctx);
-			break;
-
-		case KSHARK_PLUGIN_CLOSE:
-			handler_count += plugin->close(kshark_ctx);
-			break;
-
-		default:
-			return -EINVAL;
-		}
+// 		switch (task_id) {
+// 		case KSHARK_PLUGIN_INIT:
+// 			handler_count += plugin->init(kshark_ctx, sd);
+// 			break;
+// 
+// 		case KSHARK_PLUGIN_UPDATE:
+// 			plugin->close(kshark_ctx, sd);
+// 			handler_count += plugin->init(kshark_ctx, sd);
+// 			break;
+// 
+// 		case KSHARK_PLUGIN_CLOSE:
+// 			handler_count += plugin->close(kshark_ctx, sd);
+// 			break;
+// 
+// 		default:
+// 			return -EINVAL;
+// 		}
 	}
 
 	return handler_count;
diff --git a/kernel-shark/src/libkshark-plugin.h b/kernel-shark/src/libkshark-plugin.h
index a39db29..4aad3a0 100644
--- a/kernel-shark/src/libkshark-plugin.h
+++ b/kernel-shark/src/libkshark-plugin.h
@@ -16,6 +16,9 @@
 extern "C" {
 #endif // __cplusplus
 
+// C
+#include <stdint.h>
+
 // trace-cmd
 #include "event-parse.h"
 
@@ -43,7 +46,7 @@
  * A function type to be used when defining load/reload/unload plugin
  * functions.
  */
-typedef int (*kshark_plugin_load_func)(struct kshark_context *);
+typedef int (*kshark_plugin_load_func)(struct kshark_context *, int);
 
 struct kshark_trace_histo;
 
@@ -59,7 +62,7 @@
 /** A function type to be used when defining plugin functions for drawing. */
 typedef void
 (*kshark_plugin_draw_handler_func)(struct kshark_cpp_argv *argv,
-				   int val, int draw_action);
+				   int sd, int val, int draw_action);
 
 /**
  * A function type to be used when defining plugin functions for data
@@ -147,6 +150,15 @@
 
 void kshark_free_event_handler_list(struct kshark_event_handler *handlers);
 
+/** Linked list of Data Stream identifiers. */
+struct kshark_stream_list {
+	/** Pointer to the next Data stream identifier. */
+	struct kshark_stream_list	*next;
+
+	/** Data stream identifier. */
+	uint8_t		stream_id;
+};
+
 /** Linked list of plugins. */
 struct kshark_plugin_list {
 	/** Pointer to the next Plugin. */
@@ -155,6 +167,9 @@
 	/** The plugin object file to load. */
 	char				*file;
 
+	/** List of Data streams. */
+	struct kshark_stream_list	*streams;
+
 	/** Plugin Event handler. */
 	void				*handle;
 
@@ -165,16 +180,28 @@
 	kshark_plugin_load_func		close;
 };
 
-int kshark_register_plugin(struct kshark_context *kshark_ctx,
-			   const char *file);
+struct kshark_plugin_list *
+kshark_register_plugin(struct kshark_context *kshark_ctx, const char *file);
 
 void kshark_unregister_plugin(struct kshark_context *kshark_ctx,
 			      const char *file);
 
 void kshark_free_plugin_list(struct kshark_plugin_list *plugins);
 
-int kshark_handle_plugins(struct kshark_context *kshark_ctx,
-			  enum kshark_plugin_actions  task_id);
+struct kshark_plugin_list *
+kshark_find_plugin(struct kshark_plugin_list *plugins, const char *file);
+
+void kshark_plugin_add_stream(struct kshark_plugin_list *plugin, int sd);
+
+void kshark_plugin_remove_stream(struct kshark_plugin_list *plugin, int sd);
+
+int kshark_handle_plugin(struct kshark_context *kshark_ctx,
+			 struct kshark_plugin_list *plugin,
+			 int sd,
+			 enum kshark_plugin_actions task_id);
+
+int kshark_handle_all_plugins(struct kshark_context *kshark_ctx, int sd,
+			      enum kshark_plugin_actions  task_id);
 
 #ifdef __cplusplus
 }
diff --git a/kernel-shark/src/libkshark.c b/kernel-shark/src/libkshark.c
index 84f2d86..780eb01 100644
--- a/kernel-shark/src/libkshark.c
+++ b/kernel-shark/src/libkshark.c
@@ -155,6 +155,7 @@
 		goto fail;
 
 	stream->file = NULL;
+	stream->calib = NULL;
 	stream->calib_array = NULL;
 	stream->calib_array_size = 0;
 
@@ -283,6 +284,7 @@
  */
 int kshark_open(struct kshark_context *kshark_ctx, const char *file)
 {
+	struct kshark_plugin_list *plugin;
 	int sd, rt;
 
 	sd = kshark_add_stream(kshark_ctx);
@@ -295,6 +297,9 @@
 
 	kshark_ctx->n_streams++;
 
+	for (plugin = kshark_ctx->plugins; plugin; plugin = plugin->next)
+		kshark_plugin_add_stream(plugin, sd);
+
 	return sd;
 }
 
@@ -339,6 +344,7 @@
 
 	stream = kshark_get_data_stream(kshark_ctx, sd);
 	if (stream) {
+		kshark_handle_all_plugins(kshark_ctx, sd, KSHARK_PLUGIN_CLOSE);
 		kshark_stream_close(kshark_ctx->stream[sd]);
 		kshark_stream_free(kshark_ctx->stream[sd]);
 		kshark_ctx->stream[sd] = NULL;
@@ -354,26 +360,14 @@
 }
 
 /**
- * @brief Deinitialize kshark session. Should be called after closing all
- *	  open trace data files and before your application terminates.
+ * @brief Close all currently open trace data file and free the trace data handle.
  *
- * @param kshark_ctx: Optional input location for session context pointer.
- *		      If it points to a context of a sessuin, that sessuin
- *		      will be deinitialize. If it points to NULL, it will
- *		      deinitialize the current session.
+ * @param kshark_ctx: Input location for the session context pointer.
  */
-void kshark_free(struct kshark_context *kshark_ctx)
+void kshark_close_all(struct kshark_context *kshark_ctx)
 {
 	int i, *stream_ids, n_streams;
 
-	if (kshark_ctx == NULL) {
-		if (kshark_context_handler == NULL)
-			return;
-
-		kshark_ctx = kshark_context_handler;
-		/* kshark_ctx_handler will be set to NULL below. */
-	}
-
 	stream_ids = kshark_all_streams(kshark_ctx);
 
 	/*
@@ -385,10 +379,32 @@
 		kshark_close(kshark_ctx, stream_ids[i]);
 
 	free(stream_ids);
+}
+
+/**
+ * @brief Deinitialize kshark session. Should be called after closing all
+ *	  open trace data files and before your application terminates.
+ *
+ * @param kshark_ctx: Optional input location for session context pointer.
+ *		      If it points to a context of a sessuin, that sessuin
+ *		      will be deinitialize. If it points to NULL, it will
+ *		      deinitialize the current session.
+ */
+void kshark_free(struct kshark_context *kshark_ctx)
+{
+	if (kshark_ctx == NULL) {
+		if (kshark_context_handler == NULL)
+			return;
+
+		kshark_ctx = kshark_context_handler;
+		/* kshark_ctx_handler will be set to NULL below. */
+	}
+
+	kshark_close_all(kshark_ctx);
+
 	free(kshark_ctx->stream);
 
 	if (kshark_ctx->plugins) {
-		kshark_handle_plugins(kshark_ctx, KSHARK_PLUGIN_CLOSE);
 		kshark_free_plugin_list(kshark_ctx->plugins);
 		kshark_free_event_handler_list(kshark_ctx->event_handlers);
 	}
@@ -676,54 +692,50 @@
 	 * stream->filter_mask. The value of the EVENT_VIEW flag in
 	 * stream->filter_mask will be used instead.
 	 */
-	int event_mask = kshark_ctx->filter_mask;
+	int event_mask = kshark_ctx->filter_mask & ~KS_GRAPH_VIEW_FILTER_MASK;
 
 	e->visible &= ~event_mask;
 }
 
-/**
- * @brief This function loops over the array of entries specified by "data"
- *	  and "n_entries" and sets the "visible" fields of each entry
- *	  according to the criteria provided by the filters of the session's
- *	  context. The field "filter_mask" of the session's context is used to
- *	  control the level of visibility/invisibility of the entries which
- *	  are filtered-out.
- *	  WARNING: Do not use this function if the advanced filter is set.
- *	  Applying the advanced filter requires access to prevent_record,
- *	  hence the data has to be reloaded using kshark_load_data_entries().
- *
- * @param kshark_ctx: Input location for the session context pointer.
- * @param sd: Data stream identifier.
- * @param data: Input location for the trace data to be filtered.
- * @param n_entries: The size of the inputted data.
- */
-void kshark_filter_entries(struct kshark_context *kshark_ctx, int sd,
+static void filter_entries(struct kshark_context *kshark_ctx, int sd,
 			   struct kshark_entry **data, size_t n_entries)
 {
-	struct kshark_data_stream *stream;
-	int i;
+	struct kshark_data_stream *stream = NULL;
+	size_t i;
 
-	stream = kshark_get_data_stream(kshark_ctx, sd);
-	if (!stream)
-		return;
+	if (sd >= 0) {
+		/* We will filter particular Data stream. */
+		stream = kshark_get_data_stream(kshark_ctx, sd);
+		if (!stream)
+			return;
 
-	if (stream->advanced_event_filter->filters) {
-		/* The advanced filter is set. */
-		fprintf(stderr,
-			"Failed to filter (sd = %i)!\n", sd);
-		fprintf(stderr,
-			"Reset the Advanced filter or reload the data.\n");
-		return;
+		if (stream->advanced_event_filter->filters) {
+			/* The advanced filter is set. */
+			fprintf(stderr,
+				"Failed to filter (sd = %i)!\n", sd);
+			fprintf(stderr,
+				"Reset the Advanced filter or reload the data.\n");
+
+			return;
+		}
+
+		if (!kshark_filter_is_set(kshark_ctx, sd))
+			return;
 	}
 
-	if (!kshark_filter_is_set(kshark_ctx, sd))
-		return;
-
 	/* Apply only the Id filters. */
 	for (i = 0; i < n_entries; ++i) {
-		/* Chack is the entry belongs to this stream. */
-		if (data[i]->stream_id != sd)
-			continue;
+		if (sd >= 0) {
+			/*
+			 * We only filter particular stream. Chack is the entry
+			 * belongs to this stream.
+			 */
+			if (data[i]->stream_id != sd)
+				continue;
+		} else {
+			/* We filter all streams. */
+			stream = kshark_ctx->stream[data[i]->stream_id];
+		}
 
 		/* Start with and entry which is visible everywhere. */
 		data[i]->visible = 0xFF;
@@ -744,6 +756,50 @@
 
 /**
  * @brief This function loops over the array of entries specified by "data"
+ *	  and "n_entries" and sets the "visible" fields of each entry from a
+ *	  given Data stream according to the criteria provided by the filters
+ *	  of the session's context. The field "filter_mask" of the session's
+ *	  context is used to control the level of visibility/invisibility of
+ *	  the entries which are filtered-out.
+ *	  WARNING: Do not use this function if the advanced filter is set.
+ *	  Applying the advanced filter requires access to prevent_record,
+ *	  hence the data has to be reloaded using kshark_load_data_entries().
+ *
+ * @param kshark_ctx: Input location for the session context pointer.
+ * @param sd: Data stream identifier.
+ * @param data: Input location for the trace data to be filtered.
+ * @param n_entries: The size of the inputted data.
+ */
+void kshark_filter_stream_entries(struct kshark_context *kshark_ctx, int sd,
+			   struct kshark_entry **data, size_t n_entries)
+{
+	if (sd >= 0)
+		filter_entries(kshark_ctx, sd, data, n_entries);
+}
+
+/**
+ * @brief This function loops over the array of entries specified by "data"
+ *	  and "n_entries" and sets the "visible" fields of each entry from
+ *	  all Data stream according to the criteria provided by the filters
+ *	  of the session's context. The field "filter_mask" of the session's
+ *	  context is used to control the level of visibility/invisibility of
+ *	  the entries which are filtered-out.
+ *	  WARNING: Do not use this function if the advanced filter is set.
+ *	  Applying the advanced filter requires access to prevent_record,
+ *	  hence the data has to be reloaded using kshark_load_data_entries().
+ *
+ * @param kshark_ctx: Input location for the session context pointer.
+ * @param data: Input location for the trace data to be filtered.
+ * @param n_entries: The size of the inputted data.
+ */
+void kshark_filter_all_entries(struct kshark_context *kshark_ctx,
+			       struct kshark_entry **data, size_t n_entries)
+{
+	filter_entries(kshark_ctx, -1, data, n_entries);
+}
+
+/**
+ * @brief This function loops over the array of entries specified by "data"
  *	  and "n_entries" and resets the "visible" fields of each entry to
  *	  the default value of "0xFF" (visible everywhere).
  *
@@ -941,6 +997,11 @@
 					entry = &temp_rec->entry;
 					missed_events_action(kshark_ctx, rec, entry);
 
+					if (stream->calib && stream->calib_array)
+						stream->calib(entry, stream->calib_array);
+
+					entry->stream_id = sd;
+
 					temp_next = &temp_rec->next;
 					++count;
 
@@ -952,6 +1013,9 @@
 				kshark_set_entry_values(stream, rec, entry);
 				entry->stream_id = sd;
 
+				if (stream->calib && stream->calib_array)
+					stream->calib(entry, stream->calib_array);
+
 				/* Execute all plugin-provided actions (if any). */
 				evt_handler = kshark_ctx->event_handlers;
 				while ((evt_handler = kshark_find_event_handler(evt_handler,
@@ -1100,6 +1164,7 @@
 
 	free_rec_list(rec_list, n_cpus, type);
 	*data_rows = rows;
+
 	return total;
 
  fail_free:
@@ -1183,6 +1248,45 @@
 	return -ENOMEM;
 }
 
+ssize_t kshark_load_all_data_entries(struct kshark_context *kshark_ctx,
+				     struct kshark_entry ***data_rows)
+{
+	size_t data_size = 0;;
+	int i, *stream_ids, sd;
+
+	if (!kshark_ctx->n_streams)
+		return data_size;
+
+	stream_ids = kshark_all_streams(kshark_ctx);
+	sd = stream_ids[0];
+
+	data_size = kshark_load_data_entries(kshark_ctx, sd, data_rows);
+
+	for (i = 1; i < kshark_ctx->n_streams; ++i) {
+		struct kshark_entry **stream_data_rows = NULL;
+		struct kshark_entry **merged_data_rows;
+		size_t stream_data_size;
+
+		sd = stream_ids[i];
+		stream_data_size = kshark_load_data_entries(kshark_ctx, sd,
+							    &stream_data_rows);
+
+		merged_data_rows =
+			kshark_data_merge(*data_rows, data_size,
+					  stream_data_rows, stream_data_size);
+
+		free(stream_data_rows);
+		free(*data_rows);
+
+		stream_data_rows = *data_rows = NULL;
+
+		*data_rows = merged_data_rows;
+		data_size += stream_data_size;
+	}
+
+	return data_size;
+}
+
 /**
  * @brief A read of a record from a Data stream at a specific offset.
  *
@@ -1742,9 +1846,9 @@
  *	    Else false.
  */
 bool kshark_match_pid(struct kshark_context *kshark_ctx,
-		      struct kshark_entry *e, int sd, int pid)
+		      struct kshark_entry *e, int sd, int *pid)
 {
-	if (e->stream_id == sd && e->pid == pid)
+	if (e->stream_id == sd && e->pid == *pid)
 		return true;
 
 	return false;
@@ -1762,15 +1866,32 @@
  *	    Else false.
  */
 bool kshark_match_cpu(struct kshark_context *kshark_ctx,
-		      struct kshark_entry *e, int sd, int cpu)
+		      struct kshark_entry *e, int sd, int *cpu)
 {
-	if (e->stream_id == sd && e->cpu == cpu)
+	if (e->stream_id == sd && e->cpu == *cpu)
 		return true;
 
 	return false;
 }
 
 /**
+ * @brief Simple Pid matching function to be user for data requests.
+ *
+ * @param kshark_ctx: Input location for the session context pointer.
+ * @param e: kshark_entry to be checked.
+ * @param sd: Data stream identifier.
+ * @param pid: Matching condition value.
+ *
+ * @returns True if the event Id of the entry matches the value of "event_id".
+ *	    Else false.
+ */
+bool kshark_match_event_id(struct kshark_context *kshark_ctx,
+			   struct kshark_entry *e, int sd, int *event_id)
+{
+	return e->stream_id == sd && e->event_id == *event_id;
+}
+
+/**
  * @brief Create Data request. The request defines the properties of the
  *	  requested kshark_entry.
  *
@@ -1791,7 +1912,7 @@
  */
 struct kshark_entry_request *
 kshark_entry_request_alloc(size_t first, size_t n,
-			   matching_condition_func cond, int sd, int val,
+			   matching_condition_func cond, int sd, int *values,
 			   bool vis_only, int vis_mask)
 {
 	struct kshark_entry_request *req = malloc(sizeof(*req));
@@ -1807,7 +1928,7 @@
 	req->n = n;
 	req->cond = cond;
 	req->sd = sd;
-	req->val = val;
+	req->values = values;
 	req->vis_only = vis_only;
 	req->vis_mask = vis_mask;
 
@@ -1861,7 +1982,7 @@
 	 */
 	assert((inc > 0 && start < end) || (inc < 0 && start > end));
 	for (i = start; i != end; i += inc) {
-		if (req->cond(kshark_ctx, data[i], req->sd, req->val)) {
+		if (req->cond(kshark_ctx, data[i], req->sd, req->values)) {
 			/*
 			 * Data satisfying the condition has been found.
 			 */
@@ -1945,87 +2066,89 @@
 	return get_entry(req, data, index, req->first, end, -1);
 }
 
+void kshark_offset_calib(struct kshark_entry *e, int64_t *atgv)
+{
+	e->ts += atgv[0];
+}
+
+void kshark_linear_clock_calib(struct kshark_entry *e, int64_t *atgv)
+{
+	e->ts = atgv[0] + e->ts * atgv[1];
+}
+
+
+static size_t copy_prior_data(struct kshark_entry **merged_data,
+			      struct kshark_entry **prior_data,
+			      size_t a_size,
+			      uint64_t t)
+{
+	size_t mid, l = 0, h = a_size - 1;
+
+	/*
+	 * After executing the BSEARCH macro, "l" will be the index of the last
+	 * prior entry having timestamp < t  and "h" will be the index of the
+	 * first prior entry having timestamp >= t.
+	 */
+	BSEARCH(h, l, prior_data[mid]->ts < t);
+
+	/*
+	 * Copy all entries before "t" (in time).
+	 */
+	memcpy(merged_data, prior_data, h * sizeof(*prior_data));
+
+	return h;
+}
+
 /**
  * @brief Merge two trace data streams.
  *
- * @param prior_data: Input location for the prior trace data. The clock used
- *		      to record the prior data will be used by the merged data.
- * @param prior_size: The size of the prior trace data.
- * @param associated_data: Input location for the trace data to be merged to
- *			   the prior. The clock of the associated data will be
- * 			   calibrated in order to be compatible with the clock
- * 			   of the prior data.
- * @param associated_size: The size of the associated trace data.
- * @param calib: Callback function providing the calibration of the clock of
- *		 the associated data.
- * @param argv: Array of arguments for the calibration function.
+ * @param data_a: Input location for the prior trace data.
+ * @param a_size: The size of the prior trace data.
+ * @param data_b: Input location for the trace data to be merged to
+ *			   the prior.
+ * @param b_size: The size of the associated trace data.
  *
  * @returns Merged and sorted in time trace data. The user is responsible for
  *	    freeing the elements of the outputted array.
  */
-struct kshark_entry **kshark_data_merge(struct kshark_entry **prior_data,
-					size_t prior_size,
-					struct kshark_entry **associated_data,
-					size_t associated_size,
-					time_calib_func calib,
-					int64_t *argv)
+struct kshark_entry **kshark_data_merge(struct kshark_entry **data_a,
+					size_t a_size,
+					struct kshark_entry **data_b,
+					size_t b_size)
 {
-	size_t i = 0, prior_count = 0, assc_count = 0;
-	size_t tot = prior_size + associated_size;
-	size_t mid, l = 0, h = prior_size - 1;
+	size_t i = 0, a_count = 0, b_count = 0;
+	size_t tot = a_size + b_size, cpy_size;
 	struct kshark_entry **merged_data;
 
 	merged_data = calloc(tot, sizeof(*merged_data));
-
-	/*
-	 * Calibrate the timestamp of the first entry of the associated data.
-	 */
-	calib(associated_data[0], argv);
-
-	if (prior_data[0]->ts < associated_data[0]->ts) {
-		/*
-		 * After executing the BSEARCH macro, "l" will be the index of
-		 * the last prior entry having timestamp < associated_data[0]->ts
-		 * and "h" will be the index of the first prior entry having
-		 * timestamp >= associated_data[0]->ts.
-		 */
-		BSEARCH(h, l, prior_data[mid]->ts < associated_data[0]->ts);
-
-		/*
-		 * The prior data needs no time calibration. Copy all entries
-		 * before (in time) the first entry of the associated data.
-		 */
-		memcpy(merged_data, prior_data, h * sizeof(*prior_data));
-		i = prior_count = h;
+	if (data_a[0]->ts < data_b[0]->ts) {
+		i = a_count = copy_prior_data(merged_data, data_a,
+					      a_size, data_b[0]->ts);
+	} else {
+		i = b_count = copy_prior_data(merged_data, data_b,
+					      b_size, data_a[0]->ts);
 	}
 
 	for (; i < tot; ++i) {
-		if (prior_data[prior_count]->ts <= associated_data[assc_count]->ts) {
-			merged_data[i] = prior_data[prior_count++];
-			if (prior_count == prior_size)
+		if (data_a[a_count]->ts <= data_b[b_count]->ts) {
+			merged_data[i] = data_a[a_count++];
+			if (a_count == a_size)
 				break;
 		} else {
-			merged_data[i] = associated_data[assc_count++];
-			if (assc_count == associated_size)
+			merged_data[i] = data_b[b_count++];
+			if (b_count == b_size)
 				break;
-
-			/* Calibrate the timestamp of the following entry. */
-			calib(associated_data[assc_count], argv);
 		}
 	}
 
-	if (prior_count == prior_size && assc_count < associated_size) {
-		/* Calibrate and copy the remaining associated data. */
-		for (++i; i < tot; ++i) {
-			merged_data[i] = associated_data[assc_count++];
-			calib(merged_data[i], argv);
-		}
-	} else if (prior_count < prior_size && assc_count == associated_size) {
-		/* Copy the remaining prior data. */
-		++i;
-		memcpy(&merged_data[i], &prior_data[prior_count],
-		       (tot - i) * sizeof(*prior_data));
-	}
+	/* Copy the remaining data. */
+	++i;
+	cpy_size = (tot - i) * sizeof(*merged_data);
+
+	if (a_count == a_size && b_count < b_size)
+		memcpy(&merged_data[i], &data_b[b_count], cpy_size);
+	else if (a_count < a_size && b_count == b_size)
+		memcpy(&merged_data[i], &data_a[a_count], cpy_size);
 
 	return merged_data;
 }
diff --git a/kernel-shark/src/libkshark.h b/kernel-shark/src/libkshark.h
index 05e3c47..c576655 100644
--- a/kernel-shark/src/libkshark.h
+++ b/kernel-shark/src/libkshark.h
@@ -87,6 +87,11 @@
 	int			 pid;
 };
 
+/**
+ * Timestamp calibration function type. To be user for system clock calibration.
+ */
+typedef void (*time_calib_func) (struct kshark_entry *, int64_t *);
+
 /** tructure representing a stream of trace data. */
 struct kshark_data_stream {
 	/** Input handle for the trace data file. */
@@ -104,6 +109,9 @@
 	/** The size of the array of time calibration constants. */
 	size_t			calib_array_size;
 
+	/** System clock calibration function. */
+	time_calib_func		calib;
+
 	/** Hash table of task PIDs. */
 	struct kshark_task_list	**tasks;
 
@@ -176,6 +184,9 @@
 ssize_t kshark_load_data_entries(struct kshark_context *kshark_ctx, int sd,
 				 struct kshark_entry ***data_rows);
 
+ssize_t kshark_load_all_data_entries(struct kshark_context *kshark_ctx,
+				     struct kshark_entry ***data_rows);
+
 ssize_t kshark_load_data_records(struct kshark_context *kshark_ctx, int sd,
 				 struct tep_record ***data_rows);
 
@@ -184,6 +195,8 @@
 
 void kshark_close(struct kshark_context *kshark_ctx, int sd);
 
+void kshark_close_all(struct kshark_context *kshark_ctx);
+
 void kshark_free(struct kshark_context *kshark_ctx);
 
 inline static struct kshark_data_stream *
@@ -300,9 +313,12 @@
 
 bool kshark_filter_is_set(struct kshark_context *kshark_ctx, int sd);
 
-void kshark_filter_entries(struct kshark_context *kshark_ctx, int sd,
-			   struct kshark_entry **data,
-			   size_t n_entries);
+void kshark_filter_stream_entries(struct kshark_context *kshark_ctx, int sd,
+				  struct kshark_entry **data,
+				  size_t n_entries);
+
+void kshark_filter_all_entries(struct kshark_context *kshark_ctx,
+			       struct kshark_entry **data, size_t n_entries);
 
 void kshark_clear_all_filters(struct kshark_context *kshark_ctx,
 			      struct kshark_entry **data,
@@ -338,10 +354,13 @@
 				   size_t l, size_t h);
 
 bool kshark_match_pid(struct kshark_context *kshark_ctx,
-		      struct kshark_entry *e, int sd, int pid);
+		      struct kshark_entry *e, int sd, int *pid);
 
 bool kshark_match_cpu(struct kshark_context *kshark_ctx,
-		      struct kshark_entry *e, int sd, int cpu);
+		      struct kshark_entry *e, int sd, int *cpu);
+
+bool kshark_match_event_id(struct kshark_context *kshark_ctx,
+			   struct kshark_entry *e, int sd, int *event_id);
 
 /**
  * Empty bin identifier.
@@ -359,7 +378,7 @@
 /** Matching condition function type. To be user for data requests */
 typedef bool (matching_condition_func)(struct kshark_context*,
 				       struct kshark_entry*,
-				       int, int);
+				       int, int*);
 
 /**
  * Data request structure, defining the properties of the required
@@ -387,7 +406,7 @@
 	/**
 	 * Matching condition value, used by the Matching condition function.
 	 */
-	int val;
+	int *values;
 
 	/** If true, a visible entry is requested. */
 	bool vis_only;
@@ -401,7 +420,7 @@
 
 struct kshark_entry_request *
 kshark_entry_request_alloc(size_t first, size_t n,
-			   matching_condition_func cond, int sd, int val,
+			   matching_condition_func cond, int sd, int *values,
 			   bool vis_only, int vis_mask);
 
 void kshark_free_entry_request(struct kshark_entry_request *req);
@@ -416,22 +435,19 @@
 		      struct kshark_entry **data,
 		      ssize_t *index);
 
-/**
- * Timestamp calibration function type. To be user for merging data streams.
- */
-typedef void (*time_calib_func) (struct kshark_entry *, int64_t *);
+void kshark_offset_calib(struct kshark_entry *e, int64_t *atgv);
+
+void kshark_linear_clock_calib(struct kshark_entry *e, int64_t *atgv);
 
 struct kshark_entry **kshark_data_merge(struct kshark_entry **prior_data,
 					size_t prior_size,
 					struct kshark_entry **associated_data,
-					size_t associated_size,
-					time_calib_func calib,
-					int64_t *argv);
+					size_t associated_size);
 
 /**
  * Data collections are used to optimize the search for an entry having an
- * abstract property, defined by a Matching condition function and a value.
- * When a collection is processed, the data which is relevant for the
+ * abstract property, defined by a Matching condition function and an array of
+ * values. When a collection is processed, the data which is relevant for the
  * collection is enclosed in "Data intervals", defined by pairs of "Resume" and
  * "Break" points. It is guaranteed that the data outside of the intervals
  * contains no entries satisfying the abstract matching condition. However, the
@@ -449,13 +465,15 @@
 	matching_condition_func *cond;
 
 	/** Data stream identifier. */
-	int sd;
+	int stream_id;
 
 	/**
-	 * Matching condition value, used by the Matching condition finction
-	 * to define the collections.
+	 * Array of matching condition values, used by the Matching condition
+	 * finction to define the collection.
 	 */
-	int val;
+	int *values;
+
+	int n_val;
 
 	/**
 	 * Array of indexes defining the beginning of each individual data
@@ -478,23 +496,24 @@
 			      struct kshark_entry **data,
 			      size_t n_rows,
 			      matching_condition_func cond,
-			      int sd, int val,
+			      int sd, int *values, size_t n_val,
 			      size_t margin);
 
 struct kshark_entry_collection *
 kshark_register_data_collection(struct kshark_context *kshark_ctx,
 				struct kshark_entry **data, size_t n_rows,
-				matching_condition_func cond, int sd, int val,
+				matching_condition_func cond,
+				int sd, int *values, size_t n_val,
 				size_t margin);
 
 void kshark_unregister_data_collection(struct kshark_entry_collection **col,
 				       matching_condition_func cond,
-				       int sd, int val);
+				       int sd, int *values, size_t n_val);
 
 struct kshark_entry_collection *
 kshark_find_data_collection(struct kshark_entry_collection *col,
 			    matching_condition_func cond,
-			    int sd, int val);
+			    int sd, int *values, size_t n_val);
 
 void kshark_reset_data_collection(struct kshark_entry_collection *col);
 
@@ -684,12 +703,12 @@
 		      enum kshark_config_formats format);
 
 int kshark_import_dstream(struct kshark_context *kshark_ctx,
-			  struct kshark_config_doc *conf,
+			  struct kshark_config_doc *conf/*,
 			  struct kshark_entry ***data_rows,
-			  size_t *data_size);
+			  size_t *data_size*/);
 
 bool kshark_export_all_dstreams(struct kshark_context *kshark_ctx,
-				struct kshark_config_doc *conf);
+				struct kshark_config_doc **conf);
 
 bool kshark_import_all_dstreams(struct kshark_context *kshark_ctx,
 				struct kshark_config_doc *conf,
diff --git a/kernel-shark/src/plugins/CMakeLists.txt b/kernel-shark/src/plugins/CMakeLists.txt
index c9b5bdd..1fa2fc3 100644
--- a/kernel-shark/src/plugins/CMakeLists.txt
+++ b/kernel-shark/src/plugins/CMakeLists.txt
@@ -23,15 +23,15 @@
              SOURCE sched_events.c SchedEvents.cpp)
 list(APPEND PLUGIN_LIST "sched_events default") # This plugin will be loaded by default
 
-BUILD_PLUGIN(NAME rename_sched_events
-           SOURCE rename_sched_events.c)
-list(APPEND PLUGIN_LIST "rename_sched_events") # This plugin isn't loaded by default
+# BUILD_PLUGIN(NAME rename_sched_events
+#              SOURCE rename_sched_events.c)
+# list(APPEND PLUGIN_LIST "rename_sched_events") # This plugin isn't loaded by default
 
-BUILD_PLUGIN(NAME missed_events
-             SOURCE missed_events.c MissedEvents.cpp)
-list(APPEND PLUGIN_LIST "missed_events default") # This plugin will be loaded by default
+# BUILD_PLUGIN(NAME missed_events
+#              SOURCE missed_events.c MissedEvents.cpp)
+# list(APPEND PLUGIN_LIST "missed_events default") # This plugin will be loaded by default
 
-install(TARGETS sched_events missed_events
-        LIBRARY DESTINATION ${_INSTALL_PREFIX}/lib/kshark/)
+#install(TARGETS sched_events missed_events
+#        LIBRARY DESTINATION ${_INSTALL_PREFIX}/lib/kshark/)
 
 set(PLUGINS ${PLUGIN_LIST} PARENT_SCOPE)
diff --git a/kernel-shark/src/plugins/MissedEvents.cpp b/kernel-shark/src/plugins/MissedEvents.cpp
index 05dfcb5..4d28c6c 100644
--- a/kernel-shark/src/plugins/MissedEvents.cpp
+++ b/kernel-shark/src/plugins/MissedEvents.cpp
@@ -86,7 +86,7 @@
 
 static void pluginDraw(kshark_context *kshark_ctx,
 		       KsCppArgV *argvCpp,
-		       int val, int draw_action)
+		       int sd, int val, int draw_action)
 {
 	int height = argvCpp->_graph->getHeight();
 	const kshark_entry *entry(nullptr);
@@ -97,12 +97,12 @@
 	for (int bin = 0; bin < nBins; ++bin) {
 		if (draw_action == KSHARK_PLUGIN_TASK_DRAW)
 			entry = ksmodel_get_task_missed_events(argvCpp->_histo,
-							       bin, val,
+							       bin, sd, val,
 							       nullptr,
 							       &index);
 		if (draw_action == KSHARK_PLUGIN_CPU_DRAW)
 			entry = ksmodel_get_cpu_missed_events(argvCpp->_histo,
-							      bin, val,
+							      bin, sd, val,
 							      nullptr,
 							      &index);
 
@@ -123,7 +123,7 @@
  * @param draw_action: Draw action identifier.
  */
 void draw_missed_events(kshark_cpp_argv *argv_c,
-			int val, int draw_action)
+			int sd, int val, int draw_action)
 {
 	kshark_context *kshark_ctx(NULL);
 
@@ -142,7 +142,7 @@
 		return;
 
 	try {
-		pluginDraw(kshark_ctx, argvCpp, val, draw_action);
+		pluginDraw(kshark_ctx, argvCpp, sd, val, draw_action);
 	} catch (const std::exception &exc) {
 		std::cerr << "Exception in MissedEvents\n" << exc.what();
 	}
diff --git a/kernel-shark/src/plugins/SchedEvents.cpp b/kernel-shark/src/plugins/SchedEvents.cpp
index 8408657..63c120c 100644
--- a/kernel-shark/src/plugins/SchedEvents.cpp
+++ b/kernel-shark/src/plugins/SchedEvents.cpp
@@ -32,8 +32,6 @@
 
 //! @endcond
 
-extern struct plugin_sched_context *plugin_sched_context_handler;
-
 /** Sched Event identifier. */
 enum class SchedEvent {
 	/** Sched Switch Event. */
@@ -48,7 +46,7 @@
 		       kshark_trace_histo *histo,
 		       kshark_entry_collection *col,
 		       SchedEvent e,
-		       int pid,
+		       int sd, int pid,
 		       KsPlot::Graph *graph,
 		       KsPlot::PlotObjList *shapes)
 {
@@ -56,7 +54,7 @@
 	ssize_t indexClose(0), indexOpen(0), indexME(0);
 	std::function<void(int)> ifSchedBack;
 	KsPlot::Rectangle *rec = nullptr;
-	int height = graph->getHeight() * .3;
+	int height = graph->height() * .3;
 
 	auto openBox = [&] (const KsPlot::Point &p)
 	{
@@ -108,11 +106,12 @@
 		 * condition defined by kshark_match_pid.
 		 */
 		entryClose = ksmodel_get_entry_back(histo, bin, false,
-						 plugin_switch_match_entry_pid,
-						 pid, col, &indexClose);
+						    plugin_switch_match_entry_pid,
+						    sd, &pid, col, &indexClose);
 
 		entryME = ksmodel_get_task_missed_events(histo,
-							 bin, pid,
+							 bin,
+							 sd, pid,
 							 col,
 							 &indexME);
 
@@ -125,7 +124,7 @@
 			entryOpen =
 				ksmodel_get_entry_back(histo, bin, false,
 						       plugin_switch_match_rec_pid,
-						       pid, col, &indexOpen);
+						       sd, &pid, col, &indexOpen);
 
 		} else {
 			/*
@@ -136,13 +135,13 @@
 			entryOpen =
 				ksmodel_get_entry_back(histo, bin, false,
 						       plugin_wakeup_match_rec_pid,
-						       pid,
+						       sd, &pid,
 						       col,
 						       &indexOpen);
 
 			if (entryOpen) {
 				int cpu = ksmodel_get_cpu_back(histo, bin,
-								      pid,
+								      sd, pid,
 								      false,
 								      col,
 								      nullptr);
@@ -201,7 +200,7 @@
  */
 static void secondPass(kshark_entry **data,
 		       kshark_entry_collection *col,
-		       int pid)
+		       int sd, int pid)
 {
 	if (!col)
 		return;
@@ -219,7 +218,7 @@
 		kshark_entry_request *req =
 			kshark_entry_request_alloc(first, n,
 						   plugin_switch_match_rec_pid,
-						   pid,
+						   sd, &pid,
 						   false,
 						   KS_GRAPH_VIEW_FILTER_MASK);
 
@@ -255,7 +254,7 @@
  * @param pid: Process Id.
  * @param draw_action: Draw action identifier.
  */
-void plugin_draw(kshark_cpp_argv *argv_c, int pid, int draw_action)
+void plugin_draw(kshark_cpp_argv *argv_c, int sd, int pid, int draw_action)
 {
 	plugin_sched_context *plugin_ctx;
 	kshark_context *kshark_ctx(NULL);
@@ -264,7 +263,7 @@
 	if (draw_action != KSHARK_PLUGIN_TASK_DRAW || pid == 0)
 		return;
 
-	plugin_ctx = plugin_sched_context_handler;
+	plugin_ctx = get_sched_context(sd);
 	if (!plugin_ctx || !kshark_instance(&kshark_ctx))
 		return;
 
@@ -275,7 +274,7 @@
 	 * coll = NULL.
 	 */
 	col = kshark_find_data_collection(plugin_ctx->collections,
-					  plugin_match_pid, pid);
+					  plugin_match_pid, sd, &pid, 1);
 	if (!col) {
 		/*
 		 * If a data collection for this task does not exist,
@@ -287,26 +286,27 @@
 		col = kshark_add_collection_to_list(kshark_ctx,
 						    &plugin_ctx->collections,
 						    data, size,
-						    plugin_match_pid, pid,
+						    plugin_match_pid,
+						    sd, &pid, 1,
 						    KS_TASK_COLLECTION_MARGIN);
 	}
 
 	if (!tracecmd_filter_id_find(plugin_ctx->second_pass_hash, pid)) {
 		/* The second pass for this task is not done yet. */
-		secondPass(argvCpp->_histo->data, col, pid);
+		secondPass(argvCpp->_histo->data, col, sd, pid);
 		tracecmd_filter_id_add(plugin_ctx->second_pass_hash, pid);
 	}
 
 	try {
 		pluginDraw(plugin_ctx, kshark_ctx,
 			   argvCpp->_histo, col,
-			   SchedEvent::Wakeup, pid,
+			   SchedEvent::Wakeup, sd, pid,
 			   argvCpp->_graph, argvCpp->_shapes);
 
-		pluginDraw(plugin_ctx, kshark_ctx,
-			   argvCpp->_histo, col,
-			   SchedEvent::Switch, pid,
-			   argvCpp->_graph, argvCpp->_shapes);
+// 		pluginDraw(plugin_ctx, kshark_ctx,
+// 			   argvCpp->_histo, col,
+// 			   SchedEvent::Switch, sd, pid,
+// 			   argvCpp->_graph, argvCpp->_shapes);
 	} catch (const std::exception &exc) {
 		std::cerr << "Exception in SchedEvents\n" << exc.what();
 	}
diff --git a/kernel-shark/src/plugins/missed_events.c b/kernel-shark/src/plugins/missed_events.c
index cf652bf..cc41e22 100644
--- a/kernel-shark/src/plugins/missed_events.c
+++ b/kernel-shark/src/plugins/missed_events.c
@@ -19,10 +19,13 @@
 {}
 
 /** Load this plugin. */
-int KSHARK_PLUGIN_INITIALIZER(struct kshark_context *kshark_ctx)
+int KSHARK_PLUGIN_INITIALIZER(struct kshark_context *kshark_ctx, int sd)
 {
+	printf("--> missed_events init %i\n", sd);
+
 	kshark_register_event_handler(&kshark_ctx->event_handlers,
 				      KS_EVENT_OVERFLOW,
+				      sd,
 				      nop_action,
 				      draw_missed_events);
 
@@ -30,10 +33,13 @@
 }
 
 /** Unload this plugin. */
-int KSHARK_PLUGIN_DEINITIALIZER(struct kshark_context *kshark_ctx)
+int KSHARK_PLUGIN_DEINITIALIZER(struct kshark_context *kshark_ctx, int sd)
 {
+	printf("<-- missed_events close %i\n", sd);
+
 	kshark_unregister_event_handler(&kshark_ctx->event_handlers,
 					KS_EVENT_OVERFLOW,
+					sd,
 					nop_action,
 					draw_missed_events);
 
diff --git a/kernel-shark/src/plugins/missed_events.h b/kernel-shark/src/plugins/missed_events.h
index e05d79a..3777375 100644
--- a/kernel-shark/src/plugins/missed_events.h
+++ b/kernel-shark/src/plugins/missed_events.h
@@ -21,7 +21,7 @@
 #endif
 
 void draw_missed_events(struct kshark_cpp_argv *argv,
-			int pid, int draw_action);
+			int sd, int pid, int draw_action);
 
 #ifdef __cplusplus
 }
diff --git a/kernel-shark/src/plugins/sched_events.c b/kernel-shark/src/plugins/sched_events.c
index d0fd15e..02b461d 100644
--- a/kernel-shark/src/plugins/sched_events.c
+++ b/kernel-shark/src/plugins/sched_events.c
@@ -21,7 +21,9 @@
 #include "plugins/sched_events.h"
 
 /** Plugin context instance. */
-struct plugin_sched_context *plugin_sched_context_handler = NULL;
+// struct plugin_sched_context *plugin_sched_context_handler = NULL;
+static struct plugin_sched_context *
+plugin_sched_context_handler[KS_MAX_NUM_STREAMS] = {NULL};
 
 static bool define_wakeup_event(struct tep_handle *tep, const char *wakeup_name,
 				struct tep_event **wakeup_event,
@@ -50,34 +52,43 @@
 	free(plugin_ctx);
 }
 
-static bool plugin_sched_init_context(struct kshark_context *kshark_ctx)
+struct plugin_sched_context *get_sched_context(int sd)
+{
+	return plugin_sched_context_handler[sd];
+}
+
+static bool plugin_sched_init_context(struct kshark_context *kshark_ctx,
+				      int sd)
 {
 	struct plugin_sched_context *plugin_ctx;
+	struct kshark_data_stream *stream;
 	struct tep_event *event;
 	bool wakeup_found;
 
-	/* No context should exist when we initialize the plugin. */
-	assert(plugin_sched_context_handler == NULL);
+	stream = kshark_get_data_stream(kshark_ctx, sd);
+	if (!stream)
+		return false;
 
-	plugin_sched_context_handler =
-		calloc(1, sizeof(*plugin_sched_context_handler));
-	if (!plugin_sched_context_handler) {
+	/* No context should exist when we initialize the plugin. */
+	assert(plugin_sched_context_handler[sd] == NULL);
+
+	plugin_ctx = calloc(1, sizeof(*plugin_ctx));
+	if (!plugin_ctx) {
 		fprintf(stderr,
 			"Failed to allocate memory for plugin_sched_context.\n");
 		return false;
 	}
 
-	plugin_ctx = plugin_sched_context_handler;
-	plugin_ctx->handle = kshark_ctx->handle;
-	plugin_ctx->pevent = kshark_ctx->pevent;
+	plugin_ctx->handle = stream->handle;
+	plugin_ctx->pevent = stream->pevent;
 	plugin_ctx->collections = NULL;
 
 	event = tep_find_event_by_name(plugin_ctx->pevent,
 				       "sched", "sched_switch");
 	if (!event) {
+		fprintf(stderr, "No sched_switch events in stream %i.\n", sd);
 		plugin_free_context(plugin_ctx);
 		plugin_sched_context_handler = NULL;
-
 		return false;
 	}
 
@@ -91,20 +102,20 @@
 	plugin_ctx->sched_switch_prev_state_field =
 		tep_find_field(event, "prev_state");
 
-
-	wakeup_found = define_wakeup_event(kshark_ctx->pevent, "sched_wakeup",
+	wakeup_found = define_wakeup_event(stream->pevent, "sched_wakeup",
 					   &plugin_ctx->sched_wakeup_event,
 					   &plugin_ctx->sched_wakeup_pid_field);
 
-	wakeup_found |= define_wakeup_event(kshark_ctx->pevent, "sched_wakeup_new",
-					   &plugin_ctx->sched_wakeup_new_event,
-					   &plugin_ctx->sched_wakeup_new_pid_field);
+	wakeup_found |= define_wakeup_event(stream->pevent, "sched_wakeup_new",
+					    &plugin_ctx->sched_wakeup_new_event,
+					    &plugin_ctx->sched_wakeup_new_pid_field);
 
-	wakeup_found |= define_wakeup_event(kshark_ctx->pevent, "sched_waking",
-					   &plugin_ctx->sched_waking_event,
-					   &plugin_ctx->sched_waking_pid_field);
+	wakeup_found |= define_wakeup_event(stream->pevent, "sched_waking",
+					    &plugin_ctx->sched_waking_event,
+					    &plugin_ctx->sched_waking_pid_field);
 
 	plugin_ctx->second_pass_hash = tracecmd_filter_id_hash_alloc();
+	plugin_sched_context_handler[sd] = plugin_ctx;
 
 	return true;
 }
@@ -114,10 +125,10 @@
  *
  * @param record: Input location for a sched_switch record.
  */
-int plugin_get_next_pid(struct tep_record *record)
+int plugin_get_next_pid(struct tep_record *record, int sd)
 {
 	struct plugin_sched_context *plugin_ctx =
-		plugin_sched_context_handler;
+		plugin_sched_context_handler[sd];
 	unsigned long long val;
 	int ret;
 
@@ -129,13 +140,15 @@
 
 static void plugin_register_command(struct kshark_context *kshark_ctx,
 				    struct tep_record *record,
-				    int pid)
+				    int sd, int pid)
 {
 	struct plugin_sched_context *plugin_ctx =
-		plugin_sched_context_handler;
+		plugin_sched_context_handler[sd];
+	struct kshark_data_stream *stream;
 	const char *comm;
 
-	if (!plugin_ctx->sched_switch_comm_field)
+	stream = kshark_get_data_stream(kshark_ctx, sd);
+	if (!stream || !plugin_ctx->sched_switch_comm_field)
 		return;
 
 	comm = record->data + plugin_ctx->sched_switch_comm_field->offset;
@@ -144,21 +157,23 @@
 	 * implemented as a wrapper function in libtracevent.
 	 */
 
-	if (!tep_is_pid_registered(kshark_ctx->pevent, pid))
-			tep_register_comm(kshark_ctx->pevent, comm, pid);
+	if (!tep_is_pid_registered(stream->pevent, pid))
+			tep_register_comm(stream->pevent, comm, pid);
 }
 
-static int find_wakeup_pid(struct kshark_context *kshark_ctx, struct kshark_entry *e,
+int find_wakeup_pid(struct kshark_context *kshark_ctx, struct kshark_entry *e, int sd,
 		    struct tep_event *wakeup_event, struct tep_format_field *pid_field)
 {
+	struct kshark_data_stream *stream;
 	struct tep_record *record;
 	unsigned long long val;
 	int ret;
 
-	if (!wakeup_event || e->event_id != wakeup_event->id)
+	stream = kshark_get_data_stream(kshark_ctx, sd);
+	if (!stream || !wakeup_event || e->event_id != wakeup_event->id)
 		return -1;
 
-	record = tracecmd_read_at(kshark_ctx->handle, e->offset, NULL);
+	record = tracecmd_read_at(stream->handle, e->offset, NULL);
 	ret = tep_read_number_field(pid_field, record->data, &val);
 	free_record(record);
 
@@ -171,7 +186,7 @@
 static bool wakeup_match_rec_pid(struct plugin_sched_context *plugin_ctx,
 				 struct kshark_context *kshark_ctx,
 				 struct kshark_entry *e,
-				 int pid)
+				 int sd, int pid)
 {
 	struct tep_event *wakeup_events[] = {
 		plugin_ctx->sched_waking_event,
@@ -186,7 +201,8 @@
 	int i, wakeup_pid = -1;
 
 	for (i = 0; i < sizeof(wakeup_events) / sizeof(wakeup_events[0]); i++) {
-		wakeup_pid = find_wakeup_pid(kshark_ctx, e, wakeup_events[i], wakeup_fields[i]);
+		wakeup_pid = find_wakeup_pid(kshark_ctx, e, sd,
+					     wakeup_events[i], wakeup_fields[i]);
 		if (wakeup_pid >= 0)
 			break;
 	}
@@ -210,15 +226,16 @@
  */
 bool plugin_wakeup_match_rec_pid(struct kshark_context *kshark_ctx,
 				 struct kshark_entry *e,
-				 int pid)
+				 int sd, int *pid)
 {
 	struct plugin_sched_context *plugin_ctx;
 
-	plugin_ctx = plugin_sched_context_handler;
-	if (!plugin_ctx)
+	plugin_ctx = plugin_sched_context_handler[sd];
+
+	if (e->stream_id != sd)
 		return false;
 
-	return wakeup_match_rec_pid(plugin_ctx, kshark_ctx, e, pid);
+	return wakeup_match_rec_pid(plugin_ctx, kshark_ctx, e, sd, *pid);
 }
 
 /**
@@ -233,19 +250,20 @@
  */
 bool plugin_switch_match_rec_pid(struct kshark_context *kshark_ctx,
 				 struct kshark_entry *e,
-				 int pid)
+				 int sd, int *pid)
 {
 	struct plugin_sched_context *plugin_ctx;
 	unsigned long long val;
 	int ret, switch_pid = -1;
 
-	plugin_ctx = plugin_sched_context_handler;
+	plugin_ctx = plugin_sched_context_handler[sd];
 
 	if (plugin_ctx->sched_switch_event &&
+	    e->stream_id == sd &&
 	    e->event_id == plugin_ctx->sched_switch_event->id) {
 		struct tep_record *record;
 
-		record = tracecmd_read_at(kshark_ctx->handle, e->offset, NULL);
+		record = kshark_read_at(kshark_ctx, sd, e->offset);
 		ret = tep_read_number_field(plugin_ctx->sched_switch_prev_state_field,
 					    record->data, &val);
 
@@ -255,7 +273,7 @@
 		free_record(record);
 	}
 
-	if (switch_pid >= 0 && switch_pid == pid)
+	if (switch_pid >= 0 && switch_pid == *pid)
 		return true;
 
 	return false;
@@ -273,15 +291,16 @@
  */
 bool plugin_switch_match_entry_pid(struct kshark_context *kshark_ctx,
 				   struct kshark_entry *e,
-				   int pid)
+				   int sd, int *pid)
 {
 	struct plugin_sched_context *plugin_ctx;
 
-	plugin_ctx = plugin_sched_context_handler;
+	plugin_ctx = plugin_sched_context_handler[sd];
 
 	if (plugin_ctx->sched_switch_event &&
 	    e->event_id == plugin_ctx->sched_switch_event->id &&
-	    e->pid == pid)
+	    e->stream_id == sd &&
+	    e->pid == *pid)
 		return true;
 
 	return false;
@@ -299,69 +318,74 @@
  *	    Otherwise false.
  */
 bool plugin_match_pid(struct kshark_context *kshark_ctx,
-		      struct kshark_entry *e, int pid)
+		      struct kshark_entry *e, int sd, int *pid)
 {
-	return plugin_switch_match_entry_pid(kshark_ctx, e, pid) ||
-	       plugin_switch_match_rec_pid(kshark_ctx, e, pid) ||
-	       plugin_wakeup_match_rec_pid(kshark_ctx, e, pid);
+	return plugin_switch_match_entry_pid(kshark_ctx, e, sd, pid) ||
+	       plugin_switch_match_rec_pid(kshark_ctx, e, sd, pid) ||
+	       plugin_wakeup_match_rec_pid(kshark_ctx, e, sd, pid);
 }
 
 static void plugin_sched_action(struct kshark_context *kshark_ctx,
 				struct tep_record *rec,
 				struct kshark_entry *entry)
 {
-	int pid = plugin_get_next_pid(rec);
+	int pid = plugin_get_next_pid(rec, entry->stream_id);
 	if (pid >= 0) {
 		entry->pid = pid;
-		plugin_register_command(kshark_ctx, rec, entry->pid);
+		plugin_register_command(kshark_ctx, rec,
+					entry->stream_id,
+					entry->pid);
 	}
 }
 
-static int plugin_sched_init(struct kshark_context *kshark_ctx)
+static int plugin_sched_init(struct kshark_context *kshark_ctx, int sd)
 {
 	struct plugin_sched_context *plugin_ctx;
 
-	if (!plugin_sched_init_context(kshark_ctx))
+	if (!plugin_sched_init_context(kshark_ctx, sd))
 		return 0;
 
-	plugin_ctx = plugin_sched_context_handler;
+	plugin_ctx = plugin_sched_context_handler[sd];
 
 	kshark_register_event_handler(&kshark_ctx->event_handlers,
 				      plugin_ctx->sched_switch_event->id,
+				      sd,
 				      plugin_sched_action,
 				      plugin_draw);
 
 	return 1;
 }
 
-static int plugin_sched_close(struct kshark_context *kshark_ctx)
+static int plugin_sched_close(struct kshark_context *kshark_ctx, int sd)
 {
 	struct plugin_sched_context *plugin_ctx;
 
-	if (!plugin_sched_context_handler)
+	plugin_ctx = plugin_sched_context_handler[sd];
+	if (!plugin_ctx)
 		return 0;
 
-	plugin_ctx = plugin_sched_context_handler;
-
 	kshark_unregister_event_handler(&kshark_ctx->event_handlers,
 					plugin_ctx->sched_switch_event->id,
+					sd,
 					plugin_sched_action,
 					plugin_draw);
 
-	plugin_free_context(plugin_ctx);
-	plugin_sched_context_handler = NULL;
+	plugin_free_context(plugin_sched_context_handler[sd]);
+	plugin_sched_context_handler[sd] = NULL;
 
 	return 1;
 }
 
 /** Load this plugin. */
-int KSHARK_PLUGIN_INITIALIZER(struct kshark_context *kshark_ctx)
+int KSHARK_PLUGIN_INITIALIZER(struct kshark_context *kshark_ctx, int sd)
 {
-	return plugin_sched_init(kshark_ctx);
+	printf("--> sched init %i\n", sd);
+	return plugin_sched_init(kshark_ctx, sd);
 }
 
 /** Unload this plugin. */
-int KSHARK_PLUGIN_DEINITIALIZER(struct kshark_context *kshark_ctx)
+int KSHARK_PLUGIN_DEINITIALIZER(struct kshark_context *kshark_ctx, int sd)
 {
-	return plugin_sched_close(kshark_ctx);
+	printf("<-- sched close %i\n", sd);
+	return plugin_sched_close(kshark_ctx, sd);
 }
diff --git a/kernel-shark/src/plugins/sched_events.h b/kernel-shark/src/plugins/sched_events.h
index dbc9963..96cc904 100644
--- a/kernel-shark/src/plugins/sched_events.h
+++ b/kernel-shark/src/plugins/sched_events.h
@@ -64,22 +64,25 @@
 	struct tracecmd_filter_id	*second_pass_hash;
 };
 
-int plugin_get_next_pid(struct tep_record *record);
+struct plugin_sched_context *get_sched_context(int sd);
+
+int plugin_get_next_pid(struct tep_record *record, int sd);
 
 bool plugin_wakeup_match_rec_pid(struct kshark_context *kshark_ctx,
-				 struct kshark_entry *e, int pid);
+				 struct kshark_entry *e, int sd, int *pid);
 
 bool plugin_switch_match_rec_pid(struct kshark_context *kshark_ctx,
-				 struct kshark_entry *e, int pid);
+				 struct kshark_entry *e, int sd, int *pid);
 
 bool plugin_switch_match_entry_pid(struct kshark_context *kshark_ctx,
 				   struct kshark_entry *e,
-				   int pid);
+				   int sd, int *pid);
 
 bool plugin_match_pid(struct kshark_context *kshark_ctx,
-		      struct kshark_entry *e, int pid);
+		      struct kshark_entry *e, int sd, int *pid);
 
-void plugin_draw(struct kshark_cpp_argv *argv, int pid, int draw_action);
+void plugin_draw(struct kshark_cpp_argv *argv, int sd, int pid,
+		 int draw_action);
 
 #ifdef __cplusplus
 }