001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.regionserver;
019
020import java.util.List;
021
022import org.apache.hadoop.hbase.Cell;
023import org.apache.hadoop.hbase.HBaseInterfaceAudience;
024import org.apache.yetus.audience.InterfaceAudience;
025import org.apache.yetus.audience.InterfaceStability;
026import org.apache.hadoop.hbase.client.metrics.ServerSideScanMetrics;
027
028/**
029 * ScannerContext instances encapsulate limit tracking AND progress towards those limits during
030 * invocations of {@link InternalScanner#next(java.util.List)} and
031 * {@link RegionScanner#next(java.util.List)}.
032 * <p>
033 * A ScannerContext instance should be updated periodically throughout execution whenever progress
034 * towards a limit has been made. Each limit can be checked via the appropriate checkLimit method.
035 * <p>
036 * Once a limit has been reached, the scan will stop. The invoker of
037 * {@link InternalScanner#next(java.util.List)} or {@link RegionScanner#next(java.util.List)} can
038 * use the appropriate check*Limit methods to see exactly which limits have been reached.
039 * Alternatively, {@link #checkAnyLimitReached(LimitScope)} is provided to see if ANY limit was
040 * reached
041 * <p>
042 * {@link NoLimitScannerContext#NO_LIMIT} is an immutable static definition that can be used
043 * whenever a {@link ScannerContext} is needed but limits do not need to be enforced.
044 * <p>
045 * NOTE: It is important that this class only ever expose setter methods that can be safely skipped
046 * when limits should be NOT enforced. This is because of the necessary immutability of the class
047 * {@link NoLimitScannerContext}. If a setter cannot be safely skipped, the immutable nature of
048 * {@link NoLimitScannerContext} will lead to incorrect behavior.
049 */
050@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.COPROC)
051@InterfaceStability.Evolving
052public class ScannerContext {
053
054  LimitFields limits;
055  /**
056   * A different set of progress fields. Only include batch, dataSize and heapSize. Compare to
057   * LimitFields, ProgressFields doesn't contain time field. As we save a deadline in LimitFields,
058   * so use {@link System#currentTimeMillis()} directly when check time limit.
059   */
060  ProgressFields progress;
061
062  /**
063   * The state of the scanner after the invocation of {@link InternalScanner#next(java.util.List)}
064   * or {@link RegionScanner#next(java.util.List)}.
065   */
066  NextState scannerState;
067  private static final NextState DEFAULT_STATE = NextState.MORE_VALUES;
068
069  /**
070   * Used as an indication to invocations of {@link InternalScanner#next(java.util.List)} and
071   * {@link RegionScanner#next(java.util.List)} that, if true, the progress tracked within this
072   * {@link ScannerContext} instance should be considered while evaluating the limits. Useful for
073   * enforcing a set of limits across multiple calls (i.e. the limit may not be reached in a single
074   * invocation, but any progress made should be considered in future invocations)
075   * <p>
076   * Defaulting this value to false means that, by default, any tracked progress will be wiped clean
077   * on invocations to {@link InternalScanner#next(java.util.List)} and
078   * {@link RegionScanner#next(java.util.List)} and the call will be treated as though no progress
079   * has been made towards the limits so far.
080   * <p>
081   * This is an important mechanism. Users of Internal/Region scanners expect that they can define
082   * some limits and then repeatedly invoke {@link InternalScanner#next(List)} or
083   * {@link RegionScanner#next(List)} where each invocation respects these limits separately.
084   * <p>
085   * For example: <pre> {@code
086   * ScannerContext context = new ScannerContext.newBuilder().setBatchLimit(5).build();
087   * RegionScanner scanner = ...
088   * List<Cell> results = new ArrayList<Cell>();
089   * while(scanner.next(results, context)) {
090   *   // Do something with a batch of 5 cells
091   * }
092   * }</pre> However, in the case of RPCs, the server wants to be able to define a set of
093   * limits for a particular RPC request and have those limits respected across multiple
094   * invocations. This means that the progress made towards the limits in earlier calls will be
095   * saved and considered in future invocations
096   */
097  boolean keepProgress;
098  private static boolean DEFAULT_KEEP_PROGRESS = false;
099
100  private Cell lastPeekedCell = null;
101
102  /**
103   * Tracks the relevant server side metrics during scans. null when metrics should not be tracked
104   */
105  final ServerSideScanMetrics metrics;
106
107  ScannerContext(boolean keepProgress, LimitFields limitsToCopy, boolean trackMetrics) {
108    this.limits = new LimitFields();
109    if (limitsToCopy != null) {
110      this.limits.copy(limitsToCopy);
111    }
112
113    // Progress fields are initialized to 0
114    progress = new ProgressFields(0, 0, 0);
115
116    this.keepProgress = keepProgress;
117    this.scannerState = DEFAULT_STATE;
118    this.metrics = trackMetrics ? new ServerSideScanMetrics() : null;
119  }
120
121  boolean isTrackingMetrics() {
122    return this.metrics != null;
123  }
124
125  /**
126   * Get the metrics instance. Should only be called after a call to {@link #isTrackingMetrics()}
127   * has been made to confirm that metrics are indeed being tracked.
128   * @return {@link ServerSideScanMetrics} instance that is tracking metrics for this scan
129   */
130  ServerSideScanMetrics getMetrics() {
131    assert isTrackingMetrics();
132    return this.metrics;
133  }
134
135  /**
136   * @return true if the progress tracked so far in this instance will be considered during an
137   *         invocation of {@link InternalScanner#next(java.util.List)} or
138   *         {@link RegionScanner#next(java.util.List)}. false when the progress tracked so far
139   *         should not be considered and should instead be wiped away via {@link #clearProgress()}
140   */
141  boolean getKeepProgress() {
142    return keepProgress;
143  }
144
145  void setKeepProgress(boolean keepProgress) {
146    this.keepProgress = keepProgress;
147  }
148
149  /**
150   * Progress towards the batch limit has been made. Increment internal tracking of batch progress
151   */
152  void incrementBatchProgress(int batch) {
153    int currentBatch = progress.getBatch();
154    progress.setBatch(currentBatch + batch);
155  }
156
157  /**
158   * Progress towards the size limit has been made. Increment internal tracking of size progress
159   */
160  void incrementSizeProgress(long dataSize, long heapSize) {
161    long curDataSize = progress.getDataSize();
162    progress.setDataSize(curDataSize + dataSize);
163    long curHeapSize = progress.getHeapSize();
164    progress.setHeapSize(curHeapSize + heapSize);
165  }
166
167  /**
168   * Update the time progress with {@link System#currentTimeMillis()}
169   * @deprecated will be removed in 3.0
170   */
171  @Deprecated
172  void updateTimeProgress() {
173  }
174
175  int getBatchProgress() {
176    return progress.getBatch();
177  }
178
179  long getDataSizeProgress() {
180    return progress.getDataSize();
181  }
182
183  long getHeapSizeProgress() {
184    return progress.getHeapSize();
185  }
186
187  /**
188   * @deprecated will be removed in 3.0
189   */
190  @Deprecated
191  long getTimeProgress() {
192    return System.currentTimeMillis();
193  }
194
195  /**
196   * @deprecated will be removed in 3.0
197   */
198  @Deprecated
199  void setProgress(int batchProgress, long sizeProgress, long heapSizeProgress, long timeProgress) {
200    setProgress(batchProgress, sizeProgress, heapSizeProgress);
201  }
202
203  void setProgress(int batchProgress, long sizeProgress, long heapSizeProgress) {
204    setBatchProgress(batchProgress);
205    setSizeProgress(sizeProgress, heapSizeProgress);
206  }
207
208  void setSizeProgress(long dataSizeProgress, long heapSizeProgress) {
209    progress.setDataSize(dataSizeProgress);
210    progress.setHeapSize(heapSizeProgress);
211  }
212
213  void setBatchProgress(int batchProgress) {
214    progress.setBatch(batchProgress);
215  }
216
217  /**
218   * @deprecated will be removed in 3.0
219   */
220  @Deprecated
221  void setTimeProgress(long timeProgress) {
222  }
223
224  /**
225   * Clear away any progress that has been made so far. All progress fields are reset to initial
226   * values
227   */
228  void clearProgress() {
229    progress.setFields(0, 0, 0);
230  }
231
232  /**
233   * Note that this is not a typical setter. This setter returns the {@link NextState} that was
234   * passed in so that methods can be invoked against the new state. Furthermore, this pattern
235   * allows the {@link NoLimitScannerContext} to cleanly override this setter and simply return the
236   * new state, thus preserving the immutability of {@link NoLimitScannerContext}
237   * @param state
238   * @return The state that was passed in.
239   */
240  NextState setScannerState(NextState state) {
241    if (!NextState.isValidState(state)) {
242      throw new IllegalArgumentException("Cannot set to invalid state: " + state);
243    }
244
245    this.scannerState = state;
246    return state;
247  }
248
249  /**
250   * @return true when we have more cells for the current row. This usually because we have reached
251   *         a limit in the middle of a row
252   */
253  boolean mayHaveMoreCellsInRow() {
254    return scannerState == NextState.SIZE_LIMIT_REACHED_MID_ROW ||
255        scannerState == NextState.TIME_LIMIT_REACHED_MID_ROW ||
256        scannerState == NextState.BATCH_LIMIT_REACHED;
257  }
258
259  /**
260   * @param checkerScope
261   * @return true if the batch limit can be enforced in the checker's scope
262   */
263  boolean hasBatchLimit(LimitScope checkerScope) {
264    return limits.canEnforceBatchLimitFromScope(checkerScope) && limits.getBatch() > 0;
265  }
266
267  /**
268   * @param checkerScope
269   * @return true if the size limit can be enforced in the checker's scope
270   */
271  boolean hasSizeLimit(LimitScope checkerScope) {
272    return limits.canEnforceSizeLimitFromScope(checkerScope)
273        && (limits.getDataSize() > 0 || limits.getHeapSize() > 0);
274  }
275
276  /**
277   * @param checkerScope
278   * @return true if the time limit can be enforced in the checker's scope
279   */
280  boolean hasTimeLimit(LimitScope checkerScope) {
281    return limits.canEnforceTimeLimitFromScope(checkerScope) && limits.getTime() > 0;
282  }
283
284  /**
285   * @param checkerScope
286   * @return true if any limit can be enforced within the checker's scope
287   */
288  boolean hasAnyLimit(LimitScope checkerScope) {
289    return hasBatchLimit(checkerScope) || hasSizeLimit(checkerScope) || hasTimeLimit(checkerScope);
290  }
291
292  /**
293   * @param scope The scope in which the size limit will be enforced
294   */
295  void setSizeLimitScope(LimitScope scope) {
296    limits.setSizeScope(scope);
297  }
298
299  /**
300   * @param scope The scope in which the time limit will be enforced
301   */
302  void setTimeLimitScope(LimitScope scope) {
303    limits.setTimeScope(scope);
304  }
305
306  int getBatchLimit() {
307    return limits.getBatch();
308  }
309
310  long getDataSizeLimit() {
311    return limits.getDataSize();
312  }
313
314  long getTimeLimit() {
315    return limits.getTime();
316  }
317
318  /**
319   * @param checkerScope The scope that the limit is being checked from
320   * @return true when the limit is enforceable from the checker's scope and it has been reached
321   */
322  boolean checkBatchLimit(LimitScope checkerScope) {
323    return hasBatchLimit(checkerScope) && progress.getBatch() >= limits.getBatch();
324  }
325
326  /**
327   * @param checkerScope The scope that the limit is being checked from
328   * @return true when the limit is enforceable from the checker's scope and it has been reached
329   */
330  boolean checkSizeLimit(LimitScope checkerScope) {
331    return hasSizeLimit(checkerScope) && (progress.getDataSize() >= limits.getDataSize()
332        || progress.getHeapSize() >= limits.getHeapSize());
333  }
334
335  /**
336   * @param checkerScope The scope that the limit is being checked from. The time limit is always
337   *          checked against {@link System#currentTimeMillis()}
338   * @return true when the limit is enforceable from the checker's scope and it has been reached
339   */
340  boolean checkTimeLimit(LimitScope checkerScope) {
341    return hasTimeLimit(checkerScope) && (System.currentTimeMillis() >= limits.getTime());
342  }
343
344  /**
345   * @param checkerScope The scope that the limits are being checked from
346   * @return true when some limit is enforceable from the checker's scope and it has been reached
347   */
348  boolean checkAnyLimitReached(LimitScope checkerScope) {
349    return checkSizeLimit(checkerScope) || checkBatchLimit(checkerScope)
350        || checkTimeLimit(checkerScope);
351  }
352
353  Cell getLastPeekedCell() {
354    return lastPeekedCell;
355  }
356
357  void setLastPeekedCell(Cell lastPeekedCell) {
358    this.lastPeekedCell = lastPeekedCell;
359  }
360
361  @Override
362  public String toString() {
363    StringBuilder sb = new StringBuilder();
364    sb.append("{");
365
366    sb.append("limits:");
367    sb.append(limits);
368
369    sb.append(", progress:");
370    sb.append(progress);
371
372    sb.append(", keepProgress:");
373    sb.append(keepProgress);
374
375    sb.append(", state:");
376    sb.append(scannerState);
377
378    sb.append("}");
379    return sb.toString();
380  }
381
382  public static Builder newBuilder() {
383    return new Builder();
384  }
385
386  public static Builder newBuilder(boolean keepProgress) {
387    return new Builder(keepProgress);
388  }
389
390  public static final class Builder {
391    boolean keepProgress = DEFAULT_KEEP_PROGRESS;
392    boolean trackMetrics = false;
393    LimitFields limits = new LimitFields();
394
395    private Builder() {
396    }
397
398    private Builder(boolean keepProgress) {
399      this.keepProgress = keepProgress;
400    }
401
402    public Builder setKeepProgress(boolean keepProgress) {
403      this.keepProgress = keepProgress;
404      return this;
405    }
406
407    public Builder setTrackMetrics(boolean trackMetrics) {
408      this.trackMetrics = trackMetrics;
409      return this;
410    }
411
412    public Builder setSizeLimit(LimitScope sizeScope, long dataSizeLimit, long heapSizeLimit) {
413      limits.setDataSize(dataSizeLimit);
414      limits.setHeapSize(heapSizeLimit);
415      limits.setSizeScope(sizeScope);
416      return this;
417    }
418
419    public Builder setTimeLimit(LimitScope timeScope, long timeLimit) {
420      limits.setTime(timeLimit);
421      limits.setTimeScope(timeScope);
422      return this;
423    }
424
425    public Builder setBatchLimit(int batchLimit) {
426      limits.setBatch(batchLimit);
427      return this;
428    }
429
430    public ScannerContext build() {
431      return new ScannerContext(keepProgress, limits, trackMetrics);
432    }
433  }
434
435  /**
436   * The possible states a scanner may be in following a call to {@link InternalScanner#next(List)}
437   */
438  public enum NextState {
439    MORE_VALUES(true, false),
440    NO_MORE_VALUES(false, false),
441    SIZE_LIMIT_REACHED(true, true),
442
443    /**
444     * Special case of size limit reached to indicate that the size limit was reached in the middle
445     * of a row and thus a partial results was formed
446     */
447    SIZE_LIMIT_REACHED_MID_ROW(true, true),
448    TIME_LIMIT_REACHED(true, true),
449
450    /**
451     * Special case of time limit reached to indicate that the time limit was reached in the middle
452     * of a row and thus a partial results was formed
453     */
454    TIME_LIMIT_REACHED_MID_ROW(true, true),
455    BATCH_LIMIT_REACHED(true, true);
456
457    private final boolean moreValues;
458    private final boolean limitReached;
459
460    private NextState(boolean moreValues, boolean limitReached) {
461      this.moreValues = moreValues;
462      this.limitReached = limitReached;
463    }
464
465    /**
466     * @return true when the state indicates that more values may follow those that have been
467     *         returned
468     */
469    public boolean hasMoreValues() {
470      return this.moreValues;
471    }
472
473    /**
474     * @return true when the state indicates that a limit has been reached and scan should stop
475     */
476    public boolean limitReached() {
477      return this.limitReached;
478    }
479
480    public static boolean isValidState(NextState state) {
481      return state != null;
482    }
483
484    public static boolean hasMoreValues(NextState state) {
485      return isValidState(state) && state.hasMoreValues();
486    }
487  }
488
489  /**
490   * The various scopes where a limit can be enforced. Used to differentiate when a limit should be
491   * enforced or not.
492   */
493  public enum LimitScope {
494    /**
495     * Enforcing a limit between rows means that the limit will not be considered until all the
496     * cells for a particular row have been retrieved
497     */
498    BETWEEN_ROWS(0),
499
500    /**
501     * Enforcing a limit between cells means that the limit will be considered after each full cell
502     * has been retrieved
503     */
504    BETWEEN_CELLS(1);
505
506    /**
507     * When enforcing a limit, we must check that the scope is appropriate for enforcement.
508     * <p>
509     * To communicate this concept, each scope has a depth. A limit will be enforced if the depth of
510     * the checker's scope is less than or equal to the limit's scope. This means that when checking
511     * limits, the checker must know their own scope (i.e. are they checking the limits between
512     * rows, between cells, etc...)
513     */
514    final int depth;
515
516    LimitScope(int depth) {
517      this.depth = depth;
518    }
519
520    final int depth() {
521      return depth;
522    }
523
524    /**
525     * @param checkerScope The scope in which the limit is being checked
526     * @return true when the checker is in a scope that indicates the limit can be enforced. Limits
527     *         can be enforced from "higher or equal" scopes (i.e. the checker's scope is at a
528     *         lesser depth than the limit)
529     */
530    boolean canEnforceLimitFromScope(LimitScope checkerScope) {
531      return checkerScope != null && checkerScope.depth() <= depth;
532    }
533  }
534
535  /**
536   * The different fields that can be used as limits in calls to
537   * {@link InternalScanner#next(java.util.List)} and {@link RegionScanner#next(java.util.List)}
538   */
539  private static class LimitFields {
540    /**
541     * Default values of the limit fields. Defined such that if a field does NOT change from its
542     * default, it will not be enforced
543     */
544    private static int DEFAULT_BATCH = -1;
545    private static long DEFAULT_SIZE = -1L;
546    private static long DEFAULT_TIME = -1L;
547
548    /**
549     * Default scope that is assigned to a limit if a scope is not specified.
550     */
551    private static final LimitScope DEFAULT_SCOPE = LimitScope.BETWEEN_ROWS;
552
553    // The batch limit will always be enforced between cells, thus, there isn't a field to hold the
554    // batch scope
555    int batch = DEFAULT_BATCH;
556
557    LimitScope sizeScope = DEFAULT_SCOPE;
558    // The sum of cell data sizes(key + value). The Cell data might be in on heap or off heap area.
559    long dataSize = DEFAULT_SIZE;
560    // The sum of heap space occupied by all tracked cells. This includes Cell POJO's overhead as
561    // such AND data cells of Cells which are in on heap area.
562    long heapSize = DEFAULT_SIZE;
563
564    LimitScope timeScope = DEFAULT_SCOPE;
565    long time = DEFAULT_TIME;
566
567    /**
568     * Fields keep their default values.
569     */
570    LimitFields() {
571    }
572
573    LimitFields(int batch, LimitScope sizeScope, long size, long heapSize, LimitScope timeScope,
574        long time) {
575      setFields(batch, sizeScope, size, heapSize, timeScope, time);
576    }
577
578    void copy(LimitFields limitsToCopy) {
579      if (limitsToCopy != null) {
580        setFields(limitsToCopy.getBatch(), limitsToCopy.getSizeScope(), limitsToCopy.getDataSize(),
581            limitsToCopy.getHeapSize(), limitsToCopy.getTimeScope(), limitsToCopy.getTime());
582      }
583    }
584
585    /**
586     * Set all fields together.
587     */
588    void setFields(int batch, LimitScope sizeScope, long dataSize, long heapSize,
589        LimitScope timeScope, long time) {
590      setBatch(batch);
591      setSizeScope(sizeScope);
592      setDataSize(dataSize);
593      setHeapSize(heapSize);
594      setTimeScope(timeScope);
595      setTime(time);
596    }
597
598    int getBatch() {
599      return this.batch;
600    }
601
602    void setBatch(int batch) {
603      this.batch = batch;
604    }
605
606    /**
607     * @param checkerScope
608     * @return true when the limit can be enforced from the scope of the checker
609     */
610    boolean canEnforceBatchLimitFromScope(LimitScope checkerScope) {
611      return LimitScope.BETWEEN_CELLS.canEnforceLimitFromScope(checkerScope);
612    }
613
614    long getDataSize() {
615      return this.dataSize;
616    }
617
618    long getHeapSize() {
619      return this.heapSize;
620    }
621
622    void setDataSize(long dataSize) {
623      this.dataSize = dataSize;
624    }
625
626    void setHeapSize(long heapSize) {
627      this.heapSize = heapSize;
628    }
629
630    /**
631     * @return {@link LimitScope} indicating scope in which the size limit is enforced
632     */
633    LimitScope getSizeScope() {
634      return this.sizeScope;
635    }
636
637    /**
638     * Change the scope in which the size limit is enforced
639     */
640    void setSizeScope(LimitScope scope) {
641      this.sizeScope = scope;
642    }
643
644    /**
645     * @param checkerScope
646     * @return true when the limit can be enforced from the scope of the checker
647     */
648    boolean canEnforceSizeLimitFromScope(LimitScope checkerScope) {
649      return this.sizeScope.canEnforceLimitFromScope(checkerScope);
650    }
651
652    long getTime() {
653      return this.time;
654    }
655
656    void setTime(long time) {
657      this.time = time;
658    }
659
660    /**
661     * @return {@link LimitScope} indicating scope in which the time limit is enforced
662     */
663    LimitScope getTimeScope() {
664      return this.timeScope;
665    }
666
667    /**
668     * Change the scope in which the time limit is enforced
669     */
670    void setTimeScope(LimitScope scope) {
671      this.timeScope = scope;
672    }
673
674    /**
675     * @param checkerScope
676     * @return true when the limit can be enforced from the scope of the checker
677     */
678    boolean canEnforceTimeLimitFromScope(LimitScope checkerScope) {
679      return this.timeScope.canEnforceLimitFromScope(checkerScope);
680    }
681
682    @Override
683    public String toString() {
684      StringBuilder sb = new StringBuilder();
685      sb.append("{");
686
687      sb.append("batch:");
688      sb.append(batch);
689
690      sb.append(", dataSize:");
691      sb.append(dataSize);
692
693      sb.append(", heapSize:");
694      sb.append(heapSize);
695
696      sb.append(", sizeScope:");
697      sb.append(sizeScope);
698
699      sb.append(", time:");
700      sb.append(time);
701
702      sb.append(", timeScope:");
703      sb.append(timeScope);
704
705      sb.append("}");
706      return sb.toString();
707    }
708  }
709
710  private static class ProgressFields {
711
712    private static int DEFAULT_BATCH = -1;
713    private static long DEFAULT_SIZE = -1L;
714
715    // The batch limit will always be enforced between cells, thus, there isn't a field to hold the
716    // batch scope
717    int batch = DEFAULT_BATCH;
718
719    // The sum of cell data sizes(key + value). The Cell data might be in on heap or off heap area.
720    long dataSize = DEFAULT_SIZE;
721    // The sum of heap space occupied by all tracked cells. This includes Cell POJO's overhead as
722    // such AND data cells of Cells which are in on heap area.
723    long heapSize = DEFAULT_SIZE;
724
725    /**
726     * Fields keep their default values.
727     */
728    ProgressFields() {
729    }
730
731    ProgressFields(int batch, long size, long heapSize) {
732      setFields(batch, size, heapSize);
733    }
734
735    /**
736     * Set all fields together.
737     */
738    void setFields(int batch, long dataSize, long heapSize) {
739      setBatch(batch);
740      setDataSize(dataSize);
741      setHeapSize(heapSize);
742    }
743
744    int getBatch() {
745      return this.batch;
746    }
747
748    void setBatch(int batch) {
749      this.batch = batch;
750    }
751
752    long getDataSize() {
753      return this.dataSize;
754    }
755
756    long getHeapSize() {
757      return this.heapSize;
758    }
759
760    void setDataSize(long dataSize) {
761      this.dataSize = dataSize;
762    }
763
764    void setHeapSize(long heapSize) {
765      this.heapSize = heapSize;
766    }
767
768    @Override
769    public String toString() {
770      StringBuilder sb = new StringBuilder();
771      sb.append("{");
772
773      sb.append("batch:");
774      sb.append(batch);
775
776      sb.append(", dataSize:");
777      sb.append(dataSize);
778
779      sb.append(", heapSize:");
780      sb.append(heapSize);
781
782      sb.append("}");
783      return sb.toString();
784    }
785  }
786}