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