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
32namespace disk_cache {
33namespace {
34
35const char kBlockFileBackendType[] = "block_file";
36const char kSimpleBackendType[] = "simple";
37
38const char kDiskCacheType[] = "disk_cache";
39const char kAppCacheType[] = "app_cache";
40
41const char kPrivateDirty[] = "Private_Dirty:";
42const char kReadWrite[] = "rw-";
43const char kHeap[] = "[heap]";
44const char kKb[] = "kB";
45
46struct 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
78void 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
89scoped_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
138bool 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.
163bool 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
194uint64 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
227bool 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
244void 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
256bool 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
267bool 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
299int main(int argc, char** argv) {
300  return !disk_cache::Main(argc, argv);
301}
302