diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md index 335cb5195e0..f5b2cbc31bf 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -74,53 +74,54 @@ Some features are enabled by default. You can disable these feature by setting t These features are early in their development lifecycle and so are not yet supported in Grafana Cloud. Experimental features might be changed or removed without prior notice. -| Feature toggle name | Description | -| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------ | -| `live-service-web-worker` | This will use a webworker thread to processes events rather than the main thread | -| `queryOverLive` | Use Grafana Live WebSocket to execute backend queries | -| `lokiExperimentalStreaming` | Support new streaming approach for loki (prototype, needs special loki build) | -| `storage` | Configurable storage for dashboards, datasources, and resources | -| `newTraceViewHeader` | Shows the new trace view header | -| `datasourceQueryMultiStatus` | Introduce HTTP 207 Multi Status for api/ds/query | -| `traceToMetrics` | Enable trace to metrics links | -| `prometheusWideSeries` | Enable wide series responses in the Prometheus datasource | -| `canvasPanelNesting` | Allow elements nesting | -| `scenes` | Experimental framework to build interactive dashboards | -| `disableSecretsCompatibility` | Disable duplicated secret storage in legacy tables | -| `logRequestsInstrumentedAsUnknown` | Logs the path for requests that are instrumented as unknown | -| `showDashboardValidationWarnings` | Show warnings when dashboards do not validate against the schema | -| `mysqlAnsiQuotes` | Use double quotes to escape keyword in a MySQL query | -| `nestedFolderPicker` | Enables the still in-development new folder picker to support nested folders | -| `showTraceId` | Show trace ids for requests | -| `alertingBacktesting` | Rule backtesting API for alerting | -| `editPanelCSVDragAndDrop` | Enables drag and drop for CSV and Excel files | -| `lokiQuerySplitting` | Split large interval queries into subqueries with smaller time intervals | -| `lokiQuerySplittingConfig` | Give users the option to configure split durations for Loki queries | -| `individualCookiePreferences` | Support overriding cookie preferences per user | -| `onlyExternalOrgRoleSync` | Prohibits a user from changing organization roles synced with external auth providers | -| `traceqlSearch` | Enables the 'TraceQL Search' tab for the Tempo datasource which provides a UI to generate TraceQL queries | -| `timeSeriesTable` | Enable time series table transformer & sparkline cell type | -| `prometheusResourceBrowserCache` | Displays browser caching options in Prometheus data source configuration | -| `influxdbBackendMigration` | Query InfluxDB InfluxQL without the proxy | -| `clientTokenRotation` | Replaces the current in-request token rotation so that the client initiates the rotation | -| `disableSSEDataplane` | Disables dataplane specific processing in server side expressions. | -| `alertStateHistoryLokiSecondary` | Enable Grafana to write alert state history to an external Loki instance in addition to Grafana annotations. | -| `alertStateHistoryLokiPrimary` | Enable a remote Loki instance as the primary source for state history reads. | -| `alertStateHistoryLokiOnly` | Disable Grafana alerts from emitting annotations when a remote Loki instance is available. | -| `unifiedRequestLog` | Writes error logs to the request logger | -| `pyroscopeFlameGraph` | Changes flame graph to pyroscope one | -| `extraThemes` | Enables extra themes | -| `lokiPredefinedOperations` | Adds predefined query operations to Loki query editor | -| `pluginsFrontendSandbox` | Enables the plugins frontend sandbox | -| `frontendSandboxMonitorOnly` | Enables monitor only in the plugin frontend sandbox (if enabled) | -| `cloudWatchLogsMonacoEditor` | Enables the Monaco editor for CloudWatch Logs queries | -| `exploreScrollableLogsContainer` | Improves the scrolling behavior of logs in Explore | -| `recordedQueriesMulti` | Enables writing multiple items from a single query within Recorded Queries | -| `pluginsDynamicAngularDetectionPatterns` | Enables fetching Angular detection patterns for plugins from GCOM and fallback to hardcoded ones | -| `alertingLokiRangeToInstant` | Rewrites eligible loki range queries to instant queries | -| `flameGraphV2` | New version of flame graph with new features | -| `elasticToggleableFilters` | Enable support to toggle filters off from the query through the Logs Details component | -| `vizAndWidgetSplit` | Split panels between vizualizations and widgets | +| Feature toggle name | Description | +| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | +| `live-service-web-worker` | This will use a webworker thread to processes events rather than the main thread | +| `queryOverLive` | Use Grafana Live WebSocket to execute backend queries | +| `lokiExperimentalStreaming` | Support new streaming approach for loki (prototype, needs special loki build) | +| `storage` | Configurable storage for dashboards, datasources, and resources | +| `newTraceViewHeader` | Shows the new trace view header | +| `datasourceQueryMultiStatus` | Introduce HTTP 207 Multi Status for api/ds/query | +| `traceToMetrics` | Enable trace to metrics links | +| `prometheusWideSeries` | Enable wide series responses in the Prometheus datasource | +| `canvasPanelNesting` | Allow elements nesting | +| `scenes` | Experimental framework to build interactive dashboards | +| `disableSecretsCompatibility` | Disable duplicated secret storage in legacy tables | +| `logRequestsInstrumentedAsUnknown` | Logs the path for requests that are instrumented as unknown | +| `showDashboardValidationWarnings` | Show warnings when dashboards do not validate against the schema | +| `mysqlAnsiQuotes` | Use double quotes to escape keyword in a MySQL query | +| `nestedFolderPicker` | Enables the still in-development new folder picker to support nested folders | +| `showTraceId` | Show trace ids for requests | +| `alertingBacktesting` | Rule backtesting API for alerting | +| `editPanelCSVDragAndDrop` | Enables drag and drop for CSV and Excel files | +| `lokiQuerySplitting` | Split large interval queries into subqueries with smaller time intervals | +| `lokiQuerySplittingConfig` | Give users the option to configure split durations for Loki queries | +| `individualCookiePreferences` | Support overriding cookie preferences per user | +| `onlyExternalOrgRoleSync` | Prohibits a user from changing organization roles synced with external auth providers | +| `traceqlSearch` | Enables the 'TraceQL Search' tab for the Tempo datasource which provides a UI to generate TraceQL queries | +| `timeSeriesTable` | Enable time series table transformer & sparkline cell type | +| `prometheusResourceBrowserCache` | Displays browser caching options in Prometheus data source configuration | +| `influxdbBackendMigration` | Query InfluxDB InfluxQL without the proxy | +| `clientTokenRotation` | Replaces the current in-request token rotation so that the client initiates the rotation | +| `disableSSEDataplane` | Disables dataplane specific processing in server side expressions. | +| `alertStateHistoryLokiSecondary` | Enable Grafana to write alert state history to an external Loki instance in addition to Grafana annotations. | +| `alertStateHistoryLokiPrimary` | Enable a remote Loki instance as the primary source for state history reads. | +| `alertStateHistoryLokiOnly` | Disable Grafana alerts from emitting annotations when a remote Loki instance is available. | +| `unifiedRequestLog` | Writes error logs to the request logger | +| `pyroscopeFlameGraph` | Changes flame graph to pyroscope one | +| `extraThemes` | Enables extra themes | +| `lokiPredefinedOperations` | Adds predefined query operations to Loki query editor | +| `pluginsFrontendSandbox` | Enables the plugins frontend sandbox | +| `frontendSandboxMonitorOnly` | Enables monitor only in the plugin frontend sandbox (if enabled) | +| `cloudWatchLogsMonacoEditor` | Enables the Monaco editor for CloudWatch Logs queries | +| `exploreScrollableLogsContainer` | Improves the scrolling behavior of logs in Explore | +| `recordedQueriesMulti` | Enables writing multiple items from a single query within Recorded Queries | +| `pluginsDynamicAngularDetectionPatterns` | Enables fetching Angular detection patterns for plugins from GCOM and fallback to hardcoded ones | +| `alertingLokiRangeToInstant` | Rewrites eligible loki range queries to instant queries | +| `flameGraphV2` | New version of flame graph with new features | +| `elasticToggleableFilters` | Enable support to toggle filters off from the query through the Logs Details component | +| `vizAndWidgetSplit` | Split panels between vizualizations and widgets | +| `prometheusIncrementalQueryInstrumentation` | Adds RudderStack events to incremental queries | ## Development feature toggles diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 82e5d36eb8c..b43a0168447 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -108,4 +108,5 @@ export interface FeatureToggles { flameGraphV2?: boolean; elasticToggleableFilters?: boolean; vizAndWidgetSplit?: boolean; + prometheusIncrementalQueryInstrumentation?: boolean; } diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 995e49fa09c..9e5c54eedb6 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -614,5 +614,12 @@ var ( FrontendOnly: true, Owner: grafanaDashboardsSquad, }, + { + Name: "prometheusIncrementalQueryInstrumentation", + Description: "Adds RudderStack events to incremental queries", + FrontendOnly: true, + Stage: FeatureStageExperimental, + Owner: grafanaObservabilityMetricsSquad, + }, } ) diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 9597928bb91..98138e32100 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -89,3 +89,4 @@ alertingLokiRangeToInstant,experimental,@grafana/alerting-squad,false,false,fals flameGraphV2,experimental,@grafana/observability-traces-and-profiling,false,false,false,true elasticToggleableFilters,experimental,@grafana/observability-logs,false,false,false,true vizAndWidgetSplit,experimental,@grafana/dashboards-squad,false,false,false,true +prometheusIncrementalQueryInstrumentation,experimental,@grafana/observability-metrics,false,false,false,true diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 2dd78b03ab0..0c4bb0114d6 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -366,4 +366,8 @@ const ( // FlagVizAndWidgetSplit // Split panels between vizualizations and widgets FlagVizAndWidgetSplit = "vizAndWidgetSplit" + + // FlagPrometheusIncrementalQueryInstrumentation + // Adds RudderStack events to incremental queries + FlagPrometheusIncrementalQueryInstrumentation = "prometheusIncrementalQueryInstrumentation" ) diff --git a/public/app/features/dashboard/services/TimeSrv.ts b/public/app/features/dashboard/services/TimeSrv.ts index b3df6f21736..41b7a68b655 100644 --- a/public/app/features/dashboard/services/TimeSrv.ts +++ b/public/app/features/dashboard/services/TimeSrv.ts @@ -29,8 +29,8 @@ export class TimeSrv { oldRefresh?: string; timeModel?: TimeModel; timeAtLoad: RawTimeRange; + refreshMS?: number; private autoRefreshBlocked?: boolean; - private refreshMS?: number; constructor(private contextSrv: ContextSrv) { // default time diff --git a/public/app/plugins/datasource/prometheus/querycache/QueryCache.ts b/public/app/plugins/datasource/prometheus/querycache/QueryCache.ts index a2295d77e5b..9ce6e910c2e 100644 --- a/public/app/plugins/datasource/prometheus/querycache/QueryCache.ts +++ b/public/app/plugins/datasource/prometheus/querycache/QueryCache.ts @@ -9,9 +9,10 @@ import { parseDuration, } from '@grafana/data/src'; import { faro } from '@grafana/faro-web-sdk'; -import { config } from '@grafana/runtime/src'; +import { config, reportInteraction } from '@grafana/runtime/src'; import { amendTable, Table, trimTable } from 'app/features/live/data/amendTimeSeries'; +import { getTimeSrv } from '../../../../features/dashboard/services/TimeSrv'; import { PromQuery } from '../types'; // dashboardUID + panelId + refId @@ -54,6 +55,9 @@ interface ProfileData extends DatasourceProfileData { bytes: number | null; dashboardUID: string; panelId?: number; + from: string; + queryRangeSeconds: number; + refreshIntervalMs: number; } /** @@ -77,8 +81,8 @@ export class QueryCache { private perfObeserver?: PerformanceObserver; private shouldProfile: boolean; - // send profile events every 5 minutes - sendEventsInterval = 60000 * 5; + // send profile events every 10 minutes + sendEventsInterval = 60000 * 10; pendingRequestIdsToTargSigs = new Map(); @@ -92,9 +96,11 @@ export class QueryCache { panelId: string; dashId: string; expr: string; - interval: string; + refreshIntervalMs: number; sent: boolean; datasource: string; + from: string; + queryRangeSeconds: number; } >(); @@ -114,7 +120,10 @@ export class QueryCache { this.overlapWindowMs = durationToMilliseconds(duration); } - if (config.grafanaJavascriptAgent.enabled && options.profileFunction !== undefined) { + if ( + (config.grafanaJavascriptAgent.enabled || config.featureToggles?.prometheusIncrementalQueryInstrumentation) && + options.profileFunction !== undefined + ) { this.profile(); this.shouldProfile = true; } else { @@ -167,8 +176,10 @@ export class QueryCache { panelId: currentRequest.panelId?.toString() ?? '', dashId: currentRequest.dashboardUID ?? '', expr: currentRequest.expr ?? '', - interval: currentRequest.interval ?? '', + refreshIntervalMs: currentRequest.refreshIntervalMs ?? 0, sent: false, + from: currentRequest.from ?? '', + queryRangeSeconds: currentRequest.queryRangeSeconds ?? 0, }); // We don't need to save each subsequent request, only the first one @@ -201,25 +212,36 @@ export class QueryCache { for (let [key, value] of entries) { if (!value.sent) { - this.pendingAccumulatedEvents.set(key, { ...value, sent: true }); - faro.api.pushEvent( - 'incremental query response size', - { - datasource: value.datasource.toString(), - requestCount: value.requestCount.toString(), - savedBytesTotal: value.savedBytesTotal.toString(), - initialRequestSize: value.initialRequestSize.toString(), - lastRequestSize: value.lastRequestSize.toString(), - panelId: value.panelId.toString(), - dashId: value.dashId.toString(), - expr: value.expr.toString(), - interval: value.interval.toString(), - }, - 'no-interaction', - { + const event = { + datasource: value.datasource.toString(), + requestCount: value.requestCount.toString(), + savedBytesTotal: value.savedBytesTotal.toString(), + initialRequestSize: value.initialRequestSize.toString(), + lastRequestSize: value.lastRequestSize.toString(), + panelId: value.panelId.toString(), + dashId: value.dashId.toString(), + expr: value.expr.toString(), + refreshIntervalMs: value.refreshIntervalMs.toString(), + from: value.from.toString(), + queryRangeSeconds: value.queryRangeSeconds.toString(), + }; + + if (config.featureToggles.prometheusIncrementalQueryInstrumentation) { + reportInteraction('grafana_incremental_queries_profile', event); + } else if (faro.api.pushEvent) { + faro.api.pushEvent('incremental query response size', event, 'no-interaction', { skipDedupe: true, - } - ); + }); + } + + this.pendingAccumulatedEvents.set(key, { + ...value, + sent: true, + requestCount: 0, + savedBytesTotal: 0, + initialRequestSize: 0, + lastRequestSize: 0, + }); } } }; @@ -238,6 +260,8 @@ export class QueryCache { let doPartialQuery = shouldCache; let prevTo: TimestampMs | undefined = undefined; + const refreshIntervalMs = getTimeSrv().refreshMS; + // pre-compute reqTargSigs const reqTargSigs = new Map(); request.targets.forEach((targ) => { @@ -251,6 +275,9 @@ export class QueryCache { bytes: null, panelId: request.panelId, dashboardUID: request.dashboardUID ?? '', + from: request.rangeRaw?.from.toString() ?? '', + queryRangeSeconds: request.range.to.diff(request.range.from, 'seconds') ?? '', + refreshIntervalMs: refreshIntervalMs ?? 0, }); }