Skip to content

Commit

Permalink
Add support for --trace-to for python and use it in repl tests (#28179
Browse files Browse the repository at this point in the history
)

* Start adding tracing start/stop functions

* Add raii-like support for tracing

* Add raii-like support for tracing

* Fix compile logic

* Switch linux, mac, android to C++17 by default

* Start outputting trace data

* Upload traces that are gathered

* Fix names

* Allow placeholders in script args too

* Allow from-string tracing

* Do not newline-separate restyle path otherwise only the first argument is processed

* Restyle

* Add some additional types

* Minor python fixes

* Import ctypes

* Things run now

* Add trace bits to our tests

* Undo restyle-diff change

* Fix some typos in naming

* Add perfetto for darwin too

* mobile-device-test.py does not suppor trace-to yet

* Make mobile device test also be able to trace. Mobile device test seems super slow

* Restyled by autopep8

* Restyled by isort

---------

Co-authored-by: Andrei Litvin <[email protected]>
Co-authored-by: Restyled.io <[email protected]>
  • Loading branch information
3 people authored and pull[bot] committed Sep 14, 2023
1 parent 80dc946 commit 0ff49d6
Show file tree
Hide file tree
Showing 9 changed files with 331 additions and 56 deletions.
44 changes: 26 additions & 18 deletions .github/workflows/tests.yaml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@
path = third_party/perfetto/repo
url = https://github.com/google/perfetto.git
branch = master
platforms = linux,android
platforms = linux,android,darwin
[submodule "third_party/asr/components"]
path = third_party/asr/components
url = https://github.com/asriot/asriot_components.git
Expand Down
8 changes: 6 additions & 2 deletions scripts/tests/run_python_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import datetime
import logging
import os
import os.path
import queue
import re
import shlex
Expand Down Expand Up @@ -72,7 +73,7 @@ def DumpProgramOutputToQueue(thread_list: typing.List[threading.Thread], tag: st
@click.option("--factoryreset", is_flag=True,
help='Remove app config and repl configs (/tmp/chip* and /tmp/repl*) before running the tests.')
@click.option("--app-args", type=str, default='',
help='The extra arguments passed to the device.')
help='The extra arguments passed to the device. Can use placholders like {SCRIPT_BASE_NAME}')
@click.option("--script", type=click.Path(exists=True), default=os.path.join(DEFAULT_CHIP_ROOT,
'src',
'controller',
Expand All @@ -81,10 +82,13 @@ def DumpProgramOutputToQueue(thread_list: typing.List[threading.Thread], tag: st
'test_scripts',
'mobile-device-test.py'), help='Test script to use.')
@click.option("--script-args", type=str, default='',
help='Path to the test script to use, omit to use the default test script (mobile-device-test.py).')
help='Script arguments, can use placeholders like {SCRIPT_BASE_NAME}.')
@click.option("--script-gdb", is_flag=True,
help='Run script through gdb')
def main(app: str, factoryreset: bool, app_args: str, script: str, script_args: str, script_gdb: bool):
app_args = app_args.replace('{SCRIPT_BASE_NAME}', os.path.splitext(os.path.basename(script))[0])
script_args = script_args.replace('{SCRIPT_BASE_NAME}', os.path.splitext(os.path.basename(script))[0])

if factoryreset:
# Remove native app config
retcode = subprocess.call("rm -rf /tmp/chip* /tmp/repl*", shell=True)
Expand Down
11 changes: 11 additions & 0 deletions src/controller/python/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ shared_library("ChipDeviceCtrl") {
"chip/internal/CommissionerImpl.cpp",
"chip/logging/LoggingRedirect.cpp",
"chip/native/PyChipError.cpp",
"chip/tracing/TracingSetup.cpp",
"chip/utils/DeviceProxyUtils.cpp",
]
defines += [ "CHIP_CONFIG_MAX_GROUPS_PER_FABRIC=50" ]
Expand Down Expand Up @@ -120,8 +121,16 @@ shared_library("ChipDeviceCtrl") {
public_deps += [
"${chip_root}/src/controller/data_model",
"${chip_root}/src/credentials:file_attestation_trust_store",
"${chip_root}/src/tracing/json",
"${chip_root}/src/tracing/perfetto",
"${chip_root}/src/tracing/perfetto:file_output",
"${chip_root}/third_party/jsoncpp",
]

deps = [
"${chip_root}/src/tracing/perfetto:event_storage",
"${chip_root}/src/tracing/perfetto:simple_initialization",
]
} else {
public_deps += [ "$chip_data_model" ]
}
Expand Down Expand Up @@ -238,6 +247,7 @@ chip_python_wheel_action("chip-core") {
"chip/setup_payload/__init__.py",
"chip/setup_payload/setup_payload.py",
"chip/storage/__init__.py",
"chip/tracing/__init__.py",
"chip/utils/CommissioningBuildingBlocks.py",
"chip/utils/__init__.py",
"chip/yaml/__init__.py",
Expand Down Expand Up @@ -292,6 +302,7 @@ chip_python_wheel_action("chip-core") {
"chip.clusters",
"chip.setup_payload",
"chip.storage",
"chip.tracing",
]

if (!chip_controller) {
Expand Down
8 changes: 6 additions & 2 deletions src/controller/python/chip/native/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,5 +197,9 @@ def Init(bluetoothAdapter: int = None):
_GetLibraryHandle(False).pychip_CommonStackInit(ctypes.c_char_p(params))


def GetLibraryHandle():
return _GetLibraryHandle(True)
class HandleFlags(enum.Flag):
REQUIRE_INITIALIZATION = enum.auto()


def GetLibraryHandle(flags=HandleFlags.REQUIRE_INITIALIZATION):
return _GetLibraryHandle(HandleFlags.REQUIRE_INITIALIZATION in flags)
104 changes: 104 additions & 0 deletions src/controller/python/chip/tracing/TracingSetup.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
*
* Copyright (c) 2023 Project CHIP Authors
* All rights reserved.
*
* 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.
*/

#include <controller/python/chip/native/PyChipError.h>
#include <platform/PlatformManager.h>

#include <tracing/json/json_tracing.h>
#include <tracing/perfetto/event_storage.h>
#include <tracing/perfetto/file_output.h>
#include <tracing/perfetto/perfetto_tracing.h>
#include <tracing/perfetto/simple_initialize.h>
#include <tracing/registry.h>

namespace {

using chip::DeviceLayer::PlatformMgr;

class ScopedStackLock
{
public:
ScopedStackLock() { PlatformMgr().LockChipStack(); }

~ScopedStackLock() { PlatformMgr().UnlockChipStack(); }
};

chip::Tracing::Json::JsonBackend gJsonBackend;

chip::Tracing::Perfetto::FileTraceOutput gPerfettoFileOutput;
chip::Tracing::Perfetto::PerfettoBackend gPerfettoBackend;

} // namespace

extern "C" void pychip_tracing_start_json_log(const char * file_name)
{

ScopedStackLock lock;

gJsonBackend.CloseFile(); // just in case, ensure no file output
chip::Tracing::Register(gJsonBackend);
}

extern "C" PyChipError pychip_tracing_start_json_file(const char * file_name)
{
ScopedStackLock lock;

CHIP_ERROR err = gJsonBackend.OpenFile(file_name);
if (err != CHIP_NO_ERROR)
{
return ToPyChipError(err);
}
chip::Tracing::Register(gJsonBackend);
return ToPyChipError(CHIP_NO_ERROR);
}

extern "C" void pychip_tracing_start_perfetto_system()
{
ScopedStackLock lock;

chip::Tracing::Perfetto::Initialize(perfetto::kSystemBackend);
chip::Tracing::Perfetto::RegisterEventTrackingStorage();
chip::Tracing::Register(gPerfettoBackend);
}

extern "C" PyChipError pychip_tracing_start_perfetto_file(const char * file_name)
{
ScopedStackLock lock;

chip::Tracing::Perfetto::Initialize(perfetto::kInProcessBackend);
chip::Tracing::Perfetto::RegisterEventTrackingStorage();

CHIP_ERROR err = gPerfettoFileOutput.Open(file_name);
if (err != CHIP_NO_ERROR)
{
return ToPyChipError(err);
}
chip::Tracing::Register(gPerfettoBackend);

return ToPyChipError(CHIP_NO_ERROR);
}

extern "C" void pychip_tracing_stop()
{
ScopedStackLock lock;

chip::Tracing::Perfetto::FlushEventTrackingStorage();
gPerfettoFileOutput.Close();
chip::Tracing::Unregister(gPerfettoBackend);
chip::Tracing::Unregister(gJsonBackend);
}
125 changes: 125 additions & 0 deletions src/controller/python/chip/tracing/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
#
# Copyright (c) 2023 Project CHIP Authors
#
# 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.
#

import ctypes
from enum import Enum, auto
from typing import Optional

import chip.native
from chip.native import PyChipError


def _GetTracingLibraryHandle() -> ctypes.CDLL:
""" Get the native library handle with tracing methods initialized.
Retreives the CHIP native library handle and attaches signatures to
native methods.
"""

# Getting a handle without requiring init, as tracing methods
# do not require chip stack startup
handle = chip.native.GetLibraryHandle(chip.native.HandleFlags(0))

# Uses one of the type decorators as an indicator for everything being
# initialized.
if not handle.pychip_tracing_start_json_file.argtypes:
setter = chip.native.NativeLibraryHandleMethodArguments(handle)

setter.Set('pychip_tracing_start_json_log', None, [])
setter.Set('pychip_tracing_start_json_file', PyChipError, [ctypes.c_char_p])

setter.Set('pychip_tracing_start_perfetto_system', None, [])
setter.Set('pychip_tracing_start_perfetto_file', PyChipError, [ctypes.c_char_p])

setter.Set('pychip_tracing_stop', None, [])

return handle


class TraceType(Enum):
JSON = auto()
PERFETTO = auto()


def StartTracingTo(trace_type: TraceType, file_name: Optional[str] = None):
"""
Initiate tracing to the specified destination.
Note that only one active trace can exist of a given type (i.e. cannot trace both
to files and logs/system).
"""
handle = _GetTracingLibraryHandle()

if trace_type == TraceType.JSON:
if file_name is None:
handle.pychip_tracing_start_json_log()
else:
handle.pychip_tracing_start_json_file(file_name.encode('utf-8')).raise_on_error()
elif trace_type == TraceType.PERFETTO:
if file_name is None:
handle.pychip_tracing_start_perfetto_system()
else:
handle.pychip_tracing_start_perfetto_file(file_name.encode('utf-8')).raise_on_error()
else:
raise ValueError("unknown trace type")


def StopTracing():
"""
Make sure tracing is stopped.
MUST be called before application exits.
"""
_GetTracingLibraryHandle().pychip_tracing_stop()


class TracingContext:
"""Allows scoped enter/exit for tracing, like:
with TracingContext() as tracing:
tracing.Start(TraceType.JSON)
# ...
"""

def Start(self, trace_type: TraceType, file_name: Optional[str] = None):
StartTracingTo(trace_type, file_name)

def StartFromString(self, destination: str):
"""
Convert a human string to a perfetto start.
Supports json:log, json:path, perfetto, perfetto:path
"""
if destination == 'perfetto':
self.Start(TraceType.PERFETTO)
elif destination == 'json:log':
self.Start(TraceType.JSON)
elif destination.startswith("json:"):
self.Start(TraceType.JSON, destination[5:])
elif destination.startswith("perfetto:"):
self.Start(TraceType.PERFETTO, destination[9:])
else:
raise ValueError("Invalid trace-to destination: %r", destination)

def __init__(self):
pass

def __enter__(self):
return self

def __exit__(self, type, value, traceback):
StopTracing()
15 changes: 12 additions & 3 deletions src/controller/python/test/test_scripts/mobile-device-test.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import click
import coloredlogs
from base import BaseTestHelper, FailIfNot, SetTestSet, TestFail, TestTimeout, logger
from chip.tracing import TracingContext
from cluster_objects import ClusterObjectTests
from network_commissioning import NetworkCommissioningTests

Expand Down Expand Up @@ -246,8 +247,12 @@ def do_tests(controller_nodeid, device_nodeid, address, timeout, discriminator,
default='',
type=str,
help="Path that contains valid and trusted PAA Root Certificates.")
@click.option('--trace-to',
multiple=True,
default=[],
help="Trace location")
def run(controller_nodeid, device_nodeid, address, timeout, discriminator, setup_pin, enable_test, disable_test, log_level,
log_format, print_test_list, paa_trust_store_path):
log_format, print_test_list, paa_trust_store_path, trace_to):
coloredlogs.install(level=log_level, fmt=log_format, logger=logger)

if print_test_list:
Expand All @@ -267,8 +272,12 @@ def run(controller_nodeid, device_nodeid, address, timeout, discriminator, setup
logger.info(f"\tEnabled Tests: {enable_test}")
logger.info(f"\tDisabled Tests: {disable_test}")
SetTestSet(enable_test, disable_test)
do_tests(controller_nodeid, device_nodeid, address, timeout,
discriminator, setup_pin, paa_trust_store_path)
with TracingContext() as tracing_ctx:
for destination in trace_to:
tracing_ctx.StartFromString(destination)

do_tests(controller_nodeid, device_nodeid, address, timeout,
discriminator, setup_pin, paa_trust_store_path)


if __name__ == "__main__":
Expand Down
Loading

0 comments on commit 0ff49d6

Please sign in to comment.