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