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