Skip to content

Commit

Permalink
feat(prometheus): allow to rename names of the metrics
Browse files Browse the repository at this point in the history
  • Loading branch information
ardatan committed Feb 8, 2023
1 parent e25a49e commit bcf29df
Show file tree
Hide file tree
Showing 9 changed files with 585 additions and 120 deletions.
6 changes: 6 additions & 0 deletions .changeset/sharp-deers-leave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@graphql-mesh/plugin-prometheus': minor
'@graphql-mesh/types': minor
---

Now you can customize the names of the metrics
137 changes: 96 additions & 41 deletions examples/openapi-orbit/tests/__snapshots__/orbit.test.js.snap

Large diffs are not rendered by default.

37 changes: 37 additions & 0 deletions packages/plugins/prometheus/src/createHistogramForEnvelop.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Histogram, Registry } from 'prom-client';

export const commonLabelsForEnvelop = ['operationType', 'operationName'] as const;

export function commonFillLabelsFnForEnvelop(params: {
operationName?: string;
operationType?: string;
}) {
return {
operationName: params.operationName!,
operationType: params.operationType!,
};
}

interface CreateHistogramContainerForEnvelop {
defaultName: string;
help: string;
valueFromConfig: string | boolean;
registry: Registry;
}

export function createHistogramForEnvelop({
defaultName,
help,
valueFromConfig,
registry,
}: CreateHistogramContainerForEnvelop) {
return {
histogram: new Histogram({
name: typeof valueFromConfig === 'string' ? valueFromConfig : defaultName,
help,
labelNames: commonLabelsForEnvelop,
registers: [registry],
}),
fillLabelsFn: commonFillLabelsFnForEnvelop,
};
}
217 changes: 200 additions & 17 deletions packages/plugins/prometheus/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
import { usePrometheus } from '@graphql-yoga/plugin-prometheus';
import type { Plugin as YogaPlugin } from 'graphql-yoga';
import { Counter, register as defaultRegistry, Histogram, Registry, Summary } from 'prom-client';
import { MeshPlugin, MeshPluginOptions, YamlConfig } from '@graphql-mesh/types';
import { getHeadersObj, loadFromModuleExportExpression } from '@graphql-mesh/utils';
import { Histogram, register as defaultRegistry, Registry } from 'prom-client';
import type { Plugin as YogaPlugin } from 'graphql-yoga';
import {
usePrometheus,
PrometheusTracingPluginConfig as YogaPromPluginConfig,
} from '@graphql-yoga/plugin-prometheus';
import {
commonFillLabelsFnForEnvelop,
commonLabelsForEnvelop,
createHistogramForEnvelop,
} from './createHistogramForEnvelop';

type HistogramContainer = Exclude<YogaPromPluginConfig['http'], boolean>;
type CounterContainer = Exclude<YogaPromPluginConfig['requestCount'], boolean>;
type SummaryContainer = Exclude<YogaPromPluginConfig['requestSummary'], boolean>;

export default async function useMeshPrometheus(
pluginOptions: MeshPluginOptions<YamlConfig.PrometheusConfig>,
Expand All @@ -14,29 +26,200 @@ export default async function useMeshPrometheus(
defaultExportName: 'default',
})
: defaultRegistry;
const fetchHistogram = new Histogram({
name: 'graphql_mesh_fetch_duration',
help: 'Time spent on outgoing HTTP calls',
labelNames: ['url', 'method', 'requestHeaders', 'statusCode', 'statusText', 'responseHeaders'],
registers: [registry],
});
const delegateHistogram = new Histogram({
name: 'graphql_mesh_delegate_duration',
help: 'Time spent on delegate execution',
labelNames: ['sourceName', 'typeName', 'fieldName', 'args', 'key'],
registers: [registry],
});

let fetchHistogram: Histogram | undefined;

if (pluginOptions.fetch) {
const name =
typeof pluginOptions.fetch === 'string' ? pluginOptions.fetch : 'graphql_mesh_fetch_duration';
fetchHistogram = new Histogram({
name,
help: 'Time spent on outgoing HTTP calls',
labelNames: [
'url',
'method',
'requestHeaders',
'statusCode',
'statusText',
'responseHeaders',
],
registers: [registry],
});
}

let delegateHistogram: Histogram | undefined;

if (pluginOptions.delegation) {
const name =
typeof pluginOptions.delegation === 'string'
? pluginOptions.delegation
: 'graphql_mesh_delegate_duration';
delegateHistogram = new Histogram({
name,
help: 'Time spent on delegate execution',
labelNames: ['sourceName', 'typeName', 'fieldName', 'args', 'key'],
registers: [registry],
});
}

let httpHistogram: HistogramContainer | undefined;

if (pluginOptions.http) {
const labelNames = ['url', 'method', 'statusCode', 'statusText', 'responseHeaders'];
if (pluginOptions.httpRequestHeaders) {
labelNames.push('requestHeaders');
}
const name =
typeof pluginOptions.http === 'string' ? pluginOptions.http : 'graphql_mesh_http_duration';
httpHistogram = {
histogram: new Histogram({
name,
help: 'Time spent on incoming HTTP requests',
labelNames,
registers: [registry],
}),
fillLabelsFn(_, { request, response }) {
const labels: Record<string, string> = {
url: request.url,
method: request.method,
statusCode: response.status,
statusText: response.statusText,
responseHeaders: JSON.stringify(getHeadersObj(response.headers)),
};
if (pluginOptions.httpRequestHeaders) {
labels.requestHeaders = JSON.stringify(getHeadersObj(request.headers));
}
return labels;
},
};
}

let requestCounter: CounterContainer | undefined;

if (pluginOptions.requestCount) {
const name =
typeof pluginOptions.requestCount === 'string'
? pluginOptions.requestCount
: 'graphql_mesh_request_count';
requestCounter = {
counter: new Counter({
name,
help: 'Counts the amount of GraphQL requests executed',
labelNames: commonLabelsForEnvelop,
registers: [registry],
}),
fillLabelsFn: commonFillLabelsFnForEnvelop,
};
}

let requestSummary: SummaryContainer | undefined;

if (pluginOptions.requestSummary) {
const name =
typeof pluginOptions.requestSummary === 'string'
? pluginOptions.requestSummary
: 'graphql_mesh_request_time_summary';
requestSummary = {
summary: new Summary({
name,
help: 'Summary to measure the time to complete GraphQL operations',
labelNames: commonLabelsForEnvelop,
registers: [registry],
}),
fillLabelsFn: commonFillLabelsFnForEnvelop,
};
}

let errorsCounter: CounterContainer | undefined;

if (pluginOptions.errors) {
const name =
typeof pluginOptions.errors === 'string' ? pluginOptions.errors : 'graphql_mesh_error_result';
errorsCounter = {
counter: new Counter({
name,
help: 'Counts the amount of errors reported from all phases',
labelNames: ['operationType', 'operationName', 'path', 'phase'] as const,
registers: [registry],
}),
fillLabelsFn: params => ({
operationName: params.operationName!,
operationType: params.operationType!,
path: params.error?.path?.join('.'),
phase: params.errorPhase!,
}),
};
}

let deprecatedCounter: CounterContainer | undefined;

if (pluginOptions.deprecatedFields) {
const name =
typeof pluginOptions.deprecatedFields === 'string'
? pluginOptions.deprecatedFields
: 'graphql_mesh_deprecated_fields';
deprecatedCounter = {
counter: new Counter({
name,
help: 'Counts the amount of deprecated fields used in selection sets',
labelNames: ['operationType', 'operationName', 'fieldName', 'typeName'] as const,
registers: [registry],
}),
fillLabelsFn: params => ({
operationName: params.operationName!,
operationType: params.operationType!,
fieldName: params.deprecationInfo?.fieldName,
typeName: params.deprecationInfo?.typeName,
}),
};
}

return {
onPluginInit({ addPlugin }) {
addPlugin(
usePrometheus({
...pluginOptions,
http: httpHistogram,
requestCount: requestCounter,
requestTotalDuration: createHistogramForEnvelop({
defaultName: 'graphql_mesh_request_duration',
valueFromConfig: pluginOptions.requestTotalDuration,
help: 'Time spent on running the GraphQL operation from parse to execute',
registry,
}),
requestSummary,
parse: createHistogramForEnvelop({
defaultName: 'graphql_mesh_parse_duration',
valueFromConfig: pluginOptions.parse,
help: 'Time spent on parsing the GraphQL operation',
registry,
}),
validate: createHistogramForEnvelop({
defaultName: 'graphql_mesh_validate_duration',
valueFromConfig: pluginOptions.validate,
help: 'Time spent on validating the GraphQL operation',
registry,
}),
contextBuilding: createHistogramForEnvelop({
defaultName: 'graphql_mesh_context_building_duration',
valueFromConfig: pluginOptions.contextBuilding,
help: 'Time spent on building the GraphQL context',
registry,
}),
execute: createHistogramForEnvelop({
defaultName: 'graphql_mesh_execute_duration',
valueFromConfig: pluginOptions.execute,
help: 'Time spent on executing the GraphQL operation',
registry,
}),
errors: errorsCounter,
deprecatedFields: deprecatedCounter,
registry,
}),
);
},
onDelegate({ sourceName, typeName, fieldName, args, key }) {
if (pluginOptions.delegation !== false) {
if (delegateHistogram) {
const start = Date.now();
return () => {
const end = Date.now();
Expand All @@ -56,7 +239,7 @@ export default async function useMeshPrometheus(
return undefined;
},
onFetch({ url, options }) {
if (pluginOptions.fetch !== false) {
if (fetchHistogram) {
const start = Date.now();
return ({ response }) => {
const end = Date.now();
Expand Down
34 changes: 21 additions & 13 deletions packages/plugins/prometheus/yaml-config.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,32 @@ extend type Plugin {
}

type PrometheusConfig @md {
requestCount: Boolean
requestTotalDuration: Boolean
requestSummary: Boolean
parse: Boolean
validate: Boolean
contextBuilding: Boolean
execute: Boolean
errors: Boolean
resolvers: Boolean
resolversWhiteList: [String]
deprecatedFields: Boolean
delegation: Boolean
fetch: Boolean
requestCount: BooleanOrString
requestTotalDuration: BooleanOrString
requestSummary: BooleanOrString
parse: BooleanOrString
validate: BooleanOrString
contextBuilding: BooleanOrString
execute: BooleanOrString
errors: BooleanOrString
deprecatedFields: BooleanOrString

skipIntrospection: Boolean
registry: String

# Mesh specific flags
delegation: BooleanOrString
fetch: BooleanOrString
fetchRequestHeaders: Boolean

# Yoga specific flags
http: BooleanOrString
httpRequestHeaders: Boolean
"""
The path to the metrics endpoint
default: `/metrics`
"""
endpoint: String
}

union BooleanOrString = Boolean | String
Loading

0 comments on commit bcf29df

Please sign in to comment.