Performance metrics: Differentiate requests that are from page load (#24327)

* POC page_load_context header

* refactor to make this.pageLoadContext a private field, and relocate setTimeout code

* add pageLoadContext to `ObserveAPIEndpointDuration` method

* move to telemetry_actions

* move PageContext constant in webapp

* give server control of possible page_load_context values

* variable name change

---------

Co-authored-by: mickmister <mickmister>
Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
Michael Kochell
2023-10-13 14:59:12 -04:00
коммит произвёл GitHub
родитель 72c75f01ce
Коммит e9da1ee8ca
8 изменённых файлов: 49 добавлений и 8 удалений

Просмотреть файл

@@ -407,8 +407,15 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != model.APIURLSuffix+"/websocket" { if r.URL.Path != model.APIURLSuffix+"/websocket" {
elapsed := float64(time.Since(now)) / float64(time.Second) elapsed := float64(time.Since(now)) / float64(time.Second)
pageLoadContext := r.Header.Get("X-Page-Load-Context")
if pageLoadContext != "page_load" && pageLoadContext != "reconnect" {
pageLoadContext = ""
}
originClient := string(originClient(r)) originClient := string(originClient(r))
c.App.Metrics().ObserveAPIEndpointDuration(h.HandlerName, r.Method, statusCode, originClient, elapsed)
c.App.Metrics().ObserveAPIEndpointDuration(h.HandlerName, r.Method, statusCode, originClient, pageLoadContext, elapsed)
} }
} }
} }

Просмотреть файл

@@ -58,7 +58,7 @@ type MetricsInterface interface {
IncrementFilesSearchCounter() IncrementFilesSearchCounter()
ObserveFilesSearchDuration(elapsed float64) ObserveFilesSearchDuration(elapsed float64)
ObserveStoreMethodDuration(method, success string, elapsed float64) ObserveStoreMethodDuration(method, success string, elapsed float64)
ObserveAPIEndpointDuration(endpoint, method, statusCode, originClient string, elapsed float64) ObserveAPIEndpointDuration(endpoint, method, statusCode, originClient, pageLoadContext string, elapsed float64)
IncrementPostIndexCounter() IncrementPostIndexCounter()
IncrementFileIndexCounter() IncrementFileIndexCounter()
IncrementUserIndexCounter() IncrementUserIndexCounter()

Просмотреть файл

@@ -239,9 +239,9 @@ func (_m *MetricsInterface) IncrementWebsocketReconnectEvent(eventType string) {
_m.Called(eventType) _m.Called(eventType)
} }
// ObserveAPIEndpointDuration provides a mock function with given fields: endpoint, method, statusCode, originClient, elapsed // ObserveAPIEndpointDuration provides a mock function with given fields: endpoint, method, statusCode, originClient, pageLoadContext, elapsed
func (_m *MetricsInterface) ObserveAPIEndpointDuration(endpoint string, method string, statusCode string, originClient string, elapsed float64) { func (_m *MetricsInterface) ObserveAPIEndpointDuration(endpoint string, method string, statusCode string, originClient string, pageLoadContext string, elapsed float64) {
_m.Called(endpoint, method, statusCode, originClient, elapsed) _m.Called(endpoint, method, statusCode, originClient, pageLoadContext, elapsed)
} }
// ObserveClusterRequestDuration provides a mock function with given fields: elapsed // ObserveClusterRequestDuration provides a mock function with given fields: elapsed

Просмотреть файл

@@ -19,6 +19,8 @@ const SUPPORTS_MEASURE_METHODS = isSupported([
performance.clearMeasures, performance.clearMeasures,
]); ]);
const HEADER_X_PAGE_LOAD_CONTEXT = 'X-Page-Load-Context';
export function isTelemetryEnabled(state) { export function isTelemetryEnabled(state) {
const config = getConfig(state); const config = getConfig(state);
return config.DiagnosticsEnabled === 'true'; return config.DiagnosticsEnabled === 'true';
@@ -262,3 +264,16 @@ function updateRequestCountAtMark(name) {
function getRequestCountAtMark(name) { function getRequestCountAtMark(name) {
return requestCountAtMark[name] ?? 0; return requestCountAtMark[name] ?? 0;
} }
/**
* This allows the server to know that a given HTTP request occurred during page load or reconnect.
* The server then uses this information to store metrics fields based on the request context.
* The setTimeout approach is a "best effort" approach that will produce false positives.
* A more accurate approach will result in more obtrusive code, which would add risk and maintenance cost.
*/
export const temporarilySetPageLoadContext = (pageLoadContext) => {
Client4.setHeader(HEADER_X_PAGE_LOAD_CONTEXT, pageLoadContext);
setTimeout(() => {
Client4.removeHeader(HEADER_X_PAGE_LOAD_CONTEXT);
}, 5000);
};

Просмотреть файл

@@ -118,9 +118,11 @@ import RemovedFromChannelModal from 'components/removed_from_channel_modal';
import WebSocketClient from 'client/web_websocket_client'; import WebSocketClient from 'client/web_websocket_client';
import {loadPlugin, loadPluginsIfNecessary, removePlugin} from 'plugins'; import {loadPlugin, loadPluginsIfNecessary, removePlugin} from 'plugins';
import {getHistory} from 'utils/browser_history'; import {getHistory} from 'utils/browser_history';
import {ActionTypes, Constants, AnnouncementBarMessages, SocketEvents, UserStatuses, ModalIdentifiers, WarnMetricTypes} from 'utils/constants'; import {ActionTypes, Constants, AnnouncementBarMessages, SocketEvents, UserStatuses, ModalIdentifiers, WarnMetricTypes, PageLoadContext} from 'utils/constants';
import {getSiteURL} from 'utils/url'; import {getSiteURL} from 'utils/url';
import {temporarilySetPageLoadContext} from './telemetry_actions';
const dispatch = store.dispatch; const dispatch = store.dispatch;
const getState = store.getState; const getState = store.getState;
@@ -211,6 +213,8 @@ export function reconnect() {
// eslint-disable-next-line // eslint-disable-next-line
console.log('Reconnecting WebSocket'); console.log('Reconnecting WebSocket');
temporarilySetPageLoadContext(PageLoadContext.RECONNECT);
dispatch({ dispatch({
type: GeneralTypes.WEBSOCKET_SUCCESS, type: GeneralTypes.WEBSOCKET_SUCCESS,
timestamp: Date.now(), timestamp: Date.now(),

Просмотреть файл

@@ -24,7 +24,7 @@ import type {ActionResult} from 'mattermost-redux/types/actions';
import {loadRecentlyUsedCustomEmojis} from 'actions/emoji_actions'; import {loadRecentlyUsedCustomEmojis} from 'actions/emoji_actions';
import * as GlobalActions from 'actions/global_actions'; import * as GlobalActions from 'actions/global_actions';
import {measurePageLoadTelemetry, trackEvent, trackSelectorMetrics} from 'actions/telemetry_actions.jsx'; import {measurePageLoadTelemetry, temporarilySetPageLoadContext, trackEvent, trackSelectorMetrics} from 'actions/telemetry_actions.jsx';
import BrowserStore from 'stores/browser_store'; import BrowserStore from 'stores/browser_store';
import store from 'stores/redux_store'; import store from 'stores/redux_store';
@@ -53,7 +53,7 @@ import webSocketClient from 'client/web_websocket_client';
import {initializePlugins} from 'plugins'; import {initializePlugins} from 'plugins';
import Pluggable from 'plugins/pluggable'; import Pluggable from 'plugins/pluggable';
import A11yController from 'utils/a11y_controller'; import A11yController from 'utils/a11y_controller';
import {StoragePrefixes} from 'utils/constants'; import {PageLoadContext, StoragePrefixes} from 'utils/constants';
import {EmojiIndicesByAlias} from 'utils/emoji'; import {EmojiIndicesByAlias} from 'utils/emoji';
import {getSiteURL} from 'utils/url'; import {getSiteURL} from 'utils/url';
import * as UserAgent from 'utils/user_agent'; import * as UserAgent from 'utils/user_agent';
@@ -415,6 +415,8 @@ export default class Root extends React.PureComponent<Props, State> {
}; };
componentDidMount() { componentDidMount() {
temporarilySetPageLoadContext(PageLoadContext.PAGE_LOAD);
this.mounted = true; this.mounted = true;
this.initiateMeRequests(); this.initiateMeRequests();

Просмотреть файл

@@ -2162,4 +2162,9 @@ export const OverActiveUserLimits = {
MAX: 0.1, MAX: 0.1,
} as const; } as const;
export const PageLoadContext = {
PAGE_LOAD: 'page_load',
RECONNECT: 'reconnect',
} as const;
export default Constants; export default Constants;

Просмотреть файл

@@ -216,6 +216,14 @@ export default class Client4 {
this.defaultHeaders['Accept-Language'] = locale; this.defaultHeaders['Accept-Language'] = locale;
} }
setHeader(header: string, value: string) {
this.defaultHeaders[header] = value;
}
removeHeader(header: string) {
delete this.defaultHeaders[header];
}
setEnableLogging(enable: boolean) { setEnableLogging(enable: boolean) {
this.enableLogging = enable; this.enableLogging = enable;
} }