Project
Loading...
Searching...
No Matches
test_Memory.cxx
Go to the documentation of this file.
1// Copyright 2019-2026 CERN and copyright holders of ALICE O2.
2// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3// All rights not expressly granted are reserved.
4//
5// This software is distributed under the terms of the GNU General Public
6// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7//
8// In applying this license CERN does not waive the privileges and immunities
9// granted to it by virtue of its status as an Intergovernmental Organization
10// or submit itself to any jurisdiction.
11
12// o2-test-framework-aod-native-memory [--threads N] [--pread] <file>...
13//
14// Checks that decoding a DF uses bounded private memory. Every heap
15// allocation (ours and the codecs') is tracked by interposing malloc. Columns
16// are decoded into MAP_SHARED anonymous memory, standing in for the shared
17// memory DPL provides, so the output itself is not private.
18//
19// Per DF, private memory is allowed for: parsed metadata, VLA offsets, and
20// per worker a constant decode scratch (boolean window plus codec state). The
21// decode phase must stay within threads × kDecodeScratchBound, whatever the
22// size of the baskets. In pread mode the compressed input of each basket in
23// flight is allowed on top.
24
26#include "AODNative/Container.h"
27#include "AODNative/Errors.h"
28#include <arrow/memory_pool.h>
29#include <atomic>
30#include <cstdio>
31#include <cstring>
32#include <fstream>
33#include <malloc.h>
34#include <string>
35#include <sys/mman.h>
36
37using namespace o2::aod::native;
38
39// ---- malloc interposition ---------------------------------------------------
40
41extern "C" void* __libc_malloc(size_t);
42extern "C" void* __libc_calloc(size_t, size_t);
43extern "C" void* __libc_realloc(void*, size_t);
44extern "C" void* __libc_memalign(size_t, size_t);
45extern "C" void __libc_free(void*);
46
47namespace
48{
49std::atomic<int64_t> gCurrent{0};
50std::atomic<int64_t> gPeak{0};
51std::atomic<int64_t> gLargest{0};
52
53void track(void* p)
54{
55 if (!p) {
56 return;
57 }
58 int64_t n = malloc_usable_size(p);
59 auto now = gCurrent.fetch_add(n) + n;
60 for (auto peak = gPeak.load(); now > peak && !gPeak.compare_exchange_weak(peak, now);) {
61 }
62 for (auto largest = gLargest.load(); n > largest && !gLargest.compare_exchange_weak(largest, n);) {
63 }
64}
65
66void untrack(void* p)
67{
68 if (p) {
69 gCurrent.fetch_sub(malloc_usable_size(p));
70 }
71}
72} // namespace
73
74extern "C" {
75void* malloc(size_t n)
76{
77 auto* p = __libc_malloc(n);
78 track(p);
79 return p;
80}
81void* calloc(size_t a, size_t b)
82{
83 auto* p = __libc_calloc(a, b);
84 track(p);
85 return p;
86}
87void* realloc(void* old, size_t n)
88{
89 untrack(old);
90 auto* p = __libc_realloc(old, n);
91 track(p ? p : (n ? nullptr : p));
92 return p;
93}
94void free(void* p)
95{
96 untrack(p);
97 __libc_free(p);
98}
99void* memalign(size_t a, size_t n)
100{
101 auto* p = __libc_memalign(a, n);
102 track(p);
103 return p;
104}
105void* aligned_alloc(size_t a, size_t n) { return memalign(a, n); }
106int posix_memalign(void** out, size_t a, size_t n)
107{
108 *out = memalign(a, n);
109 return *out ? 0 : ENOMEM;
110}
111}
112
113// ---- measurement ------------------------------------------------------------
114
115namespace
116{
120constexpr int64_t kDecodeScratchBound = 9 * 1024 * 1024;
121
127int64_t valueAllocationBound(int compress)
128{
129 return compress / 100 == 2 ? kDecodeScratchBound : 640 * 1024;
130}
131
132struct Phase {
134 Phase()
135 {
136 start = gCurrent.load();
137 gPeak = start;
138 gLargest = 0;
139 }
140 int64_t peakIncrease() const { return gPeak.load() - start; }
141 int64_t retained() const { return gCurrent.load() - start; }
142};
143
144int64_t rssAnonKiB()
145{
146 std::ifstream status("/proc/self/status");
147 std::string line;
148 while (std::getline(status, line)) {
149 if (line.starts_with("RssAnon:")) {
150 return std::stoll(line.substr(8));
151 }
152 }
153 return -1;
154}
155
156std::string mib(int64_t bytes)
157{
158 char buf[32];
159 std::snprintf(buf, sizeof(buf), "%.2f MiB", bytes / 1048576.);
160 return buf;
161}
162
163struct SharedBuffer {
164 uint8_t* data = nullptr;
165 size_t size = 0;
166 explicit SharedBuffer(size_t n) : size(std::max<size_t>(n, 1))
167 {
168 void* p = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
169 if (p == MAP_FAILED) {
170 throw std::bad_alloc();
171 }
172 data = (uint8_t*)p;
173 }
174 ~SharedBuffer() { munmap(data, size); }
175};
176} // namespace
177
178int main(int argc, char** argv)
179{
180 Options options;
181 std::vector<std::string> files;
182 for (int i = 1; i < argc; ++i) {
183 if (std::strcmp(argv[i], "--threads") == 0 && i + 1 < argc) {
184 options.threads = std::stoi(argv[++i]);
185 } else if (std::strcmp(argv[i], "--pread") == 0) {
186 options.localIO = Options::LocalIO::PRead;
187 } else {
188 files.push_back(argv[i]);
189 }
190 }
191 if (files.empty()) {
192 std::fprintf(stderr, "usage: %s [--threads N] [--pread] <file>...\n", argv[0]);
193 return 2;
194 }
195 int failures = 0;
196 std::printf("%-18s %-6s %11s %10s %10s %12s %13s %13s %15s %10s\n", "file", "DF", "decoded", "metadata", "offsets",
197 "decode peak", "values alloc", "bools alloc", "largest basket", "RssAnon +");
198 for (auto& path : files) {
199 try {
200 auto source = std::shared_ptr<ByteSource>(ByteSource::open(path, options));
201 RootFile file{source};
202 for (auto& df : file.dataFrames()) {
203 auto rssBefore = rssAnonKiB();
204 Phase metadata;
205 std::vector<TreeInfo> trees;
206 for (auto& key : file.keysOf(df)) {
207 if (key.className == "TTree") {
208 trees.push_back(file.readTree(key));
209 }
210 }
211 auto metadataBytes = metadata.peakIncrease();
212
213 Phase offsets;
214 BranchDecoder decoder{*source, options};
215 std::vector<std::vector<ColumnOp>> plans;
216 for (auto& tree : trees) {
218 plans.push_back(planColumns(tree, datasetSchema(physicalSchema(tree)), decoder, arrow::system_memory_pool(), rows));
219 }
220 auto offsetsBytes = offsets.peakIncrease();
221
222 int64_t decodePeak = 0, valuesAlloc = 0, boolsAlloc = 0, decoded = 0;
223 uint32_t largestBasket = 0;
224 for (auto& plan : plans) {
225 for (auto& op : plan) {
226 for (auto& b : op.branch->baskets) {
227 largestBasket = std::max(largestBasket, b.nbytes);
228 }
229 SharedBuffer out{op.byteSize};
230 Phase decode;
231 executeColumnOp(decoder, op, out.data);
232 decodePeak = std::max(decodePeak, decode.peakIncrease());
233 auto& largest = op.kind == ColumnOp::Kind::Booleans ? boolsAlloc : valuesAlloc;
234 largest = std::max(largest, gLargest.load());
235 decoded += op.byteSize;
236 }
237 }
238 // In pread mode, the compressed input of each basket in flight is private too.
239 bool pread = options.localIO == Options::LocalIO::PRead;
240 int64_t inputAllowance = pread ? (int64_t)options.threads * largestBasket : 0;
241 auto rssDelta = (rssAnonKiB() - rssBefore) * 1024;
242 std::string verdict = "ok";
243 if (decodePeak > options.threads * kDecodeScratchBound + inputAllowance) {
244 verdict = "FAIL: decode peak > " + mib(options.threads * kDecodeScratchBound + inputAllowance);
245 } else if (!pread && valuesAlloc > valueAllocationBound(file.header().compress)) {
246 verdict = "FAIL: value column allocation > " + mib(valueAllocationBound(file.header().compress));
247 }
248 std::printf("%-18s %-6s %11s %10s %10s %12s %13s %13s %15s %10s %s\n", path.substr(path.rfind('/') + 1).c_str(), df.c_str(),
249 mib(decoded).c_str(), mib(metadataBytes).c_str(), mib(offsetsBytes).c_str(), mib(decodePeak).c_str(),
250 mib(valuesAlloc).c_str(), mib(boolsAlloc).c_str(), mib(largestBasket).c_str(), mib(rssDelta).c_str(), verdict.c_str());
251 failures += verdict != "ok";
252 }
253 } catch (FormatError const& e) {
254 std::fprintf(stderr, "%s: %s\n", path.c_str(), e.what());
255 ++failures;
256 }
257 }
258 return failures ? 1 : 0;
259}
int32_t i
uint32_t op
size_t bytes
StringRef key
static std::unique_ptr< ByteSource > open(std::string url, const Options &)
GLdouble n
Definition glcorearb.h:1982
GLsizeiptr size
Definition glcorearb.h:659
GLuint GLsizei const GLuint const GLintptr * offsets
Definition glcorearb.h:2595
GLboolean GLboolean GLboolean b
Definition glcorearb.h:1233
GLsizei GLsizei GLchar * source
Definition glcorearb.h:798
GLboolean * data
Definition glcorearb.h:298
GLsizei const GLchar *const * path
Definition glcorearb.h:3591
GLuint start
Definition glcorearb.h:469
GLboolean GLboolean GLboolean GLboolean a
Definition glcorearb.h:1233
GLenum GLuint GLenum GLsizei const GLchar * buf
Definition glcorearb.h:2514
std::shared_ptr< arrow::Schema > datasetSchema(std::shared_ptr< arrow::Schema > const &physical)
The physical schema without the *_size fields, as DataInputDirector derives it.
std::shared_ptr< arrow::Schema > physicalSchema(TreeInfo const &tree)
std::vector< ColumnOp > planColumns(TreeInfo const &tree, std::shared_ptr< arrow::Schema > const &schema, BranchDecoder &decoder, arrow::MemoryPool *pool, int64_t &rows)
void executeColumnOp(BranchDecoder &decoder, ColumnOp const &op, uint8_t *dst)
Fills the buffer of op, which is exactly op.byteSize bytes.
uint8_t itsSharedClusterMap uint8_t
uint64_t now() noexcept
Definition Clock.h:69
int32_t const char int32_t line
const bool const int TrackITSInternal< NLayers > & track
constexpr size_t max
int posix_memalign(void **out, size_t a, size_t n)
void __libc_free(void *)
void * realloc(void *old, size_t n)
void * __libc_malloc(size_t)
void * __libc_memalign(size_t, size_t)
void * malloc(size_t n)
void * calloc(size_t a, size_t b)
void free(void *p)
void * aligned_alloc(size_t a, size_t n)
void * __libc_realloc(void *, size_t)
void * memalign(size_t a, size_t n)
void * __libc_calloc(size_t, size_t)
#define main
coder decode(ctfImage, triggersD, clustersD)
std::unique_ptr< TTree > tree((TTree *) flIn.Get(std::string(o2::base::NameConf::CTFTREENAME).c_str()))
std::vector< ReadoutWindowData > rows