1 // Copyright 2013 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <cstdlib>
6 #include <fstream>
7 #include <iostream>
8 #include <memory>
9 #include <string>
10 #include <vector>
11
12 #include "base/at_exit.h"
13 #include "base/command_line.h"
14 #include "base/files/file_path.h"
15 #include "base/functional/bind.h"
16 #include "base/functional/callback.h"
17 #include "base/logging.h"
18 #include "base/memory/ptr_util.h"
19 #include "base/message_loop/message_pump_type.h"
20 #include "base/run_loop.h"
21 #include "base/strings/string_number_conversions.h"
22 #include "base/strings/string_piece.h"
23 #include "base/strings/string_split.h"
24 #include "base/strings/string_util.h"
25 #include "base/strings/stringprintf.h"
26 #include "base/task/single_thread_task_executor.h"
27 #include "base/task/thread_pool/thread_pool_instance.h"
28 #include "net/base/cache_type.h"
29 #include "net/base/net_errors.h"
30 #include "net/disk_cache/disk_cache.h"
31 #include "net/disk_cache/simple/simple_backend_impl.h"
32 #include "net/disk_cache/simple/simple_index.h"
33
34 namespace disk_cache {
35 namespace {
36
37 const char kBlockFileBackendType[] = "block_file";
38 const char kSimpleBackendType[] = "simple";
39
40 const char kDiskCacheType[] = "disk_cache";
41 const char kAppCacheType[] = "app_cache";
42
43 const char kPrivateDirty[] = "Private_Dirty:";
44 const char kReadWrite[] = "rw-";
45 const char kHeap[] = "[heap]";
46 const char kKb[] = "kB";
47
48 struct CacheSpec {
49 public:
Parsedisk_cache::__anonce43f2e40111::CacheSpec50 static std::unique_ptr<CacheSpec> Parse(const std::string& spec_string) {
51 std::vector<std::string> tokens = base::SplitString(
52 spec_string, ":", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
53 if (tokens.size() != 3)
54 return nullptr;
55 if (tokens[0] != kBlockFileBackendType && tokens[0] != kSimpleBackendType)
56 return nullptr;
57 if (tokens[1] != kDiskCacheType && tokens[1] != kAppCacheType)
58 return nullptr;
59 return base::WrapUnique(new CacheSpec(
60 tokens[0] == kBlockFileBackendType ? net::CACHE_BACKEND_BLOCKFILE
61 : net::CACHE_BACKEND_SIMPLE,
62 tokens[1] == kDiskCacheType ? net::DISK_CACHE : net::APP_CACHE,
63 base::FilePath(tokens[2])));
64 }
65
66 const net::BackendType backend_type;
67 const net::CacheType cache_type;
68 const base::FilePath path;
69
70 private:
CacheSpecdisk_cache::__anonce43f2e40111::CacheSpec71 CacheSpec(net::BackendType backend_type,
72 net::CacheType cache_type,
73 const base::FilePath& path)
74 : backend_type(backend_type),
75 cache_type(cache_type),
76 path(path) {
77 }
78 };
79
SetSuccessCodeOnCompletion(base::RunLoop * run_loop,bool * succeeded,int net_error)80 void SetSuccessCodeOnCompletion(base::RunLoop* run_loop,
81 bool* succeeded,
82 int net_error) {
83 if (net_error == net::OK) {
84 *succeeded = true;
85 } else {
86 *succeeded = false;
87 }
88 run_loop->Quit();
89 }
90
CreateAndInitBackend(const CacheSpec & spec)91 std::unique_ptr<Backend> CreateAndInitBackend(const CacheSpec& spec) {
92 base::RunLoop run_loop;
93 BackendResult result;
94 result = CreateCacheBackend(
95 spec.cache_type, spec.backend_type, /*file_operations=*/nullptr,
96 spec.path, 0, disk_cache::ResetHandling::kNeverReset, /*net_log=*/nullptr,
97 base::BindOnce(
98 [](BackendResult* out, base::RunLoop* run_loop,
99 BackendResult async_result) {
100 *out = std::move(async_result);
101 run_loop->Quit();
102 },
103 &result, &run_loop));
104 if (result.net_error == net::ERR_IO_PENDING)
105 run_loop.Run();
106 if (result.net_error != net::OK) {
107 LOG(ERROR) << "Could not initialize backend in "
108 << spec.path.LossyDisplayName();
109 return nullptr;
110 }
111 // For the simple cache, the index may not be initialized yet.
112 bool succeeded = false;
113 if (spec.backend_type == net::CACHE_BACKEND_SIMPLE) {
114 base::RunLoop index_run_loop;
115 net::CompletionOnceCallback index_callback = base::BindOnce(
116 &SetSuccessCodeOnCompletion, &index_run_loop, &succeeded);
117 SimpleBackendImpl* simple_backend =
118 static_cast<SimpleBackendImpl*>(result.backend.get());
119 simple_backend->index()->ExecuteWhenReady(std::move(index_callback));
120 index_run_loop.Run();
121 if (!succeeded) {
122 LOG(ERROR) << "Could not initialize Simple Cache in "
123 << spec.path.LossyDisplayName();
124 return nullptr;
125 }
126 }
127 DCHECK(result.backend);
128 return std::move(result.backend);
129 }
130
131 // Parses range lines from /proc/<PID>/smaps, e.g. (anonymous read write):
132 // 7f819d88b000-7f819d890000 rw-p 00000000 00:00 0
ParseRangeLine(const std::string & line,std::vector<std::string> * tokens,bool * is_anonymous_read_write)133 bool ParseRangeLine(const std::string& line,
134 std::vector<std::string>* tokens,
135 bool* is_anonymous_read_write) {
136 *tokens = base::SplitString(line, base::kWhitespaceASCII,
137 base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
138 if (tokens->size() == 5) {
139 const std::string& mode = (*tokens)[1];
140 *is_anonymous_read_write = !mode.compare(0, 3, kReadWrite);
141 return true;
142 }
143 // On Android, most of the memory is allocated in the heap, instead of being
144 // mapped.
145 if (tokens->size() == 6) {
146 const std::string& type = (*tokens)[5];
147 *is_anonymous_read_write = (type == kHeap);
148 return true;
149 }
150 return false;
151 }
152
153 // Parses range property lines from /proc/<PID>/smaps, e.g.:
154 // Private_Dirty: 16 kB
155 //
156 // Returns |false| iff it recognizes a new range line. Outputs non-zero |size|
157 // only if parsing succeeded.
ParseRangeProperty(const std::string & line,std::vector<std::string> * tokens,uint64_t * size,bool * is_private_dirty)158 bool ParseRangeProperty(const std::string& line,
159 std::vector<std::string>* tokens,
160 uint64_t* size,
161 bool* is_private_dirty) {
162 *tokens = base::SplitString(line, base::kWhitespaceASCII,
163 base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
164
165 // If the line is long, attempt to parse new range outside of this scope.
166 if (tokens->size() > 3)
167 return false;
168
169 // Skip the line on other parsing error occasions.
170 if (tokens->size() < 3)
171 return true;
172 const std::string& type = (*tokens)[0];
173 if (type != kPrivateDirty)
174 return true;
175 const std::string& unit = (*tokens)[2];
176 if (unit != kKb) {
177 LOG(WARNING) << "Discarding value not in kB: " << line;
178 return true;
179 }
180 const std::string& size_str = (*tokens)[1];
181 uint64_t map_size = 0;
182 if (!base::StringToUint64(size_str, &map_size))
183 return true;
184 *is_private_dirty = true;
185 *size = map_size;
186 return true;
187 }
188
GetMemoryConsumption()189 uint64_t GetMemoryConsumption() {
190 std::ifstream maps_file(
191 base::StringPrintf("/proc/%d/smaps", getpid()).c_str());
192 if (!maps_file.good()) {
193 LOG(ERROR) << "Could not open smaps file.";
194 return false;
195 }
196 std::string line;
197 std::vector<std::string> tokens;
198 uint64_t total_size = 0;
199 if (!std::getline(maps_file, line) || line.empty())
200 return total_size;
201 while (true) {
202 bool is_anonymous_read_write = false;
203 if (!ParseRangeLine(line, &tokens, &is_anonymous_read_write)) {
204 LOG(WARNING) << "Parsing smaps - did not expect line: " << line;
205 }
206 if (!std::getline(maps_file, line) || line.empty())
207 return total_size;
208 bool is_private_dirty = false;
209 uint64_t size = 0;
210 while (ParseRangeProperty(line, &tokens, &size, &is_private_dirty)) {
211 if (is_anonymous_read_write && is_private_dirty) {
212 total_size += size;
213 is_private_dirty = false;
214 }
215 if (!std::getline(maps_file, line) || line.empty())
216 return total_size;
217 }
218 }
219 }
220
CacheMemTest(const std::vector<std::unique_ptr<CacheSpec>> & specs)221 bool CacheMemTest(const std::vector<std::unique_ptr<CacheSpec>>& specs) {
222 std::vector<std::unique_ptr<Backend>> backends;
223 for (const auto& it : specs) {
224 std::unique_ptr<Backend> backend = CreateAndInitBackend(*it);
225 if (!backend)
226 return false;
227 std::cout << "Number of entries in " << it->path.LossyDisplayName() << " : "
228 << backend->GetEntryCount() << std::endl;
229 backends.push_back(std::move(backend));
230 }
231 const uint64_t memory_consumption = GetMemoryConsumption();
232 std::cout << "Private dirty memory: " << memory_consumption << " kB"
233 << std::endl;
234 return true;
235 }
236
PrintUsage(std::ostream * stream)237 void PrintUsage(std::ostream* stream) {
238 *stream << "Usage: disk_cache_mem_test "
239 << "--spec-1=<spec> "
240 << "[--spec-2=<spec>]"
241 << std::endl
242 << " with <cache_spec>=<backend_type>:<cache_type>:<cache_path>"
243 << std::endl
244 << " <backend_type>='block_file'|'simple'" << std::endl
245 << " <cache_type>='disk_cache'|'app_cache'" << std::endl
246 << " <cache_path>=file system path" << std::endl;
247 }
248
ParseAndStoreSpec(const std::string & spec_str,std::vector<std::unique_ptr<CacheSpec>> * specs)249 bool ParseAndStoreSpec(const std::string& spec_str,
250 std::vector<std::unique_ptr<CacheSpec>>* specs) {
251 std::unique_ptr<CacheSpec> spec = CacheSpec::Parse(spec_str);
252 if (!spec) {
253 PrintUsage(&std::cerr);
254 return false;
255 }
256 specs->push_back(std::move(spec));
257 return true;
258 }
259
Main(int argc,char ** argv)260 bool Main(int argc, char** argv) {
261 base::AtExitManager at_exit_manager;
262 base::SingleThreadTaskExecutor executor(base::MessagePumpType::IO);
263 base::ThreadPoolInstance::CreateAndStartWithDefaultParams(
264 "disk_cache_memory_test");
265 base::CommandLine::Init(argc, argv);
266 const base::CommandLine& command_line =
267 *base::CommandLine::ForCurrentProcess();
268 if (command_line.HasSwitch("help")) {
269 PrintUsage(&std::cout);
270 return true;
271 }
272 if ((command_line.GetSwitches().size() != 1 &&
273 command_line.GetSwitches().size() != 2) ||
274 !command_line.HasSwitch("spec-1") ||
275 (command_line.GetSwitches().size() == 2 &&
276 !command_line.HasSwitch("spec-2"))) {
277 PrintUsage(&std::cerr);
278 return false;
279 }
280 std::vector<std::unique_ptr<CacheSpec>> specs;
281 const std::string spec_str_1 = command_line.GetSwitchValueASCII("spec-1");
282 if (!ParseAndStoreSpec(spec_str_1, &specs))
283 return false;
284 if (command_line.HasSwitch("spec-2")) {
285 const std::string spec_str_2 = command_line.GetSwitchValueASCII("spec-2");
286 if (!ParseAndStoreSpec(spec_str_2, &specs))
287 return false;
288 }
289 return CacheMemTest(specs);
290 }
291
292 } // namespace
293 } // namespace disk_cache
294
main(int argc,char ** argv)295 int main(int argc, char** argv) {
296 return !disk_cache::Main(argc, argv);
297 }
298