mirror of https://github.com/grafana/grafana
Prometheus: Support utf8 metric/label/label value suggestions in code mode (#98253)
* utf8 metrics for prometheus devenv * introduce utf8 support * completions and suggestions * don't wrap the utf8 label in quotes * linting * support series endpointpull/98274/head
parent
862c0ce9b5
commit
4c0fa629da
@ -0,0 +1,35 @@ |
||||
import { escapeForUtf8Support, utf8Support } from './utf8_support'; |
||||
|
||||
describe('utf8 support', () => { |
||||
it('should return utf8 labels wrapped in quotes', () => { |
||||
const labels = ['valid:label', 'metric_label', 'utf8 label with space 🤘', '']; |
||||
const expected = ['valid:label', 'metric_label', `"utf8 label with space 🤘"`, '']; |
||||
const supportedLabels = labels.map(utf8Support); |
||||
expect(supportedLabels).toEqual(expected); |
||||
}); |
||||
}); |
||||
|
||||
describe('applyValueEncodingEscaping', () => { |
||||
it('should return utf8 labels wrapped in quotes', () => { |
||||
const labels = [ |
||||
'no:escaping_required', |
||||
'mysystem.prod.west.cpu.load', |
||||
'mysystem.prod.west.cpu.load_total', |
||||
'http.status:sum', |
||||
'my lovely_http.status:sum', |
||||
'花火', |
||||
'label with 😱', |
||||
]; |
||||
const expected = [ |
||||
'no:escaping_required', |
||||
'U__mysystem_2e_prod_2e_west_2e_cpu_2e_load', |
||||
'U__mysystem_2e_prod_2e_west_2e_cpu_2e_load__total', |
||||
'U__http_2e_status:sum', |
||||
'U__my_20_lovely__http_2e_status:sum', |
||||
'U___82b1__706b_', |
||||
'U__label_20_with_20__1f631_', |
||||
]; |
||||
const excapedLabels = labels.map(escapeForUtf8Support); |
||||
expect(excapedLabels).toEqual(expected); |
||||
}); |
||||
}); |
@ -0,0 +1,81 @@ |
||||
export const utf8Support = (value: string) => { |
||||
if (value === '') { |
||||
return value; |
||||
} |
||||
const isLegacyLabel = isValidLegacyName(value); |
||||
if (isLegacyLabel) { |
||||
return value; |
||||
} |
||||
return `"${value}"`; |
||||
}; |
||||
|
||||
export const escapeForUtf8Support = (value: string) => { |
||||
const isLegacyLabel = isValidLegacyName(value); |
||||
if (isLegacyLabel) { |
||||
return value; |
||||
} |
||||
|
||||
let escaped = 'U__'; |
||||
|
||||
for (let i = 0; i < value.length; i++) { |
||||
const char = value[i]; |
||||
const codePoint = value.codePointAt(i); |
||||
|
||||
if (char === '_') { |
||||
escaped += '__'; |
||||
} else if (codePoint !== undefined && isValidLegacyRune(char, i)) { |
||||
escaped += char; |
||||
} else if (codePoint === undefined || !isValidCodePoint(codePoint)) { |
||||
escaped += '_FFFD_'; |
||||
} else { |
||||
escaped += '_'; |
||||
escaped += codePoint.toString(16); // Convert code point to hexadecimal
|
||||
escaped += '_'; |
||||
} |
||||
|
||||
// Handle surrogate pairs for characters outside the Basic Multilingual Plane
|
||||
if (codePoint !== undefined && codePoint > 0xffff) { |
||||
i++; // Skip the second half of the surrogate pair
|
||||
} |
||||
} |
||||
|
||||
return escaped; |
||||
}; |
||||
|
||||
export const isValidLegacyName = (name: string): boolean => { |
||||
if (name.length === 0) { |
||||
return false; |
||||
} |
||||
|
||||
for (let i = 0; i < name.length; i++) { |
||||
const char = name[i]; |
||||
if (!isValidLegacyRune(char, i)) { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
return true; |
||||
}; |
||||
|
||||
// const labelNamePriorToUtf8Support = /^[a-zA-Z_:][a-zA-Z0-9_:]*$/;
|
||||
// instead of regex we use rune check (converted from prometheus code)
|
||||
// https://github.com/prometheus/common/blob/main/model/metric.go#L426-L428
|
||||
const isValidLegacyRune = (char: string, index: number): boolean => { |
||||
const codePoint = char.codePointAt(0); |
||||
if (codePoint === undefined) { |
||||
return false; |
||||
} |
||||
|
||||
return ( |
||||
(codePoint >= 97 && codePoint <= 122) || // 'a' to 'z'
|
||||
(codePoint >= 65 && codePoint <= 90) || // 'A' to 'Z'
|
||||
codePoint === 95 || // '_'
|
||||
codePoint === 58 || // ':'
|
||||
(codePoint >= 48 && codePoint <= 57 && index > 0) // '0' to '9', but not at the start
|
||||
); |
||||
}; |
||||
|
||||
const isValidCodePoint = (codePoint: number): boolean => { |
||||
// Validate the code point for UTF-8 compliance if needed.
|
||||
return codePoint >= 0 && codePoint <= 0x10ffff; |
||||
}; |
Loading…
Reference in new issue