You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iotdb.apache.org by hu...@apache.org on 2023/03/25 00:59:50 UTC

[iotdb] branch master updated: [IOTDB-5679] Implement model storage on MLNode (#9337)

This is an automated email from the ASF dual-hosted git repository.

hui pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iotdb.git


The following commit(s) were added to refs/heads/master by this push:
     new 1db187cd85 [IOTDB-5679] Implement model storage on MLNode (#9337)
1db187cd85 is described below

commit 1db187cd85b0ac54d80511189e98e1b0585d3f85
Author: Yong Liu <li...@gmail.com>
AuthorDate: Sat Mar 25 08:59:42 2023 +0800

    [IOTDB-5679] Implement model storage on MLNode (#9337)
    
    Co-authored-by: zhouhang <11...@qq.com>
---
 mlnode/.gitignore                       |  2 +-
 mlnode/iotdb/mlnode/config.py           | 27 +++++++++-
 mlnode/iotdb/mlnode/constant.py         |  2 +
 mlnode/iotdb/mlnode/exception.py        |  5 ++
 mlnode/iotdb/mlnode/model_storage.py    | 95 +++++++++++++++++++++++++++++++++
 mlnode/iotdb/mlnode/serde.py            | 10 ++--
 mlnode/iotdb/mlnode/util.py             |  1 -
 mlnode/requirements.txt                 |  1 +
 mlnode/requirements_dev.txt             |  6 ++-
 mlnode/resources/conf/iotdb-mlnode.toml |  8 +++
 mlnode/test/test_model_storage.py       | 78 +++++++++++++++++++++++++++
 mlnode/test/test_serde.py               | 54 ++++++++++++++++---
 12 files changed, 274 insertions(+), 15 deletions(-)

diff --git a/mlnode/.gitignore b/mlnode/.gitignore
index ba68b5e54e..9ba0ff6df8 100644
--- a/mlnode/.gitignore
+++ b/mlnode/.gitignore
@@ -3,4 +3,4 @@
 # generated by Pypi
 /build/
 /dist/
-/*.egg-info/
+/*.egg-info/
\ No newline at end of file
diff --git a/mlnode/iotdb/mlnode/config.py b/mlnode/iotdb/mlnode/config.py
index 64155be03c..e59338209a 100644
--- a/mlnode/iotdb/mlnode/config.py
+++ b/mlnode/iotdb/mlnode/config.py
@@ -20,7 +20,8 @@ import os
 from dynaconf import Dynaconf
 
 from iotdb.mlnode.constant import (MLNODE_CONF_DIRECTORY_NAME,
-                                   MLNODE_CONF_FILE_NAME)
+                                   MLNODE_CONF_FILE_NAME,
+                                   MLNODE_MODEL_STORAGE_DIRECTORY_NAME)
 from iotdb.mlnode.exception import BadNodeUrlError
 from iotdb.mlnode.log import logger
 from iotdb.mlnode.util import parse_endpoint_url
@@ -33,6 +34,12 @@ class MLNodeConfig(object):
         self.__mn_rpc_address: str = "127.0.0.1"
         self.__mn_rpc_port: int = 10810
 
+        # Directory to save models
+        self.__mn_model_storage_dir = MLNODE_MODEL_STORAGE_DIRECTORY_NAME
+
+        # Cache number of model storage to avoid repeated loading
+        self.__mn_model_storage_cache_size = 30
+
         # Target ConfigNode to be connected by MLNode
         self.__mn_target_config_node: TEndPoint = TEndPoint("127.0.0.1", 10710)
 
@@ -51,6 +58,18 @@ class MLNodeConfig(object):
     def set_mn_rpc_port(self, mn_rpc_port: int) -> None:
         self.__mn_rpc_port = mn_rpc_port
 
+    def get_mn_model_storage_dir(self) -> str:
+        return self.__mn_model_storage_dir
+
+    def set_mn_model_storage_dir(self, mn_model_storage_dir: str):
+        self.__mn_model_storage_dir = mn_model_storage_dir
+
+    def get_mn_model_storage_cache_size(self) -> int:
+        return self.__mn_model_storage_cache_size
+
+    def set_mn_model_storage_cache_size(self, mn_model_storage_cache_size: int):
+        self.__mn_model_storage_cache_size = mn_model_storage_cache_size
+
     def get_mn_target_config_node(self) -> TEndPoint:
         return self.__mn_target_config_node
 
@@ -90,6 +109,12 @@ class MLNodeDescriptor(object):
             if file_configs.mn_rpc_port is not None:
                 self.__config.set_mn_rpc_port(file_configs.mn_rpc_port)
 
+            if file_configs.mn_model_storage_dir is not None:
+                self.__config.set_mn_model_storage_dir(file_configs.mn_model_storage_dir)
+
+            if file_configs.mn_model_storage_cache_size is not None:
+                self.__config.set_mn_model_storage_cachesize(file_configs.mn_model_storage_cache_size)
+
             if file_configs.mn_target_config_node is not None:
                 self.__config.set_mn_target_config_node(file_configs.mn_target_config_node)
 
diff --git a/mlnode/iotdb/mlnode/constant.py b/mlnode/iotdb/mlnode/constant.py
index 95f25f506c..8a38aa95d8 100644
--- a/mlnode/iotdb/mlnode/constant.py
+++ b/mlnode/iotdb/mlnode/constant.py
@@ -19,3 +19,5 @@
 MLNODE_CONF_DIRECTORY_NAME = "conf"
 MLNODE_CONF_FILE_NAME = "iotdb-mlnode.toml"
 MLNODE_LOG_CONF_FILE_NAME = "logging_config.ini"
+
+MLNODE_MODEL_STORAGE_DIRECTORY_NAME = "models"
diff --git a/mlnode/iotdb/mlnode/exception.py b/mlnode/iotdb/mlnode/exception.py
index 350916a665..6307909a9a 100644
--- a/mlnode/iotdb/mlnode/exception.py
+++ b/mlnode/iotdb/mlnode/exception.py
@@ -24,3 +24,8 @@ class _BaseError(Exception):
 class BadNodeUrlError(_BaseError):
     def __init__(self, node_url: str):
         self.message = "Bad node url: {}".format(node_url)
+
+
+class ModelNotExistError(_BaseError):
+    def __init__(self, file_path: str):
+        self.message = "Model path: ({}) not exists".format(file_path)
diff --git a/mlnode/iotdb/mlnode/model_storage.py b/mlnode/iotdb/mlnode/model_storage.py
new file mode 100644
index 0000000000..ee745689b1
--- /dev/null
+++ b/mlnode/iotdb/mlnode/model_storage.py
@@ -0,0 +1,95 @@
+# 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 json
+import os
+import shutil
+
+import torch
+import torch.nn as nn
+from pylru import lrucache
+
+from iotdb.mlnode.config import config
+from iotdb.mlnode.exception import ModelNotExistError
+
+
+class ModelStorage(object):
+    def __init__(self):
+        self.__model_dir = os.path.join(os.getcwd(), config.get_mn_model_storage_dir())
+        if not os.path.exists(self.__model_dir):
+            os.mkdir(self.__model_dir)
+
+        self.__model_cache = lrucache(config.get_mn_model_storage_cache_size())
+
+    def save_model(self,
+                   model: nn.Module,
+                   model_config: dict,
+                   model_id: str,
+                   trial_id: str) -> None:
+        """
+        Note: model config for time series should contain 'input_len' and 'input_vars'
+        """
+        model_dir_path = os.path.join(self.__model_dir, f'{model_id}')
+        if not os.path.exists(model_dir_path):
+            os.mkdir(model_dir_path)
+        model_file_path = os.path.join(model_dir_path, f'{trial_id}.pt')
+
+        sample_input = [torch.randn(1, model_config['input_len'], model_config['input_vars'])]
+        torch.jit.save(torch.jit.trace(model, sample_input),
+                       model_file_path,
+                       _extra_files={'model_config': json.dumps(model_config)})
+
+    def load_model(self, model_id: str, trial_id: str) -> (torch.jit.ScriptModule, dict):
+        """
+        Returns:
+            jit_model: a ScriptModule contains model architecture and parameters, which can be deployed cross-platform
+            model_config: a dict contains model attributes
+        """
+        file_path = os.path.join(self.__model_dir, f'{model_id}', f'{trial_id}.pt')
+        if model_id in self.__model_cache:
+            return self.__model_cache[file_path]
+        else:
+            if not os.path.exists(file_path):
+                raise ModelNotExistError(file_path)
+            else:
+                tmp_dict = {'model_config': ''}
+                jit_model = torch.jit.load(file_path, _extra_files=tmp_dict)
+                model_config = json.loads(tmp_dict['model_config'])
+                self.__model_cache[file_path] = jit_model, model_config
+                return jit_model, model_config
+
+    def delete_model(self, model_id: str) -> None:
+        model_dir_path = os.path.join(self.__model_dir, f'{model_id}')
+        if os.path.exists(model_dir_path):
+            for file_name in os.listdir(model_dir_path):
+                self.__remove_from_cache(os.path.join(model_dir_path, file_name))
+            shutil.rmtree(model_dir_path)
+
+    def delete_trial(self, model_id: str, trial_id: str) -> None:
+        model_file_path = os.path.join(self.__model_dir, f'{model_id}', f'{trial_id}.pt')
+        self.__remove_from_cache(model_file_path)
+        if os.path.exists(model_file_path):
+            os.remove(model_file_path)
+
+    def __remove_from_cache(self, file_path: str) -> None:
+        if file_path in self.__model_cache:
+            del self.__model_cache[file_path]
+
+
+# initialize a singleton
+model_storage = ModelStorage()
diff --git a/mlnode/iotdb/mlnode/serde.py b/mlnode/iotdb/mlnode/serde.py
index 4f491db606..26860faf38 100644
--- a/mlnode/iotdb/mlnode/serde.py
+++ b/mlnode/iotdb/mlnode/serde.py
@@ -17,6 +17,7 @@
 #
 import numpy as np
 import pandas as pd
+
 from iotdb.utils.IoTDBConstants import TSDataType
 
 TIMESTAMP_STR = "Time"
@@ -81,12 +82,10 @@ def convert_to_df(name_list, type_list, name_index, binary_list):
         for i in range(len(column_values)):
             column_name = column_name_list[i + 1]
 
-            location = (
-                    column_ordinal_dict[column_name] - START_INDEX
-            )
-
+            location = column_ordinal_dict[column_name] - START_INDEX
             if location < 0:
                 continue
+
             data_type = column_type_deduplicated_list[location]
             value_buffer = column_values[location]
             value_buffer_len = len(value_buffer)
@@ -200,7 +199,8 @@ def deserialize(buffer):
     column_values = [None] * value_column_count
     null_indicators = [None] * value_column_count
     for i in range(value_column_count):
-        column_value, nullIndicator, buffer = read_column(column_encodings[i + 1], buffer, data_types[i], position_count)
+        column_value, nullIndicator, buffer = read_column(column_encodings[i + 1], buffer, data_types[i],
+                                                          position_count)
         column_values[i] = column_value
         null_indicators[i] = nullIndicator
 
diff --git a/mlnode/iotdb/mlnode/util.py b/mlnode/iotdb/mlnode/util.py
index c15ec6b89f..8932479c4a 100644
--- a/mlnode/iotdb/mlnode/util.py
+++ b/mlnode/iotdb/mlnode/util.py
@@ -17,7 +17,6 @@
 #
 from iotdb.mlnode.exception import BadNodeUrlError
 from iotdb.mlnode.log import logger
-
 from iotdb.thrift.common.ttypes import TEndPoint
 
 
diff --git a/mlnode/requirements.txt b/mlnode/requirements.txt
index b5a8578e24..05397f0df5 100644
--- a/mlnode/requirements.txt
+++ b/mlnode/requirements.txt
@@ -20,3 +20,4 @@ pandas>=1.3.5
 numpy>=1.21.4
 apache-iotdb
 poetry
+pylru
diff --git a/mlnode/requirements_dev.txt b/mlnode/requirements_dev.txt
index e9a9f4bb38..f3e9ad3cf6 100644
--- a/mlnode/requirements_dev.txt
+++ b/mlnode/requirements_dev.txt
@@ -18,4 +18,8 @@
 
 -r requirements.txt
 # Pytest to run tests
-pytest==7.2.0
+
+pytest
+thrift
+dynaconf
+torch
\ No newline at end of file
diff --git a/mlnode/resources/conf/iotdb-mlnode.toml b/mlnode/resources/conf/iotdb-mlnode.toml
index 0c82425ece..a029509e64 100644
--- a/mlnode/resources/conf/iotdb-mlnode.toml
+++ b/mlnode/resources/conf/iotdb-mlnode.toml
@@ -31,6 +31,14 @@ mn_rpc_address = "127.0.0.1"
 # Datatype: int
 mn_rpc_port = 10810
 
+# Directory to save models
+# Datatype: String
+mn_model_storage_dir = "models"
+
+# Cache number of model storage to avoid repeated loading
+# Datatype: int
+mn_model_storage_cachesize = 30
+
 ####################
 ### Target Config Node
 ####################
diff --git a/mlnode/test/test_model_storage.py b/mlnode/test/test_model_storage.py
new file mode 100644
index 0000000000..99857db37e
--- /dev/null
+++ b/mlnode/test/test_model_storage.py
@@ -0,0 +1,78 @@
+# 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 os
+import time
+
+import torch.nn as nn
+
+from iotdb.mlnode.config import config
+from iotdb.mlnode.model_storage import model_storage
+
+
+class TestModel(nn.Module):
+    def __init__(self):
+        super(TestModel, self).__init__()
+        self.layer = nn.Identity()
+
+    def forward(self, x):
+        return self.layer(x)
+
+
+model = TestModel()
+model_config = {
+    'input_len': 1,
+    'input_vars': 1,
+    'id': time.time()
+}
+
+
+def test_save_model():
+    trial_id = 'tid_0'
+    model_id = 'mid_test_model_save'
+    model_storage.save_model(model, model_config, model_id=model_id, trial_id=trial_id)
+    assert os.path.exists(os.path.join(config.get_mn_model_storage_dir(), model_id, f'{trial_id}.pt'))
+
+
+def test_load_model():
+    trial_id = 'tid_0'
+    model_id = 'mid_test_model_load'
+    model_storage.save_model(model, model_config, model_id=model_id, trial_id=trial_id)
+    model_loaded, model_config_loaded = model_storage.load_model(model_id=model_id, trial_id=trial_id)
+    assert model_config == model_config_loaded
+
+
+def test_delete_model():
+    trial_id1 = 'tid_1'
+    trial_id2 = 'tid_2'
+    model_id = 'mid_test_model_delete'
+    model_storage.save_model(model, model_config, model_id=model_id, trial_id=trial_id1)
+    model_storage.save_model(model, model_config, model_id=model_id, trial_id=trial_id2)
+    model_storage.delete_model(model_id=model_id)
+    assert not os.path.exists(os.path.join(config.get_mn_model_storage_dir(), model_id, f'{trial_id1}.pt'))
+    assert not os.path.exists(os.path.join(config.get_mn_model_storage_dir(), model_id, f'{trial_id2}.pt'))
+    assert not os.path.exists(os.path.join(config.get_mn_model_storage_dir(), model_id))
+
+
+def test_delete_trial():
+    trial_id = 'tid_0'
+    model_id = 'mid_test_model_delete'
+    model_storage.save_model(model, model_config, model_id=model_id, trial_id=trial_id)
+    model_storage.delete_trial(model_id=model_id, trial_id=trial_id)
+    assert not os.path.exists(os.path.join(config.get_mn_model_storage_dir(), model_id, f'{trial_id}.pt'))
diff --git a/mlnode/test/test_serde.py b/mlnode/test/test_serde.py
index c7ff5c49b9..c05083be41 100644
--- a/mlnode/test/test_serde.py
+++ b/mlnode/test/test_serde.py
@@ -16,13 +16,12 @@
 # under the License.
 #
 
-import random
-import time
 import numpy as np
 import pandas as pd
-from iotdb.mlnode.serde import convert_to_df
 from pandas.testing import assert_frame_equal
 
+from iotdb.mlnode.serde import convert_to_df
+
 device_id = "root.wt1"
 
 ts_path_lst = [
@@ -43,11 +42,54 @@ measurements = [
 ]
 
 simple_binary = [
-    b'\x00\x00\x00\x06\x04\x03\x05\x00\x02\x01\x00\x00\x00\x14\x02\x02\x01\x03\x00\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\t\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x [...]
+    b'\x00\x00\x00\x06\x04\x03\x05\x00\x02\x01\x00\x00\x00\x14\x02\x02\x01\x03\x00\x02\x01\x00\x00\x00\x00\x00\x00'
+    b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03'
+    b'\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00'
+    b'\x00\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\t\x00\x00\x00\x00\x00\x00\x00'
+    b'\n\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x00\x00\r\x00\x00\x00'
+    b'\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x0f\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00'
+    b'\x00\x11\x00\x00\x00\x00\x00\x00\x00\x12\x00\x00\x00\x00\x00\x00\x00\x13\x00?\xc2\xfd\xe3\x85\xa0\xe9D?\xd2\x93'
+    b'^\xf7\xb2(\xb2?\xe4$\x8cf\xd6"\xe2?\xe9L\xdc\x0b\xd9\xfd\xe3?\xb5+57\xf01V?\xed\xf0\xae\xc9\x81\x93\xbe?\xde'
+    b'\xc7\x8fK7\x0b\x04?\xdc\x88\xd0h\xe3\x99B?\xed\x94\x1c\x1c\x15_c?\xe0\xe4g\xcepe\xef?\xde$\xde\x10\x96\xfc\x05'
+    b'?\x95\x9b\xddk\xabt\xb8?\xd8\x1a3\xe8\x8f\xcb\xe5?\xd8\x14\x0c\xd2Kf\xdc?\xd4A\x83xE\x0b"?\xeb\xb41\xa5\xbfl'
+    b'\xbd?\xdf\'\xa0-\x06\x9eU?\xcb\xcc_\xaa\t\xa9L?\xd1\xc5s1z\xf7B?\xea\xab\xdc\x16\xc1\xb8r\x00>\xc6\xcf\xca>\x08'
+    b'\xc1\xc6=\xc11\x97>\xa0&7?W\x14\x0b>\x94o\x97=\x8c\xad\x05>\xed2\x96>Bgg?nX:?1t\xac?\x13\xb6\xe1?I\x82*?6\xfb'
+    b'\x08?Q\xb0j>\x08K5?n\xd8!?7\xe2\xc1>\xdcG@?^x\x9d\x00\x00\x00\x00\x05text1\x00\x00\x00\x05text2\x00\x00\x00'
+    b'\x05text2\x00\x00\x00\x05text1\x00\x00\x00\x05text1\x00\x00\x00\x05text1\x00\x00\x00\x05text2\x00\x00\x00'
+    b'\x05text2\x00\x00\x00\x05text2\x00\x00\x00\x05text1\x00\x00\x00\x05text1\x00\x00\x00\x05text1\x00\x00\x00'
+    b'\x05text2\x00\x00\x00\x05text2\x00\x00\x00\x05text1\x00\x00\x00\x05text2\x00\x00\x00\x05text1\x00\x00\x00'
+    b'\x05text1\x00\x00\x00\x05text1\x00\x00\x00\x05text1\x00['
+    b'\xd0\xe0\x00\x00\x00\x00\x00\x00\x00\x00\\\x00\x00\x00\x00\x00\x00\x00W\x00\x00\x00\x00\x00\x00\x00\r\x00\x00'
+    b'\x00\x00\x00\x00\x00S\x00\x00\x00\x00\x00\x00\x00G\x00\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x00S'
+    b'\x00\x00\x00\x00\x00\x00\x00>\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00P\x00\x00\x00\x00\x00'
+    b'\x00\x00\x17\x00\x00\x00\x00\x00\x00\x00\x1c\x00\x00\x00\x00\x00\x00\x00]\x00\x00\x00\x00\x00\x00\x00\x1e\x00'
+    b'\x00\x00\x00\x00\x00\x00\x15\x00\x00\x00\x00\x00\x00\x00V\x00\x00\x00\x00\x00\x00\x00Q\x00\x00\x00\x00\x00\x00'
+    b'\x00-\x00\x00\x00\x00\x00\x00\x00>\x00\x00\x00\x00\x00\x00\x00Y\x00\x00\x00\x00]\x00\x00\x00@\x00\x00\x00/\x00'
+    b'\x00\x00\x17\x00\x00\x00P\x00\x00\x00O\x00\x00\x00\x0f\x00\x00\x00+\x00\x00\x00O\x00\x00\x00\x12\x00\x00\x00'
+    b'\x11\x00\x00\x00:\x00\x00\x00L\x00\x00\x00-\x00\x00\x00\x1b\x00\x00\x00W\x00\x00\x00['
+    b'\x00\x00\x00\x19\x00\x00\x00H\x00\x00\x00_']
 binary_with_null = \
     [
-        b'\x00\x00\x00\x06\x04\x03\x05\x00\x02\x01\x00\x00\x00\x13\x02\x02\x01\x03\x00\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\t\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x [...]
-        b'\x00\x00\x00\x06\x04\x03\x05\x00\x02\x01\x00\x00\x00\x01\x02\x02\x01\x03\x04\x02\x04\x00\x00\x00\x00\x00\x00\x00\x00\x13\x00?\xd3\xc7\xae#\x86\xe1j\x00?\x10\xea!\x00\x00\x00\x00\x05text1\x00\x01\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00?\x01\x01\x80']
+        b'\x00\x00\x00\x06\x04\x03\x05\x00\x02\x01\x00\x00\x00\x13\x02\x02\x01\x03\x00\x02\x01\x00\x00\x00\x00\x00'
+        b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00'
+        b'\x00\x03\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x06'
+        b'\x00\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\t\x00\x00\x00'
+        b'\x00\x00\x00\x00\n\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x00'
+        b'\x00\r\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x0f\x00\x00\x00\x00\x00\x00\x00\x10\x00'
+        b'\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x12\x01W4\xc0?\xe0\x07z\xef['
+        b'\x18\xd0?\xd1=j\xa5l/\x8a?\xd4,<\x0ex\xb9\xf0?\xea\xfb\x1c~\x99Nq?\xe2\xe3\xce\xf3y\xee\xf4?\xc0\xd6\x03'
+        b'\xc0\x8b\x19,?\xe8\x15\x91\x0f!t\xf5?\xed\xc7%@JuA?\xea#OkLX,'
+        b'\x011\x11\x00=IC7>\xf6~\x18>\xa4P\x89>\xa0\xee\x08<\xf6\xe8\xd2<\xe1\xaa\xa0>\x89\xa1~?v4Q>\x9eK\xda>\xe6'
+        b'\x1e)>\xd4F_?-\x17c?*\x01\x8f>\xe8\t+\x01G\xdc\xa0\x00\x00\x00\x05text1\x00\x00\x00\x05text1\x00\x00\x00'
+        b'\x05text2\x00\x00\x00\x05text1\x00\x00\x00\x05text1\x00\x00\x00\x05text2\x00\x00\x00\x05text2\x00\x00\x00'
+        b'\x05text2\x01&\xec@\x88\x02\x00\x01\xee\xb6\xa0\x00\x00\x00\x00\x00\x00\x00B\x00\x00\x00\x00\x00\x00\x00'
+        b'\x15\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00V\x00\x00\x00\x00\x00\x00\x00>\x00\x00\x00'
+        b'\x00\x00\x00\x00#\x01r[\x00\x00\x00\x00['
+        b'\x00\x00\x00\x0b\x00\x00\x00T\x00\x00\x00P\x00\x00\x00W\x00\x00\x00\x11\x00\x00\x00)\x00\x00\x00<\x00\x00'
+        b'\x00\x0e\x00\x00\x00\x18',
+        b'\x00\x00\x00\x06\x04\x03\x05\x00\x02\x01\x00\x00\x00\x01\x02\x02\x01\x03\x04\x02\x04\x00\x00\x00\x00\x00'
+        b'\x00\x00\x00\x13\x00?\xd3\xc7\xae#\x86\xe1j\x00?\x10\xea!\x00\x00\x00\x00\x05text1\x00\x01\x80\x00\x00\x00'
+        b'\x00\x00\x00\x00\x00\x00?\x01\x01\x80']
 column_names = ['root.wt1.altitude', 'root.wt1.temperature', 'root.wt1.angle', 'root.wt1.windspeed',
                 'root.wt1.hardware', 'root.wt1.status']
 data_type_list = ['INT64', 'FLOAT', 'INT32', 'DOUBLE', 'TEXT', 'BOOLEAN']