aboutsummaryrefslogtreecommitdiff
path: root/src/dctv/test_stdlib.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/dctv/test_stdlib.py')
-rw-r--r--src/dctv/test_stdlib.py218
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)