You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

scorch.go 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669
  1. // Copyright (c) 2018 Couchbase, Inc.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package scorch
  15. import (
  16. "encoding/json"
  17. "fmt"
  18. "io/ioutil"
  19. "os"
  20. "sync"
  21. "sync/atomic"
  22. "time"
  23. "github.com/RoaringBitmap/roaring"
  24. "github.com/blevesearch/bleve/v2/registry"
  25. index "github.com/blevesearch/bleve_index_api"
  26. segment "github.com/blevesearch/scorch_segment_api/v2"
  27. bolt "go.etcd.io/bbolt"
  28. )
  29. const Name = "scorch"
  30. const Version uint8 = 2
  31. var ErrClosed = fmt.Errorf("scorch closed")
  32. type Scorch struct {
  33. nextSegmentID uint64
  34. stats Stats
  35. iStats internalStats
  36. readOnly bool
  37. version uint8
  38. config map[string]interface{}
  39. analysisQueue *index.AnalysisQueue
  40. path string
  41. unsafeBatch bool
  42. rootLock sync.RWMutex
  43. root *IndexSnapshot // holds 1 ref-count on the root
  44. rootPersisted []chan error // closed when root is persisted
  45. persistedCallbacks []index.BatchCallback
  46. nextSnapshotEpoch uint64
  47. eligibleForRemoval []uint64 // Index snapshot epochs that are safe to GC.
  48. ineligibleForRemoval map[string]bool // Filenames that should not be GC'ed yet.
  49. numSnapshotsToKeep int
  50. closeCh chan struct{}
  51. introductions chan *segmentIntroduction
  52. persists chan *persistIntroduction
  53. merges chan *segmentMerge
  54. introducerNotifier chan *epochWatcher
  55. persisterNotifier chan *epochWatcher
  56. rootBolt *bolt.DB
  57. asyncTasks sync.WaitGroup
  58. onEvent func(event Event)
  59. onAsyncError func(err error)
  60. forceMergeRequestCh chan *mergerCtrl
  61. segPlugin SegmentPlugin
  62. }
  63. type internalStats struct {
  64. persistEpoch uint64
  65. persistSnapshotSize uint64
  66. mergeEpoch uint64
  67. mergeSnapshotSize uint64
  68. newSegBufBytesAdded uint64
  69. newSegBufBytesRemoved uint64
  70. analysisBytesAdded uint64
  71. analysisBytesRemoved uint64
  72. }
  73. func NewScorch(storeName string,
  74. config map[string]interface{},
  75. analysisQueue *index.AnalysisQueue) (index.Index, error) {
  76. rv := &Scorch{
  77. version: Version,
  78. config: config,
  79. analysisQueue: analysisQueue,
  80. nextSnapshotEpoch: 1,
  81. closeCh: make(chan struct{}),
  82. ineligibleForRemoval: map[string]bool{},
  83. forceMergeRequestCh: make(chan *mergerCtrl, 1),
  84. segPlugin: defaultSegmentPlugin,
  85. }
  86. forcedSegmentType, forcedSegmentVersion, err := configForceSegmentTypeVersion(config)
  87. if err != nil {
  88. return nil, err
  89. }
  90. if forcedSegmentType != "" && forcedSegmentVersion != 0 {
  91. err := rv.loadSegmentPlugin(forcedSegmentType,
  92. uint32(forcedSegmentVersion))
  93. if err != nil {
  94. return nil, err
  95. }
  96. }
  97. rv.root = &IndexSnapshot{parent: rv, refs: 1, creator: "NewScorch"}
  98. ro, ok := config["read_only"].(bool)
  99. if ok {
  100. rv.readOnly = ro
  101. }
  102. ub, ok := config["unsafe_batch"].(bool)
  103. if ok {
  104. rv.unsafeBatch = ub
  105. }
  106. ecbName, ok := config["eventCallbackName"].(string)
  107. if ok {
  108. rv.onEvent = RegistryEventCallbacks[ecbName]
  109. }
  110. aecbName, ok := config["asyncErrorCallbackName"].(string)
  111. if ok {
  112. rv.onAsyncError = RegistryAsyncErrorCallbacks[aecbName]
  113. }
  114. return rv, nil
  115. }
  116. // configForceSegmentTypeVersion checks if the caller has requested a
  117. // specific segment type/version
  118. func configForceSegmentTypeVersion(config map[string]interface{}) (string, uint32, error) {
  119. forcedSegmentVersion, err := parseToInteger(config["forceSegmentVersion"])
  120. if err != nil {
  121. return "", 0, nil
  122. }
  123. forcedSegmentType, ok := config["forceSegmentType"].(string)
  124. if !ok {
  125. return "", 0, fmt.Errorf(
  126. "forceSegmentVersion set to %d, must also specify forceSegmentType", forcedSegmentVersion)
  127. }
  128. return forcedSegmentType, uint32(forcedSegmentVersion), nil
  129. }
  130. func (s *Scorch) NumEventsBlocking() uint64 {
  131. eventsCompleted := atomic.LoadUint64(&s.stats.TotEventTriggerCompleted)
  132. eventsStarted := atomic.LoadUint64(&s.stats.TotEventTriggerStarted)
  133. return eventsStarted - eventsCompleted
  134. }
  135. func (s *Scorch) fireEvent(kind EventKind, dur time.Duration) {
  136. if s.onEvent != nil {
  137. atomic.AddUint64(&s.stats.TotEventTriggerStarted, 1)
  138. s.onEvent(Event{Kind: kind, Scorch: s, Duration: dur})
  139. atomic.AddUint64(&s.stats.TotEventTriggerCompleted, 1)
  140. }
  141. }
  142. func (s *Scorch) fireAsyncError(err error) {
  143. if s.onAsyncError != nil {
  144. s.onAsyncError(err)
  145. }
  146. atomic.AddUint64(&s.stats.TotOnErrors, 1)
  147. }
  148. func (s *Scorch) Open() error {
  149. err := s.openBolt()
  150. if err != nil {
  151. return err
  152. }
  153. s.asyncTasks.Add(1)
  154. go s.introducerLoop()
  155. if !s.readOnly && s.path != "" {
  156. s.asyncTasks.Add(1)
  157. go s.persisterLoop()
  158. s.asyncTasks.Add(1)
  159. go s.mergerLoop()
  160. }
  161. return nil
  162. }
  163. func (s *Scorch) openBolt() error {
  164. var ok bool
  165. s.path, ok = s.config["path"].(string)
  166. if !ok {
  167. return fmt.Errorf("must specify path")
  168. }
  169. if s.path == "" {
  170. s.unsafeBatch = true
  171. }
  172. var rootBoltOpt = *bolt.DefaultOptions
  173. if s.readOnly {
  174. rootBoltOpt.ReadOnly = true
  175. } else {
  176. if s.path != "" {
  177. err := os.MkdirAll(s.path, 0700)
  178. if err != nil {
  179. return err
  180. }
  181. }
  182. }
  183. if boltTimeoutStr, ok := s.config["bolt_timeout"].(string); ok {
  184. var err error
  185. boltTimeout, err := time.ParseDuration(boltTimeoutStr)
  186. if err != nil {
  187. return fmt.Errorf("invalid duration specified for bolt_timeout: %v", err)
  188. }
  189. rootBoltOpt.Timeout = boltTimeout
  190. }
  191. rootBoltPath := s.path + string(os.PathSeparator) + "root.bolt"
  192. var err error
  193. if s.path != "" {
  194. s.rootBolt, err = bolt.Open(rootBoltPath, 0600, &rootBoltOpt)
  195. if err != nil {
  196. return err
  197. }
  198. // now see if there is any existing state to load
  199. err = s.loadFromBolt()
  200. if err != nil {
  201. _ = s.Close()
  202. return err
  203. }
  204. }
  205. atomic.StoreUint64(&s.stats.TotFileSegmentsAtRoot, uint64(len(s.root.segment)))
  206. s.introductions = make(chan *segmentIntroduction)
  207. s.persists = make(chan *persistIntroduction)
  208. s.merges = make(chan *segmentMerge)
  209. s.introducerNotifier = make(chan *epochWatcher, 1)
  210. s.persisterNotifier = make(chan *epochWatcher, 1)
  211. s.closeCh = make(chan struct{})
  212. s.forceMergeRequestCh = make(chan *mergerCtrl, 1)
  213. if !s.readOnly && s.path != "" {
  214. err := s.removeOldZapFiles() // Before persister or merger create any new files.
  215. if err != nil {
  216. _ = s.Close()
  217. return err
  218. }
  219. }
  220. s.numSnapshotsToKeep = NumSnapshotsToKeep
  221. if v, ok := s.config["numSnapshotsToKeep"]; ok {
  222. var t int
  223. if t, err = parseToInteger(v); err != nil {
  224. return fmt.Errorf("numSnapshotsToKeep parse err: %v", err)
  225. }
  226. if t > 0 {
  227. s.numSnapshotsToKeep = t
  228. }
  229. }
  230. return nil
  231. }
  232. func (s *Scorch) Close() (err error) {
  233. startTime := time.Now()
  234. defer func() {
  235. s.fireEvent(EventKindClose, time.Since(startTime))
  236. }()
  237. s.fireEvent(EventKindCloseStart, 0)
  238. // signal to async tasks we want to close
  239. close(s.closeCh)
  240. // wait for them to close
  241. s.asyncTasks.Wait()
  242. // now close the root bolt
  243. if s.rootBolt != nil {
  244. err = s.rootBolt.Close()
  245. s.rootLock.Lock()
  246. if s.root != nil {
  247. err2 := s.root.DecRef()
  248. if err == nil {
  249. err = err2
  250. }
  251. }
  252. s.root = nil
  253. s.rootLock.Unlock()
  254. }
  255. return
  256. }
  257. func (s *Scorch) Update(doc index.Document) error {
  258. b := index.NewBatch()
  259. b.Update(doc)
  260. return s.Batch(b)
  261. }
  262. func (s *Scorch) Delete(id string) error {
  263. b := index.NewBatch()
  264. b.Delete(id)
  265. return s.Batch(b)
  266. }
  267. // Batch applices a batch of changes to the index atomically
  268. func (s *Scorch) Batch(batch *index.Batch) (err error) {
  269. start := time.Now()
  270. defer func() {
  271. s.fireEvent(EventKindBatchIntroduction, time.Since(start))
  272. }()
  273. resultChan := make(chan index.Document, len(batch.IndexOps))
  274. var numUpdates uint64
  275. var numDeletes uint64
  276. var numPlainTextBytes uint64
  277. var ids []string
  278. for docID, doc := range batch.IndexOps {
  279. if doc != nil {
  280. // insert _id field
  281. doc.AddIDField()
  282. numUpdates++
  283. numPlainTextBytes += doc.NumPlainTextBytes()
  284. } else {
  285. numDeletes++
  286. }
  287. ids = append(ids, docID)
  288. }
  289. // FIXME could sort ids list concurrent with analysis?
  290. if numUpdates > 0 {
  291. go func() {
  292. for k := range batch.IndexOps {
  293. doc := batch.IndexOps[k]
  294. if doc != nil {
  295. // put the work on the queue
  296. s.analysisQueue.Queue(func() {
  297. analyze(doc)
  298. resultChan <- doc
  299. })
  300. }
  301. }
  302. }()
  303. }
  304. // wait for analysis result
  305. analysisResults := make([]index.Document, int(numUpdates))
  306. var itemsDeQueued uint64
  307. var totalAnalysisSize int
  308. for itemsDeQueued < numUpdates {
  309. result := <-resultChan
  310. resultSize := result.Size()
  311. atomic.AddUint64(&s.iStats.analysisBytesAdded, uint64(resultSize))
  312. totalAnalysisSize += resultSize
  313. analysisResults[itemsDeQueued] = result
  314. itemsDeQueued++
  315. }
  316. close(resultChan)
  317. defer atomic.AddUint64(&s.iStats.analysisBytesRemoved, uint64(totalAnalysisSize))
  318. atomic.AddUint64(&s.stats.TotAnalysisTime, uint64(time.Since(start)))
  319. indexStart := time.Now()
  320. // notify handlers that we're about to introduce a segment
  321. s.fireEvent(EventKindBatchIntroductionStart, 0)
  322. var newSegment segment.Segment
  323. var bufBytes uint64
  324. if len(analysisResults) > 0 {
  325. newSegment, bufBytes, err = s.segPlugin.New(analysisResults)
  326. if err != nil {
  327. return err
  328. }
  329. atomic.AddUint64(&s.iStats.newSegBufBytesAdded, bufBytes)
  330. } else {
  331. atomic.AddUint64(&s.stats.TotBatchesEmpty, 1)
  332. }
  333. err = s.prepareSegment(newSegment, ids, batch.InternalOps, batch.PersistedCallback())
  334. if err != nil {
  335. if newSegment != nil {
  336. _ = newSegment.Close()
  337. }
  338. atomic.AddUint64(&s.stats.TotOnErrors, 1)
  339. } else {
  340. atomic.AddUint64(&s.stats.TotUpdates, numUpdates)
  341. atomic.AddUint64(&s.stats.TotDeletes, numDeletes)
  342. atomic.AddUint64(&s.stats.TotBatches, 1)
  343. atomic.AddUint64(&s.stats.TotIndexedPlainTextBytes, numPlainTextBytes)
  344. }
  345. atomic.AddUint64(&s.iStats.newSegBufBytesRemoved, bufBytes)
  346. atomic.AddUint64(&s.stats.TotIndexTime, uint64(time.Since(indexStart)))
  347. return err
  348. }
  349. func (s *Scorch) prepareSegment(newSegment segment.Segment, ids []string,
  350. internalOps map[string][]byte, persistedCallback index.BatchCallback) error {
  351. // new introduction
  352. introduction := &segmentIntroduction{
  353. id: atomic.AddUint64(&s.nextSegmentID, 1),
  354. data: newSegment,
  355. ids: ids,
  356. obsoletes: make(map[uint64]*roaring.Bitmap),
  357. internal: internalOps,
  358. applied: make(chan error),
  359. persistedCallback: persistedCallback,
  360. }
  361. if !s.unsafeBatch {
  362. introduction.persisted = make(chan error, 1)
  363. }
  364. // optimistically prepare obsoletes outside of rootLock
  365. s.rootLock.RLock()
  366. root := s.root
  367. root.AddRef()
  368. s.rootLock.RUnlock()
  369. defer func() { _ = root.DecRef() }()
  370. for _, seg := range root.segment {
  371. delta, err := seg.segment.DocNumbers(ids)
  372. if err != nil {
  373. return err
  374. }
  375. introduction.obsoletes[seg.id] = delta
  376. }
  377. introStartTime := time.Now()
  378. s.introductions <- introduction
  379. // block until this segment is applied
  380. err := <-introduction.applied
  381. if err != nil {
  382. return err
  383. }
  384. if introduction.persisted != nil {
  385. err = <-introduction.persisted
  386. }
  387. introTime := uint64(time.Since(introStartTime))
  388. atomic.AddUint64(&s.stats.TotBatchIntroTime, introTime)
  389. if atomic.LoadUint64(&s.stats.MaxBatchIntroTime) < introTime {
  390. atomic.StoreUint64(&s.stats.MaxBatchIntroTime, introTime)
  391. }
  392. return err
  393. }
  394. func (s *Scorch) SetInternal(key, val []byte) error {
  395. b := index.NewBatch()
  396. b.SetInternal(key, val)
  397. return s.Batch(b)
  398. }
  399. func (s *Scorch) DeleteInternal(key []byte) error {
  400. b := index.NewBatch()
  401. b.DeleteInternal(key)
  402. return s.Batch(b)
  403. }
  404. // Reader returns a low-level accessor on the index data. Close it to
  405. // release associated resources.
  406. func (s *Scorch) Reader() (index.IndexReader, error) {
  407. return s.currentSnapshot(), nil
  408. }
  409. func (s *Scorch) currentSnapshot() *IndexSnapshot {
  410. s.rootLock.RLock()
  411. rv := s.root
  412. if rv != nil {
  413. rv.AddRef()
  414. }
  415. s.rootLock.RUnlock()
  416. return rv
  417. }
  418. func (s *Scorch) Stats() json.Marshaler {
  419. return &s.stats
  420. }
  421. func (s *Scorch) diskFileStats(rootSegmentPaths map[string]struct{}) (uint64,
  422. uint64, uint64) {
  423. var numFilesOnDisk, numBytesUsedDisk, numBytesOnDiskByRoot uint64
  424. if s.path != "" {
  425. finfos, err := ioutil.ReadDir(s.path)
  426. if err == nil {
  427. for _, finfo := range finfos {
  428. if !finfo.IsDir() {
  429. numBytesUsedDisk += uint64(finfo.Size())
  430. numFilesOnDisk++
  431. if rootSegmentPaths != nil {
  432. fname := s.path + string(os.PathSeparator) + finfo.Name()
  433. if _, fileAtRoot := rootSegmentPaths[fname]; fileAtRoot {
  434. numBytesOnDiskByRoot += uint64(finfo.Size())
  435. }
  436. }
  437. }
  438. }
  439. }
  440. }
  441. // if no root files path given, then consider all disk files.
  442. if rootSegmentPaths == nil {
  443. return numFilesOnDisk, numBytesUsedDisk, numBytesUsedDisk
  444. }
  445. return numFilesOnDisk, numBytesUsedDisk, numBytesOnDiskByRoot
  446. }
  447. func (s *Scorch) StatsMap() map[string]interface{} {
  448. m := s.stats.ToMap()
  449. indexSnapshot := s.currentSnapshot()
  450. defer func() {
  451. _ = indexSnapshot.Close()
  452. }()
  453. rootSegPaths := indexSnapshot.diskSegmentsPaths()
  454. s.rootLock.RLock()
  455. m["CurFilesIneligibleForRemoval"] = uint64(len(s.ineligibleForRemoval))
  456. s.rootLock.RUnlock()
  457. numFilesOnDisk, numBytesUsedDisk, numBytesOnDiskByRoot := s.diskFileStats(rootSegPaths)
  458. m["CurOnDiskBytes"] = numBytesUsedDisk
  459. m["CurOnDiskFiles"] = numFilesOnDisk
  460. // TODO: consider one day removing these backwards compatible
  461. // names for apps using the old names
  462. m["updates"] = m["TotUpdates"]
  463. m["deletes"] = m["TotDeletes"]
  464. m["batches"] = m["TotBatches"]
  465. m["errors"] = m["TotOnErrors"]
  466. m["analysis_time"] = m["TotAnalysisTime"]
  467. m["index_time"] = m["TotIndexTime"]
  468. m["term_searchers_started"] = m["TotTermSearchersStarted"]
  469. m["term_searchers_finished"] = m["TotTermSearchersFinished"]
  470. m["num_plain_text_bytes_indexed"] = m["TotIndexedPlainTextBytes"]
  471. m["num_items_introduced"] = m["TotIntroducedItems"]
  472. m["num_items_persisted"] = m["TotPersistedItems"]
  473. m["num_recs_to_persist"] = m["TotItemsToPersist"]
  474. // total disk bytes found in index directory inclusive of older snapshots
  475. m["num_bytes_used_disk"] = numBytesUsedDisk
  476. // total disk bytes by the latest root index, exclusive of older snapshots
  477. m["num_bytes_used_disk_by_root"] = numBytesOnDiskByRoot
  478. // num_bytes_used_disk_by_root_reclaimable is an approximation about the
  479. // reclaimable disk space in an index. (eg: from a full compaction)
  480. m["num_bytes_used_disk_by_root_reclaimable"] = uint64(float64(numBytesOnDiskByRoot) *
  481. indexSnapshot.reClaimableDocsRatio())
  482. m["num_files_on_disk"] = numFilesOnDisk
  483. m["num_root_memorysegments"] = m["TotMemorySegmentsAtRoot"]
  484. m["num_root_filesegments"] = m["TotFileSegmentsAtRoot"]
  485. m["num_persister_nap_pause_completed"] = m["TotPersisterNapPauseCompleted"]
  486. m["num_persister_nap_merger_break"] = m["TotPersisterMergerNapBreak"]
  487. m["total_compaction_written_bytes"] = m["TotFileMergeWrittenBytes"]
  488. return m
  489. }
  490. func (s *Scorch) Analyze(d index.Document) {
  491. analyze(d)
  492. }
  493. func analyze(d index.Document) {
  494. d.VisitFields(func(field index.Field) {
  495. if field.Options().IsIndexed() {
  496. field.Analyze()
  497. if d.HasComposite() && field.Name() != "_id" {
  498. // see if any of the composite fields need this
  499. d.VisitComposite(func(cf index.CompositeField) {
  500. cf.Compose(field.Name(), field.AnalyzedLength(), field.AnalyzedTokenFrequencies())
  501. })
  502. }
  503. }
  504. })
  505. }
  506. func (s *Scorch) AddEligibleForRemoval(epoch uint64) {
  507. s.rootLock.Lock()
  508. if s.root == nil || s.root.epoch != epoch {
  509. s.eligibleForRemoval = append(s.eligibleForRemoval, epoch)
  510. }
  511. s.rootLock.Unlock()
  512. }
  513. func (s *Scorch) MemoryUsed() (memUsed uint64) {
  514. indexSnapshot := s.currentSnapshot()
  515. if indexSnapshot == nil {
  516. return
  517. }
  518. defer func() {
  519. _ = indexSnapshot.Close()
  520. }()
  521. // Account for current root snapshot overhead
  522. memUsed += uint64(indexSnapshot.Size())
  523. // Account for snapshot that the persister may be working on
  524. persistEpoch := atomic.LoadUint64(&s.iStats.persistEpoch)
  525. persistSnapshotSize := atomic.LoadUint64(&s.iStats.persistSnapshotSize)
  526. if persistEpoch != 0 && indexSnapshot.epoch > persistEpoch {
  527. // the snapshot that the persister is working on isn't the same as
  528. // the current snapshot
  529. memUsed += persistSnapshotSize
  530. }
  531. // Account for snapshot that the merger may be working on
  532. mergeEpoch := atomic.LoadUint64(&s.iStats.mergeEpoch)
  533. mergeSnapshotSize := atomic.LoadUint64(&s.iStats.mergeSnapshotSize)
  534. if mergeEpoch != 0 && indexSnapshot.epoch > mergeEpoch {
  535. // the snapshot that the merger is working on isn't the same as
  536. // the current snapshot
  537. memUsed += mergeSnapshotSize
  538. }
  539. memUsed += (atomic.LoadUint64(&s.iStats.newSegBufBytesAdded) -
  540. atomic.LoadUint64(&s.iStats.newSegBufBytesRemoved))
  541. memUsed += (atomic.LoadUint64(&s.iStats.analysisBytesAdded) -
  542. atomic.LoadUint64(&s.iStats.analysisBytesRemoved))
  543. return memUsed
  544. }
  545. func (s *Scorch) markIneligibleForRemoval(filename string) {
  546. s.rootLock.Lock()
  547. s.ineligibleForRemoval[filename] = true
  548. s.rootLock.Unlock()
  549. }
  550. func (s *Scorch) unmarkIneligibleForRemoval(filename string) {
  551. s.rootLock.Lock()
  552. delete(s.ineligibleForRemoval, filename)
  553. s.rootLock.Unlock()
  554. }
  555. func init() {
  556. registry.RegisterIndexType(Name, NewScorch)
  557. }
  558. func parseToInteger(i interface{}) (int, error) {
  559. switch v := i.(type) {
  560. case float64:
  561. return int(v), nil
  562. case int:
  563. return v, nil
  564. default:
  565. return 0, fmt.Errorf("expects int or float64 value")
  566. }
  567. }