blob: dce23e5f3a9a34d8a27bf0a377715fc0fd7ff2b8 [file]
/*
* 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.
*/
#include "commands/sketch_layout.h"
#include <algorithm>
#include <iomanip>
#include <limits>
#include <sstream>
#include "common/allocator/byte_stream.h"
#include "common/device_id.h"
#include "format/output_format.h"
#include "utils/errno_define.h"
namespace tsfile_cli {
namespace {
constexpr int32_t kMinChunkHeaderSize = 7;
constexpr int32_t kMaxHeaderProbeBytes = 1 << 20;
constexpr int32_t kDeviceHeaderProbeBytes = 64 << 10;
const char* const kMetadataMissing = "<metadata-missing>";
std::string to_string_i64(int64_t v) {
std::ostringstream ss;
ss << v;
return ss.str();
}
std::string java_bool_string(bool value) { return value ? "true" : "false"; }
std::string java_floating_string(double value) {
std::ostringstream ss;
ss << std::setprecision(15) << value;
std::string result = ss.str();
if (result.find('.') == std::string::npos &&
result.find('E') == std::string::npos &&
result.find('e') == std::string::npos) {
result += ".0";
}
return result;
}
std::string java_stat_base(storage::Statistic* stat) {
std::ostringstream ss;
ss << "startTime: " << stat->start_time_ << " endTime: " << stat->end_time_
<< " count: " << stat->count_;
return ss.str();
}
template <typename T>
std::string java_numeric_stat_string(storage::Statistic* stat, T min_value,
T max_value, T first_value, T last_value,
const std::string& sum_value) {
std::ostringstream ss;
ss << java_stat_base(stat) << " [minValue:" << min_value
<< ",maxValue:" << max_value << ",firstValue:" << first_value
<< ",lastValue:" << last_value << ",sumValue:" << sum_value << "]";
return ss.str();
}
std::string statistic_string(storage::Statistic* stat) {
if (stat == nullptr) {
return std::string();
}
switch (stat->get_type()) {
case common::VECTOR:
return java_stat_base(stat);
case common::INT32:
case common::DATE: {
storage::Int32Statistic* typed =
dynamic_cast<storage::Int32Statistic*>(stat);
return typed == nullptr
? stat->to_string()
: java_numeric_stat_string(
stat, typed->min_value_, typed->max_value_,
typed->first_value_, typed->last_value_,
to_string_i64(typed->sum_value_));
}
case common::INT64:
case common::TIMESTAMP: {
storage::Int64Statistic* typed =
dynamic_cast<storage::Int64Statistic*>(stat);
return typed == nullptr
? stat->to_string()
: java_numeric_stat_string(
stat, typed->min_value_, typed->max_value_,
typed->first_value_, typed->last_value_,
java_floating_string(typed->sum_value_));
}
case common::FLOAT: {
storage::FloatStatistic* typed =
dynamic_cast<storage::FloatStatistic*>(stat);
return typed == nullptr
? stat->to_string()
: java_numeric_stat_string(
stat, java_floating_string(typed->min_value_),
java_floating_string(typed->max_value_),
java_floating_string(typed->first_value_),
java_floating_string(typed->last_value_),
java_floating_string(typed->sum_value_));
}
case common::DOUBLE: {
storage::DoubleStatistic* typed =
dynamic_cast<storage::DoubleStatistic*>(stat);
return typed == nullptr
? stat->to_string()
: java_numeric_stat_string(
stat, java_floating_string(typed->min_value_),
java_floating_string(typed->max_value_),
java_floating_string(typed->first_value_),
java_floating_string(typed->last_value_),
java_floating_string(typed->sum_value_));
}
case common::BOOLEAN: {
storage::BooleanStatistic* typed =
dynamic_cast<storage::BooleanStatistic*>(stat);
if (typed == nullptr) {
return stat->to_string();
}
std::ostringstream ss;
ss << java_stat_base(stat)
<< " [firstValue=" << java_bool_string(typed->first_value_)
<< ", lastValue=" << java_bool_string(typed->last_value_)
<< ", sumValue=" << typed->sum_value_ << "]";
return ss.str();
}
case common::TEXT: {
storage::TextStatistic* typed =
dynamic_cast<storage::TextStatistic*>(stat);
if (typed == nullptr) {
return stat->to_string();
}
std::ostringstream ss;
ss << java_stat_base(stat)
<< " [firstValue:" << typed->first_value_.to_std_string()
<< ",lastValue:" << typed->last_value_.to_std_string() << "]";
return ss.str();
}
case common::STRING: {
storage::StringStatistic* typed =
dynamic_cast<storage::StringStatistic*>(stat);
if (typed == nullptr) {
return stat->to_string();
}
std::ostringstream ss;
ss << java_stat_base(stat)
<< " [firstValue:" << typed->first_value_.to_std_string()
<< ", lastValue:" << typed->last_value_.to_std_string()
<< ", minValue:" << typed->min_value_.to_std_string()
<< ", maxValue:" << typed->max_value_.to_std_string() << "]";
return ss.str();
}
case common::BLOB:
return "BlobStatistics{}";
default:
return stat->to_string();
}
}
bool is_chunk_marker(unsigned char marker) {
const unsigned char marker_type = marker & 0x3F;
return marker == static_cast<unsigned char>(storage::CHUNK_HEADER_MARKER) ||
marker == static_cast<unsigned char>(
storage::ONLY_ONE_PAGE_CHUNK_HEADER_MARKER) ||
marker_type ==
static_cast<unsigned char>(storage::CHUNK_HEADER_MARKER) ||
marker_type == static_cast<unsigned char>(
storage::ONLY_ONE_PAGE_CHUNK_HEADER_MARKER);
}
bool is_one_page_chunk(char chunk_type) {
return (static_cast<unsigned char>(chunk_type) & 0x3F) ==
static_cast<unsigned char>(
storage::ONLY_ONE_PAGE_CHUNK_HEADER_MARKER);
}
int64_t read_i64_from_big_endian(char* buffer) {
return static_cast<int64_t>(common::SerializationUtil::read_ui64(buffer));
}
uint32_t java_int_string_size(const std::string& s) {
return static_cast<uint32_t>(4 + s.size());
}
uint32_t java_var_int_size(uint32_t value) {
uint32_t u_value = value << 1;
uint32_t position = 1;
while ((u_value & 0xFFFFFF80) != 0) {
u_value >>= 7;
position++;
}
return position;
}
uint32_t unsigned_var_int_size(uint32_t value) {
uint32_t position = 1;
while ((value & 0xFFFFFF80) != 0) {
value >>= 7;
position++;
}
return position;
}
} // namespace
SketchLayout::SketchLayout()
: pa_(common::MOD_TSFILE_READER), tsfile_meta_(&pa_) {
pa_.init(4096, common::MOD_TSFILE_READER);
}
SketchLayout::~SketchLayout() {
node_by_offset_.clear();
metadata_nodes_.clear();
footer_.roots.clear();
}
int SketchLayout::load_file(const std::string& path) {
path_ = path;
int ret = file_.open(path_);
if (ret != common::E_OK) {
return ret;
}
file_size_ = file_.file_size();
if (file_size_ < kSketchFileHeaderSize + kSketchFileTailSize) {
return common::E_TSFILE_CORRUPTED;
}
std::vector<char> head;
ret = read_block(0, kSketchFileHeaderSize, &head);
if (ret != common::E_OK) {
return ret;
}
head_magic_.assign(head.data(), storage::MAGIC_STRING_TSFILE_LEN);
version_ = head[storage::MAGIC_STRING_TSFILE_LEN];
std::vector<char> tail;
ret = read_block(file_size_ - kSketchFileTailSize, kSketchFileTailSize,
&tail);
if (ret != common::E_OK) {
return ret;
}
file_metadata_size_ = common::SerializationUtil::read_ui32(tail.data());
tail_magic_.assign(tail.data() + 4, storage::MAGIC_STRING_TSFILE_LEN);
file_metadata_pos_ = file_size_ -
static_cast<int64_t>(file_metadata_size_) -
kSketchFileTailSize;
if (file_metadata_size_ == 0 ||
file_metadata_pos_ < kSketchFileHeaderSize ||
file_metadata_pos_ >= file_size_) {
return common::E_TSFILE_CORRUPTED;
}
if (file_metadata_size_ >
static_cast<uint32_t>(std::numeric_limits<int32_t>::max())) {
add_blocker(
"TsFileMetadata is larger than the C++ sketch reader "
"can buffer; metadata output is truncated.");
return common::E_OK;
}
ret = read_block(file_metadata_pos_, file_metadata_size_, &metadata_buf_);
if (ret != common::E_OK) {
return ret;
}
common::ByteStream metadata_stream;
metadata_stream.wrap_from(metadata_buf_.data(),
static_cast<int32_t>(metadata_buf_.size()));
ret = tsfile_meta_.deserialize_from(metadata_stream);
if (ret != common::E_OK) {
return ret;
}
if (metadata_stream.read_pos() > metadata_buf_.size()) {
return common::E_TSFILE_CORRUPTED;
}
ret = trace_footer_layout();
if (ret == common::E_OK) {
metadata_loaded_ = true;
}
return ret;
}
int SketchLayout::collect_metadata_layout() {
for (auto& root_entry : tsfile_meta_.table_metadata_index_node_map_) {
const std::string& table = root_entry.first;
int64_t offset = -1;
int64_t end_offset = -1;
auto off = root_offsets_by_table_.find(table);
if (off != root_offsets_by_table_.end()) {
offset = off->second;
}
auto end = root_ends_by_table_.find(table);
if (end != root_ends_by_table_.end()) {
end_offset = end->second;
}
add_metadata_node(table, "", offset, end_offset, true,
root_entry.second);
int ret = traverse_index_node(table, root_entry.second, true,
std::shared_ptr<storage::IDeviceID>());
if (ret != common::E_OK) {
return ret;
}
}
return common::E_OK;
}
int SketchLayout::scan_data_area() {
int64_t pos = kSketchFileHeaderSize;
ChunkGroupSketch* current_group = nullptr;
while (pos < file_metadata_pos_) {
unsigned char marker = 0;
int ret = read_marker(pos, &marker);
if (ret != common::E_OK) {
return ret;
}
if (marker == static_cast<unsigned char>(storage::SEPARATOR_MARKER)) {
separator_offset_ = pos;
separator_marker_ = marker;
break;
}
if (marker ==
static_cast<unsigned char>(storage::CHUNK_GROUP_HEADER_MARKER)) {
ChunkGroupSketch group;
group.offset = pos;
ret = parse_chunk_group_header(pos, &group);
if (ret != common::E_OK) {
return ret;
}
groups_.push_back(group);
current_group = &groups_.back();
pos += 1 + group.device_id_size;
continue;
}
if (marker ==
static_cast<unsigned char>(storage::OPERATION_INDEX_RANGE)) {
OperationIndexRangeSketch op;
op.offset = pos;
ret = parse_operation_index_range(pos, &op);
if (ret != common::E_OK) {
return ret;
}
operation_ranges_.push_back(op);
pos += 1 + 16;
continue;
}
if (is_chunk_marker(marker)) {
if (current_group == nullptr) {
return common::E_TSFILE_CORRUPTED;
}
ChunkSketch chunk;
ret = parse_chunk(pos, current_group->device, &chunk);
if (ret != common::E_OK) {
return ret;
}
pos += chunk.header_size + chunk.header.data_size_;
current_group->chunks.push_back(chunk);
continue;
}
return common::E_TSFILE_CORRUPTED;
}
if (separator_offset_ < 0) {
return common::E_TSFILE_CORRUPTED;
}
return common::E_OK;
}
void SketchLayout::close() { file_.close(); }
void SketchLayout::add_blocker(const std::string& blocker) {
if (std::find(blockers_.begin(), blockers_.end(), blocker) ==
blockers_.end()) {
blockers_.push_back(blocker);
}
}
std::string SketchLayout::error_text(int ret) const {
return std::string(error_code_message(ret)) + " (code " +
to_string_i64(ret) + ")";
}
int SketchLayout::read_block(int64_t offset, int64_t len,
std::vector<char>* out) {
if (len < 0) {
return common::E_OUT_OF_RANGE;
}
if (len > static_cast<int64_t>(std::numeric_limits<int32_t>::max())) {
add_blocker("read range [" + to_string_i64(offset) + ", " +
to_string_i64(offset + len) +
") exceeds the C++ sketch reader buffer limit; output "
"is truncated.");
return common::E_OUT_OF_RANGE;
}
out->assign(static_cast<size_t>(len), '\0');
if (len == 0) {
return common::E_OK;
}
int32_t read_len = 0;
int ret =
file_.read(offset, out->data(), static_cast<int32_t>(len), read_len);
if (ret != common::E_OK) {
return ret;
}
return read_len == static_cast<int32_t>(len) ? common::E_OK
: common::E_TSFILE_CORRUPTED;
}
int SketchLayout::read_marker(int64_t offset, unsigned char* marker) {
char ch = 0;
int32_t read_len = 0;
int ret = file_.read(offset, &ch, 1, read_len);
if (ret != common::E_OK) {
return ret;
}
if (read_len != 1) {
return common::E_TSFILE_CORRUPTED;
}
*marker = static_cast<unsigned char>(ch);
return common::E_OK;
}
int SketchLayout::trace_footer_layout() {
common::ByteStream bs;
bs.wrap_from(metadata_buf_.data(),
static_cast<int32_t>(metadata_buf_.size()));
int ret = common::E_OK;
footer_.table_index_count_offset = file_metadata_pos_ + bs.read_pos();
if (RET_FAIL(common::SerializationUtil::read_var_uint(
footer_.table_index_count, bs))) {
return ret;
}
int64_t java_pos = footer_.table_index_count_offset + 4;
common::PageArena trace_pa(common::MOD_TSFILE_READER);
trace_pa.init(4096, common::MOD_TSFILE_READER);
for (uint32_t i = 0; i < footer_.table_index_count; ++i) {
FooterRootSketch root;
if (RET_FAIL(common::SerializationUtil::read_var_str(root.table, bs))) {
return ret;
}
root.table_name_offset = java_pos;
root.table_name_size = java_int_string_size(root.table);
java_pos += root.table_name_size;
const int64_t actual_node_offset = file_metadata_pos_ + bs.read_pos();
root.node_offset = java_pos;
storage::MetaIndexNode trace_node(&trace_pa);
if (RET_FAIL(trace_node.device_deserialize_from(bs))) {
return ret;
}
root.node_size = static_cast<uint32_t>(
file_metadata_pos_ + bs.read_pos() - actual_node_offset);
java_pos += root.node_size;
auto node_it =
tsfile_meta_.table_metadata_index_node_map_.find(root.table);
if (node_it != tsfile_meta_.table_metadata_index_node_map_.end()) {
root.node = node_it->second;
}
root_offsets_by_table_[root.table] = root.node_offset;
root_ends_by_table_[root.table] = root.node_offset + root.node_size;
footer_.roots.push_back(root);
}
footer_.table_schema_count_offset = java_pos;
if (RET_FAIL(common::SerializationUtil::read_var_uint(
footer_.table_schema_count, bs))) {
return ret;
}
java_pos += 4;
for (uint32_t i = 0; i < footer_.table_schema_count; ++i) {
FooterSchemaSketch schema;
if (RET_FAIL(
common::SerializationUtil::read_var_str(schema.table, bs))) {
return ret;
}
schema.table_name_offset = java_pos;
schema.table_name_size = java_int_string_size(schema.table);
const uint32_t actual_schema_start = bs.read_pos();
schema.schema_offset = java_pos;
storage::TableSchema trace_schema;
if (RET_FAIL(trace_schema.deserialize(bs))) {
return ret;
}
schema.schema_size = bs.read_pos() - actual_schema_start;
java_pos += schema.table_name_size + schema.schema_size;
auto schema_it = tsfile_meta_.table_schemas_.find(schema.table);
if (schema_it != tsfile_meta_.table_schemas_.end()) {
schema.schema = schema_it->second;
}
footer_.schemas.push_back(schema);
}
footer_.meta_offset_offset = java_pos;
if (RET_FAIL(common::SerializationUtil::read_i64(footer_.meta_offset_value,
bs))) {
return ret;
}
java_pos += 8;
footer_.bloom_filter_size_offset = java_pos;
if (RET_FAIL(common::SerializationUtil::read_var_uint(
footer_.bloom_filter_data_size, bs))) {
return ret;
}
java_pos += 4;
footer_.bloom_filter_offset = java_pos;
if (bs.remaining_size() < footer_.bloom_filter_data_size) {
return common::E_TSFILE_CORRUPTED;
}
bs.wrapped_buf_advance_read_pos(footer_.bloom_filter_data_size);
java_pos += footer_.bloom_filter_data_size;
if (footer_.bloom_filter_data_size > 0) {
if (RET_FAIL(common::SerializationUtil::read_var_uint(
footer_.bloom_filter_size, bs))) {
return ret;
}
if (RET_FAIL(common::SerializationUtil::read_var_uint(
footer_.bloom_filter_hash_count, bs))) {
return ret;
}
}
footer_.bloom_filter_serialized_size =
footer_.bloom_filter_data_size == 0
? 1
: footer_.bloom_filter_data_size + 4;
footer_.properties_offset = file_metadata_pos_ + bs.read_pos();
if (RET_FAIL(common::SerializationUtil::read_var_int(
footer_.properties_count, bs))) {
return ret;
}
for (int32_t i = 0; i < footer_.properties_count; ++i) {
FooterPropertySketch prop;
prop.offset = file_metadata_pos_ + bs.read_pos();
std::string* value = nullptr;
if (RET_FAIL(common::SerializationUtil::read_var_str(prop.key, bs))) {
return ret;
}
if (RET_FAIL(common::SerializationUtil::read_var_char_ptr(value, bs))) {
delete value;
return ret;
}
if (value != nullptr) {
prop.value = *value;
delete value;
}
footer_.properties.push_back(prop);
}
if (bs.read_pos() != metadata_buf_.size()) {
return common::E_TSFILE_CORRUPTED;
}
return common::E_OK;
}
void SketchLayout::add_metadata_node(
const std::string& table, const std::string& device, int64_t offset,
int64_t end_offset, bool device_entries,
std::shared_ptr<storage::MetaIndexNode> node) {
MetadataNodeSketch rec;
rec.table = table;
rec.device = device;
rec.offset = offset;
rec.end_offset = end_offset;
rec.device_entries = device_entries;
rec.node = node;
metadata_nodes_.push_back(rec);
if (offset >= 0) {
node_by_offset_[offset] = node;
}
}
int SketchLayout::traverse_index_node(
const std::string& table,
const std::shared_ptr<storage::MetaIndexNode>& node, bool device_entries,
std::shared_ptr<storage::IDeviceID> device_id) {
if (node == nullptr) {
return common::E_OK;
}
if (node->node_type_ == storage::LEAF_MEASUREMENT) {
return collect_timeseries_index(table, node, device_id);
}
for (size_t i = 0; i < node->children_.size(); ++i) {
const std::shared_ptr<storage::IMetaIndexEntry>& child =
node->children_[i];
int64_t child_end = node->end_offset_;
if (i + 1 < node->children_.size()) {
child_end = node->children_[i + 1]->get_offset();
}
std::shared_ptr<storage::IDeviceID> next_device = device_id;
bool child_device_entries = false;
if (node->node_type_ == storage::LEAF_DEVICE) {
next_device = child->get_device_id();
child_device_entries = false;
} else if (node->node_type_ == storage::INTERNAL_DEVICE) {
child_device_entries = true;
} else if (node->node_type_ == storage::INTERNAL_MEASUREMENT) {
child_device_entries = false;
} else {
return common::E_TSFILE_CORRUPTED;
}
std::shared_ptr<storage::MetaIndexNode> child_node;
int ret = read_index_node(child->get_offset(), child_end,
child_device_entries, &child_node);
if (ret != common::E_OK) {
return ret;
}
std::string device_name =
next_device == nullptr ? "" : next_device->get_device_name();
add_metadata_node(table, device_name, child->get_offset(), child_end,
child_device_entries, child_node);
ret = traverse_index_node(table, child_node, child_device_entries,
next_device);
if (ret != common::E_OK) {
return ret;
}
}
return common::E_OK;
}
int SketchLayout::read_index_node(
int64_t offset, int64_t end_offset, bool device_entries,
std::shared_ptr<storage::MetaIndexNode>* out) {
if (end_offset <= offset) {
return common::E_TSFILE_CORRUPTED;
}
std::vector<char> buf;
int ret = read_block(offset, end_offset - offset, &buf);
if (ret != common::E_OK) {
return ret;
}
void* node_buf = pa_.alloc(sizeof(storage::MetaIndexNode));
if (node_buf == nullptr) {
return common::E_OOM;
}
storage::MetaIndexNode* node = new (node_buf) storage::MetaIndexNode(&pa_);
std::shared_ptr<storage::MetaIndexNode> holder(
node, storage::MetaIndexNode::self_deleter);
common::ByteStream bs;
bs.wrap_from(buf.data(), static_cast<int32_t>(buf.size()));
ret = device_entries ? node->device_deserialize_from(bs)
: node->deserialize_from(bs);
if (ret != common::E_OK) {
return ret;
}
*out = holder;
return common::E_OK;
}
int SketchLayout::collect_timeseries_index(
const std::string& table,
const std::shared_ptr<storage::MetaIndexNode>& node,
const std::shared_ptr<storage::IDeviceID>& device_id) {
(void)table;
for (size_t i = 0; i < node->children_.size(); ++i) {
const std::shared_ptr<storage::IMetaIndexEntry>& child =
node->children_[i];
int64_t start = child->get_offset();
int64_t end = node->end_offset_;
if (i + 1 < node->children_.size()) {
end = node->children_[i + 1]->get_offset();
}
if (end <= start) {
return common::E_TSFILE_CORRUPTED;
}
std::vector<char> buf;
int ret = read_block(start, end - start, &buf);
if (ret != common::E_OK) {
return ret;
}
common::ByteStream bs;
bs.wrap_from(buf.data(), static_cast<int32_t>(buf.size()));
while (bs.has_remaining()) {
uint32_t before = bs.read_pos();
void* ts_buf = pa_.alloc(sizeof(storage::TimeseriesIndex));
if (ts_buf == nullptr) {
return common::E_OOM;
}
storage::TimeseriesIndex* ts_index =
new (ts_buf) storage::TimeseriesIndex();
ret = ts_index->deserialize_from(bs, &pa_);
if (ret != common::E_OK) {
return ret;
}
if (bs.read_pos() <= before) {
return common::E_TSFILE_CORRUPTED;
}
add_timeseries_record(start + before, start + bs.read_pos(),
device_id, ts_index);
}
}
return common::E_OK;
}
void SketchLayout::add_timeseries_record(
int64_t offset, int64_t end_offset,
const std::shared_ptr<storage::IDeviceID>& device_id,
storage::TimeseriesIndex* ts_index) {
std::string measurement = ts_index->get_measurement_name().to_std_string();
std::string device =
device_id == nullptr ? std::string() : device_id->get_device_name();
std::string path =
device.empty() ? measurement : device + "." + measurement;
TimeseriesSketch rec;
rec.offset = offset;
rec.end_offset = end_offset;
rec.device = device;
rec.measurement = measurement;
rec.path = path;
rec.data_type = tsdatatype_name(ts_index->get_data_type());
rec.statistics = statistic_string(ts_index->get_statistic());
rec.size_without_chunk_metadata =
static_cast<uint32_t>(end_offset - offset);
common::SimpleList<storage::ChunkMeta*>* chunks =
ts_index->get_chunk_meta_list();
rec.chunk_count = chunks == nullptr ? 0 : chunks->size();
if (chunks == nullptr) {
timeseries_by_offset_[offset] = rec;
return;
}
uint32_t chunk_index = 0;
for (common::SimpleList<storage::ChunkMeta*>::Iterator it = chunks->begin();
it != chunks->end(); it++, chunk_index++) {
storage::ChunkMeta* chunk_meta = it.get();
if (chunk_meta == nullptr) {
continue;
}
ChunkMetadataSketch chunk_rec;
chunk_rec.offset = chunk_meta->offset_of_chunk_header_;
chunk_rec.size =
serialized_chunk_metadata_size(chunk_meta, chunk_index != 0);
if (rec.size_without_chunk_metadata >= chunk_rec.size) {
rec.size_without_chunk_metadata -= chunk_rec.size;
}
rec.chunks.push_back(chunk_rec);
std::string stat = statistic_string(chunk_meta->statistic_);
if (stat.empty() && rec.chunk_count == 1) {
stat = rec.statistics;
}
chunk_path_by_offset_[chunk_meta->offset_of_chunk_header_] = path;
chunk_stat_by_offset_[chunk_meta->offset_of_chunk_header_] = stat;
}
timeseries_by_offset_[offset] = rec;
}
uint32_t SketchLayout::serialized_chunk_metadata_size(
storage::ChunkMeta* chunk_meta, bool include_statistics) {
if (include_statistics && chunk_meta->statistic_ == nullptr) {
include_statistics = false;
}
common::ByteStream bs(128, common::MOD_TSFILE_READER);
if (chunk_meta->serialize_to(bs, include_statistics) != common::E_OK) {
return 0;
}
return bs.total_size();
}
int SketchLayout::parse_chunk_group_header(int64_t marker_offset,
ChunkGroupSketch* group) {
const int64_t remaining = file_metadata_pos_ - marker_offset - 1;
const int32_t read_size = static_cast<int32_t>(
std::min<int64_t>(remaining, kDeviceHeaderProbeBytes));
if (read_size <= 0) {
return common::E_TSFILE_CORRUPTED;
}
std::vector<char> buf;
int ret = read_block(marker_offset + 1, read_size, &buf);
if (ret != common::E_OK) {
return ret;
}
common::ByteStream bs;
bs.wrap_from(buf.data(), read_size);
std::shared_ptr<storage::IDeviceID> device_id =
std::make_shared<storage::StringArrayDeviceID>();
ret = device_id->deserialize(bs);
if (ret != common::E_OK) {
return ret;
}
group->device = device_id->get_device_name();
group->device_id_size = bs.read_pos();
if (group->device_id_size == 0) {
return common::E_TSFILE_CORRUPTED;
}
return common::E_OK;
}
int SketchLayout::parse_operation_index_range(int64_t marker_offset,
OperationIndexRangeSketch* op) {
std::vector<char> buf;
int ret = read_block(marker_offset + 1, 16, &buf);
if (ret != common::E_OK) {
return ret;
}
op->min_plan_index = read_i64_from_big_endian(buf.data());
op->max_plan_index = read_i64_from_big_endian(buf.data() + 8);
return common::E_OK;
}
int SketchLayout::parse_chunk(int64_t chunk_offset, const std::string& device,
ChunkSketch* chunk) {
(void)device;
int ret = parse_chunk_header(chunk_offset, chunk);
if (ret != common::E_OK) {
return ret;
}
const int64_t data_offset = chunk_offset + chunk->header_size;
const int64_t total_end = data_offset + chunk->header.data_size_;
if (total_end > file_metadata_pos_) {
return common::E_TSFILE_CORRUPTED;
}
std::vector<char> data;
if (chunk->header.data_size_ >
static_cast<uint32_t>(std::numeric_limits<int32_t>::max())) {
add_blocker("chunk data at offset " + to_string_i64(data_offset) +
" exceeds the C++ sketch reader buffer limit; page "
"output is truncated.");
} else {
ret = read_block(data_offset, chunk->header.data_size_, &data);
if (ret != common::E_OK) {
return ret;
}
ret = parse_pages(data_offset, data, chunk);
if (ret != common::E_OK) {
return ret;
}
}
std::map<int64_t, std::string>::const_iterator path_it =
chunk_path_by_offset_.find(chunk_offset);
if (path_it != chunk_path_by_offset_.end()) {
chunk->path = path_it->second;
} else {
chunk->path = kMetadataMissing;
add_blocker("chunk metadata missing for chunk header offset " +
to_string_i64(chunk_offset));
}
std::map<int64_t, std::string>::const_iterator stat_it =
chunk_stat_by_offset_.find(chunk_offset);
if (stat_it != chunk_stat_by_offset_.end()) {
chunk->statistics = stat_it->second;
}
return common::E_OK;
}
int SketchLayout::parse_chunk_header(int64_t chunk_offset, ChunkSketch* chunk) {
const int64_t remaining = file_metadata_pos_ - chunk_offset;
const int32_t max_probe = static_cast<int32_t>(
std::min<int64_t>(remaining, kMaxHeaderProbeBytes));
if (max_probe < kMinChunkHeaderSize) {
return common::E_TSFILE_CORRUPTED;
}
int32_t probe = std::min<int32_t>(4096, max_probe);
probe = std::max<int32_t>(probe, kMinChunkHeaderSize);
while (probe <= max_probe) {
std::vector<char> buf;
int ret = read_block(chunk_offset, probe, &buf);
if (ret != common::E_OK) {
return ret;
}
common::ByteStream bs;
bs.wrap_from(buf.data(), probe);
storage::ChunkHeader header;
ret = header.deserialize_from(bs);
if (ret == common::E_OK) {
chunk->offset = chunk_offset;
chunk->header = header;
chunk->header_size = bs.read_pos();
return common::E_OK;
}
if (probe == max_probe) {
return common::E_TSFILE_CORRUPTED;
}
probe = std::min<int32_t>(max_probe, probe * 2);
}
return common::E_TSFILE_CORRUPTED;
}
int SketchLayout::parse_pages(int64_t data_offset,
const std::vector<char>& data,
ChunkSketch* chunk) {
common::ByteStream bs;
bs.wrap_from(data.data(), static_cast<int32_t>(data.size()));
const bool one_page = is_one_page_chunk(chunk->header.chunk_type_);
int page_id = 0;
while (bs.has_remaining()) {
const uint32_t header_start = bs.read_pos();
storage::PageHeader page_header;
int ret = page_header.deserialize_from(bs, !one_page,
chunk->header.data_type_);
if (ret != common::E_OK) {
return ret;
}
const uint32_t header_end = bs.read_pos();
if (bs.remaining_size() < page_header.compressed_size_) {
return common::E_TSFILE_CORRUPTED;
}
PageSketch page;
page.page_id = page_id++;
page.header_offset = data_offset + header_start;
page.data_offset = data_offset + header_end;
page.uncompressed_size = page_header.uncompressed_size_;
page.compressed_size = page_header.compressed_size_;
const uint32_t actual_header_size = header_end - header_start;
const uint32_t actual_size_prefix =
unsigned_var_int_size(page.uncompressed_size) +
unsigned_var_int_size(page.compressed_size);
const uint32_t java_size_prefix =
java_var_int_size(page.uncompressed_size) +
java_var_int_size(page.compressed_size);
page.header_size =
actual_header_size - actual_size_prefix + java_size_prefix;
page.statistics = statistic_string(page_header.statistic_);
page.has_statistics = page_header.statistic_ != nullptr;
chunk->pages.push_back(page);
bs.wrapped_buf_advance_read_pos(page_header.compressed_size_);
if (one_page) {
break;
}
}
chunk->header.num_of_pages_ = static_cast<int32_t>(chunk->pages.size());
if (bs.has_remaining()) {
return common::E_TSFILE_CORRUPTED;
}
return common::E_OK;
}
} // namespace tsfile_cli