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  // Set this to true will have the same behavior with reaching the time limit.
103  // This is used when you want to make the current RSRpcService.scan returns immediately. For
104  // example, when we want to switch from pread to stream, we can only do it after the rpc call is
105  // returned.
106  private boolean returnImmediately;
107
108  /**
109   * Tracks the relevant server side metrics during scans. null when metrics should not be tracked
110   */
111  final ServerSideScanMetrics metrics;
112
113  ScannerContext(boolean keepProgress, LimitFields limitsToCopy, boolean trackMetrics) {
114    this.limits = new LimitFields();
115    if (limitsToCopy != null) {
116      this.limits.copy(limitsToCopy);
117    }
118
119    // Progress fields are initialized to 0
120    progress = new ProgressFields(0, 0, 0);
121
122    this.keepProgress = keepProgress;
123    this.scannerState = DEFAULT_STATE;
124    this.metrics = trackMetrics ? new ServerSideScanMetrics() : null;
125  }
126
127  public boolean isTrackingMetrics() {
128    return this.metrics != null;
129  }
130
131  /**
132   * Get the metrics instance. Should only be called after a call to {@link #isTrackingMetrics()}
133   * has been made to confirm that metrics are indeed being tracked.
134   * @return {@link ServerSideScanMetrics} instance that is tracking metrics for this scan
135   */
136  public ServerSideScanMetrics getMetrics() {
137    assert isTrackingMetrics();
138    return this.metrics;
139  }
140
141  /**
142   * @return true if the progress tracked so far in this instance will be considered during an
143   *         invocation of {@link InternalScanner#next(java.util.List)} or
144   *         {@link RegionScanner#next(java.util.List)}. false when the progress tracked so far
145   *         should not be considered and should instead be wiped away via {@link #clearProgress()}
146   */
147  boolean getKeepProgress() {
148    return keepProgress;
149  }
150
151  void setKeepProgress(boolean keepProgress) {
152    this.keepProgress = keepProgress;
153  }
154
155  /**
156   * Progress towards the batch limit has been made. Increment internal tracking of batch progress
157   */
158  void incrementBatchProgress(int batch) {
159    int currentBatch = progress.getBatch();
160    progress.setBatch(currentBatch + batch);
161  }
162
163  /**
164   * Progress towards the size limit has been made. Increment internal tracking of size progress
165   */
166  void incrementSizeProgress(long dataSize, long heapSize) {
167    long curDataSize = progress.getDataSize();
168    progress.setDataSize(curDataSize + dataSize);
169    long curHeapSize = progress.getHeapSize();
170    progress.setHeapSize(curHeapSize + heapSize);
171  }
172
173  /**
174   * Update the time progress with {@link System#currentTimeMillis()}
175   * @deprecated will be removed in 3.0
176   */
177  @Deprecated
178  void updateTimeProgress() {
179  }
180
181  int getBatchProgress() {
182    return progress.getBatch();
183  }
184
185  long getDataSizeProgress() {
186    return progress.getDataSize();
187  }
188
189  long getHeapSizeProgress() {
190    return progress.getHeapSize();
191  }
192
193  /**
194   * @deprecated will be removed in 3.0
195   */
196  @Deprecated
197  long getTimeProgress() {
198    return System.currentTimeMillis();
199  }
200
201  /**
202   * @deprecated will be removed in 3.0
203   */
204  @Deprecated
205  void setProgress(int batchProgress, long sizeProgress, long heapSizeProgress, long timeProgress) {
206    setProgress(batchProgress, sizeProgress, heapSizeProgress);
207  }
208
209  void setProgress(int batchProgress, long sizeProgress, long heapSizeProgress) {
210    setBatchProgress(batchProgress);
211    setSizeProgress(sizeProgress, heapSizeProgress);
212  }
213
214  void setSizeProgress(long dataSizeProgress, long heapSizeProgress) {
215    progress.setDataSize(dataSizeProgress);
216    progress.setHeapSize(heapSizeProgress);
217  }
218
219  void setBatchProgress(int batchProgress) {
220    progress.setBatch(batchProgress);
221  }
222
223  /**
224   * @deprecated will be removed in 3.0
225   */
226  @Deprecated
227  void setTimeProgress(long timeProgress) {
228  }
229
230  /**
231   * Clear away any progress that has been made so far. All progress fields are reset to initial
232   * values
233   */
234  void clearProgress() {
235    progress.setFields(0, 0, 0);
236  }
237
238  /**
239   * Note that this is not a typical setter. This setter returns the {@link NextState} that was
240   * passed in so that methods can be invoked against the new state. Furthermore, this pattern
241   * allows the {@link NoLimitScannerContext} to cleanly override this setter and simply return the
242   * new state, thus preserving the immutability of {@link NoLimitScannerContext}
243   * @param state
244   * @return The state that was passed in.
245   */
246  NextState setScannerState(NextState state) {
247    if (!NextState.isValidState(state)) {
248      throw new IllegalArgumentException("Cannot set to invalid state: " + state);
249    }
250
251    this.scannerState = state;
252    return state;
253  }
254
255  /**
256   * @return true when we have more cells for the current row. This usually because we have reached
257   *         a limit in the middle of a row
258   */
259  boolean mayHaveMoreCellsInRow() {
260    return scannerState == NextState.SIZE_LIMIT_REACHED_MID_ROW ||
261        scannerState == NextState.TIME_LIMIT_REACHED_MID_ROW ||
262        scannerState == NextState.BATCH_LIMIT_REACHED;
263  }
264
265  /**
266   * @param checkerScope
267   * @return true if the batch limit can be enforced in the checker's scope
268   */
269  boolean hasBatchLimit(LimitScope checkerScope) {
270    return limits.canEnforceBatchLimitFromScope(checkerScope) && limits.getBatch() > 0;
271  }
272
273  /**
274   * @param checkerScope
275   * @return true if the size limit can be enforced in the checker's scope
276   */
277  boolean hasSizeLimit(LimitScope checkerScope) {
278    return limits.canEnforceSizeLimitFromScope(checkerScope)
279        && (limits.getDataSize() > 0 || limits.getHeapSize() > 0);
280  }
281
282  /**
283   * @param checkerScope
284   * @return true if the time limit can be enforced in the checker's scope
285   */
286  boolean hasTimeLimit(LimitScope checkerScope) {
287    return limits.canEnforceTimeLimitFromScope(checkerScope) &&
288      (limits.getTime() > 0 || returnImmediately);
289  }
290
291  /**
292   * @param checkerScope
293   * @return true if any limit can be enforced within the checker's scope
294   */
295  boolean hasAnyLimit(LimitScope checkerScope) {
296    return hasBatchLimit(checkerScope) || hasSizeLimit(checkerScope) || hasTimeLimit(checkerScope);
297  }
298
299  /**
300   * @param scope The scope in which the size limit will be enforced
301   */
302  void setSizeLimitScope(LimitScope scope) {
303    limits.setSizeScope(scope);
304  }
305
306  /**
307   * @param scope The scope in which the time limit will be enforced
308   */
309  void setTimeLimitScope(LimitScope scope) {
310    limits.setTimeScope(scope);
311  }
312
313  int getBatchLimit() {
314    return limits.getBatch();
315  }
316
317  long getDataSizeLimit() {
318    return limits.getDataSize();
319  }
320
321  long getTimeLimit() {
322    return limits.getTime();
323  }
324
325  /**
326   * @param checkerScope The scope that the limit is being checked from
327   * @return true when the limit is enforceable from the checker's scope and it has been reached
328   */
329  boolean checkBatchLimit(LimitScope checkerScope) {
330    return hasBatchLimit(checkerScope) && progress.getBatch() >= limits.getBatch();
331  }
332
333  /**
334   * @param checkerScope The scope that the limit is being checked from
335   * @return true when the limit is enforceable from the checker's scope and it has been reached
336   */
337  boolean checkSizeLimit(LimitScope checkerScope) {
338    return hasSizeLimit(checkerScope) && (progress.getDataSize() >= limits.getDataSize()
339        || progress.getHeapSize() >= limits.getHeapSize());
340  }
341
342  /**
343   * @param checkerScope The scope that the limit is being checked from. The time limit is always
344   *          checked against {@link System#currentTimeMillis()}
345   * @return true when the limit is enforceable from the checker's scope and it has been reached
346   */
347  boolean checkTimeLimit(LimitScope checkerScope) {
348    return hasTimeLimit(checkerScope) &&
349      (returnImmediately || System.currentTimeMillis() >= limits.getTime());
350  }
351
352  /**
353   * @param checkerScope The scope that the limits are being checked from
354   * @return true when some limit is enforceable from the checker's scope and it has been reached
355   */
356  boolean checkAnyLimitReached(LimitScope checkerScope) {
357    return checkSizeLimit(checkerScope) || checkBatchLimit(checkerScope)
358        || checkTimeLimit(checkerScope);
359  }
360
361  Cell getLastPeekedCell() {
362    return lastPeekedCell;
363  }
364
365  void setLastPeekedCell(Cell lastPeekedCell) {
366    this.lastPeekedCell = lastPeekedCell;
367  }
368
369  void returnImmediately() {
370    this.returnImmediately = true;
371  }
372
373  @Override
374  public String toString() {
375    StringBuilder sb = new StringBuilder();
376    sb.append("{");
377
378    sb.append("limits:");
379    sb.append(limits);
380
381    sb.append(", progress:");
382    sb.append(progress);
383
384    sb.append(", keepProgress:");
385    sb.append(keepProgress);
386
387    sb.append(", state:");
388    sb.append(scannerState);
389
390    sb.append("}");
391    return sb.toString();
392  }
393
394  public static Builder newBuilder() {
395    return new Builder();
396  }
397
398  public static Builder newBuilder(boolean keepProgress) {
399    return new Builder(keepProgress);
400  }
401
402  public static final class Builder {
403    boolean keepProgress = DEFAULT_KEEP_PROGRESS;
404    boolean trackMetrics = false;
405    LimitFields limits = new LimitFields();
406
407    private Builder() {
408    }
409
410    private Builder(boolean keepProgress) {
411      this.keepProgress = keepProgress;
412    }
413
414    public Builder setKeepProgress(boolean keepProgress) {
415      this.keepProgress = keepProgress;
416      return this;
417    }
418
419    public Builder setTrackMetrics(boolean trackMetrics) {
420      this.trackMetrics = trackMetrics;
421      return this;
422    }
423
424    public Builder setSizeLimit(LimitScope sizeScope, long dataSizeLimit, long heapSizeLimit) {
425      limits.setDataSize(dataSizeLimit);
426      limits.setHeapSize(heapSizeLimit);
427      limits.setSizeScope(sizeScope);
428      return this;
429    }
430
431    public Builder setTimeLimit(LimitScope timeScope, long timeLimit) {
432      limits.setTime(timeLimit);
433      limits.setTimeScope(timeScope);
434      return this;
435    }
436
437    public Builder setBatchLimit(int batchLimit) {
438      limits.setBatch(batchLimit);
439      return this;
440    }
441
442    public ScannerContext build() {
443      return new ScannerContext(keepProgress, limits, trackMetrics);
444    }
445  }
446
447  /**
448   * The possible states a scanner may be in following a call to {@link InternalScanner#next(List)}
449   */
450  public enum NextState {
451    MORE_VALUES(true, false),
452    NO_MORE_VALUES(false, false),
453    SIZE_LIMIT_REACHED(true, true),
454
455    /**
456     * Special case of size limit reached to indicate that the size limit was reached in the middle
457     * of a row and thus a partial results was formed
458     */
459    SIZE_LIMIT_REACHED_MID_ROW(true, true),
460    TIME_LIMIT_REACHED(true, true),
461
462    /**
463     * Special case of time limit reached to indicate that the time limit was reached in the middle
464     * of a row and thus a partial results was formed
465     */
466    TIME_LIMIT_REACHED_MID_ROW(true, true),
467    BATCH_LIMIT_REACHED(true, true);
468
469    private final boolean moreValues;
470    private final boolean limitReached;
471
472    private NextState(boolean moreValues, boolean limitReached) {
473      this.moreValues = moreValues;
474      this.limitReached = limitReached;
475    }
476
477    /**
478     * @return true when the state indicates that more values may follow those that have been
479     *         returned
480     */
481    public boolean hasMoreValues() {
482      return this.moreValues;
483    }
484
485    /**
486     * @return true when the state indicates that a limit has been reached and scan should stop
487     */
488    public boolean limitReached() {
489      return this.limitReached;
490    }
491
492    public static boolean isValidState(NextState state) {
493      return state != null;
494    }
495
496    public static boolean hasMoreValues(NextState state) {
497      return isValidState(state) && state.hasMoreValues();
498    }
499  }
500
501  /**
502   * The various scopes where a limit can be enforced. Used to differentiate when a limit should be
503   * enforced or not.
504   */
505  public enum LimitScope {
506    /**
507     * Enforcing a limit between rows means that the limit will not be considered until all the
508     * cells for a particular row have been retrieved
509     */
510    BETWEEN_ROWS(0),
511
512    /**
513     * Enforcing a limit between cells means that the limit will be considered after each full cell
514     * has been retrieved
515     */
516    BETWEEN_CELLS(1);
517
518    /**
519     * When enforcing a limit, we must check that the scope is appropriate for enforcement.
520     * <p>
521     * To communicate this concept, each scope has a depth. A limit will be enforced if the depth of
522     * the checker's scope is less than or equal to the limit's scope. This means that when checking
523     * limits, the checker must know their own scope (i.e. are they checking the limits between
524     * rows, between cells, etc...)
525     */
526    final int depth;
527
528    LimitScope(int depth) {
529      this.depth = depth;
530    }
531
532    final int depth() {
533      return depth;
534    }
535
536    /**
537     * @param checkerScope The scope in which the limit is being checked
538     * @return true when the checker is in a scope that indicates the limit can be enforced. Limits
539     *         can be enforced from "higher or equal" scopes (i.e. the checker's scope is at a
540     *         lesser depth than the limit)
541     */
542    boolean canEnforceLimitFromScope(LimitScope checkerScope) {
543      return checkerScope != null && checkerScope.depth() <= depth;
544    }
545  }
546
547  /**
548   * The different fields that can be used as limits in calls to
549   * {@link InternalScanner#next(java.util.List)} and {@link RegionScanner#next(java.util.List)}
550   */
551  private static class LimitFields {
552    /**
553     * Default values of the limit fields. Defined such that if a field does NOT change from its
554     * default, it will not be enforced
555     */
556    private static int DEFAULT_BATCH = -1;
557    private static long DEFAULT_SIZE = -1L;
558    private static long DEFAULT_TIME = -1L;
559
560    /**
561     * Default scope that is assigned to a limit if a scope is not specified.
562     */
563    private static final LimitScope DEFAULT_SCOPE = LimitScope.BETWEEN_ROWS;
564
565    // The batch limit will always be enforced between cells, thus, there isn't a field to hold the
566    // batch scope
567    int batch = DEFAULT_BATCH;
568
569    LimitScope sizeScope = DEFAULT_SCOPE;
570    // The sum of cell data sizes(key + value). The Cell data might be in on heap or off heap area.
571    long dataSize = DEFAULT_SIZE;
572    // The sum of heap space occupied by all tracked cells. This includes Cell POJO's overhead as
573    // such AND data cells of Cells which are in on heap area.
574    long heapSize = DEFAULT_SIZE;
575
576    LimitScope timeScope = DEFAULT_SCOPE;
577    long time = DEFAULT_TIME;
578
579    /**
580     * Fields keep their default values.
581     */
582    LimitFields() {
583    }
584
585    void copy(LimitFields limitsToCopy) {
586      if (limitsToCopy != null) {
587        setFields(limitsToCopy.getBatch(), limitsToCopy.getSizeScope(), limitsToCopy.getDataSize(),
588            limitsToCopy.getHeapSize(), limitsToCopy.getTimeScope(), limitsToCopy.getTime());
589      }
590    }
591
592    /**
593     * Set all fields together.
594     */
595    void setFields(int batch, LimitScope sizeScope, long dataSize, long heapSize,
596        LimitScope timeScope, long time) {
597      setBatch(batch);
598      setSizeScope(sizeScope);
599      setDataSize(dataSize);
600      setHeapSize(heapSize);
601      setTimeScope(timeScope);
602      setTime(time);
603    }
604
605    int getBatch() {
606      return this.batch;
607    }
608
609    void setBatch(int batch) {
610      this.batch = batch;
611    }
612
613    /**
614     * @param checkerScope
615     * @return true when the limit can be enforced from the scope of the checker
616     */
617    boolean canEnforceBatchLimitFromScope(LimitScope checkerScope) {
618      return LimitScope.BETWEEN_CELLS.canEnforceLimitFromScope(checkerScope);
619    }
620
621    long getDataSize() {
622      return this.dataSize;
623    }
624
625    long getHeapSize() {
626      return this.heapSize;
627    }
628
629    void setDataSize(long dataSize) {
630      this.dataSize = dataSize;
631    }
632
633    void setHeapSize(long heapSize) {
634      this.heapSize = heapSize;
635    }
636
637    /**
638     * @return {@link LimitScope} indicating scope in which the size limit is enforced
639     */
640    LimitScope getSizeScope() {
641      return this.sizeScope;
642    }
643
644    /**
645     * Change the scope in which the size limit is enforced
646     */
647    void setSizeScope(LimitScope scope) {
648      this.sizeScope = scope;
649    }
650
651    /**
652     * @param checkerScope
653     * @return true when the limit can be enforced from the scope of the checker
654     */
655    boolean canEnforceSizeLimitFromScope(LimitScope checkerScope) {
656      return this.sizeScope.canEnforceLimitFromScope(checkerScope);
657    }
658
659    long getTime() {
660      return this.time;
661    }
662
663    void setTime(long time) {
664      this.time = time;
665    }
666
667    /**
668     * @return {@link LimitScope} indicating scope in which the time limit is enforced
669     */
670    LimitScope getTimeScope() {
671      return this.timeScope;
672    }
673
674    /**
675     * Change the scope in which the time limit is enforced
676     */
677    void setTimeScope(LimitScope scope) {
678      this.timeScope = scope;
679    }
680
681    /**
682     * @param checkerScope
683     * @return true when the limit can be enforced from the scope of the checker
684     */
685    boolean canEnforceTimeLimitFromScope(LimitScope checkerScope) {
686      return this.timeScope.canEnforceLimitFromScope(checkerScope);
687    }
688
689    @Override
690    public String toString() {
691      StringBuilder sb = new StringBuilder();
692      sb.append("{");
693
694      sb.append("batch:");
695      sb.append(batch);
696
697      sb.append(", dataSize:");
698      sb.append(dataSize);
699
700      sb.append(", heapSize:");
701      sb.append(heapSize);
702
703      sb.append(", sizeScope:");
704      sb.append(sizeScope);
705
706      sb.append(", time:");
707      sb.append(time);
708
709      sb.append(", timeScope:");
710      sb.append(timeScope);
711
712      sb.append("}");
713      return sb.toString();
714    }
715  }
716
717  private static class ProgressFields {
718
719    private static int DEFAULT_BATCH = -1;
720    private static long DEFAULT_SIZE = -1L;
721
722    // The batch limit will always be enforced between cells, thus, there isn't a field to hold the
723    // batch scope
724    int batch = DEFAULT_BATCH;
725
726    // The sum of cell data sizes(key + value). The Cell data might be in on heap or off heap area.
727    long dataSize = DEFAULT_SIZE;
728    // The sum of heap space occupied by all tracked cells. This includes Cell POJO's overhead as
729    // such AND data cells of Cells which are in on heap area.
730    long heapSize = DEFAULT_SIZE;
731
732    ProgressFields(int batch, long size, long heapSize) {
733      setFields(batch, size, heapSize);
734    }
735
736    /**
737     * Set all fields together.
738     */
739    void setFields(int batch, long dataSize, long heapSize) {
740      setBatch(batch);
741      setDataSize(dataSize);
742      setHeapSize(heapSize);
743    }
744
745    int getBatch() {
746      return this.batch;
747    }
748
749    void setBatch(int batch) {
750      this.batch = batch;
751    }
752
753    long getDataSize() {
754      return this.dataSize;
755    }
756
757    long getHeapSize() {
758      return this.heapSize;
759    }
760
761    void setDataSize(long dataSize) {
762      this.dataSize = dataSize;
763    }
764
765    void setHeapSize(long heapSize) {
766      this.heapSize = heapSize;
767    }
768
769    @Override
770    public String toString() {
771      StringBuilder sb = new StringBuilder();
772      sb.append("{");
773
774      sb.append("batch:");
775      sb.append(batch);
776
777      sb.append(", dataSize:");
778      sb.append(dataSize);
779
780      sb.append(", heapSize:");
781      sb.append(heapSize);
782
783      sb.append("}");
784      return sb.toString();
785    }
786  }
787}