Home | History | Annotate | Download | only in disk_cache_memory_test
      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