From 79aef6a36f318c702bf32c350826ee33cf70206a Mon Sep 17 00:00:00 2001
From: Brian Vaughn <bvaughn@fb.com>
Date: Tue, 18 Jan 2022 10:08:56 -0500
Subject: [PATCH 1/5] DevTools only records Timeline data when Profiling

React will call the DevTools profiling hooks unconditionally (for DEV and profiling builds) but DevTools will only log the data to the User Timing API when DevTools is profiling.
---
 .../src/__tests__/TimelineProfiler-test.js    | 732 +++++++++---------
 .../src/__tests__/preprocessData-test.js      |   5 +
 .../src/backend/profilingHooks.js             |  87 ++-
 .../src/backend/renderer.js                   |  29 +-
 4 files changed, 458 insertions(+), 395 deletions(-)

diff --git a/packages/react-devtools-shared/src/__tests__/TimelineProfiler-test.js b/packages/react-devtools-shared/src/__tests__/TimelineProfiler-test.js
index 1a369acca27bd..3a6c96c476d27 100644
--- a/packages/react-devtools-shared/src/__tests__/TimelineProfiler-test.js
+++ b/packages/react-devtools-shared/src/__tests__/TimelineProfiler-test.js
@@ -118,10 +118,17 @@ describe('Timeline profiler', () => {
     setPerformanceMock(null);
   });
 
-  it('should mark sync render without suspends or state updates', () => {
-    renderHelper(<div />);
+  describe('when profiling', () => {
+    beforeEach(() => {
+      const store = global.store;
 
-    expect(clearedMarks).toMatchInlineSnapshot(`
+      utils.act(() => store.profilerStore.startProfiling());
+    });
+
+    it('should mark sync render without suspends or state updates', () => {
+      renderHelper(<div />);
+
+      expect(clearedMarks).toMatchInlineSnapshot(`
       Array [
         "--schedule-render-1",
         "--render-start-1",
@@ -137,22 +144,22 @@ describe('Timeline profiler', () => {
         "--commit-stop",
       ]
     `);
-  });
+    });
 
-  it('should mark concurrent render without suspends or state updates', () => {
-    renderRootHelper(<div />);
+    it('should mark concurrent render without suspends or state updates', () => {
+      renderRootHelper(<div />);
 
-    expect(clearedMarks).toMatchInlineSnapshot(`
+      expect(clearedMarks).toMatchInlineSnapshot(`
       Array [
         "--schedule-render-16",
       ]
     `);
 
-    clearPendingMarks();
+      clearPendingMarks();
 
-    expect(Scheduler).toFlushUntilNextPaint([]);
+      expect(Scheduler).toFlushUntilNextPaint([]);
 
-    expect(clearedMarks).toMatchInlineSnapshot(`
+      expect(clearedMarks).toMatchInlineSnapshot(`
       Array [
         "--render-start-16",
         "--render-stop",
@@ -167,27 +174,27 @@ describe('Timeline profiler', () => {
         "--commit-stop",
       ]
     `);
-  });
+    });
 
-  it('should mark render yields', async () => {
-    function Bar() {
-      Scheduler.unstable_yieldValue('Bar');
-      return null;
-    }
+    it('should mark render yields', async () => {
+      function Bar() {
+        Scheduler.unstable_yieldValue('Bar');
+        return null;
+      }
 
-    function Foo() {
-      Scheduler.unstable_yieldValue('Foo');
-      return <Bar />;
-    }
+      function Foo() {
+        Scheduler.unstable_yieldValue('Foo');
+        return <Bar />;
+      }
 
-    React.startTransition(() => {
-      renderRootHelper(<Foo />);
-    });
+      React.startTransition(() => {
+        renderRootHelper(<Foo />);
+      });
 
-    // Do one step of work.
-    expect(Scheduler).toFlushAndYieldThrough(['Foo']);
+      // Do one step of work.
+      expect(Scheduler).toFlushAndYieldThrough(['Foo']);
 
-    expect(clearedMarks).toMatchInlineSnapshot(`
+      expect(clearedMarks).toMatchInlineSnapshot(`
       Array [
         "--schedule-render-64",
         "--render-start-64",
@@ -196,21 +203,21 @@ describe('Timeline profiler', () => {
         "--render-yield",
       ]
     `);
-  });
+    });
 
-  it('should mark sync render with suspense that resolves', async () => {
-    const fakeSuspensePromise = Promise.resolve(true);
-    function Example() {
-      throw fakeSuspensePromise;
-    }
+    it('should mark sync render with suspense that resolves', async () => {
+      const fakeSuspensePromise = Promise.resolve(true);
+      function Example() {
+        throw fakeSuspensePromise;
+      }
 
-    renderHelper(
-      <React.Suspense fallback={null}>
-        <Example />
-      </React.Suspense>,
-    );
+      renderHelper(
+        <React.Suspense fallback={null}>
+          <Example />
+        </React.Suspense>,
+      );
 
-    expect(clearedMarks).toMatchInlineSnapshot(`
+      expect(clearedMarks).toMatchInlineSnapshot(`
       Array [
         "--schedule-render-1",
         "--render-start-1",
@@ -230,29 +237,29 @@ describe('Timeline profiler', () => {
       ]
     `);
 
-    clearPendingMarks();
+      clearPendingMarks();
 
-    await fakeSuspensePromise;
-    expect(clearedMarks).toMatchInlineSnapshot(`
+      await fakeSuspensePromise;
+      expect(clearedMarks).toMatchInlineSnapshot(`
         Array [
           "--suspense-resolved-0-Example",
         ]
     `);
-  });
+    });
 
-  it('should mark sync render with suspense that rejects', async () => {
-    const fakeSuspensePromise = Promise.reject(new Error('error'));
-    function Example() {
-      throw fakeSuspensePromise;
-    }
+    it('should mark sync render with suspense that rejects', async () => {
+      const fakeSuspensePromise = Promise.reject(new Error('error'));
+      function Example() {
+        throw fakeSuspensePromise;
+      }
 
-    renderHelper(
-      <React.Suspense fallback={null}>
-        <Example />
-      </React.Suspense>,
-    );
+      renderHelper(
+        <React.Suspense fallback={null}>
+          <Example />
+        </React.Suspense>,
+      );
 
-    expect(clearedMarks).toMatchInlineSnapshot(`
+      expect(clearedMarks).toMatchInlineSnapshot(`
       Array [
         "--schedule-render-1",
         "--render-start-1",
@@ -272,35 +279,35 @@ describe('Timeline profiler', () => {
       ]
     `);
 
-    clearPendingMarks();
+      clearPendingMarks();
 
-    await expect(fakeSuspensePromise).rejects.toThrow();
-    expect(clearedMarks).toContain(`--suspense-rejected-0-Example`);
-  });
+      await expect(fakeSuspensePromise).rejects.toThrow();
+      expect(clearedMarks).toContain(`--suspense-rejected-0-Example`);
+    });
 
-  it('should mark concurrent render with suspense that resolves', async () => {
-    const fakeSuspensePromise = Promise.resolve(true);
-    function Example() {
-      throw fakeSuspensePromise;
-    }
+    it('should mark concurrent render with suspense that resolves', async () => {
+      const fakeSuspensePromise = Promise.resolve(true);
+      function Example() {
+        throw fakeSuspensePromise;
+      }
 
-    renderRootHelper(
-      <React.Suspense fallback={null}>
-        <Example />
-      </React.Suspense>,
-    );
+      renderRootHelper(
+        <React.Suspense fallback={null}>
+          <Example />
+        </React.Suspense>,
+      );
 
-    expect(clearedMarks).toMatchInlineSnapshot(`
+      expect(clearedMarks).toMatchInlineSnapshot(`
       Array [
         "--schedule-render-16",
       ]
     `);
 
-    clearPendingMarks();
+      clearPendingMarks();
 
-    expect(Scheduler).toFlushUntilNextPaint([]);
+      expect(Scheduler).toFlushUntilNextPaint([]);
 
-    expect(clearedMarks).toMatchInlineSnapshot(`
+      expect(clearedMarks).toMatchInlineSnapshot(`
       Array [
         "--render-start-16",
         "--component-render-start-Example",
@@ -319,39 +326,39 @@ describe('Timeline profiler', () => {
       ]
     `);
 
-    clearPendingMarks();
+      clearPendingMarks();
 
-    await fakeSuspensePromise;
-    expect(clearedMarks).toMatchInlineSnapshot(`
+      await fakeSuspensePromise;
+      expect(clearedMarks).toMatchInlineSnapshot(`
       Array [
         "--suspense-resolved-0-Example",
       ]
     `);
-  });
+    });
 
-  it('should mark concurrent render with suspense that rejects', async () => {
-    const fakeSuspensePromise = Promise.reject(new Error('error'));
-    function Example() {
-      throw fakeSuspensePromise;
-    }
+    it('should mark concurrent render with suspense that rejects', async () => {
+      const fakeSuspensePromise = Promise.reject(new Error('error'));
+      function Example() {
+        throw fakeSuspensePromise;
+      }
 
-    renderRootHelper(
-      <React.Suspense fallback={null}>
-        <Example />
-      </React.Suspense>,
-    );
+      renderRootHelper(
+        <React.Suspense fallback={null}>
+          <Example />
+        </React.Suspense>,
+      );
 
-    expect(clearedMarks).toMatchInlineSnapshot(`
+      expect(clearedMarks).toMatchInlineSnapshot(`
       Array [
         "--schedule-render-16",
       ]
     `);
 
-    clearPendingMarks();
+      clearPendingMarks();
 
-    expect(Scheduler).toFlushUntilNextPaint([]);
+      expect(Scheduler).toFlushUntilNextPaint([]);
 
-    expect(clearedMarks).toMatchInlineSnapshot(`
+      expect(clearedMarks).toMatchInlineSnapshot(`
       Array [
         "--render-start-16",
         "--component-render-start-Example",
@@ -370,40 +377,40 @@ describe('Timeline profiler', () => {
       ]
     `);
 
-    clearPendingMarks();
+      clearPendingMarks();
 
-    await expect(fakeSuspensePromise).rejects.toThrow();
-    expect(clearedMarks).toMatchInlineSnapshot(`
+      await expect(fakeSuspensePromise).rejects.toThrow();
+      expect(clearedMarks).toMatchInlineSnapshot(`
         Array [
           "--suspense-rejected-0-Example",
         ]
     `);
-  });
+    });
 
-  it('should mark cascading class component state updates', () => {
-    class Example extends React.Component {
-      state = {didMount: false};
-      componentDidMount() {
-        this.setState({didMount: true});
-      }
-      render() {
-        return null;
+    it('should mark cascading class component state updates', () => {
+      class Example extends React.Component {
+        state = {didMount: false};
+        componentDidMount() {
+          this.setState({didMount: true});
+        }
+        render() {
+          return null;
+        }
       }
-    }
 
-    renderRootHelper(<Example />);
+      renderRootHelper(<Example />);
 
-    expect(clearedMarks).toMatchInlineSnapshot(`
+      expect(clearedMarks).toMatchInlineSnapshot(`
       Array [
         "--schedule-render-16",
       ]
     `);
 
-    clearPendingMarks();
+      clearPendingMarks();
 
-    expect(Scheduler).toFlushUntilNextPaint([]);
+      expect(Scheduler).toFlushUntilNextPaint([]);
 
-    expect(clearedMarks).toMatchInlineSnapshot(`
+      expect(clearedMarks).toMatchInlineSnapshot(`
       Array [
         "--render-start-16",
         "--component-render-start-Example",
@@ -432,31 +439,31 @@ describe('Timeline profiler', () => {
         "--commit-stop",
       ]
     `);
-  });
+    });
 
-  it('should mark cascading class component force updates', () => {
-    class Example extends React.Component {
-      componentDidMount() {
-        this.forceUpdate();
-      }
-      render() {
-        return null;
+    it('should mark cascading class component force updates', () => {
+      class Example extends React.Component {
+        componentDidMount() {
+          this.forceUpdate();
+        }
+        render() {
+          return null;
+        }
       }
-    }
 
-    renderRootHelper(<Example />);
+      renderRootHelper(<Example />);
 
-    expect(clearedMarks).toMatchInlineSnapshot(`
+      expect(clearedMarks).toMatchInlineSnapshot(`
         Array [
           "--schedule-render-16",
         ]
     `);
 
-    clearPendingMarks();
+      clearPendingMarks();
 
-    expect(Scheduler).toFlushUntilNextPaint([]);
+      expect(Scheduler).toFlushUntilNextPaint([]);
 
-    expect(clearedMarks).toMatchInlineSnapshot(`
+      expect(clearedMarks).toMatchInlineSnapshot(`
       Array [
         "--render-start-16",
         "--component-render-start-Example",
@@ -485,42 +492,42 @@ describe('Timeline profiler', () => {
         "--commit-stop",
       ]
     `);
-  });
+    });
 
-  it('should mark render phase state updates for class component', () => {
-    class Example extends React.Component {
-      state = {didRender: false};
-      render() {
-        if (this.state.didRender === false) {
-          this.setState({didRender: true});
+    it('should mark render phase state updates for class component', () => {
+      class Example extends React.Component {
+        state = {didRender: false};
+        render() {
+          if (this.state.didRender === false) {
+            this.setState({didRender: true});
+          }
+          return null;
         }
-        return null;
       }
-    }
 
-    renderRootHelper(<Example />);
+      renderRootHelper(<Example />);
 
-    expect(clearedMarks).toMatchInlineSnapshot(`
+      expect(clearedMarks).toMatchInlineSnapshot(`
         Array [
           "--schedule-render-16",
         ]
     `);
 
-    clearPendingMarks();
+      clearPendingMarks();
 
-    let errorMessage;
-    spyOn(console, 'error').and.callFake(message => {
-      errorMessage = message;
-    });
+      let errorMessage;
+      spyOn(console, 'error').and.callFake(message => {
+        errorMessage = message;
+      });
 
-    expect(Scheduler).toFlushUntilNextPaint([]);
+      expect(Scheduler).toFlushUntilNextPaint([]);
 
-    expect(console.error).toHaveBeenCalledTimes(1);
-    expect(errorMessage).toContain(
-      'Cannot update during an existing state transition',
-    );
+      expect(console.error).toHaveBeenCalledTimes(1);
+      expect(errorMessage).toContain(
+        'Cannot update during an existing state transition',
+      );
 
-    expect(clearedMarks).toMatchInlineSnapshot(`
+      expect(clearedMarks).toMatchInlineSnapshot(`
       Array [
         "--render-start-16",
         "--component-render-start-Example",
@@ -538,42 +545,42 @@ describe('Timeline profiler', () => {
         "--commit-stop",
       ]
     `);
-  });
+    });
 
-  it('should mark render phase force updates for class component', () => {
-    class Example extends React.Component {
-      state = {didRender: false};
-      render() {
-        if (this.state.didRender === false) {
-          this.forceUpdate(() => this.setState({didRender: true}));
+    it('should mark render phase force updates for class component', () => {
+      class Example extends React.Component {
+        state = {didRender: false};
+        render() {
+          if (this.state.didRender === false) {
+            this.forceUpdate(() => this.setState({didRender: true}));
+          }
+          return null;
         }
-        return null;
       }
-    }
 
-    renderRootHelper(<Example />);
+      renderRootHelper(<Example />);
 
-    expect(clearedMarks).toMatchInlineSnapshot(`
+      expect(clearedMarks).toMatchInlineSnapshot(`
         Array [
           "--schedule-render-16",
         ]
     `);
 
-    clearPendingMarks();
+      clearPendingMarks();
 
-    let errorMessage;
-    spyOn(console, 'error').and.callFake(message => {
-      errorMessage = message;
-    });
+      let errorMessage;
+      spyOn(console, 'error').and.callFake(message => {
+        errorMessage = message;
+      });
 
-    expect(Scheduler).toFlushUntilNextPaint([]);
+      expect(Scheduler).toFlushUntilNextPaint([]);
 
-    expect(console.error).toHaveBeenCalledTimes(1);
-    expect(errorMessage).toContain(
-      'Cannot update during an existing state transition',
-    );
+      expect(console.error).toHaveBeenCalledTimes(1);
+      expect(errorMessage).toContain(
+        'Cannot update during an existing state transition',
+      );
 
-    expect(clearedMarks).toMatchInlineSnapshot(`
+      expect(clearedMarks).toMatchInlineSnapshot(`
       Array [
         "--render-start-16",
         "--component-render-start-Example",
@@ -591,30 +598,30 @@ describe('Timeline profiler', () => {
         "--commit-stop",
       ]
     `);
-  });
+    });
 
-  it('should mark cascading layout updates', () => {
-    function Example() {
-      const [didMount, setDidMount] = React.useState(false);
-      React.useLayoutEffect(() => {
-        setDidMount(true);
-      }, []);
-      return didMount;
-    }
+    it('should mark cascading layout updates', () => {
+      function Example() {
+        const [didMount, setDidMount] = React.useState(false);
+        React.useLayoutEffect(() => {
+          setDidMount(true);
+        }, []);
+        return didMount;
+      }
 
-    renderRootHelper(<Example />);
+      renderRootHelper(<Example />);
 
-    expect(clearedMarks).toMatchInlineSnapshot(`
+      expect(clearedMarks).toMatchInlineSnapshot(`
         Array [
           "--schedule-render-16",
         ]
     `);
 
-    clearPendingMarks();
+      clearPendingMarks();
 
-    expect(Scheduler).toFlushUntilNextPaint([]);
+      expect(Scheduler).toFlushUntilNextPaint([]);
 
-    expect(clearedMarks).toMatchInlineSnapshot(`
+      expect(clearedMarks).toMatchInlineSnapshot(`
       Array [
         "--render-start-16",
         "--component-render-start-Example",
@@ -645,24 +652,24 @@ describe('Timeline profiler', () => {
         "--commit-stop",
       ]
     `);
-  });
+    });
 
-  // This test is coupled to lane implementation details, so I'm disabling it in
-  // the new fork until it stabilizes so we don't have to repeatedly update it.
-  it('should mark cascading passive updates', () => {
-    function Example() {
-      const [didMount, setDidMount] = React.useState(false);
-      React.useEffect(() => {
-        setDidMount(true);
-      }, []);
-      return didMount;
-    }
+    // This test is coupled to lane implementation details, so I'm disabling it in
+    // the new fork until it stabilizes so we don't have to repeatedly update it.
+    it('should mark cascading passive updates', () => {
+      function Example() {
+        const [didMount, setDidMount] = React.useState(false);
+        React.useEffect(() => {
+          setDidMount(true);
+        }, []);
+        return didMount;
+      }
 
-    renderRootHelper(<Example />);
+      renderRootHelper(<Example />);
 
-    expect(Scheduler).toFlushAndYield([]);
+      expect(Scheduler).toFlushAndYield([]);
 
-    expect(clearedMarks).toMatchInlineSnapshot(`
+      expect(clearedMarks).toMatchInlineSnapshot(`
       Array [
         "--schedule-render-16",
         "--render-start-16",
@@ -696,22 +703,22 @@ describe('Timeline profiler', () => {
         "--commit-stop",
       ]
     `);
-  });
+    });
 
-  it('should mark render phase updates', () => {
-    function Example() {
-      const [didRender, setDidRender] = React.useState(false);
-      if (!didRender) {
-        setDidRender(true);
+    it('should mark render phase updates', () => {
+      function Example() {
+        const [didRender, setDidRender] = React.useState(false);
+        if (!didRender) {
+          setDidRender(true);
+        }
+        return didRender;
       }
-      return didRender;
-    }
 
-    renderRootHelper(<Example />);
+      renderRootHelper(<Example />);
 
-    expect(Scheduler).toFlushAndYield([]);
+      expect(Scheduler).toFlushAndYield([]);
 
-    expect(clearedMarks).toMatchInlineSnapshot(`
+      expect(clearedMarks).toMatchInlineSnapshot(`
       Array [
         "--schedule-render-16",
         "--render-start-16",
@@ -730,35 +737,35 @@ describe('Timeline profiler', () => {
         "--commit-stop",
       ]
     `);
-  });
+    });
 
-  it('should mark sync render that throws', async () => {
-    spyOn(console, 'error');
+    it('should mark sync render that throws', async () => {
+      spyOn(console, 'error');
 
-    class ErrorBoundary extends React.Component {
-      state = {error: null};
-      componentDidCatch(error) {
-        this.setState({error});
-      }
-      render() {
-        if (this.state.error) {
-          return null;
+      class ErrorBoundary extends React.Component {
+        state = {error: null};
+        componentDidCatch(error) {
+          this.setState({error});
+        }
+        render() {
+          if (this.state.error) {
+            return null;
+          }
+          return this.props.children;
         }
-        return this.props.children;
       }
-    }
 
-    function ExampleThatThrows() {
-      throw Error('Expected error');
-    }
+      function ExampleThatThrows() {
+        throw Error('Expected error');
+      }
 
-    renderHelper(
-      <ErrorBoundary>
-        <ExampleThatThrows />
-      </ErrorBoundary>,
-    );
+      renderHelper(
+        <ErrorBoundary>
+          <ExampleThatThrows />
+        </ErrorBoundary>,
+      );
 
-    expect(clearedMarks).toMatchInlineSnapshot(`
+      expect(clearedMarks).toMatchInlineSnapshot(`
       Array [
         "--schedule-render-1",
         "--render-start-1",
@@ -792,46 +799,46 @@ describe('Timeline profiler', () => {
         "--commit-stop",
       ]
     `);
-  });
+    });
 
-  it('should mark concurrent render that throws', async () => {
-    spyOn(console, 'error');
+    it('should mark concurrent render that throws', async () => {
+      spyOn(console, 'error');
 
-    class ErrorBoundary extends React.Component {
-      state = {error: null};
-      componentDidCatch(error) {
-        this.setState({error});
-      }
-      render() {
-        if (this.state.error) {
-          return null;
+      class ErrorBoundary extends React.Component {
+        state = {error: null};
+        componentDidCatch(error) {
+          this.setState({error});
+        }
+        render() {
+          if (this.state.error) {
+            return null;
+          }
+          return this.props.children;
         }
-        return this.props.children;
       }
-    }
 
-    function ExampleThatThrows() {
-      // eslint-disable-next-line no-throw-literal
-      throw 'Expected error';
-    }
+      function ExampleThatThrows() {
+        // eslint-disable-next-line no-throw-literal
+        throw 'Expected error';
+      }
 
-    renderRootHelper(
-      <ErrorBoundary>
-        <ExampleThatThrows />
-      </ErrorBoundary>,
-    );
+      renderRootHelper(
+        <ErrorBoundary>
+          <ExampleThatThrows />
+        </ErrorBoundary>,
+      );
 
-    expect(clearedMarks).toMatchInlineSnapshot(`
+      expect(clearedMarks).toMatchInlineSnapshot(`
         Array [
           "--schedule-render-16",
         ]
     `);
 
-    clearPendingMarks();
+      clearPendingMarks();
 
-    expect(Scheduler).toFlushUntilNextPaint([]);
+      expect(Scheduler).toFlushUntilNextPaint([]);
 
-    expect(clearedMarks).toMatchInlineSnapshot(`
+      expect(clearedMarks).toMatchInlineSnapshot(`
       Array [
         "--render-start-16",
         "--component-render-start-ErrorBoundary",
@@ -872,56 +879,56 @@ describe('Timeline profiler', () => {
         "--commit-stop",
       ]
     `);
-  });
+    });
 
-  it('should mark passive and layout effects', async () => {
-    function ComponentWithEffects() {
-      React.useLayoutEffect(() => {
-        Scheduler.unstable_yieldValue('layout 1 mount');
-        return () => {
-          Scheduler.unstable_yieldValue('layout 1 unmount');
-        };
-      }, []);
-
-      React.useEffect(() => {
-        Scheduler.unstable_yieldValue('passive 1 mount');
-        return () => {
-          Scheduler.unstable_yieldValue('passive 1 unmount');
-        };
-      }, []);
-
-      React.useLayoutEffect(() => {
-        Scheduler.unstable_yieldValue('layout 2 mount');
-        return () => {
-          Scheduler.unstable_yieldValue('layout 2 unmount');
-        };
-      }, []);
-
-      React.useEffect(() => {
-        Scheduler.unstable_yieldValue('passive 2 mount');
-        return () => {
-          Scheduler.unstable_yieldValue('passive 2 unmount');
-        };
-      }, []);
-
-      React.useEffect(() => {
-        Scheduler.unstable_yieldValue('passive 3 mount');
-        return () => {
-          Scheduler.unstable_yieldValue('passive 3 unmount');
-        };
-      }, []);
-
-      return null;
-    }
+    it('should mark passive and layout effects', async () => {
+      function ComponentWithEffects() {
+        React.useLayoutEffect(() => {
+          Scheduler.unstable_yieldValue('layout 1 mount');
+          return () => {
+            Scheduler.unstable_yieldValue('layout 1 unmount');
+          };
+        }, []);
+
+        React.useEffect(() => {
+          Scheduler.unstable_yieldValue('passive 1 mount');
+          return () => {
+            Scheduler.unstable_yieldValue('passive 1 unmount');
+          };
+        }, []);
+
+        React.useLayoutEffect(() => {
+          Scheduler.unstable_yieldValue('layout 2 mount');
+          return () => {
+            Scheduler.unstable_yieldValue('layout 2 unmount');
+          };
+        }, []);
+
+        React.useEffect(() => {
+          Scheduler.unstable_yieldValue('passive 2 mount');
+          return () => {
+            Scheduler.unstable_yieldValue('passive 2 unmount');
+          };
+        }, []);
+
+        React.useEffect(() => {
+          Scheduler.unstable_yieldValue('passive 3 mount');
+          return () => {
+            Scheduler.unstable_yieldValue('passive 3 unmount');
+          };
+        }, []);
+
+        return null;
+      }
 
-    const unmount = renderRootHelper(<ComponentWithEffects />);
+      const unmount = renderRootHelper(<ComponentWithEffects />);
 
-    expect(Scheduler).toFlushUntilNextPaint([
-      'layout 1 mount',
-      'layout 2 mount',
-    ]);
+      expect(Scheduler).toFlushUntilNextPaint([
+        'layout 1 mount',
+        'layout 2 mount',
+      ]);
 
-    expect(clearedMarks).toMatchInlineSnapshot(`
+      expect(clearedMarks).toMatchInlineSnapshot(`
       Array [
         "--schedule-render-16",
         "--render-start-16",
@@ -944,15 +951,15 @@ describe('Timeline profiler', () => {
       ]
     `);
 
-    clearPendingMarks();
+      clearPendingMarks();
 
-    expect(Scheduler).toFlushAndYield([
-      'passive 1 mount',
-      'passive 2 mount',
-      'passive 3 mount',
-    ]);
+      expect(Scheduler).toFlushAndYield([
+        'passive 1 mount',
+        'passive 2 mount',
+        'passive 3 mount',
+      ]);
 
-    expect(clearedMarks).toMatchInlineSnapshot(`
+      expect(clearedMarks).toMatchInlineSnapshot(`
       Array [
         "--passive-effects-start-16",
         "--component-passive-effect-mount-start-ComponentWithEffects",
@@ -965,21 +972,21 @@ describe('Timeline profiler', () => {
       ]
     `);
 
-    clearPendingMarks();
+      clearPendingMarks();
 
-    expect(Scheduler).toFlushAndYield([]);
+      expect(Scheduler).toFlushAndYield([]);
 
-    unmount();
+      unmount();
 
-    expect(Scheduler).toHaveYielded([
-      'layout 1 unmount',
-      'layout 2 unmount',
-      'passive 1 unmount',
-      'passive 2 unmount',
-      'passive 3 unmount',
-    ]);
+      expect(Scheduler).toHaveYielded([
+        'layout 1 unmount',
+        'layout 2 unmount',
+        'passive 1 unmount',
+        'passive 2 unmount',
+        'passive 3 unmount',
+      ]);
 
-    expect(clearedMarks).toMatchInlineSnapshot(`
+      expect(clearedMarks).toMatchInlineSnapshot(`
       Array [
         "--schedule-render-1",
         "--render-start-1",
@@ -1007,13 +1014,13 @@ describe('Timeline profiler', () => {
         "--commit-stop",
       ]
     `);
-  });
+    });
 
-  describe('lane labels', () => {
-    it('regression test SyncLane', () => {
-      renderHelper(<div />);
+    describe('lane labels', () => {
+      it('regression test SyncLane', () => {
+        renderHelper(<div />);
 
-      expect(clearedMarks).toMatchInlineSnapshot(`
+        expect(clearedMarks).toMatchInlineSnapshot(`
         Array [
           "--schedule-render-1",
           "--render-start-1",
@@ -1029,39 +1036,39 @@ describe('Timeline profiler', () => {
           "--commit-stop",
         ]
       `);
-    });
+      });
 
-    it('regression test DefaultLane', () => {
-      renderRootHelper(<div />);
-      expect(clearedMarks).toMatchInlineSnapshot(`
+      it('regression test DefaultLane', () => {
+        renderRootHelper(<div />);
+        expect(clearedMarks).toMatchInlineSnapshot(`
           Array [
             "--schedule-render-16",
           ]
       `);
-    });
+      });
 
-    it('regression test InputDiscreteLane', async () => {
-      const targetRef = React.createRef(null);
+      it('regression test InputDiscreteLane', async () => {
+        const targetRef = React.createRef(null);
 
-      function App() {
-        const [count, setCount] = React.useState(0);
-        const handleClick = () => {
-          setCount(count + 1);
-        };
-        return <button ref={targetRef} onClick={handleClick} />;
-      }
+        function App() {
+          const [count, setCount] = React.useState(0);
+          const handleClick = () => {
+            setCount(count + 1);
+          };
+          return <button ref={targetRef} onClick={handleClick} />;
+        }
 
-      renderRootHelper(<App />);
-      expect(Scheduler).toFlushAndYield([]);
+        renderRootHelper(<App />);
+        expect(Scheduler).toFlushAndYield([]);
 
-      clearedMarks.splice(0);
+        clearedMarks.splice(0);
 
-      targetRef.current.click();
+        targetRef.current.click();
 
-      // Wait a frame, for React to process the "click" update.
-      await Promise.resolve();
+        // Wait a frame, for React to process the "click" update.
+        await Promise.resolve();
 
-      expect(clearedMarks).toMatchInlineSnapshot(`
+        expect(clearedMarks).toMatchInlineSnapshot(`
         Array [
           "--schedule-state-update-1-App",
           "--render-start-1",
@@ -1079,29 +1086,29 @@ describe('Timeline profiler', () => {
           "--commit-stop",
         ]
       `);
-    });
+      });
 
-    it('regression test InputContinuousLane', async () => {
-      const targetRef = React.createRef(null);
+      it('regression test InputContinuousLane', async () => {
+        const targetRef = React.createRef(null);
 
-      function App() {
-        const [count, setCount] = React.useState(0);
-        const handleMouseOver = () => setCount(count + 1);
-        return <div ref={targetRef} onMouseOver={handleMouseOver} />;
-      }
+        function App() {
+          const [count, setCount] = React.useState(0);
+          const handleMouseOver = () => setCount(count + 1);
+          return <div ref={targetRef} onMouseOver={handleMouseOver} />;
+        }
 
-      renderRootHelper(<App />);
-      expect(Scheduler).toFlushAndYield([]);
+        renderRootHelper(<App />);
+        expect(Scheduler).toFlushAndYield([]);
 
-      clearedMarks.splice(0);
+        clearedMarks.splice(0);
 
-      const event = document.createEvent('MouseEvents');
-      event.initEvent('mouseover', true, true);
-      dispatchAndSetCurrentEvent(targetRef.current, event);
+        const event = document.createEvent('MouseEvents');
+        event.initEvent('mouseover', true, true);
+        dispatchAndSetCurrentEvent(targetRef.current, event);
 
-      expect(Scheduler).toFlushAndYield([]);
+        expect(Scheduler).toFlushAndYield([]);
 
-      expect(clearedMarks).toMatchInlineSnapshot(`
+        expect(clearedMarks).toMatchInlineSnapshot(`
         Array [
           "--schedule-state-update-4-App",
           "--render-start-4",
@@ -1119,6 +1126,15 @@ describe('Timeline profiler', () => {
           "--commit-stop",
         ]
       `);
+      });
+    });
+  });
+
+  describe('when not profiling', () => {
+    it('should not log any marks', () => {
+      renderHelper(<div />);
+
+      expect(clearedMarks).toMatchInlineSnapshot(`Array []`);
     });
   });
 });
diff --git a/packages/react-devtools-shared/src/__tests__/preprocessData-test.js b/packages/react-devtools-shared/src/__tests__/preprocessData-test.js
index b513db2e7e78d..76efd51b77e50 100644
--- a/packages/react-devtools-shared/src/__tests__/preprocessData-test.js
+++ b/packages/react-devtools-shared/src/__tests__/preprocessData-test.js
@@ -73,6 +73,11 @@ describe('Timeline profiler', () => {
     utils = require('./utils');
     utils.beforeEachProfiling();
 
+    const store = global.store;
+
+    // Start profiling so that data will actually be recorded.
+    utils.act(() => store.profilerStore.startProfiling());
+
     React = require('react');
     ReactDOM = require('react-dom');
     Scheduler = require('scheduler');
diff --git a/packages/react-devtools-shared/src/backend/profilingHooks.js b/packages/react-devtools-shared/src/backend/profilingHooks.js
index 2280d6156e683..c6624e2334ba3 100644
--- a/packages/react-devtools-shared/src/backend/profilingHooks.js
+++ b/packages/react-devtools-shared/src/backend/profilingHooks.js
@@ -63,21 +63,26 @@ export function setPerformanceMock_ONLY_FOR_TESTING(
   supportsUserTimingV3 = performanceMock !== null;
 }
 
-function markAndClear(markName) {
-  // This method won't be called unless these functions are defined, so we can skip the extra typeof check.
-  ((performanceTarget: any): Performance).mark(markName);
-  ((performanceTarget: any): Performance).clearMarks(markName);
-}
+export type ToggleProfilingStatus = (value: boolean) => void;
+
+type Response = {|
+  profilingHooks: DevToolsProfilingHooks,
+  toggleProfilingStatus: ToggleProfilingStatus,
+|};
 
 export function createProfilingHooks({
   getDisplayNameForFiber,
+  getIsProfiling,
   getLaneLabelMap,
   reactVersion,
 }: {|
   getDisplayNameForFiber: (fiber: Fiber) => string | null,
+  getIsProfiling: () => boolean,
   getLaneLabelMap?: () => Map<Lane, string> | null,
   reactVersion: string,
-|}): DevToolsProfilingHooks {
+|}): Response {
+  let isProfiling: boolean = false;
+
   function markMetadata() {
     markAndClear(`--react-version-${reactVersion}`);
     markAndClear(`--profiler-version-${SCHEDULING_PROFILER_VERSION}`);
@@ -115,6 +120,17 @@ export function createProfilingHooks({
     }
   }
 
+  function markAndClear(markName) {
+    // Only record User Timing marks if DevTools is profiling.
+    if (!isProfiling) {
+      return;
+    }
+
+    // This method won't be called unless these functions are defined, so we can skip the extra typeof check.
+    ((performanceTarget: any): Performance).mark(markName);
+    ((performanceTarget: any): Performance).clearMarks(markName);
+  }
+
   function markCommitStarted(lanes: Lanes): void {
     if (supportsUserTimingV3) {
       markAndClear(`--commit-start-${lanes}`);
@@ -337,30 +353,41 @@ export function createProfilingHooks({
     }
   }
 
+  function toggleProfilingStatus(value: boolean) {
+    isProfiling = value;
+
+    if (value) {
+      // TODO (timeline) Log metadata (e.g. profiler and React versions, lane labels).
+    }
+  }
+
   return {
-    markCommitStarted,
-    markCommitStopped,
-    markComponentRenderStarted,
-    markComponentRenderStopped,
-    markComponentPassiveEffectMountStarted,
-    markComponentPassiveEffectMountStopped,
-    markComponentPassiveEffectUnmountStarted,
-    markComponentPassiveEffectUnmountStopped,
-    markComponentLayoutEffectMountStarted,
-    markComponentLayoutEffectMountStopped,
-    markComponentLayoutEffectUnmountStarted,
-    markComponentLayoutEffectUnmountStopped,
-    markComponentErrored,
-    markComponentSuspended,
-    markLayoutEffectsStarted,
-    markLayoutEffectsStopped,
-    markPassiveEffectsStarted,
-    markPassiveEffectsStopped,
-    markRenderStarted,
-    markRenderYielded,
-    markRenderStopped,
-    markRenderScheduled,
-    markForceUpdateScheduled,
-    markStateUpdateScheduled,
+    profilingHooks: {
+      markCommitStarted,
+      markCommitStopped,
+      markComponentRenderStarted,
+      markComponentRenderStopped,
+      markComponentPassiveEffectMountStarted,
+      markComponentPassiveEffectMountStopped,
+      markComponentPassiveEffectUnmountStarted,
+      markComponentPassiveEffectUnmountStopped,
+      markComponentLayoutEffectMountStarted,
+      markComponentLayoutEffectMountStopped,
+      markComponentLayoutEffectUnmountStarted,
+      markComponentLayoutEffectUnmountStopped,
+      markComponentErrored,
+      markComponentSuspended,
+      markLayoutEffectsStarted,
+      markLayoutEffectsStopped,
+      markPassiveEffectsStarted,
+      markPassiveEffectsStopped,
+      markRenderStarted,
+      markRenderYielded,
+      markRenderStopped,
+      markRenderScheduled,
+      markForceUpdateScheduled,
+      markStateUpdateScheduled,
+    },
+    toggleProfilingStatus,
   };
 }
diff --git a/packages/react-devtools-shared/src/backend/renderer.js b/packages/react-devtools-shared/src/backend/renderer.js
index daa4d4e1030c3..6ca82222a137b 100644
--- a/packages/react-devtools-shared/src/backend/renderer.js
+++ b/packages/react-devtools-shared/src/backend/renderer.js
@@ -94,6 +94,7 @@ import hasOwnProperty from 'shared/hasOwnProperty';
 import {getStyleXData} from './StyleX/utils';
 import {createProfilingHooks} from './profilingHooks';
 
+import type {ToggleProfilingStatus} from './profilingHooks';
 import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
 import type {
   ChangeDescription,
@@ -627,14 +628,20 @@ export function attach(
     };
   }
 
+  let toggleProfilingStatus: null | ToggleProfilingStatus = null;
   if (typeof injectProfilingHooks === 'function') {
-    injectProfilingHooks(
-      createProfilingHooks({
-        getDisplayNameForFiber,
-        getLaneLabelMap,
-        reactVersion: version,
-      }),
-    );
+    const response = createProfilingHooks({
+      getDisplayNameForFiber,
+      getIsProfiling: () => isProfiling,
+      getLaneLabelMap,
+      reactVersion: version,
+    });
+
+    // Pass the Profiling hooks to the reconciler for it to call during render.
+    injectProfilingHooks(response.profilingHooks);
+
+    // Hang onto this toggle so we can notify the external methods of profiling status changes.
+    toggleProfilingStatus = response.toggleProfilingStatus;
   }
 
   // Tracks Fibers with recently changed number of error/warning messages.
@@ -3989,11 +3996,19 @@ export function attach(
     isProfiling = true;
     profilingStartTime = getCurrentTime();
     rootToCommitProfilingMetadataMap = new Map();
+
+    if (toggleProfilingStatus !== null) {
+      toggleProfilingStatus(true);
+    }
   }
 
   function stopProfiling() {
     isProfiling = false;
     recordChangeDescriptions = false;
+
+    if (toggleProfilingStatus !== null) {
+      toggleProfilingStatus(false);
+    }
   }
 
   // Automatically start profiling so that we don't miss timing info from initial "mount".

From 562e70e9afd8a5e2723857904aab5cface2b3b2e Mon Sep 17 00:00:00 2001
From: Brian Vaughn <bvaughn@fb.com>
Date: Wed, 19 Jan 2022 09:27:28 -0500
Subject: [PATCH 2/5] Moved DevTools test serializers into a separate folder

---
 .../{ => __serializers__}/dehydratedValueSerializer.js |  0
 .../__tests__/{ => __serializers__}/hookSerializer.js  |  0
 .../inspectedElementSerializer.js                      |  0
 .../__tests__/{ => __serializers__}/storeSerializer.js |  0
 .../treeContextStateSerializer.js                      |  0
 .../src/__tests__/storeStressSync-test.js              |  2 +-
 .../src/__tests__/storeStressTestConcurrent-test.js    |  2 +-
 scripts/jest/config.build-devtools.js                  | 10 +++++-----
 8 files changed, 7 insertions(+), 7 deletions(-)
 rename packages/react-devtools-shared/src/__tests__/{ => __serializers__}/dehydratedValueSerializer.js (100%)
 rename packages/react-devtools-shared/src/__tests__/{ => __serializers__}/hookSerializer.js (100%)
 rename packages/react-devtools-shared/src/__tests__/{ => __serializers__}/inspectedElementSerializer.js (100%)
 rename packages/react-devtools-shared/src/__tests__/{ => __serializers__}/storeSerializer.js (100%)
 rename packages/react-devtools-shared/src/__tests__/{ => __serializers__}/treeContextStateSerializer.js (100%)

diff --git a/packages/react-devtools-shared/src/__tests__/dehydratedValueSerializer.js b/packages/react-devtools-shared/src/__tests__/__serializers__/dehydratedValueSerializer.js
similarity index 100%
rename from packages/react-devtools-shared/src/__tests__/dehydratedValueSerializer.js
rename to packages/react-devtools-shared/src/__tests__/__serializers__/dehydratedValueSerializer.js
diff --git a/packages/react-devtools-shared/src/__tests__/hookSerializer.js b/packages/react-devtools-shared/src/__tests__/__serializers__/hookSerializer.js
similarity index 100%
rename from packages/react-devtools-shared/src/__tests__/hookSerializer.js
rename to packages/react-devtools-shared/src/__tests__/__serializers__/hookSerializer.js
diff --git a/packages/react-devtools-shared/src/__tests__/inspectedElementSerializer.js b/packages/react-devtools-shared/src/__tests__/__serializers__/inspectedElementSerializer.js
similarity index 100%
rename from packages/react-devtools-shared/src/__tests__/inspectedElementSerializer.js
rename to packages/react-devtools-shared/src/__tests__/__serializers__/inspectedElementSerializer.js
diff --git a/packages/react-devtools-shared/src/__tests__/storeSerializer.js b/packages/react-devtools-shared/src/__tests__/__serializers__/storeSerializer.js
similarity index 100%
rename from packages/react-devtools-shared/src/__tests__/storeSerializer.js
rename to packages/react-devtools-shared/src/__tests__/__serializers__/storeSerializer.js
diff --git a/packages/react-devtools-shared/src/__tests__/treeContextStateSerializer.js b/packages/react-devtools-shared/src/__tests__/__serializers__/treeContextStateSerializer.js
similarity index 100%
rename from packages/react-devtools-shared/src/__tests__/treeContextStateSerializer.js
rename to packages/react-devtools-shared/src/__tests__/__serializers__/treeContextStateSerializer.js
diff --git a/packages/react-devtools-shared/src/__tests__/storeStressSync-test.js b/packages/react-devtools-shared/src/__tests__/storeStressSync-test.js
index f1684b87b7930..57558525f49de 100644
--- a/packages/react-devtools-shared/src/__tests__/storeStressSync-test.js
+++ b/packages/react-devtools-shared/src/__tests__/storeStressSync-test.js
@@ -28,7 +28,7 @@ describe('StoreStress (Legacy Mode)', () => {
     act = utils.act;
     legacyRender = utils.legacyRender;
 
-    print = require('./storeSerializer').print;
+    print = require('./__serializers__/storeSerializer').print;
   });
 
   // This is a stress test for the tree mount/update/unmount traversal.
diff --git a/packages/react-devtools-shared/src/__tests__/storeStressTestConcurrent-test.js b/packages/react-devtools-shared/src/__tests__/storeStressTestConcurrent-test.js
index 0f4a373e164c3..d8f6d67a2ad79 100644
--- a/packages/react-devtools-shared/src/__tests__/storeStressTestConcurrent-test.js
+++ b/packages/react-devtools-shared/src/__tests__/storeStressTestConcurrent-test.js
@@ -28,7 +28,7 @@ describe('StoreStressConcurrent', () => {
     // this helper with the real thing.
     actAsync = require('./utils').actAsync;
 
-    print = require('./storeSerializer').print;
+    print = require('./__serializers__/storeSerializer').print;
   });
 
   // TODO: Remove this in favor of @gate pragma
diff --git a/scripts/jest/config.build-devtools.js b/scripts/jest/config.build-devtools.js
index e14ecb48c3485..d5ad97f2d2e68 100644
--- a/scripts/jest/config.build-devtools.js
+++ b/scripts/jest/config.build-devtools.js
@@ -62,19 +62,19 @@ module.exports = Object.assign({}, baseConfig, {
   testRegex: 'packages/react-devtools-shared/.+/__tests__/[^]+.test.js$',
   snapshotSerializers: [
     require.resolve(
-      '../../packages/react-devtools-shared/src/__tests__/dehydratedValueSerializer.js'
+      '../../packages/react-devtools-shared/src/__tests__/__serializers__/dehydratedValueSerializer.js'
     ),
     require.resolve(
-      '../../packages/react-devtools-shared/src/__tests__/hookSerializer.js'
+      '../../packages/react-devtools-shared/src/__tests__/__serializers__/hookSerializer.js'
     ),
     require.resolve(
-      '../../packages/react-devtools-shared/src/__tests__/inspectedElementSerializer.js'
+      '../../packages/react-devtools-shared/src/__tests__/__serializers__/inspectedElementSerializer.js'
     ),
     require.resolve(
-      '../../packages/react-devtools-shared/src/__tests__/storeSerializer.js'
+      '../../packages/react-devtools-shared/src/__tests__/__serializers__/storeSerializer.js'
     ),
     require.resolve(
-      '../../packages/react-devtools-shared/src/__tests__/treeContextStateSerializer.js'
+      '../../packages/react-devtools-shared/src/__tests__/__serializers__/treeContextStateSerializer.js'
     ),
   ],
   setupFiles: [

From 00d9d806662b772b4f70e8f4440a3fc261d8d197 Mon Sep 17 00:00:00 2001
From: Brian Vaughn <bvaughn@fb.com>
Date: Wed, 19 Jan 2022 09:33:44 -0500
Subject: [PATCH 3/5] Added numeric serializer

---
 .../numberToFixedSerializer.js                | 24 +++++++++++++++++++
 scripts/jest/config.build-devtools.js         |  3 +++
 2 files changed, 27 insertions(+)
 create mode 100644 packages/react-devtools-shared/src/__tests__/__serializers__/numberToFixedSerializer.js

diff --git a/packages/react-devtools-shared/src/__tests__/__serializers__/numberToFixedSerializer.js b/packages/react-devtools-shared/src/__tests__/__serializers__/numberToFixedSerializer.js
new file mode 100644
index 0000000000000..2b0fc1f07e78a
--- /dev/null
+++ b/packages/react-devtools-shared/src/__tests__/__serializers__/numberToFixedSerializer.js
@@ -0,0 +1,24 @@
+const MAX_DECIMAL_PLACES = 3;
+
+// test() is part of Jest's serializer API
+export function test(maybeNumber) {
+  return (
+    typeof maybeNumber === 'number' &&
+    Number.isFinite(maybeNumber) &&
+    !Number.isInteger(maybeNumber) &&
+    !Number.isNaN(maybeNumber)
+  );
+}
+
+// print() is part of Jest's serializer API
+export function print(number, serialize, indent) {
+  const string = number.toString();
+  const pieces = string.split('.');
+  if (pieces.length === 2) {
+    if (pieces[1].length > MAX_DECIMAL_PLACES) {
+      return number.toFixed(MAX_DECIMAL_PLACES);
+    }
+  }
+
+  return string;
+}
diff --git a/scripts/jest/config.build-devtools.js b/scripts/jest/config.build-devtools.js
index d5ad97f2d2e68..276d209054745 100644
--- a/scripts/jest/config.build-devtools.js
+++ b/scripts/jest/config.build-devtools.js
@@ -76,6 +76,9 @@ module.exports = Object.assign({}, baseConfig, {
     require.resolve(
       '../../packages/react-devtools-shared/src/__tests__/__serializers__/treeContextStateSerializer.js'
     ),
+    require.resolve(
+      '../../packages/react-devtools-shared/src/__tests__/__serializers__/numberToFixedSerializer.js'
+    ),
   ],
   setupFiles: [
     ...baseConfig.setupFiles,

From dc61e115fad3c2ff6d53e259e68cffa5d993e566 Mon Sep 17 00:00:00 2001
From: Brian Vaughn <bvaughn@fb.com>
Date: Wed, 19 Jan 2022 09:37:37 -0500
Subject: [PATCH 4/5] Updated snapshot numbers to match precision

---
 .../src/__tests__/preprocessData-test.js      | 46 +++++++++----------
 1 file changed, 23 insertions(+), 23 deletions(-)

diff --git a/packages/react-devtools-shared/src/__tests__/preprocessData-test.js b/packages/react-devtools-shared/src/__tests__/preprocessData-test.js
index 76efd51b77e50..9123e81ef90a9 100644
--- a/packages/react-devtools-shared/src/__tests__/preprocessData-test.js
+++ b/packages/react-devtools-shared/src/__tests__/preprocessData-test.js
@@ -444,7 +444,7 @@ describe('Timeline profiler', () => {
             Object {
               "batchUID": 0,
               "depth": 0,
-              "duration": 0.004999999999999999,
+              "duration": 0.005,
               "lanes": Array [
                 9,
               ],
@@ -464,7 +464,7 @@ describe('Timeline profiler', () => {
             Object {
               "batchUID": 0,
               "depth": 0,
-              "duration": 0.002999999999999999,
+              "duration": 0.003,
               "lanes": Array [
                 9,
               ],
@@ -474,7 +474,7 @@ describe('Timeline profiler', () => {
             Object {
               "batchUID": 0,
               "depth": 1,
-              "duration": 0.0010000000000000009,
+              "duration": 0.001,
               "lanes": Array [
                 9,
               ],
@@ -534,7 +534,7 @@ describe('Timeline profiler', () => {
             Object {
               "batchUID": 0,
               "depth": 0,
-              "duration": 0.004999999999999999,
+              "duration": 0.005,
               "lanes": Array [
                 9,
               ],
@@ -554,7 +554,7 @@ describe('Timeline profiler', () => {
             Object {
               "batchUID": 0,
               "depth": 0,
-              "duration": 0.002999999999999999,
+              "duration": 0.003,
               "lanes": Array [
                 9,
               ],
@@ -564,7 +564,7 @@ describe('Timeline profiler', () => {
             Object {
               "batchUID": 0,
               "depth": 1,
-              "duration": 0.0010000000000000009,
+              "duration": 0.001,
               "lanes": Array [
                 9,
               ],
@@ -661,7 +661,7 @@ describe('Timeline profiler', () => {
             Object {
               "batchUID": 0,
               "depth": 1,
-              "duration": 0.0009999999999999992,
+              "duration": 0.001,
               "lanes": Array [
                 0,
               ],
@@ -753,7 +753,7 @@ describe('Timeline profiler', () => {
             Object {
               "batchUID": 0,
               "depth": 1,
-              "duration": 0.0009999999999999992,
+              "duration": 0.001,
               "lanes": Array [
                 0,
               ],
@@ -870,7 +870,7 @@ describe('Timeline profiler', () => {
             Object {
               "batchUID": 0,
               "depth": 1,
-              "duration": 0.0009999999999999992,
+              "duration": 0.001,
               "lanes": Array [
                 4,
               ],
@@ -892,7 +892,7 @@ describe('Timeline profiler', () => {
             Object {
               "batchUID": 1,
               "depth": 0,
-              "duration": 0.012000000000000004,
+              "duration": 0.012,
               "lanes": Array [
                 4,
               ],
@@ -902,7 +902,7 @@ describe('Timeline profiler', () => {
             Object {
               "batchUID": 1,
               "depth": 0,
-              "duration": 0.0030000000000000027,
+              "duration": 0.003,
               "lanes": Array [
                 4,
               ],
@@ -912,7 +912,7 @@ describe('Timeline profiler', () => {
             Object {
               "batchUID": 1,
               "depth": 0,
-              "duration": 0.008000000000000004,
+              "duration": 0.008,
               "lanes": Array [
                 4,
               ],
@@ -922,7 +922,7 @@ describe('Timeline profiler', () => {
             Object {
               "batchUID": 1,
               "depth": 1,
-              "duration": 0.0010000000000000009,
+              "duration": 0.001,
               "lanes": Array [
                 4,
               ],
@@ -932,7 +932,7 @@ describe('Timeline profiler', () => {
             Object {
               "batchUID": 1,
               "depth": 0,
-              "duration": 0.0029999999999999957,
+              "duration": 0.003,
               "lanes": Array [
                 4,
               ],
@@ -951,21 +951,21 @@ describe('Timeline profiler', () => {
           },
           Object {
             "componentName": "App",
-            "duration": 0.0020000000000000018,
+            "duration": 0.002,
             "timestamp": 0.018,
             "type": "passive-effect-mount",
             "warning": null,
           },
           Object {
             "componentName": "App",
-            "duration": 0.0010000000000000009,
+            "duration": 0.001,
             "timestamp": 0.023,
             "type": "render",
             "warning": null,
           },
           Object {
             "componentName": "App",
-            "duration": 0.0010000000000000009,
+            "duration": 0.001,
             "timestamp": 0.036,
             "type": "passive-effect-mount",
             "warning": null,
@@ -1057,7 +1057,7 @@ describe('Timeline profiler', () => {
             Object {
               "batchUID": 0,
               "depth": 1,
-              "duration": 0.0009999999999999992,
+              "duration": 0.001,
               "lanes": Array [
                 4,
               ],
@@ -1077,7 +1077,7 @@ describe('Timeline profiler', () => {
             Object {
               "batchUID": 1,
               "depth": 0,
-              "duration": 0.012000000000000004,
+              "duration": 0.012,
               "lanes": Array [
                 4,
               ],
@@ -1087,7 +1087,7 @@ describe('Timeline profiler', () => {
             Object {
               "batchUID": 1,
               "depth": 0,
-              "duration": 0.0030000000000000027,
+              "duration": 0.003,
               "lanes": Array [
                 4,
               ],
@@ -1097,7 +1097,7 @@ describe('Timeline profiler', () => {
             Object {
               "batchUID": 1,
               "depth": 0,
-              "duration": 0.008000000000000004,
+              "duration": 0.008,
               "lanes": Array [
                 4,
               ],
@@ -1107,7 +1107,7 @@ describe('Timeline profiler', () => {
             Object {
               "batchUID": 1,
               "depth": 1,
-              "duration": 0.0010000000000000009,
+              "duration": 0.001,
               "lanes": Array [
                 4,
               ],
@@ -1117,7 +1117,7 @@ describe('Timeline profiler', () => {
             Object {
               "batchUID": 1,
               "depth": 0,
-              "duration": 0.0029999999999999957,
+              "duration": 0.003,
               "lanes": Array [
                 4,
               ],

From 3d8192ba00ab3a2bff8a7227307de3cd341265b4 Mon Sep 17 00:00:00 2001
From: Brian Vaughn <bvaughn@fb.com>
Date: Thu, 20 Jan 2022 14:40:09 -0500
Subject: [PATCH 5/5] DevTools logs Timeline metadata only once, when profiling
 starts.

---
 .../src/__tests__/TimelineProfiler-test.js    | 154 +++---------------
 .../src/__tests__/preprocessData-test.js      |  82 +++++-----
 .../src/backend/profilingHooks.js             |  32 ++--
 3 files changed, 81 insertions(+), 187 deletions(-)

diff --git a/packages/react-devtools-shared/src/__tests__/TimelineProfiler-test.js b/packages/react-devtools-shared/src/__tests__/TimelineProfiler-test.js
index 3a6c96c476d27..8c0fdab072a02 100644
--- a/packages/react-devtools-shared/src/__tests__/TimelineProfiler-test.js
+++ b/packages/react-devtools-shared/src/__tests__/TimelineProfiler-test.js
@@ -121,8 +121,32 @@ describe('Timeline profiler', () => {
   describe('when profiling', () => {
     beforeEach(() => {
       const store = global.store;
+      utils.act(() => store.profilerStore.startProfiling());
+
+      // Clear inital metadata marks to make tests below less noisy.
+      clearPendingMarks();
+    });
+
+    it('should log metadata once when profiling starts', () => {
+      clearPendingMarks();
 
+      const store = global.store;
+      utils.act(() => store.profilerStore.stopProfiling());
       utils.act(() => store.profilerStore.startProfiling());
+
+      // Note that we use toContain() rather than toMatchInlineSnapshot() because of the test renderer.
+      // It also gets registered with DevTools and results in duplicate metadata marks.
+      expect(clearedMarks).toContain('--react-version-<filtered-version>');
+      expect(clearedMarks).toContain('--profiler-version-1');
+      expect(clearedMarks).toContain(
+        '--react-internal-module-start-<filtered-file-system-path>',
+      );
+      expect(clearedMarks).toContain(
+        '--react-internal-module-stop-<filtered-file-system-path>',
+      );
+      expect(clearedMarks).toContain(
+        '--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen',
+      );
     });
 
     it('should mark sync render without suspends or state updates', () => {
@@ -134,11 +158,6 @@ describe('Timeline profiler', () => {
         "--render-start-1",
         "--render-stop",
         "--commit-start-1",
-        "--react-version-<filtered-version>",
-        "--profiler-version-1",
-        "--react-internal-module-start-<filtered-file-system-path>",
-        "--react-internal-module-stop-<filtered-file-system-path>",
-        "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
         "--layout-effects-start-1",
         "--layout-effects-stop",
         "--commit-stop",
@@ -164,11 +183,6 @@ describe('Timeline profiler', () => {
         "--render-start-16",
         "--render-stop",
         "--commit-start-16",
-        "--react-version-<filtered-version>",
-        "--profiler-version-1",
-        "--react-internal-module-start-<filtered-file-system-path>",
-        "--react-internal-module-stop-<filtered-file-system-path>",
-        "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
         "--layout-effects-start-16",
         "--layout-effects-stop",
         "--commit-stop",
@@ -226,11 +240,6 @@ describe('Timeline profiler', () => {
         "--suspense-suspend-0-Example-mount-1-",
         "--render-stop",
         "--commit-start-1",
-        "--react-version-<filtered-version>",
-        "--profiler-version-1",
-        "--react-internal-module-start-<filtered-file-system-path>",
-        "--react-internal-module-stop-<filtered-file-system-path>",
-        "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
         "--layout-effects-start-1",
         "--layout-effects-stop",
         "--commit-stop",
@@ -268,11 +277,6 @@ describe('Timeline profiler', () => {
         "--suspense-suspend-0-Example-mount-1-",
         "--render-stop",
         "--commit-start-1",
-        "--react-version-<filtered-version>",
-        "--profiler-version-1",
-        "--react-internal-module-start-<filtered-file-system-path>",
-        "--react-internal-module-stop-<filtered-file-system-path>",
-        "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
         "--layout-effects-start-1",
         "--layout-effects-stop",
         "--commit-stop",
@@ -315,11 +319,6 @@ describe('Timeline profiler', () => {
         "--suspense-suspend-0-Example-mount-16-",
         "--render-stop",
         "--commit-start-16",
-        "--react-version-<filtered-version>",
-        "--profiler-version-1",
-        "--react-internal-module-start-<filtered-file-system-path>",
-        "--react-internal-module-stop-<filtered-file-system-path>",
-        "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
         "--layout-effects-start-16",
         "--layout-effects-stop",
         "--commit-stop",
@@ -366,11 +365,6 @@ describe('Timeline profiler', () => {
         "--suspense-suspend-0-Example-mount-16-",
         "--render-stop",
         "--commit-start-16",
-        "--react-version-<filtered-version>",
-        "--profiler-version-1",
-        "--react-internal-module-start-<filtered-file-system-path>",
-        "--react-internal-module-stop-<filtered-file-system-path>",
-        "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
         "--layout-effects-start-16",
         "--layout-effects-stop",
         "--commit-stop",
@@ -417,11 +411,6 @@ describe('Timeline profiler', () => {
         "--component-render-stop",
         "--render-stop",
         "--commit-start-16",
-        "--react-version-<filtered-version>",
-        "--profiler-version-1",
-        "--react-internal-module-start-<filtered-file-system-path>",
-        "--react-internal-module-stop-<filtered-file-system-path>",
-        "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
         "--layout-effects-start-16",
         "--schedule-state-update-1-Example",
         "--layout-effects-stop",
@@ -430,11 +419,6 @@ describe('Timeline profiler', () => {
         "--component-render-stop",
         "--render-stop",
         "--commit-start-1",
-        "--react-version-<filtered-version>",
-        "--profiler-version-1",
-        "--react-internal-module-start-<filtered-file-system-path>",
-        "--react-internal-module-stop-<filtered-file-system-path>",
-        "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
         "--commit-stop",
         "--commit-stop",
       ]
@@ -470,11 +454,6 @@ describe('Timeline profiler', () => {
         "--component-render-stop",
         "--render-stop",
         "--commit-start-16",
-        "--react-version-<filtered-version>",
-        "--profiler-version-1",
-        "--react-internal-module-start-<filtered-file-system-path>",
-        "--react-internal-module-stop-<filtered-file-system-path>",
-        "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
         "--layout-effects-start-16",
         "--schedule-forced-update-1-Example",
         "--layout-effects-stop",
@@ -483,11 +462,6 @@ describe('Timeline profiler', () => {
         "--component-render-stop",
         "--render-stop",
         "--commit-start-1",
-        "--react-version-<filtered-version>",
-        "--profiler-version-1",
-        "--react-internal-module-start-<filtered-file-system-path>",
-        "--react-internal-module-stop-<filtered-file-system-path>",
-        "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
         "--commit-stop",
         "--commit-stop",
       ]
@@ -535,11 +509,6 @@ describe('Timeline profiler', () => {
         "--component-render-stop",
         "--render-stop",
         "--commit-start-16",
-        "--react-version-<filtered-version>",
-        "--profiler-version-1",
-        "--react-internal-module-start-<filtered-file-system-path>",
-        "--react-internal-module-stop-<filtered-file-system-path>",
-        "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
         "--layout-effects-start-16",
         "--layout-effects-stop",
         "--commit-stop",
@@ -588,11 +557,6 @@ describe('Timeline profiler', () => {
         "--component-render-stop",
         "--render-stop",
         "--commit-start-16",
-        "--react-version-<filtered-version>",
-        "--profiler-version-1",
-        "--react-internal-module-start-<filtered-file-system-path>",
-        "--react-internal-module-stop-<filtered-file-system-path>",
-        "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
         "--layout-effects-start-16",
         "--layout-effects-stop",
         "--commit-stop",
@@ -628,11 +592,6 @@ describe('Timeline profiler', () => {
         "--component-render-stop",
         "--render-stop",
         "--commit-start-16",
-        "--react-version-<filtered-version>",
-        "--profiler-version-1",
-        "--react-internal-module-start-<filtered-file-system-path>",
-        "--react-internal-module-stop-<filtered-file-system-path>",
-        "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
         "--layout-effects-start-16",
         "--component-layout-effect-mount-start-Example",
         "--schedule-state-update-1-Example",
@@ -643,11 +602,6 @@ describe('Timeline profiler', () => {
         "--component-render-stop",
         "--render-stop",
         "--commit-start-1",
-        "--react-version-<filtered-version>",
-        "--profiler-version-1",
-        "--react-internal-module-start-<filtered-file-system-path>",
-        "--react-internal-module-stop-<filtered-file-system-path>",
-        "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
         "--commit-stop",
         "--commit-stop",
       ]
@@ -677,11 +631,6 @@ describe('Timeline profiler', () => {
         "--component-render-stop",
         "--render-stop",
         "--commit-start-16",
-        "--react-version-<filtered-version>",
-        "--profiler-version-1",
-        "--react-internal-module-start-<filtered-file-system-path>",
-        "--react-internal-module-stop-<filtered-file-system-path>",
-        "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
         "--layout-effects-start-16",
         "--layout-effects-stop",
         "--commit-stop",
@@ -695,11 +644,6 @@ describe('Timeline profiler', () => {
         "--component-render-stop",
         "--render-stop",
         "--commit-start-16",
-        "--react-version-<filtered-version>",
-        "--profiler-version-1",
-        "--react-internal-module-start-<filtered-file-system-path>",
-        "--react-internal-module-stop-<filtered-file-system-path>",
-        "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
         "--commit-stop",
       ]
     `);
@@ -727,11 +671,6 @@ describe('Timeline profiler', () => {
         "--component-render-stop",
         "--render-stop",
         "--commit-start-16",
-        "--react-version-<filtered-version>",
-        "--profiler-version-1",
-        "--react-internal-module-start-<filtered-file-system-path>",
-        "--react-internal-module-stop-<filtered-file-system-path>",
-        "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
         "--layout-effects-start-16",
         "--layout-effects-stop",
         "--commit-stop",
@@ -777,11 +716,6 @@ describe('Timeline profiler', () => {
         "--error-ExampleThatThrows-mount-Expected error",
         "--render-stop",
         "--commit-start-1",
-        "--react-version-<filtered-version>",
-        "--profiler-version-1",
-        "--react-internal-module-start-<filtered-file-system-path>",
-        "--react-internal-module-stop-<filtered-file-system-path>",
-        "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
         "--layout-effects-start-1",
         "--schedule-state-update-1-ErrorBoundary",
         "--layout-effects-stop",
@@ -791,11 +725,6 @@ describe('Timeline profiler', () => {
         "--component-render-stop",
         "--render-stop",
         "--commit-start-1",
-        "--react-version-<filtered-version>",
-        "--profiler-version-1",
-        "--react-internal-module-start-<filtered-file-system-path>",
-        "--react-internal-module-stop-<filtered-file-system-path>",
-        "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
         "--commit-stop",
       ]
     `);
@@ -857,11 +786,6 @@ describe('Timeline profiler', () => {
         "--error-ExampleThatThrows-mount-Expected error",
         "--render-stop",
         "--commit-start-16",
-        "--react-version-<filtered-version>",
-        "--profiler-version-1",
-        "--react-internal-module-start-<filtered-file-system-path>",
-        "--react-internal-module-stop-<filtered-file-system-path>",
-        "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
         "--layout-effects-start-16",
         "--schedule-state-update-1-ErrorBoundary",
         "--layout-effects-stop",
@@ -870,11 +794,6 @@ describe('Timeline profiler', () => {
         "--component-render-stop",
         "--render-stop",
         "--commit-start-1",
-        "--react-version-<filtered-version>",
-        "--profiler-version-1",
-        "--react-internal-module-start-<filtered-file-system-path>",
-        "--react-internal-module-stop-<filtered-file-system-path>",
-        "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
         "--commit-stop",
         "--commit-stop",
       ]
@@ -936,11 +855,6 @@ describe('Timeline profiler', () => {
         "--component-render-stop",
         "--render-stop",
         "--commit-start-16",
-        "--react-version-<filtered-version>",
-        "--profiler-version-1",
-        "--react-internal-module-start-<filtered-file-system-path>",
-        "--react-internal-module-stop-<filtered-file-system-path>",
-        "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
         "--layout-effects-start-16",
         "--component-layout-effect-mount-start-ComponentWithEffects",
         "--component-layout-effect-mount-stop",
@@ -992,11 +906,6 @@ describe('Timeline profiler', () => {
         "--render-start-1",
         "--render-stop",
         "--commit-start-1",
-        "--react-version-<filtered-version>",
-        "--profiler-version-1",
-        "--react-internal-module-start-<filtered-file-system-path>",
-        "--react-internal-module-stop-<filtered-file-system-path>",
-        "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
         "--component-layout-effect-unmount-start-ComponentWithEffects",
         "--component-layout-effect-unmount-stop",
         "--component-layout-effect-unmount-start-ComponentWithEffects",
@@ -1026,11 +935,6 @@ describe('Timeline profiler', () => {
           "--render-start-1",
           "--render-stop",
           "--commit-start-1",
-          "--react-version-<filtered-version>",
-          "--profiler-version-1",
-          "--react-internal-module-start-<filtered-file-system-path>",
-          "--react-internal-module-stop-<filtered-file-system-path>",
-          "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
           "--layout-effects-start-1",
           "--layout-effects-stop",
           "--commit-stop",
@@ -1076,11 +980,6 @@ describe('Timeline profiler', () => {
           "--component-render-stop",
           "--render-stop",
           "--commit-start-1",
-          "--react-version-<filtered-version>",
-          "--profiler-version-1",
-          "--react-internal-module-start-<filtered-file-system-path>",
-          "--react-internal-module-stop-<filtered-file-system-path>",
-          "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
           "--layout-effects-start-1",
           "--layout-effects-stop",
           "--commit-stop",
@@ -1116,11 +1015,6 @@ describe('Timeline profiler', () => {
           "--component-render-stop",
           "--render-stop",
           "--commit-start-4",
-          "--react-version-<filtered-version>",
-          "--profiler-version-1",
-          "--react-internal-module-start-<filtered-file-system-path>",
-          "--react-internal-module-stop-<filtered-file-system-path>",
-          "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
           "--layout-effects-start-4",
           "--layout-effects-stop",
           "--commit-stop",
diff --git a/packages/react-devtools-shared/src/__tests__/preprocessData-test.js b/packages/react-devtools-shared/src/__tests__/preprocessData-test.js
index 9123e81ef90a9..a51a263b5ef8b 100644
--- a/packages/react-devtools-shared/src/__tests__/preprocessData-test.js
+++ b/packages/react-devtools-shared/src/__tests__/preprocessData-test.js
@@ -73,11 +73,6 @@ describe('Timeline profiler', () => {
     utils = require('./utils');
     utils.beforeEachProfiling();
 
-    const store = global.store;
-
-    // Start profiling so that data will actually be recorded.
-    utils.act(() => store.profilerStore.startProfiling());
-
     React = require('react');
     ReactDOM = require('react-dom');
     Scheduler = require('scheduler');
@@ -86,6 +81,11 @@ describe('Timeline profiler', () => {
       .setPerformanceMock_ONLY_FOR_TESTING;
     setPerformanceMock(createUserTimingPolyfill());
 
+    const store = global.store;
+
+    // Start profiling so that data will actually be recorded.
+    utils.act(() => store.profilerStore.startProfiling());
+
     global.IS_REACT_ACT_ENVIRONMENT = true;
   });
 
@@ -397,7 +397,7 @@ describe('Timeline profiler', () => {
         "suspenseEvents": Array [],
         "thrownErrors": Array [],
       }
-    `);
+  `);
     });
 
     it('should process legacy data format (before lane labels were added)', async () => {
@@ -631,11 +631,11 @@ describe('Timeline profiler', () => {
             Object {
               "batchUID": 0,
               "depth": 0,
-              "duration": 0.01,
+              "duration": 0.005,
               "lanes": Array [
                 0,
               ],
-              "timestamp": 0.004,
+              "timestamp": 0.009,
               "type": "render-idle",
             },
             Object {
@@ -645,17 +645,17 @@ describe('Timeline profiler', () => {
               "lanes": Array [
                 0,
               ],
-              "timestamp": 0.004,
+              "timestamp": 0.009,
               "type": "render",
             },
             Object {
               "batchUID": 0,
               "depth": 0,
-              "duration": 0.008,
+              "duration": 0.003,
               "lanes": Array [
                 0,
               ],
-              "timestamp": 0.006,
+              "timestamp": 0.011,
               "type": "commit",
             },
             Object {
@@ -723,11 +723,11 @@ describe('Timeline profiler', () => {
             Object {
               "batchUID": 0,
               "depth": 0,
-              "duration": 0.01,
+              "duration": 0.005,
               "lanes": Array [
                 0,
               ],
-              "timestamp": 0.004,
+              "timestamp": 0.009,
               "type": "render-idle",
             },
             Object {
@@ -737,17 +737,17 @@ describe('Timeline profiler', () => {
               "lanes": Array [
                 0,
               ],
-              "timestamp": 0.004,
+              "timestamp": 0.009,
               "type": "render",
             },
             Object {
               "batchUID": 0,
               "depth": 0,
-              "duration": 0.008,
+              "duration": 0.003,
               "lanes": Array [
                 0,
               ],
-              "timestamp": 0.006,
+              "timestamp": 0.011,
               "type": "commit",
             },
             Object {
@@ -801,7 +801,7 @@ describe('Timeline profiler', () => {
             "lanes": Array [
               0,
             ],
-            "timestamp": 0.003,
+            "timestamp": 0.008,
             "type": "schedule-render",
             "warning": null,
           },
@@ -840,11 +840,11 @@ describe('Timeline profiler', () => {
             Object {
               "batchUID": 0,
               "depth": 0,
-              "duration": 0.012,
+              "duration": 0.007,
               "lanes": Array [
                 4,
               ],
-              "timestamp": 0.004,
+              "timestamp": 0.009,
               "type": "render-idle",
             },
             Object {
@@ -854,17 +854,17 @@ describe('Timeline profiler', () => {
               "lanes": Array [
                 4,
               ],
-              "timestamp": 0.004,
+              "timestamp": 0.009,
               "type": "render",
             },
             Object {
               "batchUID": 0,
               "depth": 0,
-              "duration": 0.008,
+              "duration": 0.003,
               "lanes": Array [
                 4,
               ],
-              "timestamp": 0.008,
+              "timestamp": 0.013,
               "type": "commit",
             },
             Object {
@@ -892,7 +892,7 @@ describe('Timeline profiler', () => {
             Object {
               "batchUID": 1,
               "depth": 0,
-              "duration": 0.012,
+              "duration": 0.007,
               "lanes": Array [
                 4,
               ],
@@ -912,7 +912,7 @@ describe('Timeline profiler', () => {
             Object {
               "batchUID": 1,
               "depth": 0,
-              "duration": 0.008,
+              "duration": 0.003,
               "lanes": Array [
                 4,
               ],
@@ -926,7 +926,7 @@ describe('Timeline profiler', () => {
               "lanes": Array [
                 4,
               ],
-              "timestamp": 0.032,
+              "timestamp": 0.027,
               "type": "layout-effects",
             },
             Object {
@@ -936,7 +936,7 @@ describe('Timeline profiler', () => {
               "lanes": Array [
                 4,
               ],
-              "timestamp": 0.035,
+              "timestamp": 0.03,
               "type": "passive-effects",
             },
           ],
@@ -945,7 +945,7 @@ describe('Timeline profiler', () => {
           Object {
             "componentName": "App",
             "duration": 0.001,
-            "timestamp": 0.005,
+            "timestamp": 0.01,
             "type": "render",
             "warning": null,
           },
@@ -966,12 +966,12 @@ describe('Timeline profiler', () => {
           Object {
             "componentName": "App",
             "duration": 0.001,
-            "timestamp": 0.036,
+            "timestamp": 0.031,
             "type": "passive-effect-mount",
             "warning": null,
           },
         ],
-        "duration": 0.038,
+        "duration": 0.033,
         "flamechart": Array [],
         "internalModuleSourceToRanges": Map {
           undefined => Array [
@@ -1027,11 +1027,11 @@ describe('Timeline profiler', () => {
             Object {
               "batchUID": 0,
               "depth": 0,
-              "duration": 0.012,
+              "duration": 0.007,
               "lanes": Array [
                 4,
               ],
-              "timestamp": 0.004,
+              "timestamp": 0.009,
               "type": "render-idle",
             },
             Object {
@@ -1041,17 +1041,17 @@ describe('Timeline profiler', () => {
               "lanes": Array [
                 4,
               ],
-              "timestamp": 0.004,
+              "timestamp": 0.009,
               "type": "render",
             },
             Object {
               "batchUID": 0,
               "depth": 0,
-              "duration": 0.008,
+              "duration": 0.003,
               "lanes": Array [
                 4,
               ],
-              "timestamp": 0.008,
+              "timestamp": 0.013,
               "type": "commit",
             },
             Object {
@@ -1077,7 +1077,7 @@ describe('Timeline profiler', () => {
             Object {
               "batchUID": 1,
               "depth": 0,
-              "duration": 0.012,
+              "duration": 0.007,
               "lanes": Array [
                 4,
               ],
@@ -1097,7 +1097,7 @@ describe('Timeline profiler', () => {
             Object {
               "batchUID": 1,
               "depth": 0,
-              "duration": 0.008,
+              "duration": 0.003,
               "lanes": Array [
                 4,
               ],
@@ -1111,7 +1111,7 @@ describe('Timeline profiler', () => {
               "lanes": Array [
                 4,
               ],
-              "timestamp": 0.032,
+              "timestamp": 0.027,
               "type": "layout-effects",
             },
             Object {
@@ -1121,7 +1121,7 @@ describe('Timeline profiler', () => {
               "lanes": Array [
                 4,
               ],
-              "timestamp": 0.035,
+              "timestamp": 0.03,
               "type": "passive-effects",
             },
           ],
@@ -1161,7 +1161,7 @@ describe('Timeline profiler', () => {
             "lanes": Array [
               4,
             ],
-            "timestamp": 0.003,
+            "timestamp": 0.008,
             "type": "schedule-render",
             "warning": null,
           },
@@ -1258,7 +1258,7 @@ describe('Timeline profiler', () => {
           "timestamp": 0.005,
         },
       ]
-    `);
+  `);
     });
 
     it('should include a suspended resource "displayName" if one is set', async () => {
@@ -1845,8 +1845,6 @@ describe('Timeline profiler', () => {
             ),
           );
 
-          clearPendingMarks();
-
           const testMarks = [creactCpuProfilerSample()];
 
           // Start profiling and suspend during a render.
diff --git a/packages/react-devtools-shared/src/backend/profilingHooks.js b/packages/react-devtools-shared/src/backend/profilingHooks.js
index c6624e2334ba3..c14fe49de9ada 100644
--- a/packages/react-devtools-shared/src/backend/profilingHooks.js
+++ b/packages/react-devtools-shared/src/backend/profilingHooks.js
@@ -134,17 +134,6 @@ export function createProfilingHooks({
   function markCommitStarted(lanes: Lanes): void {
     if (supportsUserTimingV3) {
       markAndClear(`--commit-start-${lanes}`);
-
-      // Certain types of metadata should be logged infrequently.
-      // Normally we would log this during module init,
-      // but there's no guarantee a user is profiling at that time.
-      // Commits happen infrequently (less than renders or state updates)
-      // so we log this extra information along with a commit.
-      // It will likely be logged more than once but that's okay.
-      //
-      // TODO (timeline) Only log this once, when profiling starts.
-      // For the first phase– refactoring– we'll match the previous behavior.
-      markMetadata();
     }
   }
 
@@ -354,10 +343,23 @@ export function createProfilingHooks({
   }
 
   function toggleProfilingStatus(value: boolean) {
-    isProfiling = value;
-
-    if (value) {
-      // TODO (timeline) Log metadata (e.g. profiler and React versions, lane labels).
+    if (isProfiling !== value) {
+      isProfiling = value;
+
+      if (supportsUserTimingV3) {
+        if (isProfiling) {
+          // Some metadata only needs to be logged once per session.
+          // Log it at the start of the session.
+          //
+          // TODO (timeline)
+          // This is the right time to general and store one-off metadata like this,
+          // but using the User Timing API for it will leave things temporarily broken,
+          // because Chrome locks you to the Performance tab once you start recording.
+          // We'll clean this up with a subsequent commit though,
+          // when we store this data in memory like we do with the legacy profiler.
+          markMetadata();
+        }
+      }
     }
   }