From 2fe9a57f8c20e686eb10700b30426e708862edf6 Mon Sep 17 00:00:00 2001 From: Matthias Diener Date: Wed, 18 Dec 2024 18:14:09 +0100 Subject: [PATCH 01/25] switch to pyproject --- .github/workflows/ci.yaml | 25 +++++++------- pyproject.toml | 72 +++++++++++++++++++++++++++++++++++++++ setup.cfg | 26 -------------- setup.py | 60 -------------------------------- 4 files changed, 84 insertions(+), 99 deletions(-) create mode 100644 pyproject.toml delete mode 100644 setup.cfg delete mode 100644 setup.py diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index dc4780c..d3ceff7 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -8,19 +8,18 @@ on: - cron: '5 0 * * *' jobs: - flake8: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v4 - with: - # matches compat target in setup.py - python-version: '3.7' - - name: Run flake8 - run: | - pip install -U flake8 pep8-naming flake8-quotes flake8-comprehensions flake8-isort types-psutil numpy flake8-bugbear - flake8 setup.py doc/conf.py logpyle bin/* examples/*.py test/*.py + ruff: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + - name: Ruff check + run: | + python -m pip install ruff + ruff check examples: runs-on: ${{ matrix.os }} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..9d750c2 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,72 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "logpyle" +version = "2023.5" +authors = [ + { name = "Andreas Kloeckner", email = "inform@tiker.net" }, +] +description = "Time series logging for Python" +dependencies = [ + "pytools>=2011.1", + "pymbolic", +] +readme = "README.md" +license = { file="LICENSE" } +requires-python = ">=3.7" +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "Intended Audience :: Other Audience", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: MIT License", + "Natural Language :: English", + "Programming Language :: Python", + "Programming Language :: Python :: 3 :: Only", + "Topic :: Scientific/Engineering", + "Topic :: Scientific/Engineering :: Information Analysis", + "Topic :: Scientific/Engineering :: Mathematics", + "Topic :: Scientific/Engineering :: Visualization", + "Topic :: Software Development :: Libraries", + "Topic :: Utilities", +] + +[project.urls] +"Homepage" = "https://github.com/illinois-ceesd/logpyle/" +"Bug Tracker" = "https://github.com/illinois-ceesd/logpyle/issues" + +[tool.ruff] +preview = true + +[tool.ruff.lint] +extend-select = [ + "B", # flake8-bugbear + "C", # flake8-comprehensions + "E", # pycodestyle + "F", # pyflakes + "G", # flake8-logging-format + "I", # flake8-isort + "N", # pep8-naming + "NPY", # numpy + "Q", # flake8-quotes + "UP", # pyupgrade + "RUF", # ruff + "W", # pycodestyle +] + +[tool.ruff.lint.flake8-quotes] +docstring-quotes = "double" +inline-quotes = "double" +multiline-quotes = "double" + + +[tool.typos.default] +extend-ignore-re = [ + "(?Rm)^.*(#|//)\\s*spellchecker:\\s*disable-line$" +] + +[tool.typos.files] +extend-exclude = [ +] diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index ae94ed2..0000000 --- a/setup.cfg +++ /dev/null @@ -1,26 +0,0 @@ -[flake8] -ignore = E126,E127,E128,E123,E226,E241,E242,E265,E402,W503,E731 -max-line-length = 85 - -inline-quotes = " -docstring-quotes = " -multiline-quotes = """ - -[wheel] -universal = 1 - -[isort] -line_length = 85 - -[mypy] -warn_unused_ignores = True -# strict = True - -[mypy-matplotlib.*] -ignore_missing_imports = True - -[mypy-pylab] -ignore_missing_imports = True - -[mypy-mpi4py] -ignore_missing_imports = True diff --git a/setup.py b/setup.py deleted file mode 100644 index 6ad16c4..0000000 --- a/setup.py +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env python - -from setuptools import setup - -ver_dic = {} -version_file = open("logpyle/version.py") -try: - version_file_contents = version_file.read() -finally: - version_file.close() - -exec(compile(version_file_contents, "logpyle/version.py", "exec"), ver_dic) - -with open("README.md") as fh: - long_description = fh.read() - -setup(name="logpyle", - version=ver_dic["VERSION_TEXT"], - description="Time series logging for Python", - long_description=long_description, - long_description_content_type="text/markdown", - classifiers=[ - "Development Status :: 4 - Beta", - "Intended Audience :: Developers", - "Intended Audience :: Other Audience", - "Intended Audience :: Science/Research", - "License :: OSI Approved :: MIT License", - "Natural Language :: English", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", - "Topic :: Scientific/Engineering", - "Topic :: Scientific/Engineering :: Information Analysis", - "Topic :: Scientific/Engineering :: Mathematics", - "Topic :: Scientific/Engineering :: Visualization", - "Topic :: Software Development :: Libraries", - "Topic :: Utilities", - ], - - python_requires="~=3.7", - - install_requires=[ - "pytools>=2011.1", - "pymbolic", - ], - - package_data={"logpyle": ["py.typed"]}, - scripts=[ - "bin/logtool", - "bin/runalyzer-gather", - "bin/runalyzer", - "bin/htmlalyzer", - "bin/upgrade-db", - ], - - author="Andreas Kloeckner", - url="https://github.com/illinois-ceesd/logpyle", - author_email="inform@tiker.net", - license="MIT", - packages=["logpyle"]) From 90b48587f409c5fe83bab2dfa3cd09750ab8abb8 Mon Sep 17 00:00:00 2001 From: Matthias Diener Date: Wed, 18 Dec 2024 18:19:48 +0100 Subject: [PATCH 02/25] ruff fixes #1 --- examples/log-mpi.py | 11 +++++- examples/log.py | 13 +++++-- examples/optional-log.py | 11 +++++- logpyle/HTMLalyzer/__init__.py | 18 ++++----- logpyle/HTMLalyzer/main.py | 6 +-- logpyle/__init__.py | 34 ++++++++++++----- logpyle/runalyzer.py | 43 +++++++++++++-------- logpyle/runalyzer_gather.py | 18 ++++----- test/test_logManager.py | 17 +++++++-- test/test_quantities.py | 68 ++++++++++++++++++++-------------- 10 files changed, 153 insertions(+), 86 deletions(-) diff --git a/examples/log-mpi.py b/examples/log-mpi.py index 8cf8d68..bed48d4 100755 --- a/examples/log-mpi.py +++ b/examples/log-mpi.py @@ -8,8 +8,15 @@ from mpi4py import MPI -from logpyle import (IntervalTimer, LogManager, LogQuantity, add_general_quantities, - add_run_info, add_simulation_quantities, set_dt) +from logpyle import ( + IntervalTimer, + LogManager, + LogQuantity, + add_general_quantities, + add_run_info, + add_simulation_quantities, + set_dt, +) class Fifteen(LogQuantity): diff --git a/examples/log.py b/examples/log.py index 68f38b7..f85aad7 100755 --- a/examples/log.py +++ b/examples/log.py @@ -5,9 +5,16 @@ from time import sleep from warnings import warn -from logpyle import (GCStats, IntervalTimer, LogManager, LogQuantity, - add_general_quantities, add_run_info, add_simulation_quantities, - set_dt) +from logpyle import ( + GCStats, + IntervalTimer, + LogManager, + LogQuantity, + add_general_quantities, + add_run_info, + add_simulation_quantities, + set_dt, +) class Fifteen(LogQuantity): diff --git a/examples/optional-log.py b/examples/optional-log.py index 6190dca..dbfb84e 100755 --- a/examples/optional-log.py +++ b/examples/optional-log.py @@ -5,8 +5,15 @@ from time import sleep from typing import Union -from logpyle import (IntervalTimer, LogManager, _SubTimer, add_general_quantities, - add_run_info, add_simulation_quantities, set_dt) +from logpyle import ( + IntervalTimer, + LogManager, + _SubTimer, + add_general_quantities, + add_run_info, + add_simulation_quantities, + set_dt, +) def main(use_logpyle: bool) -> None: diff --git a/logpyle/HTMLalyzer/__init__.py b/logpyle/HTMLalyzer/__init__.py index d54dc27..317d654 100644 --- a/logpyle/HTMLalyzer/__init__.py +++ b/logpyle/HTMLalyzer/__init__.py @@ -24,7 +24,7 @@ def get_current_hash() -> str: ] for file in files: - with open(html_path+"/../"+file, "rb") as f: + with open(html_path + "/../" + file, "rb") as f: binary_data = f.read() m = hashlib.sha256() m.update(binary_data) @@ -64,17 +64,17 @@ def build() -> None: ] files_dict = {} for name in filenames_to_copy: - with open(html_path+"/../"+name, "rb") as f: + with open(html_path + "/../" + name, "rb") as f: binary_data = f.read() data = base64.b64encode(binary_data) # insert as single line of text files_dict[name] = data.decode("utf-8") # get templating files - with open(html_path+"/templates/index.html") as f: + with open(html_path + "/templates/index.html") as f: main_template = Template(f.read()) - with open(html_path+"/templates/newFile.html", "r") as f: + with open(html_path + "/templates/newFile.html") as f: new_file_html = f.read() - with open(html_path+"/main.py", "r") as f: + with open(html_path + "/main.py") as f: main_py = f.read() # insert main.py dependencies as strings @@ -89,9 +89,9 @@ def build() -> None: version_py_file=files_dict["version.py"], ) - with open(html_path+"/main.css", "r") as f: + with open(html_path + "/main.css") as f: main_css = f.read() - with open(html_path+"/main.js", "r") as f: + with open(html_path + "/main.js") as f: main_js = f.read() # create HTMLalyzer as a string @@ -103,11 +103,11 @@ def build() -> None: # write html file to logpyle/HTMLalyzer/ filename = "htmlalyzer.html" - with open(html_path+"/"+filename, mode="w", encoding="utf-8") as message: + with open(html_path + "/" + filename, mode="w", encoding="utf-8") as message: message.write(content) # store file hashes - with open(html_path+"/file_hashes.txt", "w") as f: + with open(html_path + "/file_hashes.txt", "w") as f: hashes_str = get_current_hash() f.write(hashes_str) diff --git a/logpyle/HTMLalyzer/main.py b/logpyle/HTMLalyzer/main.py index 824ceac..9603225 100644 --- a/logpyle/HTMLalyzer/main.py +++ b/logpyle/HTMLalyzer/main.py @@ -49,7 +49,7 @@ async def import_logpyle() -> None: whl_binary_data = base64.decodebytes(whl_base_64) with open(pymbolic_whl_file_name, "wb") as f: f.write(whl_binary_data) - await micropip.install("emfs:"+pymbolic_whl_file_name) + await micropip.install("emfs:" + pymbolic_whl_file_name) # install logpyle in ecmascript virtual filesystem os.mkdir("./logpyle") @@ -106,7 +106,7 @@ async def run_plot(event: Any) -> None: run_db = make_wrapped_db(file_dict[id].names, True, True) q1 = document.getElementById("quantity1_" + str(id)).value q2 = document.getElementById("quantity2_" + str(id)).value - query = "select ${}, ${}".format(q1, q2) + query = f"select ${q1}, ${q2}" cursor = run_db.db.execute(run_db.mangle_sql(query)) columnnames = [column[0] for column in cursor.description] run_db.plot_cursor(cursor, labels=columnnames) @@ -274,7 +274,7 @@ async def store_file(event: Any) -> None: for row in cursor: q_id, q_name, q_unit, q_desc, q_rank_agg = row tmp_cur = run_db.db.execute(run_db.mangle_sql( - "select ${}".format(q_name))) + f"select ${q_name}")) vals = list(tmp_cur) file_dict[id].quantities[q_name] = {"vals": vals, "id": q_id, diff --git a/logpyle/__init__.py b/logpyle/__init__.py index 810dfc8..d58fd4c 100644 --- a/logpyle/__init__.py +++ b/logpyle/__init__.py @@ -76,8 +76,22 @@ from dataclasses import dataclass from sqlite3 import Connection from time import monotonic as time_monotonic -from typing import (TYPE_CHECKING, Any, Callable, Dict, Generator, Iterable, List, - Optional, Sequence, TextIO, Tuple, Type, Union, cast) +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Dict, + Generator, + Iterable, + List, + Optional, + Sequence, + TextIO, + Tuple, + Type, + Union, + cast, +) from pymbolic.compiler import CompiledExpression # type: ignore[import-untyped] from pymbolic.primitives import Expression # type: ignore[import-untyped] @@ -992,7 +1006,7 @@ def tick_after(self) -> None: self.save() # print watches - if self.tick_count+1 >= self.next_watch_tick: + if self.tick_count + 1 >= self.next_watch_tick: self._watch_tick() self.t_log += time_monotonic() - tick_start_time @@ -1216,7 +1230,7 @@ def write_datafile(self, filename: str, expr_x: Expression, outf = open(filename, "w") outf.write(f"# {label_x} vs. {label_y}\n") for dx, dy in zip(data_x, data_y): - outf.write("{}\t{}\n".format(repr(dx), repr(dy))) + outf.write(f"{dx!r}\t{dy!r}\n") outf.close() def plot_matplotlib(self, expr_x: Expression, expr_y: Expression) -> None: @@ -1327,7 +1341,7 @@ def __call__(self, lst: List[Any]) -> Any: def _calculate_next_watch_tick(self) -> None: ticks_per_interval = (self.tick_count - / max(1, time_monotonic()-self.start_time) + / max(1, time_monotonic() - self.start_time) * self.watch_interval) self.next_watch_tick = self.tick_count + int(max(1, ticks_per_interval)) @@ -1620,7 +1634,7 @@ def __init__(self, name: str = "t_wall") -> None: self.start = time_monotonic() def __call__(self) -> float: - return time_monotonic()-self.start + return time_monotonic() - self.start class ETA(LogQuantity): @@ -1636,11 +1650,11 @@ def __init__(self, total_steps: int, name: str = "t_eta") -> None: self.start = time_monotonic() def __call__(self) -> float: - fraction_done = self.steps/self.total_steps + fraction_done = self.steps / self.total_steps self.steps += 1 - time_spent = time_monotonic()-self.start + time_spent = time_monotonic() - self.start if fraction_done > 1e-9: - return time_spent/fraction_done-time_spent + return time_spent / fraction_done - time_spent else: return 0 @@ -1734,7 +1748,7 @@ def __init__(self, name: str = "memory_usage_hwm") -> None: if os.uname().sysname == "Linux": self.fac = 1024 elif os.uname().sysname == "Darwin": - self.fac = 1024*1024 + self.fac = 1024 * 1024 else: raise ValueError("MemoryHwm is only supported on Linux/Mac.") diff --git a/logpyle/runalyzer.py b/logpyle/runalyzer.py index 6250bff..c4796e0 100644 --- a/logpyle/runalyzer.py +++ b/logpyle/runalyzer.py @@ -23,8 +23,19 @@ from dataclasses import dataclass from itertools import product from sqlite3 import Connection, Cursor -from typing import (Any, Callable, Dict, Generator, List, Optional, Sequence, Set, - Tuple, Type, Union) +from typing import ( + Any, + Callable, + Dict, + Generator, + List, + Optional, + Sequence, + Set, + Tuple, + Type, + Union, +) from pytools import Table @@ -38,7 +49,7 @@ class PlotStyle: PLOT_STYLES = [ PlotStyle(dashes=dashes, color=color) for dashes, color in product( - [(), (12, 2), (4, 2), (2, 2), (2, 8)], + [(), (12, 2), (4, 2), (2, 2), (2, 8)], ["blue", "green", "red", "magenta", "cyan"], )] @@ -217,7 +228,7 @@ def replace_magic_column(match: Any) -> str: qry, _ = magic_column_re.subn(replace_magic_column, qry) other_clauses = [ # noqa: F841 - "UNION", "INTERSECT", "EXCEPT", "WHERE", "GROUP", + "UNION", "INTERSECT", "EXCEPT", "WHERE", "GROUP", "HAVING", "ORDER", "LIMIT", ";"] from_clause = "from runs " @@ -225,9 +236,7 @@ def replace_magic_column(match: Any) -> str: for tbl, rank_aggregator in magic_columns: if rank_aggregator is not None: full_tbl = f"{rank_aggregator}_{tbl}" - full_tbl_src = "{} as {}".format( - self.get_rank_agg_table(tbl, rank_aggregator), - full_tbl) + full_tbl_src = f"{self.get_rank_agg_table(tbl, rank_aggregator)} as {full_tbl}" if last_tbl is not None: addendum = f" and {last_tbl}.step = {full_tbl}.step" @@ -238,17 +247,15 @@ def replace_magic_column(match: Any) -> str: full_tbl_src = tbl if last_tbl is not None: - addendum = " and {}.step = {}.step and {}.rank={}.rank".format( - last_tbl, full_tbl, last_tbl, full_tbl) + addendum = f" and {last_tbl}.step = {full_tbl}.step and {last_tbl}.rank={full_tbl}.rank" else: addendum = "" - from_clause += " inner join {} on ({}.run_id = runs.id{}) ".format( - full_tbl_src, full_tbl, addendum) + from_clause += f" inner join {full_tbl_src} on ({full_tbl}.run_id = runs.id{addendum}) " last_tbl = full_tbl def get_clause_indices(qry: str) -> Dict[str, int]: - other_clauses = ["UNION", "INTERSECT", "EXCEPT", "WHERE", "GROUP", + other_clauses = ["UNION", "INTERSECT", "EXCEPT", "WHERE", "GROUP", "HAVING", "ORDER", "LIMIT", ";"] result = {} @@ -267,7 +274,7 @@ def get_clause_indices(qry: str) -> Dict[str, int]: clause_indices = get_clause_indices(qry) if not clause_indices: - qry = qry+" "+from_clause + qry = qry + " " + from_clause else: first_clause_idx = min(clause_indices.values()) qry = ( @@ -345,7 +352,7 @@ def execute_magic(self, cmdline: str) -> None: args = "" else: cmd = cmdline[1:cmd_end] - args = cmdline[cmd_end+1:] + args = cmdline[cmd_end + 1:] if cmd == "help": print(""" @@ -508,8 +515,12 @@ def auto_gather(filenames: List[str]) -> sqlite3.Connection: return sqlite3.connect(filenames[0]) # create in memory database of files to be gathered - from logpyle.runalyzer_gather import (FeatureGatherer, gather_multi_file, - make_name_map, scan) + from logpyle.runalyzer_gather import ( + FeatureGatherer, + gather_multi_file, + make_name_map, + scan, + ) print("Creating an in memory database from provided files") from os.path import exists infiles = [f for f in filenames if exists(f)] diff --git a/logpyle/runalyzer_gather.py b/logpyle/runalyzer_gather.py index 5fcae5c..56eff6e 100644 --- a/logpyle/runalyzer_gather.py +++ b/logpyle/runalyzer_gather.py @@ -105,13 +105,13 @@ def __init__(self, features_from_dir: bool = False, colon_idx = line.find(":") assert colon_idx != -1 - entries = [val.strip() for val in line[colon_idx+1:].split(",")] + entries = [val.strip() for val in line[colon_idx + 1:].split(",")] features = [] for entry in entries: equal_idx = entry.find("=") assert equal_idx != -1 features.append((entry[:equal_idx],) - + sql_type_and_value_from_str(entry[equal_idx+1:])) + + sql_type_and_value_from_str(entry[equal_idx + 1:])) self.dir_to_features[line[:colon_idx]] = features @@ -220,7 +220,7 @@ def gather_multi_file(outfile: str, infiles: List[str], fmap: Dict[str, str], tgt_name = fmap.get(fname, fname) if tgt_name.lower() in sqlite_keywords: - feature_col_name_map[fname] = tgt_name+"_" + feature_col_name_map[fname] = tgt_name + "_" else: feature_col_name_map[fname] = tgt_name @@ -230,7 +230,7 @@ def gather_multi_file(outfile: str, infiles: List[str], fmap: Dict[str, str], "id integer primary key", "dirname text", "filename text", - ] + ["{} {}".format(feature_col_name_map[fname], ftype) + ] + [f"{feature_col_name_map[fname]} {ftype}" for fname, ftype in features.items()] db_conn.execute("create table runs (%s)" % ",".join(run_columns)) db_conn.execute("create index runs_id on runs (id)") @@ -287,7 +287,7 @@ def gather_multi_file(outfile: str, infiles: List[str], fmap: Dict[str, str], qry = "insert into runs ({}) values ({})".format( ",".join(["id", "dirname", "filename"] + [feature_col_name_map[f[0]] for f in dbfeatures]), - ",".join("?" * (len(dbfeatures)+3))) + ",".join("?" * (len(dbfeatures) + 3))) db_conn.execute(qry, [run_id, dirname(dbname), basename(dbname)] + [_normalize_types(f[2]) for f in dbfeatures]) @@ -296,12 +296,12 @@ def gather_multi_file(outfile: str, infiles: List[str], fmap: Dict[str, str], def transfer_data_table_multi(db_conn: Connection, tbl_name: str, data_table: DataTable) -> None: - my_data = [(run_id,)+d for d in data_table.data] # noqa: B023 + my_data = [(run_id,) + d for d in data_table.data] # noqa: B023 db_conn.executemany(f"insert into {tbl_name} (%s) values (%s)" % ("run_id," + ", ".join(data_table.column_names), - ", ".join("?" * (len(data_table.column_names)+1))), + ", ".join("?" * (len(data_table.column_names) + 1))), my_data) transfer_data_table_multi(db_conn, "warnings", logmgr.get_warnings()) @@ -317,8 +317,8 @@ def transfer_data_table_multi(db_conn: Connection, tbl_name: str, % tgt_qname) db_conn.execute( - "create index {}_main on {} (run_id,step,rank)" - .format(tgt_qname, tgt_qname)) + f"create index {tgt_qname}_main on {tgt_qname} (run_id,step,rank)" + ) agg = qdat.default_aggregator try: diff --git a/test/test_logManager.py b/test/test_logManager.py index 837a132..14cd505 100644 --- a/test/test_logManager.py +++ b/test/test_logManager.py @@ -7,9 +7,18 @@ import pytest from pymbolic.primitives import Variable -from logpyle import (EventCounter, IntervalTimer, LogManager, LogQuantity, - PushLogQuantity, add_general_quantities, add_run_info, - add_simulation_quantities, set_dt, time_and_count_function) +from logpyle import ( + EventCounter, + IntervalTimer, + LogManager, + LogQuantity, + PushLogQuantity, + add_general_quantities, + add_run_info, + add_simulation_quantities, + set_dt, + time_and_count_function, +) def test_start_time_has_past(basic_logmgr: LogManager): @@ -626,7 +635,7 @@ def has_contents(str1): basic_logmgr.write_datafile(filename, "t_wall", "t_wall") - file_object = open(filename, "r") + file_object = open(filename) lines = file_object.readlines() lines = filter(has_contents, lines) diff --git a/test/test_quantities.py b/test/test_quantities.py index 93fb756..85d0a0e 100644 --- a/test/test_quantities.py +++ b/test/test_quantities.py @@ -4,10 +4,22 @@ import pytest -from logpyle import (ETA, CallableLogQuantityAdapter, GCStats, IntervalTimer, - LogManager, LogQuantity, MultiLogQuantity, MultiPostLogQuantity, - PostLogQuantity, PushLogQuantity, StepToStepDuration, - TimestepCounter, TimestepDuration, WallTime) +from logpyle import ( + ETA, + CallableLogQuantityAdapter, + GCStats, + IntervalTimer, + LogManager, + LogQuantity, + MultiLogQuantity, + MultiPostLogQuantity, + PostLogQuantity, + PushLogQuantity, + StepToStepDuration, + TimestepCounter, + TimestepDuration, + WallTime, +) # (name, value, unit, description, call_func) test_logquantity_types = [ @@ -16,28 +28,28 @@ 1, "1", "Q init to 1", - lambda x: x+1 + lambda x: x + 1 ), ( "Quantity_name", 1, None, "Q init to 1", - lambda x: x+1 + lambda x: x + 1 ), ( "Quantity_name", 1, "1", None, - lambda x: x+1 + lambda x: x + 1 ), ( "Quantity_name", 1, None, None, - lambda x: x+1 + lambda x: x + 1 ), ] @@ -91,11 +103,11 @@ def test_logquantity(basic_logmgr, custom_logquantity): basic_logmgr.tick_before() # custom_logquantity should have been called middle_val = getattr(custom_logquantity, custom_logquantity.name) - assert middle_val == predicted_list[i+1] + assert middle_val == predicted_list[i + 1] basic_logmgr.tick_after() post_val = getattr(custom_logquantity, custom_logquantity.name) - assert post_val == predicted_list[i+1] + assert post_val == predicted_list[i + 1] cur_vals = getattr(custom_logquantity, custom_logquantity.name) calculated_list.append(cur_vals) @@ -174,7 +186,7 @@ def test_post_logquantity(basic_logmgr, custom_post_logquantity): basic_logmgr.tick_after() post_val = getattr(custom_post_logquantity, custom_post_logquantity.name) - assert post_val == predicted_list[i+1] + assert post_val == predicted_list[i + 1] cur_vals = getattr(custom_post_logquantity, custom_post_logquantity.name) calculated_list.append(cur_vals) @@ -196,49 +208,49 @@ def test_post_logquantity(basic_logmgr, custom_post_logquantity): [1, 2], ["1", "1"], ["Q init to 1", "Q init to 2"], - lambda x, y: [x+1, y+1] + lambda x, y: [x + 1, y + 1] ), ( ["Quantity_1", "Quantity_2"], [1, 2], [None, "1"], ["Q init to 1", "Q init to 2"], - lambda x, y: [x+1, y+1] + lambda x, y: [x + 1, y + 1] ), ( ["Quantity_1", "Quantity_2"], [1, 2], ["1", None], ["Q init to 1", "Q init to 2"], - lambda x, y: [x+1, y+1] + lambda x, y: [x + 1, y + 1] ), ( ["Quantity_1", "Quantity_2"], [1, 2], ["1", "1"], [None, "Q init to 2"], - lambda x, y: [x+1, y+1] + lambda x, y: [x + 1, y + 1] ), ( ["Quantity_1", "Quantity_2"], [1, 2], ["1", "1"], ["Q init to 1", None], - lambda x, y: [x+1, y+1] + lambda x, y: [x + 1, y + 1] ), ( ["Quantity_1", "Quantity_2"], [1, 2], None, ["Q init to 1", "Q init to 2"], - lambda x, y: [x+1, y+1] + lambda x, y: [x + 1, y + 1] ), ( ["Quantity_1", "Quantity_2"], [1, 2], ["1", "1"], None, - lambda x, y: [x+1, y+1] + lambda x, y: [x + 1, y + 1] ), ] @@ -302,14 +314,14 @@ def test_multi_log_quantity(basic_logmgr, custom_multi_log_quantity): for name in custom_multi_log_quantity.names: middle_vals.append(getattr(custom_multi_log_quantity, name)) assert len(middle_vals) == len(init_values) - assert middle_vals == predicted_list[i+1] + assert middle_vals == predicted_list[i + 1] basic_logmgr.tick_after() post_vals = [] for name in custom_multi_log_quantity.names: post_vals.append(getattr(custom_multi_log_quantity, name)) assert len(post_vals) == len(init_values) - assert post_vals == predicted_list[i+1] + assert post_vals == predicted_list[i + 1] cur_vals = [] for name in custom_multi_log_quantity.names: @@ -407,7 +419,7 @@ def test_multi_post_logquantity(basic_logmgr, custom_multi_post_logquantity): for name in custom_multi_post_logquantity.names: post_vals.append(getattr(custom_multi_post_logquantity, name)) assert len(post_vals) == len(init_values) - assert post_vals == predicted_list[i+1] + assert post_vals == predicted_list[i + 1] cur_vals = [] for name in custom_multi_post_logquantity.names: @@ -617,8 +629,8 @@ def test_interval_timer_subtimer(basic_logmgr: LogManager): n = 20 for _ in range(n): - good_sleep_time = (random.random()/10 + 0.1) - bad_sleep_time = (random.random()/10 + 0.1) + good_sleep_time = (random.random() / 10 + 0.1) + bad_sleep_time = (random.random() / 10 + 0.1) expected_timer_list.append(good_sleep_time) sub_timer = timer.get_sub_timer() @@ -651,8 +663,8 @@ def test_interval_timer_subtimer_blocking(basic_logmgr: LogManager): n = 20 for _ in range(n): - good_sleep_time = (random.random()/10 + 0.1) - bad_sleep_time = (random.random()/10 + 0.1) + good_sleep_time = (random.random() / 10 + 0.1) + bad_sleep_time = (random.random() / 10 + 0.1) expected_timer_list.append(good_sleep_time) sub_timer = timer.get_sub_timer() @@ -681,7 +693,7 @@ def test_accurate_eta_quantity(basic_logmgr: LogManager): n = 30 - test_timer = ETA(n-1, "t_fin") + test_timer = ETA(n - 1, "t_fin") basic_logmgr.add_quantity(test_timer) sleep_time = 0.02 @@ -698,7 +710,7 @@ def test_accurate_eta_quantity(basic_logmgr: LogManager): if i > 0: # ETA isn't available on step 0. - assert abs(predicted_time-eta_time) < tol + assert abs(predicted_time - eta_time) < tol print(i, eta_time, predicted_time, abs(eta_time - predicted_time)) assert 0 <= eta_time < 1e-12 @@ -722,7 +734,7 @@ def test_gc_stats(basic_logmgr: LogManager): basic_logmgr.tick_before() soon_tobe_lost_ref = ["garb1", "garb2", "garb3"] * istep - outer_list.append(([soon_tobe_lost_ref])) + outer_list.append([soon_tobe_lost_ref]) basic_logmgr.tick_after() From c6debbf51b27d1f25b3033ffac1fba3836bb54f1 Mon Sep 17 00:00:00 2001 From: Matthias Diener Date: Thu, 19 Dec 2024 10:47:37 +0100 Subject: [PATCH 03/25] ruff part 2 --- examples/log-mpi.py | 2 +- examples/log.py | 2 +- examples/optional-log.py | 2 +- logpyle/HTMLalyzer/main.py | 18 +++++----- logpyle/__init__.py | 68 +++++++++++++++++++------------------ logpyle/runalyzer.py | 41 +++++++++++----------- logpyle/runalyzer_gather.py | 40 +++++++++++----------- pyproject.toml | 4 +++ test/test_gather.py | 2 +- test/test_logManager.py | 6 ++-- test/test_upgrade_db.py | 4 +-- 11 files changed, 97 insertions(+), 92 deletions(-) diff --git a/examples/log-mpi.py b/examples/log-mpi.py index bed48d4..e68ef2b 100755 --- a/examples/log-mpi.py +++ b/examples/log-mpi.py @@ -75,7 +75,7 @@ def main() -> None: # Illustrate warnings/logging capture if uniform(0, 1) < 0.05: - warn("Oof. Something went awry.") + warn("Oof. Something went awry.", stacklevel=2) if istep == 16: logger.warning("test logging") diff --git a/examples/log.py b/examples/log.py index f85aad7..40b1abd 100755 --- a/examples/log.py +++ b/examples/log.py @@ -66,7 +66,7 @@ def main() -> None: # Illustrate warnings capture if uniform(0, 1) < 0.05: - warn("Oof. Something went awry.") + warn("Oof. Something went awry.", stacklevel=2) if istep == 50: logger.warning("test logging") diff --git a/examples/optional-log.py b/examples/optional-log.py index dbfb84e..6739159 100755 --- a/examples/optional-log.py +++ b/examples/optional-log.py @@ -16,7 +16,7 @@ ) -def main(use_logpyle: bool) -> None: +def main(use_logpyle: bool) -> None: # noqa: C901 if use_logpyle: logmgr = LogManager("optional-log.sqlite", "w") else: diff --git a/logpyle/HTMLalyzer/main.py b/logpyle/HTMLalyzer/main.py index 9603225..13ada45 100644 --- a/logpyle/HTMLalyzer/main.py +++ b/logpyle/HTMLalyzer/main.py @@ -98,7 +98,7 @@ def add_file_func() -> None: next_id = next_id + 1 -async def run_plot(event: Any) -> None: +async def run_plot(event: Any) -> None: # noqa: RUF029 from logpyle.runalyzer import make_wrapped_db id = event.target.getAttribute("param") output = document.getElementById("output" + str(id)) @@ -114,7 +114,7 @@ async def run_plot(event: Any) -> None: output.id = "output" + str(id) -async def run_chart(event: Any) -> None: +async def run_chart(event: Any) -> None: # noqa: RUF029 id = event.target.getAttribute("param") x_quantity: str = document.getElementById("quantity1_" + str(id)).value x = file_dict[id].quantities[x_quantity] @@ -147,7 +147,7 @@ async def run_chart(event: Any) -> None: ) -async def add_table_list(event: Any) -> None: +async def add_table_list(event: Any) -> None: # noqa: RUF029 id = event.target.getAttribute("param") quantity = document.getElementById("tableQuantitySelect" + str(id)).value table_list = document.getElementById("tableList" + str(id)) @@ -165,7 +165,7 @@ async def add_table_list(event: Any) -> None: table_list.appendChild(item) -async def add_line(event: Any) -> None: +async def add_line(event: Any) -> None: # noqa: RUF029 id = event.target.getAttribute("param1") i = event.target.param2 event.target.param2 = i + 1 @@ -190,7 +190,7 @@ async def add_line(event: Any) -> None: y_quantities.appendChild(y_div) -async def remove_table_ele(event: Any) -> None: +async def remove_table_ele(event: Any) -> None: # noqa: RUF029 event.target.parentElement.remove() @@ -264,7 +264,7 @@ async def store_file(event: Any) -> None: run_db = make_wrapped_db(file_dict[id].names, True, True) cursor = run_db.db.execute("select * from runs") columns = [col[0] for col in cursor.description] - vals = list(list(cursor)[0]) + vals = list(next(iter(cursor))) for (col, val) in zip(columns, vals): file_dict[id].constants[col] = val @@ -364,7 +364,7 @@ async def store_file(event: Any) -> None: table_button = document.getElementById("tableButton" + str(id)) table_button.addEventListener("click", create_proxy(add_table_list)) - # add quantites to table dropdown + # add quantities to table dropdown table_select = document.getElementById("tableQuantitySelect" + str(id)) for quantity in file_dict[id].quantities: item = document.createElement("option") @@ -383,6 +383,6 @@ async def store_file(event: Any) -> None: # init file storage structure file_dict: dict[str, Any] = {} # ensure logpyle and dependencies are present -asyncio.ensure_future(import_logpyle()) -# ensure that one analysis pannel is present to begin with +asyncio.ensure_future(import_logpyle()) # noqa: RUF006 +# ensure that one analysis panel is present to begin with add_file_func() diff --git a/logpyle/__init__.py b/logpyle/__init__.py index d58fd4c..c7bbdff 100644 --- a/logpyle/__init__.py +++ b/logpyle/__init__.py @@ -70,9 +70,6 @@ import logging import sys - -logger = logging.getLogger(__name__) - from dataclasses import dataclass from sqlite3 import Connection from time import monotonic as time_monotonic @@ -97,6 +94,8 @@ from pymbolic.primitives import Expression # type: ignore[import-untyped] from pytools.datatable import DataTable +logger = logging.getLogger(__name__) + if TYPE_CHECKING and not getattr(sys, "_BUILDING_SPHINX_DOCS", False): import mpi4py @@ -502,7 +501,7 @@ class LogManager: .. automethod:: tick_after """ - def __init__(self, filename: Optional[str] = None, mode: str = "r", + def __init__(self, filename: Optional[str] = None, mode: str = "r", # noqa: C901 mpi_comm: Optional["mpi4py.MPI.Comm"] = None, capture_warnings: bool = True, watch_interval: float = 1.0, @@ -578,7 +577,7 @@ def __init__(self, filename: Optional[str] = None, mode: str = "r", import os file_base, file_extension = os.path.splitext(filename) if self.is_parallel: - file_base += "-rank%d" % self.rank + file_base += f"-rank{self.rank}" while True: suffix = "" @@ -606,10 +605,10 @@ def __init__(self, filename: Optional[str] = None, mode: str = "r", self.mode = mode try: self.db_conn.execute("select * from quantities;") - except sqlite.OperationalError: + except sqlite.OperationalError as err: # we're building a new database if mode == "r": - raise RuntimeError("Log database '%s' not found" % filename) + raise RuntimeError(f"Log database '{filename}' not found") from err self.schema_version = _set_up_schema(self.db_conn) self.set_constant("schema_version", self.schema_version) @@ -634,7 +633,7 @@ def __init__(self, filename: Optional[str] = None, mode: str = "r", else: # we've opened an existing database if mode == "w": - raise RuntimeError("Log database '%s' already exists" % filename) + raise RuntimeError(f"Log database '{filename}' already exists") if mode == "wu": # try again with a new suffix @@ -729,11 +728,11 @@ def _showwarning(message: Union[Warning, str], category: Type[Warning], warnings.showwarning = _showwarning else: from warnings import warn - warn("Warnings capture already enabled") + warn("Warnings capture already enabled", stacklevel=2) else: if self.old_showwarning is None: from warnings import warn - warn("Warnings capture already disabled") + warn("Warnings capture already disabled", stacklevel=2) else: warnings.showwarning = self.old_showwarning self.old_showwarning = None @@ -766,13 +765,13 @@ def emit(self, record: logging.LogRecord) -> None: root_logger.addHandler(self.logging_handler) elif self.logging_handler: from warnings import warn - warn("Logging capture already enabled") + warn("Logging capture already enabled", stacklevel=2) else: if self.logging_handler: root_logger.removeHandler(self.logging_handler) elif self.logging_handler is None: from warnings import warn - warn("Logging capture already disabled") + warn("Logging capture already disabled", stacklevel=2) self.logging_handler = None @@ -787,11 +786,11 @@ def get_logging(self) -> DataTable: if self.schema_version < 3: from warnings import warn - warn("This database lacks a 'logging' table") + warn("This database lacks a 'logging' table", stacklevel=2) return result for row in self.db_conn.execute( - "select %s from logging" % (", ".join(columns))): + "select {} from logging".format(", ".join(columns))): result.insert_row(row) return result @@ -831,13 +830,13 @@ def get_table(self, q_name: str) -> DataTable: """Return a :class:`~pytools.datatable.DataTable` of the data logged for the quantity *q_name*.""" if q_name not in self.quantity_data: - raise KeyError("invalid quantity name '%s'" % q_name) + raise KeyError(f"invalid quantity name '{q_name}'") result = DataTable( ["step", "rank", "value"]) for row in self.db_conn.execute( - "select step, rank, value from %s" % q_name): + f"select step, rank, value from {q_name}"): result.insert_row(row) return result @@ -856,7 +855,7 @@ def get_warnings(self) -> DataTable: result = DataTable(columns) for row in self.db_conn.execute( - "select %s from warnings" % (", ".join(columns))): + "select {} from warnings".format(", ".join(columns))): result.insert_row(row) return result @@ -936,10 +935,10 @@ def _insert_datapoint(self, name: str, value: Optional[float]) -> None: self.last_values[name] = value try: - self.db_conn.execute("insert into %s values (?,?,?)" % name, + self.db_conn.execute(f"insert into {name} values (?,?,?)", (self.tick_count, self.rank, float(value))) except Exception: - print("while adding datapoint for '%s':" % name) + print(f"while adding datapoint for '{name}':") raise def _update_t_log(self, name: str, value: float) -> None: @@ -952,7 +951,7 @@ def _update_t_log(self, name: str, value: float) -> None: self.db_conn.execute(f"update {name} set value = {float(value)} \ where rank = {self.rank} and step = {self.tick_count}") except Exception: - print("while adding datapoint for '%s':" % name) + print(f"while adding datapoint for '{name}':") raise def _gather_for_descriptor(self, gd: _GatherDescriptor) -> None: @@ -1053,7 +1052,7 @@ def save(self) -> None: # Even when encountering a commit error, we want to continue # running the application. from warnings import warn - warn("encountered sqlite error during commit: %s" % e) + warn(f"encountered sqlite error during commit: {e}", stacklevel=2) self.last_save_time = time_monotonic() @@ -1066,18 +1065,18 @@ def add_quantity(self, quantity: LogQuantity, interval: int = 1) -> None: def add_internal(name: str, unit: Optional[str], description: Optional[str], def_agg: Optional[Callable[..., Any]]) -> None: - logger.debug("add log quantity '%s'" % name) + logger.debug(f"adding log quantity '{name}'") if name in self.quantity_data: - raise RuntimeError("cannot add the same quantity '%s' twice" % name) + raise RuntimeError(f"cannot add the same quantity '{name}' twice") self.quantity_data[name] = _QuantityData(unit, description, def_agg) from pickle import dumps self.db_conn.execute("""insert into quantities values (?,?,?,?)""", ( name, unit, description, bytes(dumps(def_agg)))) - self.db_conn.execute("""create table %s - (step integer, rank integer, value real)""" % name) + self.db_conn.execute(f"""create table {name} + (step integer, rank integer, value real)""") gd = _GatherDescriptor(quantity, interval) if isinstance(quantity, PostLogQuantity): @@ -1254,7 +1253,8 @@ def _parse_expr(self, expr: Expression) -> Any: return parsed - def _get_expr_dep_data(self, parsed: Expression) \ + def _get_expr_dep_data(self, # noqa: C901 + parsed: Expression) \ -> Tuple[Expression, List[_DependencyData]]: class Nth: def __init__(self, n: int) -> None: @@ -1282,9 +1282,10 @@ def __call__(self, lst: List[Any]) -> Any: if agg_func is None: if self.is_parallel: raise ValueError( - "must specify explicit aggregator for '%s'" % name) + f"must specify explicit aggregator for '{name}'") - agg_func = lambda lst: lst[0] + def agg_func(lst): + return lst[0] elif isinstance(dep, Lookup): assert isinstance(dep.aggregate, Variable) name = dep.aggregate.name @@ -1312,10 +1313,11 @@ def __call__(self, lst: List[Any]) -> Any: agg_func = sum elif agg_name == "norm2": from math import sqrt - agg_func = lambda iterable: sqrt( - sum(entry**2 for entry in iterable)) + + def agg_func(iterable): + return sqrt(sum(entry ** 2 for entry in iterable)) else: - raise ValueError("invalid rank aggregator '%s'" % agg_name) + raise ValueError(f"invalid rank aggregator '{agg_name}'") elif isinstance(dep, Subscript): assert isinstance(dep.aggregate, Variable) name = dep.aggregate.name @@ -1328,7 +1330,7 @@ def __call__(self, lst: List[Any]) -> Any: assert agg_func this_dep_data = _DependencyData(name=name, qdat=qdat, agg_func=agg_func, - varname="logvar%d" % dep_idx, expr=dep, + varname=f"logvar{dep_idx}", expr=dep, nonlocal_agg=nonlocal_agg) dep_data.append(this_dep_data) @@ -1602,7 +1604,7 @@ def __init__(self, name: str = "t_init") -> None: import psutil except ModuleNotFoundError: from warnings import warn - warn("Measuring the init time requires the 'psutil' module.") + warn("Measuring the init time requires the 'psutil' module.", stacklevel=2) self.done = True else: self.create_time = psutil.Process().create_time() diff --git a/logpyle/runalyzer.py b/logpyle/runalyzer.py index c4796e0..58642cd 100644 --- a/logpyle/runalyzer.py +++ b/logpyle/runalyzer.py @@ -17,9 +17,6 @@ import logging - -logger = logging.getLogger(__name__) - from dataclasses import dataclass from itertools import product from sqlite3 import Connection, Cursor @@ -39,6 +36,8 @@ from pytools import Table +logger = logging.getLogger(__name__) + @dataclass(frozen=True) class PlotStyle: @@ -78,12 +77,10 @@ def get_rank_agg_table(self, qty: str, logger.info("Building temporary rank aggregation table {tbl_name}.") - self.db.execute("create temporary table %s as " - "select run_id, step, %s(value) as value " - "from %s group by run_id,step" % ( - tbl_name, rank_aggregator, qty)) - self.db.execute("create index %s_run_step on %s (run_id,step)" - % (tbl_name, tbl_name)) + self.db.execute(f"create temporary table {tbl_name} as " + f"select run_id, step, {rank_aggregator}(value) as value " + f"from {qty} group by run_id,step") + self.db.execute(f"create index {tbl_name}_run_step on {tbl_name} (run_id,step)") self.rank_agg_tables.add((qty, rank_aggregator)) return tbl_name @@ -104,7 +101,7 @@ def scatter_cursor(self, cursor: Cursor, labels: Optional[List[str]] = None, if self.interactive: plt.show() - def plot_cursor(self, cursor: Cursor, labels: Optional[List[str]] = None, + def plot_cursor(self, cursor: Cursor, labels: Optional[List[str]] = None, # noqa: C901 *args: Any, **kwargs: Any) -> None: from matplotlib.pyplot import legend, plot, show @@ -196,14 +193,14 @@ def split_cursor(cursor: Cursor) -> Generator[ def table_from_cursor(cursor: Cursor) -> Table: tbl = Table() - tbl.add_row(tuple([column[0] for column in cursor.description])) + tbl.add_row(tuple(column[0] for column in cursor.description)) for row in cursor: tbl.add_row(row) return tbl class MagicRunDB(RunDB): - def mangle_sql(self, qry: str) -> str: + def mangle_sql(self, qry: str) -> str: # noqa: C901 up_qry = qry.upper() if "FROM" in up_qry and "$$" not in up_qry: return qry @@ -222,7 +219,7 @@ def replace_magic_column(match: Any) -> str: return f"{rank_aggregator}_{qty_name}.value AS {qty_name}" else: magic_columns.add((qty_name, None)) - return "%s.value AS %s" % (qty_name, qty_name) + return f"{qty_name}.value AS {qty_name}" magic_column_re = re.compile(r"\$([a-zA-Z][A-Za-z0-9_]*)(\.[a-z]*)?") qry, _ = magic_column_re.subn(replace_magic_column, qry) @@ -236,7 +233,8 @@ def replace_magic_column(match: Any) -> str: for tbl, rank_aggregator in magic_columns: if rank_aggregator is not None: full_tbl = f"{rank_aggregator}_{tbl}" - full_tbl_src = f"{self.get_rank_agg_table(tbl, rank_aggregator)} as {full_tbl}" + full_tbl_src = \ + f"{self.get_rank_agg_table(tbl, rank_aggregator)} as {full_tbl}" if last_tbl is not None: addendum = f" and {last_tbl}.step = {full_tbl}.step" @@ -247,11 +245,14 @@ def replace_magic_column(match: Any) -> str: full_tbl_src = tbl if last_tbl is not None: - addendum = f" and {last_tbl}.step = {full_tbl}.step and {last_tbl}.rank={full_tbl}.rank" + addendum = f" and {last_tbl}.step = {full_tbl}.step and " \ + f"{last_tbl}.rank={full_tbl}.rank" else: addendum = "" - from_clause += f" inner join {full_tbl_src} on ({full_tbl}.run_id = runs.id{addendum}) " + from_clause += \ + f" inner join {full_tbl_src}" \ + f" on ({full_tbl}.run_id = runs.id{addendum}) " last_tbl = full_tbl def get_clause_indices(qry: str) -> Dict[str, int]: @@ -261,7 +262,7 @@ def get_clause_indices(qry: str) -> Dict[str, int]: result = {} up_qry = qry.upper() for clause in other_clauses: - clause_match = re.search(r"\b%s\b" % clause, up_qry) + clause_match = re.search(rf"\b{clause}\b", up_qry) if clause_match is not None: result[clause] = clause_match.start() @@ -269,7 +270,7 @@ def get_clause_indices(qry: str) -> Dict[str, int]: # add 'from' if "$$" in qry: - qry = qry.replace("$$", " %s " % from_clause) + qry = qry.replace("$$", f" {from_clause} ") else: clause_indices = get_clause_indices(qry) @@ -286,7 +287,7 @@ def get_clause_indices(qry: str) -> Dict[str, int]: def make_runalyzer_symbols(db: RunDB) \ - -> Dict[str, Union[RunDB, str, None, Callable[..., Any]]]: + -> Dict[str, Union[RunDB, str, Callable[..., Any], None]]: return { "__name__": "__console__", "__doc__": None, @@ -345,7 +346,7 @@ def push(self, cmdline: str) -> bool: return self.last_push_result - def execute_magic(self, cmdline: str) -> None: + def execute_magic(self, cmdline: str) -> None: # noqa: C901 cmd_end = cmdline.find(" ") if cmd_end == -1: cmd = cmdline[1:] diff --git a/logpyle/runalyzer_gather.py b/logpyle/runalyzer_gather.py index 56eff6e..fc08cca 100644 --- a/logpyle/runalyzer_gather.py +++ b/logpyle/runalyzer_gather.py @@ -1,11 +1,5 @@ import re import sqlite3 - -bool_feat_re = re.compile(r"^([a-z]+)(True|False)$") -int_feat_re = re.compile(r"^([a-z]+)([0-9]+)$") -real_feat_re = re.compile(r"^([a-z]+)([0-9]+\.?[0-9]*)$") -str_feat_re = re.compile(r"^([a-z]+)([A-Z][A-Za-z_0-9]+)$") - from sqlite3 import Connection from typing import Any, Dict, List, Optional, Tuple, Union, cast @@ -13,6 +7,11 @@ from logpyle import LogManager +bool_feat_re = re.compile(r"^([a-z]+)(True|False)$") +int_feat_re = re.compile(r"^([a-z]+)([0-9]+)$") +real_feat_re = re.compile(r"^([a-z]+)([0-9]+\.?[0-9]*)$") +str_feat_re = re.compile(r"^([a-z]+)([A-Z][A-Za-z_0-9]+)$") + sqlite_keywords = """ abort action add after all alter analyze and as asc attach autoincrement before begin between by cascade case cast check @@ -43,7 +42,7 @@ def parse_dir_feature(feat: str, number: int) \ str_match = str_feat_re.match(feat) if str_match is not None: return (str_match.group(1), "text", str_match.group(2)) - return ("dirfeat%d" % number, "text", feat) + return (f"dirfeat{number}", "text", feat) def larger_sql_type(type_a: Optional[str], type_b: Optional[str]) -> Optional[str]: @@ -110,8 +109,9 @@ def __init__(self, features_from_dir: bool = False, for entry in entries: equal_idx = entry.find("=") assert equal_idx != -1 - features.append((entry[:equal_idx],) - + sql_type_and_value_from_str(entry[equal_idx + 1:])) + features.append((entry[:equal_idx], + *sql_type_and_value_from_str( + entry[equal_idx + 1:]))) self.dir_to_features[line[:colon_idx]] = features @@ -126,13 +126,13 @@ def get_db_features(self, dbname: str, logmgr: LogManager) -> List[Any]: for i, feat in enumerate(dn.split("-"))) for name, value in logmgr.constants.items(): - features.append((name,) + sql_type_and_value(value)) + features.append((name, *sql_type_and_value(value))) return features -def scan(fg: FeatureGatherer, dbnames: List[str], progress: bool = True) \ - -> Tuple[Dict[str, Any], Dict[str, int]]: +def scan(fg: FeatureGatherer, dbnames: List[str], # noqa: C901 + progress: bool = True) -> Tuple[Dict[str, Any], Dict[str, int]]: features: Dict[str, Any] = {} dbname_to_run_id = {} uid_to_run_id: Dict[str, int] = {} @@ -147,7 +147,7 @@ def scan(fg: FeatureGatherer, dbnames: List[str], progress: bool = True) \ try: logmgr = LogManager(dbname, "r") except Exception: - print("Trouble with file '%s'" % dbname) + print(f"Trouble with file '{dbname}'") raise unique_run_id = cast(str, logmgr.constants.get("unique_run_id")) @@ -232,7 +232,7 @@ def gather_multi_file(outfile: str, infiles: List[str], fmap: Dict[str, str], "filename text", ] + [f"{feature_col_name_map[fname]} {ftype}" for fname, ftype in features.items()] - db_conn.execute("create table runs (%s)" % ",".join(run_columns)) + db_conn.execute("create table runs ({})".format(",".join(run_columns))) db_conn.execute("create index runs_id on runs (id)") # Caveat: the next three tables need to match the tables in _set_up_schema, @@ -296,7 +296,7 @@ def gather_multi_file(outfile: str, infiles: List[str], fmap: Dict[str, str], def transfer_data_table_multi(db_conn: Connection, tbl_name: str, data_table: DataTable) -> None: - my_data = [(run_id,) + d for d in data_table.data] # noqa: B023 + my_data = [(run_id, *d) for d in data_table.data] # noqa: B023 db_conn.executemany(f"insert into {tbl_name} (%s) values (%s)" % ("run_id," @@ -312,13 +312,11 @@ def transfer_data_table_multi(db_conn: Connection, tbl_name: str, if tgt_qname not in created_tables: created_tables.add(tgt_qname) - db_conn.execute("create table %s (" - "run_id integer, step integer, rank integer, value real)" - % tgt_qname) + db_conn.execute(f"create table {tgt_qname} (" + "run_id integer, step integer, rank integer, value real)") db_conn.execute( - f"create index {tgt_qname}_main on {tgt_qname} (run_id,step,rank)" - ) + f"create index {tgt_qname}_main on {tgt_qname} (run_id,step,rank)") agg = qdat.default_aggregator try: @@ -334,7 +332,7 @@ def transfer_data_table_multi(db_conn: Connection, tbl_name: str, cursor = logmgr.db_conn.execute( f"select {run_id},step,rank,value from {qname}") - db_conn.executemany("insert into %s values (?,?,?,?)" % tgt_qname, + db_conn.executemany(f"insert into {tgt_qname} values (?,?,?,?)", cursor) logmgr.close() pb.finished() # type: ignore[no-untyped-call] diff --git a/pyproject.toml b/pyproject.toml index 9d750c2..681c903 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,6 +56,10 @@ extend-select = [ "W", # pycodestyle ] +extend-ignore = [ + "G004", # Logging statement uses f-string +] + [tool.ruff.lint.flake8-quotes] docstring-quotes = "double" inline-quotes = "double" diff --git a/test/test_gather.py b/test/test_gather.py index 211c1e9..ca7cebd 100644 --- a/test/test_gather.py +++ b/test/test_gather.py @@ -29,7 +29,7 @@ def create_log(filename: str) -> None: logmgr.tick_before() if i == 5: - warn("warning from fifth timestep") + warn("warning from fifth timestep", stacklevel=2) if i == 10: logger.warning("test on tenth timestep") diff --git a/test/test_logManager.py b/test/test_logManager.py index 14cd505..30caea0 100644 --- a/test/test_logManager.py +++ b/test/test_logManager.py @@ -32,7 +32,7 @@ def test_empty_on_init(basic_logmgr: LogManager): def test_basic_warning(): with pytest.warns(UserWarning): - warn("Oof. Something went awry.", UserWarning) + warn("Oof. Something went awry.", UserWarning, stacklevel=2) def test_warnings_capture_from_warnings_module(basic_logmgr: LogManager): @@ -41,7 +41,7 @@ def test_warnings_capture_from_warnings_module(basic_logmgr: LogManager): basic_logmgr.tick_before() - warn(first_warning_message, first_warning_type) + warn(first_warning_message, first_warning_type, stacklevel=2) # ensure that the warning was captured properly print(basic_logmgr.warning_data[0]) @@ -52,7 +52,7 @@ def test_warnings_capture_from_warnings_module(basic_logmgr: LogManager): second_warning_message = "Not a warning: Second warning message" second_warning_type = UserWarning - warn(second_warning_message, second_warning_type) + warn(second_warning_message, second_warning_type, stacklevel=2) # ensure that the warning was captured properly print(basic_logmgr.warning_data[1]) diff --git a/test/test_upgrade_db.py b/test/test_upgrade_db.py index b5f9d38..c219849 100644 --- a/test/test_upgrade_db.py +++ b/test/test_upgrade_db.py @@ -26,8 +26,8 @@ def test_upgrade_v2_v3(): conn = sqlite3.connect(upgraded_name) try: print(list(conn.execute("select * from logging"))) - except sqlite3.OperationalError: + except sqlite3.OperationalError as err: os.remove(upgraded_name) - raise AssertionError(f"{upgraded_name} is not a v3 database") + raise AssertionError(f"{upgraded_name} is not a v3 database") from err conn.close() os.remove(upgraded_name) From c1b1121c9f6b3c63f2be50598b5ceac339ca4a73 Mon Sep 17 00:00:00 2001 From: Matthias Diener Date: Thu, 19 Dec 2024 10:48:39 +0100 Subject: [PATCH 04/25] typos --- doc/analysis.rst | 6 +++--- logpyle/HTMLalyzer/htmlalyzer.html | 10 +++++----- logpyle/HTMLalyzer/main.py | 6 +++--- test/test_logManager.py | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/doc/analysis.rst b/doc/analysis.rst index e55cfb1..30dd4f3 100644 --- a/doc/analysis.rst +++ b/doc/analysis.rst @@ -32,7 +32,7 @@ the hood, this ensures that the quantity is gathered from all present run_ids. Magic refers to the usage of customized commands to operate on our mangled and non-mangled SQL queries. To issue a magic command, preceding it with a ``.``. -This will issue a command to runalyzer instead of refering to a python symbol. +This will issue a command to runalyzer instead of referring to a python symbol. Running the script ------------------ @@ -222,11 +222,11 @@ before serving it. Usage ----- After the virtual environment has been setup, click the ``Add file`` button -to add a pannel for analysis. +to add a panel for analysis. To analyze a run, click on the browse button to upload one or more files. These files will be gathered together under the hood. You can then select -quantites from the X and Y dropdowns. If you would like to keep track of +quantities from the X and Y dropdowns. If you would like to keep track of multiple quantities in the same graph, you can press ``Add Line to Plot`` to add a Y dropdown. diff --git a/logpyle/HTMLalyzer/htmlalyzer.html b/logpyle/HTMLalyzer/htmlalyzer.html index 11bcad4..64aea75 100644 --- a/logpyle/HTMLalyzer/htmlalyzer.html +++ b/logpyle/HTMLalyzer/htmlalyzer.html @@ -518,7 +518,7 @@ names = ["$" + s for s in names] query_args = ", ".join(names) - # shoud remake in the future to store the connection instead of + # should remake in the future to store the connection instead of # re-gathering it every time the button is pressed run_db = make_wrapped_db(file_dict[id].names, True, True) run_db.print_cursor(run_db.q("select " + query_args)) @@ -618,7 +618,7 @@ # create plot group chart_button = document.getElementById("chartsButton" + str(id)) chart_button .addEventListener("click", create_proxy(run_chart)) - # add quantites to quantity 1 dropdown + # add quantities to quantity 1 dropdown plot_q1_select = document.getElementById("quantity1_" + str(id)) for quantity in file_dict[id].quantities: item = document.createElement("option") @@ -628,7 +628,7 @@ if quantity == "step": plot_q1_select.value = quantity - # add quantites to quantity 2 dropdown + # add quantities to quantity 2 dropdown plot_q2_select = document.getElementById("quantity2_" + str(id)) for quantity in file_dict[id].quantities: item = document.createElement("option") @@ -645,7 +645,7 @@ table_button = document.getElementById("tableButton" + str(id)) table_button.addEventListener("click", create_proxy(add_table_list)) - # add quantites to table dropdown + # add quantities to table dropdown table_select = document.getElementById("tableQuantitySelect" + str(id)) for quantity in file_dict[id].quantities: item = document.createElement("option") @@ -665,7 +665,7 @@ file_dict: dict[str, Any] = {} # ensure logpyle and dependencies are present asyncio.ensure_future(import_logpyle()) -# ensure that one analysis pannel is present to begin with +# ensure that one analysis panel is present to begin with add_file_func() diff --git a/logpyle/HTMLalyzer/main.py b/logpyle/HTMLalyzer/main.py index 13ada45..ce924f0 100644 --- a/logpyle/HTMLalyzer/main.py +++ b/logpyle/HTMLalyzer/main.py @@ -237,7 +237,7 @@ def print_table(event: Any) -> None: names = ["$" + s for s in names] query_args = ", ".join(names) - # shoud remake in the future to store the connection instead of + # should remake in the future to store the connection instead of # re-gathering it every time the button is pressed run_db = make_wrapped_db(file_dict[id].names, True, True) run_db.print_cursor(run_db.q("select " + query_args)) @@ -337,7 +337,7 @@ async def store_file(event: Any) -> None: # create plot group chart_button = document.getElementById("chartsButton" + str(id)) chart_button .addEventListener("click", create_proxy(run_chart)) - # add quantites to quantity 1 dropdown + # add quantities to quantity 1 dropdown plot_q1_select = document.getElementById("quantity1_" + str(id)) for quantity in file_dict[id].quantities: item = document.createElement("option") @@ -347,7 +347,7 @@ async def store_file(event: Any) -> None: if quantity == "step": plot_q1_select.value = quantity - # add quantites to quantity 2 dropdown + # add quantities to quantity 2 dropdown plot_q2_select = document.getElementById("quantity2_" + str(id)) for quantity in file_dict[id].quantities: item = document.createElement("option") diff --git a/test/test_logManager.py b/test/test_logManager.py index 30caea0..136351c 100644 --- a/test/test_logManager.py +++ b/test/test_logManager.py @@ -728,7 +728,7 @@ def test_eventcounter(basic_logmgr: LogManager): # transfer counter1's count to counter2's basic_logmgr.tick_before() - # at the beggining of tick, counter should clear + # at the beginning of tick, counter should clear print(counter1.events) assert counter1.events == 0 From 9714f7137a304ac1e0c2be13ae592e7ebe594ce2 Mon Sep 17 00:00:00 2001 From: Matthias Diener Date: Thu, 19 Dec 2024 11:31:53 +0100 Subject: [PATCH 05/25] dist scripts --- bin/upgrade-db | 0 pyproject.toml | 16 +++++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) mode change 100644 => 100755 bin/upgrade-db diff --git a/bin/upgrade-db b/bin/upgrade-db old mode 100644 new mode 100755 diff --git a/pyproject.toml b/pyproject.toml index 681c903..e0a679a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,9 +2,23 @@ requires = ["hatchling"] build-backend = "hatchling.build" +[tool.hatch.build.targets.sdist.force-include] +"bin/htmlalyzer" = "logpyle/htmlalyzer" +"bin/logtool" = "logpyle/logtool" +"bin/runalyzer" = "logpyle/runalyzer" +"bin/runalyzer-gather" = "logpyle/runalyzer-gather" +"bin/upgrade-db" = "logpyle/upgrade-db" + +[tool.hatch.build.targets.wheel.force-include] +"bin/htmlalyzer" = "logpyle/htmlalyzer" +"bin/logtool" = "logpyle/logtool" +"bin/runalyzer" = "logpyle/runalyzer" +"bin/runalyzer-gather" = "logpyle/runalyzer-gather" +"bin/upgrade-db" = "logpyle/upgrade-db" + [project] name = "logpyle" -version = "2023.5" +version = "2024.0" authors = [ { name = "Andreas Kloeckner", email = "inform@tiker.net" }, ] From 45d5d033b453a493d4f29ba8f65dafaa4e5cd363 Mon Sep 17 00:00:00 2001 From: Matthias Diener Date: Thu, 19 Dec 2024 23:40:34 +0100 Subject: [PATCH 06/25] switch to setuptools --- pyproject.toml | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e0a679a..2cd2e8a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,20 +1,15 @@ [build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" +requires = ["setuptools"] +build-backend = "setuptools.build_meta" -[tool.hatch.build.targets.sdist.force-include] -"bin/htmlalyzer" = "logpyle/htmlalyzer" -"bin/logtool" = "logpyle/logtool" -"bin/runalyzer" = "logpyle/runalyzer" -"bin/runalyzer-gather" = "logpyle/runalyzer-gather" -"bin/upgrade-db" = "logpyle/upgrade-db" - -[tool.hatch.build.targets.wheel.force-include] -"bin/htmlalyzer" = "logpyle/htmlalyzer" -"bin/logtool" = "logpyle/logtool" -"bin/runalyzer" = "logpyle/runalyzer" -"bin/runalyzer-gather" = "logpyle/runalyzer-gather" -"bin/upgrade-db" = "logpyle/upgrade-db" +[tool.setuptools] +script-files = [ + "bin/htmlalyzer", + "bin/logtool", + "bin/runalyzer-gather", + "bin/runalyzer", + "bin/upgrade-db", +] [project] name = "logpyle" From d3d02eb9bfbbeb16001e3d3f1e58e2e752c01c0d Mon Sep 17 00:00:00 2001 From: Matthias Diener Date: Thu, 19 Dec 2024 23:48:25 +0100 Subject: [PATCH 07/25] ci --- .github/workflows/ci.yaml | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d3ceff7..457d14b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -21,6 +21,12 @@ jobs: python -m pip install ruff ruff check + typos: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: crate-ci/typos@master + examples: runs-on: ${{ matrix.os }} strategy: @@ -32,7 +38,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} on ${{ matrix.os }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install prerequisites @@ -150,7 +156,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3.x' - name: "Main Script" @@ -164,20 +170,20 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3.x' - name: "Main Script" run: | curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/main/prepare-and-run-mypy.sh - export EXTRA_INSTALL="pytools numpy types-psutil pymbolic" + export EXTRA_INSTALL="pytools numpy types-psutil pymbolic mpi4py matplotlib pylab" . ./prepare-and-run-mypy.sh python3 pytest: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3.x' @@ -195,7 +201,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3.x' - name: Compare coverage with 'main' branch @@ -225,7 +231,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3.x' @@ -244,7 +250,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3.x' From 302959ba6365b6ac43f1a2830efd56459bbc7ffa Mon Sep 17 00:00:00 2001 From: Matthias Diener Date: Fri, 20 Dec 2024 11:57:45 +0100 Subject: [PATCH 08/25] better __version__ --- logpyle/__init__.py | 9 ++++++--- logpyle/version.py | 3 --- pyproject.toml | 1 + 3 files changed, 7 insertions(+), 6 deletions(-) delete mode 100644 logpyle/version.py diff --git a/logpyle/__init__.py b/logpyle/__init__.py index c7bbdff..fb89ff5 100644 --- a/logpyle/__init__.py +++ b/logpyle/__init__.py @@ -63,10 +63,13 @@ THE SOFTWARE. """ +try: + import importlib.metadata as importlib_metadata +except ModuleNotFoundError: # pragma: no cover + # Python 3.7 + import importlib_metadata # type: ignore[no-redef] -import logpyle.version - -__version__ = logpyle.version.VERSION_TEXT +__version__ = importlib_metadata.version(__package__ or __name__) import logging import sys diff --git a/logpyle/version.py b/logpyle/version.py deleted file mode 100644 index 02b5ece..0000000 --- a/logpyle/version.py +++ /dev/null @@ -1,3 +0,0 @@ -VERSION = (2023, 4, 1) -VERSION_STATUS = "" -VERSION_TEXT = ".".join(str(x) for x in VERSION) + VERSION_STATUS diff --git a/pyproject.toml b/pyproject.toml index 2cd2e8a..d67eb67 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,6 +21,7 @@ description = "Time series logging for Python" dependencies = [ "pytools>=2011.1", "pymbolic", + "importlib_metadata;python_version<'3.8'", ] readme = "README.md" license = { file="LICENSE" } From 596872114fe4e8510cd9e29258435af80829a691 Mon Sep 17 00:00:00 2001 From: Matthias Diener Date: Fri, 20 Dec 2024 12:03:30 +0100 Subject: [PATCH 09/25] doc conf --- doc/conf.py | 94 +++++------------------------------------------------ 1 file changed, 9 insertions(+), 85 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index 4162076..90ee5db 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -2,98 +2,23 @@ import sys -# -- General configuration ------------------------------------------------ +from urllib.request import urlopen -# If your documentation needs a minimal Sphinx version, state it here. -# -# needs_sphinx = '1.0' +from logpyle import __version__ -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - "sphinx.ext.autodoc", - "sphinx.ext.doctest", - "sphinx.ext.intersphinx", - "sphinx.ext.mathjax", - "sphinx.ext.todo", - "sphinx.ext.viewcode", - "sphinx.ext.napoleon", - "sphinx_copybutton", -] -autoclass_content = "class" -# autodoc_typehints = "description" - -# Add any paths that contain templates here, relative to this directory. -templates_path = ["_templates"] - -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# -# source_suffix = ['.rst', '.md'] -source_suffix = ".rst" +sys._BUILDING_SPHINX_DOCS = True -# The master toctree document. -master_doc = "index" +_conf_url = \ + "https://raw.githubusercontent.com/matthiasdiener/sphinxconfig/main/sphinxconfig.py" +with urlopen(_conf_url) as _inf: + exec(compile(_inf.read(), _conf_url, "exec"), globals()) # General information about the project. project = "logpyle" copyright = "2017, Andreas Kloeckner" author = "Andreas Kloeckner" - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -ver_dic = {} -exec( - compile(open("../logpyle/version.py").read(), "../logpyle/version.py", "exec"), - ver_dic, -) -version = ".".join(str(x) for x in ver_dic["VERSION"]) -release = ver_dic["VERSION_TEXT"] - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = "en" - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = "sphinx" - -# If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = True - -nitpicky = True - - -# -- Options for HTML output ---------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = "furo" - -html_theme_options = {} - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# -# html_theme_options = {} - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -# html_static_path = ['_static'] +version = __version__ +release = __version__ intersphinx_mapping = { @@ -105,4 +30,3 @@ } -sys._BUILDING_SPHINX_DOCS = True From b475abcf26c42e2b6988c854e7b25a61fd56a0f3 Mon Sep 17 00:00:00 2001 From: Matthias Diener Date: Fri, 20 Dec 2024 22:48:54 +0100 Subject: [PATCH 10/25] doc fixes --- doc/conf.py | 10 +++++++++- logpyle/__init__.py | 38 +++++++++++++++++++------------------- 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index 90ee5db..ecd1946 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -9,10 +9,18 @@ sys._BUILDING_SPHINX_DOCS = True _conf_url = \ - "https://raw.githubusercontent.com/matthiasdiener/sphinxconfig/main/sphinxconfig.py" + "https://raw.githubusercontent.com/inducer/sphinxconfig/main/sphinxconfig.py" with urlopen(_conf_url) as _inf: exec(compile(_inf.read(), _conf_url, "exec"), globals()) +old_linkcode_resolve = linkcode_resolve + +def lc(domain, info): + linkcode_url = "https://github.com/illinois-ceesd/logpyle/blob/main/{filepath}#L{linestart}-L{linestop}" + return old_linkcode_resolve(domain, info, linkcode_url=linkcode_url) + +linkcode_resolve = lc + # General information about the project. project = "logpyle" copyright = "2017, Andreas Kloeckner" diff --git a/logpyle/__init__.py b/logpyle/__init__.py index fb89ff5..ac2527c 100644 --- a/logpyle/__init__.py +++ b/logpyle/__init__.py @@ -93,8 +93,8 @@ cast, ) -from pymbolic.compiler import CompiledExpression # type: ignore[import-untyped] -from pymbolic.primitives import Expression # type: ignore[import-untyped] +from pymbolic.compiler import CompiledExpression +from pymbolic.primitives import ExpressionNode from pytools.datatable import DataTable logger = logging.getLogger(__name__) @@ -418,15 +418,15 @@ class _DependencyData: qdat: _QuantityData agg_func: Callable[..., Any] varname: str - expr: Expression + expr: ExpressionNode nonlocal_agg: bool table: Optional[DataTable] = None @dataclass class _WatchInfo: - parsed: Expression - expr: Expression + parsed: ExpressionNode + expr: ExpressionNode dep_data: List[_DependencyData] compiled: CompiledExpression unit: Optional[str] @@ -896,7 +896,7 @@ def add_watches(self, watches: List[Union[str, Tuple[str, str]]]) -> None: self.have_nonlocal_watches = self.have_nonlocal_watches or \ any(dd.nonlocal_agg for dd in dep_data) - from pymbolic import compile # type: ignore[import-untyped] + from pymbolic import compile compiled = compile(parsed, [dd.varname for dd in dep_data]) watch_info = _WatchInfo(parsed=parsed, expr=expr, dep_data=dep_data, @@ -1104,10 +1104,10 @@ def add_internal(name: str, unit: Optional[str], description: Optional[str], self.save() - def get_expr_dataset(self, expression: Expression, + def get_expr_dataset(self, expression: ExpressionNode, description: Optional[str] = None, unit: Optional[str] = None) \ - -> Tuple[Union[str, Any], Union[str, Any, None], + -> Tuple[Union[str, Any], Union[str, Any], List[Tuple[int, Any]]]: """Prepare a time-series dataset for a given expression. @@ -1165,7 +1165,7 @@ def get_expr_dataset(self, expression: Expression, return (description, unit, data) - def get_joint_dataset(self, expressions: Sequence[Expression]) -> List[Any]: + def get_joint_dataset(self, expressions: Sequence[ExpressionNode]) -> List[Any]: """Return a joint data set for a list of expressions. :arg expressions: a list of either strings representing @@ -1198,7 +1198,7 @@ def get_joint_dataset(self, expressions: Sequence[Expression]) -> List[Any]: return zipped_dubs - def get_plot_data(self, expr_x: Expression, expr_y: Expression, + def get_plot_data(self, expr_x: ExpressionNode, expr_y: ExpressionNode, min_step: Optional[int] = None, max_step: Optional[int] = None) \ -> Tuple[Tuple[Any, str, str], Tuple[Any, str, str]]: @@ -1224,8 +1224,8 @@ def get_plot_data(self, expr_x: Expression, expr_y: Expression, return (data_x, descr_x, unit_x), \ (data_y, descr_y, unit_y) - def write_datafile(self, filename: str, expr_x: Expression, - expr_y: Expression) -> None: + def write_datafile(self, filename: str, expr_x: ExpressionNode, + expr_y: ExpressionNode) -> None: (data_x, label_x, _), (data_y, label_y, _) = self.get_plot_data( expr_x, expr_y) @@ -1235,7 +1235,7 @@ def write_datafile(self, filename: str, expr_x: Expression, outf.write(f"{dx!r}\t{dy!r}\n") outf.close() - def plot_matplotlib(self, expr_x: Expression, expr_y: Expression) -> None: + def plot_matplotlib(self, expr_x: ExpressionNode, expr_y: ExpressionNode) -> None: from matplotlib.pyplot import plot, xlabel, ylabel (data_x, descr_x, unit_x), (data_y, descr_y, unit_y) = \ @@ -1247,7 +1247,7 @@ def plot_matplotlib(self, expr_x: Expression, expr_y: Expression) -> None: # {{{ private functionality - def _parse_expr(self, expr: Expression) -> Any: + def _parse_expr(self, expr: str) -> Any: from pymbolic import parse, substitute parsed = parse(expr) @@ -1257,8 +1257,8 @@ def _parse_expr(self, expr: Expression) -> Any: return parsed def _get_expr_dep_data(self, # noqa: C901 - parsed: Expression) \ - -> Tuple[Expression, List[_DependencyData]]: + parsed: ExpressionNode) \ + -> Tuple[ExpressionNode, List[_DependencyData]]: class Nth: def __init__(self, n: int) -> None: self.n = n @@ -1266,7 +1266,7 @@ def __init__(self, n: int) -> None: def __call__(self, lst: List[Any]) -> Any: return lst[self.n] - import pymbolic.mapper.dependency as pmd # type: ignore[import-untyped] + import pymbolic.mapper.dependency as pmd deps = pmd.DependencyMapper(include_calls=False)(parsed) # gather information on aggregation expressions @@ -1287,7 +1287,7 @@ def __call__(self, lst: List[Any]) -> Any: raise ValueError( f"must specify explicit aggregator for '{name}'") - def agg_func(lst): + def agg_func(lst: Sequence[Any]) -> Any: return lst[0] elif isinstance(dep, Lookup): assert isinstance(dep.aggregate, Variable) @@ -1317,7 +1317,7 @@ def agg_func(lst): elif agg_name == "norm2": from math import sqrt - def agg_func(iterable): + def agg_func(iterable: Iterable[Any]) -> float: return sqrt(sum(entry ** 2 for entry in iterable)) else: raise ValueError(f"invalid rank aggregator '{agg_name}'") From e57294877759b510445ff07ed3c18dbad57ec453 Mon Sep 17 00:00:00 2001 From: Matthias Diener Date: Sun, 22 Dec 2024 11:37:53 +0100 Subject: [PATCH 11/25] ruff --- doc/conf.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index ecd1946..0ca19ec 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -1,7 +1,6 @@ #!/usr/bin/env python3 import sys - from urllib.request import urlopen from logpyle import __version__ @@ -13,12 +12,14 @@ with urlopen(_conf_url) as _inf: exec(compile(_inf.read(), _conf_url, "exec"), globals()) -old_linkcode_resolve = linkcode_resolve +old_linkcode_resolve = linkcode_resolve # noqa: F821 (linkcode_resolve comes from the URL above) + def lc(domain, info): linkcode_url = "https://github.com/illinois-ceesd/logpyle/blob/main/{filepath}#L{linestart}-L{linestop}" return old_linkcode_resolve(domain, info, linkcode_url=linkcode_url) + linkcode_resolve = lc # General information about the project. @@ -36,5 +37,3 @@ def lc(domain, info): "pytools": ("https://documen.tician.de/pytools/", None), "mpi4py": ("https://mpi4py.readthedocs.io/en/stable/", None), } - - From 82b0f303bc740060626ac5d8d9d42310a81b5164 Mon Sep 17 00:00:00 2001 From: Matthias Diener Date: Sun, 22 Dec 2024 11:39:04 +0100 Subject: [PATCH 12/25] hash for htmlalyzer --- logpyle/HTMLalyzer/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/logpyle/HTMLalyzer/__init__.py b/logpyle/HTMLalyzer/__init__.py index 317d654..9497d54 100644 --- a/logpyle/HTMLalyzer/__init__.py +++ b/logpyle/HTMLalyzer/__init__.py @@ -15,7 +15,6 @@ def get_current_hash() -> str: "__init__.py", "runalyzer.py", "runalyzer_gather.py", - "version.py", "HTMLalyzer/templates/index.html", "HTMLalyzer/templates/newFile.html", "HTMLalyzer/main.css", From 90b1c5023b034dad944a5dc2aadd68e83ca64283 Mon Sep 17 00:00:00 2001 From: Matthias Diener Date: Sun, 22 Dec 2024 11:50:35 +0100 Subject: [PATCH 13/25] bump minimum Python to 3.10 --- .github/workflows/ci.yaml | 4 +- examples/log-mpi.py | 3 +- examples/optional-log.py | 3 +- logpyle/HTMLalyzer/main.py | 2 +- logpyle/__init__.py | 164 +++++++++++++++++------------------- logpyle/runalyzer.py | 49 +++++------ logpyle/runalyzer_gather.py | 34 ++++---- pyproject.toml | 3 +- test/test_quantities.py | 14 +-- 9 files changed, 128 insertions(+), 148 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 457d14b..3bd134a 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -32,7 +32,7 @@ jobs: strategy: fail-fast: true matrix: - python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12', '3.x'] + python-version: ['3.10', '3.11', '3.12', '3.x'] os: [ubuntu-latest, macos-13] steps: @@ -175,6 +175,8 @@ jobs: python-version: '3.x' - name: "Main Script" run: | + set -x + sudo apt-get update && sudo apt-get install -y libopenmpi-dev curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/main/prepare-and-run-mypy.sh export EXTRA_INSTALL="pytools numpy types-psutil pymbolic mpi4py matplotlib pylab" . ./prepare-and-run-mypy.sh python3 diff --git a/examples/log-mpi.py b/examples/log-mpi.py index e68ef2b..232b71e 100755 --- a/examples/log-mpi.py +++ b/examples/log-mpi.py @@ -1,9 +1,10 @@ #!/usr/bin/env python3 import logging +from collections.abc import Callable from random import uniform from time import sleep -from typing import Any, Callable +from typing import Any from warnings import warn from mpi4py import MPI diff --git a/examples/optional-log.py b/examples/optional-log.py index 6739159..af30635 100755 --- a/examples/optional-log.py +++ b/examples/optional-log.py @@ -3,7 +3,6 @@ from contextlib import nullcontext from random import uniform from time import sleep -from typing import Union from logpyle import ( IntervalTimer, @@ -32,7 +31,7 @@ def main(use_logpyle: bool) -> None: # noqa: C901 if logmgr: vis_timer = IntervalTimer("t_vis", "Time spent visualizing") logmgr.add_quantity(vis_timer) - time_vis: Union[_SubTimer, nullcontext[None]] = vis_timer.get_sub_timer() + time_vis: _SubTimer | nullcontext[None] = vis_timer.get_sub_timer() else: time_vis = nullcontext() diff --git a/logpyle/HTMLalyzer/main.py b/logpyle/HTMLalyzer/main.py index ce924f0..2dd5060 100644 --- a/logpyle/HTMLalyzer/main.py +++ b/logpyle/HTMLalyzer/main.py @@ -265,7 +265,7 @@ async def store_file(event: Any) -> None: cursor = run_db.db.execute("select * from runs") columns = [col[0] for col in cursor.description] vals = list(next(iter(cursor))) - for (col, val) in zip(columns, vals): + for (col, val) in zip(columns, vals, strict=False): file_dict[id].constants[col] = val # extract quantities from sqlite file diff --git a/logpyle/__init__.py b/logpyle/__init__.py index ac2527c..b942a22 100644 --- a/logpyle/__init__.py +++ b/logpyle/__init__.py @@ -63,33 +63,21 @@ THE SOFTWARE. """ -try: - import importlib.metadata as importlib_metadata -except ModuleNotFoundError: # pragma: no cover - # Python 3.7 - import importlib_metadata # type: ignore[no-redef] +import importlib.metadata -__version__ = importlib_metadata.version(__package__ or __name__) +__version__ = importlib.metadata.version(__package__ or __name__) import logging import sys +from collections.abc import Callable, Generator, Iterable, Sequence from dataclasses import dataclass from sqlite3 import Connection from time import monotonic as time_monotonic from typing import ( TYPE_CHECKING, Any, - Callable, - Dict, - Generator, - Iterable, - List, Optional, - Sequence, TextIO, - Tuple, - Type, - Union, cast, ) @@ -118,8 +106,8 @@ class LogQuantity: sort_weight = 0 - def __init__(self, name: str, unit: Optional[str] = None, - description: Optional[str] = None) -> None: + def __init__(self, name: str, unit: str | None = None, + description: str | None = None) -> None: """Create a new quantity. Parameters @@ -186,9 +174,9 @@ class MultiLogQuantity: """ sort_weight = 0 - def __init__(self, names: List[str], - units: Optional[Sequence[Optional[str]]] = None, - descriptions: Optional[Sequence[Optional[str]]] = None) -> None: + def __init__(self, names: list[str], + units: Sequence[str | None] | None = None, + descriptions: Sequence[str | None] | None = None) -> None: """Create a new quantity. Parameters @@ -205,17 +193,17 @@ def __init__(self, names: List[str], self.names = names if units is None: - self.units: Sequence[Optional[str]] = len(names) * [None] + self.units: Sequence[str | None] = len(names) * [None] else: self.units = units if descriptions is None: - self.descriptions: Sequence[Optional[str]] = len(names) * [None] + self.descriptions: Sequence[str | None] = len(names) * [None] else: self.descriptions = descriptions @property - def default_aggregators(self) -> List[None]: + def default_aggregators(self) -> list[None]: """List of default aggregators.""" return [None] * len(self.names) @@ -223,7 +211,7 @@ def tick(self) -> None: """Perform updates required at every :class:`LogManager` tick.""" pass - def __call__(self) -> Iterable[Optional[float]]: + def __call__(self) -> Iterable[float | None]: """Return an iterable of the current values of the diagnostic represented by this :class:`MultiLogQuantity`. @@ -248,9 +236,9 @@ class MultiPostLogQuantity(MultiLogQuantity, PostLogQuantity): class DtConsumer: def __init__(self) -> None: - self.dt: Optional[float] = None + self.dt: float | None = None - def set_dt(self, dt: Optional[float]) -> None: + def set_dt(self, dt: float | None) -> None: self.dt = dt @@ -266,24 +254,24 @@ def tick(self) -> None: class SimulationLogQuantity(PostLogQuantity, DtConsumer): """A source of loggable scalars that needs to know the simulation timestep.""" - def __init__(self, name: str, unit: Optional[str] = None, - description: Optional[str] = None) -> None: + def __init__(self, name: str, unit: str | None = None, + description: str | None = None) -> None: PostLogQuantity.__init__(self, name, unit, description) DtConsumer.__init__(self) class PushLogQuantity(LogQuantity): - def __init__(self, name: str, unit: Optional[str] = None, - description: Optional[str] = None) -> None: + def __init__(self, name: str, unit: str | None = None, + description: str | None = None) -> None: LogQuantity.__init__(self, name, unit, description) - self.value: Optional[float] = None + self.value: float | None = None def push_value(self, value: float) -> None: if self.value is not None: raise RuntimeError("can't push two values per cycle") self.value = value - def __call__(self) -> Optional[float]: + def __call__(self) -> float | None: v = self.value self.value = None return v @@ -292,7 +280,7 @@ def __call__(self) -> Optional[float]: class CallableLogQuantityAdapter(LogQuantity): """Adapt a 0-ary callable as a :class:`LogQuantity`.""" def __init__(self, callable: Callable[[], float], name: str, - unit: Optional[str] = None, description: Optional[str] = None) \ + unit: str | None = None, description: str | None = None) \ -> None: self.callable = callable LogQuantity.__init__(self, name, unit, description) @@ -313,13 +301,13 @@ class _GatherDescriptor: @dataclass(frozen=True) class _QuantityData: - unit: Optional[str] - description: Optional[str] - default_aggregator: Optional[Callable[..., Any]] + unit: str | None + description: str | None + default_aggregator: Callable[..., Any] | None -def _join_by_first_of_tuple(list_of_iterables: List[Iterable[Any]]) \ - -> Generator[Tuple[int, List[Any]], None, None]: +def _join_by_first_of_tuple(list_of_iterables: list[Iterable[Any]]) \ + -> Generator[tuple[int, list[Any]], None, None]: loi = [i.__iter__() for i in list_of_iterables] if not loi: return @@ -420,16 +408,16 @@ class _DependencyData: varname: str expr: ExpressionNode nonlocal_agg: bool - table: Optional[DataTable] = None + table: DataTable | None = None @dataclass class _WatchInfo: parsed: ExpressionNode expr: ExpressionNode - dep_data: List[_DependencyData] + dep_data: list[_DependencyData] compiled: CompiledExpression - unit: Optional[str] + unit: str | None format: str @@ -504,7 +492,7 @@ class LogManager: .. automethod:: tick_after """ - def __init__(self, filename: Optional[str] = None, mode: str = "r", # noqa: C901 + def __init__(self, filename: str | None = None, mode: str = "r", # noqa: C901 mpi_comm: Optional["mpi4py.MPI.Comm"] = None, capture_warnings: bool = True, watch_interval: float = 1.0, @@ -534,13 +522,13 @@ def __init__(self, filename: Optional[str] = None, mode: str = "r", # noqa: C90 assert isinstance(mode, str), "mode must be a string" assert mode in ["w", "r", "wu", "wo"], "invalid mode" - self.quantity_data: Dict[str, _QuantityData] = {} - self.last_values: Dict[str, Optional[float]] = {} - self.before_gather_descriptors: List[_GatherDescriptor] = [] - self.after_gather_descriptors: List[_GatherDescriptor] = [] + self.quantity_data: dict[str, _QuantityData] = {} + self.last_values: dict[str, float | None] = {} + self.before_gather_descriptors: list[_GatherDescriptor] = [] + self.after_gather_descriptors: list[_GatherDescriptor] = [] self.tick_count = 0 - self.constants: Dict[str, object] = {} + self.constants: dict[str, object] = {} self.last_save_time = time_monotonic() @@ -563,7 +551,7 @@ def __init__(self, filename: Optional[str] = None, mode: str = "r", # noqa: C90 self.weakref_finalize: Callable[..., Any] = lambda: None # watch stuff - self.watches: List[_WatchInfo] = [] + self.watches: list[_WatchInfo] = [] self.have_nonlocal_watches = False # Interval between printing watches, in seconds @@ -572,7 +560,7 @@ def __init__(self, filename: Optional[str] = None, mode: str = "r", # noqa: C90 # database binding import sqlite3 as sqlite - self.sqlite_filename: Optional[str] = None + self.sqlite_filename: str | None = None if filename is None: file_base = ":memory:" file_extension = "" @@ -652,13 +640,13 @@ def __init__(self, filename: Optional[str] = None, mode: str = "r", # noqa: C90 # {{{ warnings/logging capture - self.warning_data: List[_LogWarningInfo] = [] - self.old_showwarning: Optional[Callable[..., Any]] = None + self.warning_data: list[_LogWarningInfo] = [] + self.old_showwarning: Callable[..., Any] | None = None if capture_warnings and self.mode[0] == "w": self.capture_warnings(True) - self.logging_data: List[_LogWarningInfo] = [] - self.logging_handler: Optional[logging.Handler] = None + self.logging_data: list[_LogWarningInfo] = [] + self.logging_handler: logging.Handler | None = None if capture_logging and self.mode[0] == "w": self.capture_logging(True) @@ -685,7 +673,7 @@ def __init__(self, filename: Optional[str] = None, mode: str = "r", # noqa: C90 def __del__(self) -> None: self.weakref_finalize() - def enable_save_on_sigterm(self) -> Union[Callable[..., Any], int, None]: + def enable_save_on_sigterm(self) -> Callable[..., Any] | int | None: """Enable saving the log on SIGTERM. :returns: The previous SIGTERM handler. @@ -704,9 +692,9 @@ def sighndl(_signo: int, _stackframe: Any) -> None: def capture_warnings(self, enable: bool = True) -> None: """Enable or disable :mod:`warnings` capture.""" - def _showwarning(message: Union[Warning, str], category: Type[Warning], - filename: str, lineno: int, file: Optional[TextIO] = None, - line: Optional[str] = None) -> None: + def _showwarning(message: Warning | str, category: type[Warning], + filename: str, lineno: int, file: TextIO | None = None, + line: str | None = None) -> None: assert self.old_showwarning self.old_showwarning(message, category, filename, lineno, file, line) @@ -863,7 +851,7 @@ def get_warnings(self) -> DataTable: return result - def add_watches(self, watches: List[Union[str, Tuple[str, str]]]) -> None: + def add_watches(self, watches: list[str | tuple[str, str]]) -> None: """Add quantities that are printed after every time step. :arg watches: @@ -931,7 +919,7 @@ def set_constant(self, name: str, value: Any) -> None: self.db_conn.execute("insert into constants values (?,?)", (name, value)) - def _insert_datapoint(self, name: str, value: Optional[float]) -> None: + def _insert_datapoint(self, name: str, value: float | None) -> None: if value is None: return @@ -961,7 +949,7 @@ def _gather_for_descriptor(self, gd: _GatherDescriptor) -> None: if self.tick_count % gd.interval == 0: q_value = gd.quantity() if isinstance(gd.quantity, MultiLogQuantity): - for name, value in zip(gd.quantity.names, q_value): + for name, value in zip(gd.quantity.names, q_value, strict=False): self._insert_datapoint(name, value) else: self._insert_datapoint(gd.quantity.name, q_value) @@ -1066,8 +1054,8 @@ def add_quantity(self, quantity: LogQuantity, interval: int = 1) -> None: :arg interval: interval (in time steps) when to gather this quantity. """ - def add_internal(name: str, unit: Optional[str], description: Optional[str], - def_agg: Optional[Callable[..., Any]]) -> None: + def add_internal(name: str, unit: str | None, description: str | None, + def_agg: Callable[..., Any] | None) -> None: logger.debug(f"adding log quantity '{name}'") if name in self.quantity_data: @@ -1095,7 +1083,7 @@ def add_internal(name: str, unit: Optional[str], description: Optional[str], quantity.names, quantity.units, quantity.descriptions, - quantity.default_aggregators): + quantity.default_aggregators, strict=False): add_internal(name, unit, description, def_agg) else: add_internal(quantity.name, @@ -1105,10 +1093,10 @@ def add_internal(name: str, unit: Optional[str], description: Optional[str], self.save() def get_expr_dataset(self, expression: ExpressionNode, - description: Optional[str] = None, - unit: Optional[str] = None) \ - -> Tuple[Union[str, Any], Union[str, Any], - List[Tuple[int, Any]]]: + description: str | None = None, + unit: str | None = None) \ + -> tuple[str | Any, str | Any, + list[tuple[int, Any]]]: """Prepare a time-series dataset for a given expression. :arg expression: A :mod:`pymbolic` expression that may involve @@ -1165,7 +1153,7 @@ def get_expr_dataset(self, expression: ExpressionNode, return (description, unit, data) - def get_joint_dataset(self, expressions: Sequence[ExpressionNode]) -> List[Any]: + def get_joint_dataset(self, expressions: Sequence[ExpressionNode]) -> list[Any]: """Return a joint data set for a list of expressions. :arg expressions: a list of either strings representing @@ -1192,16 +1180,16 @@ def get_joint_dataset(self, expressions: Sequence[ExpressionNode]) -> List[Any]: dubs.append(dub) - zipped_dubs = list(zip(*dubs)) + zipped_dubs = list(zip(*dubs, strict=False)) zipped_dubs[2] = list( _join_by_first_of_tuple(zipped_dubs[2])) return zipped_dubs def get_plot_data(self, expr_x: ExpressionNode, expr_y: ExpressionNode, - min_step: Optional[int] = None, - max_step: Optional[int] = None) \ - -> Tuple[Tuple[Any, str, str], Tuple[Any, str, str]]: + min_step: int | None = None, + max_step: int | None = None) \ + -> tuple[tuple[Any, str, str], tuple[Any, str, str]]: """Generate plot-ready data. :returns: ``(data_x, descr_x, unit_x), (data_y, descr_y, unit_y)`` @@ -1216,7 +1204,7 @@ def get_plot_data(self, expr_x: ExpressionNode, expr_y: ExpressionNode, stepless_data = [tup for _step, tup in data] if stepless_data: - data_x, data_y = list(zip(*stepless_data)) + data_x, data_y = list(zip(*stepless_data, strict=False)) else: data_x = () data_y = () @@ -1231,7 +1219,7 @@ def write_datafile(self, filename: str, expr_x: ExpressionNode, outf = open(filename, "w") outf.write(f"# {label_x} vs. {label_y}\n") - for dx, dy in zip(data_x, data_y): + for dx, dy in zip(data_x, data_y, strict=False): outf.write(f"{dx!r}\t{dy!r}\n") outf.close() @@ -1258,12 +1246,12 @@ def _parse_expr(self, expr: str) -> Any: def _get_expr_dep_data(self, # noqa: C901 parsed: ExpressionNode) \ - -> Tuple[ExpressionNode, List[_DependencyData]]: + -> tuple[ExpressionNode, list[_DependencyData]]: class Nth: def __init__(self, n: int) -> None: self.n = n - def __call__(self, lst: List[Any]) -> Any: + def __call__(self, lst: list[Any]) -> Any: return lst[self.n] import pymbolic.mapper.dependency as pmd @@ -1366,7 +1354,7 @@ def _watch_tick(self) -> None: if self.rank == self.head_rank: assert gathered_data - values: Dict[str, List[Optional[float]]] = {} + values: dict[str, list[float | None]] = {} for data_block in gathered_data: for name, value in data_block.items(): values.setdefault(name, []).append(value) @@ -1436,7 +1424,7 @@ class IntervalTimer(PostLogQuantity): .. automethod:: add_time """ - def __init__(self, name: str, description: Optional[str] = None) -> None: + def __init__(self, name: str, description: str | None = None) -> None: LogQuantity.__init__(self, name, "s", description) self.elapsed: float = 0 @@ -1482,7 +1470,7 @@ class EventCounter(PostLogQuantity): """ def __init__(self, name: str = "interval", - description: Optional[str] = None) -> None: + description: str | None = None) -> None: PostLogQuantity.__init__(self, name, "1", description) self.events = 0 @@ -1506,7 +1494,7 @@ def __call__(self) -> int: def time_and_count_function(f: Callable[..., Any], timer: IntervalTimer, - counter: Optional[EventCounter] = None, + counter: EventCounter | None = None, increment: int = 1) -> Callable[..., Any]: def inner_f(*args: Any, **kwargs: Any) -> Any: if counter is not None: @@ -1552,14 +1540,14 @@ class StepToStepDuration(PostLogQuantity): def __init__(self, name: str = "t_2step") -> None: PostLogQuantity.__init__(self, name, "s", "Step-to-step duration") - self.last_start_time: Optional[float] = None - self.last2_start_time: Optional[float] = None + self.last_start_time: float | None = None + self.last2_start_time: float | None = None def prepare_for_tick(self) -> None: self.last2_start_time = self.last_start_time self.last_start_time = time_monotonic() - def __call__(self) -> Optional[float]: + def __call__(self) -> float | None: if self.last2_start_time is None or self.last_start_time is None: return None else: @@ -1613,7 +1601,7 @@ def __init__(self, name: str = "t_init") -> None: self.create_time = psutil.Process().create_time() self.done = False - def __call__(self) -> Optional[float]: + def __call__(self) -> float | None: if self.done: return None @@ -1693,7 +1681,7 @@ class Timestep(SimulationLogQuantity): def __init__(self, name: str = "dt", unit: str = "s") -> None: SimulationLogQuantity.__init__(self, name, unit, "Simulation Timestep") - def __call__(self) -> Optional[float]: + def __call__(self) -> float | None: return self.dt @@ -1804,10 +1792,10 @@ def __init__(self) -> None: assert len(names) == len(units) == len(descriptions) == 13 - super().__init__(names, cast(List[Optional[str]], units), - cast(List[Optional[str]], descriptions)) + super().__init__(names, cast(list[str | None], units), + cast(list[str | None], descriptions)) - def __call__(self) -> Iterable[Optional[float]]: + def __call__(self) -> Iterable[float | None]: import gc enabled = gc.isenabled() diff --git a/logpyle/runalyzer.py b/logpyle/runalyzer.py index 58642cd..29ffeda 100644 --- a/logpyle/runalyzer.py +++ b/logpyle/runalyzer.py @@ -17,21 +17,12 @@ import logging +from collections.abc import Callable, Generator, Sequence from dataclasses import dataclass from itertools import product from sqlite3 import Connection, Cursor from typing import ( Any, - Callable, - Dict, - Generator, - List, - Optional, - Sequence, - Set, - Tuple, - Type, - Union, ) from pytools import Table @@ -41,7 +32,7 @@ @dataclass(frozen=True) class PlotStyle: - dashes: Tuple[int, ...] + dashes: tuple[int, ...] color: str @@ -57,7 +48,7 @@ class RunDB: def __init__(self, db: Connection, interactive: bool) -> None: self.db = db self.interactive = interactive - self.rank_agg_tables: Set[Tuple[str, Callable[..., Any]]] = set() + self.rank_agg_tables: set[tuple[str, Callable[..., Any]]] = set() def __del__(self) -> None: self.db.close() @@ -84,11 +75,11 @@ def get_rank_agg_table(self, qty: str, self.rank_agg_tables.add((qty, rank_aggregator)) return tbl_name - def scatter_cursor(self, cursor: Cursor, labels: Optional[List[str]] = None, + def scatter_cursor(self, cursor: Cursor, labels: list[str] | None = None, *args: Any, **kwargs: Any) -> None: import matplotlib.pyplot as plt - data_args = tuple(zip(*list(cursor))) + data_args = tuple(zip(*list(cursor), strict=False)) plt.scatter(*(data_args + args), **kwargs) if isinstance(labels, list) and len(labels) == 2: @@ -101,7 +92,7 @@ def scatter_cursor(self, cursor: Cursor, labels: Optional[List[str]] = None, if self.interactive: plt.show() - def plot_cursor(self, cursor: Cursor, labels: Optional[List[str]] = None, # noqa: C901 + def plot_cursor(self, cursor: Cursor, labels: list[str] | None = None, # noqa: C901 *args: Any, **kwargs: Any) -> None: from matplotlib.pyplot import legend, plot, show @@ -113,7 +104,7 @@ def plot_cursor(self, cursor: Cursor, labels: Optional[List[str]] = None, # noq kwargs["dashes"] = style.dashes kwargs["color"] = style.color - x, y = list(zip(*list(cursor))) + x, y = list(zip(*list(cursor), strict=False)) p = plot(x, y, *args, **kwargs) if isinstance(labels, list) and len(labels) == 2: @@ -126,13 +117,13 @@ def plot_cursor(self, cursor: Cursor, labels: Optional[List[str]] = None, # noq elif len(cursor.description) > 2: small_legend = kwargs.pop("small_legend", True) - def format_label(kv_pairs: Sequence[Tuple[str, Any]]) -> str: + def format_label(kv_pairs: Sequence[tuple[str, Any]]) -> str: return " ".join(f"{column}:{value}" for column, value in kv_pairs) format_label = kwargs.pop("format_label", format_label) - def do_plot(x: List[float], y: List[float], - row_rest: Tuple[Any, ...]) -> None: + def do_plot(x: list[float], y: list[float], + row_rest: tuple[Any, ...]) -> None: my_kwargs = kwargs.copy() style = PLOT_STYLES[style_idx[0] % len(PLOT_STYLES)] if auto_style: @@ -142,7 +133,7 @@ def do_plot(x: List[float], y: List[float], my_kwargs.setdefault("label", format_label(list(zip( (col[0] for col in cursor.description[2:]), - row_rest)))) + row_rest, strict=False)))) plot(x, y, *args, hold=True, **my_kwargs) style_idx[0] += 1 @@ -166,10 +157,10 @@ def print_cursor(self, cursor: Cursor) -> None: def split_cursor(cursor: Cursor) -> Generator[ - Tuple[List[Any], List[Any], Optional[Tuple[Any, ...]]], None, None]: + tuple[list[Any], list[Any], tuple[Any, ...] | None], None, None]: - x: List[Any] = [] - y: List[Any] = [] + x: list[Any] = [] + y: list[Any] = [] last_rest = None for row in cursor: row_tuple = tuple(row) @@ -255,7 +246,7 @@ def replace_magic_column(match: Any) -> str: f" on ({full_tbl}.run_id = runs.id{addendum}) " last_tbl = full_tbl - def get_clause_indices(qry: str) -> Dict[str, int]: + def get_clause_indices(qry: str) -> dict[str, int]: other_clauses = ["UNION", "INTERSECT", "EXCEPT", "WHERE", "GROUP", "HAVING", "ORDER", "LIMIT", ";"] @@ -287,7 +278,7 @@ def get_clause_indices(qry: str) -> Dict[str, int]: def make_runalyzer_symbols(db: RunDB) \ - -> Dict[str, Union[RunDB, str, Callable[..., Any], None]]: + -> dict[str, RunDB | str | Callable[..., Any] | None]: return { "__name__": "__console__", "__doc__": None, @@ -432,7 +423,7 @@ def __init__(self) -> None: class StdDeviation(Variance): - def finalize(self) -> Optional[float]: + def finalize(self) -> float | None: result = Variance.finalize(self) # type: ignore[no-untyped-call] if result is None: @@ -496,7 +487,7 @@ def is_gathered(conn: sqlite3.Connection) -> bool: return False -def auto_gather(filenames: List[str]) -> sqlite3.Connection: +def auto_gather(filenames: list[str]) -> sqlite3.Connection: # allow for creating ungathered files. # Check if database has been gathered, if not, create one in memory @@ -540,7 +531,7 @@ def auto_gather(filenames: List[str]) -> sqlite3.Connection: # {{{ main program def make_wrapped_db( - filenames: List[str], interactive: bool, + filenames: list[str], interactive: bool, mangle: bool, gather: bool = True ) -> RunDB: if gather: @@ -560,7 +551,7 @@ def make_wrapped_db( db.create_function("pow", 2, pow) if mangle: - db_wrap_class: Type[RunDB] = MagicRunDB + db_wrap_class: type[RunDB] = MagicRunDB else: db_wrap_class = RunDB diff --git a/logpyle/runalyzer_gather.py b/logpyle/runalyzer_gather.py index fc08cca..d1e0bc1 100644 --- a/logpyle/runalyzer_gather.py +++ b/logpyle/runalyzer_gather.py @@ -1,7 +1,7 @@ import re import sqlite3 from sqlite3 import Connection -from typing import Any, Dict, List, Optional, Tuple, Union, cast +from typing import Any, cast from pytools.datatable import DataTable @@ -29,7 +29,7 @@ def parse_dir_feature(feat: str, number: int) \ - -> Tuple[Union[str, Any], str, Union[str, Any]]: + -> tuple[str | Any, str, str | Any]: bool_match = bool_feat_re.match(feat) if bool_match is not None: return (bool_match.group(1), "integer", int(bool_match.group(2) == "True")) @@ -45,7 +45,7 @@ def parse_dir_feature(feat: str, number: int) \ return (f"dirfeat{number}", "text", feat) -def larger_sql_type(type_a: Optional[str], type_b: Optional[str]) -> Optional[str]: +def larger_sql_type(type_a: str | None, type_b: str | None) -> str | None: assert type_a in [None, "text", "real", "integer"] assert type_b in [None, "text", "real", "integer"] @@ -62,7 +62,7 @@ def larger_sql_type(type_a: Optional[str], type_b: Optional[str]) -> Optional[st def sql_type_and_value(value: Any) \ - -> Tuple[Optional[str], Union[int, float, str, None]]: + -> tuple[str | None, int | float | str | None]: if value is None: return None, None elif isinstance(value, bool): @@ -76,7 +76,7 @@ def sql_type_and_value(value: Any) \ def sql_type_and_value_from_str(value: str) \ - -> Tuple[Optional[str], Union[int, float, str, None]]: + -> tuple[str | None, int | float | str | None]: if value == "None": return None, None elif value in ["True", "False"]: @@ -95,7 +95,7 @@ def sql_type_and_value_from_str(value: str) \ class FeatureGatherer: def __init__(self, features_from_dir: bool = False, - features_file: Optional[str] = None) -> None: + features_file: str | None = None) -> None: self.features_from_dir = features_from_dir self.dir_to_features = {} @@ -115,7 +115,7 @@ def __init__(self, features_from_dir: bool = False, self.dir_to_features[line[:colon_idx]] = features - def get_db_features(self, dbname: str, logmgr: LogManager) -> List[Any]: + def get_db_features(self, dbname: str, logmgr: LogManager) -> list[Any]: from os.path import dirname dn = dirname(dbname) @@ -131,11 +131,11 @@ def get_db_features(self, dbname: str, logmgr: LogManager) -> List[Any]: return features -def scan(fg: FeatureGatherer, dbnames: List[str], # noqa: C901 - progress: bool = True) -> Tuple[Dict[str, Any], Dict[str, int]]: - features: Dict[str, Any] = {} +def scan(fg: FeatureGatherer, dbnames: list[str], # noqa: C901 + progress: bool = True) -> tuple[dict[str, Any], dict[str, int]]: + features: dict[str, Any] = {} dbname_to_run_id = {} - uid_to_run_id: Dict[str, int] = {} + uid_to_run_id: dict[str, int] = {} next_run_id = 1 from pytools import ProgressBar @@ -181,9 +181,9 @@ def scan(fg: FeatureGatherer, dbnames: List[str], # noqa: C901 return features, dbname_to_run_id -def make_name_map(map_str: str) -> Dict[str, str]: +def make_name_map(map_str: str) -> dict[str, str]: import re - result: Dict[str, str] = {} + result: dict[str, str] = {} if not map_str: return result @@ -208,10 +208,10 @@ def _normalize_types(x: Any) -> Any: return x -def gather_multi_file(outfile: str, infiles: List[str], fmap: Dict[str, str], - qmap: Dict[str, str], fg: FeatureGatherer, - features: Dict[str, Any], - dbname_to_run_id: Dict[str, int]) -> sqlite3.Connection: +def gather_multi_file(outfile: str, infiles: list[str], fmap: dict[str, str], + qmap: dict[str, str], fg: FeatureGatherer, + features: dict[str, Any], + dbname_to_run_id: dict[str, int]) -> sqlite3.Connection: from pytools import ProgressBar pb = ProgressBar("Importing...", len(infiles)) # type: ignore[no-untyped-call] diff --git a/pyproject.toml b/pyproject.toml index d67eb67..31b643e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,11 +21,10 @@ description = "Time series logging for Python" dependencies = [ "pytools>=2011.1", "pymbolic", - "importlib_metadata;python_version<'3.8'", ] readme = "README.md" license = { file="LICENSE" } -requires-python = ">=3.7" +requires-python = ">=3.10" classifiers = [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", diff --git a/test/test_quantities.py b/test/test_quantities.py index 85d0a0e..1ca5de0 100644 --- a/test/test_quantities.py +++ b/test/test_quantities.py @@ -262,7 +262,7 @@ def custom_multi_log_quantity(request): class TestLogQuantity(MultiLogQuantity): def __init__(self, names, parameters) -> None: super().__init__(names, units, descriptions) - for name, parameter in zip(names, parameters): + for name, parameter in zip(names, parameters, strict=False): setattr(self, name, parameter) self.func = call_func @@ -272,7 +272,7 @@ def __call__(self): # update value every time quantity is called new_vals = self.func(*values) - for name, val in zip(self.names, new_vals): + for name, val in zip(self.names, new_vals, strict=False): setattr(self, name, val) return new_vals @@ -360,7 +360,7 @@ def custom_multi_post_logquantity(request): class TestLogQuantity(MultiPostLogQuantity): def __init__(self, names, parameters) -> None: super().__init__(names, units, descriptions) - for name, parameter in zip(names, parameters): + for name, parameter in zip(names, parameters, strict=False): setattr(self, name, parameter) self.func = call_func @@ -370,7 +370,7 @@ def __call__(self): # update value every time quantity is called new_vals = self.func(*values) - for name, val in zip(self.names, new_vals): + for name, val in zip(self.names, new_vals, strict=False): setattr(self, name, val) return new_vals @@ -509,7 +509,7 @@ def test_steptostep_and_timestepduration_quantity( print(actual_times, sleep_times) # assert that these quantities only differ by a max of tol # defined above - for (predicted, actual) in zip(sleep_times, actual_times): + for (predicted, actual) in zip(sleep_times, actual_times, strict=False): assert abs(actual - predicted) < tol @@ -650,7 +650,7 @@ def test_interval_timer_subtimer(basic_logmgr: LogManager): print(expected_timer_list) # enforce equality of durations - for tup in zip(val_list, expected_timer_list): + for tup in zip(val_list, expected_timer_list, strict=False): assert abs(tup[0] - tup[1]) < tol @@ -682,7 +682,7 @@ def test_interval_timer_subtimer_blocking(basic_logmgr: LogManager): print(expected_timer_list) # enforce equality of durations - for tup in zip(val_list, expected_timer_list): + for tup in zip(val_list, expected_timer_list, strict=False): assert abs(tup[0] - tup[1]) < tol From 030f62c90851ad42e48584ea0e15e877d157310a Mon Sep 17 00:00:00 2001 From: Matthias Diener Date: Sun, 22 Dec 2024 13:04:33 +0100 Subject: [PATCH 14/25] remove pylab --- .github/workflows/ci.yaml | 2 +- logpyle/runalyzer.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 3bd134a..f7c6f0c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -178,7 +178,7 @@ jobs: set -x sudo apt-get update && sudo apt-get install -y libopenmpi-dev curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/main/prepare-and-run-mypy.sh - export EXTRA_INSTALL="pytools numpy types-psutil pymbolic mpi4py matplotlib pylab" + export EXTRA_INSTALL="pytools numpy types-psutil pymbolic mpi4py matplotlib" . ./prepare-and-run-mypy.sh python3 pytest: diff --git a/logpyle/runalyzer.py b/logpyle/runalyzer.py index 29ffeda..2e502f5 100644 --- a/logpyle/runalyzer.py +++ b/logpyle/runalyzer.py @@ -397,7 +397,7 @@ def execute_magic(self, cmdline: str) -> None: # noqa: C901 elif cmd == "logging": self.db.print_cursor(self.db.q("select * from logging")) elif cmd == "title": - from pylab import title + from matplotlib.pyplot import title title(args) elif cmd == "plot": cursor = self.db.db.execute(self.db.mangle_sql(args)) From 6479a0670bbc8e7368d76e47baa419356bec4bef Mon Sep 17 00:00:00 2001 From: Matthias Diener Date: Sun, 22 Dec 2024 13:10:54 +0100 Subject: [PATCH 15/25] htmlalyzer adjustments --- logpyle/HTMLalyzer/__init__.py | 2 -- logpyle/HTMLalyzer/htmlalyzer.html | 6 ------ logpyle/HTMLalyzer/main.py | 6 ------ 3 files changed, 14 deletions(-) diff --git a/logpyle/HTMLalyzer/__init__.py b/logpyle/HTMLalyzer/__init__.py index 9497d54..a63133b 100644 --- a/logpyle/HTMLalyzer/__init__.py +++ b/logpyle/HTMLalyzer/__init__.py @@ -58,7 +58,6 @@ def build() -> None: "__init__.py", "runalyzer.py", "runalyzer_gather.py", - "version.py", pymbolic_whl_path, ] files_dict = {} @@ -85,7 +84,6 @@ def build() -> None: logpyle_py_file=files_dict["__init__.py"], runalyzer_py_file=files_dict["runalyzer.py"], runalyzer_gather_py_file=files_dict["runalyzer_gather.py"], - version_py_file=files_dict["version.py"], ) with open(html_path + "/main.css") as f: diff --git a/logpyle/HTMLalyzer/htmlalyzer.html b/logpyle/HTMLalyzer/htmlalyzer.html index 64aea75..3446dfe 100644 --- a/logpyle/HTMLalyzer/htmlalyzer.html +++ b/logpyle/HTMLalyzer/htmlalyzer.html @@ -308,7 +308,6 @@ logpyle_py_file = "IiIiCkxvZyBRdWFudGl0eSBBYnN0cmFjdCBJbnRlcmZhY2VzCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgouLiBhdXRvY2xhc3M6OiBMb2dRdWFudGl0eQouLiBhdXRvY2xhc3M6OiBQb3N0TG9nUXVhbnRpdHkKLi4gYXV0b2NsYXNzOjogTXVsdGlMb2dRdWFudGl0eQouLiBhdXRvY2xhc3M6OiBNdWx0aVBvc3RMb2dRdWFudGl0eQoKTG9nIE1hbmFnZXIKLS0tLS0tLS0tLS0KCi4uIGF1dG9jbGFzczo6IExvZ01hbmFnZXIKLi4gYXV0b2Z1bmN0aW9uOjogYWRkX3J1bl9pbmZvCgpCdWlsdC1pbiBMb2cgR2VuZXJhbC1QdXJwb3NlIFF1YW50aXRpZXMKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCi4uIGF1dG9jbGFzczo6IEludGVydmFsVGltZXIKLi4gYXV0b2NsYXNzOjogTG9nVXBkYXRlRHVyYXRpb24KLi4gYXV0b2NsYXNzOjogRXZlbnRDb3VudGVyCi4uIGF1dG9jbGFzczo6IFRpbWVzdGVwQ291bnRlcgouLiBhdXRvY2xhc3M6OiBTdGVwVG9TdGVwRHVyYXRpb24KLi4gYXV0b2NsYXNzOjogVGltZXN0ZXBEdXJhdGlvbgouLiBhdXRvY2xhc3M6OiBJbml0VGltZQouLiBhdXRvY2xhc3M6OiBXYWxsVGltZQouLiBhdXRvY2xhc3M6OiBFVEEKLi4gYXV0b2NsYXNzOjogTWVtb3J5SHdtCi4uIGF1dG9jbGFzczo6IEdDU3RhdHMKLi4gYXV0b2Z1bmN0aW9uOjogYWRkX2dlbmVyYWxfcXVhbnRpdGllcwoKQnVpbHQtaW4gTG9nIFNpbXVsYXRpb24tUmVsYXRlZCBRdWFudGl0aWVzCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQouLiBhdXRvY2xhc3M6OiBTaW11bGF0aW9uVGltZQouLiBhdXRvY2xhc3M6OiBUaW1lc3RlcAouLiBhdXRvZnVuY3Rpb246OiBzZXRfZHQKLi4gYXV0b2Z1bmN0aW9uOjogYWRkX3NpbXVsYXRpb25fcXVhbnRpdGllcwoKCkludGVybmFsIHN0dWZmIHRoYXQgaXMgb25seSBoZXJlIGJlY2F1c2UgdGhlIGRvY3VtZW50YXRpb24gdG9vbCB3YW50cyBpdAotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KLi4gYXV0b2NsYXNzOjogX1N1YlRpbWVyCiIiIgoKX19jb3B5cmlnaHRfXyA9ICJDb3B5cmlnaHQgKEMpIDIwMDktMjAxMyBBbmRyZWFzIEtsb2Vja25lciIKCl9fbGljZW5zZV9fID0gIiIiClBlcm1pc3Npb24gaXMgaGVyZWJ5IGdyYW50ZWQsIGZyZWUgb2YgY2hhcmdlLCB0byBhbnkgcGVyc29uIG9idGFpbmluZyBhIGNvcHkKb2YgdGhpcyBzb2Z0d2FyZSBhbmQgYXNzb2NpYXRlZCBkb2N1bWVudGF0aW9uIGZpbGVzICh0aGUgIlNvZnR3YXJlIiksIHRvIGRlYWwKaW4gdGhlIFNvZnR3YXJlIHdpdGhvdXQgcmVzdHJpY3Rpb24sIGluY2x1ZGluZyB3aXRob3V0IGxpbWl0YXRpb24gdGhlIHJpZ2h0cwp0byB1c2UsIGNvcHksIG1vZGlmeSwgbWVyZ2UsIHB1Ymxpc2gsIGRpc3RyaWJ1dGUsIHN1YmxpY2Vuc2UsIGFuZC9vciBzZWxsCmNvcGllcyBvZiB0aGUgU29mdHdhcmUsIGFuZCB0byBwZXJtaXQgcGVyc29ucyB0byB3aG9tIHRoZSBTb2Z0d2FyZSBpcwpmdXJuaXNoZWQgdG8gZG8gc28sIHN1YmplY3QgdG8gdGhlIGZvbGxvd2luZyBjb25kaXRpb25zOgoKVGhlIGFib3ZlIGNvcHlyaWdodCBub3RpY2UgYW5kIHRoaXMgcGVybWlzc2lvbiBub3RpY2Ugc2hhbGwgYmUgaW5jbHVkZWQgaW4KYWxsIGNvcGllcyBvciBzdWJzdGFudGlhbCBwb3J0aW9ucyBvZiB0aGUgU29mdHdhcmUuCgpUSEUgU09GVFdBUkUgSVMgUFJPVklERUQgIkFTIElTIiwgV0lUSE9VVCBXQVJSQU5UWSBPRiBBTlkgS0lORCwgRVhQUkVTUyBPUgpJTVBMSUVELCBJTkNMVURJTkcgQlVUIE5PVCBMSU1JVEVEIFRPIFRIRSBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWSwKRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UgQU5EIE5PTklORlJJTkdFTUVOVC4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFCkFVVEhPUlMgT1IgQ09QWVJJR0hUIEhPTERFUlMgQkUgTElBQkxFIEZPUiBBTlkgQ0xBSU0sIERBTUFHRVMgT1IgT1RIRVIKTElBQklMSVRZLCBXSEVUSEVSIElOIEFOIEFDVElPTiBPRiBDT05UUkFDVCwgVE9SVCBPUiBPVEhFUldJU0UsIEFSSVNJTkcgRlJPTSwKT1VUIE9GIE9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgU09GVFdBUkUgT1IgVEhFIFVTRSBPUiBPVEhFUiBERUFMSU5HUyBJTgpUSEUgU09GVFdBUkUuCiIiIgoKCmltcG9ydCBsb2dweWxlLnZlcnNpb24KCl9fdmVyc2lvbl9fID0gbG9ncHlsZS52ZXJzaW9uLlZFUlNJT05fVEVYVAoKCmltcG9ydCBsb2dnaW5nCmltcG9ydCBzeXMKCmxvZ2dlciA9IGxvZ2dpbmcuZ2V0TG9nZ2VyKF9fbmFtZV9fKQoKZnJvbSBkYXRhY2xhc3NlcyBpbXBvcnQgZGF0YWNsYXNzCmZyb20gc3FsaXRlMyBpbXBvcnQgQ29ubmVjdGlvbgpmcm9tIHRpbWUgaW1wb3J0IG1vbm90b25pYyBhcyB0aW1lX21vbm90b25pYwpmcm9tIHR5cGluZyBpbXBvcnQgKFRZUEVfQ0hFQ0tJTkcsIEFueSwgQ2FsbGFibGUsIERpY3QsIEdlbmVyYXRvciwgSXRlcmFibGUsCiAgICAgICAgICAgICAgICAgICAgTGlzdCwgT3B0aW9uYWwsIFNlcXVlbmNlLCBUZXh0SU8sIFR1cGxlLCBUeXBlLCBVbmlvbiwgY2FzdCkKCmZyb20gcHltYm9saWMuY29tcGlsZXIgaW1wb3J0IENvbXBpbGVkRXhwcmVzc2lvbiAgIyB0eXBlOiBpZ25vcmVbaW1wb3J0XQpmcm9tIHB5bWJvbGljLm1hcHBlci5kZXBlbmRlbmN5IGltcG9ydCBEZXBlbmRlbmN5TWFwcGVyICAjIHR5cGU6IGlnbm9yZVtpbXBvcnRdCmZyb20gcHltYm9saWMucHJpbWl0aXZlcyBpbXBvcnQgRXhwcmVzc2lvbiAgIyB0eXBlOiBpZ25vcmVbaW1wb3J0XQpmcm9tIHB5dG9vbHMuZGF0YXRhYmxlIGltcG9ydCBEYXRhVGFibGUKCmlmIFRZUEVfQ0hFQ0tJTkcgYW5kIG5vdCBnZXRhdHRyKHN5cywgIl9CVUlMRElOR19TUEhJTlhfRE9DUyIsIEZhbHNlKToKICAgIGltcG9ydCBtcGk0cHkKCgojIHt7eyBhYnN0cmFjdCBsb2dnaW5nIGludGVyZmFjZQoKY2xhc3MgTG9nUXVhbnRpdHk6CiAgICAiIiJBIHNvdXJjZSBvZiBhIGxvZ2dhYmxlIHNjYWxhciB0aGF0IGlzIGdhdGhlcmVkIGF0IHRoZSBzdGFydCBvZiBlYWNoIHRpbWUgc3RlcC4KCiAgICBRdWFudGl0eSB2YWx1ZXMgYXJlIGdhdGhlcmVkIGluIDptZXRoOmBMb2dNYW5hZ2VyLnRpY2tfYmVmb3JlYC4KCiAgICAuLiBhdXRvbWV0aG9kOjogX19pbml0X18KICAgIC4uIGF1dG9tZXRob2Q6OiB0aWNrCiAgICAuLiBhdXRvcHJvcGVydHk6OiBkZWZhdWx0X2FnZ3JlZ2F0b3IKICAgIC4uIGF1dG9tZXRob2Q6OiBfX2NhbGxfXwogICAgIiIiCgogICAgc29ydF93ZWlnaHQgPSAwCgogICAgZGVmIF9faW5pdF9fKHNlbGYsIG5hbWU6IHN0ciwgdW5pdDogT3B0aW9uYWxbc3RyXSA9IE5vbmUsCiAgICAgICAgICAgICAgICAgZGVzY3JpcHRpb246IE9wdGlvbmFsW3N0cl0gPSBOb25lKSAtPiBOb25lOgogICAgICAgICIiIkNyZWF0ZSBhIG5ldyBxdWFudGl0eS4KCiAgICAgICAgUGFyYW1ldGVycwogICAgICAgIC0tLS0tLS0tLS0KICAgICAgICBuYW1lCiAgICAgICAgICBRdWFudGl0eSBuYW1lLgoKICAgICAgICB1bml0CiAgICAgICAgICBRdWFudGl0eSB1bml0LgoKICAgICAgICBkZXNjcmlwdGlvbgogICAgICAgICAgUXVhbnRpdHkgZGVzY3JpcHRpb24uCiAgICAgICAgIiIiCiAgICAgICAgc2VsZi5uYW1lID0gbmFtZQogICAgICAgIHNlbGYudW5pdCA9IHVuaXQKICAgICAgICBzZWxmLmRlc2NyaXB0aW9uID0gZGVzY3JpcHRpb24KCiAgICBAcHJvcGVydHkKICAgIGRlZiBkZWZhdWx0X2FnZ3JlZ2F0b3Ioc2VsZikgLT4gTm9uZToKICAgICAgICAiIiJEZWZhdWx0IHJhbmsgYWdncmVnYXRpb24gZnVuY3Rpb24uIiIiCiAgICAgICAgcmV0dXJuIE5vbmUKCiAgICBkZWYgdGljayhzZWxmKSAtPiBOb25lOgogICAgICAgICIiIlBlcmZvcm0gdXBkYXRlcyByZXF1aXJlZCBhdCBldmVyeSA6Y2xhc3M6YExvZ01hbmFnZXJgIHRpY2suIiIiCiAgICAgICAgcGFzcwoKICAgIGRlZiBfX2NhbGxfXyhzZWxmKSAtPiBBbnk6CiAgICAgICAgIiIiUmV0dXJuIHRoZSBjdXJyZW50IHZhbHVlIG9mIHRoZSBkaWFnbm9zdGljIHJlcHJlc2VudGVkIGJ5IHRoaXMKICAgICAgICA6Y2xhc3M6YExvZ1F1YW50aXR5YCBvciBOb25lIGlmIG5vIHZhbHVlIGlzIGF2YWlsYWJsZS4KCiAgICAgICAgVGhpcyBpcyBvbmx5IGNhbGxlZCBpZiB0aGUgaW52b2NhdGlvbiBpbnRlcnZhbCBjYWxscyBmb3IgaXQuCiAgICAgICAgIiIiCiAgICAgICAgcmFpc2UgTm90SW1wbGVtZW50ZWRFcnJvcgoKCmNsYXNzIFBvc3RMb2dRdWFudGl0eShMb2dRdWFudGl0eSk6CiAgICAiIiJBIHNvdXJjZSBvZiBhIGxvZ2dhYmxlIHNjYWxhciB0aGF0IGlzIGdhdGhlcmVkIGFmdGVyIGVhY2ggdGltZSBzdGVwLgoKICAgIFF1YW50aXR5IHZhbHVlcyBhcmUgZ2F0aGVyZWQgaW4gOm1ldGg6YExvZ01hbmFnZXIudGlja19hZnRlcmAuCgogICAgLi4gYXV0b21ldGhvZDo6IF9faW5pdF9fCiAgICAuLiBhdXRvbWV0aG9kOjogdGljawogICAgLi4gYXV0b3Byb3BlcnR5OjogZGVmYXVsdF9hZ2dyZWdhdG9yCiAgICAuLiBhdXRvbWV0aG9kOjogX19jYWxsX18KICAgIC4uIGF1dG9tZXRob2Q6OiBwcmVwYXJlX2Zvcl90aWNrCiAgICAiIiIKICAgIHNvcnRfd2VpZ2h0ID0gMAoKICAgIGRlZiBwcmVwYXJlX2Zvcl90aWNrKHNlbGYpIC0+IE5vbmU6CiAgICAgICAgIiIiUGVyZm9ybSAob3B0aW9uYWwpIHVwZGF0ZSBhdCA6bWV0aDpgTG9nTWFuYWdlci50aWNrX2JlZm9yZWAuIiIiCiAgICAgICAgcGFzcwoKCmNsYXNzIE11bHRpTG9nUXVhbnRpdHk6CiAgICAiIiJBIHNvdXJjZSBvZiBhIGxpc3Qgb2YgbG9nZ2FibGUgc2NhbGFycyBnYXRoZXJlZCBhdCB0aGUgc3RhcnQgb2YgZWFjaCB0aW1lCiAgICBzdGVwLgoKICAgIFF1YW50aXR5IHZhbHVlcyBhcmUgZ2F0aGVyZWQgaW4gOm1ldGg6YExvZ01hbmFnZXIudGlja19iZWZvcmVgLgoKICAgIC4uIGF1dG9tZXRob2Q6OiBfX2luaXRfXwogICAgLi4gYXV0b21ldGhvZDo6IHRpY2sKICAgIC4uIGF1dG9wcm9wZXJ0eTo6IGRlZmF1bHRfYWdncmVnYXRvcnMKICAgIC4uIGF1dG9tZXRob2Q6OiBfX2NhbGxfXwogICAgIiIiCiAgICBzb3J0X3dlaWdodCA9IDAKCiAgICBkZWYgX19pbml0X18oc2VsZiwgbmFtZXM6IExpc3Rbc3RyXSwKICAgICAgICAgICAgICAgICB1bml0czogT3B0aW9uYWxbU2VxdWVuY2VbT3B0aW9uYWxbc3RyXV1dID0gTm9uZSwKICAgICAgICAgICAgICAgICBkZXNjcmlwdGlvbnM6IE9wdGlvbmFsW1NlcXVlbmNlW09wdGlvbmFsW3N0cl1dXSA9IE5vbmUpIC0+IE5vbmU6CiAgICAgICAgIiIiQ3JlYXRlIGEgbmV3IHF1YW50aXR5LgoKICAgICAgICBQYXJhbWV0ZXJzCiAgICAgICAgLS0tLS0tLS0tLQogICAgICAgIG5hbWVzCiAgICAgICAgICBMaXN0IG9mIHF1YW50aXR5IG5hbWVzLgoKICAgICAgICB1bml0cwogICAgICAgICAgTGlzdCBvZiBxdWFudGl0eSB1bml0cy4KCiAgICAgICAgZGVzY3JpcHRpb25zCiAgICAgICAgICBMaXN0IG9mIHF1YW50aXR5IGRlc2NyaXB0aW9ucy4KICAgICAgICAiIiIKICAgICAgICBzZWxmLm5hbWVzID0gbmFtZXMKCiAgICAgICAgaWYgdW5pdHMgaXMgTm9uZToKICAgICAgICAgICAgc2VsZi51bml0czogU2VxdWVuY2VbT3B0aW9uYWxbc3RyXV0gPSBsZW4obmFtZXMpICogW05vbmVdCiAgICAgICAgZWxzZToKICAgICAgICAgICAgc2VsZi51bml0cyA9IHVuaXRzCgogICAgICAgIGlmIGRlc2NyaXB0aW9ucyBpcyBOb25lOgogICAgICAgICAgICBzZWxmLmRlc2NyaXB0aW9uczogU2VxdWVuY2VbT3B0aW9uYWxbc3RyXV0gPSBsZW4obmFtZXMpICogW05vbmVdCiAgICAgICAgZWxzZToKICAgICAgICAgICAgc2VsZi5kZXNjcmlwdGlvbnMgPSBkZXNjcmlwdGlvbnMKCiAgICBAcHJvcGVydHkKICAgIGRlZiBkZWZhdWx0X2FnZ3JlZ2F0b3JzKHNlbGYpIC0+IExpc3RbTm9uZV06CiAgICAgICAgIiIiTGlzdCBvZiBkZWZhdWx0IGFnZ3JlZ2F0b3JzLiIiIgogICAgICAgIHJldHVybiBbTm9uZV0gKiBsZW4oc2VsZi5uYW1lcykKCiAgICBkZWYgdGljayhzZWxmKSAtPiBOb25lOgogICAgICAgICIiIlBlcmZvcm0gdXBkYXRlcyByZXF1aXJlZCBhdCBldmVyeSA6Y2xhc3M6YExvZ01hbmFnZXJgIHRpY2suIiIiCiAgICAgICAgcGFzcwoKICAgIGRlZiBfX2NhbGxfXyhzZWxmKSAtPiBJdGVyYWJsZVtPcHRpb25hbFtmbG9hdF1dOgogICAgICAgICIiIlJldHVybiBhbiBpdGVyYWJsZSBvZiB0aGUgY3VycmVudCB2YWx1ZXMgb2YgdGhlIGRpYWdub3N0aWMgcmVwcmVzZW50ZWQKICAgICAgICBieSB0aGlzIDpjbGFzczpgTXVsdGlMb2dRdWFudGl0eWAuCgogICAgICAgIFRoaXMgaXMgb25seSBjYWxsZWQgaWYgdGhlIGludm9jYXRpb24gaW50ZXJ2YWwgY2FsbHMgZm9yIGl0LgogICAgICAgICIiIgogICAgICAgIHJhaXNlIE5vdEltcGxlbWVudGVkRXJyb3IKCgpjbGFzcyBNdWx0aVBvc3RMb2dRdWFudGl0eShNdWx0aUxvZ1F1YW50aXR5LCBQb3N0TG9nUXVhbnRpdHkpOgogICAgIiIiQSBzb3VyY2Ugb2YgYSBsaXN0IG9mIGxvZ2dhYmxlIHNjYWxhcnMgZ2F0aGVyZWQgYWZ0ZXIgZWFjaCB0aW1lIHN0ZXAuCgogICAgUXVhbnRpdHkgdmFsdWVzIGFyZSBnYXRoZXJlZCBpbiA6bWV0aDpgTG9nTWFuYWdlci50aWNrX2FmdGVyYC4KCiAgICAuLiBhdXRvbWV0aG9kOjogX19pbml0X18KICAgIC4uIGF1dG9tZXRob2Q6OiB0aWNrCiAgICAuLiBhdXRvcHJvcGVydHk6OiBkZWZhdWx0X2FnZ3JlZ2F0b3JzCiAgICAuLiBhdXRvbWV0aG9kOjogX19jYWxsX18KICAgIC4uIGF1dG9tZXRob2Q6OiBwcmVwYXJlX2Zvcl90aWNrCiAgICAiIiIKICAgIHBhc3MKCgpjbGFzcyBEdENvbnN1bWVyOgogICAgZGVmIF9faW5pdF9fKHNlbGYpIC0+IE5vbmU6CiAgICAgICAgc2VsZi5kdDogT3B0aW9uYWxbZmxvYXRdID0gTm9uZQoKICAgIGRlZiBzZXRfZHQoc2VsZiwgZHQ6IE9wdGlvbmFsW2Zsb2F0XSkgLT4gTm9uZToKICAgICAgICBzZWxmLmR0ID0gZHQKCgpjbGFzcyBUaW1lVHJhY2tlcihEdENvbnN1bWVyKToKICAgIGRlZiBfX2luaXRfXyhzZWxmLCBzdGFydDogZmxvYXQgPSAwKSAtPiBOb25lOgogICAgICAgIER0Q29uc3VtZXIuX19pbml0X18oc2VsZikKICAgICAgICBzZWxmLnQgPSBzdGFydAoKICAgIGRlZiB0aWNrKHNlbGYpIC0+IE5vbmU6CiAgICAgICAgc2VsZi50ICs9IGNhc3QoZmxvYXQsIHNlbGYuZHQpCgoKY2xhc3MgU2ltdWxhdGlvbkxvZ1F1YW50aXR5KFBvc3RMb2dRdWFudGl0eSwgRHRDb25zdW1lcik6CiAgICAiIiJBIHNvdXJjZSBvZiBsb2dnYWJsZSBzY2FsYXJzIHRoYXQgbmVlZHMgdG8ga25vdyB0aGUgc2ltdWxhdGlvbiB0aW1lc3RlcC4iIiIKCiAgICBkZWYgX19pbml0X18oc2VsZiwgbmFtZTogc3RyLCB1bml0OiBPcHRpb25hbFtzdHJdID0gTm9uZSwKICAgICAgICAgICAgICAgICBkZXNjcmlwdGlvbjogT3B0aW9uYWxbc3RyXSA9IE5vbmUpIC0+IE5vbmU6CiAgICAgICAgUG9zdExvZ1F1YW50aXR5Ll9faW5pdF9fKHNlbGYsIG5hbWUsIHVuaXQsIGRlc2NyaXB0aW9uKQogICAgICAgIER0Q29uc3VtZXIuX19pbml0X18oc2VsZikKCgpjbGFzcyBQdXNoTG9nUXVhbnRpdHkoTG9nUXVhbnRpdHkpOgogICAgZGVmIF9faW5pdF9fKHNlbGYsIG5hbWU6IHN0ciwgdW5pdDogT3B0aW9uYWxbc3RyXSA9IE5vbmUsCiAgICAgICAgICAgICAgICAgZGVzY3JpcHRpb246IE9wdGlvbmFsW3N0cl0gPSBOb25lKSAtPiBOb25lOgogICAgICAgIExvZ1F1YW50aXR5Ll9faW5pdF9fKHNlbGYsIG5hbWUsIHVuaXQsIGRlc2NyaXB0aW9uKQogICAgICAgIHNlbGYudmFsdWU6IE9wdGlvbmFsW2Zsb2F0XSA9IE5vbmUKCiAgICBkZWYgcHVzaF92YWx1ZShzZWxmLCB2YWx1ZTogZmxvYXQpIC0+IE5vbmU6CiAgICAgICAgaWYgc2VsZi52YWx1ZSBpcyBub3QgTm9uZToKICAgICAgICAgICAgcmFpc2UgUnVudGltZUVycm9yKCJjYW4ndCBwdXNoIHR3byB2YWx1ZXMgcGVyIGN5Y2xlIikKICAgICAgICBzZWxmLnZhbHVlID0gdmFsdWUKCiAgICBkZWYgX19jYWxsX18oc2VsZikgLT4gT3B0aW9uYWxbZmxvYXRdOgogICAgICAgIHYgPSBzZWxmLnZhbHVlCiAgICAgICAgc2VsZi52YWx1ZSA9IE5vbmUKICAgICAgICByZXR1cm4gdgoKCmNsYXNzIENhbGxhYmxlTG9nUXVhbnRpdHlBZGFwdGVyKExvZ1F1YW50aXR5KToKICAgICIiIkFkYXB0IGEgMC1hcnkgY2FsbGFibGUgYXMgYSA6Y2xhc3M6YExvZ1F1YW50aXR5YC4iIiIKICAgIGRlZiBfX2luaXRfXyhzZWxmLCBjYWxsYWJsZTogQ2FsbGFibGVbW10sIGZsb2F0XSwgbmFtZTogc3RyLAogICAgICAgICAgICAgICAgIHVuaXQ6IE9wdGlvbmFsW3N0cl0gPSBOb25lLCBkZXNjcmlwdGlvbjogT3B0aW9uYWxbc3RyXSA9IE5vbmUpIFwKICAgICAgICAgICAgICAgICAgICAtPiBOb25lOgogICAgICAgIHNlbGYuY2FsbGFibGUgPSBjYWxsYWJsZQogICAgICAgIExvZ1F1YW50aXR5Ll9faW5pdF9fKHNlbGYsIG5hbWUsIHVuaXQsIGRlc2NyaXB0aW9uKQoKICAgIGRlZiBfX2NhbGxfXyhzZWxmKSAtPiBmbG9hdDoKICAgICAgICByZXR1cm4gc2VsZi5jYWxsYWJsZSgpCgojIH19fQoKCiMge3t7IG1hbmFnZXIgZnVuY3Rpb25hbGl0eQoKQGRhdGFjbGFzcyhmcm96ZW49VHJ1ZSkKY2xhc3MgX0dhdGhlckRlc2NyaXB0b3I6CiAgICBxdWFudGl0eTogTG9nUXVhbnRpdHkKICAgIGludGVydmFsOiBpbnQKCgpAZGF0YWNsYXNzKGZyb3plbj1UcnVlKQpjbGFzcyBfUXVhbnRpdHlEYXRhOgogICAgdW5pdDogT3B0aW9uYWxbc3RyXQogICAgZGVzY3JpcHRpb246IE9wdGlvbmFsW3N0cl0KICAgIGRlZmF1bHRfYWdncmVnYXRvcjogT3B0aW9uYWxbQ2FsbGFibGVbLi4uLCBBbnldXQoKCmRlZiBfam9pbl9ieV9maXJzdF9vZl90dXBsZShsaXN0X29mX2l0ZXJhYmxlczogTGlzdFtJdGVyYWJsZVtBbnldXSkgXAogICAgICAgIC0+IEdlbmVyYXRvcltUdXBsZVtpbnQsIExpc3RbQW55XV0sIE5vbmUsIE5vbmVdOgogICAgbG9pID0gW2kuX19pdGVyX18oKSBmb3IgaSBpbiBsaXN0X29mX2l0ZXJhYmxlc10KICAgIGlmIG5vdCBsb2k6CiAgICAgICAgcmV0dXJuCiAgICBrZXlfdmFscyA9IFtuZXh0KGl0ZXIpIGZvciBpdGVyIGluIGxvaV0KICAgIGtleXMgPSBba3ZbMF0gZm9yIGt2IGluIGtleV92YWxzXQogICAgdmFsdWVzID0gW2t2WzFdIGZvciBrdiBpbiBrZXlfdmFsc10KICAgIHRhcmdldF9rZXkgPSBtYXgoa2V5cykKCiAgICBmb3JjZV9hZHZhbmNlID0gRmFsc2UKCiAgICBpID0gMAogICAgd2hpbGUgVHJ1ZToKICAgICAgICB3aGlsZSBrZXlzW2ldIDwgdGFyZ2V0X2tleSBvciBmb3JjZV9hZHZhbmNlOgogICAgICAgICAgICB0cnk6CiAgICAgICAgICAgICAgICBuZXdfa2V5LCBuZXdfdmFsdWUgPSBuZXh0KGxvaVtpXSkKICAgICAgICAgICAgZXhjZXB0IFN0b3BJdGVyYXRpb246CiAgICAgICAgICAgICAgICByZXR1cm4KICAgICAgICAgICAgYXNzZXJ0IGtleXNbaV0gPCBuZXdfa2V5CiAgICAgICAgICAgIGtleXNbaV0gPSBuZXdfa2V5CiAgICAgICAgICAgIHZhbHVlc1tpXSA9IG5ld192YWx1ZQogICAgICAgICAgICBpZiBuZXdfa2V5ID4gdGFyZ2V0X2tleToKICAgICAgICAgICAgICAgIHRhcmdldF9rZXkgPSBuZXdfa2V5CgogICAgICAgICAgICBmb3JjZV9hZHZhbmNlID0gRmFsc2UKCiAgICAgICAgaSArPSAxCiAgICAgICAgaWYgaSA+PSBsZW4obG9pKToKICAgICAgICAgICAgaSA9IDAKCiAgICAgICAgaWYgbWluKGtleXMpID09IHRhcmdldF9rZXk6CiAgICAgICAgICAgIHlpZWxkIHRhcmdldF9rZXksIHZhbHVlc1s6XQogICAgICAgICAgICBmb3JjZV9hZHZhbmNlID0gVHJ1ZQoKCmRlZiBfZ2V0X3VuaXF1ZV9pZCgpIC0+IHN0cjoKICAgIGZyb20gdXVpZCBpbXBvcnQgdXVpZDEKICAgIHJldHVybiB1dWlkMSgpLmhleAoKCmRlZiBfZ2V0X3VuaXF1ZV9zdWZmaXgoKSAtPiBzdHI6CiAgICBmcm9tIGRhdGV0aW1lIGltcG9ydCBkYXRldGltZQogICAgcmV0dXJuICItIiArIGRhdGV0aW1lLnV0Y25vdygpLnN0cmZ0aW1lKCIlWSVtJWQtJUglTSVTIikKCgpkZWYgX3NldF91cF9zY2hlbWEoZGJfY29ubjogQ29ubmVjdGlvbikgLT4gaW50OgogICAgIyBpbml0aWFsaXplIG5ldyBkYXRhYmFzZQogICAgZGJfY29ubi5leGVjdXRlKCIiIgogICAgICBjcmVhdGUgdGFibGUgcXVhbnRpdGllcyAoCiAgICAgICAgbmFtZSB0ZXh0LAogICAgICAgIHVuaXQgdGV4dCwKICAgICAgICBkZXNjcmlwdGlvbiB0ZXh0LAogICAgICAgIGRlZmF1bHRfYWdncmVnYXRvciBibG9iKSIiIikKICAgIGRiX2Nvbm4uZXhlY3V0ZSgiIiIKICAgICAgY3JlYXRlIHRhYmxlIGNvbnN0YW50cyAoCiAgICAgICAgbmFtZSB0ZXh0LAogICAgICAgIHZhbHVlIGJsb2IpIiIiKQoKICAgICMgc2NoZW1hX3ZlcnNpb24gPCAyIGlzIG1pc3NpbmcgdGhlICdyYW5rJyBmaWVsZC4KICAgICMgc2NoZW1hX3ZlcnNpb24gPCAzIGlzIG1pc3NpbmcgdGhlICd1bml4dGltZScgZmllbGQuCiAgICBkYl9jb25uLmV4ZWN1dGUoIiIiCiAgICAgIGNyZWF0ZSB0YWJsZSB3YXJuaW5ncyAoCiAgICAgICAgcmFuayBpbnRlZ2VyLAogICAgICAgIHN0ZXAgaW50ZWdlciwKICAgICAgICB1bml4dGltZSBpbnRlZ2VyLAogICAgICAgIG1lc3NhZ2UgdGV4dCwKICAgICAgICBjYXRlZ29yeSB0ZXh0LAogICAgICAgIGZpbGVuYW1lIHRleHQsCiAgICAgICAgbGluZW5vIGludGVnZXIKICAgICAgICApIiIiKQoKICAgICMgc2NoZW1hX3ZlcnNpb24gPCAzIGRvZXMgbm90IGhhdmUgdGhlIGxvZ2dpbmcgdGFibGUKICAgIGRiX2Nvbm4uZXhlY3V0ZSgiIiIKICAgICAgY3JlYXRlIHRhYmxlIGxvZ2dpbmcgKAogICAgICAgIHJhbmsgaW50ZWdlciwKICAgICAgICBzdGVwIGludGVnZXIsCiAgICAgICAgdW5peHRpbWUgaW50ZWdlciwKICAgICAgICBsZXZlbCB0ZXh0LAogICAgICAgIG1lc3NhZ2UgdGV4dCwKICAgICAgICBmaWxlbmFtZSB0ZXh0LAogICAgICAgIGxpbmVubyBpbnRlZ2VyCiAgICAgICAgKSIiIikKCiAgICBzY2hlbWFfdmVyc2lvbiA9IDMKICAgIHJldHVybiBzY2hlbWFfdmVyc2lvbgoKCkBkYXRhY2xhc3MKY2xhc3MgX0RlcGVuZGVuY3lEYXRhOgogICAgbmFtZTogc3RyCiAgICBxZGF0OiBfUXVhbnRpdHlEYXRhCiAgICBhZ2dfZnVuYzogQ2FsbGFibGVbLi4uLCBBbnldCiAgICB2YXJuYW1lOiBzdHIKICAgIGV4cHI6IEV4cHJlc3Npb24KICAgIG5vbmxvY2FsX2FnZzogYm9vbAogICAgdGFibGU6IE9wdGlvbmFsW0RhdGFUYWJsZV0gPSBOb25lCgoKQGRhdGFjbGFzcwpjbGFzcyBfV2F0Y2hJbmZvOgogICAgcGFyc2VkOiBFeHByZXNzaW9uCiAgICBleHByOiBFeHByZXNzaW9uCiAgICBkZXBfZGF0YTogTGlzdFtfRGVwZW5kZW5jeURhdGFdCiAgICBjb21waWxlZDogQ29tcGlsZWRFeHByZXNzaW9uCiAgICB1bml0OiBPcHRpb25hbFtzdHJdCiAgICBmb3JtYXQ6IHN0cgoKCkBkYXRhY2xhc3MoZnJvemVuPVRydWUpCmNsYXNzIF9Mb2dXYXJuaW5nSW5mbzoKICAgIHRpY2tfY291bnQ6IGludAogICAgdGltZTogZmxvYXQKICAgIG1lc3NhZ2U6IHN0cgogICAgY2F0ZWdvcnk6IHN0cgogICAgZmlsZW5hbWU6IHN0cgogICAgbGluZW5vOiBpbnQKCgpjbGFzcyBMb2dNYW5hZ2VyOgogICAgIiIiQSBkaXN0cmlidXRlZC1tZW1vcnktY2FwYWJsZSBkaWFnbm9zdGljIHRpbWUtc2VyaWVzIGxvZ2dpbmcgZmFjaWxpdHkuCiAgICBJdCBpcyBtZWFudCB0byBsb2cgZGF0YSBmcm9tIGEgY29tcHV0YXRpb24sIHdpdGggY2VydGFpbiBsb2cgcXVhbnRpdGllcwogICAgYXZhaWxhYmxlIGJlZm9yZSBhIGN5Y2xlLCBhbmQgY2VydGFpbiBvdGhlciBvbmVzIGFmdGVyd2FyZHMuIEEgdGltZWxpbmUgb2YKICAgIGludm9jYXRpb25zIGxvb2tzIGFzIGZvbGxvd3M6OgoKICAgICAgICB0aWNrX2JlZm9yZSgpCiAgICAgICAgY29tcHV0ZS4uLgogICAgICAgIHRpY2tfYWZ0ZXIoKQoKICAgICAgICB0aWNrX2JlZm9yZSgpCiAgICAgICAgY29tcHV0ZS4uLgogICAgICAgIHRpY2tfYWZ0ZXIoKQoKICAgICAgICAuLi4KCiAgICBJbiBhIHRpbWUtZGVwZW5kZW50IHNpbXVsYXRpb24sIGVhY2ggZ3JvdXAgb2YgOm1ldGg6YHRpY2tfYmVmb3JlYAogICAgOm1ldGg6YHRpY2tfYWZ0ZXJgIGNhbGxzIGNhcHR1cmVzIGRhdGEgZm9yIGEgc2luZ2xlIHRpbWUgc3RhdGUsCiAgICBuYW1lbHkgdGhhdCBpbiB3aGljaCB0aGUgZGF0YSBtYXkgaGF2ZSBiZWVuICpiZWZvcmUqIHRoZSAiY29tcHV0ZSIKICAgIHN0ZXAuIEhvd2V2ZXIsIHNvbWUgZGF0YSAoc3VjaCBhcyB0aGUgbGVuZ3RoIG9mIHRoZSB0aW1lc3RlcCB0YWtlbgogICAgaW4gYSB0aW1lLWFkYXB0aXZlIG1ldGhvZCkgbWF5IG9ubHkgYmUgYXZhaWxhYmxlICphZnRlciogdGhlIGNvbXBsZXRpb24KICAgIG9mIHRoZSAiY29tcHV0ZS4uLiIgc3RhZ2UsIHdoaWNoIGlzIHdoeSA6bWV0aDpgdGlja19hZnRlcmAgZXhpc3RzLgoKICAgIEEgOmNsYXNzOmBMb2dNYW5hZ2VyYCBsb2dzIGFueSBudW1iZXIgb2YgbmFtZWQgdGltZSBzZXJpZXMgb2YgZmxvYXRzIHRvCiAgICBhIGZpbGUuIE5vbi10aW1lLXNlcmllcyBkYXRhLCBpbiB0aGUgZm9ybSBvZiBjb25zdGFudHMsIGlzIGFsc28KICAgIHN1cHBvcnRlZCBhbmQgc2F2ZWQuCgogICAgSWYgTVBJIHBhcmFsbGVsaXNtIGlzIHVzZWQsIHRoZSAiaGVhZCByYW5rIiBiZWxvdyBhbHdheXMgcmVmZXJzIHRvCiAgICByYW5rIDAuCgogICAgQ29tbWFuZCBsaW5lIHRvb2xzIGNhbGxlZCA6Y29tbWFuZDpgcnVuYWx5emVyYCBhcmUgYXZhaWxhYmxlIGZvciBsb29raW5nCiAgICBhdCB0aGUgZGF0YSBpbiBhIHNhdmVkIGxvZy4KCiAgICAuLiBhdXRvbWV0aG9kOjogX19pbml0X18KICAgIC4uIGF1dG9tZXRob2Q6OiBzYXZlCiAgICAuLiBhdXRvbWV0aG9kOjogY2xvc2UKCiAgICAuLiBydWJyaWM6OiBEYXRhIHJldHJpZXZhbAoKICAgIC4uIGF1dG9tZXRob2Q6OiBnZXRfdGFibGUKICAgIC4uIGF1dG9tZXRob2Q6OiBnZXRfd2FybmluZ3MKICAgIC4uIGF1dG9tZXRob2Q6OiBnZXRfbG9nZ2luZwogICAgLi4gYXV0b21ldGhvZDo6IGdldF9leHByX2RhdGFzZXQKICAgIC4uIGF1dG9tZXRob2Q6OiBnZXRfam9pbnRfZGF0YXNldAoKICAgIC4uIHJ1YnJpYzo6IENvbmZpZ3VyYXRpb24KCiAgICAuLiBhdXRvbWV0aG9kOjogY2FwdHVyZV93YXJuaW5ncwogICAgLi4gYXV0b21ldGhvZDo6IGNhcHR1cmVfbG9nZ2luZwogICAgLi4gYXV0b21ldGhvZDo6IGFkZF93YXRjaGVzCiAgICAuLiBhdXRvbWV0aG9kOjogc2V0X3dhdGNoX2ludGVydmFsCiAgICAuLiBhdXRvbWV0aG9kOjogc2V0X2NvbnN0YW50CiAgICAuLiBhdXRvbWV0aG9kOjogYWRkX3F1YW50aXR5CgogICAgLi4gcnVicmljOjogVGltZSBMb29wCgogICAgLi4gYXV0b21ldGhvZDo6IHRpY2tfYmVmb3JlCiAgICAuLiBhdXRvbWV0aG9kOjogdGlja19hZnRlcgogICAgIiIiCgogICAgZGVmIF9faW5pdF9fKHNlbGYsIGZpbGVuYW1lOiBPcHRpb25hbFtzdHJdID0gTm9uZSwgbW9kZTogc3RyID0gInIiLAogICAgICAgICAgICAgICAgIG1waV9jb21tOiBPcHRpb25hbFsibXBpNHB5Lk1QSS5Db21tIl0gPSBOb25lLAogICAgICAgICAgICAgICAgIGNhcHR1cmVfd2FybmluZ3M6IGJvb2wgPSBUcnVlLCBjb21taXRfaW50ZXJ2YWw6IGludCA9IDkwLAogICAgICAgICAgICAgICAgIHdhdGNoX2ludGVydmFsOiBmbG9hdCA9IDEuMCwKICAgICAgICAgICAgICAgICBjYXB0dXJlX2xvZ2dpbmc6IGJvb2wgPSBUcnVlKSAtPiBOb25lOgogICAgICAgICIiIkluaXRpYWxpemUgdGhpcyBsb2cgbWFuYWdlciBpbnN0YW5jZS4KCiAgICAgICAgOmFyZyBmaWxlbmFtZTogSWYgZ2l2ZW4sIHRoZSBmaWxlbmFtZSB0byB3aGljaCB0aGlzIGxvZyBpcyBib3VuZC4KICAgICAgICAgIElmIHRoaXMgZGF0YWJhc2UgZXhpc3RzLCB0aGUgY3VycmVudCBzdGF0ZSBpcyBsb2FkZWQgZnJvbSBpdC4KICAgICAgICA6YXJnIG1vZGU6IE9uZSBvZiAidyIsICJyIiBmb3Igd3JpdGUsIHJlYWQuICJ3IiBhc3N1bWVzIHRoYXQgdGhlCiAgICAgICAgICBkYXRhYmFzZSBpcyBpbml0aWFsbHkgZW1wdHkuIE1heSBhbHNvIGJlICJ3dSIgdG8gaW5kaWNhdGUgdGhhdAogICAgICAgICAgYSB1bmlxdWUgZmlsZW5hbWUgc2hvdWxkIGJlIGNob3NlbiBhdXRvbWF0aWNhbGx5LiBNYXkgYWxzbyBiZSAid28iCiAgICAgICAgICB0byBpbmRpY2F0ZSB0aGF0IHRoZSBmaWxlIHNob3VsZCBiZSBvdmVyd3JpdHRlbi4KICAgICAgICA6YXJnIG1waV9jb21tOiBBbiBvcHRpb25hbCA6Y2xhc3M6YG1waTRweS5NUEkuQ29tbWAgb2JqZWN0LgogICAgICAgICAgSWYgZ2l2ZW4sIGxvZ3MgYXJlIHBlcmlvZGljYWxseSBzeW5jaHJvbml6ZWQgdG8gdGhlIGhlYWQgbm9kZSwKICAgICAgICAgIHdoaWNoIHRoZW4gd3JpdGVzIHRoZW0gb3V0IHRvIGRpc2suCiAgICAgICAgOmFyZyBjYXB0dXJlX3dhcm5pbmdzOiBUYXAgdGhlIFB5dGhvbiB3YXJuaW5ncyBmYWNpbGl0eSBhbmQgc2F2ZSB3YXJuaW5ncwogICAgICAgICAgdG8gdGhlIGxvZyBmaWxlLgogICAgICAgIDphcmcgY29tbWl0X2ludGVydmFsOiBhY3R1YWxseSBwZXJmb3JtIGEgY29tbWl0IG9ubHkgZXZlcnkgTiB0aW1lcyBhIGNvbW1pdAogICAgICAgICAgaXMgcmVxdWVzdGVkLgogICAgICAgIDphcmcgd2F0Y2hfaW50ZXJ2YWw6IHByaW50IHdhdGNoZXMgZXZlcnkgTiBzZWNvbmRzLgogICAgICAgICIiIgoKICAgICAgICBhc3NlcnQgaXNpbnN0YW5jZShtb2RlLCBzdHIpLCAibW9kZSBtdXN0IGJlIGEgc3RyaW5nIgogICAgICAgIGFzc2VydCBtb2RlIGluIFsidyIsICJyIiwgInd1IiwgIndvIl0sICJpbnZhbGlkIG1vZGUiCgogICAgICAgIHNlbGYucXVhbnRpdHlfZGF0YTogRGljdFtzdHIsIF9RdWFudGl0eURhdGFdID0ge30KICAgICAgICBzZWxmLmxhc3RfdmFsdWVzOiBEaWN0W3N0ciwgT3B0aW9uYWxbZmxvYXRdXSA9IHt9CiAgICAgICAgc2VsZi5iZWZvcmVfZ2F0aGVyX2Rlc2NyaXB0b3JzOiBMaXN0W19HYXRoZXJEZXNjcmlwdG9yXSA9IFtdCiAgICAgICAgc2VsZi5hZnRlcl9nYXRoZXJfZGVzY3JpcHRvcnM6IExpc3RbX0dhdGhlckRlc2NyaXB0b3JdID0gW10KICAgICAgICBzZWxmLnRpY2tfY291bnQgPSAwCgogICAgICAgIHNlbGYuY29tbWl0X2ludGVydmFsID0gY29tbWl0X2ludGVydmFsCiAgICAgICAgc2VsZi5jb21taXRfY291bnRkb3duID0gY29tbWl0X2ludGVydmFsCgogICAgICAgIHNlbGYuY29uc3RhbnRzOiBEaWN0W3N0ciwgb2JqZWN0XSA9IHt9CgogICAgICAgIHNlbGYubGFzdF9zYXZlX3RpbWUgPSB0aW1lX21vbm90b25pYygpCgogICAgICAgICMgc2VsZi10aW1pbmcKICAgICAgICBzZWxmLnN0YXJ0X3RpbWUgPSB0aW1lX21vbm90b25pYygpCiAgICAgICAgc2VsZi50X2xvZzogZmxvYXQgPSAwCgogICAgICAgICMgcGFyYWxsZWwgc3VwcG9ydAogICAgICAgIHNlbGYuaGVhZF9yYW5rID0gMAogICAgICAgIHNlbGYubXBpX2NvbW0gPSBtcGlfY29tbQogICAgICAgIHNlbGYuaXNfcGFyYWxsZWwgPSBtcGlfY29tbSBpcyBub3QgTm9uZQoKICAgICAgICBpZiBtcGlfY29tbSBpcyBOb25lOgogICAgICAgICAgICBzZWxmLnJhbmsgPSAwCiAgICAgICAgZWxzZToKICAgICAgICAgICAgc2VsZi5yYW5rID0gbXBpX2NvbW0ucmFuawogICAgICAgICAgICBzZWxmLmhlYWRfcmFuayA9IDAKCiAgICAgICAgIyB3YXRjaCBzdHVmZgogICAgICAgIHNlbGYud2F0Y2hlczogTGlzdFtfV2F0Y2hJbmZvXSA9IFtdCiAgICAgICAgc2VsZi5oYXZlX25vbmxvY2FsX3dhdGNoZXMgPSBGYWxzZQoKICAgICAgICAjIEludGVydmFsIGJldHdlZW4gcHJpbnRpbmcgd2F0Y2hlcywgaW4gc2Vjb25kcwogICAgICAgIHNlbGYuc2V0X3dhdGNoX2ludGVydmFsKHdhdGNoX2ludGVydmFsKQoKICAgICAgICAjIGRhdGFiYXNlIGJpbmRpbmcKICAgICAgICBpbXBvcnQgc3FsaXRlMyBhcyBzcWxpdGUKCiAgICAgICAgc2VsZi5zcWxpdGVfZmlsZW5hbWU6IE9wdGlvbmFsW3N0cl0gPSBOb25lCiAgICAgICAgaWYgZmlsZW5hbWUgaXMgTm9uZToKICAgICAgICAgICAgZmlsZV9iYXNlID0gIjptZW1vcnk6IgogICAgICAgICAgICBmaWxlX2V4dGVuc2lvbiA9ICIiCiAgICAgICAgZWxzZToKICAgICAgICAgICAgaW1wb3J0IG9zCiAgICAgICAgICAgIGZpbGVfYmFzZSwgZmlsZV9leHRlbnNpb24gPSBvcy5wYXRoLnNwbGl0ZXh0KGZpbGVuYW1lKQogICAgICAgICAgICBpZiBzZWxmLmlzX3BhcmFsbGVsOgogICAgICAgICAgICAgICAgZmlsZV9iYXNlICs9ICItcmFuayVkIiAlIHNlbGYucmFuawoKICAgICAgICB3aGlsZSBUcnVlOgogICAgICAgICAgICBzdWZmaXggPSAiIgoKICAgICAgICAgICAgaWYgbW9kZSA9PSAid3UiIGFuZCBub3QgZmlsZV9iYXNlID09ICI6bWVtb3J5OiI6CiAgICAgICAgICAgICAgICBpZiBzZWxmLmlzX3BhcmFsbGVsOgogICAgICAgICAgICAgICAgICAgIGFzc2VydCBzZWxmLm1waV9jb21tCiAgICAgICAgICAgICAgICAgICAgc3VmZml4ID0gc2VsZi5tcGlfY29tbS5iY2FzdChfZ2V0X3VuaXF1ZV9zdWZmaXgoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJvb3Q9c2VsZi5oZWFkX3JhbmspCiAgICAgICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgICAgIHN1ZmZpeCA9IF9nZXRfdW5pcXVlX3N1ZmZpeCgpCgogICAgICAgICAgICBmaWxlbmFtZSA9IGZpbGVfYmFzZSArIHN1ZmZpeCArIGZpbGVfZXh0ZW5zaW9uCiAgICAgICAgICAgIGlmIG5vdCBmaWxlX2Jhc2UgPT0gIjptZW1vcnk6IjoKICAgICAgICAgICAgICAgIHNlbGYuc3FsaXRlX2ZpbGVuYW1lID0gZmlsZW5hbWUKCiAgICAgICAgICAgIGlmIG1vZGUgPT0gIndvIjoKICAgICAgICAgICAgICAgIGltcG9ydCBvcwogICAgICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgICAgIG9zLnJlbW92ZShmaWxlbmFtZSkKICAgICAgICAgICAgICAgIGV4Y2VwdCBPU0Vycm9yOgogICAgICAgICAgICAgICAgICAgIHBhc3MKCiAgICAgICAgICAgIHNlbGYuZGJfY29ubiA9IHNxbGl0ZS5jb25uZWN0KGZpbGVuYW1lLCB0aW1lb3V0PTMwKQogICAgICAgICAgICBzZWxmLm1vZGUgPSBtb2RlCiAgICAgICAgICAgIHRyeToKICAgICAgICAgICAgICAgIHNlbGYuZGJfY29ubi5leGVjdXRlKCJzZWxlY3QgKiBmcm9tIHF1YW50aXRpZXM7IikKICAgICAgICAgICAgZXhjZXB0IHNxbGl0ZS5PcGVyYXRpb25hbEVycm9yOgogICAgICAgICAgICAgICAgIyB3ZSdyZSBidWlsZGluZyBhIG5ldyBkYXRhYmFzZQogICAgICAgICAgICAgICAgaWYgbW9kZSA9PSAiciI6CiAgICAgICAgICAgICAgICAgICAgcmFpc2UgUnVudGltZUVycm9yKCJMb2cgZGF0YWJhc2UgJyVzJyBub3QgZm91bmQiICUgZmlsZW5hbWUpCgogICAgICAgICAgICAgICAgc2VsZi5zY2hlbWFfdmVyc2lvbiA9IF9zZXRfdXBfc2NoZW1hKHNlbGYuZGJfY29ubikKICAgICAgICAgICAgICAgIHNlbGYuc2V0X2NvbnN0YW50KCJzY2hlbWFfdmVyc2lvbiIsIHNlbGYuc2NoZW1hX3ZlcnNpb24pCgogICAgICAgICAgICAgICAgc2VsZi5zZXRfY29uc3RhbnQoImlzX3BhcmFsbGVsIiwgc2VsZi5pc19wYXJhbGxlbCkKCiAgICAgICAgICAgICAgICAjIHNldCBnbG9iYWxseSB1bmlxdWUgcnVuX2lkCiAgICAgICAgICAgICAgICBpZiBzZWxmLmlzX3BhcmFsbGVsOgogICAgICAgICAgICAgICAgICAgIGFzc2VydCBzZWxmLm1waV9jb21tCiAgICAgICAgICAgICAgICAgICAgc2VsZi5zZXRfY29uc3RhbnQoInVuaXF1ZV9ydW5faWQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VsZi5tcGlfY29tbS5iY2FzdChfZ2V0X3VuaXF1ZV9pZCgpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJvb3Q9c2VsZi5oZWFkX3JhbmspKQogICAgICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgICAgICBzZWxmLnNldF9jb25zdGFudCgidW5pcXVlX3J1bl9pZCIsIF9nZXRfdW5pcXVlX2lkKCkpCgogICAgICAgICAgICAgICAgaWYgc2VsZi5pc19wYXJhbGxlbDoKICAgICAgICAgICAgICAgICAgICBhc3NlcnQgc2VsZi5tcGlfY29tbQogICAgICAgICAgICAgICAgICAgIHNlbGYuc2V0X2NvbnN0YW50KCJyYW5rX2NvdW50Iiwgc2VsZi5tcGlfY29tbS5HZXRfc2l6ZSgpKQogICAgICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgICAgICBzZWxmLnNldF9jb25zdGFudCgicmFua19jb3VudCIsIDEpCgogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgIyB3ZSd2ZSBvcGVuZWQgYW4gZXhpc3RpbmcgZGF0YWJhc2UKICAgICAgICAgICAgICAgIGlmIG1vZGUgPT0gInciOgogICAgICAgICAgICAgICAgICAgIHJhaXNlIFJ1bnRpbWVFcnJvcigiTG9nIGRhdGFiYXNlICclcycgYWxyZWFkeSBleGlzdHMiICUgZmlsZW5hbWUpCgogICAgICAgICAgICAgICAgaWYgbW9kZSA9PSAid3UiOgogICAgICAgICAgICAgICAgICAgICMgdHJ5IGFnYWluIHdpdGggYSBuZXcgc3VmZml4CiAgICAgICAgICAgICAgICAgICAgY29udGludWUKCiAgICAgICAgICAgICAgICBpZiBtb2RlID09ICJ3byI6CiAgICAgICAgICAgICAgICAgICAgIyB0cnkgYWdhaW4sIHNvbWVvbmUgbWlnaHQgaGF2ZSBjcmVhdGVkIGEgZmlsZSB3aXRoIHRoZSBzYW1lIG5hbWUKICAgICAgICAgICAgICAgICAgICBjb250aW51ZQoKICAgICAgICAgICAgICAgIHNlbGYuX2xvYWQoKQoKICAgICAgICAgICAgYnJlYWsKCiAgICAgICAgIyB7e3sgd2FybmluZ3MvbG9nZ2luZyBjYXB0dXJlCgogICAgICAgIHNlbGYud2FybmluZ19kYXRhOiBMaXN0W19Mb2dXYXJuaW5nSW5mb10gPSBbXQogICAgICAgIHNlbGYub2xkX3Nob3d3YXJuaW5nOiBPcHRpb25hbFtDYWxsYWJsZVsuLi4sIEFueV1dID0gTm9uZQogICAgICAgIGlmIGNhcHR1cmVfd2FybmluZ3MgYW5kIHNlbGYubW9kZVswXSA9PSAidyI6CiAgICAgICAgICAgIHNlbGYuY2FwdHVyZV93YXJuaW5ncyhUcnVlKQoKICAgICAgICBzZWxmLmxvZ2dpbmdfZGF0YTogTGlzdFtfTG9nV2FybmluZ0luZm9dID0gW10KICAgICAgICBzZWxmLmxvZ2dpbmdfaGFuZGxlcjogT3B0aW9uYWxbbG9nZ2luZy5IYW5kbGVyXSA9IE5vbmUKICAgICAgICBpZiBjYXB0dXJlX2xvZ2dpbmcgYW5kIHNlbGYubW9kZVswXSA9PSAidyI6CiAgICAgICAgICAgIHNlbGYuY2FwdHVyZV9sb2dnaW5nKFRydWUpCgogICAgICAgICMgfX19CgogICAgZGVmIGNhcHR1cmVfd2FybmluZ3Moc2VsZiwgZW5hYmxlOiBib29sID0gVHJ1ZSkgLT4gTm9uZToKICAgICAgICBkZWYgX3Nob3d3YXJuaW5nKG1lc3NhZ2U6IFVuaW9uW1dhcm5pbmcsIHN0cl0sIGNhdGVnb3J5OiBUeXBlW1dhcm5pbmddLAogICAgICAgICAgICAgICAgICAgICAgICAgZmlsZW5hbWU6IHN0ciwgbGluZW5vOiBpbnQsIGZpbGU6IE9wdGlvbmFsW1RleHRJT10gPSBOb25lLAogICAgICAgICAgICAgICAgICAgICAgICAgbGluZTogT3B0aW9uYWxbc3RyXSA9IE5vbmUpIC0+IE5vbmU6CiAgICAgICAgICAgIGFzc2VydCBzZWxmLm9sZF9zaG93d2FybmluZwogICAgICAgICAgICBzZWxmLm9sZF9zaG93d2FybmluZyhtZXNzYWdlLCBjYXRlZ29yeSwgZmlsZW5hbWUsIGxpbmVubywgZmlsZSwgbGluZSkKCiAgICAgICAgICAgIGZyb20gdGltZSBpbXBvcnQgdGltZQoKICAgICAgICAgICAgc2VsZi53YXJuaW5nX2RhdGEuYXBwZW5kKF9Mb2dXYXJuaW5nSW5mbygKICAgICAgICAgICAgICAgIHRpY2tfY291bnQ9c2VsZi50aWNrX2NvdW50LAogICAgICAgICAgICAgICAgdGltZT10aW1lKCksCiAgICAgICAgICAgICAgICBtZXNzYWdlPXN0cihtZXNzYWdlKSwKICAgICAgICAgICAgICAgIGNhdGVnb3J5PXN0cihjYXRlZ29yeSksCiAgICAgICAgICAgICAgICBmaWxlbmFtZT1maWxlbmFtZSwKICAgICAgICAgICAgICAgIGxpbmVubz1saW5lbm8KICAgICAgICAgICAgKSkKCiAgICAgICAgaW1wb3J0IHdhcm5pbmdzCiAgICAgICAgaWYgZW5hYmxlOgogICAgICAgICAgICBpZiBzZWxmLnNjaGVtYV92ZXJzaW9uIDwgMzoKICAgICAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoIldhcm5pbmdzIGNhcHR1cmUgbmVlZHMgYXQgbGVhc3Qgc2NoZW1hX3ZlcnNpb24gMywgIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGYiIGdvdCB7c2VsZi5zY2hlbWFfdmVyc2lvbn0iKQogICAgICAgICAgICBpZiBzZWxmLm9sZF9zaG93d2FybmluZyBpcyBOb25lOgogICAgICAgICAgICAgICAgc2VsZi5vbGRfc2hvd3dhcm5pbmcgPSB3YXJuaW5ncy5zaG93d2FybmluZwogICAgICAgICAgICAgICAgd2FybmluZ3Muc2hvd3dhcm5pbmcgPSBfc2hvd3dhcm5pbmcKICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgIHJhaXNlIFJ1bnRpbWVFcnJvcigiV2FybmluZ3MgY2FwdHVyZSB3YXMgZW5hYmxlZCB0d2ljZSIpCiAgICAgICAgZWxzZToKICAgICAgICAgICAgaWYgc2VsZi5vbGRfc2hvd3dhcm5pbmcgaXMgTm9uZToKICAgICAgICAgICAgICAgIHJhaXNlIFJ1bnRpbWVFcnJvcigKICAgICAgICAgICAgICAgICAgICAgICAgIldhcm5pbmdzIGNhcHR1cmUgd2FzIGRpc2FibGVkLCBidXQgbmV2ZXIgZW5hYmxlZCIpCgogICAgICAgICAgICB3YXJuaW5ncy5zaG93d2FybmluZyA9IHNlbGYub2xkX3Nob3d3YXJuaW5nCiAgICAgICAgICAgIHNlbGYub2xkX3Nob3d3YXJuaW5nID0gTm9uZQoKICAgIGRlZiBjYXB0dXJlX2xvZ2dpbmcoc2VsZiwgZW5hYmxlOiBib29sID0gVHJ1ZSkgLT4gTm9uZToKICAgICAgICBjbGFzcyBMb2dweWxlTG9nSGFuZGxlcihsb2dnaW5nLkhhbmRsZXIpOgogICAgICAgICAgICBkZWYgX19pbml0X18oc2VsZiwgbWdyOiBMb2dNYW5hZ2VyKSAtPiBOb25lOgogICAgICAgICAgICAgICAgbG9nZ2luZy5IYW5kbGVyLl9faW5pdF9fKHNlbGYpCiAgICAgICAgICAgICAgICBzZWxmLm1nciA9IG1ncgoKICAgICAgICAgICAgZGVmIGVtaXQoc2VsZiwgcmVjb3JkOiBsb2dnaW5nLkxvZ1JlY29yZCkgLT4gTm9uZToKICAgICAgICAgICAgICAgIGZyb20gdGltZSBpbXBvcnQgdGltZQogICAgICAgICAgICAgICAgc2VsZi5tZ3IubG9nZ2luZ19kYXRhLmFwcGVuZCgKICAgICAgICAgICAgICAgICAgICBfTG9nV2FybmluZ0luZm8odGlja19jb3VudD1zZWxmLm1nci50aWNrX2NvdW50LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRpbWU9dGltZSgpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1lc3NhZ2U9cmVjb3JkLmdldE1lc3NhZ2UoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjYXRlZ29yeT1yZWNvcmQubGV2ZWxuYW1lLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbGVuYW1lPXJlY29yZC5wYXRobmFtZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsaW5lbm89cmVjb3JkLmxpbmVubykpCgogICAgICAgIHJvb3RfbG9nZ2VyID0gbG9nZ2luZy5nZXRMb2dnZXIoKQoKICAgICAgICBpZiBlbmFibGU6CiAgICAgICAgICAgIGlmIHNlbGYuc2NoZW1hX3ZlcnNpb24gPCAzOgogICAgICAgICAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigiTG9nZ2luZyBjYXB0dXJlIG5lZWRzIGF0IGxlYXN0IHNjaGVtYV92ZXJzaW9uIDMsICIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmIiBnb3Qge3NlbGYuc2NoZW1hX3ZlcnNpb259IikKICAgICAgICAgICAgaWYgc2VsZi5tb2RlWzBdID09ICJ3IiBhbmQgc2VsZi5sb2dnaW5nX2hhbmRsZXIgaXMgTm9uZToKICAgICAgICAgICAgICAgIHNlbGYubG9nZ2luZ19oYW5kbGVyID0gTG9ncHlsZUxvZ0hhbmRsZXIoc2VsZikKICAgICAgICAgICAgICAgIHJvb3RfbG9nZ2VyLmFkZEhhbmRsZXIoc2VsZi5sb2dnaW5nX2hhbmRsZXIpCiAgICAgICAgICAgIGVsaWYgc2VsZi5sb2dnaW5nX2hhbmRsZXI6CiAgICAgICAgICAgICAgICBmcm9tIHdhcm5pbmdzIGltcG9ydCB3YXJuCiAgICAgICAgICAgICAgICB3YXJuKCJMb2dnaW5nIGNhcHR1cmUgYWxyZWFkeSBlbmFibGVkIikKICAgICAgICBlbHNlOgogICAgICAgICAgICBpZiBzZWxmLmxvZ2dpbmdfaGFuZGxlcjoKICAgICAgICAgICAgICAgIHJvb3RfbG9nZ2VyLnJlbW92ZUhhbmRsZXIoc2VsZi5sb2dnaW5nX2hhbmRsZXIpCiAgICAgICAgICAgIHNlbGYubG9nZ2luZ19oYW5kbGVyID0gTm9uZQoKICAgIGRlZiBnZXRfbG9nZ2luZyhzZWxmKSAtPiBEYXRhVGFibGU6CiAgICAgICAgIyBNYXRjaCB0aGUgdGFibGUgc2V0IHVwIGJ5IF9zZXRfdXBfc2NoZW1hCiAgICAgICAgY29sdW1ucyA9IFsicmFuayIsICJzdGVwIiwgInVuaXh0aW1lIiwgImxldmVsIiwgIm1lc3NhZ2UiLCAiZmlsZW5hbWUiLAogICAgICAgICAgICAgICAgICAgImxpbmVubyJdCgogICAgICAgIHJlc3VsdCA9IERhdGFUYWJsZShjb2x1bW5zKQoKICAgICAgICBpZiBzZWxmLnNjaGVtYV92ZXJzaW9uIDwgMzoKICAgICAgICAgICAgZnJvbSB3YXJuaW5ncyBpbXBvcnQgd2FybgogICAgICAgICAgICB3YXJuKCJUaGlzIGRhdGFiYXNlIGxhY2tzIGEgJ2xvZ2dpbmcnIHRhYmxlIikKICAgICAgICAgICAgcmV0dXJuIHJlc3VsdAoKICAgICAgICBmb3Igcm93IGluIHNlbGYuZGJfY29ubi5leGVjdXRlKAogICAgICAgICAgICAgICAgInNlbGVjdCAlcyBmcm9tIGxvZ2dpbmciICUgKCIsICIuam9pbihjb2x1bW5zKSkpOgogICAgICAgICAgICByZXN1bHQuaW5zZXJ0X3Jvdyhyb3cpCgogICAgICAgIHJldHVybiByZXN1bHQKCiAgICBkZWYgX2xvYWQoc2VsZikgLT4gTm9uZToKICAgICAgICBpZiBzZWxmLm1waV9jb21tIGFuZCBzZWxmLm1waV9jb21tLnJhbmsgIT0gc2VsZi5oZWFkX3Jhbms6CiAgICAgICAgICAgIHJldHVybgoKICAgICAgICBmcm9tIHBpY2tsZSBpbXBvcnQgbG9hZHMKICAgICAgICBmb3IgbmFtZSwgdmFsdWUgaW4gc2VsZi5kYl9jb25uLmV4ZWN1dGUoInNlbGVjdCBuYW1lLCB2YWx1ZSBmcm9tIGNvbnN0YW50cyIpOgogICAgICAgICAgICBzZWxmLmNvbnN0YW50c1tuYW1lXSA9IGxvYWRzKHZhbHVlKQoKICAgICAgICBzZWxmLnNjaGVtYV92ZXJzaW9uID0gY2FzdChpbnQsIHNlbGYuY29uc3RhbnRzLmdldCgic2NoZW1hX3ZlcnNpb24iLCAwKSkKCiAgICAgICAgc2VsZi5pc19wYXJhbGxlbCA9IGJvb2woc2VsZi5jb25zdGFudHNbImlzX3BhcmFsbGVsIl0pCgogICAgICAgIGZvciBuYW1lLCB1bml0LCBkZXNjcmlwdGlvbiwgZGVmX2FnZyBpbiBzZWxmLmRiX2Nvbm4uZXhlY3V0ZSgKICAgICAgICAgICAgICAgICJzZWxlY3QgbmFtZSwgdW5pdCwgZGVzY3JpcHRpb24sIGRlZmF1bHRfYWdncmVnYXRvciAiCiAgICAgICAgICAgICAgICAiZnJvbSBxdWFudGl0aWVzIik6CiAgICAgICAgICAgIHNlbGYucXVhbnRpdHlfZGF0YVtuYW1lXSA9IF9RdWFudGl0eURhdGEoCiAgICAgICAgICAgICAgICAgICAgdW5pdCwgZGVzY3JpcHRpb24sIGxvYWRzKGRlZl9hZ2cpKQoKICAgIGRlZiBjbG9zZShzZWxmKSAtPiBOb25lOgogICAgICAgIGlmIHNlbGYub2xkX3Nob3d3YXJuaW5nIGlzIG5vdCBOb25lOgogICAgICAgICAgICBzZWxmLmNhcHR1cmVfd2FybmluZ3MoRmFsc2UpCgogICAgICAgIGlmIHNlbGYubG9nZ2luZ19oYW5kbGVyOgogICAgICAgICAgICBzZWxmLmNhcHR1cmVfbG9nZ2luZyhGYWxzZSkKCiAgICAgICAgc2VsZi5zYXZlKCkKICAgICAgICBzZWxmLmRiX2Nvbm4uY2xvc2UoKQoKICAgIGRlZiBnZXRfdGFibGUoc2VsZiwgcV9uYW1lOiBzdHIpIC0+IERhdGFUYWJsZToKICAgICAgICBpZiBxX25hbWUgbm90IGluIHNlbGYucXVhbnRpdHlfZGF0YToKICAgICAgICAgICAgcmFpc2UgS2V5RXJyb3IoImludmFsaWQgcXVhbnRpdHkgbmFtZSAnJXMnIiAlIHFfbmFtZSkKCiAgICAgICAgcmVzdWx0ID0gRGF0YVRhYmxlKAogICAgICAgICAgICBbInN0ZXAiLCAicmFuayIsICJ2YWx1ZSJdKQoKICAgICAgICBmb3Igcm93IGluIHNlbGYuZGJfY29ubi5leGVjdXRlKAogICAgICAgICAgICAgICAgInNlbGVjdCBzdGVwLCByYW5rLCB2YWx1ZSBmcm9tICVzIiAlIHFfbmFtZSk6CiAgICAgICAgICAgIHJlc3VsdC5pbnNlcnRfcm93KHJvdykKCiAgICAgICAgcmV0dXJuIHJlc3VsdAoKICAgIGRlZiBnZXRfd2FybmluZ3Moc2VsZikgLT4gRGF0YVRhYmxlOgogICAgICAgICMgTWF0Y2ggdGhlIHRhYmxlIHNldCB1cCBieSBfc2V0X3VwX3NjaGVtYQogICAgICAgIGNvbHVtbnMgPSBbInN0ZXAiLCAibWVzc2FnZSIsICJjYXRlZ29yeSIsICJmaWxlbmFtZSIsICJsaW5lbm8iXQogICAgICAgIGlmIHNlbGYuc2NoZW1hX3ZlcnNpb24gPj0gMjoKICAgICAgICAgICAgY29sdW1ucy5pbnNlcnQoMCwgInJhbmsiKQoKICAgICAgICAgICAgaWYgc2VsZi5zY2hlbWFfdmVyc2lvbiA+PSAzOgogICAgICAgICAgICAgICAgY29sdW1ucy5pbnNlcnQoMiwgInVuaXh0aW1lIikKCiAgICAgICAgcmVzdWx0ID0gRGF0YVRhYmxlKGNvbHVtbnMpCgogICAgICAgIGZvciByb3cgaW4gc2VsZi5kYl9jb25uLmV4ZWN1dGUoCiAgICAgICAgICAgICAgICAic2VsZWN0ICVzIGZyb20gd2FybmluZ3MiICUgKCIsICIuam9pbihjb2x1bW5zKSkpOgogICAgICAgICAgICByZXN1bHQuaW5zZXJ0X3Jvdyhyb3cpCgogICAgICAgIHJldHVybiByZXN1bHQKCiAgICBkZWYgYWRkX3dhdGNoZXMoc2VsZiwgd2F0Y2hlczogTGlzdFtVbmlvbltzdHIsIFR1cGxlW3N0ciwgc3RyXV1dKSAtPiBOb25lOgogICAgICAgICIiIkFkZCBxdWFudGl0aWVzIHRoYXQgYXJlIHByaW50ZWQgYWZ0ZXIgZXZlcnkgdGltZSBzdGVwLgoKICAgICAgICA6YXJnIHdhdGNoZXM6CiAgICAgICAgICAgIExpc3Qgb2YgZXhwcmVzc2lvbnMgdG8gd2F0Y2guIEVhY2ggZWxlbWVudCBjYW4gZWl0aGVyIGJlCiAgICAgICAgICAgIGEgc3RyaW5nIG9mIHRoZSBleHByZXNzaW9uIHRvIHdhdGNoLCBvciBhIHR1cGxlIG9mIHRoZSBleHByZXNzaW9uCiAgICAgICAgICAgIGFuZCBhIGZvcm1hdCBzdHJpbmcuIEluIHRoZSBmb3JtYXQgc3RyaW5nLCB5b3UgY2FuIHVzZSB0aGUgY3VzdG9tCiAgICAgICAgICAgIGZpZWxkcyBgYHtkaXNwbGF5fWBgLCBgYHt2YWx1ZX1gYCwgYW5kIGBge3VuaXR9YGAgdG8gaW5kaWNhdGUgd2hlcmUgdGhlCiAgICAgICAgICAgIHdhdGNoIGV4cHJlc3Npb24sIHZhbHVlLCBhbmQgdW5pdCBzaG91bGQgYmUgcHJpbnRlZC4gVGhlIGRlZmF1bHQgZm9ybWF0CiAgICAgICAgICAgIHN0cmluZyBmb3IgZWFjaCB3YXRjaCBpcyBgYHtkaXNwbGF5fT17dmFsdWU6Z317dW5pdH1gYC4KICAgICAgICAiIiIKCiAgICAgICAgZGVmYXVsdF9mb3JtYXQgPSAie2Rpc3BsYXl9PXt2YWx1ZTpnfXt1bml0fSB8ICIKCiAgICAgICAgZm9yIHdhdGNoIGluIHdhdGNoZXM6CiAgICAgICAgICAgIGlmIGlzaW5zdGFuY2Uod2F0Y2gsIHR1cGxlKToKICAgICAgICAgICAgICAgIGV4cHIsIGZtdCA9IHdhdGNoCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICBleHByID0gd2F0Y2gKICAgICAgICAgICAgICAgIGZtdCA9IGRlZmF1bHRfZm9ybWF0CgogICAgICAgICAgICBwYXJzZWQgPSBzZWxmLl9wYXJzZV9leHByKGV4cHIpCiAgICAgICAgICAgIHBhcnNlZCwgZGVwX2RhdGEgPSBzZWxmLl9nZXRfZXhwcl9kZXBfZGF0YShwYXJzZWQpCgogICAgICAgICAgICBpZiBsZW4oZGVwX2RhdGEpID09IDE6CiAgICAgICAgICAgICAgICB1bml0ID0gZGVwX2RhdGFbMF0ucWRhdC51bml0CiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICB1bml0ID0gTm9uZQoKICAgICAgICAgICAgZnJvbSBweXRvb2xzIGltcG9ydCBhbnkKICAgICAgICAgICAgc2VsZi5oYXZlX25vbmxvY2FsX3dhdGNoZXMgPSBzZWxmLmhhdmVfbm9ubG9jYWxfd2F0Y2hlcyBvciBcCiAgICAgICAgICAgICAgICAgICAgYW55KGRkLm5vbmxvY2FsX2FnZyBmb3IgZGQgaW4gZGVwX2RhdGEpCgogICAgICAgICAgICBmcm9tIHB5bWJvbGljIGltcG9ydCBjb21waWxlICAjIHR5cGU6IGlnbm9yZVtpbXBvcnRdCiAgICAgICAgICAgIGNvbXBpbGVkID0gY29tcGlsZShwYXJzZWQsIFtkZC52YXJuYW1lIGZvciBkZCBpbiBkZXBfZGF0YV0pCgogICAgICAgICAgICB3YXRjaF9pbmZvID0gX1dhdGNoSW5mbyhwYXJzZWQ9cGFyc2VkLCBleHByPWV4cHIsIGRlcF9kYXRhPWRlcF9kYXRhLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb21waWxlZD1jb21waWxlZCwgdW5pdD11bml0LCBmb3JtYXQ9Zm10KQoKICAgICAgICAgICAgc2VsZi53YXRjaGVzLmFwcGVuZCh3YXRjaF9pbmZvKQoKICAgIGRlZiBzZXRfd2F0Y2hfaW50ZXJ2YWwoc2VsZiwgaW50ZXJ2YWw6IGZsb2F0KSAtPiBOb25lOgogICAgICAgICIiIlNldCB0aGUgaW50ZXJ2YWwgKGluIHNlY29uZHMpIGJldHdlZW4gdGhlIHRpbWUgd2F0Y2hlcyBhcmUgcHJpbnRlZC4KCiAgICAgICAgOmFyZyBpbnRlcnZhbDogd2F0Y2ggcHJpbnRpbmcgaW50ZXJ2YWwgaW4gc2Vjb25kcy4KICAgICAgICAiIiIKICAgICAgICBzZWxmLndhdGNoX2ludGVydmFsID0gaW50ZXJ2YWwKICAgICAgICBzZWxmLm5leHRfd2F0Y2hfdGljayA9IHNlbGYudGlja19jb3VudCArIDEKCiAgICBkZWYgc2V0X2NvbnN0YW50KHNlbGYsIG5hbWU6IHN0ciwgdmFsdWU6IEFueSkgLT4gTm9uZToKICAgICAgICAiIiJNYWtlIGEgbmFtZWQsIGNvbnN0YW50IHZhbHVlIGF2YWlsYWJsZSBpbiB0aGUgbG9nLgoKICAgICAgICA6YXJnIG5hbWU6IHRoZSBuYW1lIG9mIHRoZSBjb25zdGFudC4KICAgICAgICA6YXJnIHZhbHVlOiB0aGUgdmFsdWUgb2YgdGhlIGNvbnN0YW50LgogICAgICAgICIiIgogICAgICAgIGV4aXN0ZWQgPSBuYW1lIGluIHNlbGYuY29uc3RhbnRzCiAgICAgICAgc2VsZi5jb25zdGFudHNbbmFtZV0gPSB2YWx1ZQoKICAgICAgICBmcm9tIHBpY2tsZSBpbXBvcnQgZHVtcHMKICAgICAgICB2YWx1ZSA9IGJ5dGVzKGR1bXBzKHZhbHVlKSkKCiAgICAgICAgaWYgZXhpc3RlZDoKICAgICAgICAgICAgc2VsZi5kYl9jb25uLmV4ZWN1dGUoInVwZGF0ZSBjb25zdGFudHMgc2V0IHZhbHVlID0gPyB3aGVyZSBuYW1lID0gPyIsCiAgICAgICAgICAgICAgICAgICAgKHZhbHVlLCBuYW1lKSkKICAgICAgICBlbHNlOgogICAgICAgICAgICBzZWxmLmRiX2Nvbm4uZXhlY3V0ZSgiaW5zZXJ0IGludG8gY29uc3RhbnRzIHZhbHVlcyAoPyw/KSIsCiAgICAgICAgICAgICAgICAgICAgKG5hbWUsIHZhbHVlKSkKCiAgICAgICAgc2VsZi5fY29tbWl0KCkKCiAgICBkZWYgX2luc2VydF9kYXRhcG9pbnQoc2VsZiwgbmFtZTogc3RyLCB2YWx1ZTogT3B0aW9uYWxbZmxvYXRdKSAtPiBOb25lOgogICAgICAgIGlmIHZhbHVlIGlzIE5vbmU6CiAgICAgICAgICAgIHJldHVybgoKICAgICAgICBzZWxmLmxhc3RfdmFsdWVzW25hbWVdID0gdmFsdWUKCiAgICAgICAgdHJ5OgogICAgICAgICAgICBzZWxmLmRiX2Nvbm4uZXhlY3V0ZSgiaW5zZXJ0IGludG8gJXMgdmFsdWVzICg/LD8sPykiICUgbmFtZSwKICAgICAgICAgICAgICAgICAgICAoc2VsZi50aWNrX2NvdW50LCBzZWxmLnJhbmssIGZsb2F0KHZhbHVlKSkpCiAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbjoKICAgICAgICAgICAgcHJpbnQoIndoaWxlIGFkZGluZyBkYXRhcG9pbnQgZm9yICclcyc6IiAlIG5hbWUpCiAgICAgICAgICAgIHJhaXNlCgogICAgZGVmIF91cGRhdGVfdF9sb2coc2VsZiwgbmFtZTogc3RyLCB2YWx1ZTogZmxvYXQpIC0+IE5vbmU6CiAgICAgICAgaWYgdmFsdWUgaXMgTm9uZToKICAgICAgICAgICAgcmV0dXJuCgogICAgICAgIHNlbGYubGFzdF92YWx1ZXNbbmFtZV0gPSB2YWx1ZQoKICAgICAgICB0cnk6CiAgICAgICAgICAgIHNlbGYuZGJfY29ubi5leGVjdXRlKGYidXBkYXRlIHtuYW1lfSBzZXQgdmFsdWUgPSB7ZmxvYXQodmFsdWUpfSBcCiAgICAgICAgICAgICAgICB3aGVyZSByYW5rID0ge3NlbGYucmFua30gYW5kIHN0ZXAgPSB7c2VsZi50aWNrX2NvdW50fSIpCiAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbjoKICAgICAgICAgICAgcHJpbnQoIndoaWxlIGFkZGluZyBkYXRhcG9pbnQgZm9yICclcyc6IiAlIG5hbWUpCiAgICAgICAgICAgIHJhaXNlCgogICAgZGVmIF9nYXRoZXJfZm9yX2Rlc2NyaXB0b3Ioc2VsZiwgZ2Q6IF9HYXRoZXJEZXNjcmlwdG9yKSAtPiBOb25lOgogICAgICAgIGlmIHNlbGYudGlja19jb3VudCAlIGdkLmludGVydmFsID09IDA6CiAgICAgICAgICAgIHFfdmFsdWUgPSBnZC5xdWFudGl0eSgpCiAgICAgICAgICAgIGlmIGlzaW5zdGFuY2UoZ2QucXVhbnRpdHksIE11bHRpTG9nUXVhbnRpdHkpOgogICAgICAgICAgICAgICAgZm9yIG5hbWUsIHZhbHVlIGluIHppcChnZC5xdWFudGl0eS5uYW1lcywgcV92YWx1ZSk6CiAgICAgICAgICAgICAgICAgICAgc2VsZi5faW5zZXJ0X2RhdGFwb2ludChuYW1lLCB2YWx1ZSkKICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgIHNlbGYuX2luc2VydF9kYXRhcG9pbnQoZ2QucXVhbnRpdHkubmFtZSwgcV92YWx1ZSkKCiAgICBkZWYgdGlja19iZWZvcmUoc2VsZikgLT4gTm9uZToKICAgICAgICAiIiJSZWNvcmQgZGF0YSBwb2ludHMgZnJvbSBlYWNoIGFkZGVkIDpjbGFzczpgTG9nUXVhbnRpdHlgIHRoYXQKICAgICAgICBpcyBub3QgYW4gaW5zdGFuY2Ugb2YgOmNsYXNzOmBQb3N0TG9nUXVhbnRpdHlgLiBBbHNvLCBpbnZva2UKICAgICAgICA6bWV0aDpgUG9zdExvZ1F1YW50aXR5LnByZXBhcmVfZm9yX3RpY2tgIG9uIDpjbGFzczpgUG9zdExvZ1F1YW50aXR5YAogICAgICAgIGluc3RhbmNlcy4KICAgICAgICAiIiIKICAgICAgICB0aWNrX3N0YXJ0X3RpbWUgPSB0aW1lX21vbm90b25pYygpCgogICAgICAgIGZvciBnZCBpbiBzZWxmLmJlZm9yZV9nYXRoZXJfZGVzY3JpcHRvcnM6CiAgICAgICAgICAgIHNlbGYuX2dhdGhlcl9mb3JfZGVzY3JpcHRvcihnZCkKCiAgICAgICAgZm9yIGdkIGluIHNlbGYuYWZ0ZXJfZ2F0aGVyX2Rlc2NyaXB0b3JzOgogICAgICAgICAgICBjYXN0KFBvc3RMb2dRdWFudGl0eSwgZ2QucXVhbnRpdHkpLnByZXBhcmVfZm9yX3RpY2soKQoKICAgICAgICBzZWxmLnRfbG9nID0gdGltZV9tb25vdG9uaWMoKSAtIHRpY2tfc3RhcnRfdGltZQoKICAgIGRlZiB0aWNrX2FmdGVyKHNlbGYpIC0+IE5vbmU6CiAgICAgICAgIiIiUmVjb3JkIGRhdGEgcG9pbnRzIGZyb20gZWFjaCBhZGRlZCA6Y2xhc3M6YExvZ1F1YW50aXR5YCB0aGF0CiAgICAgICAgaXMgYW4gaW5zdGFuY2Ugb2YgOmNsYXNzOmBQb3N0TG9nUXVhbnRpdHlgLgoKICAgICAgICBNYXkgYWxzbyBjaGVja3BvaW50IGRhdGEgdG8gZGlzay4KICAgICAgICAiIiIKICAgICAgICB0aWNrX3N0YXJ0X3RpbWUgPSB0aW1lX21vbm90b25pYygpCgogICAgICAgIGZvciBnZF9sc3QgaW4gW3NlbGYuYmVmb3JlX2dhdGhlcl9kZXNjcmlwdG9ycywKICAgICAgICAgICAgICAgIHNlbGYuYWZ0ZXJfZ2F0aGVyX2Rlc2NyaXB0b3JzXToKICAgICAgICAgICAgZm9yIGdkIGluIGdkX2xzdDoKICAgICAgICAgICAgICAgIGdkLnF1YW50aXR5LnRpY2soKQoKICAgICAgICBmb3IgZ2QgaW4gc2VsZi5hZnRlcl9nYXRoZXJfZGVzY3JpcHRvcnM6CiAgICAgICAgICAgIHNlbGYuX2dhdGhlcl9mb3JfZGVzY3JpcHRvcihnZCkKCiAgICAgICAgaWYgdGlja19zdGFydF90aW1lIC0gc2VsZi5zdGFydF90aW1lID4gMTUqNjA6CiAgICAgICAgICAgIHNhdmVfaW50ZXJ2YWwgPSA1KjYwCiAgICAgICAgZWxzZToKICAgICAgICAgICAgc2F2ZV9pbnRlcnZhbCA9IDE1CgogICAgICAgIGlmIHRpY2tfc3RhcnRfdGltZSA+IHNlbGYubGFzdF9zYXZlX3RpbWUgKyBzYXZlX2ludGVydmFsOgogICAgICAgICAgICBzZWxmLnNhdmUoKQoKICAgICAgICAjIHByaW50IHdhdGNoZXMKICAgICAgICBpZiBzZWxmLnRpY2tfY291bnQrMSA+PSBzZWxmLm5leHRfd2F0Y2hfdGljazoKICAgICAgICAgICAgc2VsZi5fd2F0Y2hfdGljaygpCgogICAgICAgIHNlbGYudF9sb2cgKz0gdGltZV9tb25vdG9uaWMoKSAtIHRpY2tfc3RhcnRfdGltZQoKICAgICAgICAjIEFkanVzdCBsb2cgdXBkYXRlIHRpbWUocyksIHRfbG9nCiAgICAgICAgZm9yIGdkIGluIHNlbGYuYWZ0ZXJfZ2F0aGVyX2Rlc2NyaXB0b3JzOgogICAgICAgICAgICBpZiBpc2luc3RhbmNlKGdkLnF1YW50aXR5LCBMb2dVcGRhdGVEdXJhdGlvbik6CiAgICAgICAgICAgICAgICBzZWxmLl91cGRhdGVfdF9sb2coZ2QucXVhbnRpdHkubmFtZSwgZ2QucXVhbnRpdHkoKSkKCiAgICAgICAgc2VsZi50aWNrX2NvdW50ICs9IDEKCiAgICBkZWYgX2NvbW1pdChzZWxmKSAtPiBOb25lOgogICAgICAgIHNlbGYuY29tbWl0X2NvdW50ZG93biAtPSAxCiAgICAgICAgaWYgc2VsZi5jb21taXRfY291bnRkb3duIDw9IDA6CiAgICAgICAgICAgIHNlbGYuY29tbWl0X2NvdW50ZG93biA9IHNlbGYuY29tbWl0X2ludGVydmFsCiAgICAgICAgICAgIHNlbGYuZGJfY29ubi5jb21taXQoKQoKICAgIGRlZiBzYXZlX2xvZ2dpbmcoc2VsZikgLT4gTm9uZToKICAgICAgICBmb3IgbG9nIGluIHNlbGYubG9nZ2luZ19kYXRhOgogICAgICAgICAgICBzZWxmLmRiX2Nvbm4uZXhlY3V0ZSgKICAgICAgICAgICAgICAgICJpbnNlcnQgaW50byBsb2dnaW5nIHZhbHVlcyAoPyw/LD8sPyw/LD8sPykiLAogICAgICAgICAgICAgICAgKHNlbGYucmFuaywgbG9nLnRpY2tfY291bnQsIGxvZy50aW1lLAogICAgICAgICAgICAgICAgbG9nLmNhdGVnb3J5LCBsb2cubWVzc2FnZSwgbG9nLmZpbGVuYW1lLAogICAgICAgICAgICAgICAgbG9nLmxpbmVubykpCgogICAgICAgIHNlbGYubG9nZ2luZ19kYXRhID0gW10KCiAgICBkZWYgc2F2ZV93YXJuaW5ncyhzZWxmKSAtPiBOb25lOgogICAgICAgIGZvciB3IGluIHNlbGYud2FybmluZ19kYXRhOgogICAgICAgICAgICBzZWxmLmRiX2Nvbm4uZXhlY3V0ZSgKICAgICAgICAgICAgICAgICJpbnNlcnQgaW50byB3YXJuaW5ncyB2YWx1ZXMgKD8sPyw/LD8sPyw/LD8pIiwKICAgICAgICAgICAgICAgIChzZWxmLnJhbmssIHcudGlja19jb3VudCwgdy50aW1lLCB3Lm1lc3NhZ2UsCiAgICAgICAgICAgICAgICAgICAgdy5jYXRlZ29yeSwgdy5maWxlbmFtZSwgdy5saW5lbm8pKQoKICAgICAgICBzZWxmLndhcm5pbmdfZGF0YSA9IFtdCgogICAgZGVmIHNhdmUoc2VsZikgLT4gTm9uZToKICAgICAgICBpZiBzZWxmLm1vZGVbMF0gPT0gInciOgogICAgICAgICAgICBzZWxmLnNhdmVfbG9nZ2luZygpCiAgICAgICAgICAgIHNlbGYuc2F2ZV93YXJuaW5ncygpCgogICAgICAgIGZyb20gc3FsaXRlMyBpbXBvcnQgT3BlcmF0aW9uYWxFcnJvcgogICAgICAgIHRyeToKICAgICAgICAgICAgc2VsZi5kYl9jb25uLmNvbW1pdCgpCiAgICAgICAgZXhjZXB0IE9wZXJhdGlvbmFsRXJyb3IgYXMgZToKICAgICAgICAgICAgZnJvbSB3YXJuaW5ncyBpbXBvcnQgd2FybgogICAgICAgICAgICB3YXJuKCJlbmNvdW50ZXJlZCBzcWxpdGUgZXJyb3IgZHVyaW5nIGNvbW1pdDogJXMiICUgZSkKCiAgICAgICAgc2VsZi5sYXN0X3NhdmVfdGltZSA9IHRpbWVfbW9ub3RvbmljKCkKCiAgICBkZWYgYWRkX3F1YW50aXR5KHNlbGYsIHF1YW50aXR5OiBMb2dRdWFudGl0eSwgaW50ZXJ2YWw6IGludCA9IDEpIC0+IE5vbmU6CiAgICAgICAgIiIiQWRkIGEgOmNsYXNzOmBMb2dRdWFudGl0eWAgdG8gdGhpcyBtYW5hZ2VyLgoKICAgICAgICA6YXJnIHF1YW50aXR5OiBhZGQgdGhlIHNwZWNpZmllZCA6Y2xhc3M6YExvZ1F1YW50aXR5YC4KICAgICAgICA6YXJnIGludGVydmFsOiBpbnRlcnZhbCAoaW4gdGltZSBzdGVwcykgd2hlbiB0byBnYXRoZXIgdGhpcyBxdWFudGl0eS4KICAgICAgICAiIiIKCiAgICAgICAgZGVmIGFkZF9pbnRlcm5hbChuYW1lOiBzdHIsIHVuaXQ6IE9wdGlvbmFsW3N0cl0sIGRlc2NyaXB0aW9uOiBPcHRpb25hbFtzdHJdLAogICAgICAgICAgICAgICAgICAgICAgICAgZGVmX2FnZzogT3B0aW9uYWxbQ2FsbGFibGVbLi4uLCBBbnldXSkgLT4gTm9uZToKICAgICAgICAgICAgbG9nZ2VyLmRlYnVnKCJhZGQgbG9nIHF1YW50aXR5ICclcyciICUgbmFtZSkKCiAgICAgICAgICAgIGlmIG5hbWUgaW4gc2VsZi5xdWFudGl0eV9kYXRhOgogICAgICAgICAgICAgICAgcmFpc2UgUnVudGltZUVycm9yKCJjYW5ub3QgYWRkIHRoZSBzYW1lIHF1YW50aXR5ICclcycgdHdpY2UiICUgbmFtZSkKICAgICAgICAgICAgc2VsZi5xdWFudGl0eV9kYXRhW25hbWVdID0gX1F1YW50aXR5RGF0YSh1bml0LCBkZXNjcmlwdGlvbiwgZGVmX2FnZykKCiAgICAgICAgICAgIGZyb20gcGlja2xlIGltcG9ydCBkdW1wcwogICAgICAgICAgICBzZWxmLmRiX2Nvbm4uZXhlY3V0ZSgiIiJpbnNlcnQgaW50byBxdWFudGl0aWVzIHZhbHVlcyAoPyw/LD8sPykiIiIsICgKICAgICAgICAgICAgICAgIG5hbWUsIHVuaXQsIGRlc2NyaXB0aW9uLAogICAgICAgICAgICAgICAgYnl0ZXMoZHVtcHMoZGVmX2FnZykpKSkKICAgICAgICAgICAgc2VsZi5kYl9jb25uLmV4ZWN1dGUoIiIiY3JlYXRlIHRhYmxlICVzCiAgICAgICAgICAgICAgKHN0ZXAgaW50ZWdlciwgcmFuayBpbnRlZ2VyLCB2YWx1ZSByZWFsKSIiIiAlIG5hbWUpCgogICAgICAgICAgICBzZWxmLl9jb21taXQoKQoKICAgICAgICBnZCA9IF9HYXRoZXJEZXNjcmlwdG9yKHF1YW50aXR5LCBpbnRlcnZhbCkKICAgICAgICBpZiBpc2luc3RhbmNlKHF1YW50aXR5LCBQb3N0TG9nUXVhbnRpdHkpOgogICAgICAgICAgICBnZF9saXN0ID0gc2VsZi5hZnRlcl9nYXRoZXJfZGVzY3JpcHRvcnMKICAgICAgICBlbHNlOgogICAgICAgICAgICBnZF9saXN0ID0gc2VsZi5iZWZvcmVfZ2F0aGVyX2Rlc2NyaXB0b3JzCgogICAgICAgIGdkX2xpc3QuYXBwZW5kKGdkKQogICAgICAgIGdkX2xpc3Quc29ydChrZXk9bGFtYmRhIGdkOiBnZC5xdWFudGl0eS5zb3J0X3dlaWdodCkKCiAgICAgICAgaWYgaXNpbnN0YW5jZShxdWFudGl0eSwgTXVsdGlMb2dRdWFudGl0eSk6CiAgICAgICAgICAgIGZvciBuYW1lLCB1bml0LCBkZXNjcmlwdGlvbiwgZGVmX2FnZyBpbiB6aXAoCiAgICAgICAgICAgICAgICAgICAgcXVhbnRpdHkubmFtZXMsCiAgICAgICAgICAgICAgICAgICAgcXVhbnRpdHkudW5pdHMsCiAgICAgICAgICAgICAgICAgICAgcXVhbnRpdHkuZGVzY3JpcHRpb25zLAogICAgICAgICAgICAgICAgICAgIHF1YW50aXR5LmRlZmF1bHRfYWdncmVnYXRvcnMpOgogICAgICAgICAgICAgICAgYWRkX2ludGVybmFsKG5hbWUsIHVuaXQsIGRlc2NyaXB0aW9uLCBkZWZfYWdnKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIGFkZF9pbnRlcm5hbChxdWFudGl0eS5uYW1lLAogICAgICAgICAgICAgICAgICAgIHF1YW50aXR5LnVuaXQsIHF1YW50aXR5LmRlc2NyaXB0aW9uLAogICAgICAgICAgICAgICAgICAgIHF1YW50aXR5LmRlZmF1bHRfYWdncmVnYXRvcikKCiAgICBkZWYgZ2V0X2V4cHJfZGF0YXNldChzZWxmLCBleHByZXNzaW9uOiBFeHByZXNzaW9uLAogICAgICAgICAgICAgICAgICAgICAgICAgZGVzY3JpcHRpb246IE9wdGlvbmFsW3N0cl0gPSBOb25lLAogICAgICAgICAgICAgICAgICAgICAgICAgdW5pdDogT3B0aW9uYWxbc3RyXSA9IE5vbmUpIFwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIC0+IFR1cGxlW1VuaW9uW3N0ciwgQW55XSwgVW5pb25bc3RyLCBBbnksIE5vbmVdLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTGlzdFtUdXBsZVtpbnQsIEFueV1dXToKICAgICAgICAiIiJQcmVwYXJlIGEgdGltZS1zZXJpZXMgZGF0YXNldCBmb3IgYSBnaXZlbiBleHByZXNzaW9uLgoKICAgICAgICA6YXJnIGV4cHJlc3Npb246IEEgOm1vZDpgcHltYm9saWNgIGV4cHJlc3Npb24gdGhhdCBtYXkgaW52b2x2ZQogICAgICAgICAgdGhlIHRpbWUtc2VyaWVzIHZhcmlhYmxlcyBhbmQgdGhlIGNvbnN0YW50cyBpbiB0aGlzIDpjbGFzczpgTG9nTWFuYWdlcmAuCiAgICAgICAgICBJZiB0aGVyZSBpcyBkYXRhIGZyb20gbXVsdGlwbGUgcmFua3MgZm9yIGEgcXVhbnRpdHkgb2NjdXJyaW5nIGluCiAgICAgICAgICB0aGlzIGV4cHJlc3Npb24sIGFuIGFnZ3JlZ2F0b3IgbWF5IGhhdmUgdG8gYmUgc3BlY2lmaWVkLgogICAgICAgIDpyZXR1cm5zOiBgYChkZXNjcmlwdGlvbiwgdW5pdCwgdGFibGUpYGAsIHdoZXJlICp0YWJsZSoKICAgICAgICAgIGlzIGEgbGlzdCBvZiB0dXBsZXMgYGAodGlja19uYnIsIHZhbHVlKWBgLgoKICAgICAgICBBZ2dyZWdhdG9ycyBhcmUgc3BlY2lmaWVkIGFzIGZvbGxvd3M6CiAgICAgICAgICAgIC0gYGBxdHkubWluYGAsIGBgcXR5Lm1heGBgLCBgYHF0eS5hdmdgYCwgYGBxdHkuc3VtYGAsIGBgcXR5Lm5vcm0yYGAsCiAgICAgICAgICAgICAgYGBxdHkubWVkaWFuYGAKICAgICAgICAgICAgLSBgYHF0eVtyYW5rX25icl1gYAogICAgICAgICAgICAtIGBgcXR5LmxvY2BgCiAgICAgICAgIiIiCgogICAgICAgIHBhcnNlZCA9IHNlbGYuX3BhcnNlX2V4cHIoZXhwcmVzc2lvbikKICAgICAgICBwYXJzZWQsIGRlcF9kYXRhID0gc2VsZi5fZ2V0X2V4cHJfZGVwX2RhdGEocGFyc2VkKQoKICAgICAgICAjIGFnZ3JlZ2F0ZSB0YWJsZSBkYXRhCiAgICAgICAgZm9yIGRkIGluIGRlcF9kYXRhOgogICAgICAgICAgICB0YWJsZSA9IHNlbGYuZ2V0X3RhYmxlKGRkLm5hbWUpCiAgICAgICAgICAgIHRhYmxlLnNvcnQoWyJzdGVwIl0pCiAgICAgICAgICAgIGRkLnRhYmxlID0gdGFibGUuYWdncmVnYXRlZChbInN0ZXAiXSwgICMgdHlwZTogaWdub3JlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAidmFsdWUiLCBkZC5hZ2dfZnVuYykuZGF0YQoKICAgICAgICAjIGV2YWx1YXRlIHVuaXQgYW5kIGRlc2NyaXB0aW9uLCBpZiBuZWNlc3NhcnkKICAgICAgICBpZiB1bml0IGlzIE5vbmU6CiAgICAgICAgICAgIGZyb20gcHltYm9saWMgaW1wb3J0IHBhcnNlLCBzdWJzdGl0dXRlCgogICAgICAgICAgICB1bml0X2RpY3QgPSB7ZGQudmFybmFtZTogZGQucWRhdC51bml0IGZvciBkZCBpbiBkZXBfZGF0YX0KICAgICAgICAgICAgZnJvbSBweXRvb2xzIGltcG9ydCBhbGwKICAgICAgICAgICAgaWYgYWxsKHYgaXMgbm90IE5vbmUgZm9yIHYgaW4gdW5pdF9kaWN0LnZhbHVlcygpKToKICAgICAgICAgICAgICAgIHVuaXRfZGljdCA9IHtrOiBwYXJzZSh2KSBmb3IgaywgdiBpbiB1bml0X2RpY3QuaXRlbXMoKX0KICAgICAgICAgICAgICAgIHVuaXQgPSBzdWJzdGl0dXRlKHBhcnNlZCwgdW5pdF9kaWN0KQogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgdW5pdCA9IE5vbmUKCiAgICAgICAgaWYgZGVzY3JpcHRpb24gaXMgTm9uZToKICAgICAgICAgICAgZGVzY3JpcHRpb24gPSBleHByZXNzaW9uCgogICAgICAgICMgY29tcGlsZSBhbmQgZXZhbHVhdGUKICAgICAgICBmcm9tIHB5bWJvbGljIGltcG9ydCBjb21waWxlCiAgICAgICAgY29tcGlsZWQgPSBjb21waWxlKHBhcnNlZCwgW2RkLnZhcm5hbWUgZm9yIGRkIGluIGRlcF9kYXRhXSkKCiAgICAgICAgZGF0YSA9IFtdCgogICAgICAgIGZvciBrZXksIHZhbHVlcyBpbiBfam9pbl9ieV9maXJzdF9vZl90dXBsZSgKICAgICAgICAgICAgICAgIFtkZC50YWJsZSBmb3IgZGQgaW4gZGVwX2RhdGEgaWYgZGQudGFibGVdKToKICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgZGF0YS5hcHBlbmQoKGtleSwgY29tcGlsZWQoKnZhbHVlcykpKQogICAgICAgICAgICBleGNlcHQgWmVyb0RpdmlzaW9uRXJyb3I6CiAgICAgICAgICAgICAgICBwYXNzCgogICAgICAgIHJldHVybiAoZGVzY3JpcHRpb24sIHVuaXQsIGRhdGEpCgogICAgZGVmIGdldF9qb2ludF9kYXRhc2V0KHNlbGYsIGV4cHJlc3Npb25zOiBTZXF1ZW5jZVtFeHByZXNzaW9uXSkgLT4gTGlzdFtBbnldOgogICAgICAgICIiIlJldHVybiBhIGpvaW50IGRhdGEgc2V0IGZvciBhIGxpc3Qgb2YgZXhwcmVzc2lvbnMuCgogICAgICAgIDphcmcgZXhwcmVzc2lvbnM6IGEgbGlzdCBvZiBlaXRoZXIgc3RyaW5ncyByZXByZXNlbnRpbmcKICAgICAgICAgIGV4cHJlc3Npb25zIGRpcmVjdGx5LCBvciB0cmlwbGVzIChkZXNjciwgdW5pdCwgZXhwcikuCiAgICAgICAgICBJbiB0aGUgZm9ybWVyIGNhc2UsIHRoZSBkZXNjcmlwdGlvbiBhbmQgdGhlIHVuaXQgYXJlCiAgICAgICAgICBmb3VuZCBhdXRvbWF0aWNhbGx5LCBpZiBwb3NzaWJsZS4gSW4gdGhlIGxhdHRlciBjYXNlLAogICAgICAgICAgdGhleSBhcmUgdXNlZCBhcyBzcGVjaWZpZWQuCiAgICAgICAgOnJldHVybnM6IEEgdHJpcGxlIGBgKGRlc2NyaXB0aW9ucywgdW5pdHMsIHRhYmxlKWBgLCB3aGVyZQogICAgICAgICAgICAqdGFibGUqIGlzIGEgYSBsaXN0IG9mIGBgWyh0c3RlcCwgKHZhbF9leHByMSwgdmFsX2V4cHIyLC4uLikuLi5dYGAuCiAgICAgICAgIiIiCgogICAgICAgICMgZHVicyBpcyBhIGxpc3Qgb2YgKGRlc2MsIHVuaXQsIHRhYmxlKSB0cmlwbGVzIGFzCiAgICAgICAgIyByZXR1cm5lZCBieSBnZXRfZXhwcl9kYXRhc2V0CiAgICAgICAgZHVicyA9IFtdCiAgICAgICAgZm9yIGV4cHIgaW4gZXhwcmVzc2lvbnM6CiAgICAgICAgICAgIGlmIGlzaW5zdGFuY2UoZXhwciwgc3RyKToKICAgICAgICAgICAgICAgIGR1YiA9IHNlbGYuZ2V0X2V4cHJfZGF0YXNldChleHByKQogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgZXhwcl9kZXNjciwgZXhwcl91bml0LCBleHByX3N0ciA9IGV4cHIKICAgICAgICAgICAgICAgIGR1YiA9IHNlbGYuZ2V0X2V4cHJfZGF0YXNldCgKICAgICAgICAgICAgICAgICAgICAgICAgZXhwcl9zdHIsCiAgICAgICAgICAgICAgICAgICAgICAgIGRlc2NyaXB0aW9uPWV4cHJfZGVzY3IsCiAgICAgICAgICAgICAgICAgICAgICAgIHVuaXQ9ZXhwcl91bml0KQoKICAgICAgICAgICAgZHVicy5hcHBlbmQoZHViKQoKICAgICAgICB6aXBwZWRfZHVicyA9IGxpc3QoemlwKCpkdWJzKSkKICAgICAgICB6aXBwZWRfZHVic1syXSA9IGxpc3QoCiAgICAgICAgICAgICAgICBfam9pbl9ieV9maXJzdF9vZl90dXBsZSh6aXBwZWRfZHVic1syXSkpCgogICAgICAgIHJldHVybiB6aXBwZWRfZHVicwoKICAgIGRlZiBnZXRfcGxvdF9kYXRhKHNlbGYsIGV4cHJfeDogRXhwcmVzc2lvbiwgZXhwcl95OiBFeHByZXNzaW9uLAogICAgICAgICAgICAgICAgICAgICAgbWluX3N0ZXA6IE9wdGlvbmFsW2ludF0gPSBOb25lLAogICAgICAgICAgICAgICAgICAgICAgbWF4X3N0ZXA6IE9wdGlvbmFsW2ludF0gPSBOb25lKSBcCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAtPiBUdXBsZVtUdXBsZVtBbnksIHN0ciwgc3RyXSwgVHVwbGVbQW55LCBzdHIsIHN0cl1dOgogICAgICAgICIiIkdlbmVyYXRlIHBsb3QtcmVhZHkgZGF0YS4KCiAgICAgICAgOnJldHVybnM6IGBgKGRhdGFfeCwgZGVzY3JfeCwgdW5pdF94KSwgKGRhdGFfeSwgZGVzY3JfeSwgdW5pdF95KWBgCiAgICAgICAgIiIiCiAgICAgICAgKGRlc2NyX3gsIGRlc2NyX3kpLCAodW5pdF94LCB1bml0X3kpLCBkYXRhID0gXAogICAgICAgICAgICAgICAgc2VsZi5nZXRfam9pbnRfZGF0YXNldChbZXhwcl94LCBleHByX3ldKQogICAgICAgIGlmIG1pbl9zdGVwIGlzIG5vdCBOb25lOgogICAgICAgICAgICBkYXRhID0gWyhzdGVwLCB0dXApIGZvciBzdGVwLCB0dXAgaW4gZGF0YSBpZiBtaW5fc3RlcCA8PSBzdGVwXQogICAgICAgIGlmIG1heF9zdGVwIGlzIG5vdCBOb25lOgogICAgICAgICAgICBkYXRhID0gWyhzdGVwLCB0dXApIGZvciBzdGVwLCB0dXAgaW4gZGF0YSBpZiBzdGVwIDw9IG1heF9zdGVwXQoKICAgICAgICBzdGVwbGVzc19kYXRhID0gW3R1cCBmb3Igc3RlcCwgdHVwIGluIGRhdGFdCgogICAgICAgIGlmIHN0ZXBsZXNzX2RhdGE6CiAgICAgICAgICAgIGRhdGFfeCwgZGF0YV95ID0gbGlzdCh6aXAoKnN0ZXBsZXNzX2RhdGEpKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIGRhdGFfeCA9IFtdCiAgICAgICAgICAgIGRhdGFfeSA9IFtdCgogICAgICAgIHJldHVybiAoZGF0YV94LCBkZXNjcl94LCB1bml0X3gpLCBcCiAgICAgICAgICAgICAgIChkYXRhX3ksIGRlc2NyX3ksIHVuaXRfeSkKCiAgICBkZWYgd3JpdGVfZGF0YWZpbGUoc2VsZiwgZmlsZW5hbWU6IHN0ciwgZXhwcl94OiBFeHByZXNzaW9uLAogICAgICAgICAgICAgICAgICAgICAgIGV4cHJfeTogRXhwcmVzc2lvbikgLT4gTm9uZToKICAgICAgICAoZGF0YV94LCBsYWJlbF94LCBfKSwgKGRhdGFfeSwgbGFiZWxfeSwgXykgPSBzZWxmLmdldF9wbG90X2RhdGEoCiAgICAgICAgICAgICAgICBleHByX3gsIGV4cHJfeSkKCiAgICAgICAgb3V0ZiA9IG9wZW4oZmlsZW5hbWUsICJ3IikKICAgICAgICBvdXRmLndyaXRlKGYiIyB7bGFiZWxfeH0gdnMuIHtsYWJlbF95fVxuIikKICAgICAgICBmb3IgZHgsIGR5IGluIHppcChkYXRhX3gsIGRhdGFfeSk6CiAgICAgICAgICAgIG91dGYud3JpdGUoInt9XHR7fVxuIi5mb3JtYXQocmVwcihkeCksIHJlcHIoZHkpKSkKICAgICAgICBvdXRmLmNsb3NlKCkKCiAgICBkZWYgcGxvdF9tYXRwbG90bGliKHNlbGYsIGV4cHJfeDogRXhwcmVzc2lvbiwgZXhwcl95OiBFeHByZXNzaW9uKSAtPiBOb25lOgogICAgICAgIGZyb20gbWF0cGxvdGxpYi5weXBsb3QgaW1wb3J0IHBsb3QsIHhsYWJlbCwgeWxhYmVsCgogICAgICAgIChkYXRhX3gsIGRlc2NyX3gsIHVuaXRfeCksIChkYXRhX3ksIGRlc2NyX3ksIHVuaXRfeSkgPSBcCiAgICAgICAgICAgICAgICBzZWxmLmdldF9wbG90X2RhdGEoZXhwcl94LCBleHByX3kpCgogICAgICAgIHhsYWJlbChmIntkZXNjcl94fSBbe3VuaXRfeH1dIikKICAgICAgICB5bGFiZWwoZiJ7ZGVzY3JfeX0gW3t1bml0X3l9XSIpCiAgICAgICAgcGxvdChkYXRhX3gsIGRhdGFfeSkKCiAgICAjIHt7eyBwcml2YXRlIGZ1bmN0aW9uYWxpdHkKCiAgICBkZWYgX3BhcnNlX2V4cHIoc2VsZiwgZXhwcjogRXhwcmVzc2lvbikgLT4gQW55OgogICAgICAgIGZyb20gcHltYm9saWMgaW1wb3J0IHBhcnNlLCBzdWJzdGl0dXRlCiAgICAgICAgcGFyc2VkID0gcGFyc2UoZXhwcikKCiAgICAgICAgIyBzdWJzdGl0dXRlIGluIGdsb2JhbCBjb25zdGFudHMKICAgICAgICBwYXJzZWQgPSBzdWJzdGl0dXRlKHBhcnNlZCwgc2VsZi5jb25zdGFudHMpCgogICAgICAgIHJldHVybiBwYXJzZWQKCiAgICBkZWYgX2dldF9leHByX2RlcF9kYXRhKHNlbGYsIHBhcnNlZDogRXhwcmVzc2lvbikgXAogICAgICAgICAgICAtPiBUdXBsZVtFeHByZXNzaW9uLCBMaXN0W19EZXBlbmRlbmN5RGF0YV1dOgogICAgICAgIGNsYXNzIE50aDoKICAgICAgICAgICAgZGVmIF9faW5pdF9fKHNlbGYsIG46IGludCkgLT4gTm9uZToKICAgICAgICAgICAgICAgIHNlbGYubiA9IG4KCiAgICAgICAgICAgIGRlZiBfX2NhbGxfXyhzZWxmLCBsc3Q6IExpc3RbQW55XSkgLT4gQW55OgogICAgICAgICAgICAgICAgcmV0dXJuIGxzdFtzZWxmLm5dCgogICAgICAgIGRlcHMgPSBEZXBlbmRlbmN5TWFwcGVyKGluY2x1ZGVfY2FsbHM9RmFsc2UpKHBhcnNlZCkKCiAgICAgICAgIyBnYXRoZXIgaW5mb3JtYXRpb24gb24gYWdncmVnYXRpb24gZXhwcmVzc2lvbnMKICAgICAgICBkZXBfZGF0YSA9IFtdCiAgICAgICAgZnJvbSBweW1ib2xpYy5wcmltaXRpdmVzIGltcG9ydCBMb29rdXAsIFN1YnNjcmlwdCwgVmFyaWFibGUKICAgICAgICBmb3IgZGVwX2lkeCwgZGVwIGluIGVudW1lcmF0ZShkZXBzKToKICAgICAgICAgICAgbm9ubG9jYWxfYWdnID0gVHJ1ZQoKICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShkZXAsIFZhcmlhYmxlKToKICAgICAgICAgICAgICAgIG5hbWUgPSBkZXAubmFtZQoKICAgICAgICAgICAgICAgIGlmIG5hbWUgPT0gIm1hdGgiOgogICAgICAgICAgICAgICAgICAgIGNvbnRpbnVlCgogICAgICAgICAgICAgICAgYWdnX2Z1bmMgPSBzZWxmLnF1YW50aXR5X2RhdGFbbmFtZV0uZGVmYXVsdF9hZ2dyZWdhdG9yCiAgICAgICAgICAgICAgICBpZiBhZ2dfZnVuYyBpcyBOb25lOgogICAgICAgICAgICAgICAgICAgIGlmIHNlbGYuaXNfcGFyYWxsZWw6CiAgICAgICAgICAgICAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIm11c3Qgc3BlY2lmeSBleHBsaWNpdCBhZ2dyZWdhdG9yIGZvciAnJXMnIiAlIG5hbWUpCgogICAgICAgICAgICAgICAgICAgIGFnZ19mdW5jID0gbGFtYmRhIGxzdDogbHN0WzBdCiAgICAgICAgICAgIGVsaWYgaXNpbnN0YW5jZShkZXAsIExvb2t1cCk6CiAgICAgICAgICAgICAgICBhc3NlcnQgaXNpbnN0YW5jZShkZXAuYWdncmVnYXRlLCBWYXJpYWJsZSkKICAgICAgICAgICAgICAgIG5hbWUgPSBkZXAuYWdncmVnYXRlLm5hbWUKICAgICAgICAgICAgICAgIGFnZ19uYW1lID0gZGVwLm5hbWUKCiAgICAgICAgICAgICAgICBpZiBhZ2dfbmFtZSA9PSAibG9jIjoKICAgICAgICAgICAgICAgICAgICBhZ2dfZnVuYyA9IE50aChzZWxmLnJhbmspCiAgICAgICAgICAgICAgICAgICAgbm9ubG9jYWxfYWdnID0gRmFsc2UKICAgICAgICAgICAgICAgIGVsaWYgYWdnX25hbWUgPT0gIm1pbiI6CiAgICAgICAgICAgICAgICAgICAgYWdnX2Z1bmMgPSBtaW4KICAgICAgICAgICAgICAgIGVsaWYgYWdnX25hbWUgPT0gIm1heCI6CiAgICAgICAgICAgICAgICAgICAgYWdnX2Z1bmMgPSBtYXgKICAgICAgICAgICAgICAgIGVsaWYgYWdnX25hbWUgPT0gImF2ZyI6CiAgICAgICAgICAgICAgICAgICAgZnJvbSBzdGF0aXN0aWNzIGltcG9ydCBmbWVhbgogICAgICAgICAgICAgICAgICAgIGFnZ19mdW5jID0gZm1lYW4KICAgICAgICAgICAgICAgIGVsaWYgYWdnX25hbWUgPT0gIm1lZGlhbiI6CiAgICAgICAgICAgICAgICAgICAgZnJvbSBzdGF0aXN0aWNzIGltcG9ydCBtZWRpYW4KICAgICAgICAgICAgICAgICAgICBhZ2dfZnVuYyA9IG1lZGlhbgogICAgICAgICAgICAgICAgZWxpZiBhZ2dfbmFtZSA9PSAic3VtIjoKICAgICAgICAgICAgICAgICAgICBhZ2dfZnVuYyA9IHN1bQogICAgICAgICAgICAgICAgZWxpZiBhZ2dfbmFtZSA9PSAibm9ybTIiOgogICAgICAgICAgICAgICAgICAgIGZyb20gbWF0aCBpbXBvcnQgc3FydAogICAgICAgICAgICAgICAgICAgIGFnZ19mdW5jID0gbGFtYmRhIGl0ZXJhYmxlOiBzcXJ0KAogICAgICAgICAgICAgICAgICAgICAgICAgICAgc3VtKGVudHJ5KioyIGZvciBlbnRyeSBpbiBpdGVyYWJsZSkpCiAgICAgICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoImludmFsaWQgcmFuayBhZ2dyZWdhdG9yICclcyciICUgYWdnX25hbWUpCiAgICAgICAgICAgIGVsaWYgaXNpbnN0YW5jZShkZXAsIFN1YnNjcmlwdCk6CiAgICAgICAgICAgICAgICBhc3NlcnQgaXNpbnN0YW5jZShkZXAuYWdncmVnYXRlLCBWYXJpYWJsZSkKICAgICAgICAgICAgICAgIG5hbWUgPSBkZXAuYWdncmVnYXRlLm5hbWUKCiAgICAgICAgICAgICAgICBmcm9tIHB5bWJvbGljIGltcG9ydCBldmFsdWF0ZQogICAgICAgICAgICAgICAgYWdnX2Z1bmMgPSBOdGgoZXZhbHVhdGUoZGVwLmluZGV4KSkKCiAgICAgICAgICAgIHFkYXQgPSBzZWxmLnF1YW50aXR5X2RhdGFbbmFtZV0KCiAgICAgICAgICAgIGFzc2VydCBhZ2dfZnVuYwoKICAgICAgICAgICAgdGhpc19kZXBfZGF0YSA9IF9EZXBlbmRlbmN5RGF0YShuYW1lPW5hbWUsIHFkYXQ9cWRhdCwgYWdnX2Z1bmM9YWdnX2Z1bmMsCiAgICAgICAgICAgICAgICAgICAgdmFybmFtZT0ibG9ndmFyJWQiICUgZGVwX2lkeCwgZXhwcj1kZXAsCiAgICAgICAgICAgICAgICAgICAgbm9ubG9jYWxfYWdnPW5vbmxvY2FsX2FnZykKICAgICAgICAgICAgZGVwX2RhdGEuYXBwZW5kKHRoaXNfZGVwX2RhdGEpCgogICAgICAgICMgc3Vic3RpdHV0ZSBpbiB0aGUgImxvZ3ZhciIgdmFyaWFibGUgbmFtZXMKICAgICAgICBmcm9tIHB5bWJvbGljIGltcG9ydCBzdWJzdGl0dXRlLCB2YXIKICAgICAgICBwYXJzZWQgPSBzdWJzdGl0dXRlKHBhcnNlZCwKICAgICAgICAgICAgICAgIHtkZC5leHByOiB2YXIoZGQudmFybmFtZSkgZm9yIGRkIGluIGRlcF9kYXRhfSkKCiAgICAgICAgcmV0dXJuIHBhcnNlZCwgZGVwX2RhdGEKCiAgICBkZWYgX2NhbGN1bGF0ZV9uZXh0X3dhdGNoX3RpY2soc2VsZikgLT4gTm9uZToKICAgICAgICB0aWNrc19wZXJfaW50ZXJ2YWwgPSAoc2VsZi50aWNrX2NvdW50CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC8gbWF4KDEsIHRpbWVfbW9ub3RvbmljKCktc2VsZi5zdGFydF90aW1lKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAqIHNlbGYud2F0Y2hfaW50ZXJ2YWwpCiAgICAgICAgc2VsZi5uZXh0X3dhdGNoX3RpY2sgPSBzZWxmLnRpY2tfY291bnQgKyBpbnQobWF4KDEsIHRpY2tzX3Blcl9pbnRlcnZhbCkpCgogICAgZGVmIF93YXRjaF90aWNrKHNlbGYpIC0+IE5vbmU6CiAgICAgICAgIiIiUHJpbnQgdGhlIHdhdGNoZXMgYWZ0ZXIgYSB0aWNrLiIiIgogICAgICAgIGlmIG5vdCBzZWxmLmhhdmVfbm9ubG9jYWxfd2F0Y2hlcyBhbmQgc2VsZi5yYW5rICE9IHNlbGYuaGVhZF9yYW5rOgogICAgICAgICAgICByZXR1cm4KCiAgICAgICAgZGF0YV9ibG9jayA9IHtxbmFtZTogc2VsZi5sYXN0X3ZhbHVlcy5nZXQocW5hbWUsIDApCiAgICAgICAgICAgICAgICBmb3IgcW5hbWUgaW4gc2VsZi5xdWFudGl0eV9kYXRhLmtleXMoKX0KCiAgICAgICAgaWYgc2VsZi5tcGlfY29tbSBpcyBub3QgTm9uZSBhbmQgc2VsZi5oYXZlX25vbmxvY2FsX3dhdGNoZXM6CiAgICAgICAgICAgIGdhdGhlcmVkX2RhdGEgPSBzZWxmLm1waV9jb21tLmdhdGhlcihkYXRhX2Jsb2NrLCBzZWxmLmhlYWRfcmFuaykKICAgICAgICBlbHNlOgogICAgICAgICAgICBnYXRoZXJlZF9kYXRhID0gW2RhdGFfYmxvY2tdCgogICAgICAgIGlmIHNlbGYucmFuayA9PSBzZWxmLmhlYWRfcmFuazoKICAgICAgICAgICAgYXNzZXJ0IGdhdGhlcmVkX2RhdGEKCiAgICAgICAgICAgIHZhbHVlczogRGljdFtzdHIsIExpc3RbT3B0aW9uYWxbZmxvYXRdXV0gPSB7fQogICAgICAgICAgICBmb3IgZGF0YV9ibG9jayBpbiBnYXRoZXJlZF9kYXRhOgogICAgICAgICAgICAgICAgZm9yIG5hbWUsIHZhbHVlIGluIGRhdGFfYmxvY2suaXRlbXMoKToKICAgICAgICAgICAgICAgICAgICB2YWx1ZXMuc2V0ZGVmYXVsdChuYW1lLCBbXSkuYXBwZW5kKHZhbHVlKQoKICAgICAgICAgICAgZGVmIGNvbXB1dGVfd2F0Y2hfc3RyKHdhdGNoOiBfV2F0Y2hJbmZvKSAtPiBzdHI6CiAgICAgICAgICAgICAgICBkaXNwbGF5ID0gd2F0Y2guZXhwcgogICAgICAgICAgICAgICAgdW5pdCA9IHdhdGNoLnVuaXQgaWYgd2F0Y2gudW5pdCBub3QgaW4gWyIxIiwgTm9uZV0gZWxzZSAiIgogICAgICAgICAgICAgICAgdmFsdWUgPSB3YXRjaC5jb21waWxlZCgKICAgICAgICAgICAgICAgICAgICAgICAgKltkZC5hZ2dfZnVuYyh2YWx1ZXNbZGQubmFtZV0pCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBmb3IgZGQgaW4gd2F0Y2guZGVwX2RhdGFdKQogICAgICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgICAgIHJldHVybiBmInt3YXRjaC5mb3JtYXR9Ii5mb3JtYXQoZGlzcGxheT1kaXNwbGF5LCB2YWx1ZT12YWx1ZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHVuaXQ9dW5pdCkKICAgICAgICAgICAgICAgIGV4Y2VwdCBaZXJvRGl2aXNpb25FcnJvcjoKICAgICAgICAgICAgICAgICAgICByZXR1cm4gZiJ7ZGlzcGxheX06ZGl2MCIKICAgICAgICAgICAgaWYgc2VsZi53YXRjaGVzOgogICAgICAgICAgICAgICAgcHJpbnQoIiIuam9pbigKICAgICAgICAgICAgICAgICAgICAgICAgY29tcHV0ZV93YXRjaF9zdHIod2F0Y2gpIGZvciB3YXRjaCBpbiBzZWxmLndhdGNoZXMpLAogICAgICAgICAgICAgICAgICAgICAgZmx1c2g9VHJ1ZSkKCiAgICAgICAgc2VsZi5fY2FsY3VsYXRlX25leHRfd2F0Y2hfdGljaygpCgogICAgICAgIGlmIHNlbGYubXBpX2NvbW0gaXMgbm90IE5vbmUgYW5kIHNlbGYuaGF2ZV9ub25sb2NhbF93YXRjaGVzOgogICAgICAgICAgICBzZWxmLm5leHRfd2F0Y2hfdGljayA9IHNlbGYubXBpX2NvbW0uYmNhc3QoCiAgICAgICAgICAgICAgICAgICAgc2VsZi5uZXh0X3dhdGNoX3RpY2ssIHNlbGYuaGVhZF9yYW5rKQoKICAgICMgfX19CgojIH19fQoKCiMge3t7IGFjdHVhbCBkYXRhIGxvZ2dlcnMKCmNsYXNzIF9TdWJUaW1lcjoKICAgIGRlZiBfX2luaXRfXyhzZWxmLCBpdGltZXI6ICJJbnRlcnZhbFRpbWVyIikgLT4gTm9uZToKICAgICAgICBzZWxmLml0aW1lciA9IGl0aW1lcgogICAgICAgIHNlbGYuZWxhcHNlZCA9IDAuMAoKICAgIGRlZiBzdGFydChzZWxmKSAtPiAiX1N1YlRpbWVyIjoKICAgICAgICBzZWxmLnN0YXJ0X3RpbWUgPSB0aW1lX21vbm90b25pYygpCiAgICAgICAgcmV0dXJuIHNlbGYKCiAgICBkZWYgc3RvcChzZWxmKSAtPiAiX1N1YlRpbWVyIjoKICAgICAgICBzZWxmLmVsYXBzZWQgKz0gdGltZV9tb25vdG9uaWMoKSAtIHNlbGYuc3RhcnRfdGltZQogICAgICAgIGRlbCBzZWxmLnN0YXJ0X3RpbWUKICAgICAgICByZXR1cm4gc2VsZgoKICAgIGRlZiBfX2VudGVyX18oc2VsZikgLT4gTm9uZToKICAgICAgICBzZWxmLnN0YXJ0KCkKCiAgICBkZWYgX19leGl0X18oc2VsZiwgZXhjX3R5cGU6IEFueSwgZXhjX3ZhbDogQW55LCBleGNfdGI6IEFueSkgLT4gTm9uZToKICAgICAgICBzZWxmLnN0b3AoKQogICAgICAgIHNlbGYuc3VibWl0KCkKCiAgICBkZWYgc3VibWl0KHNlbGYpIC0+IE5vbmU6CiAgICAgICAgc2VsZi5pdGltZXIuYWRkX3RpbWUoc2VsZi5lbGFwc2VkKQogICAgICAgIHNlbGYuZWxhcHNlZCA9IDAKCgpjbGFzcyBJbnRlcnZhbFRpbWVyKFBvc3RMb2dRdWFudGl0eSk6CiAgICAiIiJSZWNvcmRzIGVsYXBzZWQgdGltZXMgc3VwcGxpZWQgYnkgdGhlIHVzZXIgZWl0aGVyIHRocm91Z2gKICAgIHN1Yi10aW1lcnMsIG9yIGJ5IGV4cGxpY2l0bHkgY2FsbGluZyA6bWV0aDpgYWRkX3RpbWVgLgoKICAgIC4uIGF1dG9tZXRob2Q6OiBfX2luaXRfXwogICAgLi4gYXV0b21ldGhvZDo6IGdldF9zdWJfdGltZXIKICAgIC4uIGF1dG9tZXRob2Q6OiBzdGFydF9zdWJfdGltZXIKICAgIC4uIGF1dG9tZXRob2Q6OiBhZGRfdGltZQogICAgIiIiCgogICAgZGVmIF9faW5pdF9fKHNlbGYsIG5hbWU6IHN0ciwgZGVzY3JpcHRpb246IE9wdGlvbmFsW3N0cl0gPSBOb25lKSAtPiBOb25lOgogICAgICAgIExvZ1F1YW50aXR5Ll9faW5pdF9fKHNlbGYsIG5hbWUsICJzIiwgZGVzY3JpcHRpb24pCiAgICAgICAgc2VsZi5lbGFwc2VkOiBmbG9hdCA9IDAKCiAgICBkZWYgZ2V0X3N1Yl90aW1lcihzZWxmKSAtPiBfU3ViVGltZXI6CiAgICAgICAgcmV0dXJuIF9TdWJUaW1lcihzZWxmKQoKICAgIGRlZiBzdGFydF9zdWJfdGltZXIoc2VsZikgLT4gX1N1YlRpbWVyOgogICAgICAgIHN1Yl90aW1lciA9IF9TdWJUaW1lcihzZWxmKQogICAgICAgIHN1Yl90aW1lci5zdGFydCgpCiAgICAgICAgcmV0dXJuIHN1Yl90aW1lcgoKICAgIGRlZiBhZGRfdGltZShzZWxmLCB0OiBmbG9hdCkgLT4gTm9uZToKICAgICAgICBzZWxmLnN0YXJ0X3RpbWUgPSB0aW1lX21vbm90b25pYygpCiAgICAgICAgc2VsZi5lbGFwc2VkICs9IHQKCiAgICBkZWYgX19jYWxsX18oc2VsZikgLT4gZmxvYXQ6CiAgICAgICAgcmVzdWx0ID0gc2VsZi5lbGFwc2VkCiAgICAgICAgc2VsZi5lbGFwc2VkID0gMAogICAgICAgIHJldHVybiByZXN1bHQKCgpjbGFzcyBMb2dVcGRhdGVEdXJhdGlvbihQb3N0TG9nUXVhbnRpdHkpOgogICAgIiIiUmVjb3JkcyBob3cgbG9uZyB0aGUgbGFzdCBsb2cgdXBkYXRlIGluIDpjbGFzczpgTG9nTWFuYWdlcmAgdG9vay4KCiAgICAuLiBhdXRvbWV0aG9kOjogX19pbml0X18KICAgICIiIgoKICAgIGRlZiBfX2luaXRfXyhzZWxmLCBtZ3I6IExvZ01hbmFnZXIsIG5hbWU6IHN0ciA9ICJ0X2xvZyIpIC0+IE5vbmU6CiAgICAgICAgTG9nUXVhbnRpdHkuX19pbml0X18oc2VsZiwgbmFtZSwgInMiLCAiVGltZSBzcGVudCB1cGRhdGluZyB0aGUgbG9nIikKICAgICAgICBzZWxmLmxvZ19tYW5hZ2VyID0gbWdyCgogICAgZGVmIF9fY2FsbF9fKHNlbGYpIC0+IGZsb2F0OgogICAgICAgIHJldHVybiBzZWxmLmxvZ19tYW5hZ2VyLnRfbG9nCgoKY2xhc3MgRXZlbnRDb3VudGVyKFBvc3RMb2dRdWFudGl0eSk6CiAgICAiIiJDb3VudHMgZXZlbnRzIHNpZ25hbGVkIGJ5IDptZXRoOmBhZGRgLgoKICAgIC4uIGF1dG9tZXRob2Q6OiBfX2luaXRfXwogICAgLi4gYXV0b21ldGhvZDo6IGFkZAogICAgLi4gYXV0b21ldGhvZDo6IHRyYW5zZmVyCiAgICAiIiIKCiAgICBkZWYgX19pbml0X18oc2VsZiwgbmFtZTogc3RyID0gImludGVydmFsIiwKICAgICAgICAgICAgICAgICBkZXNjcmlwdGlvbjogT3B0aW9uYWxbc3RyXSA9IE5vbmUpIC0+IE5vbmU6CiAgICAgICAgUG9zdExvZ1F1YW50aXR5Ll9faW5pdF9fKHNlbGYsIG5hbWUsICIxIiwgZGVzY3JpcHRpb24pCiAgICAgICAgc2VsZi5ldmVudHMgPSAwCgogICAgZGVmIGFkZChzZWxmLCBuOiBpbnQgPSAxKSAtPiBOb25lOgogICAgICAgIHNlbGYuZXZlbnRzICs9IG4KCiAgICBkZWYgdHJhbnNmZXIoc2VsZiwgY291bnRlcjogQW55KSAtPiBOb25lOgogICAgICAgIHNlbGYuZXZlbnRzICs9IGNvdW50ZXIucG9wKCkKCiAgICBkZWYgcHJlcGFyZV9mb3JfdGljayhzZWxmKSAtPiBOb25lOgogICAgICAgIHNlbGYuZXZlbnRzID0gMAoKICAgIGRlZiBfX2NhbGxfXyhzZWxmKSAtPiBpbnQ6CiAgICAgICAgcmVzdWx0ID0gc2VsZi5ldmVudHMKICAgICAgICByZXR1cm4gcmVzdWx0CgoKZGVmIHRpbWVfYW5kX2NvdW50X2Z1bmN0aW9uKGY6IENhbGxhYmxlWy4uLiwgQW55XSwgdGltZXI6IEludGVydmFsVGltZXIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb3VudGVyOiBPcHRpb25hbFtFdmVudENvdW50ZXJdID0gTm9uZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluY3JlbWVudDogaW50ID0gMSkgLT4gQ2FsbGFibGVbLi4uLCBBbnldOgogICAgZGVmIGlubmVyX2YoKmFyZ3M6IEFueSwgKiprd2FyZ3M6IEFueSkgLT4gQW55OgogICAgICAgIGlmIGNvdW50ZXIgaXMgbm90IE5vbmU6CiAgICAgICAgICAgIGNvdW50ZXIuYWRkKGluY3JlbWVudCkKICAgICAgICBzdWJfdGltZXIgPSB0aW1lci5zdGFydF9zdWJfdGltZXIoKQogICAgICAgIHRyeToKICAgICAgICAgICAgcmV0dXJuIGYoKmFyZ3MsICoqa3dhcmdzKQogICAgICAgIGZpbmFsbHk6CiAgICAgICAgICAgIHN1Yl90aW1lci5zdG9wKCkuc3VibWl0KCkKCiAgICByZXR1cm4gaW5uZXJfZgoKCmNsYXNzIFRpbWVzdGVwQ291bnRlcihMb2dRdWFudGl0eSk6CiAgICAiIiJDb3VudHMgdGhlIG51bWJlciBvZiB0aW1lcyA6Y2xhc3M6YExvZ01hbmFnZXJgIHRpY2tzLiIiIgoKICAgIGRlZiBfX2luaXRfXyhzZWxmLCBuYW1lOiBzdHIgPSAic3RlcCIpIC0+IE5vbmU6CiAgICAgICAgTG9nUXVhbnRpdHkuX19pbml0X18oc2VsZiwgbmFtZSwgIjEiLCAiVGltZXN0ZXBzIikKICAgICAgICBzZWxmLnN0ZXBzID0gMAoKICAgIGRlZiBfX2NhbGxfXyhzZWxmKSAtPiBpbnQ6CiAgICAgICAgcmVzdWx0ID0gc2VsZi5zdGVwcwogICAgICAgIHNlbGYuc3RlcHMgKz0gMQogICAgICAgIHJldHVybiByZXN1bHQKCgpjbGFzcyBTdGVwVG9TdGVwRHVyYXRpb24oUG9zdExvZ1F1YW50aXR5KToKICAgICIiIlJlY29yZHMgdGhlIHdhbGwgdGltZSBiZXR3ZWVuIHRoZSBzdGFydHMgb2YgY29uc2VjdXRpdmUgdGltZSBzdGVwcywgaS5lLiwKICAgIHRoZSB3YWxsIHRpbWUgYmV0d2VlbiA6bWV0aDpgTG9nTWFuYWdlci50aWNrX2JlZm9yZWAgb2Ygc3RlcCB4IGFuZAogICAgOm1ldGg6YExvZ01hbmFnZXIudGlja19iZWZvcmVgIG9mIHN0ZXAgeCsxLiBUaGUgdmFsdWUgc3RvcmVkIGlzIHRoZSB2YWx1ZSBmb3IKICAgIHN0ZXAgeCsxLgoKICAgIC4uIG5vdGU6OgoKICAgICAgICBJbiBtb3N0IGNhc2VzLCB0aGlzIHF1YW50aXR5IHNob3VsZCBhcHByb3hpbWF0ZWx5IG1hdGNoIGBgdF9zdGVwYGAgKwogICAgICAgIGBgdF9sb2dgYC4gSWYgaXQgZG9lcyBub3QsIGl0IG1pZ2h0IGluZGljYXRlIHRoYXQgdGhlIGFwcGxpY2F0aW9uCiAgICAgICAgcGVyZm9ybXMgb3BlcmF0aW9ucyBvdXRzaWRlIDptZXRoOmBMb2dNYW5hZ2VyLnRpY2tfYmVmb3JlYCBhbmQKICAgICAgICA6bWV0aDpgTG9nTWFuYWdlci50aWNrX2FmdGVyYCwgb3IgdGhhdCBzb21lIG90aGVyIHRpbWUgaXMgbm90IGJlaW5nCiAgICAgICAgYWNjb3VudGVkIGZvci4KCiAgICAuLiBhdXRvbWV0aG9kOjogX19pbml0X18KICAgICIiIgoKICAgIGRlZiBfX2luaXRfXyhzZWxmLCBuYW1lOiBzdHIgPSAidF8yc3RlcCIpIC0+IE5vbmU6CiAgICAgICAgUG9zdExvZ1F1YW50aXR5Ll9faW5pdF9fKHNlbGYsIG5hbWUsICJzIiwgIlN0ZXAtdG8tc3RlcCBkdXJhdGlvbiIpCiAgICAgICAgc2VsZi5sYXN0X3N0YXJ0X3RpbWU6IE9wdGlvbmFsW2Zsb2F0XSA9IE5vbmUKICAgICAgICBzZWxmLmxhc3QyX3N0YXJ0X3RpbWU6IE9wdGlvbmFsW2Zsb2F0XSA9IE5vbmUKCiAgICBkZWYgcHJlcGFyZV9mb3JfdGljayhzZWxmKSAtPiBOb25lOgogICAgICAgIHNlbGYubGFzdDJfc3RhcnRfdGltZSA9IHNlbGYubGFzdF9zdGFydF90aW1lCiAgICAgICAgc2VsZi5sYXN0X3N0YXJ0X3RpbWUgPSB0aW1lX21vbm90b25pYygpCgogICAgZGVmIF9fY2FsbF9fKHNlbGYpIC0+IE9wdGlvbmFsW2Zsb2F0XToKICAgICAgICBpZiBzZWxmLmxhc3QyX3N0YXJ0X3RpbWUgaXMgTm9uZSBvciBzZWxmLmxhc3Rfc3RhcnRfdGltZSBpcyBOb25lOgogICAgICAgICAgICByZXR1cm4gTm9uZQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHJldHVybiBzZWxmLmxhc3Rfc3RhcnRfdGltZSAtIHNlbGYubGFzdDJfc3RhcnRfdGltZQoKCmNsYXNzIFRpbWVzdGVwRHVyYXRpb24oUG9zdExvZ1F1YW50aXR5KToKICAgICIiIlJlY29yZHMgdGhlIHdhbGwgdGltZSBiZXR3ZWVuIGludm9jYXRpb25zIG9mIDptZXRoOmBMb2dNYW5hZ2VyLnRpY2tfYmVmb3JlYAogICAgYW5kIDptZXRoOmBMb2dNYW5hZ2VyLnRpY2tfYWZ0ZXJgLCBpLmUuLCB0aGUgZHVyYXRpb24gb2YgdGhlIHRpbWUgc3RlcC4KCiAgICAuLiBhdXRvbWV0aG9kOjogX19pbml0X18KICAgICIiIgoKICAgICMgV2Ugd291bGQgbGlrZSB0byBydW4gbGFzdCwgc28gdGhhdCBpZiBsb2cgZ2F0aGVyaW5nIHRha2VzIGFueQogICAgIyBzaWduaWZpY2FudCB0aW1lLCB3ZSBjYXRjaCB0aGF0LCB0b28uIChDVURBIHN5bmMtb24tdGltZS10YWtpbmcsCiAgICAjIEknbSBsb29raW5nIGF0IHlvdS4pCiAgICBzb3J0X3dlaWdodCA9IDEwMDAKCiAgICBkZWYgX19pbml0X18oc2VsZiwgbmFtZTogc3RyID0gInRfc3RlcCIpIC0+IE5vbmU6CiAgICAgICAgUG9zdExvZ1F1YW50aXR5Ll9faW5pdF9fKHNlbGYsIG5hbWUsICJzIiwgIlRpbWUgc3RlcCBkdXJhdGlvbiIpCgogICAgZGVmIHByZXBhcmVfZm9yX3RpY2soc2VsZikgLT4gTm9uZToKICAgICAgICBzZWxmLmxhc3Rfc3RhcnQgPSB0aW1lX21vbm90b25pYygpCgogICAgZGVmIF9fY2FsbF9fKHNlbGYpIC0+IGZsb2F0OgogICAgICAgIG5vdyA9IHRpbWVfbW9ub3RvbmljKCkKICAgICAgICBhc3NlcnQgaGFzYXR0cihzZWxmLCAibGFzdF9zdGFydCIpLCAidGlja19hZnRlciBjYWxsZWQgd2l0aG91dCB0aWNrX2JlZm9yZSIKICAgICAgICByZXN1bHQgPSBub3cgLSBzZWxmLmxhc3Rfc3RhcnQKICAgICAgICBkZWwgc2VsZi5sYXN0X3N0YXJ0CiAgICAgICAgcmV0dXJuIHJlc3VsdAoKCmNsYXNzIEluaXRUaW1lKExvZ1F1YW50aXR5KToKICAgICIiIlN0b3JlcyB0aGUgdGltZSBpdCB0b29rIGZvciB0aGUgYXBwbGljYXRpb24gdG8gaW5pdGlhbGl6ZS4KCiAgICBNZWFzdXJlcyB0aGUgdGltZSBmcm9tIHByb2Nlc3Mgc3RhcnQgdG8gdGhlIHN0YXJ0IG9mIHRoZSBmaXJzdCB0aW1lIHN0ZXAuCgogICAgLi4gYXV0b21ldGhvZDo6IF9faW5pdF9fCiAgICAiIiIKCiAgICBkZWYgX19pbml0X18oc2VsZiwgbmFtZTogc3RyID0gInRfaW5pdCIpIC0+IE5vbmU6CiAgICAgICAgTG9nUXVhbnRpdHkuX19pbml0X18oc2VsZiwgbmFtZSwgInMiLCAiSW5pdCB0aW1lIikKCiAgICAgICAgdHJ5OgogICAgICAgICAgICBpbXBvcnQgcHN1dGlsCiAgICAgICAgZXhjZXB0IE1vZHVsZU5vdEZvdW5kRXJyb3I6CiAgICAgICAgICAgIGZyb20gd2FybmluZ3MgaW1wb3J0IHdhcm4KICAgICAgICAgICAgd2FybigiTWVhc3VyaW5nIHRoZSBpbml0IHRpbWUgcmVxdWlyZXMgdGhlICdwc3V0aWwnIG1vZHVsZS4iKQogICAgICAgICAgICBzZWxmLmRvbmUgPSBUcnVlCiAgICAgICAgZWxzZToKICAgICAgICAgICAgc2VsZi5jcmVhdGVfdGltZSA9IHBzdXRpbC5Qcm9jZXNzKCkuY3JlYXRlX3RpbWUoKQogICAgICAgICAgICBzZWxmLmRvbmUgPSBGYWxzZQoKICAgIGRlZiBfX2NhbGxfXyhzZWxmKSAtPiBPcHRpb25hbFtmbG9hdF06CiAgICAgICAgaWYgc2VsZi5kb25lOgogICAgICAgICAgICByZXR1cm4gTm9uZQoKICAgICAgICBzZWxmLmRvbmUgPSBUcnVlCiAgICAgICAgZnJvbSB0aW1lIGltcG9ydCB0aW1lCgogICAgICAgICMgQ2FuJ3QgdXNlIHRpbWVfbW9ub3RvbmljKCkgaGVyZSBzaW5jZSB0aGF0IGRvZXMgKm5vdCogcmV0dXJuCiAgICAgICAgIyB0aGUgdGltZSBzaW5jZSB0aGUgVU5JWCBlcG9jaCAobGlrZSB0aW1lKCkgYW5kCiAgICAgICAgIyBwc3V0aWwuUHJvY2Vzcy5jcmVhdGVfdGltZSgpIGRvKSwgYnV0IGZyb20gYW5vdGhlciAodW5kZWZpbmVkKQogICAgICAgICMgcmVmZXJlbmNlIHBvaW50LgogICAgICAgIHJldHVybiB0aW1lKCkgLSBzZWxmLmNyZWF0ZV90aW1lCgoKY2xhc3MgV2FsbFRpbWUoTG9nUXVhbnRpdHkpOgogICAgIiIiUmVjb3JkcyAobW9ub3RvbmljYWxseSBpbmNyZWFzaW5nKSB3YWxsIHRpbWUgc2luY2UgdGhlIHF1YW50aXR5IHdhcwogICAgaW5pdGlhbGl6ZWQuCgogICAgLi4gYXV0b21ldGhvZDo6IF9faW5pdF9fCiAgICAiIiIKICAgIGRlZiBfX2luaXRfXyhzZWxmLCBuYW1lOiBzdHIgPSAidF93YWxsIikgLT4gTm9uZToKICAgICAgICBMb2dRdWFudGl0eS5fX2luaXRfXyhzZWxmLCBuYW1lLCAicyIsICJXYWxsIHRpbWUiKQoKICAgICAgICBzZWxmLnN0YXJ0ID0gdGltZV9tb25vdG9uaWMoKQoKICAgIGRlZiBfX2NhbGxfXyhzZWxmKSAtPiBmbG9hdDoKICAgICAgICByZXR1cm4gdGltZV9tb25vdG9uaWMoKS1zZWxmLnN0YXJ0CgoKY2xhc3MgRVRBKExvZ1F1YW50aXR5KToKICAgICIiIlJlY29yZHMgYW4gZXN0aW1hdGUgb2YgaG93IGxvbmcgdGhlIGNvbXB1dGF0aW9uIHdpbGwgc3RpbGwgdGFrZS4KCiAgICAuLiBhdXRvbWV0aG9kOjogX19pbml0X18KICAgICIiIgogICAgZGVmIF9faW5pdF9fKHNlbGYsIHRvdGFsX3N0ZXBzOiBpbnQsIG5hbWU6IHN0ciA9ICJ0X2V0YSIpIC0+IE5vbmU6CiAgICAgICAgTG9nUXVhbnRpdHkuX19pbml0X18oc2VsZiwgbmFtZSwgInMiLCAiRXN0aW1hdGVkIHJlbWFpbmluZyBkdXJhdGlvbiIpCgogICAgICAgIHNlbGYuc3RlcHMgPSAwCiAgICAgICAgc2VsZi50b3RhbF9zdGVwcyA9IHRvdGFsX3N0ZXBzCiAgICAgICAgc2VsZi5zdGFydCA9IHRpbWVfbW9ub3RvbmljKCkKCiAgICBkZWYgX19jYWxsX18oc2VsZikgLT4gZmxvYXQ6CiAgICAgICAgZnJhY3Rpb25fZG9uZSA9IHNlbGYuc3RlcHMvc2VsZi50b3RhbF9zdGVwcwogICAgICAgIHNlbGYuc3RlcHMgKz0gMQogICAgICAgIHRpbWVfc3BlbnQgPSB0aW1lX21vbm90b25pYygpLXNlbGYuc3RhcnQKICAgICAgICBpZiBmcmFjdGlvbl9kb25lID4gMWUtOToKICAgICAgICAgICAgcmV0dXJuIHRpbWVfc3BlbnQvZnJhY3Rpb25fZG9uZS10aW1lX3NwZW50CiAgICAgICAgZWxzZToKICAgICAgICAgICAgcmV0dXJuIDAKCgpkZWYgYWRkX2dlbmVyYWxfcXVhbnRpdGllcyhtZ3I6IExvZ01hbmFnZXIpIC0+IE5vbmU6CiAgICAiIiJBZGQgZ2VuZXJhbGx5IGFwcGxpY2FibGUgOmNsYXNzOmBMb2dRdWFudGl0eWAgb2JqZWN0cyB0byAqbWdyKi4iIiIKCiAgICBtZ3IuYWRkX3F1YW50aXR5KFRpbWVzdGVwRHVyYXRpb24oKSkKICAgIG1nci5hZGRfcXVhbnRpdHkoU3RlcFRvU3RlcER1cmF0aW9uKCkpCiAgICBtZ3IuYWRkX3F1YW50aXR5KFdhbGxUaW1lKCkpCiAgICBtZ3IuYWRkX3F1YW50aXR5KExvZ1VwZGF0ZUR1cmF0aW9uKG1ncikpCiAgICBtZ3IuYWRkX3F1YW50aXR5KFRpbWVzdGVwQ291bnRlcigpKQogICAgbWdyLmFkZF9xdWFudGl0eShJbml0VGltZSgpKQogICAgbWdyLmFkZF9xdWFudGl0eShNZW1vcnlId20oKSkKCgpjbGFzcyBTaW11bGF0aW9uVGltZShUaW1lVHJhY2tlciwgTG9nUXVhbnRpdHkpOgogICAgIiIiUmVjb3JkIChtb25vdG9uaWNhbGx5IGluY3JlYXNpbmcpIHNpbXVsYXRpb24gdGltZS4iIiIKCiAgICBkZWYgX19pbml0X18oc2VsZiwgbmFtZTogc3RyID0gInRfc2ltIiwgc3RhcnQ6IGZsb2F0ID0gMCkgLT4gTm9uZToKICAgICAgICBMb2dRdWFudGl0eS5fX2luaXRfXyhzZWxmLCBuYW1lLCAicyIsICJTaW11bGF0aW9uIFRpbWUiKQogICAgICAgIFRpbWVUcmFja2VyLl9faW5pdF9fKHNlbGYsIHN0YXJ0KQoKICAgIGRlZiBfX2NhbGxfXyhzZWxmKSAtPiBmbG9hdDoKICAgICAgICByZXR1cm4gc2VsZi50CgoKY2xhc3MgVGltZXN0ZXAoU2ltdWxhdGlvbkxvZ1F1YW50aXR5KToKICAgICIiIlJlY29yZCB0aGUgbWFnbml0dWRlIG9mIHRoZSBzaW11bGF0ZWQgdGltZSBzdGVwLiIiIgoKICAgIGRlZiBfX2luaXRfXyhzZWxmLCBuYW1lOiBzdHIgPSAiZHQiLCB1bml0OiBzdHIgPSAicyIpIC0+IE5vbmU6CiAgICAgICAgU2ltdWxhdGlvbkxvZ1F1YW50aXR5Ll9faW5pdF9fKHNlbGYsIG5hbWUsIHVuaXQsICJTaW11bGF0aW9uIFRpbWVzdGVwIikKCiAgICBkZWYgX19jYWxsX18oc2VsZikgLT4gT3B0aW9uYWxbZmxvYXRdOgogICAgICAgIHJldHVybiBzZWxmLmR0CgoKZGVmIHNldF9kdChtZ3I6IExvZ01hbmFnZXIsIGR0OiBmbG9hdCkgLT4gTm9uZToKICAgICIiIlNldCB0aGUgc2ltdWxhdGlvbiB0aW1lc3RlcCBvbiA6Y2xhc3M6YExvZ01hbmFnZXJgIGBgbWdyYGAgdG8gYGBkdGBgLgoKICAgIDphcmcgbWdyOiB0aGUgOmNsYXNzOmBMb2dNYW5hZ2VyYCBpbnN0YW5jZS4KICAgIDphcmcgZHQ6IHRoZSBzaW11bGF0aW9uIHRpbWVzdGVwLgogICAgIiIiCgogICAgZm9yIGdkX2xzdCBpbiBbbWdyLmJlZm9yZV9nYXRoZXJfZGVzY3JpcHRvcnMsCiAgICAgICAgICAgIG1nci5hZnRlcl9nYXRoZXJfZGVzY3JpcHRvcnNdOgogICAgICAgIGZvciBnZCBpbiBnZF9sc3Q6CiAgICAgICAgICAgIGlmIGlzaW5zdGFuY2UoZ2QucXVhbnRpdHksIER0Q29uc3VtZXIpOgogICAgICAgICAgICAgICAgZ2QucXVhbnRpdHkuc2V0X2R0KGR0KQoKCmRlZiBhZGRfc2ltdWxhdGlvbl9xdWFudGl0aWVzKG1ncjogTG9nTWFuYWdlcikgLT4gTm9uZToKICAgICIiIkFkZCA6Y2xhc3M6YExvZ1F1YW50aXR5YCBvYmplY3RzIHJlbGF0aW5nIHRvIHNpbXVsYXRpb24gdGltZS4KCiAgICA6YXJnIG1ncjogdGhlIDpjbGFzczpgTG9nTWFuYWdlcmAgaW5zdGFuY2UuCiAgICAiIiIKICAgIG1nci5hZGRfcXVhbnRpdHkoU2ltdWxhdGlvblRpbWUoKSkKICAgIG1nci5hZGRfcXVhbnRpdHkoVGltZXN0ZXAoKSkKCgpkZWYgYWRkX3J1bl9pbmZvKG1ncjogTG9nTWFuYWdlcikgLT4gTm9uZToKICAgICIiIkFkZCBnZW5lcmljIHJ1biBtZXRhZGF0YSwgc3VjaCBhcyBjb21tYW5kIGxpbmUsIGhvc3QsIGFuZCB0aW1lLiIiIgoKICAgIHRyeToKICAgICAgICBpbXBvcnQgcHN1dGlsCiAgICBleGNlcHQgTW9kdWxlTm90Rm91bmRFcnJvcjoKICAgICAgICBpbXBvcnQgc3lzCiAgICAgICAgbWdyLnNldF9jb25zdGFudCgiY21kbGluZSIsICIgIi5qb2luKHN5cy5hcmd2KSkKICAgIGVsc2U6CiAgICAgICAgbWdyLnNldF9jb25zdGFudCgiY21kbGluZSIsICIgIi5qb2luKHBzdXRpbC5Qcm9jZXNzKCkuY21kbGluZSgpKSkKCiAgICBmcm9tIHNvY2tldCBpbXBvcnQgZ2V0aG9zdG5hbWUKICAgIG1nci5zZXRfY29uc3RhbnQoIm1hY2hpbmUiLCBnZXRob3N0bmFtZSgpKQogICAgZnJvbSB0aW1lIGltcG9ydCBsb2NhbHRpbWUsIHN0cmZ0aW1lLCB0aW1lCiAgICBtZ3Iuc2V0X2NvbnN0YW50KCJkYXRlIiwgc3RyZnRpbWUoIiVhLCAlZCAlYiAlWSAlSDolTTolUyAlWiIsIGxvY2FsdGltZSgpKSkKICAgIG1nci5zZXRfY29uc3RhbnQoInVuaXh0aW1lIiwgdGltZSgpKQoKCmNsYXNzIE1lbW9yeUh3bShQb3N0TG9nUXVhbnRpdHkpOgogICAgIiIiUmVjb3JkIChtb25vdG9uaWNhbGx5IGluY3JlYXNpbmcpIG1lbW9yeSBoaWdoIHdhdGVyIG1hcmsgKEhXTSkgaW4gTUJ5dGVzLiIiIgogICAgZGVmIF9faW5pdF9fKHNlbGYsIG5hbWU6IHN0ciA9ICJtZW1vcnlfdXNhZ2VfaHdtIikgLT4gTm9uZToKICAgICAgICBQb3N0TG9nUXVhbnRpdHkuX19pbml0X18oc2VsZiwgbmFtZSwgIk1CeXRlIiwgIk1lbW9yeSBIaWdoIFdhdGVyIE1hcmsiKQogICAgICAgIGltcG9ydCBvcwogICAgICAgIGlmIG9zLnVuYW1lKCkuc3lzbmFtZSA9PSAiTGludXgiOgogICAgICAgICAgICBzZWxmLmZhYyA9IDEwMjQKICAgICAgICBlbGlmIG9zLnVuYW1lKCkuc3lzbmFtZSA9PSAiRGFyd2luIjoKICAgICAgICAgICAgc2VsZi5mYWMgPSAxMDI0KjEwMjQKICAgICAgICBlbHNlOgogICAgICAgICAgICByYWlzZSBWYWx1ZUVycm9yKCJNZW1vcnlId20gaXMgb25seSBzdXBwb3J0ZWQgb24gTGludXgvTWFjLiIpCgogICAgZGVmIF9fY2FsbF9fKHNlbGYpIC0+IGZsb2F0OgogICAgICAgIGZyb20gcmVzb3VyY2UgaW1wb3J0IFJVU0FHRV9TRUxGLCBnZXRydXNhZ2UKICAgICAgICByZXMgPSBnZXRydXNhZ2UoUlVTQUdFX1NFTEYpCiAgICAgICAgcmV0dXJuIHJlcy5ydV9tYXhyc3MgLyBzZWxmLmZhYwoKCmNsYXNzIEdDU3RhdHMoTXVsdGlQb3N0TG9nUXVhbnRpdHkpOgogICAgIiIiUmVjb3JkIEdhcmJhZ2UgQ29sbGVjdGlvbiBzdGF0aXN0aWNzLgoKICAgIEluZm9ybWF0aW9uIHJlZ2FyZGluZyB0aGUgbWVhbmluZyBvZiB0aGVzZSB2YWx1ZXMgY2FuIGJlIGZvdW5kIGF0OgogICAgICAgIC0gaHR0cHM6Ly9kb2NzLnB5dGhvbi5vcmcvMy9saWJyYXJ5L2djLmh0bWwKICAgICAgICAtIGh0dHBzOi8vYWxleC5kenlvYmEuY29tL2Jsb2cvYXJjLXZzLWdjCgogICAgICAgICAgLi4gICMgbm9xYTogRTUwMQogICAgICAgIC0gaHR0cHM6Ly9zdGFja292ZXJmbG93LmNvbS9xdWVzdGlvbnMvNjQ1NjE0ODgvcHl0aG9ucy1nYy1nZXQtb2JqZWN0cy1mcm9tLWdldC1jb3VudAogICAgICAgIC0gaHR0cHM6Ly9naXRodWIuY29tL3B5dGhvbi9jcHl0aG9uL2Jsb2IvbWFpbi9Nb2R1bGVzL2djbW9kdWxlLmMKICAgICIiIgogICAgZGVmIF9faW5pdF9fKHNlbGYpIC0+IE5vbmU6CiAgICAgICAgbmFtZXMgPSBbICAjIGdjLmlzZW5hYmxlZCgpOgogICAgICAgICAgICAgICAgICAiZ2NfaXNlbmFibGVkIiwKICAgICAgICAgICAgICAgICAgICMgZ2MuZ2V0X2NvdW50KCk6CiAgICAgICAgICAgICAgICAgICJnY19jb3VudF9nZW4wIiwgImdjX2NvdW50X2dlbjEiLCAiZ2NfY291bnRfZ2VuMiIsCiAgICAgICAgICAgICAgICAgICAjIGdjLmdldF9zdGF0cygpOgogICAgICAgICAgICAgICAgICAiZ2NfY29sbGVjdGlvbnNfZ2VuMCIsICJnY19jb2xsZWN0ZWRfZ2VuMCIsCiAgICAgICAgICAgICAgICAgICJnY191bmNvbGxlY3RhYmxlX2dlbjAiLAogICAgICAgICAgICAgICAgICAiZ2NfY29sbGVjdGlvbnNfZ2VuMSIsICJnY19jb2xsZWN0ZWRfZ2VuMSIsCiAgICAgICAgICAgICAgICAgICJnY191bmNvbGxlY3RhYmxlX2dlbjEiLAogICAgICAgICAgICAgICAgICAiZ2NfY29sbGVjdGlvbnNfZ2VuMiIsICJnY19jb2xsZWN0ZWRfZ2VuMiIsCiAgICAgICAgICAgICAgICAgICJnY191bmNvbGxlY3RhYmxlX2dlbjIiLAogICAgICAgICAgICAgICAgIF0KCiAgICAgICAgdW5pdHMgPSBbImJvb2wiLAogICAgICAgICAgICAgICAgICIxIiwgIjEiLCAiMSIsCiAgICAgICAgICAgICAgICAgIjEiLCAiMSIsICIxIiwgIjEiLCAiMSIsICIxIiwgIjEiLCAiMSIsICIxIl0KCiAgICAgICAgZGVzY3JpcHRpb25zID0gWyJJcyBhdXRvbWF0aWMgR0MgZW5hYmxlZD8iLAogICAgICAgICAgICAgICAgICAgICAgICAiR0MgY291bnQgZ2VuMCIsICJHQyBjb3VudCBnZW4xIiwgIkdDIGNvdW50IGdlbjIiLAogICAgICAgICAgICAgICAgICAgICAgICAiR0MgY29sbGVjdGlvbnMgZ2VuMCIsICJHQyBvYmplY3RzIGNvbGxlY3RlZCBnZW4wIiwKICAgICAgICAgICAgICAgICAgICAgICAgIkdDIG9iamVjdHMgdW5jb2xsZWN0YWJsZSBnZW4wIiwKICAgICAgICAgICAgICAgICAgICAgICAgIkdDIGNvbGxlY3Rpb25zIGdlbjEiLCAiR0Mgb2JqZWN0cyBjb2xsZWN0ZWQgZ2VuMSIsCiAgICAgICAgICAgICAgICAgICAgICAgICJHQyBvYmplY3RzIHVuY29sbGVjdGFibGUgZ2VuMSIsCiAgICAgICAgICAgICAgICAgICAgICAgICJHQyBjb2xsZWN0aW9ucyBnZW4yIiwgIkdDIG9iamVjdHMgY29sbGVjdGVkIGdlbjIiLAogICAgICAgICAgICAgICAgICAgICAgICAiR0Mgb2JqZWN0cyB1bmNvbGxlY3RhYmxlIGdlbjIiLAogICAgICAgICAgICAgICAgICAgICAgICBdCgogICAgICAgIGFzc2VydCBsZW4obmFtZXMpID09IGxlbih1bml0cykgPT0gbGVuKGRlc2NyaXB0aW9ucykgPT0gMTMKCiAgICAgICAgc3VwZXIoKS5fX2luaXRfXyhuYW1lcywgY2FzdChMaXN0W09wdGlvbmFsW3N0cl1dLCB1bml0cyksCiAgICAgICAgICAgICAgICAgICAgICAgICBjYXN0KExpc3RbT3B0aW9uYWxbc3RyXV0sIGRlc2NyaXB0aW9ucykpCgogICAgZGVmIF9fY2FsbF9fKHNlbGYpIC0+IEl0ZXJhYmxlW09wdGlvbmFsW2Zsb2F0XV06CiAgICAgICAgaW1wb3J0IGdjCgogICAgICAgIGVuYWJsZWQgPSBnYy5pc2VuYWJsZWQoKQogICAgICAgIGNvdW50cyA9IGdjLmdldF9jb3VudCgpCiAgICAgICAgc3RhdHMgPSBnYy5nZXRfc3RhdHMoKQoKICAgICAgICByZXR1cm4gW2VuYWJsZWQsCiAgICAgICAgICAgICAgICBjb3VudHNbMF0sIGNvdW50c1sxXSwgY291bnRzWzJdLAogICAgICAgICAgICAgICAgc3RhdHNbMF1bImNvbGxlY3Rpb25zIl0sIHN0YXRzWzBdWyJjb2xsZWN0ZWQiXSwKICAgICAgICAgICAgICAgIHN0YXRzWzBdWyJ1bmNvbGxlY3RhYmxlIl0sCiAgICAgICAgICAgICAgICBzdGF0c1sxXVsiY29sbGVjdGlvbnMiXSwgc3RhdHNbMV1bImNvbGxlY3RlZCJdLAogICAgICAgICAgICAgICAgc3RhdHNbMV1bInVuY29sbGVjdGFibGUiXSwKICAgICAgICAgICAgICAgIHN0YXRzWzJdWyJjb2xsZWN0aW9ucyJdLCBzdGF0c1syXVsiY29sbGVjdGVkIl0sCiAgICAgICAgICAgICAgICBzdGF0c1syXVsidW5jb2xsZWN0YWJsZSJdCiAgICAgICAgICAgICAgICBdCgojIH19fQoKIyB2aW06IGZvbGRtZXRob2Q9bWFya2VyCg==" runalyzer_py_file = "IyEgL3Vzci9iaW4vZW52IHB5dGhvbgoKaW1wb3J0IGNvZGUKaW1wb3J0IHNxbGl0ZTMKCnRyeToKICAgIGltcG9ydCByZWFkbGluZQogICAgaW1wb3J0IHJsY29tcGxldGVyICAjIG5vcWE6IEY0MDEKICAgIEhBVkVfUkVBRExJTkUgPSBUcnVlCmV4Y2VwdCBJbXBvcnRFcnJvcjoKICAgIEhBVkVfUkVBRExJTkUgPSBGYWxzZQoKCmltcG9ydCBsb2dnaW5nCgpsb2dnZXIgPSBsb2dnaW5nLmdldExvZ2dlcihfX25hbWVfXykKCmZyb20gZGF0YWNsYXNzZXMgaW1wb3J0IGRhdGFjbGFzcwpmcm9tIGl0ZXJ0b29scyBpbXBvcnQgcHJvZHVjdApmcm9tIHNxbGl0ZTMgaW1wb3J0IENvbm5lY3Rpb24sIEN1cnNvcgpmcm9tIHR5cGluZyBpbXBvcnQgKEFueSwgQ2FsbGFibGUsIERpY3QsIEdlbmVyYXRvciwgTGlzdCwgT3B0aW9uYWwsIFNlcXVlbmNlLAogICAgICAgICAgICAgICAgICAgIFNldCwgVHVwbGUsIFR5cGUsIFVuaW9uKQoKZnJvbSBweXRvb2xzIGltcG9ydCBUYWJsZQoKCkBkYXRhY2xhc3MoZnJvemVuPVRydWUpCmNsYXNzIFBsb3RTdHlsZToKICAgIGRhc2hlczogVHVwbGVbaW50LCAuLi5dCiAgICBjb2xvcjogc3RyCgoKUExPVF9TVFlMRVMgPSBbCiAgICAgICAgUGxvdFN0eWxlKGRhc2hlcz1kYXNoZXMsIGNvbG9yPWNvbG9yKQogICAgICAgIGZvciBkYXNoZXMsIGNvbG9yIGluIHByb2R1Y3QoCiAgICAgICAgICAgIFsoKSwgKDEyLCAyKSwgKDQsIDIpLCAgKDIsIDIpLCAoMiwgOCldLAogICAgICAgICAgICBbImJsdWUiLCAiZ3JlZW4iLCAicmVkIiwgIm1hZ2VudGEiLCAiY3lhbiJdLAogICAgICAgICAgICApXQoKCmNsYXNzIFJ1bkRCOgogICAgZGVmIF9faW5pdF9fKHNlbGYsIGRiOiBDb25uZWN0aW9uLCBpbnRlcmFjdGl2ZTogYm9vbCkgLT4gTm9uZToKICAgICAgICBzZWxmLmRiID0gZGIKICAgICAgICBzZWxmLmludGVyYWN0aXZlID0gaW50ZXJhY3RpdmUKICAgICAgICBzZWxmLnJhbmtfYWdnX3RhYmxlczogU2V0W1R1cGxlW3N0ciwgQ2FsbGFibGVbLi4uLCBBbnldXV0gPSBzZXQoKQoKICAgIGRlZiBfX2RlbF9fKHNlbGYpIC0+IE5vbmU6CiAgICAgICAgc2VsZi5kYi5jbG9zZSgpCgogICAgZGVmIHEoc2VsZiwgcXJ5OiBzdHIsICpleHRyYV9hcmdzOiBBbnkpIC0+IEN1cnNvcjoKICAgICAgICByZXR1cm4gc2VsZi5kYi5leGVjdXRlKHNlbGYubWFuZ2xlX3NxbChxcnkpLCBleHRyYV9hcmdzKQoKICAgIGRlZiBtYW5nbGVfc3FsKHNlbGYsIHFyeTogc3RyKSAtPiBzdHI6CiAgICAgICAgcmV0dXJuIHFyeQoKICAgIGRlZiBnZXRfcmFua19hZ2dfdGFibGUoc2VsZiwgcXR5OiBzdHIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHJhbmtfYWdncmVnYXRvcjogQ2FsbGFibGVbLi4uLCBBbnldKSAtPiBzdHI6CiAgICAgICAgdGJsX25hbWUgPSBmInJhbmthZ2dfe3JhbmtfYWdncmVnYXRvcn1fe3F0eX0iCgogICAgICAgIGlmIChxdHksIHJhbmtfYWdncmVnYXRvcikgaW4gc2VsZi5yYW5rX2FnZ190YWJsZXM6CiAgICAgICAgICAgIHJldHVybiB0YmxfbmFtZQoKICAgICAgICBsb2dnZXIuaW5mbygiQnVpbGRpbmcgdGVtcG9yYXJ5IHJhbmsgYWdncmVnYXRpb24gdGFibGUge3RibF9uYW1lfS4iKQoKICAgICAgICBzZWxmLmRiLmV4ZWN1dGUoImNyZWF0ZSB0ZW1wb3JhcnkgdGFibGUgJXMgYXMgIgogICAgICAgICAgICAgICAgInNlbGVjdCBydW5faWQsIHN0ZXAsICVzKHZhbHVlKSBhcyB2YWx1ZSAiCiAgICAgICAgICAgICAgICAiZnJvbSAlcyBncm91cCBieSBydW5faWQsc3RlcCIgJSAoCiAgICAgICAgICAgICAgICAgICAgdGJsX25hbWUsIHJhbmtfYWdncmVnYXRvciwgcXR5KSkKICAgICAgICBzZWxmLmRiLmV4ZWN1dGUoImNyZWF0ZSBpbmRleCAlc19ydW5fc3RlcCBvbiAlcyAocnVuX2lkLHN0ZXApIgogICAgICAgICAgICAgICAgJSAodGJsX25hbWUsIHRibF9uYW1lKSkKICAgICAgICBzZWxmLnJhbmtfYWdnX3RhYmxlcy5hZGQoKHF0eSwgcmFua19hZ2dyZWdhdG9yKSkKICAgICAgICByZXR1cm4gdGJsX25hbWUKCiAgICBkZWYgc2NhdHRlcl9jdXJzb3Ioc2VsZiwgY3Vyc29yOiBDdXJzb3IsIGxhYmVsczogT3B0aW9uYWxbTGlzdFtzdHJdXSA9IE5vbmUsCiAgICAgICAgICAgICAgICAgICAgICAgKmFyZ3M6IEFueSwgKiprd2FyZ3M6IEFueSkgLT4gTm9uZToKICAgICAgICBpbXBvcnQgbWF0cGxvdGxpYi5weXBsb3QgYXMgcGx0CgogICAgICAgIGRhdGFfYXJncyA9IHR1cGxlKHppcCgqbGlzdChjdXJzb3IpKSkKICAgICAgICBwbHQuc2NhdHRlcigqKGRhdGFfYXJncyArIGFyZ3MpLCAqKmt3YXJncykKCiAgICAgICAgaWYgaXNpbnN0YW5jZShsYWJlbHMsIGxpc3QpIGFuZCBsZW4obGFiZWxzKSA9PSAyOgogICAgICAgICAgICBwbHQueGxhYmVsKGxhYmVsc1swXSkKICAgICAgICAgICAgcGx0LnlsYWJlbChsYWJlbHNbMV0pCiAgICAgICAgZWxpZiBsYWJlbHMgaXMgbm90IE5vbmU6CiAgICAgICAgICAgIHJhaXNlIFR5cGVFcnJvcigiVGhlICdsYWJlbHMnIHBhcmFtZXRlciBtdXN0IGJlIGEgbGlzdCB3aXRoIHR3byIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICJlbGVtZW50cy4iKQoKICAgICAgICBpZiBzZWxmLmludGVyYWN0aXZlOgogICAgICAgICAgICBwbHQuc2hvdygpCgogICAgZGVmIHBsb3RfY3Vyc29yKHNlbGYsIGN1cnNvcjogQ3Vyc29yLCBsYWJlbHM6IE9wdGlvbmFsW0xpc3Rbc3RyXV0gPSBOb25lLAogICAgICAgICAgICAgICAgICAgICphcmdzOiBBbnksICoqa3dhcmdzOiBBbnkpIC0+IE5vbmU6CiAgICAgICAgZnJvbSBtYXRwbG90bGliLnB5cGxvdCBpbXBvcnQgbGVnZW5kLCBwbG90LCBzaG93CgogICAgICAgIGF1dG9fc3R5bGUgPSBrd2FyZ3MucG9wKCJhdXRvX3N0eWxlIiwgVHJ1ZSkKCiAgICAgICAgaWYgbGVuKGN1cnNvci5kZXNjcmlwdGlvbikgPT0gMjoKICAgICAgICAgICAgaWYgYXV0b19zdHlsZToKICAgICAgICAgICAgICAgIHN0eWxlID0gUExPVF9TVFlMRVNbMF0KICAgICAgICAgICAgICAgIGt3YXJnc1siZGFzaGVzIl0gPSBzdHlsZS5kYXNoZXMKICAgICAgICAgICAgICAgIGt3YXJnc1siY29sb3IiXSA9IHN0eWxlLmNvbG9yCgogICAgICAgICAgICB4LCB5ID0gbGlzdCh6aXAoKmxpc3QoY3Vyc29yKSkpCiAgICAgICAgICAgIHAgPSBwbG90KHgsIHksICphcmdzLCAqKmt3YXJncykKCiAgICAgICAgICAgIGlmIGlzaW5zdGFuY2UobGFiZWxzLCBsaXN0KSBhbmQgbGVuKGxhYmVscykgPT0gMjoKICAgICAgICAgICAgICAgIHBbMF0uYXhlcy5zZXRfeGxhYmVsKGxhYmVsc1swXSkKICAgICAgICAgICAgICAgIHBbMF0uYXhlcy5zZXRfeWxhYmVsKGxhYmVsc1sxXSkKICAgICAgICAgICAgZWxpZiBsYWJlbHMgaXMgbm90IE5vbmU6CiAgICAgICAgICAgICAgICByYWlzZSBUeXBlRXJyb3IoIlRoZSAnbGFiZWxzJyBwYXJhbWV0ZXIgbXVzdCBiZSBhIGxpc3Qgd2l0aCB0d28iCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIiBlbGVtZW50cy4iKQoKICAgICAgICBlbGlmIGxlbihjdXJzb3IuZGVzY3JpcHRpb24pID4gMjoKICAgICAgICAgICAgc21hbGxfbGVnZW5kID0ga3dhcmdzLnBvcCgic21hbGxfbGVnZW5kIiwgVHJ1ZSkKCiAgICAgICAgICAgIGRlZiBmb3JtYXRfbGFiZWwoa3ZfcGFpcnM6IFNlcXVlbmNlW1R1cGxlW3N0ciwgQW55XV0pIC0+IHN0cjoKICAgICAgICAgICAgICAgIHJldHVybiAiICIuam9pbihmIntjb2x1bW59Ont2YWx1ZX0iCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBmb3IgY29sdW1uLCB2YWx1ZSBpbiBrdl9wYWlycykKICAgICAgICAgICAgZm9ybWF0X2xhYmVsID0ga3dhcmdzLnBvcCgiZm9ybWF0X2xhYmVsIiwgZm9ybWF0X2xhYmVsKQoKICAgICAgICAgICAgZGVmIGRvX3Bsb3QoeDogTGlzdFtmbG9hdF0sIHk6IExpc3RbZmxvYXRdLAogICAgICAgICAgICAgICAgICAgICAgICByb3dfcmVzdDogVHVwbGVbQW55LCAuLi5dKSAtPiBOb25lOgogICAgICAgICAgICAgICAgbXlfa3dhcmdzID0ga3dhcmdzLmNvcHkoKQogICAgICAgICAgICAgICAgc3R5bGUgPSBQTE9UX1NUWUxFU1tzdHlsZV9pZHhbMF0gJSBsZW4oUExPVF9TVFlMRVMpXQogICAgICAgICAgICAgICAgaWYgYXV0b19zdHlsZToKICAgICAgICAgICAgICAgICAgICBteV9rd2FyZ3Muc2V0ZGVmYXVsdCgiZGFzaGVzIiwgc3R5bGUuZGFzaGVzKQogICAgICAgICAgICAgICAgICAgIG15X2t3YXJncy5zZXRkZWZhdWx0KCJjb2xvciIsIHN0eWxlLmNvbG9yKQoKICAgICAgICAgICAgICAgIG15X2t3YXJncy5zZXRkZWZhdWx0KCJsYWJlbCIsCiAgICAgICAgICAgICAgICAgICAgICAgIGZvcm1hdF9sYWJlbChsaXN0KHppcCgKICAgICAgICAgICAgICAgICAgICAgICAgICAgIChjb2xbMF0gZm9yIGNvbCBpbiBjdXJzb3IuZGVzY3JpcHRpb25bMjpdKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJvd19yZXN0KSkpKQoKICAgICAgICAgICAgICAgIHBsb3QoeCwgeSwgKmFyZ3MsIGhvbGQ9VHJ1ZSwgKipteV9rd2FyZ3MpCiAgICAgICAgICAgICAgICBzdHlsZV9pZHhbMF0gKz0gMQoKICAgICAgICAgICAgc3R5bGVfaWR4ID0gWzBdCiAgICAgICAgICAgIGZvciB4LCB5LCByZXN0IGluIHNwbGl0X2N1cnNvcihjdXJzb3IpOgogICAgICAgICAgICAgICAgZG9fcGxvdCh4LCB5LCByZXN0KSAgIyB0eXBlOiBpZ25vcmVbYXJnLXR5cGVdCgogICAgICAgICAgICBpZiBzbWFsbF9sZWdlbmQ6CiAgICAgICAgICAgICAgICBmcm9tIG1hdHBsb3RsaWIuZm9udF9tYW5hZ2VyIGltcG9ydCBGb250UHJvcGVydGllcwogICAgICAgICAgICAgICAgbGVnZW5kKHBhZD0wLjA0LCBwcm9wPUZvbnRQcm9wZXJ0aWVzKHNpemU9OCksIGxvYz0iYmVzdCIsCiAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsc2VwPTApCiAgICAgICAgZWxzZToKICAgICAgICAgICAgcmFpc2UgVmFsdWVFcnJvcigiaW52YWxpZCBudW1iZXIgb2YgY29sdW1ucyIpCgogICAgICAgIGlmIHNlbGYuaW50ZXJhY3RpdmU6CiAgICAgICAgICAgIHNob3coKQoKICAgIGRlZiBwcmludF9jdXJzb3Ioc2VsZiwgY3Vyc29yOiBDdXJzb3IpIC0+IE5vbmU6CiAgICAgICAgcHJpbnQodGFibGVfZnJvbV9jdXJzb3IoY3Vyc29yKSkKCgpkZWYgc3BsaXRfY3Vyc29yKGN1cnNvcjogQ3Vyc29yKSAtPiBHZW5lcmF0b3JbCiAgICAgICAgVHVwbGVbTGlzdFtBbnldLCBMaXN0W0FueV0sIE9wdGlvbmFsW1R1cGxlW0FueSwgLi4uXV1dLCBOb25lLCBOb25lXToKCiAgICB4OiBMaXN0W0FueV0gPSBbXQogICAgeTogTGlzdFtBbnldID0gW10KICAgIGxhc3RfcmVzdCA9IE5vbmUKICAgIGZvciByb3cgaW4gY3Vyc29yOgogICAgICAgIHJvd190dXBsZSA9IHR1cGxlKHJvdykKICAgICAgICByb3dfcmVzdCA9IHJvd190dXBsZVsyOl0KCiAgICAgICAgaWYgbGFzdF9yZXN0IGlzIE5vbmU6CiAgICAgICAgICAgIGxhc3RfcmVzdCA9IHJvd19yZXN0CgogICAgICAgIGlmIHJvd19yZXN0ICE9IGxhc3RfcmVzdDoKICAgICAgICAgICAgeWllbGQgeCwgeSwgbGFzdF9yZXN0CiAgICAgICAgICAgIGRlbCB4WzpdCiAgICAgICAgICAgIGRlbCB5WzpdCgogICAgICAgICAgICBsYXN0X3Jlc3QgPSByb3dfcmVzdAoKICAgICAgICB4LmFwcGVuZChyb3dfdHVwbGVbMF0pCiAgICAgICAgeS5hcHBlbmQocm93X3R1cGxlWzFdKQogICAgaWYgeDoKICAgICAgICB5aWVsZCB4LCB5LCBsYXN0X3Jlc3QKCgpkZWYgdGFibGVfZnJvbV9jdXJzb3IoY3Vyc29yOiBDdXJzb3IpIC0+IFRhYmxlOgogICAgdGJsID0gVGFibGUoKQogICAgdGJsLmFkZF9yb3codHVwbGUoW2NvbHVtblswXSBmb3IgY29sdW1uIGluIGN1cnNvci5kZXNjcmlwdGlvbl0pKQogICAgZm9yIHJvdyBpbiBjdXJzb3I6CiAgICAgICAgdGJsLmFkZF9yb3cocm93KQogICAgcmV0dXJuIHRibAoKCmNsYXNzIE1hZ2ljUnVuREIoUnVuREIpOgogICAgZGVmIG1hbmdsZV9zcWwoc2VsZiwgcXJ5OiBzdHIpIC0+IHN0cjoKICAgICAgICB1cF9xcnkgPSBxcnkudXBwZXIoKQogICAgICAgIGlmICJGUk9NIiBpbiB1cF9xcnkgYW5kICIkJCIgbm90IGluIHVwX3FyeToKICAgICAgICAgICAgcmV0dXJuIHFyeQoKICAgICAgICBtYWdpY19jb2x1bW5zID0gc2V0KCkKICAgICAgICBpbXBvcnQgcmUKCiAgICAgICAgIyBzaG91bGQgYmU6IHJlLk1hdGNoW0FueV0KICAgICAgICBkZWYgcmVwbGFjZV9tYWdpY19jb2x1bW4obWF0Y2g6IEFueSkgLT4gc3RyOgogICAgICAgICAgICBxdHlfbmFtZSA9IG1hdGNoLmdyb3VwKDEpCiAgICAgICAgICAgIHJhbmtfYWdncmVnYXRvciA9IG1hdGNoLmdyb3VwKDIpCgogICAgICAgICAgICBpZiByYW5rX2FnZ3JlZ2F0b3IgaXMgbm90IE5vbmU6CiAgICAgICAgICAgICAgICByYW5rX2FnZ3JlZ2F0b3IgPSByYW5rX2FnZ3JlZ2F0b3JbMTpdCiAgICAgICAgICAgICAgICBtYWdpY19jb2x1bW5zLmFkZCgocXR5X25hbWUsIHJhbmtfYWdncmVnYXRvcikpCiAgICAgICAgICAgICAgICByZXR1cm4gZiJ7cmFua19hZ2dyZWdhdG9yfV97cXR5X25hbWV9LnZhbHVlIEFTIHtxdHlfbmFtZX0iCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICBtYWdpY19jb2x1bW5zLmFkZCgocXR5X25hbWUsIE5vbmUpKQogICAgICAgICAgICAgICAgcmV0dXJuICIlcy52YWx1ZSBBUyAlcyIgJSAocXR5X25hbWUsIHF0eV9uYW1lKQoKICAgICAgICBtYWdpY19jb2x1bW5fcmUgPSByZS5jb21waWxlKHIiXCQoW2EtekEtWl1bQS1aYS16MC05X10qKShcLlthLXpdKik/IikKICAgICAgICBxcnksIF8gPSBtYWdpY19jb2x1bW5fcmUuc3VibihyZXBsYWNlX21hZ2ljX2NvbHVtbiwgcXJ5KQoKICAgICAgICBvdGhlcl9jbGF1c2VzID0gWyAgIyBub3FhOiBGODQxCiAgICAgICAgICAgICAgICAiVU5JT04iLCAgIklOVEVSU0VDVCIsICJFWENFUFQiLCAiV0hFUkUiLCAiR1JPVVAiLAogICAgICAgICAgICAgICAgIkhBVklORyIsICJPUkRFUiIsICJMSU1JVCIsICI7Il0KCiAgICAgICAgZnJvbV9jbGF1c2UgPSAiZnJvbSBydW5zICIKICAgICAgICBsYXN0X3RibCA9IE5vbmUKICAgICAgICBmb3IgdGJsLCByYW5rX2FnZ3JlZ2F0b3IgaW4gbWFnaWNfY29sdW1uczoKICAgICAgICAgICAgaWYgcmFua19hZ2dyZWdhdG9yIGlzIG5vdCBOb25lOgogICAgICAgICAgICAgICAgZnVsbF90YmwgPSBmIntyYW5rX2FnZ3JlZ2F0b3J9X3t0Ymx9IgogICAgICAgICAgICAgICAgZnVsbF90Ymxfc3JjID0gInt9IGFzIHt9Ii5mb3JtYXQoCiAgICAgICAgICAgICAgICAgICAgICAgIHNlbGYuZ2V0X3JhbmtfYWdnX3RhYmxlKHRibCwgcmFua19hZ2dyZWdhdG9yKSwKICAgICAgICAgICAgICAgICAgICAgICAgZnVsbF90YmwpCgogICAgICAgICAgICAgICAgaWYgbGFzdF90YmwgaXMgbm90IE5vbmU6CiAgICAgICAgICAgICAgICAgICAgYWRkZW5kdW0gPSBmIiBhbmQge2xhc3RfdGJsfS5zdGVwID0ge2Z1bGxfdGJsfS5zdGVwIgogICAgICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgICAgICBhZGRlbmR1bSA9ICIiCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICBmdWxsX3RibCA9IHRibAogICAgICAgICAgICAgICAgZnVsbF90Ymxfc3JjID0gdGJsCgogICAgICAgICAgICAgICAgaWYgbGFzdF90YmwgaXMgbm90IE5vbmU6CiAgICAgICAgICAgICAgICAgICAgYWRkZW5kdW0gPSAiIGFuZCB7fS5zdGVwID0ge30uc3RlcCBhbmQge30ucmFuaz17fS5yYW5rIi5mb3JtYXQoCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYXN0X3RibCwgZnVsbF90YmwsIGxhc3RfdGJsLCBmdWxsX3RibCkKICAgICAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICAgICAgYWRkZW5kdW0gPSAiIgoKICAgICAgICAgICAgZnJvbV9jbGF1c2UgKz0gIiBpbm5lciBqb2luIHt9IG9uICh7fS5ydW5faWQgPSBydW5zLmlke30pICIuZm9ybWF0KAogICAgICAgICAgICAgICAgICAgIGZ1bGxfdGJsX3NyYywgZnVsbF90YmwsIGFkZGVuZHVtKQogICAgICAgICAgICBsYXN0X3RibCA9IGZ1bGxfdGJsCgogICAgICAgIGRlZiBnZXRfY2xhdXNlX2luZGljZXMocXJ5OiBzdHIpIC0+IERpY3Rbc3RyLCBpbnRdOgogICAgICAgICAgICBvdGhlcl9jbGF1c2VzID0gWyJVTklPTiIsICAiSU5URVJTRUNUIiwgIkVYQ0VQVCIsICJXSEVSRSIsICJHUk9VUCIsCiAgICAgICAgICAgICAgICAgICAgIkhBVklORyIsICJPUkRFUiIsICJMSU1JVCIsICI7Il0KCiAgICAgICAgICAgIHJlc3VsdCA9IHt9CiAgICAgICAgICAgIHVwX3FyeSA9IHFyeS51cHBlcigpCiAgICAgICAgICAgIGZvciBjbGF1c2UgaW4gb3RoZXJfY2xhdXNlczoKICAgICAgICAgICAgICAgIGNsYXVzZV9tYXRjaCA9IHJlLnNlYXJjaChyIlxiJXNcYiIgJSBjbGF1c2UsIHVwX3FyeSkKICAgICAgICAgICAgICAgIGlmIGNsYXVzZV9tYXRjaCBpcyBub3QgTm9uZToKICAgICAgICAgICAgICAgICAgICByZXN1bHRbY2xhdXNlXSA9IGNsYXVzZV9tYXRjaC5zdGFydCgpCgogICAgICAgICAgICByZXR1cm4gcmVzdWx0CgogICAgICAgICMgYWRkICdmcm9tJwogICAgICAgIGlmICIkJCIgaW4gcXJ5OgogICAgICAgICAgICBxcnkgPSBxcnkucmVwbGFjZSgiJCQiLCAiICVzICIgJSBmcm9tX2NsYXVzZSkKICAgICAgICBlbHNlOgogICAgICAgICAgICBjbGF1c2VfaW5kaWNlcyA9IGdldF9jbGF1c2VfaW5kaWNlcyhxcnkpCgogICAgICAgICAgICBpZiBub3QgY2xhdXNlX2luZGljZXM6CiAgICAgICAgICAgICAgICBxcnkgPSBxcnkrIiAiK2Zyb21fY2xhdXNlCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICBmaXJzdF9jbGF1c2VfaWR4ID0gbWluKGNsYXVzZV9pbmRpY2VzLnZhbHVlcygpKQogICAgICAgICAgICAgICAgcXJ5ID0gKAogICAgICAgICAgICAgICAgICAgICAgICBxcnlbOmZpcnN0X2NsYXVzZV9pZHhdCiAgICAgICAgICAgICAgICAgICAgICAgICsgZnJvbV9jbGF1c2UKICAgICAgICAgICAgICAgICAgICAgICAgKyBxcnlbZmlyc3RfY2xhdXNlX2lkeDpdKQoKICAgICAgICByZXR1cm4gcXJ5CgoKZGVmIG1ha2VfcnVuYWx5emVyX3N5bWJvbHMoZGI6IFJ1bkRCKSBcCiAgICAgICAgLT4gRGljdFtzdHIsIFVuaW9uW1J1bkRCLCBzdHIsIE5vbmUsIENhbGxhYmxlWy4uLiwgQW55XV1dOgogICAgcmV0dXJuIHsKICAgICAgICAgICAgIl9fbmFtZV9fIjogIl9fY29uc29sZV9fIiwKICAgICAgICAgICAgIl9fZG9jX18iOiBOb25lLAogICAgICAgICAgICAiZGIiOiBkYiwKICAgICAgICAgICAgIm1hbmdsZV9zcWwiOiBkYi5tYW5nbGVfc3FsLAogICAgICAgICAgICAicSI6IGRiLnEsCiAgICAgICAgICAgICJkYnBsb3QiOiBkYi5wbG90X2N1cnNvciwKICAgICAgICAgICAgImRic2NhdHRlciI6IGRiLnNjYXR0ZXJfY3Vyc29yLAogICAgICAgICAgICAiZGJwcmludCI6IGRiLnByaW50X2N1cnNvciwKICAgICAgICAgICAgInNwbGl0X2N1cnNvciI6IHNwbGl0X2N1cnNvciwKICAgICAgICAgICAgInRhYmxlX2Zyb21fY3Vyc29yIjogdGFibGVfZnJvbV9jdXJzb3IsCiAgICAgICAgICAgIH0KCgpjbGFzcyBSdW5hbHl6ZXJDb25zb2xlKGNvZGUuSW50ZXJhY3RpdmVDb25zb2xlKToKICAgIGRlZiBfX2luaXRfXyhzZWxmLCBkYjogUnVuREIpIC0+IE5vbmU6CiAgICAgICAgc2VsZi5kYiA9IGRiCiAgICAgICAgY29kZS5JbnRlcmFjdGl2ZUNvbnNvbGUuX19pbml0X18oc2VsZiwKICAgICAgICAgICAgICAgIG1ha2VfcnVuYWx5emVyX3N5bWJvbHMoZGIpKQoKICAgICAgICB0cnk6CiAgICAgICAgICAgIGltcG9ydCBudW1weSAgIyBub3FhOiBGNDAxCiAgICAgICAgICAgIHNlbGYucnVuc291cmNlKCJmcm9tIG51bXB5IGltcG9ydCAqIikKICAgICAgICBleGNlcHQgSW1wb3J0RXJyb3I6CiAgICAgICAgICAgIHBhc3MKCiAgICAgICAgdHJ5OgogICAgICAgICAgICBpbXBvcnQgbWF0cGxvdGxpYi5weXBsb3QgICMgbm9xYQogICAgICAgICAgICBzZWxmLnJ1bnNvdXJjZSgiZnJvbSBtYXRwbG90bGliLnB5cGxvdCBpbXBvcnQgKiIpCiAgICAgICAgZXhjZXB0IEltcG9ydEVycm9yOgogICAgICAgICAgICBwYXNzCiAgICAgICAgZXhjZXB0IFJ1bnRpbWVFcnJvcjoKICAgICAgICAgICAgcGFzcwoKICAgICAgICBpZiBIQVZFX1JFQURMSU5FOgogICAgICAgICAgICBpbXBvcnQgYXRleGl0CiAgICAgICAgICAgIGltcG9ydCBvcwoKICAgICAgICAgICAgaGlzdGZpbGUgPSBvcy5wYXRoLmpvaW4ob3MuZW52aXJvblsiSE9NRSJdLCAiLnJ1bmFseXplcmhpc3QiKQogICAgICAgICAgICBpZiBvcy5hY2Nlc3MoaGlzdGZpbGUsIG9zLlJfT0spOgogICAgICAgICAgICAgICAgcmVhZGxpbmUucmVhZF9oaXN0b3J5X2ZpbGUoaGlzdGZpbGUpCiAgICAgICAgICAgIGF0ZXhpdC5yZWdpc3RlcihyZWFkbGluZS53cml0ZV9oaXN0b3J5X2ZpbGUsIGhpc3RmaWxlKQogICAgICAgICAgICByZWFkbGluZS5wYXJzZV9hbmRfYmluZCgidGFiOiBjb21wbGV0ZSIpCgogICAgICAgIHNlbGYubGFzdF9wdXNoX3Jlc3VsdCA9IEZhbHNlCgogICAgZGVmIHB1c2goc2VsZiwgY21kbGluZTogc3RyKSAtPiBib29sOgogICAgICAgIGlmIGNtZGxpbmUuc3RhcnRzd2l0aCgiLiIpOgogICAgICAgICAgICB0cnk6CiAgICAgICAgICAgICAgICBzZWxmLmV4ZWN1dGVfbWFnaWMoY21kbGluZSkKICAgICAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbjoKICAgICAgICAgICAgICAgIGltcG9ydCB0cmFjZWJhY2sKICAgICAgICAgICAgICAgIHRyYWNlYmFjay5wcmludF9leGMoKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHNlbGYubGFzdF9wdXNoX3Jlc3VsdCA9IGNvZGUuSW50ZXJhY3RpdmVDb25zb2xlLnB1c2goc2VsZiwgY21kbGluZSkKCiAgICAgICAgcmV0dXJuIHNlbGYubGFzdF9wdXNoX3Jlc3VsdAoKICAgIGRlZiBleGVjdXRlX21hZ2ljKHNlbGYsIGNtZGxpbmU6IHN0cikgLT4gTm9uZToKICAgICAgICBjbWRfZW5kID0gY21kbGluZS5maW5kKCIgIikKICAgICAgICBpZiBjbWRfZW5kID09IC0xOgogICAgICAgICAgICBjbWQgPSBjbWRsaW5lWzE6XQogICAgICAgICAgICBhcmdzID0gIiIKICAgICAgICBlbHNlOgogICAgICAgICAgICBjbWQgPSBjbWRsaW5lWzE6Y21kX2VuZF0KICAgICAgICAgICAgYXJncyA9IGNtZGxpbmVbY21kX2VuZCsxOl0KCiAgICAgICAgaWYgY21kID09ICJoZWxwIjoKICAgICAgICAgICAgcHJpbnQoIiIiCkNvbW1hbmRzOgogLmhlbHAgICAgICAgIHNob3cgdGhpcyBoZWxwIG1lc3NhZ2UKIC5xIFNRTCAgICAgICBleGVjdXRlIGEgKHBvdGVudGlhbGx5IG1hbmdsZWQpIHF1ZXJ5CiAuY29uc3RhbnRzICAgc2hvdyBhIGxpc3Qgb2YgKGNvbnN0YW50KSBydW4gcHJvcGVydGllcwogLnF1YW50aXRpZXMgIHNob3cgYSBsaXN0IG9mIHRpbWUtZGVwZW5kZW50IHF1YW50aXRpZXMKIC53YXJuaW5ncyAgICBzaG93IGEgbGlzdCBvZiB3YXJuaW5ncwogLmxvZ2dpbmcgICAgIHNob3cgYSBsaXN0IG9mIGxvZ2dpbmcgbWVzc2FnZXMKClBsb3R0aW5nOgogLnBsb3QgU1FMICAgIHBsb3QgcmVzdWx0cyBvZiAocG90ZW50aWFsbHkgbWFuZ2xlZCkgcXVlcnkuCiAgICAgICAgICAgICAgcmVzdWx0IHNldHMgY2FuIGJlICh4LHkpIG9yICh4LHksZGVzY3IxLGRlc2NyMiwuLi4pLAogICAgICAgICAgICAgIGluIHdoaWNoIGNhc2UgYSBuZXcgcGxvdCB3aWxsIGJlIHN0YXJ0ZWQgZm9yIGVhY2gKICAgICAgICAgICAgICB0dXBsZSAoZGVzY3IxLCBkZXNjcjIsIC4uLikKIC5zY2F0dGVyIFNRTCBtYWtlIHNjYXR0ZXJwbG90IHJlc3VsdHMgb2YgKHBvdGVudGlhbGx5IG1hbmdsZWQpIHF1ZXJ5LgogICAgICAgICAgICAgIHJlc3VsdCBzZXRzIGNhbiBoYXZlIGJldHdlZW4gdHdvIGFuZCBmb3VyIGNvbHVtbnMKICAgICAgICAgICAgICBmb3IgKHgseSxzaXplLGNvbG9yKS4KClNRTCBtYW5nbGluZywgaWYgcmVxdWVzdGVkICgiTWFnaWNTUUwiKToKICAgIHNlbGVjdCAkcXVhbnRpdHkgd2hlcmUgcHJlZChmZWF0dXJlKQoKQ3VzdG9tIFNRTGl0ZSBhZ2dyZWdhdGVzOgogICAgc3RkZGV2LCB2YXIsIG5vcm0xLCBub3JtMgoKQXZhaWxhYmxlIFB5dGhvbiBzeW1ib2xzOgogICAgZGI6IHRoZSBTUUxpdGUgZGF0YWJhc2UKICAgIG1hbmdsZV9zcWwocXVlcnlfc3RyKTogbWFuZ2xlIHRoZSBTUUwgcXVlcnkgc3RyaW5nIHF1ZXJ5X3N0cgogICAgcShxdWVyeV9zdHIpOiBnZXQgZGIgY3Vyc29yIGZvciBtYW5nbGVkIHF1ZXJ5X3N0cgogICAgZGJwbG90KGN1cnNvcik6IHBsb3QgcmVzdWx0IG9mIGN1cnNvcgogICAgZGJzY2F0dGVyKGN1cnNvcik6IG1ha2Ugc2NhdHRlcnBsb3QgcmVzdWx0IG9mIGN1cnNvcgogICAgZGJwcmludChjdXJzb3IpOiBwcmludCByZXN1bHQgb2YgY3Vyc29yCiAgICBzcGxpdF9jdXJzb3IoY3Vyc29yKTogeCx5LGRhdGEgZ2F0aGVyIHRoYXQgLnBsb3QgdXNlcyBpbnRlcm5hbGx5CiAgICB0YWJsZV9mcm9tX2N1cnNvcihjdXJzb3IpOiBDcmVhdGUgYSBwcmludGFibGUgdGFibGUgZnJvbSBhIGN1cnNvcgoiIiIpCiAgICAgICAgZWxpZiBjbWQgPT0gInEiOgogICAgICAgICAgICBzZWxmLmRiLnByaW50X2N1cnNvcihzZWxmLmRiLnEoYXJncykpCgogICAgICAgIGVsaWYgY21kID09ICJydW5wcm9wcyIgb3IgY21kID09ICJjb25zdGFudHMiOgogICAgICAgICAgICBjdXJzb3IgPSBzZWxmLmRiLmRiLmV4ZWN1dGUoInNlbGVjdCAqIGZyb20gcnVucyIpCiAgICAgICAgICAgIGNvbHVtbnMgPSBbY29sdW1uWzBdIGZvciBjb2x1bW4gaW4gY3Vyc29yLmRlc2NyaXB0aW9uXQogICAgICAgICAgICBjb2x1bW5zLnNvcnQoKQogICAgICAgICAgICBmb3IgY29sIGluIGNvbHVtbnM6CiAgICAgICAgICAgICAgICBwcmludChjb2wpCiAgICAgICAgZWxpZiBjbWQgPT0gInF1YW50aXRpZXMiOgogICAgICAgICAgICBzZWxmLmRiLnByaW50X2N1cnNvcihzZWxmLmRiLnEoInNlbGVjdCAqIGZyb20gcXVhbnRpdGllcyBvcmRlciBieSBuYW1lIikpCiAgICAgICAgZWxpZiBjbWQgPT0gIndhcm5pbmdzIjoKICAgICAgICAgICAgc2VsZi5kYi5wcmludF9jdXJzb3Ioc2VsZi5kYi5xKCJzZWxlY3QgKiBmcm9tIHdhcm5pbmdzIikpCiAgICAgICAgZWxpZiBjbWQgPT0gImxvZ2dpbmciOgogICAgICAgICAgICBzZWxmLmRiLnByaW50X2N1cnNvcihzZWxmLmRiLnEoInNlbGVjdCAqIGZyb20gbG9nZ2luZyIpKQogICAgICAgIGVsaWYgY21kID09ICJ0aXRsZSI6CiAgICAgICAgICAgIGZyb20gcHlsYWIgaW1wb3J0IHRpdGxlCiAgICAgICAgICAgIHRpdGxlKGFyZ3MpCiAgICAgICAgZWxpZiBjbWQgPT0gInBsb3QiOgogICAgICAgICAgICBjdXJzb3IgPSBzZWxmLmRiLmRiLmV4ZWN1dGUoc2VsZi5kYi5tYW5nbGVfc3FsKGFyZ3MpKQogICAgICAgICAgICBjb2x1bW5uYW1lcyA9IFtjb2x1bW5bMF0gZm9yIGNvbHVtbiBpbiBjdXJzb3IuZGVzY3JpcHRpb25dCiAgICAgICAgICAgIHNlbGYuZGIucGxvdF9jdXJzb3IoY3Vyc29yLCBsYWJlbHM9Y29sdW1ubmFtZXMpCiAgICAgICAgZWxpZiBjbWQgPT0gInNjYXR0ZXIiOgogICAgICAgICAgICBjdXJzb3IgPSBzZWxmLmRiLmRiLmV4ZWN1dGUoc2VsZi5kYi5tYW5nbGVfc3FsKGFyZ3MpKQogICAgICAgICAgICBjb2x1bW5uYW1lcyA9IFtjb2x1bW5bMF0gZm9yIGNvbHVtbiBpbiBjdXJzb3IuZGVzY3JpcHRpb25dCiAgICAgICAgICAgIHNlbGYuZGIuc2NhdHRlcl9jdXJzb3IoY3Vyc29yLCBsYWJlbHM9Y29sdW1ubmFtZXMpCiAgICAgICAgZWxzZToKICAgICAgICAgICAgcHJpbnQoImludmFsaWQgbWFnaWMgY29tbWFuZCIpCgoKIyB7e3sgY3VzdG9tIGFnZ3JlZ2F0ZXMKCmZyb20gcHl0b29scyBpbXBvcnQgVmFyaWFuY2VBZ2dyZWdhdG9yICAjIG5vcWE6IEU0MDIKCgpjbGFzcyBWYXJpYW5jZShWYXJpYW5jZUFnZ3JlZ2F0b3IpOgogICAgZGVmIF9faW5pdF9fKHNlbGYpIC0+IE5vbmU6CiAgICAgICAgVmFyaWFuY2VBZ2dyZWdhdG9yLl9faW5pdF9fKHNlbGYsICAjIHR5cGU6IGlnbm9yZVtuby11bnR5cGVkLWNhbGxdCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVudGlyZV9wb3A9VHJ1ZSkKCgpjbGFzcyBTdGREZXZpYXRpb24oVmFyaWFuY2UpOgogICAgZGVmIGZpbmFsaXplKHNlbGYpIC0+IE9wdGlvbmFsW2Zsb2F0XToKICAgICAgICByZXN1bHQgPSBWYXJpYW5jZS5maW5hbGl6ZShzZWxmKSAgIyB0eXBlOiBpZ25vcmVbbm8tdW50eXBlZC1jYWxsXQoKICAgICAgICBpZiByZXN1bHQgaXMgTm9uZToKICAgICAgICAgICAgcmV0dXJuIE5vbmUKICAgICAgICBlbHNlOgogICAgICAgICAgICBmcm9tIG1hdGggaW1wb3J0IHNxcnQKICAgICAgICAgICAgcmV0dXJuIHNxcnQocmVzdWx0KQoKCmNsYXNzIE5vcm0xOgogICAgZGVmIF9faW5pdF9fKHNlbGYpIC0+IE5vbmU6CiAgICAgICAgc2VsZi5hYnNfc3VtID0gMC4wCgogICAgZGVmIHN0ZXAoc2VsZiwgdmFsdWU6IGZsb2F0KSAtPiBOb25lOgogICAgICAgIHNlbGYuYWJzX3N1bSArPSBhYnModmFsdWUpCgogICAgZGVmIGZpbmFsaXplKHNlbGYpIC0+IGZsb2F0OgogICAgICAgIHJldHVybiBzZWxmLmFic19zdW0KCgpjbGFzcyBOb3JtMjoKICAgIGRlZiBfX2luaXRfXyhzZWxmKSAtPiBOb25lOgogICAgICAgIHNlbGYuc3F1YXJlX3N1bSA9IDAuMAoKICAgIGRlZiBzdGVwKHNlbGYsIHZhbHVlOiBmbG9hdCkgLT4gTm9uZToKICAgICAgICBzZWxmLnNxdWFyZV9zdW0gKz0gdmFsdWUqKjIKCiAgICBkZWYgZmluYWxpemUoc2VsZikgLT4gZmxvYXQ6CiAgICAgICAgZnJvbSBtYXRoIGltcG9ydCBzcXJ0CiAgICAgICAgcmV0dXJuIHNxcnQoc2VsZi5zcXVhcmVfc3VtKQoKCmRlZiBteV9zcHJpbnRmKGZvcm1hdDogc3RyLCBhcmc6IHN0cikgLT4gc3RyOgogICAgcmV0dXJuIGZvcm1hdCAlIGFyZwoKIyB9fX0KCgpkZWYgYXV0b19nYXRoZXIoZmlsZW5hbWVzOiBMaXN0W3N0cl0pIC0+IHNxbGl0ZTMuQ29ubmVjdGlvbjoKICAgICMgYWxsb3cgZm9yIGNyZWF0aW5nIHVuZ2F0aGVyZWQgZmlsZXMuCiAgICAjIENoZWNrIGlmIGRhdGFiYXNlIGhhcyBiZWVuIGdhdGhlcmVkLCBpZiBub3QsIGNyZWF0ZSBvbmUgaW4gbWVtb3J5CgogICAgIyB1bnRpbCBubyBmaWxlcyBoYXZlIGJlZW4gY2hlY2tlZCwgYXNzdW1lIG5vbmUgaGF2ZSBiZWVuIGdhdGhlcmVkCiAgICBnYXRoZXJlZCA9IEZhbHNlCiAgICAjIGNoZWNrIGlmIGFueSBvZiB0aGUgcHJvdmlkZWQgZmlsZXMgaGF2ZSBiZWVuIGdhdGhlcmVkCiAgICBmb3IgZiBpbiBmaWxlbmFtZXM6CiAgICAgICAgZGIgPSBzcWxpdGUzLmNvbm5lY3QoZikKICAgICAgICBjdXIgPSBkYi5jdXJzb3IoKQoKICAgICAgICAjIGdldCBhIGxpc3Qgb2YgdGFibGVzIHdpdGggdGhlIG5hbWUgb2YgJ3J1bnMnCiAgICAgICAgcmVzID0gbGlzdChjdXIuZXhlY3V0ZSgiIiIKICAgICAgICAgICAgICAgICAgICAgICAgICAgIFNFTEVDVCBuYW1lCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBGUk9NIHNxbGl0ZV9tYXN0ZXIKICAgICAgICAgICAgICAgICAgICAgICAgICAgIFdIRVJFIHR5cGU9J3RhYmxlJyBBTkQgbmFtZT0ncnVucycKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIiIiKSkKICAgICAgICAjIHRoZXJlIGV4aXN0cyBhIHRhYmxlIHdpdGggdGhlIG5hbWUgb2YgJ3J1bnMnCiAgICAgICAgaWYgbGVuKHJlcykgPT0gMToKICAgICAgICAgICAgZ2F0aGVyZWQgPSBUcnVlCgogICAgaWYgZ2F0aGVyZWQ6CiAgICAgICAgIyBnYXRoZXJlZCBmaWxlcyBzaG91bGQgb25seSBoYXZlIG9uZSBmaWxlCiAgICAgICAgaWYgbGVuKGZpbGVuYW1lcykgPiAxOgogICAgICAgICAgICByYWlzZSBFeGNlcHRpb24oIlJ1bmFseXppbmcgbXVsdGlwbGUgZ2F0aGVyZWQgZmlsZXMgaXMgbm90IHN1cHBvcnRlZCEhISIpCgogICAgICAgIHJldHVybiBzcWxpdGUzLmNvbm5lY3QoZmlsZW5hbWVzWzBdKQoKICAgICMgY3JlYXRlIGluIG1lbW9yeSBkYXRhYmFzZSBvZiBmaWxlcyB0byBiZSBnYXRoZXJlZAogICAgZnJvbSBsb2dweWxlLnJ1bmFseXplcl9nYXRoZXIgaW1wb3J0IChGZWF0dXJlR2F0aGVyZXIsIGdhdGhlcl9tdWx0aV9maWxlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYWtlX25hbWVfbWFwLCBzY2FuKQogICAgcHJpbnQoIkNyZWF0aW5nIGFuIGluIG1lbW9yeSBkYXRhYmFzZSBmcm9tIHByb3ZpZGVkIGZpbGVzIikKICAgIGZyb20gb3MucGF0aCBpbXBvcnQgZXhpc3RzCiAgICBpbmZpbGVzID0gW2YgZm9yIGYgaW4gZmlsZW5hbWVzIGlmIGV4aXN0cyhmKV0KICAgICMgbGlzdCBvZiBydW4gZmVhdHVyZXMgYXMge25hbWU6IHNxbF90eXBlfQogICAgZmcgPSBGZWF0dXJlR2F0aGVyZXIoRmFsc2UsIE5vbmUpCiAgICBmZWF0dXJlcywgZGJuYW1lX3RvX3J1bl9pZCA9IHNjYW4oZmcsIGluZmlsZXMpCgogICAgZm1hcCA9IG1ha2VfbmFtZV9tYXAoIiIpCiAgICBxbWFwID0gbWFrZV9uYW1lX21hcCgiIikKCiAgICBjb25uZWN0aW9uID0gZ2F0aGVyX211bHRpX2ZpbGUoIjptZW1vcnk6IiwgaW5maWxlcywgZm1hcCwgcW1hcCwgZmcsIGZlYXR1cmVzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRibmFtZV90b19ydW5faWQpCiAgICByZXR1cm4gY29ubmVjdGlvbgoKCiMge3t7IG1haW4gcHJvZ3JhbQoKZGVmIG1ha2Vfd3JhcHBlZF9kYihmaWxlbmFtZXM6IExpc3Rbc3RyXSwgaW50ZXJhY3RpdmU6IGJvb2wsIG1hbmdsZTogYm9vbCkgLT4gUnVuREI6CiAgICBkYiA9IGF1dG9fZ2F0aGVyKGZpbGVuYW1lcykKICAgIGRiLmNyZWF0ZV9hZ2dyZWdhdGUoInN0ZGRldiIsIDEsIFN0ZERldmlhdGlvbikgICMgdHlwZTogaWdub3JlW2FyZy10eXBlXQogICAgZGIuY3JlYXRlX2FnZ3JlZ2F0ZSgidmFyIiwgMSwgVmFyaWFuY2UpCiAgICBkYi5jcmVhdGVfYWdncmVnYXRlKCJub3JtMSIsIDEsIE5vcm0xKSAgIyB0eXBlOiBpZ25vcmVbYXJnLXR5cGVdCiAgICBkYi5jcmVhdGVfYWdncmVnYXRlKCJub3JtMiIsIDEsIE5vcm0yKSAgIyB0eXBlOiBpZ25vcmVbYXJnLXR5cGVdCgogICAgZGIuY3JlYXRlX2Z1bmN0aW9uKCJzcHJpbnRmIiwgMiwgbXlfc3ByaW50ZikKICAgIGZyb20gbWF0aCBpbXBvcnQgcG93LCBzcXJ0CiAgICBkYi5jcmVhdGVfZnVuY3Rpb24oInNxcnQiLCAxLCBzcXJ0KQogICAgZGIuY3JlYXRlX2Z1bmN0aW9uKCJwb3ciLCAyLCBwb3cpCgogICAgaWYgbWFuZ2xlOgogICAgICAgIGRiX3dyYXBfY2xhc3M6IFR5cGVbUnVuREJdID0gTWFnaWNSdW5EQgogICAgZWxzZToKICAgICAgICBkYl93cmFwX2NsYXNzID0gUnVuREIKCiAgICByZXR1cm4gZGJfd3JhcF9jbGFzcyhkYiwgaW50ZXJhY3RpdmU9aW50ZXJhY3RpdmUpCgojIH19fQoKIyB2aW06IGZvbGRtZXRob2Q9bWFya2VyCg==" runalyzer_gather_py_file = "aW1wb3J0IHJlCmltcG9ydCBzcWxpdGUzCgpib29sX2ZlYXRfcmUgPSByZS5jb21waWxlKHIiXihbYS16XSspKFRydWV8RmFsc2UpJCIpCmludF9mZWF0X3JlID0gcmUuY29tcGlsZShyIl4oW2Etel0rKShbMC05XSspJCIpCnJlYWxfZmVhdF9yZSA9IHJlLmNvbXBpbGUociJeKFthLXpdKykoWzAtOV0rXC4/WzAtOV0qKSQiKQpzdHJfZmVhdF9yZSA9IHJlLmNvbXBpbGUociJeKFthLXpdKykoW0EtWl1bQS1aYS16XzAtOV0rKSQiKQoKZnJvbSBzcWxpdGUzIGltcG9ydCBDb25uZWN0aW9uCmZyb20gdHlwaW5nIGltcG9ydCBBbnksIERpY3QsIExpc3QsIE9wdGlvbmFsLCBUdXBsZSwgVW5pb24sIGNhc3QKCmZyb20gcHl0b29scy5kYXRhdGFibGUgaW1wb3J0IERhdGFUYWJsZQoKZnJvbSBsb2dweWxlIGltcG9ydCBMb2dNYW5hZ2VyCgpzcWxpdGVfa2V5d29yZHMgPSAiIiIKICAgIGFib3J0IGFjdGlvbiBhZGQgYWZ0ZXIgYWxsIGFsdGVyIGFuYWx5emUgYW5kIGFzIGFzYyBhdHRhY2gKICAgIGF1dG9pbmNyZW1lbnQgYmVmb3JlIGJlZ2luIGJldHdlZW4gYnkgY2FzY2FkZSBjYXNlIGNhc3QgY2hlY2sKICAgIGNvbGxhdGUgY29sdW1uIGNvbW1pdCBjb25mbGljdCBjb25zdHJhaW50IGNyZWF0ZSBjcm9zcyBjdXJyZW50X2RhdGUKICAgIGN1cnJlbnRfdGltZSBjdXJyZW50X3RpbWVzdGFtcCBkYXRhYmFzZSBkZWZhdWx0IGRlZmVycmFibGUgZGVmZXJyZWQKICAgIGRlbGV0ZSBkZXNjIGRldGFjaCBkaXN0aW5jdCBkcm9wIGVhY2ggZWxzZSBlbmQgZXNjYXBlIGV4Y2VwdAogICAgZXhjbHVzaXZlIGV4aXN0cyBleHBsYWluIGZhaWwgZm9yIGZvcmVpZ24gZnJvbSBmdWxsIGdsb2IgZ3JvdXAKICAgIGhhdmluZyBpZiBpZ25vcmUgaW1tZWRpYXRlIGluIGluZGV4IGluZGV4ZWQgaW5pdGlhbGx5IGlubmVyIGluc2VydAogICAgaW5zdGVhZCBpbnRlcnNlY3QgaW50byBpcyBpc251bGwgam9pbiBrZXkgbGVmdCBsaWtlIGxpbWl0IG1hdGNoCiAgICBuYXR1cmFsIG5vIG5vdCBub3RudWxsIG51bGwgb2Ygb2Zmc2V0IG9uIG9yIG9yZGVyIG91dGVyIHBsYW4gcHJhZ21hCiAgICBwcmltYXJ5IHF1ZXJ5IHJhaXNlIHJlZmVyZW5jZXMgcmVnZXhwIHJlaW5kZXggcmVsZWFzZSByZW5hbWUKICAgIHJlcGxhY2UgcmVzdHJpY3QgcmlnaHQgcm9sbGJhY2sgcm93IHNhdmVwb2ludCBzZWxlY3Qgc2V0IHRhYmxlIHRlbXAKICAgIHRlbXBvcmFyeSB0aGVuIHRvIHRyYW5zYWN0aW9uIHRyaWdnZXIgdW5pb24gdW5pcXVlIHVwZGF0ZSB1c2luZwogICAgdmFjdXVtIHZhbHVlcyB2aWV3IHZpcnR1YWwgd2hlbiB3aGVyZSIiIi5zcGxpdCgpCgoKZGVmIHBhcnNlX2Rpcl9mZWF0dXJlKGZlYXQ6IHN0ciwgbnVtYmVyOiBpbnQpIFwKICAgICAgICAgICAgICAgICAgICAgICAgLT4gVHVwbGVbVW5pb25bc3RyLCBBbnldLCBzdHIsIFVuaW9uW3N0ciwgQW55XV06CiAgICBib29sX21hdGNoID0gYm9vbF9mZWF0X3JlLm1hdGNoKGZlYXQpCiAgICBpZiBib29sX21hdGNoIGlzIG5vdCBOb25lOgogICAgICAgIHJldHVybiAoYm9vbF9tYXRjaC5ncm91cCgxKSwgImludGVnZXIiLCBpbnQoYm9vbF9tYXRjaC5ncm91cCgyKSA9PSAiVHJ1ZSIpKQogICAgaW50X21hdGNoID0gaW50X2ZlYXRfcmUubWF0Y2goZmVhdCkKICAgIGlmIGludF9tYXRjaCBpcyBub3QgTm9uZToKICAgICAgICByZXR1cm4gKGludF9tYXRjaC5ncm91cCgxKSwgImludGVnZXIiLCBmbG9hdChpbnRfbWF0Y2guZ3JvdXAoMikpKQogICAgcmVhbF9tYXRjaCA9IHJlYWxfZmVhdF9yZS5tYXRjaChmZWF0KQogICAgaWYgcmVhbF9tYXRjaCBpcyBub3QgTm9uZToKICAgICAgICByZXR1cm4gKHJlYWxfbWF0Y2guZ3JvdXAoMSksICJyZWFsIiwgZmxvYXQocmVhbF9tYXRjaC5ncm91cCgyKSkpCiAgICBzdHJfbWF0Y2ggPSBzdHJfZmVhdF9yZS5tYXRjaChmZWF0KQogICAgaWYgc3RyX21hdGNoIGlzIG5vdCBOb25lOgogICAgICAgIHJldHVybiAoc3RyX21hdGNoLmdyb3VwKDEpLCAidGV4dCIsIHN0cl9tYXRjaC5ncm91cCgyKSkKICAgIHJldHVybiAoImRpcmZlYXQlZCIgJSBudW1iZXIsICJ0ZXh0IiwgZmVhdCkKCgpkZWYgbGFyZ2VyX3NxbF90eXBlKHR5cGVfYTogT3B0aW9uYWxbc3RyXSwgdHlwZV9iOiBPcHRpb25hbFtzdHJdKSAtPiBPcHRpb25hbFtzdHJdOgogICAgYXNzZXJ0IHR5cGVfYSBpbiBbTm9uZSwgInRleHQiLCAicmVhbCIsICJpbnRlZ2VyIl0KICAgIGFzc2VydCB0eXBlX2IgaW4gW05vbmUsICJ0ZXh0IiwgInJlYWwiLCAiaW50ZWdlciJdCgogICAgaWYgdHlwZV9hIGlzIE5vbmU6CiAgICAgICAgcmV0dXJuIHR5cGVfYgogICAgaWYgdHlwZV9iIGlzIE5vbmU6CiAgICAgICAgcmV0dXJuIHR5cGVfYQogICAgaWYgInRleHQiIGluIFt0eXBlX2EsIHR5cGVfYl06CiAgICAgICAgcmV0dXJuICJ0ZXh0IgogICAgaWYgInJlYWwiIGluIFt0eXBlX2EsIHR5cGVfYl06CiAgICAgICAgcmV0dXJuICJyZWFsIgogICAgYXNzZXJ0IHR5cGVfYSA9PSB0eXBlX2IgPT0gImludGVnZXIiCiAgICByZXR1cm4gImludGVnZXIiCgoKZGVmIHNxbF90eXBlX2FuZF92YWx1ZSh2YWx1ZTogQW55KSBcCiAgICAgICAgICAgICAgICAgICAgICAgIC0+IFR1cGxlW09wdGlvbmFsW3N0cl0sIFVuaW9uW2ludCwgZmxvYXQsIHN0ciwgTm9uZV1dOgogICAgaWYgdmFsdWUgaXMgTm9uZToKICAgICAgICByZXR1cm4gTm9uZSwgTm9uZQogICAgZWxpZiBpc2luc3RhbmNlKHZhbHVlLCBib29sKToKICAgICAgICByZXR1cm4gImludGVnZXIiLCBpbnQodmFsdWUpCiAgICBlbGlmIGlzaW5zdGFuY2UodmFsdWUsIGludCk6CiAgICAgICAgcmV0dXJuICJpbnRlZ2VyIiwgdmFsdWUKICAgIGVsaWYgaXNpbnN0YW5jZSh2YWx1ZSwgZmxvYXQpOgogICAgICAgIHJldHVybiAicmVhbCIsIHZhbHVlCiAgICBlbHNlOgogICAgICAgIHJldHVybiAidGV4dCIsIHN0cih2YWx1ZSkKCgpkZWYgc3FsX3R5cGVfYW5kX3ZhbHVlX2Zyb21fc3RyKHZhbHVlOiBzdHIpIFwKICAgICAgICAgICAgICAgICAgICAgICAgLT4gVHVwbGVbT3B0aW9uYWxbc3RyXSwgVW5pb25baW50LCBmbG9hdCwgc3RyLCBOb25lXV06CiAgICBpZiB2YWx1ZSA9PSAiTm9uZSI6CiAgICAgICAgcmV0dXJuIE5vbmUsIE5vbmUKICAgIGVsaWYgdmFsdWUgaW4gWyJUcnVlIiwgIkZhbHNlIl06CiAgICAgICAgcmV0dXJuICJpbnRlZ2VyIiwgdmFsdWUgPT0gIlRydWUiCiAgICBlbHNlOgogICAgICAgIHRyeToKICAgICAgICAgICAgcmV0dXJuICJpbnRlZ2VyIiwgaW50KHZhbHVlKQogICAgICAgIGV4Y2VwdCBWYWx1ZUVycm9yOgogICAgICAgICAgICBwYXNzCiAgICAgICAgdHJ5OgogICAgICAgICAgICByZXR1cm4gInJlYWwiLCBmbG9hdCh2YWx1ZSkKICAgICAgICBleGNlcHQgVmFsdWVFcnJvcjoKICAgICAgICAgICAgcGFzcwogICAgICAgIHJldHVybiAidGV4dCIsIHN0cih2YWx1ZSkKCgpjbGFzcyBGZWF0dXJlR2F0aGVyZXI6CiAgICBkZWYgX19pbml0X18oc2VsZiwgZmVhdHVyZXNfZnJvbV9kaXI6IGJvb2wgPSBGYWxzZSwKICAgICAgICAgICAgICAgICBmZWF0dXJlc19maWxlOiBPcHRpb25hbFtzdHJdID0gTm9uZSkgLT4gTm9uZToKICAgICAgICBzZWxmLmZlYXR1cmVzX2Zyb21fZGlyID0gZmVhdHVyZXNfZnJvbV9kaXIKCiAgICAgICAgc2VsZi5kaXJfdG9fZmVhdHVyZXMgPSB7fQogICAgICAgIGlmIGZlYXR1cmVzX2ZpbGUgaXMgbm90IE5vbmU6CiAgICAgICAgICAgIGZvciBsaW5lIGluIG9wZW4oZmVhdHVyZXNfZmlsZSkucmVhZGxpbmVzKCk6CiAgICAgICAgICAgICAgICBjb2xvbl9pZHggPSBsaW5lLmZpbmQoIjoiKQogICAgICAgICAgICAgICAgYXNzZXJ0IGNvbG9uX2lkeCAhPSAtMQoKICAgICAgICAgICAgICAgIGVudHJpZXMgPSBbdmFsLnN0cmlwKCkgZm9yIHZhbCBpbiBsaW5lW2NvbG9uX2lkeCsxOl0uc3BsaXQoIiwiKV0KICAgICAgICAgICAgICAgIGZlYXR1cmVzID0gW10KICAgICAgICAgICAgICAgIGZvciBlbnRyeSBpbiBlbnRyaWVzOgogICAgICAgICAgICAgICAgICAgIGVxdWFsX2lkeCA9IGVudHJ5LmZpbmQoIj0iKQogICAgICAgICAgICAgICAgICAgIGFzc2VydCBlcXVhbF9pZHggIT0gLTEKICAgICAgICAgICAgICAgICAgICBmZWF0dXJlcy5hcHBlbmQoKGVudHJ5WzplcXVhbF9pZHhdLCkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICsgc3FsX3R5cGVfYW5kX3ZhbHVlX2Zyb21fc3RyKGVudHJ5W2VxdWFsX2lkeCsxOl0pKQoKICAgICAgICAgICAgICAgIHNlbGYuZGlyX3RvX2ZlYXR1cmVzW2xpbmVbOmNvbG9uX2lkeF1dID0gZmVhdHVyZXMKCiAgICBkZWYgZ2V0X2RiX2ZlYXR1cmVzKHNlbGYsIGRibmFtZTogc3RyLCBsb2dtZ3I6IExvZ01hbmFnZXIpIC0+IExpc3RbQW55XToKICAgICAgICBmcm9tIG9zLnBhdGggaW1wb3J0IGRpcm5hbWUKICAgICAgICBkbiA9IGRpcm5hbWUoZGJuYW1lKQoKICAgICAgICBmZWF0dXJlcyA9IHNlbGYuZGlyX3RvX2ZlYXR1cmVzLmdldChkbiwgW10pWzpdCgogICAgICAgIGlmIHNlbGYuZmVhdHVyZXNfZnJvbV9kaXI6CiAgICAgICAgICAgIGZlYXR1cmVzLmV4dGVuZChwYXJzZV9kaXJfZmVhdHVyZShmZWF0LCBpKQogICAgICAgICAgICAgICAgICAgIGZvciBpLCBmZWF0IGluIGVudW1lcmF0ZShkbi5zcGxpdCgiLSIpKSkKCiAgICAgICAgZm9yIG5hbWUsIHZhbHVlIGluIGxvZ21nci5jb25zdGFudHMuaXRlbXMoKToKICAgICAgICAgICAgZmVhdHVyZXMuYXBwZW5kKChuYW1lLCkgKyBzcWxfdHlwZV9hbmRfdmFsdWUodmFsdWUpKQoKICAgICAgICByZXR1cm4gZmVhdHVyZXMKCgpkZWYgc2NhbihmZzogRmVhdHVyZUdhdGhlcmVyLCBkYm5hbWVzOiBMaXN0W3N0cl0sIHByb2dyZXNzOiBib29sID0gVHJ1ZSkgXAogICAgICAgICAgICAtPiBUdXBsZVtEaWN0W3N0ciwgQW55XSwgRGljdFtzdHIsIGludF1dOgogICAgZmVhdHVyZXM6IERpY3Rbc3RyLCBBbnldID0ge30KICAgIGRibmFtZV90b19ydW5faWQgPSB7fQogICAgdWlkX3RvX3J1bl9pZDogRGljdFtzdHIsIGludF0gPSB7fQogICAgbmV4dF9ydW5faWQgPSAxCgogICAgZnJvbSBweXRvb2xzIGltcG9ydCBQcm9ncmVzc0JhcgogICAgaWYgcHJvZ3Jlc3M6CiAgICAgICAgcGIgPSBQcm9ncmVzc0JhcigiU2Nhbm5pbmcuLi4iLCAgIyB0eXBlOiBpZ25vcmVbbm8tdW50eXBlZC1jYWxsXQogICAgICAgICAgICAgICAgICAgICAgICAgbGVuKGRibmFtZXMpKQoKICAgIGZvciBkYm5hbWUgaW4gZGJuYW1lczoKICAgICAgICB0cnk6CiAgICAgICAgICAgIGxvZ21nciA9IExvZ01hbmFnZXIoZGJuYW1lLCAiciIpCiAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbjoKICAgICAgICAgICAgcHJpbnQoIlRyb3VibGUgd2l0aCBmaWxlICclcyciICUgZGJuYW1lKQogICAgICAgICAgICByYWlzZQoKICAgICAgICB1bmlxdWVfcnVuX2lkID0gY2FzdChzdHIsIGxvZ21nci5jb25zdGFudHMuZ2V0KCJ1bmlxdWVfcnVuX2lkIikpCiAgICAgICAgcnVuX2lkID0gdWlkX3RvX3J1bl9pZC5nZXQodW5pcXVlX3J1bl9pZCkKCiAgICAgICAgaWYgcnVuX2lkIGlzIE5vbmU6CiAgICAgICAgICAgIHJ1bl9pZCA9IG5leHRfcnVuX2lkCiAgICAgICAgICAgIG5leHRfcnVuX2lkICs9IDEKCiAgICAgICAgICAgIGlmIHVuaXF1ZV9ydW5faWQgaXMgbm90IE5vbmU6CiAgICAgICAgICAgICAgICB1aWRfdG9fcnVuX2lkW3VuaXF1ZV9ydW5faWRdID0gcnVuX2lkCgogICAgICAgIGRibmFtZV90b19ydW5faWRbZGJuYW1lXSA9IHJ1bl9pZAoKICAgICAgICBpZiBwcm9ncmVzczoKICAgICAgICAgICAgcGIucHJvZ3Jlc3MoKSAgIyB0eXBlOiBpZ25vcmVbbm8tdW50eXBlZC1jYWxsXQoKICAgICAgICBmb3IgZm5hbWUsIGZ0eXBlLCBmdmFsdWUgaW4gZmcuZ2V0X2RiX2ZlYXR1cmVzKGRibmFtZSwgbG9nbWdyKToKICAgICAgICAgICAgaWYgZm5hbWUgaW4gZmVhdHVyZXM6CiAgICAgICAgICAgICAgICBmZWF0dXJlc1tmbmFtZV0gPSBsYXJnZXJfc3FsX3R5cGUoZnR5cGUsIGZlYXR1cmVzW2ZuYW1lXSkKICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgIGlmIGZ0eXBlIGlzIE5vbmU6CiAgICAgICAgICAgICAgICAgICAgZnR5cGUgPSAidGV4dCIKICAgICAgICAgICAgICAgIGZlYXR1cmVzW2ZuYW1lXSA9IGZ0eXBlCgogICAgICAgIGxvZ21nci5jbG9zZSgpCgogICAgaWYgcHJvZ3Jlc3M6CiAgICAgICAgcGIuZmluaXNoZWQoKSAgIyB0eXBlOiBpZ25vcmVbbm8tdW50eXBlZC1jYWxsXQoKICAgIHJldHVybiBmZWF0dXJlcywgZGJuYW1lX3RvX3J1bl9pZAoKCmRlZiBtYWtlX25hbWVfbWFwKG1hcF9zdHI6IHN0cikgLT4gRGljdFtzdHIsIHN0cl06CiAgICBpbXBvcnQgcmUKICAgIHJlc3VsdDogRGljdFtzdHIsIHN0cl0gPSB7fQoKICAgIGlmIG5vdCBtYXBfc3RyOgogICAgICAgIHJldHVybiByZXN1bHQKCiAgICBtYXBfcmUgPSByZS5jb21waWxlKHIiXihbYS16X0EtWjAtOV0rKT0oW2Etel9BLVowLTldKykkIikKICAgIGZvciBmbWFwX2VudHJ5IGluIG1hcF9zdHIuc3BsaXQoIiwiKToKICAgICAgICBtYXRjaCA9IG1hcF9yZS5tYXRjaChmbWFwX2VudHJ5KQogICAgICAgIGlmIG5vdCAobWF0Y2ggYW5kIG1hdGNoLmdyb3VwKDEpIGFuZCBtYXRjaC5ncm91cCgyKSk6CiAgICAgICAgICAgIHJhaXNlIFJ1bnRpbWVFcnJvcigKICAgICAgICAgICAgICAgICAgICAiQXJndW1lbnRzIHRvIC1tIHNob3VsZCBoYXZlIHRoZSBmb3JtIEYxPUZOQU1FMSxGMj1GTkFNRTIsLi4uIikKICAgICAgICByZXN1bHRbbWF0Y2guZ3JvdXAoMSldID0gbWF0Y2guZ3JvdXAoMikKCiAgICByZXR1cm4gcmVzdWx0CgoKZGVmIF9ub3JtYWxpemVfdHlwZXMoeDogQW55KSAtPiBBbnk6CiAgICAjIGdldCByaWQgb2YgbnVtcHkgdHlwZXMKICAgIGlmIGlzaW5zdGFuY2UoeCwgaW50KToKICAgICAgICByZXR1cm4gaW50KHgpCiAgICBpZiBpc2luc3RhbmNlKHgsIGZsb2F0KToKICAgICAgICByZXR1cm4gZmxvYXQoeCkKICAgIHJldHVybiB4CgoKZGVmIGdhdGhlcl9tdWx0aV9maWxlKG91dGZpbGU6IHN0ciwgaW5maWxlczogTGlzdFtzdHJdLCBmbWFwOiBEaWN0W3N0ciwgc3RyXSwKICAgICAgICAgICAgICAgICAgICAgIHFtYXA6IERpY3Rbc3RyLCBzdHJdLCBmZzogRmVhdHVyZUdhdGhlcmVyLAogICAgICAgICAgICAgICAgICAgICAgZmVhdHVyZXM6IERpY3Rbc3RyLCBBbnldLAogICAgICAgICAgICAgICAgICAgICAgZGJuYW1lX3RvX3J1bl9pZDogRGljdFtzdHIsIGludF0pIC0+IHNxbGl0ZTMuQ29ubmVjdGlvbjoKICAgIGZyb20gcHl0b29scyBpbXBvcnQgUHJvZ3Jlc3NCYXIKICAgIHBiID0gUHJvZ3Jlc3NCYXIoIkltcG9ydGluZy4uLiIsIGxlbihpbmZpbGVzKSkgICMgdHlwZTogaWdub3JlW25vLXVudHlwZWQtY2FsbF0KCiAgICBmZWF0dXJlX2NvbF9uYW1lX21hcCA9IHt9CiAgICBmb3IgZm5hbWUgaW4gZmVhdHVyZXM6CiAgICAgICAgdGd0X25hbWUgPSBmbWFwLmdldChmbmFtZSwgZm5hbWUpCgogICAgICAgIGlmIHRndF9uYW1lLmxvd2VyKCkgaW4gc3FsaXRlX2tleXdvcmRzOgogICAgICAgICAgICBmZWF0dXJlX2NvbF9uYW1lX21hcFtmbmFtZV0gPSB0Z3RfbmFtZSsiXyIKICAgICAgICBlbHNlOgogICAgICAgICAgICBmZWF0dXJlX2NvbF9uYW1lX21hcFtmbmFtZV0gPSB0Z3RfbmFtZQoKICAgIGltcG9ydCBzcWxpdGUzCiAgICBkYl9jb25uID0gc3FsaXRlMy5jb25uZWN0KG91dGZpbGUpCiAgICBydW5fY29sdW1ucyA9IFsKICAgICAgICAgICAgImlkIGludGVnZXIgcHJpbWFyeSBrZXkiLAogICAgICAgICAgICAiZGlybmFtZSB0ZXh0IiwKICAgICAgICAgICAgImZpbGVuYW1lIHRleHQiLAogICAgICAgICAgICBdICsgWyJ7fSB7fSIuZm9ybWF0KGZlYXR1cmVfY29sX25hbWVfbWFwW2ZuYW1lXSwgZnR5cGUpCiAgICAgICAgICAgICAgICAgICAgZm9yIGZuYW1lLCBmdHlwZSBpbiBmZWF0dXJlcy5pdGVtcygpXQogICAgZGJfY29ubi5leGVjdXRlKCJjcmVhdGUgdGFibGUgcnVucyAoJXMpIiAlICIsIi5qb2luKHJ1bl9jb2x1bW5zKSkKICAgIGRiX2Nvbm4uZXhlY3V0ZSgiY3JlYXRlIGluZGV4IHJ1bnNfaWQgb24gcnVucyAoaWQpIikKCiAgICAjIENhdmVhdDogdGhlIG5leHQgdGhyZWUgdGFibGVzIG5lZWQgdG8gbWF0Y2ggdGhlIHRhYmxlcyBpbiBfc2V0X3VwX3NjaGVtYSwKICAgICMgcGx1cyB0aGUgJ2lkJy8ncnVuX2lkJyBjb2x1bW5zLgogICAgZGJfY29ubi5leGVjdXRlKCIiImNyZWF0ZSB0YWJsZSBxdWFudGl0aWVzICgKICAgICAgICAgICAgaWQgaW50ZWdlciBwcmltYXJ5IGtleSwKICAgICAgICAgICAgbmFtZSB0ZXh0LAogICAgICAgICAgICB1bml0IHRleHQsCiAgICAgICAgICAgIGRlc2NyaXB0aW9uIHRleHQsCiAgICAgICAgICAgIHJhbmtfYWdncmVnYXRvciB0ZXh0CiAgICAgICAgICAgICkiIiIpCgogICAgZGJfY29ubi5leGVjdXRlKCIiIgogICAgICBjcmVhdGUgdGFibGUgd2FybmluZ3MgKAogICAgICAgIHJ1bl9pZCBpbnRlZ2VyLAogICAgICAgIHJhbmsgaW50ZWdlciwKICAgICAgICBzdGVwIGludGVnZXIsCiAgICAgICAgdW5peHRpbWUgaW50ZWdlciwKICAgICAgICBtZXNzYWdlIHRleHQsCiAgICAgICAgY2F0ZWdvcnkgdGV4dCwKICAgICAgICBmaWxlbmFtZSB0ZXh0LAogICAgICAgIGxpbmVubyBpbnRlZ2VyCiAgICAgICAgKSIiIikKCiAgICBkYl9jb25uLmV4ZWN1dGUoIiIiCiAgICAgIGNyZWF0ZSB0YWJsZSBsb2dnaW5nICgKICAgICAgICBydW5faWQgaW50ZWdlciwKICAgICAgICByYW5rIGludGVnZXIsCiAgICAgICAgc3RlcCBpbnRlZ2VyLAogICAgICAgIHVuaXh0aW1lIGludGVnZXIsCiAgICAgICAgbGV2ZWwgdGV4dCwKICAgICAgICBtZXNzYWdlIHRleHQsCiAgICAgICAgZmlsZW5hbWUgdGV4dCwKICAgICAgICBsaW5lbm8gaW50ZWdlcgogICAgICAgICkiIiIpCgogICAgY3JlYXRlZF90YWJsZXMgPSBzZXQoKQoKICAgIGZyb20gb3MucGF0aCBpbXBvcnQgYmFzZW5hbWUsIGRpcm5hbWUKCiAgICB3cml0dGVuX3J1bl9pZHMgPSBzZXQoKQoKICAgIGZvciBkYm5hbWUgaW4gaW5maWxlczoKICAgICAgICBwYi5wcm9ncmVzcygpICAjIHR5cGU6IGlnbm9yZVtuby11bnR5cGVkLWNhbGxdCgogICAgICAgIHJ1bl9pZCA9IGRibmFtZV90b19ydW5faWRbZGJuYW1lXQoKICAgICAgICBsb2dtZ3IgPSBMb2dNYW5hZ2VyKGRibmFtZSwgInIiKQoKICAgICAgICBpZiBydW5faWQgbm90IGluIHdyaXR0ZW5fcnVuX2lkczoKICAgICAgICAgICAgZGJmZWF0dXJlcyA9IGZnLmdldF9kYl9mZWF0dXJlcyhkYm5hbWUsIGxvZ21ncikKICAgICAgICAgICAgcXJ5ID0gImluc2VydCBpbnRvIHJ1bnMgKHt9KSB2YWx1ZXMgKHt9KSIuZm9ybWF0KAogICAgICAgICAgICAgICAgIiwiLmpvaW4oWyJpZCIsICJkaXJuYW1lIiwgImZpbGVuYW1lIl0KICAgICAgICAgICAgICAgICAgICArIFtmZWF0dXJlX2NvbF9uYW1lX21hcFtmWzBdXSBmb3IgZiBpbiBkYmZlYXR1cmVzXSksCiAgICAgICAgICAgICAgICAiLCIuam9pbigiPyIgKiAobGVuKGRiZmVhdHVyZXMpKzMpKSkKICAgICAgICAgICAgZGJfY29ubi5leGVjdXRlKHFyeSwKICAgICAgICAgICAgICAgICAgICBbcnVuX2lkLCBkaXJuYW1lKGRibmFtZSksIGJhc2VuYW1lKGRibmFtZSldCiAgICAgICAgICAgICAgICAgICAgKyBbX25vcm1hbGl6ZV90eXBlcyhmWzJdKSBmb3IgZiBpbiBkYmZlYXR1cmVzXSkKCiAgICAgICAgICAgIHdyaXR0ZW5fcnVuX2lkcy5hZGQocnVuX2lkKQoKICAgICAgICBkZWYgdHJhbnNmZXJfZGF0YV90YWJsZV9tdWx0aShkYl9jb25uOiBDb25uZWN0aW9uLCB0YmxfbmFtZTogc3RyLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGFfdGFibGU6IERhdGFUYWJsZSkgLT4gTm9uZToKICAgICAgICAgICAgbXlfZGF0YSA9IFsocnVuX2lkLCkrZCBmb3IgZCBpbiBkYXRhX3RhYmxlLmRhdGFdCgogICAgICAgICAgICBkYl9jb25uLmV4ZWN1dGVtYW55KGYiaW5zZXJ0IGludG8ge3RibF9uYW1lfSAoJXMpIHZhbHVlcyAoJXMpIiAlCiAgICAgICAgICAgICAgICAoInJ1bl9pZCwiCiAgICAgICAgICAgICAgICAgICAgKyAiLCAiLmpvaW4oZGF0YV90YWJsZS5jb2x1bW5fbmFtZXMpLAogICAgICAgICAgICAgICAgICAgICIsICIuam9pbigiPyIgKiAobGVuKGRhdGFfdGFibGUuY29sdW1uX25hbWVzKSsxKSkpLAogICAgICAgICAgICAgICAgbXlfZGF0YSkKCiAgICAgICAgdHJhbnNmZXJfZGF0YV90YWJsZV9tdWx0aShkYl9jb25uLCAid2FybmluZ3MiLCBsb2dtZ3IuZ2V0X3dhcm5pbmdzKCkpCiAgICAgICAgdHJhbnNmZXJfZGF0YV90YWJsZV9tdWx0aShkYl9jb25uLCAibG9nZ2luZyIsIGxvZ21nci5nZXRfbG9nZ2luZygpKQoKICAgICAgICBmb3IgcW5hbWUsIHFkYXQgaW4gbG9nbWdyLnF1YW50aXR5X2RhdGEuaXRlbXMoKToKICAgICAgICAgICAgdGd0X3FuYW1lID0gcW1hcC5nZXQocW5hbWUsIHFuYW1lKQoKICAgICAgICAgICAgaWYgdGd0X3FuYW1lIG5vdCBpbiBjcmVhdGVkX3RhYmxlczoKICAgICAgICAgICAgICAgIGNyZWF0ZWRfdGFibGVzLmFkZCh0Z3RfcW5hbWUpCiAgICAgICAgICAgICAgICBkYl9jb25uLmV4ZWN1dGUoImNyZWF0ZSB0YWJsZSAlcyAoIgogICAgICAgICAgICAgICAgICAicnVuX2lkIGludGVnZXIsIHN0ZXAgaW50ZWdlciwgcmFuayBpbnRlZ2VyLCB2YWx1ZSByZWFsKSIKICAgICAgICAgICAgICAgICAgJSB0Z3RfcW5hbWUpCgogICAgICAgICAgICAgICAgZGJfY29ubi5leGVjdXRlKAogICAgICAgICAgICAgICAgICAgICAgICAiY3JlYXRlIGluZGV4IHt9X21haW4gb24ge30gKHJ1bl9pZCxzdGVwLHJhbmspIgogICAgICAgICAgICAgICAgICAgICAgICAuZm9ybWF0KHRndF9xbmFtZSwgdGd0X3FuYW1lKSkKCiAgICAgICAgICAgICAgICBhZ2cgPSBxZGF0LmRlZmF1bHRfYWdncmVnYXRvcgogICAgICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgICAgIGFnZyA9IGFnZy5fX25hbWVfXyAgIyB0eXBlOiBpZ25vcmVbdW5pb24tYXR0ciwgYXNzaWdubWVudF0KICAgICAgICAgICAgICAgIGV4Y2VwdCBBdHRyaWJ1dGVFcnJvcjoKICAgICAgICAgICAgICAgICAgICBpZiBhZ2cgaXMgbm90IE5vbmU6CiAgICAgICAgICAgICAgICAgICAgICAgIGFnZyA9IHN0cihhZ2cpICAjIHR5cGU6IGlnbm9yZVthc3NpZ25tZW50XQoKICAgICAgICAgICAgICAgIGRiX2Nvbm4uZXhlY3V0ZSgiaW5zZXJ0IGludG8gcXVhbnRpdGllcyAiCiAgICAgICAgICAgICAgICAgICAgICAgICIobmFtZSx1bml0LGRlc2NyaXB0aW9uLHJhbmtfYWdncmVnYXRvcikiCiAgICAgICAgICAgICAgICAgICAgICAgICJ2YWx1ZXMgKD8sPyw/LD8pIiwKICAgICAgICAgICAgICAgICAgICAgICAgKHRndF9xbmFtZSwgcWRhdC51bml0LCBxZGF0LmRlc2NyaXB0aW9uLCBhZ2cpKQoKICAgICAgICAgICAgY3Vyc29yID0gbG9nbWdyLmRiX2Nvbm4uZXhlY3V0ZSgKICAgICAgICAgICAgICAgICAgICBmInNlbGVjdCB7cnVuX2lkfSxzdGVwLHJhbmssdmFsdWUgZnJvbSB7cW5hbWV9IikKICAgICAgICAgICAgZGJfY29ubi5leGVjdXRlbWFueSgiaW5zZXJ0IGludG8gJXMgdmFsdWVzICg/LD8sPyw/KSIgJSB0Z3RfcW5hbWUsCiAgICAgICAgICAgICAgICAgICAgY3Vyc29yKQogICAgICAgIGxvZ21nci5jbG9zZSgpCiAgICBwYi5maW5pc2hlZCgpICAjIHR5cGU6IGlnbm9yZVtuby11bnR5cGVkLWNhbGxdCgogICAgZGJfY29ubi5jb21taXQoKQogICAgcmV0dXJuIGRiX2Nvbm4K" -version_py_file = "VkVSU0lPTiA9ICgyMDIzLCAyLCAzKQpWRVJTSU9OX1NUQVRVUyA9ICIiClZFUlNJT05fVEVYVCA9ICIuIi5qb2luKHN0cih4KSBmb3IgeCBpbiBWRVJTSU9OKSArIFZFUlNJT05fU1RBVFVTCg==" pymbolic_whl_file_str = "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" @@ -349,11 +348,6 @@ py_binary_data = base64.decodebytes(py_base_64) with open("logpyle/runalyzer_gather.py", "wb") as f: f.write(py_binary_data) - # copy version.py - py_base_64 = version_py_file.encode("utf-8") - py_binary_data = base64.decodebytes(py_base_64) - with open("logpyle/version.py", "wb") as f: - f.write(py_binary_data) def clear_term() -> None: diff --git a/logpyle/HTMLalyzer/main.py b/logpyle/HTMLalyzer/main.py index 2dd5060..b345a80 100644 --- a/logpyle/HTMLalyzer/main.py +++ b/logpyle/HTMLalyzer/main.py @@ -27,7 +27,6 @@ def __init__(self, names: list[str]): logpyle_py_file = "$logpyle_py_file" runalyzer_py_file = "$runalyzer_py_file" runalyzer_gather_py_file = "$runalyzer_gather_py_file" -version_py_file = "$version_py_file" pymbolic_whl_file_str = "$pymbolic_whl_file_str" @@ -68,11 +67,6 @@ async def import_logpyle() -> None: py_binary_data = base64.decodebytes(py_base_64) with open("logpyle/runalyzer_gather.py", "wb") as f: f.write(py_binary_data) - # copy version.py - py_base_64 = version_py_file.encode("utf-8") - py_binary_data = base64.decodebytes(py_base_64) - with open("logpyle/version.py", "wb") as f: - f.write(py_binary_data) def clear_term() -> None: From 4b908ba166d5d9ce41caa696efc6db673a45145a Mon Sep 17 00:00:00 2001 From: Matthias Diener Date: Sun, 22 Dec 2024 13:19:51 +0100 Subject: [PATCH 16/25] mypy fixes --- logpyle/__init__.py | 23 ++++++++++++----------- logpyle/runalyzer.py | 1 + 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/logpyle/__init__.py b/logpyle/__init__.py index b942a22..13d450c 100644 --- a/logpyle/__init__.py +++ b/logpyle/__init__.py @@ -414,7 +414,7 @@ class _DependencyData: @dataclass class _WatchInfo: parsed: ExpressionNode - expr: ExpressionNode + expr: str dep_data: list[_DependencyData] compiled: CompiledExpression unit: str | None @@ -885,7 +885,7 @@ def add_watches(self, watches: list[str | tuple[str, str]]) -> None: any(dd.nonlocal_agg for dd in dep_data) from pymbolic import compile - compiled = compile(parsed, [dd.varname for dd in dep_data]) + compiled = compile(parsed, [dd.varname for dd in dep_data]) # type: ignore[no-untyped-call] watch_info = _WatchInfo(parsed=parsed, expr=expr, dep_data=dep_data, compiled=compiled, unit=unit, format=fmt) @@ -1092,14 +1092,14 @@ def add_internal(name: str, unit: str | None, description: str | None, self.save() - def get_expr_dataset(self, expression: ExpressionNode, + def get_expr_dataset(self, expression: str, description: str | None = None, unit: str | None = None) \ -> tuple[str | Any, str | Any, list[tuple[int, Any]]]: """Prepare a time-series dataset for a given expression. - :arg expression: A :mod:`pymbolic` expression that may involve + :arg expression: A :mod:`pymbolic`-like expression that may involve the time-series variables and the constants in this :class:`LogManager`. If there is data from multiple ranks for a quantity occurring in this expression, an aggregator may have to be specified. @@ -1127,7 +1127,7 @@ def get_expr_dataset(self, expression: ExpressionNode, if unit is None: from pymbolic import parse, substitute - unit_dict = {dd.varname: dd.qdat.unit for dd in dep_data} + unit_dict: dict[str, Any] = {dd.varname: dd.qdat.unit for dd in dep_data} from pytools import all if all(v is not None for v in unit_dict.values()): unit_dict = {k: parse(v) for k, v in unit_dict.items()} @@ -1140,7 +1140,7 @@ def get_expr_dataset(self, expression: ExpressionNode, # compile and evaluate from pymbolic import compile - compiled = compile(parsed, [dd.varname for dd in dep_data]) + compiled = compile(parsed, [dd.varname for dd in dep_data]) # type: ignore[no-untyped-call] data = [] @@ -1153,7 +1153,8 @@ def get_expr_dataset(self, expression: ExpressionNode, return (description, unit, data) - def get_joint_dataset(self, expressions: Sequence[ExpressionNode]) -> list[Any]: + def get_joint_dataset(self, expressions: Sequence[str | tuple[str, str, str]]) \ + -> list[Any]: """Return a joint data set for a list of expressions. :arg expressions: a list of either strings representing @@ -1186,7 +1187,7 @@ def get_joint_dataset(self, expressions: Sequence[ExpressionNode]) -> list[Any]: return zipped_dubs - def get_plot_data(self, expr_x: ExpressionNode, expr_y: ExpressionNode, + def get_plot_data(self, expr_x: str, expr_y: str, min_step: int | None = None, max_step: int | None = None) \ -> tuple[tuple[Any, str, str], tuple[Any, str, str]]: @@ -1212,8 +1213,8 @@ def get_plot_data(self, expr_x: ExpressionNode, expr_y: ExpressionNode, return (data_x, descr_x, unit_x), \ (data_y, descr_y, unit_y) - def write_datafile(self, filename: str, expr_x: ExpressionNode, - expr_y: ExpressionNode) -> None: + def write_datafile(self, filename: str, expr_x: str, + expr_y: str) -> None: (data_x, label_x, _), (data_y, label_y, _) = self.get_plot_data( expr_x, expr_y) @@ -1223,7 +1224,7 @@ def write_datafile(self, filename: str, expr_x: ExpressionNode, outf.write(f"{dx!r}\t{dy!r}\n") outf.close() - def plot_matplotlib(self, expr_x: ExpressionNode, expr_y: ExpressionNode) -> None: + def plot_matplotlib(self, expr_x: str, expr_y: str) -> None: from matplotlib.pyplot import plot, xlabel, ylabel (data_x, descr_x, unit_x), (data_y, descr_y, unit_y) = \ diff --git a/logpyle/runalyzer.py b/logpyle/runalyzer.py index 2e502f5..a3e3188 100644 --- a/logpyle/runalyzer.py +++ b/logpyle/runalyzer.py @@ -106,6 +106,7 @@ def plot_cursor(self, cursor: Cursor, labels: list[str] | None = None, # noqa: x, y = list(zip(*list(cursor), strict=False)) p = plot(x, y, *args, **kwargs) + assert p[0].axes if isinstance(labels, list) and len(labels) == 2: p[0].axes.set_xlabel(labels[0]) From e336900762aa58d4657965faf79c371582372da4 Mon Sep 17 00:00:00 2001 From: Matthias Diener Date: Sun, 22 Dec 2024 13:22:29 +0100 Subject: [PATCH 17/25] test 3.13 --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f7c6f0c..9923d7a 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -32,7 +32,7 @@ jobs: strategy: fail-fast: true matrix: - python-version: ['3.10', '3.11', '3.12', '3.x'] + python-version: ['3.10', '3.11', '3.12', '3.13', '3.x'] os: [ubuntu-latest, macos-13] steps: From 40ed7d26333644a6769a01cca10e009b9dd960be Mon Sep 17 00:00:00 2001 From: Matthias Diener Date: Mon, 23 Dec 2024 11:12:41 +0100 Subject: [PATCH 18/25] simplify linkcode_resolve --- doc/conf.py | 9 ++++----- logpyle/__init__.py | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index 0ca19ec..7ef7688 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -1,11 +1,10 @@ #!/usr/bin/env python3 -import sys from urllib.request import urlopen from logpyle import __version__ -sys._BUILDING_SPHINX_DOCS = True +# {{{ linkcode_resolve _conf_url = \ "https://raw.githubusercontent.com/inducer/sphinxconfig/main/sphinxconfig.py" @@ -15,12 +14,12 @@ old_linkcode_resolve = linkcode_resolve # noqa: F821 (linkcode_resolve comes from the URL above) -def lc(domain, info): +def linkcode_resolve(*args, **kwargs): linkcode_url = "https://github.com/illinois-ceesd/logpyle/blob/main/{filepath}#L{linestart}-L{linestop}" - return old_linkcode_resolve(domain, info, linkcode_url=linkcode_url) + return old_linkcode_resolve(*args, **kwargs, linkcode_url=linkcode_url) -linkcode_resolve = lc +# }}} # General information about the project. project = "logpyle" diff --git a/logpyle/__init__.py b/logpyle/__init__.py index 13d450c..ef21ca3 100644 --- a/logpyle/__init__.py +++ b/logpyle/__init__.py @@ -87,7 +87,7 @@ logger = logging.getLogger(__name__) -if TYPE_CHECKING and not getattr(sys, "_BUILDING_SPHINX_DOCS", False): +if TYPE_CHECKING: import mpi4py From c2ad400fd2d1f05e45c909edd2b5731b8bf124e2 Mon Sep 17 00:00:00 2001 From: Matthias Diener Date: Mon, 23 Dec 2024 11:23:33 +0100 Subject: [PATCH 19/25] sys --- logpyle/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/logpyle/__init__.py b/logpyle/__init__.py index ef21ca3..8c82f90 100644 --- a/logpyle/__init__.py +++ b/logpyle/__init__.py @@ -685,7 +685,6 @@ def enable_save_on_sigterm(self) -> Callable[..., Any] | int | None: def sighndl(_signo: int, _stackframe: Any) -> None: self.weakref_finalize() - import sys sys.exit(_signo) return signal.signal(signal.SIGTERM, sighndl) @@ -1721,7 +1720,6 @@ def add_run_info(mgr: LogManager) -> None: try: import psutil except ModuleNotFoundError: - import sys mgr.set_constant("cmdline", " ".join(sys.argv)) else: mgr.set_constant("cmdline", " ".join(psutil.Process().cmdline())) From c11ccc6ec78e3ca157676b0c5eedaa05db47409c Mon Sep 17 00:00:00 2001 From: Matthias Diener Date: Mon, 6 Jan 2025 12:05:07 +0100 Subject: [PATCH 20/25] rephrase warnings --- .github/workflows/ci.yaml | 4 ++-- examples/log-mpi.py | 2 +- examples/log.py | 2 +- test/test_logManager.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 9923d7a..2904191 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -58,7 +58,7 @@ jobs: ## Check for warnings and logging runalyzer summary.sqlite -c 'db.print_cursor(db.q("select * from warnings"))' - runalyzer summary.sqlite -c 'db.print_cursor(db.q("select * from warnings"))' | grep Oof + runalyzer summary.sqlite -c 'db.print_cursor(db.q("select * from warnings"))' | grep "test warning" runalyzer summary.sqlite -c 'db.print_cursor(db.q("select * from logging"))' runalyzer summary.sqlite -c 'db.print_cursor(db.q("select * from logging"))' | grep WARNING @@ -94,7 +94,7 @@ jobs: ## Check for warnings and logging runalyzer summary.sqlite -c 'db.print_cursor(db.q("select * from warnings"))' - runalyzer summary.sqlite -c 'db.print_cursor(db.q("select * from warnings"))' | grep Oof + runalyzer summary.sqlite -c 'db.print_cursor(db.q("select * from warnings"))' | grep "test warning" runalyzer summary.sqlite -c 'db.print_cursor(db.q("select * from logging"))' runalyzer summary.sqlite -c 'db.print_cursor(db.q("select * from logging"))' | grep WARNING diff --git a/examples/log-mpi.py b/examples/log-mpi.py index 232b71e..8e28717 100755 --- a/examples/log-mpi.py +++ b/examples/log-mpi.py @@ -76,7 +76,7 @@ def main() -> None: # Illustrate warnings/logging capture if uniform(0, 1) < 0.05: - warn("Oof. Something went awry.", stacklevel=2) + warn("test warning to test warnings capture", stacklevel=2) if istep == 16: logger.warning("test logging") diff --git a/examples/log.py b/examples/log.py index 40b1abd..9fa46df 100755 --- a/examples/log.py +++ b/examples/log.py @@ -66,7 +66,7 @@ def main() -> None: # Illustrate warnings capture if uniform(0, 1) < 0.05: - warn("Oof. Something went awry.", stacklevel=2) + warn("test warning to test warnings capture", stacklevel=2) if istep == 50: logger.warning("test logging") diff --git a/test/test_logManager.py b/test/test_logManager.py index 136351c..aaf1ffe 100644 --- a/test/test_logManager.py +++ b/test/test_logManager.py @@ -32,7 +32,7 @@ def test_empty_on_init(basic_logmgr: LogManager): def test_basic_warning(): with pytest.warns(UserWarning): - warn("Oof. Something went awry.", UserWarning, stacklevel=2) + warn("test warning to test warnings capture", UserWarning, stacklevel=2) def test_warnings_capture_from_warnings_module(basic_logmgr: LogManager): From f841504e4f515bad29d169a00f43fcbb90884109 Mon Sep 17 00:00:00 2001 From: Matthias Diener Date: Mon, 6 Jan 2025 12:29:49 +0100 Subject: [PATCH 21/25] avoid race condition when creating tables --- logpyle/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/logpyle/__init__.py b/logpyle/__init__.py index 8c82f90..e671605 100644 --- a/logpyle/__init__.py +++ b/logpyle/__init__.py @@ -361,20 +361,20 @@ def _get_unique_suffix() -> str: def _set_up_schema(db_conn: Connection) -> int: # initialize new database db_conn.execute(""" - create table quantities ( + create table if not exists quantities ( name text, unit text, description text, default_aggregator blob)""") db_conn.execute(""" - create table constants ( + create table if not exists constants ( name text, value blob)""") # schema_version < 2 is missing the 'rank' field. # schema_version < 3 is missing the 'unixtime' field. db_conn.execute(""" - create table warnings ( + create table if not exists warnings ( rank integer, step integer, unixtime integer, @@ -386,7 +386,7 @@ def _set_up_schema(db_conn: Connection) -> int: # schema_version < 3 does not have the logging table db_conn.execute(""" - create table logging ( + create table if not exists logging ( rank integer, step integer, unixtime integer, From 4e50062c5a28e06ddddb8cb2ddd238b59b1cfd1c Mon Sep 17 00:00:00 2001 From: Matthias Diener Date: Mon, 6 Jan 2025 12:37:21 +0100 Subject: [PATCH 22/25] Revert "avoid race condition when creating tables" This reverts commit f841504e4f515bad29d169a00f43fcbb90884109. --- logpyle/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/logpyle/__init__.py b/logpyle/__init__.py index e671605..8c82f90 100644 --- a/logpyle/__init__.py +++ b/logpyle/__init__.py @@ -361,20 +361,20 @@ def _get_unique_suffix() -> str: def _set_up_schema(db_conn: Connection) -> int: # initialize new database db_conn.execute(""" - create table if not exists quantities ( + create table quantities ( name text, unit text, description text, default_aggregator blob)""") db_conn.execute(""" - create table if not exists constants ( + create table constants ( name text, value blob)""") # schema_version < 2 is missing the 'rank' field. # schema_version < 3 is missing the 'unixtime' field. db_conn.execute(""" - create table if not exists warnings ( + create table warnings ( rank integer, step integer, unixtime integer, @@ -386,7 +386,7 @@ def _set_up_schema(db_conn: Connection) -> int: # schema_version < 3 does not have the logging table db_conn.execute(""" - create table if not exists logging ( + create table logging ( rank integer, step integer, unixtime integer, From 553d85b09c286e80a29057c6825a286e3fa2c441 Mon Sep 17 00:00:00 2001 From: Matthias Diener Date: Mon, 6 Jan 2025 12:38:10 +0100 Subject: [PATCH 23/25] rephrase again --- .github/workflows/ci.yaml | 4 ++-- examples/log-mpi.py | 2 +- examples/log.py | 2 +- test/test_logManager.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2904191..44b9977 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -58,7 +58,7 @@ jobs: ## Check for warnings and logging runalyzer summary.sqlite -c 'db.print_cursor(db.q("select * from warnings"))' - runalyzer summary.sqlite -c 'db.print_cursor(db.q("select * from warnings"))' | grep "test warning" + runalyzer summary.sqlite -c 'db.print_cursor(db.q("select * from warnings"))' | grep "warnings capture test" runalyzer summary.sqlite -c 'db.print_cursor(db.q("select * from logging"))' runalyzer summary.sqlite -c 'db.print_cursor(db.q("select * from logging"))' | grep WARNING @@ -94,7 +94,7 @@ jobs: ## Check for warnings and logging runalyzer summary.sqlite -c 'db.print_cursor(db.q("select * from warnings"))' - runalyzer summary.sqlite -c 'db.print_cursor(db.q("select * from warnings"))' | grep "test warning" + runalyzer summary.sqlite -c 'db.print_cursor(db.q("select * from warnings"))' | grep "warnings capture test" runalyzer summary.sqlite -c 'db.print_cursor(db.q("select * from logging"))' runalyzer summary.sqlite -c 'db.print_cursor(db.q("select * from logging"))' | grep WARNING diff --git a/examples/log-mpi.py b/examples/log-mpi.py index 8e28717..5560aee 100755 --- a/examples/log-mpi.py +++ b/examples/log-mpi.py @@ -76,7 +76,7 @@ def main() -> None: # Illustrate warnings/logging capture if uniform(0, 1) < 0.05: - warn("test warning to test warnings capture", stacklevel=2) + warn("warnings capture test", stacklevel=2) if istep == 16: logger.warning("test logging") diff --git a/examples/log.py b/examples/log.py index 9fa46df..3d5b890 100755 --- a/examples/log.py +++ b/examples/log.py @@ -66,7 +66,7 @@ def main() -> None: # Illustrate warnings capture if uniform(0, 1) < 0.05: - warn("test warning to test warnings capture", stacklevel=2) + warn("warnings capture test", stacklevel=2) if istep == 50: logger.warning("test logging") diff --git a/test/test_logManager.py b/test/test_logManager.py index aaf1ffe..dbd59fc 100644 --- a/test/test_logManager.py +++ b/test/test_logManager.py @@ -32,7 +32,7 @@ def test_empty_on_init(basic_logmgr: LogManager): def test_basic_warning(): with pytest.warns(UserWarning): - warn("test warning to test warnings capture", UserWarning, stacklevel=2) + warn("warnings capture test", UserWarning, stacklevel=2) def test_warnings_capture_from_warnings_module(basic_logmgr: LogManager): From 2658b54f050c5e1d246cc37e31fda56cdc5dd7ce Mon Sep 17 00:00:00 2001 From: Matthias Diener Date: Mon, 6 Jan 2025 12:40:03 +0100 Subject: [PATCH 24/25] bump to macos-latest --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 44b9977..5fed1ba 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -33,7 +33,7 @@ jobs: fail-fast: true matrix: python-version: ['3.10', '3.11', '3.12', '3.13', '3.x'] - os: [ubuntu-latest, macos-13] + os: [ubuntu-latest, macos-latest] steps: - uses: actions/checkout@v4 From eb633248583e6d942a3f45d8f3464d978c91b160 Mon Sep 17 00:00:00 2001 From: Matthias Diener Date: Mon, 6 Jan 2025 13:45:28 +0100 Subject: [PATCH 25/25] switch to open mpi --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 5fed1ba..776b9de 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -44,7 +44,7 @@ jobs: - name: Install prerequisites run: | [[ $(uname) == "Darwin" ]] && brew install open-mpi - [[ $(uname) == "Linux" ]] && sudo apt-get update && sudo apt-get install -y mpich libmpich-dev + [[ $(uname) == "Linux" ]] && sudo apt-get update && sudo apt-get install -y libopenmpi-dev pip install wheel matplotlib mpi4py psutil pip install -e . - name: Run and test examples