diff options
Diffstat (limited to 'src/dctv/test_stdlib.py')
-rw-r--r-- | src/dctv/test_stdlib.py | 218 |
1 files changed, 218 insertions, 0 deletions
diff --git a/src/dctv/test_stdlib.py b/src/dctv/test_stdlib.py new file mode 100644 index 0000000..4c298b4 --- /dev/null +++ b/src/dctv/test_stdlib.py @@ -0,0 +1,218 @@ +# Copyright (C) 2020 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http:#www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Tests for stdlib.sql""" + +# pylint: disable=missing-docstring,bad-whitespace + +import logging +from cytoolz import first, valmap +import pytest +import numpy as np + +from modernmp.util import the + +from .util import ( + cached_property, + final, + override, + ureg, +) + +from .query import ( + DURATION_SCHEMA, + QueryNode, + SPAN_UNPARTITIONED_TIME_MAJOR, + STRING_SCHEMA, + TS_SCHEMA, + TableKind, + TableSchema, + TableSorting, +) + +from .test_query import ( + TestQueryTable, + TestQueryTableResult, +) + +from .test_thread_analysis import ( + TraceMaker, +) + +from .model import ( + TraceAnalysisSession, + TraceContext, +) + +from .thread_analysis import ( + UTID_SCHEMA, +) + +log = logging.getLogger(__name__) + +@final +class TestTraceContext(TraceContext): + """Trace context deriving information from a TraceMaker""" + + @override + def __init__(self, ft): + super().__init__(("test_trace_context", id(self))) + self.__ft = the(TraceMaker, ft) + + def __get_enabled_events(self): + return sorted(self.__ft.get_known_tables()) + + @cached_property + def __tables(self): + return self.__ft.make_tables() + + @override + def make_event_query_table(self, event_type): + return self.__tables[event_type] + + @override + def _get_metadata(self): + return { + "nr_cpus": self.__ft.nr_cpus, + "enabled_events": self.__get_enabled_events(), + } + + @override + def _get_known_event_types(self): + return self.__get_enabled_events() + + @override + def get_last_ts_query(self): + return QueryNode.scalar( + ureg().ns*self.__ft.markers["trace_end"]) + + @override + def get_snapshot_data(self, number): + return self.__ft.get_snapshot_data(number) + +def sq(session, sql): + """Execute a query in a session for test""" + qt = session.parse_sql_query(sql) + if qt.table_schema.kind == TableKind.SPAN: + qt = qt.to_schema(sorting=TableSorting.TIME_MAJOR) + def _fix_column(array): + return [None if masked else value + for value, masked in zip(array, np.ma.getmaskarray(array))] + result = valmap(_fix_column, + dict(session.sql_query(qt, want_schema=False))) + + return TestQueryTableResult( + {c: result[c] for c in qt.columns}, + table_schema=qt.table_schema, + column_queries=[(c, qt[c]) for c in qt.columns]) + +def sq1(session, sql): + """Like sq(), but for one column""" + ret = sq(session, sql) + assert len(ret) == 1 + return first(ret.values()) + +# Ugh. This really hacky SQL fragment goes in front of stdlib.sql and +# transforms the environment created by the TraceMaker into the one +# stdlib expects. We should really generalize this better. +SQL_ADAPTER = """ +CREATE VIEW raw_events.dctv_snapshot_end AS +SELECT EVENT 0 AS snapshot_id FROM raw_events.snapshot_end; +CREATE VIEW raw_events.dctv_snapshot_start AS +SELECT EVENT number AS snapshot_id FROM raw_events.snapshot_start; +""" + +def _mount_tm(ft): + s = TraceAnalysisSession() + s.mount_trace(["trace"], TestTraceContext(ft), sql_prefix=SQL_ADAPTER) + return ft.markers, s + +def test_basic(): + ft = (TraceMaker(nr_cpus=1) + .snapshot_start() + .note_scanned_process(tgid=1, comm="init", parent=None) + .note_scanned_process(tgid=125, parent=1) + .gap(10, "initial_snapshot_end") + .snapshot_end() + .gap(10, "tgid_101_start") + .start_process(tgid=101) + .gap(10) + .end_trace()) + m, s = _mount_tm(ft) + assert sq(s, "SELECT 123") == TestQueryTable( + names=["123"], + rows=[[123]]) + assert sq1(s, "SELECT * FROM trace.metadata.enabled_events") == [ + "sched_process_exit", + "sched_switch", + "sched_waking", + "snapshot_end", + "snapshot_start", + "task_newtask", + ] + e = m["trace_end"] + assert sq(s, "SELECT SPAN * FROM trace.good") == TestQueryTable( + names=["_ts", "_end"], + schemas=[TS_SCHEMA, DURATION_SCHEMA], + rows=[ + [m["initial_snapshot_end"], e], + ], table_schema=SPAN_UNPARTITIONED_TIME_MAJOR) + + q = "SELECT SPAN * FROM trace.states_p_tid" + wanted = TestQueryTable( + names=["_ts", "_end", "tid", "state"], + schemas=[TS_SCHEMA, DURATION_SCHEMA, UTID_SCHEMA, STRING_SCHEMA], + rows=[ + [10, e, -100000, "S"], + [10, e, -125, "S"], + [10, e, -1, "S"], + [20, e, -101, "+"], + ], table_schema=TableSchema(TableKind.SPAN, + "tid", + TableSorting.TIME_MAJOR)) + assert sq(s, q) == wanted + +@pytest.mark.skip("XXX FIXME") +def test_process_history(): + ft = (TraceMaker(nr_cpus=1) + .snapshot_start() + .note_scanned_process(tgid=1, comm="init", parent=None) + .gap(10, "initial_snapshot_end") + .snapshot_end() + .gap(10, "tgid_101_start") + .start_process(tgid=101) + .gap(10) + .switch_in(cpu=0, + next_tid=101, + prev_tid=0, + prev_state="S") + .end_trace()) + m, s = _mount_tm(ft) + e = m["trace_end"] + wanted = TestQueryTable( + names=["_ts", "_end", "tid", "state"], + schemas=[TS_SCHEMA, DURATION_SCHEMA, UTID_SCHEMA, STRING_SCHEMA], + rows=[ + [10, e, -100001, "S"], + [10, e, -100000, "S"], + [10, e, -1, "S"], + [20, e, -101, "+"], + ], table_schema=TableSchema(TableKind.SPAN, + "tid", + TableSorting.TIME_MAJOR)) + q = """ + SELECT SPAN * FROM + trace.states_p_tid + LIMIT 5 + """ + sq(s, q).equals_test_qt(wanted) |