1// Copyright (c) 2012 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 "dbus/dbus_statistics.h"
6
7#include <set>
8
9#include "base/logging.h"
10#include "base/memory/scoped_ptr.h"
11#include "base/stl_util.h"
12#include "base/strings/stringprintf.h"
13#include "base/threading/platform_thread.h"
14#include "base/time/time.h"
15
16namespace dbus {
17
18namespace {
19
20// Used to store dbus statistics sorted alphabetically by service, interface,
21// then method (using std::string <).
22struct Stat {
23  Stat(const std::string& service,
24       const std::string& interface,
25       const std::string& method)
26      : service(service),
27        interface(interface),
28        method(method),
29        sent_method_calls(0),
30        received_signals(0),
31        sent_blocking_method_calls(0) {
32  }
33  std::string service;
34  std::string interface;
35  std::string method;
36  int sent_method_calls;
37  int received_signals;
38  int sent_blocking_method_calls;
39
40  bool Compare(const Stat& other) const {
41    if (service != other.service)
42      return service < other.service;
43    if (interface != other.interface)
44      return interface < other.interface;
45    return method < other.method;
46  }
47
48  struct PtrCompare {
49    bool operator()(Stat* lhs, Stat* rhs) const {
50      DCHECK(lhs && rhs);
51      return lhs->Compare(*rhs);
52    }
53  };
54};
55
56typedef std::set<Stat*, Stat::PtrCompare> StatSet;
57
58//------------------------------------------------------------------------------
59// DBusStatistics
60
61// Simple class for gathering DBus usage statistics.
62class DBusStatistics {
63 public:
64  DBusStatistics()
65      : start_time_(base::Time::Now()),
66        origin_thread_id_(base::PlatformThread::CurrentId()) {
67  }
68
69  ~DBusStatistics() {
70    DCHECK_EQ(origin_thread_id_, base::PlatformThread::CurrentId());
71    STLDeleteContainerPointers(stats_.begin(), stats_.end());
72  }
73
74  // Enum to specify which field in Stat to increment in AddStat
75  enum StatType {
76    TYPE_SENT_METHOD_CALLS,
77    TYPE_RECEIVED_SIGNALS,
78    TYPE_SENT_BLOCKING_METHOD_CALLS
79  };
80
81  // Add a call to |method| for |interface|. See also MethodCall in message.h.
82  void AddStat(const std::string& service,
83               const std::string& interface,
84               const std::string& method,
85               StatType type) {
86    if (base::PlatformThread::CurrentId() != origin_thread_id_) {
87      DVLOG(1) << "Ignoring DBusStatistics::AddStat call from thread: "
88               << base::PlatformThread::CurrentId();
89      return;
90    }
91    Stat* stat = GetStat(service, interface, method, true);
92    DCHECK(stat);
93    if (type == TYPE_SENT_METHOD_CALLS)
94      ++stat->sent_method_calls;
95    else if (type == TYPE_RECEIVED_SIGNALS)
96      ++stat->received_signals;
97    else if (type == TYPE_SENT_BLOCKING_METHOD_CALLS)
98      ++stat->sent_blocking_method_calls;
99    else
100      NOTREACHED();
101  }
102
103  // Look up the Stat entry in |stats_|. If |add_stat| is true, add a new entry
104  // if one does not already exist.
105  Stat* GetStat(const std::string& service,
106                const std::string& interface,
107                const std::string& method,
108                bool add_stat) {
109    DCHECK_EQ(origin_thread_id_, base::PlatformThread::CurrentId());
110    scoped_ptr<Stat> stat(new Stat(service, interface, method));
111    StatSet::iterator found = stats_.find(stat.get());
112    if (found != stats_.end())
113      return *found;
114    if (!add_stat)
115      return NULL;
116    found = stats_.insert(stat.release()).first;
117    return *found;
118  }
119
120  StatSet& stats() { return stats_; }
121  base::Time start_time() { return start_time_; }
122
123 private:
124  StatSet stats_;
125  base::Time start_time_;
126  base::PlatformThreadId origin_thread_id_;
127
128  DISALLOW_COPY_AND_ASSIGN(DBusStatistics);
129};
130
131DBusStatistics* g_dbus_statistics = NULL;
132
133}  // namespace
134
135//------------------------------------------------------------------------------
136
137namespace statistics {
138
139void Initialize() {
140  if (g_dbus_statistics)
141    delete g_dbus_statistics;  // reset statistics
142  g_dbus_statistics = new DBusStatistics();
143}
144
145void Shutdown() {
146  delete g_dbus_statistics;
147  g_dbus_statistics = NULL;
148}
149
150void AddSentMethodCall(const std::string& service,
151                       const std::string& interface,
152                       const std::string& method) {
153  if (!g_dbus_statistics)
154    return;
155  g_dbus_statistics->AddStat(
156      service, interface, method, DBusStatistics::TYPE_SENT_METHOD_CALLS);
157}
158
159void AddReceivedSignal(const std::string& service,
160                       const std::string& interface,
161                       const std::string& method) {
162  if (!g_dbus_statistics)
163    return;
164  g_dbus_statistics->AddStat(
165      service, interface, method, DBusStatistics::TYPE_RECEIVED_SIGNALS);
166}
167
168void AddBlockingSentMethodCall(const std::string& service,
169                               const std::string& interface,
170                               const std::string& method) {
171  if (!g_dbus_statistics)
172    return;
173  g_dbus_statistics->AddStat(
174      service, interface, method,
175      DBusStatistics::TYPE_SENT_BLOCKING_METHOD_CALLS);
176}
177
178// NOTE: If the output format is changed, be certain to change the test
179// expectations as well.
180std::string GetAsString(ShowInString show, FormatString format) {
181  if (!g_dbus_statistics)
182    return "DBusStatistics not initialized.";
183
184  const StatSet& stats = g_dbus_statistics->stats();
185  if (stats.empty())
186    return "No DBus calls.";
187
188  base::TimeDelta dtime = base::Time::Now() - g_dbus_statistics->start_time();
189  int dminutes = dtime.InMinutes();
190  dminutes = std::max(dminutes, 1);
191
192  std::string result;
193  int sent = 0, received = 0, sent_blocking = 0;
194  // Stats are stored in order by service, then interface, then method.
195  for (StatSet::const_iterator iter = stats.begin(); iter != stats.end(); ) {
196    StatSet::const_iterator cur_iter = iter;
197    StatSet::const_iterator next_iter = ++iter;
198    const Stat* stat = *cur_iter;
199    sent += stat->sent_method_calls;
200    received += stat->received_signals;
201    sent_blocking += stat->sent_blocking_method_calls;
202    // If this is not the last stat, and if the next stat matches the current
203    // stat, continue.
204    if (next_iter != stats.end() &&
205        (*next_iter)->service == stat->service &&
206        (show < SHOW_INTERFACE || (*next_iter)->interface == stat->interface) &&
207        (show < SHOW_METHOD || (*next_iter)->method == stat->method))
208      continue;
209
210    if (!sent && !received && !sent_blocking)
211        continue;  // No stats collected for this line, skip it and continue.
212
213    // Add a line to the result and clear the counts.
214    std::string line;
215    if (show == SHOW_SERVICE) {
216      line += stat->service;
217    } else {
218      // The interface usually includes the service so don't show both.
219      line += stat->interface;
220      if (show >= SHOW_METHOD)
221        line += "." + stat->method;
222    }
223    line += base::StringPrintf(":");
224    if (sent_blocking) {
225      line += base::StringPrintf(" Sent (BLOCKING):");
226      if (format == FORMAT_TOTALS)
227        line += base::StringPrintf(" %d", sent_blocking);
228      else if (format == FORMAT_PER_MINUTE)
229        line += base::StringPrintf(" %d/min", sent_blocking / dminutes);
230      else if (format == FORMAT_ALL)
231        line += base::StringPrintf(" %d (%d/min)",
232                                   sent_blocking, sent_blocking / dminutes);
233    }
234    if (sent) {
235      line += base::StringPrintf(" Sent:");
236      if (format == FORMAT_TOTALS)
237        line += base::StringPrintf(" %d", sent);
238      else if (format == FORMAT_PER_MINUTE)
239        line += base::StringPrintf(" %d/min", sent / dminutes);
240      else if (format == FORMAT_ALL)
241        line += base::StringPrintf(" %d (%d/min)", sent, sent / dminutes);
242    }
243    if (received) {
244      line += base::StringPrintf(" Received:");
245      if (format == FORMAT_TOTALS)
246        line += base::StringPrintf(" %d", received);
247      else if (format == FORMAT_PER_MINUTE)
248        line += base::StringPrintf(" %d/min", received / dminutes);
249      else if (format == FORMAT_ALL)
250        line += base::StringPrintf(
251            " %d (%d/min)", received, received / dminutes);
252    }
253    result += line + "\n";
254    sent = 0;
255    sent_blocking = 0;
256    received = 0;
257  }
258  return result;
259}
260
261namespace testing {
262
263bool GetCalls(const std::string& service,
264              const std::string& interface,
265              const std::string& method,
266              int* sent,
267              int* received,
268              int* blocking) {
269  if (!g_dbus_statistics)
270    return false;
271  Stat* stat = g_dbus_statistics->GetStat(service, interface, method, false);
272  if (!stat)
273    return false;
274  *sent = stat->sent_method_calls;
275  *received = stat->received_signals;
276  *blocking = stat->sent_blocking_method_calls;
277  return true;
278}
279
280}  // namespace testing
281
282}  // namespace statistics
283}  // namespace dbus
284