@@ -279,7 +279,7 @@ struct HufHist {
279
279
}
280
280
};
281
281
282
- void DoComputeHist (optional< CompactObjType> type, EngineShard* shard, ConnectionContext* cntx,
282
+ void DoComputeHist (CompactObjType type, EngineShard* shard, ConnectionContext* cntx,
283
283
HufHist* dest) {
284
284
auto & db_slice = cntx->ns ->GetDbSlice (shard->shard_id ());
285
285
DbTable* dbt = db_slice.GetDBTable (cntx->db_index ());
@@ -294,26 +294,26 @@ void DoComputeHist(optional<CompactObjType> type, EngineShard* shard, Connection
294
294
do {
295
295
cursor = table.Traverse (cursor, [&](PrimeIterator it) {
296
296
scratch.clear ();
297
- if (! type) {
297
+ if (type == kInvalidCompactObjType ) { // KEYSPACE
298
298
it->first .GetString (&scratch);
299
- } else if (* type == OBJ_STRING && it->second .ObjType () == OBJ_STRING) {
299
+ } else if (type == OBJ_STRING && it->second .ObjType () == OBJ_STRING) {
300
300
it->second .GetString (&scratch);
301
- } else if (* type == OBJ_ZSET && it->second .ObjType () == OBJ_ZSET) {
301
+ } else if (type == OBJ_ZSET && it->second .ObjType () == OBJ_ZSET) {
302
302
container_utils::IterateSortedSet (
303
303
it->second .GetRobjWrapper (), [&](container_utils::ContainerEntry entry, double ) {
304
304
if (entry.value ) {
305
305
HIST_add (dest->hist .data (), entry.value , entry.length );
306
306
}
307
307
return true ;
308
308
});
309
- } else if (* type == OBJ_LIST && it->second .ObjType () == OBJ_LIST) {
309
+ } else if (type == OBJ_LIST && it->second .ObjType () == OBJ_LIST) {
310
310
container_utils::IterateList (it->second , [&](container_utils::ContainerEntry entry) {
311
311
if (entry.value ) {
312
312
HIST_add (dest->hist .data (), entry.value , entry.length );
313
313
}
314
314
return true ;
315
315
});
316
- } else if (* type == OBJ_HASH && it->second .ObjType () == OBJ_HASH) {
316
+ } else if (type == OBJ_HASH && it->second .ObjType () == OBJ_HASH) {
317
317
container_utils::IterateMap (it->second , [&](container_utils::ContainerEntry key,
318
318
container_utils::ContainerEntry value) {
319
319
if (key.value ) {
@@ -596,9 +596,11 @@ void DebugCmd::Run(CmdArgList args, facade::SinkReplyBuilder* builder) {
596
596
" traffic logging is stopped." ,
597
597
" RECVSIZE [<tid> | ENABLE | DISABLE]" ,
598
598
" Prints the histogram of the received request sizes on the given thread" ,
599
- " COMPRESSION [type]"
599
+ " COMPRESSION [IMPORT <bintable> | EXPORT] [ type]" ,
600
600
" Estimate the compressibility of values of the given type. if no type is given, " ,
601
- " checks compressibility of keys" ,
601
+ " checks compressibility of keys. If IN is specified, then the provided " ,
602
+ " bintable is used to check compressibility. If OUT is specified, then " ,
603
+ " the serialized table is printed as well" ,
602
604
" IOSTATS [PS]" ,
603
605
" Prints IO stats per thread. If PS is specified, prints thread-level stats " ,
604
606
" per second." ,
@@ -1281,14 +1283,29 @@ void DebugCmd::Keys(CmdArgList args, facade::SinkReplyBuilder* builder) {
1281
1283
}
1282
1284
1283
1285
void DebugCmd::Compression (CmdArgList args, facade::SinkReplyBuilder* builder) {
1284
- optional<CompactObjType> type;
1285
- if (args.size () > 0 ) {
1286
- string_view type_str = ArgS (args, 0 );
1286
+ CompactObjType type = kInvalidCompactObjType ;
1287
+ CmdArgParser parser (args);
1288
+ string bintable;
1289
+ bool print_bintable = false ;
1290
+
1291
+ if (parser.Check (" EXPORT" )) {
1292
+ print_bintable = true ;
1293
+ } else {
1294
+ parser.Check (" IMPORT" , &bintable);
1295
+ }
1296
+
1297
+ if (parser.HasNext ()) {
1298
+ string_view type_str = parser.Next ();
1287
1299
type = ObjTypeFromString (type_str);
1288
- if (! type) {
1300
+ if (type == kInvalidCompactObjType ) {
1289
1301
return builder->SendError (kSyntaxErr );
1290
1302
}
1291
1303
}
1304
+
1305
+ if (parser.HasError ()) {
1306
+ return builder->SendError (parser.Error ()->MakeReply ());
1307
+ }
1308
+
1292
1309
auto * rb = static_cast <RedisReplyBuilder*>(builder);
1293
1310
1294
1311
fb2::Mutex mu;
@@ -1300,26 +1317,72 @@ void DebugCmd::Compression(CmdArgList args, facade::SinkReplyBuilder* builder) {
1300
1317
hist.Merge (local);
1301
1318
});
1302
1319
1303
- HUF_CREATE_STATIC_CTABLE (huf_ctable, HufHist::kMaxSymbol );
1304
-
1305
1320
size_t num_bits = 0 , compressed_size = 0 , raw_size = 0 ;
1321
+ unsigned table_max_symbol = 255 ;
1306
1322
1307
1323
if (hist.max_symbol ) {
1324
+ HUF_CREATE_STATIC_CTABLE (huf_ctable, HufHist::kMaxSymbol );
1325
+
1308
1326
unique_ptr<uint32_t []> wrkspace (new uint32_t [HUF_CTABLE_WORKSPACE_SIZE_U32]);
1309
1327
constexpr size_t kWspSize = HUF_CTABLE_WORKSPACE_SIZE;
1310
- num_bits = HUF_buildCTable_wksp (huf_ctable, hist.hist .data (), hist.max_symbol , 0 ,
1311
- wrkspace.get (), kWspSize );
1312
1328
1313
- compressed_size = HUF_estimateCompressedSize (huf_ctable, hist.hist .data (), hist.max_symbol );
1329
+ if (bintable.empty ()) {
1330
+ table_max_symbol = hist.max_symbol ;
1331
+ num_bits = HUF_buildCTable_wksp (huf_ctable, hist.hist .data (), table_max_symbol, 0 ,
1332
+ wrkspace.get (), kWspSize );
1333
+ if (HUF_isError (num_bits)) {
1334
+ return rb->SendError (StrCat (" Internal error: " , HUF_getErrorName (num_bits)));
1335
+ }
1336
+ } else {
1337
+ // Try to read the bintable and create a ctable from it.
1338
+ unsigned has_zero_weights = 1 ;
1339
+
1340
+ size_t read_size = HUF_readCTable (huf_ctable, &table_max_symbol, bintable.data (),
1341
+ bintable.size (), &has_zero_weights);
1342
+ if (HUF_isError (read_size)) {
1343
+ return rb->SendError (StrCat (" Internal error: " , HUF_getErrorName (read_size)));
1344
+ }
1345
+ if (read_size != bintable.size ()) {
1346
+ return rb->SendError (" Invalid bintable" );
1347
+ }
1348
+ }
1349
+
1350
+ compressed_size = HUF_estimateCompressedSize (huf_ctable, hist.hist .data (), table_max_symbol);
1351
+ for (unsigned i = table_max_symbol + 1 ; i <= hist.max_symbol ; i++) {
1352
+ compressed_size += hist.hist [i];
1353
+ }
1314
1354
raw_size = 0 ;
1315
- for (unsigned i = 0 ; i < hist.max_symbol ; i++) {
1355
+ for (unsigned i = 0 ; i <= hist.max_symbol ; i++) {
1316
1356
raw_size += hist.hist [i];
1317
1357
}
1358
+
1359
+ if (print_bintable) {
1360
+ // Reverse engineered: (maxSymbolValue + 1) / 2 + 1.
1361
+ constexpr unsigned kMaxTableSize = 130 ;
1362
+ bintable.resize (kMaxTableSize );
1363
+
1364
+ // Seems we can reuse the same workspace, its capacity is enough.
1365
+ size_t res = HUF_writeCTable_wksp (bintable.data (), kMaxTableSize , huf_ctable,
1366
+ table_max_symbol, num_bits, wrkspace.get (), kWspSize );
1367
+ if (HUF_isError (res)) {
1368
+ return rb->SendError (StrCat (" Internal error: " , HUF_getErrorName (res)));
1369
+ }
1370
+ bintable.resize (res);
1371
+ } else {
1372
+ bintable.clear ();
1373
+ }
1318
1374
}
1319
1375
1320
- rb->StartCollection (5 , RedisReplyBuilder::CollectionType::MAP);
1376
+ unsigned map_len = print_bintable ? 7 : 6 ;
1377
+
1378
+ rb->StartCollection (map_len, RedisReplyBuilder::CollectionType::MAP);
1321
1379
rb->SendSimpleString (" max_symbol" );
1322
1380
rb->SendLong (hist.max_symbol );
1381
+
1382
+ // in case we load a bintable, table_max_symbol may be different from max_symbol.
1383
+ // if it's smaller, it means our table can not encode all symbols.
1384
+ rb->SendSimpleString (" table_max_symbol" );
1385
+ rb->SendLong (table_max_symbol);
1323
1386
rb->SendSimpleString (" max_bits" );
1324
1387
rb->SendLong (num_bits);
1325
1388
rb->SendSimpleString (" raw_size" );
@@ -1329,6 +1392,10 @@ void DebugCmd::Compression(CmdArgList args, facade::SinkReplyBuilder* builder) {
1329
1392
rb->SendSimpleString (" ratio" );
1330
1393
double ratio = raw_size > 0 ? static_cast <double >(compressed_size) / raw_size : 0 ;
1331
1394
rb->SendDouble (ratio);
1395
+ if (print_bintable) {
1396
+ rb->SendSimpleString (" bintable" );
1397
+ rb->SendBulkString (bintable);
1398
+ }
1332
1399
}
1333
1400
1334
1401
void DebugCmd::IOStats (CmdArgList args, facade::SinkReplyBuilder* builder) {
0 commit comments