github.com/siglens/siglens@v0.0.0-20240328180423-f7ce9ae441ed/pkg/segment/reader/segread/timereader.go (about) 1 /* 2 Copyright 2023. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package segread 18 19 import ( 20 "errors" 21 "fmt" 22 "io" 23 "os" 24 "sort" 25 "sync" 26 27 "github.com/cespare/xxhash" 28 "github.com/siglens/siglens/pkg/blob" 29 "github.com/siglens/siglens/pkg/config" 30 "github.com/siglens/siglens/pkg/segment/structs" 31 "github.com/siglens/siglens/pkg/segment/utils" 32 toputils "github.com/siglens/siglens/pkg/utils" 33 log "github.com/sirupsen/logrus" 34 ) 35 36 var rawTimestampsBufferPool = sync.Pool{ 37 New: func() interface{} { 38 // The Pool's New function should generally only return pointer 39 // types, since a pointer can be put into the return interface 40 // value without an allocation: 41 slice := make([]uint64, utils.DEFAULT_TIME_SLICE_SIZE) 42 return &slice 43 }, 44 } 45 46 type timeBlockRequest struct { 47 tsRec []byte 48 blkNum uint16 49 numRecs uint16 50 } 51 52 type TimeRangeReader struct { 53 timeFD *os.File 54 timestampKey string 55 timeMetadata map[uint16]*structs.BlockMetadataHolder 56 blockRecCount map[uint16]uint16 57 58 loadedBlockNum uint16 59 blockTimestamps []uint64 60 blockReadBuffer []byte 61 blockUncompressedBuffer []byte // raw buffer to re-use for decompressing 62 numBlockReadTimestamps uint16 63 loadedBlock bool 64 allInUseFiles []string 65 } 66 67 // returns a new TimeRangeReader and any errors encountered 68 // the caller is responsible for calling TimeRangeReader.Close() when finished using it to close the fd 69 func InitNewTimeReader(segKey string, tsKey string, blockMetadata map[uint16]*structs.BlockMetadataHolder, 70 blkRecCount map[uint16]uint16, qid uint64) (*TimeRangeReader, error) { 71 allInUseFiles := make([]string, 0) 72 var err error 73 fName := fmt.Sprintf("%v_%v.csg", segKey, xxhash.Sum64String(tsKey)) 74 if tsKey != "" { 75 err = blob.DownloadSegmentBlob(fName, true) 76 } else { 77 err = fmt.Errorf("InitNewTimeReader: failed to download segsetfile due to unknown segset col %+v", fName) 78 } 79 if err != nil { 80 log.Errorf("qid=%d, InitNewTimeReader failed to download file. %+v, err=%v", qid, fName, err) 81 return nil, err 82 } 83 fd, err := os.OpenFile(fName, os.O_RDONLY, 0644) 84 if err != nil { 85 log.Errorf("qid=%d, InitNewSegFileReader: failed to open time column file %s. Error: %+v", qid, fName, err) 86 return &TimeRangeReader{}, err 87 } 88 allInUseFiles = append(allInUseFiles, fName) 89 90 return &TimeRangeReader{ 91 timeFD: fd, 92 timestampKey: tsKey, 93 timeMetadata: blockMetadata, 94 blockRecCount: blkRecCount, 95 blockTimestamps: *rawTimestampsBufferPool.Get().(*[]uint64), 96 blockReadBuffer: *fileReadBufferPool.Get().(*[]byte), 97 blockUncompressedBuffer: *uncompressedReadBufferPool.Get().(*[]byte), 98 loadedBlock: false, 99 allInUseFiles: allInUseFiles, 100 }, nil 101 } 102 103 func InitNewTimeReaderWithFD(tsFD *os.File, tsKey string, blockMetadata map[uint16]*structs.BlockMetadataHolder, 104 blkRecCount map[uint16]uint16, qid uint64) (*TimeRangeReader, error) { 105 106 return &TimeRangeReader{ 107 timeFD: tsFD, 108 timestampKey: tsKey, 109 timeMetadata: blockMetadata, 110 blockRecCount: blkRecCount, 111 blockTimestamps: *rawTimestampsBufferPool.Get().(*[]uint64), 112 blockReadBuffer: *fileReadBufferPool.Get().(*[]byte), 113 blockUncompressedBuffer: *uncompressedReadBufferPool.Get().(*[]byte), 114 loadedBlock: false, 115 }, nil 116 } 117 118 func InitNewTimeReaderFromBlockSummaries(segKey string, tsKey string, blockMetadata map[uint16]*structs.BlockMetadataHolder, 119 blockSummaries []*structs.BlockSummary, qid uint64) (*TimeRangeReader, error) { 120 121 blkRecCount := make(map[uint16]uint16) 122 for blkIdx, blkSum := range blockSummaries { 123 blkRecCount[uint16(blkIdx)] = blkSum.RecCount 124 } 125 return InitNewTimeReader(segKey, tsKey, blockMetadata, blkRecCount, qid) 126 } 127 128 // highly optimized for subsequent calls to handle the same blockNum 129 func (trr *TimeRangeReader) GetTimeStampForRecord(blockNum uint16, recordNum uint16, qid uint64) (uint64, error) { 130 if !trr.loadedBlock || trr.loadedBlockNum != blockNum { 131 err := trr.readAllTimestampsForBlock(blockNum) 132 if err != nil { 133 return 0, err 134 } 135 } 136 137 if recordNum >= uint16(trr.numBlockReadTimestamps) { 138 log.Errorf("qid=%v, GetTimeStampForRecord: failed to get timestamp for recNum %d blkNum %d. Number of read timestamps is %d", 139 qid, recordNum, blockNum, trr.numBlockReadTimestamps) 140 return 0, errors.New("record number is out of range") 141 } 142 return trr.blockTimestamps[recordNum], nil 143 } 144 145 func (trr *TimeRangeReader) GetAllTimeStampsForBlock(blockNum uint16) ([]uint64, error) { 146 if !trr.loadedBlock || trr.loadedBlockNum != blockNum { 147 err := trr.readAllTimestampsForBlock(blockNum) 148 if err != nil { 149 return nil, err 150 } 151 } 152 153 return trr.blockTimestamps[:trr.numBlockReadTimestamps], nil 154 } 155 156 func (trr *TimeRangeReader) readAllTimestampsForBlock(blockNum uint16) error { 157 158 err := trr.resizeSliceForBlock(blockNum) 159 if err != nil { 160 log.Errorf("readAllTimestampsForBlock: failed to resize internal slice for block %d. Err: %+v", blockNum, err) 161 return errors.New("requested blockNum does not exist") 162 } 163 164 blockMeta, ok := trr.timeMetadata[blockNum] 165 if !ok { 166 log.Errorf("readAllTimestampsForBlock: failed to find block %d in all block metadata %+v", blockNum, trr.timeMetadata) 167 return errors.New("requested blockNum does not exist") 168 } 169 blkOff, ok := blockMeta.ColumnBlockOffset[trr.timestampKey] 170 if !ok { 171 log.Errorf("readAllTimestampsForBlock: failed to find block offset for timestamp key %+v in block %d. All block offsets %+v", 172 trr.timestampKey, blockNum, blockMeta.ColumnBlockOffset) 173 return errors.New("requested blockNum does not exist") 174 } 175 blkLen, ok := blockMeta.ColumnBlockLen[trr.timestampKey] 176 if !ok { 177 log.Errorf("readAllTimestampsForBlock: failed to find block length for timestamp key %+v in block %d. All block offsets %+v", 178 trr.timestampKey, blockNum, blockMeta.ColumnBlockLen) 179 return errors.New("requested blockNum does not exist") 180 } 181 182 if uint32(len(trr.blockReadBuffer)) < blkLen { 183 newArr := make([]byte, blkLen-uint32(len(trr.blockReadBuffer))) 184 trr.blockReadBuffer = append(trr.blockReadBuffer, newArr...) 185 } 186 _, err = trr.timeFD.ReadAt(trr.blockReadBuffer[:blkLen], blkOff) 187 if err != nil { 188 if err != io.EOF { 189 trr.loadedBlock = false 190 log.Errorf("readAllTimestampsForBlock: error reading file at blkLen: %+v blkOff: %+v error: %+v", blkLen, blkOff, err) 191 return err 192 } 193 return nil 194 } 195 196 rawTSVal := trr.blockReadBuffer[:blkLen] 197 numRecs := trr.blockRecCount[blockNum] 198 decoded, err := convertRawRecordsToTimestamps(rawTSVal, numRecs, trr.blockTimestamps) 199 if err != nil { 200 log.Errorf("convertRawRecordsToTimestamps failed %+v", err) 201 return err 202 } 203 204 trr.numBlockReadTimestamps = numRecs 205 trr.blockTimestamps = decoded 206 trr.loadedBlock = true 207 trr.loadedBlockNum = blockNum 208 209 return nil 210 } 211 212 func (trr *TimeRangeReader) resizeSliceForBlock(blockNum uint16) error { 213 214 numRecs, ok := trr.blockRecCount[blockNum] 215 if !ok { 216 log.Errorf("readAllTimestampsForBlock: failed to find block %d in all blocks: %+v", blockNum, trr.blockRecCount) 217 return errors.New("blockNum not found") 218 } 219 if currBufSize := len(trr.blockTimestamps); int(numRecs) > currBufSize { 220 toAdd := int(numRecs) - currBufSize 221 newSlice := make([]uint64, toAdd) 222 trr.blockTimestamps = append(trr.blockTimestamps, newSlice...) 223 } 224 return nil 225 } 226 227 func (trr *TimeRangeReader) Close() error { 228 if trr.timeFD == nil { 229 return errors.New("tried to close an unopened time reader") 230 } 231 trr.returnBuffers() 232 err := blob.SetSegSetFilesAsNotInUse(trr.allInUseFiles) 233 if err != nil { 234 log.Errorf("Failed to release needed segment files from local storage %+v! Err: %+v", trr.allInUseFiles, err) 235 } 236 return trr.timeFD.Close() 237 } 238 239 func (trr *TimeRangeReader) returnBuffers() { 240 uncompressedReadBufferPool.Put(&trr.blockUncompressedBuffer) 241 fileReadBufferPool.Put(&trr.blockReadBuffer) 242 rawTimestampsBufferPool.Put(&trr.blockTimestamps) 243 } 244 245 func convertRawRecordsToTimestamps(rawRec []byte, numRecs uint16, bufToUse []uint64) ([]uint64, error) { 246 if bufToUse == nil { 247 bufToUse = make([]uint64, numRecs) 248 } 249 if currBufSize := len(bufToUse); int(numRecs) > currBufSize { 250 toAdd := int(numRecs) - currBufSize 251 newSlice := make([]uint64, toAdd) 252 bufToUse = append(bufToUse, newSlice...) 253 } 254 255 oPtr := uint32(0) 256 if rawRec[oPtr] != utils.TIMESTAMP_TOPDIFF_VARENC[0] { 257 log.Errorf("received an unknown encoding type for typestamp column! expected %+v got %+v", 258 utils.TIMESTAMP_TOPDIFF_VARENC[0], rawRec[oPtr]) 259 return nil, fmt.Errorf("received an unknown encoding type for typestamp column! expected %+v got %+v", 260 utils.TIMESTAMP_TOPDIFF_VARENC[0], rawRec[oPtr]) 261 } 262 oPtr++ 263 264 tsType := rawRec[oPtr] 265 oPtr++ 266 267 lowTs := toputils.BytesToUint64LittleEndian(rawRec[oPtr:]) 268 oPtr += 8 269 270 switch tsType { 271 case structs.TS_Type8: 272 var tsVal uint8 273 for i := uint16(0); i < numRecs; i++ { 274 tsVal = uint8(rawRec[oPtr]) 275 bufToUse[i] = uint64(tsVal) + lowTs 276 oPtr += 1 277 } 278 case structs.TS_Type16: 279 var tsVal uint16 280 for i := uint16(0); i < numRecs; i++ { 281 tsVal = toputils.BytesToUint16LittleEndian(rawRec[oPtr:]) 282 bufToUse[i] = uint64(tsVal) + lowTs 283 oPtr += 2 284 } 285 case structs.TS_Type32: 286 var tsVal uint32 287 for i := uint16(0); i < numRecs; i++ { 288 tsVal = toputils.BytesToUint32LittleEndian(rawRec[oPtr:]) 289 bufToUse[i] = uint64(tsVal) + lowTs 290 oPtr += 4 291 } 292 case structs.TS_Type64: 293 var tsVal uint64 294 for i := uint16(0); i < numRecs; i++ { 295 tsVal = toputils.BytesToUint64LittleEndian(rawRec[oPtr:]) 296 bufToUse[i] = uint64(tsVal) + lowTs 297 oPtr += 8 298 } 299 } 300 301 return bufToUse, nil 302 } 303 304 func readChunkFromFile(fd *os.File, buf []byte, blkLen uint32, blkOff int64) ([]byte, error) { 305 if uint32(len(buf)) < blkLen { 306 newArr := make([]byte, blkLen-uint32(len(buf))) 307 buf = append(buf, newArr...) 308 } 309 buf = buf[:blkLen] 310 _, err := fd.ReadAt(buf, blkOff) 311 if err != nil { 312 log.Errorf("readChunkFromFile: failed to read timestamp file! %+v bytes at offset %+v. error %+v", 313 blkLen, blkOff, err) 314 return nil, err 315 } 316 return buf, nil 317 } 318 319 // When the caller of this function is done with retVal, they should call 320 // ReturnTimeBuffers(retVal) to return the buffers to rawTimestampsBufferPool. 321 func processTimeBlocks(allRequests chan *timeBlockRequest, wg *sync.WaitGroup, retVal map[uint16][]uint64, 322 retLock *sync.Mutex) { 323 defer wg.Done() 324 for req := range allRequests { 325 bufToUse := *rawTimestampsBufferPool.Get().(*[]uint64) 326 decoded, err := convertRawRecordsToTimestamps(req.tsRec, req.numRecs, bufToUse) 327 if err != nil { 328 log.Errorf("convertRawRecordsToTimestamps failed %+v", err) 329 continue 330 } 331 retLock.Lock() 332 retVal[req.blkNum] = decoded 333 retLock.Unlock() 334 } 335 } 336 337 // When the caller of this function is done with the returned map, they should 338 // call ReturnTimeBuffers() on it to return the buffers to rawTimestampsBufferPool. 339 func ReadAllTimestampsForBlock(blks map[uint16]*structs.BlockMetadataHolder, segKey string, 340 blockSummaries []*structs.BlockSummary, parallelism int64) (map[uint16][]uint64, error) { 341 342 if len(blks) == 0 { 343 return make(map[uint16][]uint64), nil 344 } 345 tsKey := config.GetTimeStampKey() 346 fName := fmt.Sprintf("%s_%v.csg", segKey, xxhash.Sum64String(tsKey)) 347 err := blob.DownloadSegmentBlob(fName, true) 348 if err != nil { 349 log.Errorf("ReadAllTimestampsForBlock: failed to download time column file %s. Error: %+v", fName, err) 350 return nil, err 351 } 352 353 defer func() { 354 err := blob.SetBlobAsNotInUse(fName) 355 if err != nil { 356 log.Errorf("ReadAllTimestampsForBlock: failed to set blob as not in use %s. Error: %+v", fName, err) 357 } 358 }() 359 fd, err := os.OpenFile(fName, os.O_RDONLY, 0644) 360 if err != nil { 361 log.Errorf("ReadAllTimestampsForBlock: failed to open time column file %s. Error: %+v", fName, err) 362 return nil, err 363 } 364 defer fd.Close() 365 366 allBlocks := make([]uint16, 0) 367 for n := range blks { 368 allBlocks = append(allBlocks, n) 369 } 370 371 sort.Slice(allBlocks, func(i, j int) bool { 372 return allBlocks[i] < allBlocks[j] 373 }) 374 375 retVal := make(map[uint16][]uint64) 376 var retLock sync.Mutex 377 minIdx := 0 378 allReadJob := make(chan *timeBlockRequest) 379 var readerWG sync.WaitGroup 380 for i := int64(0); i < parallelism; i++ { 381 readerWG.Add(1) 382 go processTimeBlocks(allReadJob, &readerWG, retVal, &retLock) 383 } 384 385 for minIdx < len(allBlocks) { 386 minBlkNum := allBlocks[minIdx] 387 lastBlkNum := minBlkNum 388 firstBlkOff := blks[minBlkNum].ColumnBlockOffset[tsKey] 389 blkLen := blks[minBlkNum].ColumnBlockLen[tsKey] 390 maxIdx := minIdx 391 for { 392 nextIdx := maxIdx + 1 393 if nextIdx >= len(allBlocks) { 394 break 395 } 396 nextBlkNum := allBlocks[nextIdx] 397 if nextBlkNum == lastBlkNum+1 { 398 maxIdx++ 399 blkLen += blks[nextBlkNum].ColumnBlockLen[tsKey] 400 lastBlkNum = nextBlkNum 401 } else { 402 break 403 } 404 } 405 buffer := *uncompressedReadBufferPool.Get().(*[]byte) 406 rawChunk, err := readChunkFromFile(fd, buffer, blkLen, firstBlkOff) 407 defer uncompressedReadBufferPool.Put(&rawChunk) 408 if err != nil { 409 log.Errorf("ReadAllTimestampsForBlock: Failed to read chunk from file! %+v", err) 410 continue 411 } 412 413 for currBlk := minBlkNum; currBlk <= allBlocks[maxIdx]; currBlk++ { 414 readOffset := blks[currBlk].ColumnBlockOffset[tsKey] - firstBlkOff 415 readLen := int64(blks[currBlk].ColumnBlockLen[tsKey]) 416 rawBlock := rawChunk[readOffset : readOffset+readLen] 417 numRecs := blockSummaries[currBlk].RecCount 418 allReadJob <- &timeBlockRequest{tsRec: rawBlock, blkNum: currBlk, numRecs: numRecs} 419 } 420 minIdx = maxIdx + 1 421 } 422 close(allReadJob) 423 readerWG.Wait() 424 return retVal, nil 425 } 426 427 func ReturnTimeBuffers(og map[uint16][]uint64) { 428 for _, v := range og { 429 rawTimestampsBufferPool.Put(&v) 430 } 431 }