1cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// Copyright 2014 The Chromium Authors. All rights reserved.
2cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
3cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// found in the LICENSE file.
4cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
5cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)#include "chrome/browser/metrics/plugin_metrics_provider.h"
6cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
7cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)#include <string>
8cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
9cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)#include "base/prefs/pref_registry_simple.h"
10cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)#include "base/prefs/pref_service.h"
11cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)#include "base/prefs/scoped_user_pref_update.h"
12cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)#include "base/stl_util.h"
13cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)#include "base/strings/utf_string_conversions.h"
145f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)#include "base/time/time.h"
15cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)#include "chrome/browser/browser_process.h"
16cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)#include "chrome/browser/plugins/plugin_prefs.h"
17cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)#include "chrome/browser/profiles/profile_manager.h"
18cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)#include "chrome/common/pref_names.h"
19cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)#include "components/metrics/proto/system_profile.pb.h"
20cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)#include "content/public/browser/child_process_data.h"
21cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)#include "content/public/browser/plugin_service.h"
22cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)#include "content/public/common/process_type.h"
23cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)#include "content/public/common/webplugininfo.h"
24cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
25cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)namespace {
26cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
275f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)// Delay for RecordCurrentState execution.
285f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)const int kRecordStateDelayMs = 15 * base::Time::kMillisecondsPerSecond;
295f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
30cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// Returns the plugin preferences corresponding for this user, if available.
31cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// If multiple user profiles are loaded, returns the preferences corresponding
32cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// to an arbitrary one of the profiles.
33cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)PluginPrefs* GetPluginPrefs() {
34cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  ProfileManager* profile_manager = g_browser_process->profile_manager();
35cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
36cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (!profile_manager) {
37cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // The profile manager can be NULL when testing.
38cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return NULL;
39cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
40cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
41cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  std::vector<Profile*> profiles = profile_manager->GetLoadedProfiles();
42cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (profiles.empty())
43cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return NULL;
44cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
45cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return PluginPrefs::GetForProfile(profiles.front()).get();
46cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)}
47cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
48cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// Fills |plugin| with the info contained in |plugin_info| and |plugin_prefs|.
49cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)void SetPluginInfo(const content::WebPluginInfo& plugin_info,
50cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                   const PluginPrefs* plugin_prefs,
51cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                   metrics::SystemProfileProto::Plugin* plugin) {
52cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  plugin->set_name(base::UTF16ToUTF8(plugin_info.name));
53cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  plugin->set_filename(plugin_info.path.BaseName().AsUTF8Unsafe());
54cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  plugin->set_version(base::UTF16ToUTF8(plugin_info.version));
55cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  plugin->set_is_pepper(plugin_info.is_pepper_plugin());
56cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (plugin_prefs)
57cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    plugin->set_is_disabled(!plugin_prefs->IsPluginEnabled(plugin_info));
58cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)}
59cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
60cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)}  // namespace
61cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
62cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// This is used to quickly log stats from child process related notifications in
63cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// PluginMetricsProvider::child_stats_buffer_.  The buffer's contents are
64cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// transferred out when Local State is periodically saved.  The information is
65cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// then reported to the UMA server on next launch.
66cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)struct PluginMetricsProvider::ChildProcessStats {
67cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) public:
68cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  explicit ChildProcessStats(int process_type)
69cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      : process_launches(0),
70cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        process_crashes(0),
71cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        instances(0),
72cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        loading_errors(0),
73cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        process_type(process_type) {}
74cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
75cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // This constructor is only used by the map to return some default value for
76cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // an index for which no value has been assigned.
77cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  ChildProcessStats()
78cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      : process_launches(0),
79cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        process_crashes(0),
80cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        instances(0),
81cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        loading_errors(0),
82cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        process_type(content::PROCESS_TYPE_UNKNOWN) {}
83cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
84cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // The number of times that the given child process has been launched
85cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  int process_launches;
86cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
87cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // The number of times that the given child process has crashed
88cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  int process_crashes;
89cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
90cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // The number of instances of this child process that have been created.
91cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // An instance is a DOM object rendered by this child process during a page
92cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // load.
93cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  int instances;
94cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
95cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // The number of times there was an error loading an instance of this child
96cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // process.
97cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  int loading_errors;
98cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
99cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  int process_type;
100cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)};
101cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
102cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)PluginMetricsProvider::PluginMetricsProvider(PrefService* local_state)
103cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    : local_state_(local_state),
104cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      weak_ptr_factory_(this) {
105cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  DCHECK(local_state_);
106cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
107cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  BrowserChildProcessObserver::Add(this);
108cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)}
109cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
110cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)PluginMetricsProvider::~PluginMetricsProvider() {
111cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  BrowserChildProcessObserver::Remove(this);
112cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)}
113cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
114cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)void PluginMetricsProvider::GetPluginInformation(
115cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    const base::Closure& done_callback) {
116cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  content::PluginService::GetInstance()->GetPlugins(
117cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      base::Bind(&PluginMetricsProvider::OnGotPlugins,
118cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                 weak_ptr_factory_.GetWeakPtr(),
119cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                 done_callback));
120cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)}
121cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
122cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)void PluginMetricsProvider::ProvideSystemProfileMetrics(
123cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    metrics::SystemProfileProto* system_profile_proto) {
124cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  PluginPrefs* plugin_prefs = GetPluginPrefs();
125cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  for (size_t i = 0; i < plugins_.size(); ++i) {
126cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    SetPluginInfo(plugins_[i], plugin_prefs,
127cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                  system_profile_proto->add_plugin());
128cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
129cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)}
130cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
131cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)void PluginMetricsProvider::ProvideStabilityMetrics(
132cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    metrics::SystemProfileProto* system_profile_proto) {
1335f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  RecordCurrentStateIfPending();
134cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  const base::ListValue* plugin_stats_list = local_state_->GetList(
135cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      prefs::kStabilityPluginStats);
136cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (!plugin_stats_list)
137cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return;
138cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
139cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  metrics::SystemProfileProto::Stability* stability =
140cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      system_profile_proto->mutable_stability();
141cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  for (base::ListValue::const_iterator iter = plugin_stats_list->begin();
142cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)       iter != plugin_stats_list->end(); ++iter) {
143cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if (!(*iter)->IsType(base::Value::TYPE_DICTIONARY)) {
144cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      NOTREACHED();
145cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      continue;
146cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
147cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    base::DictionaryValue* plugin_dict =
148cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        static_cast<base::DictionaryValue*>(*iter);
149cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
150cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // Note that this search is potentially a quadratic operation, but given the
151cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // low number of plugins installed on a "reasonable" setup, this should be
152cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // fine.
153cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // TODO(isherman): Verify that this does not show up as a hotspot in
154cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // profiler runs.
155cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    const metrics::SystemProfileProto::Plugin* system_profile_plugin = NULL;
156cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    std::string plugin_name;
157cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    plugin_dict->GetString(prefs::kStabilityPluginName, &plugin_name);
158cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    for (int i = 0; i < system_profile_proto->plugin_size(); ++i) {
159cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      if (system_profile_proto->plugin(i).name() == plugin_name) {
160cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        system_profile_plugin = &system_profile_proto->plugin(i);
161cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        break;
162cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      }
163cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
164cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
165cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if (!system_profile_plugin) {
166cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      NOTREACHED();
167cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      continue;
168cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
169cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
170cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    metrics::SystemProfileProto::Stability::PluginStability* plugin_stability =
171cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        stability->add_plugin_stability();
172cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    *plugin_stability->mutable_plugin() = *system_profile_plugin;
173cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
174cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    int launches = 0;
175cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    plugin_dict->GetInteger(prefs::kStabilityPluginLaunches, &launches);
176cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if (launches > 0)
177cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      plugin_stability->set_launch_count(launches);
178cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
179cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    int instances = 0;
180cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    plugin_dict->GetInteger(prefs::kStabilityPluginInstances, &instances);
181cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if (instances > 0)
182cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      plugin_stability->set_instance_count(instances);
183cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
184cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    int crashes = 0;
185cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    plugin_dict->GetInteger(prefs::kStabilityPluginCrashes, &crashes);
186cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if (crashes > 0)
187cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      plugin_stability->set_crash_count(crashes);
188cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
189cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    int loading_errors = 0;
190cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    plugin_dict->GetInteger(prefs::kStabilityPluginLoadingErrors,
191cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                            &loading_errors);
192cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if (loading_errors > 0)
193cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      plugin_stability->set_loading_error_count(loading_errors);
194cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
195cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
196cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  local_state_->ClearPref(prefs::kStabilityPluginStats);
197cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)}
198cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
1991320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tuccivoid PluginMetricsProvider::ClearSavedStabilityMetrics() {
2001320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  local_state_->ClearPref(prefs::kStabilityPluginStats);
2011320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci}
2021320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
20346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)// Saves plugin-related updates from the in-object buffer to Local State
20446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)// for retrieval next time we send a Profile log (generally next launch).
20546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)void PluginMetricsProvider::RecordCurrentState() {
206cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  ListPrefUpdate update(local_state_, prefs::kStabilityPluginStats);
207cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  base::ListValue* plugins = update.Get();
208cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  DCHECK(plugins);
209cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
210cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  for (base::ListValue::iterator value_iter = plugins->begin();
211cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)       value_iter != plugins->end(); ++value_iter) {
212cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if (!(*value_iter)->IsType(base::Value::TYPE_DICTIONARY)) {
213cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      NOTREACHED();
214cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      continue;
215cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
216cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
217cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    base::DictionaryValue* plugin_dict =
218cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        static_cast<base::DictionaryValue*>(*value_iter);
219cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    std::string plugin_name;
220cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    plugin_dict->GetString(prefs::kStabilityPluginName, &plugin_name);
221cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if (plugin_name.empty()) {
222cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      NOTREACHED();
223cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      continue;
224cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
225cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
226cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // TODO(viettrungluu): remove conversions
227cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    base::string16 name16 = base::UTF8ToUTF16(plugin_name);
228cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if (child_process_stats_buffer_.find(name16) ==
229cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        child_process_stats_buffer_.end()) {
230cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      continue;
231cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
232cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
233cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    ChildProcessStats stats = child_process_stats_buffer_[name16];
234cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if (stats.process_launches) {
235cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      int launches = 0;
236cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      plugin_dict->GetInteger(prefs::kStabilityPluginLaunches, &launches);
237cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      launches += stats.process_launches;
238cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      plugin_dict->SetInteger(prefs::kStabilityPluginLaunches, launches);
239cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
240cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if (stats.process_crashes) {
241cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      int crashes = 0;
242cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      plugin_dict->GetInteger(prefs::kStabilityPluginCrashes, &crashes);
243cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      crashes += stats.process_crashes;
244cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      plugin_dict->SetInteger(prefs::kStabilityPluginCrashes, crashes);
245cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
246cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if (stats.instances) {
247cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      int instances = 0;
248cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      plugin_dict->GetInteger(prefs::kStabilityPluginInstances, &instances);
249cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      instances += stats.instances;
250cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      plugin_dict->SetInteger(prefs::kStabilityPluginInstances, instances);
251cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
252cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if (stats.loading_errors) {
253cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      int loading_errors = 0;
254cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      plugin_dict->GetInteger(prefs::kStabilityPluginLoadingErrors,
255cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                              &loading_errors);
256cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      loading_errors += stats.loading_errors;
257cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      plugin_dict->SetInteger(prefs::kStabilityPluginLoadingErrors,
258cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                              loading_errors);
259cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    }
260cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
261cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    child_process_stats_buffer_.erase(name16);
262cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
263cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
264cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // Now go through and add dictionaries for plugins that didn't already have
265cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // reports in Local State.
266cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  for (std::map<base::string16, ChildProcessStats>::iterator cache_iter =
267cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)           child_process_stats_buffer_.begin();
268cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)       cache_iter != child_process_stats_buffer_.end(); ++cache_iter) {
269cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    ChildProcessStats stats = cache_iter->second;
270cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
271cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // Insert only plugins information into the plugins list.
272cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if (!IsPluginProcess(stats.process_type))
273cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      continue;
274cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
275cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // TODO(viettrungluu): remove conversion
276cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    std::string plugin_name = base::UTF16ToUTF8(cache_iter->first);
277cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
278cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    base::DictionaryValue* plugin_dict = new base::DictionaryValue;
279cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
280cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    plugin_dict->SetString(prefs::kStabilityPluginName, plugin_name);
281cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    plugin_dict->SetInteger(prefs::kStabilityPluginLaunches,
282cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                            stats.process_launches);
283cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    plugin_dict->SetInteger(prefs::kStabilityPluginCrashes,
284cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                            stats.process_crashes);
285cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    plugin_dict->SetInteger(prefs::kStabilityPluginInstances,
286cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                            stats.instances);
287cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    plugin_dict->SetInteger(prefs::kStabilityPluginLoadingErrors,
288cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                            stats.loading_errors);
289cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    plugins->Append(plugin_dict);
290cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
291cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  child_process_stats_buffer_.clear();
292cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)}
293cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
294cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)void PluginMetricsProvider::LogPluginLoadingError(
295cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    const base::FilePath& plugin_path) {
296cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  content::WebPluginInfo plugin;
297cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  bool success =
298cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      content::PluginService::GetInstance()->GetPluginInfoByPath(plugin_path,
299cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                                                                 &plugin);
300cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  DCHECK(success);
301cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  ChildProcessStats& stats = child_process_stats_buffer_[plugin.name];
302cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  // Initialize the type if this entry is new.
303cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (stats.process_type == content::PROCESS_TYPE_UNKNOWN) {
304cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // The plug-in process might not actually be of type PLUGIN (which means
305cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    // NPAPI), but we only care that it is *a* plug-in process.
306cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    stats.process_type = content::PROCESS_TYPE_PLUGIN;
307cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  } else {
308cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    DCHECK(IsPluginProcess(stats.process_type));
309cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
310cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  stats.loading_errors++;
3115f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  RecordCurrentStateWithDelay(kRecordStateDelayMs);
312cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)}
313cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
314cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)void PluginMetricsProvider::SetPluginsForTesting(
315cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    const std::vector<content::WebPluginInfo>& plugins) {
316cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  plugins_ = plugins;
317cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)}
318cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
319cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// static
320cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)bool PluginMetricsProvider::IsPluginProcess(int process_type) {
321cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return (process_type == content::PROCESS_TYPE_PLUGIN ||
322cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          process_type == content::PROCESS_TYPE_PPAPI_PLUGIN ||
323cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          process_type == content::PROCESS_TYPE_PPAPI_BROKER);
324cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)}
325cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
326cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)// static
327cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)void PluginMetricsProvider::RegisterPrefs(PrefRegistrySimple* registry) {
328cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  registry->RegisterListPref(prefs::kStabilityPluginStats);
329cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)}
330cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
331cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)void PluginMetricsProvider::OnGotPlugins(
332cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    const base::Closure& done_callback,
333cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    const std::vector<content::WebPluginInfo>& plugins) {
334cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  plugins_ = plugins;
335cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  done_callback.Run();
336cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)}
337cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
338cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)PluginMetricsProvider::ChildProcessStats&
339cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)PluginMetricsProvider::GetChildProcessStats(
340cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    const content::ChildProcessData& data) {
341cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  const base::string16& child_name = data.name;
342cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (!ContainsKey(child_process_stats_buffer_, child_name)) {
343cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    child_process_stats_buffer_[child_name] =
344cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        ChildProcessStats(data.process_type);
345cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  }
346cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return child_process_stats_buffer_[child_name];
347cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)}
348cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
349cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)void PluginMetricsProvider::BrowserChildProcessHostConnected(
350cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    const content::ChildProcessData& data) {
351cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  GetChildProcessStats(data).process_launches++;
3525f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  RecordCurrentStateWithDelay(kRecordStateDelayMs);
353cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)}
354cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
355cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)void PluginMetricsProvider::BrowserChildProcessCrashed(
356cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    const content::ChildProcessData& data) {
357cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  GetChildProcessStats(data).process_crashes++;
3585f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  RecordCurrentStateWithDelay(kRecordStateDelayMs);
359cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)}
360cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
361cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)void PluginMetricsProvider::BrowserChildProcessInstanceCreated(
362cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    const content::ChildProcessData& data) {
363cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  GetChildProcessStats(data).instances++;
3645f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  RecordCurrentStateWithDelay(kRecordStateDelayMs);
3655f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)}
3665f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
3675f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)bool PluginMetricsProvider::RecordCurrentStateWithDelay(int delay_sec) {
3685f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  if (weak_ptr_factory_.HasWeakPtrs())
3695f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    return false;
3705f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
3715f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  base::MessageLoopProxy::current()->PostDelayedTask(
3725f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      FROM_HERE,
3735f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      base::Bind(&PluginMetricsProvider::RecordCurrentState,
3745f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                weak_ptr_factory_.GetWeakPtr()),
3755f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                base::TimeDelta::FromMilliseconds(delay_sec));
3765f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  return true;
3775f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)}
3785f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
3795f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)bool PluginMetricsProvider::RecordCurrentStateIfPending() {
3805f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  if (!weak_ptr_factory_.HasWeakPtrs())
3815f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    return false;
3825f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
3835f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  weak_ptr_factory_.InvalidateWeakPtrs();
3845f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  RecordCurrentState();
3855f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  return true;
386cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)}
387