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