Merge pull request !27 from kouzhenzhong/user_definedtags/v0.2.0-alpha
| @@ -0,0 +1,129 @@ | |||
| // Copyright 2020 Huawei Technologies Co., Ltd. | |||
| // | |||
| // 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. | |||
| syntax = "proto2"; | |||
| package mindinsight; | |||
| option cc_enable_arenas = true; | |||
| // Event Protocol buffer, Top define | |||
| message LineageEvent { | |||
| // Timestamp | |||
| required double wall_time = 1; | |||
| // The step of train. | |||
| optional int64 step = 2; | |||
| oneof what { | |||
| // An event file was started, with the specified version. | |||
| // Now version is "Mindspore.Event:1" | |||
| string version = 3; | |||
| // Train lineage | |||
| TrainLineage train_lineage = 4; | |||
| // Evaluation lineage | |||
| EvaluationLineage evaluation_lineage = 5; | |||
| // Dataset graph | |||
| DatasetGraph dataset_graph = 6; | |||
| // User defined info | |||
| UserDefinedInfo user_defined_info = 7; | |||
| } | |||
| } | |||
| // User defined info | |||
| message UserDefinedInfo{ | |||
| // repeated user defined info | |||
| repeated UserDefinedInfo user_info = 1; | |||
| // key/value which contains both scalar and dict | |||
| map<string, UserDefinedInfo> map_dict = 2; | |||
| map<string, int32> map_int32 = 3; | |||
| map<string, string> map_str = 4; | |||
| map<string, double> map_double = 5; | |||
| } | |||
| // TrainLineage records infos of a train. | |||
| message TrainLineage{ | |||
| message HyperParameters{ | |||
| optional string optimizer = 1; | |||
| optional float learning_rate = 2; | |||
| optional string loss_function = 3; | |||
| optional int32 epoch = 4; | |||
| optional string parallel_mode = 5; | |||
| optional int32 device_num = 6; | |||
| optional int32 batch_size = 8; | |||
| } | |||
| message TrainDataset{ | |||
| optional string train_dataset_path = 1; | |||
| optional int32 train_dataset_size = 2; | |||
| } | |||
| message Algorithm{ | |||
| optional string network = 1; | |||
| optional float loss = 2; | |||
| } | |||
| message Model{ | |||
| optional string path = 3; | |||
| optional int64 size = 4; | |||
| } | |||
| optional HyperParameters hyper_parameters = 1; | |||
| optional TrainDataset train_dataset = 2; | |||
| optional Algorithm algorithm = 3; | |||
| optional Model model = 4; | |||
| } | |||
| //EvalLineage records infos of evaluation. | |||
| message EvaluationLineage{ | |||
| message ValidDataset{ | |||
| optional string valid_dataset_path = 1; | |||
| optional int32 valid_dataset_size = 2; | |||
| } | |||
| optional string metric = 2; | |||
| optional ValidDataset valid_dataset = 3; | |||
| } | |||
| // DatasetGraph | |||
| message DatasetGraph { | |||
| repeated DatasetGraph children = 1; | |||
| optional OperationParameter parameter = 2; | |||
| repeated Operation operations = 3; | |||
| optional Operation sampler = 4; | |||
| } | |||
| message Operation { | |||
| optional OperationParameter operationParam = 1; | |||
| repeated int32 size = 2; | |||
| repeated float weights = 3; | |||
| } | |||
| message OperationParameter{ | |||
| map<string, string> mapStr = 1; | |||
| map<string, StrList> mapStrList = 2; | |||
| map<string, bool> mapBool = 3; | |||
| map<string, int32> mapInt = 4; | |||
| map<string, double> mapDouble = 5; | |||
| } | |||
| message StrList { | |||
| repeated string strValue = 1; | |||
| } | |||
| @@ -39,60 +39,9 @@ message Event { | |||
| // Summary data | |||
| Summary summary = 5; | |||
| // Train lineage | |||
| TrainLineage train_lineage = 6; | |||
| // Evaluation lineage | |||
| EvaluationLineage evaluation_lineage = 7; | |||
| // dataset graph | |||
| DatasetGraph dataset_graph = 9; | |||
| } | |||
| } | |||
| // TrainLineage records infos of a train. | |||
| message TrainLineage{ | |||
| message HyperParameters{ | |||
| optional string optimizer = 1; | |||
| optional float learning_rate = 2; | |||
| optional string loss_function = 3; | |||
| optional int32 epoch = 4; | |||
| optional string parallel_mode = 5; | |||
| optional int32 device_num = 6; | |||
| optional int32 batch_size = 8; | |||
| } | |||
| message TrainDataset{ | |||
| optional string train_dataset_path = 1; | |||
| optional int32 train_dataset_size = 2; | |||
| } | |||
| message Algorithm{ | |||
| optional string network = 1; | |||
| optional float loss = 2; | |||
| } | |||
| message Model{ | |||
| optional string path = 3; | |||
| optional int64 size = 4; | |||
| } | |||
| optional HyperParameters hyper_parameters = 1; | |||
| optional TrainDataset train_dataset = 2; | |||
| optional Algorithm algorithm = 3; | |||
| optional Model model = 4; | |||
| } | |||
| //EvalLineage records infos of evaluation. | |||
| message EvaluationLineage{ | |||
| message ValidDataset{ | |||
| optional string valid_dataset_path = 1; | |||
| optional int32 valid_dataset_size = 2; | |||
| } | |||
| optional string metric = 2; | |||
| optional ValidDataset valid_dataset = 3; | |||
| } | |||
| // A Summary is a set of named values that be produced regularly during training | |||
| message Summary { | |||
| @@ -127,29 +76,3 @@ message Summary { | |||
| // Set of values for the summary. | |||
| repeated Value value = 1; | |||
| } | |||
| // DatasetGraph | |||
| message DatasetGraph { | |||
| repeated DatasetGraph children = 1; | |||
| optional OperationParameter parameter = 2; | |||
| repeated Operation operations = 3; | |||
| optional Operation sampler = 4; | |||
| } | |||
| message Operation { | |||
| optional OperationParameter operationParam = 1; | |||
| repeated int32 size = 2; | |||
| repeated float weights = 3; | |||
| } | |||
| message OperationParameter{ | |||
| map<string, string> mapStr = 1; | |||
| map<string, StrList> mapStrList = 2; | |||
| map<string, bool> mapBool = 3; | |||
| map<string, int32> mapInt = 4; | |||
| map<string, double> mapDouble = 5; | |||
| } | |||
| message StrList { | |||
| repeated string strValue = 1; | |||
| } | |||
| @@ -112,10 +112,10 @@ def filter_summary_lineage(summary_base_dir, search_condition=None): | |||
| directories generated by training. | |||
| search_condition (dict): The search condition. When filtering and | |||
| sorting, in addition to the following supported fields, fields | |||
| prefixed with `metric_` are also supported. The fields prefixed with | |||
| `metric_` are related to the `metrics` parameter in the training | |||
| prefixed with `metric/` are also supported. The fields prefixed with | |||
| `metric/` are related to the `metrics` parameter in the training | |||
| script. For example, if the key of `metrics` parameter is | |||
| `accuracy`, the field should be `metric_accuracy`. Default: None. | |||
| `accuracy`, the field should be `metric/accuracy`. Default: None. | |||
| - summary_dir (dict): The filter condition of summary directory. | |||
| @@ -187,7 +187,7 @@ def filter_summary_lineage(summary_base_dir, search_condition=None): | |||
| >>> 'ge': 128, | |||
| >>> 'le': 256 | |||
| >>> }, | |||
| >>> 'metric_accuracy': { | |||
| >>> 'metric/accuracy': { | |||
| >>> 'lt': 0.1 | |||
| >>> }, | |||
| >>> 'sorted_name': 'summary_dir', | |||
| @@ -23,7 +23,8 @@ from mindinsight.utils.exceptions import \ | |||
| MindInsightException | |||
| from mindinsight.lineagemgr.common.validator.validate import validate_train_run_context, \ | |||
| validate_eval_run_context, validate_file_path, validate_network, \ | |||
| validate_int_params, validate_summary_record, validate_raise_exception | |||
| validate_int_params, validate_summary_record, validate_raise_exception,\ | |||
| validate_user_defined_info | |||
| from mindinsight.lineagemgr.common.exceptions.error_code import LineageErrors, LineageErrorMsg | |||
| from mindinsight.lineagemgr.common.exceptions.exceptions import LineageParamRunContextError, \ | |||
| LineageGetModelFileError, LineageLogError | |||
| @@ -71,7 +72,7 @@ class TrainLineage(Callback): | |||
| >>> lineagemgr = TrainLineage(summary_record=summary_writer) | |||
| >>> model.train(epoch_num, dataset, callbacks=[model_ckpt, summary_callback, lineagemgr]) | |||
| """ | |||
| def __init__(self, summary_record, raise_exception=False): | |||
| def __init__(self, summary_record, raise_exception=False, user_defined_info=None): | |||
| super(TrainLineage, self).__init__() | |||
| try: | |||
| validate_raise_exception(raise_exception) | |||
| @@ -85,6 +86,11 @@ class TrainLineage(Callback): | |||
| self.lineage_log_path = summary_log_path + '_lineage' | |||
| self.initial_learning_rate = None | |||
| self.user_defined_info = user_defined_info | |||
| if user_defined_info: | |||
| validate_user_defined_info(user_defined_info) | |||
| except MindInsightException as err: | |||
| log.error(err) | |||
| if raise_exception: | |||
| @@ -104,6 +110,10 @@ class TrainLineage(Callback): | |||
| """ | |||
| log.info('Initialize training lineage collection...') | |||
| if self.user_defined_info: | |||
| lineage_summary = LineageSummary(summary_log_path=self.lineage_log_path) | |||
| lineage_summary.record_user_defined_info(self.user_defined_info) | |||
| if not isinstance(run_context, RunContext): | |||
| error_msg = f'Invalid TrainLineage run_context.' | |||
| log.error(error_msg) | |||
| @@ -239,7 +249,7 @@ class EvalLineage(Callback): | |||
| >>> lineagemgr = EvalLineage(summary_record=summary_writer) | |||
| >>> model.eval(epoch_num, dataset, callbacks=[model_ckpt, summary_callback, lineagemgr]) | |||
| """ | |||
| def __init__(self, summary_record, raise_exception=False): | |||
| def __init__(self, summary_record, raise_exception=False, user_defined_info=None): | |||
| super(EvalLineage, self).__init__() | |||
| try: | |||
| validate_raise_exception(raise_exception) | |||
| @@ -251,6 +261,11 @@ class EvalLineage(Callback): | |||
| summary_log_path = summary_record.full_file_name | |||
| validate_file_path(summary_log_path) | |||
| self.lineage_log_path = summary_log_path + '_lineage' | |||
| self.user_defined_info = user_defined_info | |||
| if user_defined_info: | |||
| validate_user_defined_info(user_defined_info) | |||
| except MindInsightException as err: | |||
| log.error(err) | |||
| if raise_exception: | |||
| @@ -269,6 +284,10 @@ class EvalLineage(Callback): | |||
| MindInsightException: If validating parameter fails. | |||
| LineageLogError: If recording lineage information fails. | |||
| """ | |||
| if self.user_defined_info: | |||
| lineage_summary = LineageSummary(summary_log_path=self.lineage_log_path) | |||
| lineage_summary.record_user_defined_info(self.user_defined_info) | |||
| if not isinstance(run_context, RunContext): | |||
| error_msg = f'Invalid EvalLineage run_context.' | |||
| log.error(error_msg) | |||
| @@ -226,7 +226,7 @@ class SearchModelConditionParameter(Schema): | |||
| if not isinstance(attr, str): | |||
| raise LineageParamValueError('The search attribute not supported.') | |||
| if attr not in FIELD_MAPPING and not attr.startswith('metric_'): | |||
| if attr not in FIELD_MAPPING and not attr.startswith(('metric/','user_defined/')): | |||
| raise LineageParamValueError('The search attribute not supported.') | |||
| if not isinstance(condition, dict): | |||
| @@ -238,7 +238,7 @@ class SearchModelConditionParameter(Schema): | |||
| raise LineageParamValueError("The compare condition should be in " | |||
| "('eq', 'lt', 'gt', 'le', 'ge', 'in').") | |||
| if attr.startswith('metric_'): | |||
| if attr.startswith('metric/'): | |||
| if len(attr) == 7: | |||
| raise LineageParamValueError( | |||
| 'The search attribute not supported.' | |||
| @@ -349,12 +349,14 @@ def validate_condition(search_condition): | |||
| if "sorted_name" in search_condition: | |||
| sorted_name = search_condition.get("sorted_name") | |||
| err_msg = "The sorted_name must be in {} or start with " \ | |||
| "`metric_`.".format(list(FIELD_MAPPING.keys())) | |||
| "`metric/` or `user_defined/`.".format(list(FIELD_MAPPING.keys())) | |||
| if not isinstance(sorted_name, str): | |||
| log.error(err_msg) | |||
| raise LineageParamValueError(err_msg) | |||
| if sorted_name not in FIELD_MAPPING and not ( | |||
| sorted_name.startswith('metric_') and len(sorted_name) > 7): | |||
| if not (sorted_name in FIELD_MAPPING | |||
| or (sorted_name.startswith('metric/') and len(sorted_name) > 7) | |||
| or (sorted_name.startswith('user_defined/') and len(sorted_name) > 13) | |||
| ): | |||
| log.error(err_msg) | |||
| raise LineageParamValueError(err_msg) | |||
| @@ -393,3 +395,38 @@ def validate_path(summary_path): | |||
| raise LineageDirNotExistError("The summary path does not exist or is not a dir.") | |||
| return summary_path | |||
| def validate_user_defined_info(user_defined_info): | |||
| """ | |||
| Validate user defined info. | |||
| Args: | |||
| user_defined_info (dict): The user defined info. | |||
| Raises: | |||
| LineageParamTypeError: If the type of parameters is invalid. | |||
| LineageParamValueError: If user defined keys have been defined in lineage. | |||
| """ | |||
| if not isinstance(user_defined_info, dict): | |||
| log.error("Invalid user defined info. It should be a dict.") | |||
| raise LineageParamTypeError("Invalid user defined info. It should be dict.") | |||
| for key, value in user_defined_info: | |||
| if not isinstance(key, str): | |||
| error_msg = "Dict key type {} is not supported in user defined info." \ | |||
| "Only str is permitted now.".format(type(key)) | |||
| log.error(error_msg) | |||
| raise LineageParamTypeError(error_msg) | |||
| if not isinstance(key, (int, str, float)): | |||
| error_msg = "Dict value type {} is not supported in user defined info." \ | |||
| "Only str, int and float are permitted now.".format(type(value)) | |||
| log.error(error_msg) | |||
| raise LineageParamTypeError(error_msg) | |||
| field_map = set(FIELD_MAPPING.keys()) | |||
| user_defined_keys = set(user_defined_info.keys()) | |||
| all_keys = field_map | user_defined_keys | |||
| if len(field_map) + len(user_defined_keys) != len(all_keys): | |||
| raise LineageParamValueError("There are some keys have defined in lineage.") | |||
| @@ -236,7 +236,7 @@ class Querier: | |||
| See `ConditionType` and `ExpressionType` class for the rule of filtering | |||
| and sorting. The filtering and sorting fields are defined in | |||
| `FIELD_MAPPING` or prefixed with `metric_`. | |||
| `FIELD_MAPPING` or prefixed with `metric/` or 'user_defined/'. | |||
| If the condition is `None`, all model lineage information will be | |||
| returned. | |||
| @@ -288,7 +288,7 @@ class Querier: | |||
| if condition is None: | |||
| condition = {} | |||
| result = list(filter(_filter, self._lineage_objects)) | |||
| results = list(filter(_filter, self._lineage_objects)) | |||
| if ConditionParam.SORTED_NAME.value in condition: | |||
| sorted_name = condition.get(ConditionParam.SORTED_NAME.value) | |||
| @@ -299,19 +299,33 @@ class Querier: | |||
| ) | |||
| sorted_type = condition.get(ConditionParam.SORTED_TYPE.value) | |||
| reverse = sorted_type == 'descending' | |||
| result = sorted( | |||
| result, key=functools.cmp_to_key(_cmp), reverse=reverse | |||
| results = sorted( | |||
| results, key=functools.cmp_to_key(_cmp), reverse=reverse | |||
| ) | |||
| offset_result = self._handle_limit_and_offset(condition, result) | |||
| offset_results = self._handle_limit_and_offset(condition, results) | |||
| customized = dict() | |||
| for offset_result in offset_results: | |||
| for obj_name in ["metric", "user_defined"]: | |||
| obj = getattr(offset_result, obj_name) | |||
| if obj and isinstance(obj, dict): | |||
| for key, value in obj.items(): | |||
| label = obj_name + "/" + key | |||
| customized[label] = dict() | |||
| customized[label]["label"] = label | |||
| # user defined info is default displayed | |||
| customized[label]["required"] = True | |||
| customized[label]["type"] = type(value).__name__ | |||
| search_type = condition.get(ConditionParam.LINEAGE_TYPE.value) | |||
| lineage_info = { | |||
| 'customized': customized, | |||
| 'object': [ | |||
| item.to_dataset_lineage_dict() if search_type == LineageType.DATASET.value | |||
| else item.to_filtration_dict() for item in offset_result | |||
| else item.to_filtration_dict() for item in offset_results | |||
| ], | |||
| 'count': len(result) | |||
| 'count': len(results) | |||
| } | |||
| return lineage_info | |||
| @@ -326,7 +340,8 @@ class Querier: | |||
| Returns: | |||
| bool, `True` if the field name is valid, else `False`. | |||
| """ | |||
| return field_name not in FIELD_MAPPING and not field_name.startswith('metric_') | |||
| return field_name not in FIELD_MAPPING and \ | |||
| not field_name.startswith(('metric/', 'user_defined/')) | |||
| def _handle_limit_and_offset(self, condition, result): | |||
| """ | |||
| @@ -397,11 +412,13 @@ class Querier: | |||
| log_dir = os.path.dirname(log_path) | |||
| try: | |||
| lineage_info = LineageSummaryAnalyzer.get_summary_infos(log_path) | |||
| user_defined_info = LineageSummaryAnalyzer.get_user_defined_info(log_path) | |||
| lineage_obj = LineageObj( | |||
| log_dir, | |||
| train_lineage=lineage_info.train_lineage, | |||
| evaluation_lineage=lineage_info.eval_lineage, | |||
| dataset_graph=lineage_info.dataset_graph | |||
| dataset_graph=lineage_info.dataset_graph, | |||
| user_defined_info=user_defined_info | |||
| ) | |||
| self._lineage_objects.append(lineage_obj) | |||
| self._add_dataset_mark() | |||
| @@ -57,6 +57,8 @@ class LineageObj: | |||
| - dataset_graph (Event): Dataset graph object. | |||
| - user_defined_info (Event): User defined info object. | |||
| Raises: | |||
| LineageEventNotExistException: If train and evaluation event not exist. | |||
| LineageEventFieldNotExistException: If the special event field not exist. | |||
| @@ -72,16 +74,19 @@ class LineageObj: | |||
| _name_valid_dataset = 'valid_dataset' | |||
| _name_dataset_graph = 'dataset_graph' | |||
| _name_dataset_mark = 'dataset_mark' | |||
| _name_user_defined = 'user_defined' | |||
| def __init__(self, summary_dir, **kwargs): | |||
| self._lineage_info = { | |||
| self._name_summary_dir: summary_dir | |||
| } | |||
| user_defined_info_list = kwargs.get('user_defined_info', []) | |||
| train_lineage = kwargs.get('train_lineage') | |||
| evaluation_lineage = kwargs.get('evaluation_lineage') | |||
| dataset_graph = kwargs.get('dataset_graph') | |||
| if not any([train_lineage, evaluation_lineage, dataset_graph]): | |||
| raise LineageEventNotExistException() | |||
| self._parse_user_defined_info(user_defined_info_list) | |||
| self._parse_train_lineage(train_lineage) | |||
| self._parse_evaluation_lineage(evaluation_lineage) | |||
| self._parse_dataset_graph(dataset_graph) | |||
| @@ -107,6 +112,16 @@ class LineageObj: | |||
| """ | |||
| return self._lineage_info.get(self._name_metric) | |||
| @property | |||
| def user_defined(self): | |||
| """ | |||
| Get user defined information. | |||
| Returns: | |||
| dict, the user defined information. | |||
| """ | |||
| return self._lineage_info.get(self._name_user_defined) | |||
| @property | |||
| def hyper_parameters(self): | |||
| """ | |||
| @@ -237,19 +252,22 @@ class LineageObj: | |||
| def get_value_by_key(self, key): | |||
| """ | |||
| Get the value based on the key in `FIELD_MAPPING` or the key prefixed with `metric_`. | |||
| Get the value based on the key in `FIELD_MAPPING` or | |||
| the key prefixed with `metric/` or `user_defined/`. | |||
| Args: | |||
| key (str): The key in `FIELD_MAPPING` or prefixed with `metric_`. | |||
| key (str): The key in `FIELD_MAPPING` | |||
| or prefixed with `metric/` or `user_defined/`. | |||
| Returns: | |||
| object, the value. | |||
| """ | |||
| if key.startswith('metric_'): | |||
| metric_key = key.split('_', 1)[1] | |||
| metric = self._filtration_result.get(self._name_metric) | |||
| if metric: | |||
| return metric.get(metric_key) | |||
| if key.startswith(('metric/', 'user_defined/')): | |||
| key_name, sub_key = key.split('/', 1) | |||
| sub_value_name = self._name_metric if key_name == 'metric' else self._name_user_defined | |||
| sub_value = self._filtration_result.get(sub_value_name) | |||
| if sub_value: | |||
| return sub_value.get(sub_key) | |||
| return self._filtration_result.get(key) | |||
| def _organize_filtration_result(self): | |||
| @@ -267,6 +285,8 @@ class LineageObj: | |||
| if field.sub_name else base_attr | |||
| # add metric into filtration result | |||
| result[self._name_metric] = self.metric | |||
| result[self._name_user_defined] = self.user_defined | |||
| # add dataset_graph into filtration result | |||
| result[self._name_dataset_graph] = getattr(self, self._name_dataset_graph) | |||
| return result | |||
| @@ -342,3 +362,15 @@ class LineageObj: | |||
| if event_dict is None: | |||
| raise LineageEventFieldNotExistException(self._name_evaluation_lineage) | |||
| self._lineage_info[self._name_dataset_graph] = event_dict if event_dict else {} | |||
| def _parse_user_defined_info(self, user_defined_info_list): | |||
| """ | |||
| Parse user defined info. | |||
| Args: | |||
| user_defined_info_list (list): user defined info list. | |||
| """ | |||
| user_defined_infos = dict() | |||
| for user_defined_info in user_defined_info_list: | |||
| user_defined_infos.update(user_defined_info) | |||
| self._lineage_info[self._name_user_defined] = user_defined_infos | |||
| @@ -15,8 +15,9 @@ | |||
| """The converter between proto format event of lineage and dict.""" | |||
| import time | |||
| from mindinsight.datavisual.proto_files.mindinsight_summary_pb2 import Event | |||
| from mindinsight.lineagemgr.common.exceptions.exceptions import LineageParamTypeError | |||
| from mindinsight.datavisual.proto_files.mindinsight_lineage_pb2 import LineageEvent, UserDefinedInfo | |||
| from mindinsight.lineagemgr.common.exceptions.exceptions import LineageParamTypeError,\ | |||
| LineageParamValueError | |||
| from mindinsight.lineagemgr.common.log import logger as log | |||
| @@ -28,9 +29,9 @@ def package_dataset_graph(graph): | |||
| graph (dict): Dataset graph. | |||
| Returns: | |||
| Event, the proto message event contains dataset graph. | |||
| LineageEvent, the proto message event contains dataset graph. | |||
| """ | |||
| dataset_graph_event = Event() | |||
| dataset_graph_event = LineageEvent() | |||
| dataset_graph_event.wall_time = time.time() | |||
| dataset_graph = dataset_graph_event.dataset_graph | |||
| @@ -291,3 +292,57 @@ def _organize_parameter(parameter): | |||
| parameter_result.update(result_str_list_para) | |||
| return parameter_result | |||
| def package_user_defined_info(user_dict): | |||
| """ | |||
| Package user defined info. | |||
| Args: | |||
| user_dict(dict): User defined info dict. | |||
| Returns: | |||
| LineageEvent, the proto message event contains user defined info. | |||
| """ | |||
| user_event = LineageEvent() | |||
| user_event.wall_time = time.time() | |||
| user_defined_info = user_event.user_defined_info | |||
| _package_user_defined_info(user_dict, user_defined_info) | |||
| return user_event | |||
| def _package_user_defined_info(user_defined_dict, user_defined_message): | |||
| """ | |||
| Setting attribute in user defined proto message. | |||
| Args: | |||
| user_defined_dict (dict): User define info dict. | |||
| user_defined_message (LineageEvent): Proto message of user defined info. | |||
| Raises: | |||
| LineageParamValueError: When the value is out of range. | |||
| LineageParamTypeError: When given a type not support yet. | |||
| """ | |||
| for key, value in user_defined_dict.items(): | |||
| if not isinstance(key, str): | |||
| raise LineageParamTypeError("The key must be str.") | |||
| if isinstance(value, int): | |||
| attr_name = "map_int32" | |||
| elif isinstance(value, float): | |||
| attr_name = "map_double" | |||
| elif isinstance(value, str): | |||
| attr_name = "map_str" | |||
| else: | |||
| error_msg = "Value type {} is not supported in user defined event package." \ | |||
| "Only str, int and float are permitted now.".format(type(value)) | |||
| log.error(error_msg) | |||
| raise LineageParamTypeError(error_msg) | |||
| add_user_defined_info = user_defined_message.user_info.add() | |||
| try: | |||
| getattr(add_user_defined_info, attr_name)[key] = value | |||
| except ValueError: | |||
| raise LineageParamValueError("Value is out of range or not be supported yet.") | |||
| @@ -17,7 +17,9 @@ import struct | |||
| from collections import namedtuple | |||
| from enum import Enum | |||
| from mindinsight.datavisual.proto_files.mindinsight_summary_pb2 import Event | |||
| from google.protobuf.json_format import MessageToDict | |||
| from mindinsight.datavisual.proto_files.mindinsight_lineage_pb2 import LineageEvent | |||
| from mindinsight.datavisual.utils import crc32 | |||
| from mindinsight.lineagemgr.common.exceptions.exceptions import MindInsightException, \ | |||
| LineageVerificationException, LineageSummaryAnalyzeException | |||
| @@ -87,11 +89,11 @@ class SummaryAnalyzer: | |||
| Read event. | |||
| Returns: | |||
| Event, the event body. | |||
| LineageEvent, the event body. | |||
| """ | |||
| body_size = self._read_header() | |||
| body_str = self._read_body(body_size) | |||
| event = Event().FromString(body_str) | |||
| event = LineageEvent().FromString(body_str) | |||
| return event | |||
| def _read_header(self): | |||
| @@ -206,3 +208,51 @@ class LineageSummaryAnalyzer(SummaryAnalyzer): | |||
| raise LineageSummaryAnalyzeException() | |||
| return lineage_info | |||
| @staticmethod | |||
| def get_user_defined_info(file_path): | |||
| """ | |||
| Get user defined info. | |||
| Args: | |||
| file_path (str): The file path of summary log. | |||
| Returns: | |||
| list, the list of dict format user defined information | |||
| which converted from proto message. | |||
| """ | |||
| all_user_message = [] | |||
| summary_analyzer = SummaryAnalyzer(file_path) | |||
| for event in summary_analyzer.load_events(): | |||
| if event.HasField("user_defined_info"): | |||
| user_defined_info = MessageToDict( | |||
| event, | |||
| preserving_proto_field_name=True | |||
| ).get("user_defined_info") | |||
| user_dict = LineageSummaryAnalyzer._get_dict_from_proto(user_defined_info) | |||
| all_user_message.append(user_dict) | |||
| return all_user_message | |||
| @staticmethod | |||
| def _get_dict_from_proto(user_defined_info): | |||
| """ | |||
| Convert the proto message UserDefinedInfo to its dict format. | |||
| Args: | |||
| user_defined_info (UserDefinedInfo): The proto message of user defined info. | |||
| Returns: | |||
| dict, the converted dict. | |||
| """ | |||
| user_dict = dict() | |||
| proto_dict = user_defined_info.get("user_info") | |||
| for proto_item in proto_dict: | |||
| if proto_item and isinstance(proto_item, dict): | |||
| key, value = list(list(proto_item.values())[0].items())[0] | |||
| if isinstance(value, dict): | |||
| user_dict[key] = LineageSummaryAnalyzer._get_dict_from_proto(value) | |||
| else: | |||
| user_dict[key] = value | |||
| return user_dict | |||
| @@ -15,9 +15,9 @@ | |||
| """Record message to summary log.""" | |||
| import time | |||
| from mindinsight.datavisual.proto_files.mindinsight_summary_pb2 import Event | |||
| from mindinsight.datavisual.proto_files.mindinsight_lineage_pb2 import LineageEvent | |||
| from mindinsight.lineagemgr.summary.event_writer import EventWriter | |||
| from ._summary_adapter import package_dataset_graph | |||
| from ._summary_adapter import package_dataset_graph, package_user_defined_info | |||
| class LineageSummary: | |||
| @@ -50,9 +50,9 @@ class LineageSummary: | |||
| run_context_args (dict): The train lineage info to log. | |||
| Returns: | |||
| Event, the proto message event contains train lineage. | |||
| LineageEvent, the proto message event contains train lineage. | |||
| """ | |||
| train_lineage_event = Event() | |||
| train_lineage_event = LineageEvent() | |||
| train_lineage_event.wall_time = time.time() | |||
| # Init train_lineage message. | |||
| @@ -124,9 +124,9 @@ class LineageSummary: | |||
| run_context_args (dict): The evaluation lineage info to log. | |||
| Returns: | |||
| Event, the proto message event contains evaluation lineage. | |||
| LineageEvent, the proto message event contains evaluation lineage. | |||
| """ | |||
| train_lineage_event = Event() | |||
| train_lineage_event = LineageEvent() | |||
| train_lineage_event.wall_time = time.time() | |||
| # Init evaluation_lineage message. | |||
| @@ -165,3 +165,18 @@ class LineageSummary: | |||
| self.event_writer.write_event_to_file( | |||
| package_dataset_graph(dataset_graph).SerializeToString() | |||
| ) | |||
| def record_user_defined_info(self, user_dict): | |||
| """ | |||
| Write user defined info to summary log. | |||
| Note: | |||
| The type of references must be dict, the value should be | |||
| int32, float, string. Nested dict is not supported now. | |||
| Args: | |||
| user_dict (dict): The value user defined to be recorded. | |||
| """ | |||
| self.event_writer.write_event_to_file( | |||
| package_user_defined_info(user_dict).SerializeToString() | |||
| ) | |||
| @@ -32,7 +32,8 @@ from mindinsight.lineagemgr.common.exceptions.exceptions import (LineageFileNotF | |||
| LineageSearchConditionParamError) | |||
| from ..conftest import BASE_SUMMARY_DIR, DATASET_GRAPH, SUMMARY_DIR, SUMMARY_DIR_2 | |||
| from .....ut.lineagemgr.querier import event_data | |||
| from os import environ | |||
| LINEAGE_INFO_RUN1 = { | |||
| 'summary_dir': os.path.join(BASE_SUMMARY_DIR, 'run1'), | |||
| 'metric': { | |||
| @@ -68,6 +69,7 @@ LINEAGE_FILTRATION_EXCEPT_RUN = { | |||
| 'loss_function': 'SoftmaxCrossEntropyWithLogits', | |||
| 'train_dataset_path': None, | |||
| 'train_dataset_count': 1024, | |||
| 'user_defined': {}, | |||
| 'test_dataset_path': None, | |||
| 'test_dataset_count': None, | |||
| 'network': 'ResNet', | |||
| @@ -87,6 +89,7 @@ LINEAGE_FILTRATION_RUN1 = { | |||
| 'train_dataset_path': None, | |||
| 'train_dataset_count': 731, | |||
| 'test_dataset_path': None, | |||
| 'user_defined': {}, | |||
| 'test_dataset_count': 10240, | |||
| 'network': 'ResNet', | |||
| 'optimizer': 'Momentum', | |||
| @@ -106,6 +109,7 @@ LINEAGE_FILTRATION_RUN2 = { | |||
| 'loss_function': None, | |||
| 'train_dataset_path': None, | |||
| 'train_dataset_count': None, | |||
| 'user_defined': {}, | |||
| 'test_dataset_path': None, | |||
| 'test_dataset_count': 10240, | |||
| 'network': None, | |||
| @@ -318,6 +322,7 @@ class TestModelApi(TestCase): | |||
| def test_filter_summary_lineage(self): | |||
| """Test the interface of filter_summary_lineage.""" | |||
| expect_result = { | |||
| 'customized': event_data.CUSTOMIZED__0, | |||
| 'object': [ | |||
| LINEAGE_FILTRATION_EXCEPT_RUN, | |||
| LINEAGE_FILTRATION_RUN1, | |||
| @@ -360,16 +365,17 @@ class TestModelApi(TestCase): | |||
| SUMMARY_DIR_2 | |||
| ] | |||
| }, | |||
| 'metric_accuracy': { | |||
| 'metric/accuracy': { | |||
| 'lt': 3.0, | |||
| 'gt': 0.5 | |||
| }, | |||
| 'sorted_name': 'metric_accuracy', | |||
| 'sorted_name': 'metric/accuracy', | |||
| 'sorted_type': 'descending', | |||
| 'limit': 3, | |||
| 'offset': 0 | |||
| } | |||
| expect_result = { | |||
| 'customized': event_data.CUSTOMIZED__0, | |||
| 'object': [ | |||
| LINEAGE_FILTRATION_RUN2, | |||
| LINEAGE_FILTRATION_RUN1 | |||
| @@ -397,16 +403,17 @@ class TestModelApi(TestCase): | |||
| './run2' | |||
| ] | |||
| }, | |||
| 'metric_accuracy': { | |||
| 'metric/accuracy': { | |||
| 'lt': 3.0, | |||
| 'gt': 0.5 | |||
| }, | |||
| 'sorted_name': 'metric_accuracy', | |||
| 'sorted_name': 'metric/accuracy', | |||
| 'sorted_type': 'descending', | |||
| 'limit': 3, | |||
| 'offset': 0 | |||
| } | |||
| expect_result = { | |||
| 'customized': event_data.CUSTOMIZED__0, | |||
| 'object': [ | |||
| LINEAGE_FILTRATION_RUN2, | |||
| LINEAGE_FILTRATION_RUN1 | |||
| @@ -431,10 +438,11 @@ class TestModelApi(TestCase): | |||
| 'batch_size': { | |||
| 'ge': 30 | |||
| }, | |||
| 'sorted_name': 'metric_accuracy', | |||
| 'sorted_name': 'metric/accuracy', | |||
| 'lineage_type': None | |||
| } | |||
| expect_result = { | |||
| 'customized': event_data.CUSTOMIZED__0, | |||
| 'object': [ | |||
| LINEAGE_FILTRATION_EXCEPT_RUN, | |||
| LINEAGE_FILTRATION_RUN1 | |||
| @@ -454,6 +462,7 @@ class TestModelApi(TestCase): | |||
| 'lineage_type': 'model' | |||
| } | |||
| expect_result = { | |||
| 'customized': {}, | |||
| 'object': [], | |||
| 'count': 0 | |||
| } | |||
| @@ -479,6 +488,7 @@ class TestModelApi(TestCase): | |||
| 'lineage_type': 'dataset' | |||
| } | |||
| expect_result = { | |||
| 'customized': {}, | |||
| 'object': [ | |||
| { | |||
| 'summary_dir': summary_dir, | |||
| @@ -659,13 +669,13 @@ class TestModelApi(TestCase): | |||
| # the search condition type error | |||
| search_condition = { | |||
| 'metric_accuracy': { | |||
| 'metric/accuracy': { | |||
| 'lt': 'xxx' | |||
| } | |||
| } | |||
| self.assertRaisesRegex( | |||
| LineageSearchConditionParamError, | |||
| 'The parameter metric_accuracy is invalid.', | |||
| 'The parameter metric/accuracy is invalid.', | |||
| filter_summary_lineage, | |||
| BASE_SUMMARY_DIR, | |||
| search_condition | |||
| @@ -741,12 +751,13 @@ class TestModelApi(TestCase): | |||
| """Test the abnormal execution of the filter_summary_lineage interface.""" | |||
| # gt > lt | |||
| search_condition1 = { | |||
| 'metric_accuracy': { | |||
| 'metric/accuracy': { | |||
| 'gt': 1, | |||
| 'lt': 0.5 | |||
| } | |||
| } | |||
| expect_result = { | |||
| 'customized': {}, | |||
| 'object': [], | |||
| 'count': 0 | |||
| } | |||
| @@ -762,6 +773,7 @@ class TestModelApi(TestCase): | |||
| 'offset': 4 | |||
| } | |||
| expect_result = { | |||
| 'customized': {}, | |||
| 'object': [], | |||
| 'count': 1 | |||
| } | |||
| @@ -29,6 +29,7 @@ LINEAGE_FILTRATION_BASE = { | |||
| 'loss_function': 'SoftmaxCrossEntropyWithLogits', | |||
| 'train_dataset_path': None, | |||
| 'train_dataset_count': 64, | |||
| 'user_defined': {}, | |||
| 'test_dataset_path': None, | |||
| 'test_dataset_count': None, | |||
| 'network': 'str', | |||
| @@ -46,6 +47,7 @@ LINEAGE_FILTRATION_RUN1 = { | |||
| 'loss_function': 'SoftmaxCrossEntropyWithLogits', | |||
| 'train_dataset_path': None, | |||
| 'train_dataset_count': 64, | |||
| 'user_defined': {}, | |||
| 'test_dataset_path': None, | |||
| 'test_dataset_count': 64, | |||
| 'network': 'str', | |||
| @@ -228,12 +228,12 @@ class TestValidateSearchModelCondition(TestCase): | |||
| ) | |||
| condition = { | |||
| 'metric_attribute': { | |||
| 'metric/attribute': { | |||
| 'ge': 'xxx' | |||
| } | |||
| } | |||
| self._assert_raise_of_mindinsight_exception( | |||
| "The parameter metric_attribute is invalid. " | |||
| "The parameter metric/attribute is invalid. " | |||
| "It should be a dict and the value should be a float or a integer", | |||
| condition | |||
| ) | |||
| @@ -188,6 +188,22 @@ METRIC_0 = { | |||
| 'mse': 3.00000001 | |||
| } | |||
| CUSTOMIZED__0 = { | |||
| 'metric/accuracy': {'label': 'metric/accuracy', 'required': True, 'type': 'float'}, | |||
| } | |||
| CUSTOMIZED_0 = { | |||
| **CUSTOMIZED__0, | |||
| 'metric/mae': {'label': 'metric/mae', 'required': True, 'type': 'float'}, | |||
| 'metric/mse': {'label': 'metric/mse', 'required': True, 'type': 'float'} | |||
| } | |||
| CUSTOMIZED_1 = { | |||
| 'metric/accuracy': {'label': 'metric/accuracy', 'required': True, 'type': 'NoneType'}, | |||
| 'metric/mae': {'label': 'metric/mae', 'required': True, 'type': 'float'}, | |||
| 'metric/mse': {'label': 'metric/mse', 'required': True, 'type': 'float'} | |||
| } | |||
| METRIC_1 = { | |||
| 'accuracy': 1.0000002, | |||
| 'mae': 2.00000002, | |||
| @@ -17,7 +17,7 @@ from unittest import TestCase, mock | |||
| from google.protobuf.json_format import ParseDict | |||
| import mindinsight.datavisual.proto_files.mindinsight_summary_pb2 as summary_pb2 | |||
| import mindinsight.datavisual.proto_files.mindinsight_lineage_pb2 as summary_pb2 | |||
| from mindinsight.lineagemgr.common.exceptions.exceptions import (LineageParamTypeError, LineageQuerierParamException, | |||
| LineageSummaryAnalyzeException, | |||
| LineageSummaryParseException) | |||
| @@ -40,19 +40,19 @@ def create_lineage_info(train_event_dict, eval_event_dict, dataset_event_dict): | |||
| namedtuple, parsed lineage info. | |||
| """ | |||
| if train_event_dict is not None: | |||
| train_event = summary_pb2.Event() | |||
| train_event = summary_pb2.LineageEvent() | |||
| ParseDict(train_event_dict, train_event) | |||
| else: | |||
| train_event = None | |||
| if eval_event_dict is not None: | |||
| eval_event = summary_pb2.Event() | |||
| eval_event = summary_pb2.LineageEvent() | |||
| ParseDict(eval_event_dict, eval_event) | |||
| else: | |||
| eval_event = None | |||
| if dataset_event_dict is not None: | |||
| dataset_event = summary_pb2.Event() | |||
| dataset_event = summary_pb2.LineageEvent() | |||
| ParseDict(dataset_event_dict, dataset_event) | |||
| else: | |||
| dataset_event = None | |||
| @@ -97,6 +97,7 @@ def create_filtration_result(summary_dir, train_event_dict, | |||
| "metric": metric_dict, | |||
| "dataset_graph": dataset_dict, | |||
| "dataset_mark": '2', | |||
| "user_defined": {} | |||
| } | |||
| return filtration_result | |||
| @@ -208,7 +209,9 @@ LINEAGE_FILTRATION_5 = { | |||
| "model_size": event_data.EVENT_TRAIN_DICT_5['train_lineage']['model']['size'], | |||
| "metric": {}, | |||
| "dataset_graph": event_data.DATASET_DICT_0, | |||
| "dataset_mark": '2' | |||
| "dataset_mark": '2', | |||
| "user_defined": {} | |||
| } | |||
| LINEAGE_FILTRATION_6 = { | |||
| "summary_dir": '/path/to/summary6', | |||
| @@ -228,12 +231,14 @@ LINEAGE_FILTRATION_6 = { | |||
| "model_size": None, | |||
| "metric": event_data.METRIC_5, | |||
| "dataset_graph": event_data.DATASET_DICT_0, | |||
| "dataset_mark": '2' | |||
| "dataset_mark": '2', | |||
| "user_defined": {} | |||
| } | |||
| class TestQuerier(TestCase): | |||
| """Test the class of `Querier`.""" | |||
| @mock.patch('mindinsight.lineagemgr.querier.querier.LineageSummaryAnalyzer.get_user_defined_info') | |||
| @mock.patch('mindinsight.lineagemgr.querier.querier.LineageSummaryAnalyzer.get_summary_infos') | |||
| def setUp(self, *args): | |||
| """Initialization before test case execution.""" | |||
| @@ -242,6 +247,7 @@ class TestQuerier(TestCase): | |||
| event_data.EVENT_EVAL_DICT_0, | |||
| event_data.EVENT_DATASET_DICT_0 | |||
| ) | |||
| args[1].return_value = [] | |||
| single_summary_path = '/path/to/summary0/log0' | |||
| self.single_querier = Querier(single_summary_path) | |||
| @@ -394,6 +400,7 @@ class TestQuerier(TestCase): | |||
| 'sorted_name': 'summary_dir' | |||
| } | |||
| expected_result = { | |||
| 'customized': event_data.CUSTOMIZED_0, | |||
| 'object': [ | |||
| LINEAGE_FILTRATION_1, | |||
| LINEAGE_FILTRATION_2 | |||
| @@ -418,6 +425,7 @@ class TestQuerier(TestCase): | |||
| 'sorted_type': 'descending' | |||
| } | |||
| expected_result = { | |||
| 'customized': event_data.CUSTOMIZED_0, | |||
| 'object': [ | |||
| LINEAGE_FILTRATION_2, | |||
| LINEAGE_FILTRATION_3 | |||
| @@ -434,6 +442,7 @@ class TestQuerier(TestCase): | |||
| 'offset': 1 | |||
| } | |||
| expected_result = { | |||
| 'customized': event_data.CUSTOMIZED_0, | |||
| 'object': [ | |||
| LINEAGE_FILTRATION_2, | |||
| LINEAGE_FILTRATION_3 | |||
| @@ -446,6 +455,7 @@ class TestQuerier(TestCase): | |||
| def test_filter_summary_lineage_success_4(self): | |||
| """Test the success of filter_summary_lineage.""" | |||
| expected_result = { | |||
| 'customized': event_data.CUSTOMIZED_0, | |||
| 'object': [ | |||
| LINEAGE_FILTRATION_0, | |||
| LINEAGE_FILTRATION_1, | |||
| @@ -468,6 +478,7 @@ class TestQuerier(TestCase): | |||
| } | |||
| } | |||
| expected_result = { | |||
| 'customized': event_data.CUSTOMIZED_0, | |||
| 'object': [LINEAGE_FILTRATION_4], | |||
| 'count': 1, | |||
| } | |||
| @@ -477,10 +488,11 @@ class TestQuerier(TestCase): | |||
| def test_filter_summary_lineage_success_6(self): | |||
| """Test the success of filter_summary_lineage.""" | |||
| condition = { | |||
| 'sorted_name': 'metric_accuracy', | |||
| 'sorted_name': 'metric/accuracy', | |||
| 'sorted_type': 'ascending' | |||
| } | |||
| expected_result = { | |||
| 'customized': event_data.CUSTOMIZED_0, | |||
| 'object': [ | |||
| LINEAGE_FILTRATION_0, | |||
| LINEAGE_FILTRATION_5, | |||
| @@ -498,10 +510,11 @@ class TestQuerier(TestCase): | |||
| def test_filter_summary_lineage_success_7(self): | |||
| """Test the success of filter_summary_lineage.""" | |||
| condition = { | |||
| 'sorted_name': 'metric_accuracy', | |||
| 'sorted_name': 'metric/accuracy', | |||
| 'sorted_type': 'descending' | |||
| } | |||
| expected_result = { | |||
| 'customized': event_data.CUSTOMIZED_1, | |||
| 'object': [ | |||
| LINEAGE_FILTRATION_6, | |||
| LINEAGE_FILTRATION_4, | |||
| @@ -519,12 +532,13 @@ class TestQuerier(TestCase): | |||
| def test_filter_summary_lineage_success_8(self): | |||
| """Test the success of filter_summary_lineage.""" | |||
| condition = { | |||
| 'metric_accuracy': { | |||
| 'metric/accuracy': { | |||
| 'lt': 1.0000006, | |||
| 'gt': 1.0000004 | |||
| } | |||
| } | |||
| expected_result = { | |||
| 'customized': event_data.CUSTOMIZED_0, | |||
| 'object': [LINEAGE_FILTRATION_4], | |||
| 'count': 1, | |||
| } | |||
| @@ -538,6 +552,7 @@ class TestQuerier(TestCase): | |||
| 'offset': 3 | |||
| } | |||
| expected_result = { | |||
| 'customized': {}, | |||
| 'object': [], | |||
| 'count': 7, | |||
| } | |||
| @@ -594,11 +609,13 @@ class TestQuerier(TestCase): | |||
| with self.assertRaises(LineageSummaryParseException): | |||
| Querier(summary_path) | |||
| @mock.patch('mindinsight.lineagemgr.querier.querier.LineageSummaryAnalyzer.get_user_defined_info') | |||
| @mock.patch('mindinsight.lineagemgr.querier.querier.LineageSummaryAnalyzer.get_summary_infos') | |||
| def test_parse_fail_summary_logs_1(self, *args): | |||
| """Test the function of parsing fail summary logs.""" | |||
| lineage_infos = get_lineage_infos() | |||
| args[0].side_effect = lineage_infos | |||
| args[1].return_value = [] | |||
| summary_path = ['/path/to/summary0/log0'] | |||
| querier = Querier(summary_path) | |||
| @@ -611,6 +628,7 @@ class TestQuerier(TestCase): | |||
| self.assertListEqual(expected_result, result) | |||
| self.assertListEqual([], querier._parse_failed_paths) | |||
| @mock.patch('mindinsight.lineagemgr.querier.querier.LineageSummaryAnalyzer.get_user_defined_info') | |||
| @mock.patch('mindinsight.lineagemgr.querier.querier.LineageSummaryAnalyzer.get_summary_infos') | |||
| def test_parse_fail_summary_logs_2(self, *args): | |||
| """Test the function of parsing fail summary logs.""" | |||
| @@ -619,6 +637,7 @@ class TestQuerier(TestCase): | |||
| event_data.EVENT_EVAL_DICT_0, | |||
| event_data.EVENT_DATASET_DICT_0, | |||
| ) | |||
| args[1].return_value = [] | |||
| summary_path = ['/path/to/summary0/log0'] | |||
| querier = Querier(summary_path) | |||
| @@ -16,7 +16,7 @@ | |||
| from unittest import mock, TestCase | |||
| from unittest.mock import MagicMock | |||
| from mindinsight.datavisual.proto_files.mindinsight_summary_pb2 import Event | |||
| from mindinsight.datavisual.proto_files.mindinsight_lineage_pb2 import LineageEvent | |||
| from mindinsight.lineagemgr.common.exceptions.exceptions import LineageVerificationException, \ | |||
| LineageSummaryAnalyzeException | |||
| from mindinsight.lineagemgr.common.log import logger as log | |||
| @@ -57,7 +57,7 @@ class TestSummaryAnalyzer(TestCase): | |||
| @mock.patch.object(SummaryAnalyzer, '_read_header') | |||
| @mock.patch.object(SummaryAnalyzer, '_read_body') | |||
| @mock.patch.object(Event, 'FromString') | |||
| @mock.patch.object(LineageEvent, 'FromString') | |||
| def test_read_event(self, *args): | |||
| """Test read_event method.""" | |||
| args[2].return_value = 10 | |||