Merge "No .shift in circular buffer"
diff --git a/ui/src/frontend/perf.ts b/ui/src/frontend/perf.ts
index 3a6e938..919f1e6 100644
--- a/ui/src/frontend/perf.ts
+++ b/ui/src/frontend/perf.ts
@@ -40,6 +40,7 @@
   private _count = 0;
   private _mean = 0;
   private _lastValue = 0;
+  private _ptr = 0;
 
   private buffer: number[] = [];
 
@@ -47,10 +48,15 @@
 
   addValue(value: number) {
     this._lastValue = value;
-    this.buffer.push(value);
-    if (this.buffer.length > this._maxBufferSize) {
-      this.buffer.shift();
+    if (this.buffer.length >= this._maxBufferSize) {
+      this.buffer[this._ptr++] = value;
+      if (this._ptr >= this.buffer.length) {
+        this._ptr -= this.buffer.length;
+      }
+    } else {
+      this.buffer.push(value);
     }
+
     this._mean = (this._mean * this._count + value) / (this._count + 1);
     this._count++;
   }
diff --git a/ui/src/frontend/perf_unittest.ts b/ui/src/frontend/perf_unittest.ts
new file mode 100644
index 0000000..5ba357c
--- /dev/null
+++ b/ui/src/frontend/perf_unittest.ts
@@ -0,0 +1,57 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import {RunningStatistics} from './perf';
+
+test('buffer size is accurate before reaching max capacity', () => {
+  const buf = new RunningStatistics(10);
+
+  for (let i = 0; i < 10; i++) {
+    buf.addValue(i);
+    expect(buf.bufferSize).toEqual(i + 1);
+  }
+});
+
+test('buffer size is accurate after reaching max capacity', () => {
+  const buf = new RunningStatistics(10);
+
+  for (let i = 0; i < 10; i++) {
+    buf.addValue(i);
+  }
+
+  for (let i = 0; i < 10; i++) {
+    buf.addValue(i);
+    expect(buf.bufferSize).toEqual(10);
+  }
+});
+
+test('buffer mean is accurate before reaching max capacity', () => {
+  const buf = new RunningStatistics(10);
+
+  buf.addValue(1);
+  buf.addValue(2);
+  buf.addValue(3);
+
+  expect(buf.bufferMean).toBeCloseTo(2);
+});
+
+test('buffer mean is accurate after reaching max capacity', () => {
+  const buf = new RunningStatistics(10);
+
+  for (let i = 0; i < 20; i++) {
+    buf.addValue(2);
+  }
+
+  expect(buf.bufferMean).toBeCloseTo(2);
+});