-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathconstants.py
More file actions
356 lines (268 loc) · 11.5 KB
/
Copy pathconstants.py
File metadata and controls
356 lines (268 loc) · 11.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
import os
import math
from dataclasses import dataclass
from typing import Optional
from databricks.sdk.runtime import *
import logging
############### Set up tables #############
_TPCDS_TABLE_NAMES = {
"call_center",
"catalog_page",
"catalog_returns",
"catalog_sales",
"customer",
"customer_address",
"customer_demographics",
"date_dim",
"household_demographics",
"income_band",
"inventory",
"item",
"promotion",
"reason",
"ship_mode",
"store",
"store_returns",
"store_sales",
"time_dim",
"warehouse",
"web_page",
"web_returns",
"web_sales",
"web_site",
}
_TPCH_TABLE_NAMES = {"customer", "lineitem", "nation", "orders", "part", "region", "supplier", "partsupp"}
def check_tables_already_exist(spark, benchmarks, catalog: str, schema: str) -> bool:
if catalog == "samples":
return True
_TABLE_NAMES = []
if benchmarks == "TPCDS":
_TABLE_NAMES = _TPCDS_TABLE_NAMES
elif benchmarks == "TPCH":
_TABLE_NAMES = _TPCH_TABLE_NAMES
if benchmarks in ("TPCDS", "TPCH"):
if (spark.sql("show catalogs").where(f"catalog ILIKE '{catalog}'").limit(1).count()> 0):
if (
spark.sql(f"show databases in {catalog}")
.where(f"databaseName ILIKE '{schema}'")
.limit(1)
.count()
> 0
):
tables = set(
spark.sql(f"show tables in {catalog}.{schema}")
.where("tableName not ILIKE 'benchmark%'")
.select("tableName")
.toPandas()["tableName"]
)
return all(x in tables for x in _TABLE_NAMES)
return False
def set_up_catalog(spark, catalog:str, schema:str):
logging.info("Set up catalog and schema")
# USE HIVE METASTORE
if catalog == "hive_metastore":
spark.sql(f"CREATE SCHEMA IF NOT EXISTS {catalog}.{schema}")
spark.sql(f"USE {catalog}.{schema}")
# USE UNITY CATALOG
elif catalog != "samples":
spark.sql(f"CREATE CATALOG IF NOT EXISTS {catalog}")
spark.sql(f"GRANT USE CATALOG ON CATALOG {catalog} TO `account users`")
spark.sql(f"USE catalog {catalog}")
spark.sql(f"GRANT USE SCHEMA ON CATALOG {catalog} TO `account users`")
spark.sql(f"CREATE SCHEMA IF NOT EXISTS {catalog}.{schema}")
spark.sql(f"USE {catalog}.{schema}")
logging.info(f"Data will be saved at {catalog}.{schema}")
############### Set up widgets #############
VALID_WAREHOUSES = ["2X-Small", "X-Small", "Small", "Medium", "Large", "X-Large", "2X-Large", "3X-Large", "4X-Large"]
WORKERS_SCALE_FACTOR_MAP = {1:4, 10:4, 100:8, 1000:16, 10000:32}
# widgets in format (dbutils type, args)
_WIDGETS_BASE = [
("text", ("Warehouse Prefix", "Metimur")),
("text", ("Query Path", "queries/tpch_w_params")),
("text", ("Params Path", "queries/tpch_w_params/params.json")),
("text", ("Concurrency", "1")),
("dropdown", ("Benchmark Choice", "one-warehouse", ["one-warehouse", "multiple-warehouses", "multiple-warehouses-size"])),
("dropdown", ("Warehouse Type", "serverless", ["serverless", "pro", "classic"])),
("multiselect", ("Warehouse Sizes", "Small", VALID_WAREHOUSES)),
("dropdown", ("Query Repetition Count", "1", [str(x) for x in range(1, 101)])),
("dropdown", ("Min Clusters", "1", [str(x) for x in range(1, 41)])),
("dropdown", ("Max Clusters", "1", [str(x) for x in range(1, 41)])),
("dropdown", ("Disk Cache Enabled", "True", ["True", "False"])),
("dropdown", ("Results Cache Enabled", "False", ["True", "False"])),
]
# Use _WIDGETS in advanced notebook
_WIDGETS = _WIDGETS_BASE + [
("dropdown", ("Benchmarks", "TPCDS", ["TPCH", "TPCDS", "BYOD"])),
# ("text", ("Catalog Name", "serverless_benchmark")),
# ("text", ("Schema Name", "")),
("dropdown", ("Scale Factors", "1", ["1", "10", "100", "1000"]))
]
# Use _WIDGETS in quickstarts notebook
_WIDGETS_BENCHMARK = _WIDGETS_BASE + [
("text", ("Catalog Name", "samples")),
("text", ("Schema Name", "tpch")),
]
def _convert_to_int_safe(s: str):
try:
return int(s)
except ValueError as e:
if "invalid literal for int()" in str(e):
return s
else:
raise
except:
raise
def create_widgets(dbutils):
dbutils.widgets.removeAll()
for widget_type, args in _WIDGETS:
if widget_type == "text":
dbutils.widgets.text(*args)
elif widget_type == "dropdown":
dbutils.widgets.dropdown(*args)
elif widget_type == "multiselect":
dbutils.widgets.multiselect(*args)
else:
raise TypeError(f"{widget_type} type is not supported.")
def get_widget_values(dbutils):
widgets_dict = {args[0]: dbutils.widgets.get(args[0]) for _, args in _WIDGETS }
widgets_cleaned = {k.lower().replace(" ", "_"): v for k, v in widgets_dict.items()}
return {k: _convert_to_int_safe(v) for k, v in widgets_cleaned.items()}
def create_widgets_benchmark(dbutils):
"""Use in quickstart_db notebook"""
dbutils.widgets.removeAll()
for widget_type, args in _WIDGETS_BENCHMARK:
if args[0] != "Scale Factors" and args[0] != "Schema Path":
if widget_type == "text":
dbutils.widgets.text(*args)
elif widget_type == "dropdown":
dbutils.widgets.dropdown(*args)
elif widget_type == "multiselect":
dbutils.widgets.multiselect(*args)
else:
raise TypeError(f"{widget_type} type is not supported.")
def get_widget_values_benchmark(dbutils):
"""Use in quickstart_db notebook"""
widgets_dict = {args[0]: dbutils.widgets.get(args[0]) for _, args in _WIDGETS_BENCHMARK}
widgets_cleaned = {k.lower().replace(" ", "_"): v for k, v in widgets_dict.items()}
return {k: _convert_to_int_safe(v) for k, v in widgets_cleaned.items()}
############### Set up constants #############
@dataclass
class Constants:
############### Variables dependant upon widgets parameters ##############
# Number of times to duplicate the benchmarking run
query_repetition_count: int
# Path to query
query_path: str
# Path to params.json file
params_path: str
# Maximum number of clusters
min_clusters: int
# Maximum number of clusters
max_clusters: int
# Result Cache Enabled
results_cache_enabled: bool
# Disk Cache Enabled
disk_cache_enabled: bool
benchmark_choice: str
# Number of concurrent threads
concurrency: int
# Prefix of the warehouse
warehouse_prefix: str
# Type of the warehouse
warehouse_type: str
# Size of the warehouse cluster
warehouse_sizes: str
# # Warehouse channel name
# channel: str
# # Name of the catalog to write data to
# catalog_name: str
# # Name of the schema to write data to
# schema_name: str
# Number of GBs of data to write
scale_factors: Optional[int] = None
# # Path to schema
# schema_path: Optional[str] = None
# benchmark option
benchmarks: Optional[str] = None
############### Variables independent of user parameters #############
# Name of the job
job_name = f"[AUTOMATED] Metimur Benchmark"
# Dynamic variables that are used to create downstream variables
current_user_email = (
dbutils.notebook.entry_point.getDbutils()
.notebook()
.getContext()
.userName()
.get()
)
_cwd = os.getcwd() #.replace("/Workspace", "")
# User-specific parameters, which are used to create directories and cluster single-access-mode
current_user_name = (
current_user_email.replace(".", "_").replace("-", "_").split("@")[0]
)
# Base directory where TPC data and queries will be written
root_directory = f"dbfs:/Serverless_Benchmark"
# Additional subdirectories within the above root_directory
data_path = os.path.join(root_directory, "data")
# Location of scripts and queries
script_path = os.path.join(_cwd, "scripts")
# Location of the spark-sql-perf jar, which is used to create TPC-DS data and queries
jar_path = os.path.join(root_directory, "jars/spark-sql-perf_2.12-0.5.1-SNAPSHOT.jar")
# Location of the init script, which is responsible for installing the above jar and other prerequisites
init_script_path = os.path.join(script_path, "tpc-install.sh")
# Location of the dist whl for beaker
beaker_whl_path = os.path.join(script_path, "beaker-0.0.7-py3-none-any.whl")
# Location of the notebook that creates
# datagen_notebook_path = os.path.join(
# _cwd, "notebooks/tpc_datagen"
# )
# Location of the notebook that runs queries against written data using the beaker library
run_benchmark_notebook_path = os.path.join(
_cwd, "quickstarts"
)
# Name of the current databricks host
host = f"https://{spark.conf.get('spark.databricks.workspaceUrl')}/"
def _validate_concurrency_will_utilize_cluster(self):
required_number_of_clusters = math.ceil(self.concurrency / 10)
print(f"Warning: For optimal performance, we recommend using 1 cluster per 10 levels of concurrency. \n Set maximum number of clusters to {required_number_of_clusters} based on required concurrency")
return required_number_of_clusters
def __post_init__(self):
# Location of the notebook that creates
if self.benchmarks == "BYOD":
self.datagen_notebook_path = os.path.join(
self._cwd, "notebooks/custom_datagen"
)
else:
self.datagen_notebook_path = os.path.join(
self._cwd, "notebooks/TPC_datagen"
)
# # BRING YOUR OWN DATA BENCHMARK
# elif self.benchmarks == "BYOD":
# self.catalog_name = f"benchmark_{self.current_user_name}"
# # Validate schema_name
# assert (self.schema_name != ""), "Specify the schema_name for BYOD (bring your own data) option!"
# self.schema_name = self.schema_name
# Set up catalog and schema
# set_up_catalog(spark, self.catalog_name, self.schema_name)
# Add schema to data path
# self.data_path = os.path.join(self.data_path, self.schema_name)
if self.query_path:
self.query_path = os.path.join(self._cwd, self.query_path)
if self.params_path:
self.params_path = os.path.join(self._cwd, self.params_path)
# if self.schema_path:
# self.schema_path = os.path.join(self._cwd, self.schema_path)
# Convert result cache enabled to boolean
self.results_cache_enabled = False if self.results_cache_enabled == "False" else True
# # Determine if TPC tables already exist
# self.tables_already_exist = check_tables_already_exist(spark, self.benchmarks, self.catalog_name, self.schema_name)
# Set warehouse prefix
# Add current_user_name to warehouse to avoid conflict
self.warehouse_prefix = f"{self.current_user_name} {self.warehouse_prefix}"
# Param validations/warnings
self._validate_concurrency_will_utilize_cluster()
# self.max_clusters = self._validate_concurrency_will_utilize_cluster()
# Mapping of number of workers to scale factor, format {scale_factors: workers}
self.workers_scale_factor_map = WORKERS_SCALE_FACTOR_MAP[self.scale_factors]
# Create unique name for job
self.job_name = f"{self.job_name} {self.benchmarks} {self.benchmark_choice}"