access-profiling 0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,116 @@
1
+ # Copyright 2025 ACCESS-NRI and contributors. See the top-level COPYRIGHT file for details.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ import matplotlib.pyplot as plt
5
+ from matplotlib.figure import Figure
6
+
7
+ from access.profiling.metrics import ProfilingMetric
8
+
9
+
10
+ def calculate_column_widths(table_data: list[list], first_col_fraction: float = None) -> list:
11
+ """Calculate column widths based on content character length and required width for first column.
12
+
13
+ Args:
14
+ table_data (list[list]): Table data including headers. e.g.
15
+ [[ "ncpus", "col1", "col2", "col3"],
16
+ ["region1", 0.1, 0.2, 0.3],
17
+ ["region2", 1. , 2. , 3. ]]
18
+ first_col_fraction (float): If provided, controls the fraction of the table width
19
+ assigned to the first column. Default None.
20
+ If set to 0.0 or None, all columns have the same width.
21
+ Must be between 0.0 (inclusive) and 1.0 (exclusive).
22
+
23
+ Returns:
24
+ list : Column width fractions, adding up to 1.
25
+
26
+ Raises:
27
+ ValueError: If table_data has fewer than 2 rows or 2 columns.
28
+ ValueError: If table_data shape is not rectangular, i.e., all rows do not have the same number of columns.
29
+ """
30
+ if not table_data:
31
+ return []
32
+
33
+ # Check that table has a header row and row-label column, and no missing elements.
34
+ if len(table_data) > 1:
35
+ for row in table_data[1:]:
36
+ if len(row) != len(table_data[0]):
37
+ raise ValueError("Table rows must have the same number of elements")
38
+ else:
39
+ raise ValueError("Table must have at least 2 rows (first row is table header)")
40
+ if len(table_data[0]) < 2:
41
+ raise ValueError("Table must have at least 2 columns (first column is row label)")
42
+
43
+ if first_col_fraction is not None and not (0 <= first_col_fraction < 1):
44
+ raise ValueError("first_col_fraction must be between 0 and 1 (exclusive)")
45
+
46
+ n_cols = len(table_data[0])
47
+
48
+ # Calculate max content length for each column based on no. of chars
49
+ max_lengths = []
50
+ for col in range(n_cols):
51
+ col_lengths = [len(str(row[col])) for row in table_data]
52
+ max_lengths.append(max(col_lengths))
53
+
54
+ if first_col_fraction and first_col_fraction > 0:
55
+ # Set data columns to proportional widths based on content and first_col_fraction
56
+ data_cols_total = sum(max_lengths[1:])
57
+ base_width = (1 - first_col_fraction) / data_cols_total
58
+
59
+ col_widths = [first_col_fraction]
60
+ for length in max_lengths[1:]:
61
+ col_widths.append(length * base_width)
62
+
63
+ else:
64
+ # Equal column width
65
+ total_length = sum(max_lengths)
66
+ col_widths = [length / total_length for length in max_lengths]
67
+
68
+ return col_widths
69
+
70
+
71
+ def plot_bar_metrics(
72
+ data: dict[str, list[float]],
73
+ region_labels: list[str],
74
+ metric: ProfilingMetric,
75
+ show: bool = True,
76
+ ) -> Figure:
77
+ """Plots a grouped bar chart of a profiling metric over regions.
78
+
79
+ Regions are placed along the x-axis. Within each region group, there is one bar per
80
+ experiment, coloured by experiment name.
81
+
82
+ Args:
83
+ data (dict[str, list[float]]): Mapping of experiment name to a list of metric values,
84
+ one per region (in the same order as ``region_labels``).
85
+ region_labels (list[str]): Ordered list of region display labels for the x-axis.
86
+ metric (ProfilingMetric): The metric being plotted (used for axis labels and title).
87
+ show (bool): Whether to call ``plt.show()``. Default: True.
88
+
89
+ Returns:
90
+ Figure: The Matplotlib figure containing the bar chart.
91
+ """
92
+ exp_names = list(data.keys())
93
+ n_experiments = len(exp_names)
94
+ n_regions = len(region_labels)
95
+
96
+ fig, ax = plt.subplots(figsize=(max(8, n_experiments * n_regions * 0.8), 6))
97
+ bar_width = 0.8 / n_experiments
98
+ group_positions = list(range(n_regions))
99
+
100
+ for i, exp_name in enumerate(exp_names):
101
+ offsets = [pos + (i - (n_experiments - 1) / 2) * bar_width for pos in group_positions]
102
+ ax.bar(offsets, data[exp_name], width=bar_width, label=exp_name)
103
+
104
+ ax.set_xticks(group_positions)
105
+ ax.set_xticklabels(region_labels)
106
+ ax.set_xlabel("Region")
107
+ ax.set_ylabel(f"{metric.name} ({metric.units})")
108
+ ax.set_title(f"{metric.description}")
109
+ ax.legend(title="Experiment")
110
+ ax.grid(axis="y", linestyle="--", alpha=0.7)
111
+ fig.tight_layout()
112
+
113
+ if show:
114
+ plt.show()
115
+
116
+ return fig
@@ -0,0 +1,128 @@
1
+ # Copyright 2025 ACCESS-NRI and contributors. See the top-level COPYRIGHT file for details.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """Functions to calculate metrics related to parallel scaling of applications."""
5
+
6
+ import matplotlib.gridspec as gridspec
7
+ import matplotlib.pyplot as plt
8
+ import xarray as xr
9
+ from matplotlib.figure import Figure
10
+
11
+ from access.profiling.metrics import ProfilingMetric
12
+ from access.profiling.plotting_utils import calculate_column_widths
13
+
14
+
15
+ def parallel_speedup(stats: xr.Dataset, metric: ProfilingMetric) -> xr.DataArray:
16
+ """Calculates the parallel speedup from scaling data.
17
+
18
+ Args:
19
+ stats (Dataset): Scaling data, stored as a xarray dataset.
20
+ metric (ProfilingMetric): Metric to use for the speedup calculation.
21
+
22
+ Returns:
23
+ DataArray: Parallel speedup.
24
+ Raises:
25
+ ValueError: If metric units are not time (e.g., seconds).
26
+ """
27
+ if stats[metric].pint.dimensionality != "[time]":
28
+ raise ValueError("Metric units must be time (e.g., seconds)!")
29
+ speedup = stats[metric].sel(ncpus=stats["ncpus"].min()) / stats[metric]
30
+ speedup.name = "speedup"
31
+ return speedup
32
+
33
+
34
+ def parallel_efficiency(stats: xr.Dataset, metric: ProfilingMetric) -> xr.DataArray:
35
+ """Calculates the parallel efficiency from scaling data.
36
+
37
+ Args:
38
+ stats (Dataset): Scaling data, stored as a xarray dataset.
39
+ metric (ProfilingMetric): Metric to use for the efficiency calculation.
40
+
41
+ Returns:
42
+ DataArray: Parallel efficiency.
43
+ """
44
+ speedup = parallel_speedup(stats, metric)
45
+ eff = speedup * (speedup.ncpus.min() / speedup.ncpus)
46
+ eff = eff.pint.to("percent")
47
+ eff.name = "parallel efficiency"
48
+ return eff
49
+
50
+
51
+ def plot_scaling_metrics(
52
+ stats: list[xr.Dataset],
53
+ metric: ProfilingMetric,
54
+ xcoordinate: str = "ncpus",
55
+ first_col_fraction: float = 0.4,
56
+ show: bool = True,
57
+ ) -> Figure:
58
+ """Plots parallel speedup and efficiency from a list of datasets
59
+
60
+ Args:
61
+ stats (list[xr.Dataset]): The raw times to plot.
62
+ metric (ProfilinMetric): The metric to plot for each stat.
63
+ xcoordinate (str): The x-axis variable e.g. "ncpus".
64
+ first_col_fraction (float): The fraction of table width to assign to the row labels. Default 0.4.
65
+ show (bool): Whether to show the generated plot. Default: True.
66
+
67
+ Returns:
68
+ Figure: The Matplotlib figure on which the scaling plots and table are plotted on.
69
+
70
+ Raises:
71
+ ValueError: If region_labels is non-empty
72
+ """
73
+
74
+ # setup plots
75
+ fig = plt.figure(figsize=(15, 6))
76
+ # using gridspec so table can be added
77
+ gs = gridspec.GridSpec(2, 2, height_ratios=[3, 1], hspace=0.3)
78
+ ax1, ax2 = fig.add_subplot(gs[0, 0]), fig.add_subplot(gs[0, 1])
79
+ ax_tbl = fig.add_subplot(gs[1, :])
80
+
81
+ # add table of raw timings
82
+ tbl = [[xcoordinate] + list(stats[0][xcoordinate].values)] # first row
83
+ for stat in stats:
84
+ # calculate efficiency and speedup
85
+ efficiency = parallel_efficiency(stat, metric)
86
+ speedup = parallel_speedup(stat, metric)
87
+
88
+ # plots speedup and efficiency on their respective axes.
89
+ max_eff = 100
90
+ for region in stat.region.values:
91
+ speedup.loc[region, :].plot.line(x=xcoordinate, ax=ax1, marker="o", label=region)
92
+ efficiency.loc[region, :].plot.line(x=xcoordinate, ax=ax2, marker="o", label=region)
93
+ # find max efficiency for setting efficiency axis
94
+ max_eff = max(max_eff, efficiency.loc[region, :].max())
95
+
96
+ tbl.append([region] + [f"{val:.2f}" for val in stat[metric].loc[:, region].pint.dequantify().values])
97
+
98
+ # ideal speedup/scaling
99
+ minx = stat[xcoordinate].values.min()
100
+ nx = len(stat[xcoordinate].values)
101
+ ideal_speedups = [i / minx for i in stat[xcoordinate].values]
102
+ ax1.plot(stat[xcoordinate].values, ideal_speedups, "k:", label="ideal")
103
+ ax2.plot(stat[xcoordinate].values, [100] * nx, "k:", label="ideal")
104
+
105
+ # formatting
106
+ ax1.legend()
107
+ ax1.grid()
108
+ ax2.grid()
109
+ ax2.set_ylim((0, 1.1 * max_eff))
110
+ ax1.set_title("Parallel Speedup")
111
+ ax2.set_title("Parallel Efficiency")
112
+ ax_tbl.axis("off")
113
+ tbl_chart = ax_tbl.table(
114
+ tbl,
115
+ bbox=(0.05, 0, 0.9, 1),
116
+ cellLoc="center",
117
+ colWidths=calculate_column_widths(tbl, first_col_fraction),
118
+ )
119
+ ax_tbl.set_title(f"Timings ({stat[metric].pint.units})")
120
+ for i in range(len(tbl[0])):
121
+ tbl_chart[(0, i)].set_text_props(weight="bold")
122
+ for i in range(len(tbl)):
123
+ tbl_chart[(i, 0)].set_text_props(weight="bold")
124
+
125
+ if show:
126
+ plt.show()
127
+
128
+ return fig
@@ -0,0 +1,242 @@
1
+ # Copyright 2025 ACCESS-NRI and contributors. See the top-level COPYRIGHT file for details.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """Parser for UM profiling data.
5
+ This routine parses the inclusive timers from the UM output log
6
+ (e.g. ``atm.fort6.pe0`` for UM7) and returns a dictionary of the
7
+ profiling data. Since UM7 and UM13 provides multiple sections with timer
8
+ output - we have chosen to use the 'Wallclock times' sub-section
9
+ within the Inclusive Timer Summary section.
10
+
11
+ The profiling data is assumed to have the following
12
+ format:
13
+
14
+ ```
15
+ MPP : Inclusive timer summary
16
+
17
+ WALLCLOCK TIMES
18
+ <N> ROUTINE MEAN MEDIAN SD % of mean MAX (PE) MIN (PE)
19
+ 1 AS3 Atmos_Phys2 1308.30 1308.30 0.02 0.00% 1308.33 ( 118) 1308.26 ( 221)
20
+ 2 AP2 Boundary Layer 956.50 956.13 3.26 0.34% 981.27 ( 136) 953.28 ( 43)
21
+ 3 AS5-8 Updates 884.62 885.52 2.89 0.33% 889.49 ( 48) 879.36 ( 212)
22
+
23
+ ...
24
+
25
+ CPU TIMES (sorted by wallclock times)
26
+ <N> ROUTINE MEAN MEDIAN SD % of mean MAX (PE) MIN (PE)
27
+ ...
28
+
29
+ ```
30
+
31
+ All columns in the first sub-section, except for the numeric index and the `% of mean`, are parsed and returned.
32
+ For UM versions 13.x, there is an extra 'N' column name that appears to the left of 'ROUTINE'; this 'N' is
33
+ not present in the output from UM v7.x .
34
+
35
+ """
36
+
37
+ import logging
38
+ import os
39
+ import re
40
+ from pathlib import Path
41
+
42
+ from access.profiling.metrics import pemax, pemin, tavg, tmax, tmed, tmin, tstd
43
+ from access.profiling.parser import ProfilingParser, _convert_from_string, _read_text_file
44
+
45
+ logger = logging.getLogger(__name__)
46
+
47
+
48
+ class UMProfilingParser(ProfilingParser):
49
+ """UM profiling output parser."""
50
+
51
+ # The parsed column names that will be kept. The order needs to match
52
+ # the order of the column names in the input data (defined as ``raw_headers``
53
+ # in the ``read``` method), after discarding the ignored columns.
54
+ _metrics = [tavg, tmed, tstd, tmax, pemax, tmin, pemin]
55
+
56
+ def parse(self, file_path: str | Path | os.PathLike) -> dict:
57
+ """Parse UM profiling data from a file path.
58
+
59
+ Args:
60
+ file_path (str | Path | os.PathLike): file to parse.
61
+
62
+ Returns:
63
+ stats (dict): dictionary of parsed profiling data.
64
+ Ignores two columns ``N``, and ``% over mean`` columns.
65
+
66
+ To keep consistent column names across all parsers, the following
67
+ mapping is used:
68
+ ================== ==================
69
+ UM column name Standard metric
70
+ ================== ==================
71
+ N - (ignored)
72
+ ROUTINE region
73
+ MEAN tavg
74
+ MEDIAN tmed
75
+ SD tstd
76
+ % of mean - (ignored)
77
+ MAX tmax
78
+ (PE) pemax
79
+ MIN tmin
80
+ (PE) pemin
81
+ ================== ==================
82
+ Each key returns a list of values, one for each region. For
83
+ example, if there are 20 regions, ``stats['tavg']`` will
84
+ return a list with 20 values, one each for each of the regions.
85
+
86
+ The assumption is that people will want to look at the same metric
87
+ for *all* regions at a time; if you want to look at all metrics for
88
+ a single region, then you will have to first find the index for the
89
+ ``region``, and then extract that index from *each* of the 'metric'
90
+ lists.
91
+
92
+ Any number of column headers can be present at the beginning, i.e., before
93
+ ``ROUTINE`` (including ``N`` for UM v13+) and will be ignored when the
94
+ header is being parsed. Such columns must contain integer values for the
95
+ profiling information parsing to work, otherwise (e.g., new columns with float
96
+ data), an ``AssertionError`` is raised.
97
+
98
+ Parsing will fail if there are any unexpected columns at the end, either
99
+ at the end of the expected header, or the end of the expected columns with
100
+ profiling data (regardless of type of the column value)
101
+
102
+ Raises:
103
+ ValueError: If the UM version number can not be found in the input string data.
104
+ ValueError: If a match for any of header, footer or section (i.e., empty section)
105
+ is not found.
106
+ AssertionError: If the expected format is not found in *all* of the lines within the
107
+ profiling section.
108
+ TypeError: If file_path cannot be converted to a valid Path object.
109
+ FileNotFoundError: If file_path doesn't exist or isn't a file.
110
+ """
111
+
112
+ stream = _read_text_file(file_path)
113
+
114
+ # First create the local variable with the metrics list
115
+ metrics = self.metrics
116
+ raw_headers = ["ROUTINE", "MEAN", "MEDIAN", "SD", r"\% of mean", "MAX", r"\(PE\)", "MIN", r"\(PE\)"]
117
+
118
+ header = r"MPP : Inclusive timer summary\s+WALLCLOCK TIMES\s*"
119
+ # UM 13 has an extra header 'N' for the numeric row index (that UM7 does not)
120
+ # Writing the pattern this way avoids having to code in UM version dependent patterns
121
+ header += r"\S*\s+"
122
+ # Then skip over white-space-separated header names.
123
+ header += r"\s*".join(raw_headers) + r"\s*"
124
+ header_pattern = re.compile(header, re.MULTILINE)
125
+ header_match = header_pattern.search(stream)
126
+ if not header_match:
127
+ logger.debug("Header pattern: %s", header)
128
+ logger.debug("Input string: %s", stream)
129
+ raise ValueError(f"No matching header found in {file_path}.")
130
+ logger.debug("Found header: %s", header_match.group(0))
131
+
132
+ # This line (and any preceeding whitespace) indicates
133
+ # the end of the profiling data that we want to parse
134
+ footer = r"CPU TIMES \(sorted by wallclock times\)\s*"
135
+ footer_pattern = re.compile(footer, re.MULTILINE)
136
+ footer_match = footer_pattern.search(stream)
137
+ if not footer_match:
138
+ logger.debug("Footer pattern: %s", footer)
139
+ logger.debug("Input string: %s", stream)
140
+ raise ValueError("No matching footer found.")
141
+ logger.debug("Found footer: %s", footer_match.group(0))
142
+
143
+ # Match *everything* between the header and footer (the match could be 0 characters)
144
+ profiling_section_p = re.compile(header + r"(.*)" + footer, re.MULTILINE | re.DOTALL)
145
+ profiling_section = profiling_section_p.search(stream)
146
+
147
+ profiling_section = profiling_section.group(1)
148
+ logger.debug("Found section: %s", profiling_section)
149
+
150
+ # This is regex dark arts - seems to work, I roughly understood when I
151
+ # was refining this named capture group, but I might not be able to in
152
+ # the future. Made heavy use of the regex debugger at regex101.com :) - MS 19/9/2025
153
+ profile_line = r"^\s*[\d\s]+\s+(?P<region>[a-zA-Z][a-zA-Z:()_/\-*&0-9\s\.]+(?<!\s))"
154
+ for metric in metrics:
155
+ logger.debug(f"Adding {metric.name=}")
156
+ group_name = "".join(metric.name.split()) # remove any white-space from metric name to create group name
157
+ if metric in [pemax, pemin]:
158
+ # the pemax and pemin values are enclosed within brackets '()',
159
+ # so we need to ignore both the opening and closing brackets
160
+ add_pattern = r"\s+\(\s*(?P<" + group_name + r">[0-9.]+)\s*\)"
161
+ elif metric == tstd:
162
+ add_pattern = (
163
+ r"\s+(?P<" + group_name + r">[0-9.]+)\s+[\S]+"
164
+ ) # SD is followed by % of mean -> ignore that column
165
+ else:
166
+ add_pattern = (
167
+ r"\s+(?P<" + group_name + r">[0-9.]+)"
168
+ ) # standard white-space followed by a sequence of digits or '.'
169
+
170
+ logger.debug(f"{add_pattern=} for {metric.name=}")
171
+ profile_line += add_pattern
172
+ logger.debug(f"{profile_line=} after {metric.name=}")
173
+
174
+ profile_line += r"$" # the regex should match till the end of line.
175
+ profiling_region_p = re.compile(profile_line, re.MULTILINE)
176
+
177
+ stats = {"region": []}
178
+ stats.update({m: [] for m in self.metrics})
179
+ for line in profiling_region_p.finditer(profiling_section):
180
+ logger.debug(f"Matched line: {line.group(0)}")
181
+ stats["region"].append(line.group("region"))
182
+ for metric in metrics:
183
+ group_name = "".join(metric.name.split())
184
+ stats[metric].append(_convert_from_string(line.group(group_name)))
185
+
186
+ # Parsing is done - let's run some checks
187
+ num_lines = len(profiling_section.strip().split("\n"))
188
+ logger.debug(f"Found {num_lines} lines in profiling section")
189
+ if len(stats["region"]) != num_lines:
190
+ raise AssertionError(f"Expected {num_lines} regions, found {len(stats['region'])}.")
191
+
192
+ logger.info(f"Found {len(stats['region'])} regions with profiling info")
193
+ return stats
194
+
195
+
196
+ """Example UM7 runtime log snippet to be parsed for total wallclock runtime:
197
+
198
+ ```
199
+ END OF RUN - TIMER OUTPUT
200
+ Timer information is for whole run
201
+ PE 0 Elapsed CPU Time: 3943.63426200007
202
+ PE 0 Elapsed Wallclock Time: 3943.80157899974
203
+
204
+ Total Elapsed CPU Time: 820297.910506003
205
+ Maximum Elapsed Wallclock Time: 3944.07699399998
206
+ Speedup: 207.982225436750
207
+ ```
208
+ """
209
+
210
+
211
+ class UMTotalRuntimeParser(ProfilingParser):
212
+ """Parser for UM total runtime from the UM log file."""
213
+
214
+ _metrics = [tmax]
215
+
216
+ def parse(self, file_path: str | Path | os.PathLike) -> dict:
217
+ """Parse UM total runtime from a file.
218
+
219
+ Args:
220
+ file_path (str | Path | os.PathLike): input string to parse.
221
+
222
+ Returns:
223
+ dict: dictionary of parsed profiling data.
224
+
225
+ Raises:
226
+ ValueError: If no matching total runtime line is found.
227
+ """
228
+ stream = _read_text_file(file_path)
229
+ total_runtime_pattern = re.compile(
230
+ r"Maximum\s+Elapsed\s+Wallclock\s+Time\s*:\s*(?P<total_time>[0-9.]+)\s*",
231
+ re.MULTILINE,
232
+ )
233
+ total_runtime_match = total_runtime_pattern.search(stream)
234
+ if not total_runtime_match:
235
+ logger.debug("Total runtime pattern: %s", total_runtime_pattern)
236
+ logger.debug("Input string: %s", stream)
237
+ raise ValueError("No matching total runtime line found.")
238
+
239
+ total_time = float(total_runtime_match.group("total_time"))
240
+ logger.debug(f"Found total UM runtime: {total_time} seconds")
241
+
242
+ return {"region": ["um_total_walltime"], tmax: [total_time]}
@@ -0,0 +1,100 @@
1
+ Metadata-Version: 2.4
2
+ Name: access-profiling
3
+ Version: 0.1
4
+ Summary: Tools and utilities to profile the ACCESS climate models.
5
+ Author-email: Micael Oliveira <micael.oliveira@anu.edu.au>, Manodeep Sinha <manodeep.sinha@anu.edu.au>, Edward Yang <edward.yang@anu.edu.au>
6
+ Keywords: profiling,access
7
+ Classifier: License :: OSI Approved :: Apache Software License
8
+ Classifier: Operating System :: POSIX :: Linux
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Topic :: Utilities
11
+ Requires-Python: >=3.11.4
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: xarray
15
+ Requires-Dist: pint
16
+ Requires-Dist: pint-xarray
17
+ Requires-Dist: matplotlib
18
+ Requires-Dist: access-config-utils
19
+ Requires-Dist: experiment-runner
20
+ Requires-Dist: experiment-generator
21
+ Requires-Dist: payu
22
+ Provides-Extra: devel
23
+ Requires-Dist: pre-commit; extra == "devel"
24
+ Provides-Extra: test
25
+ Requires-Dist: pytest; extra == "test"
26
+ Requires-Dist: pytest-cov; extra == "test"
27
+ Dynamic: license-file
28
+
29
+ # access-profiling
30
+
31
+ ![CI](https://github.com/ACCESS-NRI/access-profiling/actions/workflows/ci.yml/badge.svg) [![codecov](https://codecov.io/github/ACCESS-NRI/access-profiling/graph/badge.svg?token=KtmrCtSyMv)](https://codecov.io/github/ACCESS-NRI/access-profiling) [![License](https://img.shields.io/badge/license-Apache%202.0-blue?style=flat-square)](https://opensource.org/license/apache-2-0) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
32
+
33
+ ## About
34
+
35
+ A Python package providing various tools and utilities to profile the models developed at [ACCESS-NRI](https://github.com/ACCESS-NRI).
36
+
37
+ ## Key Features
38
+
39
+ - Parsers to extract profiling data from model output.
40
+ - Functions to plot key profiling metrics.
41
+
42
+ ## Documentation
43
+
44
+ Coming soon.
45
+
46
+ ## Installation
47
+
48
+ ### Using pip
49
+
50
+ You can install the latest release directly from PyPI:
51
+ ```shell
52
+ pip install access-profiling
53
+ ```
54
+
55
+ ### From source
56
+
57
+ If you prefer to install from source:
58
+ ```shell
59
+ git clone https://github.com/ACCESS-NRI/access-profiling.git
60
+ cd access-profiling
61
+ pip install .
62
+ ```
63
+
64
+ ## Usage
65
+
66
+ Coming soon.
67
+
68
+ ## Development installation
69
+
70
+ If you intend to contribute or modify the package, it is recommended to work inside a virtual environment.
71
+
72
+ 1. Create and activate a virtual environment
73
+ ```shell
74
+ # Create a virtual environment
75
+ python3 -m venv .venv
76
+
77
+ # Activate the virtual environment
78
+ source .venv/bin/activate
79
+ ```
80
+
81
+ 2. Install in editable mode with development and test dependencies
82
+ ```shell
83
+ pip install -e ".[devel,test]"
84
+ ```
85
+ This will install the package in editable mode, meaning changes to the source code are reflected immediately without reinstallation. Development dependencies such as testing tools will also be installed.
86
+
87
+ 3. Run the test suite
88
+ ```shell
89
+ pytest
90
+ ```
91
+
92
+ ## Contributing
93
+
94
+ Contributions are welcome! Please open an issue or submit a pull request if you’d like to add features, fix bugs, or improve documentation.
95
+
96
+ For significant contributions, we recommend discussing proposed changes in an issue before opening a pull request.
97
+
98
+ ## License
99
+
100
+ This project is licensed under the Apache 2.0 License.
@@ -0,0 +1,21 @@
1
+ access/profiling/__init__.py,sha256=PZZoZy6rHr5221EaJsI3YhuLzgKFvD2fMf2FmCWvl1Y,1025
2
+ access/profiling/access_models.py,sha256=GAXfaiid9psHYOyXBAXyt2YfZJs1cjVXCkIlRx6mNGU,3186
3
+ access/profiling/cice5_parser.py,sha256=tBhRYZhMupade3ALuvNPzYEC5nzfMOeAfRTIEi0lVYU,2926
4
+ access/profiling/cylc_manager.py,sha256=YNzW4KtXhFzKWx47VrtguqroBH4O7QtTleZbaKOofsU,9777
5
+ access/profiling/cylc_parser.py,sha256=2oNJVUWXiGxiYTmGjL0jd3KBlGulTMCjI0kCaXGrVEk,5332
6
+ access/profiling/esmf_parser.py,sha256=-wIwJmgZWPIUmUeNLXXAaxXweLVbPaDC2u7K_AAfGzo,7948
7
+ access/profiling/experiment.py,sha256=K16h-a_X3V-68gkmHXRL162WsZocZf4DrIay5xhah64,11512
8
+ access/profiling/fms_parser.py,sha256=GjsKpSj6-gYLHa-go0kljdO5L_7QCKa98cOgWs6YJio,3481
9
+ access/profiling/manager.py,sha256=w-xiQRxGfp4k0i0bVKyyW-jzFrHMX040h6CeA8FG880,27825
10
+ access/profiling/metrics.py,sha256=1iDPSDcczq9sCI2ULYKI8W5opvHHLn4QR40pmw5Bu68,3300
11
+ access/profiling/parser.py,sha256=8YwNhIAVjRvJhZvA6hl7_BK118HBbG5VAY5Jp0BV7zw,9102
12
+ access/profiling/payu_manager.py,sha256=hlVLleN20-VFb1i3CrBbQYib_PhMlkeTioupDDjioRw,14421
13
+ access/profiling/payujson_parser.py,sha256=jTqtXKkoMacKB2Yc6Hoi6xcMkcrZEJs_nGv73zFFByM,2287
14
+ access/profiling/plotting_utils.py,sha256=io8-BIFZGhCkHlDeEsPxTnlR0iegCv27sxeZTUFdutU,4607
15
+ access/profiling/scaling.py,sha256=GruGTn5y3no-QxbT-kHpR9Q6RrPzpVtrkglRLSGrwbs,4620
16
+ access/profiling/um_parser.py,sha256=E8VJ7Tq5VIrTRbzrNcPfQkSnHoaNdOS_nId4G8StLDo,11202
17
+ access_profiling-0.1.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
18
+ access_profiling-0.1.dist-info/METADATA,sha256=tTDn4iq7SJQJUq2PyOt60ss6rvYxXPzeQMkuKQJbQeU,3115
19
+ access_profiling-0.1.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
20
+ access_profiling-0.1.dist-info/top_level.txt,sha256=dKU9Xvk9JgcB3ufviuSVfTY6KZ2eihlcvYerY_-00OQ,7
21
+ access_profiling-0.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+