SQUAD : Adding a memory footprint gathering script launched from ci

This patch sets up the TFM memory footprint tracking using SQUAD and CI.
A python script is launched by the ci build-config pipeline if it has
been started by a nightly build. This python script sends bl2.axf and
tfm_s.axf sizes to a SQUAD interface.

Signed-off-by: Hugo L'Hostis <hugo.lhostis@arm.com>
Change-Id: I17f8efffb3f4398a2991b8c9e387d712b981403f
diff --git a/memory_footprint.py b/memory_footprint.py
new file mode 100644
index 0000000..53644cb
--- /dev/null
+++ b/memory_footprint.py
@@ -0,0 +1,189 @@
+#!/usr/bin/env python
+
+#memory_footprint.py : Script for sending memory footprint data from the TFM CI
+#to a SQUAD web interface
+#
+#Copyright (c) 2020-2021, Arm Limited. All rights reserved.
+#
+#SPDX-License-Identifier: BSD-3-Clause
+
+
+import argparse
+import os
+import re
+import sys
+import json
+import requests
+import subprocess
+import configs
+from tfm_ci_pylib import utils
+
+# Arguments/parameters given by CI
+PATH_TO_TFM             = sys.argv[1]
+CI_CONFIG               = sys.argv[2]
+REFERENCE_CONFIGS       = sys.argv[3].split(",")
+SQUAD_TOKEN             = sys.argv[4]
+
+# local constant
+SQUAD_BASE_PROJECT_URL  = ("https://qa-reports.linaro.org/api/submit/tf/tf-m/")
+
+# This function uses arm_non_eabi_size to get the sizes of a file
+#  in the build directory of tfm
+def get_file_size(filename):
+    f_path = os.path.join(PATH_TO_TFM, "build", "bin", filename)
+    if os.path.exists(f_path) :
+        bl2_sizes = utils.arm_non_eabi_size(f_path)[0]
+        return bl2_sizes
+    else :
+        print(f_path + "Not found")
+        return -1
+
+# This function creates a json file containing all the data about
+#  memory footprint and sends this data to SQUAD
+def send_file_size(change_id, config_name, bl2_sizes, tfms_sizes):
+    url = SQUAD_BASE_PROJECT_URL + change_id + '/' + config_name
+
+    try:
+        metrics = json.dumps({ "bl2_size"  : bl2_sizes["dec"],
+                               "bl2_data"  : bl2_sizes["data"],
+                               "bl2_bss"   : bl2_sizes["bss"],
+                               "tfms_size" : tfms_sizes["dec"],
+                               "tfms_data" : tfms_sizes["data"],
+                               "tfms_bss"  : tfms_sizes["bss"]})
+    except:
+        return -1
+
+    headers = {"Auth-Token": SQUAD_TOKEN}
+    data= {"metrics": metrics}
+
+    try:
+        #Sending the data to SQUAD, 40s timeout
+        result = requests.post(url, headers=headers, data=data, timeout=40)
+    except:
+        return -1
+
+    with open(os.path.join(PATH_TO_TFM,
+                           "..",
+                           "tf-m-ci-scripts",
+                           "Memory_footprint",
+                           "filesize.json"), "w") as F:
+        #Storing the json file
+        F.write(metrics)
+
+    if not result.ok:
+        print(f"Error submitting to qa-reports: {result.reason}: {result.text}")
+        return -1
+    else :
+        print ("POST request sent to project " + config_name )
+        return 0
+
+#Function used to launch the configs.py script and get the printed output
+def get_configs_by_name(config_names):
+    clist = list(configs._builtin_configs.keys())
+    out_cfg = {}
+    for group in clist:
+        build_manager = configs.get_build_manager(group)
+        for _cfg_name in config_names:
+            if _cfg_name in build_manager._tbm_build_cfg.keys():
+                out_cfg[_cfg_name] = build_manager._tbm_build_cfg[_cfg_name]
+    return out_cfg
+
+# This funcion manipulates the format of the config given
+#  as entry to extract the name of the configuration used
+def identify_config():
+    name_config = "Unknown"
+
+    try :
+        cfg = get_configs_by_name([CI_CONFIG])[CI_CONFIG]
+        if (cfg.psa_api and cfg.isolation_level == "1" and
+            not cfg.test_regression and cfg.test_psa_api == "OFF" and
+            cfg.cmake_build_type == "Release" and cfg.with_otp == "off" and
+            cfg.with_bl2 and cfg.with_ns and
+            cfg.profile == "" and cfg.partition_ps == "ON"):
+                name_config = "CoreIPC"
+        elif (not cfg.psa_api and cfg.isolation_level == "1" and
+            not cfg.test_regression and cfg.test_psa_api == "OFF"      and
+            cfg.cmake_build_type == "Release" and cfg.with_otp == "off"  and
+            cfg.with_bl2 and cfg.with_ns and
+            cfg.profile == "" and cfg.partition_ps == "ON"):
+                name_config = "Default"
+        elif (cfg.psa_api and cfg.isolation_level == "2"    and
+            not cfg.test_regression and cfg.test_psa_api == "OFF"     and
+            cfg.cmake_build_type == "Release" and cfg.with_otp == "off" and
+            cfg.with_bl2 and cfg.with_ns and
+            cfg.profile == "" and cfg.partition_ps == "ON"):
+                name_config = "CoreIPCTfmLevel2"
+        elif (not cfg.psa_api and cfg.isolation_level == "1" and
+            not cfg.test_regression and cfg.test_psa_api == "OFF"      and
+            cfg.cmake_build_type == "Release" and cfg.with_otp == "off"  and
+            cfg.with_bl2 and cfg.with_ns and
+            cfg.profile == "profile_small" and cfg.partition_ps == "OFF"):
+                name_config = "DefaultProfileS"
+        elif (cfg.psa_api and cfg.isolation_level == "2" and
+            not cfg.test_regression and cfg.test_psa_api == "OFF"     and
+            cfg.cmake_build_type == "Release" and cfg.with_otp == "off"  and
+            cfg.with_bl2 and cfg.with_ns and
+            cfg.profile == "profile_medium" and cfg.partition_ps == "ON"):
+                name_config = "DefaultProfileM"
+        ret = [cfg.tfm_platform,cfg.toolchain_file, name_config]
+    except:
+        ret = ["Unknown", "Unknown", "Unknown"]
+    return ret
+
+# Function based on get_local_git_info() from utils, getting change id for the tfm repo
+def get_change_id(directory):
+    directory = os.path.abspath(directory)
+    cur_dir = os.path.abspath(os.getcwd())
+    cmd = "git log HEAD -n 1  --pretty=format:'%b'"
+
+    os.chdir(directory) # Going to the repo's directory
+
+    git_info_rex = re.compile(r'(?P<body>^[\s\S]*?)((?:Change-Id:\s)'
+                              r'(?P<change_id>.*)\n?)', re.MULTILINE)
+
+    r, e = subprocess.Popen(cmd,
+                            shell=True,
+                            stdout=subprocess.PIPE,
+                            stderr=subprocess.PIPE).communicate()
+
+    if e:
+        print("Error", e)
+        return -1
+    else:
+        try:
+            txt_body = r.decode('ascii')
+        except UnicodeDecodeError as E:
+            txt_body = r.decode('utf-8')
+        result = txt_body.rstrip()
+
+    try:
+        change_id = git_info_rex.search(result).groupdict()["change_id"].strip()
+    except:
+        return -1
+
+    os.chdir(cur_dir) #Going back to the initial directory
+    return change_id
+
+if __name__ == "__main__":
+    for i in range(len(REFERENCE_CONFIGS)):
+        REFERENCE_CONFIGS[i] = REFERENCE_CONFIGS[i].strip().lower()
+    config = identify_config()
+    if (config[2].lower() in REFERENCE_CONFIGS
+        and config[0] == "mps2/an521"
+        and config[1] == "toolchain_GNUARM.cmake"):
+        # Pushing data for AN521 and GNUARM
+        print("Configuration " + config[2] + " is a reference")
+        try :
+            change_id = get_change_id(PATH_TO_TFM)
+        except :
+            change_id = -1
+        bl2_sizes = get_file_size("bl2.axf")
+        tfms_sizes = get_file_size("tfm_s.axf")
+        if (bl2_sizes != -1 and change_id != -1) :
+            send_file_size(change_id, config[2], bl2_sizes, tfms_sizes)
+        else :
+            #Directory or file weren't found
+            if change_id == -1 :
+                print("Error : trusted-firmware-m repo not found")
+            else :
+                print("Error : file not found")