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