1 #include "redismodule.h"
2 #include "spec.h"
3 #include "inverted_index.h"
4 #include "cursor.h"
5
6 #define REPLY_KVNUM(n, k, v) \
7 do { \
8 RedisModule_ReplyWithSimpleString(ctx, (k)); \
9 RedisModule_ReplyWithDouble(ctx, (double)(v)); \
10 n += 2; \
11 } while (0)
12
13 #define REPLY_KVSTR(n, k, v) \
14 do { \
15 RedisModule_ReplyWithSimpleString(ctx, (k)); \
16 RedisModule_ReplyWithSimpleString(ctx, (v)); \
17 n += 2; \
18 } while (0)
19
renderIndexOptions(RedisModuleCtx * ctx,IndexSpec * sp)20 static int renderIndexOptions(RedisModuleCtx *ctx, IndexSpec *sp) {
21
22 #define ADD_NEGATIVE_OPTION(flag, str) \
23 do { \
24 if (!(sp->flags & (flag))) { \
25 RedisModule_ReplyWithStringBuffer(ctx, (str), strlen(str)); \
26 n++; \
27 } \
28 } while (0)
29
30 RedisModule_ReplyWithSimpleString(ctx, "index_options");
31 RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN);
32 int n = 0;
33 ADD_NEGATIVE_OPTION(Index_StoreFreqs, SPEC_NOFREQS_STR);
34 ADD_NEGATIVE_OPTION(Index_StoreFieldFlags, SPEC_NOFIELDS_STR);
35 ADD_NEGATIVE_OPTION(Index_StoreTermOffsets, SPEC_NOOFFSETS_STR);
36 if (sp->flags & Index_WideSchema) {
37 RedisModule_ReplyWithSimpleString(ctx, SPEC_SCHEMA_EXPANDABLE_STR);
38 n++;
39 }
40 RedisModule_ReplySetArrayLength(ctx, n);
41 return 2;
42 }
43
renderIndexDefinitions(RedisModuleCtx * ctx,IndexSpec * sp)44 static int renderIndexDefinitions(RedisModuleCtx *ctx, IndexSpec *sp) {
45 int n = 0;
46 SchemaRule *rule = sp->rule;
47 RedisModule_ReplyWithSimpleString(ctx, "index_definition");
48 RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN);
49
50 REPLY_KVSTR(n, "key_type", DocumentType_ToString(rule->type));
51
52 int num_prefixes = array_len(rule->prefixes);
53 if (num_prefixes) {
54 RedisModule_ReplyWithSimpleString(ctx, "prefixes");
55 RedisModule_ReplyWithArray(ctx, num_prefixes);
56 for (int i = 0; i < num_prefixes; ++i) {
57 RedisModule_ReplyWithSimpleString(ctx, rule->prefixes[i]);
58 }
59 n += 2;
60 }
61
62 if (rule->filter_exp_str) {
63 REPLY_KVSTR(n, "filter", rule->filter_exp_str);
64 }
65
66 if (rule->lang_default) {
67 REPLY_KVSTR(n, "default_language", RSLanguage_ToString(rule->lang_default));
68 }
69
70 if (rule->lang_field) {
71 REPLY_KVSTR(n, "language_field", rule->lang_field);
72 }
73
74 if (rule->score_default) {
75 REPLY_KVNUM(n, "default_score", rule->score_default);
76 }
77
78 if (rule->score_field) {
79 REPLY_KVSTR(n, "score_field", rule->score_field);
80 }
81
82 if (rule->payload_field) {
83 REPLY_KVSTR(n, "payload_field", rule->payload_field);
84 }
85
86 RedisModule_ReplySetArrayLength(ctx, n);
87 return 2;
88 }
89
getSpecTypeNames(int idx)90 static const char *getSpecTypeNames(int idx) {
91 switch (idx) {
92 case IXFLDPOS_FULLTEXT: return SPEC_TEXT_STR;
93 case IXFLDPOS_TAG: return SPEC_TAG_STR;
94 case IXFLDPOS_NUMERIC: return NUMERIC_STR;
95 case IXFLDPOS_GEO: return GEO_STR;
96
97 default:
98 RS_LOG_ASSERT(0, "oops");
99 break;
100 }
101 }
102
103 /* FT.INFO {index}
104 * Provide info and stats about an index
105 */
IndexInfoCommand(RedisModuleCtx * ctx,RedisModuleString ** argv,int argc)106 int IndexInfoCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
107 RedisModule_AutoMemory(ctx);
108 if (argc < 2) return RedisModule_WrongArity(ctx);
109
110 IndexSpec *sp = IndexSpec_Load(ctx, RedisModule_StringPtrLen(argv[1], NULL), 1);
111 if (sp == NULL) {
112 return RedisModule_ReplyWithError(ctx, "Unknown Index name");
113 }
114
115 RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN);
116 int n = 0;
117
118 REPLY_KVSTR(n, "index_name", sp->name);
119
120 n += renderIndexOptions(ctx, sp);
121
122 n += renderIndexDefinitions(ctx, sp);
123
124 RedisModule_ReplyWithSimpleString(ctx, "attributes");
125 RedisModule_ReplyWithArray(ctx, sp->numFields);
126 for (int i = 0; i < sp->numFields; i++) {
127 RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN);
128 RedisModule_ReplyWithSimpleString(ctx, "identifier");
129 RedisModule_ReplyWithSimpleString(ctx, sp->fields[i].path);
130 RedisModule_ReplyWithSimpleString(ctx, "attribute");
131 RedisModule_ReplyWithSimpleString(ctx, sp->fields[i].name);
132 int nn = 4;
133 const FieldSpec *fs = sp->fields + i;
134
135 // RediSearch_api - No coverage
136 if (fs->options & FieldSpec_Dynamic) {
137 REPLY_KVSTR(nn, "type", "<DYNAMIC>");
138 size_t ntypes = 0;
139
140 nn += 2;
141 RedisModule_ReplyWithSimpleString(ctx, "types");
142 RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN);
143 for (size_t jj = 0; jj < INDEXFLD_NUM_TYPES; ++jj) {
144 if (FIELD_IS(fs, INDEXTYPE_FROM_POS(jj))) {
145 ntypes++;
146 RedisModule_ReplyWithSimpleString(ctx, getSpecTypeNames(jj));
147 }
148 }
149 RedisModule_ReplySetArrayLength(ctx, ntypes);
150 } else {
151 REPLY_KVSTR(nn, "type", getSpecTypeNames(INDEXTYPE_TO_POS(fs->types)));
152 }
153
154 if (FIELD_IS(fs, INDEXFLD_T_FULLTEXT)) {
155 REPLY_KVNUM(nn, SPEC_WEIGHT_STR, fs->ftWeight);
156 }
157
158 if (FIELD_IS(fs, INDEXFLD_T_TAG)) {
159 char buf[2];
160 sprintf(buf, "%c", fs->tagSep);
161 REPLY_KVSTR(nn, SPEC_TAG_SEPARATOR_STR, buf);
162 }
163 if (FieldSpec_IsSortable(fs)) {
164 RedisModule_ReplyWithSimpleString(ctx, SPEC_SORTABLE_STR);
165 ++nn;
166 }
167 if (FieldSpec_IsNoStem(fs)) {
168 RedisModule_ReplyWithSimpleString(ctx, SPEC_NOSTEM_STR);
169 ++nn;
170 }
171 if (!FieldSpec_IsIndexable(fs)) {
172 RedisModule_ReplyWithSimpleString(ctx, SPEC_NOINDEX_STR);
173 ++nn;
174 }
175 RedisModule_ReplySetArrayLength(ctx, nn);
176 }
177 n += 2;
178
179 REPLY_KVNUM(n, "num_docs", sp->stats.numDocuments);
180 REPLY_KVNUM(n, "max_doc_id", sp->docs.maxDocId);
181 REPLY_KVNUM(n, "num_terms", sp->stats.numTerms);
182 REPLY_KVNUM(n, "num_records", sp->stats.numRecords);
183 REPLY_KVNUM(n, "inverted_sz_mb", sp->stats.invertedSize / (float)0x100000);
184 REPLY_KVNUM(n, "total_inverted_index_blocks", TotalIIBlocks);
185 // REPLY_KVNUM(n, "inverted_cap_mb", sp->stats.invertedCap / (float)0x100000);
186
187 // REPLY_KVNUM(n, "inverted_cap_ovh", 0);
188 //(float)(sp->stats.invertedCap - sp->stats.invertedSize) / (float)sp->stats.invertedCap);
189
190 REPLY_KVNUM(n, "offset_vectors_sz_mb", sp->stats.offsetVecsSize / (float)0x100000);
191 // REPLY_KVNUM(n, "skip_index_size_mb", sp->stats.skipIndexesSize / (float)0x100000);
192 // REPLY_KVNUM(n, "score_index_size_mb", sp->stats.scoreIndexesSize / (float)0x100000);
193
194 REPLY_KVNUM(n, "doc_table_size_mb", sp->docs.memsize / (float)0x100000);
195 REPLY_KVNUM(n, "sortable_values_size_mb", sp->docs.sortablesSize / (float)0x100000);
196
197 REPLY_KVNUM(n, "key_table_size_mb", TrieMap_MemUsage(sp->docs.dim.tm) / (float)0x100000);
198 REPLY_KVNUM(n, "records_per_doc_avg",
199 (float)sp->stats.numRecords / (float)sp->stats.numDocuments);
200 REPLY_KVNUM(n, "bytes_per_record_avg",
201 (float)sp->stats.invertedSize / (float)sp->stats.numRecords);
202 REPLY_KVNUM(n, "offsets_per_term_avg",
203 (float)sp->stats.offsetVecRecords / (float)sp->stats.numRecords);
204 REPLY_KVNUM(n, "offset_bits_per_record_avg",
205 8.0F * (float)sp->stats.offsetVecsSize / (float)sp->stats.offsetVecRecords);
206 REPLY_KVNUM(n, "hash_indexing_failures", sp->stats.indexingFailures);
207
208 REPLY_KVNUM(n, "indexing", !!global_spec_scanner || sp->scan_in_progress);
209
210 double percent_indexed;
211 IndexesScanner *scanner = global_spec_scanner ? global_spec_scanner : sp->scanner;
212 if (scanner || sp->scan_in_progress) {
213 if (scanner) {
214 percent_indexed =
215 scanner->totalKeys > 0 ? (double)scanner->scannedKeys / scanner->totalKeys : 0;
216 } else {
217 percent_indexed = 0;
218 }
219 } else {
220 percent_indexed = 1.0;
221 }
222
223 REPLY_KVNUM(n, "percent_indexed", percent_indexed);
224
225 if (sp->gc) {
226 RedisModule_ReplyWithSimpleString(ctx, "gc_stats");
227 GCContext_RenderStats(sp->gc, ctx);
228 n += 2;
229 }
230
231 Cursors_RenderStats(&RSCursors, sp->name, ctx);
232 n += 2;
233
234 if (sp->flags & Index_HasCustomStopwords) {
235 ReplyWithStopWordsList(ctx, sp->stopwords);
236 n += 2;
237 }
238
239 RedisModule_ReplySetArrayLength(ctx, n);
240 return REDISMODULE_OK;
241 }
242