Add parsing and uploading of JMI data to perf dashboard

Pull the JMI log file from the CFM device and parse its data to upload
to Chrome Perf Dashboard.

BUG=chromium:631858
TEST=Tested on a local setup.

Change-Id: Idb0228f283df6e89f2ecc8a2804d00ab26c2fe4e
Reviewed-on: https://chromium-review.googlesource.com/432046
Reviewed-by: Kalin Stoyanov <kalin@chromium.org>
Reviewed-by: Harpreet Grewal <harpreet@chromium.org>
Commit-Queue: Harpreet Grewal <harpreet@chromium.org>
Tested-by: Harpreet Grewal <harpreet@chromium.org>
Trybot-Ready: Harpreet Grewal <harpreet@chromium.org>
diff --git a/server/cros/cfm_jmidata_v3_helper.py b/server/cros/cfm_jmidata_v3_helper.py
new file mode 100644
index 0000000..823fb35
--- /dev/null
+++ b/server/cros/cfm_jmidata_v3_helper.py
@@ -0,0 +1,234 @@
+# Copyright (c) 2017 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import enum, json
+
+from autotest_lib.server.cros import cfm_jmidata_helper_base
+
+# Start index in the JSON object that contains Audio/Video streams related info.
+AV_INDEX = 4
+
+SSRC = u'ssrc'
+GLOBAL = u'global'
+
+AUDIO_INPUT = u'audioInputLevel'
+AUDIO_OUTPUT = u'audioOutputLevel'
+BYTES_RECEIVED = u'bytesReceived'
+BYTES_SENT = u'bytesSent'
+ADAPTATION_CHANGES = u'googAdaptationChanges'
+AVERAGE_ENCODE_TIME = u'googAvgEncodeMs'
+BANDWIDTH_LIMITED_RESOLUTION = u'googBandwidthLimitedResolution'
+CPU_LIMITED_RESOLUTION = u'googCpuLimitedResolution'
+VIDEO_ENCODE_CPU_USAGE = u'googEncodeUsagePercent'
+VIDEO_RECEIVED_FRAME_HEIGHT = u'googFrameHeightReceived'
+VIDEO_RECEIVED_FRAME_WIDTH = u'googFrameWidthReceived'
+FRAMERATE_OUTGOING = u'googFrameRateInput'
+FRAMERATE_RECEIVED = u'googFrameRateReceived'
+FRAMERATE_SENT = u'googFrameRateSent'
+FRAMERATE_DECODED = u'googFrameRateDecoded'
+FRAMERATE_OUTPUT = u'googFrameRateOutput'
+FRAME_WIDTH_SENT = u'googFrameWidthSent'
+FRAME_HEIGHT_SENT = u'googFrameHeightSent'
+FRAMES_DECODED = u'framesDecoded'
+FRAMES_ENCODED = u'framesEncoded'
+VIDEO_PACKETS_LOST = u'packetsLost'
+VIDEO_PACKETS_SENT = u'packetsSent'
+
+
+class CpuUsageType(enum.Enum):
+    BROWSER_CPU = u'browserCpuUsage'
+    GPU_CPU = u'gpuCpuUsage'
+    NUM_PROCESSORS = u'numOfProcessors'
+    NACL_EFFECTS_CPU = u'pluginCpuUsage'
+    RENDERER_CPU = u'tabCpuUsage'
+    TOTAL_CPU = u'totalCpuUsage'
+
+
+class JMIDataV3Helper(cfm_jmidata_helper_base.JMIDataHelperBase):
+    """Helper class for JMI data v3 parsing. This class helps in extracting
+    relevant JMI data from javascript log.
+
+    The class takes javascript file as input and extracts jmidata elements from
+    the file that is internally stored as a list. Whenever we need to extract
+    data i.e. audio received bytes we call a method such as
+    getAudioReceivedDataList. This method converts each string element in the
+    internal list to a json object and retrieves the relevant info from it which
+    is then stored and returned as a list.
+    """
+
+    def __init__(self, log_file_content):
+        super(JMIDataV3Helper, self).__init__(log_file_content, 'jmidatav3')
+
+    def _ExtractAllJMIDataPointsWithKey(self, jmi_type, is_audio, key):
+        """Extracts all values from the data points with the given key."""
+        data_list = []
+        for jmi_data_point in self._jmi_list:
+            json_arr = json.loads(jmi_data_point)
+            for i in range(AV_INDEX, len(json_arr)):
+                if json_arr[i] and jmi_type in json_arr[i]:
+                    jmi_obj = json_arr[i][jmi_type]
+                    #print jmi_obj
+                    this_is_audio = (AUDIO_INPUT in jmi_obj or
+                                     AUDIO_OUTPUT in jmi_obj)
+                    if this_is_audio == is_audio and key in jmi_obj:
+                        if (not isinstance(jmi_obj[key], int) and
+                                (jmi_obj[key] == 'false' or
+                                 jmi_obj[key] == 'true')):
+                            jmi_obj[key] = 1 if jmi_obj[key] == 'true' else 0
+                        data_list.append(jmi_obj[key])
+        return data_list
+
+    def GetAudioReceivedBytesList(self):
+        return self._ExtractAllJMIDataPointsWithKey(
+                jmi_type=SSRC, is_audio=True, key=BYTES_RECEIVED)
+
+    def GetAudioSentBytesList(self):
+        return self._ExtractAllJMIDataPointsWithKey(
+                jmi_type=SSRC, is_audio=True, key=BYTES_SENT)
+
+    def GetAudioReceivedEnergyList(self):
+        return self._ExtractAllJMIDataPointsWithKey(
+                jmi_type=SSRC, is_audio=True, key=AUDIO_OUTPUT)
+
+    def GetAudioSentEnergyList(self):
+        return self._ExtractAllJMIDataPointsWithKey(
+                jmi_type=SSRC, is_audio=True, key=AUDIO_INPUT)
+
+    def GetVideoSentBytesList(self):
+        return self._ExtractAllJMIDataPointsWithKey(
+                jmi_type=SSRC, is_audio=False, key=BYTES_SENT)
+
+    def GetVideoReceivedBytesList(self):
+        return self._ExtractAllJMIDataPointsWithKey(
+                jmi_type=SSRC, is_audio=False, key=BYTES_RECEIVED)
+
+    def GetVideoIncomingFramerateReceivedList(self):
+        return self._ExtractAllJMIDataPointsWithKey(
+                jmi_type=SSRC, is_audio=False, key=FRAMERATE_RECEIVED)
+
+    def GetVideoOutgoingFramerateSentList(self):
+        return self._ExtractAllJMIDataPointsWithKey(
+                jmi_type=SSRC, is_audio=False, key=FRAMERATE_SENT)
+
+    def GetVideoIncomingFramerateDecodedList(self):
+        return self._ExtractAllJMIDataPointsWithKey(
+                jmi_type=SSRC, is_audio=False, key=FRAMERATE_DECODED)
+
+    def GetVideoIncomingFramerateList(self):
+        return self._ExtractAllJMIDataPointsWithKey(
+                jmi_type=SSRC, is_audio=False, key=FRAMERATE_OUTPUT)
+
+    def GetVideoSentFrameWidthList(self):
+        return self._ExtractAllJMIDataPointsWithKey(
+                jmi_type=SSRC, is_audio=False, key=FRAME_WIDTH_SENT)
+
+    def GetVideoSentFrameHeightList(self):
+        return self._ExtractAllJMIDataPointsWithKey(
+                jmi_type=SSRC, is_audio=False, key=FRAME_HEIGHT_SENT)
+
+    def GetCPULimitedResolutionList(self):
+        return self._ExtractAllJMIDataPointsWithKey(
+                jmi_type=SSRC, is_audio=False, key=CPU_LIMITED_RESOLUTION)
+
+    def GetVideoPacketsSentList(self):
+        return self._ExtractAllJMIDataPointsWithKey(
+                jmi_type=SSRC, is_audio=False, key=VIDEO_PACKETS_SENT)
+
+    def GetVideoPacketsLostList(self):
+        return self._ExtractAllJMIDataPointsWithKey(
+                jmi_type=SSRC, is_audio=False, key=VIDEO_PACKETS_LOST)
+
+    def GetVideoIncomingFramesDecodedList(self):
+        return self._ExtractAllJMIDataPointsWithKey(
+                jmi_type=SSRC, is_audio=False, key=FRAMES_DECODED)
+
+    def GetVideoOutgoingFramesEncodedList(self):
+        return self._ExtractAllJMIDataPointsWithKey(
+                jmi_type=SSRC, is_audio=False, key=FRAMES_ENCODED)
+
+    def GetVideoAdaptationChangeList(self):
+        return self._ExtractAllJMIDataPointsWithKey(
+                jmi_type=SSRC, is_audio=False, key=ADAPTATION_CHANGES)
+
+    def GetVideoEncodeTimeList(self):
+        return self._ExtractAllJMIDataPointsWithKey(
+                jmi_type=SSRC, is_audio=False, key=AVERAGE_ENCODE_TIME)
+
+    def GetBandwidthLimitedResolutionList(self):
+        return self._ExtractAllJMIDataPointsWithKey(
+                jmi_type=SSRC, is_audio=False, key=BANDWIDTH_LIMITED_RESOLUTION)
+
+    def GetVideoReceivedFrameHeightList(self):
+        return self._ExtractAllJMIDataPointsWithKey(
+                jmi_type=SSRC, is_audio=False, key=VIDEO_RECEIVED_FRAME_HEIGHT)
+
+    def GetVideoOutgoingFramerateInputList(self):
+        return self._ExtractAllJMIDataPointsWithKey(
+                jmi_type=SSRC, is_audio=False, key=FRAMERATE_OUTGOING)
+
+    def GetVideoReceivedFrameWidthList(self):
+        return self._ExtractAllJMIDataPointsWithKey(
+                jmi_type=SSRC, is_audio=False, key=VIDEO_RECEIVED_FRAME_WIDTH)
+
+    def GetVideoEncodeCpuUsagePercentList(self):
+        return self._ExtractAllJMIDataPointsWithKey(
+                jmi_type=SSRC, is_audio=False, key=VIDEO_ENCODE_CPU_USAGE)
+
+    def GetNumberOfActiveIncomingVideoStreams(self):
+        """Retrieve number of active incoming video streams."""
+        if not self._jmi_list:
+            # JMI data hasn't started populating yet.
+            return 0
+
+        num_video_streams = []
+
+        # If JMI data has started getting populated and has video stream data.
+        for jmi_data_point in self._jmi_list:
+            json_arr = json.loads(jmi_data_point)
+            video_streams = 0
+            for i in range(AV_INDEX, len(json_arr)):
+                if json_arr[i] and SSRC in json_arr[i]:
+                    ssrc_obj = json_arr[i][SSRC]
+                    is_audio = ('audioInputLevel' in ssrc_obj or
+                            'audioOutputLevel' in ssrc_obj)
+                    is_incoming = 'bytesReceived' in ssrc_obj
+                    frame_rate_received = 'googFrameRateReceived' in ssrc_obj
+                    if ssrc_obj['mediaType'] == 'video' and \
+                            frame_rate_received:
+                        frame_rate = ssrc_obj['googFrameRateReceived']
+                        if (is_incoming and not is_audio) and \
+                                frame_rate != 0:
+                            video_streams += 1
+            num_video_streams.append(video_streams)
+        return num_video_streams
+
+    def GetCpuUsageList(self, cpu_type):
+        """Retrieves cpu usage data from JMI data.
+
+        @param cpu_type: Enum of type CpuUsageType.
+        @returns List containing CPU usage data.
+        """
+        data_list = []
+        for jmi_data_point in self._jmi_list:
+            json_arr = json.loads(jmi_data_point)
+            for i in range(AV_INDEX, len(json_arr)):
+                if json_arr[i] and GLOBAL in json_arr[i]:
+                    global_obj = json_arr[i][GLOBAL]
+                    # Some values in JMIDataV3 are set to 'null'.
+                    if cpu_type.value == u'numOfProcessors':
+                        return global_obj[cpu_type.value]
+                    elif (cpu_type.value in global_obj and
+                            self.IsFloat(global_obj[cpu_type.value])):
+                        data_list.append(float(global_obj[cpu_type.value]))
+        return data_list
+
+    def GetCpuUsageType(self):
+        return CpuUsageType
+
+    def IsFloat(self, value):
+        try:
+            float(value)
+            return True
+        except TypeError:
+            return False