]>
Commit | Line | Data |
---|---|---|
1 | // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- | |
2 | // vim: ts=8 sw=2 smarttab | |
3 | /* | |
4 | * Ceph distributed storage system | |
5 | * | |
6 | * Copyright (C) 2015 Mirantis Inc | |
7 | * | |
8 | * Author: Mykola Golub <mgolub@mirantis.com> | |
9 | * | |
10 | * This library is free software; you can redistribute it and/or | |
11 | * modify it under the terms of the GNU Lesser General Public | |
12 | * License as published by the Free Software Foundation; either | |
13 | * version 2.1 of the License, or (at your option) any later version. | |
14 | * | |
15 | */ | |
16 | ||
17 | #ifndef CRUSH_TREE_DUMPER_H | |
18 | #define CRUSH_TREE_DUMPER_H | |
19 | ||
20 | #include "CrushWrapper.h" | |
21 | #include "include/stringify.h" | |
22 | ||
23 | /** | |
24 | * CrushTreeDumper: | |
25 | * A helper class and functions to dump a crush tree. | |
26 | * | |
27 | * Example: | |
28 | * | |
29 | * class SimpleDumper : public CrushTreeDumper::Dumper<ostream> { | |
30 | * public: | |
31 | * SimpleDumper(const CrushWrapper *crush) : | |
32 | * CrushTreeDumper::Dumper<ostream>(crush) {} | |
33 | * protected: | |
34 | * virtual void dump_item(const CrushTreeDumper::Item &qi, ostream *out) { | |
35 | * *out << qi.id; | |
36 | * for (int k = 0; k < qi.depth; k++) | |
37 | * *out << "-"; | |
38 | * if (qi.is_bucket()) | |
39 | * *out << crush->get_item_name(qi.id) | |
40 | * else | |
41 | * *out << "osd." << qi.id; | |
42 | * *out << "\n"; | |
43 | * } | |
44 | * }; | |
45 | * | |
46 | * SimpleDumper(crush).dump(out); | |
47 | * | |
48 | */ | |
49 | ||
50 | namespace CrushTreeDumper { | |
51 | ||
52 | struct Item { | |
53 | int id; | |
54 | int parent; | |
55 | int depth; | |
56 | float weight; | |
57 | list<int> children; | |
58 | ||
59 | Item() : id(0), parent(0), depth(0), weight(0) {} | |
60 | Item(int i, int p, int d, float w) : id(i), parent(p), depth(d), weight(w) {} | |
61 | ||
62 | bool is_bucket() const { return id < 0; } | |
63 | }; | |
64 | ||
65 | template <typename F> | |
66 | class Dumper : public list<Item> { | |
67 | public: | |
68 | explicit Dumper(const CrushWrapper *crush_, | |
69 | const name_map_t& weight_set_names_) | |
70 | : crush(crush_), weight_set_names(weight_set_names_) { | |
71 | crush->find_nonshadow_roots(roots); | |
72 | root = roots.begin(); | |
73 | } | |
74 | explicit Dumper(const CrushWrapper *crush_, | |
75 | const name_map_t& weight_set_names_, | |
76 | bool show_shadow) | |
77 | : crush(crush_), weight_set_names(weight_set_names_) { | |
78 | if (show_shadow) { | |
79 | crush->find_roots(roots); | |
80 | } else { | |
81 | crush->find_nonshadow_roots(roots); | |
82 | } | |
83 | root = roots.begin(); | |
84 | } | |
85 | ||
86 | virtual ~Dumper() {} | |
87 | ||
88 | virtual void reset() { | |
89 | root = roots.begin(); | |
90 | touched.clear(); | |
91 | clear(); | |
92 | } | |
93 | ||
94 | virtual bool should_dump_leaf(int i) const { | |
95 | return true; | |
96 | } | |
97 | virtual bool should_dump_empty_bucket() const { | |
98 | return true; | |
99 | } | |
100 | ||
101 | bool should_dump(int id) { | |
102 | if (id >= 0) | |
103 | return should_dump_leaf(id); | |
104 | if (should_dump_empty_bucket()) | |
105 | return true; | |
106 | int s = crush->get_bucket_size(id); | |
107 | for (int k = s - 1; k >= 0; k--) { | |
108 | int c = crush->get_bucket_item(id, k); | |
109 | if (should_dump(c)) | |
110 | return true; | |
111 | } | |
112 | return false; | |
113 | } | |
114 | ||
115 | bool next(Item &qi) { | |
116 | if (empty()) { | |
117 | while (root != roots.end() && !should_dump(*root)) | |
118 | ++root; | |
119 | if (root == roots.end()) | |
120 | return false; | |
121 | push_back(Item(*root, 0, 0, crush->get_bucket_weightf(*root))); | |
122 | ++root; | |
123 | } | |
124 | ||
125 | qi = front(); | |
126 | pop_front(); | |
127 | touched.insert(qi.id); | |
128 | ||
129 | if (qi.is_bucket()) { | |
130 | // queue bucket contents, sorted by (class, name) | |
131 | int s = crush->get_bucket_size(qi.id); | |
132 | map<string,pair<int,float>> sorted; | |
133 | for (int k = s - 1; k >= 0; k--) { | |
134 | int id = crush->get_bucket_item(qi.id, k); | |
135 | if (should_dump(id)) { | |
136 | string sort_by; | |
137 | if (id >= 0) { | |
138 | const char *c = crush->get_item_class(id); | |
139 | sort_by = c ? c : ""; | |
140 | sort_by += "_"; | |
141 | char nn[80]; | |
142 | snprintf(nn, sizeof(nn), "osd.%08d", id); | |
143 | sort_by += nn; | |
144 | } else { | |
145 | sort_by = "_"; | |
146 | sort_by += crush->get_item_name(id); | |
147 | } | |
148 | sorted[sort_by] = make_pair( | |
149 | id, crush->get_bucket_item_weightf(qi.id, k)); | |
150 | } | |
151 | } | |
152 | for (auto p = sorted.rbegin(); p != sorted.rend(); ++p) { | |
153 | qi.children.push_back(p->second.first); | |
154 | push_front(Item(p->second.first, qi.id, qi.depth + 1, | |
155 | p->second.second)); | |
156 | } | |
157 | } | |
158 | return true; | |
159 | } | |
160 | ||
161 | void dump(F *f) { | |
162 | reset(); | |
163 | Item qi; | |
164 | while (next(qi)) | |
165 | dump_item(qi, f); | |
166 | } | |
167 | ||
168 | bool is_touched(int id) const { return touched.count(id) > 0; } | |
169 | ||
170 | protected: | |
171 | virtual void dump_item(const Item &qi, F *f) = 0; | |
172 | ||
173 | protected: | |
174 | const CrushWrapper *crush; | |
175 | const name_map_t &weight_set_names; | |
176 | ||
177 | private: | |
178 | set<int> touched; | |
179 | set<int> roots; | |
180 | set<int>::iterator root; | |
181 | }; | |
182 | ||
183 | inline void dump_item_fields(const CrushWrapper *crush, | |
184 | const name_map_t& weight_set_names, | |
185 | const Item &qi, Formatter *f) { | |
186 | f->dump_int("id", qi.id); | |
187 | const char *c = crush->get_item_class(qi.id); | |
188 | if (c) | |
189 | f->dump_string("device_class", c); | |
190 | if (qi.is_bucket()) { | |
191 | int type = crush->get_bucket_type(qi.id); | |
192 | f->dump_string("name", crush->get_item_name(qi.id)); | |
193 | f->dump_string("type", crush->get_type_name(type)); | |
194 | f->dump_int("type_id", type); | |
195 | } else { | |
196 | f->dump_stream("name") << "osd." << qi.id; | |
197 | f->dump_string("type", crush->get_type_name(0)); | |
198 | f->dump_int("type_id", 0); | |
199 | f->dump_float("crush_weight", qi.weight); | |
200 | f->dump_unsigned("depth", qi.depth); | |
201 | } | |
202 | if (qi.parent < 0) { | |
203 | f->open_object_section("pool_weights"); | |
204 | for (auto& p : crush->choose_args) { | |
205 | const crush_choose_arg_map& cmap = p.second; | |
206 | int bidx = -1 - qi.parent; | |
207 | const crush_bucket *b = crush->get_bucket(qi.parent); | |
208 | if (b && | |
209 | bidx < (int)cmap.size && | |
210 | cmap.args[bidx].weight_set && | |
211 | cmap.args[bidx].weight_set_size >= 1) { | |
212 | int bpos; | |
213 | for (bpos = 0; | |
214 | bpos < (int)cmap.args[bidx].weight_set[0].size && | |
215 | b->items[bpos] != qi.id; | |
216 | ++bpos) ; | |
217 | string name; | |
218 | if (p.first == CrushWrapper::DEFAULT_CHOOSE_ARGS) { | |
219 | name = "(compat)"; | |
220 | } else { | |
221 | auto q = weight_set_names.find(p.first); | |
222 | name = q != weight_set_names.end() ? q->second : | |
223 | stringify(p.first); | |
224 | } | |
225 | f->open_array_section(name.c_str()); | |
226 | for (unsigned opos = 0; | |
227 | opos < cmap.args[bidx].weight_set_size; | |
228 | ++opos) { | |
229 | float w = (float)cmap.args[bidx].weight_set[opos].weights[bpos] / | |
230 | (float)0x10000; | |
231 | f->dump_float("weight", w); | |
232 | } | |
233 | f->close_section(); | |
234 | } | |
235 | } | |
236 | f->close_section(); | |
237 | } | |
238 | } | |
239 | ||
240 | inline void dump_bucket_children(const CrushWrapper *crush, | |
241 | const Item &qi, Formatter *f) { | |
242 | if (!qi.is_bucket()) | |
243 | return; | |
244 | ||
245 | f->open_array_section("children"); | |
246 | for (list<int>::const_iterator i = qi.children.begin(); | |
247 | i != qi.children.end(); | |
248 | ++i) { | |
249 | f->dump_int("child", *i); | |
250 | } | |
251 | f->close_section(); | |
252 | } | |
253 | ||
254 | class FormattingDumper : public Dumper<Formatter> { | |
255 | public: | |
256 | explicit FormattingDumper(const CrushWrapper *crush, | |
257 | const name_map_t& weight_set_names) | |
258 | : Dumper<Formatter>(crush, weight_set_names) {} | |
259 | explicit FormattingDumper(const CrushWrapper *crush, | |
260 | const name_map_t& weight_set_names, | |
261 | bool show_shadow) | |
262 | : Dumper<Formatter>(crush, weight_set_names, show_shadow) {} | |
263 | ||
264 | protected: | |
265 | void dump_item(const Item &qi, Formatter *f) override { | |
266 | f->open_object_section("item"); | |
267 | dump_item_fields(qi, f); | |
268 | dump_bucket_children(qi, f); | |
269 | f->close_section(); | |
270 | } | |
271 | ||
272 | virtual void dump_item_fields(const Item &qi, Formatter *f) { | |
273 | CrushTreeDumper::dump_item_fields(crush, weight_set_names, qi, f); | |
274 | } | |
275 | ||
276 | virtual void dump_bucket_children(const Item &qi, Formatter *f) { | |
277 | CrushTreeDumper::dump_bucket_children(crush, qi, f); | |
278 | } | |
279 | }; | |
280 | ||
281 | } | |
282 | ||
283 | #endif |