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