Compare commits

...

52 Commits
master ... r0.5

Author SHA1 Message Date
  mindspore-ci-bot 8d1b0ce9e2 !758 update securec repository link for r0.5 5 years ago
  liangyongxiong 80254974d1 update securec repository link 5 years ago
  mindspore-ci-bot 0e8091a68b !393 profiler: updated README.md 5 years ago
  zhangyunshu 13d911d38d profiler: fixed readme 5 years ago
  mindspore-ci-bot aeb6a92c8a !390 UI fix operator list sort issue 5 years ago
  ph b87c179a1a fix operator list sort issue 5 years ago
  mindspore-ci-bot 0f511c9080 !380 Modify the time unit of aicpu profiling data 5 years ago
  mindspore-ci-bot fab5ed5b38 !379 Add DEVICE_ID to env for minddata profiling. 5 years ago
  mindspore-ci-bot 9a378ca2a9 !384 Scalar toolip optimization, accuracy details modification 5 years ago
  mindspore-ci-bot e37e17edef !388 add time unit in the column names in ui 5 years ago
  mindspore-ci-bot 3210043250 !387 UI timeline dowload and operator show AI CPU 5 years ago
  askmiao f532de929a modify aicpu timeunit ,from us to ms 5 years ago
  chenchao99 6c4dbf01e8 update the display column name in ui 5 years ago
  ph 5ee3fce0b9 ui timeline dowload and operator show AI CPU 5 years ago
  wwx691809 6d3321d67f Scalar toolip optimization, accuracy details modification 5 years ago
  yuximiao 4f47b30c4f add device_id to env when profiling 5 years ago
  mindspore-ci-bot 494ad29568 !371 update the display name of aicore detail execution time 5 years ago
  chenchao99 65040c3209 update the display name of aicore detail execution time 5 years ago
  mindspore-ci-bot e1faf65f76 !375 Profiler: fixed the issue of using unverified dir path 5 years ago
  zhangyunshu dba7e6b148 profiler: fixed timeline fuzz issue 5 years ago
  mindspore-ci-bot b74335983b !372 use custom response instead of response 5 years ago
  askmiao aed23bce18 use the custom response 5 years ago
  mindspore-ci-bot ebe61dc6cf !369 The modified information should reflect the modification difference. 5 years ago
  ggpolar b4709a0b08 The modified information should reflect the modification difference. 5 years ago
  mindspore-ci-bot f288237dc9 !363 fix the result of the number of decimal points of aicore type execution time 5 years ago
  mindspore-ci-bot 81bd191758 !364 fix oplist search issue when user turn pages 5 years ago
  WeiFeng-mindinsight fbc5b2434f fix oplist search issue when user turn pages 5 years ago
  chenchao99 780fb3b8b0 fix the result of the number of decimal points of aicore type execution time 5 years ago
  mindspore-ci-bot 70f854b2c2 !340 Add some docs for the Profiler 5 years ago
  mindspore-ci-bot ca9b983058 !360 Modify the problem of the operator name in the pie chart not completely displayed 5 years ago
  WeiFeng-mindinsight b68d5f8384 Modify the problem of the operator name in the pie chart not completely displayed 5 years ago
  mindspore-ci-bot d5b5df1f5a !356 The conversion report is adjusted to make the the report more reasonable and accurate. 5 years ago
  ggpolar 60ea09c449 The conversion report is adjusted to make the the report more reasonable and accurate. 5 years ago
  mindspore-ci-bot b16b6bf2a9 !348 fix bug of step trace, the text of small data will show outside of the rect which own it 5 years ago
  mindspore-ci-bot f1a0da9c4b !351 profiler: optimized the performance of creating timeline file 5 years ago
  mindspore-ci-bot 65f37e3373 !350 Fix minddata device_queue_op time info error. 5 years ago
  WeiFeng-mindinsight 35bdb3a675 fix bug of step trace, the text of small data will show outside of the rect which own it 5 years ago
  yuximiao 1260481b81 fix minddata wrong time tag 5 years ago
  mindspore-ci-bot 6f5d4f7041 !343 add 0.5.0-beta release note 5 years ago
  mindspore-ci-bot 97e980ae34 !345 fix bugs of --url-path-prefix parameter [r0.5] 5 years ago
  liangyongxiong 4e0874e1be enhance robust validation for --url-path-prefix parameter 5 years ago
  mindspore-ci-bot b8cefff413 !337 add null byte check in the api of get_plugins 5 years ago
  mindspore-ci-bot f5c93f29c3 !338 UI fix bug of step trace, optimize data-process and profiling-dashboard display 5 years ago
  mindspore-ci-bot aa3dacf5a3 !331 Fix issue that the python source for an AST is wrong. 5 years ago
  mindspore-ci-bot 09512a5f07 !342 Improve the overall function logic of setting threshold 5 years ago
  kouzhenzhong 98ce8fd046 add 0.5.0-beta release note 5 years ago
  wangyue01 8ce2bbe7b8 Add some docs for Profiler 5 years ago
  mindspore-ci-bot f288c1eeba !335 Modify input threshold verification and Optimizing the visual map component of echart rendering 5 years ago
  WeiFeng-mindinsight 66bb03a5bb UI fix bug of step trace, optimize data-process and profiling-dashboard display 5 years ago
  zhangyunshu 930602813e optimized the mechanism of adding framework info to timeline 5 years ago
  wangshuide2020 0fad2218fd add null byte check in the api of get_plugins 5 years ago
  ggpolar 79e1b068ee Fix issue that the python source for an AST is wrong. 5 years ago
40 changed files with 824 additions and 394 deletions
Unified View
  1. +2
    -0
      .gitignore
  2. +1
    -1
      .gitmodules
  3. +8
    -0
      README.md
  4. +30
    -0
      RELEASE.md
  5. +17
    -3
      mindinsight/backend/profiler/profile_api.py
  6. +9
    -0
      mindinsight/datavisual/common/exceptions.py
  7. +4
    -22
      mindinsight/datavisual/data_transform/summary_watcher.py
  8. +4
    -0
      mindinsight/datavisual/processors/train_task_manager.py
  9. +21
    -0
      mindinsight/datavisual/utils/utils.py
  10. +89
    -27
      mindinsight/mindconverter/ast_edits.py
  11. +3
    -4
      mindinsight/mindconverter/config.py
  12. +15
    -2
      mindinsight/profiler/README.md
  13. +32
    -4
      mindinsight/profiler/analyser/analyser.py
  14. +14
    -5
      mindinsight/profiler/analyser/base_analyser.py
  15. +2
    -2
      mindinsight/profiler/analyser/minddata_analyser.py
  16. +23
    -51
      mindinsight/profiler/analyser/timeline_analyser.py
  17. +4
    -1
      mindinsight/profiler/common/util.py
  18. +1
    -1
      mindinsight/profiler/common/validator/validate.py
  19. BIN
      mindinsight/profiler/images/data_op_profile.png
  20. BIN
      mindinsight/profiler/images/minddata_profile.png
  21. BIN
      mindinsight/profiler/images/performance_overall.png
  22. BIN
      mindinsight/profiler/images/step_trace.png
  23. BIN
      mindinsight/profiler/images/timeline.png
  24. +8
    -6
      mindinsight/profiler/parser/aicpu_data_parser.py
  25. +1
    -0
      mindinsight/profiler/profiling.py
  26. +12
    -4
      mindinsight/scripts/start.py
  27. +1
    -1
      mindinsight/ui/public/index.html
  28. +24
    -9
      mindinsight/ui/src/locales/zh-cn.json
  29. +6
    -1
      mindinsight/ui/src/router.js
  30. +31
    -23
      mindinsight/ui/src/views/train-manage/data-process.vue
  31. +26
    -24
      mindinsight/ui/src/views/train-manage/operator.vue
  32. +131
    -42
      mindinsight/ui/src/views/train-manage/profiling-dashboard.vue
  33. +6
    -4
      mindinsight/ui/src/views/train-manage/scalar.vue
  34. +200
    -84
      mindinsight/ui/src/views/train-manage/step-trace.vue
  35. +1
    -0
      mindinsight/utils/constant.py
  36. +22
    -2
      tests/st/func/datavisual/taskmanager/test_plugins_restful_api.py
  37. +35
    -34
      tests/st/func/profiler/test_op_analyser.py
  38. +22
    -20
      tests/ut/profiler/analyser/test_analyser_aicore_detail.py
  39. +14
    -12
      tests/ut/profiler/analyser/test_analyser_aicore_type.py
  40. +5
    -5
      tests/utils/resource/JOB_AICPU/expect/output_data_preprocess_aicpu_0.txt

+ 2
- 0
.gitignore View File

@@ -86,3 +86,5 @@ build/*


output/ output/
!output/README.md !output/README.md

mindinsight/ui/public/static/js/graphvizlib.wasm

+ 1
- 1
.gitmodules View File

@@ -1,3 +1,3 @@
[submodule "third_party/securec"] [submodule "third_party/securec"]
path = third_party/securec path = third_party/securec
url = https://gitee.com/openeuler/bounds_checking_function.git
url = https://gitee.com/openeuler/libboundscheck.git

+ 8
- 0
README.md View File

@@ -11,6 +11,10 @@ such as computation graph, training process metrics, etc.
Provide visualization of model parameters information, Provide visualization of model parameters information,
such as training data, model accuracy, etc. such as training data, model accuracy, etc.


- Visualization of training performance:

Provide visualization of training performance information, such as operator execution time,
data input pipeline performance, etc.


# Index # Index


@@ -91,6 +95,10 @@ The GUI of MindInsight displays the pipeline of dataset processing and augmentat


The GUI of MindInsight displays the parameters and operations of the dataset processing and augmentation. The GUI of MindInsight displays the parameters and operations of the dataset processing and augmentation.


### Performance visualization

The GUI of MindInsight displays the performance data of the neural networks.

# Installation # Installation


See [Install MindInsight](https://www.mindspore.cn/install/en). See [Install MindInsight](https://www.mindspore.cn/install/en).


+ 30
- 0
RELEASE.md View File

@@ -1,5 +1,35 @@
## MindInsight ## MindInsight


# Release 0.5.0-beta

## Major Features and Improvements
* MindSpore Profiler
* Provide performance analyse tool for the input data pipeline.
* Provide timeline analyse tool, which can show the detail of the streams/tasks.
* Provide a tool to visualize the step trace information, which can be used to analyse the general performance of the neural network in each phase.
* Provide profiling guides for the users to find the performance bottlenecks quickly.
* CPU summary operations support for CPU summary data.
* Over threshold warn support in scalar training dashboard.
* Provide more user-friendly callback function for visualization
* Provide unified callback `SummaryCollector` to log most commonly visualization event.
* Discard the original visualization callback `SummaryStep`, `TrainLineage` and `EvalLineage`.
* `SummaryRecord` provide new API `add_value` to collect data into cache for summary persistence.
* `SummaryRecord` provide new API `set_mode` to distinguish summary persistence mode at different stages.
* MindConverter supports conversion of more operators and networks, and improves its ease of use.

## Bugfixes
* Fix FileNotFound exception by adding robust check for summary watcher ([!281](https://gitee.com/mindspore/mindinsight/pulls/281)).
* UI fix operator table sort jump problem ([!283](https://gitee.com/mindspore/mindinsight/pulls/283)).
* Dataset serializer return schema json str when schema type is `mindspore.dataset.engine.Schema` ([!2185](https://gitee.com/mindspore/mindspore/pulls/2185)).


## Thanks to our Contributors
Thanks goes to these wonderful people:

Chao Chen, Congli Gao, Ye Huang, Weifeng Huang, Zhenzhong Kou, Hongzhang Li, Longfei Li, Yongxiong Liang, Chongming Liu, Pengting Luo, Yanming Miao, Gongchang Ou, Yongxiu Qu, Hui Pan, Luyu Qiu, Junyan Qin, Kai Wen, Weining Wang, Yue Wang, Zhuanke Wu, Yifan Xia, Lihua Ye, Weibiao Yu, Ximiao Yu, Yunshu Zhang, Ting Zhao, Jianfeng Zhu.

Contributions of any kind are welcome!

# Release 0.3.0-alpha # Release 0.3.0-alpha


## Major Features and Improvements ## Major Features and Improvements


+ 17
- 3
mindinsight/backend/profiler/profile_api.py View File

@@ -21,7 +21,6 @@ import json
import os import os
from flask import Blueprint from flask import Blueprint
from flask import Response
from flask import jsonify from flask import jsonify
from flask import request from flask import request
from marshmallow import ValidationError from marshmallow import ValidationError
@@ -31,7 +30,8 @@ from mindinsight.datavisual.utils.tools import get_train_id, get_profiler_dir, t
from mindinsight.datavisual.utils.tools import unquote_args from mindinsight.datavisual.utils.tools import unquote_args
from mindinsight.profiler.analyser.analyser_factory import AnalyserFactory from mindinsight.profiler.analyser.analyser_factory import AnalyserFactory
from mindinsight.profiler.analyser.minddata_analyser import MinddataAnalyser from mindinsight.profiler.analyser.minddata_analyser import MinddataAnalyser
from mindinsight.profiler.common.exceptions.exceptions import ProfilerFileNotFoundException
from mindinsight.profiler.common.exceptions.exceptions import ProfilerFileNotFoundException, \
ProfilerDirNotFoundException
from mindinsight.profiler.common.util import analyse_device_list_from_profiler_dir from mindinsight.profiler.common.util import analyse_device_list_from_profiler_dir
from mindinsight.profiler.common.validator.validate import validate_condition, validate_ui_proc from mindinsight.profiler.common.validator.validate import validate_condition, validate_ui_proc
from mindinsight.profiler.common.validator.validate import validate_minddata_pipeline_condition from mindinsight.profiler.common.validator.validate import validate_minddata_pipeline_condition
@@ -40,6 +40,7 @@ from mindinsight.profiler.common.validator.validate_path import \
from mindinsight.profiler.common.validator.validate_path import validate_and_normalize_profiler_path from mindinsight.profiler.common.validator.validate_path import validate_and_normalize_profiler_path
from mindinsight.profiler.proposer.compose_proposer import ComposeProposal from mindinsight.profiler.proposer.compose_proposer import ComposeProposal
from mindinsight.utils.exceptions import ParamValueError from mindinsight.utils.exceptions import ParamValueError
from mindinsight.backend.application import CustomResponse
BLUEPRINT = Blueprint("profile", __name__, url_prefix=settings.URL_PATH_PREFIX+settings.API_PREFIX) BLUEPRINT = Blueprint("profile", __name__, url_prefix=settings.URL_PATH_PREFIX+settings.API_PREFIX)
@@ -71,6 +72,7 @@ def get_profile_op_info():
validate_condition(search_condition) validate_condition(search_condition)
device_id = search_condition.get("device_id", "0") device_id = search_condition.get("device_id", "0")
to_int(device_id, 'device_id')
profiler_dir_abs = os.path.join(settings.SUMMARY_BASE_DIR, train_id, profiler_dir) profiler_dir_abs = os.path.join(settings.SUMMARY_BASE_DIR, train_id, profiler_dir)
try: try:
profiler_dir_abs = validate_and_normalize_path(profiler_dir_abs, "profiler") profiler_dir_abs = validate_and_normalize_path(profiler_dir_abs, "profiler")
@@ -193,6 +195,7 @@ def get_queue_info():
profile_dir = get_profiler_abs_dir(request) profile_dir = get_profiler_abs_dir(request)
device_id = unquote_args(request, "device_id") device_id = unquote_args(request, "device_id")
to_int(device_id, 'device_id')
queue_type = unquote_args(request, "type") queue_type = unquote_args(request, "type")
queue_info = {} queue_info = {}
@@ -219,6 +222,7 @@ def get_time_info():
""" """
profile_dir = get_profiler_abs_dir(request) profile_dir = get_profiler_abs_dir(request)
device_id = unquote_args(request, "device_id") device_id = unquote_args(request, "device_id")
to_int(device_id, 'device_id')
op_type = unquote_args(request, "type") op_type = unquote_args(request, "type")
time_info = { time_info = {
@@ -250,6 +254,7 @@ def get_process_summary():
""" """
profile_dir = get_profiler_abs_dir(request) profile_dir = get_profiler_abs_dir(request)
device_id = unquote_args(request, "device_id") device_id = unquote_args(request, "device_id")
to_int(device_id, 'device_id')
minddata_analyser = AnalyserFactory.instance().get_analyser( minddata_analyser = AnalyserFactory.instance().get_analyser(
'minddata', profile_dir, device_id) 'minddata', profile_dir, device_id)
@@ -304,6 +309,7 @@ def get_profile_summary_proposal():
device_id = get_device_id(request) device_id = get_device_id(request)
if not profiler_dir or not train_id: if not profiler_dir or not train_id:
raise ParamValueError("No profiler_dir or train_id.") raise ParamValueError("No profiler_dir or train_id.")
to_int(device_id, 'device_id')
profiler_dir_abs = os.path.join(settings.SUMMARY_BASE_DIR, train_id, profiler_dir) profiler_dir_abs = os.path.join(settings.SUMMARY_BASE_DIR, train_id, profiler_dir)
try: try:
@@ -320,7 +326,7 @@ def get_profile_summary_proposal():
proposal_obj = ComposeProposal(profiler_dir_abs, device_id, proposal_type_list) proposal_obj = ComposeProposal(profiler_dir_abs, device_id, proposal_type_list)
proposal_info = proposal_obj.get_proposal(options) proposal_info = proposal_obj.get_proposal(options)
# Use json.dumps for orderly return # Use json.dumps for orderly return
return Response(json.dumps(proposal_info), mimetype='application/json')
return CustomResponse(json.dumps(proposal_info), mimetype='application/json')
@BLUEPRINT.route("/profile/minddata-pipeline/op-queue", methods=["POST"]) @BLUEPRINT.route("/profile/minddata-pipeline/op-queue", methods=["POST"])
@@ -360,6 +366,7 @@ def get_minddata_pipeline_op_queue_info():
validate_minddata_pipeline_condition(condition) validate_minddata_pipeline_condition(condition)
device_id = condition.get("device_id", "0") device_id = condition.get("device_id", "0")
to_int(device_id, 'device_id')
analyser = AnalyserFactory.instance().get_analyser( analyser = AnalyserFactory.instance().get_analyser(
'minddata_pipeline', profiler_dir_abs, device_id 'minddata_pipeline', profiler_dir_abs, device_id
) )
@@ -397,6 +404,7 @@ def get_minddata_pipeline_queue_info():
raise ParamValueError("Invalid profiler dir.") raise ParamValueError("Invalid profiler dir.")
device_id = request.args.get('device_id', default='0') device_id = request.args.get('device_id', default='0')
to_int(device_id, 'device_id')
op_id = request.args.get('op_id', type=int) op_id = request.args.get('op_id', type=int)
if op_id is None: if op_id is None:
raise ParamValueError("Invalid operator id or operator id does not exist.") raise ParamValueError("Invalid operator id or operator id does not exist.")
@@ -421,6 +429,9 @@ def get_timeline_summary():
""" """
summary_dir = request.args.get("dir") summary_dir = request.args.get("dir")
profiler_dir = validate_and_normalize_profiler_path(summary_dir, settings.SUMMARY_BASE_DIR) profiler_dir = validate_and_normalize_profiler_path(summary_dir, settings.SUMMARY_BASE_DIR)
if not os.path.exists(profiler_dir):
msg = 'The profiler dir is not found!'
raise ProfilerDirNotFoundException(msg=msg)
device_id = request.args.get("device_id", default='0') device_id = request.args.get("device_id", default='0')
_ = to_int(device_id, 'device_id') _ = to_int(device_id, 'device_id')
@@ -444,6 +455,9 @@ def get_timeline_detail():
""" """
summary_dir = request.args.get("dir") summary_dir = request.args.get("dir")
profiler_dir = validate_and_normalize_profiler_path(summary_dir, settings.SUMMARY_BASE_DIR) profiler_dir = validate_and_normalize_profiler_path(summary_dir, settings.SUMMARY_BASE_DIR)
if not os.path.exists(profiler_dir):
msg = 'The profiler dir is not found!'
raise ProfilerDirNotFoundException(msg=msg)
device_id = request.args.get("device_id", default='0') device_id = request.args.get("device_id", default='0')
_ = to_int(device_id, 'device_id') _ = to_int(device_id, 'device_id')


+ 9
- 0
mindinsight/datavisual/common/exceptions.py View File

@@ -107,6 +107,15 @@ class TrainJobNotExistError(MindInsightException):
http_code=400) http_code=400)




class QueryStringContainsNullByteError(MindInsightException):
"""Query string contains null byte error."""
def __init__(self, error_detail):
error_msg = f"Query string contains null byte error. Detail: {error_detail}"
super(QueryStringContainsNullByteError, self).__init__(DataVisualErrors.QUERY_STRING_CONTAINS_NULL_BYTE,
error_msg,
http_code=400)


class PluginNotAvailableError(MindInsightException): class PluginNotAvailableError(MindInsightException):
"""The given plugin is not available.""" """The given plugin is not available."""
def __init__(self, error_detail): def __init__(self, error_detail):


+ 4
- 22
mindinsight/datavisual/data_transform/summary_watcher.py View File

@@ -22,6 +22,7 @@ from pathlib import Path
from mindinsight.datavisual.common.log import logger from mindinsight.datavisual.common.log import logger
from mindinsight.datavisual.common.validation import Validation from mindinsight.datavisual.common.validation import Validation
from mindinsight.datavisual.utils.tools import Counter from mindinsight.datavisual.utils.tools import Counter
from mindinsight.datavisual.utils.utils import contains_null_byte
from mindinsight.datavisual.common.exceptions import MaxCountExceededError from mindinsight.datavisual.common.exceptions import MaxCountExceededError
from mindinsight.utils.exceptions import FileSystemPermissionError from mindinsight.utils.exceptions import FileSystemPermissionError
@@ -61,7 +62,7 @@ class SummaryWatcher:
>>> summary_watcher = SummaryWatcher() >>> summary_watcher = SummaryWatcher()
>>> directories = summary_watcher.list_summary_directories('/summary/base/dir') >>> directories = summary_watcher.list_summary_directories('/summary/base/dir')
""" """
if self._contains_null_byte(summary_base_dir=summary_base_dir):
if contains_null_byte(summary_base_dir=summary_base_dir):
return [] return []
relative_path = os.path.join('.', '') relative_path = os.path.join('.', '')
@@ -148,25 +149,6 @@ class SummaryWatcher:
pass pass
self._update_summary_dict(summary_dict, summary_base_dir, subdir_relative_path, subdir_entry) self._update_summary_dict(summary_dict, summary_base_dir, subdir_relative_path, subdir_entry)
def _contains_null_byte(self, **kwargs):
"""
Check if arg contains null byte.
Args:
kwargs (Any): Check if arg contains null byte.
Returns:
bool, indicates if any arg contains null byte.
"""
for key, value in kwargs.items():
if not isinstance(value, str):
continue
if '\x00' in value:
logger.warning('%s contains null byte \\x00.', key)
return True
return False
def _is_valid_summary_directory(self, summary_base_dir, relative_path): def _is_valid_summary_directory(self, summary_base_dir, relative_path):
""" """
Check if the given summary directory is valid. Check if the given summary directory is valid.
@@ -276,7 +258,7 @@ class SummaryWatcher:
>>> summary_watcher = SummaryWatcher() >>> summary_watcher = SummaryWatcher()
>>> summaries = summary_watcher.is_summary_directory('/summary/base/dir', './job-01') >>> summaries = summary_watcher.is_summary_directory('/summary/base/dir', './job-01')
""" """
if self._contains_null_byte(summary_base_dir=summary_base_dir, relative_path=relative_path):
if contains_null_byte(summary_base_dir=summary_base_dir, relative_path=relative_path):
return False return False
if not self._is_valid_summary_directory(summary_base_dir, relative_path): if not self._is_valid_summary_directory(summary_base_dir, relative_path):
@@ -371,7 +353,7 @@ class SummaryWatcher:
>>> summary_watcher = SummaryWatcher() >>> summary_watcher = SummaryWatcher()
>>> summaries = summary_watcher.list_summaries('/summary/base/dir', './job-01') >>> summaries = summary_watcher.list_summaries('/summary/base/dir', './job-01')
""" """
if self._contains_null_byte(summary_base_dir=summary_base_dir, relative_path=relative_path):
if contains_null_byte(summary_base_dir=summary_base_dir, relative_path=relative_path):
return [] return []
if not self._is_valid_summary_directory(summary_base_dir, relative_path): if not self._is_valid_summary_directory(summary_base_dir, relative_path):


+ 4
- 0
mindinsight/datavisual/processors/train_task_manager.py View File

@@ -19,7 +19,9 @@ from mindinsight.datavisual.common.log import logger
from mindinsight.datavisual.common import exceptions from mindinsight.datavisual.common import exceptions
from mindinsight.datavisual.common.enums import PluginNameEnum from mindinsight.datavisual.common.enums import PluginNameEnum
from mindinsight.datavisual.common.enums import CacheStatus from mindinsight.datavisual.common.enums import CacheStatus
from mindinsight.datavisual.common.exceptions import QueryStringContainsNullByteError
from mindinsight.datavisual.common.validation import Validation from mindinsight.datavisual.common.validation import Validation
from mindinsight.datavisual.utils.utils import contains_null_byte
from mindinsight.datavisual.processors.base_processor import BaseProcessor from mindinsight.datavisual.processors.base_processor import BaseProcessor
from mindinsight.datavisual.data_transform.data_manager import DATAVISUAL_PLUGIN_KEY, DATAVISUAL_CACHE_KEY from mindinsight.datavisual.data_transform.data_manager import DATAVISUAL_PLUGIN_KEY, DATAVISUAL_CACHE_KEY


@@ -57,6 +59,8 @@ class TrainTaskManager(BaseProcessor):
dict, refer to restful api. dict, refer to restful api.
""" """
Validation.check_param_empty(train_id=train_id) Validation.check_param_empty(train_id=train_id)
if contains_null_byte(train_id=train_id):
raise QueryStringContainsNullByteError("train job id: {} contains null byte.".format(train_id))


if manual_update: if manual_update:
self._data_manager.cache_train_job(train_id) self._data_manager.cache_train_job(train_id)


+ 21
- 0
mindinsight/datavisual/utils/utils.py View File

@@ -14,6 +14,7 @@
# ============================================================================ # ============================================================================
"""Utils.""" """Utils."""
import math import math
from mindinsight.datavisual.common.log import logger




def calc_histogram_bins(count): def calc_histogram_bins(count):
@@ -45,3 +46,23 @@ def calc_histogram_bins(count):
return math.ceil(count / number_per_bucket) + 1 return math.ceil(count / number_per_bucket) + 1


return max_bins return max_bins


def contains_null_byte(**kwargs):
"""
Check if arg contains null byte.

Args:
kwargs (Any): Check if arg contains null byte.

Returns:
bool, indicates if any arg contains null byte.
"""
for key, value in kwargs.items():
if not isinstance(value, str):
continue
if '\x00' in value:
logger.warning('%s contains null byte \\x00.', key)
return True

return False

+ 89
- 27
mindinsight/mindconverter/ast_edits.py View File

@@ -32,6 +32,7 @@ from mindinsight.mindconverter.common.log import logger
from mindinsight.mindconverter.common.exceptions import NodeTypeNotSupport from mindinsight.mindconverter.common.exceptions import NodeTypeNotSupport
from mindinsight.mindconverter.forward_call import ForwardCall from mindinsight.mindconverter.forward_call import ForwardCall


LOG_FMT_INSERT = "[Insert] '%s' is inserted to the converted file."
LOG_FMT_CONVERT = "[Convert] '%s' is converted to '%s'." LOG_FMT_CONVERT = "[Convert] '%s' is converted to '%s'."
LOG_FMT_CONVERT_WITH_TIPS = "[Convert] '%s' is converted to '%s'. %s" LOG_FMT_CONVERT_WITH_TIPS = "[Convert] '%s' is converted to '%s'. %s"
LOG_FMT_NOT_CONVERT = "[UnConvert] '%s' didn't convert. %s" LOG_FMT_NOT_CONVERT = "[UnConvert] '%s' didn't convert. %s"
@@ -54,16 +55,22 @@ class _ConvertReport:
def __init__(self, is_stub=False): def __init__(self, is_stub=False):
self._is_stub = is_stub self._is_stub = is_stub
self._max_line = 0 self._max_line = 0
self._log = [] # report log, type is (severity, line, col, msg)
self._log_head = []
self._log_body = [] # report log, type is (severity, line, col, msg)


def _add_log(self, severity, line, col, msg): def _add_log(self, severity, line, col, msg):
"""Add log.""" """Add log."""
if self._is_stub: if self._is_stub:
return return
if line is None and col is None:
self._log_head.append(msg)
return
if isinstance(line, int) and isinstance(col, int): if isinstance(line, int) and isinstance(col, int):
self._log.append((severity, line, col, msg))
self._log_body.append((severity, line, col, msg))
if self._max_line < line: if self._max_line < line:
self._max_line = line self._max_line = line
else:
raise TypeError('The parameter type is incorrect.')


def info(self, line, col, msg): def info(self, line, col, msg):
"""Interface to add infer log""" """Interface to add infer log"""
@@ -73,14 +80,24 @@ class _ConvertReport:
"""Interface to add warning log""" """Interface to add warning log"""
self._add_log(logging.WARNING, line, col, msg) self._add_log(logging.WARNING, line, col, msg)


def header_msg(self, msg):
"""Interface to add header message log"""
self._add_log(logging.INFO, None, None, msg)

def get_logs(self): def get_logs(self):
"""Get convert logs""" """Get convert logs"""
logs = [] logs = []
logs.extend(self._log_head)
# sort rule: line * self._max_line + col # sort rule: line * self._max_line + col
self._log.sort(key=lambda log: log[1] * self._max_line + log[2])
for log_info in self._log:
self._log_body.sort(key=lambda log: log[1] * self._max_line + log[2])
for log_info in self._log_body:
log_info = "line %d:%d: %s" % (log_info[1], log_info[2], log_info[3]) log_info = "line %d:%d: %s" % (log_info[1], log_info[2], log_info[3])
logs.append(log_info)
if logs:
# Deduplication for logs
if logs[-1] != log_info:
logs.append(log_info)
else:
logs.append(log_info)
return logs return logs




@@ -262,7 +279,8 @@ class AstEditVisitor(ast.NodeVisitor):
new_func_name = 'construct' new_func_name = 'construct'
if func_ast_node.name == old_func_name: if func_ast_node.name == old_func_name:
func_ast_node.name = new_func_name func_ast_node.name = new_func_name
self._process_log.info(func_ast_node.lineno, func_ast_node.col_offset,
real_line_number = self._get_real_line_number(func_ast_node)
self._process_log.info(real_line_number, func_ast_node.col_offset,
LOG_FMT_CONVERT % (old_func_name, new_func_name)) LOG_FMT_CONVERT % (old_func_name, new_func_name))


def _convert_api(self): def _convert_api(self):
@@ -297,7 +315,16 @@ class AstEditVisitor(ast.NodeVisitor):
if source_prefix: if source_prefix:
pos = len(source_prefix) pos = len(source_prefix)
source_code = pasta.dump(node) source_code = pasta.dump(node)
return source_code[pos]
return source_code[pos:]

@staticmethod
def _get_real_line_number(node):
"""Get the real line number of the node."""
try:
line_number = node.lineno + len(node.decorator_list)
except AttributeError:
line_number = node.lineno
return line_number


def _replace_external_reference(self): def _replace_external_reference(self):
""" """
@@ -349,6 +376,7 @@ class AstEditVisitor(ast.NodeVisitor):
insert_pos += 1 insert_pos += 1
else: else:
try: try:
# insert pos after the last one, if last one name is replaced.
replaced_with_node = names_replaced_with[src_name] replaced_with_node = names_replaced_with[src_name]
insert_pos = self._tree.body.index(replaced_with_node) + 1 insert_pos = self._tree.body.index(replaced_with_node) + 1
except ValueError: except ValueError:
@@ -359,6 +387,8 @@ class AstEditVisitor(ast.NodeVisitor):
for insert_pos, new_node in new_import_node.items(): for insert_pos, new_node in new_import_node.items():
# Insert the node into the module # Insert the node into the module
self._tree.body.insert(insert_pos + insert_cnt, new_node) self._tree.body.insert(insert_pos + insert_cnt, new_node)
new_code = self._dump_without_prefix(new_node)
self._process_log.header_msg(LOG_FMT_INSERT % new_code.strip())
insert_cnt += 1 insert_cnt += 1


@staticmethod @staticmethod
@@ -445,8 +475,10 @@ class AstEditVisitor(ast.NodeVisitor):


is_include_sub_call = self._is_include_sub_call(call_func_node) is_include_sub_call = self._is_include_sub_call(call_func_node)
if is_include_sub_call: if is_include_sub_call:
# x.y().z splits to ['x.y()', 'z']
name_attributes = call_name.rsplit('.', 1) name_attributes = call_name.rsplit('.', 1)
else: else:
# x.y.z splits to ['x', 'y', 'z']
name_attributes = call_name.split('.') name_attributes = call_name.split('.')


# rewritten external module name # rewritten external module name
@@ -638,6 +670,55 @@ class AstEditVisitor(ast.NodeVisitor):


return new_code return new_code


@staticmethod
def _get_detail_prompt_msg(old_node, new_node):
"""Get detail converted prompt information."""
msg = None
if isinstance(old_node, ast.Call) and isinstance(new_node, ast.Call):
old_api_name = pasta.dump(old_node.func)
new_api_name = pasta.dump(new_node.func)
if new_api_name == old_api_name:
old_parameter_num = len(old_node.args) + len(old_node.keywords)
new_parameter_num = len(new_node.args) + len(new_node.keywords)
if old_parameter_num > 1:
msg = 'Parameters are converted.'
else:
if old_parameter_num == 0 and new_parameter_num == 0:
msg = 'The API name is converted to mindspore API'
else:
msg = 'Parameter is converted.'
return msg

def _convert_call(self, node, matched_api_name):
""""Convert the call node."""
new_node = None
code = pasta.dump(node)
api_name = pasta.dump(node.func)
warning_info = get_prompt_info(matched_api_name)
if warning_info is None:
warning_info = ''
if matched_api_name in ALL_MAPPING:
logger.info("Line %3d start converting API: %s", node.lineno, api_name)
new_code = self.mapping_api(node)
if new_code != code:
try:
new_node = pasta.parse(new_code).body[0].value
# find the first call name
new_api_name = new_code[:new_code.find('(')]
detail_msg = self._get_detail_prompt_msg(node, new_node)
if detail_msg:
warning_info = detail_msg + ' ' + warning_info
except AttributeError:
new_node = pasta.parse(new_code).body[0]
new_api_name = new_code
self._process_log.info(node.lineno, node.col_offset,
LOG_FMT_CONVERT_WITH_TIPS % (api_name, new_api_name, warning_info))
else:
logger.warning("Line %3d: found unsupported API: %s%s", node.lineno, api_name, warning_info)
self._process_log.warning(node.lineno, node.col_offset, LOG_FMT_NOT_CONVERT % (api_name, warning_info))

return new_node

def visit_Call(self, node): def visit_Call(self, node):
"""Callback function when visit AST tree""" """Callback function when visit AST tree"""
code = pasta.dump(node) code = pasta.dump(node)
@@ -655,26 +736,7 @@ class AstEditVisitor(ast.NodeVisitor):
new_code = code new_code = code
matched_api_name, match_case = self.match_api(node.func, self._is_forward_function) matched_api_name, match_case = self.match_api(node.func, self._is_forward_function)
if match_case in [ApiMatchingEnum.API_INFER, ApiMatchingEnum.API_MATCHED]: if match_case in [ApiMatchingEnum.API_INFER, ApiMatchingEnum.API_MATCHED]:
warning_info = get_prompt_info(matched_api_name)
if warning_info is None:
warning_info = ''
if matched_api_name in ALL_MAPPING:
logger.info("Line %3d start converting API: %s", node.lineno, api_name)
new_code = self.mapping_api(node)
if new_code != code:
try:
new_node = pasta.parse(new_code).body[0].value
# find the first call name
new_api_name = new_code[:new_code.find('(')]
except AttributeError:
new_node = pasta.parse(new_code).body[0]
new_api_name = new_code
self._process_log.info(node.lineno, node.col_offset,
LOG_FMT_CONVERT_WITH_TIPS % (api_name, new_api_name, warning_info))
else:
logger.warning("Line %3d: found unsupported API: %s%s", node.lineno, api_name, warning_info)
self._process_log.warning(node.lineno, node.col_offset, LOG_FMT_NOT_CONVERT % (api_name, warning_info))

new_node = self._convert_call(node, matched_api_name)
elif match_case in [ApiMatchingEnum.API_STANDARD, ApiMatchingEnum.API_FOUND]: elif match_case in [ApiMatchingEnum.API_STANDARD, ApiMatchingEnum.API_FOUND]:
self._process_log.warning(node.lineno, node.col_offset, LOG_FMT_NOT_CONVERT % (api_name, '')) self._process_log.warning(node.lineno, node.col_offset, LOG_FMT_NOT_CONVERT % (api_name, ''))
else: else:


+ 3
- 4
mindinsight/mindconverter/config.py View File

@@ -32,7 +32,7 @@ FUNC_MODULE = 'mindinsight.mindconverter.funcs'
class APIPt: class APIPt:
"""Base API for args parse, and API for one frame.""" """Base API for args parse, and API for one frame."""


def __init__(self, name: str, params: OrderedDict):
def __init__(self, name: str, params: dict):
self.name = name self.name = name
self.params = OrderedDict() self.params = OrderedDict()


@@ -45,7 +45,7 @@ class APIPt:
Trans value to str. Trans value to str.


Args: Args:
value (Union[str,Number,int]): Each value for params of OrderedDict.
value (Union[str,Number,int]): The value to convert.


Returns: Returns:
str, str type of value. str, str type of value.
@@ -118,7 +118,7 @@ class APIPt:
class APIMs(APIPt): class APIMs(APIPt):
"""API for MindSpore""" """API for MindSpore"""


def __init__(self, name: str, params: OrderedDict, p_attrs=None):
def __init__(self, name: str, params: dict, p_attrs=None):
self.is_primitive = name.startswith('P.') self.is_primitive = name.startswith('P.')
if self.is_primitive: if self.is_primitive:
self.p_attrs = p_attrs if p_attrs else set() self.p_attrs = p_attrs if p_attrs else set()
@@ -450,7 +450,6 @@ UNSUPPORTED_WARN_INFOS = {
"F.one_hot": "Maybe could convert to mindspore.ops.operations.OneHot.", "F.one_hot": "Maybe could convert to mindspore.ops.operations.OneHot.",
"torch.bmm": "Maybe could convert to mindspore.ops.operations.BatchMatMul.", "torch.bmm": "Maybe could convert to mindspore.ops.operations.BatchMatMul.",
"torch.cumsum": "Maybe could convert to mindspore.ops.operations.CumSum.", "torch.cumsum": "Maybe could convert to mindspore.ops.operations.CumSum.",
"F.relu": "Maybe could convert to mindspore.ops.operations.ReLU.",
"F.pad": "Maybe could convert to mindspore.ops.operations.Pad.", "F.pad": "Maybe could convert to mindspore.ops.operations.Pad.",
"F.softmax": "Maybe could convert to mindspore.ops.operations.Softmax.", "F.softmax": "Maybe could convert to mindspore.ops.operations.Softmax.",
"torch.clamp": "Maybe could convert to mindspore.ops.composite.clip_by_value.", "torch.clamp": "Maybe could convert to mindspore.ops.composite.clip_by_value.",


+ 15
- 2
mindinsight/profiler/README.md View File

@@ -65,8 +65,8 @@ Figure 2 displays the Step Trace page. The Step Trace detail will show the start
can also choose a specific step to see its step trace statistics. The graphs at the bottom of the page show how the execution time of Step Gap, Forward/Backward Propagation and can also choose a specific step to see its step trace statistics. The graphs at the bottom of the page show how the execution time of Step Gap, Forward/Backward Propagation and
Step Tail changes according to different steps, it will help to decide whether we can optimize the performance of some stages. Step Tail changes according to different steps, it will help to decide whether we can optimize the performance of some stages.


*Notice:* MindSpore choose the Foward Start/Backward End Operators automatically, The names of the two operators are shown on the page. It is possible that the two operators are
not choosen as what the user expect. Users can choose the operators from the dumped execution graph, and specify the two operators manually by setting the `FP_POINT` and `BP_POINT` environment.
*Notice:* MindSpore choose the Forward Start/Backward End Operators automatically, The names of the two operators are shown on the page. Profiler do not guarantee that the two operators are
always chosen as the user's expectation. Users can choose the two operators according to the execution graph, and specify the them manually by setting the `FP_POINT` and `BP_POINT` environment variables.
For example: `export FP_POINT=fp32_vars/conv2d/conv2Dfp32_vars/BatchNorm/FusedBatchNorm_Reduce` and `export BP_POINT=loss_scale/gradients/AddN_70`. For example: `export FP_POINT=fp32_vars/conv2d/conv2Dfp32_vars/BatchNorm/FusedBatchNorm_Reduce` and `export BP_POINT=loss_scale/gradients/AddN_70`.


### Operator Performance Analysis ### Operator Performance Analysis
@@ -138,6 +138,17 @@ The Timeline component can display:
- The MindSpore stream split strategy for this neural network; - The MindSpore stream split strategy for this neural network;
- The time of tasks executed on the device. - The time of tasks executed on the device.


How to view the timeline:

To view the detailed information of the timeline, you can click the "Download" button to save the file with the timeline information locally, and then view it through the tool.

We recommend you to use Google plugin: chrome://tracing, or Perfetto tool: https://ui.perfetto.dev/#!viewer.

- Select one of the tools mentioned above, enter the address in the browser and press Enter;
- After entered the page, click the button to load the file to view the timeline.
- For chrome tracing, using "load" button in the upper left corner.
- For Perfetto, using "Open trace file" in the left column.

Users can get the most detailed information from the Timeline: Users can get the most detailed information from the Timeline:


- From high level, users can analyse whether the stream split strategy can be optimized and whether the step tail is too long; - From high level, users can analyse whether the stream split strategy can be optimized and whether the step tail is too long;
@@ -160,3 +171,5 @@ The Profiler has the following limitations now:


* Only programs running on Ascend chip is supported. * Only programs running on Ascend chip is supported.
* To limit the data size generated by the Profiler, MindInsight suggests that for large neural network, the profiled steps should better below 10. * To limit the data size generated by the Profiler, MindInsight suggests that for large neural network, the profiled steps should better below 10.
* The parse of Timeline data is time consuming, and several step's data is usually enough for analysis. In order to speed up the data parse and UI
display, Profiler will show at most 20M data (Contain 10+ step information for large networks).

+ 32
- 4
mindinsight/profiler/analyser/analyser.py View File

@@ -34,6 +34,8 @@ class AicoreTypeAnalyser(BaseAnalyser):
ProfilerPathErrorException: If the profiling dir is invalid. ProfilerPathErrorException: If the profiling dir is invalid.
""" """
_col_names = ['op_type', 'execution_time', 'execution_frequency', 'percent'] _col_names = ['op_type', 'execution_time', 'execution_frequency', 'percent']
_col_names_in_result = ['op_type', 'execution_time (ms)',
'execution_frequency', 'percent']
_file_name_aicore_type_time = 'aicore_intermediate_{}_type.csv' _file_name_aicore_type_time = 'aicore_intermediate_{}_type.csv'


def _load(self): def _load(self):
@@ -63,6 +65,18 @@ class AicoreTypeAnalyser(BaseAnalyser):
return self._default_filter(item, filter_condition) return self._default_filter(item, filter_condition)
self._result = list(filter(_inner_filter, self._data)) self._result = list(filter(_inner_filter, self._data))


def _organize_query_result(self):
"""
Organize the query result.

Returns:
dict, the query result.
"""
for item in self._result:
item[1] = float(format(item[1], '.6f'))
self._display_col_names = self._col_names_in_result[:]
return super()._organize_query_result()

def _convert_field_type(self, row): def _convert_field_type(self, row):
""" """
Convert the field type to the specific type. Convert the field type to the specific type.
@@ -88,8 +102,10 @@ class AicoreDetailAnalyser(BaseAnalyser):
Raises: Raises:
ProfilerPathErrorException: If the profiling dir is invalid. ProfilerPathErrorException: If the profiling dir is invalid.
""" """
_col_names = ['op_name', 'op_type', 'execution_time', 'subgraph',
_col_names = ['op_name', 'op_type', 'avg_execution_time', 'subgraph',
'full_op_name', 'op_info'] 'full_op_name', 'op_info']
_col_names_in_result = ['op_name', 'op_type', 'avg_execution_time (ms)',
'subgraph', 'full_op_name', 'op_info']
_file_name_aicore_detail_time = 'aicore_intermediate_{}_detail.csv' _file_name_aicore_detail_time = 'aicore_intermediate_{}_detail.csv'
_file_name_framework_info = 'framework_raw_{}.csv' _file_name_framework_info = 'framework_raw_{}.csv'


@@ -215,11 +231,11 @@ class AicoreDetailAnalyser(BaseAnalyser):
is_display_full_op_name (bool): Whether to display the operator full is_display_full_op_name (bool): Whether to display the operator full
name. name.
""" """
self._display_col_names = self._col_names[0:4]
self._display_col_names = self._col_names_in_result[0:4]
if is_display_full_op_name: if is_display_full_op_name:
self._display_col_names.append(self._col_names[4])
self._display_col_names.append(self._col_names_in_result[4])
if is_display_detail: if is_display_detail:
self._display_col_names.append(self._col_names[5])
self._display_col_names.append(self._col_names_in_result[5])


def _convert_framework_field_type(self, row): def _convert_framework_field_type(self, row):
""" """
@@ -264,6 +280,8 @@ class AicpuAnalyser(BaseAnalyser):
""" """
_col_names = ['serial_number', 'op_type', 'total_time', 'dispatch_time', _col_names = ['serial_number', 'op_type', 'total_time', 'dispatch_time',
'run_start', 'run_end'] 'run_start', 'run_end']
_col_names_in_result = ['serial_number', 'op_type', 'total_time (ms)',
'dispatch_time (ms)', 'run_start', 'run_end']
_file_name_aicpu_time = 'aicpu_intermediate_{}.csv' _file_name_aicpu_time = 'aicpu_intermediate_{}.csv'


def _load(self): def _load(self):
@@ -294,6 +312,16 @@ class AicpuAnalyser(BaseAnalyser):
return self._default_filter(item, filter_condition) return self._default_filter(item, filter_condition)
self._result = list(filter(_inner_filter, self._data)) self._result = list(filter(_inner_filter, self._data))


def _organize_query_result(self):
"""
Organize the query result.

Returns:
dict, the query result.
"""
self._display_col_names = self._col_names_in_result[:]
return super()._organize_query_result()

def _convert_field_type(self, row): def _convert_field_type(self, row):
""" """
Convert the field type to the specific type. Convert the field type to the specific type.


+ 14
- 5
mindinsight/profiler/analyser/base_analyser.py View File

@@ -97,11 +97,7 @@ class BaseAnalyser(ABC):
self._sort(sort_condition) self._sort(sort_condition)
if group_condition: if group_condition:
self._group(group_condition) self._group(group_condition)
return {
'col_name': self._display_col_names,
'object': self._result,
'size': self._size
}
return self._organize_query_result()


@abstractmethod @abstractmethod
def _load(self): def _load(self):
@@ -244,3 +240,16 @@ class BaseAnalyser(ABC):
except ValidationError: except ValidationError:
raise ProfilerPathErrorException('The profiling dir is invalid.') raise ProfilerPathErrorException('The profiling dir is invalid.')
return normalized_profiling_dir return normalized_profiling_dir

def _organize_query_result(self):
"""
Organize the query result.

Returns:
dict, the query result.
"""
return {
'col_name': self._display_col_names,
'object': self._result,
'size': self._size
}

+ 2
- 2
mindinsight/profiler/analyser/minddata_analyser.py View File

@@ -116,13 +116,13 @@ class MinddataAnalyser(BaseAnalyser):
# op_info: 2: step num 3: cost time # op_info: 2: step num 3: cost time
if op_info[1] == "0": if op_info[1] == "0":
get_time_list.append([int(op_info[2]), float(op_info[3])]) get_time_list.append([int(op_info[2]), float(op_info[3])])
total_cost += float(op_info[3])
total_get += float(op_info[3])
elif op_info[1] == "1": elif op_info[1] == "1":
push_time_list.append([int(op_info[2]), float(op_info[3])]) push_time_list.append([int(op_info[2]), float(op_info[3])])
total_push += float(op_info[3]) total_push += float(op_info[3])
elif op_info[1] == "2": elif op_info[1] == "2":
total_time_list.append([int(op_info[2]), float(op_info[3])]) total_time_list.append([int(op_info[2]), float(op_info[3])])
total_get += float(op_info[3])
total_cost += float(op_info[3])
elif op_info and op_info[0] == "1" and info_type in ["all", "queue"]: elif op_info and op_info[0] == "1" and info_type in ["all", "queue"]:
queue_size_list.append([int(op_info[2]), int(op_info[3])]) queue_size_list.append([int(op_info[2]), int(op_info[3])])
if op_info[1] == op_info[3]: if op_info[1] == op_info[3]:


+ 23
- 51
mindinsight/profiler/analyser/timeline_analyser.py View File

@@ -34,7 +34,6 @@ class TimelineAnalyser(BaseAnalyser):
__col_names__ = ['op_name', 'stream_id', 'start_time', 'duration'] __col_names__ = ['op_name', 'stream_id', 'start_time', 'duration']
_output_timeline_data_file_path = 'output_timeline_data_{}.txt' _output_timeline_data_file_path = 'output_timeline_data_{}.txt'
_min_cycle_counter_file_path = 'min_cycle_counter_{}.txt' _min_cycle_counter_file_path = 'min_cycle_counter_{}.txt'
_timeline_filename = 'timeline_detail_{}.json'
_display_filename = 'timeline_display_{}.json' _display_filename = 'timeline_display_{}.json'
_timeline_summary_filename = 'timeline_summary_{}.json' _timeline_summary_filename = 'timeline_summary_{}.json'
_timeline_meta = [] _timeline_meta = []
@@ -64,15 +63,9 @@ class TimelineAnalyser(BaseAnalyser):
json, the content of timeline data. json, the content of timeline data.
""" """
# Search timeline json file under profiling dir. # Search timeline json file under profiling dir.
timeline_filename = self._timeline_filename.format(self._device_id)
display_filename = self._display_filename.format(self._device_id) display_filename = self._display_filename.format(self._device_id)
file_list = [filename for filename in os.listdir(self._profiling_dir)
if timeline_filename in filename or display_filename in filename]

# Check if there is a timeline json file for display # Check if there is a timeline json file for display
file_path = os.path.join(self._profiling_dir, display_filename) file_path = os.path.join(self._profiling_dir, display_filename)
if display_filename not in file_list:
file_path = os.path.join(self._profiling_dir, timeline_filename)
file_path = validate_and_normalize_path( file_path = validate_and_normalize_path(
file_path, raise_key='Invalid timeline json path.' file_path, raise_key='Invalid timeline json path.'
) )
@@ -121,39 +114,9 @@ class TimelineAnalyser(BaseAnalyser):
"""Load data according to the parsed profiling files.""" """Load data according to the parsed profiling files."""
# Write timeline to file. # Write timeline to file.
logger.info('Writing timeline file...') logger.info('Writing timeline file...')
file_size = self.write_timeline_to_json()

# If the file size is larger than 20MB, open a new file and
# write the first 20MB content into it.
if file_size > SIZE_LIMIT:
logger.debug('File size is larger than 20MB, will be resized...')
# write to json file for display
self.write_timeline_to_json_by_limitation()

self.write_timeline_to_json_by_limitation()
logger.info('Finished file writing!') logger.info('Finished file writing!')


def write_timeline_to_json(self):
"""Write timeline to json."""
timeline_filename = self._timeline_filename.format(self._device_id)
timeline_file_path = os.path.join(
self._profiling_dir,
timeline_filename
)

timeline_file_path = validate_and_normalize_path(
timeline_file_path, raise_key='Invalid timeline json path.'
)

try:
with open(timeline_file_path, 'w') as json_file:
json.dump(self._timeline_meta, json_file)
file_size = os.path.getsize(timeline_file_path)
except (IOError, OSError) as err:
logger.error('Error occurred when write timeline full details: %s', err)
raise ProfilerIOException

return file_size

def write_timeline_to_json_by_limitation(self): def write_timeline_to_json_by_limitation(self):
"""Write timeline to json by limitation.""" """Write timeline to json by limitation."""
display_filename = self._display_filename.format(self._device_id) display_filename = self._display_filename.format(self._device_id)
@@ -161,7 +124,6 @@ class TimelineAnalyser(BaseAnalyser):
self._profiling_dir, self._profiling_dir,
display_filename display_filename
) )

display_file_path = validate_and_normalize_path( display_file_path = validate_and_normalize_path(
display_file_path, raise_key='Invalid timeline display json path.' display_file_path, raise_key='Invalid timeline display json path.'
) )
@@ -224,11 +186,11 @@ class TimelineAnalyser(BaseAnalyser):


return timeline_list return timeline_list


def _parse_timeline_data(self, line_list):
def _parse_timeline_data(self, timeline):
"""Parse timeline data.""" """Parse timeline data."""
# factor to convert the time unit from 1ms to 1us for timeline display # factor to convert the time unit from 1ms to 1us for timeline display
factor = 1000 factor = 1000
op_meta = TimelineContainer(line_list)
op_meta = TimelineContainer(timeline)
timeline_dict = {} timeline_dict = {}
timeline_dict['name'] = op_meta.op_name timeline_dict['name'] = op_meta.op_name
timeline_dict['ph'] = 'X' timeline_dict['ph'] = 'X'
@@ -245,9 +207,9 @@ class TimelineAnalyser(BaseAnalyser):
self._timeline_meta.append(timeline_dict) self._timeline_meta.append(timeline_dict)


@staticmethod @staticmethod
def _update_num_of_streams(line_list, stream_count_dict):
def _update_num_of_streams(timeline, stream_count_dict):
"""Update number of streams.""" """Update number of streams."""
stream_id = line_list[1]
stream_id = timeline[1]
if stream_id not in stream_count_dict.keys(): if stream_id not in stream_count_dict.keys():
stream_count_dict[stream_id] = 1 stream_count_dict[stream_id] = 1
else: else:
@@ -328,18 +290,28 @@ class TimelineAnalyser(BaseAnalyser):
framework_obj_list (list): The framework metadata. framework_obj_list (list): The framework metadata.
""" """
logger.debug('Start adding framework info into timeline...') logger.debug('Start adding framework info into timeline...')
# Get the framework info that will be written into timeline.
framework_info_dict = {}
for framework_obj in framework_obj_list: for framework_obj in framework_obj_list:
op_name = framework_obj[0] op_name = framework_obj[0]
op_type = framework_obj[1] op_type = framework_obj[1]
op_full_name = framework_obj[4] op_full_name = framework_obj[4]
op_info = framework_obj[5] op_info = framework_obj[5]
for timeline_obj in self._timeline_meta:
if op_full_name == timeline_obj.get('name'):
timeline_obj['name'] = op_name
timeline_obj['args'] = {
'type': op_type,
'fullname': op_full_name
}
timeline_obj['args'].update(op_info)
framework_info_dict[op_full_name] = {
'name': op_name,
'args': {
'type': op_type,
'fullname': op_full_name
}
}
framework_info_dict[op_full_name]['args'].update(op_info)

# Insert framework info into timeline.
for timeline_item in self._timeline_meta:
op_full_name = timeline_item.get('name')
framework_item = framework_info_dict.get(op_full_name)
if framework_item:
timeline_item['name'] = framework_item.get('name')
timeline_item['args'] = framework_item.get('args')


logger.debug('Finished adding framework info into timeline...') logger.debug('Finished adding framework info into timeline...')

+ 4
- 1
mindinsight/profiler/common/util.py View File

@@ -118,7 +118,10 @@ def get_summary_for_step_trace(average_info, header):
def calculate_percent(partial, total): def calculate_percent(partial, total):
"""Calculate percent value.""" """Calculate percent value."""
percent = round(partial / total * 100, 2)
if total:
percent = round(partial / total * 100, 2)
else:
percent = 0
return f'{percent}%' return f'{percent}%'


+ 1
- 1
mindinsight/profiler/common/validator/validate.py View File

@@ -24,7 +24,7 @@ from mindinsight.profiler.common.exceptions.exceptions import ProfilerParamTypeE
from mindinsight.profiler.common.log import logger as log from mindinsight.profiler.common.log import logger as log
AICORE_TYPE_COL = ["op_type", "execution_time", "execution_frequency", "precent"] AICORE_TYPE_COL = ["op_type", "execution_time", "execution_frequency", "precent"]
AICORE_DETAIL_COL = ["op_name", "op_type", "execution_time", "subgraph", "full_op_name"]
AICORE_DETAIL_COL = ["op_name", "op_type", "avg_execution_time", "subgraph", "full_op_name"]
AICPU_COL = ["serial_number", "op_type", "total_time", "dispatch_time", "run_start", AICPU_COL = ["serial_number", "op_type", "total_time", "dispatch_time", "run_start",
"run_end"] "run_end"]
MINDDATA_PIPELINE_COL = [ MINDDATA_PIPELINE_COL = [


BIN
mindinsight/profiler/images/data_op_profile.png View File

Before After
Width: 1392  |  Height: 787  |  Size: 58 kB Width: 1392  |  Height: 787  |  Size: 58 kB

BIN
mindinsight/profiler/images/minddata_profile.png View File

Before After
Width: 1868  |  Height: 825  |  Size: 110 kB Width: 1868  |  Height: 825  |  Size: 110 kB

BIN
mindinsight/profiler/images/performance_overall.png View File

Before After
Width: 1901  |  Height: 919  |  Size: 90 kB Width: 1901  |  Height: 919  |  Size: 90 kB

BIN
mindinsight/profiler/images/step_trace.png View File

Before After
Width: 1878  |  Height: 910  |  Size: 96 kB Width: 1878  |  Height: 910  |  Size: 96 kB

BIN
mindinsight/profiler/images/timeline.png View File

Before After
Width: 1925  |  Height: 751  |  Size: 51 kB Width: 1925  |  Height: 751  |  Size: 51 kB

+ 8
- 6
mindinsight/profiler/parser/aicpu_data_parser.py View File

@@ -35,8 +35,9 @@ class DataPreProcessParser:


_source_file_target = 'DATA_PREPROCESS.dev.AICPU.' _source_file_target = 'DATA_PREPROCESS.dev.AICPU.'
_dst_file_title = 'title:DATA_PREPROCESS AICPU' _dst_file_title = 'title:DATA_PREPROCESS AICPU'
_dst_file_column_title = ['serial_number', 'node_type_name', 'total_time(us)',
'dispatch_time(us)', 'run_start', 'run_end']
_dst_file_column_title = ['serial_number', 'node_type_name', 'total_time(ms)',
'dispatch_time(ms)', 'run_start', 'run_end']
_ms_unit = 1000


def __init__(self, input_path, output_filename): def __init__(self, input_path, output_filename):
self._input_path = input_path self._input_path = input_path
@@ -71,8 +72,8 @@ class DataPreProcessParser:


run_start = node_list[1].split(':')[-1].split(' ')[0] run_start = node_list[1].split(':')[-1].split(' ')[0]
run_end = node_list[run_end_index].split(':')[-1].split(' ')[0] run_end = node_list[run_end_index].split(':')[-1].split(' ')[0]
total_time = thread_list[-1].split('=')[-1].split()[0]
dispatch_time = thread_list[-2].split('=')[-1].split()[0]
total_time = float(thread_list[-1].split('=')[-1].split()[0]) / self._ms_unit
dispatch_time = float(thread_list[-2].split('=')[-1].split()[0]) / self._ms_unit


return [number, node_type_name, total_time, dispatch_time, return [number, node_type_name, total_time, dispatch_time,
run_start, run_end] run_start, run_end]
@@ -112,7 +113,7 @@ class DataPreProcessParser:
result_list.append(result) result_list.append(result)
# Calculate the total time. # Calculate the total time.
total_time = result[2] total_time = result[2]
ai_cpu_total_time_summary += int(total_time)
ai_cpu_total_time_summary += total_time
# Increase node serial number. # Increase node serial number.
serial_number += 1 serial_number += 1
elif "Node" in node_line and "Thread" not in thread_line: elif "Node" in node_line and "Thread" not in thread_line:
@@ -120,7 +121,8 @@ class DataPreProcessParser:
logger.warning("The node type:%s cannot find thread data", node_type_name) logger.warning("The node type:%s cannot find thread data", node_type_name)


if result_list: if result_list:
result_list.append(["AI CPU Total Time(us):", ai_cpu_total_time_summary])
ai_cpu_total_time = format(ai_cpu_total_time_summary, '.6f')
result_list.append(["AI CPU Total Time(ms):", ai_cpu_total_time])
fwrite_format(self._output_filename, data_source=self._dst_file_title, is_print=True, fwrite_format(self._output_filename, data_source=self._dst_file_title, is_print=True,
is_start=True) is_start=True)
fwrite_format(self._output_filename, fwrite_format(self._output_filename,


+ 1
- 0
mindinsight/profiler/profiling.py View File

@@ -92,6 +92,7 @@ class Profiler:
os.environ['PROFILING_MODE'] = 'true' os.environ['PROFILING_MODE'] = 'true'
os.environ['PROFILING_OPTIONS'] = 'training_trace:task_trace' os.environ['PROFILING_OPTIONS'] = 'training_trace:task_trace'
os.environ['MINDDATA_PROFILING_DIR'] = self._output_path os.environ['MINDDATA_PROFILING_DIR'] = self._output_path
os.environ['DEVICE_ID'] = self._dev_id
# use context interface to open profiling, for the new mindspore version(after 2020.5.21) # use context interface to open profiling, for the new mindspore version(after 2020.5.21)
try: try:
import mindspore.context as context import mindspore.context as context


+ 12
- 4
mindinsight/scripts/start.py View File

@@ -122,7 +122,8 @@ class PortAction(argparse.Action):
class UrlPathPrefixAction(argparse.Action): class UrlPathPrefixAction(argparse.Action):
"""Url Path prefix action class definition.""" """Url Path prefix action class definition."""


REGEX = r'^(\/[a-zA-Z0-9-\-\.]+)+$'
INVALID_SEGMENTS = ('.', '..')
REGEX = r'^[a-zA-Z0-9_\-\.]+$'


def __call__(self, parser, namespace, values, option_string=None): def __call__(self, parser, namespace, values, option_string=None):
""" """
@@ -135,8 +136,12 @@ class UrlPathPrefixAction(argparse.Action):
option_string (str): Optional string for specific argument name. Default: None. option_string (str): Optional string for specific argument name. Default: None.
""" """
prefix = values prefix = values
if not re.match(self.REGEX, prefix):
parser.error(f'{option_string} value is invalid url path prefix')
segments = prefix.split('/')
for index, segment in enumerate(segments):
if not segment and index in (0, len(segments) - 1):
continue
if segment in self.INVALID_SEGMENTS or not re.match(self.REGEX, segment):
parser.error(f'{option_string} value is invalid url path prefix')


setattr(namespace, self.dest, prefix) setattr(namespace, self.dest, prefix)


@@ -186,7 +191,10 @@ class Command(BaseCommand):
type=str, type=str,
action=UrlPathPrefixAction, action=UrlPathPrefixAction,
help=""" help="""
Custom path prefix for web page address. Default value is ''.
Custom URL path prefix for web page address. URL path prefix
consists of segments separated by slashes. Each segment supports
alphabets / digits / underscores / dashes / dots, but cannot just
be emtpy string / single dot / double dots. Default value is ''.
""") """)


for hook in HookUtils.instance().hooks(): for hook in HookUtils.instance().hooks():


+ 1
- 1
mindinsight/ui/public/index.html View File

@@ -21,7 +21,7 @@ limitations under the License.
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" /> <meta name="viewport" content="width=device-width,initial-scale=1.0" />
<link rel="icon" href="<%= BASE_URL %>/static/img/favicon.png" />
<link rel="icon" href="static/img/favicon.png" />
<title>MindInsight</title> <title>MindInsight</title>
<style> <style>
.errorInfo { .errorInfo {


+ 24
- 9
mindinsight/ui/src/locales/zh-cn.json View File

@@ -134,7 +134,7 @@
"lessThan": "小于", "lessThan": "小于",
"applyAllSelectTag": "应用到当前所选标签", "applyAllSelectTag": "应用到当前所选标签",
"placeHolderNumber": "请输入数值", "placeHolderNumber": "请输入数值",
"noSpace":"请勿输入空格",
"noSpace": "请勿输入空格",
"sameCompare": "不能有相同的比较运算符", "sameCompare": "不能有相同的比较运算符",
"unreasonable": "逻辑不合理", "unreasonable": "逻辑不合理",
"info": "提示", "info": "提示",
@@ -248,11 +248,9 @@
"suggestions": "优化建议", "suggestions": "优化建议",
"common-profiler_tutorial": { "common-profiler_tutorial": {
"desc": "如何使用Profiler进行性能分析", "desc": "如何使用Profiler进行性能分析",
"anchor": [
"desc"
],
"anchor": ["desc"],
"url": [ "url": [
"https://www.mindspore.cn/tutorial/zh-CN/master/advanced_use/visualization_tutorials.html"
"https://www.mindspore.cn/tutorial/zh-CN/master/advanced_use/performance_profiling.html"
] ]
}, },
"step_trace-proposer_type_label": { "step_trace-proposer_type_label": {
@@ -318,7 +316,7 @@
"pipelineTopTitle": "算子间队列平均使用率", "pipelineTopTitle": "算子间队列平均使用率",
"pipelineMiddleTitle": "算子间队列关系", "pipelineMiddleTitle": "算子间队列关系",
"deviceQueueOp": "数据发送", "deviceQueueOp": "数据发送",
"deviceQueueOpTip": "前向+向",
"deviceQueueOpTip": "前向+向",
"getNext": "取数据算子", "getNext": "取数据算子",
"connectorQuene": "主机队列", "connectorQuene": "主机队列",
"getData": "数据获取", "getData": "数据获取",
@@ -356,9 +354,26 @@
"queueTip2": "队列为空比例:", "queueTip2": "队列为空比例:",
"totalCapacity": "总容量", "totalCapacity": "总容量",
"averageCapacity": "平均使用容量", "averageCapacity": "平均使用容量",
"FPMessage": "FP起始算子:",
"BPMessage": "BP终止算子:",
"approximateTime": "总时长 ≈ "
"FPMessage": "前向起始算子:",
"BPMessage": "反向终止算子:",
"approximateTime": "总时长 ≈ ",
"stepInputTip": "请输入step值(1~{max}的正整数)",
"inputError": "输入参数异常,请输入一个1~{max}的正整数",
"defaultTip": "默认展示平均值",
"downloadTimeline": "下载",
"timelineTips": {
"title1": "时间线功能可以帮您对训练过程进行分析,它可以展示:",
"content11": "- 算子分配到哪个设备 (AI CPU/AI Core) 执行;",
"content12": "- MindSpore对该网络的流切分策略;",
"content13": "- 算子在Device上的执行序列和执行时长。",
"title2": "如何查看时间线:",
"content21": "要查看时间线的详细信息,您可以点击 \"下载\" 按钮将带有时间线信息的文件保存到本地,再通过工具查看。",
"content22": "我们推荐您使用谷歌插件:chrome://tracing,或 Perfetto工具:https://ui.perfetto.dev/#!/viewer",
"content23": "选择一个工具,在浏览器地址栏输入地址并回车,进入页面点击按钮加载文件查看时间线(tracing工具点击左上角 \"load\",Perfetto工具点击左侧栏 \"Open trace file\")。",
"title3": "如何使用时间线:",
"content31": "您可以通过时间线信息分析流切分方法是否合理、迭代间隙和拖尾时间是否过长等;",
"content32": "也可以具体定位到某个算子,查看分析它的执行时间。"
}
}, },
"components": { "components": {
"summaryTitle": "训练选择", "summaryTitle": "训练选择",


+ 6
- 1
mindinsight/ui/src/router.js View File

@@ -16,6 +16,10 @@
import Vue from 'vue'; import Vue from 'vue';
import Router from 'vue-router'; import Router from 'vue-router';
Vue.use(Router); Vue.use(Router);
const VueRouterPush = Router.prototype.push;
Router.prototype.push = function push(to) {
return VueRouterPush.call(this, to).catch((err) => err);
};


export default new Router({ export default new Router({
base: process.env.BASE_URL, base: process.env.BASE_URL,
@@ -77,7 +81,8 @@ export default new Router({
children: [ children: [
{ {
path: 'profiling-dashboard', path: 'profiling-dashboard',
component: () => import('./views/train-manage/profiling-dashboard.vue'),
component: () =>
import('./views/train-manage/profiling-dashboard.vue'),
}, },
{ {
path: 'step-trace', path: 'step-trace',


+ 31
- 23
mindinsight/ui/src/views/train-manage/data-process.vue View File

@@ -70,14 +70,15 @@ limitations under the License.
<div class="cell-container device_queue_op" <div class="cell-container device_queue_op"
@click="highlight('device_queue_op')" @click="highlight('device_queue_op')"
clickKey="device_queue_op" clickKey="device_queue_op"
:style="{cursor: processSummary.count !== processSummary.maxCount ? 'default' : 'pointer'}"
v-show="!processSummary.noData"> v-show="!processSummary.noData">
<div class="title"> <div class="title">
{{$t('profiling.deviceQueueOpTip')}}
{{$t('profiling.deviceQueueOp')}}
</div> </div>
</div> </div>


<div class="queue-container" <div class="queue-container"
v-show="processSummary.count === 6 && !processSummary.noData">
v-show="processSummary.count === processSummary.maxCount && !processSummary.noData">
<div class="img"> <div class="img">
<div class="edge"> <div class="edge">
<img src="@/assets/images/data-flow.png" <img src="@/assets/images/data-flow.png"
@@ -116,7 +117,7 @@ limitations under the License.
<div class="cell-container get-next" <div class="cell-container get-next"
@click="highlight('get_next')" @click="highlight('get_next')"
clickKey="get_next" clickKey="get_next"
v-if="processSummary.count === 6 && !processSummary.noData">
v-if="processSummary.count === processSummary.maxCount && !processSummary.noData">
<div class="title"> <div class="title">
{{$t('profiling.getData')}} {{$t('profiling.getData')}}
</div> </div>
@@ -126,7 +127,7 @@ limitations under the License.
v-if="!(connectQueueChart.noData && dataQueueChart.noData && deviceQueueOpChart v-if="!(connectQueueChart.noData && dataQueueChart.noData && deviceQueueOpChart
&& getNextChart.getNextChart)"> && getNextChart.getNextChart)">
<div class="queue-step-wrap" <div class="queue-step-wrap"
v-if="processSummary.count === 6">
v-if="processSummary.count === processSummary.maxCount">
<div class="title">{{$t('profiling.queueStep')}}</div> <div class="title">{{$t('profiling.queueStep')}}</div>
<div class="chart-content"> <div class="chart-content">
<div class="chart-wrap" <div class="chart-wrap"
@@ -164,7 +165,7 @@ limitations under the License.
</div> </div>
</div> </div>
<div class="queue-step-wrap" <div class="queue-step-wrap"
v-if="processSummary.count === 6">
v-if="processSummary.count === processSummary.maxCount">
<div class="title">{{$t('profiling.operatorTimeConAnalysis')}}</div> <div class="title">{{$t('profiling.operatorTimeConAnalysis')}}</div>
<div class="chart-content second"> <div class="chart-content second">
<div class="chart-wrap analysis" <div class="chart-wrap analysis"
@@ -202,7 +203,7 @@ limitations under the License.
</div> </div>
</div> </div>
<div class="queue-step-wrap single" <div class="queue-step-wrap single"
v-if="processSummary.count !== 6">
v-if="processSummary.count !== processSummary.maxCount">
<div class="title">{{$t('profiling.queueStep')}}</div> <div class="title">{{$t('profiling.queueStep')}}</div>
<div class="chart-content"> <div class="chart-content">
<div class="chart-wrap" <div class="chart-wrap"
@@ -344,6 +345,7 @@ export default {
processSummary: { processSummary: {
noData: true, noData: true,
count: 6, count: 6,
maxCount: 6,
device: { device: {
empty: 0, empty: 0,
full: 0, full: 0,
@@ -530,7 +532,6 @@ export default {
xAxis: { xAxis: {
name: 'step', name: 'step',
data: [], data: [],
max: size,
}, },
yAxis: {}, yAxis: {},
series: [], series: [],
@@ -543,10 +544,13 @@ export default {
}; };
option.dataZoom = [ option.dataZoom = [
{ {
startValue: 0,
start: 0,
end: 100,
bottom: 0, bottom: 0,
}, },
{ {
start: 0,
end: 100,
type: 'inside', type: 'inside',
bottom: 0, bottom: 0,
}, },
@@ -602,7 +606,7 @@ export default {


this.dealProcess(data); this.dealProcess(data);
this.$nextTick(() => { this.$nextTick(() => {
if (this.processSummary.count < 6) {
if (this.processSummary.count < this.processSummary.maxCount) {
this.queryQueueInfo(this.connectQueueChart); this.queryQueueInfo(this.connectQueueChart);
} else { } else {
this.queryQueueInfo(this.connectQueueChart); this.queryQueueInfo(this.connectQueueChart);
@@ -756,11 +760,10 @@ export default {
}, },
], ],
dataZoom: [ dataZoom: [
{start: 0, end: 100, orient: 'vertical', right: 10},
{ {
orient: 'vertical',
right: 10,
},
{
start: 0,
end: 100,
type: 'inside', type: 'inside',
orient: 'vertical', orient: 'vertical',
right: 10, right: 10,
@@ -818,7 +821,6 @@ export default {
data.sample_interval data.sample_interval
}ms`, }ms`,
data: dataY.map((val, index) => index + 1), data: dataY.map((val, index) => index + 1),
max: dataY.length,
}, },
yAxis: { yAxis: {
name: '', name: '',
@@ -840,13 +842,11 @@ export default {
}, },
dataZoom: [ dataZoom: [
{ {
startValue: 0,
bottom: 10,
},
{
type: 'inside',
start: 0,
end: 100,
bottom: 10, bottom: 10,
}, },
{start: 0, end: 100, type: 'inside', bottom: 10},
], ],
}; };
echart.setOption(option); echart.setOption(option);
@@ -855,6 +855,12 @@ export default {
}); });
}, },
highlight(key) { highlight(key) {
if (
key === 'device_queue_op' &&
this.processSummary.count !== this.processSummary.maxCount
) {
return;
}
const domList = document.querySelectorAll('.md-top *[clickKey]'); const domList = document.querySelectorAll('.md-top *[clickKey]');
Array.prototype.forEach.call(domList, (dom) => { Array.prototype.forEach.call(domList, (dom) => {
if (dom.getAttribute('clickKey') === key) { if (dom.getAttribute('clickKey') === key) {
@@ -1344,6 +1350,7 @@ export default {
.chart { .chart {
height: calc(100% - 70px); height: calc(100% - 70px);
min-height: 150px; min-height: 150px;
overflow: hidden;
} }
} }
.chart-wrap.highlight { .chart-wrap.highlight {
@@ -1354,15 +1361,14 @@ export default {
height: calc(100% - 25px); height: calc(100% - 25px);
} }
} }
.queue-step-wrap.single{
.queue-step-wrap.single {
height: 100%; height: 100%;
.chart-content{
.chart-wrap{
.chart-content {
.chart-wrap {
width: 100%; width: 100%;
} }
} }
} }

} }
.pipeline-wrap { .pipeline-wrap {
height: 100%; height: 100%;
@@ -1378,6 +1384,7 @@ export default {
#average-rate { #average-rate {
height: 100%; height: 100%;
min-height: 180px; min-height: 180px;
overflow: hidden;
} }
} }
} }
@@ -1441,6 +1448,7 @@ export default {
height: 100%; height: 100%;
width: 100%; width: 100%;
min-height: 220px; min-height: 220px;
overflow: hidden;
} }
} }
.right { .right {


+ 26
- 24
mindinsight/ui/src/views/train-manage/operator.vue View File

@@ -85,7 +85,7 @@
:property="ele" :property="ele"
:key="key" :key="key"
:sortable="ele === 'op_info' ? false : 'custom'" :sortable="ele === 'op_info' ? false : 'custom'"
:width="(ele==='execution_time'|| ele==='subgraph' ||
:width="(ele==='avg_execution_time (ms)'|| ele==='subgraph' ||
ele==='op_name'|| ele==='op_type')?'220':''" ele==='op_name'|| ele==='op_type')?'220':''"
show-overflow-tooltip show-overflow-tooltip
:label="ele"> :label="ele">
@@ -122,7 +122,7 @@
:key="$index" :key="$index"
:label="item" :label="item"
:sortable="item === 'op_info' ? false : 'custom'" :sortable="item === 'op_info' ? false : 'custom'"
:width="(item==='execution_time'|| item==='subgraph' ||
:width="(item==='avg_execution_time (ms)'|| item==='subgraph' ||
item==='op_name'|| item==='op_type')?'220':''" item==='op_name'|| item==='op_type')?'220':''"
show-overflow-tooltip> show-overflow-tooltip>
</el-table-column> </el-table-column>
@@ -147,10 +147,9 @@
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="AI CPU" <el-tab-pane label="AI CPU"
class="cpu-tab" class="cpu-tab"
name="cpu"
v-if="false">
name="cpu">
<div class="cl-profiler-top" <div class="cl-profiler-top"
v-if="cpuCharts.data.length">
v-if="false">
<div> <div>
<span class="profiler-title"> <span class="profiler-title">
{{ $t('operator.operatorStatistics') }} {{ $t('operator.operatorStatistics') }}
@@ -268,7 +267,7 @@ export default {
pageTotal: 0, pageTotal: 0,
opDetailPage: { opDetailPage: {
offset: 0, offset: 0,
limit: 20,
limit: 15,
}, },
op_filter_condition: {}, op_filter_condition: {},
op_sort_condition: { op_sort_condition: {
@@ -365,7 +364,7 @@ export default {
pageTotal: 0, pageTotal: 0,
opDetailPage: { opDetailPage: {
offset: 0, offset: 0,
limit: 20,
limit: 15,
}, },
op_filter_condition: {}, op_filter_condition: {},
op_sort_condition: { op_sort_condition: {
@@ -498,6 +497,7 @@ export default {
profile: this.profile_dir, profile: this.profile_dir,
train_id: this.train_id, train_id: this.train_id,
}; };
row.op_sort_condition.name = row.op_sort_condition.name.split(' ')[0];
params.body = { params.body = {
op_type: 'aicore_detail', op_type: 'aicore_detail',
device_id: this.currentCard, device_id: this.currentCard,
@@ -528,6 +528,7 @@ export default {
row.op_sort_condition.type, row.op_sort_condition.type,
); );
} }
this.$refs.expandTable.doLayout();
}); });
} }
}) })
@@ -543,6 +544,7 @@ export default {
profile: this.profile_dir, profile: this.profile_dir,
train_id: this.train_id, train_id: this.train_id,
}; };
this.opCpuList.op_sort_condition.name = this.opCpuList.op_sort_condition.name.split(' ')[0];
params.body = { params.body = {
op_type: 'aicpu', op_type: 'aicpu',
device_id: this.currentCard, device_id: this.currentCard,
@@ -571,10 +573,6 @@ export default {
}); });
this.setOption(this.cpuCharts); this.setOption(this.cpuCharts);
} }
if (res.data.object.length > 8) {
this.opCpuList.opDetailPage.limit = 8;
res.data.object.splice(8);
}
this.formatterDetailData(this.opCpuList, res.data); this.formatterDetailData(this.opCpuList, res.data);
if (isSort) { if (isSort) {
this.$nextTick(() => { this.$nextTick(() => {
@@ -625,6 +623,7 @@ export default {
} else { } else {
this.opAllTypeList.op_filter_condition = {}; this.opAllTypeList.op_filter_condition = {};
} }
this.opAllTypeList.opDetailPage.offset = 0;
this.getCoreDetailList(this.opAllTypeList, false); this.getCoreDetailList(this.opAllTypeList, false);
} else { } else {
this.op_filter_condition = {}; this.op_filter_condition = {};
@@ -657,6 +656,7 @@ export default {
} else { } else {
this.opCpuList.op_filter_condition = {}; this.opCpuList.op_filter_condition = {};
} }
this.opCpuList.opDetailPage.offset = 0;
this.getCpuList(false); this.getCpuList(false);
}, },
/** /**
@@ -730,7 +730,10 @@ export default {
row.opDetailCol = []; row.opDetailCol = [];
row.opDetailPage.offset = 0; row.opDetailPage.offset = 0;
row.pageTotal = 0; row.pageTotal = 0;
row.op_sort_condition = {name: 'execution_time', type: 'descending'};
row.op_sort_condition = {
name: 'avg_execution_time',
type: 'descending',
};
this.getCoreDetailList(row, true); this.getCoreDetailList(row, true);
} else { } else {
this.curActiveRow = { this.curActiveRow = {
@@ -769,7 +772,7 @@ export default {
coreTableChange() { coreTableChange() {
if (this.statisticType && !this.opAllTypeList.opDetailCol.length) { if (this.statisticType && !this.opAllTypeList.opDetailCol.length) {
this.opAllTypeList.op_sort_condition = { this.opAllTypeList.op_sort_condition = {
name: 'execution_time',
name: 'avg_execution_time',
type: 'descending', type: 'descending',
}; };
this.getCoreDetailList(this.opAllTypeList, true); this.getCoreDetailList(this.opAllTypeList, true);
@@ -808,7 +811,7 @@ export default {
: chart.data[i].name; : chart.data[i].name;
legendStr = `{a|${i + 1}}{b|${name} ${chart.data[ legendStr = `{a|${i + 1}}{b|${name} ${chart.data[
i i
].value.toFixed(3)}}\n{c|${chart.data[i].percent.toFixed(2)}%}`;
].value.toFixed(6)}}\n{c|${chart.data[i].percent.toFixed(2)}%}`;
} }
} }
return legendStr; return legendStr;
@@ -817,7 +820,7 @@ export default {
itemHeight: 18, itemHeight: 18,
padding: [0, 50, 0, 0], padding: [0, 50, 0, 0],
top: '5%', top: '5%',
left: '37%',
left: '45%',
textStyle: { textStyle: {
padding: [15, 0, 0, 0], padding: [15, 0, 0, 0],
rich: { rich: {
@@ -849,9 +852,9 @@ export default {
option.series = [ option.series = [
{ {
type: 'pie', type: 'pie',
center: ['20%', '60%'],
center: ['25%', '65%'],
data: chart.data, data: chart.data,
radius: '60%',
radius: '50%',
lable: { lable: {
position: 'outer', position: 'outer',
alignTo: 'none', alignTo: 'none',
@@ -874,9 +877,7 @@ export default {
option.tooltip = { option.tooltip = {
trigger: 'axis', trigger: 'axis',
formatter: (params) => { formatter: (params) => {
return `${params[0].axisValue}<br>${
params[0].marker
}${params[0].value.toFixed(4)}`;
return `${params[0].axisValue}<br>${params[0].marker}${params[0].value}`;
}, },
confine: true, confine: true,
}; };
@@ -1094,10 +1095,10 @@ export default {
margin-bottom: 10px; margin-bottom: 10px;
} }
.cl-profiler-top { .cl-profiler-top {
height: 36%;
height: 45%;
} }
.cl-profiler-bottom { .cl-profiler-bottom {
height: 64%;
height: 55%;
padding-top: 10px; padding-top: 10px;
} }
.cpu-tab { .cpu-tab {
@@ -1105,7 +1106,7 @@ export default {
height: calc(36% + 32px); height: calc(36% + 32px);
} }
.cl-profiler-bottom { .cl-profiler-bottom {
height: calc(64% - 32px);
height: 100%;
} }
} }
.profiler-title { .profiler-title {
@@ -1125,7 +1126,8 @@ export default {
width: 100%; width: 100%;
height: 100%; height: 100%;
min-width: 1300px; min-width: 1300px;
min-height: 232px;
min-height: 306px;
overflow: hidden;
} }
} }
.chart-radio-group { .chart-radio-group {


+ 131
- 42
mindinsight/ui/src/views/train-manage/profiling-dashboard.vue View File

@@ -186,12 +186,12 @@ limitations under the License.
<div class="cell-container device_queue_op" <div class="cell-container device_queue_op"
clickKey="device_queue_op"> clickKey="device_queue_op">
<div class="title"> <div class="title">
{{$t('profiling.deviceQueueOpTip')}}
{{$t('profiling.deviceQueueOp')}}
</div> </div>
</div> </div>


<div class="queue-container" <div class="queue-container"
v-if="processSummary.count === 6">
v-if="processSummary.count === processSummary.maxCount">
<div class="img"> <div class="img">
<div class="edge"> <div class="edge">
<img src="@/assets/images/data-flow.png" <img src="@/assets/images/data-flow.png"
@@ -229,7 +229,7 @@ limitations under the License.


<div class="cell-container get-next" <div class="cell-container get-next"
clickKey="get_next" clickKey="get_next"
v-if="processSummary.count === 6">
v-if="processSummary.count === processSummary.maxCount">
<div class="title"> <div class="title">
{{$t('profiling.getData')}} {{$t('profiling.getData')}}
</div> </div>
@@ -292,10 +292,33 @@ limitations under the License.
<div class="title-wrap"> <div class="title-wrap">
<div class="title">{{ $t('profiling.timeLine') }}</div> <div class="title">{{ $t('profiling.timeLine') }}</div>
<div class="view-detail"> <div class="view-detail">
<button @click="toPerfetto()"
:disabled="perfetto.waiting"
:class="{disabled:perfetto.waiting}">{{ $t('profiling.viewDetail') }}
<i class="el-icon-d-arrow-right"></i></button>
<button @click="downloadPerfetto()"
:disabled="timeLine.waiting"
:class="{disabled:timeLine.waiting}">{{ $t('profiling.downloadTimeline') }}
</button>
</div>
<div class="tip-icon">
<el-tooltip placement="bottom"
effect="light">
<div slot="content"
class="tooltip-container">
<div class="font-size-style">{{$t("profiling.features")}}</div>
<div class="font-style">{{$t("profiling.timelineTips.title1")}}</div>
<div>{{$t("profiling.timelineTips.content11")}}</div>
<div>{{$t("profiling.timelineTips.content12")}}</div>
<div>{{$t("profiling.timelineTips.content13")}}</div>
<br>
<div class="font-style">{{$t("profiling.timelineTips.title2")}}</div>
<div>{{$t("profiling.timelineTips.content21")}}</div>
<div>{{$t("profiling.timelineTips.content22")}}</div>
<div>{{$t("profiling.timelineTips.content23")}}</div>
<br>
<div class="font-style">{{$t("profiling.timelineTips.title3")}}</div>
<div>{{$t("profiling.timelineTips.content31")}}</div>
<div>{{$t("profiling.timelineTips.content32")}}</div>
</div>
<i class="el-icon-info"></i>
</el-tooltip>
</div> </div>
</div> </div>
<div class="timeline-info" <div class="timeline-info"
@@ -351,6 +374,7 @@ export default {
totalTime: 0, totalTime: 0,
rowHeight: 60, rowHeight: 60,
markerPadding: 4, markerPadding: 4,
minRate: 0.05,
namespaceURI: 'http://www.w3.org/2000/svg', namespaceURI: 'http://www.w3.org/2000/svg',
resizeTimer: null, resizeTimer: null,
colorList: [ colorList: [
@@ -373,10 +397,8 @@ export default {
topN: [], topN: [],
colorList: ['#6C92FA', '#6CBFFF', '#4EDED2', '#7ADFA0', '#A6DD82'], colorList: ['#6C92FA', '#6CBFFF', '#4EDED2', '#7ADFA0', '#A6DD82'],
}, },
perfetto: {
url: 'https://ui.perfetto.dev/#!',
timeLine: {
data: null, data: null,
delay: 5000,
waiting: true, waiting: true,
}, },
timelineInfo: { timelineInfo: {
@@ -389,6 +411,7 @@ export default {
processSummary: { processSummary: {
noData: true, noData: true,
count: 6, count: 6,
maxCount: 6,
device: { device: {
empty: 0, empty: 0,
full: 0, full: 0,
@@ -650,20 +673,28 @@ export default {
const traceDom = document.querySelector('#trace'); const traceDom = document.querySelector('#trace');
if (traceDom) { if (traceDom) {
this.svg.totalWidth = traceDom.offsetWidth - this.svg.svgPadding * 2; this.svg.totalWidth = traceDom.offsetWidth - this.svg.svgPadding * 2;

if (this.svg.data[0] && this.svg.data[0].length) { if (this.svg.data[0] && this.svg.data[0].length) {
const svg = document.querySelector('#trace svg');
const svg = traceDom.querySelector('svg');
this.svg.totalTime = this.svg.data[0][0].duration; this.svg.totalTime = this.svg.data[0][0].duration;

if (this.svg.totalTime) { if (this.svg.totalTime) {
this.svg.colorIndex = 0; this.svg.colorIndex = 0;
const minTime = this.svg.minRate * this.svg.totalTime;

this.svg.data.forEach((row, index) => { this.svg.data.forEach((row, index) => {
if (row && row.length) { if (row && row.length) {
const dashedLine = this.addDashedLine(index); const dashedLine = this.addDashedLine(index);
svg.insertBefore(dashedLine, svg.querySelector('g')); svg.insertBefore(dashedLine, svg.querySelector('g'));

row.forEach((i) => { row.forEach((i) => {
if (i.duration) { if (i.duration) {
if (i.name) { if (i.name) {
const tempDom = this.createRect(i, index); const tempDom = this.createRect(i, index);
svg.insertBefore(tempDom, svg.querySelector('g'));
const tempStr = `g${
i.duration > minTime ? '' : '.arrow'
}`;
svg.insertBefore(tempDom, svg.querySelector(tempStr));
} else { } else {
const tempDom = this.createArrow(i, index); const tempDom = this.createArrow(i, index);
svg.appendChild(tempDom); svg.appendChild(tempDom);
@@ -690,20 +721,43 @@ export default {
line.setAttribute('style', 'stroke:#E2E2E2;stroke-width:1'); line.setAttribute('style', 'stroke:#E2E2E2;stroke-width:1');
line.setAttribute('stroke-dasharray', '5 5'); line.setAttribute('stroke-dasharray', '5 5');
const g = document.createElementNS(this.svg.namespaceURI, 'g'); const g = document.createElementNS(this.svg.namespaceURI, 'g');
g.setAttribute('class', 'dashedLine');
g.appendChild(line); g.appendChild(line);
return g; return g;
}, },
createRect(data, rowIndex) { createRect(data, rowIndex) {
const color = this.svg.colorList[this.svg.colorIndex++ % 4];
const color = this.svg.colorList[
rowIndex > 1 ? 3 : this.svg.colorIndex++ % 4
];
const height = 40; const height = 40;
const width = (data.duration / this.svg.totalTime) * this.svg.totalWidth; const width = (data.duration / this.svg.totalTime) * this.svg.totalWidth;
const fontSize = 12;
const normalRect = data.duration > this.svg.minRate * this.svg.totalTime;

const x1 = const x1 =
(data.start / this.svg.totalTime) * this.svg.totalWidth + (data.start / this.svg.totalTime) * this.svg.totalWidth +
this.svg.svgPadding; this.svg.svgPadding;
const y1 = const y1 =
rowIndex * this.svg.rowHeight + (this.svg.rowHeight - height) / 2; rowIndex * this.svg.rowHeight + (this.svg.rowHeight - height) / 2;

const g = document.createElementNS(this.svg.namespaceURI, 'g'); const g = document.createElementNS(this.svg.namespaceURI, 'g');
g.setAttribute('class', 'rect');
const gChild = document.createElementNS(this.svg.namespaceURI, 'g'); const gChild = document.createElementNS(this.svg.namespaceURI, 'g');
let name = '';
switch (data.name) {
case 'iteration_interval':
name = this.$t('profiling.lterationGap');
break;
case 'fp_and_bp':
name = this.$t('profiling.deviceQueueOpTip');
break;
case 'tail':
name = this.$t('profiling.lterationTail');
break;
default:
name = data.name;
break;
}


const rect = document.createElementNS(this.svg.namespaceURI, 'rect'); const rect = document.createElementNS(this.svg.namespaceURI, 'rect');
rect.setAttribute('x', x1); rect.setAttribute('x', x1);
@@ -716,19 +770,33 @@ export default {
this.svg.namespaceURI, this.svg.namespaceURI,
'foreignObject', 'foreignObject',
); );
foreignObject.setAttribute('x', x1);
foreignObject.setAttribute('y', y1);
foreignObject.setAttribute('height', height);
foreignObject.textContent = `${name}: ${data.duration.toFixed(4)}ms`;
const textWidth = this.getTextWidth(foreignObject.textContent);

foreignObject.setAttribute(
'x',
normalRect
? x1
: Math.min(
this.svg.svgPadding * 2 + this.svg.totalWidth - textWidth,
Math.max(0, x1 + width / 2 - textWidth / 2),
),
);

foreignObject.setAttribute(
'y',
y1 + (height - fontSize) / 2 + (normalRect ? 0 : fontSize),
);
foreignObject.setAttribute('height', fontSize);
foreignObject.setAttribute('width', width); foreignObject.setAttribute('width', width);
foreignObject.setAttribute('style', `color:${color[0]}`);
foreignObject.setAttribute( foreignObject.setAttribute(
'style',
`overflow:hidden;text-align:center;text-overflow:ellipsis;` +
`white-space:nowrap;font-size:12px;line-height:${height}px;color:${color[0]}`,
'class',
`content${normalRect ? '' : ' content-mini'}`,
); );
foreignObject.textContent = `${data.name}: ${data.duration.toFixed(4)}ms`;


const title = document.createElementNS(this.svg.namespaceURI, 'title'); const title = document.createElementNS(this.svg.namespaceURI, 'title');
title.textContent = `${data.name}: ${data.duration.toFixed(4)}ms`;
title.textContent = `${name}: ${data.duration.toFixed(4)}ms`;


gChild.appendChild(rect); gChild.appendChild(rect);
gChild.appendChild(foreignObject); gChild.appendChild(foreignObject);
@@ -746,6 +814,7 @@ export default {
const x2 = x1 + width - this.svg.markerPadding * 2; const x2 = x1 + width - this.svg.markerPadding * 2;
const y = rowIndex * this.svg.rowHeight + this.svg.rowHeight / 2; const y = rowIndex * this.svg.rowHeight + this.svg.rowHeight / 2;
const g = document.createElementNS(this.svg.namespaceURI, 'g'); const g = document.createElementNS(this.svg.namespaceURI, 'g');
g.setAttribute('class', 'arrow');


const line = document.createElementNS(this.svg.namespaceURI, 'line'); const line = document.createElementNS(this.svg.namespaceURI, 'line');
line.setAttribute('x1', x1); line.setAttribute('x1', x1);
@@ -831,24 +900,6 @@ export default {
} }
return new Uint8Array(arr); return new Uint8Array(arr);
}, },
toPerfetto() {
if (this.perfetto.data) {
const popupwin = window.open(this.perfetto.url);
setTimeout(() => {
const params = {
perfetto: {
title: '',
buffer: this.perfetto.data,
},
};
if (popupwin) {
popupwin.postMessage(params, this.perfetto.url);
}
}, this.perfetto.delay);
} else {
this.perfetto.waiting = true;
}
},
queryTimeline() { queryTimeline() {
const params = { const params = {
dir: this.relativePath, dir: this.relativePath,
@@ -869,18 +920,28 @@ export default {
.catch(() => { .catch(() => {
this.timelineInfo.noData = true; this.timelineInfo.noData = true;
}); });
this.perfetto.waiting = true;
this.timeLine.waiting = true;
RequestService.queryTimeline(params) RequestService.queryTimeline(params)
.then((res) => { .then((res) => {
if (res && res.data) { if (res && res.data) {
this.perfetto.data = this.stringToUint8Array(
this.timeLine.data = this.stringToUint8Array(
JSON.stringify(res.data), JSON.stringify(res.data),
); );
this.perfetto.waiting = false;
this.timeLine.waiting = false;
} }
}) })
.catch(() => {}); .catch(() => {});
}, },
downloadPerfetto() {
const downloadLink = document.createElement('a');
downloadLink.download = this.getDocName();
downloadLink.style.display = 'none';
const blob = new Blob([this.timeLine.data]);
downloadLink.href = URL.createObjectURL(blob);
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);
},
dealProcess(data) { dealProcess(data) {
this.processSummary.device = { this.processSummary.device = {
empty: 0, empty: 0,
@@ -912,6 +973,22 @@ export default {
this.processSummary.noData = false; this.processSummary.noData = false;
} }
}, },
getDocName() {
const dealNumber = (value) => {
const prefix = value < 10 ? '0' : '';
return prefix + value;
};
const date = new Date();
const year = date.getFullYear();
const mouth = dealNumber(date.getMonth() + 1);
const day = dealNumber(date.getDate());
const hour = dealNumber(date.getHours());
const minute = dealNumber(date.getMinutes());
const second = dealNumber(date.getSeconds());
const millisecond = date.getMilliseconds();
const timestamp = `${year}${mouth}${day}${hour}${minute}${second}${millisecond}`;
return `timeline_${this.trainingJobId}_${this.currentCard}_${timestamp}.json`;
},
}, },
destroyed() { destroyed() {
window.removeEventListener('resize', this.resizeTrace, false); window.removeEventListener('resize', this.resizeTrace, false);
@@ -1021,6 +1098,17 @@ export default {
.training-trace { .training-trace {
position: relative; position: relative;
height: 0; height: 0;
.content {
overflow: hidden;
text-align: center;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 12px;
line-height: 12px;
}
.content-mini {
overflow: visible;
}
} }
} }
} }
@@ -1198,6 +1286,7 @@ export default {
.pie-chart { .pie-chart {
width: 100%; width: 100%;
height: 260px; height: 260px;
overflow: hidden;
} }
.image-noData { .image-noData {
width: 100%; width: 100%;


+ 6
- 4
mindinsight/ui/src/views/train-manage/scalar.vue View File

@@ -952,8 +952,10 @@ export default {
} }
if ( if (
addFlag && addFlag &&
parma.value[1] >= sampleObject.zoomData[0] &&
parma.value[1] <= sampleObject.zoomData[1]
Math.ceil(parma.value[1] * 1000) / 1000 >=
sampleObject.zoomData[0] &&
Math.floor(parma.value[1] * 1000) / 1000 <=
sampleObject.zoomData[1]
) { ) {
dataCount++; dataCount++;
runArr.push(parma.seriesName); runArr.push(parma.seriesName);
@@ -1674,7 +1676,7 @@ export default {
tempOption.visualMap['pieces'] && tempOption.visualMap['pieces'] &&
tempOption.visualMap['pieces'].length > 0 tempOption.visualMap['pieces'].length > 0
) { ) {
tempOption.visualMap = false;
tempOption.visualMap = null;
tempOption.series[0].markLine = null; tempOption.series[0].markLine = null;
sampleObject.charObj.setOption(tempOption, true); sampleObject.charObj.setOption(tempOption, true);


@@ -1810,7 +1812,7 @@ export default {
tempCharOption.visualMap['pieces'] && tempCharOption.visualMap['pieces'] &&
tempCharOption.visualMap['pieces'].length > 0 tempCharOption.visualMap['pieces'].length > 0
) { ) {
tempCharOption.visualMap = false;
tempCharOption.visualMap = null;
tempCharOption.series[0].markLine = null; tempCharOption.series[0].markLine = null;
tempCharOption.series[0].lineStyle.color = sampleItem.colors; tempCharOption.series[0].lineStyle.color = sampleItem.colors;
} }


+ 200
- 84
mindinsight/ui/src/views/train-manage/step-trace.vue View File

@@ -16,21 +16,33 @@ limitations under the License.
<template> <template>
<div class="step-trace"> <div class="step-trace">
<div class="step-trace-title">{{$t('profiling.stepTraceDetail')}} <div class="step-trace-title">{{$t('profiling.stepTraceDetail')}}
<el-tooltip class="item"
effect="light"
:content="$t('profiling.defaultTip')"
placement="top">
<span class="el-icon-question"></span>
</el-tooltip>

<div class="pf-content-right"> <div class="pf-content-right">
<div class="input-wrap"> <div class="input-wrap">
<label>{{$t('profiling.stepSelect')}}</label>
<el-select v-model="selectedStep"
filterable
:placeholder="$t('profiling.selectStep')"
@change="changeStep">
<el-option v-for="item in steps"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
<label>{{steps.label}}</label>
<el-input ref="step"
v-model.number="steps.step"
:disabled="steps.disabled"
@blur="resetStep"
@keyup.native.enter="changeStep">
</el-input>
<el-button @click="changeStep"
:disabled="steps.disabled">
{{$t('public.sure')}}
</el-button>
</div> </div>
</div> </div>
<el-button class="show-average"
@click="changeStep(0)"
:disabled="steps.disabled">
{{$t('profiling.showAverage')}}
</el-button>
</div> </div>
<div class="step-message"> <div class="step-message">
<div class="step-left-padding-right"> <div class="step-left-padding-right">
@@ -137,8 +149,13 @@ export default {
relativePath: this.$route.query.path, relativePath: this.$route.query.path,
fp_start: '--', fp_start: '--',
bp_end: '--', bp_end: '--',
steps: [],
selectedStep: '',
steps: {
step: null,
trueStep: null,
max: 0,
disabled: true,
label: this.$t('profiling.stepInputTip'),
},
charts: [], charts: [],
svg: { svg: {
data: [], data: [],
@@ -147,6 +164,7 @@ export default {
totalTime: 0, totalTime: 0,
rowHeight: 60, rowHeight: 60,
markerPadding: 4, markerPadding: 4,
minRate: 0.05,
namespaceURI: 'http://www.w3.org/2000/svg', namespaceURI: 'http://www.w3.org/2000/svg',
resizeTimer: null, resizeTimer: null,
colorList: [ colorList: [
@@ -172,7 +190,7 @@ export default {
percent: 'iteration_interval_percent', percent: 'iteration_interval_percent',
}, },
{ {
name: 'Fp+bp',
name: this.$t('profiling.deviceQueueOpTip'),
id: 'fp-bp', id: 'fp-bp',
timeSummary: {}, timeSummary: {},
rate: 'fp_and_bp', rate: 'fp_and_bp',
@@ -238,17 +256,40 @@ export default {
val.clear(); val.clear();
}); });
} }
this.setStep();
this.steps = {
step: null,
max: 0,
disabled: true,
label: this.$t('profiling.stepInputTip'),
};

this.getTimeInfo('fp-bp', 'fp_and_bp'); this.getTimeInfo('fp-bp', 'fp_and_bp');
this.getTimeInfo('iter-gap', 'iteration_interval'); this.getTimeInfo('iter-gap', 'iteration_interval');
this.getTimeInfo('tailing', 'tail'); this.getTimeInfo('tailing', 'tail');
this.queryTrainingTrace(0); this.queryTrainingTrace(0);
}, },
changeStep(value) { changeStep(value) {
if (value === this.$t('profiling.showAverage')) {
value = 0;
if (value === 0) {
this.steps.step = null;
this.steps.trueStep = null;
this.queryTrainingTrace(0);
} else if (
/^[0-9]*[1-9][0-9]*$/.test(this.steps.step) &&
this.steps.step <= this.steps.max
) {
this.steps.trueStep = this.steps.step;
this.queryTrainingTrace(this.steps.step);
} else {
this.steps.step = this.steps.trueStep;
this.$message.error(
this.$t('profiling.inputError').replace('{max}', this.steps.max),
);
} }
this.queryTrainingTrace(value);
},
resetStep() {
setTimeout(() => {
this.steps.step = this.steps.trueStep;
}, 200);
}, },
getTimeInfo(id, type) { getTimeInfo(id, type) {
const params = { const params = {
@@ -270,8 +311,18 @@ export default {
}); });
} }
if (res.data && res.data.info) { if (res.data && res.data.info) {
if (this.steps.length <= 1) {
this.setStep(res.data.size);
if (res.data.size && !this.steps.max) {
this.steps.max = res.data.size;
this.steps.disabled = false;
this.steps.label = this.steps.label.replace(
'{max}',
this.steps.max,
);
}

const xAxisData = [];
for (let i = 1; i <= this.steps.max; i++) {
xAxisData.push(i);
} }
const timeInfo = []; const timeInfo = [];
Object.keys(res.data.info).forEach((val) => { Object.keys(res.data.info).forEach((val) => {
@@ -285,9 +336,8 @@ export default {
const option = { const option = {
xAxis: { xAxis: {
type: 'category', type: 'category',
data: this.steps.map((val, index) => index + 1),
data: xAxisData,
name: 'step', name: 'step',
max: this.steps.length,
}, },
yAxis: { yAxis: {
type: 'value', type: 'value',
@@ -321,24 +371,23 @@ export default {
option.yAxis.name = `${this.$t( option.yAxis.name = `${this.$t(
'profiling.iterationGapTime', 'profiling.iterationGapTime',
)}(ms)`; )}(ms)`;
this.tabsArr[0].noData = this.steps.length ? false : true;
this.tabsArr[0].noData = this.steps.max ? false : true;
} else if (type === 'fp_and_bp') { } else if (type === 'fp_and_bp') {
option.yAxis.name = `fp+bp${this.$t('profiling.time')}(ms)`;
this.tabsArr[1].noData = this.steps.length ? false : true;
option.yAxis.name = `${this.$t(
'profiling.deviceQueueOpTip',
)}${this.$t('profiling.time')}(ms)`;
this.tabsArr[1].noData = this.steps.max ? false : true;
} else if (type === 'tail') { } else if (type === 'tail') {
option.yAxis.name = `tail${this.$t('profiling.time')}(ms)`;
this.tabsArr[2].noData = this.steps.length ? false : true;
option.yAxis.name = `${this.$t(
'profiling.lterationTail',
)}${this.$t('profiling.time')}(ms)`;
this.tabsArr[2].noData = this.steps.max ? false : true;
} }
this.initChart(option, id); this.initChart(option, id);
} else {
this.steps = [];
this.selectedStep = '';
} }
} }
}, },
(error) => { (error) => {
this.steps = [];
this.selectedStep = '';
if (type === 'iteration_interval') { if (type === 'iteration_interval') {
this.tabsArr[0].noData = true; this.tabsArr[0].noData = true;
} else if (type === 'fp_and_bp') { } else if (type === 'fp_and_bp') {
@@ -349,20 +398,6 @@ export default {
}, },
); );
}, },
setStep(step = 0) {
this.steps = [];
this.steps.push({
label: this.$t('profiling.showAverage'),
value: this.$t('profiling.showAverage'),
});
for (let i = 1; i <= step; i++) {
this.steps.push({
label: i,
value: i,
});
}
this.selectedStep = this.$t('profiling.showAverage');
},
initChart(option, id) { initChart(option, id) {
this.$nextTick(() => { this.$nextTick(() => {
const chart = echarts.init(document.getElementById(id)); const chart = echarts.init(document.getElementById(id));
@@ -430,33 +465,43 @@ export default {
}, },


dealTraceData() { dealTraceData() {
this.svg.totalWidth =
document.querySelector('#trace').offsetWidth - this.svg.svgPadding * 2;
if (this.svg.data[0] && this.svg.data[0].length) {
const svg = document.querySelector('#trace svg');
this.svg.totalTime = this.svg.data[0][0].duration;
if (this.svg.totalTime) {
this.svg.colorIndex = 0;
this.svg.data.forEach((row, index) => {
if (row && row.length) {
const dashedLine = this.addDashedLine(index);
svg.insertBefore(dashedLine, svg.querySelector('g'));
row.forEach((i) => {
if (i.duration) {
if (i.name) {
const tempDom = this.createRect(i, index);
svg.insertBefore(tempDom, svg.querySelector('g'));
} else {
const tempDom = this.createArrow(i, index);
svg.appendChild(tempDom);
const traceDom = document.querySelector('#trace');
if (traceDom) {
this.svg.totalWidth = traceDom.offsetWidth - this.svg.svgPadding * 2;

if (this.svg.data[0] && this.svg.data[0].length) {
const svg = traceDom.querySelector('svg');
this.svg.totalTime = this.svg.data[0][0].duration;

if (this.svg.totalTime) {
this.svg.colorIndex = 0;
const minTime = this.svg.minRate * this.svg.totalTime;

this.svg.data.forEach((row, index) => {
if (row && row.length) {
const dashedLine = this.addDashedLine(index);
svg.insertBefore(dashedLine, svg.querySelector('g'));

row.forEach((i) => {
if (i.duration) {
if (i.name) {
const tempDom = this.createRect(i, index);
const tempStr = `g${
i.duration > minTime ? '' : '.arrow'
}`;
svg.insertBefore(tempDom, svg.querySelector(tempStr));
} else {
const tempDom = this.createArrow(i, index);
svg.appendChild(tempDom);
}
} }
}
});
}
});
});
}
});
}
} else {
this.removeTrace();
} }
} else {
this.removeTrace();
} }
}, },
addDashedLine(index) { addDashedLine(index) {
@@ -471,20 +516,43 @@ export default {
line.setAttribute('style', 'stroke:#E2E2E2;stroke-width:1'); line.setAttribute('style', 'stroke:#E2E2E2;stroke-width:1');
line.setAttribute('stroke-dasharray', '5 5'); line.setAttribute('stroke-dasharray', '5 5');
const g = document.createElementNS(this.svg.namespaceURI, 'g'); const g = document.createElementNS(this.svg.namespaceURI, 'g');
g.setAttribute('class', 'dashedLine');
g.appendChild(line); g.appendChild(line);
return g; return g;
}, },
createRect(data, rowIndex) { createRect(data, rowIndex) {
const color = this.svg.colorList[this.svg.colorIndex++ % 4];
const color = this.svg.colorList[
rowIndex > 1 ? 3 : this.svg.colorIndex++ % 4
];
const height = 40; const height = 40;
const width = (data.duration / this.svg.totalTime) * this.svg.totalWidth; const width = (data.duration / this.svg.totalTime) * this.svg.totalWidth;
const fontSize = 12;
const normalRect = data.duration > this.svg.minRate * this.svg.totalTime;

const x1 = const x1 =
(data.start / this.svg.totalTime) * this.svg.totalWidth + (data.start / this.svg.totalTime) * this.svg.totalWidth +
this.svg.svgPadding; this.svg.svgPadding;
const y1 = const y1 =
rowIndex * this.svg.rowHeight + (this.svg.rowHeight - height) / 2; rowIndex * this.svg.rowHeight + (this.svg.rowHeight - height) / 2;

const g = document.createElementNS(this.svg.namespaceURI, 'g'); const g = document.createElementNS(this.svg.namespaceURI, 'g');
g.setAttribute('class', 'rect');
const gChild = document.createElementNS(this.svg.namespaceURI, 'g'); const gChild = document.createElementNS(this.svg.namespaceURI, 'g');
let name = '';
switch (data.name) {
case 'iteration_interval':
name = this.$t('profiling.lterationGap');
break;
case 'fp_and_bp':
name = this.$t('profiling.deviceQueueOpTip');
break;
case 'tail':
name = this.$t('profiling.lterationTail');
break;
default:
name = data.name;
break;
}


const rect = document.createElementNS(this.svg.namespaceURI, 'rect'); const rect = document.createElementNS(this.svg.namespaceURI, 'rect');
rect.setAttribute('x', x1); rect.setAttribute('x', x1);
@@ -497,19 +565,33 @@ export default {
this.svg.namespaceURI, this.svg.namespaceURI,
'foreignObject', 'foreignObject',
); );
foreignObject.setAttribute('x', x1);
foreignObject.setAttribute('y', y1);
foreignObject.setAttribute('height', height);
foreignObject.textContent = `${name}: ${data.duration.toFixed(4)}ms`;
const textWidth = this.getTextWidth(foreignObject.textContent);

foreignObject.setAttribute(
'x',
normalRect
? x1
: Math.min(
this.svg.svgPadding * 2 + this.svg.totalWidth - textWidth,
Math.max(0, x1 + width / 2 - textWidth / 2),
),
);

foreignObject.setAttribute(
'y',
y1 + (height - fontSize) / 2 + (normalRect ? 0 : fontSize),
);
foreignObject.setAttribute('height', fontSize);
foreignObject.setAttribute('width', width); foreignObject.setAttribute('width', width);
foreignObject.setAttribute('style', `color:${color[0]}`);
foreignObject.setAttribute( foreignObject.setAttribute(
'style',
`overflow:hidden;text-align:center;text-overflow:ellipsis;` +
`white-space:nowrap;font-size:12px;line-height:${height}px;color:${color[0]}`,
'class',
`content${normalRect ? '' : ' content-mini'}`,
); );
foreignObject.textContent = `${data.name}: ${data.duration.toFixed(4)}ms`;


const title = document.createElementNS(this.svg.namespaceURI, 'title'); const title = document.createElementNS(this.svg.namespaceURI, 'title');
title.textContent = `${data.name}: ${data.duration.toFixed(4)}ms`;
title.textContent = `${name}: ${data.duration.toFixed(4)}ms`;


gChild.appendChild(rect); gChild.appendChild(rect);
gChild.appendChild(foreignObject); gChild.appendChild(foreignObject);
@@ -527,6 +609,7 @@ export default {
const x2 = x1 + width - this.svg.markerPadding * 2; const x2 = x1 + width - this.svg.markerPadding * 2;
const y = rowIndex * this.svg.rowHeight + this.svg.rowHeight / 2; const y = rowIndex * this.svg.rowHeight + this.svg.rowHeight / 2;
const g = document.createElementNS(this.svg.namespaceURI, 'g'); const g = document.createElementNS(this.svg.namespaceURI, 'g');
g.setAttribute('class', 'arrow');


const line = document.createElementNS(this.svg.namespaceURI, 'line'); const line = document.createElementNS(this.svg.namespaceURI, 'line');
line.setAttribute('x1', x1); line.setAttribute('x1', x1);
@@ -630,15 +713,36 @@ export default {
padding: 0 15px; padding: 0 15px;
font-size: 16px; font-size: 16px;
font-weight: bold; font-weight: bold;
.el-icon-question {
cursor: pointer;
}
.pf-content-right { .pf-content-right {
display: inline-block; display: inline-block;
margin-left: 35px; margin-left: 35px;
label {
margin-right: 10px;
.input-wrap {
font-weight: normal;
label {
margin-right: 20px;
}
.el-input {
width: 150px;
margin-right: 16px;
}
}
}
.el-button {
border: 1px solid #00a5a7;
border-radius: 2px;
background-color: white;
color: #00a5a7;
padding: 7px 15px;
&:hover {
background: rgb(230, 246, 246);
} }
} }
.input-wrap {
font-weight: normal;
.show-average {
float: right;
margin-right: 20px;
} }
} }
.step-message { .step-message {
@@ -680,6 +784,17 @@ export default {
cursor: pointer; cursor: pointer;
background-image: url('../../assets/images/download.png'); background-image: url('../../assets/images/download.png');
} }
.content {
overflow: hidden;
text-align: center;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 12px;
line-height: 12px;
}
.content-mini {
overflow: visible;
}
} }
} }
.chart-wrap { .chart-wrap {
@@ -698,6 +813,7 @@ export default {
.chart { .chart {
height: calc(100% - 85px); height: calc(100% - 85px);
min-height: 180px; min-height: 180px;
overflow: hidden;
} }
.title { .title {
margin: 0 0 15px 20px; margin: 0 0 15px 20px;


+ 1
- 0
mindinsight/utils/constant.py View File

@@ -70,6 +70,7 @@ class DataVisualErrors(Enum):
SCALAR_NOT_EXIST = 14 SCALAR_NOT_EXIST = 14
HISTOGRAM_NOT_EXIST = 15 HISTOGRAM_NOT_EXIST = 15
TRAIN_JOB_DETAIL_NOT_IN_CACHE = 16 TRAIN_JOB_DETAIL_NOT_IN_CACHE = 16
QUERY_STRING_CONTAINS_NULL_BYTE = 17




class ScriptConverterErrors(Enum): class ScriptConverterErrors(Enum):


+ 22
- 2
tests/st/func/datavisual/taskmanager/test_plugins_restful_api.py View File

@@ -79,9 +79,9 @@ class TestPlugins:
@pytest.mark.platform_x86_gpu_training @pytest.mark.platform_x86_gpu_training
@pytest.mark.platform_x86_ascend_training @pytest.mark.platform_x86_ascend_training
@pytest.mark.usefixtures("init_summary_logs") @pytest.mark.usefixtures("init_summary_logs")
@pytest.mark.parametrize("train_id", ["@#$", "./\x00home", "././/not_exist_id", dict()])
@pytest.mark.parametrize("train_id", ["@#$", "././/not_exist_id", dict()])
def test_plugins_with_special_train_id(self, client, train_id): def test_plugins_with_special_train_id(self, client, train_id):
"""Test passing train_id with special character, null_byte, invalid id, and wrong type."""
"""Test passing train_id with special character, invalid id, and wrong type."""
params = dict(train_id=train_id) params = dict(train_id=train_id)
url = get_url(BASE_URL, params) url = get_url(BASE_URL, params)


@@ -92,6 +92,26 @@ class TestPlugins:
assert response['error_code'] == '50545005' assert response['error_code'] == '50545005'
assert response['error_msg'] == "Train job is not exist. Detail: Can not find the train job in data manager." assert response['error_msg'] == "Train job is not exist. Detail: Can not find the train job in data manager."


@pytest.mark.level1
@pytest.mark.env_single
@pytest.mark.platform_x86_cpu
@pytest.mark.platform_arm_ascend_training
@pytest.mark.platform_x86_gpu_training
@pytest.mark.platform_x86_ascend_training
@pytest.mark.usefixtures("init_summary_logs")
@pytest.mark.parametrize("train_id", ["./\x00home"])
def test_plugins_with_null_byte_train_id(self, client, train_id):
"""Test passing train_id with null_byte."""
params = dict(train_id=train_id, manual_update=True)
url = get_url(BASE_URL, params)

response = client.get(url)
assert response.status_code == 400

response = response.get_json()
assert response['error_code'] == '50545011'
assert "Query string contains null byte error. " in response['error_msg']

@pytest.mark.level1 @pytest.mark.level1
@pytest.mark.env_single @pytest.mark.env_single
@pytest.mark.platform_x86_cpu @pytest.mark.platform_x86_cpu


+ 35
- 34
tests/st/func/profiler/test_op_analyser.py View File

@@ -32,7 +32,8 @@ from tests.ut.profiler import RAW_DATA_BASE


OP_GATHER_V2_INFO = { OP_GATHER_V2_INFO = {
'col_name': [ 'col_name': [
'op_name', 'op_type', 'execution_time', 'subgraph', 'full_op_name', 'op_info'
'op_name', 'op_type', 'avg_execution_time (ms)', 'subgraph', 'full_op_name',
'op_info'
], ],
'object': [ 'object': [
[ [
@@ -124,36 +125,36 @@ class TestOpAnalyser:
def test_query_aicore_type_1(self): def test_query_aicore_type_1(self):
"""Test the function of querying AICORE operator type infomation.""" """Test the function of querying AICORE operator type infomation."""
expect_result = { expect_result = {
'col_name': ['op_type', 'execution_time', 'execution_frequency', 'percent'],
'col_name': ['op_type', 'execution_time (ms)', 'execution_frequency', 'percent'],
'object': [ 'object': [
['UnsortedSegmentSum', 44.60782642857142, 2, 35.28],
['GatherV2', 43.15544147619047, 2, 34.13],
['Slice', 20.376314999999998, 16, 16.12],
['Concat', 5.80845380952381, 4, 4.59],
['Split', 2.7142774761904764, 2, 2.15],
['MatMul', 1.9366814285714287, 15, 1.53],
['Mul', 1.9029486666666666, 32, 1.51],
['StridedSliceGrad', 1.5068342857142858, 2, 1.19],
['TransData', 1.1151575238095237, 30, 0.88],
['ReluGrad', 0.8540685714285714, 5, 0.68],
['Cast', 0.4846848571428572, 15, 0.38],
['ReLU', 0.48328214285714277, 5, 0.38],
['RealDiv', 0.4228071904761905, 15, 0.33],
['StridedSlice', 0.3455687619047618, 2, 0.27],
['Adam', 0.2859357142857143, 11, 0.23],
['BiasAdd', 0.18966285714285713, 5, 0.15],
['BiasAddGrad', 0.07168142857142856, 5, 0.06],
['Tile', 0.04415833333333334, 4, 0.03],
['ReduceSum', 0.030764857142857142, 5, 0.02],
['ApplyFtrl', 0.025453571428571426, 2, 0.02],
['AtomicAddrClean', 0.019368666666666666, 8, 0.02],
['AddN', 0.012836428571428572, 1, 0.01],
['Square', 0.009799333333333334, 1, 0.01],
['SigmoidCrossEntropyWithLogitsGrad', 0.009582142857142859, 2, 0.01],
['TensorAdd', 0.009218380952380952, 3, 0.01],
['SigmoidCrossEntropyWithLogits', 0.004808571428571428, 1, 0.0],
['ReduceMean', 0.004534999999999999, 1, 0.0],
['Assign', 0.0024766666666666665, 2, 0.0],
['UnsortedSegmentSum', 44.607826, 2, 35.28],
['GatherV2', 43.155441, 2, 34.13],
['Slice', 20.376315, 16, 16.12],
['Concat', 5.808454, 4, 4.59],
['Split', 2.714277, 2, 2.15],
['MatMul', 1.936681, 15, 1.53],
['Mul', 1.902949, 32, 1.51],
['StridedSliceGrad', 1.506834, 2, 1.19],
['TransData', 1.115158, 30, 0.88],
['ReluGrad', 0.854069, 5, 0.68],
['Cast', 0.484685, 15, 0.38],
['ReLU', 0.483282, 5, 0.38],
['RealDiv', 0.422807, 15, 0.33],
['StridedSlice', 0.345569, 2, 0.27],
['Adam', 0.285936, 11, 0.23],
['BiasAdd', 0.189663, 5, 0.15],
['BiasAddGrad', 0.071681, 5, 0.06],
['Tile', 0.044158, 4, 0.03],
['ReduceSum', 0.030765, 5, 0.02],
['ApplyFtrl', 0.025454, 2, 0.02],
['AtomicAddrClean', 0.019369, 8, 0.02],
['AddN', 0.012836, 1, 0.01],
['Square', 0.009799, 1, 0.01],
['SigmoidCrossEntropyWithLogitsGrad', 0.009582, 2, 0.01],
['TensorAdd', 0.009218, 3, 0.01],
['SigmoidCrossEntropyWithLogits', 0.004809, 1, 0.0],
['ReduceMean', 0.004535, 1, 0.0],
['Assign', 0.002477, 2, 0.0],
['AssignAdd', 0.001688, 1, 0.0] ['AssignAdd', 0.001688, 1, 0.0]
], ],
'size': 29 'size': 29
@@ -176,10 +177,10 @@ class TestOpAnalyser:
def test_query_aicore_type_2(self): def test_query_aicore_type_2(self):
"""Test the function of querying AICORE operator type infomation.""" """Test the function of querying AICORE operator type infomation."""
expect_result = { expect_result = {
'col_name': ['op_type', 'execution_time', 'execution_frequency', 'percent'],
'col_name': ['op_type', 'execution_time (ms)', 'execution_frequency', 'percent'],
'object': [ 'object': [
['MatMul', 1.9366814285714287, 15, 1.53],
['Mul', 1.9029486666666666, 32, 1.51]
['MatMul', 1.936681, 15, 1.53],
['Mul', 1.902949, 32, 1.51]
], ],
'size': 2 'size': 2
} }
@@ -213,7 +214,7 @@ class TestOpAnalyser:
} }
}, },
'sort_condition': { 'sort_condition': {
'name': 'execution_time',
'name': 'avg_execution_time',
'type': 'descending' 'type': 'descending'
}, },
'group_condition': { 'group_condition': {


+ 22
- 20
tests/ut/profiler/analyser/test_analyser_aicore_detail.py View File

@@ -21,8 +21,10 @@ from unittest import TestCase
from mindinsight.profiler.analyser.analyser_factory import AnalyserFactory from mindinsight.profiler.analyser.analyser_factory import AnalyserFactory
from tests.ut.profiler import PROFILER_DIR from tests.ut.profiler import PROFILER_DIR


COL_NAMES = ['op_name', 'op_type', 'execution_time', 'subgraph', 'full_op_name',
'op_info']
COL_NAMES = ['op_name', 'op_type', 'avg_execution_time', 'subgraph',
'full_op_name', 'op_info']
COL_NAMES_IN_RESULT = ['op_name', 'op_type', 'avg_execution_time (ms)',
'subgraph', 'full_op_name', 'op_info']




def get_detail_infos(indexes=None, sort_name=None, sort_type=True): def get_detail_infos(indexes=None, sort_name=None, sort_type=True):
@@ -75,7 +77,7 @@ class TestAicoreDetailAnalyser(TestCase):
def test_query_success_1(self): def test_query_success_1(self):
"""Test the success of the querying function.""" """Test the success of the querying function."""
expect_result = { expect_result = {
'col_name': COL_NAMES,
'col_name': COL_NAMES_IN_RESULT,
'object': get_detail_infos(), 'object': get_detail_infos(),
'size': 10 'size': 10
} }
@@ -88,7 +90,7 @@ class TestAicoreDetailAnalyser(TestCase):
def test_query_success_2(self): def test_query_success_2(self):
"""Test the success of the querying function.""" """Test the success of the querying function."""
expect_result = { expect_result = {
'col_name': COL_NAMES,
'col_name': COL_NAMES_IN_RESULT,
'object': get_detail_infos(indexes=[9]), 'object': get_detail_infos(indexes=[9]),
'size': 1 'size': 1
} }
@@ -125,13 +127,13 @@ class TestAicoreDetailAnalyser(TestCase):
def test_query_success_3(self): def test_query_success_3(self):
"""Test the success of the querying function.""" """Test the success of the querying function."""
expect_result = { expect_result = {
'col_name': COL_NAMES,
'object': get_detail_infos(sort_name='execution_time', sort_type=True),
'col_name': COL_NAMES_IN_RESULT,
'object': get_detail_infos(sort_name='avg_execution_time', sort_type=True),
'size': 10 'size': 10
} }
condition = { condition = {
'sort_condition': { 'sort_condition': {
'name': 'execution_time',
'name': 'avg_execution_time',
'type': 'descending' 'type': 'descending'
} }
} }
@@ -139,7 +141,7 @@ class TestAicoreDetailAnalyser(TestCase):
self.assertDictEqual(expect_result, result) self.assertDictEqual(expect_result, result)


expect_result = { expect_result = {
'col_name': COL_NAMES,
'col_name': COL_NAMES_IN_RESULT,
'object': get_detail_infos(sort_name='op_name', sort_type=False), 'object': get_detail_infos(sort_name='op_name', sort_type=False),
'size': 10 'size': 10
} }
@@ -155,7 +157,7 @@ class TestAicoreDetailAnalyser(TestCase):
def test_query_success_4(self): def test_query_success_4(self):
"""Test the success of the querying function.""" """Test the success of the querying function."""
expect_result = { expect_result = {
'col_name': COL_NAMES,
'col_name': COL_NAMES_IN_RESULT,
'object': get_detail_infos(indexes=[2, 3]), 'object': get_detail_infos(indexes=[2, 3]),
'size': 10 'size': 10
} }
@@ -169,7 +171,7 @@ class TestAicoreDetailAnalyser(TestCase):
self.assertDictEqual(expect_result, result) self.assertDictEqual(expect_result, result)


expect_result = { expect_result = {
'col_name': COL_NAMES,
'col_name': COL_NAMES_IN_RESULT,
'object': [], 'object': [],
'size': 10 'size': 10
} }
@@ -185,9 +187,9 @@ class TestAicoreDetailAnalyser(TestCase):
def test_query_success_5(self): def test_query_success_5(self):
"""Test the success of the querying function.""" """Test the success of the querying function."""
expect_result = { expect_result = {
'col_name': COL_NAMES,
'col_name': COL_NAMES_IN_RESULT,
'object': get_detail_infos( 'object': get_detail_infos(
indexes=[1, 2], sort_name='execution_time', sort_type=True
indexes=[1, 2], sort_name='avg_execution_time', sort_type=True
), ),
'size': 4 'size': 4
} }
@@ -198,7 +200,7 @@ class TestAicoreDetailAnalyser(TestCase):
} }
}, },
'sort_condition': { 'sort_condition': {
'name': 'execution_time'
'name': 'avg_execution_time'
}, },
'group_condition': { 'group_condition': {
'limit': 2, 'limit': 2,
@@ -209,9 +211,9 @@ class TestAicoreDetailAnalyser(TestCase):
self.assertDictEqual(expect_result, result) self.assertDictEqual(expect_result, result)


expect_result = { expect_result = {
'col_name': COL_NAMES,
'col_name': COL_NAMES_IN_RESULT,
'object': get_detail_infos( 'object': get_detail_infos(
indexes=[0, 1, 2, 8], sort_name='execution_time', sort_type=True
indexes=[0, 1, 2, 8], sort_name='avg_execution_time', sort_type=True
), ),
'size': 4 'size': 4
} }
@@ -225,7 +227,7 @@ class TestAicoreDetailAnalyser(TestCase):
} }
}, },
'sort_condition': { 'sort_condition': {
'name': 'execution_time'
'name': 'avg_execution_time'
} }
} }
result = self._analyser.query(condition) result = self._analyser.query(condition)
@@ -236,7 +238,7 @@ class TestAicoreDetailAnalyser(TestCase):
detail_infos = get_detail_infos(indexes=[9]) detail_infos = get_detail_infos(indexes=[9])


expect_result = { expect_result = {
'col_name': COL_NAMES[0:5],
'col_name': COL_NAMES_IN_RESULT[0:5],
'object': [item[0:5] for item in detail_infos], 'object': [item[0:5] for item in detail_infos],
'size': 1 'size': 1
} }
@@ -252,7 +254,7 @@ class TestAicoreDetailAnalyser(TestCase):
self.assertDictEqual(expect_result, result) self.assertDictEqual(expect_result, result)


expect_result = { expect_result = {
'col_name': COL_NAMES[0:4],
'col_name': COL_NAMES_IN_RESULT[0:4],
'object': [item[0:4] for item in detail_infos], 'object': [item[0:4] for item in detail_infos],
'size': 1 'size': 1
} }
@@ -272,7 +274,7 @@ class TestAicoreDetailAnalyser(TestCase):
"""Test the success of the querying and sorting function by operator type.""" """Test the success of the querying and sorting function by operator type."""
detail_infos = get_detail_infos(indexes=[9, 0, 2, 1, 5, 3, 4]) detail_infos = get_detail_infos(indexes=[9, 0, 2, 1, 5, 3, 4])
expect_result = { expect_result = {
'col_name': COL_NAMES[0:4],
'col_name': COL_NAMES_IN_RESULT[0:4],
'object': [item[0:4] for item in detail_infos] 'object': [item[0:4] for item in detail_infos]
} }


@@ -294,7 +296,7 @@ class TestAicoreDetailAnalyser(TestCase):
"""Test the success of the querying and sorting function by operator type.""" """Test the success of the querying and sorting function by operator type."""
detail_infos = get_detail_infos(indexes=[9, 0, 2, 1, 3, 4, 8, 6]) detail_infos = get_detail_infos(indexes=[9, 0, 2, 1, 3, 4, 8, 6])
expect_result = { expect_result = {
'col_name': COL_NAMES[0:4],
'col_name': COL_NAMES_IN_RESULT[0:4],
'object': [item[0:4] for item in detail_infos] 'object': [item[0:4] for item in detail_infos]
} }




+ 14
- 12
tests/ut/profiler/analyser/test_analyser_aicore_type.py View File

@@ -21,6 +21,8 @@ from mindinsight.profiler.analyser.analyser_factory import AnalyserFactory
from tests.ut.profiler import PROFILER_DIR from tests.ut.profiler import PROFILER_DIR


COL_NAMES = ['op_type', 'execution_time', 'execution_frequency', 'percent'] COL_NAMES = ['op_type', 'execution_time', 'execution_frequency', 'percent']
COL_NAMES_IN_RESULT = ['op_type', 'execution_time (ms)', 'execution_frequency',
'percent']




def get_type_infos(indexes=None, sort_name=None, sort_type=True): def get_type_infos(indexes=None, sort_name=None, sort_type=True):
@@ -72,7 +74,7 @@ class TestAicoreTypeAnalyser(TestCase):
def test_query_success_1(self): def test_query_success_1(self):
"""Test the success of the querying function.""" """Test the success of the querying function."""
expect_result = { expect_result = {
'col_name': COL_NAMES,
'col_name': COL_NAMES_IN_RESULT,
'object': get_type_infos(), 'object': get_type_infos(),
'size': 5 'size': 5
} }
@@ -86,7 +88,7 @@ class TestAicoreTypeAnalyser(TestCase):
def test_query_success_2(self): def test_query_success_2(self):
"""Test the success of the querying function.""" """Test the success of the querying function."""
expect_result = { expect_result = {
'col_name': COL_NAMES,
'col_name': COL_NAMES_IN_RESULT,
'object': get_type_infos(indexes=[1]), 'object': get_type_infos(indexes=[1]),
'size': 1 'size': 1
} }
@@ -101,7 +103,7 @@ class TestAicoreTypeAnalyser(TestCase):
self.assertDictEqual(expect_result, result) self.assertDictEqual(expect_result, result)


expect_result = { expect_result = {
'col_name': COL_NAMES,
'col_name': COL_NAMES_IN_RESULT,
'object': get_type_infos(indexes=[0, 2, 3, 4]), 'object': get_type_infos(indexes=[0, 2, 3, 4]),
'size': 4 'size': 4
} }
@@ -116,7 +118,7 @@ class TestAicoreTypeAnalyser(TestCase):
self.assertDictEqual(expect_result, result) self.assertDictEqual(expect_result, result)


expect_result = { expect_result = {
'col_name': COL_NAMES,
'col_name': COL_NAMES_IN_RESULT,
'object': get_type_infos(indexes=[0, 1, 3]), 'object': get_type_infos(indexes=[0, 1, 3]),
'size': 3 'size': 3
} }
@@ -133,7 +135,7 @@ class TestAicoreTypeAnalyser(TestCase):
def test_query_success_3(self): def test_query_success_3(self):
"""Test the success of the querying function.""" """Test the success of the querying function."""
expect_result = { expect_result = {
'col_name': COL_NAMES,
'col_name': COL_NAMES_IN_RESULT,
'object': get_type_infos(indexes=[1, 3]), 'object': get_type_infos(indexes=[1, 3]),
'size': 2 'size': 2
} }
@@ -148,7 +150,7 @@ class TestAicoreTypeAnalyser(TestCase):
self.assertDictEqual(expect_result, result) self.assertDictEqual(expect_result, result)


expect_result = { expect_result = {
'col_name': COL_NAMES,
'col_name': COL_NAMES_IN_RESULT,
'object': get_type_infos(indexes=[0, 2, 4]), 'object': get_type_infos(indexes=[0, 2, 4]),
'size': 3 'size': 3
} }
@@ -163,7 +165,7 @@ class TestAicoreTypeAnalyser(TestCase):
self.assertDictEqual(expect_result, result) self.assertDictEqual(expect_result, result)


expect_result = { expect_result = {
'col_name': COL_NAMES,
'col_name': COL_NAMES_IN_RESULT,
'object': get_type_infos(indexes=[2, 3]), 'object': get_type_infos(indexes=[2, 3]),
'size': 2 'size': 2
} }
@@ -180,7 +182,7 @@ class TestAicoreTypeAnalyser(TestCase):
def test_query_success_4(self): def test_query_success_4(self):
"""Test the success of the querying function.""" """Test the success of the querying function."""
expect_result = { expect_result = {
'col_name': COL_NAMES,
'col_name': COL_NAMES_IN_RESULT,
'object': get_type_infos(sort_name='op_type', sort_type=True), 'object': get_type_infos(sort_name='op_type', sort_type=True),
'size': 5} 'size': 5}
condition = { condition = {
@@ -193,7 +195,7 @@ class TestAicoreTypeAnalyser(TestCase):
self.assertDictEqual(expect_result, result) self.assertDictEqual(expect_result, result)


expect_result = { expect_result = {
'col_name': COL_NAMES,
'col_name': COL_NAMES_IN_RESULT,
'object': get_type_infos(sort_name='execution_time', sort_type=False), 'object': get_type_infos(sort_name='execution_time', sort_type=False),
'size': 5 'size': 5
} }
@@ -209,7 +211,7 @@ class TestAicoreTypeAnalyser(TestCase):
def test_query_success_5(self): def test_query_success_5(self):
"""Test the success of the querying function.""" """Test the success of the querying function."""
expect_result = { expect_result = {
'col_name': COL_NAMES,
'col_name': COL_NAMES_IN_RESULT,
'object': get_type_infos(indexes=[0, 1]), 'object': get_type_infos(indexes=[0, 1]),
'size': 5 'size': 5
} }
@@ -223,7 +225,7 @@ class TestAicoreTypeAnalyser(TestCase):
self.assertDictEqual(expect_result, result) self.assertDictEqual(expect_result, result)


expect_result = { expect_result = {
'col_name': COL_NAMES,
'col_name': COL_NAMES_IN_RESULT,
'object': get_type_infos(indexes=[3, 4]), 'object': get_type_infos(indexes=[3, 4]),
'size': 5 'size': 5
} }
@@ -239,7 +241,7 @@ class TestAicoreTypeAnalyser(TestCase):
def test_query_success_6(self): def test_query_success_6(self):
"""Test the success of the querying function.""" """Test the success of the querying function."""
expect_result = { expect_result = {
'col_name': COL_NAMES,
'col_name': COL_NAMES_IN_RESULT,
'object': get_type_infos( 'object': get_type_infos(
indexes=[1, 3], sort_name='execution_time', sort_type=True indexes=[1, 3], sort_name='execution_time', sort_type=True
), ),


+ 5
- 5
tests/utils/resource/JOB_AICPU/expect/output_data_preprocess_aicpu_0.txt View File

@@ -1,6 +1,6 @@
serial_number node_type_name total_time(us) dispatch_time(us) run_start run_end
serial_number node_type_name total_time(ms) dispatch_time(ms) run_start run_end
---------------------- ---------------- ---------------- ------------------- ----------- ---------- ---------------------- ---------------- ---------------- ------------------- ----------- ----------
1 InitData 1567 100 2298200409 2298200538
2 GetNext 989 87 2302769932 2302769980
3 TruncatedNormal 1566 105 4098200409 4098200538
AI CPU Total Time(us): 4122
1 InitData 1.567 0.1 2298200409 2298200538
2 GetNext 0.989 0.087 2302769932 2302769980
3 TruncatedNormal 1.566 0.105 4098200409 4098200538
AI CPU Total Time(ms): 4.122000

Loading…
Cancel
Save