1use std::sync::Arc;
7
8use rustc_hash::FxHashMap;
9
10use crate::directories::Directory;
11use crate::dsl::Schema;
12use crate::error::Result;
13use crate::query::LazyGlobalStats;
14use crate::segment::{SegmentId, SegmentReader};
15#[cfg(feature = "native")]
16use crate::segment::{SegmentSnapshot, SegmentTracker};
17use crate::structures::CoarseCentroids;
18
19pub struct Searcher<D: Directory + 'static> {
24 #[cfg(feature = "native")]
26 _snapshot: SegmentSnapshot,
27 _phantom: std::marker::PhantomData<D>,
29 segments: Vec<Arc<SegmentReader>>,
31 schema: Arc<Schema>,
33 default_fields: Vec<crate::Field>,
35 tokenizers: Arc<crate::tokenizer::TokenizerRegistry>,
37 trained_centroids: FxHashMap<u32, Arc<CoarseCentroids>>,
39 global_stats: Arc<LazyGlobalStats>,
41 segment_map: FxHashMap<u128, usize>,
43 total_docs: u32,
45}
46
47impl<D: Directory + 'static> Searcher<D> {
48 pub async fn open(
53 directory: Arc<D>,
54 schema: Arc<Schema>,
55 segment_ids: &[String],
56 term_cache_blocks: usize,
57 ) -> Result<Self> {
58 Self::create(
59 directory,
60 schema,
61 segment_ids,
62 FxHashMap::default(),
63 term_cache_blocks,
64 )
65 .await
66 }
67
68 #[cfg(feature = "native")]
70 pub(crate) async fn from_snapshot(
71 directory: Arc<D>,
72 schema: Arc<Schema>,
73 snapshot: SegmentSnapshot,
74 trained_centroids: FxHashMap<u32, Arc<CoarseCentroids>>,
75 term_cache_blocks: usize,
76 ) -> Result<Self> {
77 let (segments, default_fields, global_stats, segment_map, total_docs) = Self::load_common(
78 &directory,
79 &schema,
80 snapshot.segment_ids(),
81 &trained_centroids,
82 term_cache_blocks,
83 &[],
84 )
85 .await?;
86
87 Ok(Self {
88 _snapshot: snapshot,
89 _phantom: std::marker::PhantomData,
90 segments,
91 schema,
92 default_fields,
93 tokenizers: Arc::new(crate::tokenizer::TokenizerRegistry::default()),
94 trained_centroids,
95 global_stats,
96 segment_map,
97 total_docs,
98 })
99 }
100
101 #[cfg(feature = "native")]
105 pub(crate) async fn from_snapshot_reuse(
106 directory: Arc<D>,
107 schema: Arc<Schema>,
108 snapshot: SegmentSnapshot,
109 trained_centroids: FxHashMap<u32, Arc<CoarseCentroids>>,
110 term_cache_blocks: usize,
111 existing_segments: &[Arc<SegmentReader>],
112 ) -> Result<Self> {
113 let (segments, default_fields, global_stats, segment_map, total_docs) = Self::load_common(
114 &directory,
115 &schema,
116 snapshot.segment_ids(),
117 &trained_centroids,
118 term_cache_blocks,
119 existing_segments,
120 )
121 .await?;
122
123 Ok(Self {
124 _snapshot: snapshot,
125 _phantom: std::marker::PhantomData,
126 segments,
127 schema,
128 default_fields,
129 tokenizers: Arc::new(crate::tokenizer::TokenizerRegistry::default()),
130 trained_centroids,
131 global_stats,
132 segment_map,
133 total_docs,
134 })
135 }
136
137 async fn create(
139 directory: Arc<D>,
140 schema: Arc<Schema>,
141 segment_ids: &[String],
142 trained_centroids: FxHashMap<u32, Arc<CoarseCentroids>>,
143 term_cache_blocks: usize,
144 ) -> Result<Self> {
145 let (segments, default_fields, global_stats, segment_map, total_docs) = Self::load_common(
146 &directory,
147 &schema,
148 segment_ids,
149 &trained_centroids,
150 term_cache_blocks,
151 &[],
152 )
153 .await?;
154
155 #[cfg(feature = "native")]
156 let _snapshot = {
157 let tracker = Arc::new(SegmentTracker::new());
158 SegmentSnapshot::new(tracker, segment_ids.to_vec())
159 };
160
161 let _ = directory; Ok(Self {
163 #[cfg(feature = "native")]
164 _snapshot,
165 _phantom: std::marker::PhantomData,
166 segments,
167 schema,
168 default_fields,
169 tokenizers: Arc::new(crate::tokenizer::TokenizerRegistry::default()),
170 trained_centroids,
171 global_stats,
172 segment_map,
173 total_docs,
174 })
175 }
176
177 async fn load_common(
179 directory: &Arc<D>,
180 schema: &Arc<Schema>,
181 segment_ids: &[String],
182 trained_centroids: &FxHashMap<u32, Arc<CoarseCentroids>>,
183 term_cache_blocks: usize,
184 existing_segments: &[Arc<SegmentReader>],
185 ) -> Result<(
186 Vec<Arc<SegmentReader>>,
187 Vec<crate::Field>,
188 Arc<LazyGlobalStats>,
189 FxHashMap<u128, usize>,
190 u32,
191 )> {
192 let segments = Self::load_segments(
193 directory,
194 schema,
195 segment_ids,
196 trained_centroids,
197 term_cache_blocks,
198 existing_segments,
199 )
200 .await?;
201 let default_fields = Self::build_default_fields(schema);
202 let global_stats = Arc::new(LazyGlobalStats::new(segments.clone()));
203 let (segment_map, total_docs) = Self::build_lookup_tables(&segments);
204 Ok((
205 segments,
206 default_fields,
207 global_stats,
208 segment_map,
209 total_docs,
210 ))
211 }
212
213 async fn load_segments(
217 directory: &Arc<D>,
218 schema: &Arc<Schema>,
219 segment_ids: &[String],
220 trained_centroids: &FxHashMap<u32, Arc<CoarseCentroids>>,
221 term_cache_blocks: usize,
222 existing_segments: &[Arc<SegmentReader>],
223 ) -> Result<Vec<Arc<SegmentReader>>> {
224 let existing_map: FxHashMap<u128, Arc<SegmentReader>> = existing_segments
226 .iter()
227 .map(|seg| (seg.meta().id, Arc::clone(seg)))
228 .collect();
229
230 let valid_segments: Vec<(usize, SegmentId)> = segment_ids
232 .iter()
233 .enumerate()
234 .filter_map(|(idx, id_str)| SegmentId::from_hex(id_str).map(|sid| (idx, sid)))
235 .collect();
236
237 let mut reused: Vec<(usize, Arc<SegmentReader>)> = Vec::new();
239 let mut to_load: Vec<(usize, SegmentId)> = Vec::new();
240 for (idx, sid) in &valid_segments {
241 if let Some(existing) = existing_map.get(&sid.0) {
242 reused.push((*idx, Arc::clone(existing)));
243 } else {
244 to_load.push((*idx, *sid));
245 }
246 }
247
248 if !existing_segments.is_empty() {
249 log::info!(
250 "[searcher] reusing {} segment readers, loading {} new",
251 reused.len(),
252 to_load.len(),
253 );
254 }
255
256 let futures: Vec<_> = to_load
258 .iter()
259 .map(|(_, segment_id)| {
260 let dir = Arc::clone(directory);
261 let sch = Arc::clone(schema);
262 let sid = *segment_id;
263 async move { SegmentReader::open(dir.as_ref(), sid, sch, term_cache_blocks).await }
264 })
265 .collect();
266
267 let results = futures::future::join_all(futures).await;
268
269 let mut loaded: Vec<(usize, Arc<SegmentReader>)> = Vec::with_capacity(valid_segments.len());
271
272 loaded.extend(reused);
274
275 for ((idx, sid), result) in to_load.into_iter().zip(results) {
277 match result {
278 Ok(mut reader) => {
279 if !trained_centroids.is_empty() {
281 reader.set_coarse_centroids(trained_centroids.clone());
282 }
283 loaded.push((idx, Arc::new(reader)));
284 }
285 Err(e) => {
286 return Err(crate::error::Error::Internal(format!(
287 "Failed to open segment {:016x}: {:?}",
288 sid.0, e
289 )));
290 }
291 }
292 }
293
294 loaded.sort_by_key(|(idx, _)| *idx);
296
297 let segments: Vec<Arc<SegmentReader>> = loaded.into_iter().map(|(_, seg)| seg).collect();
298
299 let total_docs: u64 = segments.iter().map(|s| s.meta().num_docs as u64).sum();
301 let mut total_mem = 0usize;
302 for seg in &segments {
303 let stats = seg.memory_stats();
304 let seg_total = stats.total_bytes();
305 total_mem += seg_total;
306 log::info!(
307 "[searcher] segment {:016x}: docs={}, mem={:.2} MB \
308 (term_dict={:.2} MB, store={:.2} MB, sparse={:.2} MB, dense={:.2} MB, bloom={:.2} MB)",
309 stats.segment_id,
310 stats.num_docs,
311 seg_total as f64 / (1024.0 * 1024.0),
312 stats.term_dict_cache_bytes as f64 / (1024.0 * 1024.0),
313 stats.store_cache_bytes as f64 / (1024.0 * 1024.0),
314 stats.sparse_index_bytes as f64 / (1024.0 * 1024.0),
315 stats.dense_index_bytes as f64 / (1024.0 * 1024.0),
316 stats.bloom_filter_bytes as f64 / (1024.0 * 1024.0),
317 );
318 }
319 let rss_mb = process_rss_mb();
321 log::info!(
322 "[searcher] loaded {} segments: total_docs={}, estimated_mem={:.2} MB, process_rss={:.1} MB",
323 segments.len(),
324 total_docs,
325 total_mem as f64 / (1024.0 * 1024.0),
326 rss_mb,
327 );
328
329 Ok(segments)
330 }
331
332 fn build_default_fields(schema: &Schema) -> Vec<crate::Field> {
334 if !schema.default_fields().is_empty() {
335 schema.default_fields().to_vec()
336 } else {
337 schema
338 .fields()
339 .filter(|(_, entry)| {
340 entry.indexed && entry.field_type == crate::dsl::FieldType::Text
341 })
342 .map(|(field, _)| field)
343 .collect()
344 }
345 }
346
347 pub fn schema(&self) -> &Schema {
349 &self.schema
350 }
351
352 pub fn segment_readers(&self) -> &[Arc<SegmentReader>] {
354 &self.segments
355 }
356
357 pub fn default_fields(&self) -> &[crate::Field] {
359 &self.default_fields
360 }
361
362 pub fn tokenizers(&self) -> &crate::tokenizer::TokenizerRegistry {
364 &self.tokenizers
365 }
366
367 pub fn trained_centroids(&self) -> &FxHashMap<u32, Arc<CoarseCentroids>> {
369 &self.trained_centroids
370 }
371
372 pub fn global_stats(&self) -> &Arc<LazyGlobalStats> {
374 &self.global_stats
375 }
376
377 fn build_lookup_tables(segments: &[Arc<SegmentReader>]) -> (FxHashMap<u128, usize>, u32) {
379 let mut segment_map = FxHashMap::default();
380 let mut total = 0u32;
381 for (i, seg) in segments.iter().enumerate() {
382 segment_map.insert(seg.meta().id, i);
383 total = total.saturating_add(seg.meta().num_docs);
384 }
385 (segment_map, total)
386 }
387
388 pub fn num_docs(&self) -> u32 {
390 self.total_docs
391 }
392
393 pub fn segment_map(&self) -> &FxHashMap<u128, usize> {
395 &self.segment_map
396 }
397
398 pub fn num_segments(&self) -> usize {
400 self.segments.len()
401 }
402
403 pub async fn doc(&self, segment_id: u128, doc_id: u32) -> Result<Option<crate::dsl::Document>> {
405 if let Some(&idx) = self.segment_map.get(&segment_id) {
406 return self.segments[idx].doc(doc_id).await;
407 }
408 Ok(None)
409 }
410
411 pub async fn search(
413 &self,
414 query: &dyn crate::query::Query,
415 limit: usize,
416 ) -> Result<Vec<crate::query::SearchResult>> {
417 let (results, _) = self.search_with_count(query, limit).await?;
418 Ok(results)
419 }
420
421 pub async fn search_with_count(
424 &self,
425 query: &dyn crate::query::Query,
426 limit: usize,
427 ) -> Result<(Vec<crate::query::SearchResult>, u32)> {
428 self.search_with_offset_and_count(query, limit, 0).await
429 }
430
431 pub async fn search_with_offset(
433 &self,
434 query: &dyn crate::query::Query,
435 limit: usize,
436 offset: usize,
437 ) -> Result<Vec<crate::query::SearchResult>> {
438 let (results, _) = self
439 .search_with_offset_and_count(query, limit, offset)
440 .await?;
441 Ok(results)
442 }
443
444 pub async fn search_with_offset_and_count(
446 &self,
447 query: &dyn crate::query::Query,
448 limit: usize,
449 offset: usize,
450 ) -> Result<(Vec<crate::query::SearchResult>, u32)> {
451 self.search_internal(query, limit, offset, false).await
452 }
453
454 pub async fn search_with_positions(
458 &self,
459 query: &dyn crate::query::Query,
460 limit: usize,
461 ) -> Result<(Vec<crate::query::SearchResult>, u32)> {
462 self.search_internal(query, limit, 0, true).await
463 }
464
465 async fn search_internal(
467 &self,
468 query: &dyn crate::query::Query,
469 limit: usize,
470 offset: usize,
471 collect_positions: bool,
472 ) -> Result<(Vec<crate::query::SearchResult>, u32)> {
473 let fetch_limit = offset + limit;
474
475 #[cfg(feature = "sync")]
481 if !self.segments.is_empty()
482 && tokio::runtime::Handle::current().runtime_flavor()
483 == tokio::runtime::RuntimeFlavor::MultiThread
484 {
485 return self.search_internal_parallel(query, fetch_limit, offset, collect_positions);
486 }
487
488 let futures: Vec<_> = self
490 .segments
491 .iter()
492 .map(|segment| {
493 let sid = segment.meta().id;
494 async move {
495 let (mut results, segment_seen) = if collect_positions {
496 crate::query::search_segment_with_positions_and_count(
497 segment.as_ref(),
498 query,
499 fetch_limit,
500 )
501 .await?
502 } else {
503 crate::query::search_segment_with_count(
504 segment.as_ref(),
505 query,
506 fetch_limit,
507 )
508 .await?
509 };
510 for r in &mut results {
512 r.segment_id = sid;
513 }
514 Ok::<_, crate::error::Error>((results, segment_seen))
515 }
516 })
517 .collect();
518
519 let batches = futures::future::try_join_all(futures).await?;
520 let mut total_seen: u32 = 0;
521
522 let mut sorted_batches: Vec<Vec<crate::query::SearchResult>> =
523 Vec::with_capacity(batches.len());
524 for (batch, segment_seen) in batches {
525 total_seen = total_seen.saturating_add(segment_seen);
526 if !batch.is_empty() {
527 sorted_batches.push(batch);
528 }
529 }
530
531 let results = merge_segment_results(sorted_batches, fetch_limit, offset);
532 Ok((results, total_seen))
533 }
534
535 #[cfg(feature = "sync")]
540 fn search_internal_parallel(
541 &self,
542 query: &dyn crate::query::Query,
543 fetch_limit: usize,
544 offset: usize,
545 collect_positions: bool,
546 ) -> Result<(Vec<crate::query::SearchResult>, u32)> {
547 tokio::task::block_in_place(|| {
548 self.search_internal_sync(query, fetch_limit, offset, collect_positions)
549 })
550 }
551
552 #[cfg(feature = "sync")]
557 fn search_internal_sync(
558 &self,
559 query: &dyn crate::query::Query,
560 fetch_limit: usize,
561 offset: usize,
562 collect_positions: bool,
563 ) -> Result<(Vec<crate::query::SearchResult>, u32)> {
564 use rayon::prelude::*;
565
566 let batches: Vec<Result<(Vec<crate::query::SearchResult>, u32)>> = {
567 self.segments
568 .par_iter()
569 .map(|segment| {
570 let sid = segment.meta().id;
571 let (mut results, segment_seen) = if collect_positions {
572 crate::query::search_segment_with_positions_and_count_sync(
573 segment.as_ref(),
574 query,
575 fetch_limit,
576 )?
577 } else {
578 crate::query::search_segment_with_count_sync(
579 segment.as_ref(),
580 query,
581 fetch_limit,
582 )?
583 };
584 for r in &mut results {
585 r.segment_id = sid;
586 }
587 Ok((results, segment_seen))
588 })
589 .collect()
590 };
591
592 let mut total_seen: u32 = 0;
593 let mut sorted_batches: Vec<Vec<crate::query::SearchResult>> =
594 Vec::with_capacity(batches.len());
595 for result in batches {
596 let (batch, segment_seen) = result?;
597 total_seen = total_seen.saturating_add(segment_seen);
598 if !batch.is_empty() {
599 sorted_batches.push(batch);
600 }
601 }
602
603 let results = merge_segment_results(sorted_batches, fetch_limit, offset);
604 Ok((results, total_seen))
605 }
606
607 #[cfg(feature = "sync")]
611 pub fn search_with_offset_and_count_sync(
612 &self,
613 query: &dyn crate::query::Query,
614 limit: usize,
615 offset: usize,
616 ) -> Result<(Vec<crate::query::SearchResult>, u32)> {
617 use rayon::prelude::*;
618
619 let fetch_limit = offset + limit;
620
621 let batches: Vec<Result<(Vec<crate::query::SearchResult>, u32)>> = self
622 .segments
623 .par_iter()
624 .map(|segment| {
625 let sid = segment.meta().id;
626 let (mut results, segment_seen) = crate::query::search_segment_with_count_sync(
627 segment.as_ref(),
628 query,
629 fetch_limit,
630 )?;
631 for r in &mut results {
632 r.segment_id = sid;
633 }
634 Ok((results, segment_seen))
635 })
636 .collect();
637
638 let mut total_seen: u32 = 0;
639 let mut sorted_batches: Vec<Vec<crate::query::SearchResult>> =
640 Vec::with_capacity(batches.len());
641 for result in batches {
642 let (batch, segment_seen) = result?;
643 total_seen = total_seen.saturating_add(segment_seen);
644 if !batch.is_empty() {
645 sorted_batches.push(batch);
646 }
647 }
648
649 let results = merge_segment_results(sorted_batches, fetch_limit, offset);
650 Ok((results, total_seen))
651 }
652
653 pub async fn search_fused(
673 &self,
674 queries: &[(&dyn crate::query::Query, f32)],
675 fetch_limit: usize,
676 limit: usize,
677 method: crate::query::FusionMethod,
678 combiner: crate::query::MultiValueCombiner,
679 ) -> Result<Vec<crate::query::SearchResult>> {
680 #[cfg(feature = "sync")]
685 if !self.segments.is_empty()
686 && tokio::runtime::Handle::current().runtime_flavor()
687 == tokio::runtime::RuntimeFlavor::MultiThread
688 {
689 use rayon::prelude::*;
690 let lists: Vec<Result<(Vec<crate::query::SearchResult>, f32)>> =
691 tokio::task::block_in_place(|| {
692 queries
693 .par_iter()
694 .map(|&(query, weight)| {
695 let (results, _) =
696 self.search_internal_sync(query, fetch_limit, 0, true)?;
697 Ok((results, weight))
698 })
699 .collect()
700 });
701 let lists = lists.into_iter().collect::<Result<Vec<_>>>()?;
702 return Ok(crate::query::fuse_ranked_lists_chunked(
703 lists, method, combiner, limit,
704 ));
705 }
706
707 let mut lists = Vec::with_capacity(queries.len());
708 for &(query, weight) in queries {
709 let (results, _) = self.search_with_positions(query, fetch_limit).await?;
710 lists.push((results, weight));
711 }
712 Ok(crate::query::fuse_ranked_lists_chunked(
713 lists, method, combiner, limit,
714 ))
715 }
716
717 pub async fn search_and_rerank(
722 &self,
723 query: &dyn crate::query::Query,
724 l1_limit: usize,
725 final_limit: usize,
726 config: &crate::query::RerankerConfig,
727 ) -> Result<(Vec<crate::query::SearchResult>, u32)> {
728 let (candidates, total_seen) = self.search_with_count(query, l1_limit).await?;
729 let reranked = crate::query::rerank(self, &candidates, config, final_limit).await?;
730 Ok((reranked, total_seen))
731 }
732
733 pub async fn query(
735 &self,
736 query_str: &str,
737 limit: usize,
738 ) -> Result<crate::query::SearchResponse> {
739 self.query_offset(query_str, limit, 0).await
740 }
741
742 pub async fn query_offset(
744 &self,
745 query_str: &str,
746 limit: usize,
747 offset: usize,
748 ) -> Result<crate::query::SearchResponse> {
749 let parser = self.query_parser();
750 let query = parser
751 .parse(query_str)
752 .map_err(crate::error::Error::Query)?;
753
754 let (results, _total_seen) = self
755 .search_internal(query.as_ref(), limit, offset, false)
756 .await?;
757
758 let total_hits = results.len() as u32;
759 let hits: Vec<crate::query::SearchHit> = results
760 .into_iter()
761 .map(|result| crate::query::SearchHit {
762 address: crate::query::DocAddress::new(result.segment_id, result.doc_id),
763 score: result.score,
764 matched_fields: result.extract_ordinals(),
765 })
766 .collect();
767
768 Ok(crate::query::SearchResponse { hits, total_hits })
769 }
770
771 pub fn query_parser(&self) -> crate::dsl::QueryLanguageParser {
773 let query_routers = self.schema.query_routers();
774 if !query_routers.is_empty()
775 && let Ok(router) = crate::dsl::QueryFieldRouter::from_rules(query_routers)
776 {
777 return crate::dsl::QueryLanguageParser::with_router(
778 Arc::clone(&self.schema),
779 self.default_fields.clone(),
780 Arc::clone(&self.tokenizers),
781 router,
782 );
783 }
784
785 crate::dsl::QueryLanguageParser::new(
786 Arc::clone(&self.schema),
787 self.default_fields.clone(),
788 Arc::clone(&self.tokenizers),
789 )
790 }
791
792 pub async fn get_document(
794 &self,
795 address: &crate::query::DocAddress,
796 ) -> Result<Option<crate::dsl::Document>> {
797 self.get_document_with_fields(address, None).await
798 }
799
800 pub async fn get_document_with_fields(
806 &self,
807 address: &crate::query::DocAddress,
808 fields: Option<&rustc_hash::FxHashSet<u32>>,
809 ) -> Result<Option<crate::dsl::Document>> {
810 let segment_id = address.segment_id_u128().ok_or_else(|| {
811 crate::error::Error::Query(format!("Invalid segment ID: {}", address.segment_id()))
812 })?;
813
814 if let Some(&idx) = self.segment_map.get(&segment_id) {
815 return self.segments[idx]
816 .doc_with_fields(address.doc_id, fields)
817 .await;
818 }
819
820 Ok(None)
821 }
822}
823
824fn merge_segment_results(
829 sorted_batches: Vec<Vec<crate::query::SearchResult>>,
830 fetch_limit: usize,
831 offset: usize,
832) -> Vec<crate::query::SearchResult> {
833 use std::cmp::Ordering;
834
835 struct MergeEntry {
836 score: f32,
837 batch_idx: usize,
838 pos: usize,
839 }
840 impl PartialEq for MergeEntry {
841 fn eq(&self, other: &Self) -> bool {
842 self.score == other.score
843 }
844 }
845 impl Eq for MergeEntry {}
846 impl PartialOrd for MergeEntry {
847 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
848 Some(self.cmp(other))
849 }
850 }
851 impl Ord for MergeEntry {
852 fn cmp(&self, other: &Self) -> Ordering {
853 self.score
854 .partial_cmp(&other.score)
855 .unwrap_or(Ordering::Equal)
856 }
857 }
858
859 let mut heap = std::collections::BinaryHeap::with_capacity(sorted_batches.len());
860 for (i, batch) in sorted_batches.iter().enumerate() {
861 if !batch.is_empty() {
862 heap.push(MergeEntry {
863 score: batch[0].score,
864 batch_idx: i,
865 pos: 0,
866 });
867 }
868 }
869
870 let mut results = Vec::with_capacity(fetch_limit.min(64));
871 let mut emitted = 0usize;
872 while let Some(entry) = heap.pop() {
873 if emitted >= fetch_limit {
874 break;
875 }
876 let batch = &sorted_batches[entry.batch_idx];
877 if emitted >= offset {
878 results.push(batch[entry.pos].clone());
879 }
880 emitted += 1;
881 let next_pos = entry.pos + 1;
882 if next_pos < batch.len() {
883 heap.push(MergeEntry {
884 score: batch[next_pos].score,
885 batch_idx: entry.batch_idx,
886 pos: next_pos,
887 });
888 }
889 }
890
891 results
892}
893
894fn process_rss_mb() -> f64 {
896 #[cfg(target_os = "linux")]
897 {
898 if let Ok(status) = std::fs::read_to_string("/proc/self/status") {
900 for line in status.lines() {
901 if let Some(rest) = line.strip_prefix("VmRSS:") {
902 let kb: f64 = rest
903 .trim()
904 .trim_end_matches("kB")
905 .trim()
906 .parse()
907 .unwrap_or(0.0);
908 return kb / 1024.0;
909 }
910 }
911 }
912 0.0
913 }
914 #[cfg(target_os = "macos")]
915 {
916 use std::mem;
918 #[repr(C)]
919 struct TaskBasicInfo {
920 virtual_size: u64,
921 resident_size: u64,
922 resident_size_max: u64,
923 user_time: [u32; 2],
924 system_time: [u32; 2],
925 policy: i32,
926 suspend_count: i32,
927 }
928 unsafe extern "C" {
929 fn mach_task_self() -> u32;
930 fn task_info(task: u32, flavor: u32, info: *mut TaskBasicInfo, count: *mut u32) -> i32;
931 }
932 const MACH_TASK_BASIC_INFO: u32 = 20;
933 let mut info: TaskBasicInfo = unsafe { mem::zeroed() };
934 let mut count = (mem::size_of::<TaskBasicInfo>() / mem::size_of::<u32>()) as u32;
935 let ret = unsafe {
936 task_info(
937 mach_task_self(),
938 MACH_TASK_BASIC_INFO,
939 &mut info,
940 &mut count,
941 )
942 };
943 if ret == 0 {
944 info.resident_size as f64 / (1024.0 * 1024.0)
945 } else {
946 0.0
947 }
948 }
949 #[cfg(not(any(target_os = "linux", target_os = "macos")))]
950 {
951 0.0
952 }
953}