| /* | |
| * Copyright (c) 2016-2019 Arm Limited | |
| * All rights reserved | |
| * | |
| * The license below extends only to copyright in the software and shall | |
| * not be construed as granting a license to any other intellectual | |
| * property including but not limited to intellectual property relating | |
| * to a hardware implementation of the functionality of the software | |
| * licensed hereunder. You may use the software subject to the license | |
| * terms below provided that you ensure that this notice is replicated | |
| * unmodified and in its entirety in all distributions of the software, | |
| * modified or unmodified, in source code or in binary form. | |
| * | |
| * Redistribution and use in source and binary forms, with or without | |
| * modification, are permitted provided that the following conditions are | |
| * met: redistributions of source code must retain the above copyright | |
| * notice, this list of conditions and the following disclaimer; | |
| * redistributions in binary form must reproduce the above copyright | |
| * notice, this list of conditions and the following disclaimer in the | |
| * documentation and/or other materials provided with the distribution; | |
| * neither the name of the copyright holders nor the names of its | |
| * contributors may be used to endorse or promote products derived from | |
| * this software without specific prior written permission. | |
| * | |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| */ | |
| #include "base/stats/hdf5.hh" | |
| #include "base/logging.hh" | |
| #include "base/stats/info.hh" | |
| #include "base/trace.hh" | |
| #include "debug/Stats.hh" | |
| namespace gem5 | |
| { | |
| /** | |
| * Check if all strings in a container are empty. | |
| */ | |
| template<typename T> | |
| bool emptyStrings(const T &labels) | |
| { | |
| for (const auto &s : labels) { | |
| if (!s.empty()) | |
| return false; | |
| } | |
| return true; | |
| } | |
| namespace statistics | |
| { | |
| Hdf5::Hdf5(const std::string &file, unsigned chunking, | |
| bool desc, bool formulas) | |
| : fname(file), timeChunk(chunking), | |
| enableDescriptions(desc), enableFormula(formulas), | |
| dumpCount(0) | |
| { | |
| // Tell the library not to print exceptions by default. There are | |
| // cases where we rely on exceptions to determine if we need to | |
| // create a node or if we can just open it. | |
| H5::Exception::dontPrint(); | |
| } | |
| Hdf5::~Hdf5() | |
| { | |
| } | |
| void | |
| Hdf5::begin() | |
| { | |
| h5File = H5::H5File(fname, | |
| // Truncate the file if this is the first dump | |
| dumpCount > 0 ? H5F_ACC_RDWR : H5F_ACC_TRUNC); | |
| path.push(h5File.openGroup("/")); | |
| } | |
| void | |
| Hdf5::end() | |
| { | |
| assert(valid()); | |
| dumpCount++; | |
| } | |
| bool | |
| Hdf5::valid() const | |
| { | |
| return true; | |
| } | |
| void | |
| Hdf5::beginGroup(const char *name) | |
| { | |
| auto base = path.top(); | |
| // Try to open an existing stat group corresponding to the | |
| // name. Create it if it doesn't exist. | |
| H5::Group group; | |
| try { | |
| group = base.openGroup(name); | |
| } catch (const H5::FileIException& e) { | |
| group = base.createGroup(name); | |
| } catch (const H5::GroupIException& e) { | |
| group = base.createGroup(name); | |
| } | |
| path.push(group); | |
| } | |
| void | |
| Hdf5::endGroup() | |
| { | |
| assert(!path.empty()); | |
| path.pop(); | |
| } | |
| void | |
| Hdf5::visit(const ScalarInfo &info) | |
| { | |
| // Since this stat is a scalar, we need 1-dimensional value in the | |
| // stat file. The Hdf5::appendStat helper will populate the size | |
| // of the first dimension (time). | |
| hsize_t fdims[1] = { 0, }; | |
| double data[1] = { info.result(), }; | |
| appendStat(info, 1, fdims, data); | |
| } | |
| void | |
| Hdf5::visit(const VectorInfo &info) | |
| { | |
| appendVectorInfo(info); | |
| } | |
| void | |
| Hdf5::visit(const DistInfo &info) | |
| { | |
| warn_once("HDF5 stat files don't support distributions.\n"); | |
| } | |
| void | |
| Hdf5::visit(const VectorDistInfo &info) | |
| { | |
| warn_once("HDF5 stat files don't support vector distributions.\n"); | |
| } | |
| void | |
| Hdf5::visit(const Vector2dInfo &info) | |
| { | |
| // Request a 3-dimensional stat, the first dimension will be | |
| // populated by the Hdf5::appendStat() helper. The remaining two | |
| // dimensions correspond to the stat instance. | |
| hsize_t fdims[3] = { 0, info.x, info.y }; | |
| H5::DataSet data_set = appendStat(info, 3, fdims, info.cvec.data()); | |
| if (dumpCount == 0) { | |
| if (!info.subnames.empty() && !emptyStrings(info.subnames)) | |
| addMetaData(data_set, "subnames", info.subnames); | |
| if (!info.y_subnames.empty() && !emptyStrings(info.y_subnames)) | |
| addMetaData(data_set, "y_subnames", info.y_subnames); | |
| if (!info.subdescs.empty() && !emptyStrings(info.subdescs)) | |
| addMetaData(data_set, "subdescs", info.subdescs); | |
| } | |
| } | |
| void | |
| Hdf5::visit(const FormulaInfo &info) | |
| { | |
| if (!enableFormula) | |
| return; | |
| H5::DataSet data_set = appendVectorInfo(info); | |
| if (dumpCount == 0) | |
| addMetaData(data_set, "equation", info.str()); | |
| } | |
| void | |
| Hdf5::visit(const SparseHistInfo &info) | |
| { | |
| warn_once("HDF5 stat files don't support sparse histograms.\n"); | |
| } | |
| H5::DataSet | |
| Hdf5::appendVectorInfo(const VectorInfo &info) | |
| { | |
| const VResult &vr(info.result()); | |
| // Request a 2-dimensional stat, the first dimension will be | |
| // populated by the Hdf5::appendStat() helper. The remaining | |
| // dimension correspond to the stat instance. | |
| hsize_t fdims[2] = { 0, vr.size() }; | |
| H5::DataSet data_set = appendStat(info, 2, fdims, vr.data()); | |
| if (dumpCount == 0) { | |
| if (!info.subnames.empty() && !emptyStrings(info.subnames)) | |
| addMetaData(data_set, "subnames", info.subnames); | |
| if (!info.subdescs.empty() && !emptyStrings(info.subdescs)) | |
| addMetaData(data_set, "subdescs", info.subdescs); | |
| } | |
| return data_set; | |
| } | |
| H5::DataSet | |
| Hdf5::appendStat(const Info &info, int rank, hsize_t *dims, const double *data) | |
| { | |
| H5::Group group = path.top(); | |
| H5::DataSet data_set; | |
| H5::DataSpace fspace; | |
| dims[0] = dumpCount + 1; | |
| if (dumpCount > 0) { | |
| // Get the existing stat if we have already dumped this stat | |
| // before. | |
| data_set = group.openDataSet(info.name); | |
| data_set.extend(dims); | |
| fspace = data_set.getSpace(); | |
| } else { | |
| // We don't have the stat already, create it. | |
| H5::DSetCreatPropList props; | |
| // Setup max dimensions based on the requested file dimensions | |
| std::vector<hsize_t> max_dims(rank); | |
| std::copy(dims, dims + rank, max_dims.begin()); | |
| max_dims[0] = H5S_UNLIMITED; | |
| // Setup chunking | |
| std::vector<hsize_t> chunk_dims(rank); | |
| std::copy(dims, dims + rank, chunk_dims.begin()); | |
| chunk_dims[0] = timeChunk; | |
| props.setChunk(rank, chunk_dims.data()); | |
| // Enable compression | |
| props.setDeflate(1); | |
| fspace = H5::DataSpace(rank, dims, max_dims.data()); | |
| try { | |
| DPRINTF(Stats, "Creating dataset %s in group %s\n", | |
| info.name, group.getObjnameByIdx(group.getId())); | |
| data_set = group.createDataSet(info.name, | |
| H5::PredType::NATIVE_DOUBLE, fspace, props); | |
| } catch (const H5::Exception &e) { | |
| std::string err = "Failed creating H5::DataSet " + info.name + "; "; | |
| err += e.getDetailMsg() + " in " + e.getFuncName(); | |
| // Rethrow std exception so that it's passed on to the Python world | |
| throw std::runtime_error(err); | |
| } | |
| if (enableDescriptions && !info.desc.empty()) { | |
| addMetaData(data_set, "description", info.desc); | |
| } | |
| } | |
| // The first dimension is time which isn't included in data. | |
| dims[0] = 1; | |
| H5::DataSpace mspace(rank, dims); | |
| std::vector<hsize_t> foffset(rank, 0); | |
| foffset[0] = dumpCount; | |
| fspace.selectHyperslab(H5S_SELECT_SET, dims, foffset.data()); | |
| data_set.write(data, H5::PredType::NATIVE_DOUBLE, mspace, fspace); | |
| return data_set; | |
| } | |
| void | |
| Hdf5::addMetaData(H5::DataSet &loc, const char *name, | |
| const std::vector<const char *> &values) | |
| { | |
| H5::StrType type(H5::PredType::C_S1, H5T_VARIABLE); | |
| hsize_t dims[1] = { values.size(), }; | |
| H5::DataSpace space(1, dims); | |
| H5::Attribute attribute = loc.createAttribute(name, type, space); | |
| attribute.write(type, values.data()); | |
| } | |
| void | |
| Hdf5::addMetaData(H5::DataSet &loc, const char *name, | |
| const std::vector<std::string> &values) | |
| { | |
| std::vector<const char *> cstrs(values.size()); | |
| for (int i = 0; i < values.size(); ++i) | |
| cstrs[i] = values[i].c_str(); | |
| addMetaData(loc, name, cstrs); | |
| } | |
| void | |
| Hdf5::addMetaData(H5::DataSet &loc, const char *name, | |
| const std::string &value) | |
| { | |
| H5::StrType type(H5::PredType::C_S1, value.length() + 1); | |
| hsize_t dims[1] = { 1, }; | |
| H5::DataSpace space(1, dims); | |
| H5::Attribute attribute = loc.createAttribute(name, type, space); | |
| attribute.write(type, value.c_str()); | |
| } | |
| void | |
| Hdf5::addMetaData(H5::DataSet &loc, const char *name, double value) | |
| { | |
| hsize_t dims[1] = { 1, }; | |
| H5::DataSpace space(1, dims); | |
| H5::Attribute attribute = loc.createAttribute( | |
| name, H5::PredType::NATIVE_DOUBLE, space); | |
| attribute.write(H5::PredType::NATIVE_DOUBLE, &value); | |
| } | |
| std::unique_ptr<Output> | |
| initHDF5(const std::string &filename, unsigned chunking, | |
| bool desc, bool formulas) | |
| { | |
| return std::unique_ptr<Output>( | |
| new Hdf5(simout.resolve(filename), chunking, desc, formulas)); | |
| } | |
| }; // namespace statistics | |
| } // namespace gem5 |