{"record":{"id":"8ce87ed48edc5c64","repo":"grafana/k6","slug":"parsing-metric-name-failed","errorCode":null,"errorMessage":"parsing metric name failed","messagePattern":"parsing metric name failed","errorType":"validation","errorClass":"ErrMetricNameParsing","httpStatus":null,"severity":"error","filePath":"metrics/metric.go","lineNumber":85,"sourceCode":"\t}\n\n\tsubMetric := &Submetric{\n\t\tName:   m.Name + \"{\" + keyValues + \"}\",\n\t\tSuffix: keyValues,\n\t\tTags:   tags,\n\t\tParent: m,\n\t}\n\tsubMetricMetric := m.registry.newMetric(subMetric.Name, m.Type, m.Contains)\n\tsubMetricMetric.Sub = subMetric // sigh\n\tsubMetric.Metric = subMetricMetric\n\n\tm.Submetrics = append(m.Submetrics, subMetric)\n\n\treturn subMetric, nil\n}\n\n// ErrMetricNameParsing indicates parsing a metric name failed\nvar ErrMetricNameParsing = errors.New(\"parsing metric name failed\")\n\n// ParseMetricName parses a metric name expression of the form metric_name{tag_key:tag_value,...}\n// Its first return value is the parsed metric name, second are parsed tags as as slice\n// of \"key:value\" strings. On failure, it returns an error containing the `ErrMetricNameParsing` in its chain.\nfunc ParseMetricName(name string) (string, []string, error) {\n\topeningTokenPos := strings.IndexByte(name, '{')\n\tclosingTokenPos := strings.LastIndexByte(name, '}')\n\tcontainsOpeningToken := openingTokenPos != -1\n\tcontainsClosingToken := closingTokenPos != -1\n\n\t// Neither the opening '{' token nor the closing '}' token\n\t// are present, thus the metric name only consists of a literal.\n\tif !containsOpeningToken && !containsClosingToken {\n\t\treturn name, nil, nil\n\t}\n\n\t// If the name contains an opening or closing token, but not\n\t// its counterpart, the expression is malformed.","sourceCodeStart":67,"sourceCodeEnd":103,"githubUrl":"https://github.com/grafana/k6/blob/93accf6570dcd306ca5e99cc44c393ee3797761b/metrics/metric.go#L67-L103","documentation":"ErrMetricNameParsing (metrics/metric.go:85) is the sentinel wrapped by every failure of metrics.ParseMetricName (lines 90-146). Metric name expressions have the form name{tag:value,...}; parsing fails on: unmatched '{' or '}' (line 104), '}' appearing before '{' (line 114), the last character not being '}' (line 120), or a tag entry whose value after ':' is empty (line 138). Callers such as Thresholds.Validate and outputs hit it while resolving sub-metric expressions like 'http_req_duration{status:200}'.","triggerScenarios":"Thresholds on 'http_req_duration{status' (missing brace), 'metric}{' (wrong order), 'metric{a:b}extra' (trailing text after brace), or 'http_req_duration{status:}' (empty tag value). ParseMetricName returns the parsed name and key:value tags, or an error whose chain contains this sentinel (check with errors.Is).","commonSituations":"Hand-written thresholds on tagged sub-metrics where the tag value references a non-existent or misspelled tag, or is accidentally blank; quoted braces in templated scripts; copying threshold syntax from docs into configs that strip braces.","solutions":["Fix the expression to name{tag:value} with a closing brace in last position and non-empty tag values, e.g. 'http_req_duration{status:200}': ['p(95)<800']","Multiple tags are comma-separated inside the braces: 'metric{a:1,b:2}'","If the error surfaced from Thresholds.Validate, note it is wrapped as 'unable to validate threshold expressions; reason: ...' with exit code InvalidConfig — fix the metric name expression it names"],"exampleFix":"// before\nexport const options = {\n  thresholds: { 'http_req_duration{status:}': ['p(95)<800'] },\n};\n\n// after\nexport const options = {\n  thresholds: { 'http_req_duration{status:200}': ['p(95)<800'] },\n};","handlingStrategy":"validation","validationCode":"// Go: pre-check a sub-metric expression before handing it to metrics APIs\nfunc validMetricExpr(name string) bool {\n    o := strings.IndexByte(name, '{')\n    c := strings.LastIndexByte(name, '}')\n    if o == -1 && c == -1 { return true }\n    if o == -1 || c == -1 || c < o || c != len(name)-1 { return false }\n    for _, t := range strings.Split(name[o+1:c], \",\") {\n        _, v, _ := strings.Cut(t, \":\")\n        if v == \"\" { return false }\n    }\n    return true\n}","typeGuard":null,"tryCatchPattern":"// Go: detect this sentinel in a returned error chain\nif err != nil {\n    if errors.Is(err, metrics.ErrMetricNameParsing) {\n        // fix the metric name expression: name{tag:value}\n    }\n}","preventionTips":["Memorize the grammar: name{key:value,key2:value2} with '}' as the last character and no empty tag values","Validate threshold keys (the left side of the thresholds object) in CI so brace typos fail before a run starts"],"tags":["metrics","thresholds","sub-metrics","parsing"],"backgroundTag":null,"analyzedSha":"93accf6570dcd306ca5e99cc44c393ee3797761b","analyzedAt":"2026-08-15T21:23:27.118Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}