diff --git a/model-optimizer/automation/package_BOM.txt b/model-optimizer/automation/package_BOM.txt index 4ab9ebec530..cf76f4e150d 100644 --- a/model-optimizer/automation/package_BOM.txt +++ b/model-optimizer/automation/package_BOM.txt @@ -980,6 +980,7 @@ mo/utils/find_inputs.py mo/utils/get_ov_update_message.py mo/utils/graph.py mo/utils/guess_framework.py +mo/utils/ie_version.py mo/utils/import_extensions.py mo/utils/ir_engine/__init__.py mo/utils/ir_engine/compare_graphs.py diff --git a/model-optimizer/mo/main.py b/model-optimizer/mo/main.py index c0c7d56dfab..64a73380294 100644 --- a/model-optimizer/mo/main.py +++ b/model-optimizer/mo/main.py @@ -19,6 +19,7 @@ import datetime import logging as log import os import sys +import platform import subprocess import traceback from collections import OrderedDict @@ -35,13 +36,13 @@ from mo.utils import import_extensions from mo.utils.cli_parser import get_placeholder_shapes, get_tuple_values, get_model_name, \ get_common_cli_options, get_caffe_cli_options, get_tf_cli_options, get_mxnet_cli_options, get_kaldi_cli_options, \ get_onnx_cli_options, get_mean_scale_dictionary, parse_tuple_pairs, get_freeze_placeholder_values, get_meta_info -from mo.utils.error import Error, FrameworkError +from mo.utils.error import Error, FrameworkError, classify_error_type from mo.utils.get_ov_update_message import get_ov_update_message from mo.utils.guess_framework import deduce_framework_by_namespace from mo.utils.logger import init_logger from mo.utils.model_analysis import AnalysisResults from mo.utils.utils import refer_to_faq_msg -from mo.utils.version import get_version +from mo.utils.version import get_version, get_simplified_mo_version, get_simplified_ie_version from mo.utils.versions_checker import check_requirements from mo.utils.find_ie_version import find_ie_version @@ -158,7 +159,6 @@ def prepare_ir(argv: argparse.Namespace): # If the IE was not found, it will not print the MO version, so we have to print it manually print("{}: \t{}".format("Model Optimizer version", get_version())) except Exception as e: - # TODO: send exception message pass ret_code = check_requirements(framework=argv.framework) @@ -270,19 +270,30 @@ def emit_ir(graph: Graph, argv: argparse.Namespace): output_dir = argv.output_dir if argv.output_dir != '.' else os.getcwd() orig_model_name = os.path.normpath(os.path.join(output_dir, argv.model_name)) + return_code = "not executed" # This try-except is additional reinsurance that the IE # dependency search does not break the MO pipeline try: if find_ie_version(silent=True): path_to_offline_transformations = os.path.join(os.path.realpath(os.path.dirname(__file__)), 'back', 'offline_transformations.py') - status = subprocess.run([sys.executable, path_to_offline_transformations, orig_model_name], env=os.environ, timeout=100) - if status.returncode != 0 and not argv.silent: - print("[ WARNING ] offline_transformations return code {}".format(status.returncode)) + status = subprocess.run([sys.executable, path_to_offline_transformations, orig_model_name], env=os.environ, timeout=10) + return_code = status.returncode + if return_code != 0 and not argv.silent: + print("[ WARNING ] offline_transformations return code {}".format(return_code)) except Exception as e: - # TODO: send error message pass + message = str(dict({ + "platform": platform.system(), + "mo_version": get_simplified_mo_version(), + "ie_version": get_simplified_ie_version(env=os.environ), + "python_version": sys.version, + "return_code": return_code + })) + t = tm.Telemetry() + t.send_event('mo', 'offline_transformations_status', message) + print('[ SUCCESS ] Generated IR version {} model.'.format(get_ir_version(argv))) print('[ SUCCESS ] XML file: {}.xml'.format(orig_model_name)) print('[ SUCCESS ] BIN file: {}.bin'.format(orig_model_name)) @@ -316,9 +327,9 @@ def driver(argv: argparse.Namespace): def main(cli_parser: argparse.ArgumentParser, framework: str): - telemetry = tm.Telemetry(app_name='Model Optimizer', app_version=get_version()) + telemetry = tm.Telemetry(app_name='Model Optimizer', app_version=get_simplified_mo_version()) telemetry.start_session() - telemetry.send_event('mo', 'version', get_version()) + telemetry.send_event('mo', 'version', get_simplified_mo_version()) try: # Initialize logger with 'ERROR' as default level to be able to form nice messages # before arg parser deliver log_level requested by user diff --git a/model-optimizer/mo/utils/check_ie_bindings.py b/model-optimizer/mo/utils/check_ie_bindings.py index 68d1c235b7b..41bbf6ee81e 100644 --- a/model-optimizer/mo/utils/check_ie_bindings.py +++ b/model-optimizer/mo/utils/check_ie_bindings.py @@ -18,15 +18,30 @@ import os import re +import sys import argparse +import platform + try: - # needed by find_ie_version.py which call check_ie_bindings.py as python script - import version # pylint: disable=import-error -except ImportError: - import mo.utils.version + import mo + execution_type = "mo" +except ModuleNotFoundError: + mo_root_path = os.path.normpath(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)) + sys.path.insert(0, mo_root_path) + execution_type = "install_prerequisites.{}".format("bat" if platform.system() == "Windows" else "sh") -from extract_release_version import extract_release_version +import mo.utils.version as v +import telemetry.telemetry as tm +from mo.utils.error import classify_error_type + + +def send_telemetry(mo_version: str, message: str, event_type: str): + t = tm.Telemetry(app_name='Model Optimizer', app_version=mo_version) + t.start_session() + t.send_event(execution_type, event_type, message) + t.end_session() + t.force_shutdown(1.0) def import_core_modules(silent: bool, path_to_module: str): @@ -36,26 +51,42 @@ def import_core_modules(silent: bool, path_to_module: str): import openvino # pylint: disable=import-error + if silent: + return True + ie_version = str(get_version()) - mo_version = str(version.get_version()) # pylint: disable=no-member + mo_version = str(v.get_version()) # pylint: disable=no-member - if not silent: - print("\t- {}: \t{}".format("Inference Engine found in", os.path.dirname(openvino.__file__))) - print("{}: \t{}".format("Inference Engine version", ie_version)) - print("{}: \t {}".format("Model Optimizer version", mo_version)) + print("\t- {}: \t{}".format("Inference Engine found in", os.path.dirname(openvino.__file__))) + print("{}: \t{}".format("Inference Engine version", ie_version)) + print("{}: \t {}".format("Model Optimizer version", mo_version)) + versions_mismatch = False # MO and IE version have a small difference in the beginning of version because # IE version also includes API version. For example: # Inference Engine version: 2.1.custom_HEAD_4c8eae0ee2d403f8f5ae15b2c9ad19cfa5a9e1f9 # Model Optimizer version: custom_HEAD_4c8eae0ee2d403f8f5ae15b2c9ad19cfa5a9e1f9 # So to match this versions we skip IE API version. if not re.match(r"^([0-9]+).([0-9]+).{}$".format(mo_version), ie_version): - extracted_release_version = extract_release_version() - is_custom_mo_version = extracted_release_version == (None, None) - if not silent: - print("[ WARNING ] Model Optimizer and Inference Engine versions do no match.") - print("[ WARNING ] Consider building the Inference Engine Python API from sources or reinstall OpenVINO (TM) toolkit using \"pip install openvino{}\" {}".format( - "", "(may be incompatible with the current Model Optimizer version)" if is_custom_mo_version else "=={}.{}".format(*extracted_release_version), "")) + versions_mismatch = True + extracted_mo_release_version = v.extract_release_version(mo_version) + mo_is_custom = extracted_mo_release_version == (None, None) + + print("[ WARNING ] Model Optimizer and Inference Engine versions do no match.") + print("[ WARNING ] Consider building the Inference Engine Python API from sources or reinstall OpenVINO (TM) toolkit using", end=" ") + if mo_is_custom: + print("\"pip install openvino\" (may be incompatible with the current Model Optimizer version)") + else: + print("\"pip install openvino=={}.{}\"".format(*extracted_mo_release_version)) + + simplified_mo_version = v.get_simplified_mo_version() + message = str(dict({ + "platform": platform.system(), + "mo_version": simplified_mo_version, + "ie_version": v.get_simplified_ie_version(version=ie_version), + "versions_mismatch": versions_mismatch, + })) + send_telemetry(simplified_mo_version, message, 'ie_version_check') return True except Exception as e: @@ -63,6 +94,18 @@ def import_core_modules(silent: bool, path_to_module: str): if "No module named 'openvino'" not in str(e) and not silent: print("[ WARNING ] Failed to import Inference Engine Python API in: {}".format(path_to_module)) print("[ WARNING ] {}".format(e)) + + # Send telemetry message about warning + simplified_mo_version = v.get_simplified_mo_version() + message = str(dict({ + "platform": platform.system(), + "mo_version": simplified_mo_version, + "ie_version": v.get_simplified_ie_version(env=os.environ), + "python_version": sys.version, + "error_type": classify_error_type(e), + })) + send_telemetry(simplified_mo_version, message, 'ie_import_failed') + return False diff --git a/model-optimizer/mo/utils/error.py b/model-optimizer/mo/utils/error.py index 95f4e64b422..85c5a4da266 100644 --- a/model-optimizer/mo/utils/error.py +++ b/model-optimizer/mo/utils/error.py @@ -1,5 +1,5 @@ """ - Copyright (C) 2018-2020 Intel Corporation + Copyright (C) 2018-2021 Intel Corporation Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13,6 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. """ +import re class BasicError(Exception): @@ -44,3 +45,17 @@ class InternalError(BasicError): """ Not user-friendly error: user cannot fix it and it points to the bug inside MO. """ pass + +def classify_error_type(e): + patterns = [ + # Example: No module named 'openvino.offline_transformations.offline_transformations_api' + r"No module named \'\S+\'", + # Example: cannot import name 'IECore' from 'openvino.inference_engine' (unknown location) + r"cannot import name \'\S+\'", + ] + error_message = str(e) + for pattern in patterns: + m = re.search(pattern, error_message) + if m: + return m.group(0) + return "undefined" diff --git a/model-optimizer/mo/utils/error_test.py b/model-optimizer/mo/utils/error_test.py new file mode 100644 index 00000000000..98e7adc16fe --- /dev/null +++ b/model-optimizer/mo/utils/error_test.py @@ -0,0 +1,37 @@ +""" + Copyright (C) 2021 Intel Corporation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +""" + +import unittest + +from mo.utils.error import classify_error_type + + +class TestingErrorClassifier(unittest.TestCase): + def test_no_module(self): + message = "No module named 'openvino.offline_transformations.offline_transformations_api'" + self.assertEqual(classify_error_type(message), message) + + def test_no_module_neg(self): + message = "No module 'openvino'" + self.assertEqual(classify_error_type(message), "undefined") + + def test_cannot_import_name(self): + message = "cannot import name 'IECore' from 'openvino.inference_engine' (unknown location)" + self.assertEqual(classify_error_type(message), "cannot import name 'IECore'") + + def test_cannot_import_name_neg(self): + message = "import name 'IECore' from 'openvino.inference_engine' (unknown location)" + self.assertEqual(classify_error_type(message), "undefined") diff --git a/model-optimizer/mo/utils/extract_release_version.py b/model-optimizer/mo/utils/extract_release_version.py index a3f7410925a..ee35c0e129b 100644 --- a/model-optimizer/mo/utils/extract_release_version.py +++ b/model-optimizer/mo/utils/extract_release_version.py @@ -13,30 +13,13 @@ See the License for the specific language governing permissions and limitations under the License. """ -import re try: # needed by install_prerequisites which call extract_release_version as python script - from version import get_version + from version import extract_release_version, get_version except ImportError: - from mo.utils.version import get_version - - -def extract_release_version(): - version = get_version() - patterns = [ - # captures release version set by CI for example: '2021.1.0-1028-55e4d5673a8' - r"^([0-9]+).([0-9]+)*", - # captures release version generated by MO from release branch, for example: 'custom_releases/2021/1_55e4d567' - r"_releases/([0-9]+)/([0-9]+)_*" - ] - - for pattern in patterns: - m = re.search(pattern, version) - if m and len(m.groups()) == 2: - return m.group(1), m.group(2) - return None, None + from mo.utils.version import extract_release_version, get_version if __name__ == "__main__": - print("{}.{}".format(*extract_release_version())) \ No newline at end of file + print("{}.{}".format(*extract_release_version(get_version()))) diff --git a/model-optimizer/mo/utils/ie_version.py b/model-optimizer/mo/utils/ie_version.py new file mode 100644 index 00000000000..d8534ddea1c --- /dev/null +++ b/model-optimizer/mo/utils/ie_version.py @@ -0,0 +1,27 @@ +""" + Copyright (C) 2021 Intel Corporation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +""" + + +def get_ie_version(): + try: + from openvino.inference_engine import get_version # pylint: disable=import-error + return get_version() + except: + return None + + +if __name__ == "__main__": + print(get_ie_version()) diff --git a/model-optimizer/mo/utils/version.py b/model-optimizer/mo/utils/version.py index 84ad6f4526a..b0167446b83 100644 --- a/model-optimizer/mo/utils/version.py +++ b/model-optimizer/mo/utils/version.py @@ -14,6 +14,8 @@ limitations under the License. """ import os +import re +import sys import subprocess @@ -41,3 +43,41 @@ def get_version(): with open(version_txt) as f: version = f.readline().replace('\n', '') return version + + +def extract_release_version(version: str): + patterns = [ + # captures release version set by CI for example: '2021.1.0-1028-55e4d5673a8' + r"^([0-9]+).([0-9]+)*", + # captures release version generated by MO from release branch, for example: 'custom_releases/2021/1_55e4d567' + r"_releases/([0-9]+)/([0-9]+)_*" + ] + + for pattern in patterns: + m = re.search(pattern, version) + if m and len(m.groups()) == 2: + return m.group(1), m.group(2) + return None, None + + +def simplify_version(version: str): + release_version = extract_release_version(version) + if release_version == (None, None): + return "custom" + return "{}.{}".format(*release_version) + + +def get_simplified_mo_version(): + return simplify_version(get_version()) + + +def get_simplified_ie_version(env=dict(), version=None): + if version is None: + try: + version = subprocess.check_output([sys.executable, os.path.join(os.path.dirname(__file__), "ie_version.py")], timeout=2, env=env).strip().decode() + except: + return "ie not found" + m = re.match(r"^([0-9]+).([0-9]+).(.*)", version) + if m and len(m.groups()) == 3: + return simplify_version(m.group(3)) + return "custom" diff --git a/model-optimizer/mo/utils/version_test.py b/model-optimizer/mo/utils/version_test.py index b9a74e087d1..877abcdf698 100644 --- a/model-optimizer/mo/utils/version_test.py +++ b/model-optimizer/mo/utils/version_test.py @@ -1,5 +1,5 @@ """ - Copyright (C) 2018-2020 Intel Corporation + Copyright (C) 2018-2021 Intel Corporation Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -19,8 +19,7 @@ import unittest.mock as mock from unittest.mock import mock_open from unittest.mock import patch -from mo.utils.version import get_version -from mo.utils.extract_release_version import extract_release_version +from mo.utils.version import get_version, extract_release_version, get_simplified_ie_version, get_simplified_mo_version class TestingVersion(unittest.TestCase): @@ -36,18 +35,41 @@ class TestingVersion(unittest.TestCase): def test_release_version_extractor(self, mock_open, mock_isfile): mock_isfile.return_value = True mock_open.return_value.__enter__ = mock_open - self.assertEqual(extract_release_version(), ('2021', '1')) + self.assertEqual(extract_release_version(get_version()), ('2021', '1')) @patch('os.path.isfile') @mock.patch('builtins.open', new_callable=mock_open, create=True, read_data='custom_releases/2021/1_55e4d5673a8') def test_custom_release_version_extractor(self, mock_open, mock_isfile): mock_isfile.return_value = True mock_open.return_value.__enter__ = mock_open - self.assertEqual(extract_release_version(), ('2021', '1')) + self.assertEqual(extract_release_version(get_version()), ('2021', '1')) @patch('os.path.isfile') @mock.patch('builtins.open', new_callable=mock_open, create=True, read_data='custom_my_branch/fix_55e4d5673a8') def test_release_version_extractor_neg(self, mock_open, mock_isfile): mock_isfile.return_value = True mock_open.return_value.__enter__ = mock_open - self.assertEqual(extract_release_version(), (None, None)) + self.assertEqual(extract_release_version(get_version()), (None, None)) + + @patch('os.path.isfile') + @mock.patch('builtins.open', new_callable=mock_open, create=True, read_data='custom_releases/2021/1_55e4d5673a8') + def test_simplify_mo_version_release(self, mock_open, mock_isfile): + mock_isfile.return_value = True + mock_open.return_value.__enter__ = mock_open + self.assertEqual(get_simplified_mo_version(), "2021.1") + + @patch('os.path.isfile') + @mock.patch('builtins.open', new_callable=mock_open, create=True, read_data='custom_my_branch/fix_55e4d5673a8') + def test_simplify_mo_version_custom(self, mock_open, mock_isfile): + mock_isfile.return_value = True + mock_open.return_value.__enter__ = mock_open + self.assertEqual(get_simplified_mo_version(), "custom") + + def test_simplify_ie_version_release(self): + self.assertEqual(get_simplified_ie_version(version="2.1.custom_releases/2021/3_4c8eae"), "2021.3") + + def test_simplify_ie_version_release_neg(self): + self.assertEqual(get_simplified_ie_version(version="custom_releases/2021/3_4c8eae"), "custom") + + def test_simplify_ie_version_custom(self): + self.assertEqual(get_simplified_ie_version(version="2.1.custom_my/branch/3_4c8eae"), "custom") \ No newline at end of file diff --git a/model-optimizer/telemetry/telemetry.py b/model-optimizer/telemetry/telemetry.py index f7392aecf8f..4be718c1063 100644 --- a/model-optimizer/telemetry/telemetry.py +++ b/model-optimizer/telemetry/telemetry.py @@ -40,9 +40,7 @@ class Telemetry(metaclass=SingletonMetaClass): if not hasattr(self, 'tid'): self.tid = None if app_name is not None: - # temporary disable telemetry - # self.consent = isip.isip_consent() == isip.ISIPConsent.APPROVED - self.consent = False + self.consent = isip.isip_consent() == isip.ISIPConsent.APPROVED # override default tid if tid is not None: self.tid = tid