From a870dd20042913daa4391918a6968bd348839a90 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Wed, 7 Jun 2023 11:40:19 +0300 Subject: [PATCH 001/113] add context storage benchmarking --- dff/utils/benchmark/__init__.py | 3 + dff/utils/benchmark/context_storage.py | 125 +++++++++++++++++++++++++ setup.py | 2 + 3 files changed, 130 insertions(+) create mode 100644 dff/utils/benchmark/__init__.py create mode 100644 dff/utils/benchmark/context_storage.py diff --git a/dff/utils/benchmark/__init__.py b/dff/utils/benchmark/__init__.py new file mode 100644 index 000000000..0e51c9725 --- /dev/null +++ b/dff/utils/benchmark/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +# flake8: noqa: F401 +from dff.utils.benchmark.context_storage import report as context_storage_benchmark_report diff --git a/dff/utils/benchmark/context_storage.py b/dff/utils/benchmark/context_storage.py new file mode 100644 index 000000000..1185c6bc5 --- /dev/null +++ b/dff/utils/benchmark/context_storage.py @@ -0,0 +1,125 @@ +""" +Benchmark +--------- +This module contains functions used to benchmark context storages. + +## Basic usage + +``` +from dff.utils.benchmark.context_storage import report +from dff.context_storages import context_storage_factory + +storage = context_storage_factory("postgresql+asyncpg://postgres:pass@localhost:5432/test", table_name="benchmark") + +report(storage) +``` +""" +from uuid import uuid4 +from time import perf_counter + +from pympler import asizeof +from tqdm.auto import tqdm + +from dff.context_storages import DBContextStorage +from dff.script import Context, Message + + +def get_context_size(context: Context): + """Return size of a provided context.""" + return asizeof.asizeof(context) + + +def get_context(dialog_len: int, misc_len: int): + """ + Return a context with a given number of dialog turns and a given length of misc field. + + Misc field is needed in case context storage reads only the most recent requests/responses. + + Context size is approximately 1000 * dialog_len + 100 * misc_len bytes if dialog_len and misc_len > 100. + """ + return Context( + labels={i: (f"flow_{i}", f"node_{i}") for i in range(dialog_len)}, + requests={i: Message(text=f"request_{i}") for i in range(dialog_len)}, + responses={i: Message(text=f"response_{i}") for i in range(dialog_len)}, + misc={str(i): i for i in range(misc_len)}, + ) + + +def time_context_read_write(context_storage: DBContextStorage, context: Context, context_num: int): + """ + Generate `context_num` ids and for each write into `context_storage` value of `context` under generated id, + after that read the value stored in `context_storage` under generated id and compare it to `context`. + + Keep track of the time it takes to write and read context to/from the context storage. + + This function clear context storage before and after execution. + + :param context_storage: Context storage to benchmark. + :param context: An instance of context which will be repeatedly written into context storage. + :param context_num: A number of times the context will be written and checked. + :return: Two lists: first one contains individual write times, second one contains individual read times. + :raises RuntimeError: If context written into context storage does not match read context. + """ + context_storage.clear() + + write_times: list[float] = [] + read_times: list[float] = [] + for _ in tqdm(range(context_num), desc="Benchmarking context storage"): + ctx_id = uuid4() + + # write operation benchmark + write_start = perf_counter() + context_storage[ctx_id] = context + write_times.append(perf_counter() - write_start) + + # read operation benchmark + read_start = perf_counter() + actual_context = context_storage[ctx_id] + read_times.append(perf_counter() - read_start) + + # check returned context + if actual_context != context: + raise RuntimeError(f"True context:\n{context}\nActual context:\n{actual_context}") + + context_storage.clear() + return write_times, read_times + + +def report( + context_storage: DBContextStorage, + context_num: int = 1000, + dialog_len: int = 10000, + misc_len: int = 0, +): + """ + Benchmark context storage and generate a report. + + :param context_storage: Context storage to benchmark. + :param context_num: Number of times a single context should be written to/read from context storage. + :param dialog_len: + A number of turns inside a single context. The context will contain simple text requests/responses. + :param misc_len: + Number of items in the misc field. + Use this parameter if context storage only has access to the most recent requests/responses. + """ + context = get_context(dialog_len, misc_len) + context_size = get_context_size(context) + + benchmark_stats = f"""Number of contexts: {context_num} +Dialog len: {dialog_len} +Misc len: {misc_len} +Size of one context: {context_size} ({tqdm.format_sizeof(context_size, divisor=1024)})""" + + print(f"""Starting benchmarking with following parameters: +{benchmark_stats}""") + + write, read = time_context_read_write(context_storage, context, context_num) + print(f"""-------------------------------------------------- +DB benchmark +-------------------------------------------------- +{benchmark_stats} +-------------------------------------------------- +Result +-------------------------------------------------- +Average write time for one context: {sum(write) / len(write)} s +Average read time for one context: {sum(read) / len(read)} s""") diff --git a/setup.py b/setup.py index 1ae2578a8..b587ad6ad 100644 --- a/setup.py +++ b/setup.py @@ -116,6 +116,8 @@ def merge_req_lists(*req_lists: List[str]) -> List[str]: "uvicorn~=0.21.1", "websockets~=11.0.2", "locust~=2.15", + "pympler", + "tqdm", ], requests_requirements, ) From afcdce9d9cf8ebef5d16e726429ecb2e29797caf Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Wed, 7 Jun 2023 11:40:54 +0300 Subject: [PATCH 002/113] add all dff.utils modules to doc --- dff/utils/turn_caching/singleton_turn_caching.py | 5 +++++ docs/source/conf.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/dff/utils/turn_caching/singleton_turn_caching.py b/dff/utils/turn_caching/singleton_turn_caching.py index 14397c547..450dacf5d 100644 --- a/dff/utils/turn_caching/singleton_turn_caching.py +++ b/dff/utils/turn_caching/singleton_turn_caching.py @@ -1,3 +1,8 @@ +""" +Singleton Turn Caching +---------------------- +This module contains functions for caching function results on each dialog turn. +""" import functools from typing import Callable, List, Optional diff --git a/docs/source/conf.py b/docs/source/conf.py index 2f930d977..52e87582d 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -179,7 +179,7 @@ def setup(_): ("dff.messengers", "Messenger Interfaces"), ("dff.pipeline", "Pipeline"), ("dff.script", "Script"), - ("dff.utils.testing", "Utils"), + ("dff.utils", "Utils"), ] ) pull_release_notes_from_github() From 365e996ec94c4c6f82c880b5e5807680249a8ebc Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Wed, 7 Jun 2023 11:47:17 +0300 Subject: [PATCH 003/113] fix doc --- dff/utils/benchmark/context_storage.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/dff/utils/benchmark/context_storage.py b/dff/utils/benchmark/context_storage.py index 1185c6bc5..13b3becc5 100644 --- a/dff/utils/benchmark/context_storage.py +++ b/dff/utils/benchmark/context_storage.py @@ -1,18 +1,18 @@ """ -Benchmark ---------- -This module contains functions used to benchmark context storages. +Context storage benchmarking +---------------------------- +This module contains functions for context storages benchmarking. -## Basic usage +Basic usage:: -``` -from dff.utils.benchmark.context_storage import report -from dff.context_storages import context_storage_factory -storage = context_storage_factory("postgresql+asyncpg://postgres:pass@localhost:5432/test", table_name="benchmark") + from dff.utils.benchmark.context_storage import report + from dff.context_storages import context_storage_factory + + storage = context_storage_factory("postgresql+asyncpg://postgres:pass@localhost:5432/test", table_name="benchmark") + + report(storage) -report(storage) -``` """ from uuid import uuid4 from time import perf_counter From 4d6a23f4f2115d9a38376bd5c72b62a46e5706cb Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Wed, 7 Jun 2023 19:58:11 +0300 Subject: [PATCH 004/113] add support for benchmarking multiple context storages at a time --- dff/utils/benchmark/context_storage.py | 37 ++++++++++++++++++-------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/dff/utils/benchmark/context_storage.py b/dff/utils/benchmark/context_storage.py index 13b3becc5..e19b850d9 100644 --- a/dff/utils/benchmark/context_storage.py +++ b/dff/utils/benchmark/context_storage.py @@ -64,7 +64,7 @@ def time_context_read_write(context_storage: DBContextStorage, context: Context, write_times: list[float] = [] read_times: list[float] = [] - for _ in tqdm(range(context_num), desc="Benchmarking context storage"): + for _ in tqdm(range(context_num), desc=f"Benchmarking context storage:{context_storage.full_path}"): ctx_id = uuid4() # write operation benchmark @@ -86,15 +86,15 @@ def time_context_read_write(context_storage: DBContextStorage, context: Context, def report( - context_storage: DBContextStorage, + *context_storages: DBContextStorage, context_num: int = 1000, dialog_len: int = 10000, misc_len: int = 0, ): """ - Benchmark context storage and generate a report. + Benchmark context storage(s) and generate a report. - :param context_storage: Context storage to benchmark. + :param context_storages: Context storages to benchmark. :param context_num: Number of times a single context should be written to/read from context storage. :param dialog_len: A number of turns inside a single context. The context will contain simple text requests/responses. @@ -113,13 +113,28 @@ def report( print(f"""Starting benchmarking with following parameters: {benchmark_stats}""") - write, read = time_context_read_write(context_storage, context, context_num) - print(f"""-------------------------------------------------- + line_separator = "-" * 80 + + result = f"""{line_separator} DB benchmark --------------------------------------------------- +{line_separator} {benchmark_stats} --------------------------------------------------- -Result --------------------------------------------------- +{line_separator}""" + + for context_storage in context_storages: + result += f""" +Result --- {context_storage.full_path} +{line_separator}""" + try: + write, read = time_context_read_write(context_storage, context, context_num) + + result += f""" Average write time for one context: {sum(write) / len(write)} s -Average read time for one context: {sum(read) / len(read)} s""") +Average read time for one context: {sum(read) / len(read)} s +{line_separator}""" + except Exception as e: + result += f""" +{getattr(e, 'message', repr(e))} +{line_separator}""" + + print(result) From 5815511700d573198075be26edaf96150007b89e Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Thu, 8 Jun 2023 15:02:36 +0300 Subject: [PATCH 005/113] add type annotations; option to pass multiple context storages; export as pdf --- dff/utils/benchmark/context_storage.py | 164 ++++++++++++++++++++----- 1 file changed, 135 insertions(+), 29 deletions(-) diff --git a/dff/utils/benchmark/context_storage.py b/dff/utils/benchmark/context_storage.py index e19b850d9..ab6cf5f6d 100644 --- a/dff/utils/benchmark/context_storage.py +++ b/dff/utils/benchmark/context_storage.py @@ -16,20 +16,28 @@ """ from uuid import uuid4 from time import perf_counter +import typing as tp from pympler import asizeof from tqdm.auto import tqdm +try: + import matplotlib + from matplotlib.backends.backend_pdf import PdfPages + import matplotlib.pyplot as plt +except ImportError: + matplotlib = None + from dff.context_storages import DBContextStorage from dff.script import Context, Message -def get_context_size(context: Context): +def get_context_size(context: Context) -> int: """Return size of a provided context.""" return asizeof.asizeof(context) -def get_context(dialog_len: int, misc_len: int): +def get_context(dialog_len: int, misc_len: int) -> Context: """ Return a context with a given number of dialog turns and a given length of misc field. @@ -45,7 +53,9 @@ def get_context(dialog_len: int, misc_len: int): ) -def time_context_read_write(context_storage: DBContextStorage, context: Context, context_num: int): +def time_context_read_write( + context_storage: DBContextStorage, context: Context, context_num: int +) -> tp.Tuple[tp.List[float], tp.List[float]]: """ Generate `context_num` ids and for each write into `context_storage` value of `context` under generated id, after that read the value stored in `context_storage` under generated id and compare it to `context`. @@ -62,8 +72,8 @@ def time_context_read_write(context_storage: DBContextStorage, context: Context, """ context_storage.clear() - write_times: list[float] = [] - read_times: list[float] = [] + write_times: tp.List[float] = [] + read_times: tp.List[float] = [] for _ in tqdm(range(context_num), desc=f"Benchmarking context storage:{context_storage.full_path}"): ctx_id = uuid4() @@ -90,6 +100,7 @@ def report( context_num: int = 1000, dialog_len: int = 10000, misc_len: int = 0, + pdf: tp.Optional[str] = None, ): """ Benchmark context storage(s) and generate a report. @@ -101,40 +112,135 @@ def report( :param misc_len: Number of items in the misc field. Use this parameter if context storage only has access to the most recent requests/responses. + :param pdf: + A pdf file name to save report to. + Defaults to None. + If set to None, prints the result to stdout instead of creating a pdf file. """ context = get_context(dialog_len, misc_len) context_size = get_context_size(context) - benchmark_stats = f"""Number of contexts: {context_num} -Dialog len: {dialog_len} -Misc len: {misc_len} -Size of one context: {context_size} ({tqdm.format_sizeof(context_size, divisor=1024)})""" - - print(f"""Starting benchmarking with following parameters: -{benchmark_stats}""") + benchmark_config = f"Number of contexts: {context_num}\n" \ + f"Dialog len: {dialog_len}\n" \ + f"Misc len: {misc_len}\n" \ + f"Size of one context: {context_size} ({tqdm.format_sizeof(context_size, divisor=1024)})" - line_separator = "-" * 80 + print(f"Starting benchmarking with following parameters:\n{benchmark_config}") - result = f"""{line_separator} -DB benchmark -{line_separator} -{benchmark_stats} -{line_separator}""" + benchmarking_results: tp.Dict[str, tp.Union[ + tp.Tuple[tp.List[float], tp.List[float]], + str + ]] = {} for context_storage in context_storages: - result += f""" -Result --- {context_storage.full_path} -{line_separator}""" try: write, read = time_context_read_write(context_storage, context, context_num) - result += f""" -Average write time for one context: {sum(write) / len(write)} s -Average read time for one context: {sum(read) / len(read)} s -{line_separator}""" + benchmarking_results[context_storage.full_path] = write, read except Exception as e: - result += f""" -{getattr(e, 'message', repr(e))} -{line_separator}""" + benchmarking_results[context_storage.full_path] = getattr(e, 'message', repr(e)) + + # define functions for displaying results + line_separator = "-" * 80 - print(result) + pretty_config = f"{line_separator}\nDB benchmark\n{line_separator}\n{benchmark_config}\n{line_separator}" + + def pretty_benchmark_result(storage_name, benchmarking_result) -> str: + result = f"{storage_name}\n{line_separator}\n" + if not isinstance(benchmarking_result, str): + write, read = benchmarking_result + result += f"Average write time: {sum(write) / len(write)} s\n" \ + f"Average read time: {sum(read) / len(read)} s\n{line_separator}" + else: + result += f"{benchmarking_result}\n{line_separator}" + return result + + def get_scores_and_leaderboard( + sort_by: tp.Literal["Write", "Read"] + ) -> tp.Tuple[ + tp.List[tp.Tuple[str, tp.Optional[float]]], + str + ]: + benchmark_index = 0 if sort_by == 'Write' else 1 + + scores = sorted( + [ + (storage_name, sum(result[benchmark_index]) / len(result[benchmark_index])) + for storage_name, result in benchmarking_results.items() + if not isinstance(result, str) + ], + key=lambda benchmark: benchmark[1] # sort in ascending order + ) + scores += [ + (storage_name, None) + for storage_name, result in benchmarking_results.items() + if isinstance(result, str) + ] + leaderboard = f"{sort_by} time leaderboard\n{line_separator}\n" + "\n".join( + [f"{result}{' s' if result is not None else ''}: {storage_name}" for storage_name, result in scores] + ) + "\n" + line_separator + + return scores, leaderboard + + _, write_leaderboard = get_scores_and_leaderboard("Write") + _, read_leaderboard = get_scores_and_leaderboard("Read") + + if pdf is None: + result = pretty_config + + for storage_name, benchmarking_result in benchmarking_results.items(): + result += f"\n{pretty_benchmark_result(storage_name, benchmarking_result)}" + + if len(context_storages) > 1: + result += f"\n{write_leaderboard}\n{read_leaderboard}" + + print(result) + else: + if matplotlib is None: + raise RuntimeError("`matplotlib` is required to generate pdf reports.") + + figure_size = (11, 8) + + def text_page(text, *, x=0.5, y=0.5, size=18, ha="center", family="monospace", **kwargs): + page = plt.figure(figsize=figure_size) + page.clf() + page.text(x, y, text, transform=page.transFigure, size=size, ha=ha, family=family, **kwargs) + + def scatter_page(storage_name, write, read): + plt.figure(figsize=figure_size) + plt.scatter(range(len(write)), write, label="write times") + plt.scatter(range(len(read)), read, label="read times") + plt.legend(loc='best') + plt.grid(True) + plt.title(storage_name) + + with PdfPages(pdf) as mpl_pdf: + text_page(pretty_config, size=24) + mpl_pdf.savefig() + plt.close() + + if len(context_storages) > 1: + text_page(write_leaderboard, x=0.05, size=14, ha="left") + mpl_pdf.savefig() + plt.close() + text_page(read_leaderboard, x=0.05, size=14, ha="left") + mpl_pdf.savefig() + plt.close() + + for storage_name, benchmarking_result in benchmarking_results.items(): + txt = pretty_benchmark_result(storage_name, benchmarking_result) + + if not isinstance(benchmarking_result, str): + write, read = benchmarking_result + + text_page(txt) + mpl_pdf.savefig() + plt.close() + + scatter_page(storage_name, write, read) + mpl_pdf.savefig() + plt.close() + else: + text_page(txt) + mpl_pdf.savefig() + plt.close() From 17b11fc936e8e31339c90a8c80c68f32374440ae Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Thu, 8 Jun 2023 21:03:20 +0300 Subject: [PATCH 006/113] add option to get results as a dataframe --- dff/utils/benchmark/context_storage.py | 80 +++++++++++++++++++++++++- 1 file changed, 77 insertions(+), 3 deletions(-) diff --git a/dff/utils/benchmark/context_storage.py b/dff/utils/benchmark/context_storage.py index ab6cf5f6d..bb4bb78ef 100644 --- a/dff/utils/benchmark/context_storage.py +++ b/dff/utils/benchmark/context_storage.py @@ -28,6 +28,16 @@ except ImportError: matplotlib = None +try: + import pandas +except ImportError: + pandas = None + +try: + import polars +except ImportError: + polars = None + from dff.context_storages import DBContextStorage from dff.script import Context, Message @@ -53,9 +63,46 @@ def get_context(dialog_len: int, misc_len: int) -> Context: ) +@tp.overload def time_context_read_write( - context_storage: DBContextStorage, context: Context, context_num: int + context_storage: DBContextStorage, + context: Context, + context_num: int, + as_dataframe: None = None, ) -> tp.Tuple[tp.List[float], tp.List[float]]: + ... + + +@tp.overload +def time_context_read_write( + context_storage: DBContextStorage, + context: Context, + context_num: int, + as_dataframe: tp.Literal["pandas"], +) -> "pandas.DataFrame": + ... + + +@tp.overload +def time_context_read_write( + context_storage: DBContextStorage, + context: Context, + context_num: int, + as_dataframe: tp.Literal["polars"], +) -> "polars.DataFrame": + ... + + +def time_context_read_write( + context_storage: DBContextStorage, + context: Context, + context_num: int, + as_dataframe: tp.Optional[tp.Literal["pandas", "polars"]] = None, +) -> tp.Union[ + tp.Tuple[tp.List[float], tp.List[float]], + "pandas.DataFrame", + "polars.DataFrame", +]: """ Generate `context_num` ids and for each write into `context_storage` value of `context` under generated id, after that read the value stored in `context_storage` under generated id and compare it to `context`. @@ -67,7 +114,18 @@ def time_context_read_write( :param context_storage: Context storage to benchmark. :param context: An instance of context which will be repeatedly written into context storage. :param context_num: A number of times the context will be written and checked. - :return: Two lists: first one contains individual write times, second one contains individual read times. + :param as_dataframe: + If the function should return the results as a pandas or a polars DataFrame. + If set to None, does not return a Dataframe. + Defaults to None. + :return: + Depends on `as_dataframe` parameter. + 1. By default, it is set to None in which case it returns: + two lists: first one contains individual write times, second one contains individual read times. + 2. If set to "pandas": + A pandas DataFrame with two columns: "write" and "read" which contain corresponding data series. + 3. If set to "polars": + A polars DataFrame with the same columns as in a pandas DataFrame. :raises RuntimeError: If context written into context storage does not match read context. """ context_storage.clear() @@ -92,7 +150,23 @@ def time_context_read_write( raise RuntimeError(f"True context:\n{context}\nActual context:\n{actual_context}") context_storage.clear() - return write_times, read_times + + if as_dataframe is None: + return write_times, read_times + elif as_dataframe == "pandas": + if pandas is None: + raise RuntimeError("Install `pandas` in order to get benchmark results as a pandas DataFrame.") + return pandas.DataFrame(data={ + "write": write_times, + "read": read_times + }) + elif as_dataframe == "polars": + if polars is None: + raise RuntimeError("Install `polars` in order to get benchmark results as a polars DataFrame.") + return polars.DataFrame({ + "write": write_times, + "read": read_times + }) def report( From 2c8324522ac0382e13167fdfffd5a61396facfc8 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Thu, 8 Jun 2023 21:08:15 +0300 Subject: [PATCH 007/113] format --- dff/utils/benchmark/context_storage.py | 109 ++++++++++++------------- 1 file changed, 50 insertions(+), 59 deletions(-) diff --git a/dff/utils/benchmark/context_storage.py b/dff/utils/benchmark/context_storage.py index bb4bb78ef..8debc075b 100644 --- a/dff/utils/benchmark/context_storage.py +++ b/dff/utils/benchmark/context_storage.py @@ -65,44 +65,40 @@ def get_context(dialog_len: int, misc_len: int) -> Context: @tp.overload def time_context_read_write( - context_storage: DBContextStorage, - context: Context, - context_num: int, - as_dataframe: None = None, + context_storage: DBContextStorage, + context: Context, + context_num: int, + as_dataframe: None = None, ) -> tp.Tuple[tp.List[float], tp.List[float]]: ... @tp.overload def time_context_read_write( - context_storage: DBContextStorage, - context: Context, - context_num: int, - as_dataframe: tp.Literal["pandas"], + context_storage: DBContextStorage, + context: Context, + context_num: int, + as_dataframe: tp.Literal["pandas"], ) -> "pandas.DataFrame": ... @tp.overload def time_context_read_write( - context_storage: DBContextStorage, - context: Context, - context_num: int, - as_dataframe: tp.Literal["polars"], + context_storage: DBContextStorage, + context: Context, + context_num: int, + as_dataframe: tp.Literal["polars"], ) -> "polars.DataFrame": ... def time_context_read_write( - context_storage: DBContextStorage, - context: Context, - context_num: int, - as_dataframe: tp.Optional[tp.Literal["pandas", "polars"]] = None, -) -> tp.Union[ - tp.Tuple[tp.List[float], tp.List[float]], - "pandas.DataFrame", - "polars.DataFrame", -]: + context_storage: DBContextStorage, + context: Context, + context_num: int, + as_dataframe: tp.Optional[tp.Literal["pandas", "polars"]] = None, +) -> tp.Union[tp.Tuple[tp.List[float], tp.List[float]], "pandas.DataFrame", "polars.DataFrame"]: """ Generate `context_num` ids and for each write into `context_storage` value of `context` under generated id, after that read the value stored in `context_storage` under generated id and compare it to `context`. @@ -156,25 +152,19 @@ def time_context_read_write( elif as_dataframe == "pandas": if pandas is None: raise RuntimeError("Install `pandas` in order to get benchmark results as a pandas DataFrame.") - return pandas.DataFrame(data={ - "write": write_times, - "read": read_times - }) + return pandas.DataFrame(data={"write": write_times, "read": read_times}) elif as_dataframe == "polars": if polars is None: raise RuntimeError("Install `polars` in order to get benchmark results as a polars DataFrame.") - return polars.DataFrame({ - "write": write_times, - "read": read_times - }) + return polars.DataFrame({"write": write_times, "read": read_times}) def report( - *context_storages: DBContextStorage, - context_num: int = 1000, - dialog_len: int = 10000, - misc_len: int = 0, - pdf: tp.Optional[str] = None, + *context_storages: DBContextStorage, + context_num: int = 1000, + dialog_len: int = 10000, + misc_len: int = 0, + pdf: tp.Optional[str] = None, ): """ Benchmark context storage(s) and generate a report. @@ -194,17 +184,16 @@ def report( context = get_context(dialog_len, misc_len) context_size = get_context_size(context) - benchmark_config = f"Number of contexts: {context_num}\n" \ - f"Dialog len: {dialog_len}\n" \ - f"Misc len: {misc_len}\n" \ - f"Size of one context: {context_size} ({tqdm.format_sizeof(context_size, divisor=1024)})" + benchmark_config = ( + f"Number of contexts: {context_num}\n" + f"Dialog len: {dialog_len}\n" + f"Misc len: {misc_len}\n" + f"Size of one context: {context_size} ({tqdm.format_sizeof(context_size, divisor=1024)})" + ) print(f"Starting benchmarking with following parameters:\n{benchmark_config}") - benchmarking_results: tp.Dict[str, tp.Union[ - tp.Tuple[tp.List[float], tp.List[float]], - str - ]] = {} + benchmarking_results: tp.Dict[str, tp.Union[tp.Tuple[tp.List[float], tp.List[float]], str]] = {} for context_storage in context_storages: try: @@ -212,7 +201,7 @@ def report( benchmarking_results[context_storage.full_path] = write, read except Exception as e: - benchmarking_results[context_storage.full_path] = getattr(e, 'message', repr(e)) + benchmarking_results[context_storage.full_path] = getattr(e, "message", repr(e)) # define functions for displaying results line_separator = "-" * 80 @@ -223,19 +212,18 @@ def pretty_benchmark_result(storage_name, benchmarking_result) -> str: result = f"{storage_name}\n{line_separator}\n" if not isinstance(benchmarking_result, str): write, read = benchmarking_result - result += f"Average write time: {sum(write) / len(write)} s\n" \ - f"Average read time: {sum(read) / len(read)} s\n{line_separator}" + result += ( + f"Average write time: {sum(write) / len(write)} s\n" + f"Average read time: {sum(read) / len(read)} s\n{line_separator}" + ) else: result += f"{benchmarking_result}\n{line_separator}" return result def get_scores_and_leaderboard( - sort_by: tp.Literal["Write", "Read"] - ) -> tp.Tuple[ - tp.List[tp.Tuple[str, tp.Optional[float]]], - str - ]: - benchmark_index = 0 if sort_by == 'Write' else 1 + sort_by: tp.Literal["Write", "Read"] + ) -> tp.Tuple[tp.List[tp.Tuple[str, tp.Optional[float]]], str]: + benchmark_index = 0 if sort_by == "Write" else 1 scores = sorted( [ @@ -243,16 +231,19 @@ def get_scores_and_leaderboard( for storage_name, result in benchmarking_results.items() if not isinstance(result, str) ], - key=lambda benchmark: benchmark[1] # sort in ascending order + key=lambda benchmark: benchmark[1], # sort in ascending order ) scores += [ - (storage_name, None) - for storage_name, result in benchmarking_results.items() - if isinstance(result, str) + (storage_name, None) for storage_name, result in benchmarking_results.items() if isinstance(result, str) ] - leaderboard = f"{sort_by} time leaderboard\n{line_separator}\n" + "\n".join( - [f"{result}{' s' if result is not None else ''}: {storage_name}" for storage_name, result in scores] - ) + "\n" + line_separator + leaderboard = ( + f"{sort_by} time leaderboard\n{line_separator}\n" + + "\n".join( + [f"{result}{' s' if result is not None else ''}: {storage_name}" for storage_name, result in scores] + ) + + "\n" + + line_separator + ) return scores, leaderboard @@ -284,7 +275,7 @@ def scatter_page(storage_name, write, read): plt.figure(figsize=figure_size) plt.scatter(range(len(write)), write, label="write times") plt.scatter(range(len(read)), read, label="read times") - plt.legend(loc='best') + plt.legend(loc="best") plt.grid(True) plt.title(storage_name) From e8564b11c5057de8e2cf210d21d07ba2635891bd Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Fri, 9 Jun 2023 03:30:56 +0300 Subject: [PATCH 008/113] add tutorial for benchmark --- .../context_storages/8_db_benchmarking.py | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 tutorials/context_storages/8_db_benchmarking.py diff --git a/tutorials/context_storages/8_db_benchmarking.py b/tutorials/context_storages/8_db_benchmarking.py new file mode 100644 index 000000000..c9071ddf8 --- /dev/null +++ b/tutorials/context_storages/8_db_benchmarking.py @@ -0,0 +1,86 @@ +# %% [markdown] +""" +# 8. Context storage benchmarking + +This tutorial shows how to benchmark context storages. +""" + +# %% +import pathlib +from platform import system + +from dff.context_storages import context_storage_factory + +from dff.utils.benchmark.context_storage import report + +# %% [markdown] +""" +## Context storage setup +""" + +# %% +# this cell is only required for pickle, shelve and sqlite databases +pathlib.Path("dbs").mkdir(exist_ok=True) +sqlite_file = pathlib.Path("dbs/sqlite.db") +sqlite_file.touch(exist_ok=True) +sqlite_separator = "///" if system() == "Windows" else "////" + +# %% +storages = list( + map( + context_storage_factory, + [ + "json://dbs/json.json", + "pickle://dbs/pickle.pkl", + "shelve://dbs/shelve", + "postgresql+asyncpg://postgres:pass@localhost:5432/test", + "mongodb://admin:pass@localhost:27017/admin", + "redis://:pass@localhost:6379/0", + "mysql+asyncmy://root:pass@localhost:3307/test", + f"sqlite+aiosqlite:{sqlite_separator}{sqlite_file.absolute()}", + "grpc://localhost:2136/local", + ], + ) +) + +# %% [markdown] +""" +## Generating a report + +The report will print a size of one context and stats for the context storage: +an average write time and an average write time. + +Note: context storage passed into the `report` function will be cleared. + +Setting `context_num` to 100 means that we'll run a hundred cycles of writing and reading context. +This way we'll be able to get a more accurate average read/write time as well as +check if read/write times are dependent on the number of contexts in the storage. + +You can also set the `dialog_len` and `misc_len` parameters. Those affect the size of a context. +An approximate formula is `size=1000 * dialog_len + 100 * misc_len bytes` although the report +automatically calculates the size of a context. + +Here we set `dialog_len` to 1 and `misc_len` to 0 (by default) in order for reports to +generate faster. +""" + +# %% +report(context_storage_factory("json://dbs/json.json"), context_num=100, dialog_len=1) + +# %% [markdown] +""" +You can pass multiple context storages to get average read/write times for each +as well as a comparison of all the passed storages (in the form of ordered lists). +""" + +# %% +report(*storages, context_num=100, dialog_len=1) + +# %% [markdown] +""" +You can also generate a pdf report which additionally includes +plots of write and read times for each storage. +""" + +# %% +report(*storages, context_num=100, dialog_len=1, pdf="report.pdf") From a07588ccc70a23c28eb1c1df10edcb95d73853a7 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Fri, 9 Jun 2023 03:40:39 +0300 Subject: [PATCH 009/113] update benchmark dependencies --- README.md | 1 + docs/source/get_started.rst | 1 + setup.py | 10 ++++++++-- tutorials/context_storages/8_db_benchmarking.py | 2 ++ 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b30839908..430663280 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ pip install dff[postgresql] # dependencies for using PostgreSQL pip install dff[sqlite] # dependencies for using SQLite pip install dff[ydb] # dependencies for using Yandex Database pip install dff[telegram] # dependencies for using Telegram +pip install dff[benchmark] # dependencies for benchmarking pip install dff[full] # full dependencies including all options above pip install dff[tests] # dependencies for running tests pip install dff[test_full] # full dependencies for running all tests (all options above) diff --git a/docs/source/get_started.rst b/docs/source/get_started.rst index 8c9cfa09b..172151078 100644 --- a/docs/source/get_started.rst +++ b/docs/source/get_started.rst @@ -27,6 +27,7 @@ The installation process allows the user to choose from different packages based pip install dff[sqlite] # dependencies for using SQLite pip install dff[ydb] # dependencies for using Yandex Database pip install dff[telegram] # dependencies for using Telegram + pip install dff[benchmark] # dependencies for benchmarking pip install dff[full] # full dependencies including all options above pip install dff[tests] # dependencies for running tests pip install dff[test_full] # full dependencies for running all tests (all options above) diff --git a/setup.py b/setup.py index b587ad6ad..4c06f3479 100644 --- a/setup.py +++ b/setup.py @@ -79,6 +79,11 @@ def merge_req_lists(*req_lists: List[str]) -> List[str]: "pytelegrambotapi~=4.5.1", ] +benchmark_dependencies = [ + "pympler", + "tqdm", +] + full = merge_req_lists( core, async_files_dependencies, @@ -89,6 +94,7 @@ def merge_req_lists(*req_lists: List[str]) -> List[str]: postgresql_dependencies, ydb_dependencies, telegram_dependencies, + benchmark_dependencies, ) requests_requirements = [ @@ -116,8 +122,7 @@ def merge_req_lists(*req_lists: List[str]) -> List[str]: "uvicorn~=0.21.1", "websockets~=11.0.2", "locust~=2.15", - "pympler", - "tqdm", + "matplotlib", ], requests_requirements, ) @@ -173,6 +178,7 @@ def merge_req_lists(*req_lists: List[str]) -> List[str]: "postgresql": postgresql_dependencies, # dependencies for using PostgreSQL "ydb": ydb_dependencies, # dependencies for using Yandex Database "telegram": telegram_dependencies, # dependencies for using Telegram + "benchmark": benchmark_dependencies, # dependencies for benchmarking "full": full, # full dependencies including all options above "tests": test_requirements, # dependencies for running tests "test_full": tests_full, # full dependencies for running all tests (all options above) diff --git a/tutorials/context_storages/8_db_benchmarking.py b/tutorials/context_storages/8_db_benchmarking.py index c9071ddf8..c37954698 100644 --- a/tutorials/context_storages/8_db_benchmarking.py +++ b/tutorials/context_storages/8_db_benchmarking.py @@ -80,6 +80,8 @@ """ You can also generate a pdf report which additionally includes plots of write and read times for each storage. + +Generating pdf reports requires the `matplotlib` package. """ # %% From 258be6092c872660be211593f2c23b9c883b54fa Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Mon, 19 Jun 2023 14:43:02 +0300 Subject: [PATCH 010/113] update benchmark utils - remove export as dataframe - add methods to get context/message/misc - add dimensionality to misc and message - add context update timing --- dff/utils/benchmark/context_storage.py | 172 +++++++++++++------------ 1 file changed, 92 insertions(+), 80 deletions(-) diff --git a/dff/utils/benchmark/context_storage.py b/dff/utils/benchmark/context_storage.py index 8debc075b..70ce1f605 100644 --- a/dff/utils/benchmark/context_storage.py +++ b/dff/utils/benchmark/context_storage.py @@ -17,9 +17,11 @@ from uuid import uuid4 from time import perf_counter import typing as tp +from copy import deepcopy from pympler import asizeof from tqdm.auto import tqdm +from humanize import naturalsize try: import matplotlib @@ -28,77 +30,69 @@ except ImportError: matplotlib = None -try: - import pandas -except ImportError: - pandas = None - -try: - import polars -except ImportError: - polars = None - from dff.context_storages import DBContextStorage from dff.script import Context, Message -def get_context_size(context: Context) -> int: - """Return size of a provided context.""" - return asizeof.asizeof(context) - - -def get_context(dialog_len: int, misc_len: int) -> Context: +def get_dict(lengths: tp.Tuple[int, ...]): """ - Return a context with a given number of dialog turns and a given length of misc field. + Misc dictionary build in lengths dimensions. + + :param lengths: + Dimensions of the dictionary. + Each element of the lengths tuple is the number of keys on the corresponding level of the dictionary. + The last element of the lengths tuple is the length of the str values of the dict. - Misc field is needed in case context storage reads only the most recent requests/responses. + e.g. lengths=(1, 2) produces a dictionary with 1 key that points to a string of len 2. + whereas lengths=(1, 2, 3) produces a dictionary with 1 key that points to a dictionary + with 2 keys each of which points to a string of len 3. - Context size is approximately 1000 * dialog_len + 100 * misc_len bytes if dialog_len and misc_len > 100. + So the len of lengths is the depth of the dictionary, while its values are + the width of the dictionary at each level. + :return: Misc dictionary. """ - return Context( - labels={i: (f"flow_{i}", f"node_{i}") for i in range(dialog_len)}, - requests={i: Message(text=f"request_{i}") for i in range(dialog_len)}, - responses={i: Message(text=f"response_{i}") for i in range(dialog_len)}, - misc={str(i): i for i in range(misc_len)}, - ) + def _get_dict(lengths: tp.Tuple[int, ...]): + if len(lengths) < 2: + return "." * lengths[0] + return {i: _get_dict(lengths[1:]) for i in range(lengths[0])} + + if len(lengths) > 1: + return _get_dict(lengths) + elif len(lengths) == 1: + return _get_dict((lengths[0], 0)) + else: + return _get_dict((0, 0)) -@tp.overload -def time_context_read_write( - context_storage: DBContextStorage, - context: Context, - context_num: int, - as_dataframe: None = None, -) -> tp.Tuple[tp.List[float], tp.List[float]]: - ... +def get_message(message_lengths: tp.Tuple[int, ...]): + """ + Message with misc field of message_lengths dimension. + :param message_lengths: + :return: + """ + return Message(misc=get_dict(message_lengths)) -@tp.overload -def time_context_read_write( - context_storage: DBContextStorage, - context: Context, - context_num: int, - as_dataframe: tp.Literal["pandas"], -) -> "pandas.DataFrame": - ... - +def get_context(dialog_len: int, message_lengths: tp.Tuple[int, ...], misc_lengths: tp.Tuple[int, ...]) -> Context: + """ + A context with a given number of dialog turns, a given message dimension + and a given misc dimension. + """ -@tp.overload -def time_context_read_write( - context_storage: DBContextStorage, - context: Context, - context_num: int, - as_dataframe: tp.Literal["polars"], -) -> "polars.DataFrame": - ... + return Context( + labels={i: (f"flow_{i}", f"node_{i}") for i in range(dialog_len)}, + requests={i: get_message(message_lengths) for i in range(dialog_len)}, + responses={i: get_message(message_lengths) for i in range(dialog_len)}, + misc=get_dict(misc_lengths), + ) def time_context_read_write( context_storage: DBContextStorage, context: Context, context_num: int, - as_dataframe: tp.Optional[tp.Literal["pandas", "polars"]] = None, -) -> tp.Union[tp.Tuple[tp.List[float], tp.List[float]], "pandas.DataFrame", "polars.DataFrame"]: + context_updater=None, +) -> tp.Tuple[tp.List[float], tp.List[tp.Dict[int, float]], tp.List[tp.Dict[int, float]]]: """ Generate `context_num` ids and for each write into `context_storage` value of `context` under generated id, after that read the value stored in `context_storage` under generated id and compare it to `context`. @@ -110,10 +104,7 @@ def time_context_read_write( :param context_storage: Context storage to benchmark. :param context: An instance of context which will be repeatedly written into context storage. :param context_num: A number of times the context will be written and checked. - :param as_dataframe: - If the function should return the results as a pandas or a polars DataFrame. - If set to None, does not return a Dataframe. - Defaults to None. + :param context_updater: :return: Depends on `as_dataframe` parameter. 1. By default, it is set to None in which case it returns: @@ -127,7 +118,15 @@ def time_context_read_write( context_storage.clear() write_times: tp.List[float] = [] - read_times: tp.List[float] = [] + read_times: tp.List[tp.Dict[int, float]] = [] + update_times: tp.List[tp.Dict[int, float]] = [] + + if context_updater is not None: + updated_contexts = [context] + + while updated_contexts[-1] is not None: + updated_contexts.append(context_updater(deepcopy(updated_contexts[-1]))) + for _ in tqdm(range(context_num), desc=f"Benchmarking context storage:{context_storage.full_path}"): ctx_id = uuid4() @@ -136,34 +135,42 @@ def time_context_read_write( context_storage[ctx_id] = context write_times.append(perf_counter() - write_start) + read_times.append({}) + # read operation benchmark read_start = perf_counter() actual_context = context_storage[ctx_id] - read_times.append(perf_counter() - read_start) + read_time = perf_counter() - read_start + read_times[-1][len(actual_context.labels)] = read_time # check returned context - if actual_context != context: - raise RuntimeError(f"True context:\n{context}\nActual context:\n{actual_context}") + # if actual_context != context: + # raise RuntimeError(f"True context:\n{context}\nActual context:\n{actual_context}") - context_storage.clear() + if context_updater is not None: + update_times.append({}) + + for updated_context in updated_contexts[1:-1]: + update_start = perf_counter() + context_storage[ctx_id] = updated_context + update_time = perf_counter() - update_start + update_times[-1][len(updated_context.labels)] = update_time - if as_dataframe is None: - return write_times, read_times - elif as_dataframe == "pandas": - if pandas is None: - raise RuntimeError("Install `pandas` in order to get benchmark results as a pandas DataFrame.") - return pandas.DataFrame(data={"write": write_times, "read": read_times}) - elif as_dataframe == "polars": - if polars is None: - raise RuntimeError("Install `polars` in order to get benchmark results as a polars DataFrame.") - return polars.DataFrame({"write": write_times, "read": read_times}) + read_start = perf_counter() + actual_context = context_storage[ctx_id] + read_time = perf_counter() - read_start + read_times[-1][len(actual_context.labels)] = read_time + + context_storage.clear() + return write_times, read_times, update_times def report( *context_storages: DBContextStorage, context_num: int = 1000, - dialog_len: int = 10000, - misc_len: int = 0, + dialog_len: int = 300, + message_lengths: tp.Tuple[int, ...] = (10, 10), + misc_lengths: tp.Tuple[int, ...] = (10, 10), pdf: tp.Optional[str] = None, ): """ @@ -173,22 +180,26 @@ def report( :param context_num: Number of times a single context should be written to/read from context storage. :param dialog_len: A number of turns inside a single context. The context will contain simple text requests/responses. - :param misc_len: - Number of items in the misc field. - Use this parameter if context storage only has access to the most recent requests/responses. + :param message_lengths: + :param misc_lengths: :param pdf: A pdf file name to save report to. Defaults to None. If set to None, prints the result to stdout instead of creating a pdf file. """ - context = get_context(dialog_len, misc_len) - context_size = get_context_size(context) + context = get_context(dialog_len, message_lengths, misc_lengths) + context_size = asizeof.asizeof(context) + misc_size = asizeof.asizeof(get_dict(misc_lengths)) + message_size = asizeof.asizeof(get_message(message_lengths)) benchmark_config = ( f"Number of contexts: {context_num}\n" f"Dialog len: {dialog_len}\n" - f"Misc len: {misc_len}\n" - f"Size of one context: {context_size} ({tqdm.format_sizeof(context_size, divisor=1024)})" + f"Message misc dimensions: {message_lengths}\n" + f"Misc dimensions: {misc_lengths}\n" + f"Size of misc field: {misc_size} ({naturalsize(misc_size, gnu=True)})\n" + f"Size of one message: {message_size} ({naturalsize(message_size, gnu=True)})\n" + f"Size of one context: {context_size} ({naturalsize(context_size, gnu=True)})" ) print(f"Starting benchmarking with following parameters:\n{benchmark_config}") @@ -197,6 +208,7 @@ def report( for context_storage in context_storages: try: + # todo: update report method write, read = time_context_read_write(context_storage, context, context_num) benchmarking_results[context_storage.full_path] = write, read From b970e83527ed06ad0738b496d733dc4600c1f1cc Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Tue, 20 Jun 2023 16:15:00 +0300 Subject: [PATCH 011/113] add benchmark_dbs and benchmark_streamlit --- benchmark_dbs.py | 216 ++++++++++++++++++++++++++++ benchmark_streamlit.py | 314 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 530 insertions(+) create mode 100644 benchmark_dbs.py create mode 100644 benchmark_streamlit.py diff --git a/benchmark_dbs.py b/benchmark_dbs.py new file mode 100644 index 000000000..1b4eaa462 --- /dev/null +++ b/benchmark_dbs.py @@ -0,0 +1,216 @@ +import pathlib +from platform import system +import typing as tp +from uuid import uuid4 +import json + +from pympler import asizeof +from pydantic import BaseModel, Field + +from dff.script import Context +from dff.context_storages import context_storage_factory as partial_factory +from dff.context_storages_old import context_storage_factory as dev_factory + + +import dff.utils.benchmark.context_storage as bm + + +# define benchmark classes and tools + +class DBFactory(BaseModel): + base_factory: tp.Literal["dev", "partial"] + uri: str + + def db(self): + if self.base_factory == "dev": + return dev_factory(self.uri) + else: + return partial_factory(self.uri) + + +class BenchmarkCase(BaseModel): + name: str + db_factory: DBFactory + uuid: str = Field(default_factory=lambda: str(uuid4())) + description: str = "" + context_num: int = 100 + from_dialog_len: int = 300 + to_dialog_len: int = 500 + step_dialog_len: int = 10 + message_lengths: tp.Tuple[int, ...] = (10, 10) + misc_lengths: tp.Tuple[int, ...] = (10, 10) + + def get_context_updater(self): + def _context_updater(context: Context): + start_len = len(context.requests) + if start_len + self.step_dialog_len < self.to_dialog_len: + for i in range(start_len, start_len + self.step_dialog_len): + context.add_label((f"flow_{i}", f"node_{i}")) + context.add_request(bm.get_message(self.message_lengths)) + context.add_response(bm.get_message(self.message_lengths)) + return context + else: + return None + + return _context_updater + + def sizes(self): + return { + "starting_context_size": asizeof.asizeof( + bm.get_context(self.from_dialog_len, self.message_lengths, self.misc_lengths) + ), + "final_context_size": asizeof.asizeof( + bm.get_context(self.to_dialog_len, self.message_lengths, self.misc_lengths) + ), + "misc_size": asizeof.asizeof(bm.get_dict(self.misc_lengths)), + "message_size": asizeof.asizeof(bm.get_message(self.message_lengths)), + } + + def run(self): + try: + write_times, read_times, update_times = bm.time_context_read_write( + self.db_factory.db(), + bm.get_context(self.from_dialog_len, self.message_lengths, self.misc_lengths), + self.context_num, + context_updater=self.get_context_updater() + ) + return { + "success": True, + "result": { + "write_times": write_times, + "read_times": read_times, + "update_times": update_times, + } + } + except Exception as e: + return { + "success": False, + "result": getattr(e, "message", repr(e)) + } + + +def save_results_to_file( + benchmark_cases: tp.List[BenchmarkCase], + file: str | pathlib.Path, + name: str, + description: str, +): + uuid = str(uuid4()) + result: tp.Dict[str, tp.Any] = { + "name": name, + "description": description, + "uuid": uuid, + } + for case in benchmark_cases: + result[case.uuid] = {**case.dict(), **case.sizes(), **case.run()} + + with open(file, "w", encoding="utf-8") as fd: + json.dump(result, fd) + + +def benchmark_all( + file: str | pathlib.Path, + name: str, + description: str, + db_uris: tp.Dict[str, str], + case_name_postfix: str = "", + context_num: int = 100, + from_dialog_len: int = 300, + to_dialog_len: int = 500, + step_dialog_len: int = 10, + message_lengths: tp.Tuple[int, ...] = (10, 10), + misc_lengths: tp.Tuple[int, ...] = (10, 10), +): + benchmark_cases = [] + for db, uri in db_uris.items(): + benchmark_cases.append( + BenchmarkCase( + name=db + "-dev" + case_name_postfix, + db_factory=DBFactory(base_factory="dev", uri=uri), + context_num=context_num, + from_dialog_len=from_dialog_len, + to_dialog_len=to_dialog_len, + step_dialog_len=step_dialog_len, + message_lengths=message_lengths, + misc_lengths=misc_lengths, + ) + ) + benchmark_cases.append( + BenchmarkCase( + name=db + "-partial" + case_name_postfix, + db_factory=DBFactory(base_factory="partial", uri=uri), + context_num=context_num, + from_dialog_len=from_dialog_len, + to_dialog_len=to_dialog_len, + step_dialog_len=step_dialog_len, + message_lengths=message_lengths, + misc_lengths=misc_lengths, + ) + ) + save_results_to_file(benchmark_cases, file, name, description) + + +# create dir and files + +pathlib.Path("dbs").mkdir(exist_ok=True) +sqlite_file = pathlib.Path("dbs/sqlite.db") +sqlite_file.touch(exist_ok=True) +sqlite_separator = "///" if system() == "Windows" else "////" + +dbs = { + # "JSON": "json://dbs/json.json", + # "Pickle": "pickle://dbs/pickle.pkl", + "Shelve": "shelve://dbs/shelve", + "PostgreSQL": "postgresql+asyncpg://postgres:pass@localhost:5432/test", + "MongoDB": "mongodb://admin:pass@localhost:27017/admin", + "Redis": "redis://:pass@localhost:6379/0", + "MySQL": "mysql+asyncmy://root:pass@localhost:3307/test", + "SQLite": f"sqlite+aiosqlite:{sqlite_separator}{sqlite_file.absolute()}", + # "YDB": "grpc://localhost:2136/local", +} + +# benchmark +pathlib.Path("benchmarks").mkdir(exist_ok=True) + +benchmark_all( + "benchmarks/alexaprize.json", + "Alexaprize-like dialogue benchmarks", + "This benchmark set tests against parameters that mimic dialogues from alexaprize.", + db_uris=dbs, + from_dialog_len=1, + to_dialog_len=50, + message_lengths=(3, 5, 6, 5, 3), + misc_lengths=(2, 4, 3, 8, 100), +) + +benchmark_all( + "benchmarks/alexaprize_longer.json", + "Alexaprize-like dialogue benchmarks (longer)", + "This benchmark set tests against parameters that mimic dialogues from alexaprize," + "but dialog len is increased.", + db_uris=dbs, + from_dialog_len=100, + to_dialog_len=1001, + step_dialog_len=100, + message_lengths=(3, 5, 6, 5, 3), + misc_lengths=(2, 4, 3, 8, 100), +) + +benchmark_all( + "benchmarks/short_messages.json", + "Short messages", + "This benchmark set tests with short messages, long dialog len.", + db_uris=dbs, + from_dialog_len=100, + to_dialog_len=1001, + step_dialog_len=100, + message_lengths=(2, 30), + misc_lengths=(0, 0), +) + +benchmark_all( + "benchmarks/default.json", + "Default", + "This benchmark set tests using default parameters.", + db_uris=dbs, +) diff --git a/benchmark_streamlit.py b/benchmark_streamlit.py new file mode 100644 index 000000000..1c4c15046 --- /dev/null +++ b/benchmark_streamlit.py @@ -0,0 +1,314 @@ +import streamlit as st +import json +from pathlib import Path +from statistics import mean +import pandas as pd +import numpy as np +from pympler import asizeof +from humanize import naturalsize +import altair as alt + + +st.set_page_config( + page_title="DB benchmark", + layout="wide", + initial_sidebar_state="expanded", +) + + +benchmark_results_files = Path("benchmark_results_files.json") + +if not benchmark_results_files.exists(): + with open(benchmark_results_files, "w", encoding="utf-8") as fd: + json.dump([], fd) + +if "benchmark_files" not in st.session_state: + with open(benchmark_results_files, "r", encoding="utf-8") as fd: + st.session_state["benchmark_files"] = json.load(fd) + +if "benchmarks" not in st.session_state: + st.session_state["benchmarks"] = {} + + for file in st.session_state["benchmark_files"]: + with open(file, "r", encoding="utf-8") as fd: + st.session_state["benchmarks"][file] = json.load(fd) + +if "compare" not in st.session_state: + st.session_state["compare"] = [] + + +def set_average_results(benchmark): + if not benchmark["success"] or isinstance(benchmark["result"], str): + return + + def get_complex_stats(results): + average_grouped_by_context_num = [mean(times.values()) for times in results] + average_grouped_by_dialog_len = {key: mean([times[key] for times in results]) for key in results[0].keys()} + average = mean(average_grouped_by_context_num) + return average_grouped_by_context_num, average_grouped_by_dialog_len, average + + read_stats = get_complex_stats(benchmark["result"]["read_times"]) + update_stats = get_complex_stats(benchmark["result"]["update_times"]) + + result = { + "average_write_time": mean(benchmark["result"]["write_times"]), + "average_read_time": read_stats[2], + "average_update_time": update_stats[2], + "write_times": benchmark["result"]["write_times"], + "read_times_grouped_by_context_num": read_stats[0], + "read_times_grouped_by_dialog_len": read_stats[1], + "update_times_grouped_by_context_num": update_stats[0], + "update_times_grouped_by_dialog_len": update_stats[1], + } + result["pretty_write"] = float(f'{result["average_write_time"]:.3}') + result["pretty_read"] = float(f'{result["average_read_time"]:.3}') + result["pretty_update"] = float(f'{result["average_update_time"]:.3}') + + benchmark["average_results"] = result + + +st.sidebar.text(f"Benchmarks take {naturalsize(asizeof.asizeof(st.session_state['benchmarks']))} RAM") + +add_tab, view_tab, compare_tab = st.tabs(["Benchmark sets", "View", "Compare"]) + + +############################################################################### +# Benchmark file manipulation tab +# Allows adding and deleting benchmark files +############################################################################### + +with add_tab: + benchmark_list = [] + + for file, benchmark in st.session_state["benchmarks"].items(): + benchmark_list.append( + { + "file": file, + "name": benchmark["name"], + "description": benchmark["description"], + "uuid": benchmark["uuid"], + "delete": False, + } + ) + + df = pd.DataFrame(data=benchmark_list) + edited_df = st.data_editor(df, disabled=("file", "name", "description", "uuid")) + + delist_container = st.container() + delist_container.divider() + + def delist_benchmarks(): + delisted_sets = [f"{name} ({uuid})" + for name, uuid in edited_df.loc[edited_df["delete"]][["name", "uuid"]].values + ] + + st.session_state["compare"] = [ + item for item in st.session_state["compare"] if item["benchmark_set"] not in delisted_sets + ] + + files_to_delist = edited_df.loc[edited_df["delete"]]["file"] + st.session_state["benchmark_files"] = list(set(st.session_state["benchmark_files"]) - set(files_to_delist)) + for file in files_to_delist: + del st.session_state["benchmarks"][file] + delist_container.text(f"Delisted {file}") + + + delist_container.button(label="Delist selected benchmark sets", on_click=delist_benchmarks) + + add_container = st.container() + add_container.divider() + + add_container.text_input(label="Benchmark set file", key="add_benchmark_file") + + def add_benchmark(): + benchmark_file = st.session_state["add_benchmark_file"] + if benchmark_file == "": + return + + if benchmark_file in st.session_state["benchmark_files"]: + add_container.warning("Benchmark file already added") + return + + if not Path(benchmark_file).exists(): + add_container.warning("File does not exists") + return + + with open(benchmark_file, "r", encoding="utf-8") as fd: + file_contents = json.load(fd) + + for benchmark in st.session_state["benchmarks"].values(): + if file_contents["uuid"] == benchmark["uuid"]: + add_container.warning("Benchmark with the same uuid already exists") + return + + st.session_state["benchmark_files"].append(benchmark_file) + with open(benchmark_results_files, "w", encoding="utf-8") as fd: + json.dump(list(st.session_state["benchmark_files"]), fd) + st.session_state["benchmarks"][benchmark_file] = file_contents + + add_container.text(f"Added {benchmark_file} set") + + add_container.button("Add benchmark set from file", on_click=add_benchmark) + +############################################################################### +# View tab +# Allows viewing existing benchmarks +############################################################################### + +with view_tab: + set_choice, benchmark_choice, compare = st.columns([3, 3, 1]) + + sets = { + f"{benchmark['name']} ({benchmark['uuid']})": benchmark + for benchmark in st.session_state["benchmarks"].values() + } + benchmark_set = set_choice.selectbox("Benchmark set", sets.keys()) + + if benchmark_set is None: + set_choice.warning("No benchmark sets available") + st.stop() + + selected_set = sets[benchmark_set] + + set_choice.text("Set description:") + set_choice.markdown(selected_set["description"]) + + benchmarks = { + f"{benchmark['name']} ({benchmark['uuid']})": benchmark + for field, benchmark in selected_set.items() + if field not in ("name", "description", "uuid") + } + + benchmark = benchmark_choice.selectbox("Benchmark", benchmarks.keys()) + + if benchmark is None: + benchmark_choice.warning("No benchmarks in the set") + st.stop() + + selected_benchmark = benchmarks[benchmark] + + benchmark_choice.text("Benchmark description:") + benchmark_choice.markdown(selected_benchmark["description"]) + + with st.expander("Benchmark stats"): + reproducible_stats = { + stat: selected_benchmark[stat] + for stat in ( + "db_factory", + "context_num", + "from_dialog_len", + "to_dialog_len", + "step_dialog_len", + "message_lengths", + "misc_lengths", + ) + } + + size_stats = { + stat: naturalsize(selected_benchmark[stat], gnu=True) + for stat in ( + "starting_context_size", + "final_context_size", + "misc_size", + "message_size", + ) + } + + st.json(reproducible_stats) + st.json(size_stats) + + if not selected_benchmark["success"]: + st.warning(selected_benchmark["result"]) + else: + set_average_results(selected_benchmark) + + write, read, update = st.columns(3) + + write.metric("Write", selected_benchmark["average_results"]["pretty_write"]) + read.metric("Read", selected_benchmark["average_results"]["pretty_read"]) + update.metric("Update", selected_benchmark["average_results"]["pretty_update"]) + + compare_item = { + "benchmark_set": benchmark_set, + "benchmark": benchmark, + "write": selected_benchmark["average_results"]["average_write_time"], + "read": selected_benchmark["average_results"]["average_read_time"], + "update": selected_benchmark["average_results"]["average_update_time"], + } + + def add_results_to_compare_tab(): + if compare_item not in st.session_state["compare"]: + st.session_state["compare"].append(compare_item) + else: + st.session_state["compare"].remove(compare_item) + + compare.button( + "Add to Compare" if compare_item not in st.session_state["compare"] else "Remove from Compare", + on_click=add_results_to_compare_tab + ) + + select_graph, graph = st.columns([1, 3]) + + graphs = { + "Write": selected_benchmark["average_results"]["write_times"], + "Read (grouped by contex_num)": selected_benchmark["average_results"]["read_times_grouped_by_context_num"], + "Read (grouped by dialog_len)": selected_benchmark["average_results"]["read_times_grouped_by_dialog_len"], + "Update (grouped by contex_num)": selected_benchmark["average_results"]["update_times_grouped_by_context_num"], + "Update (grouped by dialog_len)": selected_benchmark["average_results"]["update_times_grouped_by_dialog_len"], + } + + selected_graph = select_graph.selectbox("Select graph to display", graphs.keys()) + + graph_data = graphs[selected_graph] + + if isinstance(graph_data, dict): + data = pd.DataFrame({"dialog_len": graph_data.keys(), "time": graph_data.values()}) + else: + data = pd.DataFrame({"context_num": range(len(graph_data)), "time": graph_data}) + + chart = alt.Chart(data).mark_circle().encode( + x="dialog_len:Q" if isinstance(graph_data, dict) else "context_num:Q", + y="time:Q", + ).interactive() + + graph.altair_chart(chart, use_container_width=True) + + +############################################################################### +# Compare tab +# Allows viewing existing benchmarks +############################################################################### + +with compare_tab: + df = pd.DataFrame(st.session_state["compare"]) + + if not df.empty: + st.dataframe( + df.style.highlight_min( + axis=0, subset=["write", "read", "update"], props='background-color:green;' + ).highlight_max( + axis=0, subset=["write", "read", "update"], props='background-color:red;' + ) + ) + + if len(st.session_state["compare"]) == 2: + def get_diff(last_metric, first_metric): + return (last_metric / first_metric - 1) * 100 + + write, read, update = st.columns(3) + + first_dict, second_dict = st.session_state["compare"] + + columns = { + "write": write, + "read": read, + "update": update, + } + + for column_name, column in columns.items(): + column.metric( + label=column_name.title(), + value=second_dict[column_name], + delta=f"{get_diff(second_dict[column_name], first_dict[column_name])} %", + delta_color="inverse" + ) From 3c422274b4118c79a2d889930d31586b3930da54 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Tue, 20 Jun 2023 16:18:59 +0300 Subject: [PATCH 012/113] update dependencies --- benchmark_streamlit.py | 1 - setup.py | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/benchmark_streamlit.py b/benchmark_streamlit.py index 1c4c15046..dd6a48985 100644 --- a/benchmark_streamlit.py +++ b/benchmark_streamlit.py @@ -3,7 +3,6 @@ from pathlib import Path from statistics import mean import pandas as pd -import numpy as np from pympler import asizeof from humanize import naturalsize import altair as alt diff --git a/setup.py b/setup.py index 4c06f3479..0f47d0ef9 100644 --- a/setup.py +++ b/setup.py @@ -82,6 +82,7 @@ def merge_req_lists(*req_lists: List[str]) -> List[str]: benchmark_dependencies = [ "pympler", "tqdm", + "humanize", ] full = merge_req_lists( @@ -123,6 +124,9 @@ def merge_req_lists(*req_lists: List[str]) -> List[str]: "websockets~=11.0.2", "locust~=2.15", "matplotlib", + "streamlit", + "pandas", + "altair", ], requests_requirements, ) From 15795df76326f00da4bf394ecb3a9eaa981d9386 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Tue, 20 Jun 2023 17:07:42 +0300 Subject: [PATCH 013/113] use python3.8 compatible typing --- benchmark_dbs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/benchmark_dbs.py b/benchmark_dbs.py index 1b4eaa462..d54d581ea 100644 --- a/benchmark_dbs.py +++ b/benchmark_dbs.py @@ -91,7 +91,7 @@ def run(self): def save_results_to_file( benchmark_cases: tp.List[BenchmarkCase], - file: str | pathlib.Path, + file: tp.Union[str, pathlib.Path], name: str, description: str, ): @@ -109,7 +109,7 @@ def save_results_to_file( def benchmark_all( - file: str | pathlib.Path, + file: tp.Union[str, pathlib.Path], name: str, description: str, db_uris: tp.Dict[str, str], From 13cc2d7429d7113667e3b60773866aee5b79b6bd Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Tue, 20 Jun 2023 23:16:20 +0300 Subject: [PATCH 014/113] return ydb & reorder benchmark sets --- benchmark_dbs.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/benchmark_dbs.py b/benchmark_dbs.py index d54d581ea..8ba101ca6 100644 --- a/benchmark_dbs.py +++ b/benchmark_dbs.py @@ -166,7 +166,7 @@ def benchmark_all( "Redis": "redis://:pass@localhost:6379/0", "MySQL": "mysql+asyncmy://root:pass@localhost:3307/test", "SQLite": f"sqlite+aiosqlite:{sqlite_separator}{sqlite_file.absolute()}", - # "YDB": "grpc://localhost:2136/local", + "YDB": "grpc://localhost:2136/local", } # benchmark @@ -183,19 +183,6 @@ def benchmark_all( misc_lengths=(2, 4, 3, 8, 100), ) -benchmark_all( - "benchmarks/alexaprize_longer.json", - "Alexaprize-like dialogue benchmarks (longer)", - "This benchmark set tests against parameters that mimic dialogues from alexaprize," - "but dialog len is increased.", - db_uris=dbs, - from_dialog_len=100, - to_dialog_len=1001, - step_dialog_len=100, - message_lengths=(3, 5, 6, 5, 3), - misc_lengths=(2, 4, 3, 8, 100), -) - benchmark_all( "benchmarks/short_messages.json", "Short messages", @@ -214,3 +201,16 @@ def benchmark_all( "This benchmark set tests using default parameters.", db_uris=dbs, ) + +benchmark_all( + "benchmarks/alexaprize_longer.json", + "Alexaprize-like dialogue benchmarks (longer)", + "This benchmark set tests against parameters that mimic dialogues from alexaprize," + "but dialog len is increased.", + db_uris=dbs, + from_dialog_len=100, + to_dialog_len=1001, + step_dialog_len=100, + message_lengths=(3, 5, 6, 5, 3), + misc_lengths=(2, 4, 3, 8, 100), +) From 25c5b7eeee2c98d819dfc6935bb586703fd1cb95 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Wed, 21 Jun 2023 05:25:53 +0300 Subject: [PATCH 015/113] add more benchmark cases --- benchmark_dbs.py | 73 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 63 insertions(+), 10 deletions(-) diff --git a/benchmark_dbs.py b/benchmark_dbs.py index 8ba101ca6..232e087b7 100644 --- a/benchmark_dbs.py +++ b/benchmark_dbs.py @@ -108,10 +108,7 @@ def save_results_to_file( json.dump(result, fd) -def benchmark_all( - file: tp.Union[str, pathlib.Path], - name: str, - description: str, +def get_cases( db_uris: tp.Dict[str, str], case_name_postfix: str = "", context_num: int = 100, @@ -120,6 +117,7 @@ def benchmark_all( step_dialog_len: int = 10, message_lengths: tp.Tuple[int, ...] = (10, 10), misc_lengths: tp.Tuple[int, ...] = (10, 10), + description: str = "", ): benchmark_cases = [] for db, uri in db_uris.items(): @@ -133,6 +131,7 @@ def benchmark_all( step_dialog_len=step_dialog_len, message_lengths=message_lengths, misc_lengths=misc_lengths, + description=description, ) ) benchmark_cases.append( @@ -145,9 +144,36 @@ def benchmark_all( step_dialog_len=step_dialog_len, message_lengths=message_lengths, misc_lengths=misc_lengths, + description=description, ) ) - save_results_to_file(benchmark_cases, file, name, description) + return benchmark_cases + + +def benchmark_all( + file: tp.Union[str, pathlib.Path], + name: str, + description: str, + db_uris: tp.Dict[str, str], + case_name_postfix: str = "", + context_num: int = 100, + from_dialog_len: int = 300, + to_dialog_len: int = 500, + step_dialog_len: int = 10, + message_lengths: tp.Tuple[int, ...] = (10, 10), + misc_lengths: tp.Tuple[int, ...] = (10, 10), +): + save_results_to_file(get_cases( + db_uris, + case_name_postfix, + context_num, + from_dialog_len, + to_dialog_len, + step_dialog_len, + message_lengths, + misc_lengths, + description=description, + ), file, name, description) # create dir and files @@ -175,7 +201,7 @@ def benchmark_all( benchmark_all( "benchmarks/alexaprize.json", "Alexaprize-like dialogue benchmarks", - "This benchmark set tests against parameters that mimic dialogues from alexaprize.", + "Benchmark with dialogues similar to those from alexaprize.", db_uris=dbs, from_dialog_len=1, to_dialog_len=50, @@ -186,7 +212,7 @@ def benchmark_all( benchmark_all( "benchmarks/short_messages.json", "Short messages", - "This benchmark set tests with short messages, long dialog len.", + "Benchmark with short messages, long dialog len.", db_uris=dbs, from_dialog_len=100, to_dialog_len=1001, @@ -198,15 +224,14 @@ def benchmark_all( benchmark_all( "benchmarks/default.json", "Default", - "This benchmark set tests using default parameters.", + "Benchmark using default parameters.", db_uris=dbs, ) benchmark_all( "benchmarks/alexaprize_longer.json", "Alexaprize-like dialogue benchmarks (longer)", - "This benchmark set tests against parameters that mimic dialogues from alexaprize," - "but dialog len is increased.", + "Benchmark with dialogues similar to those from alexaprize, but dialog len is increased.", db_uris=dbs, from_dialog_len=100, to_dialog_len=1001, @@ -214,3 +239,31 @@ def benchmark_all( message_lengths=(3, 5, 6, 5, 3), misc_lengths=(2, 4, 3, 8, 100), ) + +save_results_to_file( + [ + *get_cases( + db_uris=dbs, + case_name_postfix="-long-dialog-len", + from_dialog_len=10000, + to_dialog_len=11001, + step_dialog_len=100, + description="Benchmark with very long dialog len." + ), + *get_cases( + db_uris=dbs, + case_name_postfix="-long-message-len", + message_lengths=(10000, 1), + description="Benchmark with messages containing many keys." + ), + *get_cases( + db_uris=dbs, + case_name_postfix="-long-misc-len", + misc_lengths=(10000, 1), + description="Benchmark with misc containing many keys." + ), + ], + file="benchmarks/extremes.json", + name="Extreme", + description="Set of benchmarks testing extreme cases." +) From 2df1054daddef081901a604deae124ad4fe2e246 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Wed, 21 Jun 2023 05:26:48 +0300 Subject: [PATCH 016/113] improve diff viewing --- benchmark_streamlit.py | 92 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 84 insertions(+), 8 deletions(-) diff --git a/benchmark_streamlit.py b/benchmark_streamlit.py index dd6a48985..e55916368 100644 --- a/benchmark_streamlit.py +++ b/benchmark_streamlit.py @@ -36,10 +36,51 @@ st.session_state["compare"] = [] +def get_diff(last_metric, first_metric): + if st.session_state["percent_compare"]: + return f"{(last_metric / first_metric - 1):.3%}" + else: + return f"{last_metric - first_metric:.3}" + + +def get_opposite_benchmark(benchmark_set, benchmark): + compare_params = ( + ["db_factory", "uri"], + ("context_num", ), + ("from_dialog_len", ), + ("to_dialog_len", ), + ("step_dialog_len", ), + ("message_lengths", ), + ("misc_lengths", ), + ) + + def get_param(bench, param): + if len(param) == 1: + return bench.get(param[0]) + else: + return get_param(bench.get(param[0]), param[1:]) + + opposite_benchmarks = [ + opposite_benchmark + for field, opposite_benchmark in benchmark_set.items() + if field not in ("name", "description", "uuid") and opposite_benchmark["uuid"] != benchmark["uuid"] and all( + get_param(benchmark, param) == get_param(opposite_benchmark, param) for param in compare_params + ) + ] + + if len(opposite_benchmarks) == 1: + return opposite_benchmarks[0] + else: + return None + + def set_average_results(benchmark): if not benchmark["success"] or isinstance(benchmark["result"], str): return + if benchmark.get("average_results") is not None: + return + def get_complex_stats(results): average_grouped_by_context_num = [mean(times.values()) for times in results] average_grouped_by_dialog_len = {key: mean([times[key] for times in results]) for key in results[0].keys()} @@ -68,6 +109,11 @@ def get_complex_stats(results): st.sidebar.text(f"Benchmarks take {naturalsize(asizeof.asizeof(st.session_state['benchmarks']))} RAM") +st.sidebar.divider() + +st.sidebar.checkbox("Compare dev and partial in view tab", value=True, key="partial_compare_checkbox") +st.sidebar.checkbox("Percent comparison", value=True, key="percent_compare") + add_tab, view_tab, compare_tab = st.tabs(["Benchmark sets", "View", "Compare"]) @@ -221,11 +267,44 @@ def add_benchmark(): else: set_average_results(selected_benchmark) + diffs = None + if st.session_state["partial_compare_checkbox"]: + opposite_benchmark = get_opposite_benchmark(selected_set, selected_benchmark) + + if opposite_benchmark: + if not opposite_benchmark["success"]: + diffs = { + "write": "-", + "read": "-", + "update": "-", + } + else: + set_average_results(opposite_benchmark) + diffs = { + key: get_diff( + selected_benchmark["average_results"][f"pretty_{key}"], + opposite_benchmark["average_results"][f"pretty_{key}"] + ) for key in ("write", "read", "update") + } + write, read, update = st.columns(3) - write.metric("Write", selected_benchmark["average_results"]["pretty_write"]) - read.metric("Read", selected_benchmark["average_results"]["pretty_read"]) - update.metric("Update", selected_benchmark["average_results"]["pretty_update"]) + columns = { + "write": write, + "read": read, + "update": update, + } + + for column_name, column in columns.items(): + column.metric( + column_name.title(), + selected_benchmark["average_results"][f"pretty_{column_name}"], + delta=diffs[column_name] if diffs else None, + delta_color="inverse" + ) + + if diffs: + st.text(f"* In comparison with {opposite_benchmark['name']} ({opposite_benchmark['uuid']})") compare_item = { "benchmark_set": benchmark_set, @@ -291,9 +370,6 @@ def add_results_to_compare_tab(): ) if len(st.session_state["compare"]) == 2: - def get_diff(last_metric, first_metric): - return (last_metric / first_metric - 1) * 100 - write, read, update = st.columns(3) first_dict, second_dict = st.session_state["compare"] @@ -307,7 +383,7 @@ def get_diff(last_metric, first_metric): for column_name, column in columns.items(): column.metric( label=column_name.title(), - value=second_dict[column_name], - delta=f"{get_diff(second_dict[column_name], first_dict[column_name])} %", + value=f"{second_dict[column_name]:.3}", + delta=get_diff(second_dict[column_name], first_dict[column_name]), delta_color="inverse" ) From cd0eabf68bbd0524dda097b31645a5b6e9bcb28d Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Wed, 21 Jun 2023 12:34:01 +0300 Subject: [PATCH 017/113] reduce dialog len for extreme cases --- benchmark_dbs.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/benchmark_dbs.py b/benchmark_dbs.py index 232e087b7..22441957e 100644 --- a/benchmark_dbs.py +++ b/benchmark_dbs.py @@ -192,7 +192,7 @@ def benchmark_all( "Redis": "redis://:pass@localhost:6379/0", "MySQL": "mysql+asyncmy://root:pass@localhost:3307/test", "SQLite": f"sqlite+aiosqlite:{sqlite_separator}{sqlite_file.absolute()}", - "YDB": "grpc://localhost:2136/local", + # "YDB": "grpc://localhost:2136/local", } # benchmark @@ -253,12 +253,16 @@ def benchmark_all( *get_cases( db_uris=dbs, case_name_postfix="-long-message-len", + from_dialog_len=1, + to_dialog_len=2, message_lengths=(10000, 1), description="Benchmark with messages containing many keys." ), *get_cases( db_uris=dbs, case_name_postfix="-long-misc-len", + from_dialog_len=1, + to_dialog_len=2, misc_lengths=(10000, 1), description="Benchmark with misc containing many keys." ), From 185d9506bca43e97a06216063261b776ba2acc2d Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Wed, 21 Jun 2023 14:38:11 +0300 Subject: [PATCH 018/113] change benchmark format --- benchmark_dbs.py | 3 ++- benchmark_new_format.py | 18 ++++++++++++++++++ benchmark_streamlit.py | 15 +++++++-------- 3 files changed, 27 insertions(+), 9 deletions(-) create mode 100644 benchmark_new_format.py diff --git a/benchmark_dbs.py b/benchmark_dbs.py index 22441957e..403e22238 100644 --- a/benchmark_dbs.py +++ b/benchmark_dbs.py @@ -100,9 +100,10 @@ def save_results_to_file( "name": name, "description": description, "uuid": uuid, + "benchmarks": {}, } for case in benchmark_cases: - result[case.uuid] = {**case.dict(), **case.sizes(), **case.run()} + result["benchmarks"][case.uuid] = {**case.dict(), **case.sizes(), **case.run()} with open(file, "w", encoding="utf-8") as fd: json.dump(result, fd) diff --git a/benchmark_new_format.py b/benchmark_new_format.py new file mode 100644 index 000000000..8166675b4 --- /dev/null +++ b/benchmark_new_format.py @@ -0,0 +1,18 @@ +import json +import pathlib + +benchmark_path = pathlib.Path("benchmarks") + +for file in benchmark_path.iterdir(): + if file.suffix == ".json": + with open(file, "r") as fd: + benchmark_set = json.load(fd) + + non_benchmark_fields = ("name", "description", "uuid", "benchmarks") + + new_benchmark_set = {k: v for k, v in benchmark_set.items() if k in non_benchmark_fields} + + new_benchmark_set["benchmarks"] = {k: v for k, v in benchmark_set.items() if k not in non_benchmark_fields} + + with open(file, "w") as fd: + json.dump(new_benchmark_set, fd) diff --git a/benchmark_streamlit.py b/benchmark_streamlit.py index e55916368..4a964e8ee 100644 --- a/benchmark_streamlit.py +++ b/benchmark_streamlit.py @@ -62,8 +62,8 @@ def get_param(bench, param): opposite_benchmarks = [ opposite_benchmark - for field, opposite_benchmark in benchmark_set.items() - if field not in ("name", "description", "uuid") and opposite_benchmark["uuid"] != benchmark["uuid"] and all( + for opposite_benchmark in benchmark_set["benchmarks"].values() + if opposite_benchmark["uuid"] != benchmark["uuid"] and all( get_param(benchmark, param) == get_param(opposite_benchmark, param) for param in compare_params ) ] @@ -125,13 +125,13 @@ def get_complex_stats(results): with add_tab: benchmark_list = [] - for file, benchmark in st.session_state["benchmarks"].items(): + for file, benchmark_set in st.session_state["benchmarks"].items(): benchmark_list.append( { "file": file, - "name": benchmark["name"], - "description": benchmark["description"], - "uuid": benchmark["uuid"], + "name": benchmark_set["name"], + "description": benchmark_set["description"], + "uuid": benchmark_set["uuid"], "delete": False, } ) @@ -220,8 +220,7 @@ def add_benchmark(): benchmarks = { f"{benchmark['name']} ({benchmark['uuid']})": benchmark - for field, benchmark in selected_set.items() - if field not in ("name", "description", "uuid") + for benchmark in selected_set["benchmarks"].values() } benchmark = benchmark_choice.selectbox("Benchmark", benchmarks.keys()) From c16cd28cd0ed9afb22662e0d2c52724c41f46ebf Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Wed, 21 Jun 2023 15:09:12 +0300 Subject: [PATCH 019/113] change benchmark format: generic factory --- benchmark_dbs.py | 17 +++++++---------- benchmark_new_format.py | 17 +++++++++++++++++ 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/benchmark_dbs.py b/benchmark_dbs.py index 403e22238..4af2eaa03 100644 --- a/benchmark_dbs.py +++ b/benchmark_dbs.py @@ -3,14 +3,12 @@ import typing as tp from uuid import uuid4 import json +import importlib from pympler import asizeof from pydantic import BaseModel, Field from dff.script import Context -from dff.context_storages import context_storage_factory as partial_factory -from dff.context_storages_old import context_storage_factory as dev_factory - import dff.utils.benchmark.context_storage as bm @@ -18,14 +16,13 @@ # define benchmark classes and tools class DBFactory(BaseModel): - base_factory: tp.Literal["dev", "partial"] uri: str + factory_module: str = "dff.context_storages" + factory: str = "context_storage_factory" def db(self): - if self.base_factory == "dev": - return dev_factory(self.uri) - else: - return partial_factory(self.uri) + module = importlib.import_module(self.factory_module) + return getattr(module, self.factory)(self.uri) class BenchmarkCase(BaseModel): @@ -125,7 +122,7 @@ def get_cases( benchmark_cases.append( BenchmarkCase( name=db + "-dev" + case_name_postfix, - db_factory=DBFactory(base_factory="dev", uri=uri), + db_factory=DBFactory(uri=uri, factory_module="dff.context_storages_old"), context_num=context_num, from_dialog_len=from_dialog_len, to_dialog_len=to_dialog_len, @@ -138,7 +135,7 @@ def get_cases( benchmark_cases.append( BenchmarkCase( name=db + "-partial" + case_name_postfix, - db_factory=DBFactory(base_factory="partial", uri=uri), + db_factory=DBFactory(uri=uri), context_num=context_num, from_dialog_len=from_dialog_len, to_dialog_len=to_dialog_len, diff --git a/benchmark_new_format.py b/benchmark_new_format.py index 8166675b4..d7e5b8dd7 100644 --- a/benchmark_new_format.py +++ b/benchmark_new_format.py @@ -14,5 +14,22 @@ new_benchmark_set["benchmarks"] = {k: v for k, v in benchmark_set.items() if k not in non_benchmark_fields} + for key, benchmark in new_benchmark_set["benchmarks"].items(): + if benchmark["db_factory"].get("base_factory") == "dev": + postfix = "_old" + elif benchmark["db_factory"].get("base_factory") == "partial": + postfix = "" + else: + postfix = None + + if postfix is not None: + benchmark["db_factory"].pop("base_factory", None) + benchmark["db_factory"].update( + { + "factory_module": "dff.context_storages" + postfix, + "factory": "context_storage_factory", + } + ) + with open(file, "w") as fd: json.dump(new_benchmark_set, fd) From 5de5a83ac06a479f85990722d0654c171cc569d0 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Wed, 21 Jun 2023 15:14:42 +0300 Subject: [PATCH 020/113] bugfix: repeated format update --- benchmark_new_format.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/benchmark_new_format.py b/benchmark_new_format.py index d7e5b8dd7..24fbecc33 100644 --- a/benchmark_new_format.py +++ b/benchmark_new_format.py @@ -12,7 +12,8 @@ new_benchmark_set = {k: v for k, v in benchmark_set.items() if k in non_benchmark_fields} - new_benchmark_set["benchmarks"] = {k: v for k, v in benchmark_set.items() if k not in non_benchmark_fields} + if "benchmarks" not in benchmark_set: + new_benchmark_set["benchmarks"] = {k: v for k, v in benchmark_set.items() if k not in non_benchmark_fields} for key, benchmark in new_benchmark_set["benchmarks"].items(): if benchmark["db_factory"].get("base_factory") == "dev": From 8fc62a89a9a1a582f08244bfcbfa9fdbdd3ca932 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Wed, 21 Jun 2023 15:21:43 +0300 Subject: [PATCH 021/113] move generic benchmark tools to utils --- benchmark_dbs.py | 172 +------------------------ dff/utils/benchmark/context_storage.py | 165 ++++++++++++++++++++++++ 2 files changed, 166 insertions(+), 171 deletions(-) diff --git a/benchmark_dbs.py b/benchmark_dbs.py index 4af2eaa03..137ccb057 100644 --- a/benchmark_dbs.py +++ b/benchmark_dbs.py @@ -1,177 +1,7 @@ import pathlib from platform import system -import typing as tp -from uuid import uuid4 -import json -import importlib -from pympler import asizeof -from pydantic import BaseModel, Field - -from dff.script import Context - -import dff.utils.benchmark.context_storage as bm - - -# define benchmark classes and tools - -class DBFactory(BaseModel): - uri: str - factory_module: str = "dff.context_storages" - factory: str = "context_storage_factory" - - def db(self): - module = importlib.import_module(self.factory_module) - return getattr(module, self.factory)(self.uri) - - -class BenchmarkCase(BaseModel): - name: str - db_factory: DBFactory - uuid: str = Field(default_factory=lambda: str(uuid4())) - description: str = "" - context_num: int = 100 - from_dialog_len: int = 300 - to_dialog_len: int = 500 - step_dialog_len: int = 10 - message_lengths: tp.Tuple[int, ...] = (10, 10) - misc_lengths: tp.Tuple[int, ...] = (10, 10) - - def get_context_updater(self): - def _context_updater(context: Context): - start_len = len(context.requests) - if start_len + self.step_dialog_len < self.to_dialog_len: - for i in range(start_len, start_len + self.step_dialog_len): - context.add_label((f"flow_{i}", f"node_{i}")) - context.add_request(bm.get_message(self.message_lengths)) - context.add_response(bm.get_message(self.message_lengths)) - return context - else: - return None - - return _context_updater - - def sizes(self): - return { - "starting_context_size": asizeof.asizeof( - bm.get_context(self.from_dialog_len, self.message_lengths, self.misc_lengths) - ), - "final_context_size": asizeof.asizeof( - bm.get_context(self.to_dialog_len, self.message_lengths, self.misc_lengths) - ), - "misc_size": asizeof.asizeof(bm.get_dict(self.misc_lengths)), - "message_size": asizeof.asizeof(bm.get_message(self.message_lengths)), - } - - def run(self): - try: - write_times, read_times, update_times = bm.time_context_read_write( - self.db_factory.db(), - bm.get_context(self.from_dialog_len, self.message_lengths, self.misc_lengths), - self.context_num, - context_updater=self.get_context_updater() - ) - return { - "success": True, - "result": { - "write_times": write_times, - "read_times": read_times, - "update_times": update_times, - } - } - except Exception as e: - return { - "success": False, - "result": getattr(e, "message", repr(e)) - } - - -def save_results_to_file( - benchmark_cases: tp.List[BenchmarkCase], - file: tp.Union[str, pathlib.Path], - name: str, - description: str, -): - uuid = str(uuid4()) - result: tp.Dict[str, tp.Any] = { - "name": name, - "description": description, - "uuid": uuid, - "benchmarks": {}, - } - for case in benchmark_cases: - result["benchmarks"][case.uuid] = {**case.dict(), **case.sizes(), **case.run()} - - with open(file, "w", encoding="utf-8") as fd: - json.dump(result, fd) - - -def get_cases( - db_uris: tp.Dict[str, str], - case_name_postfix: str = "", - context_num: int = 100, - from_dialog_len: int = 300, - to_dialog_len: int = 500, - step_dialog_len: int = 10, - message_lengths: tp.Tuple[int, ...] = (10, 10), - misc_lengths: tp.Tuple[int, ...] = (10, 10), - description: str = "", -): - benchmark_cases = [] - for db, uri in db_uris.items(): - benchmark_cases.append( - BenchmarkCase( - name=db + "-dev" + case_name_postfix, - db_factory=DBFactory(uri=uri, factory_module="dff.context_storages_old"), - context_num=context_num, - from_dialog_len=from_dialog_len, - to_dialog_len=to_dialog_len, - step_dialog_len=step_dialog_len, - message_lengths=message_lengths, - misc_lengths=misc_lengths, - description=description, - ) - ) - benchmark_cases.append( - BenchmarkCase( - name=db + "-partial" + case_name_postfix, - db_factory=DBFactory(uri=uri), - context_num=context_num, - from_dialog_len=from_dialog_len, - to_dialog_len=to_dialog_len, - step_dialog_len=step_dialog_len, - message_lengths=message_lengths, - misc_lengths=misc_lengths, - description=description, - ) - ) - return benchmark_cases - - -def benchmark_all( - file: tp.Union[str, pathlib.Path], - name: str, - description: str, - db_uris: tp.Dict[str, str], - case_name_postfix: str = "", - context_num: int = 100, - from_dialog_len: int = 300, - to_dialog_len: int = 500, - step_dialog_len: int = 10, - message_lengths: tp.Tuple[int, ...] = (10, 10), - misc_lengths: tp.Tuple[int, ...] = (10, 10), -): - save_results_to_file(get_cases( - db_uris, - case_name_postfix, - context_num, - from_dialog_len, - to_dialog_len, - step_dialog_len, - message_lengths, - misc_lengths, - description=description, - ), file, name, description) +from dff.utils.benchmark.context_storage import benchmark_all, save_results_to_file, get_cases # create dir and files diff --git a/dff/utils/benchmark/context_storage.py b/dff/utils/benchmark/context_storage.py index 70ce1f605..3b77ddee0 100644 --- a/dff/utils/benchmark/context_storage.py +++ b/dff/utils/benchmark/context_storage.py @@ -15,14 +15,20 @@ """ from uuid import uuid4 +import pathlib from time import perf_counter import typing as tp from copy import deepcopy +import json +import importlib +from pydantic import BaseModel, Field from pympler import asizeof from tqdm.auto import tqdm from humanize import naturalsize +from dff.script import Context + try: import matplotlib from matplotlib.backends.backend_pdf import PdfPages @@ -321,3 +327,162 @@ def scatter_page(storage_name, write, read): text_page(txt) mpl_pdf.savefig() plt.close() + + +class DBFactory(BaseModel): + uri: str + factory_module: str = "dff.context_storages" + factory: str = "context_storage_factory" + + def db(self): + module = importlib.import_module(self.factory_module) + return getattr(module, self.factory)(self.uri) + + +class BenchmarkCase(BaseModel): + name: str + db_factory: DBFactory + uuid: str = Field(default_factory=lambda: str(uuid4())) + description: str = "" + context_num: int = 100 + from_dialog_len: int = 300 + to_dialog_len: int = 500 + step_dialog_len: int = 10 + message_lengths: tp.Tuple[int, ...] = (10, 10) + misc_lengths: tp.Tuple[int, ...] = (10, 10) + + def get_context_updater(self): + def _context_updater(context: Context): + start_len = len(context.requests) + if start_len + self.step_dialog_len < self.to_dialog_len: + for i in range(start_len, start_len + self.step_dialog_len): + context.add_label((f"flow_{i}", f"node_{i}")) + context.add_request(get_message(self.message_lengths)) + context.add_response(get_message(self.message_lengths)) + return context + else: + return None + + return _context_updater + + def sizes(self): + return { + "starting_context_size": asizeof.asizeof( + get_context(self.from_dialog_len, self.message_lengths, self.misc_lengths) + ), + "final_context_size": asizeof.asizeof( + get_context(self.to_dialog_len, self.message_lengths, self.misc_lengths) + ), + "misc_size": asizeof.asizeof(get_dict(self.misc_lengths)), + "message_size": asizeof.asizeof(get_message(self.message_lengths)), + } + + def run(self): + try: + write_times, read_times, update_times = time_context_read_write( + self.db_factory.db(), + get_context(self.from_dialog_len, self.message_lengths, self.misc_lengths), + self.context_num, + context_updater=self.get_context_updater() + ) + return { + "success": True, + "result": { + "write_times": write_times, + "read_times": read_times, + "update_times": update_times, + } + } + except Exception as e: + return { + "success": False, + "result": getattr(e, "message", repr(e)) + } + + +def save_results_to_file( + benchmark_cases: tp.List[BenchmarkCase], + file: tp.Union[str, pathlib.Path], + name: str, + description: str, +): + uuid = str(uuid4()) + result: tp.Dict[str, tp.Any] = { + "name": name, + "description": description, + "uuid": uuid, + "benchmarks": {}, + } + for case in benchmark_cases: + result["benchmarks"][case.uuid] = {**case.dict(), **case.sizes(), **case.run()} + + with open(file, "w", encoding="utf-8") as fd: + json.dump(result, fd) + + +def get_cases( + db_uris: tp.Dict[str, str], + case_name_postfix: str = "", + context_num: int = 100, + from_dialog_len: int = 300, + to_dialog_len: int = 500, + step_dialog_len: int = 10, + message_lengths: tp.Tuple[int, ...] = (10, 10), + misc_lengths: tp.Tuple[int, ...] = (10, 10), + description: str = "", +): + benchmark_cases = [] + for db, uri in db_uris.items(): + benchmark_cases.append( + BenchmarkCase( + name=db + "-dev" + case_name_postfix, + db_factory=DBFactory(uri=uri, factory_module="dff.context_storages_old"), + context_num=context_num, + from_dialog_len=from_dialog_len, + to_dialog_len=to_dialog_len, + step_dialog_len=step_dialog_len, + message_lengths=message_lengths, + misc_lengths=misc_lengths, + description=description, + ) + ) + benchmark_cases.append( + BenchmarkCase( + name=db + "-partial" + case_name_postfix, + db_factory=DBFactory(uri=uri), + context_num=context_num, + from_dialog_len=from_dialog_len, + to_dialog_len=to_dialog_len, + step_dialog_len=step_dialog_len, + message_lengths=message_lengths, + misc_lengths=misc_lengths, + description=description, + ) + ) + return benchmark_cases + + +def benchmark_all( + file: tp.Union[str, pathlib.Path], + name: str, + description: str, + db_uris: tp.Dict[str, str], + case_name_postfix: str = "", + context_num: int = 100, + from_dialog_len: int = 300, + to_dialog_len: int = 500, + step_dialog_len: int = 10, + message_lengths: tp.Tuple[int, ...] = (10, 10), + misc_lengths: tp.Tuple[int, ...] = (10, 10), +): + save_results_to_file(get_cases( + db_uris, + case_name_postfix, + context_num, + from_dialog_len, + to_dialog_len, + step_dialog_len, + message_lengths, + misc_lengths, + description=description, + ), file, name, description) \ No newline at end of file From 1f450e55c3a52ce9b845be3a48915ff2f475f836 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Wed, 21 Jun 2023 17:31:46 +0300 Subject: [PATCH 022/113] add average read+update column --- benchmark_streamlit.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/benchmark_streamlit.py b/benchmark_streamlit.py index 4a964e8ee..33d0ffba0 100644 --- a/benchmark_streamlit.py +++ b/benchmark_streamlit.py @@ -103,6 +103,7 @@ def get_complex_stats(results): result["pretty_write"] = float(f'{result["average_write_time"]:.3}') result["pretty_read"] = float(f'{result["average_read_time"]:.3}') result["pretty_update"] = float(f'{result["average_update_time"]:.3}') + result["pretty_read+update"] = float(f'{result["average_read_time"] + result["average_update_time"]:.3}') benchmark["average_results"] = result @@ -276,6 +277,7 @@ def add_benchmark(): "write": "-", "read": "-", "update": "-", + "read+update": "-", } else: set_average_results(opposite_benchmark) @@ -283,15 +285,16 @@ def add_benchmark(): key: get_diff( selected_benchmark["average_results"][f"pretty_{key}"], opposite_benchmark["average_results"][f"pretty_{key}"] - ) for key in ("write", "read", "update") + ) for key in ("write", "read", "update", "read+update") } - write, read, update = st.columns(3) + write, read, update, read_update = st.columns(4) columns = { "write": write, "read": read, "update": update, + "read+update": read_update, } for column_name, column in columns.items(): @@ -308,9 +311,10 @@ def add_benchmark(): compare_item = { "benchmark_set": benchmark_set, "benchmark": benchmark, - "write": selected_benchmark["average_results"]["average_write_time"], - "read": selected_benchmark["average_results"]["average_read_time"], - "update": selected_benchmark["average_results"]["average_update_time"], + "write": selected_benchmark["average_results"]["pretty_write"], + "read": selected_benchmark["average_results"]["pretty_read"], + "update": selected_benchmark["average_results"]["pretty_update"], + "read+update": selected_benchmark["average_results"]["pretty_read+update"], } def add_results_to_compare_tab(): @@ -362,14 +366,14 @@ def add_results_to_compare_tab(): if not df.empty: st.dataframe( df.style.highlight_min( - axis=0, subset=["write", "read", "update"], props='background-color:green;' + axis=0, subset=["write", "read", "update", "read+update"], props='background-color:green;' ).highlight_max( - axis=0, subset=["write", "read", "update"], props='background-color:red;' + axis=0, subset=["write", "read", "update", "read+update"], props='background-color:red;' ) ) if len(st.session_state["compare"]) == 2: - write, read, update = st.columns(3) + write, read, update, read_update = st.columns(4) first_dict, second_dict = st.session_state["compare"] @@ -377,12 +381,13 @@ def add_results_to_compare_tab(): "write": write, "read": read, "update": update, + "read+update": read_update } for column_name, column in columns.items(): column.metric( label=column_name.title(), - value=f"{second_dict[column_name]:.3}", + value=f"{second_dict[column_name]}", delta=get_diff(second_dict[column_name], first_dict[column_name]), delta_color="inverse" ) From 4404ef3a8ed909ed77cdfdd7b22be8be3255fbe3 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Wed, 21 Jun 2023 19:00:19 +0300 Subject: [PATCH 023/113] add mass compare tab --- benchmark_streamlit.py | 135 ++++++++++++++++++++++++++++------------- 1 file changed, 92 insertions(+), 43 deletions(-) diff --git a/benchmark_streamlit.py b/benchmark_streamlit.py index 33d0ffba0..5fa8f5fdb 100644 --- a/benchmark_streamlit.py +++ b/benchmark_streamlit.py @@ -43,9 +43,52 @@ def get_diff(last_metric, first_metric): return f"{last_metric - first_metric:.3}" -def get_opposite_benchmark(benchmark_set, benchmark): +def add_metrics(container, value_benchmark, diff_benchmark=None): + write, read, update, read_update = container.columns(4) + column_names = ("write", "read", "update", "read+update") + + set_average_results(value_benchmark) + if not value_benchmark["success"]: + values = {key: "-" for key in column_names} + diffs = None + else: + values = { + key: value_benchmark["average_results"][f"pretty_{key}"] for key in column_names + } + + if diff_benchmark is not None: + set_average_results(diff_benchmark) + if not diff_benchmark["success"]: + diffs = {key: "-" for key in column_names} + else: + diffs = { + key: get_diff( + value_benchmark["average_results"][f"pretty_{key}"], + diff_benchmark["average_results"][f"pretty_{key}"] + ) for key in column_names + } + else: + diffs = None + + columns = { + "write": write, + "read": read, + "update": update, + "read+update": read_update, + } + + for column_name, column in columns.items(): + column.metric( + column_name.title(), + values[column_name], + delta=diffs[column_name] if diffs else None, + delta_color="inverse" + ) + + +def get_opposite_benchmarks(benchmark_set, benchmark): compare_params = ( - ["db_factory", "uri"], + ("db_factory", "uri"), ("context_num", ), ("from_dialog_len", ), ("to_dialog_len", ), @@ -68,10 +111,7 @@ def get_param(bench, param): ) ] - if len(opposite_benchmarks) == 1: - return opposite_benchmarks[0] - else: - return None + return opposite_benchmarks def set_average_results(benchmark): @@ -115,7 +155,7 @@ def get_complex_stats(results): st.sidebar.checkbox("Compare dev and partial in view tab", value=True, key="partial_compare_checkbox") st.sidebar.checkbox("Percent comparison", value=True, key="percent_compare") -add_tab, view_tab, compare_tab = st.tabs(["Benchmark sets", "View", "Compare"]) +add_tab, view_tab, compare_tab, mass_compare_tab = st.tabs(["Benchmark sets", "View", "Compare", "Mass compare"]) ############################################################################### @@ -267,45 +307,17 @@ def add_benchmark(): else: set_average_results(selected_benchmark) - diffs = None + opposite_benchmark = None + if st.session_state["partial_compare_checkbox"]: - opposite_benchmark = get_opposite_benchmark(selected_set, selected_benchmark) - - if opposite_benchmark: - if not opposite_benchmark["success"]: - diffs = { - "write": "-", - "read": "-", - "update": "-", - "read+update": "-", - } - else: - set_average_results(opposite_benchmark) - diffs = { - key: get_diff( - selected_benchmark["average_results"][f"pretty_{key}"], - opposite_benchmark["average_results"][f"pretty_{key}"] - ) for key in ("write", "read", "update", "read+update") - } - - write, read, update, read_update = st.columns(4) - - columns = { - "write": write, - "read": read, - "update": update, - "read+update": read_update, - } + opposite_benchmarks = get_opposite_benchmarks(selected_set, selected_benchmark) - for column_name, column in columns.items(): - column.metric( - column_name.title(), - selected_benchmark["average_results"][f"pretty_{column_name}"], - delta=diffs[column_name] if diffs else None, - delta_color="inverse" - ) + if len(opposite_benchmarks) == 1: + opposite_benchmark = opposite_benchmarks[0] + + add_metrics(st.container(), selected_benchmark, opposite_benchmark) - if diffs: + if opposite_benchmark is not None: st.text(f"* In comparison with {opposite_benchmark['name']} ({opposite_benchmark['uuid']})") compare_item = { @@ -391,3 +403,40 @@ def add_results_to_compare_tab(): delta=get_diff(second_dict[column_name], first_dict[column_name]), delta_color="inverse" ) + +############################################################################### +# Mass compare tab +# Allows massively comparing benchmarks inside a single set +############################################################################### + +with mass_compare_tab: + sets = { + f"{benchmark['name']} ({benchmark['uuid']})": benchmark + for benchmark in st.session_state["benchmarks"].values() + } + benchmark_set = st.selectbox("Benchmark set", sets.keys(), key="mass_compare_selectbox") + + if benchmark_set is None: + st.warning("No benchmark sets available") + st.stop() + + selected_set = sets[benchmark_set] + + added_benchmarks = set() + + for benchmark in selected_set["benchmarks"].values(): + if benchmark["uuid"] in added_benchmarks: + continue + + opposite_benchmarks = get_opposite_benchmarks(selected_set, benchmark) + + added_benchmarks.add(benchmark["uuid"]) + added_benchmarks.update({bm["uuid"] for bm in opposite_benchmarks}) + st.divider() + + if len(opposite_benchmarks) == 1: + opposite_benchmark = opposite_benchmarks[0] + st.subheader(f"{benchmark['name']} ({benchmark['uuid']})") + add_metrics(st.container(), benchmark, opposite_benchmark) + st.subheader(f"{opposite_benchmark['name']} ({opposite_benchmark['uuid']})") + add_metrics(st.container(), opposite_benchmark, benchmark) From 5fb2e695bfa4d529f3de4a7ccb5a4a581cadcd33 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Wed, 21 Jun 2023 19:00:42 +0300 Subject: [PATCH 024/113] update extreme cases params --- benchmark_dbs.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/benchmark_dbs.py b/benchmark_dbs.py index 137ccb057..eb36e71bb 100644 --- a/benchmark_dbs.py +++ b/benchmark_dbs.py @@ -73,24 +73,29 @@ *get_cases( db_uris=dbs, case_name_postfix="-long-dialog-len", + context_num=10, from_dialog_len=10000, to_dialog_len=11001, - step_dialog_len=100, + step_dialog_len=200, description="Benchmark with very long dialog len." ), *get_cases( db_uris=dbs, case_name_postfix="-long-message-len", + context_num=10, from_dialog_len=1, - to_dialog_len=2, + to_dialog_len=3, + step_dialog_len=1, message_lengths=(10000, 1), description="Benchmark with messages containing many keys." ), *get_cases( db_uris=dbs, case_name_postfix="-long-misc-len", + context_num=10, from_dialog_len=1, - to_dialog_len=2, + to_dialog_len=3, + step_dialog_len=1, misc_lengths=(10000, 1), description="Benchmark with misc containing many keys." ), From 4c5147369ffb3b8a541924b19d213971d455a2a4 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Thu, 22 Jun 2023 14:54:07 +0300 Subject: [PATCH 025/113] set step_dialog_len to 1 by default --- benchmark_dbs.py | 15 +++++---------- dff/utils/benchmark/context_storage.py | 12 ++++++------ 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/benchmark_dbs.py b/benchmark_dbs.py index eb36e71bb..ebe2f9c82 100644 --- a/benchmark_dbs.py +++ b/benchmark_dbs.py @@ -42,9 +42,8 @@ "Short messages", "Benchmark with short messages, long dialog len.", db_uris=dbs, - from_dialog_len=100, - to_dialog_len=1001, - step_dialog_len=100, + from_dialog_len=500, + to_dialog_len=550, message_lengths=(2, 30), misc_lengths=(0, 0), ) @@ -61,9 +60,8 @@ "Alexaprize-like dialogue benchmarks (longer)", "Benchmark with dialogues similar to those from alexaprize, but dialog len is increased.", db_uris=dbs, - from_dialog_len=100, - to_dialog_len=1001, - step_dialog_len=100, + from_dialog_len=500, + to_dialog_len=550, message_lengths=(3, 5, 6, 5, 3), misc_lengths=(2, 4, 3, 8, 100), ) @@ -75,8 +73,7 @@ case_name_postfix="-long-dialog-len", context_num=10, from_dialog_len=10000, - to_dialog_len=11001, - step_dialog_len=200, + to_dialog_len=10050, description="Benchmark with very long dialog len." ), *get_cases( @@ -85,7 +82,6 @@ context_num=10, from_dialog_len=1, to_dialog_len=3, - step_dialog_len=1, message_lengths=(10000, 1), description="Benchmark with messages containing many keys." ), @@ -95,7 +91,6 @@ context_num=10, from_dialog_len=1, to_dialog_len=3, - step_dialog_len=1, misc_lengths=(10000, 1), description="Benchmark with misc containing many keys." ), diff --git a/dff/utils/benchmark/context_storage.py b/dff/utils/benchmark/context_storage.py index 3b77ddee0..48ada9e60 100644 --- a/dff/utils/benchmark/context_storage.py +++ b/dff/utils/benchmark/context_storage.py @@ -346,8 +346,8 @@ class BenchmarkCase(BaseModel): description: str = "" context_num: int = 100 from_dialog_len: int = 300 - to_dialog_len: int = 500 - step_dialog_len: int = 10 + to_dialog_len: int = 311 + step_dialog_len: int = 1 message_lengths: tp.Tuple[int, ...] = (10, 10) misc_lengths: tp.Tuple[int, ...] = (10, 10) @@ -425,8 +425,8 @@ def get_cases( case_name_postfix: str = "", context_num: int = 100, from_dialog_len: int = 300, - to_dialog_len: int = 500, - step_dialog_len: int = 10, + to_dialog_len: int = 311, + step_dialog_len: int = 1, message_lengths: tp.Tuple[int, ...] = (10, 10), misc_lengths: tp.Tuple[int, ...] = (10, 10), description: str = "", @@ -470,8 +470,8 @@ def benchmark_all( case_name_postfix: str = "", context_num: int = 100, from_dialog_len: int = 300, - to_dialog_len: int = 500, - step_dialog_len: int = 10, + to_dialog_len: int = 311, + step_dialog_len: int = 1, message_lengths: tp.Tuple[int, ...] = (10, 10), misc_lengths: tp.Tuple[int, ...] = (10, 10), ): From 0a350139f7cd4c3a2860aa2a69f6f1f80da66b7e Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Fri, 23 Jun 2023 15:17:39 +0300 Subject: [PATCH 026/113] print exception message during benchmark --- dff/utils/benchmark/context_storage.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dff/utils/benchmark/context_storage.py b/dff/utils/benchmark/context_storage.py index 48ada9e60..b5373df30 100644 --- a/dff/utils/benchmark/context_storage.py +++ b/dff/utils/benchmark/context_storage.py @@ -394,9 +394,11 @@ def run(self): } } except Exception as e: + exception_message = getattr(e, "message", repr(e)) + print(exception_message) return { "success": False, - "result": getattr(e, "message", repr(e)) + "result": exception_message, } From b6a38a1a4b847e77be544ba6fcae43132ab28575 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Fri, 23 Jun 2023 15:18:46 +0300 Subject: [PATCH 027/113] store read times under supposed dialog len --- dff/utils/benchmark/context_storage.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dff/utils/benchmark/context_storage.py b/dff/utils/benchmark/context_storage.py index b5373df30..31e0df85d 100644 --- a/dff/utils/benchmark/context_storage.py +++ b/dff/utils/benchmark/context_storage.py @@ -147,7 +147,7 @@ def time_context_read_write( read_start = perf_counter() actual_context = context_storage[ctx_id] read_time = perf_counter() - read_start - read_times[-1][len(actual_context.labels)] = read_time + read_times[-1][len(context.labels)] = read_time # check returned context # if actual_context != context: @@ -165,7 +165,7 @@ def time_context_read_write( read_start = perf_counter() actual_context = context_storage[ctx_id] read_time = perf_counter() - read_start - read_times[-1][len(actual_context.labels)] = read_time + read_times[-1][len(updated_context.labels)] = read_time context_storage.clear() return write_times, read_times, update_times From eda7ccc5b469edc1f698733597130359dfefaad3 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Mon, 26 Jun 2023 04:39:04 +0300 Subject: [PATCH 028/113] set streamlit version in dependencies st.data_editor was introduces in 1.23 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 0f47d0ef9..1422e8885 100644 --- a/setup.py +++ b/setup.py @@ -124,7 +124,7 @@ def merge_req_lists(*req_lists: List[str]) -> List[str]: "websockets~=11.0.2", "locust~=2.15", "matplotlib", - "streamlit", + "streamlit==1.23.1", "pandas", "altair", ], From 60bf1524c88fa988ef81f87ac2e1e999406e764d Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Mon, 26 Jun 2023 23:31:37 +0300 Subject: [PATCH 029/113] add exist_ok flag for saving to file --- dff/utils/benchmark/context_storage.py | 52 +++++++++++++++----------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/dff/utils/benchmark/context_storage.py b/dff/utils/benchmark/context_storage.py index 31e0df85d..5da1382d8 100644 --- a/dff/utils/benchmark/context_storage.py +++ b/dff/utils/benchmark/context_storage.py @@ -407,18 +407,19 @@ def save_results_to_file( file: tp.Union[str, pathlib.Path], name: str, description: str, + exist_ok: bool = False, ): - uuid = str(uuid4()) - result: tp.Dict[str, tp.Any] = { - "name": name, - "description": description, - "uuid": uuid, - "benchmarks": {}, - } - for case in benchmark_cases: - result["benchmarks"][case.uuid] = {**case.dict(), **case.sizes(), **case.run()} - - with open(file, "w", encoding="utf-8") as fd: + with open(file, "w" if exist_ok else "x", encoding="utf-8") as fd: + uuid = str(uuid4()) + result: tp.Dict[str, tp.Any] = { + "name": name, + "description": description, + "uuid": uuid, + "benchmarks": {}, + } + for case in benchmark_cases: + result["benchmarks"][case.uuid] = {**case.dict(), **case.sizes(), **case.run()} + json.dump(result, fd) @@ -476,15 +477,22 @@ def benchmark_all( step_dialog_len: int = 1, message_lengths: tp.Tuple[int, ...] = (10, 10), misc_lengths: tp.Tuple[int, ...] = (10, 10), + exist_ok: bool = False, ): - save_results_to_file(get_cases( - db_uris, - case_name_postfix, - context_num, - from_dialog_len, - to_dialog_len, - step_dialog_len, - message_lengths, - misc_lengths, - description=description, - ), file, name, description) \ No newline at end of file + save_results_to_file( + get_cases( + db_uris, + case_name_postfix, + context_num, + from_dialog_len, + to_dialog_len, + step_dialog_len, + message_lengths, + misc_lengths, + description=description, + ), + file, + name, + description, + exist_ok=exist_ok + ) From ec19a3046e9b2dacff4111fb8f1c9cd4f6e7052e Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Tue, 27 Jun 2023 01:05:12 +0300 Subject: [PATCH 030/113] move average results calculations from streamlit to benchmark utils Also, update tool for updating benchmarks to the new format --- benchmark_new_format.py | 7 +++++ benchmark_streamlit.py | 39 ------------------------ dff/utils/benchmark/context_storage.py | 41 +++++++++++++++++++++++++- 3 files changed, 47 insertions(+), 40 deletions(-) diff --git a/benchmark_new_format.py b/benchmark_new_format.py index 24fbecc33..fccf7cfe6 100644 --- a/benchmark_new_format.py +++ b/benchmark_new_format.py @@ -1,6 +1,8 @@ import json import pathlib +from dff.utils.benchmark.context_storage import BenchmarkCase + benchmark_path = pathlib.Path("benchmarks") for file in benchmark_path.iterdir(): @@ -16,6 +18,7 @@ new_benchmark_set["benchmarks"] = {k: v for k, v in benchmark_set.items() if k not in non_benchmark_fields} for key, benchmark in new_benchmark_set["benchmarks"].items(): + # update old factory specification if benchmark["db_factory"].get("base_factory") == "dev": postfix = "_old" elif benchmark["db_factory"].get("base_factory") == "partial": @@ -32,5 +35,9 @@ } ) + # update average calculation + for benchmark in new_benchmark_set["benchmarks"].values(): + BenchmarkCase.set_average_results(benchmark) + with open(file, "w") as fd: json.dump(new_benchmark_set, fd) diff --git a/benchmark_streamlit.py b/benchmark_streamlit.py index 5fa8f5fdb..cfae8421f 100644 --- a/benchmark_streamlit.py +++ b/benchmark_streamlit.py @@ -1,7 +1,6 @@ import streamlit as st import json from pathlib import Path -from statistics import mean import pandas as pd from pympler import asizeof from humanize import naturalsize @@ -47,7 +46,6 @@ def add_metrics(container, value_benchmark, diff_benchmark=None): write, read, update, read_update = container.columns(4) column_names = ("write", "read", "update", "read+update") - set_average_results(value_benchmark) if not value_benchmark["success"]: values = {key: "-" for key in column_names} diffs = None @@ -57,7 +55,6 @@ def add_metrics(container, value_benchmark, diff_benchmark=None): } if diff_benchmark is not None: - set_average_results(diff_benchmark) if not diff_benchmark["success"]: diffs = {key: "-" for key in column_names} else: @@ -114,40 +111,6 @@ def get_param(bench, param): return opposite_benchmarks -def set_average_results(benchmark): - if not benchmark["success"] or isinstance(benchmark["result"], str): - return - - if benchmark.get("average_results") is not None: - return - - def get_complex_stats(results): - average_grouped_by_context_num = [mean(times.values()) for times in results] - average_grouped_by_dialog_len = {key: mean([times[key] for times in results]) for key in results[0].keys()} - average = mean(average_grouped_by_context_num) - return average_grouped_by_context_num, average_grouped_by_dialog_len, average - - read_stats = get_complex_stats(benchmark["result"]["read_times"]) - update_stats = get_complex_stats(benchmark["result"]["update_times"]) - - result = { - "average_write_time": mean(benchmark["result"]["write_times"]), - "average_read_time": read_stats[2], - "average_update_time": update_stats[2], - "write_times": benchmark["result"]["write_times"], - "read_times_grouped_by_context_num": read_stats[0], - "read_times_grouped_by_dialog_len": read_stats[1], - "update_times_grouped_by_context_num": update_stats[0], - "update_times_grouped_by_dialog_len": update_stats[1], - } - result["pretty_write"] = float(f'{result["average_write_time"]:.3}') - result["pretty_read"] = float(f'{result["average_read_time"]:.3}') - result["pretty_update"] = float(f'{result["average_update_time"]:.3}') - result["pretty_read+update"] = float(f'{result["average_read_time"] + result["average_update_time"]:.3}') - - benchmark["average_results"] = result - - st.sidebar.text(f"Benchmarks take {naturalsize(asizeof.asizeof(st.session_state['benchmarks']))} RAM") st.sidebar.divider() @@ -305,8 +268,6 @@ def add_benchmark(): if not selected_benchmark["success"]: st.warning(selected_benchmark["result"]) else: - set_average_results(selected_benchmark) - opposite_benchmark = None if st.session_state["partial_compare_checkbox"]: diff --git a/dff/utils/benchmark/context_storage.py b/dff/utils/benchmark/context_storage.py index 5da1382d8..3ad601e67 100644 --- a/dff/utils/benchmark/context_storage.py +++ b/dff/utils/benchmark/context_storage.py @@ -21,6 +21,7 @@ from copy import deepcopy import json import importlib +from statistics import mean from pydantic import BaseModel, Field from pympler import asizeof @@ -377,7 +378,40 @@ def sizes(self): "message_size": asizeof.asizeof(get_message(self.message_lengths)), } - def run(self): + @staticmethod + def set_average_results(benchmark): + if not benchmark["success"] or isinstance(benchmark["result"], str): + return + + def get_complex_stats(results): + average_grouped_by_context_num = [mean(times.values()) for times in results] + average_grouped_by_dialog_len = { + key: mean([times[key] for times in results]) for key in next(iter(results), {}).keys() + } + average = mean(average_grouped_by_context_num) + return average_grouped_by_context_num, average_grouped_by_dialog_len, average + + read_stats = get_complex_stats(benchmark["result"]["read_times"]) + update_stats = get_complex_stats(benchmark["result"]["update_times"]) + + result = { + "average_write_time": mean(benchmark["result"]["write_times"]), + "average_read_time": read_stats[2], + "average_update_time": update_stats[2], + "write_times": benchmark["result"]["write_times"], + "read_times_grouped_by_context_num": read_stats[0], + "read_times_grouped_by_dialog_len": read_stats[1], + "update_times_grouped_by_context_num": update_stats[0], + "update_times_grouped_by_dialog_len": update_stats[1], + } + result["pretty_write"] = float(f'{result["average_write_time"]:.3}') + result["pretty_read"] = float(f'{result["average_read_time"]:.3}') + result["pretty_update"] = float(f'{result["average_update_time"]:.3}') + result["pretty_read+update"] = float(f'{result["average_read_time"] + result["average_update_time"]:.3}') + + benchmark["average_results"] = result + + def _run(self): try: write_times, read_times, update_times = time_context_read_write( self.db_factory.db(), @@ -401,6 +435,11 @@ def run(self): "result": exception_message, } + def run(self): + benchmark = self._run() + BenchmarkCase.set_average_results(benchmark) + return benchmark + def save_results_to_file( benchmark_cases: tp.List[BenchmarkCase], From 8abc60fd4975abed0d584edaa1e54d4f945392aa Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Tue, 27 Jun 2023 12:37:16 +0300 Subject: [PATCH 031/113] rename lengths to dimensions --- benchmark_dbs.py | 16 ++-- benchmark_new_format.py | 7 ++ benchmark_streamlit.py | 8 +- dff/utils/benchmark/context_storage.py | 106 ++++++++++++------------- 4 files changed, 72 insertions(+), 65 deletions(-) diff --git a/benchmark_dbs.py b/benchmark_dbs.py index ebe2f9c82..73ba78513 100644 --- a/benchmark_dbs.py +++ b/benchmark_dbs.py @@ -33,8 +33,8 @@ db_uris=dbs, from_dialog_len=1, to_dialog_len=50, - message_lengths=(3, 5, 6, 5, 3), - misc_lengths=(2, 4, 3, 8, 100), + message_dimensions=(3, 5, 6, 5, 3), + misc_dimensions=(2, 4, 3, 8, 100), ) benchmark_all( @@ -44,8 +44,8 @@ db_uris=dbs, from_dialog_len=500, to_dialog_len=550, - message_lengths=(2, 30), - misc_lengths=(0, 0), + message_dimensions=(2, 30), + misc_dimensions=(0, 0), ) benchmark_all( @@ -62,8 +62,8 @@ db_uris=dbs, from_dialog_len=500, to_dialog_len=550, - message_lengths=(3, 5, 6, 5, 3), - misc_lengths=(2, 4, 3, 8, 100), + message_dimensions=(3, 5, 6, 5, 3), + misc_dimensions=(2, 4, 3, 8, 100), ) save_results_to_file( @@ -82,7 +82,7 @@ context_num=10, from_dialog_len=1, to_dialog_len=3, - message_lengths=(10000, 1), + message_dimensions=(10000, 1), description="Benchmark with messages containing many keys." ), *get_cases( @@ -91,7 +91,7 @@ context_num=10, from_dialog_len=1, to_dialog_len=3, - misc_lengths=(10000, 1), + misc_dimensions=(10000, 1), description="Benchmark with misc containing many keys." ), ], diff --git a/benchmark_new_format.py b/benchmark_new_format.py index fccf7cfe6..859b08f24 100644 --- a/benchmark_new_format.py +++ b/benchmark_new_format.py @@ -39,5 +39,12 @@ for benchmark in new_benchmark_set["benchmarks"].values(): BenchmarkCase.set_average_results(benchmark) + # update lengths -> dimensions renaming + for benchmark in new_benchmark_set["benchmarks"].values(): + for param in ("message", "misc"): + dimensions = benchmark.pop(f"{param}_lengths", None) + if dimensions is not None: + benchmark[f"{param}_dimensions"] = dimensions + with open(file, "w") as fd: json.dump(new_benchmark_set, fd) diff --git a/benchmark_streamlit.py b/benchmark_streamlit.py index cfae8421f..9a6177a4d 100644 --- a/benchmark_streamlit.py +++ b/benchmark_streamlit.py @@ -90,8 +90,8 @@ def get_opposite_benchmarks(benchmark_set, benchmark): ("from_dialog_len", ), ("to_dialog_len", ), ("step_dialog_len", ), - ("message_lengths", ), - ("misc_lengths", ), + ("message_dimensions", ), + ("misc_dimensions", ), ) def get_param(bench, param): @@ -247,8 +247,8 @@ def add_benchmark(): "from_dialog_len", "to_dialog_len", "step_dialog_len", - "message_lengths", - "misc_lengths", + "message_dimensions", + "misc_dimensions", ) } diff --git a/dff/utils/benchmark/context_storage.py b/dff/utils/benchmark/context_storage.py index 3ad601e67..9f0438994 100644 --- a/dff/utils/benchmark/context_storage.py +++ b/dff/utils/benchmark/context_storage.py @@ -41,46 +41,46 @@ from dff.script import Context, Message -def get_dict(lengths: tp.Tuple[int, ...]): +def get_dict(dimensions: tp.Tuple[int, ...]): """ - Misc dictionary build in lengths dimensions. + Misc dictionary build in `dimensions` dimensions. - :param lengths: + :param dimensions: Dimensions of the dictionary. - Each element of the lengths tuple is the number of keys on the corresponding level of the dictionary. - The last element of the lengths tuple is the length of the str values of the dict. + Each element of the dimensions tuple is the number of keys on the corresponding level of the dictionary. + The last element of the dimensions tuple is the length of the str values of the dict. - e.g. lengths=(1, 2) produces a dictionary with 1 key that points to a string of len 2. - whereas lengths=(1, 2, 3) produces a dictionary with 1 key that points to a dictionary + e.g. dimensions=(1, 2) produces a dictionary with 1 key that points to a string of len 2. + whereas dimensions=(1, 2, 3) produces a dictionary with 1 key that points to a dictionary with 2 keys each of which points to a string of len 3. - So the len of lengths is the depth of the dictionary, while its values are + So, the len of dimensions is the depth of the dictionary, while its values are the width of the dictionary at each level. :return: Misc dictionary. """ - def _get_dict(lengths: tp.Tuple[int, ...]): - if len(lengths) < 2: - return "." * lengths[0] - return {i: _get_dict(lengths[1:]) for i in range(lengths[0])} - - if len(lengths) > 1: - return _get_dict(lengths) - elif len(lengths) == 1: - return _get_dict((lengths[0], 0)) + def _get_dict(dimensions: tp.Tuple[int, ...]): + if len(dimensions) < 2: + return "." * dimensions[0] + return {i: _get_dict(dimensions[1:]) for i in range(dimensions[0])} + + if len(dimensions) > 1: + return _get_dict(dimensions) + elif len(dimensions) == 1: + return _get_dict((dimensions[0], 0)) else: return _get_dict((0, 0)) -def get_message(message_lengths: tp.Tuple[int, ...]): +def get_message(message_dimensions: tp.Tuple[int, ...]): """ - Message with misc field of message_lengths dimension. - :param message_lengths: + Message with misc field of message_dimensions dimension. + :param message_dimensions: :return: """ - return Message(misc=get_dict(message_lengths)) + return Message(misc=get_dict(message_dimensions)) -def get_context(dialog_len: int, message_lengths: tp.Tuple[int, ...], misc_lengths: tp.Tuple[int, ...]) -> Context: +def get_context(dialog_len: int, message_dimensions: tp.Tuple[int, ...], misc_dimensions: tp.Tuple[int, ...]) -> Context: """ A context with a given number of dialog turns, a given message dimension and a given misc dimension. @@ -88,9 +88,9 @@ def get_context(dialog_len: int, message_lengths: tp.Tuple[int, ...], misc_lengt return Context( labels={i: (f"flow_{i}", f"node_{i}") for i in range(dialog_len)}, - requests={i: get_message(message_lengths) for i in range(dialog_len)}, - responses={i: get_message(message_lengths) for i in range(dialog_len)}, - misc=get_dict(misc_lengths), + requests={i: get_message(message_dimensions) for i in range(dialog_len)}, + responses={i: get_message(message_dimensions) for i in range(dialog_len)}, + misc=get_dict(misc_dimensions), ) @@ -176,8 +176,8 @@ def report( *context_storages: DBContextStorage, context_num: int = 1000, dialog_len: int = 300, - message_lengths: tp.Tuple[int, ...] = (10, 10), - misc_lengths: tp.Tuple[int, ...] = (10, 10), + message_dimensions: tp.Tuple[int, ...] = (10, 10), + misc_dimensions: tp.Tuple[int, ...] = (10, 10), pdf: tp.Optional[str] = None, ): """ @@ -187,23 +187,23 @@ def report( :param context_num: Number of times a single context should be written to/read from context storage. :param dialog_len: A number of turns inside a single context. The context will contain simple text requests/responses. - :param message_lengths: - :param misc_lengths: + :param message_dimensions: + :param misc_dimensions: :param pdf: A pdf file name to save report to. Defaults to None. If set to None, prints the result to stdout instead of creating a pdf file. """ - context = get_context(dialog_len, message_lengths, misc_lengths) + context = get_context(dialog_len, message_dimensions, misc_dimensions) context_size = asizeof.asizeof(context) - misc_size = asizeof.asizeof(get_dict(misc_lengths)) - message_size = asizeof.asizeof(get_message(message_lengths)) + misc_size = asizeof.asizeof(get_dict(misc_dimensions)) + message_size = asizeof.asizeof(get_message(message_dimensions)) benchmark_config = ( f"Number of contexts: {context_num}\n" f"Dialog len: {dialog_len}\n" - f"Message misc dimensions: {message_lengths}\n" - f"Misc dimensions: {misc_lengths}\n" + f"Message misc dimensions: {message_dimensions}\n" + f"Misc dimensions: {misc_dimensions}\n" f"Size of misc field: {misc_size} ({naturalsize(misc_size, gnu=True)})\n" f"Size of one message: {message_size} ({naturalsize(message_size, gnu=True)})\n" f"Size of one context: {context_size} ({naturalsize(context_size, gnu=True)})" @@ -349,8 +349,8 @@ class BenchmarkCase(BaseModel): from_dialog_len: int = 300 to_dialog_len: int = 311 step_dialog_len: int = 1 - message_lengths: tp.Tuple[int, ...] = (10, 10) - misc_lengths: tp.Tuple[int, ...] = (10, 10) + message_dimensions: tp.Tuple[int, ...] = (10, 10) + misc_dimensions: tp.Tuple[int, ...] = (10, 10) def get_context_updater(self): def _context_updater(context: Context): @@ -358,8 +358,8 @@ def _context_updater(context: Context): if start_len + self.step_dialog_len < self.to_dialog_len: for i in range(start_len, start_len + self.step_dialog_len): context.add_label((f"flow_{i}", f"node_{i}")) - context.add_request(get_message(self.message_lengths)) - context.add_response(get_message(self.message_lengths)) + context.add_request(get_message(self.message_dimensions)) + context.add_response(get_message(self.message_dimensions)) return context else: return None @@ -369,13 +369,13 @@ def _context_updater(context: Context): def sizes(self): return { "starting_context_size": asizeof.asizeof( - get_context(self.from_dialog_len, self.message_lengths, self.misc_lengths) + get_context(self.from_dialog_len, self.message_dimensions, self.misc_dimensions) ), "final_context_size": asizeof.asizeof( - get_context(self.to_dialog_len, self.message_lengths, self.misc_lengths) + get_context(self.to_dialog_len, self.message_dimensions, self.misc_dimensions) ), - "misc_size": asizeof.asizeof(get_dict(self.misc_lengths)), - "message_size": asizeof.asizeof(get_message(self.message_lengths)), + "misc_size": asizeof.asizeof(get_dict(self.misc_dimensions)), + "message_size": asizeof.asizeof(get_message(self.message_dimensions)), } @staticmethod @@ -415,7 +415,7 @@ def _run(self): try: write_times, read_times, update_times = time_context_read_write( self.db_factory.db(), - get_context(self.from_dialog_len, self.message_lengths, self.misc_lengths), + get_context(self.from_dialog_len, self.message_dimensions, self.misc_dimensions), self.context_num, context_updater=self.get_context_updater() ) @@ -469,8 +469,8 @@ def get_cases( from_dialog_len: int = 300, to_dialog_len: int = 311, step_dialog_len: int = 1, - message_lengths: tp.Tuple[int, ...] = (10, 10), - misc_lengths: tp.Tuple[int, ...] = (10, 10), + message_dimensions: tp.Tuple[int, ...] = (10, 10), + misc_dimensions: tp.Tuple[int, ...] = (10, 10), description: str = "", ): benchmark_cases = [] @@ -483,8 +483,8 @@ def get_cases( from_dialog_len=from_dialog_len, to_dialog_len=to_dialog_len, step_dialog_len=step_dialog_len, - message_lengths=message_lengths, - misc_lengths=misc_lengths, + message_dimensions=message_dimensions, + misc_dimensions=misc_dimensions, description=description, ) ) @@ -496,8 +496,8 @@ def get_cases( from_dialog_len=from_dialog_len, to_dialog_len=to_dialog_len, step_dialog_len=step_dialog_len, - message_lengths=message_lengths, - misc_lengths=misc_lengths, + message_dimensions=message_dimensions, + misc_dimensions=misc_dimensions, description=description, ) ) @@ -514,8 +514,8 @@ def benchmark_all( from_dialog_len: int = 300, to_dialog_len: int = 311, step_dialog_len: int = 1, - message_lengths: tp.Tuple[int, ...] = (10, 10), - misc_lengths: tp.Tuple[int, ...] = (10, 10), + message_dimensions: tp.Tuple[int, ...] = (10, 10), + misc_dimensions: tp.Tuple[int, ...] = (10, 10), exist_ok: bool = False, ): save_results_to_file( @@ -526,8 +526,8 @@ def benchmark_all( from_dialog_len, to_dialog_len, step_dialog_len, - message_lengths, - misc_lengths, + message_dimensions, + misc_dimensions, description=description, ), file, From c65ca378837e5f79b46742f0f03e6d4905c8a48d Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Tue, 27 Jun 2023 19:47:52 +0300 Subject: [PATCH 032/113] remove context checking comments --- dff/utils/benchmark/context_storage.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/dff/utils/benchmark/context_storage.py b/dff/utils/benchmark/context_storage.py index 9f0438994..b167a64ad 100644 --- a/dff/utils/benchmark/context_storage.py +++ b/dff/utils/benchmark/context_storage.py @@ -146,14 +146,10 @@ def time_context_read_write( # read operation benchmark read_start = perf_counter() - actual_context = context_storage[ctx_id] + _ = context_storage[ctx_id] read_time = perf_counter() - read_start read_times[-1][len(context.labels)] = read_time - # check returned context - # if actual_context != context: - # raise RuntimeError(f"True context:\n{context}\nActual context:\n{actual_context}") - if context_updater is not None: update_times.append({}) @@ -164,7 +160,7 @@ def time_context_read_write( update_times[-1][len(updated_context.labels)] = update_time read_start = perf_counter() - actual_context = context_storage[ctx_id] + _ = context_storage[ctx_id] read_time = perf_counter() - read_start read_times[-1][len(updated_context.labels)] = read_time From fb999e35813cc41a8c40ec8d9ebac2b6f2593ef4 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Tue, 27 Jun 2023 20:50:11 +0300 Subject: [PATCH 033/113] move partial benchmarking logic to partial benchmark file --- benchmark_dbs.py | 80 +++++++++++++++++++++++++- dff/utils/benchmark/context_storage.py | 62 ++++---------------- 2 files changed, 89 insertions(+), 53 deletions(-) diff --git a/benchmark_dbs.py b/benchmark_dbs.py index 73ba78513..b784f544e 100644 --- a/benchmark_dbs.py +++ b/benchmark_dbs.py @@ -1,11 +1,87 @@ import pathlib +import typing as tp from platform import system -from dff.utils.benchmark.context_storage import benchmark_all, save_results_to_file, get_cases +from dff.utils.benchmark.context_storage import save_results_to_file, BenchmarkCase, DBFactory -# create dir and files +# partial-specific logic +def get_cases( + db_uris: tp.Dict[str, str], + case_name_postfix: str = "", + context_num: int = 100, + from_dialog_len: int = 300, + to_dialog_len: int = 311, + step_dialog_len: int = 1, + message_dimensions: tp.Tuple[int, ...] = (10, 10), + misc_dimensions: tp.Tuple[int, ...] = (10, 10), + description: str = "", +): + benchmark_cases = [] + for db, uri in db_uris.items(): + benchmark_cases.append( + BenchmarkCase( + name=db + "-dev" + case_name_postfix, + db_factory=DBFactory(uri=uri, factory_module="dff.context_storages_old"), + context_num=context_num, + from_dialog_len=from_dialog_len, + to_dialog_len=to_dialog_len, + step_dialog_len=step_dialog_len, + message_dimensions=message_dimensions, + misc_dimensions=misc_dimensions, + description=description, + ) + ) + benchmark_cases.append( + BenchmarkCase( + name=db + "-partial" + case_name_postfix, + db_factory=DBFactory(uri=uri), + context_num=context_num, + from_dialog_len=from_dialog_len, + to_dialog_len=to_dialog_len, + step_dialog_len=step_dialog_len, + message_dimensions=message_dimensions, + misc_dimensions=misc_dimensions, + description=description, + ) + ) + return benchmark_cases + + +def benchmark_all( + file: tp.Union[str, pathlib.Path], + name: str, + description: str, + db_uris: tp.Dict[str, str], + case_name_postfix: str = "", + context_num: int = 100, + from_dialog_len: int = 300, + to_dialog_len: int = 311, + step_dialog_len: int = 1, + message_dimensions: tp.Tuple[int, ...] = (10, 10), + misc_dimensions: tp.Tuple[int, ...] = (10, 10), + exist_ok: bool = False, +): + save_results_to_file( + get_cases( + db_uris, + case_name_postfix, + context_num, + from_dialog_len, + to_dialog_len, + step_dialog_len, + message_dimensions, + misc_dimensions, + description=description, + ), + file, + name, + description, + exist_ok=exist_ok + ) + +# create dir and files pathlib.Path("dbs").mkdir(exist_ok=True) sqlite_file = pathlib.Path("dbs/sqlite.db") sqlite_file.touch(exist_ok=True) diff --git a/dff/utils/benchmark/context_storage.py b/dff/utils/benchmark/context_storage.py index b167a64ad..7c791e950 100644 --- a/dff/utils/benchmark/context_storage.py +++ b/dff/utils/benchmark/context_storage.py @@ -458,74 +458,34 @@ def save_results_to_file( json.dump(result, fd) -def get_cases( +def benchmark_all( + file: tp.Union[str, pathlib.Path], + name: str, + description: str, db_uris: tp.Dict[str, str], - case_name_postfix: str = "", context_num: int = 100, from_dialog_len: int = 300, to_dialog_len: int = 311, step_dialog_len: int = 1, message_dimensions: tp.Tuple[int, ...] = (10, 10), misc_dimensions: tp.Tuple[int, ...] = (10, 10), - description: str = "", + exist_ok: bool = False, ): - benchmark_cases = [] - for db, uri in db_uris.items(): - benchmark_cases.append( + save_results_to_file( + [ BenchmarkCase( - name=db + "-dev" + case_name_postfix, - db_factory=DBFactory(uri=uri, factory_module="dff.context_storages_old"), - context_num=context_num, - from_dialog_len=from_dialog_len, - to_dialog_len=to_dialog_len, - step_dialog_len=step_dialog_len, - message_dimensions=message_dimensions, - misc_dimensions=misc_dimensions, + name=db_name, description=description, - ) - ) - benchmark_cases.append( - BenchmarkCase( - name=db + "-partial" + case_name_postfix, - db_factory=DBFactory(uri=uri), + db_factory=DBFactory(uri=db_uri), context_num=context_num, from_dialog_len=from_dialog_len, to_dialog_len=to_dialog_len, step_dialog_len=step_dialog_len, message_dimensions=message_dimensions, misc_dimensions=misc_dimensions, - description=description, ) - ) - return benchmark_cases - - -def benchmark_all( - file: tp.Union[str, pathlib.Path], - name: str, - description: str, - db_uris: tp.Dict[str, str], - case_name_postfix: str = "", - context_num: int = 100, - from_dialog_len: int = 300, - to_dialog_len: int = 311, - step_dialog_len: int = 1, - message_dimensions: tp.Tuple[int, ...] = (10, 10), - misc_dimensions: tp.Tuple[int, ...] = (10, 10), - exist_ok: bool = False, -): - save_results_to_file( - get_cases( - db_uris, - case_name_postfix, - context_num, - from_dialog_len, - to_dialog_len, - step_dialog_len, - message_dimensions, - misc_dimensions, - description=description, - ), + for db_name, db_uri in db_uris.items() + ], file, name, description, From e4130252a8150f21e735a68a82e6babb690b784f Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Tue, 27 Jun 2023 20:50:34 +0300 Subject: [PATCH 034/113] add partial file saving --- dff/utils/benchmark/context_storage.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dff/utils/benchmark/context_storage.py b/dff/utils/benchmark/context_storage.py index 7c791e950..c2a6b8c76 100644 --- a/dff/utils/benchmark/context_storage.py +++ b/dff/utils/benchmark/context_storage.py @@ -453,6 +453,7 @@ def save_results_to_file( "benchmarks": {}, } for case in benchmark_cases: + json.dump(result, fd) result["benchmarks"][case.uuid] = {**case.dict(), **case.sizes(), **case.run()} json.dump(result, fd) From 827861bbe8cb52c1671b4c90eb73e83e78366789 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Tue, 27 Jun 2023 23:49:01 +0300 Subject: [PATCH 035/113] update report function - Remove export as pdf - Use methods provided by BenchmarkCase --- dff/utils/benchmark/context_storage.py | 234 +++++++------------------ 1 file changed, 67 insertions(+), 167 deletions(-) diff --git a/dff/utils/benchmark/context_storage.py b/dff/utils/benchmark/context_storage.py index c2a6b8c76..7230d947e 100644 --- a/dff/utils/benchmark/context_storage.py +++ b/dff/utils/benchmark/context_storage.py @@ -28,15 +28,6 @@ from tqdm.auto import tqdm from humanize import naturalsize -from dff.script import Context - -try: - import matplotlib - from matplotlib.backends.backend_pdf import PdfPages - import matplotlib.pyplot as plt -except ImportError: - matplotlib = None - from dff.context_storages import DBContextStorage from dff.script import Context, Message @@ -168,164 +159,6 @@ def time_context_read_write( return write_times, read_times, update_times -def report( - *context_storages: DBContextStorage, - context_num: int = 1000, - dialog_len: int = 300, - message_dimensions: tp.Tuple[int, ...] = (10, 10), - misc_dimensions: tp.Tuple[int, ...] = (10, 10), - pdf: tp.Optional[str] = None, -): - """ - Benchmark context storage(s) and generate a report. - - :param context_storages: Context storages to benchmark. - :param context_num: Number of times a single context should be written to/read from context storage. - :param dialog_len: - A number of turns inside a single context. The context will contain simple text requests/responses. - :param message_dimensions: - :param misc_dimensions: - :param pdf: - A pdf file name to save report to. - Defaults to None. - If set to None, prints the result to stdout instead of creating a pdf file. - """ - context = get_context(dialog_len, message_dimensions, misc_dimensions) - context_size = asizeof.asizeof(context) - misc_size = asizeof.asizeof(get_dict(misc_dimensions)) - message_size = asizeof.asizeof(get_message(message_dimensions)) - - benchmark_config = ( - f"Number of contexts: {context_num}\n" - f"Dialog len: {dialog_len}\n" - f"Message misc dimensions: {message_dimensions}\n" - f"Misc dimensions: {misc_dimensions}\n" - f"Size of misc field: {misc_size} ({naturalsize(misc_size, gnu=True)})\n" - f"Size of one message: {message_size} ({naturalsize(message_size, gnu=True)})\n" - f"Size of one context: {context_size} ({naturalsize(context_size, gnu=True)})" - ) - - print(f"Starting benchmarking with following parameters:\n{benchmark_config}") - - benchmarking_results: tp.Dict[str, tp.Union[tp.Tuple[tp.List[float], tp.List[float]], str]] = {} - - for context_storage in context_storages: - try: - # todo: update report method - write, read = time_context_read_write(context_storage, context, context_num) - - benchmarking_results[context_storage.full_path] = write, read - except Exception as e: - benchmarking_results[context_storage.full_path] = getattr(e, "message", repr(e)) - - # define functions for displaying results - line_separator = "-" * 80 - - pretty_config = f"{line_separator}\nDB benchmark\n{line_separator}\n{benchmark_config}\n{line_separator}" - - def pretty_benchmark_result(storage_name, benchmarking_result) -> str: - result = f"{storage_name}\n{line_separator}\n" - if not isinstance(benchmarking_result, str): - write, read = benchmarking_result - result += ( - f"Average write time: {sum(write) / len(write)} s\n" - f"Average read time: {sum(read) / len(read)} s\n{line_separator}" - ) - else: - result += f"{benchmarking_result}\n{line_separator}" - return result - - def get_scores_and_leaderboard( - sort_by: tp.Literal["Write", "Read"] - ) -> tp.Tuple[tp.List[tp.Tuple[str, tp.Optional[float]]], str]: - benchmark_index = 0 if sort_by == "Write" else 1 - - scores = sorted( - [ - (storage_name, sum(result[benchmark_index]) / len(result[benchmark_index])) - for storage_name, result in benchmarking_results.items() - if not isinstance(result, str) - ], - key=lambda benchmark: benchmark[1], # sort in ascending order - ) - scores += [ - (storage_name, None) for storage_name, result in benchmarking_results.items() if isinstance(result, str) - ] - leaderboard = ( - f"{sort_by} time leaderboard\n{line_separator}\n" - + "\n".join( - [f"{result}{' s' if result is not None else ''}: {storage_name}" for storage_name, result in scores] - ) - + "\n" - + line_separator - ) - - return scores, leaderboard - - _, write_leaderboard = get_scores_and_leaderboard("Write") - _, read_leaderboard = get_scores_and_leaderboard("Read") - - if pdf is None: - result = pretty_config - - for storage_name, benchmarking_result in benchmarking_results.items(): - result += f"\n{pretty_benchmark_result(storage_name, benchmarking_result)}" - - if len(context_storages) > 1: - result += f"\n{write_leaderboard}\n{read_leaderboard}" - - print(result) - else: - if matplotlib is None: - raise RuntimeError("`matplotlib` is required to generate pdf reports.") - - figure_size = (11, 8) - - def text_page(text, *, x=0.5, y=0.5, size=18, ha="center", family="monospace", **kwargs): - page = plt.figure(figsize=figure_size) - page.clf() - page.text(x, y, text, transform=page.transFigure, size=size, ha=ha, family=family, **kwargs) - - def scatter_page(storage_name, write, read): - plt.figure(figsize=figure_size) - plt.scatter(range(len(write)), write, label="write times") - plt.scatter(range(len(read)), read, label="read times") - plt.legend(loc="best") - plt.grid(True) - plt.title(storage_name) - - with PdfPages(pdf) as mpl_pdf: - text_page(pretty_config, size=24) - mpl_pdf.savefig() - plt.close() - - if len(context_storages) > 1: - text_page(write_leaderboard, x=0.05, size=14, ha="left") - mpl_pdf.savefig() - plt.close() - text_page(read_leaderboard, x=0.05, size=14, ha="left") - mpl_pdf.savefig() - plt.close() - - for storage_name, benchmarking_result in benchmarking_results.items(): - txt = pretty_benchmark_result(storage_name, benchmarking_result) - - if not isinstance(benchmarking_result, str): - write, read = benchmarking_result - - text_page(txt) - mpl_pdf.savefig() - plt.close() - - scatter_page(storage_name, write, read) - mpl_pdf.savefig() - plt.close() - else: - text_page(txt) - mpl_pdf.savefig() - plt.close() - - class DBFactory(BaseModel): uri: str factory_module: str = "dff.context_storages" @@ -492,3 +325,70 @@ def benchmark_all( description, exist_ok=exist_ok ) + + +def report( + db_uris: tp.Dict[str, str], + context_num: int = 100, + from_dialog_len: int = 300, + to_dialog_len: int = 311, + step_dialog_len: int = 1, + message_dimensions: tp.Tuple[int, ...] = (10, 10), + misc_dimensions: tp.Tuple[int, ...] = (10, 10), +): + benchmark_cases = [ + BenchmarkCase( + name=db_name, + db_factory=DBFactory(uri=db_uri), + context_num=context_num, + from_dialog_len=from_dialog_len, + to_dialog_len=to_dialog_len, + step_dialog_len=step_dialog_len, + message_dimensions=message_dimensions, + misc_dimensions=misc_dimensions, + ) + for db_name, db_uri in db_uris.items() + ] + starting_context_size = asizeof.asizeof(get_context(from_dialog_len, message_dimensions, misc_dimensions)) + final_context_size= asizeof.asizeof(get_context(to_dialog_len, message_dimensions, misc_dimensions)) + misc_size = asizeof.asizeof(get_dict(misc_dimensions)) + message_size = asizeof.asizeof(get_message(message_dimensions)) + + benchmark_config = ( + f"Number of contexts: {context_num}\n" + f"From dialog len: {from_dialog_len}\n" + f"To dialog len: {to_dialog_len}\n" + f"Step dialog len: {step_dialog_len}\n" + f"Message misc dimensions: {message_dimensions}\n" + f"Misc dimensions: {misc_dimensions}\n" + f"Size of misc field: {misc_size} ({naturalsize(misc_size, gnu=True)})\n" + f"Size of one message: {message_size} ({naturalsize(message_size, gnu=True)})\n" + f"Starting context size: {starting_context_size} ({naturalsize(starting_context_size, gnu=True)})\n" + f"Final context size: {final_context_size} ({naturalsize(final_context_size, gnu=True)})" + ) + + # define functions for displaying results + line_separator = "-" * 80 + + print(f"Starting benchmarking with following parameters:\n{benchmark_config}") + + report_result = f"\n{line_separator}\n".join(["", "DB benchmark", benchmark_config, ""]) + + for benchmark_case in benchmark_cases: + result = benchmark_case.run() + report_result += f"\n{line_separator}\n".join( + [ + benchmark_case.name, + "".join( + [ + f"{metric.title() + ': ' + str(result['average_results']['pretty_' + metric]):20}" + if result["success"] else + result["result"] + for metric in ("write", "read", "update", "read+update") + ] + ), + "", + ] + ) + + print(report_result, end="") From a25ee56b0684d8e0a24c6149d1ef6074ed8fc8b8 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Wed, 28 Jun 2023 01:29:23 +0300 Subject: [PATCH 036/113] add BenchmarkConfig class to avoid repetition of parameters --- benchmark_dbs.py | 97 ++++++++++------------ dff/utils/benchmark/context_storage.py | 110 ++++++++++++------------- 2 files changed, 93 insertions(+), 114 deletions(-) diff --git a/benchmark_dbs.py b/benchmark_dbs.py index b784f544e..94d070a1f 100644 --- a/benchmark_dbs.py +++ b/benchmark_dbs.py @@ -2,19 +2,14 @@ import typing as tp from platform import system -from dff.utils.benchmark.context_storage import save_results_to_file, BenchmarkCase, DBFactory +from dff.utils.benchmark.context_storage import save_results_to_file, BenchmarkCase, DBFactory, BenchmarkConfig # partial-specific logic def get_cases( db_uris: tp.Dict[str, str], case_name_postfix: str = "", - context_num: int = 100, - from_dialog_len: int = 300, - to_dialog_len: int = 311, - step_dialog_len: int = 1, - message_dimensions: tp.Tuple[int, ...] = (10, 10), - misc_dimensions: tp.Tuple[int, ...] = (10, 10), + benchmark_config: BenchmarkConfig = BenchmarkConfig(), description: str = "", ): benchmark_cases = [] @@ -23,12 +18,7 @@ def get_cases( BenchmarkCase( name=db + "-dev" + case_name_postfix, db_factory=DBFactory(uri=uri, factory_module="dff.context_storages_old"), - context_num=context_num, - from_dialog_len=from_dialog_len, - to_dialog_len=to_dialog_len, - step_dialog_len=step_dialog_len, - message_dimensions=message_dimensions, - misc_dimensions=misc_dimensions, + benchmark_config=benchmark_config, description=description, ) ) @@ -36,12 +26,7 @@ def get_cases( BenchmarkCase( name=db + "-partial" + case_name_postfix, db_factory=DBFactory(uri=uri), - context_num=context_num, - from_dialog_len=from_dialog_len, - to_dialog_len=to_dialog_len, - step_dialog_len=step_dialog_len, - message_dimensions=message_dimensions, - misc_dimensions=misc_dimensions, + benchmark_config=benchmark_config, description=description, ) ) @@ -54,24 +39,14 @@ def benchmark_all( description: str, db_uris: tp.Dict[str, str], case_name_postfix: str = "", - context_num: int = 100, - from_dialog_len: int = 300, - to_dialog_len: int = 311, - step_dialog_len: int = 1, - message_dimensions: tp.Tuple[int, ...] = (10, 10), - misc_dimensions: tp.Tuple[int, ...] = (10, 10), + benchmark_config: BenchmarkConfig = BenchmarkConfig(), exist_ok: bool = False, ): save_results_to_file( get_cases( db_uris, case_name_postfix, - context_num, - from_dialog_len, - to_dialog_len, - step_dialog_len, - message_dimensions, - misc_dimensions, + benchmark_config=benchmark_config, description=description, ), file, @@ -103,14 +78,16 @@ def benchmark_all( pathlib.Path("benchmarks").mkdir(exist_ok=True) benchmark_all( - "benchmarks/alexaprize.json", + "benchmarks/alexaprize1.json", "Alexaprize-like dialogue benchmarks", "Benchmark with dialogues similar to those from alexaprize.", db_uris=dbs, - from_dialog_len=1, - to_dialog_len=50, - message_dimensions=(3, 5, 6, 5, 3), - misc_dimensions=(2, 4, 3, 8, 100), + benchmark_config=BenchmarkConfig( + from_dialog_len=1, + to_dialog_len=50, + message_dimensions=(3, 5, 6, 5, 3), + misc_dimensions=(2, 4, 3, 8, 100), + ) ) benchmark_all( @@ -118,10 +95,12 @@ def benchmark_all( "Short messages", "Benchmark with short messages, long dialog len.", db_uris=dbs, - from_dialog_len=500, - to_dialog_len=550, - message_dimensions=(2, 30), - misc_dimensions=(0, 0), + benchmark_config=BenchmarkConfig( + from_dialog_len=500, + to_dialog_len=550, + message_dimensions=(2, 30), + misc_dimensions=(0, 0), + ) ) benchmark_all( @@ -136,10 +115,12 @@ def benchmark_all( "Alexaprize-like dialogue benchmarks (longer)", "Benchmark with dialogues similar to those from alexaprize, but dialog len is increased.", db_uris=dbs, - from_dialog_len=500, - to_dialog_len=550, - message_dimensions=(3, 5, 6, 5, 3), - misc_dimensions=(2, 4, 3, 8, 100), + benchmark_config=BenchmarkConfig( + from_dialog_len=500, + to_dialog_len=550, + message_dimensions=(3, 5, 6, 5, 3), + misc_dimensions=(2, 4, 3, 8, 100), + ) ) save_results_to_file( @@ -147,27 +128,33 @@ def benchmark_all( *get_cases( db_uris=dbs, case_name_postfix="-long-dialog-len", - context_num=10, - from_dialog_len=10000, - to_dialog_len=10050, + benchmark_config=BenchmarkConfig( + context_num=10, + from_dialog_len=10000, + to_dialog_len=10050, + ), description="Benchmark with very long dialog len." ), *get_cases( db_uris=dbs, case_name_postfix="-long-message-len", - context_num=10, - from_dialog_len=1, - to_dialog_len=3, - message_dimensions=(10000, 1), + benchmark_config=BenchmarkConfig( + context_num=10, + from_dialog_len=1, + to_dialog_len=3, + message_dimensions=(10000, 1), + ), description="Benchmark with messages containing many keys." ), *get_cases( db_uris=dbs, case_name_postfix="-long-misc-len", - context_num=10, - from_dialog_len=1, - to_dialog_len=3, - misc_dimensions=(10000, 1), + benchmark_config=BenchmarkConfig( + context_num=10, + from_dialog_len=1, + to_dialog_len=3, + misc_dimensions=(10000, 1), + ), description="Benchmark with misc containing many keys." ), ], diff --git a/dff/utils/benchmark/context_storage.py b/dff/utils/benchmark/context_storage.py index 7230d947e..1bca5630d 100644 --- a/dff/utils/benchmark/context_storage.py +++ b/dff/utils/benchmark/context_storage.py @@ -169,11 +169,7 @@ def db(self): return getattr(module, self.factory)(self.uri) -class BenchmarkCase(BaseModel): - name: str - db_factory: DBFactory - uuid: str = Field(default_factory=lambda: str(uuid4())) - description: str = "" +class BenchmarkConfig(BaseModel): context_num: int = 100 from_dialog_len: int = 300 to_dialog_len: int = 311 @@ -181,19 +177,8 @@ class BenchmarkCase(BaseModel): message_dimensions: tp.Tuple[int, ...] = (10, 10) misc_dimensions: tp.Tuple[int, ...] = (10, 10) - def get_context_updater(self): - def _context_updater(context: Context): - start_len = len(context.requests) - if start_len + self.step_dialog_len < self.to_dialog_len: - for i in range(start_len, start_len + self.step_dialog_len): - context.add_label((f"flow_{i}", f"node_{i}")) - context.add_request(get_message(self.message_dimensions)) - context.add_response(get_message(self.message_dimensions)) - return context - else: - return None - - return _context_updater + class Config: + allow_mutation = False def sizes(self): return { @@ -207,6 +192,28 @@ def sizes(self): "message_size": asizeof.asizeof(get_message(self.message_dimensions)), } + +class BenchmarkCase(BaseModel): + name: str + db_factory: DBFactory + benchmark_config: BenchmarkConfig = BenchmarkConfig() + uuid: str = Field(default_factory=lambda: str(uuid4())) + description: str = "" + + def get_context_updater(self): + def _context_updater(context: Context): + start_len = len(context.requests) + if start_len + self.benchmark_config.step_dialog_len < self.benchmark_config.to_dialog_len: + for i in range(start_len, start_len + self.benchmark_config.step_dialog_len): + context.add_label((f"flow_{i}", f"node_{i}")) + context.add_request(get_message(self.benchmark_config.message_dimensions)) + context.add_response(get_message(self.benchmark_config.message_dimensions)) + return context + else: + return None + + return _context_updater + @staticmethod def set_average_results(benchmark): if not benchmark["success"] or isinstance(benchmark["result"], str): @@ -244,8 +251,12 @@ def _run(self): try: write_times, read_times, update_times = time_context_read_write( self.db_factory.db(), - get_context(self.from_dialog_len, self.message_dimensions, self.misc_dimensions), - self.context_num, + get_context( + self.benchmark_config.from_dialog_len, + self.benchmark_config.message_dimensions, + self.benchmark_config.misc_dimensions + ), + self.benchmark_config.context_num, context_updater=self.get_context_updater() ) return { @@ -287,7 +298,7 @@ def save_results_to_file( } for case in benchmark_cases: json.dump(result, fd) - result["benchmarks"][case.uuid] = {**case.dict(), **case.sizes(), **case.run()} + result["benchmarks"][case.uuid] = {**case.dict(), **case.benchmark_config.sizes(), **case.run()} json.dump(result, fd) @@ -297,12 +308,7 @@ def benchmark_all( name: str, description: str, db_uris: tp.Dict[str, str], - context_num: int = 100, - from_dialog_len: int = 300, - to_dialog_len: int = 311, - step_dialog_len: int = 1, - message_dimensions: tp.Tuple[int, ...] = (10, 10), - misc_dimensions: tp.Tuple[int, ...] = (10, 10), + benchmark_config: BenchmarkConfig = BenchmarkConfig(), exist_ok: bool = False, ): save_results_to_file( @@ -311,12 +317,7 @@ def benchmark_all( name=db_name, description=description, db_factory=DBFactory(uri=db_uri), - context_num=context_num, - from_dialog_len=from_dialog_len, - to_dialog_len=to_dialog_len, - step_dialog_len=step_dialog_len, - message_dimensions=message_dimensions, - misc_dimensions=misc_dimensions, + benchmark_config=benchmark_config, ) for db_name, db_uri in db_uris.items() ], @@ -329,38 +330,29 @@ def benchmark_all( def report( db_uris: tp.Dict[str, str], - context_num: int = 100, - from_dialog_len: int = 300, - to_dialog_len: int = 311, - step_dialog_len: int = 1, - message_dimensions: tp.Tuple[int, ...] = (10, 10), - misc_dimensions: tp.Tuple[int, ...] = (10, 10), + benchmark_config: BenchmarkConfig = BenchmarkConfig(), ): benchmark_cases = [ BenchmarkCase( name=db_name, db_factory=DBFactory(uri=db_uri), - context_num=context_num, - from_dialog_len=from_dialog_len, - to_dialog_len=to_dialog_len, - step_dialog_len=step_dialog_len, - message_dimensions=message_dimensions, - misc_dimensions=misc_dimensions, + benchmark_config=benchmark_config, ) for db_name, db_uri in db_uris.items() ] - starting_context_size = asizeof.asizeof(get_context(from_dialog_len, message_dimensions, misc_dimensions)) - final_context_size= asizeof.asizeof(get_context(to_dialog_len, message_dimensions, misc_dimensions)) - misc_size = asizeof.asizeof(get_dict(misc_dimensions)) - message_size = asizeof.asizeof(get_message(message_dimensions)) - - benchmark_config = ( - f"Number of contexts: {context_num}\n" - f"From dialog len: {from_dialog_len}\n" - f"To dialog len: {to_dialog_len}\n" - f"Step dialog len: {step_dialog_len}\n" - f"Message misc dimensions: {message_dimensions}\n" - f"Misc dimensions: {misc_dimensions}\n" + sizes = benchmark_config.sizes() + starting_context_size = sizes["starting_context_size"] + final_context_size = sizes["final_context_size"] + misc_size = sizes["misc_size"] + message_size = sizes["message_size"] + + benchmark_config_report = ( + f"Number of contexts: {benchmark_config.context_num}\n" + f"From dialog len: {benchmark_config.from_dialog_len}\n" + f"To dialog len: {benchmark_config.to_dialog_len}\n" + f"Step dialog len: {benchmark_config.step_dialog_len}\n" + f"Message misc dimensions: {benchmark_config.message_dimensions}\n" + f"Misc dimensions: {benchmark_config.misc_dimensions}\n" f"Size of misc field: {misc_size} ({naturalsize(misc_size, gnu=True)})\n" f"Size of one message: {message_size} ({naturalsize(message_size, gnu=True)})\n" f"Starting context size: {starting_context_size} ({naturalsize(starting_context_size, gnu=True)})\n" @@ -370,9 +362,9 @@ def report( # define functions for displaying results line_separator = "-" * 80 - print(f"Starting benchmarking with following parameters:\n{benchmark_config}") + print(f"Starting benchmarking with following parameters:\n{benchmark_config_report}") - report_result = f"\n{line_separator}\n".join(["", "DB benchmark", benchmark_config, ""]) + report_result = f"\n{line_separator}\n".join(["", "DB benchmark", benchmark_config_report, ""]) for benchmark_case in benchmark_cases: result = benchmark_case.run() From 0424b43416d89eeeceb84aa65628be4f59d22dbc Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Wed, 28 Jun 2023 12:50:52 +0300 Subject: [PATCH 037/113] revert add partial file saving --- dff/utils/benchmark/context_storage.py | 1 - 1 file changed, 1 deletion(-) diff --git a/dff/utils/benchmark/context_storage.py b/dff/utils/benchmark/context_storage.py index 1bca5630d..594d75e48 100644 --- a/dff/utils/benchmark/context_storage.py +++ b/dff/utils/benchmark/context_storage.py @@ -297,7 +297,6 @@ def save_results_to_file( "benchmarks": {}, } for case in benchmark_cases: - json.dump(result, fd) result["benchmarks"][case.uuid] = {**case.dict(), **case.benchmark_config.sizes(), **case.run()} json.dump(result, fd) From e6b88b7e2c970d5b5f13e6dba1d477ae1d3a9f80 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Wed, 28 Jun 2023 12:59:12 +0300 Subject: [PATCH 038/113] fix benchmark name --- benchmark_dbs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmark_dbs.py b/benchmark_dbs.py index 94d070a1f..9d3c4ccc8 100644 --- a/benchmark_dbs.py +++ b/benchmark_dbs.py @@ -78,7 +78,7 @@ def benchmark_all( pathlib.Path("benchmarks").mkdir(exist_ok=True) benchmark_all( - "benchmarks/alexaprize1.json", + "benchmarks/alexaprize.json", "Alexaprize-like dialogue benchmarks", "Benchmark with dialogues similar to those from alexaprize.", db_uris=dbs, From f68d71c020808bdd9c584527464ea7af3f836fe8 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Wed, 28 Jun 2023 13:04:21 +0300 Subject: [PATCH 039/113] update streamlit for new benchmark_config --- benchmark_streamlit.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/benchmark_streamlit.py b/benchmark_streamlit.py index 9a6177a4d..f4fe73080 100644 --- a/benchmark_streamlit.py +++ b/benchmark_streamlit.py @@ -86,12 +86,12 @@ def add_metrics(container, value_benchmark, diff_benchmark=None): def get_opposite_benchmarks(benchmark_set, benchmark): compare_params = ( ("db_factory", "uri"), - ("context_num", ), - ("from_dialog_len", ), - ("to_dialog_len", ), - ("step_dialog_len", ), - ("message_dimensions", ), - ("misc_dimensions", ), + ("benchmark_config", "context_num"), + ("benchmark_config", "from_dialog_len"), + ("benchmark_config", "to_dialog_len"), + ("benchmark_config", "step_dialog_len"), + ("benchmark_config", "message_dimensions"), + ("benchmark_config", "misc_dimensions"), ) def get_param(bench, param): @@ -243,12 +243,7 @@ def add_benchmark(): stat: selected_benchmark[stat] for stat in ( "db_factory", - "context_num", - "from_dialog_len", - "to_dialog_len", - "step_dialog_len", - "message_dimensions", - "misc_dimensions", + "benchmark_config", ) } From 0fe49a49d3d610d2c57e3331dbaeada163cf76f3 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Wed, 28 Jun 2023 13:35:47 +0300 Subject: [PATCH 040/113] update benchmark_new_format.py --- benchmark_new_format.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/benchmark_new_format.py b/benchmark_new_format.py index 859b08f24..8065a821b 100644 --- a/benchmark_new_format.py +++ b/benchmark_new_format.py @@ -46,5 +46,19 @@ if dimensions is not None: benchmark[f"{param}_dimensions"] = dimensions + # update benchmark_config + for benchmark in new_benchmark_set["benchmarks"].values(): + if "benchmark_config" not in benchmark: + benchmark_config = { + "context_num": benchmark.pop("context_num"), + "from_dialog_len": benchmark.pop("from_dialog_len"), + "to_dialog_len": benchmark.pop("to_dialog_len"), + "step_dialog_len": benchmark.pop("step_dialog_len"), + "message_dimensions": benchmark.pop("message_dimensions"), + "misc_dimensions": benchmark.pop("misc_dimensions"), + } + + benchmark["benchmark_config"] = benchmark_config + with open(file, "w") as fd: json.dump(new_benchmark_set, fd) From 9e8f3108971ce7bcd793ddee8ac2dac4827a4d2d Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Wed, 28 Jun 2023 23:49:07 +0300 Subject: [PATCH 041/113] fix bug with delisting benchmarks --- benchmark_streamlit.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/benchmark_streamlit.py b/benchmark_streamlit.py index f4fe73080..8b2a286d2 100644 --- a/benchmark_streamlit.py +++ b/benchmark_streamlit.py @@ -161,6 +161,9 @@ def delist_benchmarks(): del st.session_state["benchmarks"][file] delist_container.text(f"Delisted {file}") + with open(benchmark_results_files, "w", encoding="utf-8") as fd: + json.dump(list(st.session_state["benchmark_files"]), fd) + delist_container.button(label="Delist selected benchmark sets", on_click=delist_benchmarks) From 74947725c3cc63d97a90c17a10ef8ed24a999d7a Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Thu, 29 Jun 2023 00:09:46 +0300 Subject: [PATCH 042/113] not include zero-point in graphs --- benchmark_streamlit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmark_streamlit.py b/benchmark_streamlit.py index 8b2a286d2..a7aa54ad0 100644 --- a/benchmark_streamlit.py +++ b/benchmark_streamlit.py @@ -319,7 +319,7 @@ def add_results_to_compare_tab(): data = pd.DataFrame({"context_num": range(len(graph_data)), "time": graph_data}) chart = alt.Chart(data).mark_circle().encode( - x="dialog_len:Q" if isinstance(graph_data, dict) else "context_num:Q", + x=alt.X("dialog_len:Q" if isinstance(graph_data, dict) else "context_num:Q", scale=alt.Scale(zero=False)), y="time:Q", ).interactive() From 99a4e36ffb6236ec6a421674f79579ed65fc4d9d Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Thu, 29 Jun 2023 13:24:11 +0300 Subject: [PATCH 043/113] move get_context_updater to BenchmarkConfig --- dff/utils/benchmark/context_storage.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/dff/utils/benchmark/context_storage.py b/dff/utils/benchmark/context_storage.py index 594d75e48..fdfeb8349 100644 --- a/dff/utils/benchmark/context_storage.py +++ b/dff/utils/benchmark/context_storage.py @@ -192,28 +192,28 @@ def sizes(self): "message_size": asizeof.asizeof(get_message(self.message_dimensions)), } - -class BenchmarkCase(BaseModel): - name: str - db_factory: DBFactory - benchmark_config: BenchmarkConfig = BenchmarkConfig() - uuid: str = Field(default_factory=lambda: str(uuid4())) - description: str = "" - def get_context_updater(self): def _context_updater(context: Context): start_len = len(context.requests) - if start_len + self.benchmark_config.step_dialog_len < self.benchmark_config.to_dialog_len: - for i in range(start_len, start_len + self.benchmark_config.step_dialog_len): + if start_len + self.step_dialog_len < self.to_dialog_len: + for i in range(start_len, start_len + self.step_dialog_len): context.add_label((f"flow_{i}", f"node_{i}")) - context.add_request(get_message(self.benchmark_config.message_dimensions)) - context.add_response(get_message(self.benchmark_config.message_dimensions)) + context.add_request(get_message(self.message_dimensions)) + context.add_response(get_message(self.message_dimensions)) return context else: return None return _context_updater + +class BenchmarkCase(BaseModel): + name: str + db_factory: DBFactory + benchmark_config: BenchmarkConfig = BenchmarkConfig() + uuid: str = Field(default_factory=lambda: str(uuid4())) + description: str = "" + @staticmethod def set_average_results(benchmark): if not benchmark["success"] or isinstance(benchmark["result"], str): @@ -257,7 +257,7 @@ def _run(self): self.benchmark_config.misc_dimensions ), self.benchmark_config.context_num, - context_updater=self.get_context_updater() + context_updater=self.benchmark_config.get_context_updater() ) return { "success": True, From f787577b4f73af352f0ee4cf5c34b4bda75cc144 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Thu, 29 Jun 2023 13:27:45 +0300 Subject: [PATCH 044/113] add benchmark_dir variable for benchmark_dbs.py --- benchmark_dbs.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/benchmark_dbs.py b/benchmark_dbs.py index 9d3c4ccc8..4b8aa6d41 100644 --- a/benchmark_dbs.py +++ b/benchmark_dbs.py @@ -75,10 +75,12 @@ def benchmark_all( } # benchmark -pathlib.Path("benchmarks").mkdir(exist_ok=True) +benchmark_dir = pathlib.Path("benchmarks") + +benchmark_dir.mkdir(exist_ok=True) benchmark_all( - "benchmarks/alexaprize.json", + benchmark_dir / "alexaprize.json", "Alexaprize-like dialogue benchmarks", "Benchmark with dialogues similar to those from alexaprize.", db_uris=dbs, @@ -91,7 +93,7 @@ def benchmark_all( ) benchmark_all( - "benchmarks/short_messages.json", + benchmark_dir / "short_messages.json", "Short messages", "Benchmark with short messages, long dialog len.", db_uris=dbs, @@ -104,14 +106,14 @@ def benchmark_all( ) benchmark_all( - "benchmarks/default.json", + benchmark_dir / "default.json", "Default", "Benchmark using default parameters.", db_uris=dbs, ) benchmark_all( - "benchmarks/alexaprize_longer.json", + benchmark_dir / "alexaprize_longer.json", "Alexaprize-like dialogue benchmarks (longer)", "Benchmark with dialogues similar to those from alexaprize, but dialog len is increased.", db_uris=dbs, @@ -158,7 +160,7 @@ def benchmark_all( description="Benchmark with misc containing many keys." ), ], - file="benchmarks/extremes.json", + file=benchmark_dir / "extremes.json", name="Extreme", description="Set of benchmarks testing extreme cases." ) From 1dbb7f9cbe5d6d4f7472338b8ebf87f60d261dd6 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Thu, 29 Jun 2023 18:49:00 +0300 Subject: [PATCH 045/113] add get_context method to BenchmarkConfig --- dff/utils/benchmark/context_storage.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/dff/utils/benchmark/context_storage.py b/dff/utils/benchmark/context_storage.py index fdfeb8349..4fd420f01 100644 --- a/dff/utils/benchmark/context_storage.py +++ b/dff/utils/benchmark/context_storage.py @@ -180,10 +180,13 @@ class BenchmarkConfig(BaseModel): class Config: allow_mutation = False + def get_context(self): + return get_context(self.from_dialog_len, self.message_dimensions, self.misc_dimensions) + def sizes(self): return { "starting_context_size": asizeof.asizeof( - get_context(self.from_dialog_len, self.message_dimensions, self.misc_dimensions) + self.get_context() ), "final_context_size": asizeof.asizeof( get_context(self.to_dialog_len, self.message_dimensions, self.misc_dimensions) @@ -251,11 +254,7 @@ def _run(self): try: write_times, read_times, update_times = time_context_read_write( self.db_factory.db(), - get_context( - self.benchmark_config.from_dialog_len, - self.benchmark_config.message_dimensions, - self.benchmark_config.misc_dimensions - ), + self.benchmark_config.get_context(), self.benchmark_config.context_num, context_updater=self.benchmark_config.get_context_updater() ) From 8e2ab5437c857c72c2e59e97fcafa193361052d8 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Thu, 29 Jun 2023 23:50:23 +0300 Subject: [PATCH 046/113] change update benchmarking logic Now deepcopy is only done while iterating over context_num. --- dff/utils/benchmark/context_storage.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/dff/utils/benchmark/context_storage.py b/dff/utils/benchmark/context_storage.py index 4fd420f01..a1c9b6a02 100644 --- a/dff/utils/benchmark/context_storage.py +++ b/dff/utils/benchmark/context_storage.py @@ -119,18 +119,14 @@ def time_context_read_write( read_times: tp.List[tp.Dict[int, float]] = [] update_times: tp.List[tp.Dict[int, float]] = [] - if context_updater is not None: - updated_contexts = [context] - - while updated_contexts[-1] is not None: - updated_contexts.append(context_updater(deepcopy(updated_contexts[-1]))) - for _ in tqdm(range(context_num), desc=f"Benchmarking context storage:{context_storage.full_path}"): + tmp_context = deepcopy(context) + ctx_id = uuid4() # write operation benchmark write_start = perf_counter() - context_storage[ctx_id] = context + context_storage[ctx_id] = tmp_context write_times.append(perf_counter() - write_start) read_times.append({}) @@ -139,21 +135,25 @@ def time_context_read_write( read_start = perf_counter() _ = context_storage[ctx_id] read_time = perf_counter() - read_start - read_times[-1][len(context.labels)] = read_time + read_times[-1][len(tmp_context.labels)] = read_time if context_updater is not None: update_times.append({}) - for updated_context in updated_contexts[1:-1]: + tmp_context = context_updater(tmp_context) + + while tmp_context is not None: update_start = perf_counter() - context_storage[ctx_id] = updated_context + context_storage[ctx_id] = tmp_context update_time = perf_counter() - update_start - update_times[-1][len(updated_context.labels)] = update_time + update_times[-1][len(tmp_context.labels)] = update_time read_start = perf_counter() _ = context_storage[ctx_id] read_time = perf_counter() - read_start - read_times[-1][len(updated_context.labels)] = read_time + read_times[-1][len(tmp_context.labels)] = read_time + + tmp_context = context_updater(tmp_context) context_storage.clear() return write_times, read_times, update_times From 87413340d714333392542e359bc7f02febd7495a Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Sun, 2 Jul 2023 15:48:10 +0300 Subject: [PATCH 047/113] return empty dicts as update_times if context_updater is None --- dff/utils/benchmark/context_storage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dff/utils/benchmark/context_storage.py b/dff/utils/benchmark/context_storage.py index a1c9b6a02..3174c2665 100644 --- a/dff/utils/benchmark/context_storage.py +++ b/dff/utils/benchmark/context_storage.py @@ -130,6 +130,7 @@ def time_context_read_write( write_times.append(perf_counter() - write_start) read_times.append({}) + update_times.append({}) # read operation benchmark read_start = perf_counter() @@ -138,7 +139,6 @@ def time_context_read_write( read_times[-1][len(tmp_context.labels)] = read_time if context_updater is not None: - update_times.append({}) tmp_context = context_updater(tmp_context) From 5a4eb7507ad5e512d2c1508ba25e1399c61ff387 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Mon, 3 Jul 2023 00:22:06 +0300 Subject: [PATCH 048/113] remove write_times from average_results --- benchmark_streamlit.py | 2 +- dff/utils/benchmark/context_storage.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/benchmark_streamlit.py b/benchmark_streamlit.py index a7aa54ad0..dc1f1feeb 100644 --- a/benchmark_streamlit.py +++ b/benchmark_streamlit.py @@ -302,7 +302,7 @@ def add_results_to_compare_tab(): select_graph, graph = st.columns([1, 3]) graphs = { - "Write": selected_benchmark["average_results"]["write_times"], + "Write": selected_benchmark["result"]["write_times"], "Read (grouped by contex_num)": selected_benchmark["average_results"]["read_times_grouped_by_context_num"], "Read (grouped by dialog_len)": selected_benchmark["average_results"]["read_times_grouped_by_dialog_len"], "Update (grouped by contex_num)": selected_benchmark["average_results"]["update_times_grouped_by_context_num"], diff --git a/dff/utils/benchmark/context_storage.py b/dff/utils/benchmark/context_storage.py index 3174c2665..423ca0a61 100644 --- a/dff/utils/benchmark/context_storage.py +++ b/dff/utils/benchmark/context_storage.py @@ -237,7 +237,6 @@ def get_complex_stats(results): "average_write_time": mean(benchmark["result"]["write_times"]), "average_read_time": read_stats[2], "average_update_time": update_stats[2], - "write_times": benchmark["result"]["write_times"], "read_times_grouped_by_context_num": read_stats[0], "read_times_grouped_by_dialog_len": read_stats[1], "update_times_grouped_by_context_num": update_stats[0], From a4e71c74bc62669fc97d5a3380e79b629d0b3b11 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Mon, 3 Jul 2023 00:57:21 +0300 Subject: [PATCH 049/113] add support for no update times --- benchmark_streamlit.py | 2 ++ dff/utils/benchmark/context_storage.py | 19 +++++++++++++------ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/benchmark_streamlit.py b/benchmark_streamlit.py index dc1f1feeb..d7ac6f2d0 100644 --- a/benchmark_streamlit.py +++ b/benchmark_streamlit.py @@ -36,6 +36,8 @@ def get_diff(last_metric, first_metric): + if last_metric is None or first_metric is None: + return "-" if st.session_state["percent_compare"]: return f"{(last_metric / first_metric - 1):.3%}" else: diff --git a/dff/utils/benchmark/context_storage.py b/dff/utils/benchmark/context_storage.py index 423ca0a61..a900d1270 100644 --- a/dff/utils/benchmark/context_storage.py +++ b/dff/utils/benchmark/context_storage.py @@ -223,11 +223,14 @@ def set_average_results(benchmark): return def get_complex_stats(results): + if len(results) == 0 or len(results[0]) == 0: + return [], {}, None + average_grouped_by_context_num = [mean(times.values()) for times in results] average_grouped_by_dialog_len = { - key: mean([times[key] for times in results]) for key in next(iter(results), {}).keys() + key: mean([times[key] for times in results]) for key in results[0].keys() } - average = mean(average_grouped_by_context_num) + average = float(mean(average_grouped_by_context_num)) return average_grouped_by_context_num, average_grouped_by_dialog_len, average read_stats = get_complex_stats(benchmark["result"]["read_times"]) @@ -242,10 +245,14 @@ def get_complex_stats(results): "update_times_grouped_by_context_num": update_stats[0], "update_times_grouped_by_dialog_len": update_stats[1], } - result["pretty_write"] = float(f'{result["average_write_time"]:.3}') - result["pretty_read"] = float(f'{result["average_read_time"]:.3}') - result["pretty_update"] = float(f'{result["average_update_time"]:.3}') - result["pretty_read+update"] = float(f'{result["average_read_time"] + result["average_update_time"]:.3}') + result["pretty_write"] = float(f'{result["average_write_time"]:.3}')\ + if result["average_write_time"] is not None else None + result["pretty_read"] = float(f'{result["average_read_time"]:.3}')\ + if result["average_read_time"] is not None else None + result["pretty_update"] = float(f'{result["average_update_time"]:.3}')\ + if result["average_update_time"] is not None else None + result["pretty_read+update"] = float(f'{result["average_read_time"] + result["average_update_time"]:.3}')\ + if result["average_read_time"] is not None and result["average_update_time"] is not None else None benchmark["average_results"] = result From d7cb2906aa18d28a01315b6ad81b6a77845dc862 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Mon, 3 Jul 2023 02:23:04 +0300 Subject: [PATCH 050/113] group sizes stat --- benchmark_new_format.py | 11 +++++++++++ benchmark_streamlit.py | 9 ++------- dff/utils/benchmark/context_storage.py | 2 +- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/benchmark_new_format.py b/benchmark_new_format.py index 8065a821b..dc2cf69dc 100644 --- a/benchmark_new_format.py +++ b/benchmark_new_format.py @@ -60,5 +60,16 @@ benchmark["benchmark_config"] = benchmark_config + # update sizes + for benchmark in new_benchmark_set["benchmarks"].values(): + if "sizes" not in benchmark: + sizes = { + key: benchmark.pop(key) for key in ( + "starting_context_size", "final_context_size", "misc_size", "message_size" + ) + } + + benchmark["sizes"] = sizes + with open(file, "w") as fd: json.dump(new_benchmark_set, fd) diff --git a/benchmark_streamlit.py b/benchmark_streamlit.py index d7ac6f2d0..e0c341422 100644 --- a/benchmark_streamlit.py +++ b/benchmark_streamlit.py @@ -253,13 +253,8 @@ def add_benchmark(): } size_stats = { - stat: naturalsize(selected_benchmark[stat], gnu=True) - for stat in ( - "starting_context_size", - "final_context_size", - "misc_size", - "message_size", - ) + stat: naturalsize(value, gnu=True) + for stat, value in selected_benchmark["sizes"].items() } st.json(reproducible_stats) diff --git a/dff/utils/benchmark/context_storage.py b/dff/utils/benchmark/context_storage.py index a900d1270..7b209bea4 100644 --- a/dff/utils/benchmark/context_storage.py +++ b/dff/utils/benchmark/context_storage.py @@ -302,7 +302,7 @@ def save_results_to_file( "benchmarks": {}, } for case in benchmark_cases: - result["benchmarks"][case.uuid] = {**case.dict(), **case.benchmark_config.sizes(), **case.run()} + result["benchmarks"][case.uuid] = {**case.dict(), "sizes": case.benchmark_config.sizes(), **case.run()} json.dump(result, fd) From d3d66c03896fb7997fc8ca925c71236cf7a9cd98 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Mon, 3 Jul 2023 11:51:32 +0300 Subject: [PATCH 051/113] add benchmark tests --- tests/utils/test_benchmark.py | 274 ++++++++++++++++++++++++++++++++++ 1 file changed, 274 insertions(+) create mode 100644 tests/utils/test_benchmark.py diff --git a/tests/utils/test_benchmark.py b/tests/utils/test_benchmark.py new file mode 100644 index 000000000..27fec2bb5 --- /dev/null +++ b/tests/utils/test_benchmark.py @@ -0,0 +1,274 @@ +from copy import deepcopy +import json + +import pytest + +import dff.utils.benchmark.context_storage as bm +from dff.context_storages import JSONContextStorage + + +def test_get_dict(): + assert bm.get_dict(()) == {} + assert bm.get_dict((1,)) == {0: ""} + assert bm.get_dict((2, 3)) == {0: "...", 1: "..."} + assert bm.get_dict((2, 3, 4)) == {0: {0: "....", 1: "....", 2: "...."}, 1: {0: "....", 1: "....", 2: "...."}} + + +def test_get_context(): + context = bm.get_context(2, (1, 2), (2, 3)) + assert context == bm.Context( + id=context.id, + labels={0: ("flow_0", "node_0"), 1: ("flow_1", "node_1")}, + requests={0: bm.Message(misc={0: ".."}), 1: bm.Message(misc={0: ".."})}, + responses={0: bm.Message(misc={0: ".."}), 1: bm.Message(misc={0: ".."})}, + misc={0: "...", 1: "..."} + ) + + +def test_benchmark_config(): + config = bm.BenchmarkConfig( + from_dialog_len=1, + to_dialog_len=5, + message_dimensions=(2, 2), + misc_dimensions=(3, 3, 3) + ) + context = config.get_context() + actual_context = bm.get_context(1, (2, 2), (3, 3, 3)) + actual_context.id = context.id + assert context == actual_context + + sizes = config.sizes() + for size in ("starting_context_size", "final_context_size", "misc_size", "message_size"): + assert isinstance(sizes[size], int) + assert sizes[size] > 0 + + context_updater = config.get_context_updater() + + contexts = [context] + + while contexts[-1] is not None: + contexts.append(context_updater(deepcopy(contexts[-1]))) + + assert len(contexts) == 5 + + for index, context in enumerate(contexts): + if context is not None: + assert len(context.labels) == len(context.requests) == len(context.responses) == index + 1 + + actual_context = bm.get_context(index + 1, (2, 2), (3, 3, 3)) + actual_context.id = context.id + assert context == actual_context + + +def test_context_updater_with_steps(): + config = bm.BenchmarkConfig( + from_dialog_len=1, + to_dialog_len=11, + step_dialog_len=3, + message_dimensions=(2, 2), + misc_dimensions=(3, 3, 3) + ) + + context_updater = config.get_context_updater() + + contexts = [config.get_context()] + + while contexts[-1] is not None: + contexts.append(context_updater(deepcopy(contexts[-1]))) + + assert len(contexts) == 5 + + for index, context in zip(range(1, 11, 3), contexts): + if context is not None: + assert len(context.labels) == len(context.requests) == len(context.responses) == index + + actual_context = bm.get_context(index, (2, 2), (3, 3, 3)) + actual_context.id = context.id + assert context == actual_context + + +def test_db_factory(tmp_path): + factory = bm.DBFactory(uri=f"json://{tmp_path}/json.json") + + db = factory.db() + + assert isinstance(db, JSONContextStorage) + + +@pytest.fixture +def context_storage(tmp_path) -> JSONContextStorage: + factory = bm.DBFactory(uri=f"json://{tmp_path}/json.json") + + return factory.db() + + +def test_time_context_read_write(context_storage): + config = bm.BenchmarkConfig( + context_num=5, + from_dialog_len=1, + to_dialog_len=11, + step_dialog_len=3, + message_dimensions=(2, 2), + misc_dimensions=(3, 3, 3) + ) + + results = bm.time_context_read_write( + context_storage, + config.get_context(), + config.context_num, + config.get_context_updater() + ) + + assert len(context_storage) == 0 + + assert len(results) == 3 + + write, read, update = results + + assert len(write) == len(read) == len(update) == config.context_num + + assert all([isinstance(write_time, float) and write_time > 0 for write_time in write]) + + for read_item in read: + assert list(read_item.keys()) == list(range(1, 11, 3)) + assert all([isinstance(read_time, float) and read_time > 0 for read_time in read_item.values()]) + + for update_item in update: + assert list(update_item.keys()) == list(range(1, 11, 3))[1:] + assert all([isinstance(update_time, float) and update_time > 0 for update_time in update_item.values()]) + + +def test_time_context_read_write_without_updates(context_storage): + config = bm.BenchmarkConfig( + context_num=5, + from_dialog_len=1, + to_dialog_len=2, + step_dialog_len=3, + message_dimensions=(2, 2), + misc_dimensions=(3, 3, 3) + ) + + results = bm.time_context_read_write( + context_storage, + config.get_context(), + config.context_num, + None, + ) + _, read, update = results + + assert list(read[0].keys()) == [1] + assert list(update[0].keys()) == [] + + results = bm.time_context_read_write( + context_storage, + config.get_context(), + config.context_num, + config.get_context_updater(), # context updater returns None + ) + _, read, update = results + + assert list(read[0].keys()) == [1] + assert list(update[0].keys()) == [] + + +def test_average_results(): + benchmark = { + "success": False, + "result": "error", + } + + bm.BenchmarkCase.set_average_results(benchmark) + + assert "average_results" not in benchmark + + benchmark = { + "success": True, + "result": { + "write_times": [1, 2], + "read_times": [{0: 3, 1: 4}, {0: 5, 1: 6}], + "update_times": [{0: 7, 1: 8}, {0: 9, 1: 10}], + } + } + + bm.BenchmarkCase.set_average_results(benchmark) + + assert benchmark["average_results"] == { + "average_write_time": 1.5, + "average_read_time": 4.5, + "average_update_time": 8.5, + "read_times_grouped_by_context_num": [3.5, 5.5], + "read_times_grouped_by_dialog_len": {0: 4, 1: 5}, + "update_times_grouped_by_context_num": [7.5, 9.5], + "update_times_grouped_by_dialog_len": {0: 8, 1: 9}, + "pretty_write": 1.5, + "pretty_read": 4.5, + "pretty_update": 8.5, + "pretty_read+update": 13, + } + + benchmark = { + "success": True, + "result": { + "write_times": [1, 2], + "read_times": [{0: 3}, {0: 5}], + "update_times": [{}, {}], + } + } + + bm.BenchmarkCase.set_average_results(benchmark) + + +def test_benchmark_case(tmp_path): + case = bm.BenchmarkCase( + name="", + db_factory=bm.DBFactory(uri=f"json://{tmp_path}/json.json"), + benchmark_config=bm.BenchmarkConfig( + context_num=5, + from_dialog_len=1, + to_dialog_len=11, + step_dialog_len=3, + message_dimensions=(2, 2), + misc_dimensions=(3, 3, 3) + ), + ) + + benchmark = case.run() + assert benchmark["success"] + + assert all([isinstance(write_time, float) and write_time > 0 for write_time in benchmark["result"]["write_times"]]) + + for read_item in benchmark["result"]["read_times"]: + assert list(read_item.keys()) == list(range(1, 11, 3)) + assert all([isinstance(read_time, float) and read_time > 0 for read_time in read_item.values()]) + + for update_item in benchmark["result"]["update_times"]: + assert list(update_item.keys()) == list(range(1, 11, 3))[1:] + assert all([isinstance(update_time, float) and update_time > 0 for update_time in update_item.values()]) + + +def test_save_to_file(tmp_path): + bm.benchmark_all( + tmp_path / "result.json", + "test", + "test", + {"json": f"json://{tmp_path}/json.json"}, + bm.BenchmarkConfig( + context_num=5, + from_dialog_len=1, + to_dialog_len=11, + step_dialog_len=3, + message_dimensions=(2, 2), + misc_dimensions=(3, 3, 3) + ) + ) + + with open(tmp_path / "result.json", "r", encoding="utf-8") as fd: + benchmark_set = json.load(fd) + + assert set(benchmark_set.keys()) == {"name", "description", "uuid", "benchmarks"} + + benchmark = tuple(benchmark_set["benchmarks"].values())[0] + + assert set(benchmark.keys()) == { + "name", "db_factory", "benchmark_config", "uuid", "description", "sizes", "success", "result", "average_results" + } From e995195942481bf997771d5126b0969a62c5263e Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Mon, 3 Jul 2023 19:31:02 +0300 Subject: [PATCH 052/113] clear context storage at the end of each context_num cycle --- dff/utils/benchmark/context_storage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dff/utils/benchmark/context_storage.py b/dff/utils/benchmark/context_storage.py index 7b209bea4..82ccb0d40 100644 --- a/dff/utils/benchmark/context_storage.py +++ b/dff/utils/benchmark/context_storage.py @@ -155,7 +155,7 @@ def time_context_read_write( tmp_context = context_updater(tmp_context) - context_storage.clear() + context_storage.clear() return write_times, read_times, update_times From a92ca572f75fbf552234c66ad3af91076e263da5 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Tue, 4 Jul 2023 14:39:12 +0300 Subject: [PATCH 053/113] save benchmarks in a list --- benchmark_new_format.py | 3 +++ benchmark_streamlit.py | 10 +++++----- dff/utils/benchmark/context_storage.py | 4 ++-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/benchmark_new_format.py b/benchmark_new_format.py index dc2cf69dc..b54c22bd8 100644 --- a/benchmark_new_format.py +++ b/benchmark_new_format.py @@ -71,5 +71,8 @@ benchmark["sizes"] = sizes + benchmarks = new_benchmark_set.pop("benchmarks") + new_benchmark_set["benchmarks"] = list(benchmarks.values()) + with open(file, "w") as fd: json.dump(new_benchmark_set, fd) diff --git a/benchmark_streamlit.py b/benchmark_streamlit.py index e0c341422..03cd0846c 100644 --- a/benchmark_streamlit.py +++ b/benchmark_streamlit.py @@ -104,7 +104,7 @@ def get_param(bench, param): opposite_benchmarks = [ opposite_benchmark - for opposite_benchmark in benchmark_set["benchmarks"].values() + for opposite_benchmark in benchmark_set["benchmarks"] if opposite_benchmark["uuid"] != benchmark["uuid"] and all( get_param(benchmark, param) == get_param(opposite_benchmark, param) for param in compare_params ) @@ -229,7 +229,7 @@ def add_benchmark(): benchmarks = { f"{benchmark['name']} ({benchmark['uuid']})": benchmark - for benchmark in selected_set["benchmarks"].values() + for benchmark in selected_set["benchmarks"] } benchmark = benchmark_choice.selectbox("Benchmark", benchmarks.keys()) @@ -367,8 +367,8 @@ def add_results_to_compare_tab(): with mass_compare_tab: sets = { - f"{benchmark['name']} ({benchmark['uuid']})": benchmark - for benchmark in st.session_state["benchmarks"].values() + f"{benchmark_set['name']} ({benchmark_set['uuid']})": benchmark_set + for benchmark_set in st.session_state["benchmarks"].values() } benchmark_set = st.selectbox("Benchmark set", sets.keys(), key="mass_compare_selectbox") @@ -380,7 +380,7 @@ def add_results_to_compare_tab(): added_benchmarks = set() - for benchmark in selected_set["benchmarks"].values(): + for benchmark in selected_set["benchmarks"]: if benchmark["uuid"] in added_benchmarks: continue diff --git a/dff/utils/benchmark/context_storage.py b/dff/utils/benchmark/context_storage.py index 82ccb0d40..91fa8e5ea 100644 --- a/dff/utils/benchmark/context_storage.py +++ b/dff/utils/benchmark/context_storage.py @@ -299,10 +299,10 @@ def save_results_to_file( "name": name, "description": description, "uuid": uuid, - "benchmarks": {}, + "benchmarks": [], } for case in benchmark_cases: - result["benchmarks"][case.uuid] = {**case.dict(), "sizes": case.benchmark_config.sizes(), **case.run()} + result["benchmarks"].append({**case.dict(), "sizes": case.benchmark_config.sizes(), **case.run()}) json.dump(result, fd) From 370d13877988207997702365c1c8ef34b5d9abef Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Wed, 5 Jul 2023 13:02:39 +0300 Subject: [PATCH 054/113] add json schema for benchmark results --- setup.py | 1 + tests/utils/test_benchmark.py | 17 +- utils/db_benchmark/benchmark_schema.json | 235 +++++++++++++++++++++++ 3 files changed, 251 insertions(+), 2 deletions(-) create mode 100644 utils/db_benchmark/benchmark_schema.json diff --git a/setup.py b/setup.py index 18e50a435..275ab1e75 100644 --- a/setup.py +++ b/setup.py @@ -125,6 +125,7 @@ def merge_req_lists(*req_lists: List[str]) -> List[str]: "streamlit-chat~=0.1.1", "pandas", "altair", + "jsonschema==4.17.3", ], requests_requirements, ) diff --git a/tests/utils/test_benchmark.py b/tests/utils/test_benchmark.py index 27fec2bb5..659ae7387 100644 --- a/tests/utils/test_benchmark.py +++ b/tests/utils/test_benchmark.py @@ -1,12 +1,17 @@ from copy import deepcopy import json +import pathlib +from jsonschema import validate import pytest import dff.utils.benchmark.context_storage as bm from dff.context_storages import JSONContextStorage +ROOT_DIR = pathlib.Path(__file__).parent.parent.parent + + def test_get_dict(): assert bm.get_dict(()) == {} assert bm.get_dict((1,)) == {0: ""} @@ -247,11 +252,14 @@ def test_benchmark_case(tmp_path): def test_save_to_file(tmp_path): + with open(ROOT_DIR / "utils/db_benchmark/benchmark_schema.json", "r", encoding="utf-8") as fd: + schema = json.load(fd) + bm.benchmark_all( tmp_path / "result.json", "test", "test", - {"json": f"json://{tmp_path}/json.json"}, + {"json": f"json://{tmp_path}/json.json", "error": "NONE"}, bm.BenchmarkConfig( context_num=5, from_dialog_len=1, @@ -267,8 +275,13 @@ def test_save_to_file(tmp_path): assert set(benchmark_set.keys()) == {"name", "description", "uuid", "benchmarks"} - benchmark = tuple(benchmark_set["benchmarks"].values())[0] + benchmark = tuple(benchmark_set["benchmarks"])[0] assert set(benchmark.keys()) == { "name", "db_factory", "benchmark_config", "uuid", "description", "sizes", "success", "result", "average_results" } + + assert not benchmark_set["benchmarks"][1]["success"] + assert "average_results" not in benchmark_set["benchmarks"][1] + + validate(instance=benchmark_set, schema=schema) diff --git a/utils/db_benchmark/benchmark_schema.json b/utils/db_benchmark/benchmark_schema.json new file mode 100644 index 000000000..ae4129127 --- /dev/null +++ b/utils/db_benchmark/benchmark_schema.json @@ -0,0 +1,235 @@ +{ + "title": "Benchmark set", + "description": "A structure containing results of multiple benchmarks", + "type": "object", + "properties": { + "name": { + "description": "Name of the benchmark set", + "type": "string" + }, + "description": { + "description": "Description of the benchmark set", + "type": "string" + }, + "uuid": { + "description": "Unique id of the benchmark set", + "type": "string" + }, + "benchmarks": { + "description": "A list of benchmarks in the set with results", + "type": "array", + "items": { + "title": "Benchmark", + "description": "A singular benchmark with results", + "type": "object", + "properties": { + "name": { + "description": "Name of the benchmark", + "type": "string" + }, + "description": { + "description": "Description of the benchmark", + "type": "string" + }, + "uuid": { + "description": "Unique id of the benchmark", + "type": "string" + }, + "success": { + "description": "Whether benchmark process did not raise an exception", + "type": "boolean" + }, + "db_factory": { + "description": "Configuration of the context storage that was benchmarked", + "type": "object", + "properties": { + "uri": { + "description": "URI of the context storage", + "type": "string" + }, + "factory_module": { + "description": "A module containing context storage factory", + "type": "string" + }, + "factory": { + "description": "Name of a context storage factory inside the module", + "type": "string" + } + }, + "required": ["uri", "factory_module", "factory"] + }, + "benchmark_config": { + "description": "Configuration of the benchmark", + "type": "object", + "properties": { + "context_num": { + "description": "Number of times each context was written/read", + "type": "integer", + "minimum": 1 + }, + "from_dialog_len": { + "description": "Starting dialog len", + "type": "integer", + "minimum": 0 + }, + "to_dialog_len": { + "description": "Final dialog len (exclusive)", + "type": "integer", + "minimum": 1 + }, + "step_dialog_len": { + "description": "Increment step of dialog len", + "type": "integer", + "minimum": 1 + }, + "message_dimensions": { + "description": "Dimensions of a misc field of each message", + "type": "array", + "items": { + "type": "integer", + "minimum": 0 + } + }, + "misc_dimensions": { + "description": "Dimensions of a misc field of contexts", + "type": "array", + "items": { + "type": "integer", + "minimum": 0 + } + } + }, + "required": ["context_num", "from_dialog_len", "to_dialog_len", "step_dialog_len", "message_dimensions", "misc_dimensions"] + }, + "sizes": { + "description": "A dictionary with size statistics for objects used during benchmarking", + "type": "object", + "properties": { + "starting_context_size": { + "description": "Context size of from_dialog_len length", + "type": "integer", + "minimum": 1 + }, + "final_context_size": { + "description": "Context size of to_dialog_len length", + "type": "integer", + "minimum": 1 + }, + "misc_size": { + "description": "Size of the misc field of a context", + "type": "integer", + "minimum": 1 + }, + "message_size": { + "description": "Size of a message", + "type": "integer", + "minimum": 1 + } + }, + "required": ["starting_context_size", "final_context_size", "misc_size", "message_size"] + }, + "result": { + "description": "Raw benchmark results or error message", + "oneOf": [ + { + "type": "object", + "properties": { + "write_times": { + "description": "List of write times; list index corresponds to context_num", + "type": "array", + "items": {"type": "number", "minimum": 0} + }, + "read_times": { + "description": "List of read times w.r.t. dialog_len; list index corresponds to context_num", + "type": "array", + "items": { + "type": "object", + "description": "Dictionary in which keys are equal to dialog_len of a context and values to read time of a context" + } + }, + "update_times": { + "description": "List of update times w.r.t. dialog_len; list index corresponds to context_num", + "type": "array", + "items": { + "type": "object", + "description": "Dictionary in which keys are equal to dialog_len of a context and values to update time of a context" + } + } + }, + "required": ["write_times", "read_times", "update_times"] + }, + { + "type": "string" + } + ] + + }, + "average_results": { + "description": "Calculated average statistics for benchmark results", + "type": "object", + "properties": { + "average_write_time": {"type": "number"}, + "average_read_time": {"type": "number"}, + "average_update_time": {"type": "number"}, + "read_times_grouped_by_context_num": { + "description": "List of read times w.r.t. context_num (each number is an average read time for that context_num)", + "type": "array", + "items": {"type": "number", "minimum": 0} + }, + "read_times_grouped_by_dialog_len": { + "description": "Mapping from dialog_len to read times (each value is an average read time for that dialog_len)", + "type": "object", + "items": {"type": "number", "minimum": 0} + }, + "update_times_grouped_by_context_num": { + "description": "List of update times w.r.t. context_num (each number is an average update time for that context_num)", + "type": "array", + "items": {"type": "number", "minimum": 0} + }, + "update_times_grouped_by_dialog_len": { + "description": "Mapping from dialog_len to update times (each value is an average update time for that dialog_len)", + "type": "object", + "items": {"type": "number", "minimum": 0} + }, + "pretty_write": { + "description": "Average write time with 3 significant digits", + "type": "number", + "minimum": 0 + }, + "pretty_read": { + "description": "Average read time with 3 significant digits", + "type": "number", + "minimum": 0 + }, + "pretty_update": { + "description": "Average update time with 3 significant digits", + "type": "number", + "minimum": 0 + }, + "pretty_read+update": { + "description": "Sum of average read and update times with 3 significant digits", + "type": "number", + "minimum": 0 + } + }, + "required": [ + "average_write_time", + "average_read_time", + "average_update_time", + "read_times_grouped_by_context_num", + "read_times_grouped_by_dialog_len", + "update_times_grouped_by_context_num", + "update_times_grouped_by_dialog_len", + "pretty_write", + "pretty_read", + "pretty_update", + "pretty_read+update" + ] + } + }, + "required": ["name", "description", "uuid", "success", "db_factory", "benchmark_config", "sizes", "result"] + } + } + }, + "required": ["name", "description", "uuid", "benchmarks"] +} \ No newline at end of file From 435af6ef4af5b03c2bb42b1afac8084b195164a6 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Wed, 5 Jul 2023 14:06:08 +0300 Subject: [PATCH 055/113] move streamlit to utils --- .../db_benchmark/benchmark_streamlit.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename benchmark_streamlit.py => utils/db_benchmark/benchmark_streamlit.py (100%) diff --git a/benchmark_streamlit.py b/utils/db_benchmark/benchmark_streamlit.py similarity index 100% rename from benchmark_streamlit.py rename to utils/db_benchmark/benchmark_streamlit.py From 40a053c82c75ff6d687ca8df3edbfc5572cdf3e4 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Wed, 5 Jul 2023 18:34:44 +0300 Subject: [PATCH 056/113] refactor benchmark streamlit --- utils/db_benchmark/benchmark_streamlit.py | 38 ++++++++++++++++++----- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/utils/db_benchmark/benchmark_streamlit.py b/utils/db_benchmark/benchmark_streamlit.py index 03cd0846c..5b18b0a03 100644 --- a/utils/db_benchmark/benchmark_streamlit.py +++ b/utils/db_benchmark/benchmark_streamlit.py @@ -1,10 +1,31 @@ -import streamlit as st +""" +Benchmark Streamlit +------------------- +This module provides a streamlit interface to view benchmark results. +The interface has 4 tabs: + +- "Benchmark sets" -- Here you can add or remove benchmark set files (generated by `save_results_to_file` function). +- "View" -- Here you can view various stats for each benchmark in the set (as well as add benchmarks to compare tab). +- "Compare" -- Here you can compare benchmarks added via "View" tab. +- "Mass compare" -- This tab lets you compare all benchmarks in a particular set. + +Run this file with `streamlit run {path/to/this/file}`. + +When run, this module will search for a file named "benchmark_results_files.json" in the directory +where the command above was executed. +If the file does not exist there, it will be created. +The file is used to store paths to benchmark result files. + +Benchmark result files added via this module are not changed (only read). +""" import json from pathlib import Path + import pandas as pd from pympler import asizeof from humanize import naturalsize import altair as alt +import streamlit as st st.set_page_config( @@ -13,15 +34,16 @@ initial_sidebar_state="expanded", ) +BENCHMARK_RESULTS_FILES = Path("benchmark_results_files.json") +# This file stores links to benchmark set files generated by `save_results_to_file`. -benchmark_results_files = Path("benchmark_results_files.json") -if not benchmark_results_files.exists(): - with open(benchmark_results_files, "w", encoding="utf-8") as fd: +if not BENCHMARK_RESULTS_FILES.exists(): + with open(BENCHMARK_RESULTS_FILES, "w", encoding="utf-8") as fd: json.dump([], fd) if "benchmark_files" not in st.session_state: - with open(benchmark_results_files, "r", encoding="utf-8") as fd: + with open(BENCHMARK_RESULTS_FILES, "r", encoding="utf-8") as fd: st.session_state["benchmark_files"] = json.load(fd) if "benchmarks" not in st.session_state: @@ -163,7 +185,7 @@ def delist_benchmarks(): del st.session_state["benchmarks"][file] delist_container.text(f"Delisted {file}") - with open(benchmark_results_files, "w", encoding="utf-8") as fd: + with open(BENCHMARK_RESULTS_FILES, "w", encoding="utf-8") as fd: json.dump(list(st.session_state["benchmark_files"]), fd) @@ -196,7 +218,7 @@ def add_benchmark(): return st.session_state["benchmark_files"].append(benchmark_file) - with open(benchmark_results_files, "w", encoding="utf-8") as fd: + with open(BENCHMARK_RESULTS_FILES, "w", encoding="utf-8") as fd: json.dump(list(st.session_state["benchmark_files"]), fd) st.session_state["benchmarks"][benchmark_file] = file_contents @@ -362,7 +384,7 @@ def add_results_to_compare_tab(): ############################################################################### # Mass compare tab -# Allows massively comparing benchmarks inside a single set +# Allows comparing all benchmarks inside a single set ############################################################################### with mass_compare_tab: From f8ecda960447f2e445cb0f3f1e7caf1edfa26423 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Thu, 6 Jul 2023 02:33:17 +0300 Subject: [PATCH 057/113] remove partial-dev comparison tools Revert this commit in tmp/benchmark_partial. --- utils/db_benchmark/benchmark_streamlit.py | 128 ++++------------------ 1 file changed, 22 insertions(+), 106 deletions(-) diff --git a/utils/db_benchmark/benchmark_streamlit.py b/utils/db_benchmark/benchmark_streamlit.py index 5b18b0a03..a1f89ad11 100644 --- a/utils/db_benchmark/benchmark_streamlit.py +++ b/utils/db_benchmark/benchmark_streamlit.py @@ -57,40 +57,17 @@ st.session_state["compare"] = [] -def get_diff(last_metric, first_metric): - if last_metric is None or first_metric is None: - return "-" - if st.session_state["percent_compare"]: - return f"{(last_metric / first_metric - 1):.3%}" - else: - return f"{last_metric - first_metric:.3}" - - -def add_metrics(container, value_benchmark, diff_benchmark=None): +def add_metrics(container, value_benchmark): write, read, update, read_update = container.columns(4) column_names = ("write", "read", "update", "read+update") if not value_benchmark["success"]: values = {key: "-" for key in column_names} - diffs = None else: values = { key: value_benchmark["average_results"][f"pretty_{key}"] for key in column_names } - if diff_benchmark is not None: - if not diff_benchmark["success"]: - diffs = {key: "-" for key in column_names} - else: - diffs = { - key: get_diff( - value_benchmark["average_results"][f"pretty_{key}"], - diff_benchmark["average_results"][f"pretty_{key}"] - ) for key in column_names - } - else: - diffs = None - columns = { "write": write, "read": read, @@ -102,46 +79,11 @@ def add_metrics(container, value_benchmark, diff_benchmark=None): column.metric( column_name.title(), values[column_name], - delta=diffs[column_name] if diffs else None, - delta_color="inverse" ) -def get_opposite_benchmarks(benchmark_set, benchmark): - compare_params = ( - ("db_factory", "uri"), - ("benchmark_config", "context_num"), - ("benchmark_config", "from_dialog_len"), - ("benchmark_config", "to_dialog_len"), - ("benchmark_config", "step_dialog_len"), - ("benchmark_config", "message_dimensions"), - ("benchmark_config", "misc_dimensions"), - ) - - def get_param(bench, param): - if len(param) == 1: - return bench.get(param[0]) - else: - return get_param(bench.get(param[0]), param[1:]) - - opposite_benchmarks = [ - opposite_benchmark - for opposite_benchmark in benchmark_set["benchmarks"] - if opposite_benchmark["uuid"] != benchmark["uuid"] and all( - get_param(benchmark, param) == get_param(opposite_benchmark, param) for param in compare_params - ) - ] - - return opposite_benchmarks - - st.sidebar.text(f"Benchmarks take {naturalsize(asizeof.asizeof(st.session_state['benchmarks']))} RAM") -st.sidebar.divider() - -st.sidebar.checkbox("Compare dev and partial in view tab", value=True, key="partial_compare_checkbox") -st.sidebar.checkbox("Percent comparison", value=True, key="percent_compare") - add_tab, view_tab, compare_tab, mass_compare_tab = st.tabs(["Benchmark sets", "View", "Compare", "Mass compare"]) @@ -285,18 +227,7 @@ def add_benchmark(): if not selected_benchmark["success"]: st.warning(selected_benchmark["result"]) else: - opposite_benchmark = None - - if st.session_state["partial_compare_checkbox"]: - opposite_benchmarks = get_opposite_benchmarks(selected_set, selected_benchmark) - - if len(opposite_benchmarks) == 1: - opposite_benchmark = opposite_benchmarks[0] - - add_metrics(st.container(), selected_benchmark, opposite_benchmark) - - if opposite_benchmark is not None: - st.text(f"* In comparison with {opposite_benchmark['name']} ({opposite_benchmark['uuid']})") + add_metrics(st.container(), selected_benchmark) compare_item = { "benchmark_set": benchmark_set, @@ -362,26 +293,6 @@ def add_results_to_compare_tab(): ) ) - if len(st.session_state["compare"]) == 2: - write, read, update, read_update = st.columns(4) - - first_dict, second_dict = st.session_state["compare"] - - columns = { - "write": write, - "read": read, - "update": update, - "read+update": read_update - } - - for column_name, column in columns.items(): - column.metric( - label=column_name.title(), - value=f"{second_dict[column_name]}", - delta=get_diff(second_dict[column_name], first_dict[column_name]), - delta_color="inverse" - ) - ############################################################################### # Mass compare tab # Allows comparing all benchmarks inside a single set @@ -400,21 +311,26 @@ def add_results_to_compare_tab(): selected_set = sets[benchmark_set] - added_benchmarks = set() - - for benchmark in selected_set["benchmarks"]: - if benchmark["uuid"] in added_benchmarks: - continue + compare_items = [] - opposite_benchmarks = get_opposite_benchmarks(selected_set, benchmark) + for selected_benchmark in selected_set["benchmarks"]: + compare_items.append( + { + "benchmark": f"{selected_benchmark['name']} ({selected_benchmark['uuid']})", + "write": selected_benchmark["average_results"]["pretty_write"], + "read": selected_benchmark["average_results"]["pretty_read"], + "update": selected_benchmark["average_results"]["pretty_update"], + "read+update": selected_benchmark["average_results"]["pretty_read+update"], + } + ) - added_benchmarks.add(benchmark["uuid"]) - added_benchmarks.update({bm["uuid"] for bm in opposite_benchmarks}) - st.divider() + df = pd.DataFrame(compare_items) - if len(opposite_benchmarks) == 1: - opposite_benchmark = opposite_benchmarks[0] - st.subheader(f"{benchmark['name']} ({benchmark['uuid']})") - add_metrics(st.container(), benchmark, opposite_benchmark) - st.subheader(f"{opposite_benchmark['name']} ({opposite_benchmark['uuid']})") - add_metrics(st.container(), opposite_benchmark, benchmark) + if not df.empty: + st.dataframe( + df.style.highlight_min( + axis=0, subset=["write", "read", "update", "read+update"], props='background-color:green;' + ).highlight_max( + axis=0, subset=["write", "read", "update", "read+update"], props='background-color:red;' + ) + ) \ No newline at end of file From 32f5dd8c7307bf641b61c687fb547ea5aaed55e4 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Thu, 6 Jul 2023 14:29:20 +0300 Subject: [PATCH 058/113] add doc & update tutorial --- dff/utils/benchmark/context_storage.py | 250 +++++++++++++++--- docs/source/conf.py | 1 + setup.py | 1 + .../context_storages/8_db_benchmarking.py | 113 +++++--- 4 files changed, 293 insertions(+), 72 deletions(-) diff --git a/dff/utils/benchmark/context_storage.py b/dff/utils/benchmark/context_storage.py index 91fa8e5ea..118fdc267 100644 --- a/dff/utils/benchmark/context_storage.py +++ b/dff/utils/benchmark/context_storage.py @@ -3,16 +3,14 @@ ---------------------------- This module contains functions for context storages benchmarking. -Basic usage:: +The basic function is :py:func:`~.time_context_read_write` but it has a low level interface. +Higher level wrappers of the function provided by this module are: - from dff.utils.benchmark.context_storage import report - from dff.context_storages import context_storage_factory - - storage = context_storage_factory("postgresql+asyncpg://postgres:pass@localhost:5432/test", table_name="benchmark") - - report(storage) +- :py:func:`~.save_results_to_file` and :py:func:`~.benchmark_all` are used to save benchmark results to a file. +- :py:func:`~.report` is used to print results to stdout. +Wrappers use :py:class:`~.BenchmarkConfig` to configure benchmarks. """ from uuid import uuid4 import pathlib @@ -34,20 +32,19 @@ def get_dict(dimensions: tp.Tuple[int, ...]): """ - Misc dictionary build in `dimensions` dimensions. + Return misc dictionary build in `dimensions` dimensions. :param dimensions: Dimensions of the dictionary. Each element of the dimensions tuple is the number of keys on the corresponding level of the dictionary. - The last element of the dimensions tuple is the length of the str values of the dict. + The last element of the dimensions tuple is the length of the string values of the dict. - e.g. dimensions=(1, 2) produces a dictionary with 1 key that points to a string of len 2. - whereas dimensions=(1, 2, 3) produces a dictionary with 1 key that points to a dictionary + e.g. dimensions=(1, 2) returns a dictionary with 1 key that points to a string of len 2. + whereas dimensions=(1, 2, 3) returns a dictionary with 1 key that points to a dictionary with 2 keys each of which points to a string of len 3. So, the len of dimensions is the depth of the dictionary, while its values are the width of the dictionary at each level. - :return: Misc dictionary. """ def _get_dict(dimensions: tp.Tuple[int, ...]): if len(dimensions) < 2: @@ -64,19 +61,27 @@ def _get_dict(dimensions: tp.Tuple[int, ...]): def get_message(message_dimensions: tp.Tuple[int, ...]): """ - Message with misc field of message_dimensions dimension. - :param message_dimensions: - :return: + Return message with a non-empty misc field. + + :param message_dimensions: Dimensions of the misc field of the message. See :py:func:`~.get_dict`. """ return Message(misc=get_dict(message_dimensions)) -def get_context(dialog_len: int, message_dimensions: tp.Tuple[int, ...], misc_dimensions: tp.Tuple[int, ...]) -> Context: - """ - A context with a given number of dialog turns, a given message dimension - and a given misc dimension. +def get_context( + dialog_len: int, + message_dimensions: tp.Tuple[int, ...], + misc_dimensions: tp.Tuple[int, ...], +) -> Context: """ + Return context with a non-empty misc, labels, requests, responses fields. + :param dialog_len: Number of labels, requests and responses. + :param message_dimensions: + A parameter used to generate messages for requests and responses. See :py:func:`~.get_message`. + :param misc_dimensions: + A parameter used to generate misc field. See :py:func:`~.get_dict`. + """ return Context( labels={i: (f"flow_{i}", f"node_{i}") for i in range(dialog_len)}, requests={i: get_message(message_dimensions) for i in range(dialog_len)}, @@ -92,26 +97,42 @@ def time_context_read_write( context_updater=None, ) -> tp.Tuple[tp.List[float], tp.List[tp.Dict[int, float]], tp.List[tp.Dict[int, float]]]: """ - Generate `context_num` ids and for each write into `context_storage` value of `context` under generated id, - after that read the value stored in `context_storage` under generated id and compare it to `context`. - - Keep track of the time it takes to write and read context to/from the context storage. + Benchmark "context_storage" by writing and reading `context` into it / from it `context_num` times. + If context_updater is not None it is used to update `context` and use it to benchmark updating contexts + (as well as reading updated contexts). - This function clear context storage before and after execution. + This function clears "context_storage" before and after execution. :param context_storage: Context storage to benchmark. :param context: An instance of context which will be repeatedly written into context storage. - :param context_num: A number of times the context will be written and checked. + :param context_num: A number of times the context will be written and read. :param context_updater: + None or a function. + If not None, function should accept :py:class:`~.Context` and return an updated :py:class:`~.Context`. + The updated context can be either the same object (at the same pointer) or a different object (e.g. copied). + The updated context should have a higher dialog length than the received context + (to emulate context updating during dialog). + The function should return `None` to stop updating contexts. + For an example of such function, see :py:meth:`~.BenchmarkConfig.get_context_updater`. + + To avoid keeping many contexts in memory, + this function will be called with every argument `context_num` times (for each cycle), so it should + return the same updated context for the same context and shouldn't take a long time to update a context. :return: - Depends on `as_dataframe` parameter. - 1. By default, it is set to None in which case it returns: - two lists: first one contains individual write times, second one contains individual read times. - 2. If set to "pandas": - A pandas DataFrame with two columns: "write" and "read" which contain corresponding data series. - 3. If set to "polars": - A polars DataFrame with the same columns as in a pandas DataFrame. - :raises RuntimeError: If context written into context storage does not match read context. + A tuple of 3 elements. + + The first element -- a list of write times. Its length is equal to `context_num`. + + The second element -- a list of dictionaries with read times. + Each dictionary maps from int to float. The key in the mapping is the `dialog_len` of the context and the + values are the read times for the corresponding `dialog_len`. + If `context_updater` is None, all dictionaries will have only one key -- dialog length of `context`. + Otherwise, the dictionaries will also have a key for each updated context. + + The third element -- a list of dictionaries with update times. + Structurally the same as the second element, but none of the elements here have a key for + dialog_len of the `context`. + So if `context_updater` is None, all dictionaries will be empty. """ context_storage.clear() @@ -119,7 +140,7 @@ def time_context_read_write( read_times: tp.List[tp.Dict[int, float]] = [] update_times: tp.List[tp.Dict[int, float]] = [] - for _ in tqdm(range(context_num), desc=f"Benchmarking context storage:{context_storage.full_path}"): + for _ in tqdm(range(context_num), desc=f"Benchmarking context storage:{context_storage.full_path}", leave=False): tmp_context = deepcopy(context) ctx_id = uuid4() @@ -160,30 +181,82 @@ def time_context_read_write( class DBFactory(BaseModel): + """ + A class for storing information about context storage to benchmark. + Also used to create a context storage from the configuration. + """ uri: str + """URI of the context storage.""" factory_module: str = "dff.context_storages" + """A module containing `factory`.""" factory: str = "context_storage_factory" + """Name of the context storage factory. (function that creates context storages from URIs)""" def db(self): + """ + Create a context storage using `factory` from `uri`. + """ module = importlib.import_module(self.factory_module) return getattr(module, self.factory)(self.uri) class BenchmarkConfig(BaseModel): + """ + Configuration for a benchmark. Sets dialog len, misc sizes, number of benchmarks. + """ context_num: int = 100 + """ + Number of times the contexts will be benchmarked. + Increasing this number decreases standard error of the mean for benchmarked data. + """ from_dialog_len: int = 300 + """Starting dialog len of a context.""" to_dialog_len: int = 311 + """ + Final dialog len of a context. + :py:meth:`~.BenchmarkConfig.get_context_updater` will return contexts + until their dialog len is less then `to_dialog_len`. + """ step_dialog_len: int = 1 + """ + Increment step for dialog len. + :py:meth:`~.BenchmarkConfig.get_context_updater` will return contexts + increasing dialog len by `step_dialog_len`. + """ message_dimensions: tp.Tuple[int, ...] = (10, 10) + """ + Dimensions of misc dictionaries inside messages. + See :py:func:`~.get_message`. + """ misc_dimensions: tp.Tuple[int, ...] = (10, 10) + """ + Dimensions of misc dictionary. + See :py:func:`~.get_dict`. + """ class Config: allow_mutation = False def get_context(self): + """ + Return context with `from_dialog_len`, `message_dimensions`, `misc_dimensions`. + + Wraps :py:func:`~.get_context`. + """ return get_context(self.from_dialog_len, self.message_dimensions, self.misc_dimensions) def sizes(self): + """ + Return sizes of objects defined by this config. + + :return: + A dictionary with 4 elements: + - "starting_context_size" -- size of a context with `from_dialog_len`. + - "final_context_size" -- size of a context with `to_dialog_len`. + A context of this size will never actually be benchmarked. + - "misc_size" -- size of a misc field of a context. + - "message_size" -- size of a misc field of a message. + """ return { "starting_context_size": asizeof.asizeof( self.get_context() @@ -196,8 +269,17 @@ def sizes(self): } def get_context_updater(self): + """ + Return context updater function based on configuration. + + :return: + A function that accepts a context, modifies it and returns it. + The updated context has `step_dialog_len` more labels, requests and responses, + unless such dialog len would be equal to `to_dialog_len` or exceed than it, + in which case None is returned. + """ def _context_updater(context: Context): - start_len = len(context.requests) + start_len = len(context.labels) if start_len + self.step_dialog_len < self.to_dialog_len: for i in range(start_len, start_len + self.step_dialog_len): context.add_label((f"flow_{i}", f"node_{i}")) @@ -211,14 +293,52 @@ def _context_updater(context: Context): class BenchmarkCase(BaseModel): + """ + This class represents a benchmark case and includes + information about it, its configuration and configuration of a context storage to benchmark. + """ name: str + """Name of a benchmark case.""" db_factory: DBFactory + """DBFactory that specifies context storage to benchmark.""" benchmark_config: BenchmarkConfig = BenchmarkConfig() + """Benchmark configuration.""" uuid: str = Field(default_factory=lambda: str(uuid4())) + """Unique id of the case. Defaults to a random uuid.""" description: str = "" + """Description of the case. Defaults to an empty string.""" @staticmethod def set_average_results(benchmark): + """ + Modify `benchmark` dictionary to include averaged benchmark results. + + Add field "average_results" to the benchmark that contains the following fields: + + - average_write_time + - average_read_time + - average_update_time + - read_times_grouped_by_context_num -- a list of read times. + Each element is the average of read times with the same context_num. + - read_times_grouped_by_dialog_len -- a dictionary of read times. + Its values are the averages of read times with the same dialog_len, + its keys are dialog_len values. + - update_times_grouped_by_context_num + - update_times_grouped_by_dialog_len + - pretty_write -- average write time with only 3 significant digits. + - pretty_read + - pretty_update + - pretty_read+update -- sum of average read and update times with only 3 significant digits. + + :param benchmark: + A dictionary returned by `BenchmarkCase._run`. + Should include a "success" and "result" fields. + "success" field should be true. + "result" field should be a dictionary with the values returned by + :py:func:`~.time_context_read_write` and keys + "write_times", "read_times" and "update_times". + :return: None + """ if not benchmark["success"] or isinstance(benchmark["result"], str): return @@ -281,6 +401,20 @@ def _run(self): } def run(self): + """ + Run benchmark, return results. + + :return: + A dictionary with 3 keys: "success", "result", "average_results". + + Success is a bool value. It is false if an exception was raised during benchmarking. + + Result is either an exception message or a dictionary with 3 keys + ("write_times", "read_times", "update_times"). + Values of those fields are the values returned by :py:func:`~.time_context_read_write`. + + Average results field is as described in :py:meth:`~.BenchmarkCase.set_average_results`. + """ benchmark = self._run() BenchmarkCase.set_average_results(benchmark) return benchmark @@ -293,6 +427,28 @@ def save_results_to_file( description: str, exist_ok: bool = False, ): + """ + Benchmark all `benchmark_cases` and save results to a file. + + Result are saved in json format with this schema (click to expand): + + .. collapse:: utils/db_benchmark/benchmark_schema.json + + .. literalinclude:: ../../../utils/db_benchmark/benchmark_schema.json + + + Files created by this function cen be viewed with the streamlit app located in the same directory: + + .. collapse:: utils/db_benchmark/benchmark_streamlit.py + + .. literalinclude:: ../../../utils/db_benchmark/benchmark_streamlit.py + + :param benchmark_cases: A list of benchmark cases that specify benchmarks. + :param file: File to save results to. + :param name: Name of the benchmark set. + :param description: Description of the benchmark set. + :param exist_ok: Whether to continue if the file already exists. + """ with open(file, "w" if exist_ok else "x", encoding="utf-8") as fd: uuid = str(uuid4()) result: tp.Dict[str, tp.Any] = { @@ -315,6 +471,20 @@ def benchmark_all( benchmark_config: BenchmarkConfig = BenchmarkConfig(), exist_ok: bool = False, ): + """ + A wrapper for :py:func:`~.save_results_to_file`. + + Generates `benchmark_cases` from `db_uris` and `benchmark_config`: + URIs inside `db_uris` dictionary are used to initialize :py:class:`~.DBFactory` instances + which are then used along with `benchmark_config` to initialize :py:class:`~.BenchmarkCase` instances. + + :param file: File to save results to. + :param name: Name of the benchmark set. + :param description: Description of the benchmark set. The same description is used for benchmark cases. + :param db_uris: A mapping from DB names to DB URIs. The names are used as names for benchmark cases. + :param benchmark_config: A benchmark config to use in all benchmark cases. + :param exist_ok: Whether to continue if the file already exists. + """ save_results_to_file( [ BenchmarkCase( @@ -336,6 +506,15 @@ def report( db_uris: tp.Dict[str, str], benchmark_config: BenchmarkConfig = BenchmarkConfig(), ): + """ + Benchmark DBs with a config and print results to stdout. + + Printed stats contain benchmark config, object sizes, average benchmark values for successful cases and + exception message for unsuccessful cases. + + :param db_uris: A mapping from DB names to DB uris. DB names are used as names for benchmark cases. + :param benchmark_config: Benchmark config to use in all benchmark cases. + """ benchmark_cases = [ BenchmarkCase( name=db_name, @@ -363,7 +542,6 @@ def report( f"Final context size: {final_context_size} ({naturalsize(final_context_size, gnu=True)})" ) - # define functions for displaying results line_separator = "-" * 80 print(f"Starting benchmarking with following parameters:\n{benchmark_config_report}") diff --git a/docs/source/conf.py b/docs/source/conf.py index 52e87582d..ed86add16 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -36,6 +36,7 @@ "sphinx.ext.viewcode", "sphinx.ext.mathjax", "sphinx.ext.extlinks", + "sphinx_toolbox.collapse", "sphinxcontrib.katex", "sphinx_copybutton", "sphinx_favicon", diff --git a/setup.py b/setup.py index 275ab1e75..fc2253888 100644 --- a/setup.py +++ b/setup.py @@ -150,6 +150,7 @@ def merge_req_lists(*req_lists: List[str]) -> List[str]: "nbsphinx==0.9.1", "jupytext==1.14.5", "jupyter==1.0.0", + "sphinx-toolbox==3.4.0", ], requests_requirements, ) diff --git a/tutorials/context_storages/8_db_benchmarking.py b/tutorials/context_storages/8_db_benchmarking.py index c37954698..c271c0214 100644 --- a/tutorials/context_storages/8_db_benchmarking.py +++ b/tutorials/context_storages/8_db_benchmarking.py @@ -3,15 +3,18 @@ # 8. Context storage benchmarking This tutorial shows how to benchmark context storages. + +For more info see [API reference](../apiref/dff.utils.benchmark.context_storage.rst). """ # %% import pathlib from platform import system +import tempfile from dff.context_storages import context_storage_factory -from dff.utils.benchmark.context_storage import report +import dff.utils.benchmark.context_storage as benchmark # %% [markdown] """ @@ -20,69 +23,107 @@ # %% # this cell is only required for pickle, shelve and sqlite databases -pathlib.Path("dbs").mkdir(exist_ok=True) -sqlite_file = pathlib.Path("dbs/sqlite.db") +tutorial_dir = pathlib.Path(tempfile.mkdtemp()) +db_path = tutorial_dir / "dbs" +db_path.mkdir() +sqlite_file = db_path / "sqlite.db" sqlite_file.touch(exist_ok=True) sqlite_separator = "///" if system() == "Windows" else "////" # %% -storages = list( - map( - context_storage_factory, - [ - "json://dbs/json.json", - "pickle://dbs/pickle.pkl", - "shelve://dbs/shelve", - "postgresql+asyncpg://postgres:pass@localhost:5432/test", - "mongodb://admin:pass@localhost:27017/admin", - "redis://:pass@localhost:6379/0", - "mysql+asyncmy://root:pass@localhost:3307/test", - f"sqlite+aiosqlite:{sqlite_separator}{sqlite_file.absolute()}", - "grpc://localhost:2136/local", - ], - ) -) +storages = { + "JSON": f"json://{db_path}/json.json", + "Pickle": f"pickle://{db_path}/pickle.pkl", + "Shelve": f"shelve://{db_path}/shelve", + "PostgreSQL": "postgresql+asyncpg://postgres:pass@localhost:5432/test", + "MongoDB": "mongodb://admin:pass@localhost:27017/admin", + "Redis": "redis://:pass@localhost:6379/0", + "MySQL": "mysql+asyncmy://root:pass@localhost:3307/test", + "SQLite": f"sqlite+aiosqlite:{sqlite_separator}{sqlite_file.absolute()}", + "YDB": "grpc://localhost:2136/local", +} # %% [markdown] """ ## Generating a report The report will print a size of one context and stats for the context storage: -an average write time and an average write time. +average write, read, update times. Note: context storage passed into the `report` function will be cleared. -Setting `context_num` to 100 means that we'll run a hundred cycles of writing and reading context. +Setting `context_num` to 50 means that we'll run fifty cycles of writing and reading context. This way we'll be able to get a more accurate average read/write time as well as check if read/write times are dependent on the number of contexts in the storage. -You can also set the `dialog_len` and `misc_len` parameters. Those affect the size of a context. -An approximate formula is `size=1000 * dialog_len + 100 * misc_len bytes` although the report -automatically calculates the size of a context. +You can also configure the `dialog_len`, `message_dimensions` and `misc_dimensions` parameters. +This allows you to set the context you want the benchmarks to run with. -Here we set `dialog_len` to 1 and `misc_len` to 0 (by default) in order for reports to -generate faster. +For more info on each configuration parameter see [BenchmarkConfig]( +../apiref/dff.utils.benchmark.context_storage.rst#dff.utils.benchmark.context_storage.BenchmarkConfig +). """ # %% -report(context_storage_factory("json://dbs/json.json"), context_num=100, dialog_len=1) +benchmark.report( + storages, + benchmark_config=benchmark.BenchmarkConfig( + context_num=50, + from_dialog_len=1, + to_dialog_len=5, + message_dimensions=(3, 10), + misc_dimensions=(3, 10), + ) +) # %% [markdown] """ -You can pass multiple context storages to get average read/write times for each -as well as a comparison of all the passed storages (in the form of ordered lists). +## Saving benchmark results to a file + +Instead of printing the results into console, you can save them to a file. + +For that there exist two functions: +[benchmark_all]( +../apiref/dff.utils.benchmark.context_storage.rst#dff.utils.benchmark.context_storage.benchmark_all +) +and +[save_results_to_file]( +../apiref/dff.utils.benchmark.context_storage.rst#dff.utils.benchmark.context_storage.save_results_to_file +). + +The first one is a higher-level wrapper of the second one. + +The files are saved according to this [schema](../../../utils/db_benchmark/benchmark_schema.json). + +The schema can also be found on [github]( +https://github.com/deeppavlov/dialog_flow_framework/blob/dev/utils/db_benchmark/benchmark_schema.json +). """ # %% -report(*storages, context_num=100, dialog_len=1) +benchmark.benchmark_all( + file=tutorial_dir / "results.json", + name="Tutorial benchmark", + description="Benchmark for tutorial", + db_uris=storages, + benchmark_config=benchmark.BenchmarkConfig( + context_num=50, + from_dialog_len=1, + to_dialog_len=5, + message_dimensions=(3, 10), + misc_dimensions=(3, 10), + ) +) # %% [markdown] """ -You can also generate a pdf report which additionally includes -plots of write and read times for each storage. +## Viewing benchmark results -Generating pdf reports requires the `matplotlib` package. -""" +Now that the results are saved to a file you can either view them manually or using [our streamlit app]( +../../../utils/db_benchmark/benchmark_streamlit.py +). -# %% -report(*storages, context_num=100, dialog_len=1, pdf="report.pdf") +The app can also be found on [github]( +https://github.com/deeppavlov/dialog_flow_framework/blob/dev/utils/db_benchmark/benchmark_streamlit.py +). +""" From d9a161d8bc06775239accd4cd2f92bf2af025b43 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Thu, 6 Jul 2023 14:39:06 +0300 Subject: [PATCH 059/113] move benchmark configs to utils --- benchmark_dbs.py => utils/db_benchmark/benchmark_dbs.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename benchmark_dbs.py => utils/db_benchmark/benchmark_dbs.py (100%) diff --git a/benchmark_dbs.py b/utils/db_benchmark/benchmark_dbs.py similarity index 100% rename from benchmark_dbs.py rename to utils/db_benchmark/benchmark_dbs.py From f52931a40a5b489281af82249f8af3c664b7b7cd Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Thu, 6 Jul 2023 14:44:17 +0300 Subject: [PATCH 060/113] remove partial from benchmark_dbs.py --- utils/db_benchmark/benchmark_dbs.py | 132 ++++++++++------------------ 1 file changed, 47 insertions(+), 85 deletions(-) diff --git a/utils/db_benchmark/benchmark_dbs.py b/utils/db_benchmark/benchmark_dbs.py index 4b8aa6d41..d390858d7 100644 --- a/utils/db_benchmark/benchmark_dbs.py +++ b/utils/db_benchmark/benchmark_dbs.py @@ -1,59 +1,12 @@ +""" +Benchmark DBs +------------- +This module contains config presets for benchmarks. +""" import pathlib -import typing as tp from platform import system -from dff.utils.benchmark.context_storage import save_results_to_file, BenchmarkCase, DBFactory, BenchmarkConfig - - -# partial-specific logic -def get_cases( - db_uris: tp.Dict[str, str], - case_name_postfix: str = "", - benchmark_config: BenchmarkConfig = BenchmarkConfig(), - description: str = "", -): - benchmark_cases = [] - for db, uri in db_uris.items(): - benchmark_cases.append( - BenchmarkCase( - name=db + "-dev" + case_name_postfix, - db_factory=DBFactory(uri=uri, factory_module="dff.context_storages_old"), - benchmark_config=benchmark_config, - description=description, - ) - ) - benchmark_cases.append( - BenchmarkCase( - name=db + "-partial" + case_name_postfix, - db_factory=DBFactory(uri=uri), - benchmark_config=benchmark_config, - description=description, - ) - ) - return benchmark_cases - - -def benchmark_all( - file: tp.Union[str, pathlib.Path], - name: str, - description: str, - db_uris: tp.Dict[str, str], - case_name_postfix: str = "", - benchmark_config: BenchmarkConfig = BenchmarkConfig(), - exist_ok: bool = False, -): - save_results_to_file( - get_cases( - db_uris, - case_name_postfix, - benchmark_config=benchmark_config, - description=description, - ), - file, - name, - description, - exist_ok=exist_ok - ) +from dff.utils.benchmark.context_storage import save_results_to_file, BenchmarkConfig, benchmark_all, BenchmarkCase, DBFactory # create dir and files @@ -127,38 +80,47 @@ def benchmark_all( save_results_to_file( [ - *get_cases( - db_uris=dbs, - case_name_postfix="-long-dialog-len", - benchmark_config=BenchmarkConfig( - context_num=10, - from_dialog_len=10000, - to_dialog_len=10050, - ), - description="Benchmark with very long dialog len." - ), - *get_cases( - db_uris=dbs, - case_name_postfix="-long-message-len", - benchmark_config=BenchmarkConfig( - context_num=10, - from_dialog_len=1, - to_dialog_len=3, - message_dimensions=(10000, 1), - ), - description="Benchmark with messages containing many keys." - ), - *get_cases( - db_uris=dbs, - case_name_postfix="-long-misc-len", - benchmark_config=BenchmarkConfig( - context_num=10, - from_dialog_len=1, - to_dialog_len=3, - misc_dimensions=(10000, 1), - ), - description="Benchmark with misc containing many keys." - ), + *[ + BenchmarkCase( + db_factory=DBFactory(uri=uri), + name=name+"-long-dialog-len", + benchmark_config=BenchmarkConfig( + context_num=10, + from_dialog_len=10000, + to_dialog_len=10050, + ), + description="Benchmark with very long dialog len." + ) + for name, uri in dbs.items() + ], + *[ + BenchmarkCase( + db_factory=DBFactory(uri=uri), + name=name+"-long-message-len", + benchmark_config=BenchmarkConfig( + context_num=10, + from_dialog_len=1, + to_dialog_len=3, + message_dimensions=(10000, 1), + ), + description="Benchmark with messages containing many keys." + ) + for name, uri in dbs.items() + ], + *[ + BenchmarkCase( + db_factory=DBFactory(uri=uri), + name=name+"-long-misc-len", + benchmark_config=BenchmarkConfig( + context_num=10, + from_dialog_len=1, + to_dialog_len=3, + misc_dimensions=(10000, 1), + ), + description="Benchmark with misc containing many keys." + ) + for name, uri in dbs.items() + ], ], file=benchmark_dir / "extremes.json", name="Extreme", From 41ea85c681c1790d7e1e592b9e700665351406f4 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Thu, 6 Jul 2023 14:44:36 +0300 Subject: [PATCH 061/113] remove format updater for benchmark files --- benchmark_new_format.py | 78 ----------------------------------------- 1 file changed, 78 deletions(-) delete mode 100644 benchmark_new_format.py diff --git a/benchmark_new_format.py b/benchmark_new_format.py deleted file mode 100644 index b54c22bd8..000000000 --- a/benchmark_new_format.py +++ /dev/null @@ -1,78 +0,0 @@ -import json -import pathlib - -from dff.utils.benchmark.context_storage import BenchmarkCase - -benchmark_path = pathlib.Path("benchmarks") - -for file in benchmark_path.iterdir(): - if file.suffix == ".json": - with open(file, "r") as fd: - benchmark_set = json.load(fd) - - non_benchmark_fields = ("name", "description", "uuid", "benchmarks") - - new_benchmark_set = {k: v for k, v in benchmark_set.items() if k in non_benchmark_fields} - - if "benchmarks" not in benchmark_set: - new_benchmark_set["benchmarks"] = {k: v for k, v in benchmark_set.items() if k not in non_benchmark_fields} - - for key, benchmark in new_benchmark_set["benchmarks"].items(): - # update old factory specification - if benchmark["db_factory"].get("base_factory") == "dev": - postfix = "_old" - elif benchmark["db_factory"].get("base_factory") == "partial": - postfix = "" - else: - postfix = None - - if postfix is not None: - benchmark["db_factory"].pop("base_factory", None) - benchmark["db_factory"].update( - { - "factory_module": "dff.context_storages" + postfix, - "factory": "context_storage_factory", - } - ) - - # update average calculation - for benchmark in new_benchmark_set["benchmarks"].values(): - BenchmarkCase.set_average_results(benchmark) - - # update lengths -> dimensions renaming - for benchmark in new_benchmark_set["benchmarks"].values(): - for param in ("message", "misc"): - dimensions = benchmark.pop(f"{param}_lengths", None) - if dimensions is not None: - benchmark[f"{param}_dimensions"] = dimensions - - # update benchmark_config - for benchmark in new_benchmark_set["benchmarks"].values(): - if "benchmark_config" not in benchmark: - benchmark_config = { - "context_num": benchmark.pop("context_num"), - "from_dialog_len": benchmark.pop("from_dialog_len"), - "to_dialog_len": benchmark.pop("to_dialog_len"), - "step_dialog_len": benchmark.pop("step_dialog_len"), - "message_dimensions": benchmark.pop("message_dimensions"), - "misc_dimensions": benchmark.pop("misc_dimensions"), - } - - benchmark["benchmark_config"] = benchmark_config - - # update sizes - for benchmark in new_benchmark_set["benchmarks"].values(): - if "sizes" not in benchmark: - sizes = { - key: benchmark.pop(key) for key in ( - "starting_context_size", "final_context_size", "misc_size", "message_size" - ) - } - - benchmark["sizes"] = sizes - - benchmarks = new_benchmark_set.pop("benchmarks") - new_benchmark_set["benchmarks"] = list(benchmarks.values()) - - with open(file, "w") as fd: - json.dump(new_benchmark_set, fd) From 465f41bcb003838540eeb7894a5fa0788ce7db8b Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Thu, 6 Jul 2023 14:56:44 +0300 Subject: [PATCH 062/113] fix mass compare bug --- utils/db_benchmark/benchmark_streamlit.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/utils/db_benchmark/benchmark_streamlit.py b/utils/db_benchmark/benchmark_streamlit.py index a1f89ad11..d66027796 100644 --- a/utils/db_benchmark/benchmark_streamlit.py +++ b/utils/db_benchmark/benchmark_streamlit.py @@ -314,15 +314,16 @@ def add_results_to_compare_tab(): compare_items = [] for selected_benchmark in selected_set["benchmarks"]: - compare_items.append( - { - "benchmark": f"{selected_benchmark['name']} ({selected_benchmark['uuid']})", - "write": selected_benchmark["average_results"]["pretty_write"], - "read": selected_benchmark["average_results"]["pretty_read"], - "update": selected_benchmark["average_results"]["pretty_update"], - "read+update": selected_benchmark["average_results"]["pretty_read+update"], - } - ) + if selected_benchmark["success"]: + compare_items.append( + { + "benchmark": f"{selected_benchmark['name']} ({selected_benchmark['uuid']})", + "write": selected_benchmark["average_results"]["pretty_write"], + "read": selected_benchmark["average_results"]["pretty_read"], + "update": selected_benchmark["average_results"]["pretty_update"], + "read+update": selected_benchmark["average_results"]["pretty_read+update"], + } + ) df = pd.DataFrame(compare_items) From 958db10b92a5fa8267b1783ad0fc33ffe6709c90 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Thu, 6 Jul 2023 14:57:14 +0300 Subject: [PATCH 063/113] lint & format --- dff/utils/benchmark/context_storage.py | 46 +++++++------ tests/utils/test_benchmark.py | 42 ++++++------ .../context_storages/8_db_benchmarking.py | 9 ++- utils/db_benchmark/benchmark_dbs.py | 28 ++++---- utils/db_benchmark/benchmark_streamlit.py | 65 +++++++++---------- 5 files changed, 97 insertions(+), 93 deletions(-) diff --git a/dff/utils/benchmark/context_storage.py b/dff/utils/benchmark/context_storage.py index 118fdc267..7be2d46fe 100644 --- a/dff/utils/benchmark/context_storage.py +++ b/dff/utils/benchmark/context_storage.py @@ -46,6 +46,7 @@ def get_dict(dimensions: tp.Tuple[int, ...]): So, the len of dimensions is the depth of the dictionary, while its values are the width of the dictionary at each level. """ + def _get_dict(dimensions: tp.Tuple[int, ...]): if len(dimensions) < 2: return "." * dimensions[0] @@ -160,7 +161,6 @@ def time_context_read_write( read_times[-1][len(tmp_context.labels)] = read_time if context_updater is not None: - tmp_context = context_updater(tmp_context) while tmp_context is not None: @@ -185,6 +185,7 @@ class DBFactory(BaseModel): A class for storing information about context storage to benchmark. Also used to create a context storage from the configuration. """ + uri: str """URI of the context storage.""" factory_module: str = "dff.context_storages" @@ -204,6 +205,7 @@ class BenchmarkConfig(BaseModel): """ Configuration for a benchmark. Sets dialog len, misc sizes, number of benchmarks. """ + context_num: int = 100 """ Number of times the contexts will be benchmarked. @@ -258,9 +260,7 @@ def sizes(self): - "message_size" -- size of a misc field of a message. """ return { - "starting_context_size": asizeof.asizeof( - self.get_context() - ), + "starting_context_size": asizeof.asizeof(self.get_context()), "final_context_size": asizeof.asizeof( get_context(self.to_dialog_len, self.message_dimensions, self.misc_dimensions) ), @@ -278,6 +278,7 @@ def get_context_updater(self): unless such dialog len would be equal to `to_dialog_len` or exceed than it, in which case None is returned. """ + def _context_updater(context: Context): start_len = len(context.labels) if start_len + self.step_dialog_len < self.to_dialog_len: @@ -297,6 +298,7 @@ class BenchmarkCase(BaseModel): This class represents a benchmark case and includes information about it, its configuration and configuration of a context storage to benchmark. """ + name: str """Name of a benchmark case.""" db_factory: DBFactory @@ -347,9 +349,7 @@ def get_complex_stats(results): return [], {}, None average_grouped_by_context_num = [mean(times.values()) for times in results] - average_grouped_by_dialog_len = { - key: mean([times[key] for times in results]) for key in results[0].keys() - } + average_grouped_by_dialog_len = {key: mean([times[key] for times in results]) for key in results[0].keys()} average = float(mean(average_grouped_by_context_num)) return average_grouped_by_context_num, average_grouped_by_dialog_len, average @@ -365,14 +365,20 @@ def get_complex_stats(results): "update_times_grouped_by_context_num": update_stats[0], "update_times_grouped_by_dialog_len": update_stats[1], } - result["pretty_write"] = float(f'{result["average_write_time"]:.3}')\ - if result["average_write_time"] is not None else None - result["pretty_read"] = float(f'{result["average_read_time"]:.3}')\ - if result["average_read_time"] is not None else None - result["pretty_update"] = float(f'{result["average_update_time"]:.3}')\ - if result["average_update_time"] is not None else None - result["pretty_read+update"] = float(f'{result["average_read_time"] + result["average_update_time"]:.3}')\ - if result["average_read_time"] is not None and result["average_update_time"] is not None else None + result["pretty_write"] = ( + float(f'{result["average_write_time"]:.3}') if result["average_write_time"] is not None else None + ) + result["pretty_read"] = ( + float(f'{result["average_read_time"]:.3}') if result["average_read_time"] is not None else None + ) + result["pretty_update"] = ( + float(f'{result["average_update_time"]:.3}') if result["average_update_time"] is not None else None + ) + result["pretty_read+update"] = ( + float(f'{result["average_read_time"] + result["average_update_time"]:.3}') + if result["average_read_time"] is not None and result["average_update_time"] is not None + else None + ) benchmark["average_results"] = result @@ -382,7 +388,7 @@ def _run(self): self.db_factory.db(), self.benchmark_config.get_context(), self.benchmark_config.context_num, - context_updater=self.benchmark_config.get_context_updater() + context_updater=self.benchmark_config.get_context_updater(), ) return { "success": True, @@ -390,7 +396,7 @@ def _run(self): "write_times": write_times, "read_times": read_times, "update_times": update_times, - } + }, } except Exception as e: exception_message = getattr(e, "message", repr(e)) @@ -498,7 +504,7 @@ def benchmark_all( file, name, description, - exist_ok=exist_ok + exist_ok=exist_ok, ) @@ -556,8 +562,8 @@ def report( "".join( [ f"{metric.title() + ': ' + str(result['average_results']['pretty_' + metric]):20}" - if result["success"] else - result["result"] + if result["success"] + else result["result"] for metric in ("write", "read", "update", "read+update") ] ), diff --git a/tests/utils/test_benchmark.py b/tests/utils/test_benchmark.py index 659ae7387..a1e0b89c3 100644 --- a/tests/utils/test_benchmark.py +++ b/tests/utils/test_benchmark.py @@ -26,16 +26,13 @@ def test_get_context(): labels={0: ("flow_0", "node_0"), 1: ("flow_1", "node_1")}, requests={0: bm.Message(misc={0: ".."}), 1: bm.Message(misc={0: ".."})}, responses={0: bm.Message(misc={0: ".."}), 1: bm.Message(misc={0: ".."})}, - misc={0: "...", 1: "..."} + misc={0: "...", 1: "..."}, ) def test_benchmark_config(): config = bm.BenchmarkConfig( - from_dialog_len=1, - to_dialog_len=5, - message_dimensions=(2, 2), - misc_dimensions=(3, 3, 3) + from_dialog_len=1, to_dialog_len=5, message_dimensions=(2, 2), misc_dimensions=(3, 3, 3) ) context = config.get_context() actual_context = bm.get_context(1, (2, 2), (3, 3, 3)) @@ -67,11 +64,7 @@ def test_benchmark_config(): def test_context_updater_with_steps(): config = bm.BenchmarkConfig( - from_dialog_len=1, - to_dialog_len=11, - step_dialog_len=3, - message_dimensions=(2, 2), - misc_dimensions=(3, 3, 3) + from_dialog_len=1, to_dialog_len=11, step_dialog_len=3, message_dimensions=(2, 2), misc_dimensions=(3, 3, 3) ) context_updater = config.get_context_updater() @@ -114,14 +107,11 @@ def test_time_context_read_write(context_storage): to_dialog_len=11, step_dialog_len=3, message_dimensions=(2, 2), - misc_dimensions=(3, 3, 3) + misc_dimensions=(3, 3, 3), ) results = bm.time_context_read_write( - context_storage, - config.get_context(), - config.context_num, - config.get_context_updater() + context_storage, config.get_context(), config.context_num, config.get_context_updater() ) assert len(context_storage) == 0 @@ -150,7 +140,7 @@ def test_time_context_read_write_without_updates(context_storage): to_dialog_len=2, step_dialog_len=3, message_dimensions=(2, 2), - misc_dimensions=(3, 3, 3) + misc_dimensions=(3, 3, 3), ) results = bm.time_context_read_write( @@ -192,7 +182,7 @@ def test_average_results(): "write_times": [1, 2], "read_times": [{0: 3, 1: 4}, {0: 5, 1: 6}], "update_times": [{0: 7, 1: 8}, {0: 9, 1: 10}], - } + }, } bm.BenchmarkCase.set_average_results(benchmark) @@ -217,7 +207,7 @@ def test_average_results(): "write_times": [1, 2], "read_times": [{0: 3}, {0: 5}], "update_times": [{}, {}], - } + }, } bm.BenchmarkCase.set_average_results(benchmark) @@ -233,7 +223,7 @@ def test_benchmark_case(tmp_path): to_dialog_len=11, step_dialog_len=3, message_dimensions=(2, 2), - misc_dimensions=(3, 3, 3) + misc_dimensions=(3, 3, 3), ), ) @@ -266,8 +256,8 @@ def test_save_to_file(tmp_path): to_dialog_len=11, step_dialog_len=3, message_dimensions=(2, 2), - misc_dimensions=(3, 3, 3) - ) + misc_dimensions=(3, 3, 3), + ), ) with open(tmp_path / "result.json", "r", encoding="utf-8") as fd: @@ -278,7 +268,15 @@ def test_save_to_file(tmp_path): benchmark = tuple(benchmark_set["benchmarks"])[0] assert set(benchmark.keys()) == { - "name", "db_factory", "benchmark_config", "uuid", "description", "sizes", "success", "result", "average_results" + "name", + "db_factory", + "benchmark_config", + "uuid", + "description", + "sizes", + "success", + "result", + "average_results", } assert not benchmark_set["benchmarks"][1]["success"] diff --git a/tutorials/context_storages/8_db_benchmarking.py b/tutorials/context_storages/8_db_benchmarking.py index c271c0214..e9f4537d3 100644 --- a/tutorials/context_storages/8_db_benchmarking.py +++ b/tutorials/context_storages/8_db_benchmarking.py @@ -12,8 +12,6 @@ from platform import system import tempfile -from dff.context_storages import context_storage_factory - import dff.utils.benchmark.context_storage as benchmark # %% [markdown] @@ -73,7 +71,7 @@ to_dialog_len=5, message_dimensions=(3, 10), misc_dimensions=(3, 10), - ) + ), ) # %% [markdown] @@ -112,14 +110,15 @@ to_dialog_len=5, message_dimensions=(3, 10), misc_dimensions=(3, 10), - ) + ), ) # %% [markdown] """ ## Viewing benchmark results -Now that the results are saved to a file you can either view them manually or using [our streamlit app]( +Now that the results are saved to a file you can either view them manually or +use [our streamlit app]( ../../../utils/db_benchmark/benchmark_streamlit.py ). diff --git a/utils/db_benchmark/benchmark_dbs.py b/utils/db_benchmark/benchmark_dbs.py index d390858d7..75ac0713c 100644 --- a/utils/db_benchmark/benchmark_dbs.py +++ b/utils/db_benchmark/benchmark_dbs.py @@ -6,7 +6,13 @@ import pathlib from platform import system -from dff.utils.benchmark.context_storage import save_results_to_file, BenchmarkConfig, benchmark_all, BenchmarkCase, DBFactory +from dff.utils.benchmark.context_storage import ( + save_results_to_file, + BenchmarkConfig, + benchmark_all, + BenchmarkCase, + DBFactory, +) # create dir and files @@ -42,7 +48,7 @@ to_dialog_len=50, message_dimensions=(3, 5, 6, 5, 3), misc_dimensions=(2, 4, 3, 8, 100), - ) + ), ) benchmark_all( @@ -55,7 +61,7 @@ to_dialog_len=550, message_dimensions=(2, 30), misc_dimensions=(0, 0), - ) + ), ) benchmark_all( @@ -75,7 +81,7 @@ to_dialog_len=550, message_dimensions=(3, 5, 6, 5, 3), misc_dimensions=(2, 4, 3, 8, 100), - ) + ), ) save_results_to_file( @@ -83,46 +89,46 @@ *[ BenchmarkCase( db_factory=DBFactory(uri=uri), - name=name+"-long-dialog-len", + name=name + "-long-dialog-len", benchmark_config=BenchmarkConfig( context_num=10, from_dialog_len=10000, to_dialog_len=10050, ), - description="Benchmark with very long dialog len." + description="Benchmark with very long dialog len.", ) for name, uri in dbs.items() ], *[ BenchmarkCase( db_factory=DBFactory(uri=uri), - name=name+"-long-message-len", + name=name + "-long-message-len", benchmark_config=BenchmarkConfig( context_num=10, from_dialog_len=1, to_dialog_len=3, message_dimensions=(10000, 1), ), - description="Benchmark with messages containing many keys." + description="Benchmark with messages containing many keys.", ) for name, uri in dbs.items() ], *[ BenchmarkCase( db_factory=DBFactory(uri=uri), - name=name+"-long-misc-len", + name=name + "-long-misc-len", benchmark_config=BenchmarkConfig( context_num=10, from_dialog_len=1, to_dialog_len=3, misc_dimensions=(10000, 1), ), - description="Benchmark with misc containing many keys." + description="Benchmark with misc containing many keys.", ) for name, uri in dbs.items() ], ], file=benchmark_dir / "extremes.json", name="Extreme", - description="Set of benchmarks testing extreme cases." + description="Set of benchmarks testing extreme cases.", ) diff --git a/utils/db_benchmark/benchmark_streamlit.py b/utils/db_benchmark/benchmark_streamlit.py index d66027796..1c62b102a 100644 --- a/utils/db_benchmark/benchmark_streamlit.py +++ b/utils/db_benchmark/benchmark_streamlit.py @@ -64,9 +64,7 @@ def add_metrics(container, value_benchmark): if not value_benchmark["success"]: values = {key: "-" for key in column_names} else: - values = { - key: value_benchmark["average_results"][f"pretty_{key}"] for key in column_names - } + values = {key: value_benchmark["average_results"][f"pretty_{key}"] for key in column_names} columns = { "write": write, @@ -113,9 +111,9 @@ def add_metrics(container, value_benchmark): delist_container.divider() def delist_benchmarks(): - delisted_sets = [f"{name} ({uuid})" - for name, uuid in edited_df.loc[edited_df["delete"]][["name", "uuid"]].values - ] + delisted_sets = [ + f"{name} ({uuid})" for name, uuid in edited_df.loc[edited_df["delete"]][["name", "uuid"]].values + ] st.session_state["compare"] = [ item for item in st.session_state["compare"] if item["benchmark_set"] not in delisted_sets @@ -130,7 +128,6 @@ def delist_benchmarks(): with open(BENCHMARK_RESULTS_FILES, "w", encoding="utf-8") as fd: json.dump(list(st.session_state["benchmark_files"]), fd) - delist_container.button(label="Delist selected benchmark sets", on_click=delist_benchmarks) add_container = st.container() @@ -177,8 +174,7 @@ def add_benchmark(): set_choice, benchmark_choice, compare = st.columns([3, 3, 1]) sets = { - f"{benchmark['name']} ({benchmark['uuid']})": benchmark - for benchmark in st.session_state["benchmarks"].values() + f"{benchmark['name']} ({benchmark['uuid']})": benchmark for benchmark in st.session_state["benchmarks"].values() } benchmark_set = set_choice.selectbox("Benchmark set", sets.keys()) @@ -191,10 +187,7 @@ def add_benchmark(): set_choice.text("Set description:") set_choice.markdown(selected_set["description"]) - benchmarks = { - f"{benchmark['name']} ({benchmark['uuid']})": benchmark - for benchmark in selected_set["benchmarks"] - } + benchmarks = {f"{benchmark['name']} ({benchmark['uuid']})": benchmark for benchmark in selected_set["benchmarks"]} benchmark = benchmark_choice.selectbox("Benchmark", benchmarks.keys()) @@ -216,10 +209,7 @@ def add_benchmark(): ) } - size_stats = { - stat: naturalsize(value, gnu=True) - for stat, value in selected_benchmark["sizes"].items() - } + size_stats = {stat: naturalsize(value, gnu=True) for stat, value in selected_benchmark["sizes"].items()} st.json(reproducible_stats) st.json(size_stats) @@ -246,17 +236,19 @@ def add_results_to_compare_tab(): compare.button( "Add to Compare" if compare_item not in st.session_state["compare"] else "Remove from Compare", - on_click=add_results_to_compare_tab + on_click=add_results_to_compare_tab, ) select_graph, graph = st.columns([1, 3]) + average_results = selected_benchmark["average_results"] + graphs = { "Write": selected_benchmark["result"]["write_times"], - "Read (grouped by contex_num)": selected_benchmark["average_results"]["read_times_grouped_by_context_num"], - "Read (grouped by dialog_len)": selected_benchmark["average_results"]["read_times_grouped_by_dialog_len"], - "Update (grouped by contex_num)": selected_benchmark["average_results"]["update_times_grouped_by_context_num"], - "Update (grouped by dialog_len)": selected_benchmark["average_results"]["update_times_grouped_by_dialog_len"], + "Read (grouped by contex_num)": average_results["read_times_grouped_by_context_num"], + "Read (grouped by dialog_len)": average_results["read_times_grouped_by_dialog_len"], + "Update (grouped by contex_num)": average_results["update_times_grouped_by_context_num"], + "Update (grouped by dialog_len)": average_results["update_times_grouped_by_dialog_len"], } selected_graph = select_graph.selectbox("Select graph to display", graphs.keys()) @@ -268,10 +260,17 @@ def add_results_to_compare_tab(): else: data = pd.DataFrame({"context_num": range(len(graph_data)), "time": graph_data}) - chart = alt.Chart(data).mark_circle().encode( - x=alt.X("dialog_len:Q" if isinstance(graph_data, dict) else "context_num:Q", scale=alt.Scale(zero=False)), - y="time:Q", - ).interactive() + chart = ( + alt.Chart(data) + .mark_circle() + .encode( + x=alt.X( + "dialog_len:Q" if isinstance(graph_data, dict) else "context_num:Q", scale=alt.Scale(zero=False) + ), + y="time:Q", + ) + .interactive() + ) graph.altair_chart(chart, use_container_width=True) @@ -287,10 +286,8 @@ def add_results_to_compare_tab(): if not df.empty: st.dataframe( df.style.highlight_min( - axis=0, subset=["write", "read", "update", "read+update"], props='background-color:green;' - ).highlight_max( - axis=0, subset=["write", "read", "update", "read+update"], props='background-color:red;' - ) + axis=0, subset=["write", "read", "update", "read+update"], props="background-color:green;" + ).highlight_max(axis=0, subset=["write", "read", "update", "read+update"], props="background-color:red;") ) ############################################################################### @@ -330,8 +327,6 @@ def add_results_to_compare_tab(): if not df.empty: st.dataframe( df.style.highlight_min( - axis=0, subset=["write", "read", "update", "read+update"], props='background-color:green;' - ).highlight_max( - axis=0, subset=["write", "read", "update", "read+update"], props='background-color:red;' - ) - ) \ No newline at end of file + axis=0, subset=["write", "read", "update", "read+update"], props="background-color:green;" + ).highlight_max(axis=0, subset=["write", "read", "update", "read+update"], props="background-color:red;") + ) From 6d55a3f6538aef2470fecc86b530930bb1c943e8 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Thu, 6 Jul 2023 15:26:05 +0300 Subject: [PATCH 064/113] add utils to backup_files for test_coverage --- .github/workflows/test_coverage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_coverage.yml b/.github/workflows/test_coverage.yml index 711629b8b..1f1f1cb1b 100644 --- a/.github/workflows/test_coverage.yml +++ b/.github/workflows/test_coverage.yml @@ -41,7 +41,7 @@ jobs: - name: clean environment run: | - export backup_files=( tests tutorials .env_file makefile .coveragerc ) + export backup_files=( tests tutorials utils .env_file makefile .coveragerc ) mkdir /tmp/backup for i in "${backup_files[@]}" ; do mv "$i" /tmp/backup ; done rm -rf ..?* .[!.]* * From a9402ee939842240d2a4481388da9a9674f621c1 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Thu, 6 Jul 2023 15:30:51 +0300 Subject: [PATCH 065/113] skip benchmark tests if benchmark not installed --- tests/utils/test_benchmark.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/utils/test_benchmark.py b/tests/utils/test_benchmark.py index a1e0b89c3..afce084cd 100644 --- a/tests/utils/test_benchmark.py +++ b/tests/utils/test_benchmark.py @@ -2,11 +2,15 @@ import json import pathlib -from jsonschema import validate import pytest -import dff.utils.benchmark.context_storage as bm -from dff.context_storages import JSONContextStorage +try: + from jsonschema import validate + + import dff.utils.benchmark.context_storage as bm + from dff.context_storages import JSONContextStorage +except ImportError: + pytest.skip(reason="`dff[benchmark,tests]` not installed", allow_module_level=True) ROOT_DIR = pathlib.Path(__file__).parent.parent.parent From 7b5c502a38dbfa72a1970e648037b25c3f3bd275 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Thu, 6 Jul 2023 17:19:26 +0300 Subject: [PATCH 066/113] Revert "remove format updater for benchmark files" This reverts commit 41ea85c681c1790d7e1e592b9e700665351406f4. --- benchmark_new_format.py | 78 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 benchmark_new_format.py diff --git a/benchmark_new_format.py b/benchmark_new_format.py new file mode 100644 index 000000000..b54c22bd8 --- /dev/null +++ b/benchmark_new_format.py @@ -0,0 +1,78 @@ +import json +import pathlib + +from dff.utils.benchmark.context_storage import BenchmarkCase + +benchmark_path = pathlib.Path("benchmarks") + +for file in benchmark_path.iterdir(): + if file.suffix == ".json": + with open(file, "r") as fd: + benchmark_set = json.load(fd) + + non_benchmark_fields = ("name", "description", "uuid", "benchmarks") + + new_benchmark_set = {k: v for k, v in benchmark_set.items() if k in non_benchmark_fields} + + if "benchmarks" not in benchmark_set: + new_benchmark_set["benchmarks"] = {k: v for k, v in benchmark_set.items() if k not in non_benchmark_fields} + + for key, benchmark in new_benchmark_set["benchmarks"].items(): + # update old factory specification + if benchmark["db_factory"].get("base_factory") == "dev": + postfix = "_old" + elif benchmark["db_factory"].get("base_factory") == "partial": + postfix = "" + else: + postfix = None + + if postfix is not None: + benchmark["db_factory"].pop("base_factory", None) + benchmark["db_factory"].update( + { + "factory_module": "dff.context_storages" + postfix, + "factory": "context_storage_factory", + } + ) + + # update average calculation + for benchmark in new_benchmark_set["benchmarks"].values(): + BenchmarkCase.set_average_results(benchmark) + + # update lengths -> dimensions renaming + for benchmark in new_benchmark_set["benchmarks"].values(): + for param in ("message", "misc"): + dimensions = benchmark.pop(f"{param}_lengths", None) + if dimensions is not None: + benchmark[f"{param}_dimensions"] = dimensions + + # update benchmark_config + for benchmark in new_benchmark_set["benchmarks"].values(): + if "benchmark_config" not in benchmark: + benchmark_config = { + "context_num": benchmark.pop("context_num"), + "from_dialog_len": benchmark.pop("from_dialog_len"), + "to_dialog_len": benchmark.pop("to_dialog_len"), + "step_dialog_len": benchmark.pop("step_dialog_len"), + "message_dimensions": benchmark.pop("message_dimensions"), + "misc_dimensions": benchmark.pop("misc_dimensions"), + } + + benchmark["benchmark_config"] = benchmark_config + + # update sizes + for benchmark in new_benchmark_set["benchmarks"].values(): + if "sizes" not in benchmark: + sizes = { + key: benchmark.pop(key) for key in ( + "starting_context_size", "final_context_size", "misc_size", "message_size" + ) + } + + benchmark["sizes"] = sizes + + benchmarks = new_benchmark_set.pop("benchmarks") + new_benchmark_set["benchmarks"] = list(benchmarks.values()) + + with open(file, "w") as fd: + json.dump(new_benchmark_set, fd) From 5e98c0eb2bc36f58d31f988855a80801158a86c0 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Thu, 6 Jul 2023 17:19:55 +0300 Subject: [PATCH 067/113] move format updater to utils --- .../db_benchmark/benchmark_new_format.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename benchmark_new_format.py => utils/db_benchmark/benchmark_new_format.py (100%) diff --git a/benchmark_new_format.py b/utils/db_benchmark/benchmark_new_format.py similarity index 100% rename from benchmark_new_format.py rename to utils/db_benchmark/benchmark_new_format.py From a9a7af2f99e7fb75545cca3bb1114adcca6dc4df Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Thu, 6 Jul 2023 23:03:30 +0300 Subject: [PATCH 068/113] move format updating to a separate function --- utils/db_benchmark/benchmark_new_format.py | 140 +++++++++++---------- 1 file changed, 76 insertions(+), 64 deletions(-) diff --git a/utils/db_benchmark/benchmark_new_format.py b/utils/db_benchmark/benchmark_new_format.py index b54c22bd8..57dd0b3f6 100644 --- a/utils/db_benchmark/benchmark_new_format.py +++ b/utils/db_benchmark/benchmark_new_format.py @@ -1,78 +1,90 @@ +""" +New format +---------- +Converts old benchmark result files to new formats +""" import json import pathlib +import typing as tp from dff.utils.benchmark.context_storage import BenchmarkCase benchmark_path = pathlib.Path("benchmarks") -for file in benchmark_path.iterdir(): - if file.suffix == ".json": - with open(file, "r") as fd: - benchmark_set = json.load(fd) - - non_benchmark_fields = ("name", "description", "uuid", "benchmarks") - - new_benchmark_set = {k: v for k, v in benchmark_set.items() if k in non_benchmark_fields} - - if "benchmarks" not in benchmark_set: - new_benchmark_set["benchmarks"] = {k: v for k, v in benchmark_set.items() if k not in non_benchmark_fields} - - for key, benchmark in new_benchmark_set["benchmarks"].items(): - # update old factory specification - if benchmark["db_factory"].get("base_factory") == "dev": - postfix = "_old" - elif benchmark["db_factory"].get("base_factory") == "partial": - postfix = "" - else: - postfix = None - - if postfix is not None: - benchmark["db_factory"].pop("base_factory", None) - benchmark["db_factory"].update( - { - "factory_module": "dff.context_storages" + postfix, - "factory": "context_storage_factory", - } - ) - # update average calculation - for benchmark in new_benchmark_set["benchmarks"].values(): - BenchmarkCase.set_average_results(benchmark) - - # update lengths -> dimensions renaming - for benchmark in new_benchmark_set["benchmarks"].values(): - for param in ("message", "misc"): - dimensions = benchmark.pop(f"{param}_lengths", None) - if dimensions is not None: - benchmark[f"{param}_dimensions"] = dimensions - - # update benchmark_config - for benchmark in new_benchmark_set["benchmarks"].values(): - if "benchmark_config" not in benchmark: - benchmark_config = { - "context_num": benchmark.pop("context_num"), - "from_dialog_len": benchmark.pop("from_dialog_len"), - "to_dialog_len": benchmark.pop("to_dialog_len"), - "step_dialog_len": benchmark.pop("step_dialog_len"), - "message_dimensions": benchmark.pop("message_dimensions"), - "misc_dimensions": benchmark.pop("misc_dimensions"), - } +def update_benchmark_file(benchmark_set_file: tp.Union[pathlib.Path, str]): + with open(benchmark_set_file, "r") as fd: + benchmark_set = json.load(fd) + + non_benchmark_fields = ("name", "description", "uuid", "benchmarks") + + new_benchmark_set = {k: v for k, v in benchmark_set.items() if k in non_benchmark_fields} - benchmark["benchmark_config"] = benchmark_config + if "benchmarks" not in benchmark_set: + new_benchmark_set["benchmarks"] = {k: v for k, v in benchmark_set.items() if k not in non_benchmark_fields} - # update sizes - for benchmark in new_benchmark_set["benchmarks"].values(): - if "sizes" not in benchmark: - sizes = { - key: benchmark.pop(key) for key in ( - "starting_context_size", "final_context_size", "misc_size", "message_size" - ) + for key, benchmark in new_benchmark_set["benchmarks"].items(): + # update old factory specification + if benchmark["db_factory"].get("base_factory") == "dev": + postfix = "_old" + elif benchmark["db_factory"].get("base_factory") == "partial": + postfix = "" + else: + postfix = None + + if postfix is not None: + benchmark["db_factory"].pop("base_factory", None) + benchmark["db_factory"].update( + { + "factory_module": "dff.context_storages" + postfix, + "factory": "context_storage_factory", } + ) + + # update average calculation + for benchmark in new_benchmark_set["benchmarks"].values(): + BenchmarkCase.set_average_results(benchmark) + + # update lengths -> dimensions renaming + for benchmark in new_benchmark_set["benchmarks"].values(): + for param in ("message", "misc"): + dimensions = benchmark.pop(f"{param}_lengths", None) + if dimensions is not None: + benchmark[f"{param}_dimensions"] = dimensions + + # update benchmark_config + for benchmark in new_benchmark_set["benchmarks"].values(): + if "benchmark_config" not in benchmark: + benchmark_config = { + "context_num": benchmark.pop("context_num"), + "from_dialog_len": benchmark.pop("from_dialog_len"), + "to_dialog_len": benchmark.pop("to_dialog_len"), + "step_dialog_len": benchmark.pop("step_dialog_len"), + "message_dimensions": benchmark.pop("message_dimensions"), + "misc_dimensions": benchmark.pop("misc_dimensions"), + } + + benchmark["benchmark_config"] = benchmark_config + + # update sizes + for benchmark in new_benchmark_set["benchmarks"].values(): + if "sizes" not in benchmark: + sizes = { + key: benchmark.pop(key) for key in ( + "starting_context_size", "final_context_size", "misc_size", "message_size" + ) + } + + benchmark["sizes"] = sizes + + benchmarks = new_benchmark_set.pop("benchmarks") + new_benchmark_set["benchmarks"] = list(benchmarks.values()) - benchmark["sizes"] = sizes + with open(benchmark_set_file, "w") as fd: + json.dump(new_benchmark_set, fd) - benchmarks = new_benchmark_set.pop("benchmarks") - new_benchmark_set["benchmarks"] = list(benchmarks.values()) - with open(file, "w") as fd: - json.dump(new_benchmark_set, fd) +if __name__ == "__main__": + for file in benchmark_path.iterdir(): + if file.suffix == ".json": + update_benchmark_file(file) From 266e28043b16b20975fd3a36826ddeb9b0ef88c7 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Thu, 6 Jul 2023 23:09:42 +0300 Subject: [PATCH 069/113] remove old format support from format updater --- utils/db_benchmark/benchmark_new_format.py | 62 +++------------------- 1 file changed, 6 insertions(+), 56 deletions(-) diff --git a/utils/db_benchmark/benchmark_new_format.py b/utils/db_benchmark/benchmark_new_format.py index 57dd0b3f6..9ce18deaa 100644 --- a/utils/db_benchmark/benchmark_new_format.py +++ b/utils/db_benchmark/benchmark_new_format.py @@ -7,8 +7,6 @@ import pathlib import typing as tp -from dff.utils.benchmark.context_storage import BenchmarkCase - benchmark_path = pathlib.Path("benchmarks") @@ -16,58 +14,13 @@ def update_benchmark_file(benchmark_set_file: tp.Union[pathlib.Path, str]): with open(benchmark_set_file, "r") as fd: benchmark_set = json.load(fd) - non_benchmark_fields = ("name", "description", "uuid", "benchmarks") - - new_benchmark_set = {k: v for k, v in benchmark_set.items() if k in non_benchmark_fields} - - if "benchmarks" not in benchmark_set: - new_benchmark_set["benchmarks"] = {k: v for k, v in benchmark_set.items() if k not in non_benchmark_fields} - - for key, benchmark in new_benchmark_set["benchmarks"].items(): - # update old factory specification - if benchmark["db_factory"].get("base_factory") == "dev": - postfix = "_old" - elif benchmark["db_factory"].get("base_factory") == "partial": - postfix = "" - else: - postfix = None - - if postfix is not None: - benchmark["db_factory"].pop("base_factory", None) - benchmark["db_factory"].update( - { - "factory_module": "dff.context_storages" + postfix, - "factory": "context_storage_factory", - } - ) - - # update average calculation - for benchmark in new_benchmark_set["benchmarks"].values(): - BenchmarkCase.set_average_results(benchmark) - - # update lengths -> dimensions renaming - for benchmark in new_benchmark_set["benchmarks"].values(): - for param in ("message", "misc"): - dimensions = benchmark.pop(f"{param}_lengths", None) - if dimensions is not None: - benchmark[f"{param}_dimensions"] = dimensions - - # update benchmark_config - for benchmark in new_benchmark_set["benchmarks"].values(): - if "benchmark_config" not in benchmark: - benchmark_config = { - "context_num": benchmark.pop("context_num"), - "from_dialog_len": benchmark.pop("from_dialog_len"), - "to_dialog_len": benchmark.pop("to_dialog_len"), - "step_dialog_len": benchmark.pop("step_dialog_len"), - "message_dimensions": benchmark.pop("message_dimensions"), - "misc_dimensions": benchmark.pop("misc_dimensions"), - } - - benchmark["benchmark_config"] = benchmark_config + # move benchmarks from dict to list + if isinstance(benchmark_set.get("benchmarks"), dict): + benchmarks = benchmark_set.pop("benchmarks") + benchmark_set["benchmarks"] = list(benchmarks.values()) # update sizes - for benchmark in new_benchmark_set["benchmarks"].values(): + for benchmark in benchmark_set["benchmarks"]: if "sizes" not in benchmark: sizes = { key: benchmark.pop(key) for key in ( @@ -77,11 +30,8 @@ def update_benchmark_file(benchmark_set_file: tp.Union[pathlib.Path, str]): benchmark["sizes"] = sizes - benchmarks = new_benchmark_set.pop("benchmarks") - new_benchmark_set["benchmarks"] = list(benchmarks.values()) - with open(benchmark_set_file, "w") as fd: - json.dump(new_benchmark_set, fd) + json.dump(benchmark_set, fd) if __name__ == "__main__": From d18025c61b7bacaf1b68ebd94ccd22089a66b2ec Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Thu, 6 Jul 2023 23:10:22 +0300 Subject: [PATCH 070/113] use format updater in streamlit --- utils/db_benchmark/benchmark_streamlit.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/utils/db_benchmark/benchmark_streamlit.py b/utils/db_benchmark/benchmark_streamlit.py index 1c62b102a..be99aaabf 100644 --- a/utils/db_benchmark/benchmark_streamlit.py +++ b/utils/db_benchmark/benchmark_streamlit.py @@ -27,6 +27,11 @@ import altair as alt import streamlit as st +try: + from benchmark_new_format import update_benchmark_file +except ImportError: + update_benchmark_file = None + st.set_page_config( page_title="DB benchmark", @@ -50,6 +55,8 @@ st.session_state["benchmarks"] = {} for file in st.session_state["benchmark_files"]: + if update_benchmark_file is not None: + update_benchmark_file(file) with open(file, "r", encoding="utf-8") as fd: st.session_state["benchmarks"][file] = json.load(fd) @@ -148,6 +155,9 @@ def add_benchmark(): add_container.warning("File does not exists") return + if update_benchmark_file is not None: + update_benchmark_file(benchmark_file) + with open(benchmark_file, "r", encoding="utf-8") as fd: file_contents = json.load(fd) From d9cbca561189c9979b02412dfebdfd41a62b7fe9 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Fri, 7 Jul 2023 00:21:16 +0300 Subject: [PATCH 071/113] add ability to upload files to streamlit, add all files from one directory --- utils/db_benchmark/benchmark_streamlit.py | 72 +++++++++++++++++++---- 1 file changed, 61 insertions(+), 11 deletions(-) diff --git a/utils/db_benchmark/benchmark_streamlit.py b/utils/db_benchmark/benchmark_streamlit.py index be99aaabf..fab43d9bc 100644 --- a/utils/db_benchmark/benchmark_streamlit.py +++ b/utils/db_benchmark/benchmark_streamlit.py @@ -19,7 +19,9 @@ Benchmark result files added via this module are not changed (only read). """ import json +import pathlib from pathlib import Path +from uuid import uuid4 import pandas as pd from pympler import asizeof @@ -42,6 +44,11 @@ BENCHMARK_RESULTS_FILES = Path("benchmark_results_files.json") # This file stores links to benchmark set files generated by `save_results_to_file`. +UPLOAD_FILES_DIR = Path("uploaded_benchmarks") +# This directory stores all the benchmarks uploaded via the streamlit interface + +UPLOAD_FILES_DIR.mkdir(exist_ok=True) + if not BENCHMARK_RESULTS_FILES.exists(): with open(BENCHMARK_RESULTS_FILES, "w", encoding="utf-8") as fd: @@ -137,22 +144,18 @@ def delist_benchmarks(): delist_container.button(label="Delist selected benchmark sets", on_click=delist_benchmarks) - add_container = st.container() - add_container.divider() - - add_container.text_input(label="Benchmark set file", key="add_benchmark_file") + def _add_benchmark(benchmark_file, container): + benchmark_file = str(benchmark_file) - def add_benchmark(): - benchmark_file = st.session_state["add_benchmark_file"] if benchmark_file == "": return if benchmark_file in st.session_state["benchmark_files"]: - add_container.warning("Benchmark file already added") + container.warning(f"Benchmark file already added: {benchmark_file}") return if not Path(benchmark_file).exists(): - add_container.warning("File does not exists") + container.warning(f"File does not exists: {benchmark_file}") return if update_benchmark_file is not None: @@ -163,7 +166,7 @@ def add_benchmark(): for benchmark in st.session_state["benchmarks"].values(): if file_contents["uuid"] == benchmark["uuid"]: - add_container.warning("Benchmark with the same uuid already exists") + container.warning(f"Benchmark with the same uuid already exists: {benchmark_file}") return st.session_state["benchmark_files"].append(benchmark_file) @@ -171,9 +174,56 @@ def add_benchmark(): json.dump(list(st.session_state["benchmark_files"]), fd) st.session_state["benchmarks"][benchmark_file] = file_contents - add_container.text(f"Added {benchmark_file} set") + container.text(f"Added {benchmark_file}") + + st.divider() + + add_container, add_from_dir_container = st.columns(2) + + add_container.text_input(label="Benchmark set file", key="add_benchmark_file") + + def add_benchmark(): + _add_benchmark(st.session_state["add_benchmark_file"], add_container) + + add_container.button("Add one file from file", on_click=add_benchmark) + + add_from_dir_container.text_input(label="Directory with benchmark files", key="add_from_dir") + + def add_from_dir(): + dir_path = pathlib.Path(st.session_state["add_from_dir"]) + if dir_path.is_dir(): + for file in dir_path.iterdir(): + _add_benchmark(file, add_from_dir_container) + + add_from_dir_container.button("Add all files from directory", on_click=add_from_dir) + + st.divider() + + upload_container = st.container() + + def process_uploaded_files(): + uploaded_files = st.session_state["benchmark_file_uploader"] + if uploaded_files is not None: + if len(uploaded_files) > 0: + new_uploaded_file_dir = UPLOAD_FILES_DIR / str(uuid4()) + new_uploaded_file_dir.mkdir() + + for file in uploaded_files: + file_path = new_uploaded_file_dir / file.name + with open(file_path, "wb") as uploaded_file_descriptor: + uploaded_file_descriptor.write(file.read()) + + _add_benchmark(file_path, upload_container) + + with upload_container.form("upload_form", clear_on_submit=True): + st.file_uploader( + "Upload benchmark results", + accept_multiple_files=True, + type="json", + key="benchmark_file_uploader" + ) + st.form_submit_button("Submit", on_click=process_uploaded_files) - add_container.button("Add benchmark set from file", on_click=add_benchmark) ############################################################################### # View tab From 65ef6a206e7e7b19908374ed884fabb6c3e8dde5 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Mon, 10 Jul 2023 21:17:41 +0300 Subject: [PATCH 072/113] store benchmarks for a specific db in one file Instead of storing benchmarks on a per-config basis, store benchmarks on a per-db basis. --- dff/utils/benchmark/context_storage.py | 24 +++-- tests/utils/test_benchmark.py | 46 ++++++-- utils/db_benchmark/benchmark_dbs.py | 141 ++++++++----------------- 3 files changed, 93 insertions(+), 118 deletions(-) diff --git a/dff/utils/benchmark/context_storage.py b/dff/utils/benchmark/context_storage.py index 7be2d46fe..9dea921ae 100644 --- a/dff/utils/benchmark/context_storage.py +++ b/dff/utils/benchmark/context_storage.py @@ -206,7 +206,7 @@ class BenchmarkConfig(BaseModel): Configuration for a benchmark. Sets dialog len, misc sizes, number of benchmarks. """ - context_num: int = 100 + context_num: int = 30 """ Number of times the contexts will be benchmarked. Increasing this number decreases standard error of the mean for benchmarked data. @@ -463,7 +463,9 @@ def save_results_to_file( "uuid": uuid, "benchmarks": [], } - for case in benchmark_cases: + cases = tqdm(benchmark_cases, leave=False) + for case in cases: + cases.set_description(f"Benchmarking: {case.name}") result["benchmarks"].append({**case.dict(), "sizes": case.benchmark_config.sizes(), **case.run()}) json.dump(result, fd) @@ -473,33 +475,33 @@ def benchmark_all( file: tp.Union[str, pathlib.Path], name: str, description: str, - db_uris: tp.Dict[str, str], - benchmark_config: BenchmarkConfig = BenchmarkConfig(), + db_uri: str, + benchmark_configs: tp.Dict[str, BenchmarkConfig], exist_ok: bool = False, ): """ A wrapper for :py:func:`~.save_results_to_file`. - Generates `benchmark_cases` from `db_uris` and `benchmark_config`: - URIs inside `db_uris` dictionary are used to initialize :py:class:`~.DBFactory` instances - which are then used along with `benchmark_config` to initialize :py:class:`~.BenchmarkCase` instances. + Generates `benchmark_cases` from `db_uri` and `benchmark_configs`: + `db_uri` is used to initialize :py:class:`~.DBFactory` instance + which is then used along with `benchmark_configs` to initialize :py:class:`~.BenchmarkCase` instances. :param file: File to save results to. :param name: Name of the benchmark set. :param description: Description of the benchmark set. The same description is used for benchmark cases. - :param db_uris: A mapping from DB names to DB URIs. The names are used as names for benchmark cases. - :param benchmark_config: A benchmark config to use in all benchmark cases. + :param db_uri: URI of the database to benchmark + :param benchmark_configs: Mapping from case names to configs. :param exist_ok: Whether to continue if the file already exists. """ save_results_to_file( [ BenchmarkCase( - name=db_name, + name=case_name, description=description, db_factory=DBFactory(uri=db_uri), benchmark_config=benchmark_config, ) - for db_name, db_uri in db_uris.items() + for case_name, benchmark_config in benchmark_configs.items() ], file, name, diff --git a/tests/utils/test_benchmark.py b/tests/utils/test_benchmark.py index afce084cd..128bc9ad4 100644 --- a/tests/utils/test_benchmark.py +++ b/tests/utils/test_benchmark.py @@ -253,15 +253,17 @@ def test_save_to_file(tmp_path): tmp_path / "result.json", "test", "test", - {"json": f"json://{tmp_path}/json.json", "error": "NONE"}, - bm.BenchmarkConfig( - context_num=5, - from_dialog_len=1, - to_dialog_len=11, - step_dialog_len=3, - message_dimensions=(2, 2), - misc_dimensions=(3, 3, 3), - ), + f"json://{tmp_path}/json.json", + { + "config": bm.BenchmarkConfig( + context_num=5, + from_dialog_len=1, + to_dialog_len=11, + step_dialog_len=3, + message_dimensions=(2, 2), + misc_dimensions=(3, 3, 3), + ) + } ) with open(tmp_path / "result.json", "r", encoding="utf-8") as fd: @@ -283,7 +285,29 @@ def test_save_to_file(tmp_path): "average_results", } - assert not benchmark_set["benchmarks"][1]["success"] - assert "average_results" not in benchmark_set["benchmarks"][1] + validate(instance=benchmark_set, schema=schema) + + bm.benchmark_all( + tmp_path / "result_unsuccessful.json", + "test", + "test", + "None", + { + "config": bm.BenchmarkConfig( + context_num=5, + from_dialog_len=1, + to_dialog_len=11, + step_dialog_len=3, + message_dimensions=(2, 2), + misc_dimensions=(3, 3, 3), + ) + } + ) + + with open(tmp_path / "result_unsuccessful.json", "r", encoding="utf-8") as fd: + benchmark_set = json.load(fd) + + assert not benchmark_set["benchmarks"][0]["success"] + assert "average_results" not in benchmark_set["benchmarks"][0] validate(instance=benchmark_set, schema=schema) diff --git a/utils/db_benchmark/benchmark_dbs.py b/utils/db_benchmark/benchmark_dbs.py index 75ac0713c..2aad9aba9 100644 --- a/utils/db_benchmark/benchmark_dbs.py +++ b/utils/db_benchmark/benchmark_dbs.py @@ -7,11 +7,8 @@ from platform import system from dff.utils.benchmark.context_storage import ( - save_results_to_file, BenchmarkConfig, benchmark_all, - BenchmarkCase, - DBFactory, ) @@ -38,97 +35,49 @@ benchmark_dir.mkdir(exist_ok=True) -benchmark_all( - benchmark_dir / "alexaprize.json", - "Alexaprize-like dialogue benchmarks", - "Benchmark with dialogues similar to those from alexaprize.", - db_uris=dbs, - benchmark_config=BenchmarkConfig( - from_dialog_len=1, - to_dialog_len=50, - message_dimensions=(3, 5, 6, 5, 3), - misc_dimensions=(2, 4, 3, 8, 100), - ), -) - -benchmark_all( - benchmark_dir / "short_messages.json", - "Short messages", - "Benchmark with short messages, long dialog len.", - db_uris=dbs, - benchmark_config=BenchmarkConfig( - from_dialog_len=500, - to_dialog_len=550, - message_dimensions=(2, 30), - misc_dimensions=(0, 0), - ), -) - -benchmark_all( - benchmark_dir / "default.json", - "Default", - "Benchmark using default parameters.", - db_uris=dbs, -) - -benchmark_all( - benchmark_dir / "alexaprize_longer.json", - "Alexaprize-like dialogue benchmarks (longer)", - "Benchmark with dialogues similar to those from alexaprize, but dialog len is increased.", - db_uris=dbs, - benchmark_config=BenchmarkConfig( - from_dialog_len=500, - to_dialog_len=550, - message_dimensions=(3, 5, 6, 5, 3), - misc_dimensions=(2, 4, 3, 8, 100), - ), -) -save_results_to_file( - [ - *[ - BenchmarkCase( - db_factory=DBFactory(uri=uri), - name=name + "-long-dialog-len", - benchmark_config=BenchmarkConfig( - context_num=10, - from_dialog_len=10000, - to_dialog_len=10050, - ), - description="Benchmark with very long dialog len.", - ) - for name, uri in dbs.items() - ], - *[ - BenchmarkCase( - db_factory=DBFactory(uri=uri), - name=name + "-long-message-len", - benchmark_config=BenchmarkConfig( - context_num=10, - from_dialog_len=1, - to_dialog_len=3, - message_dimensions=(10000, 1), - ), - description="Benchmark with messages containing many keys.", - ) - for name, uri in dbs.items() - ], - *[ - BenchmarkCase( - db_factory=DBFactory(uri=uri), - name=name + "-long-misc-len", - benchmark_config=BenchmarkConfig( - context_num=10, - from_dialog_len=1, - to_dialog_len=3, - misc_dimensions=(10000, 1), - ), - description="Benchmark with misc containing many keys.", - ) - for name, uri in dbs.items() - ], - ], - file=benchmark_dir / "extremes.json", - name="Extreme", - description="Set of benchmarks testing extreme cases.", -) +for db_name, db_uri in dbs.items(): + benchmark_all( + benchmark_dir / f"{db_name}.json", + db_name, + description="Basic configs", + db_uri=db_uri, + benchmark_configs={ + "large-misc": BenchmarkConfig( + from_dialog_len=1, + to_dialog_len=50, + message_dimensions=(3, 5, 6, 5, 3), + misc_dimensions=(2, 4, 3, 8, 100), + ), + "short-messages": BenchmarkConfig( + from_dialog_len=500, + to_dialog_len=550, + message_dimensions=(2, 30), + misc_dimensions=(0, 0), + ), + "default": BenchmarkConfig(), + "large-misc--long-dialog": BenchmarkConfig( + from_dialog_len=500, + to_dialog_len=550, + message_dimensions=(3, 5, 6, 5, 3), + misc_dimensions=(2, 4, 3, 8, 100), + ), + "very-long-dialog-len": BenchmarkConfig( + context_num=10, + from_dialog_len=10000, + to_dialog_len=10050, + ), + "very-long-message-len": BenchmarkConfig( + context_num=10, + from_dialog_len=1, + to_dialog_len=3, + message_dimensions=(10000, 1), + ), + "very-long-misc-len": BenchmarkConfig( + context_num=10, + from_dialog_len=1, + to_dialog_len=3, + misc_dimensions=(10000, 1), + ), + } + ) From 97002c8e7780cdde24c1a856984dd5a1e029fc80 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Tue, 11 Jul 2023 01:21:29 +0300 Subject: [PATCH 073/113] change report function, update tutorial Report function now only lets users view results from files. --- dff/utils/benchmark/context_storage.py | 128 ++++++++++-------- .../context_storages/8_db_benchmarking.py | 109 ++++++++------- 2 files changed, 132 insertions(+), 105 deletions(-) diff --git a/dff/utils/benchmark/context_storage.py b/dff/utils/benchmark/context_storage.py index 9dea921ae..a01a607f6 100644 --- a/dff/utils/benchmark/context_storage.py +++ b/dff/utils/benchmark/context_storage.py @@ -7,10 +7,13 @@ Higher level wrappers of the function provided by this module are: -- :py:func:`~.save_results_to_file` and :py:func:`~.benchmark_all` are used to save benchmark results to a file. -- :py:func:`~.report` is used to print results to stdout. +- :py:func:`~.save_results_to_file` -- saves results for a list of benchmark cases. +- :py:func:`~.benchmark_all` -- a wrapper of `save_results_to_file`. Creates cases from configs. Wrappers use :py:class:`~.BenchmarkConfig` to configure benchmarks. + +To view files generated by :py:func:`~.save_results_to_file` use either :py:func:`~.report` or +`streamlit app <../../../utils/db_benchmark/benchmark_streamlit.py>`_. """ from uuid import uuid4 import pathlib @@ -20,6 +23,7 @@ import json import importlib from statistics import mean +import typing_extensions as tp_e from pydantic import BaseModel, Field from pympler import asizeof @@ -443,7 +447,8 @@ def save_results_to_file( .. literalinclude:: ../../../utils/db_benchmark/benchmark_schema.json - Files created by this function cen be viewed with the streamlit app located in the same directory: + Files created by this function cen be viewed either by using :py:func:`~.report` or + streamlit app located in the utils directory: .. collapse:: utils/db_benchmark/benchmark_streamlit.py @@ -511,66 +516,77 @@ def benchmark_all( def report( - db_uris: tp.Dict[str, str], - benchmark_config: BenchmarkConfig = BenchmarkConfig(), + file: tp.Union[str, pathlib.Path], + display: tp.Set[tp_e.Literal['name', 'desc', 'config', 'sizes', 'metrics']] = set({'name', 'metrics'}), ): """ - Benchmark DBs with a config and print results to stdout. + Print average results from a result file to stdout. - Printed stats contain benchmark config, object sizes, average benchmark values for successful cases and + Printed stats contain benchmark configs, object sizes, average benchmark values for successful cases and exception message for unsuccessful cases. - :param db_uris: A mapping from DB names to DB uris. DB names are used as names for benchmark cases. - :param benchmark_config: Benchmark config to use in all benchmark cases. + :param file: File with benchmark results generated by :py:func:`~.save_results_to_file`. + :param display: + A set of objects to display in results. + Values allowed inside the set: + + - "name" -- displays the name of the benchmark case. + - "desc" -- displays the description of the benchmark case. + - "config" -- displays the config of the benchmark case. + - "sizes" -- displays size stats for the config. + - "metrics" -- displays average write, read, update read+update times. """ - benchmark_cases = [ - BenchmarkCase( - name=db_name, - db_factory=DBFactory(uri=db_uri), - benchmark_config=benchmark_config, + with open(file, "r", encoding="utf-8") as fd: + file_contents = json.load(fd) + + def get_benchmark_config_report(benchmark_config, sizes) -> tp.Tuple[str, str]: + benchmark_config = BenchmarkConfig(**benchmark_config) + starting_context_size = sizes["starting_context_size"] + final_context_size = sizes["final_context_size"] + misc_size = sizes["misc_size"] + message_size = sizes["message_size"] + + return ( + f"Number of contexts: {benchmark_config.context_num}\n" + f"From dialog len: {benchmark_config.from_dialog_len}\n" + f"To dialog len: {benchmark_config.to_dialog_len}\n" + f"Step dialog len: {benchmark_config.step_dialog_len}\n" + f"Message misc dimensions: {benchmark_config.message_dimensions}\n" + f"Misc dimensions: {benchmark_config.misc_dimensions}\n", + f"Size of misc field: {misc_size} ({naturalsize(misc_size, gnu=True)})\n" + f"Size of one message: {message_size} ({naturalsize(message_size, gnu=True)})\n" + f"Starting context size: {starting_context_size} ({naturalsize(starting_context_size, gnu=True)})\n" + f"Final context size: {final_context_size} ({naturalsize(final_context_size, gnu=True)})" ) - for db_name, db_uri in db_uris.items() - ] - sizes = benchmark_config.sizes() - starting_context_size = sizes["starting_context_size"] - final_context_size = sizes["final_context_size"] - misc_size = sizes["misc_size"] - message_size = sizes["message_size"] - - benchmark_config_report = ( - f"Number of contexts: {benchmark_config.context_num}\n" - f"From dialog len: {benchmark_config.from_dialog_len}\n" - f"To dialog len: {benchmark_config.to_dialog_len}\n" - f"Step dialog len: {benchmark_config.step_dialog_len}\n" - f"Message misc dimensions: {benchmark_config.message_dimensions}\n" - f"Misc dimensions: {benchmark_config.misc_dimensions}\n" - f"Size of misc field: {misc_size} ({naturalsize(misc_size, gnu=True)})\n" - f"Size of one message: {message_size} ({naturalsize(message_size, gnu=True)})\n" - f"Starting context size: {starting_context_size} ({naturalsize(starting_context_size, gnu=True)})\n" - f"Final context size: {final_context_size} ({naturalsize(final_context_size, gnu=True)})" - ) - line_separator = "-" * 80 - - print(f"Starting benchmarking with following parameters:\n{benchmark_config_report}") - - report_result = f"\n{line_separator}\n".join(["", "DB benchmark", benchmark_config_report, ""]) - - for benchmark_case in benchmark_cases: - result = benchmark_case.run() - report_result += f"\n{line_separator}\n".join( - [ - benchmark_case.name, - "".join( - [ - f"{metric.title() + ': ' + str(result['average_results']['pretty_' + metric]):20}" - if result["success"] - else result["result"] - for metric in ("write", "read", "update", "read+update") - ] - ), - "", - ] - ) + sep = "-" * 80 + + report_result = "\n".join([sep, file_contents["name"], sep, file_contents["description"], sep, ""]) + + for benchmark in file_contents["benchmarks"]: + config, sizes = get_benchmark_config_report(benchmark["benchmark_config"], benchmark["sizes"]) + + reported_values = { + "name": benchmark["name"], + "desc": benchmark["description"], + "config": config, + "sizes": sizes, + "metrics": "".join( + [ + f"{metric.title() + ': ' + str(benchmark['average_results']['pretty_' + metric]):20}" + if benchmark["success"] + else benchmark["result"] + for metric in ("write", "read", "update", "read+update") + ] + ), + } + + result = [] + for value_name, value in reported_values.items(): + if value_name in display: + result.append(value) + result.append("") + + report_result += f"\n{sep}\n".join(result) print(report_result, end="") diff --git a/tutorials/context_storages/8_db_benchmarking.py b/tutorials/context_storages/8_db_benchmarking.py index e9f4537d3..d9149d75c 100644 --- a/tutorials/context_storages/8_db_benchmarking.py +++ b/tutorials/context_storages/8_db_benchmarking.py @@ -41,44 +41,11 @@ "YDB": "grpc://localhost:2136/local", } -# %% [markdown] -""" -## Generating a report - -The report will print a size of one context and stats for the context storage: -average write, read, update times. - -Note: context storage passed into the `report` function will be cleared. - -Setting `context_num` to 50 means that we'll run fifty cycles of writing and reading context. -This way we'll be able to get a more accurate average read/write time as well as -check if read/write times are dependent on the number of contexts in the storage. - -You can also configure the `dialog_len`, `message_dimensions` and `misc_dimensions` parameters. -This allows you to set the context you want the benchmarks to run with. - -For more info on each configuration parameter see [BenchmarkConfig]( -../apiref/dff.utils.benchmark.context_storage.rst#dff.utils.benchmark.context_storage.BenchmarkConfig -). -""" - -# %% -benchmark.report( - storages, - benchmark_config=benchmark.BenchmarkConfig( - context_num=50, - from_dialog_len=1, - to_dialog_len=5, - message_dimensions=(3, 10), - misc_dimensions=(3, 10), - ), -) - # %% [markdown] """ ## Saving benchmark results to a file -Instead of printing the results into console, you can save them to a file. +Benchmark results are saved to files. For that there exist two functions: [benchmark_all]( @@ -89,7 +56,32 @@ ../apiref/dff.utils.benchmark.context_storage.rst#dff.utils.benchmark.context_storage.save_results_to_file ). +Note: context storages passed into these functions will be cleared. + +### Configuration + The first one is a higher-level wrapper of the second one. +The first function accepts [BenchmarkCases]( +../apiref/dff.utils.benchmark.context_storage.rst#dff.utils.benchmark.context_storage.BenchmarkCase +) which configure databases that are being benchmark and configurations of the benchmarks. +The second function accepts only a single URI for the database and several benchmark configurations. +So, the second function is simpler to use, while the first function allows for more configuration +(e.g. having different databases benchmarked in a single file). + +Both function use [BenchmarkConfig]( +../apiref/dff.utils.benchmark.context_storage.rst#dff.utils.benchmark.context_storage.BenchmarkConfig +) to configure benchmark behaviour. + +It has several parameters: + +Setting `context_num` to 50 means that we'll run fifty cycles of writing and reading context. +This way we'll be able to get a more accurate average read/write time as well as +check if read/write times are dependent on the number of contexts in the storage. + +You can also configure the `dialog_len`, `message_dimensions` and `misc_dimensions` parameters. +This allows you to set the contexts you want your database to be benchmarked with. + +### File structure The files are saved according to this [schema](../../../utils/db_benchmark/benchmark_schema.json). @@ -99,26 +91,30 @@ """ # %% -benchmark.benchmark_all( - file=tutorial_dir / "results.json", - name="Tutorial benchmark", - description="Benchmark for tutorial", - db_uris=storages, - benchmark_config=benchmark.BenchmarkConfig( - context_num=50, - from_dialog_len=1, - to_dialog_len=5, - message_dimensions=(3, 10), - misc_dimensions=(3, 10), - ), -) +for db_name, db_uri in storages.items(): + benchmark.benchmark_all( + file=tutorial_dir / f"{db_name}.json", + name="Tutorial benchmark", + description="Benchmark for tutorial", + db_uri=db_uri, + benchmark_configs={ + "simple_config": benchmark.BenchmarkConfig( + context_num=50, + from_dialog_len=1, + to_dialog_len=5, + message_dimensions=(3, 10), + misc_dimensions=(3, 10), + ), + } + ) # %% [markdown] """ ## Viewing benchmark results -Now that the results are saved to a file you can either view them manually or -use [our streamlit app]( +Now that the results are saved to a file you can either view them using [report]( +../apiref/dff.utils.benchmark.context_storage.rst#dff.utils.benchmark.context_storage.report +) function or the [streamlit app]( ../../../utils/db_benchmark/benchmark_streamlit.py ). @@ -126,3 +122,18 @@ https://github.com/deeppavlov/dialog_flow_framework/blob/dev/utils/db_benchmark/benchmark_streamlit.py ). """ + +# %% [markdown] +""" +### Using the report function + +The report function will print specified information from a given file. + +By default it prints the name and average metrics for each case. +""" + +# %% +benchmark.report( + file=tutorial_dir / "Shelve.json", + display={"name", "config", "metrics"} +) From 99ee6a2d187e8d589a2d102782b5458d1e515f4f Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Tue, 11 Jul 2023 01:29:42 +0300 Subject: [PATCH 074/113] remove literal include of the files --- dff/utils/benchmark/context_storage.py | 13 +++---------- docs/source/conf.py | 1 - setup.py | 1 - 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/dff/utils/benchmark/context_storage.py b/dff/utils/benchmark/context_storage.py index a01a607f6..a7d5a3805 100644 --- a/dff/utils/benchmark/context_storage.py +++ b/dff/utils/benchmark/context_storage.py @@ -440,19 +440,12 @@ def save_results_to_file( """ Benchmark all `benchmark_cases` and save results to a file. - Result are saved in json format with this schema (click to expand): - - .. collapse:: utils/db_benchmark/benchmark_schema.json - - .. literalinclude:: ../../../utils/db_benchmark/benchmark_schema.json - + Result are saved in json format with this schema: + `utils/db_benchmark/benchmark_schema.json <../../../utils/db_benchmark/benchmark_schema.json>`_. Files created by this function cen be viewed either by using :py:func:`~.report` or streamlit app located in the utils directory: - - .. collapse:: utils/db_benchmark/benchmark_streamlit.py - - .. literalinclude:: ../../../utils/db_benchmark/benchmark_streamlit.py + `utils/db_benchmark/benchmark_streamlit.py <../../../utils/db_benchmark/benchmark_streamlit.py>`_. :param benchmark_cases: A list of benchmark cases that specify benchmarks. :param file: File to save results to. diff --git a/docs/source/conf.py b/docs/source/conf.py index ed86add16..52e87582d 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -36,7 +36,6 @@ "sphinx.ext.viewcode", "sphinx.ext.mathjax", "sphinx.ext.extlinks", - "sphinx_toolbox.collapse", "sphinxcontrib.katex", "sphinx_copybutton", "sphinx_favicon", diff --git a/setup.py b/setup.py index fc2253888..275ab1e75 100644 --- a/setup.py +++ b/setup.py @@ -150,7 +150,6 @@ def merge_req_lists(*req_lists: List[str]) -> List[str]: "nbsphinx==0.9.1", "jupytext==1.14.5", "jupyter==1.0.0", - "sphinx-toolbox==3.4.0", ], requests_requirements, ) From 791be50768bf354168fe922d6f5445472e080e45 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Tue, 11 Jul 2023 01:31:04 +0300 Subject: [PATCH 075/113] format --- dff/utils/benchmark/context_storage.py | 4 ++-- tests/utils/test_benchmark.py | 4 ++-- tutorials/context_storages/8_db_benchmarking.py | 7 ++----- utils/db_benchmark/benchmark_dbs.py | 2 +- utils/db_benchmark/benchmark_new_format.py | 5 ++--- utils/db_benchmark/benchmark_streamlit.py | 5 +---- 6 files changed, 10 insertions(+), 17 deletions(-) diff --git a/dff/utils/benchmark/context_storage.py b/dff/utils/benchmark/context_storage.py index a7d5a3805..ef424e0e2 100644 --- a/dff/utils/benchmark/context_storage.py +++ b/dff/utils/benchmark/context_storage.py @@ -510,7 +510,7 @@ def benchmark_all( def report( file: tp.Union[str, pathlib.Path], - display: tp.Set[tp_e.Literal['name', 'desc', 'config', 'sizes', 'metrics']] = set({'name', 'metrics'}), + display: tp.Set[tp_e.Literal["name", "desc", "config", "sizes", "metrics"]] = set({"name", "metrics"}), ): """ Print average results from a result file to stdout. @@ -549,7 +549,7 @@ def get_benchmark_config_report(benchmark_config, sizes) -> tp.Tuple[str, str]: f"Size of misc field: {misc_size} ({naturalsize(misc_size, gnu=True)})\n" f"Size of one message: {message_size} ({naturalsize(message_size, gnu=True)})\n" f"Starting context size: {starting_context_size} ({naturalsize(starting_context_size, gnu=True)})\n" - f"Final context size: {final_context_size} ({naturalsize(final_context_size, gnu=True)})" + f"Final context size: {final_context_size} ({naturalsize(final_context_size, gnu=True)})", ) sep = "-" * 80 diff --git a/tests/utils/test_benchmark.py b/tests/utils/test_benchmark.py index 128bc9ad4..3ff349ca3 100644 --- a/tests/utils/test_benchmark.py +++ b/tests/utils/test_benchmark.py @@ -263,7 +263,7 @@ def test_save_to_file(tmp_path): message_dimensions=(2, 2), misc_dimensions=(3, 3, 3), ) - } + }, ) with open(tmp_path / "result.json", "r", encoding="utf-8") as fd: @@ -301,7 +301,7 @@ def test_save_to_file(tmp_path): message_dimensions=(2, 2), misc_dimensions=(3, 3, 3), ) - } + }, ) with open(tmp_path / "result_unsuccessful.json", "r", encoding="utf-8") as fd: diff --git a/tutorials/context_storages/8_db_benchmarking.py b/tutorials/context_storages/8_db_benchmarking.py index d9149d75c..90e9a1e5d 100644 --- a/tutorials/context_storages/8_db_benchmarking.py +++ b/tutorials/context_storages/8_db_benchmarking.py @@ -105,7 +105,7 @@ message_dimensions=(3, 10), misc_dimensions=(3, 10), ), - } + }, ) # %% [markdown] @@ -133,7 +133,4 @@ """ # %% -benchmark.report( - file=tutorial_dir / "Shelve.json", - display={"name", "config", "metrics"} -) +benchmark.report(file=tutorial_dir / "Shelve.json", display={"name", "config", "metrics"}) diff --git a/utils/db_benchmark/benchmark_dbs.py b/utils/db_benchmark/benchmark_dbs.py index 2aad9aba9..8c9deda96 100644 --- a/utils/db_benchmark/benchmark_dbs.py +++ b/utils/db_benchmark/benchmark_dbs.py @@ -79,5 +79,5 @@ to_dialog_len=3, misc_dimensions=(10000, 1), ), - } + }, ) diff --git a/utils/db_benchmark/benchmark_new_format.py b/utils/db_benchmark/benchmark_new_format.py index 9ce18deaa..acc1c6585 100644 --- a/utils/db_benchmark/benchmark_new_format.py +++ b/utils/db_benchmark/benchmark_new_format.py @@ -23,9 +23,8 @@ def update_benchmark_file(benchmark_set_file: tp.Union[pathlib.Path, str]): for benchmark in benchmark_set["benchmarks"]: if "sizes" not in benchmark: sizes = { - key: benchmark.pop(key) for key in ( - "starting_context_size", "final_context_size", "misc_size", "message_size" - ) + key: benchmark.pop(key) + for key in ("starting_context_size", "final_context_size", "misc_size", "message_size") } benchmark["sizes"] = sizes diff --git a/utils/db_benchmark/benchmark_streamlit.py b/utils/db_benchmark/benchmark_streamlit.py index fab43d9bc..7496ebf73 100644 --- a/utils/db_benchmark/benchmark_streamlit.py +++ b/utils/db_benchmark/benchmark_streamlit.py @@ -217,10 +217,7 @@ def process_uploaded_files(): with upload_container.form("upload_form", clear_on_submit=True): st.file_uploader( - "Upload benchmark results", - accept_multiple_files=True, - type="json", - key="benchmark_file_uploader" + "Upload benchmark results", accept_multiple_files=True, type="json", key="benchmark_file_uploader" ) st.form_submit_button("Submit", on_click=process_uploaded_files) From 7c5f7ed09dcafb6bd25684892fb89d1c9f70d221 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Tue, 11 Jul 2023 15:11:51 +0300 Subject: [PATCH 076/113] add ability to edit name and description of benchmark sets --- utils/db_benchmark/benchmark_streamlit.py | 26 +++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/utils/db_benchmark/benchmark_streamlit.py b/utils/db_benchmark/benchmark_streamlit.py index 7496ebf73..8182a9a2f 100644 --- a/utils/db_benchmark/benchmark_streamlit.py +++ b/utils/db_benchmark/benchmark_streamlit.py @@ -118,8 +118,30 @@ def add_metrics(container, value_benchmark): } ) - df = pd.DataFrame(data=benchmark_list) - edited_df = st.data_editor(df, disabled=("file", "name", "description", "uuid")) + benchmark_list_df = pd.DataFrame(data=benchmark_list) + + df_container = st.container() + + def edit_name_desc(): + edited_rows = st.session_state["result_df"]["edited_rows"] + + for row, edits in edited_rows.items(): + for column, column_value in edits.items(): + if column in ("name", "description"): + edited_file = benchmark_list_df.iat[row, 0] + st.session_state["benchmarks"][edited_file][column] = column_value + + with open(edited_file, "w", encoding="utf-8") as edited_fd: + json.dump(st.session_state["benchmarks"][edited_file], edited_fd) + + df_container.text(f"row {row}: changed {column} to '{column_value}'") + + edited_df = df_container.data_editor( + benchmark_list_df, + key="result_df", + disabled=("file", "uuid"), + on_change=edit_name_desc + ) delist_container = st.container() delist_container.divider() From 2d7432b9af5e54ddd27d85788ce184abb0cff4ed Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Wed, 12 Jul 2023 01:01:50 +0300 Subject: [PATCH 077/113] add help for displayed metrics --- utils/db_benchmark/benchmark_streamlit.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/utils/db_benchmark/benchmark_streamlit.py b/utils/db_benchmark/benchmark_streamlit.py index 8182a9a2f..e9c6a0795 100644 --- a/utils/db_benchmark/benchmark_streamlit.py +++ b/utils/db_benchmark/benchmark_streamlit.py @@ -87,10 +87,19 @@ def add_metrics(container, value_benchmark): "read+update": read_update, } + metric_help = { + "write": "Average write time for a context with from_dialog_len turns into a clean context storage.", + "read": "Average read time (dialog_len ranges between from_dialog_len and to_dialog_len).", + "update": "Average update time (dialog_len ranges between from_dialog_len and to_dialog_len).", + "read+update": "Sum of average read and update times." + " This metric is the time context_storage interface takes during each of the dialog turns." + } + for column_name, column in columns.items(): column.metric( column_name.title(), values[column_name], + help=metric_help[column_name] ) From 10e6f29b53013d6e80f1b732e311268a61515cd2 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Thu, 13 Jul 2023 15:25:49 +0300 Subject: [PATCH 078/113] reformat --- utils/db_benchmark/benchmark_streamlit.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/utils/db_benchmark/benchmark_streamlit.py b/utils/db_benchmark/benchmark_streamlit.py index e9c6a0795..4a7bae05a 100644 --- a/utils/db_benchmark/benchmark_streamlit.py +++ b/utils/db_benchmark/benchmark_streamlit.py @@ -92,15 +92,11 @@ def add_metrics(container, value_benchmark): "read": "Average read time (dialog_len ranges between from_dialog_len and to_dialog_len).", "update": "Average update time (dialog_len ranges between from_dialog_len and to_dialog_len).", "read+update": "Sum of average read and update times." - " This metric is the time context_storage interface takes during each of the dialog turns." + " This metric is the time context_storage interface takes during each of the dialog turns.", } for column_name, column in columns.items(): - column.metric( - column_name.title(), - values[column_name], - help=metric_help[column_name] - ) + column.metric(column_name.title(), values[column_name], help=metric_help[column_name]) st.sidebar.text(f"Benchmarks take {naturalsize(asizeof.asizeof(st.session_state['benchmarks']))} RAM") @@ -146,10 +142,7 @@ def edit_name_desc(): df_container.text(f"row {row}: changed {column} to '{column_value}'") edited_df = df_container.data_editor( - benchmark_list_df, - key="result_df", - disabled=("file", "uuid"), - on_change=edit_name_desc + benchmark_list_df, key="result_df", disabled=("file", "uuid"), on_change=edit_name_desc ) delist_container = st.container() From 8f23fd512d09a12b291295a0637b86237ec960ff Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Tue, 18 Jul 2023 14:29:10 +0300 Subject: [PATCH 079/113] preserve file order when delisting benchmarks --- utils/db_benchmark/benchmark_streamlit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/db_benchmark/benchmark_streamlit.py b/utils/db_benchmark/benchmark_streamlit.py index 4a7bae05a..68a52cd8e 100644 --- a/utils/db_benchmark/benchmark_streamlit.py +++ b/utils/db_benchmark/benchmark_streamlit.py @@ -158,8 +158,8 @@ def delist_benchmarks(): ] files_to_delist = edited_df.loc[edited_df["delete"]]["file"] - st.session_state["benchmark_files"] = list(set(st.session_state["benchmark_files"]) - set(files_to_delist)) for file in files_to_delist: + st.session_state["benchmark_files"].remove(file) del st.session_state["benchmarks"][file] delist_container.text(f"Delisted {file}") From 2b2c2bb05df5d8bac4a5b39b2b88e5ef747d5941 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Wed, 16 Aug 2023 20:18:14 +0300 Subject: [PATCH 080/113] remove typing as tp --- dff/utils/benchmark/context_storage.py | 41 +++++++++++++------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/dff/utils/benchmark/context_storage.py b/dff/utils/benchmark/context_storage.py index ef424e0e2..821ccb900 100644 --- a/dff/utils/benchmark/context_storage.py +++ b/dff/utils/benchmark/context_storage.py @@ -18,12 +18,11 @@ from uuid import uuid4 import pathlib from time import perf_counter -import typing as tp +from typing import Tuple, List, Dict, Union, Set, Literal from copy import deepcopy import json import importlib from statistics import mean -import typing_extensions as tp_e from pydantic import BaseModel, Field from pympler import asizeof @@ -34,7 +33,7 @@ from dff.script import Context, Message -def get_dict(dimensions: tp.Tuple[int, ...]): +def get_dict(dimensions: Tuple[int, ...]): """ Return misc dictionary build in `dimensions` dimensions. @@ -51,7 +50,7 @@ def get_dict(dimensions: tp.Tuple[int, ...]): the width of the dictionary at each level. """ - def _get_dict(dimensions: tp.Tuple[int, ...]): + def _get_dict(dimensions: Tuple[int, ...]): if len(dimensions) < 2: return "." * dimensions[0] return {i: _get_dict(dimensions[1:]) for i in range(dimensions[0])} @@ -64,7 +63,7 @@ def _get_dict(dimensions: tp.Tuple[int, ...]): return _get_dict((0, 0)) -def get_message(message_dimensions: tp.Tuple[int, ...]): +def get_message(message_dimensions: Tuple[int, ...]): """ Return message with a non-empty misc field. @@ -75,8 +74,8 @@ def get_message(message_dimensions: tp.Tuple[int, ...]): def get_context( dialog_len: int, - message_dimensions: tp.Tuple[int, ...], - misc_dimensions: tp.Tuple[int, ...], + message_dimensions: Tuple[int, ...], + misc_dimensions: Tuple[int, ...], ) -> Context: """ Return context with a non-empty misc, labels, requests, responses fields. @@ -100,7 +99,7 @@ def time_context_read_write( context: Context, context_num: int, context_updater=None, -) -> tp.Tuple[tp.List[float], tp.List[tp.Dict[int, float]], tp.List[tp.Dict[int, float]]]: +) -> Tuple[List[float], List[Dict[int, float]], List[Dict[int, float]]]: """ Benchmark "context_storage" by writing and reading `context` into it / from it `context_num` times. If context_updater is not None it is used to update `context` and use it to benchmark updating contexts @@ -141,9 +140,9 @@ def time_context_read_write( """ context_storage.clear() - write_times: tp.List[float] = [] - read_times: tp.List[tp.Dict[int, float]] = [] - update_times: tp.List[tp.Dict[int, float]] = [] + write_times: List[float] = [] + read_times: List[Dict[int, float]] = [] + update_times: List[Dict[int, float]] = [] for _ in tqdm(range(context_num), desc=f"Benchmarking context storage:{context_storage.full_path}", leave=False): tmp_context = deepcopy(context) @@ -229,12 +228,12 @@ class BenchmarkConfig(BaseModel): :py:meth:`~.BenchmarkConfig.get_context_updater` will return contexts increasing dialog len by `step_dialog_len`. """ - message_dimensions: tp.Tuple[int, ...] = (10, 10) + message_dimensions: Tuple[int, ...] = (10, 10) """ Dimensions of misc dictionaries inside messages. See :py:func:`~.get_message`. """ - misc_dimensions: tp.Tuple[int, ...] = (10, 10) + misc_dimensions: Tuple[int, ...] = (10, 10) """ Dimensions of misc dictionary. See :py:func:`~.get_dict`. @@ -431,8 +430,8 @@ def run(self): def save_results_to_file( - benchmark_cases: tp.List[BenchmarkCase], - file: tp.Union[str, pathlib.Path], + benchmark_cases: List[BenchmarkCase], + file: Union[str, pathlib.Path], name: str, description: str, exist_ok: bool = False, @@ -455,7 +454,7 @@ def save_results_to_file( """ with open(file, "w" if exist_ok else "x", encoding="utf-8") as fd: uuid = str(uuid4()) - result: tp.Dict[str, tp.Any] = { + result: Dict[str, Any] = { "name": name, "description": description, "uuid": uuid, @@ -470,11 +469,11 @@ def save_results_to_file( def benchmark_all( - file: tp.Union[str, pathlib.Path], + file: Union[str, pathlib.Path], name: str, description: str, db_uri: str, - benchmark_configs: tp.Dict[str, BenchmarkConfig], + benchmark_configs: Dict[str, BenchmarkConfig], exist_ok: bool = False, ): """ @@ -509,8 +508,8 @@ def benchmark_all( def report( - file: tp.Union[str, pathlib.Path], - display: tp.Set[tp_e.Literal["name", "desc", "config", "sizes", "metrics"]] = set({"name", "metrics"}), + file: Union[str, pathlib.Path], + display: Set[Literal["name", "desc", "config", "sizes", "metrics"]] = set({"name", "metrics"}), ): """ Print average results from a result file to stdout. @@ -532,7 +531,7 @@ def report( with open(file, "r", encoding="utf-8") as fd: file_contents = json.load(fd) - def get_benchmark_config_report(benchmark_config, sizes) -> tp.Tuple[str, str]: + def get_benchmark_config_report(benchmark_config, sizes) -> Tuple[str, str]: benchmark_config = BenchmarkConfig(**benchmark_config) starting_context_size = sizes["starting_context_size"] final_context_size = sizes["final_context_size"] From acb0557e6ee3b1806c3fde87373921fdcfb00198 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Wed, 16 Aug 2023 20:24:29 +0300 Subject: [PATCH 081/113] add type annotations --- dff/utils/benchmark/context_storage.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dff/utils/benchmark/context_storage.py b/dff/utils/benchmark/context_storage.py index 821ccb900..16a7f0707 100644 --- a/dff/utils/benchmark/context_storage.py +++ b/dff/utils/benchmark/context_storage.py @@ -18,7 +18,7 @@ from uuid import uuid4 import pathlib from time import perf_counter -from typing import Tuple, List, Dict, Union, Set, Literal +from typing import Tuple, List, Dict, Union, Set, Literal, Optional, Callable from copy import deepcopy import json import importlib @@ -98,7 +98,7 @@ def time_context_read_write( context_storage: DBContextStorage, context: Context, context_num: int, - context_updater=None, + context_updater: Optional[Callable[[Context], Optional[Context]]] = None, ) -> Tuple[List[float], List[Dict[int, float]], List[Dict[int, float]]]: """ Benchmark "context_storage" by writing and reading `context` into it / from it `context_num` times. @@ -242,7 +242,7 @@ class BenchmarkConfig(BaseModel): class Config: allow_mutation = False - def get_context(self): + def get_context(self) -> Context: """ Return context with `from_dialog_len`, `message_dimensions`, `misc_dimensions`. @@ -271,7 +271,7 @@ def sizes(self): "message_size": asizeof.asizeof(get_message(self.message_dimensions)), } - def get_context_updater(self): + def get_context_updater(self) -> Callable[[Context], Optional[Context]]: """ Return context updater function based on configuration. @@ -282,7 +282,7 @@ def get_context_updater(self): in which case None is returned. """ - def _context_updater(context: Context): + def _context_updater(context: Context) -> Optional[Context]: start_len = len(context.labels) if start_len + self.step_dialog_len < self.to_dialog_len: for i in range(start_len, start_len + self.step_dialog_len): From fbb3a5f362739cc60221945d061099d71d3863f2 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Wed, 16 Aug 2023 21:22:35 +0300 Subject: [PATCH 082/113] move model configuration to kwargs --- dff/utils/benchmark/context_storage.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/dff/utils/benchmark/context_storage.py b/dff/utils/benchmark/context_storage.py index 16a7f0707..6e0f40bc8 100644 --- a/dff/utils/benchmark/context_storage.py +++ b/dff/utils/benchmark/context_storage.py @@ -204,7 +204,7 @@ def db(self): return getattr(module, self.factory)(self.uri) -class BenchmarkConfig(BaseModel): +class BenchmarkConfig(BaseModel, frozen=True): """ Configuration for a benchmark. Sets dialog len, misc sizes, number of benchmarks. """ @@ -239,9 +239,6 @@ class BenchmarkConfig(BaseModel): See :py:func:`~.get_dict`. """ - class Config: - allow_mutation = False - def get_context(self) -> Context: """ Return context with `from_dialog_len`, `message_dimensions`, `misc_dimensions`. From bdc6277c085c79a003a0bb9f99d0332112eb21ab Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Wed, 16 Aug 2023 21:23:34 +0300 Subject: [PATCH 083/113] change misc key type to str --- dff/utils/benchmark/context_storage.py | 2 +- tests/utils/test_benchmark.py | 15 +++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/dff/utils/benchmark/context_storage.py b/dff/utils/benchmark/context_storage.py index 6e0f40bc8..628c9addd 100644 --- a/dff/utils/benchmark/context_storage.py +++ b/dff/utils/benchmark/context_storage.py @@ -53,7 +53,7 @@ def get_dict(dimensions: Tuple[int, ...]): def _get_dict(dimensions: Tuple[int, ...]): if len(dimensions) < 2: return "." * dimensions[0] - return {i: _get_dict(dimensions[1:]) for i in range(dimensions[0])} + return {str(i): _get_dict(dimensions[1:]) for i in range(dimensions[0])} if len(dimensions) > 1: return _get_dict(dimensions) diff --git a/tests/utils/test_benchmark.py b/tests/utils/test_benchmark.py index 3ff349ca3..b99a00591 100644 --- a/tests/utils/test_benchmark.py +++ b/tests/utils/test_benchmark.py @@ -18,9 +18,12 @@ def test_get_dict(): assert bm.get_dict(()) == {} - assert bm.get_dict((1,)) == {0: ""} - assert bm.get_dict((2, 3)) == {0: "...", 1: "..."} - assert bm.get_dict((2, 3, 4)) == {0: {0: "....", 1: "....", 2: "...."}, 1: {0: "....", 1: "....", 2: "...."}} + assert bm.get_dict((1,)) == {"0": ""} + assert bm.get_dict((2, 3)) == {"0": "...", "1": "..."} + assert bm.get_dict((2, 3, 4)) == { + "0": {"0": "....", "1": "....", "2": "...."}, + "1": {"0": "....", "1": "....", "2": "...."} + } def test_get_context(): @@ -28,9 +31,9 @@ def test_get_context(): assert context == bm.Context( id=context.id, labels={0: ("flow_0", "node_0"), 1: ("flow_1", "node_1")}, - requests={0: bm.Message(misc={0: ".."}), 1: bm.Message(misc={0: ".."})}, - responses={0: bm.Message(misc={0: ".."}), 1: bm.Message(misc={0: ".."})}, - misc={0: "...", 1: "..."}, + requests={0: bm.Message(misc={"0": ".."}), 1: bm.Message(misc={"0": ".."})}, + responses={0: bm.Message(misc={"0": ".."}), 1: bm.Message(misc={"0": ".."})}, + misc={"0": "...", "1": "..."}, ) From a36ddd3313ce8eb587ee09ffb5574ca4b0ff291d Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Wed, 16 Aug 2023 22:02:08 +0300 Subject: [PATCH 084/113] accept context factory for benchmark instead of context --- dff/utils/benchmark/context_storage.py | 23 +++++++++++------------ tests/utils/test_benchmark.py | 6 +++--- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/dff/utils/benchmark/context_storage.py b/dff/utils/benchmark/context_storage.py index 628c9addd..fcaa4cd71 100644 --- a/dff/utils/benchmark/context_storage.py +++ b/dff/utils/benchmark/context_storage.py @@ -19,7 +19,6 @@ import pathlib from time import perf_counter from typing import Tuple, List, Dict, Union, Set, Literal, Optional, Callable -from copy import deepcopy import json import importlib from statistics import mean @@ -96,19 +95,19 @@ def get_context( def time_context_read_write( context_storage: DBContextStorage, - context: Context, + context_factory: Callable[[], Context], context_num: int, context_updater: Optional[Callable[[Context], Optional[Context]]] = None, ) -> Tuple[List[float], List[Dict[int, float]], List[Dict[int, float]]]: """ - Benchmark "context_storage" by writing and reading `context` into it / from it `context_num` times. - If context_updater is not None it is used to update `context` and use it to benchmark updating contexts - (as well as reading updated contexts). + Benchmark "context_storage" by writing and reading `context`s generated by `context_factory` + into it / from it `context_num` times. + If context_updater is not None it is used to update `context`s and benchmark update operation. This function clears "context_storage" before and after execution. :param context_storage: Context storage to benchmark. - :param context: An instance of context which will be repeatedly written into context storage. + :param context_factory: A function that creates contexts which will be written into context storage. :param context_num: A number of times the context will be written and read. :param context_updater: None or a function. @@ -120,8 +119,7 @@ def time_context_read_write( For an example of such function, see :py:meth:`~.BenchmarkConfig.get_context_updater`. To avoid keeping many contexts in memory, - this function will be called with every argument `context_num` times (for each cycle), so it should - return the same updated context for the same context and shouldn't take a long time to update a context. + this function will be called repeatedly at least `context_num` times. :return: A tuple of 3 elements. @@ -130,12 +128,13 @@ def time_context_read_write( The second element -- a list of dictionaries with read times. Each dictionary maps from int to float. The key in the mapping is the `dialog_len` of the context and the values are the read times for the corresponding `dialog_len`. - If `context_updater` is None, all dictionaries will have only one key -- dialog length of `context`. + If `context_updater` is None, all dictionaries will have only one key -- + dialog length of the context returned by `context_factory`. Otherwise, the dictionaries will also have a key for each updated context. The third element -- a list of dictionaries with update times. Structurally the same as the second element, but none of the elements here have a key for - dialog_len of the `context`. + dialog_len of the context returned by `context_factory`. So if `context_updater` is None, all dictionaries will be empty. """ context_storage.clear() @@ -145,7 +144,7 @@ def time_context_read_write( update_times: List[Dict[int, float]] = [] for _ in tqdm(range(context_num), desc=f"Benchmarking context storage:{context_storage.full_path}", leave=False): - tmp_context = deepcopy(context) + tmp_context = context_factory() ctx_id = uuid4() @@ -386,7 +385,7 @@ def _run(self): try: write_times, read_times, update_times = time_context_read_write( self.db_factory.db(), - self.benchmark_config.get_context(), + self.benchmark_config.get_context, self.benchmark_config.context_num, context_updater=self.benchmark_config.get_context_updater(), ) diff --git a/tests/utils/test_benchmark.py b/tests/utils/test_benchmark.py index b99a00591..9192aea94 100644 --- a/tests/utils/test_benchmark.py +++ b/tests/utils/test_benchmark.py @@ -118,7 +118,7 @@ def test_time_context_read_write(context_storage): ) results = bm.time_context_read_write( - context_storage, config.get_context(), config.context_num, config.get_context_updater() + context_storage, config.get_context, config.context_num, config.get_context_updater() ) assert len(context_storage) == 0 @@ -152,7 +152,7 @@ def test_time_context_read_write_without_updates(context_storage): results = bm.time_context_read_write( context_storage, - config.get_context(), + config.get_context, config.context_num, None, ) @@ -163,7 +163,7 @@ def test_time_context_read_write_without_updates(context_storage): results = bm.time_context_read_write( context_storage, - config.get_context(), + config.get_context, config.context_num, config.get_context_updater(), # context updater returns None ) From 5d671a0619f40ec91051ba56e67e8b6522ca1f6a Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Thu, 17 Aug 2023 13:07:43 +0300 Subject: [PATCH 085/113] add type hints for test cases --- tests/utils/test_benchmark.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/utils/test_benchmark.py b/tests/utils/test_benchmark.py index 9192aea94..9f77a9e29 100644 --- a/tests/utils/test_benchmark.py +++ b/tests/utils/test_benchmark.py @@ -1,6 +1,6 @@ from copy import deepcopy import json -import pathlib +from pathlib import Path import pytest @@ -13,7 +13,7 @@ pytest.skip(reason="`dff[benchmark,tests]` not installed", allow_module_level=True) -ROOT_DIR = pathlib.Path(__file__).parent.parent.parent +ROOT_DIR = Path(__file__).parent.parent.parent def test_get_dict(): @@ -92,7 +92,7 @@ def test_context_updater_with_steps(): assert context == actual_context -def test_db_factory(tmp_path): +def test_db_factory(tmp_path: Path): factory = bm.DBFactory(uri=f"json://{tmp_path}/json.json") db = factory.db() @@ -101,13 +101,13 @@ def test_db_factory(tmp_path): @pytest.fixture -def context_storage(tmp_path) -> JSONContextStorage: +def context_storage(tmp_path: Path) -> JSONContextStorage: factory = bm.DBFactory(uri=f"json://{tmp_path}/json.json") return factory.db() -def test_time_context_read_write(context_storage): +def test_time_context_read_write(context_storage: JSONContextStorage): config = bm.BenchmarkConfig( context_num=5, from_dialog_len=1, @@ -140,7 +140,7 @@ def test_time_context_read_write(context_storage): assert all([isinstance(update_time, float) and update_time > 0 for update_time in update_item.values()]) -def test_time_context_read_write_without_updates(context_storage): +def test_time_context_read_write_without_updates(context_storage: JSONContextStorage): config = bm.BenchmarkConfig( context_num=5, from_dialog_len=1, @@ -220,7 +220,7 @@ def test_average_results(): bm.BenchmarkCase.set_average_results(benchmark) -def test_benchmark_case(tmp_path): +def test_benchmark_case(tmp_path: Path): case = bm.BenchmarkCase( name="", db_factory=bm.DBFactory(uri=f"json://{tmp_path}/json.json"), @@ -248,7 +248,7 @@ def test_benchmark_case(tmp_path): assert all([isinstance(update_time, float) and update_time > 0 for update_time in update_item.values()]) -def test_save_to_file(tmp_path): +def test_save_to_file(tmp_path: Path): with open(ROOT_DIR / "utils/db_benchmark/benchmark_schema.json", "r", encoding="utf-8") as fd: schema = json.load(fd) From 819dcae92400c9f25a558a1c0b3d8e5f8967b934 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Thu, 17 Aug 2023 13:22:17 +0300 Subject: [PATCH 086/113] uncomment dbs in benchmark_dbs.py --- utils/db_benchmark/benchmark_dbs.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/utils/db_benchmark/benchmark_dbs.py b/utils/db_benchmark/benchmark_dbs.py index 8c9deda96..6776b0f5f 100644 --- a/utils/db_benchmark/benchmark_dbs.py +++ b/utils/db_benchmark/benchmark_dbs.py @@ -19,15 +19,15 @@ sqlite_separator = "///" if system() == "Windows" else "////" dbs = { - # "JSON": "json://dbs/json.json", - # "Pickle": "pickle://dbs/pickle.pkl", + "JSON": "json://dbs/json.json", + "Pickle": "pickle://dbs/pickle.pkl", "Shelve": "shelve://dbs/shelve", "PostgreSQL": "postgresql+asyncpg://postgres:pass@localhost:5432/test", "MongoDB": "mongodb://admin:pass@localhost:27017/admin", "Redis": "redis://:pass@localhost:6379/0", "MySQL": "mysql+asyncmy://root:pass@localhost:3307/test", "SQLite": f"sqlite+aiosqlite:{sqlite_separator}{sqlite_file.absolute()}", - # "YDB": "grpc://localhost:2136/local", + "YDB": "grpc://localhost:2136/local", } # benchmark From 8e53b2ead636f064d6eab3954dc2509db9fb28c7 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Thu, 17 Aug 2023 13:53:43 +0300 Subject: [PATCH 087/113] add an explanation in tutorial --- tutorials/context_storages/8_db_benchmarking.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tutorials/context_storages/8_db_benchmarking.py b/tutorials/context_storages/8_db_benchmarking.py index 90e9a1e5d..ca79adb3d 100644 --- a/tutorials/context_storages/8_db_benchmarking.py +++ b/tutorials/context_storages/8_db_benchmarking.py @@ -108,6 +108,14 @@ }, ) +# %% [markdown] +""" +Running the cell above will create a file with benchmark results for every benchmarked DB: +""" + +# %% +list(tutorial_dir.iterdir()) + # %% [markdown] """ ## Viewing benchmark results From e50158c17b4fb91d68138d4381906e2eab280257 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Thu, 17 Aug 2023 13:54:01 +0300 Subject: [PATCH 088/113] remove benchmark_new_format.py --- utils/db_benchmark/benchmark_new_format.py | 39 ---------------------- utils/db_benchmark/benchmark_streamlit.py | 10 ------ 2 files changed, 49 deletions(-) delete mode 100644 utils/db_benchmark/benchmark_new_format.py diff --git a/utils/db_benchmark/benchmark_new_format.py b/utils/db_benchmark/benchmark_new_format.py deleted file mode 100644 index acc1c6585..000000000 --- a/utils/db_benchmark/benchmark_new_format.py +++ /dev/null @@ -1,39 +0,0 @@ -""" -New format ----------- -Converts old benchmark result files to new formats -""" -import json -import pathlib -import typing as tp - -benchmark_path = pathlib.Path("benchmarks") - - -def update_benchmark_file(benchmark_set_file: tp.Union[pathlib.Path, str]): - with open(benchmark_set_file, "r") as fd: - benchmark_set = json.load(fd) - - # move benchmarks from dict to list - if isinstance(benchmark_set.get("benchmarks"), dict): - benchmarks = benchmark_set.pop("benchmarks") - benchmark_set["benchmarks"] = list(benchmarks.values()) - - # update sizes - for benchmark in benchmark_set["benchmarks"]: - if "sizes" not in benchmark: - sizes = { - key: benchmark.pop(key) - for key in ("starting_context_size", "final_context_size", "misc_size", "message_size") - } - - benchmark["sizes"] = sizes - - with open(benchmark_set_file, "w") as fd: - json.dump(benchmark_set, fd) - - -if __name__ == "__main__": - for file in benchmark_path.iterdir(): - if file.suffix == ".json": - update_benchmark_file(file) diff --git a/utils/db_benchmark/benchmark_streamlit.py b/utils/db_benchmark/benchmark_streamlit.py index 68a52cd8e..6f3911fdf 100644 --- a/utils/db_benchmark/benchmark_streamlit.py +++ b/utils/db_benchmark/benchmark_streamlit.py @@ -29,11 +29,6 @@ import altair as alt import streamlit as st -try: - from benchmark_new_format import update_benchmark_file -except ImportError: - update_benchmark_file = None - st.set_page_config( page_title="DB benchmark", @@ -62,8 +57,6 @@ st.session_state["benchmarks"] = {} for file in st.session_state["benchmark_files"]: - if update_benchmark_file is not None: - update_benchmark_file(file) with open(file, "r", encoding="utf-8") as fd: st.session_state["benchmarks"][file] = json.load(fd) @@ -182,9 +175,6 @@ def _add_benchmark(benchmark_file, container): container.warning(f"File does not exists: {benchmark_file}") return - if update_benchmark_file is not None: - update_benchmark_file(benchmark_file) - with open(benchmark_file, "r", encoding="utf-8") as fd: file_contents = json.load(fd) From 55fdece99a7af967c2e6d090dc1dd6187d55a8aa Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Thu, 17 Aug 2023 15:18:48 +0300 Subject: [PATCH 089/113] add info messages --- utils/db_benchmark/benchmark_streamlit.py | 37 +++++++++++++++-------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/utils/db_benchmark/benchmark_streamlit.py b/utils/db_benchmark/benchmark_streamlit.py index 6f3911fdf..e5a75fac8 100644 --- a/utils/db_benchmark/benchmark_streamlit.py +++ b/utils/db_benchmark/benchmark_streamlit.py @@ -120,6 +120,8 @@ def add_metrics(container, value_benchmark): df_container = st.container() + df_container.info("In the table below you can view all your files with benchmark results as well as delete them.") + def edit_name_desc(): edited_rows = st.session_state["result_df"]["edited_rows"] @@ -135,31 +137,30 @@ def edit_name_desc(): df_container.text(f"row {row}: changed {column} to '{column_value}'") edited_df = df_container.data_editor( - benchmark_list_df, key="result_df", disabled=("file", "uuid"), on_change=edit_name_desc + benchmark_list_df, key="result_df", disabled=("file", "uuid"), on_change=edit_name_desc, ) - delist_container = st.container() - delist_container.divider() + delete_container = st.container() - def delist_benchmarks(): - delisted_sets = [ + def delete_benchmarks(): + deleted_sets = [ f"{name} ({uuid})" for name, uuid in edited_df.loc[edited_df["delete"]][["name", "uuid"]].values ] st.session_state["compare"] = [ - item for item in st.session_state["compare"] if item["benchmark_set"] not in delisted_sets + item for item in st.session_state["compare"] if item["benchmark_set"] not in deleted_sets ] - files_to_delist = edited_df.loc[edited_df["delete"]]["file"] - for file in files_to_delist: + files_to_delete = edited_df.loc[edited_df["delete"]]["file"] + for file in files_to_delete: st.session_state["benchmark_files"].remove(file) del st.session_state["benchmarks"][file] - delist_container.text(f"Delisted {file}") + delete_container.text(f"Deleted {file}") with open(BENCHMARK_RESULTS_FILES, "w", encoding="utf-8") as fd: json.dump(list(st.session_state["benchmark_files"]), fd) - delist_container.button(label="Delist selected benchmark sets", on_click=delist_benchmarks) + delete_container.button(label="Delete selected benchmark sets", on_click=delete_benchmarks) def _add_benchmark(benchmark_file, container): benchmark_file = str(benchmark_file) @@ -192,6 +193,8 @@ def _add_benchmark(benchmark_file, container): st.divider() + st.info("Below you can add your benchmark files (either from local files or via uploading).") + add_container, add_from_dir_container = st.columns(2) add_container.text_input(label="Benchmark set file", key="add_benchmark_file") @@ -211,8 +214,6 @@ def add_from_dir(): add_from_dir_container.button("Add all files from directory", on_click=add_from_dir) - st.divider() - upload_container = st.container() def process_uploaded_files(): @@ -305,9 +306,13 @@ def add_results_to_compare_tab(): else: st.session_state["compare"].remove(compare_item) + item_in_compare = compare_item not in st.session_state["compare"] + compare.button( - "Add to Compare" if compare_item not in st.session_state["compare"] else "Remove from Compare", + "Add to Compare" if item_in_compare else "Remove from Compare", on_click=add_results_to_compare_tab, + help="Add current benchmark to the 'Compare' tab." if item_in_compare + else "Remove current benchmark from the 'Compare' tab." ) select_graph, graph = st.columns([1, 3]) @@ -354,12 +359,16 @@ def add_results_to_compare_tab(): with compare_tab: df = pd.DataFrame(st.session_state["compare"]) + st.info("Here you can compare metrics of different benchmarks. Add them here via the 'View' tab.") + if not df.empty: st.dataframe( df.style.highlight_min( axis=0, subset=["write", "read", "update", "read+update"], props="background-color:green;" ).highlight_max(axis=0, subset=["write", "read", "update", "read+update"], props="background-color:red;") ) + else: + st.warning("Currently, there are no benchmarks to compare.") ############################################################################### # Mass compare tab @@ -367,6 +376,8 @@ def add_results_to_compare_tab(): ############################################################################### with mass_compare_tab: + st.info("Here you can compare benchmarks inside of a specific set.") + sets = { f"{benchmark_set['name']} ({benchmark_set['uuid']})": benchmark_set for benchmark_set in st.session_state["benchmarks"].values() From 62a33d916842283fe3674d125374186af273b2fd Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Thu, 17 Aug 2023 19:52:08 +0300 Subject: [PATCH 090/113] randomize strings returned by `get_dict` --- dff/utils/benchmark/context_storage.py | 5 ++++- tests/utils/test_benchmark.py | 23 +++++++++++++++-------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/dff/utils/benchmark/context_storage.py b/dff/utils/benchmark/context_storage.py index fcaa4cd71..d549271d7 100644 --- a/dff/utils/benchmark/context_storage.py +++ b/dff/utils/benchmark/context_storage.py @@ -22,6 +22,8 @@ import json import importlib from statistics import mean +import string +import random from pydantic import BaseModel, Field from pympler import asizeof @@ -51,7 +53,8 @@ def get_dict(dimensions: Tuple[int, ...]): def _get_dict(dimensions: Tuple[int, ...]): if len(dimensions) < 2: - return "." * dimensions[0] + # get a random string of length dimensions[0] + return "".join(random.choice(string.printable) for _ in range(dimensions[0])) return {str(i): _get_dict(dimensions[1:]) for i in range(dimensions[0])} if len(dimensions) > 1: diff --git a/tests/utils/test_benchmark.py b/tests/utils/test_benchmark.py index 9f77a9e29..ff3f20508 100644 --- a/tests/utils/test_benchmark.py +++ b/tests/utils/test_benchmark.py @@ -1,6 +1,7 @@ from copy import deepcopy import json from pathlib import Path +import random import pytest @@ -17,27 +18,31 @@ def test_get_dict(): + random.seed(42) assert bm.get_dict(()) == {} assert bm.get_dict((1,)) == {"0": ""} - assert bm.get_dict((2, 3)) == {"0": "...", "1": "..."} + assert bm.get_dict((2, 3)) == {"0": ">e3", "1": " zv"} assert bm.get_dict((2, 3, 4)) == { - "0": {"0": "....", "1": "....", "2": "...."}, - "1": {"0": "....", "1": "....", "2": "...."} + "0": {"0": "sh d", "1": "] (b", "2": ".S43"}, + "1": {"0": "brt#", "1": ":3*p", "2": "|@`("} } def test_get_context(): + random.seed(42) context = bm.get_context(2, (1, 2), (2, 3)) assert context == bm.Context( id=context.id, labels={0: ("flow_0", "node_0"), 1: ("flow_1", "node_1")}, - requests={0: bm.Message(misc={"0": ".."}), 1: bm.Message(misc={"0": ".."})}, - responses={0: bm.Message(misc={"0": ".."}), 1: bm.Message(misc={"0": ".."})}, - misc={"0": "...", "1": "..."}, + requests={0: bm.Message(misc={"0": ">e"}), 1: bm.Message(misc={"0": "3 "})}, + responses={0: bm.Message(misc={"0": "zv"}), 1: bm.Message(misc={"0": "sh"})}, + misc={"0": " d]", "1": " (b"}, ) -def test_benchmark_config(): +def test_benchmark_config(monkeypatch: pytest.MonkeyPatch): + monkeypatch.setattr(random, "choice", lambda x: ".") + config = bm.BenchmarkConfig( from_dialog_len=1, to_dialog_len=5, message_dimensions=(2, 2), misc_dimensions=(3, 3, 3) ) @@ -69,7 +74,9 @@ def test_benchmark_config(): assert context == actual_context -def test_context_updater_with_steps(): +def test_context_updater_with_steps(monkeypatch: pytest.MonkeyPatch): + monkeypatch.setattr(random, "choice", lambda x: ".") + config = bm.BenchmarkConfig( from_dialog_len=1, to_dialog_len=11, step_dialog_len=3, message_dimensions=(2, 2), misc_dimensions=(3, 3, 3) ) From c1f6fbeff4ed28fb017eba761905dbcc34045200 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Thu, 17 Aug 2023 23:17:51 +0300 Subject: [PATCH 091/113] add comments --- utils/db_benchmark/benchmark_dbs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/db_benchmark/benchmark_dbs.py b/utils/db_benchmark/benchmark_dbs.py index 6776b0f5f..abea9c130 100644 --- a/utils/db_benchmark/benchmark_dbs.py +++ b/utils/db_benchmark/benchmark_dbs.py @@ -12,7 +12,7 @@ ) -# create dir and files +# these files are required for file-based dbs pathlib.Path("dbs").mkdir(exist_ok=True) sqlite_file = pathlib.Path("dbs/sqlite.db") sqlite_file.touch(exist_ok=True) @@ -30,7 +30,7 @@ "YDB": "grpc://localhost:2136/local", } -# benchmark +# benchmarks will be saved to this directory benchmark_dir = pathlib.Path("benchmarks") benchmark_dir.mkdir(exist_ok=True) From 837e285e66d5c8b9bfbe5cb28597904c924c860b Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Thu, 17 Aug 2023 23:33:12 +0300 Subject: [PATCH 092/113] rename benchmark.context_storage to db_benchmark.benchmark --- dff/utils/benchmark/__init__.py | 3 --- dff/utils/db_benchmark/__init__.py | 3 +++ .../benchmark.py} | 0 tests/utils/test_benchmark.py | 2 +- tutorials/context_storages/8_db_benchmarking.py | 14 +++++++------- utils/db_benchmark/benchmark_dbs.py | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) delete mode 100644 dff/utils/benchmark/__init__.py create mode 100644 dff/utils/db_benchmark/__init__.py rename dff/utils/{benchmark/context_storage.py => db_benchmark/benchmark.py} (100%) diff --git a/dff/utils/benchmark/__init__.py b/dff/utils/benchmark/__init__.py deleted file mode 100644 index 0e51c9725..000000000 --- a/dff/utils/benchmark/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -*- coding: utf-8 -*- -# flake8: noqa: F401 -from dff.utils.benchmark.context_storage import report as context_storage_benchmark_report diff --git a/dff/utils/db_benchmark/__init__.py b/dff/utils/db_benchmark/__init__.py new file mode 100644 index 000000000..2c49f662e --- /dev/null +++ b/dff/utils/db_benchmark/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +# flake8: noqa: F401 +from dff.utils.db_benchmark.benchmark import report as context_storage_benchmark_report diff --git a/dff/utils/benchmark/context_storage.py b/dff/utils/db_benchmark/benchmark.py similarity index 100% rename from dff/utils/benchmark/context_storage.py rename to dff/utils/db_benchmark/benchmark.py diff --git a/tests/utils/test_benchmark.py b/tests/utils/test_benchmark.py index ff3f20508..f6efc5206 100644 --- a/tests/utils/test_benchmark.py +++ b/tests/utils/test_benchmark.py @@ -8,7 +8,7 @@ try: from jsonschema import validate - import dff.utils.benchmark.context_storage as bm + import dff.utils.db_benchmark.benchmark as bm from dff.context_storages import JSONContextStorage except ImportError: pytest.skip(reason="`dff[benchmark,tests]` not installed", allow_module_level=True) diff --git a/tutorials/context_storages/8_db_benchmarking.py b/tutorials/context_storages/8_db_benchmarking.py index ca79adb3d..44ab8ace5 100644 --- a/tutorials/context_storages/8_db_benchmarking.py +++ b/tutorials/context_storages/8_db_benchmarking.py @@ -4,7 +4,7 @@ This tutorial shows how to benchmark context storages. -For more info see [API reference](../apiref/dff.utils.benchmark.context_storage.rst). +For more info see [API reference](../apiref/dff.utils.db_benchmark.benchmark.rst). """ # %% @@ -12,7 +12,7 @@ from platform import system import tempfile -import dff.utils.benchmark.context_storage as benchmark +import dff.utils.db_benchmark.benchmark as benchmark # %% [markdown] """ @@ -49,11 +49,11 @@ For that there exist two functions: [benchmark_all]( -../apiref/dff.utils.benchmark.context_storage.rst#dff.utils.benchmark.context_storage.benchmark_all +../apiref/dff.utils.db_benchmark.benchmark.rst#dff.utils.db_benchmark.benchmark.benchmark_all ) and [save_results_to_file]( -../apiref/dff.utils.benchmark.context_storage.rst#dff.utils.benchmark.context_storage.save_results_to_file +../apiref/dff.utils.db_benchmark.benchmark.rst#dff.utils.db_benchmark.benchmark.save_results_to_file ). Note: context storages passed into these functions will be cleared. @@ -62,14 +62,14 @@ The first one is a higher-level wrapper of the second one. The first function accepts [BenchmarkCases]( -../apiref/dff.utils.benchmark.context_storage.rst#dff.utils.benchmark.context_storage.BenchmarkCase +../apiref/dff.utils.db_benchmark.benchmark.rst#dff.utils.db_benchmark.benchmark.BenchmarkCase ) which configure databases that are being benchmark and configurations of the benchmarks. The second function accepts only a single URI for the database and several benchmark configurations. So, the second function is simpler to use, while the first function allows for more configuration (e.g. having different databases benchmarked in a single file). Both function use [BenchmarkConfig]( -../apiref/dff.utils.benchmark.context_storage.rst#dff.utils.benchmark.context_storage.BenchmarkConfig +../apiref/dff.utils.db_benchmark.benchmark.rst#dff.utils.db_benchmark.benchmark.BenchmarkConfig ) to configure benchmark behaviour. It has several parameters: @@ -121,7 +121,7 @@ ## Viewing benchmark results Now that the results are saved to a file you can either view them using [report]( -../apiref/dff.utils.benchmark.context_storage.rst#dff.utils.benchmark.context_storage.report +../apiref/dff.utils.db_benchmark.benchmark.rst#dff.utils.db_benchmark.benchmark.report ) function or the [streamlit app]( ../../../utils/db_benchmark/benchmark_streamlit.py ). diff --git a/utils/db_benchmark/benchmark_dbs.py b/utils/db_benchmark/benchmark_dbs.py index abea9c130..0cc3da5f4 100644 --- a/utils/db_benchmark/benchmark_dbs.py +++ b/utils/db_benchmark/benchmark_dbs.py @@ -6,7 +6,7 @@ import pathlib from platform import system -from dff.utils.benchmark.context_storage import ( +from dff.utils.db_benchmark.benchmark import ( BenchmarkConfig, benchmark_all, ) From 85df6d1e8caaccaacd2f805d9a374cd2beb1fa50 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Thu, 17 Aug 2023 23:44:18 +0300 Subject: [PATCH 093/113] move report function to a separate module --- dff/utils/db_benchmark/__init__.py | 2 +- dff/utils/db_benchmark/benchmark.py | 85 +----------------- dff/utils/db_benchmark/report.py | 89 +++++++++++++++++++ .../context_storages/8_db_benchmarking.py | 5 +- 4 files changed, 97 insertions(+), 84 deletions(-) create mode 100644 dff/utils/db_benchmark/report.py diff --git a/dff/utils/db_benchmark/__init__.py b/dff/utils/db_benchmark/__init__.py index 2c49f662e..a77fe9d22 100644 --- a/dff/utils/db_benchmark/__init__.py +++ b/dff/utils/db_benchmark/__init__.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- # flake8: noqa: F401 -from dff.utils.db_benchmark.benchmark import report as context_storage_benchmark_report +from dff.utils.db_benchmark.report import report diff --git a/dff/utils/db_benchmark/benchmark.py b/dff/utils/db_benchmark/benchmark.py index d549271d7..b9a774c68 100644 --- a/dff/utils/db_benchmark/benchmark.py +++ b/dff/utils/db_benchmark/benchmark.py @@ -12,13 +12,14 @@ Wrappers use :py:class:`~.BenchmarkConfig` to configure benchmarks. -To view files generated by :py:func:`~.save_results_to_file` use either :py:func:`~.report` or +To view files generated by :py:func:`~.save_results_to_file` use either +:py:func:`~dff.utils.db_benchmark.report.report` or `streamlit app <../../../utils/db_benchmark/benchmark_streamlit.py>`_. """ from uuid import uuid4 import pathlib from time import perf_counter -from typing import Tuple, List, Dict, Union, Set, Literal, Optional, Callable +from typing import Tuple, List, Dict, Union, Optional, Callable import json import importlib from statistics import mean @@ -28,7 +29,6 @@ from pydantic import BaseModel, Field from pympler import asizeof from tqdm.auto import tqdm -from humanize import naturalsize from dff.context_storages import DBContextStorage from dff.script import Context, Message @@ -441,7 +441,7 @@ def save_results_to_file( Result are saved in json format with this schema: `utils/db_benchmark/benchmark_schema.json <../../../utils/db_benchmark/benchmark_schema.json>`_. - Files created by this function cen be viewed either by using :py:func:`~.report` or + Files created by this function cen be viewed either by using :py:func:`~dff.utils.db_benchmark.report.report` or streamlit app located in the utils directory: `utils/db_benchmark/benchmark_streamlit.py <../../../utils/db_benchmark/benchmark_streamlit.py>`_. @@ -504,80 +504,3 @@ def benchmark_all( description, exist_ok=exist_ok, ) - - -def report( - file: Union[str, pathlib.Path], - display: Set[Literal["name", "desc", "config", "sizes", "metrics"]] = set({"name", "metrics"}), -): - """ - Print average results from a result file to stdout. - - Printed stats contain benchmark configs, object sizes, average benchmark values for successful cases and - exception message for unsuccessful cases. - - :param file: File with benchmark results generated by :py:func:`~.save_results_to_file`. - :param display: - A set of objects to display in results. - Values allowed inside the set: - - - "name" -- displays the name of the benchmark case. - - "desc" -- displays the description of the benchmark case. - - "config" -- displays the config of the benchmark case. - - "sizes" -- displays size stats for the config. - - "metrics" -- displays average write, read, update read+update times. - """ - with open(file, "r", encoding="utf-8") as fd: - file_contents = json.load(fd) - - def get_benchmark_config_report(benchmark_config, sizes) -> Tuple[str, str]: - benchmark_config = BenchmarkConfig(**benchmark_config) - starting_context_size = sizes["starting_context_size"] - final_context_size = sizes["final_context_size"] - misc_size = sizes["misc_size"] - message_size = sizes["message_size"] - - return ( - f"Number of contexts: {benchmark_config.context_num}\n" - f"From dialog len: {benchmark_config.from_dialog_len}\n" - f"To dialog len: {benchmark_config.to_dialog_len}\n" - f"Step dialog len: {benchmark_config.step_dialog_len}\n" - f"Message misc dimensions: {benchmark_config.message_dimensions}\n" - f"Misc dimensions: {benchmark_config.misc_dimensions}\n", - f"Size of misc field: {misc_size} ({naturalsize(misc_size, gnu=True)})\n" - f"Size of one message: {message_size} ({naturalsize(message_size, gnu=True)})\n" - f"Starting context size: {starting_context_size} ({naturalsize(starting_context_size, gnu=True)})\n" - f"Final context size: {final_context_size} ({naturalsize(final_context_size, gnu=True)})", - ) - - sep = "-" * 80 - - report_result = "\n".join([sep, file_contents["name"], sep, file_contents["description"], sep, ""]) - - for benchmark in file_contents["benchmarks"]: - config, sizes = get_benchmark_config_report(benchmark["benchmark_config"], benchmark["sizes"]) - - reported_values = { - "name": benchmark["name"], - "desc": benchmark["description"], - "config": config, - "sizes": sizes, - "metrics": "".join( - [ - f"{metric.title() + ': ' + str(benchmark['average_results']['pretty_' + metric]):20}" - if benchmark["success"] - else benchmark["result"] - for metric in ("write", "read", "update", "read+update") - ] - ), - } - - result = [] - for value_name, value in reported_values.items(): - if value_name in display: - result.append(value) - result.append("") - - report_result += f"\n{sep}\n".join(result) - - print(report_result, end="") diff --git a/dff/utils/db_benchmark/report.py b/dff/utils/db_benchmark/report.py new file mode 100644 index 000000000..558870e9b --- /dev/null +++ b/dff/utils/db_benchmark/report.py @@ -0,0 +1,89 @@ +""" +Report +-------- +This method contains a function to print benchmark results to console. +""" +import pathlib +from typing import Union, Set, Literal, Tuple +import json + +from humanize import naturalsize + +from dff.utils.db_benchmark.benchmark import BenchmarkConfig + + +def report( + file: Union[str, pathlib.Path], + display: Set[Literal["name", "desc", "config", "sizes", "metrics"]] = set({"name", "metrics"}), +): + """ + Print average results from a result file to stdout. + + Printed stats contain benchmark configs, object sizes, average benchmark values for successful cases and + exception message for unsuccessful cases. + + :param file: File with benchmark results generated by :py:func:`~.save_results_to_file`. + :param display: + A set of objects to display in results. + Values allowed inside the set: + + - "name" -- displays the name of the benchmark case. + - "desc" -- displays the description of the benchmark case. + - "config" -- displays the config of the benchmark case. + - "sizes" -- displays size stats for the config. + - "metrics" -- displays average write, read, update read+update times. + """ + with open(file, "r", encoding="utf-8") as fd: + file_contents = json.load(fd) + + def get_benchmark_config_report(benchmark_config, sizes) -> Tuple[str, str]: + benchmark_config = BenchmarkConfig(**benchmark_config) + starting_context_size = sizes["starting_context_size"] + final_context_size = sizes["final_context_size"] + misc_size = sizes["misc_size"] + message_size = sizes["message_size"] + + return ( + f"Number of contexts: {benchmark_config.context_num}\n" + f"From dialog len: {benchmark_config.from_dialog_len}\n" + f"To dialog len: {benchmark_config.to_dialog_len}\n" + f"Step dialog len: {benchmark_config.step_dialog_len}\n" + f"Message misc dimensions: {benchmark_config.message_dimensions}\n" + f"Misc dimensions: {benchmark_config.misc_dimensions}\n", + f"Size of misc field: {misc_size} ({naturalsize(misc_size, gnu=True)})\n" + f"Size of one message: {message_size} ({naturalsize(message_size, gnu=True)})\n" + f"Starting context size: {starting_context_size} ({naturalsize(starting_context_size, gnu=True)})\n" + f"Final context size: {final_context_size} ({naturalsize(final_context_size, gnu=True)})", + ) + + sep = "-" * 80 + + report_result = "\n".join([sep, file_contents["name"], sep, file_contents["description"], sep, ""]) + + for benchmark in file_contents["benchmarks"]: + config, sizes = get_benchmark_config_report(benchmark["benchmark_config"], benchmark["sizes"]) + + reported_values = { + "name": benchmark["name"], + "desc": benchmark["description"], + "config": config, + "sizes": sizes, + "metrics": "".join( + [ + f"{metric.title() + ': ' + str(benchmark['average_results']['pretty_' + metric]):20}" + if benchmark["success"] + else benchmark["result"] + for metric in ("write", "read", "update", "read+update") + ] + ), + } + + result = [] + for value_name, value in reported_values.items(): + if value_name in display: + result.append(value) + result.append("") + + report_result += f"\n{sep}\n".join(result) + + print(report_result, end="") diff --git a/tutorials/context_storages/8_db_benchmarking.py b/tutorials/context_storages/8_db_benchmarking.py index 44ab8ace5..b6582c53a 100644 --- a/tutorials/context_storages/8_db_benchmarking.py +++ b/tutorials/context_storages/8_db_benchmarking.py @@ -13,6 +13,7 @@ import tempfile import dff.utils.db_benchmark.benchmark as benchmark +from dff.utils.db_benchmark.report import report # %% [markdown] """ @@ -121,7 +122,7 @@ ## Viewing benchmark results Now that the results are saved to a file you can either view them using [report]( -../apiref/dff.utils.db_benchmark.benchmark.rst#dff.utils.db_benchmark.benchmark.report +../apiref/dff.utils.db_benchmark.report.rst#dff.utils.db_benchmark.report.report ) function or the [streamlit app]( ../../../utils/db_benchmark/benchmark_streamlit.py ). @@ -141,4 +142,4 @@ """ # %% -benchmark.report(file=tutorial_dir / "Shelve.json", display={"name", "config", "metrics"}) +report(file=tutorial_dir / "Shelve.json", display={"name", "config", "metrics"}) From 7054c1bdfd1d9d95f15907aa083e94f5396cb451 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Thu, 17 Aug 2023 23:52:40 +0300 Subject: [PATCH 094/113] import Path instead of pathlib --- dff/utils/db_benchmark/benchmark.py | 6 +++--- dff/utils/db_benchmark/report.py | 4 ++-- tutorials/context_storages/8_db_benchmarking.py | 4 ++-- utils/db_benchmark/benchmark_dbs.py | 8 ++++---- utils/db_benchmark/benchmark_streamlit.py | 3 +-- 5 files changed, 12 insertions(+), 13 deletions(-) diff --git a/dff/utils/db_benchmark/benchmark.py b/dff/utils/db_benchmark/benchmark.py index b9a774c68..d720c332b 100644 --- a/dff/utils/db_benchmark/benchmark.py +++ b/dff/utils/db_benchmark/benchmark.py @@ -17,7 +17,7 @@ `streamlit app <../../../utils/db_benchmark/benchmark_streamlit.py>`_. """ from uuid import uuid4 -import pathlib +from pathlib import Path from time import perf_counter from typing import Tuple, List, Dict, Union, Optional, Callable import json @@ -430,7 +430,7 @@ def run(self): def save_results_to_file( benchmark_cases: List[BenchmarkCase], - file: Union[str, pathlib.Path], + file: Union[str, Path], name: str, description: str, exist_ok: bool = False, @@ -468,7 +468,7 @@ def save_results_to_file( def benchmark_all( - file: Union[str, pathlib.Path], + file: Union[str, Path], name: str, description: str, db_uri: str, diff --git a/dff/utils/db_benchmark/report.py b/dff/utils/db_benchmark/report.py index 558870e9b..6853c33e1 100644 --- a/dff/utils/db_benchmark/report.py +++ b/dff/utils/db_benchmark/report.py @@ -3,7 +3,7 @@ -------- This method contains a function to print benchmark results to console. """ -import pathlib +from pathlib import Path from typing import Union, Set, Literal, Tuple import json @@ -13,7 +13,7 @@ def report( - file: Union[str, pathlib.Path], + file: Union[str, Path], display: Set[Literal["name", "desc", "config", "sizes", "metrics"]] = set({"name", "metrics"}), ): """ diff --git a/tutorials/context_storages/8_db_benchmarking.py b/tutorials/context_storages/8_db_benchmarking.py index b6582c53a..b0fd3d068 100644 --- a/tutorials/context_storages/8_db_benchmarking.py +++ b/tutorials/context_storages/8_db_benchmarking.py @@ -8,7 +8,7 @@ """ # %% -import pathlib +from pathlib import Path from platform import system import tempfile @@ -22,7 +22,7 @@ # %% # this cell is only required for pickle, shelve and sqlite databases -tutorial_dir = pathlib.Path(tempfile.mkdtemp()) +tutorial_dir = Path(tempfile.mkdtemp()) db_path = tutorial_dir / "dbs" db_path.mkdir() sqlite_file = db_path / "sqlite.db" diff --git a/utils/db_benchmark/benchmark_dbs.py b/utils/db_benchmark/benchmark_dbs.py index 0cc3da5f4..b05e41bcd 100644 --- a/utils/db_benchmark/benchmark_dbs.py +++ b/utils/db_benchmark/benchmark_dbs.py @@ -3,7 +3,7 @@ ------------- This module contains config presets for benchmarks. """ -import pathlib +from pathlib import Path from platform import system from dff.utils.db_benchmark.benchmark import ( @@ -13,8 +13,8 @@ # these files are required for file-based dbs -pathlib.Path("dbs").mkdir(exist_ok=True) -sqlite_file = pathlib.Path("dbs/sqlite.db") +Path("dbs").mkdir(exist_ok=True) +sqlite_file = Path("dbs/sqlite.db") sqlite_file.touch(exist_ok=True) sqlite_separator = "///" if system() == "Windows" else "////" @@ -31,7 +31,7 @@ } # benchmarks will be saved to this directory -benchmark_dir = pathlib.Path("benchmarks") +benchmark_dir = Path("benchmarks") benchmark_dir.mkdir(exist_ok=True) diff --git a/utils/db_benchmark/benchmark_streamlit.py b/utils/db_benchmark/benchmark_streamlit.py index e5a75fac8..41b9a93b2 100644 --- a/utils/db_benchmark/benchmark_streamlit.py +++ b/utils/db_benchmark/benchmark_streamlit.py @@ -19,7 +19,6 @@ Benchmark result files added via this module are not changed (only read). """ import json -import pathlib from pathlib import Path from uuid import uuid4 @@ -207,7 +206,7 @@ def add_benchmark(): add_from_dir_container.text_input(label="Directory with benchmark files", key="add_from_dir") def add_from_dir(): - dir_path = pathlib.Path(st.session_state["add_from_dir"]) + dir_path = Path(st.session_state["add_from_dir"]) if dir_path.is_dir(): for file in dir_path.iterdir(): _add_benchmark(file, add_from_dir_container) From 9cf1ef0d1b22ec2f375149b1b0b032744ba40a95 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Thu, 17 Aug 2023 23:56:30 +0300 Subject: [PATCH 095/113] fix doc --- dff/utils/db_benchmark/report.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dff/utils/db_benchmark/report.py b/dff/utils/db_benchmark/report.py index 6853c33e1..54206b09b 100644 --- a/dff/utils/db_benchmark/report.py +++ b/dff/utils/db_benchmark/report.py @@ -22,7 +22,9 @@ def report( Printed stats contain benchmark configs, object sizes, average benchmark values for successful cases and exception message for unsuccessful cases. - :param file: File with benchmark results generated by :py:func:`~.save_results_to_file`. + :param file: + File with benchmark results generated by + :py:func:`~dff.utils.db_benchmark.benchmark.save_results_to_file`. :param display: A set of objects to display in results. Values allowed inside the set: From 75849028672ad578b9b29ce2637c2f9ccc36c230 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Fri, 18 Aug 2023 00:00:37 +0300 Subject: [PATCH 096/113] reformat --- dff/utils/db_benchmark/benchmark.py | 2 +- tests/utils/test_benchmark.py | 2 +- utils/db_benchmark/benchmark_streamlit.py | 10 +++++++--- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/dff/utils/db_benchmark/benchmark.py b/dff/utils/db_benchmark/benchmark.py index d720c332b..7f0f66329 100644 --- a/dff/utils/db_benchmark/benchmark.py +++ b/dff/utils/db_benchmark/benchmark.py @@ -19,7 +19,7 @@ from uuid import uuid4 from pathlib import Path from time import perf_counter -from typing import Tuple, List, Dict, Union, Optional, Callable +from typing import Tuple, List, Dict, Union, Optional, Callable, Any import json import importlib from statistics import mean diff --git a/tests/utils/test_benchmark.py b/tests/utils/test_benchmark.py index f6efc5206..86527f826 100644 --- a/tests/utils/test_benchmark.py +++ b/tests/utils/test_benchmark.py @@ -24,7 +24,7 @@ def test_get_dict(): assert bm.get_dict((2, 3)) == {"0": ">e3", "1": " zv"} assert bm.get_dict((2, 3, 4)) == { "0": {"0": "sh d", "1": "] (b", "2": ".S43"}, - "1": {"0": "brt#", "1": ":3*p", "2": "|@`("} + "1": {"0": "brt#", "1": ":3*p", "2": "|@`("}, } diff --git a/utils/db_benchmark/benchmark_streamlit.py b/utils/db_benchmark/benchmark_streamlit.py index 41b9a93b2..ca7e4306a 100644 --- a/utils/db_benchmark/benchmark_streamlit.py +++ b/utils/db_benchmark/benchmark_streamlit.py @@ -136,7 +136,10 @@ def edit_name_desc(): df_container.text(f"row {row}: changed {column} to '{column_value}'") edited_df = df_container.data_editor( - benchmark_list_df, key="result_df", disabled=("file", "uuid"), on_change=edit_name_desc, + benchmark_list_df, + key="result_df", + disabled=("file", "uuid"), + on_change=edit_name_desc, ) delete_container = st.container() @@ -310,8 +313,9 @@ def add_results_to_compare_tab(): compare.button( "Add to Compare" if item_in_compare else "Remove from Compare", on_click=add_results_to_compare_tab, - help="Add current benchmark to the 'Compare' tab." if item_in_compare - else "Remove current benchmark from the 'Compare' tab." + help="Add current benchmark to the 'Compare' tab." + if item_in_compare + else "Remove current benchmark from the 'Compare' tab.", ) select_graph, graph = st.columns([1, 3]) From aa65bc71e57545b2177426725dd305ddbf185111 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Fri, 18 Aug 2023 00:04:01 +0300 Subject: [PATCH 097/113] add imports to __init__.py --- dff/utils/db_benchmark/__init__.py | 8 ++++++++ tutorials/context_storages/8_db_benchmarking.py | 5 ++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/dff/utils/db_benchmark/__init__.py b/dff/utils/db_benchmark/__init__.py index a77fe9d22..d98df8afa 100644 --- a/dff/utils/db_benchmark/__init__.py +++ b/dff/utils/db_benchmark/__init__.py @@ -1,3 +1,11 @@ # -*- coding: utf-8 -*- # flake8: noqa: F401 +from dff.utils.db_benchmark.benchmark import ( + time_context_read_write, + DBFactory, + BenchmarkConfig, + BenchmarkCase, + save_results_to_file, + benchmark_all, +) from dff.utils.db_benchmark.report import report diff --git a/tutorials/context_storages/8_db_benchmarking.py b/tutorials/context_storages/8_db_benchmarking.py index b0fd3d068..7ecfd44e8 100644 --- a/tutorials/context_storages/8_db_benchmarking.py +++ b/tutorials/context_storages/8_db_benchmarking.py @@ -12,8 +12,7 @@ from platform import system import tempfile -import dff.utils.db_benchmark.benchmark as benchmark -from dff.utils.db_benchmark.report import report +import dff.utils.db_benchmark as benchmark # %% [markdown] """ @@ -142,4 +141,4 @@ """ # %% -report(file=tutorial_dir / "Shelve.json", display={"name", "config", "metrics"}) +benchmark.report(file=tutorial_dir / "Shelve.json", display={"name", "config", "metrics"}) From 25eabff26420078ba096b7b1f50da2fb2f2e36c4 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Fri, 18 Aug 2023 00:08:56 +0300 Subject: [PATCH 098/113] minor report change --- dff/utils/db_benchmark/report.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dff/utils/db_benchmark/report.py b/dff/utils/db_benchmark/report.py index 54206b09b..825f13a04 100644 --- a/dff/utils/db_benchmark/report.py +++ b/dff/utils/db_benchmark/report.py @@ -51,7 +51,8 @@ def get_benchmark_config_report(benchmark_config, sizes) -> Tuple[str, str]: f"To dialog len: {benchmark_config.to_dialog_len}\n" f"Step dialog len: {benchmark_config.step_dialog_len}\n" f"Message misc dimensions: {benchmark_config.message_dimensions}\n" - f"Misc dimensions: {benchmark_config.misc_dimensions}\n", + f"Misc dimensions: {benchmark_config.misc_dimensions}", + f"Size of misc field: {misc_size} ({naturalsize(misc_size, gnu=True)})\n" f"Size of one message: {message_size} ({naturalsize(message_size, gnu=True)})\n" f"Starting context size: {starting_context_size} ({naturalsize(starting_context_size, gnu=True)})\n" From e9283978c950a9abd12e769545b0c46ea80077d2 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Fri, 18 Aug 2023 00:32:34 +0300 Subject: [PATCH 099/113] replace deprecated method call --- dff/utils/db_benchmark/benchmark.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dff/utils/db_benchmark/benchmark.py b/dff/utils/db_benchmark/benchmark.py index 7f0f66329..8e8ade10e 100644 --- a/dff/utils/db_benchmark/benchmark.py +++ b/dff/utils/db_benchmark/benchmark.py @@ -460,9 +460,10 @@ def save_results_to_file( "benchmarks": [], } cases = tqdm(benchmark_cases, leave=False) + case: BenchmarkCase for case in cases: cases.set_description(f"Benchmarking: {case.name}") - result["benchmarks"].append({**case.dict(), "sizes": case.benchmark_config.sizes(), **case.run()}) + result["benchmarks"].append({**case.model_dump(), "sizes": case.benchmark_config.sizes(), **case.run()}) json.dump(result, fd) From 72e991b37844f97d09c1d6899e2192ac4a771f37 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Fri, 18 Aug 2023 00:50:19 +0300 Subject: [PATCH 100/113] rename context vars --- dff/utils/db_benchmark/benchmark.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/dff/utils/db_benchmark/benchmark.py b/dff/utils/db_benchmark/benchmark.py index 8e8ade10e..29a58c26c 100644 --- a/dff/utils/db_benchmark/benchmark.py +++ b/dff/utils/db_benchmark/benchmark.py @@ -147,13 +147,13 @@ def time_context_read_write( update_times: List[Dict[int, float]] = [] for _ in tqdm(range(context_num), desc=f"Benchmarking context storage:{context_storage.full_path}", leave=False): - tmp_context = context_factory() + context = context_factory() ctx_id = uuid4() # write operation benchmark write_start = perf_counter() - context_storage[ctx_id] = tmp_context + context_storage[ctx_id] = context write_times.append(perf_counter() - write_start) read_times.append({}) @@ -163,23 +163,23 @@ def time_context_read_write( read_start = perf_counter() _ = context_storage[ctx_id] read_time = perf_counter() - read_start - read_times[-1][len(tmp_context.labels)] = read_time + read_times[-1][len(context.labels)] = read_time if context_updater is not None: - tmp_context = context_updater(tmp_context) + updated_context = context_updater(context) - while tmp_context is not None: + while updated_context is not None: update_start = perf_counter() - context_storage[ctx_id] = tmp_context + context_storage[ctx_id] = updated_context update_time = perf_counter() - update_start - update_times[-1][len(tmp_context.labels)] = update_time + update_times[-1][len(updated_context.labels)] = update_time read_start = perf_counter() _ = context_storage[ctx_id] read_time = perf_counter() - read_start - read_times[-1][len(tmp_context.labels)] = read_time + read_times[-1][len(updated_context.labels)] = read_time - tmp_context = context_updater(tmp_context) + updated_context = context_updater(updated_context) context_storage.clear() return write_times, read_times, update_times From 87b18204845b51cecaab9cf91e21d4460d238ec8 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Fri, 18 Aug 2023 03:06:15 +0300 Subject: [PATCH 101/113] generalize BenchmarkConfig --- dff/utils/db_benchmark/__init__.py | 1 + dff/utils/db_benchmark/basic_config.py | 215 ++++++++++++++++++ dff/utils/db_benchmark/benchmark.py | 173 +++----------- dff/utils/db_benchmark/report.py | 37 +-- tests/utils/test_benchmark.py | 54 ++--- .../context_storages/8_db_benchmarking.py | 43 +++- utils/db_benchmark/benchmark_dbs.py | 43 +--- utils/db_benchmark/benchmark_schema.json | 70 +----- utils/db_benchmark/benchmark_streamlit.py | 7 +- 9 files changed, 332 insertions(+), 311 deletions(-) create mode 100644 dff/utils/db_benchmark/basic_config.py diff --git a/dff/utils/db_benchmark/__init__.py b/dff/utils/db_benchmark/__init__.py index d98df8afa..1b994d21a 100644 --- a/dff/utils/db_benchmark/__init__.py +++ b/dff/utils/db_benchmark/__init__.py @@ -9,3 +9,4 @@ benchmark_all, ) from dff.utils.db_benchmark.report import report +from dff.utils.db_benchmark.basic_config import BasicBenchmarkConfig, basic_configurations diff --git a/dff/utils/db_benchmark/basic_config.py b/dff/utils/db_benchmark/basic_config.py new file mode 100644 index 000000000..8020e58f6 --- /dev/null +++ b/dff/utils/db_benchmark/basic_config.py @@ -0,0 +1,215 @@ +""" +Basic Config +------------ +This module contains basic benchmark configurations. + +It defines a simple configurations class (:py:class:`~.BasicBenchmarkConfig`) +as well as a set of configurations that covers different dialogs a user might have and some edge-cases +(:py:data:`~.basic_configurations`). +""" +from typing import Tuple, Optional, Dict +import string +import random + +from humanize import naturalsize +from pympler import asizeof + +from dff.script import Message, Context +from dff.utils.db_benchmark.benchmark import BenchmarkConfig + + +def get_dict(dimensions: Tuple[int, ...]): + """ + Return misc dictionary build in `dimensions` dimensions. + + :param dimensions: + Dimensions of the dictionary. + Each element of the dimensions tuple is the number of keys on the corresponding level of the dictionary. + The last element of the dimensions tuple is the length of the string values of the dict. + + e.g. dimensions=(1, 2) returns a dictionary with 1 key that points to a string of len 2. + whereas dimensions=(1, 2, 3) returns a dictionary with 1 key that points to a dictionary + with 2 keys each of which points to a string of len 3. + + So, the len of dimensions is the depth of the dictionary, while its values are + the width of the dictionary at each level. + """ + + def _get_dict(dimensions: Tuple[int, ...]): + if len(dimensions) < 2: + # get a random string of length dimensions[0] + return "".join(random.choice(string.printable) for _ in range(dimensions[0])) + return {str(i): _get_dict(dimensions[1:]) for i in range(dimensions[0])} + + if len(dimensions) > 1: + return _get_dict(dimensions) + elif len(dimensions) == 1: + return _get_dict((dimensions[0], 0)) + else: + return _get_dict((0, 0)) + + +def get_message(message_dimensions: Tuple[int, ...]): + """ + Return message with a non-empty misc field. + + :param message_dimensions: Dimensions of the misc field of the message. See :py:func:`~.get_dict`. + """ + return Message(misc=get_dict(message_dimensions)) + + +def get_context( + dialog_len: int, + message_dimensions: Tuple[int, ...], + misc_dimensions: Tuple[int, ...], +) -> Context: + """ + Return context with a non-empty misc, labels, requests, responses fields. + + :param dialog_len: Number of labels, requests and responses. + :param message_dimensions: + A parameter used to generate messages for requests and responses. See :py:func:`~.get_message`. + :param misc_dimensions: + A parameter used to generate misc field. See :py:func:`~.get_dict`. + """ + return Context( + labels={i: (f"flow_{i}", f"node_{i}") for i in range(dialog_len)}, + requests={i: get_message(message_dimensions) for i in range(dialog_len)}, + responses={i: get_message(message_dimensions) for i in range(dialog_len)}, + misc=get_dict(misc_dimensions), + ) + + +class BasicBenchmarkConfig(BenchmarkConfig, frozen=True): + """ + A simple benchmark configuration that generates contexts using two parameters: + + - `message_dimensions` -- to configure the way messages are generated. + - `misc_dimensions` -- to configure size of context's misc field. + + Dialog length is configured using `from_dialog_len`, `to_dialog_len`, `step_dialog_len`. + """ + + context_num: int = 30 + """ + Number of times the contexts will be benchmarked. + Increasing this number decreases standard error of the mean for benchmarked data. + """ + from_dialog_len: int = 300 + """Starting dialog len of a context.""" + to_dialog_len: int = 311 + """ + Final dialog len of a context. + :py:meth:`~.BasicBenchmarkConfig.context_updater` will return contexts + until their dialog len is less then `to_dialog_len`. + """ + step_dialog_len: int = 1 + """ + Increment step for dialog len. + :py:meth:`~.BasicBenchmarkConfig.context_updater` will return contexts + increasing dialog len by `step_dialog_len`. + """ + message_dimensions: Tuple[int, ...] = (10, 10) + """ + Dimensions of misc dictionaries inside messages. + See :py:func:`~.get_message`. + """ + misc_dimensions: Tuple[int, ...] = (10, 10) + """ + Dimensions of misc dictionary. + See :py:func:`~.get_dict`. + """ + + def get_context(self) -> Context: + """ + Return context with `from_dialog_len`, `message_dimensions`, `misc_dimensions`. + + Wraps :py:func:`~.get_context`. + """ + return get_context(self.from_dialog_len, self.message_dimensions, self.misc_dimensions) + + def info(self): + """ + Return fields of this instance and sizes of objects defined by this config. + + :return: + A dictionary with two keys. + Key "params" stores fields of this configuration. + Key "sizes" stores string representation of following values: + - "starting_context_size" -- size of a context with `from_dialog_len`. + - "final_context_size" -- size of a context with `to_dialog_len`. + A context of this size will never actually be benchmarked. + - "misc_size" -- size of a misc field of a context. + - "message_size" -- size of a misc field of a message. + """ + return { + "params": self.model_dump(), + "sizes": { + "starting_context_size": naturalsize(asizeof.asizeof(self.get_context()), gnu=True), + "final_context_size": naturalsize( + asizeof.asizeof( + get_context(self.to_dialog_len, self.message_dimensions, self.misc_dimensions) + ), + gnu=True + ), + "misc_size": naturalsize(asizeof.asizeof(get_dict(self.misc_dimensions)), gnu=True), + "message_size": naturalsize(asizeof.asizeof(get_message(self.message_dimensions)), gnu=True), + } + } + + def context_updater(self, context: Context) -> Optional[Context]: + """ + Update context to have `step_dialog_len` more labels, requests and responses, + unless such dialog len would be equal to `to_dialog_len` or exceed than it, + in which case None is returned. + """ + start_len = len(context.labels) + if start_len + self.step_dialog_len < self.to_dialog_len: + for i in range(start_len, start_len + self.step_dialog_len): + context.add_label((f"flow_{i}", f"node_{i}")) + context.add_request(get_message(self.message_dimensions)) + context.add_response(get_message(self.message_dimensions)) + return context + else: + return None + + +basic_configurations = { + "large-misc": BasicBenchmarkConfig( + from_dialog_len=1, + to_dialog_len=50, + message_dimensions=(3, 5, 6, 5, 3), + misc_dimensions=(2, 4, 3, 8, 100), + ), + "short-messages": BasicBenchmarkConfig( + from_dialog_len=500, + to_dialog_len=550, + message_dimensions=(2, 30), + misc_dimensions=(0, 0), + ), + "default": BasicBenchmarkConfig(), + "large-misc--long-dialog": BasicBenchmarkConfig( + from_dialog_len=500, + to_dialog_len=550, + message_dimensions=(3, 5, 6, 5, 3), + misc_dimensions=(2, 4, 3, 8, 100), + ), + "very-long-dialog-len": BasicBenchmarkConfig( + context_num=10, + from_dialog_len=10000, + to_dialog_len=10050, + ), + "very-long-message-len": BasicBenchmarkConfig( + context_num=10, + from_dialog_len=1, + to_dialog_len=3, + message_dimensions=(10000, 1), + ), + "very-long-misc-len": BasicBenchmarkConfig( + context_num=10, + from_dialog_len=1, + to_dialog_len=3, + misc_dimensions=(10000, 1), + ), +} +"""Configuration that covers many dialog cases (as well as some edge-cases).""" diff --git a/dff/utils/db_benchmark/benchmark.py b/dff/utils/db_benchmark/benchmark.py index 29a58c26c..06713101d 100644 --- a/dff/utils/db_benchmark/benchmark.py +++ b/dff/utils/db_benchmark/benchmark.py @@ -10,7 +10,9 @@ - :py:func:`~.save_results_to_file` -- saves results for a list of benchmark cases. - :py:func:`~.benchmark_all` -- a wrapper of `save_results_to_file`. Creates cases from configs. -Wrappers use :py:class:`~.BenchmarkConfig` to configure benchmarks. +Wrappers use :py:class:`~.BenchmarkConfig` interface to configure benchmarks. +A simple configuration class as well as a configuration set are provided by +:py:mod:`dff.utils.db_benchmark.basic_config`. To view files generated by :py:func:`~.save_results_to_file` use either :py:func:`~dff.utils.db_benchmark.report.report` or @@ -23,77 +25,13 @@ import json import importlib from statistics import mean -import string -import random +import abc from pydantic import BaseModel, Field -from pympler import asizeof from tqdm.auto import tqdm from dff.context_storages import DBContextStorage -from dff.script import Context, Message - - -def get_dict(dimensions: Tuple[int, ...]): - """ - Return misc dictionary build in `dimensions` dimensions. - - :param dimensions: - Dimensions of the dictionary. - Each element of the dimensions tuple is the number of keys on the corresponding level of the dictionary. - The last element of the dimensions tuple is the length of the string values of the dict. - - e.g. dimensions=(1, 2) returns a dictionary with 1 key that points to a string of len 2. - whereas dimensions=(1, 2, 3) returns a dictionary with 1 key that points to a dictionary - with 2 keys each of which points to a string of len 3. - - So, the len of dimensions is the depth of the dictionary, while its values are - the width of the dictionary at each level. - """ - - def _get_dict(dimensions: Tuple[int, ...]): - if len(dimensions) < 2: - # get a random string of length dimensions[0] - return "".join(random.choice(string.printable) for _ in range(dimensions[0])) - return {str(i): _get_dict(dimensions[1:]) for i in range(dimensions[0])} - - if len(dimensions) > 1: - return _get_dict(dimensions) - elif len(dimensions) == 1: - return _get_dict((dimensions[0], 0)) - else: - return _get_dict((0, 0)) - - -def get_message(message_dimensions: Tuple[int, ...]): - """ - Return message with a non-empty misc field. - - :param message_dimensions: Dimensions of the misc field of the message. See :py:func:`~.get_dict`. - """ - return Message(misc=get_dict(message_dimensions)) - - -def get_context( - dialog_len: int, - message_dimensions: Tuple[int, ...], - misc_dimensions: Tuple[int, ...], -) -> Context: - """ - Return context with a non-empty misc, labels, requests, responses fields. - - :param dialog_len: Number of labels, requests and responses. - :param message_dimensions: - A parameter used to generate messages for requests and responses. See :py:func:`~.get_message`. - :param misc_dimensions: - A parameter used to generate misc field. See :py:func:`~.get_dict`. - """ - return Context( - labels={i: (f"flow_{i}", f"node_{i}") for i in range(dialog_len)}, - requests={i: get_message(message_dimensions) for i in range(dialog_len)}, - responses={i: get_message(message_dimensions) for i in range(dialog_len)}, - misc=get_dict(misc_dimensions), - ) +from dff.script import Context def time_context_read_write( @@ -119,7 +57,8 @@ def time_context_read_write( The updated context should have a higher dialog length than the received context (to emulate context updating during dialog). The function should return `None` to stop updating contexts. - For an example of such function, see :py:meth:`~.BenchmarkConfig.get_context_updater`. + For an example of such function, see implementation of + :py:meth:`dff.utils.db_benchmark.basic_config.BasicBenchmarkConfig.context_updater`. To avoid keeping many contexts in memory, this function will be called repeatedly at least `context_num` times. @@ -206,9 +145,17 @@ def db(self): return getattr(module, self.factory)(self.uri) -class BenchmarkConfig(BaseModel, frozen=True): +class BenchmarkConfig(BaseModel, abc.ABC, frozen=True): """ - Configuration for a benchmark. Sets dialog len, misc sizes, number of benchmarks. + Configuration for a benchmark. + + Defines methods and parameters required to run :py:func:`~.time_context_read_write`. + Also defines a method (`info`) for displaying information about this configuration. + + A simple way to configure benchmarks is provided by + :py:class:`~.dff.utils.db_benchmark.basic_config.BasicBenchmarkConfig`. + + Inherit from this class only if `BasicBenchmarkConfig` is not enough for your benchmarking needs. """ context_num: int = 30 @@ -216,83 +163,35 @@ class BenchmarkConfig(BaseModel, frozen=True): Number of times the contexts will be benchmarked. Increasing this number decreases standard error of the mean for benchmarked data. """ - from_dialog_len: int = 300 - """Starting dialog len of a context.""" - to_dialog_len: int = 311 - """ - Final dialog len of a context. - :py:meth:`~.BenchmarkConfig.get_context_updater` will return contexts - until their dialog len is less then `to_dialog_len`. - """ - step_dialog_len: int = 1 - """ - Increment step for dialog len. - :py:meth:`~.BenchmarkConfig.get_context_updater` will return contexts - increasing dialog len by `step_dialog_len`. - """ - message_dimensions: Tuple[int, ...] = (10, 10) - """ - Dimensions of misc dictionaries inside messages. - See :py:func:`~.get_message`. - """ - misc_dimensions: Tuple[int, ...] = (10, 10) - """ - Dimensions of misc dictionary. - See :py:func:`~.get_dict`. - """ + @abc.abstractmethod def get_context(self) -> Context: """ - Return context with `from_dialog_len`, `message_dimensions`, `misc_dimensions`. + Return context to benchmark read and write operations with. - Wraps :py:func:`~.get_context`. + This function will be called `context_num` times. """ - return get_context(self.from_dialog_len, self.message_dimensions, self.misc_dimensions) + ... - def sizes(self): + @abc.abstractmethod + def info(self) -> Dict[str, Any]: """ - Return sizes of objects defined by this config. - - :return: - A dictionary with 4 elements: - - "starting_context_size" -- size of a context with `from_dialog_len`. - - "final_context_size" -- size of a context with `to_dialog_len`. - A context of this size will never actually be benchmarked. - - "misc_size" -- size of a misc field of a context. - - "message_size" -- size of a misc field of a message. + Return a dictionary with information about this configuration. """ - return { - "starting_context_size": asizeof.asizeof(self.get_context()), - "final_context_size": asizeof.asizeof( - get_context(self.to_dialog_len, self.message_dimensions, self.misc_dimensions) - ), - "misc_size": asizeof.asizeof(get_dict(self.misc_dimensions)), - "message_size": asizeof.asizeof(get_message(self.message_dimensions)), - } + ... - def get_context_updater(self) -> Callable[[Context], Optional[Context]]: + @abc.abstractmethod + def context_updater(self, context: Context) -> Optional[Context]: """ - Return context updater function based on configuration. + Update context with new dialog turns or return `None` to stop updates. - :return: - A function that accepts a context, modifies it and returns it. - The updated context has `step_dialog_len` more labels, requests and responses, - unless such dialog len would be equal to `to_dialog_len` or exceed than it, - in which case None is returned. - """ + This function is used to benchmark update and read operations. - def _context_updater(context: Context) -> Optional[Context]: - start_len = len(context.labels) - if start_len + self.step_dialog_len < self.to_dialog_len: - for i in range(start_len, start_len + self.step_dialog_len): - context.add_label((f"flow_{i}", f"node_{i}")) - context.add_request(get_message(self.message_dimensions)) - context.add_response(get_message(self.message_dimensions)) - return context - else: - return None + This function will be called AT LEAST `context_num` times. - return _context_updater + :return: Updated context or `None` to stop updating context. + """ + ... class BenchmarkCase(BaseModel): @@ -305,7 +204,7 @@ class BenchmarkCase(BaseModel): """Name of a benchmark case.""" db_factory: DBFactory """DBFactory that specifies context storage to benchmark.""" - benchmark_config: BenchmarkConfig = BenchmarkConfig() + benchmark_config: BenchmarkConfig """Benchmark configuration.""" uuid: str = Field(default_factory=lambda: str(uuid4())) """Unique id of the case. Defaults to a random uuid.""" @@ -390,7 +289,7 @@ def _run(self): self.db_factory.db(), self.benchmark_config.get_context, self.benchmark_config.context_num, - context_updater=self.benchmark_config.get_context_updater(), + self.benchmark_config.context_updater, ) return { "success": True, @@ -463,7 +362,7 @@ def save_results_to_file( case: BenchmarkCase for case in cases: cases.set_description(f"Benchmarking: {case.name}") - result["benchmarks"].append({**case.model_dump(), "sizes": case.benchmark_config.sizes(), **case.run()}) + result["benchmarks"].append({**case.model_dump(exclude={"benchmark_config"}), "benchmark_config": case.benchmark_config.info(), **case.run()}) json.dump(result, fd) diff --git a/dff/utils/db_benchmark/report.py b/dff/utils/db_benchmark/report.py index 825f13a04..e65778966 100644 --- a/dff/utils/db_benchmark/report.py +++ b/dff/utils/db_benchmark/report.py @@ -4,17 +4,13 @@ This method contains a function to print benchmark results to console. """ from pathlib import Path -from typing import Union, Set, Literal, Tuple +from typing import Union, Set, Literal import json -from humanize import naturalsize - -from dff.utils.db_benchmark.benchmark import BenchmarkConfig - def report( file: Union[str, Path], - display: Set[Literal["name", "desc", "config", "sizes", "metrics"]] = set({"name", "metrics"}), + display: Set[Literal["name", "desc", "config", "metrics"]] = set({"name", "metrics"}), ): """ Print average results from a result file to stdout. @@ -31,46 +27,21 @@ def report( - "name" -- displays the name of the benchmark case. - "desc" -- displays the description of the benchmark case. - - "config" -- displays the config of the benchmark case. - - "sizes" -- displays size stats for the config. + - "config" -- displays the config info of the benchmark case. - "metrics" -- displays average write, read, update read+update times. """ with open(file, "r", encoding="utf-8") as fd: file_contents = json.load(fd) - def get_benchmark_config_report(benchmark_config, sizes) -> Tuple[str, str]: - benchmark_config = BenchmarkConfig(**benchmark_config) - starting_context_size = sizes["starting_context_size"] - final_context_size = sizes["final_context_size"] - misc_size = sizes["misc_size"] - message_size = sizes["message_size"] - - return ( - f"Number of contexts: {benchmark_config.context_num}\n" - f"From dialog len: {benchmark_config.from_dialog_len}\n" - f"To dialog len: {benchmark_config.to_dialog_len}\n" - f"Step dialog len: {benchmark_config.step_dialog_len}\n" - f"Message misc dimensions: {benchmark_config.message_dimensions}\n" - f"Misc dimensions: {benchmark_config.misc_dimensions}", - - f"Size of misc field: {misc_size} ({naturalsize(misc_size, gnu=True)})\n" - f"Size of one message: {message_size} ({naturalsize(message_size, gnu=True)})\n" - f"Starting context size: {starting_context_size} ({naturalsize(starting_context_size, gnu=True)})\n" - f"Final context size: {final_context_size} ({naturalsize(final_context_size, gnu=True)})", - ) - sep = "-" * 80 report_result = "\n".join([sep, file_contents["name"], sep, file_contents["description"], sep, ""]) for benchmark in file_contents["benchmarks"]: - config, sizes = get_benchmark_config_report(benchmark["benchmark_config"], benchmark["sizes"]) - reported_values = { "name": benchmark["name"], "desc": benchmark["description"], - "config": config, - "sizes": sizes, + "config": "\n".join(f"{k}: {v}" for k, v in benchmark["benchmark_config"].items()), "metrics": "".join( [ f"{metric.title() + ': ' + str(benchmark['average_results']['pretty_' + metric]):20}" diff --git a/tests/utils/test_benchmark.py b/tests/utils/test_benchmark.py index 86527f826..bbef94195 100644 --- a/tests/utils/test_benchmark.py +++ b/tests/utils/test_benchmark.py @@ -8,8 +8,10 @@ try: from jsonschema import validate - import dff.utils.db_benchmark.benchmark as bm + import dff.utils.db_benchmark as bm + from dff.utils.db_benchmark.basic_config import get_context, get_dict, get_message from dff.context_storages import JSONContextStorage + from dff.script import Context, Message except ImportError: pytest.skip(reason="`dff[benchmark,tests]` not installed", allow_module_level=True) @@ -19,10 +21,10 @@ def test_get_dict(): random.seed(42) - assert bm.get_dict(()) == {} - assert bm.get_dict((1,)) == {"0": ""} - assert bm.get_dict((2, 3)) == {"0": ">e3", "1": " zv"} - assert bm.get_dict((2, 3, 4)) == { + assert get_dict(()) == {} + assert get_dict((1,)) == {"0": ""} + assert get_dict((2, 3)) == {"0": ">e3", "1": " zv"} + assert get_dict((2, 3, 4)) == { "0": {"0": "sh d", "1": "] (b", "2": ".S43"}, "1": {"0": "brt#", "1": ":3*p", "2": "|@`("}, } @@ -30,12 +32,12 @@ def test_get_dict(): def test_get_context(): random.seed(42) - context = bm.get_context(2, (1, 2), (2, 3)) - assert context == bm.Context( + context = get_context(2, (1, 2), (2, 3)) + assert context == Context( id=context.id, labels={0: ("flow_0", "node_0"), 1: ("flow_1", "node_1")}, - requests={0: bm.Message(misc={"0": ">e"}), 1: bm.Message(misc={"0": "3 "})}, - responses={0: bm.Message(misc={"0": "zv"}), 1: bm.Message(misc={"0": "sh"})}, + requests={0: Message(misc={"0": ">e"}), 1: Message(misc={"0": "3 "})}, + responses={0: Message(misc={"0": "zv"}), 1: Message(misc={"0": "sh"})}, misc={"0": " d]", "1": " (b"}, ) @@ -43,20 +45,19 @@ def test_get_context(): def test_benchmark_config(monkeypatch: pytest.MonkeyPatch): monkeypatch.setattr(random, "choice", lambda x: ".") - config = bm.BenchmarkConfig( + config = bm.BasicBenchmarkConfig( from_dialog_len=1, to_dialog_len=5, message_dimensions=(2, 2), misc_dimensions=(3, 3, 3) ) context = config.get_context() - actual_context = bm.get_context(1, (2, 2), (3, 3, 3)) + actual_context = get_context(1, (2, 2), (3, 3, 3)) actual_context.id = context.id assert context == actual_context - sizes = config.sizes() + info = config.info() for size in ("starting_context_size", "final_context_size", "misc_size", "message_size"): - assert isinstance(sizes[size], int) - assert sizes[size] > 0 + assert isinstance(info["sizes"][size], str) - context_updater = config.get_context_updater() + context_updater = config.context_updater contexts = [context] @@ -69,7 +70,7 @@ def test_benchmark_config(monkeypatch: pytest.MonkeyPatch): if context is not None: assert len(context.labels) == len(context.requests) == len(context.responses) == index + 1 - actual_context = bm.get_context(index + 1, (2, 2), (3, 3, 3)) + actual_context = get_context(index + 1, (2, 2), (3, 3, 3)) actual_context.id = context.id assert context == actual_context @@ -77,11 +78,11 @@ def test_benchmark_config(monkeypatch: pytest.MonkeyPatch): def test_context_updater_with_steps(monkeypatch: pytest.MonkeyPatch): monkeypatch.setattr(random, "choice", lambda x: ".") - config = bm.BenchmarkConfig( + config = bm.BasicBenchmarkConfig( from_dialog_len=1, to_dialog_len=11, step_dialog_len=3, message_dimensions=(2, 2), misc_dimensions=(3, 3, 3) ) - context_updater = config.get_context_updater() + context_updater = config.context_updater contexts = [config.get_context()] @@ -94,7 +95,7 @@ def test_context_updater_with_steps(monkeypatch: pytest.MonkeyPatch): if context is not None: assert len(context.labels) == len(context.requests) == len(context.responses) == index - actual_context = bm.get_context(index, (2, 2), (3, 3, 3)) + actual_context = get_context(index, (2, 2), (3, 3, 3)) actual_context.id = context.id assert context == actual_context @@ -115,7 +116,7 @@ def context_storage(tmp_path: Path) -> JSONContextStorage: def test_time_context_read_write(context_storage: JSONContextStorage): - config = bm.BenchmarkConfig( + config = bm.BasicBenchmarkConfig( context_num=5, from_dialog_len=1, to_dialog_len=11, @@ -125,7 +126,7 @@ def test_time_context_read_write(context_storage: JSONContextStorage): ) results = bm.time_context_read_write( - context_storage, config.get_context, config.context_num, config.get_context_updater() + context_storage, config.get_context, config.context_num, config.context_updater ) assert len(context_storage) == 0 @@ -148,7 +149,7 @@ def test_time_context_read_write(context_storage: JSONContextStorage): def test_time_context_read_write_without_updates(context_storage: JSONContextStorage): - config = bm.BenchmarkConfig( + config = bm.BasicBenchmarkConfig( context_num=5, from_dialog_len=1, to_dialog_len=2, @@ -172,7 +173,7 @@ def test_time_context_read_write_without_updates(context_storage: JSONContextSto context_storage, config.get_context, config.context_num, - config.get_context_updater(), # context updater returns None + config.context_updater, # context updater returns None ) _, read, update = results @@ -231,7 +232,7 @@ def test_benchmark_case(tmp_path: Path): case = bm.BenchmarkCase( name="", db_factory=bm.DBFactory(uri=f"json://{tmp_path}/json.json"), - benchmark_config=bm.BenchmarkConfig( + benchmark_config=bm.BasicBenchmarkConfig( context_num=5, from_dialog_len=1, to_dialog_len=11, @@ -265,7 +266,7 @@ def test_save_to_file(tmp_path: Path): "test", f"json://{tmp_path}/json.json", { - "config": bm.BenchmarkConfig( + "config": bm.BasicBenchmarkConfig( context_num=5, from_dialog_len=1, to_dialog_len=11, @@ -289,7 +290,6 @@ def test_save_to_file(tmp_path: Path): "benchmark_config", "uuid", "description", - "sizes", "success", "result", "average_results", @@ -303,7 +303,7 @@ def test_save_to_file(tmp_path: Path): "test", "None", { - "config": bm.BenchmarkConfig( + "config": bm.BasicBenchmarkConfig( context_num=5, from_dialog_len=1, to_dialog_len=11, diff --git a/tutorials/context_storages/8_db_benchmarking.py b/tutorials/context_storages/8_db_benchmarking.py index 7ecfd44e8..55902733c 100644 --- a/tutorials/context_storages/8_db_benchmarking.py +++ b/tutorials/context_storages/8_db_benchmarking.py @@ -71,6 +71,10 @@ Both function use [BenchmarkConfig]( ../apiref/dff.utils.db_benchmark.benchmark.rst#dff.utils.db_benchmark.benchmark.BenchmarkConfig ) to configure benchmark behaviour. +`BenchmarkConfig` is only an interface for benchmark configurations. Its most basic implementation is +[BasicBenchmarkConfig]( +../apiref/dff.utils.db_benchmark.basic_config.rst#dff.utils.db_benchmark.basic_config.BasicBenchmarkConfig +). It has several parameters: @@ -98,7 +102,7 @@ description="Benchmark for tutorial", db_uri=db_uri, benchmark_configs={ - "simple_config": benchmark.BenchmarkConfig( + "simple_config": benchmark.BasicBenchmarkConfig( context_num=50, from_dialog_len=1, to_dialog_len=5, @@ -142,3 +146,40 @@ # %% benchmark.report(file=tutorial_dir / "Shelve.json", display={"name", "config", "metrics"}) + +# %% [markdown] +""" +## Additional information + +### Configuration presets + +The [dff.utils.db_benchmarks.basic_config]( +../apiref/dff.utils.db_benchmark.basic_config.rst +) module also includes a dictionary containing configuration presets. Those cover various contexts and messages as well +as some edge cases. + +To use configuration presets, simply pass them to benchmark functions. +""" + +# %% +print(benchmark.basic_configurations.keys()) + +# benchmark.benchmark_all( +# ..., +# benchmark_configs=benchmark.basic_configurations +# ) + +# %% [markdown] +""" +### Custom configuration + +If the basic configuration is not enough for you, you can create your own. + +To do so, inherit from the [dff.utils.db_benchmark.benchmark.BenchmarkConfig]( +../apiref/dff.utils.db_benchmark.benchmark.rst#dff.utils.db_benchmark.benchmark.BenchmarkConfig +) class. You need to define three methods: + +- `get_context` -- method to get initial contexts. +- `info` -- method for getting display info representing the configuration. +- `context_updater` -- method for updating contexts. +""" diff --git a/utils/db_benchmark/benchmark_dbs.py b/utils/db_benchmark/benchmark_dbs.py index b05e41bcd..fd8efe69f 100644 --- a/utils/db_benchmark/benchmark_dbs.py +++ b/utils/db_benchmark/benchmark_dbs.py @@ -6,9 +6,9 @@ from pathlib import Path from platform import system -from dff.utils.db_benchmark.benchmark import ( - BenchmarkConfig, +from dff.utils.db_benchmark import ( benchmark_all, + basic_configurations ) @@ -42,42 +42,5 @@ db_name, description="Basic configs", db_uri=db_uri, - benchmark_configs={ - "large-misc": BenchmarkConfig( - from_dialog_len=1, - to_dialog_len=50, - message_dimensions=(3, 5, 6, 5, 3), - misc_dimensions=(2, 4, 3, 8, 100), - ), - "short-messages": BenchmarkConfig( - from_dialog_len=500, - to_dialog_len=550, - message_dimensions=(2, 30), - misc_dimensions=(0, 0), - ), - "default": BenchmarkConfig(), - "large-misc--long-dialog": BenchmarkConfig( - from_dialog_len=500, - to_dialog_len=550, - message_dimensions=(3, 5, 6, 5, 3), - misc_dimensions=(2, 4, 3, 8, 100), - ), - "very-long-dialog-len": BenchmarkConfig( - context_num=10, - from_dialog_len=10000, - to_dialog_len=10050, - ), - "very-long-message-len": BenchmarkConfig( - context_num=10, - from_dialog_len=1, - to_dialog_len=3, - message_dimensions=(10000, 1), - ), - "very-long-misc-len": BenchmarkConfig( - context_num=10, - from_dialog_len=1, - to_dialog_len=3, - misc_dimensions=(10000, 1), - ), - }, + benchmark_configs=basic_configurations, ) diff --git a/utils/db_benchmark/benchmark_schema.json b/utils/db_benchmark/benchmark_schema.json index ae4129127..3a7d953b4 100644 --- a/utils/db_benchmark/benchmark_schema.json +++ b/utils/db_benchmark/benchmark_schema.json @@ -60,73 +60,7 @@ }, "benchmark_config": { "description": "Configuration of the benchmark", - "type": "object", - "properties": { - "context_num": { - "description": "Number of times each context was written/read", - "type": "integer", - "minimum": 1 - }, - "from_dialog_len": { - "description": "Starting dialog len", - "type": "integer", - "minimum": 0 - }, - "to_dialog_len": { - "description": "Final dialog len (exclusive)", - "type": "integer", - "minimum": 1 - }, - "step_dialog_len": { - "description": "Increment step of dialog len", - "type": "integer", - "minimum": 1 - }, - "message_dimensions": { - "description": "Dimensions of a misc field of each message", - "type": "array", - "items": { - "type": "integer", - "minimum": 0 - } - }, - "misc_dimensions": { - "description": "Dimensions of a misc field of contexts", - "type": "array", - "items": { - "type": "integer", - "minimum": 0 - } - } - }, - "required": ["context_num", "from_dialog_len", "to_dialog_len", "step_dialog_len", "message_dimensions", "misc_dimensions"] - }, - "sizes": { - "description": "A dictionary with size statistics for objects used during benchmarking", - "type": "object", - "properties": { - "starting_context_size": { - "description": "Context size of from_dialog_len length", - "type": "integer", - "minimum": 1 - }, - "final_context_size": { - "description": "Context size of to_dialog_len length", - "type": "integer", - "minimum": 1 - }, - "misc_size": { - "description": "Size of the misc field of a context", - "type": "integer", - "minimum": 1 - }, - "message_size": { - "description": "Size of a message", - "type": "integer", - "minimum": 1 - } - }, - "required": ["starting_context_size", "final_context_size", "misc_size", "message_size"] + "type": "object" }, "result": { "description": "Raw benchmark results or error message", @@ -227,7 +161,7 @@ ] } }, - "required": ["name", "description", "uuid", "success", "db_factory", "benchmark_config", "sizes", "result"] + "required": ["name", "description", "uuid", "success", "db_factory", "benchmark_config", "result"] } } }, diff --git a/utils/db_benchmark/benchmark_streamlit.py b/utils/db_benchmark/benchmark_streamlit.py index ca7e4306a..f26785e33 100644 --- a/utils/db_benchmark/benchmark_streamlit.py +++ b/utils/db_benchmark/benchmark_streamlit.py @@ -275,7 +275,7 @@ def process_uploaded_files(): benchmark_choice.markdown(selected_benchmark["description"]) with st.expander("Benchmark stats"): - reproducible_stats = { + benchmark_stats = { stat: selected_benchmark[stat] for stat in ( "db_factory", @@ -283,10 +283,7 @@ def process_uploaded_files(): ) } - size_stats = {stat: naturalsize(value, gnu=True) for stat, value in selected_benchmark["sizes"].items()} - - st.json(reproducible_stats) - st.json(size_stats) + st.json(benchmark_stats) if not selected_benchmark["success"]: st.warning(selected_benchmark["result"]) From 2420455d4b07b4e7c5a95f981c4b1334fa4cb877 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Fri, 18 Aug 2023 03:12:15 +0300 Subject: [PATCH 102/113] add .dockerignore && add benchmark files to ignore --- .dockerignore | 31 +++++++++++++++++++++++++++++++ .gitignore | 4 ++++ 2 files changed, 35 insertions(+) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..76278a2a7 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,31 @@ +*.DS_Store* +*.egg-info/ +dist/ +venv/ +build/ +docs/source/apiref +docs/source/release_notes.rst +docs/source/tutorials +*__pycache__* +*.idea/* +.idea/* +*.pyc +.pytest_cache/* +.mypy_cache +modules/* +dm_pickle* +dialogue_manager* +GlobalUserTableAccessor* +memory_debugging* +opening_database* +_globals.py +venv* +.vscode +.coverage +.pytest_cache +htmlcov +tutorials/context_storages/dbs +dbs +benchmarks +benchmark_results_files.json +uploaded_benchmarks diff --git a/.gitignore b/.gitignore index 8c0ff0965..76278a2a7 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,7 @@ venv* .pytest_cache htmlcov tutorials/context_storages/dbs +dbs +benchmarks +benchmark_results_files.json +uploaded_benchmarks From 988f0ac88a1b4891661c3c11e289d1bdf6670e9f Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Fri, 18 Aug 2023 03:15:01 +0300 Subject: [PATCH 103/113] reformat --- dff/utils/db_benchmark/basic_config.py | 10 ++++------ dff/utils/db_benchmark/benchmark.py | 8 +++++++- tests/utils/test_benchmark.py | 2 +- tutorials/context_storages/8_db_benchmarking.py | 7 ++++--- utils/db_benchmark/benchmark_dbs.py | 5 +---- 5 files changed, 17 insertions(+), 15 deletions(-) diff --git a/dff/utils/db_benchmark/basic_config.py b/dff/utils/db_benchmark/basic_config.py index 8020e58f6..307806841 100644 --- a/dff/utils/db_benchmark/basic_config.py +++ b/dff/utils/db_benchmark/basic_config.py @@ -7,7 +7,7 @@ as well as a set of configurations that covers different dialogs a user might have and some edge-cases (:py:data:`~.basic_configurations`). """ -from typing import Tuple, Optional, Dict +from typing import Tuple, Optional import string import random @@ -147,14 +147,12 @@ def info(self): "sizes": { "starting_context_size": naturalsize(asizeof.asizeof(self.get_context()), gnu=True), "final_context_size": naturalsize( - asizeof.asizeof( - get_context(self.to_dialog_len, self.message_dimensions, self.misc_dimensions) - ), - gnu=True + asizeof.asizeof(get_context(self.to_dialog_len, self.message_dimensions, self.misc_dimensions)), + gnu=True, ), "misc_size": naturalsize(asizeof.asizeof(get_dict(self.misc_dimensions)), gnu=True), "message_size": naturalsize(asizeof.asizeof(get_message(self.message_dimensions)), gnu=True), - } + }, } def context_updater(self, context: Context) -> Optional[Context]: diff --git a/dff/utils/db_benchmark/benchmark.py b/dff/utils/db_benchmark/benchmark.py index 06713101d..b15a3f566 100644 --- a/dff/utils/db_benchmark/benchmark.py +++ b/dff/utils/db_benchmark/benchmark.py @@ -362,7 +362,13 @@ def save_results_to_file( case: BenchmarkCase for case in cases: cases.set_description(f"Benchmarking: {case.name}") - result["benchmarks"].append({**case.model_dump(exclude={"benchmark_config"}), "benchmark_config": case.benchmark_config.info(), **case.run()}) + result["benchmarks"].append( + { + **case.model_dump(exclude={"benchmark_config"}), + "benchmark_config": case.benchmark_config.info(), + **case.run(), + } + ) json.dump(result, fd) diff --git a/tests/utils/test_benchmark.py b/tests/utils/test_benchmark.py index bbef94195..d10082e87 100644 --- a/tests/utils/test_benchmark.py +++ b/tests/utils/test_benchmark.py @@ -9,7 +9,7 @@ from jsonschema import validate import dff.utils.db_benchmark as bm - from dff.utils.db_benchmark.basic_config import get_context, get_dict, get_message + from dff.utils.db_benchmark.basic_config import get_context, get_dict from dff.context_storages import JSONContextStorage from dff.script import Context, Message except ImportError: diff --git a/tutorials/context_storages/8_db_benchmarking.py b/tutorials/context_storages/8_db_benchmarking.py index 55902733c..2f41d4b32 100644 --- a/tutorials/context_storages/8_db_benchmarking.py +++ b/tutorials/context_storages/8_db_benchmarking.py @@ -71,7 +71,8 @@ Both function use [BenchmarkConfig]( ../apiref/dff.utils.db_benchmark.benchmark.rst#dff.utils.db_benchmark.benchmark.BenchmarkConfig ) to configure benchmark behaviour. -`BenchmarkConfig` is only an interface for benchmark configurations. Its most basic implementation is +`BenchmarkConfig` is only an interface for benchmark configurations. +Its most basic implementation is [BasicBenchmarkConfig]( ../apiref/dff.utils.db_benchmark.basic_config.rst#dff.utils.db_benchmark.basic_config.BasicBenchmarkConfig ). @@ -155,8 +156,8 @@ The [dff.utils.db_benchmarks.basic_config]( ../apiref/dff.utils.db_benchmark.basic_config.rst -) module also includes a dictionary containing configuration presets. Those cover various contexts and messages as well -as some edge cases. +) module also includes a dictionary containing configuration presets. +Those cover various contexts and messages as well as some edge cases. To use configuration presets, simply pass them to benchmark functions. """ diff --git a/utils/db_benchmark/benchmark_dbs.py b/utils/db_benchmark/benchmark_dbs.py index fd8efe69f..95f2e8f00 100644 --- a/utils/db_benchmark/benchmark_dbs.py +++ b/utils/db_benchmark/benchmark_dbs.py @@ -6,10 +6,7 @@ from pathlib import Path from platform import system -from dff.utils.db_benchmark import ( - benchmark_all, - basic_configurations -) +from dff.utils.db_benchmark import benchmark_all, basic_configurations # these files are required for file-based dbs From 4263b7e21c3e8c9123b5ac3a9b738274b93bb405 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Fri, 18 Aug 2023 03:37:06 +0300 Subject: [PATCH 104/113] move databases inside the benchmark_dir --- utils/db_benchmark/benchmark_dbs.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/utils/db_benchmark/benchmark_dbs.py b/utils/db_benchmark/benchmark_dbs.py index 95f2e8f00..bae475ad2 100644 --- a/utils/db_benchmark/benchmark_dbs.py +++ b/utils/db_benchmark/benchmark_dbs.py @@ -9,16 +9,23 @@ from dff.utils.db_benchmark import benchmark_all, basic_configurations +# benchmarks will be saved to this directory +benchmark_dir = Path("benchmarks") + +benchmark_dir.mkdir(exist_ok=True) + + # these files are required for file-based dbs -Path("dbs").mkdir(exist_ok=True) -sqlite_file = Path("dbs/sqlite.db") +db_path = benchmark_dir / "dbs" +db_path.mkdir(exist_ok=True) +sqlite_file = db_path / "sqlite.db" sqlite_file.touch(exist_ok=True) sqlite_separator = "///" if system() == "Windows" else "////" dbs = { - "JSON": "json://dbs/json.json", - "Pickle": "pickle://dbs/pickle.pkl", - "Shelve": "shelve://dbs/shelve", + "JSON": f"json://{db_path}/json.json", + "Pickle": f"pickle://{db_path}/pickle.pkl", + "Shelve": f"shelve://{db_path}/shelve", "PostgreSQL": "postgresql+asyncpg://postgres:pass@localhost:5432/test", "MongoDB": "mongodb://admin:pass@localhost:27017/admin", "Redis": "redis://:pass@localhost:6379/0", @@ -27,11 +34,6 @@ "YDB": "grpc://localhost:2136/local", } -# benchmarks will be saved to this directory -benchmark_dir = Path("benchmarks") - -benchmark_dir.mkdir(exist_ok=True) - for db_name, db_uri in dbs.items(): benchmark_all( From ebcbc07aad25e22876e20272f826080fa79f10de Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Fri, 18 Aug 2023 04:25:08 +0300 Subject: [PATCH 105/113] fix doc --- dff/utils/db_benchmark/basic_config.py | 7 ++++++- dff/utils/db_benchmark/benchmark.py | 6 +++--- docs/source/conf.py | 4 +++- tutorials/context_storages/8_db_benchmarking.py | 10 +++------- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/dff/utils/db_benchmark/basic_config.py b/dff/utils/db_benchmark/basic_config.py index 307806841..2e84f6b80 100644 --- a/dff/utils/db_benchmark/basic_config.py +++ b/dff/utils/db_benchmark/basic_config.py @@ -136,6 +136,7 @@ def info(self): A dictionary with two keys. Key "params" stores fields of this configuration. Key "sizes" stores string representation of following values: + - "starting_context_size" -- size of a context with `from_dialog_len`. - "final_context_size" -- size of a context with `to_dialog_len`. A context of this size will never actually be benchmarked. @@ -210,4 +211,8 @@ def context_updater(self, context: Context) -> Optional[Context]: misc_dimensions=(10000, 1), ), } -"""Configuration that covers many dialog cases (as well as some edge-cases).""" +""" +Configuration that covers many dialog cases (as well as some edge-cases). + +:meta hide-value: +""" diff --git a/dff/utils/db_benchmark/benchmark.py b/dff/utils/db_benchmark/benchmark.py index b15a3f566..d61c935bc 100644 --- a/dff/utils/db_benchmark/benchmark.py +++ b/dff/utils/db_benchmark/benchmark.py @@ -41,11 +41,11 @@ def time_context_read_write( context_updater: Optional[Callable[[Context], Optional[Context]]] = None, ) -> Tuple[List[float], List[Dict[int, float]], List[Dict[int, float]]]: """ - Benchmark "context_storage" by writing and reading `context`s generated by `context_factory` + Benchmark `context_storage` by writing and reading `context`\\s generated by `context_factory` into it / from it `context_num` times. - If context_updater is not None it is used to update `context`s and benchmark update operation. + If `context_updater` is not `None` it is used to update `context`\\s and benchmark update operation. - This function clears "context_storage" before and after execution. + This function clears `context_storage` before and after execution. :param context_storage: Context storage to benchmark. :param context_factory: A function that creates contexts which will be written into context storage. diff --git a/docs/source/conf.py b/docs/source/conf.py index 4886f237d..0a9a18fec 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -178,6 +178,8 @@ def setup(_): ("dff.messengers", "Messenger Interfaces"), ("dff.pipeline", "Pipeline"), ("dff.script", "Script"), - ("dff.utils", "Utils"), + ("dff.utils.testing", "Testing Utils"), + ("dff.utils.turn_caching", "Caching"), + ("dff.utils.db_benchmark", "DB Benchmark"), ] ) diff --git a/tutorials/context_storages/8_db_benchmarking.py b/tutorials/context_storages/8_db_benchmarking.py index 2f41d4b32..f12ea8db1 100644 --- a/tutorials/context_storages/8_db_benchmarking.py +++ b/tutorials/context_storages/8_db_benchmarking.py @@ -88,9 +88,7 @@ ### File structure -The files are saved according to this [schema](../../../utils/db_benchmark/benchmark_schema.json). - -The schema can also be found on [github]( +The files are saved according to the schema which can be found on [github]( https://github.com/deeppavlov/dialog_flow_framework/blob/dev/utils/db_benchmark/benchmark_schema.json ). """ @@ -127,11 +125,9 @@ Now that the results are saved to a file you can either view them using [report]( ../apiref/dff.utils.db_benchmark.report.rst#dff.utils.db_benchmark.report.report -) function or the [streamlit app]( -../../../utils/db_benchmark/benchmark_streamlit.py -). +) function or our streamlit app. -The app can also be found on [github]( +The app can be found on [github]( https://github.com/deeppavlov/dialog_flow_framework/blob/dev/utils/db_benchmark/benchmark_streamlit.py ). """ From 5e604bd06fc63d9ad30002089c2092e03fb2c831 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Fri, 18 Aug 2023 04:36:20 +0300 Subject: [PATCH 106/113] use tutorial directives --- .../context_storages/8_db_benchmarking.py | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/tutorials/context_storages/8_db_benchmarking.py b/tutorials/context_storages/8_db_benchmarking.py index f12ea8db1..708c9239a 100644 --- a/tutorials/context_storages/8_db_benchmarking.py +++ b/tutorials/context_storages/8_db_benchmarking.py @@ -4,9 +4,11 @@ This tutorial shows how to benchmark context storages. -For more info see [API reference](../apiref/dff.utils.db_benchmark.benchmark.rst). +For more info see [API reference](%doclink(api,utils.db_benchmark.benchmark)). """ +# %pip install dff[benchmark,json,pickle,postgresql,mongodb,redis,mysql,sqlite,ydb] + # %% from pathlib import Path from platform import system @@ -49,11 +51,11 @@ For that there exist two functions: [benchmark_all]( -../apiref/dff.utils.db_benchmark.benchmark.rst#dff.utils.db_benchmark.benchmark.benchmark_all +%doclink(api,utils.db_benchmark.benchmark,benchmark_all) ) and [save_results_to_file]( -../apiref/dff.utils.db_benchmark.benchmark.rst#dff.utils.db_benchmark.benchmark.save_results_to_file +%doclink(api,utils.db_benchmark.benchmark,save_results_to_file) ). Note: context storages passed into these functions will be cleared. @@ -62,19 +64,19 @@ The first one is a higher-level wrapper of the second one. The first function accepts [BenchmarkCases]( -../apiref/dff.utils.db_benchmark.benchmark.rst#dff.utils.db_benchmark.benchmark.BenchmarkCase +%doclink(api,utils.db_benchmark.benchmark,BenchmarkCase) ) which configure databases that are being benchmark and configurations of the benchmarks. The second function accepts only a single URI for the database and several benchmark configurations. So, the second function is simpler to use, while the first function allows for more configuration (e.g. having different databases benchmarked in a single file). Both function use [BenchmarkConfig]( -../apiref/dff.utils.db_benchmark.benchmark.rst#dff.utils.db_benchmark.benchmark.BenchmarkConfig +%doclink(api,utils.db_benchmark.benchmark,BenchmarkConfig) ) to configure benchmark behaviour. `BenchmarkConfig` is only an interface for benchmark configurations. Its most basic implementation is [BasicBenchmarkConfig]( -../apiref/dff.utils.db_benchmark.basic_config.rst#dff.utils.db_benchmark.basic_config.BasicBenchmarkConfig +%doclink(api,utils.db_benchmark.basic_config,BasicBenchmarkConfig) ). It has several parameters: @@ -124,7 +126,7 @@ ## Viewing benchmark results Now that the results are saved to a file you can either view them using [report]( -../apiref/dff.utils.db_benchmark.report.rst#dff.utils.db_benchmark.report.report +%doclink(api,utils.db_benchmark.report,report) ) function or our streamlit app. The app can be found on [github]( @@ -151,7 +153,7 @@ ### Configuration presets The [dff.utils.db_benchmarks.basic_config]( -../apiref/dff.utils.db_benchmark.basic_config.rst +%doclink(api,utils.db_benchmark.basic_config) ) module also includes a dictionary containing configuration presets. Those cover various contexts and messages as well as some edge cases. @@ -173,7 +175,7 @@ If the basic configuration is not enough for you, you can create your own. To do so, inherit from the [dff.utils.db_benchmark.benchmark.BenchmarkConfig]( -../apiref/dff.utils.db_benchmark.benchmark.rst#dff.utils.db_benchmark.benchmark.BenchmarkConfig +%doclink(api,utils.db_benchmark.benchmark,BenchmarkConfig) ) class. You need to define three methods: - `get_context` -- method to get initial contexts. From 3d546117351a8e691f0bfe3f0a218e325dad846b Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Thu, 24 Aug 2023 23:34:53 +0300 Subject: [PATCH 107/113] remove ability to add files from filesystem They can already be uploaded via the `Upload benchmark results` interface --- utils/db_benchmark/benchmark_streamlit.py | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/utils/db_benchmark/benchmark_streamlit.py b/utils/db_benchmark/benchmark_streamlit.py index f26785e33..059395a3b 100644 --- a/utils/db_benchmark/benchmark_streamlit.py +++ b/utils/db_benchmark/benchmark_streamlit.py @@ -195,26 +195,7 @@ def _add_benchmark(benchmark_file, container): st.divider() - st.info("Below you can add your benchmark files (either from local files or via uploading).") - - add_container, add_from_dir_container = st.columns(2) - - add_container.text_input(label="Benchmark set file", key="add_benchmark_file") - - def add_benchmark(): - _add_benchmark(st.session_state["add_benchmark_file"], add_container) - - add_container.button("Add one file from file", on_click=add_benchmark) - - add_from_dir_container.text_input(label="Directory with benchmark files", key="add_from_dir") - - def add_from_dir(): - dir_path = Path(st.session_state["add_from_dir"]) - if dir_path.is_dir(): - for file in dir_path.iterdir(): - _add_benchmark(file, add_from_dir_container) - - add_from_dir_container.button("Add all files from directory", on_click=add_from_dir) + st.info("Below you can upload your benchmark files.") upload_container = st.container() From e422621fc564a87564d3117c03cf99b79e9d0625 Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Thu, 24 Aug 2023 23:41:55 +0300 Subject: [PATCH 108/113] delete files from filesystem when sets are deleted via the interface Since files can no longer be added via their path on the filesystem, deleted benchmarks are always the uploaded ones. --- utils/db_benchmark/benchmark_streamlit.py | 1 + 1 file changed, 1 insertion(+) diff --git a/utils/db_benchmark/benchmark_streamlit.py b/utils/db_benchmark/benchmark_streamlit.py index 059395a3b..83e59996c 100644 --- a/utils/db_benchmark/benchmark_streamlit.py +++ b/utils/db_benchmark/benchmark_streamlit.py @@ -157,6 +157,7 @@ def delete_benchmarks(): for file in files_to_delete: st.session_state["benchmark_files"].remove(file) del st.session_state["benchmarks"][file] + Path(file).unlink() delete_container.text(f"Deleted {file}") with open(BENCHMARK_RESULTS_FILES, "w", encoding="utf-8") as fd: From ec80dc3ddd6f3ae85902285ed2ca5033395a87bc Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Fri, 25 Aug 2023 01:05:20 +0300 Subject: [PATCH 109/113] link files referenced in the documentation to docs/source --- .dockerignore | 1 + .gitignore | 1 + dff/utils/db_benchmark/benchmark.py | 6 ++--- docs/source/conf.py | 9 ++++++- docs/source/utils/link_misc_files.py | 25 +++++++++++++++++++ makefile | 1 + .../context_storages/8_db_benchmarking.py | 10 +++----- 7 files changed, 43 insertions(+), 10 deletions(-) create mode 100644 docs/source/utils/link_misc_files.py diff --git a/.dockerignore b/.dockerignore index 76278a2a7..d8583d620 100644 --- a/.dockerignore +++ b/.dockerignore @@ -4,6 +4,7 @@ dist/ venv/ build/ docs/source/apiref +docs/source/_misc docs/source/release_notes.rst docs/source/tutorials *__pycache__* diff --git a/.gitignore b/.gitignore index 76278a2a7..d8583d620 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ dist/ venv/ build/ docs/source/apiref +docs/source/_misc docs/source/release_notes.rst docs/source/tutorials *__pycache__* diff --git a/dff/utils/db_benchmark/benchmark.py b/dff/utils/db_benchmark/benchmark.py index d61c935bc..075244b69 100644 --- a/dff/utils/db_benchmark/benchmark.py +++ b/dff/utils/db_benchmark/benchmark.py @@ -16,7 +16,7 @@ To view files generated by :py:func:`~.save_results_to_file` use either :py:func:`~dff.utils.db_benchmark.report.report` or -`streamlit app <../../../utils/db_benchmark/benchmark_streamlit.py>`_. +`our streamlit app <../_misc/benchmark_streamlit.py>`_. """ from uuid import uuid4 from pathlib import Path @@ -338,11 +338,11 @@ def save_results_to_file( Benchmark all `benchmark_cases` and save results to a file. Result are saved in json format with this schema: - `utils/db_benchmark/benchmark_schema.json <../../../utils/db_benchmark/benchmark_schema.json>`_. + `utils/db_benchmark/benchmark_schema.json <../_misc/benchmark_schema.json>`_. Files created by this function cen be viewed either by using :py:func:`~dff.utils.db_benchmark.report.report` or streamlit app located in the utils directory: - `utils/db_benchmark/benchmark_streamlit.py <../../../utils/db_benchmark/benchmark_streamlit.py>`_. + `utils/db_benchmark/benchmark_streamlit.py <../_misc/benchmark_streamlit.py>`_. :param benchmark_cases: A list of benchmark cases that specify benchmarks. :param file: File to save results to. diff --git a/docs/source/conf.py b/docs/source/conf.py index bedee15f8..daa36f23d 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -7,6 +7,7 @@ sys.path.append(os.path.abspath(".")) from utils.notebook import py_percent_to_notebook # noqa: E402 from utils.generate_tutorials import generate_tutorial_links_for_notebook_creation # noqa: E402 +from utils.link_misc_files import link_misc_files # noqa: E402 from utils.regenerate_apiref import regenerate_apiref # noqa: E402 # -- Project information ----------------------------------------------------- @@ -62,7 +63,7 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ["*.py", "utils/*.py", "**/_*.py"] +exclude_patterns = ["*.py", "utils/*.py", "**/_*.py", "_misc/*.py"] html_short_title = "None" @@ -149,6 +150,12 @@ def setup(_): + link_misc_files( + [ + "utils/db_benchmark/benchmark_schema.json", + "utils/db_benchmark/benchmark_streamlit.py", + ] + ) generate_tutorial_links_for_notebook_creation( [ ("tutorials.context_storages", "Context Storages"), diff --git a/docs/source/utils/link_misc_files.py b/docs/source/utils/link_misc_files.py new file mode 100644 index 000000000..6d557f3d9 --- /dev/null +++ b/docs/source/utils/link_misc_files.py @@ -0,0 +1,25 @@ +from pathlib import Path +from typing import Iterable + + +def create_file_link(source: Path, destination: Path): + """ + Create a symlink between two files. + + :param source: Path to source file. + :param destination: Path to link file. + """ + destination.unlink(missing_ok=True) + destination.parent.mkdir(exist_ok=True, parents=True) + destination.symlink_to(source.resolve(), False) + + +def link_misc_files(files: Iterable[str]): + """ + Create links inside the `docs/source/_misc` directory. + + :param files: An iterable of files to link. + """ + for file_name in files: + file = Path(file_name) + create_file_link(file, Path("docs/source/_misc") / file.name) diff --git a/makefile b/makefile index 29c18e687..f1f196014 100644 --- a/makefile +++ b/makefile @@ -90,6 +90,7 @@ clean_docs: rm -rf docs/build rm -rf docs/tutorials rm -rf docs/source/apiref + rm -rf docs/source/_misc rm -rf docs/source/tutorials .PHONY: clean_docs diff --git a/tutorials/context_storages/8_db_benchmarking.py b/tutorials/context_storages/8_db_benchmarking.py index 708c9239a..d59f375f1 100644 --- a/tutorials/context_storages/8_db_benchmarking.py +++ b/tutorials/context_storages/8_db_benchmarking.py @@ -90,8 +90,8 @@ ### File structure -The files are saved according to the schema which can be found on [github]( -https://github.com/deeppavlov/dialog_flow_framework/blob/dev/utils/db_benchmark/benchmark_schema.json +The files are saved according to [the schema]( +../_misc/benchmark_schema.json ). """ @@ -127,10 +127,8 @@ Now that the results are saved to a file you can either view them using [report]( %doclink(api,utils.db_benchmark.report,report) -) function or our streamlit app. - -The app can be found on [github]( -https://github.com/deeppavlov/dialog_flow_framework/blob/dev/utils/db_benchmark/benchmark_streamlit.py +) function or [our streamlit app]( +../_misc/benchmark_streamlit.py ). """ From e441da0a99075faa01cf962e9055dc6b415fddcf Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Fri, 25 Aug 2023 01:05:40 +0300 Subject: [PATCH 110/113] add dependency info for streamlit app --- utils/db_benchmark/benchmark_streamlit.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/utils/db_benchmark/benchmark_streamlit.py b/utils/db_benchmark/benchmark_streamlit.py index 83e59996c..d0c1dc6d0 100644 --- a/utils/db_benchmark/benchmark_streamlit.py +++ b/utils/db_benchmark/benchmark_streamlit.py @@ -17,6 +17,11 @@ The file is used to store paths to benchmark result files. Benchmark result files added via this module are not changed (only read). + +You can install all the dependencies of this module with +``` +pip install dff[benchmark] +``` """ import json from pathlib import Path From 512ed6662fdc290a7d8a0647ae362037ea6793dc Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Fri, 25 Aug 2023 01:42:49 +0300 Subject: [PATCH 111/113] add streamlit screenshots inside the tutorial --- .../_static/images/benchmark_compare.png | Bin 0 -> 41401 bytes .../_static/images/benchmark_mass_compare.png | Bin 0 -> 82578 bytes docs/source/_static/images/benchmark_sets.png | Bin 0 -> 70193 bytes docs/source/_static/images/benchmark_view.png | Bin 0 -> 73490 bytes .../context_storages/8_db_benchmarking.py | 37 ++++++++++++++++++ 5 files changed, 37 insertions(+) create mode 100644 docs/source/_static/images/benchmark_compare.png create mode 100644 docs/source/_static/images/benchmark_mass_compare.png create mode 100644 docs/source/_static/images/benchmark_sets.png create mode 100644 docs/source/_static/images/benchmark_view.png diff --git a/docs/source/_static/images/benchmark_compare.png b/docs/source/_static/images/benchmark_compare.png new file mode 100644 index 0000000000000000000000000000000000000000..1aacd018b9d40887cbf793264b01d8de5c95e447 GIT binary patch literal 41401 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 VC1&M2Nk)3QWF$bJDu4!o{|7P5Co}*6 literal 0 HcmV?d00001 diff --git a/docs/source/_static/images/benchmark_mass_compare.png b/docs/source/_static/images/benchmark_mass_compare.png new file mode 100644 index 0000000000000000000000000000000000000000..7f0daca4520ebd66689b71aea21afa923cd54876 GIT binary patch literal 82578 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]Vs=wdy8r@g-0)1 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
    CMIgl9? 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%0DXJsIMHu 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 P_YK8_rN5L4>iGRH6dWSA literal 0 HcmV?d00001 diff --git a/docs/source/_static/images/benchmark_sets.png b/docs/source/_static/images/benchmark_sets.png new file mode 100644 index 0000000000000000000000000000000000000000..3053746fa93bc1f2cd199c9f609f5b5cf2104d1f GIT binary patch literal 70193 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 literal 0 HcmV?d00001 diff --git a/tutorials/context_storages/8_db_benchmarking.py b/tutorials/context_storages/8_db_benchmarking.py index d59f375f1..6793e6ac7 100644 --- a/tutorials/context_storages/8_db_benchmarking.py +++ b/tutorials/context_storages/8_db_benchmarking.py @@ -144,6 +144,43 @@ # %% benchmark.report(file=tutorial_dir / "Shelve.json", display={"name", "config", "metrics"}) +# %% [markdown] +""" +### Using Streamlit app + +To run the app, execute: + +``` +# download file +curl https://deeppavlov.github.io/dialog_flow_framework/_misc/benchmark_streamlit.py \ +-o benchmark_streamlit.py +# install dependencies +pip install dff[benchmark] +# run +streamlit run benchmark_streamlit.py +``` + +You can upload files with benchmark results using the first tab of the app ("Benchmark sets"): + +.. figure:: ../_static/images/benchmark_sets.png + +The second tab ("View") lets you inspect individual benchmark results. +It also allows you to add a specific benchmark result +to the "Compare" tab via the button in the top-right corner. + +.. figure:: ../_static/images/benchmark_view.png + +In the "Compare" tab you can view main metrics (write, read, update, read+update) +of previously added benchmark results: + +.. figure:: ../_static/images/benchmark_compare.png + +"Mass compare" tab saves you the trouble of manually adding +to compare every benchmark result from a single file. + +.. figure:: ../_static/images/benchmark_mass_compare.png +""" + # %% [markdown] """ ## Additional information From bb0e86b083722ae00dc81a9601f4ef5d39730c4e Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Mon, 11 Sep 2023 11:57:26 +0300 Subject: [PATCH 112/113] reupload images --- .../_static/images/benchmark_compare.png | Bin 41401 -> 40604 bytes .../_static/images/benchmark_mass_compare.png | Bin 82578 -> 84227 bytes docs/source/_static/images/benchmark_sets.png | Bin 70193 -> 68422 bytes docs/source/_static/images/benchmark_view.png | Bin 73490 -> 63389 bytes 4 files changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/source/_static/images/benchmark_compare.png b/docs/source/_static/images/benchmark_compare.png index 1aacd018b9d40887cbf793264b01d8de5c95e447..8765ed48c7f4140c6bc8ddefb5bfc3da9a39f71d 100644 GIT binary patch literal 40604 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 GMf?Z-ff#lG literal 41401 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 VC1&M2Nk)3QWF$bJDu4!o{|7P5Co}*6 diff --git a/docs/source/_static/images/benchmark_mass_compare.png b/docs/source/_static/images/benchmark_mass_compare.png index 7f0daca4520ebd66689b71aea21afa923cd54876..71911c5546eb110da746d19cb746afab8a16a137 100644 GIT binary patch literal 84227 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]Vs=wdy8r@g-0)1 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

      CMIgl9? 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%0DXJsIMHu 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 P_YK8_rN5L4>iGRH6dWSA diff --git a/docs/source/_static/images/benchmark_sets.png b/docs/source/_static/images/benchmark_sets.png index 3053746fa93bc1f2cd199c9f609f5b5cf2104d1f..4eadd28734112649086fe7230e2b1d254bc24a64 100644 GIT binary patch literal 68422 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 z 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 From 8c419b5722cce710f7bb052958977554220c2e2e Mon Sep 17 00:00:00 2001 From: Roman Zlobin Date: Tue, 12 Sep 2023 00:00:56 +0300 Subject: [PATCH 113/113] add more exception info --- dff/utils/db_benchmark/benchmark.py | 9 ++++++--- utils/db_benchmark/benchmark_schema.json | 19 +++++++++++++++++-- utils/db_benchmark/benchmark_streamlit.py | 4 +++- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/dff/utils/db_benchmark/benchmark.py b/dff/utils/db_benchmark/benchmark.py index 075244b69..12e784fb2 100644 --- a/dff/utils/db_benchmark/benchmark.py +++ b/dff/utils/db_benchmark/benchmark.py @@ -26,6 +26,7 @@ import importlib from statistics import mean import abc +from traceback import extract_tb, StackSummary from pydantic import BaseModel, Field from tqdm.auto import tqdm @@ -300,11 +301,13 @@ def _run(self): }, } except Exception as e: - exception_message = getattr(e, "message", repr(e)) - print(exception_message) return { "success": False, - "result": exception_message, + "result": { + "type": e.__class__.__name__, + "msg": getattr(e, "message", str(e)), + "traceback": "\n".join(StackSummary.from_list(extract_tb(e.__traceback__)).format()), + }, } def run(self): diff --git a/utils/db_benchmark/benchmark_schema.json b/utils/db_benchmark/benchmark_schema.json index 3a7d953b4..f4ecfcf40 100644 --- a/utils/db_benchmark/benchmark_schema.json +++ b/utils/db_benchmark/benchmark_schema.json @@ -63,7 +63,7 @@ "type": "object" }, "result": { - "description": "Raw benchmark results or error message", + "description": "Raw benchmark results or exception info", "oneOf": [ { "type": "object", @@ -93,7 +93,22 @@ "required": ["write_times", "read_times", "update_times"] }, { - "type": "string" + "type": "object", + "properties": { + "type": { + "description": "Class name of the exception", + "type": "string" + }, + "msg": { + "description": "Exception message", + "type": "string" + }, + "traceback": { + "description": "String representation of exception traceback", + "type": "string" + } + }, + "required": ["type", "msg", "traceback"] } ] diff --git a/utils/db_benchmark/benchmark_streamlit.py b/utils/db_benchmark/benchmark_streamlit.py index d0c1dc6d0..f428148f1 100644 --- a/utils/db_benchmark/benchmark_streamlit.py +++ b/utils/db_benchmark/benchmark_streamlit.py @@ -273,7 +273,9 @@ def process_uploaded_files(): st.json(benchmark_stats) if not selected_benchmark["success"]: - st.warning(selected_benchmark["result"]) + exc_info = selected_benchmark["result"] + + st.warning(f"**{exc_info['type']}**: {exc_info['msg']}\n\nTraceback:\n\n```\n{exc_info['traceback']}\n```") else: add_metrics(st.container(), selected_benchmark)