Skip to content
This repository has been archived by the owner on May 3, 2024. It is now read-only.

Commit

Permalink
feat(server): add SSR React rendering summary metric (#991)
Browse files Browse the repository at this point in the history
* feat(server): add SSR React rendering summary metric

* feat(server): remove URL path from SSR metric
too much memory usage

* test(server): summary metrics manager

* test(integration): record oneapp_ssr_react_rendering_seconds metric

---------

Co-authored-by: Jonny Adshead <JAdshead@users.noreply.github.com>
Co-authored-by: Matthew Mallimo <matthew.c.mallimo@aexp.com>
  • Loading branch information
3 people committed Jun 14, 2023
1 parent d02f855 commit 3e24352
Show file tree
Hide file tree
Showing 8 changed files with 177 additions and 1 deletion.
5 changes: 5 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"editorconfig": true,
"singleQuote": true,
"trailingComma": "es5"
}
1 change: 1 addition & 0 deletions __tests__/integration/__snapshots__/one-app.spec.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ exports[`Tests that require Docker setup one-app successfully started metrics ha
"oneapp_holocron_module_map_poll_wait_seconds",
"oneapp_holocron_module_map_updated_total",
"oneapp_intl_cache_size_total",
"oneapp_ssr_react_rendering_seconds",
"oneapp_version_info",
"process_cpu_seconds_total",
"process_cpu_system_seconds_total",
Expand Down
82 changes: 82 additions & 0 deletions __tests__/server/metrics/summaries.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Copyright 2023 American Express Travel Related Services Company, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

describe('summaries', () => {
let Summary;

function load() {
jest.resetModules();

jest.mock('prom-client');
({ Summary } = require('prom-client'));

return require('../../../src/server/metrics/summaries');
}

describe('createSummary', () => {
it('is a function', () => {
const { createSummary } = load();
expect(createSummary).toBeInstanceOf(Function);
});

it('creates a summary with the options', () => {
const { createSummary } = load();
createSummary({ name: 'yup' });
expect(Summary).toHaveBeenCalledTimes(1);
const summary = Summary.mock.instances[0];
expect(summary).toBeInstanceOf(Summary);
});

it('does not create a new summary if one with the same name already exists', () => {
const { createSummary } = load();
createSummary({ name: 'yup' });
expect(Summary).toHaveBeenCalledTimes(1);
createSummary({ name: 'yup' });
expect(Summary).toHaveBeenCalledTimes(1);
});
});

describe('startSummaryTimer', () => {
it('is a function', () => {
const { startSummaryTimer } = load();
expect(startSummaryTimer).toBeInstanceOf(Function);
});

it('throws an error if the summary does not exist', () => {
const { startSummaryTimer } = load();
expect(() => startSummaryTimer('nope')).toThrowErrorMatchingInlineSnapshot(
'"unable to find summary nope, please create it first"'
);
});

it('calls the startTimer method of the summary', () => {
const { createSummary, startSummaryTimer } = load();
createSummary({ name: 'yup' });
const summary = Summary.mock.instances[0];
startSummaryTimer('yup');
expect(summary.startTimer).toHaveBeenCalledTimes(1);
});

it('calls the startTimer method of the summary with the arguments', () => {
const { createSummary, startSummaryTimer } = load();
createSummary({ name: 'yup' });
const summary = Summary.mock.instances[0];
startSummaryTimer('yup', 1, 'two', [null, null, null]);
expect(summary.startTimer).toHaveBeenCalledTimes(1);
expect(summary.startTimer).toHaveBeenCalledWith(1, 'two', [null, null, null]);
});
});
});
6 changes: 6 additions & 0 deletions src/server/metrics/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@

import { incrementCounter } from './counters';
import { incrementGauge, setGauge, resetGauge } from './gauges';
import { startSummaryTimer } from './summaries';

import holocron from './holocron';
import intlCache from './intl-cache';
import * as appVersion from './app-version';
import ssr from './ssr';

export {
// counters
Expand All @@ -30,8 +32,12 @@ export {
setGauge,
resetGauge,

// summaries
startSummaryTimer,

// metrics
holocron,
appVersion,
intlCache,
ssr,
};
29 changes: 29 additions & 0 deletions src/server/metrics/ssr.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright 2023 American Express Travel Related Services Company, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

import { createSummary } from './summaries';

import createMetricNamespace from './create-metric-namespace';

const ssrNamespace = createMetricNamespace('ssr');

createSummary({
name: ssrNamespace('react_rendering', 'seconds'),
help: 'time taken by React to render HTML',
labelNames: ['renderMethodName'],
});

export default ssrNamespace.getMetricNames();
39 changes: 39 additions & 0 deletions src/server/metrics/summaries.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright 2023 American Express Travel Related Services Company, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

import { Summary } from 'prom-client';

const summaries = {};

function createSummary(opts) {
const { name } = opts;
if (summaries[name]) {
return;
}
summaries[name] = new Summary(opts);
}

function startSummaryTimer(name, ...args) {
if (!summaries[name]) {
throw new Error(`unable to find summary ${name}, please create it first`);
}
return summaries[name].startTimer(...args);
}

export {
createSummary,
startSummaryTimer,
};
15 changes: 14 additions & 1 deletion src/server/plugins/reactHtml/createRequestHtmlFragment.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,20 @@
* permissions and limitations under the License.
*/

import util from 'util';

import React from 'react';
import { Provider } from 'react-redux';
import url, { Url } from 'url';
import util from 'util';
import { RouterContext, matchPromise } from '@americanexpress/one-app-router';
import { composeModules } from 'holocron';

import createCircuitBreaker from '../../utils/createCircuitBreaker';
import {
startSummaryTimer,

ssr as ssrMetrics,
} from '../../metrics';

import {
getRenderMethodName,
Expand Down Expand Up @@ -121,13 +128,19 @@ const createRequestHtmlFragment = async (request, reply, { createRoutes }) => {
? renderForStaticMarkup
: renderForString;

const finishRenderTimer = startSummaryTimer(
ssrMetrics.reactRendering,
{ renderMethodName: getRenderMethodName(state) }
);
/* eslint-disable react/jsx-props-no-spreading */
const { renderedString, helmetInfo } = renderMethod(
<Provider store={store}>
<RouterContext {...renderProps} />
</Provider>
);
/* eslint-ensable react/jsx-props-no-spreading */
finishRenderTimer();

request.appHtml = renderedString;
request.helmetInfo = helmetInfo;
}
Expand Down
1 change: 1 addition & 0 deletions src/server/ssrServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
/* eslint-disable global-require */

import path from 'path';

import bytes from 'bytes';
import compress from '@fastify/compress';
import Fastify from 'fastify';
Expand Down

0 comments on commit 3e24352

Please sign in to comment.