3 The flang plugin ``flang-omp-report`` takes one fortran
4 file in and returns a YAML report file of the input file.
5 This becomes an issue when you want to analyse an entire project
7 The purpose of this Python script is to generate a final YAML
8 summary from all of the files generated by ``flang-omp-report``.
10 Currently, it requires ``ruamel.yaml``,
11 which can be installed with:
13 ``pip3 install ruamel.yaml``
15 By default it scans the directory it is ran in
16 for any YAML files and outputs a summary to
17 stdout. It can be ran as:
19 ``python3 yaml_summarizer.py``
23 -d --directory Specify which directory to scan. Multiple directories can be searched by
24 providing a semicolon seperated list of directories.
26 -l --log Combine all yaml files into one log (instead of generating a summary)
28 -o --output Specify a directory in which to save the summary file
30 -r --recursive Recursively search directory for all yaml files
34 ``python3 yaml_summarizer.py -d ~/llvm-project/build/ -r``
36 ``python3 yaml_summarizer.py -d "~/llvm-project/build/;~/llvm-project/flang/test/Examples"``
38 ``python3 yaml_summarizer.py -l -o ~/examples/report.yaml``
44 $ python3 yaml_summarizer.py file_1.yaml file_2.yaml
45 <Unique OMP constructs with there grouped clauses from file_1.yaml and file_2.yaml>
47 Construcsts are in the form:
48 - construct: someOMPconstruct
58 $ python3 yaml_summarizer.py -l file_1.yaml file_2.yaml
60 <OMP clauses and constructs from file_1.yaml>
62 <OMP clauses and constructs from file_2.yaml>
64 Constructs are in the form:
65 - construct: someOMPConstruct
69 details: 'someDetailForClause'
75 from pathlib
import Path
76 from os
.path
import isdir
78 from ruamel
.yaml
import YAML
80 def find_yaml_files(search_directory
: Path
, search_pattern
: str):
82 Find all '.yaml' files and returns an iglob iterator to them.
85 search_pattern -- Search pattern for 'iglob' to use for finding '.yaml' files.
86 If this is set to 'None', then it will default to just searching
87 for all '.yaml' files in the current directory.
89 # @TODO: Currently *all* yaml files are read - regardless of whether they have
90 # been generated with 'flang-omp-report' or not. This might result in the script
91 # reading files that it should ignore.
93 return glob
.iglob(str(search_directory
.joinpath(search_pattern
)), recursive
=True)
95 return glob
.iglob(str("/" + search_pattern
), recursive
=True)
97 def process_log(data
, result
: list):
99 Process the data input as a 'log' to the result array. This esssentially just
100 stitches together all of the input '.yaml' files into one result.
103 data -- Data from yaml.load() for a yaml file. So the type can be 'Any'.
104 result -- Array to add the processed data to.
107 items
= result
.get(datum
['file'], [])
108 items
.append({"construct" : datum
['construct'],
109 "line" : datum
['line'],
110 "clauses" : datum
['clauses']})
111 result
[datum
['file']] = items
113 def add_clause(datum
, construct
):
115 Add clauses to the construct if they're missing
116 Otherwise increment their count by one.
119 datum -- Data construct containing clauses to check.
120 construct -- Construct to add or increment clause count.
122 to_check
= [i
['clause'] for i
in construct
['clauses']]
123 to_add
= [i
['clause'] for i
in datum
['clauses']]
124 clauses
= construct
["clauses"]
127 for clause
in clauses
:
128 if clause
["clause"] == item
:
131 clauses
.append({"clause" : item
,
134 def process_summary(data
, result
: dict):
136 Process the data input as a 'summary' to the 'result' dictionary.
139 data -- Data from yaml.load() for a yaml file. So the type can be 'Any'.
140 result -- Dictionary to add the processed data to.
143 construct
= next((item
for item
in result
144 if item
["construct"] == datum
["construct"]), None)
146 # Add the construct and clauses to the summary if
147 # they haven't been seen before
149 for i
in datum
['clauses']:
150 clauses
.append({"clause" : i
['clause'],
152 result
.append({"construct" : datum
['construct'],
154 "clauses" : clauses
})
156 construct
["count"] += 1
158 add_clause(datum
, construct
)
160 def clean_summary(result
):
161 """ Cleans the result after processing the yaml files with summary format."""
162 # Remove all "clauses" that are empty to keep things compact
163 for construct
in result
:
164 if construct
["clauses"] == []:
165 construct
.pop("clauses")
167 def clean_log(result
):
168 """ Cleans the result after processing the yaml files with log format."""
169 for constructs
in result
.values():
170 for construct
in constructs
:
171 if construct
["clauses"] == []:
172 construct
.pop("clauses")
174 def output_result(yaml
: YAML
, result
, output_file
: Path
):
176 Outputs result to either 'stdout' or to a output file.
179 result -- Format result to output.
180 output_file -- File to output result to. If this is 'None' then result will be
181 outputted to 'stdout'.
184 with
open(output_file
, 'w+', encoding
='utf-8') as file:
185 if output_file
.suffix
== ".yaml":
186 yaml
.dump(result
, file)
190 yaml
.dump(result
, sys
.stdout
)
192 def process_yaml(search_directories
: list, search_pattern
: str,
193 result_format
: str, output_file
: Path
):
195 Reads each yaml file, calls the appropiate format function for
196 the file and then ouputs the result to either 'stdout' or to an output file.
199 search_directories -- List of directory paths to search for '.yaml' files in.
200 search_pattern -- String pattern formatted for use with glob.iglob to find all
202 result_format -- String representing output format. Current supported strings are: 'log'.
203 output_file -- Path to output file (If value is None, then default to outputting to 'stdout').
205 if result_format
== "log":
208 clean_report
= clean_log
211 action
= process_summary
212 clean_report
= clean_summary
216 for search_directory
in search_directories
:
217 for file in find_yaml_files(search_directory
, search_pattern
):
218 with
open(file, "r", encoding
='utf-8') as yaml_file
:
219 data
= yaml
.load(yaml_file
)
222 if clean_report
is not None:
225 output_result(yaml
, result
, output_file
)
227 def create_arg_parser():
228 """ Create and return a argparse.ArgumentParser modified for script. """
229 parser
= argparse
.ArgumentParser()
230 parser
.add_argument("-d", "--directory", help="Specify a directory to scan",
231 dest
="dir", type=str)
232 parser
.add_argument("-o", "--output", help="Writes to a file instead of\
233 stdout", dest
="output", type=str)
234 parser
.add_argument("-r", "--recursive", help="Recursive search for .yaml files",
235 dest
="recursive", type=bool, nargs
='?', const
=True, default
=False)
237 exclusive_parser
= parser
.add_mutually_exclusive_group()
238 exclusive_parser
.add_argument("-l", "--log", help="Modifies report format: "
239 "Combines the log '.yaml' files into one file.",
240 action
='store_true', dest
='log')
243 def parse_arguments():
244 """ Parses arguments given to script and returns a tuple of processed arguments. """
245 parser
= create_arg_parser()
246 args
= parser
.parse_args()
249 search_directory
= [Path(path
) for path
in args
.dir.split(";")]
251 search_directory
= [Path
.cwd()]
254 search_pattern
= "**/*.yaml"
256 search_pattern
= "*.yaml"
259 result_format
= "log"
261 result_format
= "summary"
264 if isdir(args
.output
):
265 output_file
= Path(args
.output
).joinpath("summary.yaml")
266 elif isdir(Path(args
.output
).resolve().parent
):
267 output_file
= Path(args
.output
)
271 return (search_directory
, search_pattern
, result_format
, output_file
)
274 """ Main function of script. """
275 (search_directory
, search_pattern
, result_format
, output_file
) = parse_arguments()
277 process_yaml(search_directory
, search_pattern
, result_format
, output_file
)
281 if __name__
== "__main__":