Skip to content

Commit 04f7ecd

Browse files
Cap size of the profiling file (#14)
* Profiling file can now be capped to a maximum size to prevent from running out of memory in constrained systems (as described in #13). * When capping is set, two rotating files are used instead of ones and only the most recent events are stored. Fix issue #13
2 parents 5aaa926 + a04dee7 commit 04f7ecd

File tree

10 files changed

+231
-92
lines changed

10 files changed

+231
-92
lines changed

README.md

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,14 @@ uprofile::timeBegin("my_custom_function");
5050
uprofile::timeEnd("my_custom_function");
5151
```
5252

53+
#### Limit the size of the profiling file
54+
55+
```cpp
56+
uprofile::start("uprofile.log", 500000 /* max file size in bytes */);
57+
```
58+
59+
It will generate two rotating files with the most recent events. With the above example, both files will be `uprofile_0.log` and `uprofile_1.log`.
60+
5361
### GPU monitoring
5462
5563
The library also supports GPU metrics monitoring like usage and memory. Since GPU monitoring is specific to each vendor, an interface `IGPUMonitor` is available to abstract each vendor monitor system.
@@ -86,8 +94,8 @@ Here is the list of GPUs supported by `cppuprofile`
8694
The build process is based on CMake. Minimum version is 2.8.
8795
8896
```commandline
89-
$ cmake --configure . -B ../build-cppuprofile
90-
$ cmake --build ../build-cppuprofile
97+
$ cmake -Bbuild .
98+
$ cmake --build build
9199
```
92100

93101
### Shared/dynamic library
@@ -106,7 +114,7 @@ If you want to disable profiling in Release mode or if you want to only enable p
106114
To disable the profiling:
107115

108116
```commandline
109-
$ cmake --configure . -B ../build-cppuprofile -DPROFILE_ENABLED=OFF
117+
$ cmake -Bbuild . -DPROFILE_ENABLED=OFF
110118
```
111119

112120
## Tools
@@ -136,9 +144,9 @@ The project provides a C++ sample application called `uprof-sample`
136144
that shows how to use the `cppuprofile` library. You can build it with `SAMPLE_ENABLED` option:
137145

138146
```commandline
139-
$ cmake --configure . -B ../build-cppuprofile -DSAMPLE_ENABLED=ON
140-
$ cmake --build ../build-cppuprofile
141-
$ ../build-cppuprofile/sample/uprof-sample
147+
$ cmake -Bbuild . -DSAMPLE_ENABLED=ON
148+
$ cmake --build build
149+
$ ./build/sample/uprof-sample
142150
```
143151

144152
## Windows support limitations

lib/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ SET(UProfile_PUBLIC_HEADERS
4141
SET(UProfile_IMPL
4242
uprofile.cpp
4343
uprofileimpl.cpp
44+
eventsfile.h
45+
eventsfile.cpp
4446
util/timer.cpp
4547
util/cpumonitor.cpp
4648
)

lib/eventsfile.cpp

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Software Name : cppuprofile
2+
// SPDX-FileCopyrightText: Copyright (c) 2024 Orange
3+
// SPDX-License-Identifier: BSD-3-Clause
4+
//
5+
// This software is distributed under the BSD License;
6+
// see the LICENSE file for more details.
7+
//
8+
// Author: Cédric CHEDALEUX <cedric.chedaleux@orange.com> et al.
9+
10+
#include "eventsfile.h"
11+
12+
#include <iostream>
13+
#include <sstream>
14+
15+
using namespace std;
16+
17+
namespace uprofile
18+
{
19+
20+
EventsFile::EventsFile(const char* filepath, unsigned long long maxCapSize) :
21+
m_maxCapSize(maxCapSize)
22+
{
23+
if (m_maxCapSize > 0) {
24+
string str = string(filepath);
25+
auto found = str.find_last_of('.');
26+
bool hasExtension = found != std::string::npos;
27+
string basePath = hasExtension ? str.substr(0, found) : str;
28+
string extension = hasExtension ? str.substr(found) : "";
29+
for (int i = 0; i < ROTATING_FILES_NUMBER; ++i) {
30+
m_filePaths.emplace_back(basePath + "_" + std::to_string(i) + extension);
31+
}
32+
} else {
33+
m_filePaths.emplace_back(filepath);
34+
}
35+
36+
m_file.open(m_filePaths[m_currentFileIdx], std::ios::out);
37+
if (!m_file.is_open()) {
38+
std::cerr << "Failed to open file: " << m_filePaths[m_currentFileIdx] << std::endl;
39+
}
40+
}
41+
42+
EventsFile::~EventsFile()
43+
{
44+
m_file.close();
45+
}
46+
47+
void EventsFile::write(const std::string& event, unsigned long long timestamp, const std::list<std::string>& data)
48+
{
49+
string csvSeparator(";");
50+
stringstream ss;
51+
ss << event << csvSeparator << timestamp;
52+
for (auto it = data.cbegin(); it != data.cend(); ++it) {
53+
ss << csvSeparator << *it;
54+
}
55+
ss << "\n";
56+
string line = ss.str();
57+
58+
// Total size of all rotating files should never exceed the defined max cap size
59+
if (m_maxCapSize > 0 && m_currentFileSize + line.size() > m_maxCapSize / ROTATING_FILES_NUMBER) {
60+
rotateFile();
61+
}
62+
63+
std::lock_guard<std::mutex> guard(m_fileMutex);
64+
m_file << line;
65+
m_file.flush();
66+
m_currentFileSize += line.size();
67+
}
68+
69+
void EventsFile::rotateFile()
70+
{
71+
m_file.close();
72+
m_currentFileSize = 0;
73+
if (++m_currentFileIdx >= m_filePaths.size()) {
74+
m_currentFileIdx = 0;
75+
}
76+
m_file.open(m_filePaths[m_currentFileIdx], std::ios::out);
77+
if (!m_file.is_open()) {
78+
std::cerr << "Failed to open file: " << m_filePaths[m_currentFileIdx] << std::endl;
79+
}
80+
}
81+
82+
}

lib/eventsfile.h

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Software Name : cppuprofile
2+
// SPDX-FileCopyrightText: Copyright (c) 2024 Orange
3+
// SPDX-License-Identifier: BSD-3-Clause
4+
//
5+
// This software is distributed under the BSD License;
6+
// see the LICENSE file for more details.
7+
//
8+
// Author: Cédric CHEDALEUX <cedric.chedaleux@orange.com> et al.
9+
10+
#ifndef EVENTSFILE_H_
11+
#define EVENTSFILE_H_
12+
13+
#include <fstream>
14+
#include <list>
15+
#include <memory>
16+
#include <mutex>
17+
#include <string>
18+
#include <vector>
19+
20+
namespace uprofile
21+
{
22+
23+
class EventsFile
24+
{
25+
public:
26+
EventsFile(const char* filepath, unsigned long long maxCapSize);
27+
~EventsFile();
28+
29+
void write(const std::string& event, unsigned long long timestamp, const std::list<std::string>& data);
30+
31+
static const int ROTATING_FILES_NUMBER = 2;
32+
33+
private:
34+
std::mutex m_fileMutex;
35+
std::ofstream m_file;
36+
std::vector<std::string> m_filePaths;
37+
unsigned int m_currentFileIdx = 0;
38+
unsigned long long m_currentFileSize = 0;
39+
unsigned long long m_maxCapSize = 0;
40+
41+
void rotateFile();
42+
};
43+
using EventsFilePtr = std::shared_ptr<EventsFile>;
44+
45+
}
46+
47+
#endif /* EVENTSFILE_H_ */

lib/uprofile.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ using namespace std::chrono;
3434
namespace uprofile
3535
{
3636

37-
void start(const char* file)
37+
void start(const char* filepath, unsigned long long maxCapSize)
3838
{
39-
UPROFILE_INSTANCE_CALL(start, file);
39+
UPROFILE_INSTANCE_CALL(start, filepath, maxCapSize);
4040
}
4141

4242
void stop()

lib/uprofile.h

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,15 @@ namespace uprofile
2929
/**
3030
* @ingroup uprofile
3131
* @brief Start profiling to periodically record monitored events to a file
32-
* @param file: file path where events will be saved
32+
* @param filepath: file path where events will be saved
33+
* @param maxCapSize: maximum file size in bytes
34+
*
35+
* By default, the file is unbounded.
36+
*
37+
* If you have some storage constraints, set maxCapSize parameter for generating two rotating files (<file>_0.<ext> and <file>_1.<ext>)
38+
* Each file will have a maximum size of maxCapSize/2 (In this mode, more recent events will override older events)
3339
*/
34-
UPROFAPI void start(const char* file);
40+
UPROFAPI void start(const char* filepath, unsigned long long maxCapSize = 0);
3541

3642
/**
3743
* @ingroup uprofile

lib/uprofileimpl.cpp

Lines changed: 33 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,9 @@ UProfileImpl::~UProfileImpl()
5252
removeGPUMonitor();
5353
}
5454

55-
void UProfileImpl::start(const char* file)
55+
void UProfileImpl::start(const char* filepath, unsigned long long maxCapSize)
5656
{
57-
m_file.open(file, std::ios::out);
58-
if (!m_file.is_open()) {
59-
std::cerr << "Failed to open file: " << file << std::endl;
60-
}
57+
m_file = make_shared<EventsFile>(filepath, maxCapSize);
6158
}
6259

6360
void UProfileImpl::addGPUMonitor(IGPUMonitor* monitor)
@@ -226,7 +223,7 @@ void UProfileImpl::stop()
226223
if (m_gpuMonitor) {
227224
m_gpuMonitor->stop();
228225
}
229-
m_file.close();
226+
m_file.reset();
230227
}
231228

232229
void UProfileImpl::setTimestampUnit(TimestampUnit tsUnit)
@@ -236,43 +233,37 @@ void UProfileImpl::setTimestampUnit(TimestampUnit tsUnit)
236233

237234
void UProfileImpl::write(ProfilingType type, const std::list<std::string>& data)
238235
{
239-
if (m_file.is_open()) {
240-
const char csvSeparator = ';';
241-
std::string strType;
242-
switch (type) {
243-
case ProfilingType::TIME_EXEC:
244-
strType = "time_exec";
245-
break;
246-
case ProfilingType::TIME_EVENT:
247-
strType = "time_event";
248-
break;
249-
case ProfilingType::PROCESS_MEMORY:
250-
strType = "proc_mem";
251-
break;
252-
case ProfilingType::SYSTEM_MEMORY:
253-
strType = "sys_mem";
254-
break;
255-
case ProfilingType::CPU:
256-
strType = "cpu";
257-
break;
258-
case ProfilingType::GPU_USAGE:
259-
strType = "gpu";
260-
break;
261-
case ProfilingType::GPU_MEMORY:
262-
strType = "gpu_mem";
263-
break;
264-
default:
265-
strType = "undefined";
266-
break;
267-
}
268-
std::lock_guard<std::mutex> guard(m_fileMutex);
269-
m_file << strType.c_str() << csvSeparator << getTimestamp();
270-
for (auto it = data.cbegin(); it != data.cend(); ++it) {
271-
m_file << csvSeparator << *it;
272-
}
273-
m_file << "\n";
274-
m_file.flush();
236+
if (!m_file) {
237+
return;
238+
}
239+
std::string strType;
240+
switch (type) {
241+
case ProfilingType::TIME_EXEC:
242+
strType = "time_exec";
243+
break;
244+
case ProfilingType::TIME_EVENT:
245+
strType = "time_event";
246+
break;
247+
case ProfilingType::PROCESS_MEMORY:
248+
strType = "proc_mem";
249+
break;
250+
case ProfilingType::SYSTEM_MEMORY:
251+
strType = "sys_mem";
252+
break;
253+
case ProfilingType::CPU:
254+
strType = "cpu";
255+
break;
256+
case ProfilingType::GPU_USAGE:
257+
strType = "gpu";
258+
break;
259+
case ProfilingType::GPU_MEMORY:
260+
strType = "gpu_mem";
261+
break;
262+
default:
263+
strType = "undefined";
264+
break;
275265
}
266+
m_file->write(strType, getTimestamp(), data);
276267
}
277268

278269
unsigned long long UProfileImpl::getTimestamp() const

lib/uprofileimpl.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#ifndef UPROFILEIMPL_H_
1111
#define UPROFILEIMPL_H_
1212

13+
#include "eventsfile.h"
1314
#include "igpumonitor.h"
1415
#include "timestampunit.h"
1516
#include "util/cpumonitor.h"
@@ -40,7 +41,7 @@ class UProfileImpl
4041
virtual ~UProfileImpl();
4142

4243
// Implementation
43-
void start(const char* file);
44+
void start(const char* filepath, unsigned long long maxCapSize = 0);
4445
void stop();
4546
void addGPUMonitor(IGPUMonitor* monitor);
4647
void removeGPUMonitor();
@@ -73,7 +74,7 @@ class UProfileImpl
7374

7475
TimestampUnit m_tsUnit;
7576
std::map<std::string, unsigned long long> m_steps; // Store steps (title, start time)
76-
std::ofstream m_file;
77+
EventsFilePtr m_file = nullptr;
7778
Timer m_processMemoryMonitorTimer;
7879
Timer m_systemMemoryMonitorTimer;
7980
Timer m_cpuMonitorTimer;

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
numpy
22
plotly
33
pandas
4+
pre-commit

0 commit comments

Comments
 (0)