1 // Copyright 2018 The Fuchsia 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 <errno.h>
6 #include <fcntl.h>
7 #include <getopt.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <unistd.h>
12
13 #include <fbl/string_buffer.h>
14 #include <fbl/unique_fd.h>
15 #include <fuchsia/io/c/fidl.h>
16 #include <fuchsia/minfs/c/fidl.h>
17 #include <lib/fzl/fdio.h>
18 #include <zircon/device/block.h>
19 #include <zircon/device/device.h>
20 #include <zircon/types.h>
21
22 #include <utility>
23
24 namespace {
25
26 using MinfsMetrics = fuchsia_minfs_Metrics;
27
Usage()28 int Usage() {
29 fprintf(stdout, "usage: storage-metrics [ <option>* ] [paths]\n");
30 fprintf(stdout, " storage-metrics reports metrics for storage components (block"
31 " devices and filesystems). It is currently limited to minfs\n");
32 fprintf(stdout, " --clear : clears metrics on block devices supporting paths\n");
33 fprintf(stdout, " --enable_metrics=[true|false] : enables or disables metrics"
34 " for the filesystems supporting path\n");
35 fprintf(stdout, " --help : Show this help message\n");
36 return -1;
37 }
38
39 // Type to track whether whether a boolean flag without a default value has been set
40 enum class BooleanFlagState { kUnset,
41 kEnable,
42 kDisable };
43
44 struct StorageMetricOptions {
45 // True indicates that a call to retrieve block device metrics should also clear those metrics.
46 bool clear_block = false;
47 // Value passed to a filesystem toggle metrics request.
48 BooleanFlagState enable_fs_metrics = BooleanFlagState::kUnset;
49 };
50
PrintFsMetrics(const MinfsMetrics & metrics,const char * path)51 void PrintFsMetrics(const MinfsMetrics& metrics, const char* path) {
52 printf("Filesystem Metrics for: %s\n", path);
53 printf("General IO metrics\n");
54 printf("create calls: %lu\n", metrics.create_calls);
55 printf("successful create calls: %lu\n", metrics.create_calls_success);
56 printf("create nanoseconds: %lu\n", metrics.create_ticks);
57 printf("\n");
58
59 printf("read calls: %lu\n", metrics.read_calls);
60 printf("bytes read: %lu\n", metrics.read_size);
61 printf("read nanoseconds: %lu\n", metrics.read_ticks);
62 printf("\n");
63
64 printf("write calls: %lu\n", metrics.write_calls);
65 printf("bytes written: %lu\n", metrics.write_size);
66 printf("write nanoseconds: %lu\n", metrics.write_ticks);
67 printf("\n");
68
69 printf("truncate calls: %lu\n", metrics.truncate_calls);
70 printf("truncate nanoseconds: %lu\n", metrics.truncate_ticks);
71 printf("\n");
72
73 printf("unlink calls: %lu\n", metrics.unlink_calls);
74 printf("successful unlink calls: %lu\n", metrics.unlink_calls_success);
75 printf("unlink nanoseconds: %lu\n", metrics.unlink_ticks);
76 printf("\n");
77
78 printf("rename calls: %lu\n", metrics.rename_calls);
79 printf("successful rename calls: %lu\n", metrics.rename_calls_success);
80 printf("rename nanoseconds: %lu\n", metrics.rename_ticks);
81 printf("\n");
82
83 printf("Vnode initialization metrics\n");
84 printf("initialized VMOs: %lu\n", metrics.initialized_vmos);
85 printf("initialized direct blocks: %u\n", metrics.init_dnum_count);
86 printf("initialized indirect blocks: %u\n", metrics.init_inum_count);
87 printf("initialized doubly indirect blocks: %u\n", metrics.init_dinum_count);
88 printf("bytes of files initialized: %lu\n", metrics.init_user_data_size);
89 printf("ticks during initialization: %lu\n", metrics.init_user_data_ticks);
90 printf("\n");
91
92 printf("Internal vnode open metrics\n");
93 printf("vnodes opened: %lu\n", metrics.vnodes_opened);
94 printf("vnodes open cache hits: %lu\n", metrics.vnodes_opened_cache_hit);
95 printf("vnode open nanoseconds: %lu\n", metrics.vnode_open_ticks);
96 printf("\n");
97
98 printf("Internal vnode lookup metrics\n");
99 printf("lookup calls: %lu\n", metrics.lookup_calls);
100 printf("successful lookup calls: %lu\n", metrics.lookup_calls_success);
101 printf("lookup nanoseconds: %lu\n", metrics.lookup_ticks);
102 }
103
104 // Sends a FIDL call to enable or disable filesystem metrics for path
EnableFsMetrics(const char * path,bool enable)105 zx_status_t EnableFsMetrics(const char* path, bool enable) {
106 fbl::unique_fd fd(open(path, O_RDONLY | O_DIRECTORY));
107 if (!fd) {
108 fprintf(stderr, "Error opening %s, errno %d (%s)\n", path, errno, strerror(errno));
109 return ZX_ERR_IO;
110 }
111
112 zx_status_t status;
113 fzl::FdioCaller caller(std::move(fd));
114 zx_status_t rc = fuchsia_minfs_MinfsToggleMetrics(caller.borrow_channel(), enable, &status);
115 if (rc != ZX_OK || status != ZX_OK) {
116 fprintf(stderr, "Error toggling metrics for %s, status %d\n",
117 path, (rc != ZX_OK) ? rc : status);
118 return (rc != ZX_OK) ? rc : status;
119 }
120 return status;
121 }
122
123 // Retrieves the Filesystem metrics for path. Only supports Minfs.
GetFsMetrics(const char * path,MinfsMetrics * out_metrics)124 zx_status_t GetFsMetrics(const char* path, MinfsMetrics* out_metrics) {
125 fbl::unique_fd fd(open(path, O_RDONLY | O_DIRECTORY));
126 if (!fd) {
127 fprintf(stderr, "Error opening %s, errno %d (%s)\n", path, errno, strerror(errno));
128 return ZX_ERR_IO;
129 }
130
131 zx_status_t status;
132 fzl::FdioCaller caller(std::move(fd));
133 zx_status_t rc = fuchsia_minfs_MinfsGetMetrics(caller.borrow_channel(), &status, out_metrics);
134 if (status == ZX_ERR_UNAVAILABLE) {
135 fprintf(stderr, "Metrics Unavailable for %s\n", path);
136 return status;
137 } else if (rc != ZX_OK || status != ZX_OK) {
138 fprintf(stderr, "Error getting metrics for %s, status %d\n",
139 path, (rc != ZX_OK) ? rc : status);
140 return (rc != ZX_OK) ? rc : status;
141 }
142 return status;
143 }
144
PrintBlockMetrics(const char * dev,const block_stats_t & stats)145 void PrintBlockMetrics(const char* dev, const block_stats_t& stats) {
146 printf(R"(
147 Block Metrics for device path: %s
148 total submitted block ops: %zu
149 total submitted blocks: %zu
150 total submitted read ops: %zu
151 total submitted blocks read: %zu
152 total submitted write ops: %zu
153 total submitted blocks written: %zu
154 )",
155 dev, stats.total_ops, stats.total_blocks, stats.total_reads,
156 stats.total_blocks_read, stats.total_writes, stats.total_blocks_written);
157 }
158
159 // Retrieves metrics for the block device at dev. Clears metrics if clear is true.
GetBlockMetrics(const char * dev,bool clear,block_stats_t * stats)160 zx_status_t GetBlockMetrics(const char* dev, bool clear, block_stats_t* stats) {
161 fbl::unique_fd fd(open(dev, O_RDONLY));
162 if (!fd) {
163 fprintf(stderr, "Error opening %s, errno %d (%s)\n", dev, errno, strerror(errno));
164 return ZX_ERR_IO;
165 }
166 ssize_t rc = ioctl_block_get_stats(fd.get(), &clear, stats);
167 if (rc < 0) {
168 fprintf(stderr, "Error getting stats for %s\n", dev);
169 return static_cast<zx_status_t>(rc);
170 }
171 return ZX_OK;
172 }
173
ParseCommandLineArguments(int argc,char ** argv,StorageMetricOptions * options)174 void ParseCommandLineArguments(int argc, char** argv, StorageMetricOptions* options) {
175 static const struct option opts[] = {
176 {"clear", optional_argument, NULL, 'c'},
177 {"enable_metrics", optional_argument, NULL, 'e'},
178 {"help", no_argument, NULL, 'h'},
179 {0, 0, 0, 0},
180 };
181 for (int opt; (opt = getopt_long(argc, argv, "c::e::h", opts, nullptr)) != -1;) {
182 switch (opt) {
183 case 'c':
184 options->clear_block = (optarg == nullptr || strcmp(optarg, "true") == 0);
185 break;
186 case 'e':
187 options->enable_fs_metrics = (optarg == nullptr || strcmp(optarg, "true") == 0)
188 ? BooleanFlagState::kEnable
189 : BooleanFlagState::kDisable;
190 break;
191 case 'h':
192 __FALLTHROUGH;
193 default:
194 Usage();
195 }
196 }
197 }
198
199 // Retrieves filesystem metrics for the filesystem at path and prints them.
RunFsMetrics(const fbl::StringBuffer<PATH_MAX> path,const StorageMetricOptions options)200 void RunFsMetrics(const fbl::StringBuffer<PATH_MAX> path, const StorageMetricOptions options) {
201 fbl::unique_fd fd(open(path.c_str(), O_RDONLY | O_ADMIN));
202 if (!fd) {
203 fd.reset(open(path.c_str(), O_RDONLY));
204 if (!fd) {
205 fprintf(stderr, "storage-metrics could not open target: %s, errno %d (%s)\n",
206 path.c_str(), errno, strerror(errno));
207 return;
208 }
209 }
210
211 fuchsia_io_FilesystemInfo info;
212 zx_status_t status;
213 fzl::FdioCaller caller(std::move(fd));
214 zx_status_t io_status = fuchsia_io_DirectoryAdminQueryFilesystem(caller.borrow_channel(),
215 &status, &info);
216 if (io_status != ZX_OK || status != ZX_OK) {
217 fprintf(stderr, "storage-metrics could not open %s, status %d\n",
218 path.c_str(), (io_status != ZX_OK) ? io_status : status);
219 return;
220 }
221
222 // Skip any filesystems that aren't minfs
223 info.name[fuchsia_io_MAX_FS_NAME_BUFFER - 1] = '\0';
224 const char* name = reinterpret_cast<const char*>(info.name);
225 if (strcmp(name, "minfs") != 0) {
226 fprintf(stderr, "storage-metrics does not support filesystem type %s\n", name);
227 return;
228 }
229
230 zx_status_t rc;
231 // The order of these conditionals allows for stats to be output regardless of the
232 // value of enable.
233 if (options.enable_fs_metrics == BooleanFlagState::kEnable) {
234 rc = EnableFsMetrics(path.c_str(), true);
235 if (rc != ZX_OK) {
236 fprintf(stderr, "storage-metrics could not enable filesystem metrics for %s,"
237 " status %d\n",
238 path.c_str(), rc);
239 return;
240 }
241 }
242 MinfsMetrics metrics;
243 rc = GetFsMetrics(path.c_str(), &metrics);
244 if (rc == ZX_OK) {
245 PrintFsMetrics(metrics, path.c_str());
246 } else {
247 fprintf(stderr, "storage-metrics could not get filesystem metrics for %s,"
248 " status %d\n",
249 path.c_str(), rc);
250 return;
251 }
252 if (options.enable_fs_metrics == BooleanFlagState::kDisable) {
253 rc = EnableFsMetrics(path.c_str(), false);
254 if (rc != ZX_OK) {
255 fprintf(stderr, "storage-metrics could not disable filesystem metrics for %s,"
256 " status %d\n",
257 path.c_str(), rc);
258 }
259 }
260 }
261
262 // Retrieves and prints metrics for the block device associated with the filesystem at path.
RunBlockMetrics(const fbl::StringBuffer<PATH_MAX> path,const StorageMetricOptions options)263 void RunBlockMetrics(const fbl::StringBuffer<PATH_MAX> path, const StorageMetricOptions options) {
264 fbl::unique_fd fd(open(path.c_str(), O_RDONLY | O_ADMIN));
265 if (!fd) {
266 fd.reset(open(path.c_str(), O_RDONLY));
267 if (!fd) {
268 fprintf(stderr, "storage-metrics could not open target: %s, errno %d (%s)\n",
269 path.c_str(), errno, strerror(errno));
270 return;
271 }
272 }
273
274 char device_buffer[1024];
275 size_t path_len;
276 zx_status_t status;
277 fzl::FdioCaller caller(std::move(fd));
278 zx_status_t io_status = fuchsia_io_DirectoryAdminGetDevicePath(caller.borrow_channel(), &status,
279 device_buffer,
280 sizeof(device_buffer) - 1,
281 &path_len);
282 const char* device_path = nullptr;
283 if (io_status == ZX_OK && status == ZX_OK) {
284 device_buffer[path_len] = '\0';
285 device_path = device_buffer;
286 }
287
288 zx_status_t rc;
289 block_stats_t stats;
290 if (device_path != nullptr) {
291 rc = GetBlockMetrics(device_path, options.clear_block, &stats);
292 if (rc == ZX_OK) {
293 PrintBlockMetrics(device_path, stats);
294 } else {
295 fprintf(stderr, "storage-metrics could not retrieve block metrics for %s,"
296 " status %d\n",
297 path.c_str(), rc);
298 }
299 } else {
300 fprintf(stderr, "storage-metrics could not get the block device for %s\n",
301 path.c_str());
302 }
303 }
304
305 } //namespace
306
main(int argc,char ** argv)307 int main(int argc, char** argv) {
308 StorageMetricOptions options;
309 ParseCommandLineArguments(argc, argv, &options);
310 // Iterate through the remaining arguments, which are all paths
311 for (int i = optind; i < argc; i++) {
312 fbl::StringBuffer<PATH_MAX> path;
313 path.Append(argv[i]);
314
315 printf("Metrics for: %s\n", path.c_str());
316 RunFsMetrics(path, options);
317 RunBlockMetrics(path, options);
318 printf("\n");
319 }
320
321 return 0;
322 }
323