github.com/matrixorigin/matrixone@v1.2.0/pkg/util/metric/v2/dashboard/grafana_dashboard.go (about)

     1  // Copyright 2023 Matrix Origin
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package dashboard
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"fmt"
    21  	"net/http"
    22  
    23  	"github.com/K-Phoen/grabana"
    24  	"github.com/K-Phoen/grabana/axis"
    25  	"github.com/K-Phoen/grabana/dashboard"
    26  	"github.com/K-Phoen/grabana/graph"
    27  	"github.com/K-Phoen/grabana/row"
    28  	"github.com/K-Phoen/grabana/target/prometheus"
    29  	"github.com/K-Phoen/grabana/timeseries"
    30  	tsaxis "github.com/K-Phoen/grabana/timeseries/axis"
    31  	"github.com/K-Phoen/grabana/variable/datasource"
    32  	"github.com/K-Phoen/grabana/variable/interval"
    33  	"github.com/K-Phoen/grabana/variable/query"
    34  )
    35  
    36  var (
    37  	moFolderName = "Matrixone"
    38  )
    39  
    40  type DashboardCreator struct {
    41  	cli             *grabana.Client
    42  	dataSource      string
    43  	extraFilterFunc func() string
    44  	by              string
    45  	filterOptions   []dashboard.Option
    46  }
    47  
    48  func NewCloudDashboardCreator(
    49  	host,
    50  	username,
    51  	password,
    52  	dataSource string) *DashboardCreator {
    53  	dc := &DashboardCreator{
    54  		cli:        grabana.NewClient(http.DefaultClient, host, grabana.WithBasicAuth(username, password)),
    55  		dataSource: dataSource,
    56  	}
    57  	dc.extraFilterFunc = dc.getCloudFilters
    58  	dc.by = "pod"
    59  	dc.initCloudFilterOptions()
    60  	return dc
    61  }
    62  
    63  func NewLocalDashboardCreator(
    64  	host,
    65  	username,
    66  	password,
    67  	dataSource string) *DashboardCreator {
    68  	dc := &DashboardCreator{
    69  		cli:        grabana.NewClient(http.DefaultClient, host, grabana.WithBasicAuth(username, password)),
    70  		dataSource: dataSource,
    71  	}
    72  	dc.extraFilterFunc = dc.getLocalFilters
    73  	dc.by = "instance"
    74  	dc.initLocalFilterOptions()
    75  	return dc
    76  }
    77  
    78  func NewK8SDashboardCreator(
    79  	host,
    80  	username,
    81  	password,
    82  	dataSource string) *DashboardCreator {
    83  	dc := &DashboardCreator{
    84  		cli:        grabana.NewClient(http.DefaultClient, host, grabana.WithBasicAuth(username, password)),
    85  		dataSource: dataSource,
    86  	}
    87  	dc.extraFilterFunc = dc.getK8SFilters
    88  	dc.by = "pod"
    89  	dc.initK8SFilterOptions()
    90  	return dc
    91  }
    92  
    93  func NewCloudCtrlPlaneDashboardCreator(
    94  	host,
    95  	username,
    96  	password,
    97  	dataSource string) *DashboardCreator {
    98  	dc := &DashboardCreator{
    99  		cli:        grabana.NewClient(http.DefaultClient, host, grabana.WithBasicAuth(username, password)),
   100  		dataSource: AutoUnitPrometheusDatasource,
   101  	}
   102  	dc.extraFilterFunc = dc.getCloudFilters
   103  	dc.by = "pod"
   104  	dc.initCloudCtrlPlaneFilterOptions(dataSource)
   105  	return dc
   106  }
   107  
   108  func (c *DashboardCreator) Create() error {
   109  	if err := c.initTxnDashboard(); err != nil {
   110  		return err
   111  	}
   112  
   113  	if err := c.initLogTailDashboard(); err != nil {
   114  		return err
   115  	}
   116  
   117  	if err := c.initTaskDashboard(); err != nil {
   118  		return err
   119  	}
   120  
   121  	if err := c.initFileServiceDashboard(); err != nil {
   122  		return err
   123  	}
   124  
   125  	if err := c.initRPCDashboard(); err != nil {
   126  		return err
   127  	}
   128  
   129  	if err := c.initMemDashboard(); err != nil {
   130  		return err
   131  	}
   132  
   133  	if err := c.initRuntimeDashboard(); err != nil {
   134  		return err
   135  	}
   136  
   137  	if err := c.initTraceDashboard(); err != nil {
   138  		return err
   139  	}
   140  
   141  	if err := c.initProxyDashboard(); err != nil {
   142  		return err
   143  	}
   144  
   145  	if err := c.initFrontendDashboard(); err != nil {
   146  		return err
   147  	}
   148  
   149  	return nil
   150  }
   151  
   152  func (c *DashboardCreator) createFolder(name string) (*grabana.Folder, error) {
   153  	return c.cli.FindOrCreateFolder(context.Background(), name)
   154  }
   155  
   156  func (c *DashboardCreator) withGraph(
   157  	title string,
   158  	span float32,
   159  	pql string,
   160  	legend string,
   161  	opts ...axis.Option) row.Option {
   162  	return c.withMultiGraph(
   163  		title,
   164  		span,
   165  		[]string{pql},
   166  		[]string{legend},
   167  		opts...,
   168  	)
   169  }
   170  
   171  func (c *DashboardCreator) withMultiGraph(
   172  	title string,
   173  	span float32,
   174  	queries []string,
   175  	legends []string,
   176  	axisOpts ...axis.Option) row.Option {
   177  	opts := []graph.Option{
   178  		graph.Span(span),
   179  		graph.DataSource(c.dataSource),
   180  		graph.LeftYAxis(axisOpts...)}
   181  
   182  	for i, query := range queries {
   183  		opts = append(opts,
   184  			graph.WithPrometheusTarget(
   185  				query,
   186  				prometheus.Legend(legends[i]),
   187  			))
   188  	}
   189  
   190  	return row.WithGraph(
   191  		title,
   192  		opts...,
   193  	)
   194  }
   195  
   196  func (c *DashboardCreator) getHistogram(
   197  	title string,
   198  	metric string,
   199  	percents []float64,
   200  	column float32,
   201  	axisOptions ...axis.Option) row.Option {
   202  	return c.getHistogramWithExtraBy(title, metric, percents, column, "", axisOptions...)
   203  }
   204  
   205  func SpanNulls(always bool) timeseries.Option {
   206  	return func(ts *timeseries.TimeSeries) error {
   207  		ts.Builder.TimeseriesPanel.FieldConfig.Defaults.Custom.SpanNulls = always
   208  		return nil
   209  	}
   210  }
   211  
   212  func (c *DashboardCreator) getPercentHist(
   213  	title string,
   214  	metric string,
   215  	percents []float64,
   216  	opts ...timeseries.Option) row.Option {
   217  	options := []timeseries.Option{
   218  		timeseries.DataSource(c.dataSource),
   219  		timeseries.FillOpacity(0),
   220  		timeseries.Height("300px"),
   221  		timeseries.Axis(tsaxis.Unit("s")),
   222  	}
   223  	options = append(options, opts...)
   224  	for i := 0; i < len(percents); i++ {
   225  		percent := percents[i]
   226  		query := fmt.Sprintf("histogram_quantile(%f, sum(rate(%s[$interval])) by (le, "+c.by+"))", percent, metric)
   227  		legend := fmt.Sprintf("P%.0f", percent*100)
   228  		options = append(options, timeseries.WithPrometheusTarget(
   229  			query,
   230  			prometheus.Legend(legend),
   231  		))
   232  	}
   233  	return row.WithTimeSeries(title, options...)
   234  }
   235  
   236  func (c *DashboardCreator) getTimeSeries(
   237  	title string, pql []string, legend []string,
   238  	opts ...timeseries.Option) row.Option {
   239  	options := []timeseries.Option{
   240  		timeseries.DataSource(c.dataSource),
   241  		timeseries.FillOpacity(0),
   242  		timeseries.Height("300px"),
   243  	}
   244  	options = append(options, opts...)
   245  	for i := range pql {
   246  		options = append(options, timeseries.WithPrometheusTarget(
   247  			pql[i],
   248  			prometheus.Legend(legend[i]),
   249  		))
   250  	}
   251  	return row.WithTimeSeries(title, options...)
   252  }
   253  
   254  func (c *DashboardCreator) getHistogramWithExtraBy(
   255  	title string,
   256  	metric string,
   257  	percents []float64,
   258  	column float32,
   259  	extraBy string,
   260  	axisOptions ...axis.Option) row.Option {
   261  
   262  	var queries []string
   263  	var legends []string
   264  	for i := 0; i < len(percents); i++ {
   265  		percent := percents[i]
   266  
   267  		query := fmt.Sprintf("histogram_quantile(%f, sum(rate(%s[$interval])) by (le))", percent, metric)
   268  		legend := fmt.Sprintf("P%.2f%%", percent*100)
   269  		if len(extraBy) > 0 {
   270  			query = fmt.Sprintf("histogram_quantile(%f, sum(rate(%s[$interval])) by (le, %s))", percent, metric, extraBy)
   271  			legend = fmt.Sprintf("{{ "+extraBy+" }}(P%.2f%%)", percent*100)
   272  		}
   273  		queries = append(queries, query)
   274  		legends = append(legends, legend)
   275  	}
   276  	return c.withMultiGraph(
   277  		title,
   278  		column,
   279  		queries,
   280  		legends,
   281  		axisOptions...,
   282  	)
   283  }
   284  
   285  func (c *DashboardCreator) getMultiHistogram(
   286  	metrics []string,
   287  	legends []string,
   288  	percents []float64,
   289  	columns []float32,
   290  	axisOptions ...axis.Option) []row.Option {
   291  	var options []row.Option
   292  	for i := 0; i < len(percents); i++ {
   293  		percent := percents[i]
   294  
   295  		var queries []string
   296  		for _, metric := range metrics {
   297  			queries = append(queries,
   298  				fmt.Sprintf("histogram_quantile(%f, sum(rate(%s[$interval]))  by (le))", percent, metric))
   299  		}
   300  
   301  		options = append(options,
   302  			c.withMultiGraph(
   303  				fmt.Sprintf("P%f time", percent*100),
   304  				columns[i],
   305  				queries,
   306  				legends,
   307  				axisOptions...))
   308  	}
   309  	return options
   310  }
   311  
   312  func (c *DashboardCreator) withRowOptions(rows ...dashboard.Option) []dashboard.Option {
   313  	rows = append(rows,
   314  		dashboard.AutoRefresh("30s"),
   315  		dashboard.Time("now-30m", "now"),
   316  		dashboard.VariableAsInterval(
   317  			"interval",
   318  			interval.Default("1m"),
   319  			interval.Values([]string{"1m", "5m", "10m", "30m", "1h", "6h", "12h"}),
   320  		))
   321  	return append(rows, c.filterOptions...)
   322  }
   323  
   324  func (c *DashboardCreator) getMetricWithFilter(name string, filter string) string {
   325  	var metric bytes.Buffer
   326  	extraFilters := c.extraFilterFunc()
   327  
   328  	if len(filter) == 0 && len(extraFilters) == 0 {
   329  		return name
   330  	}
   331  
   332  	metric.WriteString(name)
   333  	metric.WriteString("{")
   334  	if filter != "" {
   335  		metric.WriteString(filter)
   336  		if len(extraFilters) > 0 {
   337  			metric.WriteString(",")
   338  		}
   339  	}
   340  	if len(extraFilters) > 0 {
   341  		metric.WriteString(c.extraFilterFunc())
   342  	}
   343  	metric.WriteString("}")
   344  	return metric.String()
   345  }
   346  
   347  func (c *DashboardCreator) getCloudFilters() string {
   348  	return `matrixone_cloud_main_cluster=~"$physicalCluster", matrixorigin_io_owner=~"$owner", pod=~"$pod"`
   349  }
   350  
   351  func (c *DashboardCreator) initCloudFilterOptions() {
   352  	c.filterOptions = append(c.filterOptions,
   353  		dashboard.VariableAsQuery(
   354  			"physicalCluster",
   355  			query.DataSource(c.dataSource),
   356  			query.DefaultAll(),
   357  			query.IncludeAll(),
   358  			query.Multiple(),
   359  			query.Label("main_cluster"),
   360  			query.Request(`label_values(up, matrixone_cloud_main_cluster)`),
   361  		),
   362  		dashboard.VariableAsQuery(
   363  			"owner",
   364  			query.DataSource(c.dataSource),
   365  			query.DefaultAll(),
   366  			query.IncludeAll(),
   367  			query.Multiple(),
   368  			query.Label("owner"),
   369  			query.Request(`label_values(up{matrixone_cloud_main_cluster=~"$physicalCluster"}, matrixorigin_io_owner)`),
   370  			query.AllValue(".*"),
   371  			query.Refresh(query.TimeChange),
   372  		),
   373  		dashboard.VariableAsQuery(
   374  			"pod",
   375  			query.DataSource(c.dataSource),
   376  			query.DefaultAll(),
   377  			query.IncludeAll(),
   378  			query.Multiple(),
   379  			query.Label("pod"),
   380  			query.Request(`label_values(up{matrixone_cloud_main_cluster="$physicalCluster", matrixorigin_io_owner=~"$owner"},pod)`),
   381  			query.Refresh(query.TimeChange),
   382  		))
   383  }
   384  
   385  const Prometheus = "prometheus"
   386  const AutoUnitPrometheusDatasource = `${ds_prom}`
   387  
   388  func (c *DashboardCreator) initCloudCtrlPlaneFilterOptions(metaDatasource string) {
   389  	c.filterOptions = append(c.filterOptions,
   390  		dashboard.VariableAsQuery(
   391  			"unit",
   392  			query.DataSource(metaDatasource),
   393  			query.Label("unit"),
   394  			query.Request(`label_values(mo_cluster_info, unit)`),
   395  		),
   396  		dashboard.VariableAsDatasource(
   397  			"ds_prom",
   398  			datasource.Type(Prometheus),
   399  			datasource.Regex(`/$unit-prometheus/`),
   400  		),
   401  		dashboard.VariableAsQuery(
   402  			"physicalCluster",
   403  			query.DataSource(`${ds_prom}`),
   404  			query.Label("main_cluster"),
   405  			query.Request(`label_values(up, matrixone_cloud_main_cluster)`),
   406  		),
   407  		dashboard.VariableAsQuery(
   408  			"owner",
   409  			query.DataSource(`${ds_prom}`),
   410  			query.DefaultAll(),
   411  			query.IncludeAll(),
   412  			query.Multiple(),
   413  			query.Label("owner"),
   414  			query.Request(`label_values(up{matrixone_cloud_main_cluster=~"$physicalCluster"}, matrixorigin_io_owner)`),
   415  			query.AllValue(".*"),
   416  			query.Refresh(query.TimeChange),
   417  		),
   418  		dashboard.VariableAsQuery(
   419  			"pod",
   420  			query.DataSource(`${ds_prom}`),
   421  			query.DefaultAll(),
   422  			query.IncludeAll(),
   423  			query.Multiple(),
   424  			query.Label("pod"),
   425  			query.Request(`label_values(up{matrixone_cloud_main_cluster="$physicalCluster", matrixorigin_io_owner=~"$owner"},pod)`),
   426  			query.Refresh(query.TimeChange),
   427  		))
   428  }
   429  
   430  func (c *DashboardCreator) getLocalFilters() string {
   431  	return ""
   432  }
   433  
   434  func (c *DashboardCreator) initLocalFilterOptions() {
   435  	c.filterOptions = append(c.filterOptions,
   436  		dashboard.VariableAsQuery(
   437  			"instance",
   438  			query.DataSource(c.dataSource),
   439  			query.DefaultAll(),
   440  			query.IncludeAll(),
   441  			query.Multiple(),
   442  			query.Label("instance"),
   443  			query.Request("label_values(instance)"),
   444  		))
   445  }
   446  
   447  func (c *DashboardCreator) getK8SFilters() string {
   448  	return `namespace=~"$namespace", pod=~"$pod"`
   449  }
   450  
   451  func (c *DashboardCreator) initK8SFilterOptions() {
   452  	c.filterOptions = append(c.filterOptions,
   453  		dashboard.VariableAsQuery(
   454  			"namespace",
   455  			query.DataSource(c.dataSource),
   456  			query.DefaultAll(),
   457  			query.IncludeAll(),
   458  			query.Multiple(),
   459  			query.Label("namespace"),
   460  			query.Request("label_values(namespace)"),
   461  		),
   462  		dashboard.VariableAsQuery(
   463  			"pod",
   464  			query.DataSource(c.dataSource),
   465  			query.DefaultAll(),
   466  			query.IncludeAll(),
   467  			query.Multiple(),
   468  			query.Label("pod"),
   469  			query.Request("label_values(pod)"),
   470  		))
   471  }