Imported Upstream version 1.7.0
[platform/core/ml/nnfw.git] / runtime / libs / benchmark / src / MemoryPoller.cpp
1 /*
2  * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include "benchmark/MemoryPoller.h"
18 #include "benchmark/Types.h"
19
20 #include <vector>
21 #include <fstream>
22 #include <sstream>
23 #include <stdexcept>
24 #include <cassert>
25 #include <iostream>
26
27 namespace
28 {
29
30 const std::string proc_status_path("/proc/self/status");
31 const std::string gpu_memory_path("/sys/kernel/debug/mali0/gpu_memory");
32 const std::string proc_smaps_path("/proc/self/smaps");
33
34 bool isStrNumber(const std::string &s)
35 {
36   return !s.empty() &&
37          std::find_if(s.begin(), s.end(), [](char c) { return !std::isdigit(c); }) == s.end();
38 }
39
40 std::vector<std::string> splitLine(std::string line, std::string delimiters = " \n\t")
41 {
42   std::vector<std::string> words;
43   size_t prev = 0, pos;
44
45   while ((pos = line.find_first_of(delimiters, prev)) != std::string::npos)
46   {
47     if (pos > prev)
48       words.emplace_back(line.substr(prev, pos - prev));
49     prev = pos + 1;
50   }
51
52   if (prev < line.length())
53     words.emplace_back(line.substr(prev, std::string::npos));
54
55   return words;
56 }
57
58 std::vector<std::string> getValueFromFileStatus(const std::string &file, const std::string &key)
59 {
60   std::ifstream ifs(file);
61   assert(ifs.is_open());
62
63   std::string line;
64   std::vector<std::string> val;
65
66   bool found = false;
67   while (std::getline(ifs, line))
68   {
69     if (line.find(key) != std::string::npos)
70     {
71       found = true;
72       break;
73     }
74   }
75   ifs.close();
76
77   if (!found)
78   {
79     // NOTE. the process which uses gpu resources cannot be there yet at the model-load phase.
80     // At that time, just return empty.
81     return val;
82   }
83
84   val = splitLine(line);
85   return val;
86 }
87
88 // Because of smaps' structure, returns sum value as uint32_t
89 uint32_t getSumValueFromFileSmaps(const std::string &file, const std::string &key)
90 {
91   std::ifstream ifs(file);
92   assert(ifs.is_open());
93
94   std::string line;
95   uint32_t sum = 0;
96   while (std::getline(ifs, line))
97   {
98     if (line.find(key) != std::string::npos)
99     {
100       // an example by splitLine()
101       // `Pss:                   0 kB`
102       // val[0]: "Pss:", val[1]: "0" val[2]: "kB"
103       auto val = splitLine(line);
104       assert(val.size() != 0);
105       // SwapPss could show so that check where Pss is at the beginning
106       if (val[0].find("Pss") != 0)
107       {
108         continue;
109       }
110       sum += std::stoul(val[1]);
111     }
112   }
113
114   return sum;
115 }
116
117 } // namespace
118
119 namespace benchmark
120 {
121
122 MemoryPoller::MemoryPoller(std::chrono::milliseconds duration, bool gpu_poll)
123     : _duration(duration), _run(false), _term(false), _gpu_poll(gpu_poll)
124 {
125   if (prepareMemoryPolling() == false)
126     throw std::runtime_error("failed to prepare memory pooling");
127
128   _thread = std::thread{&MemoryPoller::process, this};
129 }
130
131 bool MemoryPoller::start(PhaseEnum phase)
132 {
133   if (std::find(_phases.begin(), _phases.end(), phase) != _phases.end())
134   {
135     std::cerr << getPhaseString(phase) << " is already processing/processed..." << std::endl;
136     return false;
137   }
138
139   {
140     std::lock_guard<std::mutex> lock(_mutex);
141     _phases.emplace_back(phase);
142     _rss_map[phase] = 0;
143     _hwm_map[phase] = 0;
144     _pss_map[phase] = 0;
145   }
146
147   _run = true;
148   _cond_var_started.notify_all();
149   return true;
150 }
151
152 bool MemoryPoller::end(PhaseEnum phase)
153 {
154   if (std::find(_phases.begin(), _phases.end(), phase) == _phases.end())
155   {
156     std::cerr << getPhaseString(phase) << " is not started..." << std::endl;
157     return false;
158   }
159
160   uint32_t mem = 0;
161   bool stop = false;
162   {
163     std::lock_guard<std::mutex> lock(_mutex);
164     _phases.remove(phase);
165     stop = (_phases.size() == 0);
166   }
167
168   if (_rss_map[phase] == 0)
169   {
170     uint32_t mem = getVmRSS();
171     if (_gpu_poll)
172     {
173       mem += getGpuMemory();
174     }
175     _rss_map[phase] = mem;
176   }
177
178   if (_hwm_map[phase] == 0)
179   {
180     uint32_t mem = getVmHWM();
181     if (_gpu_poll)
182     {
183       mem += getGpuMemory();
184     }
185     _hwm_map[phase] = mem;
186   }
187
188   if (_pss_map[phase] == 0)
189   {
190     uint32_t mem = getPssSum();
191     _pss_map[phase] = mem;
192   }
193
194   if (stop)
195   {
196     _run = false;
197     _cond_var_started.notify_all();
198   }
199
200   return true;
201 }
202
203 void MemoryPoller::process()
204 {
205   std::unique_lock<std::mutex> lock_started(_mutex_started);
206   while (true)
207   {
208     _cond_var_started.wait(lock_started, [&]() { return _run || _term; });
209     if (_term)
210       break;
211
212     std::unique_lock<std::mutex> lock(_mutex);
213
214     uint32_t cur_rss = getVmRSS();
215     uint32_t cur_hwm = getVmHWM();
216     if (_gpu_poll)
217     {
218       auto gpu_mem = getGpuMemory();
219       cur_rss += gpu_mem;
220       cur_hwm += gpu_mem;
221     }
222     uint32_t cur_pss = getPssSum();
223
224     for (auto &phase : _phases)
225     {
226       auto &rss = _rss_map.at(phase);
227       if (rss < cur_rss)
228         rss = cur_rss;
229       // hwm is gradually increasing
230       auto &hwm = _hwm_map.at(phase);
231       hwm = cur_hwm;
232       auto &pss = _pss_map.at(phase);
233       if (pss < cur_pss)
234         pss = cur_pss;
235     }
236
237     lock.unlock();
238
239     std::this_thread::sleep_for(std::chrono::milliseconds(_duration));
240   }
241 }
242
243 bool MemoryPoller::prepareMemoryPolling()
244 {
245   // VmRSS
246   {
247     std::ifstream ifs(proc_status_path);
248     if (!ifs.is_open())
249     {
250       std::cerr << "failed to open " << proc_status_path << std::endl;
251       return false;
252     }
253     ifs.close();
254   }
255
256   // (Additionally) GpuMemory
257   if (_gpu_poll)
258   {
259     std::ifstream ifs(gpu_memory_path);
260     if (!ifs.is_open())
261     {
262       std::cerr << "failed to open " << gpu_memory_path << std::endl;
263       return false;
264     }
265     ifs.close();
266
267     // Needs process name
268     auto val = getValueFromFileStatus(proc_status_path, "Name");
269     assert(val.size() != 0);
270     _process_name = val[1];
271   }
272
273   // PSS
274   {
275     std::ifstream ifs(proc_smaps_path);
276     if (!ifs.is_open())
277     {
278       std::cerr << "failed to open " << proc_smaps_path << std::endl;
279       return false;
280     }
281     ifs.close();
282   }
283
284   return true;
285 }
286
287 uint32_t MemoryPoller::getVmRSS()
288 {
289   auto val = getValueFromFileStatus(proc_status_path, "VmRSS");
290   if (val.size() == 0)
291     return 0;
292   assert(isStrNumber(val[1]));
293   return std::stoul(val[1]);
294 }
295
296 uint32_t MemoryPoller::getVmHWM()
297 {
298   auto val = getValueFromFileStatus(proc_status_path, "VmHWM");
299   if (val.size() == 0)
300     return 0;
301   // key: value
302   assert(isStrNumber(val[1]));
303   return std::stoul(val[1]);
304 }
305
306 uint32_t MemoryPoller::getGpuMemory()
307 {
308   assert(!_process_name.empty());
309   auto val = getValueFromFileStatus(gpu_memory_path, _process_name);
310   if (val.size() == 0)
311     return 0;
312   // process_name -> pid -> gpu_mem -> max_gpu_mem
313   assert(isStrNumber(val[2]));
314   return std::stoul(val[2]);
315 }
316
317 uint32_t MemoryPoller::getPssSum() { return getSumValueFromFileSmaps(proc_smaps_path, "Pss"); }
318
319 } // namespace benchmark