| # Licensed to the Apache Software Foundation (ASF) under one | |
| # or more contributor license agreements. See the NOTICE file | |
| # distributed with this work for additional information | |
| # regarding copyright ownership. The ASF licenses this file | |
| # to you under the Apache License, Version 2.0 (the | |
| # "License"); you may not use this file except in compliance | |
| # with the License. You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, | |
| # software distributed under the License is distributed on an | |
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
| # KIND, either express or implied. See the License for the | |
| # specific language governing permissions and limitations | |
| # under the License. | |
| # | |
| import argparse | |
| import random | |
| import time | |
| import numpy as np | |
| import pandas as pd | |
| from iotdb.Session import Session | |
| from iotdb.utils.IoTDBConstants import TSDataType | |
| from iotdb.utils.NumpyTablet import NumpyTablet | |
| from iotdb.utils.Tablet import Tablet | |
| # the data type specified the byte order (i.e. endian) | |
| FORMAT_CHAR_OF_TYPES = { | |
| TSDataType.BOOLEAN: ">?", | |
| TSDataType.FLOAT: ">f4", | |
| TSDataType.DOUBLE: ">f8", | |
| TSDataType.INT32: ">i4", | |
| TSDataType.INT64: ">i8", | |
| TSDataType.TEXT: "str", | |
| } | |
| # the time column name in the csv file. | |
| TIME_STR = "time" | |
| def load_csv_data(measure_tstype_infos: dict, data_file_name: str) -> pd.DataFrame: | |
| """ | |
| load csv data. | |
| :param measure_tstype_infos: key(str): measurement name, value(TSDataType): measurement data type | |
| :param data_file_name: the csv file name to load | |
| :return: data in format of pd.DataFrame. | |
| """ | |
| metadata_for_pd = {TIME_STR: FORMAT_CHAR_OF_TYPES[TSDataType.INT64]} | |
| for _measure, _type in measure_tstype_infos.items(): | |
| metadata_for_pd[_measure] = FORMAT_CHAR_OF_TYPES[_type] | |
| df = pd.read_csv(data_file_name, dtype=metadata_for_pd) | |
| return df | |
| def generate_csv_data( | |
| measure_tstype_infos: dict, data_file_name: str, _row: int, seed=0 | |
| ) -> None: | |
| """ | |
| generate csv data randomly according to given measurements and their data types. | |
| :param measure_tstype_infos: key(str): measurement name, value(TSDataType): measurement data type | |
| :param data_file_name: the csv file name to output | |
| :param _row: tablet row number | |
| :param seed: random seed | |
| """ | |
| import random | |
| random.seed(seed) | |
| CHAR_BASE = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" | |
| def generate_data(_type: TSDataType): | |
| if _type == TSDataType.BOOLEAN: | |
| return [random.randint(0, 1) == 1 for _ in range(_row)] | |
| elif _type == TSDataType.INT32: | |
| return [random.randint(-(2**31), 2**31) for _ in range(_row)] | |
| elif _type == TSDataType.INT64: | |
| return [random.randint(-(2**63), 2**63) for _ in range(_row)] | |
| elif _type == TSDataType.FLOAT: | |
| return [1.5 for _ in range(_row)] | |
| elif _type == TSDataType.DOUBLE: | |
| return [0.844421 for _ in range(_row)] | |
| elif _type == TSDataType.TEXT: | |
| return [ | |
| "".join(random.choice(CHAR_BASE) for _ in range(5)) for _ in range(_row) | |
| ] | |
| else: | |
| raise TypeError("not support type:" + str(_type)) | |
| values = { | |
| TIME_STR: pd.Series( | |
| np.arange(_row), dtype=FORMAT_CHAR_OF_TYPES[TSDataType.INT64] | |
| ) | |
| } | |
| for column, data_type in measure_tstype_infos.items(): | |
| values[column] = pd.Series( | |
| generate_data(data_type), dtype=FORMAT_CHAR_OF_TYPES[data_type] | |
| ) | |
| df = pd.DataFrame(values) | |
| df.to_csv(data_file_name, index=False) | |
| print("data file has generated.") | |
| def create_open_session(): | |
| """ | |
| creating session connection. | |
| :return: | |
| """ | |
| ip = "127.0.0.1" | |
| port_ = "6667" | |
| username_ = "root" | |
| password_ = "root" | |
| session = Session( | |
| ip, port_, username_, password_, fetch_size=1024, zone_id="Asia/Shanghai" | |
| ) | |
| session.open(False) | |
| return session | |
| def check_count(expect, _session, _sql): | |
| """ | |
| check out the line number of the given SQL's query result. | |
| :param expect: expected number | |
| :param _session: IoTDB session | |
| :param _sql: query SQL | |
| """ | |
| session_data_set = _session.execute_query_statement(_sql) | |
| session_data_set.set_fetch_size(1) | |
| get_count_line = False | |
| while session_data_set.has_next(): | |
| if get_count_line: | |
| assert False, "select count return more than one line" | |
| line = session_data_set.next() | |
| actual = line.get_fields()[0].get_long_value() | |
| assert ( | |
| expect == actual | |
| ), f"count error: expect {expect} lines, actual {actual} lines" | |
| get_count_line = True | |
| if not get_count_line: | |
| assert False, "select count has no result" | |
| session_data_set.close_operation_handle() | |
| def check_query_result(expect, _session, _sql): | |
| """ | |
| check out the query result of given query. | |
| :param expect: expected result | |
| :param _session: IoTDB session | |
| :param _sql: query SQL | |
| """ | |
| session_data_set = _session.execute_query_statement(_sql) | |
| session_data_set.set_fetch_size(1) | |
| idx = 0 | |
| while session_data_set.has_next(): | |
| line = session_data_set.next() | |
| assert ( | |
| str(line) == expect[idx] | |
| ), f"line {idx}: actual {str(line)} != expect ({expect[idx]})" | |
| idx += 1 | |
| assert idx == len(expect), f"result rows: actual ({idx}) != expect ({len(expect)})" | |
| session_data_set.close_operation_handle() | |
| def performance_test( | |
| measure_tstype_infos, | |
| data_file_name, | |
| use_new=True, | |
| check_result=False, | |
| row=10000, | |
| col=5000, | |
| ): | |
| """ | |
| execute tablet insert using original or new methods. | |
| :param measure_tstype_infos: key(str): measurement name, value(TSDataType): measurement data type | |
| :param use_new: True if check out the result | |
| :param data_file_name: the csv file name to insert | |
| :param row: tablet row number | |
| :param col: tablet column number | |
| """ | |
| print( | |
| f"Test python: use new: {use_new}, row: {row}, col: {col}. measurements: {measure_tstype_infos}" | |
| ) | |
| print(f"Total points: {len(measure_tstype_infos) * row * col}") | |
| # open the session and clean data | |
| session = create_open_session() | |
| session.execute_non_query_statement("delete timeseries root.*") | |
| # test start | |
| st = time.perf_counter() | |
| csv_data = load_csv_data(measure_tstype_infos, data_file_name) | |
| load_cost = time.perf_counter() - st | |
| insert_cost = 0 | |
| measurements = list(measure_tstype_infos.keys()) | |
| data_types = list(measure_tstype_infos.values()) | |
| for i in range(0, col): | |
| # if i % 500 == 0: | |
| # print(f"insert {i} cols") | |
| device_id = "root.sg%d.%d" % (i % 8, i) | |
| if not use_new: | |
| # Use the ORIGINAL method to construct tablet | |
| timestamps_ = [] | |
| values = [] | |
| for t in range(0, row): | |
| timestamps_.append(csv_data.at[t, TIME_STR]) | |
| value_array = [] | |
| for m in measurements: | |
| value_array.append(csv_data.at[t, m]) | |
| values.append(value_array) | |
| tablet = Tablet(device_id, measurements, data_types, values, timestamps_) | |
| else: | |
| # Use the NEW method to construct numpy tablet | |
| timestamps_ = csv_data[TIME_STR].values | |
| if timestamps_.dtype != FORMAT_CHAR_OF_TYPES[TSDataType.INT64]: | |
| timestamps_ = timestamps_.astype(FORMAT_CHAR_OF_TYPES[TSDataType.INT64]) | |
| values = [] | |
| for measure, tstype in measure_tstype_infos.items(): | |
| type_char = FORMAT_CHAR_OF_TYPES[tstype] | |
| value_array = csv_data[measure].values | |
| if value_array.dtype != type_char: | |
| if not (tstype == TSDataType.TEXT and value_array.dtype == object): | |
| value_array = value_array.astype(type_char) | |
| values.append(value_array) | |
| tablet = NumpyTablet( | |
| device_id, measurements, data_types, values, timestamps_ | |
| ) | |
| cost_st = time.perf_counter() | |
| session.insert_tablet(tablet) | |
| insert_cost += time.perf_counter() - cost_st | |
| if check_result: | |
| check_count(row, session, "select count(*) from %s" % device_id) | |
| expect = [] | |
| for t in range(row): | |
| line = [str(csv_data.at[t, TIME_STR])] | |
| for m in measurements: | |
| line.append(str(csv_data.at[t, m])) | |
| expect.append("\t\t".join([v for v in line])) | |
| check_query_result( | |
| expect, session, f"select {','.join(measurements)} from {device_id}" | |
| ) | |
| print("query validation have passed") | |
| end = time.perf_counter() | |
| # clean data and close the session | |
| session.execute_non_query_statement("delete timeseries root.*") | |
| session.close() | |
| print("load cost: %.3f s" % load_cost) | |
| print("construct tablet cost: %.3f s" % (end - st - insert_cost - load_cost)) | |
| print("insert tablet cost: %.3f s" % insert_cost) | |
| print("total cost: %.3f s" % (end - st)) | |
| if __name__ == "__main__": | |
| parser = argparse.ArgumentParser(description="tablet performance comparison") | |
| parser.add_argument( | |
| "--row", type=int, default=10000, help="the row number of the input tablet" | |
| ) | |
| parser.add_argument( | |
| "--col", type=int, default=5000, help="the column number of the input tablet" | |
| ) | |
| parser.add_argument( | |
| "--check_result", "-c", action="store_true", help="True if check out the result" | |
| ) | |
| parser.add_argument( | |
| "--use_new", | |
| "-n", | |
| action="store_false", | |
| help="True if use the new tablet insert", | |
| ) | |
| parser.add_argument( | |
| "--seed", type=int, default=0, help="the random seed for generating csv data" | |
| ) | |
| parser.add_argument( | |
| "--data_file_name", type=str, default="sample.csv", help="the path of csv data" | |
| ) | |
| args = parser.parse_args() | |
| measure_tstype_infos = { | |
| "s0": TSDataType.BOOLEAN, | |
| "s1": TSDataType.FLOAT, | |
| "s2": TSDataType.INT32, | |
| "s3": TSDataType.DOUBLE, | |
| "s4": TSDataType.INT64, | |
| "s5": TSDataType.TEXT, | |
| } | |
| # if not os.path.exists(args.data_file_name): | |
| random.seed(a=args.seed, version=2) | |
| generate_csv_data(measure_tstype_infos, args.data_file_name, args.row, args.seed) | |
| performance_test( | |
| measure_tstype_infos, | |
| data_file_name=args.data_file_name, | |
| use_new=args.use_new, | |
| check_result=args.check_result, | |
| row=args.row, | |
| col=args.col, | |
| ) |