You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

test_minddata_analyzer.py 12 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. # Copyright 2021 Huawei Technologies Co., Ltd
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. # ==============================================================================
  15. """
  16. Test MindData Profiling Analyzer Support
  17. """
  18. import csv
  19. import json
  20. import os
  21. import numpy as np
  22. import mindspore.common.dtype as mstype
  23. import mindspore.dataset as ds
  24. import mindspore.dataset.transforms.c_transforms as C
  25. from mindspore.profiler.parser.minddata_analyzer import MinddataProfilingAnalyzer
  26. class TestMinddataProfilingAnalyzer():
  27. """
  28. Test the MinddataProfilingAnalyzer class
  29. """
  30. def setup_class(self):
  31. """
  32. Run once for the class
  33. """
  34. # Define filenames and path used for the MinddataProfilingAnalyzer tests. Use device_id=7.
  35. self._PIPELINE_FILE = "./pipeline_profiling_7.json"
  36. self._CPU_UTIL_FILE = "./minddata_cpu_utilization_7.json"
  37. self._DATASET_ITERATOR_FILE = "./dataset_iterator_profiling_7.txt"
  38. self._SUMMARY_JSON_FILE = "./minddata_pipeline_summary_7.json"
  39. self._SUMMARY_CSV_FILE = "./minddata_pipeline_summary_7.csv"
  40. self._ANALYZE_FILE_PATH = "./"
  41. # These are the minimum subset of expected keys (in alphabetical order) in the MindData Analyzer summary output
  42. # This is the set of keys for success case
  43. self._EXPECTED_SUMMARY_KEYS_SUCCESS = \
  44. ['avg_cpu_pct', 'avg_cpu_pct_per_worker', 'children_ids', 'num_workers', 'op_ids', 'op_names',
  45. 'parent_id', 'per_batch_time', 'per_pipeline_time', 'per_push_queue_time', 'pipeline_ops',
  46. 'queue_average_size', 'queue_empty_freq_pct', 'queue_utilization_pct']
  47. # This is the set of keys for the case which omits the keys for composite computation of more than one raw file.
  48. # This is used for the invalid user case in which the number of ops in the pipeline file does not match
  49. # the number of ops in the CPU utilization file.
  50. self._EXPECTED_SUMMARY_KEYS_OMIT_COMPOSITE = \
  51. ['avg_cpu_pct', 'children_ids', 'num_workers', 'op_ids', 'op_names',
  52. 'parent_id', 'per_batch_time', 'per_pipeline_time', 'per_push_queue_time', 'pipeline_ops',
  53. 'queue_average_size', 'queue_empty_freq_pct', 'queue_utilization_pct']
  54. def setup_method(self):
  55. """
  56. Run before each test function.
  57. """
  58. # Confirm MindData Profiling files do not yet exist
  59. assert os.path.exists(self._PIPELINE_FILE) is False
  60. assert os.path.exists(self._CPU_UTIL_FILE) is False
  61. assert os.path.exists(self._DATASET_ITERATOR_FILE) is False
  62. # Confirm MindData Profiling analyze summary files do not yet exist
  63. assert os.path.exists(self._SUMMARY_JSON_FILE) is False
  64. assert os.path.exists(self._SUMMARY_CSV_FILE) is False
  65. # Set the MindData Profiling environment variables
  66. os.environ['PROFILING_MODE'] = 'true'
  67. os.environ['MINDDATA_PROFILING_DIR'] = '.'
  68. os.environ['RANK_ID'] = '7'
  69. def teardown_method(self):
  70. """
  71. Run after each test function.
  72. """
  73. # Delete MindData profiling files generated from the test.
  74. os.remove(self._PIPELINE_FILE)
  75. os.remove(self._CPU_UTIL_FILE)
  76. os.remove(self._DATASET_ITERATOR_FILE)
  77. # Delete MindData profiling analyze summary files generated from the test.
  78. os.remove(self._SUMMARY_JSON_FILE)
  79. os.remove(self._SUMMARY_CSV_FILE)
  80. # Disable MindData Profiling environment variables
  81. del os.environ['PROFILING_MODE']
  82. del os.environ['MINDDATA_PROFILING_DIR']
  83. del os.environ['RANK_ID']
  84. def get_csv_result(self, file_pathname):
  85. """
  86. Get result from the CSV file.
  87. Args:
  88. file_pathname (str): The CSV file pathname.
  89. Returns:
  90. list[list], the parsed CSV information.
  91. """
  92. result = []
  93. with open(file_pathname, 'r') as csvfile:
  94. csv_reader = csv.reader(csvfile)
  95. for row in csv_reader:
  96. result.append(row)
  97. return result
  98. def verify_md_summary(self, md_summary_dict, EXPECTED_SUMMARY_KEYS):
  99. """
  100. Verify the content of the 3 variations of the MindData Profiling analyze summary output.
  101. """
  102. # Confirm MindData Profiling analyze summary files are created
  103. assert os.path.exists(self._SUMMARY_JSON_FILE) is True
  104. assert os.path.exists(self._SUMMARY_CSV_FILE) is True
  105. # Build a list of the sorted returned keys
  106. summary_returned_keys = list(md_summary_dict.keys())
  107. summary_returned_keys.sort()
  108. # 1. Confirm expected keys are in returned keys
  109. for k in EXPECTED_SUMMARY_KEYS:
  110. assert k in summary_returned_keys
  111. # Read summary JSON file
  112. with open(self._SUMMARY_JSON_FILE) as f:
  113. summary_json_data = json.load(f)
  114. # Build a list of the sorted JSON keys
  115. summary_json_keys = list(summary_json_data.keys())
  116. summary_json_keys.sort()
  117. # 2a. Confirm expected keys are in JSON file keys
  118. for k in EXPECTED_SUMMARY_KEYS:
  119. assert k in summary_json_keys
  120. # 2b. Confirm returned dictionary keys are identical to JSON file keys
  121. np.testing.assert_array_equal(summary_returned_keys, summary_json_keys)
  122. # Read summary CSV file
  123. summary_csv_data = self.get_csv_result(self._SUMMARY_CSV_FILE)
  124. # Build a list of the sorted CSV keys from the first column in the CSV file
  125. summary_csv_keys = []
  126. for x in summary_csv_data:
  127. summary_csv_keys.append(x[0])
  128. summary_csv_keys.sort()
  129. # 3a. Confirm expected keys are in the first column of the CSV file
  130. for k in EXPECTED_SUMMARY_KEYS:
  131. assert k in summary_csv_keys
  132. # 3b. Confirm returned dictionary keys are identical to CSV file first column keys
  133. np.testing.assert_array_equal(summary_returned_keys, summary_csv_keys)
  134. def mysource(self):
  135. """Source for data values"""
  136. for i in range(8000):
  137. yield (np.array([i]),)
  138. def test_analyze_basic(self):
  139. """
  140. Test MindData profiling analyze summary files exist with basic pipeline.
  141. Also test basic content (subset of keys and values) from the returned summary result.
  142. """
  143. # Create this basic and common linear pipeline
  144. # Generator -> Map -> Batch -> Repeat -> EpochCtrl
  145. data1 = ds.GeneratorDataset(self.mysource, ["col1"])
  146. type_cast_op = C.TypeCast(mstype.int32)
  147. data1 = data1.map(operations=type_cast_op, input_columns="col1")
  148. data1 = data1.batch(16)
  149. data1 = data1.repeat(2)
  150. num_iter = 0
  151. # Note: If create_tuple_iterator() is called with num_epochs>1, then EpochCtrlOp is added to the pipeline
  152. for _ in data1.create_dict_iterator(num_epochs=2):
  153. num_iter = num_iter + 1
  154. # Confirm number of rows returned
  155. assert num_iter == 1000
  156. # Confirm MindData Profiling files are created
  157. assert os.path.exists(self._PIPELINE_FILE) is True
  158. assert os.path.exists(self._CPU_UTIL_FILE) is True
  159. assert os.path.exists(self._DATASET_ITERATOR_FILE) is True
  160. # Call MindData Analyzer for generated MindData profiling files to generate MindData pipeline summary result
  161. md_analyzer = MinddataProfilingAnalyzer(self._ANALYZE_FILE_PATH, 7, self._ANALYZE_FILE_PATH)
  162. md_summary_dict = md_analyzer.analyze()
  163. # Verify MindData Profiling Analyze Summary output
  164. # Note: MindData Analyzer returns the result in 3 formats:
  165. # 1. returned dictionary
  166. # 2. JSON file
  167. # 3. CSV file
  168. self.verify_md_summary(md_summary_dict, self._EXPECTED_SUMMARY_KEYS_SUCCESS)
  169. # 4. Verify non-variant values or number of values in the tested pipeline for certain keys
  170. # of the returned dictionary
  171. # Note: Values of num_workers are not tested since default may change in the future
  172. # Note: Values related to queue metrics are not tested since they may vary on different execution environments
  173. assert md_summary_dict["pipeline_ops"] == ["EpochCtrl(id=0)", "Repeat(id=1)", "Batch(id=2)", "Map(id=3)",
  174. "Generator(id=4)"]
  175. assert md_summary_dict["op_names"] == ["EpochCtrl", "Repeat", "Batch", "Map", "Generator"]
  176. assert md_summary_dict["op_ids"] == [0, 1, 2, 3, 4]
  177. assert len(md_summary_dict["num_workers"]) == 5
  178. assert len(md_summary_dict["queue_average_size"]) == 5
  179. assert len(md_summary_dict["queue_utilization_pct"]) == 5
  180. assert len(md_summary_dict["queue_empty_freq_pct"]) == 5
  181. assert md_summary_dict["children_ids"] == [[1], [2], [3], [4], []]
  182. assert md_summary_dict["parent_id"] == [-1, 0, 1, 2, 3]
  183. assert len(md_summary_dict["avg_cpu_pct"]) == 5
  184. def test_analyze_sequential_pipelines_invalid(self):
  185. """
  186. Test invalid scenario in which MinddataProfilingAnalyzer is called for two sequential pipelines.
  187. """
  188. # Create the pipeline
  189. # Generator -> Map -> Batch -> EpochCtrl
  190. data1 = ds.GeneratorDataset(self.mysource, ["col1"])
  191. type_cast_op = C.TypeCast(mstype.int32)
  192. data1 = data1.map(operations=type_cast_op, input_columns="col1")
  193. data1 = data1.batch(64)
  194. # Phase 1 - For the pipeline, call create_tuple_iterator with num_epochs>1
  195. # Note: This pipeline has 4 ops: Generator -> Map -> Batch -> EpochCtrl
  196. num_iter = 0
  197. # Note: If create_tuple_iterator() is called with num_epochs>1, then EpochCtrlOp is added to the pipeline
  198. for _ in data1.create_dict_iterator(num_epochs=2):
  199. num_iter = num_iter + 1
  200. # Confirm number of rows returned
  201. assert num_iter == 125
  202. # Confirm MindData Profiling files are created
  203. assert os.path.exists(self._PIPELINE_FILE) is True
  204. assert os.path.exists(self._CPU_UTIL_FILE) is True
  205. assert os.path.exists(self._DATASET_ITERATOR_FILE) is True
  206. # Phase 2 - For the pipeline, call create_tuple_iterator with num_epochs=1
  207. # Note: This pipeline has 3 ops: Generator -> Map -> Batch
  208. num_iter = 0
  209. # Note: If create_tuple_iterator() is called with num_epochs=1, then EpochCtrlOp is NOT added to the pipeline
  210. for _ in data1.create_dict_iterator(num_epochs=1):
  211. num_iter = num_iter + 1
  212. # Confirm number of rows returned
  213. assert num_iter == 125
  214. # Confirm MindData Profiling files are created
  215. # Note: There is an MD bug in which which the pipeline file is not recreated;
  216. # it still has 4 ops instead of 3 ops
  217. assert os.path.exists(self._PIPELINE_FILE) is True
  218. assert os.path.exists(self._CPU_UTIL_FILE) is True
  219. assert os.path.exists(self._DATASET_ITERATOR_FILE) is True
  220. # Call MindData Analyzer for generated MindData profiling files to generate MindData pipeline summary result
  221. md_analyzer = MinddataProfilingAnalyzer(self._ANALYZE_FILE_PATH, 7, self._ANALYZE_FILE_PATH)
  222. md_summary_dict = md_analyzer.analyze()
  223. # Verify MindData Profiling Analyze Summary output
  224. # Use self._EXPECTED_SUMMARY_KEYS_OMIT_COMPOSITE, since composite keys are not produced, since there is a mismatch
  225. # between the 4 ops in the stale pipeline file versus the 3 ops in the recreated cpu util file
  226. self.verify_md_summary(md_summary_dict, self._EXPECTED_SUMMARY_KEYS_OMIT_COMPOSITE)
  227. # Confirm pipeline data wrongly contains info for 4 ops
  228. assert md_summary_dict["pipeline_ops"] == ["EpochCtrl(id=0)", "Batch(id=1)", "Map(id=2)",
  229. "Generator(id=3)"]
  230. # Verify CPU util data contains info for only 3 ops
  231. assert len(md_summary_dict["avg_cpu_pct"]) == 3