Buffering: initial implementation and tests (#20)
* Update source/lib/common
- CMakeLists.txt
- less verbose
- rocprofiler-common-library uses rocprofiler-headers target
- mpl.hpp
- metaprogramming header with type_list, size_of, index_of, and is_one_of
- record_header_buffer.{hpp,cpp}
- wrapper class around atomic_ring_buffer and vector of rocprofiler_record_header_t
- atomic_ring_buffer.{hpp,cpp}
- request function accepts wrap param when overwritting is not desirable
- can_clear member function
- clear member function for rewinding write pointer to start of buffer
- containers/CMakeLists.txt
- include record_header_buffer.{hpp,cpp} in build target
* Update source/lib/tests: Buffering tests
- Added buffering tests. See comments in code for description
* atomic_ring_buffer -> ring_buffer
- remove ring_buffer implementation
- rename atomic_ring_buffer to ring_buffer
* atomic_ring_buffer -> ring_buffer
- remove ring_buffer implementation
- rename atomic_ring_buffer to ring_buffer
* Update record_header_buffer
- lock, unlock, is_locked, clear, save, and load member functions
* Buffering tests
- add buffer test for save/load capability
* Update rocprofiler_memcheck.cmake
- fix erroneous spaces causing incorrect string evaluation
* Update ring_buffer
- fix exception message
* undef HIP_PROF_API
- make sure HIP_PROF_API is undefined before including hip_runtime.h
- avoid directly including hip/hip_runtime.h
* Update rocprofiler_config_interfaces
- remove stale preprocessor defines that are from old rocprofiler/roctracer
- HIP_PROF_HIP_API_STRING=1
- PROF_API_IMPL=1
* Update run-ci.py
- fix paths to suppression files
- improve printing logs to console in github actions
* Update buffering implementation
- remove support for using malloc instead of mmap in ring_buffer
- provide some info functions in record_header_buffer
- improve the testing of the save-load buffer test
* Update run-ci.py
- fix CTEST_CUSTOM_COVERAGE_EXCLUDE
* Update hip/api_args.h
- remove undef HIP_PROF_API
* Update buffering-save-load.cpp
- updated comments
* Update record_header_buffer
- default ctor
- allocate member function
- is_allocated member function
* Update buffering-save-load.cpp
- tweaked usage of record_header_buffer to delay allocation
[ROCm/rocprofiler-sdk commit: b12ef4a75e]
This commit is contained in:
committed by
GitHub
parent
d4a977349c
commit
ccd154b74c
@@ -22,19 +22,19 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "lib/common/environment.hpp"
|
||||
#include "lib/common/units.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <new>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace rocprofiler
|
||||
{
|
||||
@@ -47,7 +47,7 @@ struct ring_buffer;
|
||||
//
|
||||
namespace base
|
||||
{
|
||||
/// \struct tim::base::ring_buffer
|
||||
/// \struct rocprofiler::common::container::base::ring_buffer
|
||||
/// \brief Ring buffer implementation, with support for mmap as backend (Linux only).
|
||||
struct ring_buffer
|
||||
{
|
||||
@@ -55,15 +55,10 @@ struct ring_buffer
|
||||
friend struct container::ring_buffer;
|
||||
|
||||
ring_buffer() = default;
|
||||
explicit ring_buffer(bool _use_mmap) { set_use_mmap(_use_mmap); }
|
||||
explicit ring_buffer(size_t _size) { init(_size); }
|
||||
ring_buffer(size_t _size, bool _use_mmap);
|
||||
|
||||
~ring_buffer();
|
||||
|
||||
ring_buffer(const ring_buffer&);
|
||||
ring_buffer& operator=(const ring_buffer&);
|
||||
|
||||
ring_buffer(ring_buffer&&) noexcept;
|
||||
ring_buffer& operator=(ring_buffer&&) noexcept;
|
||||
|
||||
@@ -79,6 +74,12 @@ struct ring_buffer
|
||||
/// Destroy ring buffer.
|
||||
void destroy();
|
||||
|
||||
/// Request a pointer for writing at least \param n bytes.
|
||||
void* request(size_t n, bool wrap = true);
|
||||
|
||||
/// Retrieve a pointer for reading at least \param n bytes.
|
||||
void* retrieve(size_t n) const;
|
||||
|
||||
/// Write class-type data to buffer (uses placement new).
|
||||
template <typename Tp>
|
||||
std::pair<size_t, Tp*> write(Tp* in, std::enable_if_t<std::is_class<Tp>::value, int> = 0);
|
||||
@@ -93,24 +94,19 @@ struct ring_buffer
|
||||
template <typename Tp>
|
||||
Tp* request();
|
||||
|
||||
/// Request a pointer to an allocation for at least \param n bytes.
|
||||
void* request(size_t n);
|
||||
|
||||
/// Read class-type data from buffer (uses placement new).
|
||||
template <typename Tp>
|
||||
std::pair<size_t, Tp*> read(Tp* out, std::enable_if_t<std::is_class<Tp>::value, int> = 0) const;
|
||||
std::pair<size_t, Tp*> read(Tp* _dest,
|
||||
std::enable_if_t<std::is_class<Tp>::value, int> = 0) const;
|
||||
|
||||
/// Read non-class-type data from buffer (uses memcpy).
|
||||
template <typename Tp>
|
||||
std::pair<size_t, Tp*> read(Tp* out,
|
||||
std::pair<size_t, Tp*> read(Tp* _dest,
|
||||
std::enable_if_t<!std::is_class<Tp>::value, int> = 0) const;
|
||||
|
||||
/// Retrieve a pointer to the head allocation (read).
|
||||
template <typename Tp>
|
||||
Tp* retrieve();
|
||||
|
||||
/// Retrieve a pointer to the head allocation of at least \param n bytes (read).
|
||||
void* retrieve(size_t n);
|
||||
Tp* retrieve() const;
|
||||
|
||||
/// Returns number of bytes currently held by the buffer.
|
||||
size_t count() const { return (m_write_count - m_read_count); }
|
||||
@@ -124,42 +120,52 @@ struct ring_buffer
|
||||
/// Returns if the buffer is full.
|
||||
bool is_full() const { return (count() == m_size); }
|
||||
|
||||
/// Rewind the read position n bytes
|
||||
size_t rewind(size_t n) const;
|
||||
|
||||
/// explicitly configure to use mmap if avail
|
||||
void set_use_mmap(bool);
|
||||
|
||||
/// query whether using mmap
|
||||
bool get_use_mmap() const { return m_use_mmap; }
|
||||
|
||||
/// Display info about buffer
|
||||
std::string as_string() const;
|
||||
|
||||
/// save the entire buffer to a filestream
|
||||
void save(std::fstream& _fs);
|
||||
|
||||
/// load the entire buffer from a filestream
|
||||
void load(std::fstream& _fs);
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& os, const ring_buffer& obj)
|
||||
{
|
||||
return os << obj.as_string();
|
||||
}
|
||||
/// query whether the read pointer is zero and thus clearing is supported
|
||||
bool can_clear() const;
|
||||
|
||||
/// reset the read and write pointer to their initial values.
|
||||
/// effectively, wiping and existing memory. Please note,
|
||||
/// this should be used with care in a double buffer system
|
||||
/// where you are not actually using the read pointer.
|
||||
/// If the read pointer is non-zero, this will throw an exception
|
||||
bool clear();
|
||||
|
||||
/// reset the read and write pointer to their initial values.
|
||||
/// effectively, wiping and existing memory. Please note,
|
||||
/// this should be used with care in a double buffer system
|
||||
/// where you are not actually using the read pointer.
|
||||
bool clear(std::nothrow_t);
|
||||
|
||||
private:
|
||||
/// Returns the current write pointer.
|
||||
void* write_ptr() const { return static_cast<char*>(m_ptr) + (m_write_count % m_size); }
|
||||
void* write_ptr(size_t _write_count) const
|
||||
{
|
||||
return static_cast<char*>(m_ptr) + (_write_count % m_size);
|
||||
}
|
||||
|
||||
/// Returns the current read pointer.
|
||||
void* read_ptr() const { return static_cast<char*>(m_ptr) + (m_read_count % m_size); }
|
||||
void* read_ptr(size_t _read_count) const
|
||||
{
|
||||
return static_cast<char*>(m_ptr) + (_read_count % m_size);
|
||||
}
|
||||
|
||||
void reset();
|
||||
|
||||
private:
|
||||
bool m_init = false;
|
||||
bool m_use_mmap = true;
|
||||
bool m_use_mmap_explicit = false;
|
||||
void* m_ptr = nullptr;
|
||||
size_t m_size = 0;
|
||||
mutable size_t m_read_count = 0;
|
||||
size_t m_write_count = 0;
|
||||
bool m_init = false;
|
||||
void* m_ptr = nullptr;
|
||||
size_t m_size = 0;
|
||||
mutable std::atomic<size_t> m_read_count = 0;
|
||||
std::atomic<size_t> m_write_count = 0;
|
||||
};
|
||||
//
|
||||
template <typename Tp>
|
||||
@@ -168,28 +174,18 @@ ring_buffer::write(Tp* in, std::enable_if_t<std::is_class<Tp>::value, int>)
|
||||
{
|
||||
if(in == nullptr || m_ptr == nullptr) return {0, nullptr};
|
||||
|
||||
auto _length = sizeof(Tp);
|
||||
auto _length = sizeof(Tp);
|
||||
void* _out_p = request(_length);
|
||||
|
||||
// Make sure we don't put in more than there's room for, by writing no
|
||||
// more than there is free.
|
||||
if(_length > free())
|
||||
throw std::runtime_error("heap-buffer-overflow :: ring buffer is full. read data "
|
||||
"to avoid data corruption");
|
||||
|
||||
// if write count is at the tail of buffer, bump to the end of buffer
|
||||
auto _modulo = m_size - (m_write_count % m_size);
|
||||
if(_modulo < _length) m_write_count += _modulo;
|
||||
|
||||
// pointer in buffer
|
||||
Tp* out = reinterpret_cast<Tp*>(write_ptr());
|
||||
if(_out_p == nullptr) return {0, nullptr};
|
||||
|
||||
// Copy in.
|
||||
new((void*) out) Tp{std::move(*in)};
|
||||
new(_out_p) Tp{std::move(*in)};
|
||||
|
||||
// Update write count
|
||||
m_write_count += _length;
|
||||
// pointer in buffer
|
||||
Tp* _out = reinterpret_cast<Tp*>(_out_p);
|
||||
|
||||
return {_length, out};
|
||||
return {_length, _out};
|
||||
}
|
||||
//
|
||||
template <typename Tp>
|
||||
@@ -198,28 +194,18 @@ ring_buffer::write(Tp* in, std::enable_if_t<!std::is_class<Tp>::value, int>)
|
||||
{
|
||||
if(in == nullptr || m_ptr == nullptr) return {0, nullptr};
|
||||
|
||||
auto _length = sizeof(Tp);
|
||||
auto _length = sizeof(Tp);
|
||||
void* _out_p = request(_length);
|
||||
|
||||
// Make sure we don't put in more than there's room for, by writing no
|
||||
// more than there is free.
|
||||
if(_length > free())
|
||||
throw std::runtime_error("heap-buffer-overflow :: ring buffer is full. read data "
|
||||
"to avoid data corruption");
|
||||
|
||||
// if write count is at the tail of buffer, bump to the end of buffer
|
||||
auto _modulo = m_size - (m_write_count % m_size);
|
||||
if(_modulo < _length) m_write_count += _modulo;
|
||||
|
||||
// pointer in buffer
|
||||
Tp* out = reinterpret_cast<Tp*>(write_ptr());
|
||||
if(_out_p == nullptr) return {0, nullptr};
|
||||
|
||||
// Copy in.
|
||||
memcpy((void*) out, in, _length);
|
||||
memcpy(_out_p, in, _length);
|
||||
|
||||
// Update write count
|
||||
m_write_count += _length;
|
||||
// pointer in buffer
|
||||
Tp* _out = reinterpret_cast<Tp*>(_out_p);
|
||||
|
||||
return {_length, out};
|
||||
return {_length, _out};
|
||||
}
|
||||
//
|
||||
template <typename Tp>
|
||||
@@ -228,118 +214,70 @@ ring_buffer::request()
|
||||
{
|
||||
if(m_ptr == nullptr) return nullptr;
|
||||
|
||||
auto _length = sizeof(Tp);
|
||||
|
||||
// Make sure we don't put in more than there's room for, by writing no
|
||||
// more than there is free.
|
||||
if(_length > free())
|
||||
throw std::runtime_error("heap-buffer-overflow :: ring buffer is full. read data "
|
||||
"to avoid data corruption");
|
||||
|
||||
// if write count is at the tail of buffer, bump to the end of buffer
|
||||
auto _modulo = m_size - (m_write_count % m_size);
|
||||
if(_modulo < _length) m_write_count += _modulo;
|
||||
|
||||
// pointer in buffer
|
||||
Tp* _out = reinterpret_cast<Tp*>(write_ptr());
|
||||
|
||||
// Update write count
|
||||
m_write_count += _length;
|
||||
|
||||
return _out;
|
||||
return request(sizeof(Tp));
|
||||
}
|
||||
//
|
||||
template <typename Tp>
|
||||
std::pair<size_t, Tp*>
|
||||
ring_buffer::read(Tp* out, std::enable_if_t<std::is_class<Tp>::value, int>) const
|
||||
ring_buffer::read(Tp* _dest, std::enable_if_t<std::is_class<Tp>::value, int>) const
|
||||
{
|
||||
if(is_empty() || out == nullptr) return {0, nullptr};
|
||||
if(is_empty() || _dest == nullptr) return {0, nullptr};
|
||||
|
||||
auto _length = sizeof(Tp);
|
||||
auto _length = sizeof(Tp);
|
||||
void* _out_p = retrieve(_length);
|
||||
|
||||
// Make sure we do not read out more than there is actually in the buffer.
|
||||
if(_length > count()) throw std::runtime_error("ring buffer is empty");
|
||||
|
||||
// if read count is at the tail of buffer, bump to the end of buffer
|
||||
auto _modulo = m_size - (m_read_count % m_size);
|
||||
if(_modulo < _length) m_read_count += _modulo;
|
||||
if(_out_p == nullptr) return {0, nullptr};
|
||||
|
||||
// pointer in buffer
|
||||
Tp* in = reinterpret_cast<Tp*>(read_ptr());
|
||||
Tp* in = reinterpret_cast<Tp*>(_out_p);
|
||||
|
||||
// Copy out for BYTE, nothing magic here.
|
||||
*out = *in;
|
||||
|
||||
// Update read count.
|
||||
m_read_count += _length;
|
||||
*_dest = *in;
|
||||
|
||||
return {_length, in};
|
||||
}
|
||||
//
|
||||
template <typename Tp>
|
||||
std::pair<size_t, Tp*>
|
||||
ring_buffer::read(Tp* out, std::enable_if_t<!std::is_class<Tp>::value, int>) const
|
||||
ring_buffer::read(Tp* _dest, std::enable_if_t<!std::is_class<Tp>::value, int>) const
|
||||
{
|
||||
if(is_empty() || out == nullptr) return {0, nullptr};
|
||||
if(is_empty() || _dest == nullptr) return {0, nullptr};
|
||||
|
||||
auto _length = sizeof(Tp);
|
||||
auto _length = sizeof(Tp);
|
||||
void* _out_p = retrieve(_length);
|
||||
|
||||
if(_out_p == nullptr) return {0, nullptr};
|
||||
|
||||
// pointer in buffer
|
||||
Tp* in = reinterpret_cast<Tp*>(_out_p);
|
||||
|
||||
using Up = typename std::remove_const<Tp>::type;
|
||||
|
||||
// Make sure we do not read out more than there is actually in the buffer.
|
||||
if(_length > count()) throw std::runtime_error("ring buffer is empty");
|
||||
|
||||
// if read count is at the tail of buffer, bump to the end of buffer
|
||||
auto _modulo = m_size - (m_read_count % m_size);
|
||||
if(_modulo < _length) m_read_count += _modulo;
|
||||
|
||||
// pointer in buffer
|
||||
Tp* in = reinterpret_cast<Tp*>(read_ptr());
|
||||
|
||||
// Copy out for BYTE, nothing magic here.
|
||||
Up* _out = const_cast<Up*>(out);
|
||||
Up* _out = const_cast<Up*>(_dest);
|
||||
memcpy(_out, in, _length);
|
||||
|
||||
// Update read count.
|
||||
m_read_count += _length;
|
||||
|
||||
return {_length, in};
|
||||
}
|
||||
//
|
||||
template <typename Tp>
|
||||
Tp*
|
||||
ring_buffer::retrieve()
|
||||
ring_buffer::retrieve() const
|
||||
{
|
||||
if(m_ptr == nullptr) return nullptr;
|
||||
|
||||
auto _length = sizeof(Tp);
|
||||
|
||||
// Make sure we don't put in more than there's room for, by writing no
|
||||
// more than there is free.
|
||||
if(_length > count()) throw std::runtime_error("ring buffer is empty");
|
||||
|
||||
// if read count is at the tail of buffer, bump to the end of buffer
|
||||
auto _modulo = m_size - (m_read_count % m_size);
|
||||
if(_modulo < _length) m_read_count += _modulo;
|
||||
|
||||
// pointer in buffer
|
||||
Tp* _out = reinterpret_cast<Tp*>(read_ptr());
|
||||
|
||||
// Update write count
|
||||
m_read_count += _length;
|
||||
|
||||
return _out;
|
||||
return retrieve(sizeof(Tp));
|
||||
}
|
||||
//
|
||||
} // namespace base
|
||||
///
|
||||
/// \struct rocprofiler::container::ring_buffer
|
||||
/// \brief Ring buffer wrapper around \ref tim::base::ring_buffer for data of type Tp. If
|
||||
/// the data object size is larger than the page size (typically 4KB), behavior is
|
||||
/// undefined. During initialization, one requests a minimum number of objects and the
|
||||
/// buffer will support that number of object + the remainder of the page, e.g. if a page
|
||||
/// is 1000 bytes, the object is 1 byte, and the buffer is requested to support 1500
|
||||
/// objects, then an allocation supporting 2000 objects (i.e. 2 pages) will be created.
|
||||
//
|
||||
/// \struct rocprofiler::common::container::ring_buffer
|
||||
/// \brief Ring buffer wrapper around \ref rocprofiler::common::container::base::ring_buffer for
|
||||
/// data of type Tp. If the data object size is larger than the page size (typically 4KB), behavior
|
||||
/// is undefined. During initialization, one requests a minimum number of objects and the buffer
|
||||
/// will support that number of object + the remainder of the page, e.g. if a page is 1000 bytes,
|
||||
/// the object is 1 byte, and the buffer is requested to support 1500 objects, then an allocation
|
||||
/// supporting 2000 objects (i.e. 2 pages) will be created.
|
||||
template <typename Tp>
|
||||
struct ring_buffer : private base::ring_buffer
|
||||
{
|
||||
@@ -350,18 +288,10 @@ struct ring_buffer : private base::ring_buffer
|
||||
ring_buffer() = default;
|
||||
~ring_buffer() = default;
|
||||
|
||||
explicit ring_buffer(bool _use_mmap)
|
||||
: base_type{_use_mmap}
|
||||
{}
|
||||
|
||||
explicit ring_buffer(size_t _size)
|
||||
: base_type{_size * sizeof(Tp)}
|
||||
{}
|
||||
|
||||
ring_buffer(size_t _size, bool _use_mmap)
|
||||
: base_type{_size * sizeof(Tp), _use_mmap}
|
||||
{}
|
||||
|
||||
ring_buffer(const ring_buffer&);
|
||||
ring_buffer(ring_buffer&&) noexcept = default;
|
||||
|
||||
@@ -387,7 +317,7 @@ struct ring_buffer : private base::ring_buffer
|
||||
Tp* write(Tp* in) { return base_type::write<Tp>(in).second; }
|
||||
|
||||
/// Read data from buffer. Return pointer to location of read
|
||||
Tp* read(Tp* out) const { return base_type::read<Tp>(out).second; }
|
||||
Tp* read(Tp* _dest) const { return base_type::read<Tp>(_dest).second; }
|
||||
|
||||
/// Get an uninitialized address at tail of buffer.
|
||||
Tp* request() { return base_type::request<Tp>(); }
|
||||
@@ -407,9 +337,6 @@ struct ring_buffer : private base::ring_buffer
|
||||
/// Returns if the buffer is full.
|
||||
bool is_full() const { return (base_type::free() < sizeof(Tp)); }
|
||||
|
||||
/// Rewinds the read pointer
|
||||
size_t rewind(size_t n) const { return base_type::rewind(n); }
|
||||
|
||||
template <typename... Args>
|
||||
auto emplace(Args&&... args)
|
||||
{
|
||||
@@ -417,10 +344,8 @@ struct ring_buffer : private base::ring_buffer
|
||||
return write(&_obj);
|
||||
}
|
||||
|
||||
using base_type::get_use_mmap;
|
||||
using base_type::load;
|
||||
using base_type::save;
|
||||
using base_type::set_use_mmap;
|
||||
|
||||
std::string as_string() const
|
||||
{
|
||||
@@ -461,7 +386,7 @@ ring_buffer<Tp>::ring_buffer(const ring_buffer<Tp>& rhs)
|
||||
char* _end = static_cast<char*>(rhs.m_ptr) + rhs.m_size;
|
||||
for(size_t i = 0; i < _n; ++i)
|
||||
{
|
||||
char* _addr = static_cast<char*>(rhs.read_ptr()) + (i * sizeof(Tp));
|
||||
char* _addr = static_cast<char*>(rhs.read_ptr(m_read_count)) + (i * sizeof(Tp));
|
||||
if((_addr + sizeof(Tp)) > _end) _addr = static_cast<char*>(rhs.m_ptr);
|
||||
Tp* _in = static_cast<Tp*>(static_cast<void*>(_addr));
|
||||
write(_in);
|
||||
@@ -479,7 +404,7 @@ ring_buffer<Tp>::operator=(const ring_buffer<Tp>& rhs)
|
||||
char* _end = static_cast<char*>(rhs.m_ptr) + rhs.m_size;
|
||||
for(size_t i = 0; i < _n; ++i)
|
||||
{
|
||||
char* _addr = static_cast<char*>(rhs.read_ptr()) + (i * sizeof(Tp));
|
||||
char* _addr = static_cast<char*>(rhs.read_ptr(m_read_count)) + (i * sizeof(Tp));
|
||||
if((_addr + sizeof(Tp)) > _end) _addr = static_cast<char*>(rhs.m_ptr);
|
||||
Tp* _in = static_cast<Tp*>(static_cast<void*>(_addr));
|
||||
write(_in);
|
||||
|
||||
Reference in New Issue
Block a user