github.com/netdata/go.d.plugin@v0.58.1/modules/weblog/charts.go (about)

     1  // SPDX-License-Identifier: GPL-3.0-or-later
     2  
     3  package weblog
     4  
     5  import (
     6  	"errors"
     7  	"fmt"
     8  
     9  	"github.com/netdata/go.d.plugin/agent/module"
    10  )
    11  
    12  type (
    13  	Charts = module.Charts
    14  	Chart  = module.Chart
    15  	Dims   = module.Dims
    16  	Dim    = module.Dim
    17  )
    18  
    19  const (
    20  	prioReqTotal = module.Priority + iota
    21  	prioReqExcluded
    22  	prioReqType
    23  
    24  	prioRespCodesClass
    25  	prioRespCodes
    26  	prioRespCodes1xx
    27  	prioRespCodes2xx
    28  	prioRespCodes3xx
    29  	prioRespCodes4xx
    30  	prioRespCodes5xx
    31  
    32  	prioBandwidth
    33  
    34  	prioReqProcTime
    35  	prioRespTimeHist
    36  	prioUpsRespTime
    37  	prioUpsRespTimeHist
    38  
    39  	prioUniqIP
    40  
    41  	prioReqVhost
    42  	prioReqPort
    43  	prioReqScheme
    44  	prioReqMethod
    45  	prioReqVersion
    46  	prioReqIPProto
    47  	prioReqSSLProto
    48  	prioReqSSLCipherSuite
    49  
    50  	prioReqCustomFieldPattern  // chart per custom field, alphabetical order
    51  	prioReqCustomTimeField     // chart per custom time field, alphabetical order
    52  	prioReqCustomTimeFieldHist // histogram chart per custom time field
    53  	prioReqURLPattern
    54  	prioURLPatternStats
    55  
    56  	prioReqCustomNumericFieldSummary // 3 charts per url pattern, alphabetical order
    57  )
    58  
    59  // NOTE: inconsistency with python web_log
    60  // TODO: current histogram charts are misleading in netdata
    61  
    62  // Requests
    63  var (
    64  	reqTotal = Chart{
    65  		ID:       "requests",
    66  		Title:    "Total Requests",
    67  		Units:    "requests/s",
    68  		Fam:      "requests",
    69  		Ctx:      "web_log.requests",
    70  		Priority: prioReqTotal,
    71  		Dims: Dims{
    72  			{ID: "requests", Algo: module.Incremental},
    73  		},
    74  	}
    75  	reqExcluded = Chart{
    76  		ID:       "excluded_requests",
    77  		Title:    "Excluded Requests",
    78  		Units:    "requests/s",
    79  		Fam:      "requests",
    80  		Ctx:      "web_log.excluded_requests",
    81  		Type:     module.Stacked,
    82  		Priority: prioReqExcluded,
    83  		Dims: Dims{
    84  			{ID: "req_unmatched", Name: "unmatched", Algo: module.Incremental},
    85  		},
    86  	}
    87  	// netdata specific grouping
    88  	reqTypes = Chart{
    89  		ID:       "requests_by_type",
    90  		Title:    "Requests By Type",
    91  		Units:    "requests/s",
    92  		Fam:      "requests",
    93  		Ctx:      "web_log.type_requests",
    94  		Type:     module.Stacked,
    95  		Priority: prioReqType,
    96  		Dims: Dims{
    97  			{ID: "req_type_success", Name: "success", Algo: module.Incremental},
    98  			{ID: "req_type_bad", Name: "bad", Algo: module.Incremental},
    99  			{ID: "req_type_redirect", Name: "redirect", Algo: module.Incremental},
   100  			{ID: "req_type_error", Name: "error", Algo: module.Incremental},
   101  		},
   102  	}
   103  )
   104  
   105  // Responses
   106  var (
   107  	respCodeClass = Chart{
   108  		ID:       "responses_by_status_code_class",
   109  		Title:    "Responses By Status Code Class",
   110  		Units:    "responses/s",
   111  		Fam:      "responses",
   112  		Ctx:      "web_log.status_code_class_responses",
   113  		Type:     module.Stacked,
   114  		Priority: prioRespCodesClass,
   115  		Dims: Dims{
   116  			{ID: "resp_2xx", Name: "2xx", Algo: module.Incremental},
   117  			{ID: "resp_5xx", Name: "5xx", Algo: module.Incremental},
   118  			{ID: "resp_3xx", Name: "3xx", Algo: module.Incremental},
   119  			{ID: "resp_4xx", Name: "4xx", Algo: module.Incremental},
   120  			{ID: "resp_1xx", Name: "1xx", Algo: module.Incremental},
   121  		},
   122  	}
   123  	respCodes = Chart{
   124  		ID:       "responses_by_status_code",
   125  		Title:    "Responses By Status Code",
   126  		Units:    "responses/s",
   127  		Fam:      "responses",
   128  		Ctx:      "web_log.status_code_responses",
   129  		Type:     module.Stacked,
   130  		Priority: prioRespCodes,
   131  	}
   132  	respCodes1xx = Chart{
   133  		ID:       "status_code_class_1xx_responses",
   134  		Title:    "Informational Responses By Status Code",
   135  		Units:    "responses/s",
   136  		Fam:      "responses",
   137  		Ctx:      "web_log.status_code_class_1xx_responses",
   138  		Type:     module.Stacked,
   139  		Priority: prioRespCodes1xx,
   140  	}
   141  	respCodes2xx = Chart{
   142  		ID:       "status_code_class_2xx_responses",
   143  		Title:    "Successful Responses By Status Code",
   144  		Units:    "responses/s",
   145  		Fam:      "responses",
   146  		Ctx:      "web_log.status_code_class_2xx_responses",
   147  		Type:     module.Stacked,
   148  		Priority: prioRespCodes2xx,
   149  	}
   150  	respCodes3xx = Chart{
   151  		ID:       "status_code_class_3xx_responses",
   152  		Title:    "Redirects Responses By Status Code",
   153  		Units:    "responses/s",
   154  		Fam:      "responses",
   155  		Ctx:      "web_log.status_code_class_3xx_responses",
   156  		Type:     module.Stacked,
   157  		Priority: prioRespCodes3xx,
   158  	}
   159  	respCodes4xx = Chart{
   160  		ID:       "status_code_class_4xx_responses",
   161  		Title:    "Client Errors Responses By Status Code",
   162  		Units:    "responses/s",
   163  		Fam:      "responses",
   164  		Ctx:      "web_log.status_code_class_4xx_responses",
   165  		Type:     module.Stacked,
   166  		Priority: prioRespCodes4xx,
   167  	}
   168  	respCodes5xx = Chart{
   169  		ID:       "status_code_class_5xx_responses",
   170  		Title:    "Server Errors Responses By Status Code",
   171  		Units:    "responses/s",
   172  		Fam:      "responses",
   173  		Ctx:      "web_log.status_code_class_5xx_responses",
   174  		Type:     module.Stacked,
   175  		Priority: prioRespCodes5xx,
   176  	}
   177  )
   178  
   179  // Bandwidth
   180  var (
   181  	bandwidth = Chart{
   182  		ID:       "bandwidth",
   183  		Title:    "Bandwidth",
   184  		Units:    "kilobits/s",
   185  		Fam:      "bandwidth",
   186  		Ctx:      "web_log.bandwidth",
   187  		Type:     module.Area,
   188  		Priority: prioBandwidth,
   189  		Dims: Dims{
   190  			{ID: "bytes_received", Name: "received", Algo: module.Incremental, Mul: 8, Div: 1000},
   191  			{ID: "bytes_sent", Name: "sent", Algo: module.Incremental, Mul: -8, Div: 1000},
   192  		},
   193  	}
   194  )
   195  
   196  // Timings
   197  var (
   198  	reqProcTime = Chart{
   199  		ID:       "request_processing_time",
   200  		Title:    "Request Processing Time",
   201  		Units:    "milliseconds",
   202  		Fam:      "timings",
   203  		Ctx:      "web_log.request_processing_time",
   204  		Priority: prioReqProcTime,
   205  		Dims: Dims{
   206  			{ID: "req_proc_time_min", Name: "min", Div: 1000},
   207  			{ID: "req_proc_time_max", Name: "max", Div: 1000},
   208  			{ID: "req_proc_time_avg", Name: "avg", Div: 1000},
   209  		},
   210  	}
   211  	reqProcTimeHist = Chart{
   212  		ID:       "requests_processing_time_histogram",
   213  		Title:    "Requests Processing Time Histogram",
   214  		Units:    "requests/s",
   215  		Fam:      "timings",
   216  		Ctx:      "web_log.requests_processing_time_histogram",
   217  		Priority: prioRespTimeHist,
   218  	}
   219  )
   220  
   221  // Upstream
   222  var (
   223  	upsRespTime = Chart{
   224  		ID:       "upstream_response_time",
   225  		Title:    "Upstream Response Time",
   226  		Units:    "milliseconds",
   227  		Fam:      "timings",
   228  		Ctx:      "web_log.upstream_response_time",
   229  		Priority: prioUpsRespTime,
   230  		Dims: Dims{
   231  			{ID: "upstream_resp_time_min", Name: "min", Div: 1000},
   232  			{ID: "upstream_resp_time_max", Name: "max", Div: 1000},
   233  			{ID: "upstream_resp_time_avg", Name: "avg", Div: 1000},
   234  		},
   235  	}
   236  	upsRespTimeHist = Chart{
   237  		ID:       "upstream_responses_time_histogram",
   238  		Title:    "Upstream Responses Time Histogram",
   239  		Units:    "responses/s",
   240  		Fam:      "timings",
   241  		Ctx:      "web_log.upstream_responses_time_histogram",
   242  		Priority: prioUpsRespTimeHist,
   243  	}
   244  )
   245  
   246  // Clients
   247  var (
   248  	uniqIPsCurPoll = Chart{
   249  		ID:       "current_poll_uniq_clients",
   250  		Title:    "Current Poll Unique Clients",
   251  		Units:    "clients",
   252  		Fam:      "client",
   253  		Ctx:      "web_log.current_poll_uniq_clients",
   254  		Type:     module.Stacked,
   255  		Priority: prioUniqIP,
   256  		Dims: Dims{
   257  			{ID: "uniq_ipv4", Name: "ipv4", Algo: module.Absolute},
   258  			{ID: "uniq_ipv6", Name: "ipv6", Algo: module.Absolute},
   259  		},
   260  	}
   261  )
   262  
   263  // Request By N
   264  var (
   265  	reqByVhost = Chart{
   266  		ID:       "requests_by_vhost",
   267  		Title:    "Requests By Vhost",
   268  		Units:    "requests/s",
   269  		Fam:      "vhost",
   270  		Ctx:      "web_log.vhost_requests",
   271  		Type:     module.Stacked,
   272  		Priority: prioReqVhost,
   273  	}
   274  	reqByPort = Chart{
   275  		ID:       "requests_by_port",
   276  		Title:    "Requests By Port",
   277  		Units:    "requests/s",
   278  		Fam:      "port",
   279  		Ctx:      "web_log.port_requests",
   280  		Type:     module.Stacked,
   281  		Priority: prioReqPort,
   282  	}
   283  	reqByScheme = Chart{
   284  		ID:       "requests_by_scheme",
   285  		Title:    "Requests By Scheme",
   286  		Units:    "requests/s",
   287  		Fam:      "scheme",
   288  		Ctx:      "web_log.scheme_requests",
   289  		Type:     module.Stacked,
   290  		Priority: prioReqScheme,
   291  		Dims: Dims{
   292  			{ID: "req_http_scheme", Name: "http", Algo: module.Incremental},
   293  			{ID: "req_https_scheme", Name: "https", Algo: module.Incremental},
   294  		},
   295  	}
   296  	reqByMethod = Chart{
   297  		ID:       "requests_by_http_method",
   298  		Title:    "Requests By HTTP Method",
   299  		Units:    "requests/s",
   300  		Fam:      "http method",
   301  		Ctx:      "web_log.http_method_requests",
   302  		Type:     module.Stacked,
   303  		Priority: prioReqMethod,
   304  	}
   305  	reqByVersion = Chart{
   306  		ID:       "requests_by_http_version",
   307  		Title:    "Requests By HTTP Version",
   308  		Units:    "requests/s",
   309  		Fam:      "http version",
   310  		Ctx:      "web_log.http_version_requests",
   311  		Type:     module.Stacked,
   312  		Priority: prioReqVersion,
   313  	}
   314  	reqByIPProto = Chart{
   315  		ID:       "requests_by_ip_proto",
   316  		Title:    "Requests By IP Protocol",
   317  		Units:    "requests/s",
   318  		Fam:      "ip proto",
   319  		Ctx:      "web_log.ip_proto_requests",
   320  		Type:     module.Stacked,
   321  		Priority: prioReqIPProto,
   322  		Dims: Dims{
   323  			{ID: "req_ipv4", Name: "ipv4", Algo: module.Incremental},
   324  			{ID: "req_ipv6", Name: "ipv6", Algo: module.Incremental},
   325  		},
   326  	}
   327  	reqBySSLProto = Chart{
   328  		ID:       "requests_by_ssl_proto",
   329  		Title:    "Requests By SSL Connection Protocol",
   330  		Units:    "requests/s",
   331  		Fam:      "ssl conn",
   332  		Ctx:      "web_log.ssl_proto_requests",
   333  		Type:     module.Stacked,
   334  		Priority: prioReqSSLProto,
   335  	}
   336  	reqBySSLCipherSuite = Chart{
   337  		ID:       "requests_by_ssl_cipher_suite",
   338  		Title:    "Requests By SSL Connection Cipher Suite",
   339  		Units:    "requests/s",
   340  		Fam:      "ssl conn",
   341  		Ctx:      "web_log.ssl_cipher_suite_requests",
   342  		Type:     module.Stacked,
   343  		Priority: prioReqSSLCipherSuite,
   344  	}
   345  )
   346  
   347  // Request By N Patterns
   348  var (
   349  	reqByURLPattern = Chart{
   350  		ID:       "requests_by_url_pattern",
   351  		Title:    "URL Field Requests By Pattern",
   352  		Units:    "requests/s",
   353  		Fam:      "url ptn",
   354  		Ctx:      "web_log.url_pattern_requests",
   355  		Type:     module.Stacked,
   356  		Priority: prioReqURLPattern,
   357  	}
   358  	reqByCustomFieldPattern = Chart{
   359  		ID:       "custom_field_%s_requests_by_pattern",
   360  		Title:    "Custom Field %s Requests By Pattern",
   361  		Units:    "requests/s",
   362  		Fam:      "custom field ptn",
   363  		Ctx:      "web_log.custom_field_pattern_requests",
   364  		Type:     module.Stacked,
   365  		Priority: prioReqCustomFieldPattern,
   366  	}
   367  )
   368  
   369  // custom time field
   370  var (
   371  	reqByCustomTimeField = Chart{
   372  		ID:       "custom_time_field_%s_summary",
   373  		Title:    `Custom Time Field "%s" Summary`,
   374  		Units:    "milliseconds",
   375  		Fam:      "custom time field",
   376  		Ctx:      "web_log.custom_time_field_summary",
   377  		Priority: prioReqCustomTimeField,
   378  		Dims: Dims{
   379  			{ID: "custom_time_field_%s_time_min", Name: "min", Div: 1000},
   380  			{ID: "custom_time_field_%s_time_max", Name: "max", Div: 1000},
   381  			{ID: "custom_time_field_%s_time_avg", Name: "avg", Div: 1000},
   382  		},
   383  	}
   384  	reqByCustomTimeFieldHist = Chart{
   385  		ID:       "custom_time_field_%s_histogram",
   386  		Title:    `Custom Time Field "%s" Histogram`,
   387  		Units:    "observations",
   388  		Fam:      "custom time field",
   389  		Ctx:      "web_log.custom_time_field_histogram",
   390  		Priority: prioReqCustomTimeFieldHist,
   391  	}
   392  )
   393  
   394  var (
   395  	customNumericFieldSummaryChartTmpl = Chart{
   396  		ID:       "custom_numeric_field_%s_summary",
   397  		Title:    "Custom Numeric Field Summary",
   398  		Units:    "",
   399  		Fam:      "custom numeric fields",
   400  		Ctx:      "web_log.custom_numeric_field_%s_summary",
   401  		Priority: prioReqCustomNumericFieldSummary,
   402  		Dims: Dims{
   403  			{ID: "custom_numeric_field_%s_summary_min", Name: "min"},
   404  			{ID: "custom_numeric_field_%s_summary_max", Name: "max"},
   405  			{ID: "custom_numeric_field_%s_summary_avg", Name: "avg"},
   406  		},
   407  	}
   408  )
   409  
   410  // URL pattern stats
   411  var (
   412  	urlPatternRespCodes = Chart{
   413  		ID:       "url_pattern_%s_responses_by_status_code",
   414  		Title:    "Responses By Status Code",
   415  		Units:    "responses/s",
   416  		Fam:      "url ptn %s",
   417  		Ctx:      "web_log.url_pattern_status_code_responses",
   418  		Type:     module.Stacked,
   419  		Priority: prioURLPatternStats,
   420  	}
   421  	urlPatternReqMethods = Chart{
   422  		ID:       "url_pattern_%s_requests_by_http_method",
   423  		Title:    "Requests By HTTP Method",
   424  		Units:    "requests/s",
   425  		Fam:      "url ptn %s",
   426  		Ctx:      "web_log.url_pattern_http_method_requests",
   427  		Type:     module.Stacked,
   428  		Priority: prioURLPatternStats + 1,
   429  	}
   430  	urlPatternBandwidth = Chart{
   431  		ID:       "url_pattern_%s_bandwidth",
   432  		Title:    "Bandwidth",
   433  		Units:    "kilobits/s",
   434  		Fam:      "url ptn %s",
   435  		Ctx:      "web_log.url_pattern_bandwidth",
   436  		Type:     module.Area,
   437  		Priority: prioURLPatternStats + 2,
   438  		Dims: Dims{
   439  			{ID: "url_ptn_%s_bytes_received", Name: "received", Algo: module.Incremental, Mul: 8, Div: 1000},
   440  			{ID: "url_ptn_%s_bytes_sent", Name: "sent", Algo: module.Incremental, Mul: -8, Div: 1000},
   441  		},
   442  	}
   443  	urlPatternReqProcTime = Chart{
   444  		ID:       "url_pattern_%s_request_processing_time",
   445  		Title:    "Request Processing Time",
   446  		Units:    "milliseconds",
   447  		Fam:      "url ptn %s",
   448  		Ctx:      "web_log.url_pattern_request_processing_time",
   449  		Priority: prioURLPatternStats + 3,
   450  		Dims: Dims{
   451  			{ID: "url_ptn_%s_req_proc_time_min", Name: "min", Div: 1000},
   452  			{ID: "url_ptn_%s_req_proc_time_max", Name: "max", Div: 1000},
   453  			{ID: "url_ptn_%s_req_proc_time_avg", Name: "avg", Div: 1000},
   454  		},
   455  	}
   456  )
   457  
   458  func newReqProcTimeHistChart(histogram []float64) (*Chart, error) {
   459  	chart := reqProcTimeHist.Copy()
   460  	for i, v := range histogram {
   461  		dim := &Dim{
   462  			ID:   fmt.Sprintf("req_proc_time_hist_bucket_%d", i+1),
   463  			Name: fmt.Sprintf("%.3f", v),
   464  			Algo: module.Incremental,
   465  		}
   466  		if err := chart.AddDim(dim); err != nil {
   467  			return nil, err
   468  		}
   469  	}
   470  	if err := chart.AddDim(&Dim{
   471  		ID:   "req_proc_time_hist_count",
   472  		Name: "+Inf",
   473  		Algo: module.Incremental,
   474  	}); err != nil {
   475  		return nil, err
   476  	}
   477  	return chart, nil
   478  }
   479  
   480  func newUpsRespTimeHistChart(histogram []float64) (*Chart, error) {
   481  	chart := upsRespTimeHist.Copy()
   482  	for i, v := range histogram {
   483  		dim := &Dim{
   484  			ID:   fmt.Sprintf("upstream_resp_time_hist_bucket_%d", i+1),
   485  			Name: fmt.Sprintf("%.3f", v),
   486  			Algo: module.Incremental,
   487  		}
   488  		if err := chart.AddDim(dim); err != nil {
   489  			return nil, err
   490  		}
   491  	}
   492  	if err := chart.AddDim(&Dim{
   493  		ID:   "upstream_resp_time_hist_count",
   494  		Name: "+Inf",
   495  		Algo: module.Incremental,
   496  	}); err != nil {
   497  		return nil, err
   498  	}
   499  	return chart, nil
   500  }
   501  
   502  func newURLPatternChart(patterns []userPattern) (*Chart, error) {
   503  	chart := reqByURLPattern.Copy()
   504  	for _, p := range patterns {
   505  		dim := &Dim{
   506  			ID:   "req_url_ptn_" + p.Name,
   507  			Name: p.Name,
   508  			Algo: module.Incremental,
   509  		}
   510  		if err := chart.AddDim(dim); err != nil {
   511  			return nil, err
   512  		}
   513  	}
   514  	return chart, nil
   515  }
   516  
   517  func newURLPatternRespCodesChart(name string) *Chart {
   518  	chart := urlPatternRespCodes.Copy()
   519  	chart.ID = fmt.Sprintf(chart.ID, name)
   520  	chart.Fam = fmt.Sprintf(chart.Fam, name)
   521  	return chart
   522  }
   523  
   524  func newURLPatternReqMethodsChart(name string) *Chart {
   525  	chart := urlPatternReqMethods.Copy()
   526  	chart.ID = fmt.Sprintf(chart.ID, name)
   527  	chart.Fam = fmt.Sprintf(chart.Fam, name)
   528  	return chart
   529  }
   530  
   531  func newURLPatternBandwidthChart(name string) *Chart {
   532  	chart := urlPatternBandwidth.Copy()
   533  	chart.ID = fmt.Sprintf(chart.ID, name)
   534  	chart.Fam = fmt.Sprintf(chart.Fam, name)
   535  	for _, d := range chart.Dims {
   536  		d.ID = fmt.Sprintf(d.ID, name)
   537  	}
   538  	return chart
   539  }
   540  
   541  func newURLPatternReqProcTimeChart(name string) *Chart {
   542  	chart := urlPatternReqProcTime.Copy()
   543  	chart.ID = fmt.Sprintf(chart.ID, name)
   544  	chart.Fam = fmt.Sprintf(chart.Fam, name)
   545  	for _, d := range chart.Dims {
   546  		d.ID = fmt.Sprintf(d.ID, name)
   547  	}
   548  	return chart
   549  }
   550  
   551  func newCustomFieldCharts(fields []customField) (Charts, error) {
   552  	charts := Charts{}
   553  	for _, f := range fields {
   554  		chart, err := newCustomFieldChart(f)
   555  		if err != nil {
   556  			return nil, err
   557  		}
   558  		if err := charts.Add(chart); err != nil {
   559  			return nil, err
   560  		}
   561  	}
   562  	return charts, nil
   563  }
   564  
   565  func newCustomFieldChart(f customField) (*Chart, error) {
   566  	chart := reqByCustomFieldPattern.Copy()
   567  	chart.ID = fmt.Sprintf(chart.ID, f.Name)
   568  	chart.Title = fmt.Sprintf(chart.Title, f.Name)
   569  	for _, p := range f.Patterns {
   570  		dim := &Dim{
   571  			ID:   fmt.Sprintf("custom_field_%s_%s", f.Name, p.Name),
   572  			Name: p.Name,
   573  			Algo: module.Incremental,
   574  		}
   575  		if err := chart.AddDim(dim); err != nil {
   576  			return nil, err
   577  		}
   578  	}
   579  	return chart, nil
   580  }
   581  
   582  func newCustomTimeFieldCharts(fields []customTimeField) (Charts, error) {
   583  	charts := Charts{}
   584  	for i, f := range fields {
   585  		chartTime, err := newCustomTimeFieldChart(f)
   586  		if err != nil {
   587  			return nil, err
   588  		}
   589  		chartTime.Priority += i
   590  		if err := charts.Add(chartTime); err != nil {
   591  			return nil, err
   592  		}
   593  		if len(f.Histogram) < 1 {
   594  			continue
   595  		}
   596  
   597  		chartHist, err := newCustomTimeFieldHistChart(f)
   598  		if err != nil {
   599  			return nil, err
   600  		}
   601  		chartHist.Priority += i
   602  
   603  		if err := charts.Add(chartHist); err != nil {
   604  			return nil, err
   605  		}
   606  	}
   607  	return charts, nil
   608  }
   609  
   610  func newCustomTimeFieldChart(f customTimeField) (*Chart, error) {
   611  	chart := reqByCustomTimeField.Copy()
   612  	chart.ID = fmt.Sprintf(chart.ID, f.Name)
   613  	chart.Title = fmt.Sprintf(chart.Title, f.Name)
   614  	for _, d := range chart.Dims {
   615  		d.ID = fmt.Sprintf(d.ID, f.Name)
   616  	}
   617  	return chart, nil
   618  }
   619  
   620  func newCustomTimeFieldHistChart(f customTimeField) (*Chart, error) {
   621  	chart := reqByCustomTimeFieldHist.Copy()
   622  	chart.ID = fmt.Sprintf(chart.ID, f.Name)
   623  	chart.Title = fmt.Sprintf(chart.Title, f.Name)
   624  	for i, v := range f.Histogram {
   625  		dim := &Dim{
   626  			ID:   fmt.Sprintf("custom_time_field_%s_time_hist_bucket_%d", f.Name, i+1),
   627  			Name: fmt.Sprintf("%.3f", v),
   628  			Algo: module.Incremental,
   629  		}
   630  		if err := chart.AddDim(dim); err != nil {
   631  			return nil, err
   632  		}
   633  	}
   634  	if err := chart.AddDim(&Dim{
   635  		ID:   fmt.Sprintf("custom_time_field_%s_time_hist_count", f.Name),
   636  		Name: "+Inf",
   637  		Algo: module.Incremental,
   638  	}); err != nil {
   639  		return nil, err
   640  	}
   641  	return chart, nil
   642  }
   643  
   644  func (w *WebLog) createCharts(line *logLine) error {
   645  	if line.empty() {
   646  		return errors.New("empty line")
   647  	}
   648  	w.charts = nil
   649  	// Following charts are created during runtime:
   650  	//   - reqBySSLProto, reqBySSLCipherSuite - it is likely line has no SSL stuff at this moment
   651  	charts := &Charts{
   652  		reqTotal.Copy(),
   653  		reqExcluded.Copy(),
   654  	}
   655  	if line.hasVhost() {
   656  		if err := addVhostCharts(charts); err != nil {
   657  			return err
   658  		}
   659  	}
   660  	if line.hasPort() {
   661  		if err := addPortCharts(charts); err != nil {
   662  			return err
   663  		}
   664  	}
   665  	if line.hasReqScheme() {
   666  		if err := addSchemeCharts(charts); err != nil {
   667  			return err
   668  		}
   669  	}
   670  	if line.hasReqClient() {
   671  		if err := addClientCharts(charts); err != nil {
   672  			return err
   673  		}
   674  	}
   675  	if line.hasReqMethod() {
   676  		if err := addMethodCharts(charts, w.URLPatterns); err != nil {
   677  			return err
   678  		}
   679  	}
   680  	if line.hasReqURL() {
   681  		if err := addURLCharts(charts, w.URLPatterns); err != nil {
   682  			return err
   683  		}
   684  	}
   685  	if line.hasReqProto() {
   686  		if err := addReqProtoCharts(charts); err != nil {
   687  			return err
   688  		}
   689  	}
   690  	if line.hasRespCode() {
   691  		if err := addRespCodesCharts(charts, w.GroupRespCodes); err != nil {
   692  			return err
   693  		}
   694  	}
   695  	if line.hasReqSize() || line.hasRespSize() {
   696  		if err := addBandwidthCharts(charts, w.URLPatterns); err != nil {
   697  			return err
   698  		}
   699  	}
   700  	if line.hasReqProcTime() {
   701  		if err := addReqProcTimeCharts(charts, w.Histogram, w.URLPatterns); err != nil {
   702  			return err
   703  		}
   704  	}
   705  	if line.hasUpsRespTime() {
   706  		if err := addUpstreamRespTimeCharts(charts, w.Histogram); err != nil {
   707  			return err
   708  		}
   709  	}
   710  	if line.hasCustomFields() {
   711  		if len(w.CustomFields) > 0 {
   712  			if err := addCustomFieldsCharts(charts, w.CustomFields); err != nil {
   713  				return err
   714  			}
   715  		}
   716  		if len(w.CustomTimeFields) > 0 {
   717  			if err := addCustomTimeFieldsCharts(charts, w.CustomTimeFields); err != nil {
   718  				return err
   719  			}
   720  		}
   721  		if len(w.CustomNumericFields) > 0 {
   722  			if err := addCustomNumericFieldsCharts(charts, w.CustomNumericFields); err != nil {
   723  				return err
   724  			}
   725  		}
   726  	}
   727  
   728  	w.charts = charts
   729  
   730  	return nil
   731  }
   732  
   733  func addVhostCharts(charts *Charts) error {
   734  	return charts.Add(reqByVhost.Copy())
   735  }
   736  
   737  func addPortCharts(charts *Charts) error {
   738  	return charts.Add(reqByPort.Copy())
   739  }
   740  
   741  func addSchemeCharts(charts *Charts) error {
   742  	return charts.Add(reqByScheme.Copy())
   743  }
   744  
   745  func addClientCharts(charts *Charts) error {
   746  	if err := charts.Add(reqByIPProto.Copy()); err != nil {
   747  		return err
   748  	}
   749  	return charts.Add(uniqIPsCurPoll.Copy())
   750  }
   751  
   752  func addMethodCharts(charts *Charts, patterns []userPattern) error {
   753  	if err := charts.Add(reqByMethod.Copy()); err != nil {
   754  		return err
   755  	}
   756  
   757  	for _, p := range patterns {
   758  		chart := newURLPatternReqMethodsChart(p.Name)
   759  		if err := charts.Add(chart); err != nil {
   760  			return err
   761  		}
   762  	}
   763  	return nil
   764  }
   765  
   766  func addURLCharts(charts *Charts, patterns []userPattern) error {
   767  	if len(patterns) == 0 {
   768  		return nil
   769  	}
   770  	chart, err := newURLPatternChart(patterns)
   771  	if err != nil {
   772  		return err
   773  	}
   774  	if err := charts.Add(chart); err != nil {
   775  		return err
   776  	}
   777  
   778  	for _, p := range patterns {
   779  		chart := newURLPatternRespCodesChart(p.Name)
   780  		if err := charts.Add(chart); err != nil {
   781  			return err
   782  		}
   783  	}
   784  	return nil
   785  }
   786  
   787  func addReqProtoCharts(charts *Charts) error {
   788  	return charts.Add(reqByVersion.Copy())
   789  }
   790  
   791  func addRespCodesCharts(charts *Charts, group bool) error {
   792  	if err := charts.Add(reqTypes.Copy()); err != nil {
   793  		return err
   794  	}
   795  	if err := charts.Add(respCodeClass.Copy()); err != nil {
   796  		return err
   797  	}
   798  	if !group {
   799  		return charts.Add(respCodes.Copy())
   800  	}
   801  	for _, c := range []Chart{respCodes1xx, respCodes2xx, respCodes3xx, respCodes4xx, respCodes5xx} {
   802  		if err := charts.Add(c.Copy()); err != nil {
   803  			return err
   804  		}
   805  	}
   806  	return nil
   807  }
   808  
   809  func addBandwidthCharts(charts *Charts, patterns []userPattern) error {
   810  	if err := charts.Add(bandwidth.Copy()); err != nil {
   811  		return err
   812  	}
   813  
   814  	for _, p := range patterns {
   815  		chart := newURLPatternBandwidthChart(p.Name)
   816  		if err := charts.Add(chart); err != nil {
   817  			return err
   818  		}
   819  	}
   820  	return nil
   821  }
   822  
   823  func addReqProcTimeCharts(charts *Charts, histogram []float64, patterns []userPattern) error {
   824  	if err := charts.Add(reqProcTime.Copy()); err != nil {
   825  		return err
   826  	}
   827  	for _, p := range patterns {
   828  		chart := newURLPatternReqProcTimeChart(p.Name)
   829  		if err := charts.Add(chart); err != nil {
   830  			return err
   831  		}
   832  	}
   833  	if len(histogram) == 0 {
   834  		return nil
   835  	}
   836  	chart, err := newReqProcTimeHistChart(histogram)
   837  	if err != nil {
   838  		return err
   839  	}
   840  	return charts.Add(chart)
   841  }
   842  
   843  func addUpstreamRespTimeCharts(charts *Charts, histogram []float64) error {
   844  	if err := charts.Add(upsRespTime.Copy()); err != nil {
   845  		return err
   846  	}
   847  	if len(histogram) == 0 {
   848  		return nil
   849  	}
   850  	chart, err := newUpsRespTimeHistChart(histogram)
   851  	if err != nil {
   852  		return err
   853  	}
   854  	return charts.Add(chart)
   855  }
   856  
   857  func addCustomFieldsCharts(charts *Charts, fields []customField) error {
   858  	cs, err := newCustomFieldCharts(fields)
   859  	if err != nil {
   860  		return err
   861  	}
   862  	return charts.Add(cs...)
   863  }
   864  
   865  func addCustomTimeFieldsCharts(charts *Charts, fields []customTimeField) error {
   866  	cs, err := newCustomTimeFieldCharts(fields)
   867  	if err != nil {
   868  		return err
   869  	}
   870  	return charts.Add(cs...)
   871  }
   872  
   873  func addCustomNumericFieldsCharts(charts *module.Charts, fields []customNumericField) error {
   874  	for _, f := range fields {
   875  		chart := customNumericFieldSummaryChartTmpl.Copy()
   876  		chart.ID = fmt.Sprintf(chart.ID, f.Name)
   877  		chart.Units = f.Units
   878  		chart.Ctx = fmt.Sprintf(chart.Ctx, f.Name)
   879  		for _, dim := range chart.Dims {
   880  			dim.ID = fmt.Sprintf(dim.ID, f.Name)
   881  			dim.Div = f.Divisor
   882  		}
   883  
   884  		if err := charts.Add(chart); err != nil {
   885  			return err
   886  		}
   887  	}
   888  
   889  	return nil
   890  }