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>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
72c75f01ce
Коммит
e9da1ee8ca
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user