diff --git a/ember-resources/package.json b/ember-resources/package.json
index cbcad26c2..dfeb25b52 100644
--- a/ember-resources/package.json
+++ b/ember-resources/package.json
@@ -15,6 +15,7 @@
"./util": "./dist/util/index.js",
"./util/cell": "./dist/util/cell.js",
"./util/keep-latest": "./dist/util/keep-latest.js",
+ "./util/fps": "./dist/util/fps.js",
"./util/map": "./dist/util/map.js",
"./util/helper": "./dist/util/helper.js",
"./util/remote-data": "./dist/util/remote-data.js",
@@ -43,6 +44,9 @@
"util/function": [
"dist/util/function.d.ts"
],
+ "util/fps": [
+ "dist/util/fps.d.ts"
+ ],
"util/map": [
"dist/util/map.d.ts"
],
diff --git a/ember-resources/src/util/fps.ts b/ember-resources/src/util/fps.ts
new file mode 100644
index 000000000..8806d2f35
--- /dev/null
+++ b/ember-resources/src/util/fps.ts
@@ -0,0 +1,88 @@
+import { cell, resource, resourceFactory } from '../index';
+
+/**
+ * Utility that uses requestAnimationFrame to report
+ * how many frames per second the current monitor is
+ * rendering at.
+ *
+ * The result is rounded to two decimal places.
+ *
+ * ```js
+ * import { FramRate } from 'ember-resources/util/fps';
+ *
+ *
+ * {{FrameRate}}
+ *
+ * ```
+ */
+export const FrameRate = resource(({ on }) => {
+ let value = cell(0);
+ let startTime = new Date().getTime();
+ let frame: number;
+
+ let update = () => {
+ // simulate receiving data as fast as possible
+ frame = requestAnimationFrame(() => {
+ value.current++;
+ update();
+ });
+ };
+
+ on.cleanup(() => cancelAnimationFrame(frame));
+
+ // Start the infinite requestAnimationFrame chain
+ update();
+
+ return () => {
+ let elapsed = (new Date().getTime() - startTime) * 0.001;
+ let fps = value.current * Math.pow(elapsed, -1);
+ let rounded = Math.round(fps * 100) * 0.01;
+ // account for https://stackoverflow.com/a/588014/356849
+ let formatted = `${rounded}`.substring(0, 5);
+
+ return formatted;
+ };
+});
+
+/**
+ * Utility that will report the frequency of updates to tracked data.
+ *
+ * ```js
+ * import { UpdateFrequency } from 'ember-resources/util/fps';
+ *
+ * export default class Demo extends Component {
+ * @tracked someProp;
+ *
+ * @use updateFrequency = UpdateFrequency(() => this.someProp);
+ *
+ *
+ * {{this.updateFrequency}}
+ *
+ * }
+ * ```
+ *
+ * NOTE: the function passed to UpdateFrequency may not set tracked data.
+ */
+export const UpdateFrequency = resourceFactory((ofWhat: () => void, updateInterval: number) => {
+ updateInterval ||= 500;
+
+ let multiplier = 1000 / updateInterval;
+ let framesSinceUpdate = 0;
+
+ return resource(({ on }) => {
+ let value = cell(0);
+ let interval = setInterval(() => {
+ value.current = framesSinceUpdate * multiplier;
+ framesSinceUpdate = 0;
+ }, updateInterval);
+
+ on.cleanup(() => clearInterval(interval));
+
+ return () => {
+ ofWhat();
+ framesSinceUpdate++;
+
+ return value.current;
+ };
+ });
+});
diff --git a/test-app/tests/type-tests/types.ts b/test-app/tests/type-tests/types.ts
index 1ecda30b4..59a2de940 100644
--- a/test-app/tests/type-tests/types.ts
+++ b/test-app/tests/type-tests/types.ts
@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
/* eslint-disable @typescript-eslint/ban-types */
-import { expectType, type TypeEqual } from 'ts-expect';
+import { expectType,type TypeEqual } from 'ts-expect';
import type { ExpandArgs } from 'ember-resources';