Home | History | Annotate | Download | only in graphics_WebGLAquarium
      1 # Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 import logging
      6 import numpy
      7 import os
      8 import re
      9 import threading
     10 import time
     11 
     12 TRACING_DIR = '/sys/kernel/debug/tracing'
     13 BUFFER_SIZE_RUNNING = '5000'
     14 BUFFER_SIZE_IDLE = '1408'
     15 
     16 def tracing_write(file_name, msg):
     17     """Helper method to write a file in kernel debugfs.
     18 
     19     @param file_name: The file to write in debugfs.
     20     @param msg: The content to write.
     21     """
     22     with open(os.path.join(TRACING_DIR, file_name), 'w') as f:
     23         f.write(msg)
     24         f.flush()
     25 
     26 
     27 def enable_tracing(events_list=None):
     28     """Enable kernel tracing.
     29 
     30     @param events_list: The list of events to monitor.  Defaults to None to
     31         monitor all events.
     32     """
     33     tracing_write('trace_clock', 'global')
     34     tracing_write('buffer_size_kb', BUFFER_SIZE_RUNNING)
     35     if events_list:
     36         tracing_write('set_event', '\n'.join(events_list))
     37     tracing_write('tracing_on', '1')
     38 
     39 
     40 def disable_tracing():
     41     """Disable kernel tracing."""
     42     tracing_write('tracing_on', '0')
     43     tracing_write('set_event', '')
     44     tracing_write('trace', '0')
     45     tracing_write('buffer_size_kb', BUFFER_SIZE_IDLE)
     46 
     47 
     48 def get_trace_log():
     49     """Get kernel tracing log."""
     50     with open(os.path.join(TRACING_DIR, 'trace'), 'r') as f:
     51         return f.read()
     52 
     53 
     54 class Sampler(object):
     55     """Base sampler class."""
     56 
     57     def __init__(self, period, duration, events=None):
     58         self.period = period
     59         self.duration = duration
     60         self.events = events or []
     61         self.sampler_callback = None
     62         self.stop_sampling = threading.Event()
     63         self.sampling_thread = None
     64 
     65     @property
     66     def stopped(self):
     67         """Check if sampler is stopped."""
     68         return self.stop_sampling.is_set()
     69 
     70     def _do_sampling(self):
     71         """Main sampling loop."""
     72         while True:
     73             next_sampling_time = time.time() + self.period
     74             try:
     75                 enable_tracing(events_list=self.events)
     76                 if self.stop_sampling.wait(self.duration):
     77                     return
     78                 self.parse_ftrace(get_trace_log())
     79                 self.sampler_callback(self)
     80             finally:
     81                 disable_tracing()
     82                 self.reset()
     83             if self.stop_sampling.wait(next_sampling_time - time.time()):
     84                 return
     85 
     86     def start_sampling_thread(self):
     87         """Start a thread to sample events."""
     88         if not self.sampler_callback:
     89             raise RuntimeError('Sampler callback function is not set')
     90         self.stop_sampling.clear()
     91         self.sampling_thread = threading.Thread(target=self._do_sampling)
     92         self.sampling_thread.daemon = True
     93         self.sampling_thread.start()
     94 
     95     def stop_sampling_thread(self):
     96         """Stop sampling thread."""
     97         self.stop_sampling.set()
     98         self.reset()
     99 
    100     def parse_ftrace(self, data):
    101         """Parse ftrace data.
    102 
    103         @param data: The ftrace data to parse.
    104         """
    105         raise NotImplementedError
    106 
    107     def reset(self):
    108         """Reset the sampler."""
    109         raise NotImplementedError
    110 
    111 
    112 class ExynosSampler(Sampler):
    113     """Sampler for Exynos platform."""
    114     def __init__(self, *args, **kwargs):
    115         kwargs['events'] = ['exynos_page_flip_state']
    116         super(ExynosSampler, self).__init__(*args, **kwargs)
    117         self.frame_buffers = {}
    118 
    119     def reset(self):
    120         self.frame_buffers.clear()
    121 
    122     def parse_ftrace(self, data):
    123         """Read and parse the ftrace file"""
    124         # Parse using RE and named group match (?P<xxx>yyy) for clarity.
    125         # Format:
    126         # TASK-PID CPU#  |||| TIMESTAMP  FUNCTION
    127         #    X-007 [001] .... 001.000001: (a line-wrap in Python here ...)
    128         #          exynos_page_flip_state: pipe=0, fb=25, state=wait_kds
    129         re_pattern = (
    130             '\s*(?P<task>.+)-' + # task
    131             '(?P<pid>\d+)\s+' + # pid
    132             '\[(?P<cpu>\d+)\]\s+' + # cpu#
    133             '(?P<inhp>....)\s+' + # inhp: irqs-off, need-resched,
    134                                   #       hardirq/softirq, preempt-depth
    135             '(?P<timestamp>\d+\.\d+):\s+' + # timestamp
    136             '(?P<head>exynos_page_flip_state):\s+' + # head
    137             '(?P<detail>.*)') # detail: 'pipe=0, fb=25, state=wait_kds'
    138 
    139         for line in data.split('\n'):
    140             m = re.match(re_pattern, line)
    141             if m is None: # not a valid trace line (e.g. comment)
    142                 continue
    143             m_dict = m.groupdict() # named group of RE match (m_dict['task']...)
    144             pipe, fb, state = self.exynos_parser(m_dict['detail'])
    145             timestamp = float(m_dict['timestamp'])
    146             self.end_last_fb_state(pipe, fb, timestamp)
    147             self.start_new_fb_state(pipe, fb, state, timestamp)
    148 
    149         self.calc_stat()
    150 
    151     def exynos_parser(self, detail):
    152         """Parse exynos event's detail.
    153 
    154         @param detail: a string like 'pipe=0, fb=25, state=wait_kds'
    155         @return: tuple of (pipe, fb, state), pipe and fb in int,
    156                  state in string
    157         """
    158         re_pattern = (
    159             'pipe=(?P<pipe>\d+), ' + # pipe
    160             'fb=(?P<fb>\d+), ' + # fb
    161             'state=(?P<state>.*)') # state
    162         if re.match(re_pattern, detail) is None:
    163             logging.debug('==fail==' + re_pattern + ', ' + detail)
    164         m_dict = re.match(re_pattern, detail).groupdict()
    165         return int(m_dict['pipe']), int(m_dict['fb']), m_dict['state']
    166 
    167     def end_last_fb_state(self, pipe, fb, end_time):
    168         """End the currently opened state of the specified frame buffer
    169 
    170         @param pipe: the pipe id
    171         @param fb: the frame buffer id
    172         @param end_time: timestamp when the state ends
    173         """
    174         self.get_frame_buffer(pipe, fb).end_state(end_time)
    175 
    176     def start_new_fb_state(self, pipe, fb, state, start_time):
    177         """Start the specified state on the specified frame buffer
    178 
    179         @param pipe: the pipe id
    180         @param fb: the frame buffer id
    181         @param state: which state to start
    182         @param start_time: timestamp when the state starts
    183         """
    184         self.get_frame_buffer(pipe, fb).start_state(state, start_time)
    185 
    186     def calc_stat(self):
    187         """Calculate the statistics of state duration of all frame buffers"""
    188         for fb in self.frame_buffers.values():
    189             fb.calc_state_avg_stdev()
    190 
    191     def frame_buffer_unique_hash(self, pipe, fb):
    192         """A hash function that returns the unique identifier of a frame buffer.
    193            The hash is a string that is unique in the sense of pipe and fb.
    194 
    195         @param pipe: the pipe id
    196         @param fb: the frame buffer id
    197         @return: a unique hash string, like "pipe:0,fb:25"
    198         """
    199         return "pipe:%d,fb:%d" % (pipe, fb)
    200 
    201     def get_frame_buffer(self, pipe, fb):
    202         """Return the frame buffer with specified pipe and fb.
    203            If the frame buffer does not exist, create one and return it.
    204 
    205         @param pipe: the pipe id
    206         @param fb: the frame buffer id
    207         @return: the frame buffer specified by pipe and fb
    208         """
    209         key = self.frame_buffer_unique_hash(pipe, fb)
    210         if key not in self.frame_buffers:
    211             self.frame_buffers[key] = FrameBuffer(pipe, fb)
    212         return self.frame_buffers[key]
    213 
    214 
    215 class FrameBuffer():
    216     """Represents a frame buffer, which holds all its states"""
    217     def __init__(self, pipe, fb):
    218         """Initialize the frame buffer.
    219 
    220         @param pipe: the pipe id of the frame buffer
    221         @param fb: the fb id of the frame buffer
    222         """
    223         self.pipe = pipe
    224         self.fb = fb
    225         self.states = {}
    226         self.active_state = None # currently active state (to be ended later)
    227 
    228     def start_state(self, state_name, start_time):
    229         """Start the specified state
    230 
    231         @param state_name: name of the state to be started
    232         @param start_time: timestamp when the state starts
    233         """
    234         if state_name not in self.states:
    235             self.states[state_name] = State(state_name)
    236         self.states[state_name].start(start_time)
    237         self.active_state = state_name
    238 
    239     def end_state(self, end_time):
    240         """End the specified state, in which the duration will be stored
    241 
    242         @param end_time: timestamp when the state ends
    243         """
    244         if self.active_state is not None:
    245             self.states[self.active_state].end(end_time)
    246             self.active_state = None
    247 
    248     def calc_state_avg_stdev(self):
    249         """Call all states to compute its own average and standard deviation"""
    250         logging.debug("====pipe:%d, fb:%d====", self.pipe, self.fb)
    251         for s in self.states.values():
    252             s.calc_avg_stdev()
    253 
    254 
    255 class State():
    256     """Hold the data of a specific state (e.g. wait_kds, wait_apply, ...)"""
    257     def __init__(self, state_name):
    258         """Initialize data
    259 
    260         @param state_name: name of this state
    261         """
    262         self.state_name = state_name
    263         self.last_start_time = None
    264         self.duration_data = []
    265         self.avg = None
    266         self.stdev = None
    267 
    268     def start(self, start_time):
    269         """Mark this state as started by saving the start time
    270 
    271         @param start_time: timestamp when the state starts
    272         """
    273         self.last_start_time = start_time
    274 
    275     def end(self, end_time):
    276         """Save the state's duration and mark this state as ended
    277 
    278         @param end_time: timestamp when the state ends
    279         """
    280         if self.last_start_time is not None:
    281             self.duration_data.append(end_time - self.last_start_time)
    282             self.last_start_time = None
    283 
    284     def calc_avg_stdev(self):
    285         """Calculate the average and standard deviation of all duration data"""
    286         self.avg = numpy.mean(self.duration_data)
    287         self.stdev = numpy.std(self.duration_data)
    288