diff --git a/e2e/suite1/specs/exemplars.spec.ts b/e2e/suite1/specs/exemplars.spec.ts index 29ad989294c..a0c62652240 100644 --- a/e2e/suite1/specs/exemplars.spec.ts +++ b/e2e/suite1/specs/exemplars.spec.ts @@ -35,17 +35,15 @@ describe('Exemplars', () => { }); it('should be able to navigate to configured data source', () => { - e2e().intercept('POST', '**/api/v1/query_exemplars', { - fixture: 'exemplars-query-response.json', - }); - e2e().intercept('POST', '**/api/v1/query_range', { - fixture: 'prometheus-query-range-response.json', - }); - e2e().intercept('POST', '**/api/v1/query', { - fixture: 'prometheus-query-response.json', - }); - e2e().intercept('POST', '**/api/ds/query', { - fixture: 'tempo-response.json', + let intercept = 'prometheus'; + e2e().intercept('/api/ds/query', (req) => { + if (intercept === 'prometheus') { + // For second intercept, we want to send tempo response + intercept = 'tempo'; + req.reply({ fixture: 'exemplars-query-response.json' }); + } else { + req.reply({ fixture: 'tempo-response.json' }); + } }); e2e.pages.Explore.visit(); @@ -57,10 +55,10 @@ describe('Exemplars', () => { }); e2e().contains(dataSourceName).scrollIntoView().should('be.visible').click(); e2e.components.TimePicker.openButton().click(); - e2e.components.TimePicker.fromField().clear().type('2021-05-11 19:30:00'); - e2e.components.TimePicker.toField().clear().type('2021-05-11 21:40:00'); + e2e.components.TimePicker.fromField().clear().type('2021-07-10 17:10:00'); + e2e.components.TimePicker.toField().clear().type('2021-07-10 17:30:00'); e2e.components.TimePicker.applyTimeRange().click(); - e2e.components.QueryField.container().should('be.visible').type('exemplar-query{shift}{enter}'); + e2e.components.QueryField.container().should('be.visible').type('exemplar-query_bucket{shift}{enter}'); e2e.components.DataSource.Prometheus.exemplarMarker().first().trigger('mouseover'); e2e().contains('Query with gdev-tempo').click(); diff --git a/packages/grafana-e2e/cypress/fixtures/exemplars-query-response.json b/packages/grafana-e2e/cypress/fixtures/exemplars-query-response.json index 2b62d0d0104..c90d2c8457e 100644 --- a/packages/grafana-e2e/cypress/fixtures/exemplars-query-response.json +++ b/packages/grafana-e2e/cypress/fixtures/exemplars-query-response.json @@ -1,689 +1,396 @@ { - "status": "success", - "data": [ - { - "seriesLabels": { - "__name__": "tns_request_duration_seconds_bucket", - "instance": "app:80", - "job": "tns/app", - "le": "0.005", - "method": "GET", - "route": "metrics", - "status_code": "200", - "ws": "false" - }, - "exemplars": [ - { "labels": { "traceID": "6623c7a94128bac8" }, "value": "0.00216539", "timestamp": 1620761554.019 }, - { "labels": { "traceID": "4c52b2937c4bf7e9" }, "value": "0.003287295", "timestamp": 1620761569.019 }, - { "labels": { "traceID": "25fd3243a657ca60" }, "value": "0.001866048", "timestamp": 1620761584.018 }, - { "labels": { "traceID": "397a1c3c9c5787a1" }, "value": "0.003148269", "timestamp": 1620761599.02 }, - { "labels": { "traceID": "25d3b7220c72fee9" }, "value": "0.002628446", "timestamp": 1620761614.02 }, - { "labels": { "traceID": "4dee9e2e642ceb1b" }, "value": "0.002214287", "timestamp": 1620761629.019 }, - { "labels": { "traceID": "2ff6691ace11d628" }, "value": "0.00249281", "timestamp": 1620761644.019 }, - { "labels": { "traceID": "194ad24c9ba1bd07" }, "value": "0.001838908", "timestamp": 1620761659.019 }, - { "labels": { "traceID": "4b8cc07804c47e5e" }, "value": "0.001588308", "timestamp": 1620761674.018 }, - { "labels": { "traceID": "52e0b60eee403668" }, "value": "0.00358922", "timestamp": 1620761689.021 }, - { "labels": { "traceID": "70d0ee4d123218bc" }, "value": "0.00331858", "timestamp": 1620761704.02 }, - { "labels": { "traceID": "2dc43f64cff43bb2" }, "value": "0.002070016", "timestamp": 1620761734.02 }, - { "labels": { "traceID": "21bca17f97b0272f" }, "value": "0.002824294", "timestamp": 1620761749.024 }, - { "labels": { "traceID": "553d2935b363cdb7" }, "value": "0.00238151", "timestamp": 1620761764.021 }, - { "labels": { "traceID": "594a6f8d9a876bbb" }, "value": "0.004186841", "timestamp": 1620761779.023 }, - { "labels": { "traceID": "5d763419bccd426a" }, "value": "0.00338705", "timestamp": 1620761794.022 }, - { "labels": { "traceID": "2a6844e440881c61" }, "value": "0.002208834", "timestamp": 1620761809.021 }, - { "labels": { "traceID": "48831c3e3fdc6efa" }, "value": "0.002346334", "timestamp": 1620761824.023 } - ] - }, - { - "seriesLabels": { - "__name__": "tns_request_duration_seconds_bucket", - "instance": "app:80", - "job": "tns/app", - "le": "0.005", - "method": "GET", - "route": "root", - "status_code": "200", - "ws": "false" - }, - "exemplars": [ - { "labels": { "traceID": "1ea3daf127eb9d1c" }, "value": "0.003946934", "timestamp": 1620761553.987 }, - { "labels": { "traceID": "51679d5d998786b2" }, "value": "0.004066077", "timestamp": 1620761568.267 }, - { "labels": { "traceID": "382ef7c7443ac767" }, "value": "0.004315568", "timestamp": 1620761583.705 }, - { "labels": { "traceID": "2293889080db0941" }, "value": "0.004329813", "timestamp": 1620761598.805 }, - { "labels": { "traceID": "688d69666ea10ff8" }, "value": "0.004617165", "timestamp": 1620761613.998 }, - { "labels": { "traceID": "1c773f1f95bb7d18" }, "value": "0.004484882", "timestamp": 1620761628.852 }, - { "labels": { "traceID": "2bbceff02d9c1999" }, "value": "0.004462996", "timestamp": 1620761644.001 }, - { "labels": { "traceID": "cc0948b0e2131ef" }, "value": "0.00373521", "timestamp": 1620761658.815 }, - { "labels": { "traceID": "111963033b986bd2" }, "value": "0.004494162", "timestamp": 1620761673.858 }, - { "labels": { "traceID": "3fc3884869e1ba9b" }, "value": "0.003502476", "timestamp": 1620761688.887 }, - { "labels": { "traceID": "7bad9a5f3022ff02" }, "value": "0.004715123", "timestamp": 1620761701.94 }, - { "labels": { "traceID": "64a27298fe5eb126" }, "value": "0.003864008", "timestamp": 1620761717.442 }, - { "labels": { "traceID": "65899cbe575adf02" }, "value": "0.002920626", "timestamp": 1620761733.959 }, - { "labels": { "traceID": "c726ece86d4a710" }, "value": "0.003622465", "timestamp": 1620761748.911 }, - { "labels": { "traceID": "2d635e70ad017769" }, "value": "0.003963836", "timestamp": 1620761763.995 }, - { "labels": { "traceID": "3278c3d318b22864" }, "value": "0.00434111", "timestamp": 1620761779.01 }, - { "labels": { "traceID": "3f113e0178fdb0dd" }, "value": "0.004594791", "timestamp": 1620761793.751 }, - { "labels": { "traceID": "3288fae3cdbe9f5f" }, "value": "0.004687213", "timestamp": 1620761808.546 }, - { "labels": { "traceID": "20c93a191afa0d75" }, "value": "0.00297728", "timestamp": 1620761823.855 } - ] - }, - { - "seriesLabels": { - "__name__": "tns_request_duration_seconds_bucket", - "instance": "app:80", - "job": "tns/app", - "le": "0.005", - "method": "POST", - "route": "post", - "status_code": "302", - "ws": "false" - }, - "exemplars": [ - { "labels": { "traceID": "537dcbf082c2ba93" }, "value": "0.002259945", "timestamp": 1620761553.734 }, - { "labels": { "traceID": "b7b1c0f3f087234" }, "value": "0.0031329", "timestamp": 1620761567.735 }, - { "labels": { "traceID": "19d421ff7a9fbfd0" }, "value": "0.003298626", "timestamp": 1620761581.737 }, - { "labels": { "traceID": "30f169e59cd744cf" }, "value": "0.004458387", "timestamp": 1620761597.739 }, - { "labels": { "traceID": "1a113dac6e761788" }, "value": "0.003496792", "timestamp": 1620761613.737 }, - { "labels": { "traceID": "3e29e5b6d6dd7722" }, "value": "0.002952011", "timestamp": 1620761628.737 }, - { "labels": { "traceID": "48aeb901f90de9e6" }, "value": "0.004521775", "timestamp": 1620761643.738 }, - { "labels": { "traceID": "75f2894c43af3bff" }, "value": "0.003759985", "timestamp": 1620761658.738 }, - { "labels": { "traceID": "190a345256a93f09" }, "value": "0.004030832", "timestamp": 1620761673.738 }, - { "labels": { "traceID": "6646a6cab2abf449" }, "value": "0.002915072", "timestamp": 1620761688.737 }, - { "labels": { "traceID": "1a51480d3f15c93c" }, "value": "0.004487764", "timestamp": 1620761699.741 }, - { "labels": { "traceID": "64f06303658e8ba5" }, "value": "0.002668202", "timestamp": 1620761718.738 }, - { "labels": { "traceID": "6df9298b13e1484d" }, "value": "0.004423943", "timestamp": 1620761733.739 }, - { "labels": { "traceID": "acb1e8e2a771d37" }, "value": "0.0038423", "timestamp": 1620761748.739 }, - { "labels": { "traceID": "42a09adc7104c3d1" }, "value": "0.004819886", "timestamp": 1620761763.741 }, - { "labels": { "traceID": "2335f4fdbcac032f" }, "value": "0.003573412", "timestamp": 1620761778.739 }, - { "labels": { "traceID": "3f113e0178fdb0dd" }, "value": "0.004221459", "timestamp": 1620761793.742 }, - { "labels": { "traceID": "44f2710ddd429f88" }, "value": "0.004184554", "timestamp": 1620761807.74 }, - { "labels": { "traceID": "10cc4ee6f3a5fa3d" }, "value": "0.004187996", "timestamp": 1620761823.739 } - ] - }, - { - "seriesLabels": { - "__name__": "tns_request_duration_seconds_bucket", - "instance": "app:80", - "job": "tns/app", - "le": "0.005", - "method": "POST", - "route": "post", - "status_code": "400", - "ws": "false" - }, - "exemplars": [ - { "labels": { "traceID": "3f7fb970c1f294f2" }, "value": "0.000098694", "timestamp": 1620761542.735 }, - { "labels": { "traceID": "115158569f7d707a" }, "value": "0.000132017", "timestamp": 1620761568.79 }, - { "labels": { "traceID": "6b787cceb3a39d90" }, "value": "0.000090596", "timestamp": 1620761578.732 }, - { "labels": { "traceID": "51794e367d93aab0" }, "value": "0.000175678", "timestamp": 1620761598.736 }, - { "labels": { "traceID": "6b3294f54e87a19" }, "value": "0.000128528", "timestamp": 1620761622.734 }, - { "labels": { "traceID": "536ace200802727d" }, "value": "0.000287827", "timestamp": 1620761634.733 }, - { "labels": { "traceID": "664cf4e512fafb90" }, "value": "0.000077442", "timestamp": 1620761671.734 }, - { "labels": { "traceID": "19ae8ff764b395e2" }, "value": "0.000067022", "timestamp": 1620761684.734 }, - { "labels": { "traceID": "283d6dc94d6323fa" }, "value": "0.000069221", "timestamp": 1620761693.734 }, - { "labels": { "traceID": "5376cc7042f63779" }, "value": "0.000271275", "timestamp": 1620761709.735 }, - { "labels": { "traceID": "7ce1e108a390ff40" }, "value": "0.000120935", "timestamp": 1620761728.735 }, - { "labels": { "traceID": "7ad9d75271ca1f23" }, "value": "0.000124766", "timestamp": 1620761790.736 }, - { "labels": { "traceID": "463c54384a09e34d" }, "value": "0.000084398", "timestamp": 1620761808.736 }, - { "labels": { "traceID": "13dd0f69896188c9" }, "value": "0.00026161", "timestamp": 1620761830.737 } - ] - }, - { - "seriesLabels": { - "__name__": "tns_request_duration_seconds_bucket", - "instance": "app:80", - "job": "tns/app", - "le": "0.01", - "method": "GET", - "route": "metrics", - "status_code": "200", - "ws": "false" - }, - "exemplars": [ - { "labels": { "traceID": "6612571de3974238" }, "value": "0.007150668", "timestamp": 1620761719.025 } - ] - }, - { - "seriesLabels": { - "__name__": "tns_request_duration_seconds_bucket", - "instance": "app:80", - "job": "tns/app", - "le": "0.01", - "method": "GET", - "route": "root", - "status_code": "200", - "ws": "false" - }, - "exemplars": [ - { "labels": { "traceID": "3da8d4cf892051b2" }, "value": "0.006038014", "timestamp": 1620761553.851 }, - { "labels": { "traceID": "279666d141ccc700" }, "value": "0.007033261", "timestamp": 1620761568.57 }, - { "labels": { "traceID": "7bcd6efd1f7fa818" }, "value": "0.005969904", "timestamp": 1620761583.752 }, - { "labels": { "traceID": "27a3c8926f1b9877" }, "value": "0.005367895", "timestamp": 1620761598.806 }, - { "labels": { "traceID": "1ad0f0d3b79362cf" }, "value": "0.005424866", "timestamp": 1620761613.965 }, - { "labels": { "traceID": "7ff0012d0145146e" }, "value": "0.006629428", "timestamp": 1620761628.76 }, - { "labels": { "traceID": "117d0cc5f188caef" }, "value": "0.005852065", "timestamp": 1620761643.03 }, - { "labels": { "traceID": "75f2894c43af3bff" }, "value": "0.005215921", "timestamp": 1620761658.748 }, - { "labels": { "traceID": "740d7184dc4777e7" }, "value": "0.006845462", "timestamp": 1620761673.954 }, - { "labels": { "traceID": "20a5058ab26523c0" }, "value": "0.006475851", "timestamp": 1620761688.691 }, - { "labels": { "traceID": "1e7ce2508bbc0cbf" }, "value": "0.005238892", "timestamp": 1620761699.755 }, - { "labels": { "traceID": "7a92e8b7da9d8b99" }, "value": "0.006169931", "timestamp": 1620761714.961 }, - { "labels": { "traceID": "6df9298b13e1484d" }, "value": "0.005591603", "timestamp": 1620761733.747 }, - { "labels": { "traceID": "55f5ad6c51c8765e" }, "value": "0.008291714", "timestamp": 1620761748.746 }, - { "labels": { "traceID": "47feabeda632e388" }, "value": "0.006845111", "timestamp": 1620761763.953 }, - { "labels": { "traceID": "117954c3e46885a1" }, "value": "0.009813466", "timestamp": 1620761778.148 }, - { "labels": { "traceID": "cc5a60a45d0f5eb" }, "value": "0.006314917", "timestamp": 1620761793.906 }, - { "labels": { "traceID": "4f5eb90df5bb7d45" }, "value": "0.005630173", "timestamp": 1620761808.838 }, - { "labels": { "traceID": "42c09cb92589e902" }, "value": "0.005183919", "timestamp": 1620761823.389 } - ] - }, - { - "seriesLabels": { - "__name__": "tns_request_duration_seconds_bucket", - "instance": "app:80", - "job": "tns/app", - "le": "0.01", - "method": "POST", - "route": "post", - "status_code": "302", - "ws": "false" - }, - "exemplars": [ - { "labels": { "traceID": "20ccda59b5067bb5" }, "value": "0.009613555", "timestamp": 1620761566.742 }, - { "labels": { "traceID": "7bcd6efd1f7fa818" }, "value": "0.008263243", "timestamp": 1620761583.743 }, - { "labels": { "traceID": "4105caeb23bd8fc" }, "value": "0.005264553", "timestamp": 1620761607.74 }, - { "labels": { "traceID": "3f2a2cafe421a6f2" }, "value": "0.005893955", "timestamp": 1620761640.741 }, - { "labels": { "traceID": "413ca6c79c5a8213" }, "value": "0.005064354", "timestamp": 1620761656.742 }, - { "labels": { "traceID": "334ed173e4452dbc" }, "value": "0.005211702", "timestamp": 1620761661.739 }, - { "labels": { "traceID": "5f7e6c01bda53121" }, "value": "0.005207258", "timestamp": 1620761680.739 }, - { "labels": { "traceID": "6034e997a0300d4b" }, "value": "0.007681097", "timestamp": 1620761690.76 }, - { "labels": { "traceID": "1aa1d6cb1534f975" }, "value": "0.005098862", "timestamp": 1620761722.739 }, - { "labels": { "traceID": "3cdf79cbf199a051" }, "value": "0.007718517", "timestamp": 1620761747.755 }, - { "labels": { "traceID": "3e36c19d1a6358e4" }, "value": "0.005250576", "timestamp": 1620761761.74 }, - { "labels": { "traceID": "9b5a8f99f120bef" }, "value": "0.00530067", "timestamp": 1620761771.742 }, - { "labels": { "traceID": "7f084f1db1c70a40" }, "value": "0.005014128", "timestamp": 1620761797.741 }, - { "labels": { "traceID": "4a4ee0b768e93f06" }, "value": "0.007840089", "timestamp": 1620761828.748 } - ] - }, - { - "seriesLabels": { - "__name__": "tns_request_duration_seconds_bucket", - "instance": "app:80", - "job": "tns/app", - "le": "0.025", - "method": "GET", - "route": "root", - "status_code": "200", - "ws": "false" - }, - "exemplars": [ - { "labels": { "traceID": "4304572cef5c5907" }, "value": "0.010913885", "timestamp": 1620761543.749 }, - { "labels": { "traceID": "5f046e2160669cf1" }, "value": "0.012880048", "timestamp": 1620761568.799 }, - { "labels": { "traceID": "75494996cf5243ce" }, "value": "0.01032585", "timestamp": 1620761573.913 }, - { "labels": { "traceID": "927c922ea92c42e" }, "value": "0.010641447", "timestamp": 1620761641.816 }, - { "labels": { "traceID": "4812ecb2aa4ef17e" }, "value": "0.010616964", "timestamp": 1620761686.77 }, - { "labels": { "traceID": "40fc988a1cc2c68" }, "value": "0.012799888", "timestamp": 1620761697.676 }, - { "labels": { "traceID": "4c7c47e2d70f2d20" }, "value": "0.016078653", "timestamp": 1620761712.519 }, - { "labels": { "traceID": "53b30c0e9674735" }, "value": "0.010826261", "timestamp": 1620761733.248 }, - { "labels": { "traceID": "7ebc684d0e44b243" }, "value": "0.015741096", "timestamp": 1620761778.158 }, - { "labels": { "traceID": "2ee07f56d7484050" }, "value": "0.013013628", "timestamp": 1620761791.394 }, - { "labels": { "traceID": "3ae63f610201196a" }, "value": "0.010428691", "timestamp": 1620761819.722 }, - { "labels": { "traceID": "444872ee44bb36a8" }, "value": "0.014833215", "timestamp": 1620761828.663 } - ] - }, - { - "seriesLabels": { - "__name__": "tns_request_duration_seconds_bucket", - "instance": "app:80", - "job": "tns/app", - "le": "0.025", - "method": "POST", - "route": "post", - "status_code": "302", - "ws": "false" - }, - "exemplars": [ - { "labels": { "traceID": "528691a0d74de77d" }, "value": "0.014550855", "timestamp": 1620761641.766 }, - { "labels": { "traceID": "1cedd0acd4f5894d" }, "value": "0.024945017", "timestamp": 1620761701.759 }, - { "labels": { "traceID": "5a10a645c76ebe69" }, "value": "0.014772744", "timestamp": 1620761714.749 }, - { "labels": { "traceID": "7410f3eddf32d65f" }, "value": "0.014439267", "timestamp": 1620761724.749 } - ] - }, - { - "seriesLabels": { - "__name__": "tns_request_duration_seconds_bucket", - "instance": "app:80", - "job": "tns/app", - "le": "0.05", - "method": "GET", - "route": "root", - "status_code": "200", - "ws": "false" - }, - "exemplars": [ - { "labels": { "traceID": "13e7690a08a6dd90" }, "value": "0.025678532", "timestamp": 1620761641.679 }, - { "labels": { "traceID": "532bbc2195ebcc41" }, "value": "0.030629825", "timestamp": 1620761715.528 }, - { "labels": { "traceID": "2beaf0638967ac70" }, "value": "0.038696514", "timestamp": 1620761724.389 }, - { "labels": { "traceID": "42582435ad8220aa" }, "value": "0.035559249", "timestamp": 1620761779.21 } - ] - }, - { - "seriesLabels": { - "__name__": "tns_request_duration_seconds_bucket", - "instance": "app:80", - "job": "tns/app", - "le": "0.05", - "method": "POST", - "route": "post", - "status_code": "302", - "ws": "false" - }, - "exemplars": [ - { "labels": { "traceID": "5d5dbf0b9b26103a" }, "value": "0.043255969", "timestamp": 1620761716.778 }, - { "labels": { "traceID": "5f00eb10c755117a" }, "value": "0.031064295", "timestamp": 1620761726.766 } - ] - }, - { - "seriesLabels": { - "__name__": "tns_request_duration_seconds_bucket", - "instance": "app:80", - "job": "tns/app", - "le": "0.1", - "method": "GET", - "route": "root", - "status_code": "200", - "ws": "false" - }, - "exemplars": [ - { "labels": { "traceID": "245748ce121873b7" }, "value": "0.069726599", "timestamp": 1620761717.097 }, - { "labels": { "traceID": "180e9ae912bab1b3" }, "value": "0.051896104", "timestamp": 1620761728.297 } - ] - }, - { - "seriesLabels": { - "__name__": "tns_request_duration_seconds_bucket", - "instance": "app:80", - "job": "tns/app", - "le": "0.1", - "method": "GET", - "route": "root", - "status_code": "500", - "ws": "false" - }, - "exemplars": [ - { "labels": { "traceID": "5c87831717ac3fb2" }, "value": "0.094239093", "timestamp": 1620761704.008 }, - { "labels": { "traceID": "47015a4eee6cc651" }, "value": "0.081058336", "timestamp": 1620761718.984 }, - { "labels": { "traceID": "5ba6f40766a58966" }, "value": "0.05325593", "timestamp": 1620761729.949 } - ] - }, - { - "seriesLabels": { - "__name__": "tns_request_duration_seconds_bucket", - "instance": "app:80", - "job": "tns/app", - "le": "0.1", - "method": "POST", - "route": "post", - "status_code": "302", - "ws": "false" - }, - "exemplars": [ - { "labels": { "traceID": "44eb47a77e9a25c5" }, "value": "0.071755237", "timestamp": 1620761703.806 }, - { "labels": { "traceID": "22020d1a5b5c47bc" }, "value": "0.053282107", "timestamp": 1620761723.789 }, - { "labels": { "traceID": "c60780a9c221bbb" }, "value": "0.093136114", "timestamp": 1620761778.051 } - ] - }, - { - "seriesLabels": { - "__name__": "tns_request_duration_seconds_bucket", - "instance": "app:80", - "job": "tns/app", - "le": "0.25", - "method": "GET", - "route": "root", - "status_code": "200", - "ws": "false" - }, - "exemplars": [ - { "labels": { "traceID": "5740eed7b336e5c7" }, "value": "0.144092034", "timestamp": 1620761778.449 }, - { "labels": { "traceID": "562666a57c9ff0c0" }, "value": "0.103903482", "timestamp": 1620761779.461 } - ] - }, - { - "seriesLabels": { - "__name__": "tns_request_duration_seconds_bucket", - "instance": "app:80", - "job": "tns/app", - "le": "0.25", - "method": "GET", - "route": "root", - "status_code": "500", - "ws": "false" - }, - "exemplars": [ - { "labels": { "traceID": "44eb47a77e9a25c5" }, "value": "0.148749847", "timestamp": 1620761703.957 }, - { "labels": { "traceID": "75459cfc1bd7446a" }, "value": "0.113843275", "timestamp": 1620761718.541 }, - { "labels": { "traceID": "6fbcff3c0d0c8804" }, "value": "0.194787684", "timestamp": 1620761726.97 } - ] - }, - { - "seriesLabels": { - "__name__": "tns_request_duration_seconds_bucket", - "instance": "db:80", - "job": "tns/db", - "le": "0.005", - "method": "GET", - "route": "metrics", - "status_code": "200", - "ws": "false" - }, - "exemplars": [ - { "labels": { "traceID": "5a32e8a21d07dc8a" }, "value": "0.002903503", "timestamp": 1620761570.28 }, - { "labels": { "traceID": "5baddca63f5d90a7" }, "value": "0.003403121", "timestamp": 1620761585.28 }, - { "labels": { "traceID": "13a8ca5b6ce8b4e5" }, "value": "0.002366419", "timestamp": 1620761600.28 }, - { "labels": { "traceID": "7482236b6a4d70ef" }, "value": "0.002967911", "timestamp": 1620761615.28 }, - { "labels": { "traceID": "2cba65eca40f34fd" }, "value": "0.001448914", "timestamp": 1620761630.279 }, - { "labels": { "traceID": "2b46dd19c88a5efe" }, "value": "0.001533921", "timestamp": 1620761645.279 }, - { "labels": { "traceID": "214a12bdeef3b8f1" }, "value": "0.001854422", "timestamp": 1620761660.279 }, - { "labels": { "traceID": "1edb2187df3e9a84" }, "value": "0.001403631", "timestamp": 1620761675.279 }, - { "labels": { "traceID": "279ba7399bf0a102" }, "value": "0.002374148", "timestamp": 1620761690.281 }, - { "labels": { "traceID": "1d674f76346af5e1" }, "value": "0.001900167", "timestamp": 1620761705.279 }, - { "labels": { "traceID": "5266d1dc63e87f64" }, "value": "0.001881317", "timestamp": 1620761720.28 }, - { "labels": { "traceID": "1269ccd911e11972" }, "value": "0.002149382", "timestamp": 1620761735.281 }, - { "labels": { "traceID": "4b4745973c7893f9" }, "value": "0.003628674", "timestamp": 1620761765.282 }, - { "labels": { "traceID": "63065e988633d3a7" }, "value": "0.00259", "timestamp": 1620761780.282 }, - { "labels": { "traceID": "1bcd65fbb5038537" }, "value": "0.002453527", "timestamp": 1620761795.282 }, - { "labels": { "traceID": "5b5397d37915c7c2" }, "value": "0.002336174", "timestamp": 1620761810.282 }, - { "labels": { "traceID": "35d44091ebe0570" }, "value": "0.002195594", "timestamp": 1620761825.282 } - ] - }, - { - "seriesLabels": { - "__name__": "tns_request_duration_seconds_bucket", - "instance": "db:80", - "job": "tns/db", - "le": "0.005", - "method": "GET", - "route": "root", - "status_code": "200", - "ws": "false" - }, - "exemplars": [ - { "labels": { "traceID": "710d22174356e0e1" }, "value": "0.000044867", "timestamp": 1620761555.278 }, - { "labels": { "traceID": "42f1c15e54b5d128" }, "value": "0.000031916", "timestamp": 1620761570.226 }, - { "labels": { "traceID": "5f74b83ffc17aca4" }, "value": "0.000043602", "timestamp": 1620761585.059 }, - { "labels": { "traceID": "557f9f4e5fd43650" }, "value": "0.000041759", "timestamp": 1620761600.252 }, - { "labels": { "traceID": "749437467b47a353" }, "value": "0.000052233", "timestamp": 1620761615.18 }, - { "labels": { "traceID": "54fa5b6017463fec" }, "value": "0.00009653", "timestamp": 1620761630.161 }, - { "labels": { "traceID": "40abde3a75b81d54" }, "value": "0.000050126", "timestamp": 1620761645.234 }, - { "labels": { "traceID": "33d33dfb63e1b223" }, "value": "0.000026959", "timestamp": 1620761660.163 }, - { "labels": { "traceID": "1ac2a04a2a01d519" }, "value": "0.0000612", "timestamp": 1620761675.168 }, - { "labels": { "traceID": "41688684fd2f1f8" }, "value": "0.000026752", "timestamp": 1620761690.259 }, - { "labels": { "traceID": "3582e11947c9371f" }, "value": "0.00003492", "timestamp": 1620761704.514 }, - { "labels": { "traceID": "35782ca88006574c" }, "value": "0.000028053", "timestamp": 1620761720.26 }, - { "labels": { "traceID": "4d4476ce6643efdc" }, "value": "0.000064223", "timestamp": 1620761735.204 }, - { "labels": { "traceID": "36cb3bfa6db30032" }, "value": "0.000039705", "timestamp": 1620761750.269 }, - { "labels": { "traceID": "4692e4933288c43a" }, "value": "0.000031269", "timestamp": 1620761765.206 }, - { "labels": { "traceID": "1a0928a4986c108b" }, "value": "0.000032186", "timestamp": 1620761780 }, - { "labels": { "traceID": "75cec8d96b173c03" }, "value": "0.000070891", "timestamp": 1620761795.211 }, - { "labels": { "traceID": "6a130945282a41a6" }, "value": "0.000025227", "timestamp": 1620761810.221 }, - { "labels": { "traceID": "7cb16deb4fde6913" }, "value": "0.000025961", "timestamp": 1620761825.225 } - ] - }, - { - "seriesLabels": { - "__name__": "tns_request_duration_seconds_bucket", - "instance": "db:80", - "job": "tns/db", - "le": "0.005", - "method": "POST", - "route": "post", - "status_code": "204", - "ws": "false" - }, - "exemplars": [ - { "labels": { "traceID": "537dcbf082c2ba93" }, "value": "0.000097575", "timestamp": 1620761553.734 }, - { "labels": { "traceID": "39e6c907736a4c73" }, "value": "0.000081478", "timestamp": 1620761556.735 }, - { "labels": { "traceID": "19d421ff7a9fbfd0" }, "value": "0.000034594", "timestamp": 1620761581.735 } - ] - }, - { - "seriesLabels": { - "__name__": "tns_request_duration_seconds_bucket", - "instance": "db:80", - "job": "tns/db", - "le": "0.005", - "method": "POST", - "route": "post", - "status_code": "208", - "ws": "false" - }, - "exemplars": [ - { "labels": { "traceID": "61b1e057d8686851" }, "value": "0.000108024", "timestamp": 1620761554.734 }, - { "labels": { "traceID": "40818434e899043c" }, "value": "0.000040232", "timestamp": 1620761569.736 }, - { "labels": { "traceID": "62a17debadc079dd" }, "value": "0.000086612", "timestamp": 1620761584.736 }, - { "labels": { "traceID": "6bf0d79336718717" }, "value": "0.00013739", "timestamp": 1620761599.735 }, - { "labels": { "traceID": "1a2f17474be51243" }, "value": "0.000182956", "timestamp": 1620761614.736 }, - { "labels": { "traceID": "21e2d4bbef4d3308" }, "value": "0.00010539", "timestamp": 1620761629.736 }, - { "labels": { "traceID": "70753ef384fd3dee" }, "value": "0.000082617", "timestamp": 1620761644.736 }, - { "labels": { "traceID": "16b08b7985c8d771" }, "value": "0.000041379", "timestamp": 1620761659.736 }, - { "labels": { "traceID": "4412b326f2fa36fe" }, "value": "0.000114389", "timestamp": 1620761674.735 }, - { "labels": { "traceID": "bf705b43997000a" }, "value": "0.000179324", "timestamp": 1620761689.738 }, - { "labels": { "traceID": "554d18f9bf880b46" }, "value": "0.000113352", "timestamp": 1620761704.738 }, - { "labels": { "traceID": "64f06303658e8ba5" }, "value": "0.000107816", "timestamp": 1620761718.737 }, - { "labels": { "traceID": "715f9af007d9670e" }, "value": "0.000136609", "timestamp": 1620761734.738 }, - { "labels": { "traceID": "25eb144fda851c80" }, "value": "0.000064357", "timestamp": 1620761749.737 }, - { "labels": { "traceID": "cc85c3e6bc6f1e2" }, "value": "0.000063742", "timestamp": 1620761764.737 }, - { "labels": { "traceID": "1f5f4dd50f300b42" }, "value": "0.000251037", "timestamp": 1620761779.737 }, - { "labels": { "traceID": "135a2cc82ada01d0" }, "value": "0.000049377", "timestamp": 1620761794.738 }, - { "labels": { "traceID": "64683e7712f3d18a" }, "value": "0.000210094", "timestamp": 1620761809.739 }, - { "labels": { "traceID": "169726d18ab98e71" }, "value": "0.00005974", "timestamp": 1620761824.74 } - ] - }, - { - "seriesLabels": { - "__name__": "tns_request_duration_seconds_bucket", - "instance": "db:80", - "job": "tns/db", - "le": "0.01", - "method": "GET", - "route": "metrics", - "status_code": "200", - "ws": "false" - }, - "exemplars": [ - { "labels": { "traceID": "7e5433a9d13a18f5" }, "value": "0.007182854", "timestamp": 1620761555.285 }, - { "labels": { "traceID": "457140593761aa64" }, "value": "0.005264001", "timestamp": 1620761750.284 } - ] - }, - { - "seriesLabels": { - "__name__": "tns_request_duration_seconds_bucket", - "instance": "db:80", - "job": "tns/db", - "le": "0.01", - "method": "POST", - "route": "post", - "status_code": "208", - "ws": "false" - }, - "exemplars": [ - { "labels": { "traceID": "7410f3eddf32d65f" }, "value": "0.008582991", "timestamp": 1620761724.746 } - ] - }, - { - "seriesLabels": { - "__name__": "tns_request_duration_seconds_bucket", - "instance": "db:80", - "job": "tns/db", - "le": "0.025", - "method": "GET", - "route": "root", - "status_code": "200", - "ws": "false" - }, - "exemplars": [ - { "labels": { "traceID": "4c7c47e2d70f2d20" }, "value": "0.011127322", "timestamp": 1620761712.517 }, - { "labels": { "traceID": "5a33133cd9fc1f9c" }, "value": "0.018318268", "timestamp": 1620761721.172 } - ] - }, - { - "seriesLabels": { - "__name__": "tns_request_duration_seconds_bucket", - "instance": "db:80", - "job": "tns/db", - "le": "0.025", - "method": "POST", - "route": "post", - "status_code": "208", - "ws": "false" - }, - "exemplars": [ - { "labels": { "traceID": "1cedd0acd4f5894d" }, "value": "0.021432455", "timestamp": 1620761701.758 }, - { "labels": { "traceID": "5a10a645c76ebe69" }, "value": "0.011026563", "timestamp": 1620761714.748 } - ] - }, - { - "seriesLabels": { - "__name__": "tns_request_duration_seconds_bucket", - "instance": "db:80", - "job": "tns/db", - "le": "0.05", - "method": "GET", - "route": "root", - "status_code": "200", - "ws": "false" - }, - "exemplars": [ - { "labels": { "traceID": "275e17a7c6a13eb9" }, "value": "0.038704", "timestamp": 1620761719.763 }, - { "labels": { "traceID": "180e9ae912bab1b3" }, "value": "0.048015948", "timestamp": 1620761728.295 } - ] - }, - { - "seriesLabels": { - "__name__": "tns_request_duration_seconds_bucket", - "instance": "db:80", - "job": "tns/db", - "le": "0.05", - "method": "POST", - "route": "post", - "status_code": "208", - "ws": "false" - }, - "exemplars": [ - { "labels": { "traceID": "47d0ffd0c614d8c2" }, "value": "0.049099284", "timestamp": 1620761702.785 }, - { "labels": { "traceID": "23b22c3a5804c310" }, "value": "0.02679777", "timestamp": 1620761719.763 }, - { "labels": { "traceID": "5f00eb10c755117a" }, "value": "0.028521019", "timestamp": 1620761726.765 } - ] - }, - { - "seriesLabels": { - "__name__": "tns_request_duration_seconds_bucket", - "instance": "db:80", - "job": "tns/db", - "le": "0.1", - "method": "GET", - "route": "root", - "status_code": "200", - "ws": "false" - }, - "exemplars": [ - { "labels": { "traceID": "245748ce121873b7" }, "value": "0.063573579", "timestamp": 1620761717.093 } - ] - }, - { - "seriesLabels": { - "__name__": "tns_request_duration_seconds_bucket", - "instance": "db:80", - "job": "tns/db", - "le": "0.1", - "method": "GET", - "route": "root", - "status_code": "500", - "ws": "false" - }, - "exemplars": [ - { "labels": { "traceID": "70014d17952a82ad" }, "value": "0.050241318", "timestamp": 1620761705.203 }, - { "labels": { "traceID": "6246d76d84cd3844" }, "value": "0.050311235", "timestamp": 1620761720.202 }, - { "labels": { "traceID": "5ba6f40766a58966" }, "value": "0.050379771", "timestamp": 1620761729.948 } - ] - }, - { - "seriesLabels": { - "__name__": "tns_request_duration_seconds_bucket", - "instance": "db:80", - "job": "tns/db", - "le": "0.1", - "method": "POST", - "route": "post", - "status_code": "208", - "ws": "false" - }, - "exemplars": [ - { "labels": { "traceID": "44eb47a77e9a25c5" }, "value": "0.068911073", "timestamp": 1620761703.805 } - ] - }, - { - "seriesLabels": { - "__name__": "tns_request_duration_seconds_bucket", - "instance": "db:80", - "job": "tns/db", - "le": "0.25", - "method": "GET", - "route": "root", - "status_code": "500", - "ws": "false" - }, - "exemplars": [ - { "labels": { "traceID": "44eb47a77e9a25c5" }, "value": "0.145506317", "timestamp": 1620761703.956 }, - { "labels": { "traceID": "7526cf1caba0e6bd" }, "value": "0.113571728", "timestamp": 1620761719.034 }, - { "labels": { "traceID": "6fbcff3c0d0c8804" }, "value": "0.19024744", "timestamp": 1620761726.968 } - ] - }, - { - "seriesLabels": { - "__name__": "tns_request_duration_seconds_bucket", - "instance": "loadgen:80", - "job": "tns/loadgen", - "le": "0.005", - "method": "GET", - "route": "metrics", - "status_code": "200", - "ws": "false" - }, - "exemplars": [ - { "labels": { "traceID": "1fe6d9e5ab6be0a5" }, "value": "0.002076766", "timestamp": 1620761552.657 }, - { "labels": { "traceID": "577f9f410678557a" }, "value": "0.004503368", "timestamp": 1620761567.659 }, - { "labels": { "traceID": "1a8a132cb9896030" }, "value": "0.004139238", "timestamp": 1620761582.66 }, - { "labels": { "traceID": "53e8c8cea1a589a1" }, "value": "0.001708737", "timestamp": 1620761597.657 }, - { "labels": { "traceID": "50f63ab02a0f90de" }, "value": "0.001486912", "timestamp": 1620761612.656 }, - { "labels": { "traceID": "5c68c265c183c119" }, "value": "0.002758754", "timestamp": 1620761627.657 }, - { "labels": { "traceID": "404960d3dbf747ae" }, "value": "0.001970475", "timestamp": 1620761642.657 }, - { "labels": { "traceID": "514aa89c2f8df024" }, "value": "0.001930028", "timestamp": 1620761657.658 }, - { "labels": { "traceID": "4ceaad3f41d210c8" }, "value": "0.001324236", "timestamp": 1620761672.656 }, - { "labels": { "traceID": "4f04efc40d099a8c" }, "value": "0.002648599", "timestamp": 1620761687.658 }, - { "labels": { "traceID": "121f4f4e9c6795f0" }, "value": "0.001348972", "timestamp": 1620761702.657 }, - { "labels": { "traceID": "16025d6f8129a665" }, "value": "0.00154316", "timestamp": 1620761717.658 }, - { "labels": { "traceID": "798d921a274ecce5" }, "value": "0.003156098", "timestamp": 1620761732.66 }, - { "labels": { "traceID": "106b88fa469e15b7" }, "value": "0.002607822", "timestamp": 1620761747.662 }, - { "labels": { "traceID": "2179b1995c50273c" }, "value": "0.002376715", "timestamp": 1620761762.66 }, - { "labels": { "traceID": "57f635beef50aa83" }, "value": "0.003579502", "timestamp": 1620761777.66 }, - { "labels": { "traceID": "5235dbf17c79ed24" }, "value": "0.001724193", "timestamp": 1620761807.659 }, - { "labels": { "traceID": "333114bad0a9079b" }, "value": "0.001371645", "timestamp": 1620761822.658 } - ] - }, - { - "seriesLabels": { - "__name__": "tns_request_duration_seconds_bucket", - "instance": "loadgen:80", - "job": "tns/loadgen", - "le": "0.01", - "method": "GET", - "route": "metrics", - "status_code": "200", - "ws": "false" - }, - "exemplars": [ - { "labels": { "traceID": "7920f8a86e0e6b84" }, "value": "0.007650334", "timestamp": 1620761792.665 } + "results": { + "A": { + "frames": [ + { + "schema": { + "name": "histogram_quantile(0.95, sum(rate(tns_request_duration_seconds_bucket[5m])) by (le))", + "refId": "A", + "meta": { "custom": { "resultType": "matrix" } }, + "fields": [ + { "name": "Time", "type": "time", "typeInfo": { "frame": "time.Time" } }, + { + "name": "Value", + "type": "number", + "typeInfo": { "frame": "float64" }, + "labels": {}, + "config": { + "displayNameFromDS": "histogram_quantile(0.95, sum(rate(tns_request_duration_seconds_bucket[5m])) by (le))" + } + } + ] + }, + "data": { + "values": [ + [ + 1633619595000, + 1633619610000, + 1633619625000, + 1633619640000, + 1633619655000, + 1633619670000, + 1633619685000, + 1633619700000, + 1633619715000, + 1633619730000, + 1633619745000, + 1633619760000, + 1633619775000, + 1633619790000, + 1633619805000, + 1633619820000, + 1633619835000, + 1633619850000, + 1633619865000, + 1633619880000, + 1633619895000 + ], + [ + 0.07245212135073513, + 0.07253198890830721, + 0.07247862573797707, + 0.07238248338231042, + 0.07221687487740913, + 0.07223291298743946, + 0.07225427016727755, + 0.024531677091864545, + 0.02317081920915543, + 0.07548902139580993, + 0.0777721702857508, + 0.07768649905047344, + 0.07782257603228229, + 0.07788810213200052, + 0.07791835055437593, + 0.07798387201529966, + 0.07790826751849372, + 0.07794858648610933, + 0.07778729925797964, + 0.07769657495236215, + 0.077550401329267 + ] + ] + } + }, + { + "schema": { + "name": "histogram_quantile(0.95, sum(rate(tns_request_duration_seconds_bucket[5m])) by (le))", + "refId": "A", + "meta": { "custom": { "resultType": "vector" } }, + "fields": [ + { "name": "Time", "type": "time", "typeInfo": { "frame": "time.Time" } }, + { + "name": "Value", + "type": "number", + "typeInfo": { "frame": "float64" }, + "labels": {}, + "config": { + "displayNameFromDS": "histogram_quantile(0.95, sum(rate(tns_request_duration_seconds_bucket[5m])) by (le))" + } + } + ] + }, + "data": { "values": [[1633619900000], [0.0775504013292671]] } + }, + { + "schema": { + "name": "exemplar", + "refId": "A", + "meta": { "custom": { "resultType": "exemplar" } }, + "fields": [ + { "name": "Time", "type": "time", "typeInfo": { "frame": "time.Time" } }, + { "name": "Value", "type": "number", "typeInfo": { "frame": "float64" } }, + { "name": "instance", "type": "string", "typeInfo": { "frame": "string" } }, + { "name": "__name__", "type": "string", "typeInfo": { "frame": "string" } }, + { "name": "job", "type": "string", "typeInfo": { "frame": "string" } }, + { "name": "status_code", "type": "string", "typeInfo": { "frame": "string" } }, + { "name": "method", "type": "string", "typeInfo": { "frame": "string" } }, + { "name": "traceID", "type": "string", "typeInfo": { "frame": "string" } }, + { "name": "route", "type": "string", "typeInfo": { "frame": "string" } }, + { "name": "ws", "type": "string", "typeInfo": { "frame": "string" } }, + { "name": "le", "type": "string", "typeInfo": { "frame": "string" } } + ] + }, + "data": { + "values": [ + [ + 1633619598000, + 1633619622000, + 1633619625000, + 1633619646000, + 1633619658000, + 1633619682000, + 1633619695000, + 1633619712000, + 1633619712000, + 1633619724000, + 1633619717000, + 1633619742000, + 1633619757000, + 1633619771000, + 1633619784000, + 1633619801000, + 1633619806000, + 1633619833000, + 1633619833000, + 1633619845000, + 1633619862000, + 1633619877000, + 1633619889000 + ], + [ + 0.0146153, + 0.0118506, + 0.0473847, + 0.026997, + 0.0164318, + 0.0113532, + 0.0105197, + 0.162789, + 0.0556026, + 0.148856, + 0.0433809, + 0.0117758, + 0.0114496, + 0.0114099, + 0.0421927, + 0.0134148, + 0.0152827, + 0.6975967, + 0.0394788, + 0.0137441, + 0.0110939, + 0.0104496, + 0.0101284 + ], + [ + "app:80", + "app:80", + "app:80", + "app:80", + "app:80", + "app:80", + "app:80", + "app:80", + "app:80", + "app:80", + "db:80", + "app:80", + "app:80", + "app:80", + "app:80", + "app:80", + "app:80", + "app:80", + "app:80", + "app:80", + "app:80", + "app:80", + "app:80" + ], + [ + "tns_request_duration_seconds_bucket", + "tns_request_duration_seconds_bucket", + "tns_request_duration_seconds_bucket", + "tns_request_duration_seconds_bucket", + "tns_request_duration_seconds_bucket", + "tns_request_duration_seconds_bucket", + "tns_request_duration_seconds_bucket", + "tns_request_duration_seconds_bucket", + "tns_request_duration_seconds_bucket", + "tns_request_duration_seconds_bucket", + "tns_request_duration_seconds_bucket", + "tns_request_duration_seconds_bucket", + "tns_request_duration_seconds_bucket", + "tns_request_duration_seconds_bucket", + "tns_request_duration_seconds_bucket", + "tns_request_duration_seconds_bucket", + "tns_request_duration_seconds_bucket", + "tns_request_duration_seconds_bucket", + "tns_request_duration_seconds_bucket", + "tns_request_duration_seconds_bucket", + "tns_request_duration_seconds_bucket", + "tns_request_duration_seconds_bucket", + "tns_request_duration_seconds_bucket" + ], + [ + "tns/app", + "tns/app", + "tns/app", + "tns/app", + "tns/app", + "tns/app", + "tns/app", + "tns/app", + "tns/app", + "tns/app", + "tns/db", + "tns/app", + "tns/app", + "tns/app", + "tns/app", + "tns/app", + "tns/app", + "tns/app", + "tns/app", + "tns/app", + "tns/app", + "tns/app", + "tns/app" + ], + [ + "302", + "200", + "200", + "200", + "200", + "200", + "200", + "500", + "200", + "302", + "208", + "200", + "200", + "200", + "200", + "200", + "302", + "200", + "200", + "200", + "200", + "200", + "200" + ], + [ + "POST", + "GET", + "GET", + "GET", + "GET", + "GET", + "GET", + "GET", + "GET", + "POST", + "POST", + "GET", + "GET", + "GET", + "GET", + "GET", + "POST", + "GET", + "GET", + "GET", + "GET", + "GET", + "GET" + ], + [ + "6a3cf561ef6c32a0", + "396bcdf29601a149", + "57c04ef608f11158", + "77c757dab83c665f", + "3d1069567e873f5e", + "b337949f6213efd", + "21b20cbe533cf099", + "2c10b3aa30fabd66", + "42ac6088a757636b", + "2f81158008cd4dcc", + "320b803ad7323b37", + "7f15fd82aeb8b361", + "11c79266da8a74cd", + "5a8571bdcc04c990", + "3de3f4f42ccb93ae", + "23343ac91cc0638", + "5cea3aad17ab11c8", + "5d334e2843d3405a", + "3cf6834596d4b6b6", + "1ab6cff012959723", + "2f78bc2c398b8b20", + "6d5862a70c3abd42", + "f5421be4054f501" + ], + [ + "post", + "root", + "root", + "root", + "root", + "root", + "root", + "root", + "root", + "post", + "post", + "root", + "root", + "root", + "root", + "root", + "post", + "metrics", + "root", + "root", + "root", + "root", + "root" + ], + [ + "false", + "false", + "false", + "false", + "false", + "false", + "false", + "false", + "false", + "false", + "false", + "false", + "false", + "false", + "false", + "false", + "false", + "false", + "false", + "false", + "false", + "false", + "false" + ], + [ + "0.025", + "0.025", + "0.05", + "0.05", + "0.025", + "0.025", + "0.025", + "0.25", + "0.1", + "0.25", + "0.05", + "0.025", + "0.025", + "0.025", + "0.05", + "0.025", + "0.025", + "1.0", + "0.05", + "0.025", + "0.025", + "0.025", + "0.025" + ] + ] + } + } ] } - ] + } } diff --git a/pkg/tsdb/prometheus/prometheus.go b/pkg/tsdb/prometheus/prometheus.go index 6319addf85d..497ce8f1cb3 100644 --- a/pkg/tsdb/prometheus/prometheus.go +++ b/pkg/tsdb/prometheus/prometheus.go @@ -8,6 +8,7 @@ import ( "math" "net/http" "regexp" + "sort" "strconv" "strings" "time" @@ -142,16 +143,16 @@ func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest) span.SetTag("stop_unixnano", query.End.UnixNano()) defer span.Finish() - response := make(map[PrometheusQueryType]model.Value) + response := make(map[PrometheusQueryType]interface{}) + + timeRange := apiv1.Range{ + Step: query.Step, + // Align query range to step. It rounds start and end down to a multiple of step. + Start: time.Unix(int64(math.Floor((float64(query.Start.Unix()+query.UtcOffsetSec)/query.Step.Seconds()))*query.Step.Seconds()-float64(query.UtcOffsetSec)), 0), + End: time.Unix(int64(math.Floor((float64(query.End.Unix()+query.UtcOffsetSec)/query.Step.Seconds()))*query.Step.Seconds()-float64(query.UtcOffsetSec)), 0), + } if query.RangeQuery { - timeRange := apiv1.Range{ - Step: query.Step, - // Align query range to step. It rounds start and end down to a multiple of step. - Start: time.Unix(int64(math.Floor((float64(query.Start.Unix()+query.UtcOffsetSec)/query.Step.Seconds()))*query.Step.Seconds()-float64(query.UtcOffsetSec)), 0), - End: time.Unix(int64(math.Floor((float64(query.End.Unix()+query.UtcOffsetSec)/query.Step.Seconds()))*query.Step.Seconds()-float64(query.UtcOffsetSec)), 0), - } - rangeResponse, _, err := client.QueryRange(ctx, query.Expr, timeRange) if err != nil { return &result, fmt.Errorf("query: %s failed with: %v", query.Expr, err) @@ -166,6 +167,15 @@ func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest) } response[Instant] = instantResponse } + // For now, we ignore exemplar errors and continue with processing of other results + if query.ExemplarQuery { + exemplarResponse, err := client.QueryExemplars(ctx, query.Expr, timeRange.Start, timeRange.End) + if err != nil { + exemplarResponse = nil + plog.Error("Exemplar query", query.Expr, "failed with", err) + } + response[Exemplar] = exemplarResponse + } frames, err := parseResponse(response, query) if err != nil { @@ -299,53 +309,54 @@ func (s *Service) parseQuery(queryContext *backend.QueryDataRequest, dsInfo *Dat } qs = append(qs, &PrometheusQuery{ - Expr: expr, - Step: interval, - LegendFormat: model.LegendFormat, - Start: query.TimeRange.From, - End: query.TimeRange.To, - RefId: query.RefID, - InstantQuery: model.InstantQuery, - RangeQuery: rangeQuery, - UtcOffsetSec: model.UtcOffsetSec, + Expr: expr, + Step: interval, + LegendFormat: model.LegendFormat, + Start: query.TimeRange.From, + End: query.TimeRange.To, + RefId: query.RefID, + InstantQuery: model.InstantQuery, + RangeQuery: rangeQuery, + ExemplarQuery: model.ExemplarQuery, + UtcOffsetSec: model.UtcOffsetSec, }) } return qs, nil } -func parseResponse(value map[PrometheusQueryType]model.Value, query *PrometheusQuery) (data.Frames, error) { - allFrames := data.Frames{} - - for queryType, value := range value { - var frames data.Frames +func parseResponse(value map[PrometheusQueryType]interface{}, query *PrometheusQuery) (data.Frames, error) { + frames := data.Frames{} + for _, value := range value { matrix, ok := value.(model.Matrix) if ok { - frames = matrixToDataFrames(matrix, query, queryType) + matrixFrames := matrixToDataFrames(matrix, query) + frames = append(frames, matrixFrames...) + continue } vector, ok := value.(model.Vector) if ok { - frames = vectorToDataFrames(vector, query, queryType) + vectorFrames := vectorToDataFrames(vector, query) + frames = append(frames, vectorFrames...) + continue } scalar, ok := value.(*model.Scalar) if ok { - frames = scalarToDataFrames(scalar, query, queryType) + scalarFrames := scalarToDataFrames(scalar, query) + frames = append(frames, scalarFrames...) + continue } - for _, frame := range frames { - frame.Meta = &data.FrameMeta{ - Custom: map[string]PrometheusQueryType{ - "queryType": queryType, - }, - } + exemplar, ok := value.([]apiv1.ExemplarQueryResult) + if ok { + exemplarFrames := exemplarToDataFrames(exemplar, query) + frames = append(frames, exemplarFrames...) + continue } - - allFrames = append(allFrames, frames...) } - - return allFrames, nil + return frames, nil } // IsAPIError returns whether err is or wraps a Prometheus error. @@ -378,7 +389,7 @@ func calculateRateInterval(interval time.Duration, scrapeInterval string, interv return rateInterval } -func matrixToDataFrames(matrix model.Matrix, query *PrometheusQuery, queryType PrometheusQueryType) data.Frames { +func matrixToDataFrames(matrix model.Matrix, query *PrometheusQuery) data.Frames { frames := data.Frames{} for _, v := range matrix { @@ -396,23 +407,34 @@ func matrixToDataFrames(matrix model.Matrix, query *PrometheusQuery, queryType P frame := data.NewFrame(name, data.NewField("Time", nil, timeVector), data.NewField("Value", tags, values).SetConfig(&data.FieldConfig{DisplayNameFromDS: name})) + frame.Meta = &data.FrameMeta{ + Custom: map[string]string{ + "resultType": "matrix", + }, + } frames = append(frames, frame) } return frames } -func scalarToDataFrames(scalar *model.Scalar, query *PrometheusQuery, queryType PrometheusQueryType) data.Frames { +func scalarToDataFrames(scalar *model.Scalar, query *PrometheusQuery) data.Frames { timeVector := []time.Time{time.Unix(scalar.Timestamp.Unix(), 0).UTC()} values := []float64{float64(scalar.Value)} name := fmt.Sprintf("%g", values[0]) frame := data.NewFrame(name, data.NewField("Time", nil, timeVector), data.NewField("Value", nil, values).SetConfig(&data.FieldConfig{DisplayNameFromDS: name})) + frame.Meta = &data.FrameMeta{ + Custom: map[string]string{ + "resultType": "scalar", + }, + } + frames := data.Frames{frame} return frames } -func vectorToDataFrames(vector model.Vector, query *PrometheusQuery, queryType PrometheusQueryType) data.Frames { +func vectorToDataFrames(vector model.Vector, query *PrometheusQuery) data.Frames { frames := data.Frames{} for _, v := range vector { name := formatLegend(v.Metric, query) @@ -425,8 +447,146 @@ func vectorToDataFrames(vector model.Vector, query *PrometheusQuery, queryType P frame := data.NewFrame(name, data.NewField("Time", nil, timeVector), data.NewField("Value", tags, values).SetConfig(&data.FieldConfig{DisplayNameFromDS: name})) + frame.Meta = &data.FrameMeta{ + Custom: map[string]string{ + "resultType": "vector", + }, + } frames = append(frames, frame) } return frames } + +func exemplarToDataFrames(response []apiv1.ExemplarQueryResult, query *PrometheusQuery) data.Frames { + frames := data.Frames{} + events := make([]ExemplarEvent, 0) + for _, exemplarData := range response { + for _, exemplar := range exemplarData.Exemplars { + event := ExemplarEvent{} + exemplarTime := time.Unix(exemplar.Timestamp.Unix(), 0).UTC() + event.Time = exemplarTime + event.Value = float64(exemplar.Value) + event.Labels = make(map[string]string) + for label, value := range exemplar.Labels { + event.Labels[string(label)] = string(value) + } + for seriesLabel, seriesValue := range exemplarData.SeriesLabels { + event.Labels[string(seriesLabel)] = string(seriesValue) + } + + events = append(events, event) + } + } + + //Sampling of exemplars + bucketedExemplars := make(map[string][]ExemplarEvent) + values := make([]float64, 0) + + //Create bucketed exemplars based on aligned timestamp + for _, event := range events { + alignedTs := fmt.Sprintf("%.0f", math.Floor(float64(event.Time.Unix())/query.Step.Seconds())*query.Step.Seconds()) + _, ok := bucketedExemplars[alignedTs] + if !ok { + bucketedExemplars[alignedTs] = make([]ExemplarEvent, 0) + } + + bucketedExemplars[alignedTs] = append(bucketedExemplars[alignedTs], event) + values = append(values, event.Value) + } + + //Calculate standard deviation + standardDeviation := deviation(values) + + //Create slice with all of the bucketed exemplars + sampledBuckets := make([]string, len(bucketedExemplars)) + for bucketTimes := range bucketedExemplars { + sampledBuckets = append(sampledBuckets, bucketTimes) + } + sort.Strings(sampledBuckets) + + //Sample exemplars based ona value, so we are not showing too many of them + sampleExemplars := make([]ExemplarEvent, 0) + for _, bucket := range sampledBuckets { + exemplarsInBucket := bucketedExemplars[bucket] + if len(exemplarsInBucket) == 1 { + sampleExemplars = append(sampleExemplars, exemplarsInBucket[0]) + } else { + bucketValues := make([]float64, len(exemplarsInBucket)) + for _, exemplar := range exemplarsInBucket { + bucketValues = append(bucketValues, exemplar.Value) + } + sort.Slice(bucketValues, func(i, j int) bool { + return bucketValues[i] > bucketValues[j] + }) + + sampledBucketValues := make([]float64, 0) + for _, value := range bucketValues { + if len(sampledBucketValues) == 0 { + sampledBucketValues = append(sampledBucketValues, value) + } else { + // Then take values only when at least 2 standard deviation distance to previously taken value + prev := sampledBucketValues[len(sampledBucketValues)-1] + if standardDeviation != 0 && prev-value >= float64(2)*standardDeviation { + sampledBucketValues = append(sampledBucketValues, value) + } + } + } + for _, valueBucket := range sampledBucketValues { + for _, exemplar := range exemplarsInBucket { + if exemplar.Value == valueBucket { + sampleExemplars = append(sampleExemplars, exemplar) + } + } + } + } + } + + // Create DF from sampled exemplars + timeVector := make([]time.Time, 0, len(sampleExemplars)) + valuesVector := make([]float64, 0, len(sampleExemplars)) + labelsVector := make(map[string][]string, len(sampleExemplars)) + + for _, exemplar := range sampleExemplars { + timeVector = append(timeVector, exemplar.Time) + valuesVector = append(valuesVector, exemplar.Value) + + for label, value := range exemplar.Labels { + if labelsVector[label] == nil { + labelsVector[label] = make([]string, 0) + } + + labelsVector[label] = append(labelsVector[label], value) + } + } + + frame := data.NewFrame("exemplar", + data.NewField("Time", nil, timeVector), + data.NewField("Value", nil, valuesVector)) + + for label, vector := range labelsVector { + frame.Fields = append(frame.Fields, data.NewField(label, nil, vector)) + } + + frame.Meta = &data.FrameMeta{ + Custom: map[string]PrometheusQueryType{ + "resultType": "exemplar", + }, + } + + frames = append(frames, frame) + return frames +} + +func deviation(values []float64) float64 { + var sum, mean, sd float64 + valuesLen := float64(len(values)) + for _, value := range values { + sum += value + } + mean = sum / valuesLen + for j := 0; j < len(values); j++ { + sd += math.Pow(values[j]-mean, 2) + } + return math.Sqrt(sd / (valuesLen - 1)) +} diff --git a/pkg/tsdb/prometheus/prometheus_test.go b/pkg/tsdb/prometheus/prometheus_test.go index 6d2d71dc191..1224a15e7b7 100644 --- a/pkg/tsdb/prometheus/prometheus_test.go +++ b/pkg/tsdb/prometheus/prometheus_test.go @@ -6,6 +6,7 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana/pkg/tsdb/intervalv2" + apiv1 "github.com/prometheus/client_golang/api/prometheus/v1" p "github.com/prometheus/common/model" "github.com/stretchr/testify/require" ) @@ -349,6 +350,60 @@ func TestPrometheus_parseQuery(t *testing.T) { } func TestPrometheus_parseResponse(t *testing.T) { + t.Run("exemplars response should be sampled and parsed normally", func(t *testing.T) { + value := make(map[PrometheusQueryType]interface{}) + exemplars := []apiv1.ExemplarQueryResult{ + { + SeriesLabels: p.LabelSet{ + "__name__": "tns_request_duration_seconds_bucket", + "instance": "app:80", + "job": "tns/app", + }, + Exemplars: []apiv1.Exemplar{ + { + Labels: p.LabelSet{"traceID": "test1"}, + Value: 0.003535405, + Timestamp: p.TimeFromUnixNano(time.Now().Add(-2 * time.Minute).UnixNano()), + }, + { + Labels: p.LabelSet{"traceID": "test2"}, + Value: 0.005555605, + Timestamp: p.TimeFromUnixNano(time.Now().Add(-4 * time.Minute).UnixNano()), + }, + { + Labels: p.LabelSet{"traceID": "test3"}, + Value: 0.007545445, + Timestamp: p.TimeFromUnixNano(time.Now().Add(-6 * time.Minute).UnixNano()), + }, + { + Labels: p.LabelSet{"traceID": "test4"}, + Value: 0.009545445, + Timestamp: p.TimeFromUnixNano(time.Now().Add(-7 * time.Minute).UnixNano()), + }, + }, + }, + } + + value[Exemplar] = exemplars + query := &PrometheusQuery{ + LegendFormat: "legend {{app}}", + } + res, err := parseResponse(value, query) + require.NoError(t, err) + + // Test fields + require.Len(t, res, 1) + require.Equal(t, res[0].Name, "exemplar") + require.Equal(t, res[0].Fields[0].Name, "Time") + require.Equal(t, res[0].Fields[1].Name, "Value") + require.Len(t, res[0].Fields, 6) + + // Test correct values (sampled to 2) + require.Equal(t, res[0].Fields[1].Len(), 2) + require.Equal(t, res[0].Fields[1].At(0), 0.009545445) + require.Equal(t, res[0].Fields[1].At(1), 0.003535405) + }) + t.Run("matrix response should be parsed normally", func(t *testing.T) { values := []p.SamplePair{ {Value: 1, Timestamp: 1000}, @@ -357,7 +412,7 @@ func TestPrometheus_parseResponse(t *testing.T) { {Value: 4, Timestamp: 4000}, {Value: 5, Timestamp: 5000}, } - value := make(map[PrometheusQueryType]p.Value) + value := make(map[PrometheusQueryType]interface{}) value[Range] = p.Matrix{ &p.SampleStream{ Metric: p.Metric{"app": "Application", "tag2": "tag2"}, @@ -386,7 +441,7 @@ func TestPrometheus_parseResponse(t *testing.T) { }) t.Run("vector response should be parsed normally", func(t *testing.T) { - value := make(map[PrometheusQueryType]p.Value) + value := make(map[PrometheusQueryType]interface{}) value[Range] = p.Vector{ &p.Sample{ Metric: p.Metric{"app": "Application", "tag2": "tag2"}, @@ -405,6 +460,7 @@ func TestPrometheus_parseResponse(t *testing.T) { require.Len(t, res[0].Fields, 2) require.Len(t, res[0].Fields[0].Labels, 0) require.Equal(t, res[0].Fields[0].Name, "Time") + require.Equal(t, res[0].Fields[0].Name, "Time") require.Len(t, res[0].Fields[1].Labels, 2) require.Equal(t, res[0].Fields[1].Labels.String(), "app=Application, tag2=tag2") require.Equal(t, res[0].Fields[1].Name, "Value") @@ -416,7 +472,7 @@ func TestPrometheus_parseResponse(t *testing.T) { }) t.Run("scalar response should be parsed normally", func(t *testing.T) { - value := make(map[PrometheusQueryType]p.Value) + value := make(map[PrometheusQueryType]interface{}) value[Range] = &p.Scalar{ Value: 1, Timestamp: 1000, diff --git a/pkg/tsdb/prometheus/types.go b/pkg/tsdb/prometheus/types.go index 24a67cdb473..9da1585f5ef 100644 --- a/pkg/tsdb/prometheus/types.go +++ b/pkg/tsdb/prometheus/types.go @@ -15,15 +15,22 @@ type DatasourceInfo struct { } type PrometheusQuery struct { - Expr string - Step time.Duration - LegendFormat string - Start time.Time - End time.Time - RefId string - InstantQuery bool - RangeQuery bool - UtcOffsetSec int64 + Expr string + Step time.Duration + LegendFormat string + Start time.Time + End time.Time + RefId string + InstantQuery bool + RangeQuery bool + ExemplarQuery bool + UtcOffsetSec int64 +} + +type ExemplarEvent struct { + Time time.Time + Value float64 + Labels map[string]string } type QueryModel struct { @@ -34,6 +41,7 @@ type QueryModel struct { StepMode string `json:"stepMode"` RangeQuery bool `json:"range"` InstantQuery bool `json:"instant"` + ExemplarQuery bool `json:"exemplar"` IntervalFactor int64 `json:"intervalFactor"` UtcOffsetSec int64 `json:"utcOffsetSec"` } @@ -41,6 +49,7 @@ type QueryModel struct { type PrometheusQueryType string const ( - Range PrometheusQueryType = "range" - Instant PrometheusQueryType = "instant" + Range PrometheusQueryType = "range" + Instant PrometheusQueryType = "instant" + Exemplar PrometheusQueryType = "exemplar" ) diff --git a/public/app/plugins/datasource/prometheus/components/PromExemplarField.tsx b/public/app/plugins/datasource/prometheus/components/PromExemplarField.tsx index 42412381868..620a18bbeb4 100644 --- a/public/app/plugins/datasource/prometheus/components/PromExemplarField.tsx +++ b/public/app/plugins/datasource/prometheus/components/PromExemplarField.tsx @@ -2,32 +2,39 @@ import { GrafanaTheme2 } from '@grafana/data'; import { IconButton, InlineLabel, Tooltip, useStyles2 } from '@grafana/ui'; import { css, cx } from '@emotion/css'; import React, { useEffect, useState } from 'react'; +import { usePrevious } from 'react-use'; import { PrometheusDatasource } from '../datasource'; -import { filter } from 'rxjs/operators'; +import { PromQuery } from '../types'; interface Props { - isEnabled: boolean; - onChange: (isEnabled: boolean) => void; + onChange: (exemplar: boolean) => void; datasource: PrometheusDatasource; - refId: string; + query: PromQuery; } -export function PromExemplarField({ datasource, onChange, isEnabled, refId }: Props) { +export function PromExemplarField({ datasource, onChange, query }: Props) { const [error, setError] = useState(null); const styles = useStyles2(getStyles); + const prevError = usePrevious(error); useEffect(() => { - const subscription = datasource.exemplarErrors.pipe(filter((value) => refId === value.refId)).subscribe((err) => { - setError(err.error); - }); - return () => { - subscription.unsubscribe(); - }; - }, [datasource, refId]); + if (!datasource.exemplarsAvailable) { + setError('Exemplars for this query are not available'); + onChange(false); + } else if (query.instant && !query.range) { + setError('Exemplars are not available for instant queries'); + onChange(false); + } else { + setError(null); + if (prevError !== error) { + onChange(true); + } + } + }, [datasource.exemplarsAvailable, query.instant, query.range, onChange, prevError, error]); const iconButtonStyles = cx( { - [styles.activeIcon]: isEnabled, + [styles.activeIcon]: !!query.exemplar, }, styles.eyeIcon ); @@ -39,11 +46,11 @@ export function PromExemplarField({ datasource, onChange, isEnabled, refId }: Pr Exemplars { - onChange(!isEnabled); + onChange(!query.exemplar); }} /> diff --git a/public/app/plugins/datasource/prometheus/components/PromExploreExtraField.test.tsx b/public/app/plugins/datasource/prometheus/components/PromExploreExtraField.test.tsx index 992b7090b1f..5b5df499d42 100644 --- a/public/app/plugins/datasource/prometheus/components/PromExploreExtraField.test.tsx +++ b/public/app/plugins/datasource/prometheus/components/PromExploreExtraField.test.tsx @@ -1,24 +1,17 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import { PromExploreExtraFieldProps, PromExploreExtraField } from './PromExploreExtraField'; -import { Observable } from 'rxjs'; const setup = (propOverrides?: PromExploreExtraFieldProps) => { - const queryType = 'range'; - const stepValue = '1'; const query = { exemplar: false }; - const datasource = { exemplarErrors: new Observable() }; - const onStepChange = jest.fn(); - const onQueryTypeChange = jest.fn(); - const onKeyDownFunc = jest.fn(); + const datasource = {}; + const onChange = jest.fn(); + const onRunQuery = jest.fn(); const props: any = { - queryType, - stepValue, + onChange, + onRunQuery, query, - onStepChange, - onQueryTypeChange, - onKeyDownFunc, datasource, }; diff --git a/public/app/plugins/datasource/prometheus/components/PromExploreExtraField.tsx b/public/app/plugins/datasource/prometheus/components/PromExploreExtraField.tsx index 575b0b910a5..cafa6d1b18f 100644 --- a/public/app/plugins/datasource/prometheus/components/PromExploreExtraField.tsx +++ b/public/app/plugins/datasource/prometheus/components/PromExploreExtraField.tsx @@ -1,26 +1,21 @@ -// Libraries -import React, { memo } from 'react'; +import React, { memo, useEffect, useCallback } from 'react'; +import { usePrevious } from 'react-use'; +import { isEqual } from 'lodash'; import { css, cx } from '@emotion/css'; - -// Types import { InlineFormLabel, RadioButtonGroup } from '@grafana/ui'; import { PromQuery } from '../types'; import { PromExemplarField } from './PromExemplarField'; import { PrometheusDatasource } from '../datasource'; export interface PromExploreExtraFieldProps { - queryType: string; - stepValue: string; query: PromQuery; - onStepChange: (e: React.SyntheticEvent) => void; - onKeyDownFunc: (e: React.KeyboardEvent) => void; - onQueryTypeChange: (value: string) => void; onChange: (value: PromQuery) => void; + onRunQuery: () => void; datasource: PrometheusDatasource; } export const PromExploreExtraField: React.FC = memo( - ({ queryType, stepValue, query, onChange, onStepChange, onQueryTypeChange, onKeyDownFunc, datasource }) => { + ({ query, datasource, onChange, onRunQuery }) => { const rangeOptions = [ { value: 'range', label: 'Range', description: 'Run query over a range of time.' }, { @@ -31,6 +26,56 @@ export const PromExploreExtraField: React.FC = memo( { value: 'both', label: 'Both', description: 'Run an Instant query and a Range query.' }, ]; + const prevQuery = usePrevious(query); + + // Setting default values + useEffect(() => { + if (query.exemplar === undefined) { + onChange({ ...query, exemplar: true }); + } + + if (!query.instant && !query.range) { + onChange({ ...query, instant: true, range: true }); + } + }, [onChange, query]); + + const onExemplarChange = useCallback( + (exemplar: boolean) => { + if (!isEqual(query, prevQuery) || exemplar !== query.exemplar) { + onChange({ ...query, exemplar }); + } + }, + [prevQuery, query, onChange] + ); + + function onChangeQueryStep(interval: string) { + onChange({ ...query, interval }); + } + + function onStepChange(e: React.SyntheticEvent) { + if (e.currentTarget.value !== query.interval) { + onChangeQueryStep(e.currentTarget.value); + } + } + + function onReturnKeyDown(e: React.KeyboardEvent) { + if (e.key === 'Enter' && (e.shiftKey || e.ctrlKey)) { + onRunQuery(); + } + } + + function onQueryTypeChange(queryType: string) { + let nextQuery; + if (queryType === 'instant') { + nextQuery = { ...query, instant: true, range: false }; + } else if (queryType === 'range') { + nextQuery = { ...query, instant: false, range: true }; + } else { + nextQuery = { ...query, instant: true, range: true }; + } + onChange(nextQuery); + } + return (
{/*Query type field*/} @@ -46,7 +91,11 @@ export const PromExploreExtraField: React.FC = memo( > Query type - +
{/*Step field*/}
= memo( className="gf-form-input width-4" placeholder={'auto'} onChange={onStepChange} - onKeyDown={onKeyDownFunc} - value={stepValue} + onKeyDown={onReturnKeyDown} + value={query.interval ?? ''} />
- onChange({ ...query, exemplar: isEnabled })} - datasource={datasource} - /> + ); } diff --git a/public/app/plugins/datasource/prometheus/components/PromExploreQueryEditor.tsx b/public/app/plugins/datasource/prometheus/components/PromExploreQueryEditor.tsx index 44c9db1124e..c31bdc57c58 100644 --- a/public/app/plugins/datasource/prometheus/components/PromExploreQueryEditor.tsx +++ b/public/app/plugins/datasource/prometheus/components/PromExploreQueryEditor.tsx @@ -1,11 +1,7 @@ -import React, { memo, FC, useEffect } from 'react'; - -// Types +import React, { memo, FC } from 'react'; import { QueryEditorProps } from '@grafana/data'; - import { PrometheusDatasource } from '../datasource'; import { PromQuery, PromOptions } from '../types'; - import PromQueryField from './PromQueryField'; import { PromExploreExtraField } from './PromExploreExtraField'; @@ -14,47 +10,6 @@ export type Props = QueryEditorProps = (props: Props) => { const { range, query, data, datasource, history, onChange, onRunQuery } = props; - useEffect(() => { - if (query.exemplar === undefined) { - onChange({ ...query, exemplar: true }); - } - - if (!query.instant && !query.range) { - onChange({ ...query, instant: true, range: true }); - } - }, [onChange, query]); - - function onChangeQueryStep(value: string) { - const { query, onChange } = props; - const nextQuery = { ...query, interval: value }; - onChange(nextQuery); - } - - function onStepChange(e: React.SyntheticEvent) { - if (e.currentTarget.value !== query.interval) { - onChangeQueryStep(e.currentTarget.value); - } - } - - function onReturnKeyDown(e: React.KeyboardEvent) { - if (e.key === 'Enter' && (e.shiftKey || e.ctrlKey)) { - onRunQuery(); - } - } - - function onQueryTypeChange(value: string) { - const { query, onChange } = props; - let nextQuery; - if (value === 'instant') { - nextQuery = { ...query, instant: true, range: false }; - } else if (value === 'range') { - nextQuery = { ...query, instant: false, range: true }; - } else { - nextQuery = { ...query, instant: true, range: true }; - } - onChange(nextQuery); - } - return ( = (props: Props) => { history={history} data={data} ExtraFieldElement={ - + } /> ); diff --git a/public/app/plugins/datasource/prometheus/components/PromQueryEditor.tsx b/public/app/plugins/datasource/prometheus/components/PromQueryEditor.tsx index 38bc1ab25ef..e5c5c648a4c 100644 --- a/public/app/plugins/datasource/prometheus/components/PromQueryEditor.tsx +++ b/public/app/plugins/datasource/prometheus/components/PromQueryEditor.tsx @@ -110,7 +110,7 @@ export class PromQueryEditor extends PureComponent render() { const { datasource, query, range, data } = this.props; - const { formatOption, instant, interval, intervalFactorOption, legendFormat, exemplar } = this.state; + const { formatOption, instant, interval, intervalFactorOption, legendFormat } = this.state; return ( /> - + } /> diff --git a/public/app/plugins/datasource/prometheus/components/PromQueryEditorByApp.test.tsx b/public/app/plugins/datasource/prometheus/components/PromQueryEditorByApp.test.tsx index ef1713cc915..033f95caf2f 100644 --- a/public/app/plugins/datasource/prometheus/components/PromQueryEditorByApp.test.tsx +++ b/public/app/plugins/datasource/prometheus/components/PromQueryEditorByApp.test.tsx @@ -3,8 +3,6 @@ import { render, RenderResult } from '@testing-library/react'; import { PromQueryEditorByApp } from './PromQueryEditorByApp'; import { CoreApp } from '@grafana/data'; import { noop } from 'lodash'; -import { Observable } from 'rxjs'; -import { first } from 'rxjs/operators'; import { PrometheusDatasource } from '../datasource'; import { testIds as alertingTestIds } from './PromQueryEditorForAlerting'; import { testIds as regularTestIds } from './PromQueryEditor'; @@ -20,7 +18,6 @@ function setup(app: CoreApp): RenderResult { getLabelKeys: () => [], metrics: [], }, - exemplarErrors: new Observable().pipe(first()), } as unknown) as PrometheusDatasource; return render( diff --git a/public/app/plugins/datasource/prometheus/components/__snapshots__/PromExploreQueryEditor.test.tsx.snap b/public/app/plugins/datasource/prometheus/components/__snapshots__/PromExploreQueryEditor.test.tsx.snap index 9f69a2cb974..5593e70d6bc 100644 --- a/public/app/plugins/datasource/prometheus/components/__snapshots__/PromExploreQueryEditor.test.tsx.snap +++ b/public/app/plugins/datasource/prometheus/components/__snapshots__/PromExploreQueryEditor.test.tsx.snap @@ -14,9 +14,7 @@ exports[`PromExploreQueryEditor should render component 1`] = ` } } onChange={[MockFunction]} - onKeyDownFunc={[Function]} - onQueryTypeChange={[Function]} - onStepChange={[Function]} + onRunQuery={[MockFunction]} query={ Object { "expr": "", @@ -24,8 +22,6 @@ exports[`PromExploreQueryEditor should render component 1`] = ` "refId": "A", } } - queryType="both" - stepValue="1s" /> } data={ diff --git a/public/app/plugins/datasource/prometheus/components/__snapshots__/PromQueryEditor.test.tsx.snap b/public/app/plugins/datasource/prometheus/components/__snapshots__/PromQueryEditor.test.tsx.snap index 6697e6ca81e..3ef0088f8d7 100644 --- a/public/app/plugins/datasource/prometheus/components/__snapshots__/PromQueryEditor.test.tsx.snap +++ b/public/app/plugins/datasource/prometheus/components/__snapshots__/PromQueryEditor.test.tsx.snap @@ -176,9 +176,16 @@ exports[`Render PromQueryEditor with basic options should render 1`] = ` "getPrometheusTime": [MockFunction], } } - isEnabled={true} onChange={[Function]} - refId="A" + query={ + Object { + "exemplar": true, + "expr": "", + "interval": "", + "legendFormat": "", + "refId": "A", + } + } /> } diff --git a/public/app/plugins/datasource/prometheus/datasource.ts b/public/app/plugins/datasource/prometheus/datasource.ts index 82d093c6fe7..e46c4143a04 100644 --- a/public/app/plugins/datasource/prometheus/datasource.ts +++ b/public/app/plugins/datasource/prometheus/datasource.ts @@ -1,5 +1,5 @@ import { cloneDeep, defaults } from 'lodash'; -import { forkJoin, lastValueFrom, merge, Observable, of, OperatorFunction, pipe, Subject, throwError } from 'rxjs'; +import { forkJoin, lastValueFrom, merge, Observable, of, OperatorFunction, pipe, throwError } from 'rxjs'; import { catchError, filter, map, tap } from 'rxjs/operators'; import LRU from 'lru-cache'; import { @@ -43,7 +43,6 @@ import { PrometheusVariableSupport } from './variables'; import PrometheusMetricFindQuery from './metric_find_query'; export const ANNOTATION_QUERY_STEP_DEFAULT = '60s'; -const EXEMPLARS_NOT_AVAILABLE = 'Exemplars for this query are not available.'; const GET_AND_POST_METADATA_ENDPOINTS = ['api/v1/query', 'api/v1/query_range', 'api/v1/series', 'api/v1/labels']; export class PrometheusDatasource extends DataSourceWithBackend { @@ -63,7 +62,7 @@ export class PrometheusDatasource extends DataSourceWithBackend = new Subject(); + exemplarsAvailable: boolean; constructor( instanceSettings: DataSourceInstanceSettings, @@ -88,10 +87,12 @@ export class PrometheusDatasource extends DataSourceWithBackend { + init = async () => { this.loadRules(); + this.exemplarsAvailable = await this.areExemplarsAvailable(); }; getQueryDisplayText(query: PromQuery) { @@ -271,13 +272,9 @@ export class PrometheusDatasource extends DataSourceWithBackend): Observable { + shouldRunExemplarQuery(target: PromQuery): boolean { + /* We want to run exemplar query only for histogram metrics: + 1. If we haven't processd histogram metrics yet, we need to check if expr includes "_bucket" which means that it is probably histogram metric (can rarely lead to false positive). + 2. If we have processed histogram metrics, check if it is part of query expr. + */ + if (target.exemplar) { + const histogramMetrics = this.languageProvider.histogramMetrics; + + if (histogramMetrics.length > 0) { + return !!histogramMetrics.find((metric) => target.expr.includes(metric)); + } else { + return target.expr.includes('_bucket'); + } + } + + return false; + } + + processTargetV2(target: PromQuery, request: DataQueryRequest) { + const processedTarget = { + ...target, + exemplar: this.shouldRunExemplarQuery(target), + requestId: request.panelId + target.refId, + // We need to pass utcOffsetSec to backend to calculate aligned range + utcOffsetSec: this.timeSrv.timeRange().to.utcOffset() * 60, + }; + return processedTarget; + } + + query(request: DataQueryRequest): Observable { // WIP - currently we want to run trough backend only if all queries are explore + range/instant queries - const shouldRunBackendQuery = - this.access === 'proxy' && options.app === CoreApp.Explore && !options.targets.some((query) => query.exemplar); + const shouldRunBackendQuery = this.access === 'proxy' && request.app === CoreApp.Explore; if (shouldRunBackendQuery) { - const targets = options.targets.map((target) => ({ - ...target, - // We need to pass utcOffsetSec to backend to calculate aligned range - utcOffsetSec: this.timeSrv.timeRange().to.utcOffset() * 60, - })); - return super.query({ ...options, targets }).pipe(map((response) => transformV2(response, options))); + const targets = request.targets.map((target) => this.processTargetV2(target, request)); + return super + .query({ ...request, targets }) + .pipe( + map((response) => + transformV2(response, request, { exemplarTraceIdDestinations: this.exemplarTraceIdDestinations }) + ) + ); // Run queries trough browser/proxy } else { - const start = this.getPrometheusTime(options.range.from, false); - const end = this.getPrometheusTime(options.range.to, true); - const { queries, activeTargets } = this.prepareTargets(options, start, end); + const start = this.getPrometheusTime(request.range.from, false); + const end = this.getPrometheusTime(request.range.to, true); + const { queries, activeTargets } = this.prepareTargets(request, start, end); // No valid targets, return the empty result to save a round trip. if (!queries || !queries.length) { @@ -315,11 +342,11 @@ export class PrometheusDatasource extends DataSourceWithBackend { - this.exemplarErrors.next({ refId: query.refId, error: EXEMPLARS_NOT_AVAILABLE }); return of({ data: [], state: LoadingState.Done, @@ -785,6 +811,18 @@ export class PrometheusDatasource extends DataSourceWithBackend { - const { values } = processHistogramLabels(data); - - if (values && values['__name__']) { - this.histogramMetrics = values['__name__'].slice().sort(); - } - }; - provideCompletionItems = async ( { prefix, text, value, labelKey, wrapperClasses }: TypeaheadInput, context: AutocompleteContext = {} diff --git a/public/app/plugins/datasource/prometheus/language_utils.ts b/public/app/plugins/datasource/prometheus/language_utils.ts index e4859fdd15c..b2b4ad8f0a1 100644 --- a/public/app/plugins/datasource/prometheus/language_utils.ts +++ b/public/app/plugins/datasource/prometheus/language_utils.ts @@ -2,19 +2,17 @@ import { PromMetricsMetadata, PromMetricsMetadataItem } from './types'; import { addLabelToQuery } from './add_label_to_query'; import { SUGGESTIONS_LIMIT } from './language_provider'; -export const processHistogramLabels = (labels: string[]) => { +export const processHistogramMetrics = (metrics: string[]) => { const resultSet: Set = new Set(); const regexp = new RegExp('_bucket($|:)'); - for (let index = 0; index < labels.length; index++) { - const label = labels[index]; - const isHistogramValue = regexp.test(label); + for (let index = 0; index < metrics.length; index++) { + const metric = metrics[index]; + const isHistogramValue = regexp.test(metric); if (isHistogramValue) { - resultSet.add(label); + resultSet.add(metric); } } - const result = [...resultSet]; - - return { values: { __name__: result } }; + return [...resultSet]; }; export function processLabels(labels: Array<{ [key: string]: string }>, withName = false) { diff --git a/public/app/plugins/datasource/prometheus/result_transformer.test.ts b/public/app/plugins/datasource/prometheus/result_transformer.test.ts index f34358d29d0..7a03515744a 100644 --- a/public/app/plugins/datasource/prometheus/result_transformer.test.ts +++ b/public/app/plugins/datasource/prometheus/result_transformer.test.ts @@ -53,7 +53,7 @@ describe('Prometheus Result Transformer', () => { }, ], } as unknown) as DataQueryResponse; - const series = transformV2(response, options); + const series = transformV2(response, options, {}); expect(series).toEqual({ data: [{ fields: [], length: 2, meta: { preferredVisualisationType: 'graph' }, name: 'ALERTS', refId: 'A' }], state: 'Done', @@ -86,7 +86,7 @@ describe('Prometheus Result Transformer', () => { }), ], } as unknown) as DataQueryResponse; - const series = transformV2(response, options); + const series = transformV2(response, options, {}); // expect(series.data[0]).toBe({}); expect(series.data[0].fields[0].name).toEqual('time'); expect(series.data[0].fields[1].name).toEqual('label1'); @@ -137,7 +137,7 @@ describe('Prometheus Result Transformer', () => { }), ], } as unknown) as DataQueryResponse; - const series = transformV2(response, options); + const series = transformV2(response, options, {}); expect(series.data[0].fields.length).toEqual(2); expect(series.data[0].meta?.preferredVisualisationType).toEqual('graph'); expect(series.data[1].fields.length).toEqual(4); diff --git a/public/app/plugins/datasource/prometheus/result_transformer.ts b/public/app/plugins/datasource/prometheus/result_transformer.ts index 22913e7ed36..20f1a101bee 100644 --- a/public/app/plugins/datasource/prometheus/result_transformer.ts +++ b/public/app/plugins/datasource/prometheus/result_transformer.ts @@ -44,7 +44,7 @@ interface TimeAndValue { const isTableResult = (dataFrame: DataFrame, options: DataQueryRequest): boolean => { // We want to process instant results in Explore as table - if ((options.app === CoreApp.Explore && dataFrame.meta?.custom?.queryType) === 'instant') { + if ((options.app === CoreApp.Explore && dataFrame.meta?.custom?.resultType) === 'vector') { return true; } @@ -58,19 +58,46 @@ const isTableResult = (dataFrame: DataFrame, options: DataQueryRequest) { - const [tableResults, otherResults]: [DataFrame[], DataFrame[]] = partition(response.data, (dataFrame) => - isTableResult(dataFrame, options) +export function transformV2( + response: DataQueryResponse, + request: DataQueryRequest, + options: { exemplarTraceIdDestinations?: ExemplarTraceIdDestination[] } +) { + const [tableResults, results]: [DataFrame[], DataFrame[]] = partition(response.data, (dataFrame) => + isTableResult(dataFrame, request) ); - // For table results, we need to transform data frames to table data frames - const responseLength = options.targets.filter((target) => !target.hide).length; + // TABLE FRAMES: For table results, we need to transform data frames to table data frames + const responseLength = request.targets.filter((target) => !target.hide).length; const tableFrames = tableResults.map((dataFrame) => { const df = transformDFoTable(dataFrame, responseLength); return df; }); - // Everything else is processed as time_series result and graph preferredVisualisationType + const [exemplarResults, otherResults]: [DataFrame[], DataFrame[]] = partition( + results, + (dataFrame) => dataFrame.meta?.custom?.resultType === 'exemplar' + ); + + // EXEMPLAR FRAMES: We enrich exemplar frames with data links and add dataTopic meta info + const { exemplarTraceIdDestinations: destinations } = options; + const exemplarFrames = exemplarResults.map((dataFrame) => { + if (destinations?.length) { + for (const exemplarTraceIdDestination of destinations) { + const traceIDField = dataFrame.fields.find((field) => field.name === exemplarTraceIdDestination.name); + if (traceIDField) { + const links = getDataLinks(exemplarTraceIdDestination); + traceIDField.config.links = traceIDField.config.links?.length + ? [...traceIDField.config.links, ...links] + : links; + } + } + } + + return { ...dataFrame, meta: { ...dataFrame.meta, dataTopic: DataTopic.Annotations } }; + }); + + // OTHER FRAMES: Everything else is processed as time_series result and graph preferredVisualisationType const otherFrames = otherResults.map((dataFrame) => { const df = { ...dataFrame, @@ -82,7 +109,7 @@ export function transformV2(response: DataQueryResponse, options: DataQueryReque return df; }); - return { ...response, data: [...otherFrames, ...tableFrames] }; + return { ...response, data: [...otherFrames, ...tableFrames, ...exemplarFrames] }; } export function transformDFoTable(df: DataFrame, responseLength: number): DataFrame { @@ -186,7 +213,7 @@ export function transform( // Add data links if configured if (transformOptions.exemplarTraceIdDestinations?.length) { for (const exemplarTraceIdDestination of transformOptions.exemplarTraceIdDestinations) { - const traceIDField = dataFrame.fields.find((field) => field.name === exemplarTraceIdDestination!.name); + const traceIDField = dataFrame.fields.find((field) => field.name === exemplarTraceIdDestination.name); if (traceIDField) { const links = getDataLinks(exemplarTraceIdDestination); traceIDField.config.links = traceIDField.config.links?.length