Merge "Compress profile files"
diff --git a/dexlayout/dexlayout_test.cc b/dexlayout/dexlayout_test.cc
index 877ea92..d2aef27 100644
--- a/dexlayout/dexlayout_test.cc
+++ b/dexlayout/dexlayout_test.cc
@@ -41,7 +41,7 @@
"AAAAdQEAAAAQAAABAAAAjAEAAA==";
static const char kDexFileLayoutInputProfile[] =
- "cHJvADAwNQABCwABAAAAAAD1KW3+Y2xhc3Nlcy5kZXgBAA==";
+ "cHJvADAwNgAAAAAAAAgAAAB4AQMAAAAAAQ==";
// Dex file with catch handler unreferenced by try blocks.
// Constructed by building a dex file with try/catch blocks and hex editing.
diff --git a/runtime/jit/profile_compilation_info.cc b/runtime/jit/profile_compilation_info.cc
index 0acce1e..f163a59 100644
--- a/runtime/jit/profile_compilation_info.cc
+++ b/runtime/jit/profile_compilation_info.cc
@@ -18,11 +18,18 @@
#include "errno.h"
#include <limits.h>
+#include <string>
#include <vector>
#include <stdlib.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/uio.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <zlib.h>
+#include <base/time_utils.h>
#include "base/mutex.h"
#include "base/scoped_flock.h"
@@ -33,13 +40,13 @@
#include "os.h"
#include "safe_map.h"
#include "utils.h"
+#include "android-base/file.h"
namespace art {
const uint8_t ProfileCompilationInfo::kProfileMagic[] = { 'p', 'r', 'o', '\0' };
-// Last profile version: fix profman merges. Update profile version to force
-// regeneration of possibly faulty profiles.
-const uint8_t ProfileCompilationInfo::kProfileVersion[] = { '0', '0', '5', '\0' };
+// Last profile version: Compress profile data.
+const uint8_t ProfileCompilationInfo::kProfileVersion[] = { '0', '0', '6', '\0' };
static constexpr uint16_t kMaxDexFileKeyLength = PATH_MAX;
@@ -209,12 +216,12 @@
/**
* Serialization format:
- * magic,version,number_of_dex_files
- * dex_location1,number_of_classes1,methods_region_size,dex_location_checksum1, \
+ * magic,version,number_of_dex_files,uncompressed_size_of_zipped_data,compressed_data_size,
+ * zipped[dex_location1,number_of_classes1,methods_region_size,dex_location_checksum1, \
* method_encoding_11,method_encoding_12...,class_id1,class_id2...
* dex_location2,number_of_classes2,methods_region_size,dex_location_checksum2, \
* method_encoding_21,method_encoding_22...,,class_id1,class_id2...
- * .....
+ * .....]
* The method_encoding is:
* method_id,number_of_inline_caches,inline_cache1,inline_cache2...
* The inline_cache is:
@@ -228,28 +235,53 @@
* When present, there will be no class ids following.
**/
bool ProfileCompilationInfo::Save(int fd) {
+ uint64_t start = NanoTime();
ScopedTrace trace(__PRETTY_FUNCTION__);
DCHECK_GE(fd, 0);
- // Cache at most 50KB before writing.
- static constexpr size_t kMaxSizeToKeepBeforeWriting = 50 * KB;
// Use a vector wrapper to avoid keeping track of offsets when we add elements.
std::vector<uint8_t> buffer;
- WriteBuffer(fd, kProfileMagic, sizeof(kProfileMagic));
- WriteBuffer(fd, kProfileVersion, sizeof(kProfileVersion));
+ if (!WriteBuffer(fd, kProfileMagic, sizeof(kProfileMagic))) {
+ return false;
+ }
+ if (!WriteBuffer(fd, kProfileVersion, sizeof(kProfileVersion))) {
+ return false;
+ }
DCHECK_LE(info_.size(), std::numeric_limits<uint8_t>::max());
AddUintToBuffer(&buffer, static_cast<uint8_t>(info_.size()));
+ uint32_t required_capacity = 0;
+ for (const DexFileData* dex_data_ptr : info_) {
+ const DexFileData& dex_data = *dex_data_ptr;
+ uint32_t methods_region_size = GetMethodsRegionSize(dex_data);
+ required_capacity += kLineHeaderSize +
+ dex_data.profile_key.size() +
+ sizeof(uint16_t) * dex_data.class_set.size() +
+ methods_region_size;
+ }
+ if (required_capacity > kProfileSizeErrorThresholdInBytes) {
+ LOG(ERROR) << "Profile data size exceeds "
+ << std::to_string(kProfileSizeErrorThresholdInBytes)
+ << " bytes. Profile will not be written to disk.";
+ return false;
+ }
+ if (required_capacity > kProfileSizeWarningThresholdInBytes) {
+ LOG(WARNING) << "Profile data size exceeds "
+ << std::to_string(kProfileSizeWarningThresholdInBytes);
+ }
+ AddUintToBuffer(&buffer, required_capacity);
+ if (!WriteBuffer(fd, buffer.data(), buffer.size())) {
+ return false;
+ }
+ // Make sure that the buffer has enough capacity to avoid repeated resizings
+ // while we add data.
+ buffer.reserve(required_capacity);
+ buffer.clear();
+
// Dex files must be written in the order of their profile index. This
// avoids writing the index in the output file and simplifies the parsing logic.
for (const DexFileData* dex_data_ptr : info_) {
const DexFileData& dex_data = *dex_data_ptr;
- if (buffer.size() > kMaxSizeToKeepBeforeWriting) {
- if (!WriteBuffer(fd, buffer.data(), buffer.size())) {
- return false;
- }
- buffer.clear();
- }
// Note that we allow dex files without any methods or classes, so that
// inline caches can refer valid dex files.
@@ -259,16 +291,8 @@
return false;
}
- // Make sure that the buffer has enough capacity to avoid repeated resizings
- // while we add data.
uint32_t methods_region_size = GetMethodsRegionSize(dex_data);
- size_t required_capacity = buffer.size() +
- kLineHeaderSize +
- dex_data.profile_key.size() +
- sizeof(uint16_t) * dex_data.class_set.size() +
- methods_region_size;
- buffer.reserve(required_capacity);
DCHECK_LE(dex_data.profile_key.size(), std::numeric_limits<uint16_t>::max());
DCHECK_LE(dex_data.class_set.size(), std::numeric_limits<uint16_t>::max());
AddUintToBuffer(&buffer, static_cast<uint16_t>(dex_data.profile_key.size()));
@@ -285,12 +309,29 @@
for (const auto& class_id : dex_data.class_set) {
AddUintToBuffer(&buffer, class_id.index_);
}
-
- DCHECK_LE(required_capacity, buffer.size())
- << "Failed to add the expected number of bytes in the buffer";
}
- return WriteBuffer(fd, buffer.data(), buffer.size());
+ uint32_t output_size = 0;
+ std::unique_ptr<uint8_t[]> compressed_buffer = DeflateBuffer(buffer.data(),
+ required_capacity,
+ &output_size);
+
+ buffer.clear();
+ AddUintToBuffer(&buffer, output_size);
+
+ if (!WriteBuffer(fd, buffer.data(), buffer.size())) {
+ return false;
+ }
+ if (!WriteBuffer(fd, compressed_buffer.get(), output_size)) {
+ return false;
+ }
+ uint64_t total_time = NanoTime() - start;
+ VLOG(profiler) << "Compressed from "
+ << std::to_string(required_capacity)
+ << " to "
+ << std::to_string(output_size);
+ VLOG(profiler) << "Time to save profile: " << std::to_string(total_time);
+ return true;
}
void ProfileCompilationInfo::AddInlineCacheToBuffer(std::vector<uint8_t>* buffer,
@@ -584,7 +625,14 @@
uint8_t number_of_dex_files,
const ProfileLineHeader& line_header,
/*out*/std::string* error) {
- while (buffer.HasMoreData()) {
+ uint32_t unread_bytes_before_operation = buffer.CountUnreadBytes();
+ if (unread_bytes_before_operation < line_header.method_region_size_bytes) {
+ *error += "Profile EOF reached prematurely for ReadMethod";
+ return kProfileLoadBadData;
+ }
+ size_t expected_unread_bytes_after_operation = buffer.CountUnreadBytes()
+ - line_header.method_region_size_bytes;
+ while (buffer.CountUnreadBytes() > expected_unread_bytes_after_operation) {
DexFileData* const data = GetOrAddDexFileData(line_header.dex_location, line_header.checksum);
uint16_t method_index;
READ_UINT(uint16_t, buffer, method_index, error);
@@ -594,15 +642,24 @@
return false;
}
}
-
+ uint32_t total_bytes_read = unread_bytes_before_operation - buffer.CountUnreadBytes();
+ if (total_bytes_read != line_header.method_region_size_bytes) {
+ *error += "Profile data inconsistent for ReadMethods";
+ return false;
+ }
return true;
}
bool ProfileCompilationInfo::ReadClasses(SafeBuffer& buffer,
- uint16_t classes_to_read,
const ProfileLineHeader& line_header,
/*out*/std::string* error) {
- for (uint16_t i = 0; i < classes_to_read; i++) {
+ size_t unread_bytes_before_op = buffer.CountUnreadBytes();
+ if (unread_bytes_before_op < line_header.class_set_size) {
+ *error += "Profile EOF reached prematurely for ReadClasses";
+ return kProfileLoadBadData;
+ }
+
+ for (uint16_t i = 0; i < line_header.class_set_size; i++) {
uint16_t type_index;
READ_UINT(uint16_t, buffer, type_index, error);
if (!AddClassIndex(line_header.dex_location,
@@ -611,6 +668,12 @@
return false;
}
}
+ size_t total_bytes_read = unread_bytes_before_op - buffer.CountUnreadBytes();
+ uint32_t expected_bytes_read = line_header.class_set_size * sizeof(uint16_t);
+ if (total_bytes_read != expected_bytes_read) {
+ *error += "Profile data inconsistent for ReadClasses";
+ return false;
+ }
return true;
}
@@ -650,15 +713,11 @@
return false;
}
-bool ProfileCompilationInfo::SafeBuffer::HasMoreData() {
- return ptr_current_ < ptr_end_;
-}
-
ProfileCompilationInfo::ProfileLoadSatus ProfileCompilationInfo::SafeBuffer::FillFromFd(
int fd,
const std::string& source,
/*out*/std::string* error) {
- size_t byte_count = ptr_end_ - ptr_current_;
+ size_t byte_count = (ptr_end_ - ptr_current_) * sizeof(*ptr_current_);
uint8_t* buffer = ptr_current_;
while (byte_count > 0) {
int bytes_read = TEMP_FAILURE_RETRY(read(fd, buffer, byte_count));
@@ -675,15 +734,31 @@
return kProfileLoadSuccess;
}
+size_t ProfileCompilationInfo::SafeBuffer::CountUnreadBytes() {
+ return (ptr_end_ - ptr_current_) * sizeof(*ptr_current_);
+}
+
+const uint8_t* ProfileCompilationInfo::SafeBuffer::GetCurrentPtr() {
+ return ptr_current_;
+}
+
+void ProfileCompilationInfo::SafeBuffer::Advance(size_t data_size) {
+ ptr_current_ += data_size;
+}
+
ProfileCompilationInfo::ProfileLoadSatus ProfileCompilationInfo::ReadProfileHeader(
int fd,
/*out*/uint8_t* number_of_dex_files,
+ /*out*/uint32_t* uncompressed_data_size,
+ /*out*/uint32_t* compressed_data_size,
/*out*/std::string* error) {
// Read magic and version
const size_t kMagicVersionSize =
sizeof(kProfileMagic) +
sizeof(kProfileVersion) +
- sizeof(uint8_t); // number of dex files
+ sizeof(uint8_t) + // number of dex files
+ sizeof(uint32_t) + // size of uncompressed profile data
+ sizeof(uint32_t); // size of compressed profile data
SafeBuffer safe_buffer(kMagicVersionSize);
@@ -704,6 +779,14 @@
*error = "Cannot read the number of dex files";
return kProfileLoadBadData;
}
+ if (!safe_buffer.ReadUintAndAdvance<uint32_t>(uncompressed_data_size)) {
+ *error = "Cannot read the size of uncompressed data";
+ return kProfileLoadBadData;
+ }
+ if (!safe_buffer.ReadUintAndAdvance<uint32_t>(compressed_data_size)) {
+ *error = "Cannot read the size of compressed data";
+ return kProfileLoadBadData;
+ }
return kProfileLoadSuccess;
}
@@ -719,17 +802,16 @@
}
ProfileCompilationInfo::ProfileLoadSatus ProfileCompilationInfo::ReadProfileLineHeader(
- int fd,
- /*out*/ProfileLineHeader* line_header,
- /*out*/std::string* error) {
- SafeBuffer header_buffer(kLineHeaderSize);
- ProfileLoadSatus status = header_buffer.FillFromFd(fd, "ReadProfileLineHeader", error);
- if (status != kProfileLoadSuccess) {
- return status;
+ SafeBuffer& buffer,
+ /*out*/ProfileLineHeader* line_header,
+ /*out*/std::string* error) {
+ if (buffer.CountUnreadBytes() < kLineHeaderSize) {
+ *error += "Profile EOF reached prematurely for ReadProfileLineHeader";
+ return kProfileLoadBadData;
}
uint16_t dex_location_size;
- if (!ReadProfileLineHeaderElements(header_buffer, &dex_location_size, line_header, error)) {
+ if (!ReadProfileLineHeaderElements(buffer, &dex_location_size, line_header, error)) {
return kProfileLoadBadData;
}
@@ -739,18 +821,19 @@
return kProfileLoadBadData;
}
- SafeBuffer location_buffer(dex_location_size);
- status = location_buffer.FillFromFd(fd, "ReadProfileHeaderDexLocation", error);
- if (status != kProfileLoadSuccess) {
- return status;
+ if (buffer.CountUnreadBytes() < dex_location_size) {
+ *error += "Profile EOF reached prematurely for ReadProfileHeaderDexLocation";
+ return kProfileLoadBadData;
}
+ const uint8_t* base_ptr = buffer.GetCurrentPtr();
line_header->dex_location.assign(
- reinterpret_cast<char*>(location_buffer.Get()), dex_location_size);
+ reinterpret_cast<const char*>(base_ptr), dex_location_size);
+ buffer.Advance(dex_location_size);
return kProfileLoadSuccess;
}
ProfileCompilationInfo::ProfileLoadSatus ProfileCompilationInfo::ReadProfileLine(
- int fd,
+ SafeBuffer& buffer,
uint8_t number_of_dex_files,
const ProfileLineHeader& line_header,
/*out*/std::string* error) {
@@ -760,29 +843,13 @@
return kProfileLoadBadData;
}
- {
- SafeBuffer buffer(line_header.method_region_size_bytes);
- ProfileLoadSatus status = buffer.FillFromFd(fd, "ReadProfileLineMethods", error);
- if (status != kProfileLoadSuccess) {
- return status;
- }
-
- if (!ReadMethods(buffer, number_of_dex_files, line_header, error)) {
- return kProfileLoadBadData;
- }
+ if (!ReadMethods(buffer, number_of_dex_files, line_header, error)) {
+ return kProfileLoadBadData;
}
- {
- SafeBuffer buffer(sizeof(uint16_t) * line_header.class_set_size);
- ProfileLoadSatus status = buffer.FillFromFd(fd, "ReadProfileLineClasses", error);
- if (status != kProfileLoadSuccess) {
- return status;
- }
- if (!ReadClasses(buffer, line_header.class_set_size, line_header, error)) {
- return kProfileLoadBadData;
- }
+ if (!ReadClasses(buffer, line_header, error)) {
+ return kProfileLoadBadData;
}
-
return kProfileLoadSuccess;
}
@@ -821,39 +888,135 @@
}
// Read profile header: magic + version + number_of_dex_files.
uint8_t number_of_dex_files;
- ProfileLoadSatus status = ReadProfileHeader(fd, &number_of_dex_files, error);
+ uint32_t uncompressed_data_size;
+ uint32_t compressed_data_size;
+ ProfileLoadSatus status = ReadProfileHeader(fd,
+ &number_of_dex_files,
+ &uncompressed_data_size,
+ &compressed_data_size,
+ error);
+
+ if (uncompressed_data_size > kProfileSizeErrorThresholdInBytes) {
+ LOG(ERROR) << "Profile data size exceeds "
+ << std::to_string(kProfileSizeErrorThresholdInBytes)
+ << " bytes";
+ return kProfileLoadBadData;
+ }
+ if (uncompressed_data_size > kProfileSizeWarningThresholdInBytes) {
+ LOG(WARNING) << "Profile data size exceeds "
+ << std::to_string(kProfileSizeWarningThresholdInBytes)
+ << " bytes";
+ }
+
if (status != kProfileLoadSuccess) {
return status;
}
+ std::unique_ptr<uint8_t[]> compressed_data(new uint8_t[compressed_data_size]);
+ bool bytes_read_success =
+ android::base::ReadFully(fd, compressed_data.get(), compressed_data_size);
+
+ if (testEOF(fd) != 0) {
+ *error += "Unexpected data in the profile file.";
+ return kProfileLoadBadData;
+ }
+
+ if (!bytes_read_success) {
+ *error += "Unable to read compressed profile data";
+ return kProfileLoadBadData;
+ }
+
+ SafeBuffer uncompressed_data(uncompressed_data_size);
+
+ int ret = InflateBuffer(compressed_data.get(),
+ compressed_data_size,
+ uncompressed_data_size,
+ uncompressed_data.Get());
+
+ if (ret != Z_STREAM_END) {
+ *error += "Error reading uncompressed profile data";
+ return kProfileLoadBadData;
+ }
+
for (uint8_t k = 0; k < number_of_dex_files; k++) {
ProfileLineHeader line_header;
// First, read the line header to get the amount of data we need to read.
- status = ReadProfileLineHeader(fd, &line_header, error);
+ status = ReadProfileLineHeader(uncompressed_data, &line_header, error);
if (status != kProfileLoadSuccess) {
return status;
}
// Now read the actual profile line.
- status = ReadProfileLine(fd, number_of_dex_files, line_header, error);
+ status = ReadProfileLine(uncompressed_data, number_of_dex_files, line_header, error);
if (status != kProfileLoadSuccess) {
return status;
}
}
// Check that we read everything and that profiles don't contain junk data.
- int result = testEOF(fd);
- if (result == 0) {
- return kProfileLoadSuccess;
- } else if (result < 0) {
- return kProfileLoadIOError;
- } else {
+ if (uncompressed_data.CountUnreadBytes() > 0) {
*error = "Unexpected content in the profile file";
return kProfileLoadBadData;
+ } else {
+ return kProfileLoadSuccess;
}
}
+std::unique_ptr<uint8_t[]> ProfileCompilationInfo::DeflateBuffer(const uint8_t* in_buffer,
+ uint32_t in_size,
+ uint32_t* compressed_data_size) {
+ z_stream strm;
+ strm.zalloc = Z_NULL;
+ strm.zfree = Z_NULL;
+ strm.opaque = Z_NULL;
+ int ret = deflateInit(&strm, 1);
+ if (ret != Z_OK) {
+ return nullptr;
+ }
+
+ uint32_t out_size = deflateBound(&strm, in_size);
+
+ std::unique_ptr<uint8_t[]> compressed_buffer(new uint8_t[out_size]);
+ strm.avail_in = in_size;
+ strm.next_in = const_cast<uint8_t*>(in_buffer);
+ strm.avail_out = out_size;
+ strm.next_out = &compressed_buffer[0];
+ ret = deflate(&strm, Z_FINISH);
+ if (ret == Z_STREAM_ERROR) {
+ return nullptr;
+ }
+ *compressed_data_size = out_size - strm.avail_out;
+ deflateEnd(&strm);
+ return compressed_buffer;
+}
+
+int ProfileCompilationInfo::InflateBuffer(const uint8_t* in_buffer,
+ uint32_t in_size,
+ uint32_t expected_uncompressed_data_size,
+ uint8_t* out_buffer) {
+ z_stream strm;
+
+ /* allocate inflate state */
+ strm.zalloc = Z_NULL;
+ strm.zfree = Z_NULL;
+ strm.opaque = Z_NULL;
+ strm.avail_in = in_size;
+ strm.next_in = const_cast<uint8_t*>(in_buffer);
+ strm.avail_out = expected_uncompressed_data_size;
+ strm.next_out = out_buffer;
+
+ int ret;
+ inflateInit(&strm);
+ ret = inflate(&strm, Z_NO_FLUSH);
+
+ if (strm.avail_in != 0 || strm.avail_out != 0) {
+ return Z_DATA_ERROR;
+ }
+ inflateEnd(&strm);
+ return ret;
+}
+
bool ProfileCompilationInfo::MergeWith(const ProfileCompilationInfo& other) {
// First verify that all checksums match. This will avoid adding garbage to
// the current profile info.
diff --git a/runtime/jit/profile_compilation_info.h b/runtime/jit/profile_compilation_info.h
index f68ed5d..9e47cc1 100644
--- a/runtime/jit/profile_compilation_info.h
+++ b/runtime/jit/profile_compilation_info.h
@@ -284,6 +284,9 @@
kProfileLoadSuccess
};
+ const uint32_t kProfileSizeWarningThresholdInBytes = 500000U;
+ const uint32_t kProfileSizeErrorThresholdInBytes = 1000000U;
+
// Internal representation of the profile information belonging to a dex file.
// Note that we could do without profile_key (the key used to encode the dex
// file in the profile) and profile_index (the index of the dex file in the
@@ -353,6 +356,21 @@
// Checks if the profile is empty.
bool IsEmpty() const;
+ // Inflate the input buffer (in_buffer) of size in_size. It returns a buffer of
+ // compressed data for the input buffer of "compressed_data_size" size.
+ std::unique_ptr<uint8_t[]> DeflateBuffer(const uint8_t* in_buffer,
+ uint32_t in_size,
+ /*out*/uint32_t* compressed_data_size);
+
+ // Inflate the input buffer(in_buffer) of size in_size. out_size is the expected output
+ // size of the buffer. It puts the output in out_buffer. It returns Z_STREAM_END on
+ // success. On error, it returns Z_STREAM_ERROR if the compressed data is inconsistent
+ // and Z_DATA_ERROR if the stream ended prematurely or the stream has extra data.
+ int InflateBuffer(const uint8_t* in_buffer,
+ uint32_t in_size,
+ uint32_t out_size,
+ /*out*/uint8_t* out_buffer);
+
// Parsing functionality.
// The information present in the header of each profile line.
@@ -376,6 +394,10 @@
const std::string& source,
/*out*/std::string* error);
+ ProfileLoadSatus FillFromBuffer(uint8_t* buffer_ptr,
+ const std::string& source,
+ /*out*/std::string* error);
+
// Reads an uint value (high bits to low bits) and advances the current pointer
// with the number of bits read.
template <typename T> bool ReadUintAndAdvance(/*out*/ T* value);
@@ -384,16 +406,22 @@
// equal it advances the current pointer by data_size.
bool CompareAndAdvance(const uint8_t* data, size_t data_size);
- // Returns true if the buffer has more data to read.
- bool HasMoreData();
+ // Advances current pointer by data_size.
+ void Advance(size_t data_size);
+
+ // Returns the count of unread bytes.
+ size_t CountUnreadBytes();
+
+ // Returns the current pointer.
+ const uint8_t* GetCurrentPtr();
// Get the underlying raw buffer.
uint8_t* Get() { return storage_.get(); }
private:
std::unique_ptr<uint8_t[]> storage_;
- uint8_t* ptr_current_;
uint8_t* ptr_end_;
+ uint8_t* ptr_current_;
};
// Entry point for profile loding functionality.
@@ -403,10 +431,12 @@
// lines into number_of_dex_files.
ProfileLoadSatus ReadProfileHeader(int fd,
/*out*/uint8_t* number_of_dex_files,
+ /*out*/uint32_t* size_uncompressed_data,
+ /*out*/uint32_t* size_compressed_data,
/*out*/std::string* error);
// Read the header of a profile line from the given fd.
- ProfileLoadSatus ReadProfileLineHeader(int fd,
+ ProfileLoadSatus ReadProfileLineHeader(SafeBuffer& buffer,
/*out*/ProfileLineHeader* line_header,
/*out*/std::string* error);
@@ -417,14 +447,13 @@
/*out*/std::string* error);
// Read a single profile line from the given fd.
- ProfileLoadSatus ReadProfileLine(int fd,
+ ProfileLoadSatus ReadProfileLine(SafeBuffer& buffer,
uint8_t number_of_dex_files,
const ProfileLineHeader& line_header,
/*out*/std::string* error);
// Read all the classes from the buffer into the profile `info_` structure.
bool ReadClasses(SafeBuffer& buffer,
- uint16_t classes_to_read,
const ProfileLineHeader& line_header,
/*out*/std::string* error);