satisfy pylint

current v0.1.0
Síle Ekaterin Liszka 2024-01-15 17:08:01 -08:00
parent 22cf9256f8
commit 84a63253a7
Signed by: VulpineAmethyst
SSH Key Fingerprint: SHA256:VcHwQ6SUfi/p0Csfxe3SabX/TImWER0PhoJqkt+GlmE
9 changed files with 696 additions and 488 deletions

View File

@ -10,7 +10,7 @@ keywords = ['bootconfig', 'xbc', 'configuration']
dependencies = ['pyparsing']
requires-python = '>=3.7'
classifiers = [
'Development Status :: 3 - Alpha',
'Development Status :: 4 - Beta',
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Operating System :: OS Independent',
@ -43,3 +43,101 @@ addopts = [
"--import-mode=importlib",
]
pythonpath = 'src'
[tool.pylint.main]
fail-under = 10
ignore-patterns = ["^\\.#"]
jobs = 0
limit-inference-results = 100
persistent = true
py-version = "3.7"
source-roots = 'src'
suggestion-mode = true
[tool.pylint.basic]
argument-naming-style = "snake_case"
attr-naming-style = "snake_case"
bad-names = ["foo", "bar", "baz", "toto", "tutu", "tata"]
class-attribute-naming-style = "any"
class-const-naming-style = "UPPER_CASE"
class-naming-style = "PascalCase"
# XBCParser
const-naming-style = "PascalCase"
docstring-min-length = -1
function-naming-style = "snake_case"
good-names = ["i", "j", "k", "ex", "Run", "_"]
inlinevar-naming-style = "any"
method-naming-style = "snake_case"
module-naming-style = "snake_case"
no-docstring-rgx = "^_"
property-classes = ["abc.abstractproperty"]
variable-naming-style = "snake_case"
[tool.pylint.classes]
defining-attr-methods = ["__init__", "__new__", "setUp", "asyncSetUp", "__post_init__"]
exclude-protected = ["_asdict", "_fields", "_replace", "_source", "_make", "os._exit"]
valid-classmethod-first-arg = ["cls"]
valid-metaclass-classmethod-first-arg = ["mcs"]
[tool.pylint.design]
exclude-too-few-public-methods = 'XBCNode'
max-args = 5
max-attributes = 7
max-bool-expr = 5
max-branches = 12
max-locals = 15
max-parents = 7
max-public-methods = 20
max-returns = 6
max-statements = 50
min-public-methods = 2
[tool.pylint.exceptions]
overgeneral-exceptions = ["builtins.BaseException", "builtins.Exception"]
[tool.pylint.format]
ignore-long-lines = "^\\s*(# )?<?https?://\\S+>?$"
indent-after-paren = 4
indent-string = " "
max-line-length = 100
max-module-lines = 1000
[tool.pylint."messages control"]
confidence = ["HIGH", "CONTROL_FLOW", "INFERENCE", "INFERENCE_FAILURE", "UNDEFINED"]
disable = ["raw-checker-failed", "bad-inline-option", "locally-disabled", "file-ignored", "suppressed-message", "useless-suppression", "deprecated-pragma", "use-symbolic-message-instead", "use-implicit-booleaness-not-comparison-to-string", "use-implicit-booleaness-not-comparison-to-zero"]
[tool.pylint.miscellaneous]
notes = ["FIXME", "XXX", "TODO"]
[tool.pylint.refactoring]
max-nested-blocks = 5
never-returning-functions = ["sys.exit", "argparse.parse_error"]
[tool.pylint.reports]
evaluation = "max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10))"
score = true
[tool.pylint.similarities]
ignore-comments = true
ignore-docstrings = true
ignore-imports = true
ignore-signatures = true
min-similarity-lines = 4
[tool.pylint.typecheck]
contextmanager-decorators = ["contextlib.contextmanager"]
ignore-none = true
ignore-on-opaque-inference = true
ignored-checks-for-mixins = ["no-member", "not-async-context-manager", "not-context-manager", "attribute-defined-outside-init"]
ignored-classes = ["optparse.Values", "thread._local", "_thread._local", "argparse.Namespace"]
missing-member-hint = true
missing-member-hint-distance = 1
missing-member-max-choices = 1
mixin-class-rgx = ".*[Mm]ixin"
[tool.pylint.variables]
allow-global-unused-variables = true
callbacks = ["cb_", "_cb"]
dummy-variables-rgx = "_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_"
ignored-argument-names = "_.*|^ignored_|^unused_"
redefining-builtins-modules = ["six.moves", "past.builtins", "future.builtins", "builtins", "io"]

View File

@ -19,6 +19,17 @@
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
'''
This module implements support for the eXtra Boot Configuration file
format specified by the Linux kernel. For more information, please see
https://docs.kernel.org/admin-guide/bootconfig.html for more
information.
This is not a strictly-conforming implementation. In particular, this
module does not adhere to the kernel's 32,767-byte restriction and does
not enforce the maximum depth of 16 namespaces.
'''
import re
from collections.abc import Mapping, Sequence
@ -43,45 +54,67 @@ from pyparsing import (
from .utils import normalise
from .version import version as __version__
class Node:
def __init__(self, *args, type=None):
class XBCNode:
# pylint: disable=too-few-public-methods
'An XBC XBCNode.'
def __init__(self, *args, kind=None):
if isinstance(args[0], str):
self.args = args[0]
elif isinstance(args[0][0], str):
self.args = args[0][0]
else:
self.args = args[0][0][0]
self.type = type
self.type = kind
@property
def key(self):
'The key associated with the node.'
return self.args[0]
class Key(Node):
class XBCKey(XBCNode):
# pylint: disable=too-few-public-methods
'An XBC key.'
def __init__(self, *args):
# pylint: disable=super-init-not-called
self.args = args[0]
self.type = 'key'
class KeyValue(Node):
class XBCKeyValue(XBCNode):
'An XBC key/value operation.'
def __init__(self, *args):
super().__init__(args, type='kv')
super().__init__(args, kind='kv')
@property
def op(self):
'The operator being performed.'
return self.args[1]
@property
def value(self):
'The data associated with the operation.'
return self.args[2]
class Block(Node):
class XBCBlock(XBCNode):
'An XBC block.'
def __init__(self, *args):
super().__init__(args, type='block')
super().__init__(args, kind='block')
@property
def contents(self):
'The contents of the block.'
return self.args[1]
XBCParser = None
class ParseError(Exception):
'Exception for parsing errors.'
def lex(data):
# pylint: disable=too-many-locals,global-statement,unnecessary-lambda
'Run the lexer over the provided data.'
global XBCParser
if XBCParser is None:
key_fragment = Word(alphas + nums + '_-')
key = DelimitedList(key_fragment, delim='.', combine=True)
@ -94,51 +127,52 @@ assign = Literal('=')
update = Literal(':=')
append = Literal('+=')
op = assign | update | append
semi = Literal(';').suppress()
lbrace = Literal('{').suppress()
rbrace = Literal('}').suppress()
terminal = Word(';\n').suppress()
NL = Literal('\n').suppress()
WS = Word(' \t').suppress()
WS_NL = Word(' \t\n').suppress()
NL = Literal('\n').suppress() # pylint: disable=invalid-name
WS = Word(' \t').suppress() # pylint: disable=invalid-name
WS_NL = Word(' \t\n').suppress() # pylint: disable=invalid-name
comment = Literal('#') + restOfLine
values = Group(value + ZeroOrMore(Literal(',').suppress() + Optional(WS_NL) + value), aslist=True)
values = Group(
value + ZeroOrMore(
Literal(',').suppress() +
Optional(WS_NL) + value
), aslist=True
)
keyvalue = Group(key + Optional(WS) + op + Optional(WS) + values, aslist=True)
keyvalue.set_parse_action(lambda x: KeyValue(x))
keyvalue.set_parse_action(lambda x: XBCKeyValue(x))
key_stmt = key + Optional(WS) + Optional(assign).suppress() + Optional(WS)
key_stmt.set_parse_action(lambda x: Key(x))
key_stmt.set_parse_action(lambda x: XBCKey(x))
block = Forward()
statement = (keyvalue | key_stmt)
statement = keyvalue | key_stmt
statements = DelimitedList(statement, delim=terminal)
segment = Group(OneOrMore(block | statements), aslist=True)
# pylint: disable=expression-not-assigned
block << Group(key + Optional(WS) + lbrace + segment + rbrace + Optional(NL))
block.set_parse_action(lambda x: Block(x))
block.set_parse_action(lambda x: XBCBlock(x))
data = OneOrMore(segment)
XBCParser = data
XBCParser = OneOrMore(segment)
XBCParser.ignore(comment)
class ParseError(Exception):
pass
def lex(data):
tree = XBCParser.parseString(data).asList()
return tree
# pylint: disable=too-many-function-args
return XBCParser.parseString(data).asList()
def unquote(val):
'Remove quotes and trailing whitespace from values.'
if val[0] in '\'"' and val[0] == val[-1]:
return val[1:-1]
return val.strip()
def key_walk(d, key):
'Walk the key to guard against post-block key assignments.'
split = key.split('.')
for i in range(len(split) - 1, 0, -1):
@ -148,6 +182,8 @@ def key_walk(d, key):
d[x] = False
def parse_block(key, seq):
# pylint: disable=too-many-branches,too-many-statements
'Parse the AST in to a real data structure.'
if isinstance(seq, list) and len(seq) == 1 and isinstance(seq[0], list):
seq = seq[0]
@ -159,12 +195,13 @@ def parse_block(key, seq):
else:
k = item.key
if isinstance(item, Key):
if isinstance(item, XBCKey):
if k not in ret:
ret[k] = True
key_walk(ret, k)
else:
raise ParseError(f'key {k} already defined')
elif isinstance(item, KeyValue):
elif isinstance(item, XBCKeyValue):
value = item.value
op = item.op
@ -189,8 +226,8 @@ def parse_block(key, seq):
assign = value
if isinstance(assign, list):
for i in range(len(assign)):
assign[i] = unquote(assign[i])
for i, item in enumerate(assign):
assign[i] = unquote(item)
else:
assign = unquote(assign)
@ -201,7 +238,7 @@ def parse_block(key, seq):
if '.' in k:
key_walk(ret, k)
elif isinstance(item, Block):
elif isinstance(item, XBCBlock):
value = item.contents
if k not in ret:
@ -220,6 +257,7 @@ def parse_block(key, seq):
return ret
def parse(data):
'Call the lexer and then the parser.'
tree = lex(data)
d = parse_block(None, tree)
@ -227,14 +265,17 @@ def parse(data):
return d
def loads_xbc(data):
'Load XBC data provided in a string.'
return parse(data)
def load_xbc(fp):
with open(fp, mode='r') as f:
'Open a file and parse its contents.'
with open(fp, mode='r', encoding='UTF-8') as f:
return loads_xbc(f.read())
def longest_key(L):
lens = [len(x) for x in L]
def longest_key(seq):
'Find the deepest-nested key in the sequence provided.'
lens = [len(x) for x in seq]
shortest = min(lens)
if shortest < 1:
@ -245,7 +286,7 @@ def longest_key(L):
for i in range(shortest):
count = {}
for item in L:
for item in seq:
j = item[i]
if j not in count:
@ -253,40 +294,42 @@ def longest_key(L):
count[j] += 1
if len(count.keys()) == 1:
ret.append(L[0][i])
ret.append(seq[0][i])
else:
return '.'.join(ret)
return None
def longest_keys(keys):
'Find the longest keys in the sequence provided.'
keys = [k.split('.') for k in keys]
ret = set()
for i in range(len(keys)):
for j in range(1, len(keys)):
longest = longest_key([keys[i], keys[j]])
for a in keys:
for b in keys[1:]:
longest = longest_key([a, b])
if longest is not None:
ret.add(longest)
ret.discard('')
return ret
def make_block(data):
# pylint: disable=too-many-locals,too-many-branches
'Create XBC blocks.'
ret = []
leafs = []
blocks = set()
block_keys = []
for key in data.keys():
if '.' not in key:
leafs.append(key)
else:
k, rest = key.split('.', maxsplit=1)
k, _ = key.split('.', maxsplit=1)
blocks.add(k)
keys = [k for k in data.keys() if '.' in k]
temp = longest_keys(keys)
if len(temp):
if temp:
mindots = 99
for i in temp:
if 0 < i.count('.') < mindots:
@ -321,11 +364,13 @@ def make_block(data):
return ret
def saves_xbc(data):
'Export the provided dictionary to an XBC-formatted string.'
ret = make_block(data)
return '\n'.join(ret)
def save_xbc(data, filename):
with open(filename, mode='w') as f:
'Export the provided dictionary to an XBC-formatted string and save it.'
with open(filename, mode='w', encoding='UTF-8') as f:
f.write(saves_xbc(data))
__all__ = ['loads_xbc', 'load_xbc', 'saves_xbc', 'save_xbc', 'ParseError']

View File

@ -19,29 +19,37 @@
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
'''
Utility functions for saving data in XBC format.
'''
import re
from collections.abc import Sequence, Mapping
KEY_RE = re.compile(r'^[a-zA-Z0-9_-]+(?:\.(?:[a-zA-Z0-9_-]+))*$')
NVAL_RE = re.compile(r'^[^{}=+:;,\n\'"]+$')
quotes = '\'"'
other = {'"': "'", "'": '"'}
escapes = {
QUOTES = '\'"'
QUOTEMAP = {'"': "'", "'": '"'}
ESCAPES = {
'backslash': {'"': '\\x22', "'": '\\x27'},
'html': {'"': '&quot;', "'": '&apos;'},
'url': {'"': '%22', "'": '%27'}
}
def quote(data, escape='backslash'):
'Quote data according to XBC rules.'
# don't waste our time if it's a valid bare value.
if NVAL_RE.match(data) is not None:
return data
esc = None
# how shall we escape embedded quotes?
if isinstance(esc, Mapping):
if '"' in escape and "'" in escape:
esc = escape
elif escape in escapes:
esc = escapes[escape]
else:
esc = ESCAPES.get(escape, None)
if esc is None:
raise ValueError('unrecognised escape format')
@ -49,30 +57,30 @@ def quote(data, escape='backslash'):
f = data[0]
# is this a quoted string?
if f in quotes and data[-1] == f:
if f in QUOTES and data[-1] == f:
# return it if we don't need to do anything
if f not in data[1:-1]:
return data
else:
# escape embedded quotes
x = data[1:-1].replace(f, esc[f])
return f'{f}{x}{f}'
else:
# if the other quote isn't used, wrap in it
if f in quotes and other[f] not in data[1:]:
q = other[f]
return f'{q}{data}{q}'
if f in QUOTES and QUOTEMAP[f] not in data[1:]:
q = QUOTEMAP[f]
# not a quoted string, but has only one kind of quote
elif "'" in data and '"' not in data:
return f'"{data}"'
q = '"'
elif '"' in data and "'" not in data:
return f"'{data}'"
# not a quoted string and has both types; we escape one
q = "'"
else:
# not a quoted string and has both types; we escape one
data = data.replace("'", esc["'"])
return f"'{data}"
q = "'"
return f'{q}{data}{q}'
def normalise_string(string):
'Normalise values according to XBC rules.'
if not isinstance(string, str):
string = str(string)
@ -82,36 +90,41 @@ def normalise_string(string):
return string
def normalise(data):
'''Normalise values according to XBC rules.'''
if isinstance(data, str) or not isinstance(data, (Sequence, Mapping)):
return normalise_string(data)
elif isinstance(data, Sequence):
L = []
if isinstance(data, Sequence):
ret = []
for item in data:
if isinstance(item, str) or not isinstance(item, (Sequence, Mapping)):
L.append(normalise_string(item))
ret.append(normalise_string(item))
# we can unwind nested sequences
elif isinstance(item, Sequence):
L.extend(normalise(item))
ret.extend(normalise(item))
# ...but we can't do that with mappings, the format doesn't
# support it.
elif isinstance(item, Mapping):
raise ValueError('nested mapping')
else:
raise ValueError(type(value))
L = ', '.join(L)
return L
elif isinstance(data, Mapping):
# this should be impossible to reach, but nothing truly is.
raise ValueError(type(item))
ret = ', '.join(ret)
return ret
if isinstance(data, Mapping):
d = {}
for k, v in data.items():
if Key.pattern.match(k) is None:
if KEY_RE.match(k) is None:
raise KeyError(k)
else:
k = Key(k)
v = normalise(v)
d[k] = v
return d
return data

View File

@ -1 +1,2 @@
# pylint: disable=missing-module-docstring,invalid-name
version = '0.1.0'

View File

@ -1,49 +1,67 @@
'''
Tests for base-level keys.
'''
import pytest
from xbc import loads_xbc, ParseError
def test_key():
'A key by itself exists.'
assert loads_xbc('a') == {'a': True}
def test_dot_key():
assert loads_xbc('a.a') == {'a.a': True}
'A nested key by itself exists.'
assert loads_xbc('a.a') == {'a': False, 'a.a': True}
def test_key_eq():
'A key with an empty assignment exists.'
assert loads_xbc('a =') == {'a': True}
def test_keyvalue():
'A bare value can be assigned to a key.'
assert loads_xbc('a = 1') == {'a': '1'}
def test_keyvalue_space():
'A bare value can have spaces.'
assert loads_xbc('a = a b') == {'a': 'a b'}
def test_dot_keyvalue():
'A key being assigned to can have dots.'
assert loads_xbc('a.a = 1') == {'a': False, 'a.a': '1'}
def test_keys():
'Statements can be separated by semicolons.'
assert loads_xbc('a;b') == {'a': True, 'b': True}
def test_dot_keys():
assert loads_xbc('a.a;a.b') == {'a.a': True, 'a.b': True}
'Keys in compound statements can have dots.'
assert loads_xbc('a.a;a.b') == {'a': False, 'a.a': True, 'a.b': True}
def test_quoted():
'Values can be quoted with single or double quotes.'
assert loads_xbc('a = "b"') == {'a': 'b'}
def test_quoted_space():
'Quoted values can have trailing whitespace preserved.'
assert loads_xbc('a = "b "') == {'a': 'b '}
def test_array():
'Multiple values can be assigned to a single key.'
assert loads_xbc('a = 1, 2') == {'a': ['1', '2']}
def test_reassignment():
'Keys cannot be reassigned.'
with pytest.raises(ParseError):
loads_xbc('a = 1\na = 2')
def test_reassignment_colon():
'Keys cannot be reassigned, even in compound statements.'
with pytest.raises(ParseError):
loads_xbc('a = 1;a = 2')
def test_ovewrite_nonexistent():
'Keys can only be updated if they exist.'
with pytest.raises(ParseError):
loads_xbc('a := 1')

View File

@ -1,24 +1,34 @@
'''
Test output for XBC blocks.
'''
import pytest
from xbc import loads_xbc, ParseError
def test_empty():
'An empty block should cause the key to exist.'
assert loads_xbc('a {}') == {'a': True}
def test_keyvalue():
'A block should support key/value pairs.'
assert loads_xbc('a { a = 1 }') == {'a': False, 'a.a': '1'}
def test_nested_block():
'A block should support having block members.'
assert loads_xbc('a { b { c = 1 } }') == {'a.b.c': '1', 'a': False, 'a.b': False}
def test_keyvalue_and_block():
assert loads_xbc('a = 1\na { a = 1 }') == {'a': False, 'a': '1', 'a.a': '1'}
'A key/value pair can be provided for a block key prior to the block.'
assert loads_xbc('a = 1\na { a = 1 }') == {'a': '1', 'a.a': '1'}
def test_reassign_colon():
'Attempting to assign to the same key should be an error inside a block too.'
with pytest.raises(ParseError):
loads_xbc('a { a = 1; a = 2 }')
def test_assign_after_block():
'It is an error to assign to a block key after the block.'
with pytest.raises(ParseError):
loads_xbc('a { a = 1 }\na = 1')

View File

@ -1,4 +1,8 @@
'''
Tests for inputs which are invalid.
'''
import pytest
from pyparsing.exceptions import ParseException
@ -7,36 +11,44 @@ from xbc import loads_xbc, ParseError
# this should fail but does not.
@pytest.mark.xfail
def test_whitespace_vc():
'Whitespace is prohibited between values and commas.'
with pytest.raises(ParseError):
loads_xbc('x = a\n, b')
# this should fail but does not.
@pytest.mark.xfail
def test_extra_quote():
'Strings cannot contain the quote style that bookends them.'
with pytest.raises(ParseException):
loads_xbc("x = '''")
def test_lone_plus():
'Bare operators are not permitted.'
with pytest.raises(ParseException):
loads_xbc('+')
def test_lone_rbrace():
'Braces can only appear as part of a block.'
with pytest.raises(ParseException):
loads_xbc('}')
def test_lone_lbrace():
'Braces can only appear as part of a block.'
with pytest.raises(ParseException):
loads_xbc('{')
def test_lone_braces():
'Braces can only appear as part of a block.'
with pytest.raises(ParseException):
loads_xbc('{}')
def test_lone_semi():
'Semicolons are only permitted as part of a key or key/value statement.'
with pytest.raises(ParseException):
loads_xbc(';')
def test_empty():
'XBC files cannot be empty.'
with pytest.raises(ParseException):
loads_xbc('\n')
with pytest.raises(ParseException):

View File

@ -1,14 +1,15 @@
import pytest
from xbc import loads_xbc
'''
The tests in this file are samples drawn from
https://lwn.net/Articles/806002/.
'''
import pytest
from xbc import loads_xbc
def test_01():
'Key/value example.'
i = '''feature.option.foo = 1
feature.option.bar = 2'''
d = {
@ -20,6 +21,7 @@ feature.option.bar = 2'''
assert loads_xbc(i) == d
def test_02():
'Block example.'
i = '''feature.option {
foo = 1
bar = 2
@ -33,6 +35,7 @@ def test_02():
assert loads_xbc(i) == d
def test_03():
'Array example.'
i = 'feature.options = "foo", "bar"'
d = {
'feature': False,
@ -41,6 +44,7 @@ def test_03():
assert loads_xbc(i) == d
def test_10():
'Compact example.'
i = 'feature.option{foo=1;bar=2}'
d = {
'feature.option': False,
@ -51,6 +55,7 @@ def test_10():
assert loads_xbc(i) == d
def test_11():
'Example of a possible configuration.'
i = '''ftrace.event {
task.task_newtask {
filter = "pid < 128"
@ -87,16 +92,21 @@ def test_11():
'ftrace.event.synthetic': False,
'ftrace.event.synthetic.initcall_latency': False,
'ftrace.event.synthetic.initcall_latency.fields': ["unsigned long func", "u64 lat"],
'ftrace.event.synthetic.initcall_latency.actions': "hist:keys=func.sym,lat:vals=lat:sort=lat",
'ftrace.event.synthetic.initcall_latency.actions':
"hist:keys=func.sym,lat:vals=lat:sort=lat",
'ftrace.event.initcall': False,
'ftrace.event.initcall.initcall_start': False,
'ftrace.event.initcall.initcall_start.actions': "hist:keys=func:ts0=common_timestamp.usecs",
'ftrace.event.initcall.initcall_start.actions':
"hist:keys=func:ts0=common_timestamp.usecs",
'ftrace.event.initcall.initcall_finish': False,
'ftrace.event.initcall.initcall_finish.actions': "hist:keys=func:lat=common_timestamp.usecs-$ts0:onmatch(initcall.initcall_start).initcall_latency(func,$lat)"
'ftrace.event.initcall.initcall_finish.actions':
"hist:keys=func:lat=common_timestamp.usecs-$ts0:onmatch(" +
"initcall.initcall_start).initcall_latency(func,$lat)"
}
assert loads_xbc(i) == d
def test_12():
'Another example of a possible configuration.'
i = '''ftrace.event.synthetic.initcall_latency {
fields = "unsigned long func", "u64 lat"
hist {
@ -130,7 +140,8 @@ def test_12():
'ftrace.event.synthetic.initcall_latency.hist.to': False,
'ftrace.event.synthetic.initcall_latency.hist.to.event': 'initcall.initcall_finish',
'ftrace.event.synthetic.initcall_latency.hist.to.key': 'func',
'ftrace.event.synthetic.initcall_latency.hist.to.assigns': "lat=common_timestamp.usecs-$ts0",
'ftrace.event.synthetic.initcall_latency.hist.to.assigns':
"lat=common_timestamp.usecs-$ts0",
'ftrace.event.synthetic.initcall_latency.hist.to.onmatch': ['func', '$lat'],
'ftrace.event.synthetic.initcall_latency.hist.keys': ['func.sym', 'lat'],
'ftrace.event.synthetic.initcall_latency.hist.vals': 'lat',