Example #1
0
class PlotNordic():

    def __init__(self, stream=None, event_close=None, log_lvl=logging.WARNING):
        plt.rcParams['toolbar'] = 'None'
        plt.ioff()
        self.plot_config = PlotNordicConfig
        self.draw_state = DrawState(
            self.plot_config['timeline_width_init'],
            self.plot_config['event_processing_rect_height'],
            self.plot_config['event_submit_markersize'])
        self.ani = None
        self.close_event_flag = False
        self.processed_events = ProcessedEvents()

        if stream is not None:
            timeouts = {
                'descriptions': 1,
                'events': 0
            }
            self.in_stream = stream
            self.in_stream.set_timeouts(timeouts)

        if event_close is not None:
            self.event_close = event_close

        self.logger = logging.getLogger('Plot Nordic')
        self.logger_console = logging.StreamHandler()
        self.logger.setLevel(log_lvl)
        self.log_format = logging.Formatter(
            '[%(levelname)s] %(name)s: %(message)s')
        self.logger_console.setFormatter(self.log_format)
        self.logger.addHandler(self.logger_console)


    def read_data_from_files(self, events_filename, events_types_filename):
        self.processed_events.read_data_from_files(
            events_filename, events_types_filename)
        if not self.processed_events.verify():
            self.logger.warning("Missing event descriptions")

    def on_click_start_stop(self, event):
        if self.draw_state.paused:
            if self.draw_state.l_line is not None:
                self.draw_state.l_line.remove()
                self.draw_state.l_line = None
                self.draw_state.l_line_coord = None

            if self.draw_state.r_line is not None:
                self.draw_state.r_line.remove()
                self.draw_state.r_line = None
                self.draw_state.r_line_coord = None

            if self.draw_state.duration_marker is not None:
                self.draw_state.duration_marker.remove()

        self.draw_state.paused = not self.draw_state.paused

    def _prepare_plot(self, selected_events_types):

        self.draw_state.ax = plt.gca()
        self.draw_state.ax.set_navigate(False)

        fig = plt.gcf()
        fig.set_size_inches(
            self.plot_config['window_width_inch'],
            self.plot_config['window_height_inch'],
            forward=True)
        fig.canvas.draw()

        plt.xlabel("Time [s]")
        plt.title("Custom events")
        plt.grid(True)

        minimum = min(selected_events_types)
        maximum = max(selected_events_types)
        ticks = []
        labels = []
        for j in selected_events_types:
            ticks.append(j)
            labels.append(self.processed_events.registered_events_types[j].name)
        plt.yticks(ticks, labels)

        # min and max range of y axis are bigger by one so markers fit nicely
        # on plot
        self.draw_state.y_max = maximum + 1
        self.draw_state.y_height = maximum - minimum + 2
        plt.ylim([minimum - 1, maximum + 1])

        self.draw_state.selected_event_textbox = self.draw_state.ax.text(
            0.05,
            0.95,
            self.draw_state.selected_event_text,
            fontsize=10,
            transform=self.draw_state.ax.transAxes,
            verticalalignment='top',
            bbox=dict(
                boxstyle='round',
                alpha=0.5,
                facecolor='linen'))
        self.draw_state.selected_event_textbox.set_visible(False)

        fig.canvas.mpl_connect('scroll_event', self.scroll_event)
        fig.canvas.mpl_connect('button_press_event', self.button_press_event)
        fig.canvas.mpl_connect('button_release_event',
                               self.button_release_event)
        fig.canvas.mpl_connect('resize_event', PlotNordic.resize_event)
        fig.canvas.mpl_connect('close_event', self.close_event)

        plt.tight_layout()

        return fig

    def _get_relative_coords(self, event):
        # relative position of plot - x0, y0, width, height
        ax_loc = self.draw_state.ax.get_position().bounds
        window_size = plt.gcf().get_size_inches() * \
            plt.gcf().dpi  # window size - width, height
        x_rel = (event.x - ax_loc[0] * window_size[0]) \
                 / ax_loc[2] / window_size[0]
        y_rel = (event.y - ax_loc[1] * window_size[1]) \
                 / ax_loc[3] / window_size[1]
        return x_rel, y_rel

    def scroll_event(self, event):
        x_rel, _ = self._get_relative_coords(event)

        if event.button == 'up':
            if self.draw_state.paused:
                self.draw_state.timeline_max = self.draw_state.timeline_max - (1 - x_rel) * \
                    (self.draw_state.timeline_width - self.draw_state.timeline_width *
                     self.plot_config['timeline_scale_factor'])
            self.draw_state.timeline_width = self.draw_state.timeline_width * \
                self.plot_config['timeline_scale_factor']

        if event.button == 'down':
            if self.draw_state.paused:
                self.draw_state.timeline_max = self.draw_state.timeline_max + (1 - x_rel) * \
                    (self.draw_state.timeline_width / self.plot_config['timeline_scale_factor'] -
                     self.draw_state.timeline_width)
            self.draw_state.timeline_width = self.draw_state.timeline_width / \
                self.plot_config['timeline_scale_factor']

        self.draw_state.ax.set_xlim(
            self.draw_state.timeline_max -
            self.draw_state.timeline_width,
            self.draw_state.timeline_max)
        plt.draw()

    def _find_closest_event(self, x_coord, y_coord):
        filtered_id = list(filter(lambda x: x.submit.type_id == round(y_coord),
                                    self.processed_events.tracked_events))
        if len(filtered_id) == 0:
            return None
        if not self.processed_events.is_event_tracked(round(y_coord)):
            dists = list(map(lambda x: abs(x.submit.timestamp - x_coord), filtered_id))
            return filtered_id[np.argmin(dists)]
        else:
            matching_processing = list(
                filter(
                    lambda x: x.proc_start_time < x_coord < x.proc_end_time,
                    filtered_id))
            if matching_processing:
                return matching_processing[0]
            dists = list(map(lambda x: min([abs(x.submit.timestamp - x_coord),
                                abs(x.proc_start_time - x_coord), abs(x.proc_end_time - x_coord)]), filtered_id))
            return filtered_id[np.argmin(dists)]

    @staticmethod
    def _stringify_time(time_seconds):
        if time_seconds > 0.1:
            return '%.5f' % (time_seconds) + ' s'

        return '%.5f' % (1000 * time_seconds) + ' ms'

    def button_press_event(self, event):
        x_rel, y_rel = self._get_relative_coords(event)

        if event.button == MouseButton.LEFT.value:
            self.draw_state.pan_x_start1 = x_rel

        if event.button == MouseButton.MIDDLE.value:
            if self.draw_state.selected_event_submit is not None:
                for i in self.draw_state.selected_event_submit:
                    i.remove()
                self.draw_state.selected_event_submit = None

            if self.draw_state.selected_event_processing is not None:
                self.draw_state.selected_event_processing.remove()
                self.draw_state.selected_event_processing = None

            self.draw_state.selected_event_textbox.set_visible(False)

            if x_rel > 1 or x_rel < 0 or y_rel > 1 or y_rel < 0:
                plt.draw()
                return

            coord_x = self.draw_state.timeline_max - \
                (1 - x_rel) * self.draw_state.timeline_width
            coord_y = self.draw_state.y_max - \
                (1 - y_rel) * self.draw_state.y_height
            selected_event = self._find_closest_event(coord_x, coord_y)
            if selected_event is None:
                return
            event_submit = selected_event.submit

            self.draw_state.selected_event_submit = self.draw_state.ax.plot(
                event_submit.timestamp,
                event_submit.type_id,
                markersize=2*self.draw_state.event_submit_markersize,
                color='g',
                marker='o',
                linestyle=' ')

            if selected_event.proc_start_time is not None:
                self.draw_state.selected_event_processing = matplotlib.patches.Rectangle(
                    (selected_event.proc_start_time,
                    selected_event.submit.type_id -
                    self.draw_state.event_processing_rect_height),
                    selected_event.proc_end_time -
                    selected_event.proc_start_time,
                    2*self.draw_state.event_processing_rect_height,
                    color='g')
                self.draw_state.ax.add_artist(
                    self.draw_state.selected_event_processing)

            self.draw_state.selected_event_text = \
                self.processed_events.registered_events_types[event_submit.type_id].name + '\n'
            self.draw_state.selected_event_text += 'Submit: ' + \
                PlotNordic._stringify_time(event_submit.timestamp) + '\n'

            if selected_event.proc_start_time is not None:
                self.draw_state.selected_event_text += 'Processing start: ' + \
                    PlotNordic._stringify_time(
                        selected_event.proc_start_time) + '\n'
                self.draw_state.selected_event_text += 'Processing end: ' + \
                    PlotNordic._stringify_time(
                        selected_event.proc_end_time) + '\n'
                self.draw_state.selected_event_text += 'Processing time: ' + \
                    PlotNordic._stringify_time(selected_event.proc_end_time - \
                        selected_event.proc_start_time) + '\n'

            ev_type = self.processed_events.registered_events_types[event_submit.type_id]

            for i in range(0, len(ev_type.data_descriptions)):
                if ev_type.data_descriptions[i] == EM_MEM_ADDRESS_DATA_DESC:
                    continue
                self.draw_state.selected_event_text += ev_type.data_descriptions[i] + ' = '
                self.draw_state.selected_event_text += str(event_submit.data[i]) + '\n'

            self.draw_state.selected_event_textbox.set_visible(True)
            self.draw_state.selected_event_textbox.set_text(
                self.draw_state.selected_event_text)

            plt.draw()

        if event.button == MouseButton.RIGHT.value:
            self.draw_state.pan_x_start2 = x_rel

    def button_release_event(self, event):
        x_rel, y_rel = self._get_relative_coords(event)

        if event.button == MouseButton.LEFT.value:
            if self.draw_state.paused:
                if abs(x_rel - self.draw_state.pan_x_start1) < 0.01:
                    if self.draw_state.l_line is not None:
                        self.draw_state.l_line.remove()
                        self.draw_state.l_line = None
                        self.draw_state.l_line_coord = None

                    if 0 <= x_rel <= 1:
                        if 0 <= y_rel <= 1:
                            self.draw_state.l_line_coord = self.draw_state.timeline_max - \
                                (1 - x_rel) * self.draw_state.timeline_width
                            self.draw_state.l_line = plt.axvline(
                                self.draw_state.l_line_coord)
                    plt.draw()

                else:
                    self.draw_state.timeline_max = self.draw_state.timeline_max - \
                        (x_rel - self.draw_state.pan_x_start1) * \
                        self.draw_state.timeline_width
                    self.draw_state.ax.set_xlim(
                        self.draw_state.timeline_max -
                        self.draw_state.timeline_width,
                        self.draw_state.timeline_max)
                    plt.draw()

        if event.button == MouseButton.RIGHT.value:
            if self.draw_state.paused:
                if abs(x_rel - self.draw_state.pan_x_start2) < 0.01:
                    if self.draw_state.r_line is not None:
                        self.draw_state.r_line.remove()
                        self.draw_state.r_line = None
                        self.draw_state.r_line_coord = None

                    if 0 <= x_rel <= 1:
                        if 0 <= y_rel <= 1:
                            self.draw_state.r_line_coord = self.draw_state.timeline_max - \
                                (1 - x_rel) * self.draw_state.timeline_width
                            self.draw_state.r_line = plt.axvline(
                                self.draw_state.r_line_coord, color='r')
                    plt.draw()

        if self.draw_state.r_line_coord is not None and self.draw_state.l_line_coord is not None:
            if self.draw_state.duration_marker is not None:
                self.draw_state.duration_marker.remove()
            bigger_coord = max(
                self.draw_state.r_line_coord,
                self.draw_state.l_line_coord)
            smaller_coord = min(
                self.draw_state.r_line_coord,
                self.draw_state.l_line_coord)
            self.draw_state.duration_marker = plt.annotate(
                text=PlotNordic._stringify_time(
                    bigger_coord - smaller_coord), xy=(
                    smaller_coord, 0.5), xytext=(
                    bigger_coord, 0.5), arrowprops=dict(
                    arrowstyle='<->'))
        else:
            if self.draw_state.duration_marker is not None:
                self.draw_state.duration_marker.remove()
                self.draw_state.duration_marker = None

    @staticmethod
    def resize_event(event):
        plt.tight_layout()

    def close_event(self, event):
        self.close_event_flag = True

    def animate_events_real_time(self, fig):
        rects = []
        events = []
        #Receive events
        while True:
            try:
                data = self.in_stream.recv_ev()
            except StreamError as err:
                if err.args[1] == StreamError.TIMEOUT_MSG:
                    break
                self.logger.error("Receiving error: {}. Exiting".format(err))
                self.close_event(None)
                sys.exit()
            data_str = data.decode()
            tracked_event = TrackedEvent.deserialize(data_str)

            events.append(tracked_event.submit)
            self.processed_events.tracked_events.append(tracked_event)

            if tracked_event.proc_start_time is not None:
                assert tracked_event.proc_end_time is not None
                rects.append(
                    matplotlib.patches.Rectangle(
                        (tracked_event.proc_start_time,
                            tracked_event.submit.type_id -
                            self.draw_state.event_processing_rect_height/2),
                        tracked_event.proc_end_time -
                        tracked_event.proc_start_time,
                        self.draw_state.event_processing_rect_height,
                        edgecolor='black'))

        # translating plot
        if not self.draw_state.synchronized_with_events:
            # ignore translating plot for stale events
            if not self.draw_state.stale_events_displayed:
                self.draw_state.stale_events_displayed = True
            else:
            # translate plot for new events
                if len(events) != 0:
                    self.draw_state.added_time = events[-1].timestamp - \
                                                   0.3 * self.draw_state.timeline_width
                    self.draw_state.synchronized_with_events = True

        if not self.draw_state.paused:
            self.draw_state.timeline_max = time.time() - self.start_time + \
                self.draw_state.added_time
            self.draw_state.ax.set_xlim(
                self.draw_state.timeline_max -
                self.draw_state.timeline_width,
                self.draw_state.timeline_max)

        # plotting events
        y = list(map(lambda x: x.type_id, events))
        x = list(map(lambda x: x.timestamp, events))
        self.draw_state.ax.plot(
            x,
            y,
            marker='o',
            linestyle=' ',
            color='r',
            markersize=self.draw_state.event_submit_markersize)

        self.draw_state.ax.add_collection(PatchCollection(rects))
        plt.gcf().canvas.flush_events()
        if self.event_close.is_set():
            self.close_event(None)
        if self.close_event_flag:
            sys.exit()

    def plot_events_real_time(self, selected_events_types=None):
        self.start_time = time.time()
        #Receive event descriptions
        while True:
            try:
                bytes = self.in_stream.recv_desc()
                break
            except StreamError as err:
                if err.args[1] == StreamError.TIMEOUT_MSG:
                    if self.event_close.is_set():
                        self.logger.info("Module closed before receiving event descriptions.")
                        sys.exit()
                    continue
                self.logger.error("Receiving error: {}. Exiting".format(err))
                sys.exit()
        data_str = bytes.decode()
        event_types_dict = json.loads(data_str)
        self.processed_events.registered_events_types = dict((int(k), EventType.deserialize(v))
                                                             for k, v in event_types_dict.items())
        if self.processed_events.registered_events_types is None:
            self.logger.error("Event descriptors not sent properly")
            sys.exit()
        if selected_events_types is None:
            selected_events_types = list(
                self.processed_events.registered_events_types.keys())

        fig = self._prepare_plot(selected_events_types)

        self.start_stop_ax = plt.axes([0.8, 0.025, 0.1, 0.04])
        self.start_stop_button = Button(self.start_stop_ax, 'Start/Stop')
        self.start_stop_button.on_clicked(self.on_click_start_stop)
        plt.sca(self.draw_state.ax)

        self.ani = animation.FuncAnimation(
            fig,
            self.animate_events_real_time,
            interval=self.plot_config['refresh_time'])
        plt.show()

    def plot_events_from_file(
            self, selected_events_types=None, one_line=False):
        self.draw_state.paused = True
        if len(self.processed_events.tracked_events) == 0 or \
                len(self.processed_events.registered_events_types) == 0:
            self.logger.error("Please read some events data before plotting")

        if selected_events_types is None:
            selected_events_types = list(
                self.processed_events.registered_events_types.keys())

        self._prepare_plot(selected_events_types)

        x = list(map(lambda x: x.submit.timestamp, self.processed_events.tracked_events))
        y = list(map(lambda x: x.submit.type_id, self.processed_events.tracked_events))
        self.draw_state.ax.plot(
            x,
            y,
            marker='o',
            linestyle=' ',
            color='r',
            markersize=self.draw_state.event_submit_markersize)

        rects = []
        for ev in self.processed_events.tracked_events:
            if ev.proc_start_time is None:
                continue
            rects.append(
                matplotlib.patches.Rectangle(
                    (ev.proc_start_time,
                        ev.submit.type_id - self.draw_state.event_processing_rect_height/2),
                        ev.proc_end_time - ev.proc_start_time,
                        self.draw_state.event_processing_rect_height,
                        edgecolor='black'))

        self.draw_state.ax.add_collection(PatchCollection(rects))

        self.draw_state.timeline_max = max(x) + 1
        self.draw_state.timeline_width = max(x) - min(x) + 2
        self.draw_state.ax.set_xlim([min(x) - 1, max(x) + 1])

        plt.draw()
        plt.show()
class ModelCreator:
    def __init__(self,
                 own_recv_socket_dict,
                 own_send_socket_dict=None,
                 remote_socket_dict=None,
                 config=RttNordicConfig,
                 event_filename=None,
                 event_types_filename=None,
                 log_lvl=logging.INFO):
        self.config = config
        self.event_filename = event_filename
        self.event_types_filename = event_types_filename
        self.csvfile = None

        timeouts = {'descriptions': None, 'events': None}
        self.in_stream = Stream(own_recv_socket_dict, timeouts)

        if own_send_socket_dict is not None and remote_socket_dict is not None:
            self.sending = True
            self.out_stream = Stream(own_send_socket_dict,
                                     timeouts,
                                     remote_socket_dict=remote_socket_dict)
        else:
            self.sending = False

        self.timestamp_overflows = 0
        self.after_half = False

        self.processed_events = ProcessedEvents()
        self.temp_events = []
        self.submitted_event_type = None
        self.raw_data = EventsData([], {})
        self.event_processing_start_id = None
        self.event_processing_end_id = None
        self.submit_event = None
        self.start_event = None

        self.bufs = list()
        self.bcnt = 0

        self.logger = logging.getLogger('Profiler model creator')
        self.logger_console = logging.StreamHandler()
        self.logger.setLevel(log_lvl)
        self.log_format = logging.Formatter(
            '[%(levelname)s] %(name)s: %(message)s')
        self.logger_console.setFormatter(self.log_format)
        self.logger.addHandler(self.logger_console)

    def shutdown(self):
        if self.csvfile is not None:
            self.processed_events.finish_writing_data_to_files(
                self.csvfile, self.event_filename, self.event_types_filename)

    def _get_buffered_data(self, num_bytes):
        buf = bytearray()
        while len(buf) < num_bytes:
            tbuf = self.bufs[0]
            size = num_bytes - len(buf)
            if len(tbuf) <= size:
                buf.extend(tbuf)
                del self.bufs[0]
            else:
                buf.extend(tbuf[0:size])
                self.bufs[0] = tbuf[size:]
        self.bcnt -= num_bytes
        return buf

    def _read_bytes(self, num_bytes):
        while True:
            if self.bcnt >= num_bytes:
                break
            try:
                buf = self.in_stream.recv_ev()
            except StreamError as err:
                self.logger.error("Receiving error: {}".format(err))
                self.close()
            if len(buf) > 0:
                self.bufs.append(buf)
                self.bcnt += len(buf)

        return self._get_buffered_data(num_bytes)

    def _timestamp_from_ticks(self, clock_ticks):
        ts_ticks_aggregated = self.timestamp_overflows * self.config[
            'timestamp_raw_max']
        ts_ticks_aggregated += clock_ticks
        ts_s = ts_ticks_aggregated * self.config['ms_per_timestamp_tick'] / 1000
        return ts_s

    def transmit_all_events_descriptions(self):
        try:
            bytes = self.in_stream.recv_desc()
        except StreamError as err:
            self.logger.error("Receiving error: {}. Exiting".format(err))
            sys.exit()
        desc_buf = bytes.decode()
        f = StringIO(desc_buf)
        reader = csv.reader(f, delimiter=',')
        for row in reader:
            # Empty field is send after last event description
            if len(row) == 0:
                break
            name = row[0]
            id = int(row[1])
            data_type = row[2:len(row) // 2 + 1]
            data = row[len(row) // 2 + 1:]
            self.raw_data.registered_events_types[id] = EventType(
                name, data_type, data)
            if name not in ('event_processing_start', 'event_processing_end'):
                self.processed_events.registered_events_types[id] = EventType(
                    name, data_type, data)

        self.event_processing_start_id = \
            self.raw_data.get_event_type_id('event_processing_start')
        self.event_processing_end_id = \
            self.raw_data.get_event_type_id('event_processing_end')

        if self.sending:
            event_types_dict = dict(
                (k, v.serialize()) for k, v in
                self.processed_events.registered_events_types.items())
            json_et_string = json.dumps(event_types_dict)
            try:
                self.out_stream.send_desc(json_et_string.encode())
            except StreamError as err:
                self.logger.error("Error: {}. Unable to send data".format(err))
                sys.exit()

    def _read_single_event(self):
        id = int.from_bytes(self._read_bytes(1),
                            byteorder=self.config['byteorder'],
                            signed=False)
        et = self.raw_data.registered_events_types[id]

        buf = self._read_bytes(4)
        timestamp_raw = (int.from_bytes(buf,
                                        byteorder=self.config['byteorder'],
                                        signed=False))

        if self.after_half \
        and timestamp_raw < 0.4 * self.config['timestamp_raw_max']:
            self.timestamp_overflows += 1
            self.after_half = False

        if timestamp_raw > 0.6 * self.config['timestamp_raw_max']:
            self.after_half = True

        timestamp = self._timestamp_from_ticks(timestamp_raw)

        def process_int32(self, data):
            buf = self._read_bytes(4)
            data.append(
                int.from_bytes(buf,
                               byteorder=self.config['byteorder'],
                               signed=True))

        def process_uint32(self, data):
            buf = self._read_bytes(4)
            data.append(
                int.from_bytes(buf,
                               byteorder=self.config['byteorder'],
                               signed=False))

        def process_int16(self, data):
            buf = self._read_bytes(2)
            data.append(
                int.from_bytes(buf,
                               byteorder=self.config['byteorder'],
                               signed=True))

        def process_uint16(self, data):
            buf = self._read_bytes(2)
            data.append(
                int.from_bytes(buf,
                               byteorder=self.config['byteorder'],
                               signed=False))

        def process_int8(self, data):
            buf = self._read_bytes(1)
            data.append(
                int.from_bytes(buf,
                               byteorder=self.config['byteorder'],
                               signed=True))

        def process_uint8(self, data):
            buf = self._read_bytes(1)
            data.append(
                int.from_bytes(buf,
                               byteorder=self.config['byteorder'],
                               signed=False))

        def process_string(self, data):
            buf = self._read_bytes(1)
            buf = self._read_bytes(
                int.from_bytes(buf,
                               byteorder=self.config['byteorder'],
                               signed=False))
            data.append(buf.decode())

        READ_BYTES = {
            "u8": process_uint8,
            "s8": process_int8,
            "u16": process_uint16,
            "s16": process_int16,
            "u32": process_uint32,
            "s32": process_int32,
            "s": process_string,
            "t": process_uint32
        }
        data = []
        for event_data_type in et.data_types:
            READ_BYTES[event_data_type](self, data)
        return Event(id, timestamp, data)

    def _send_event(self, tracked_event):
        event_string = tracked_event.serialize()
        try:
            self.out_stream.send_ev(event_string.encode())
        except StreamError as err:
            if err.args[1] != 'closed':
                self.logger.error("Error. Unable to send data: {}".format(err))
            # Receiver has been closed
            self.close()

    def _write_event_to_file(self, csvfile, tracked_event):
        try:
            csvfile.write(tracked_event.serialize() + '\r\n')
        except IOError:
            self.logger.error("Problem with accessing csv file")
            self.close()

    def transmit_events(self):
        if self.event_filename and self.event_types_filename:
            self.csvfile = self.processed_events.init_writing_data_to_files(
                self.event_filename, self.event_types_filename)
        while True:
            event = self._read_single_event()

            if event.type_id == self.event_processing_start_id:
                self.start_event = event
                for i in range(len(self.temp_events) - 1, -1, -1):
                    # comparing memory addresses of event processing start
                    # and event submit to identify matching events
                    if self.temp_events[i].data[0] == self.start_event.data[0]:
                        self.submit_event = self.temp_events[i]
                        self.submitted_event_type = self.submit_event.type_id
                        del self.temp_events[i]
                        break

            elif event.type_id == self.event_processing_end_id:
                # comparing memory addresses of event processing start and
                # end to identify matching events
                if self.submitted_event_type is not None and event.data[0] \
                            == self.start_event.data[0]:
                    tracked_event = TrackedEvent(self.submit_event,
                                                 self.start_event.timestamp,
                                                 event.timestamp)
                    if self.csvfile is not None:
                        self._write_event_to_file(self.csvfile, tracked_event)
                    if self.sending:
                        self._send_event(tracked_event)
                    self.submitted_event_type = None

            elif not self.processed_events.is_event_tracked(event.type_id):
                tracked_event = TrackedEvent(event, None, None)
                if self.csvfile is not None:
                    self._write_event_to_file(self.csvfile, tracked_event)
                if self.sending:
                    self._send_event(tracked_event)

            else:
                self.temp_events.append(event)

    def start(self):
        self.transmit_all_events_descriptions()
        self.transmit_events()

    def close(self):
        self.logger.info("Real time transmission closed")
        self.shutdown()
        self.logger.info("Events data saved to files")
        sys.exit()
class StatsNordic():
    def __init__(self, events_filename, events_types_filename, log_lvl):
        self.data_name = events_filename.split('.')[0]
        self.processed_data = ProcessedEvents()
        self.processed_data.read_data_from_files(events_filename, events_types_filename)

        self.logger = logging.getLogger('Stats Nordic')
        self.logger_console = logging.StreamHandler()
        self.logger.setLevel(log_lvl)
        self.log_format = logging.Formatter(
            '[%(levelname)s] %(name)s: %(message)s')
        self.logger_console.setFormatter(self.log_format)
        self.logger.addHandler(self.logger_console)

    def calculate_stats_preset1(self, start_meas, end_meas):
        self.time_between_events("hid_mouse_event_dongle", EventState.SUBMIT,
                                 "hid_report_sent_event_device", EventState.SUBMIT,
                                 0.05, start_meas, end_meas)
        self.time_between_events("hid_mouse_event_dongle", EventState.SUBMIT,
                                 "hid_report_sent_event_device", EventState.SUBMIT,
                                 0.05, start_meas, end_meas)
        self.time_between_events("hid_report_sent_event_dongle", EventState.SUBMIT,
                                 "hid_report_sent_event_dongle", EventState.SUBMIT,
                                 0.05, start_meas, end_meas)
        self.time_between_events("hid_mouse_event_dongle", EventState.SUBMIT,
                                 "hid_report_sent_event_dongle", EventState.SUBMIT,
                                 0.05, start_meas, end_meas)
        self.time_between_events("hid_mouse_event_device", EventState.SUBMIT,
                                 "hid_mouse_event_dongle", EventState.SUBMIT,
                                 0.05, start_meas, end_meas)
        plt.show()

    def _get_timestamps(self, event_name, event_state, start_meas, end_meas):
        event_type_id = self.processed_data.get_event_type_id(event_name)
        if event_type_id is None:
            self.logger.error("Event name not found: " + event_name)
            return None
        if not self.processed_data.is_event_tracked(event_type_id) and event_state != EventState.SUBMIT:
            self.logger.error("This event is not tracked: " + event_name)
            return None

        trackings = list(filter(lambda x:
                      x.submit.type_id == event_type_id,
                      self.processed_data.tracked_events))

        if not isinstance(event_state, EventState):
            self.logger.error("Event state should be EventState enum")
            return None

        if event_state == EventState.SUBMIT:
            timestamps = np.fromiter(map(lambda x: x.submit.timestamp, trackings),
                                     dtype=np.float)
        elif event_state == EventState.PROC_START:
            timestamps = np.fromiter(map(lambda x: x.proc_start_time, trackings),
                                     dtype=np.float)
        elif event_state == EventState.PROC_END:
            timestamps = np.fromiter(map(lambda x: x.proc_end_time, trackings),
                                     dtype=np.float)

        timestamps = timestamps[np.where((timestamps > start_meas)
                                         & (timestamps < end_meas))]

        return timestamps

    @staticmethod
    def calculate_times_between(start_times, end_times):
        if end_times[0] <= start_times[0]:
            end_times = end_times[1:]
        if len(start_times) > len(end_times):
            start_times = start_times[:-1]

        return (end_times - start_times) * 1000

    @staticmethod
    def prepare_stats_txt(times_between):
        stats_text = "Max time: "
        stats_text += "{0:.3f}".format(max(times_between)) + "ms\n"
        stats_text += "Min time: "
        stats_text += "{0:.3f}".format(min(times_between)) + "ms\n"
        stats_text += "Mean time: "
        stats_text += "{0:.3f}".format(np.mean(times_between)) + "ms\n"
        stats_text += "Std dev of time: "
        stats_text += "{0:.3f}".format(np.std(times_between)) + "ms\n"
        stats_text += "Median time: "
        stats_text += "{0:.3f}".format(np.median(times_between)) + "ms\n"
        stats_text += "Number of records: {}".format(len(times_between)) + "\n"

        return stats_text

    def time_between_events(self, start_event_name, start_event_state,
                            end_event_name, end_event_state, hist_bin_width=0.01,
                            start_meas=0, end_meas=float('inf')):
        self.logger.info("Stats calculating: {}->{}".format(start_event_name,
                                                            end_event_name))

        start_times = self._get_timestamps(start_event_name, start_event_state,
                                           start_meas, end_meas)
        end_times = self._get_timestamps(end_event_name, end_event_state,
                                           start_meas, end_meas)

        if start_times is None or end_times is None:
            return

        if len(start_times) == 0:
            self.logger.error("No events logged: " + start_event_name)
            return

        if len(end_times) == 0:
            self.logger.error("No events logged: " + end_event_name)
            return

        if len(start_times) != len(end_times):
            self.logger.error("Number of start_times and end_times is not equal")
            self.logger.error("Got {} start_times and {} end_times".format(
                len(start_times), len(end_times)))

            return

        times_between = self.calculate_times_between(start_times, end_times)
        stats_text = self.prepare_stats_txt(times_between)

        plt.figure()

        ax = plt.gca()
        ax.text(0.05,
                0.95,
                stats_text,
                transform=ax.transAxes,
                fontsize=12,
                verticalalignment='top',
                bbox=dict(boxstyle='round',
                            alpha=0.5,
                            facecolor='linen'))

        plt.xlabel('Duration[ms]')
        plt.ylabel('Number of occurrences')

        event_status_str = {
                        EventState.SUBMIT : "submission",
                        EventState.PROC_START : "processing start",
                        EventState.PROC_END : "processing end"
        }

        title = "From " + start_event_name + ' ' + \
                event_status_str[start_event_state] + "\nto " + \
                end_event_name + ' ' + event_status_str[end_event_state] + \
                ' (' + self.data_name + ')'
        plt.title(title)
        plt.hist(times_between, bins = (int)((max(times_between) - min(times_between))
                                        / hist_bin_width))

        plt.yscale('log')
        plt.grid(True)

        if end_meas == float('inf'):
            end_meas_string = 'inf'
        else:
            end_meas_string = int(end_meas)
        dir_name = "{}{}_{}_{}/".format(OUTPUT_FOLDER, self.data_name,
                                        int(start_meas), end_meas_string)
        if not os.path.exists(dir_name):
            os.makedirs(dir_name)

        plt.savefig(dir_name +
                    title.lower().replace(' ', '_').replace('\n', '_') +'.png')