blob: 375d1e744ec5c9ad8e232b7608348d0180266bf4 [file] [log] [blame]
// Copyright 2022 The Pigweed 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
//
// https://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 "pw_thread/thread_snapshot_service.h"
#include "gtest/gtest.h"
#include "pw_protobuf/decoder.h"
#include "pw_rpc/raw/server_reader_writer.h"
#include "pw_span/span.h"
#include "pw_thread/thread_info.h"
#include "pw_thread/thread_iteration.h"
#include "pw_thread_private/thread_snapshot_service.h"
#include "pw_thread_protos/thread.pwpb.h"
#include "pw_thread_protos/thread_snapshot_service.pwpb.h"
namespace pw::thread::freertos {
namespace {
// Iterates through each proto encoded thread in the buffer.
bool EncodedThreadExists(ConstByteSpan serialized_thread_buffer,
ConstByteSpan thread_name) {
protobuf::Decoder decoder(serialized_thread_buffer);
while (decoder.Next().ok()) {
switch (decoder.FieldNumber()) {
case static_cast<uint32_t>(SnapshotThreadInfo::Fields::THREADS): {
ConstByteSpan thread_buffer;
EXPECT_EQ(OkStatus(), decoder.ReadBytes(&thread_buffer));
ConstByteSpan encoded_name;
EXPECT_EQ(OkStatus(), DecodeThreadName(thread_buffer, encoded_name));
if (encoded_name.size() == thread_name.size()) {
if (std::equal(thread_name.begin(),
thread_name.end(),
encoded_name.begin())) {
return true;
}
}
}
}
}
return false;
}
ThreadInfo CreateThreadInfoObject(std::optional<ConstByteSpan> name,
std::optional<uintptr_t> low_addr,
std::optional<uintptr_t> high_addr,
std::optional<uintptr_t> peak_addr) {
ThreadInfo thread_info;
if (name.has_value()) {
thread_info.set_thread_name(name.value());
}
if (low_addr.has_value()) {
thread_info.set_stack_low_addr(low_addr.value());
}
if (high_addr.has_value()) {
thread_info.set_stack_high_addr(high_addr.value());
}
if (peak_addr.has_value()) {
thread_info.set_stack_peak_addr(peak_addr.value());
}
return thread_info;
}
// Test creates a custom thread info object and proto encodes. Checks that the
// custom object is encoded properly.
TEST(ThreadSnapshotService, DecodeSingleThreadInfoObject) {
std::array<std::byte, RequiredServiceBufferSize(1)> encode_buffer;
SnapshotThreadInfo::MemoryEncoder encoder(encode_buffer);
ConstByteSpan name = bytes::String("MyThread\0");
ThreadInfo thread_info = CreateThreadInfoObject(
std::make_optional(name), /* thread name */
std::make_optional(
static_cast<uintptr_t>(12345678u)) /* stack low address */,
std::make_optional(static_cast<uintptr_t>(0u)) /* stack high address */,
std::make_optional(
static_cast<uintptr_t>(987654321u)) /* stack peak address */);
EXPECT_EQ(OkStatus(), ProtoEncodeThreadInfo(encoder, thread_info));
ConstByteSpan response_span(encoder);
EXPECT_TRUE(
EncodedThreadExists(response_span, thread_info.thread_name().value()));
}
TEST(ThreadSnapshotService, DecodeMultipleThreadInfoObjects) {
std::array<std::byte, RequiredServiceBufferSize(3)> encode_buffer;
SnapshotThreadInfo::MemoryEncoder encoder(encode_buffer);
ConstByteSpan name = bytes::String("MyThread1\0");
ThreadInfo thread_info_1 =
CreateThreadInfoObject(std::make_optional(name),
std::make_optional(static_cast<uintptr_t>(123u)),
std::make_optional(static_cast<uintptr_t>(1023u)),
std::make_optional(static_cast<uintptr_t>(321u)));
name = bytes::String("MyThread2\0");
ThreadInfo thread_info_2 = CreateThreadInfoObject(
std::make_optional(name),
std::make_optional(static_cast<uintptr_t>(1000u)),
std::make_optional(static_cast<uintptr_t>(999999u)),
std::make_optional(static_cast<uintptr_t>(0u)));
name = bytes::String("MyThread3\0");
ThreadInfo thread_info_3 =
CreateThreadInfoObject(std::make_optional(name),
std::make_optional(static_cast<uintptr_t>(123u)),
std::make_optional(static_cast<uintptr_t>(1023u)),
std::make_optional(static_cast<uintptr_t>(321u)));
// Encode out of order.
EXPECT_EQ(OkStatus(), ProtoEncodeThreadInfo(encoder, thread_info_3));
EXPECT_EQ(OkStatus(), ProtoEncodeThreadInfo(encoder, thread_info_1));
EXPECT_EQ(OkStatus(), ProtoEncodeThreadInfo(encoder, thread_info_2));
ConstByteSpan response_span(encoder);
EXPECT_TRUE(
EncodedThreadExists(response_span, thread_info_1.thread_name().value()));
EXPECT_TRUE(
EncodedThreadExists(response_span, thread_info_2.thread_name().value()));
EXPECT_TRUE(
EncodedThreadExists(response_span, thread_info_3.thread_name().value()));
}
TEST(ThreadSnapshotService, DefaultBufferSize) {
static std::array<std::byte, RequiredServiceBufferSize()> encode_buffer;
SnapshotThreadInfo::MemoryEncoder encoder(encode_buffer);
ConstByteSpan name = bytes::String("MyThread\0");
std::optional<uintptr_t> example_addr =
std::make_optional(std::numeric_limits<uintptr_t>::max());
ThreadInfo thread_info = CreateThreadInfoObject(
std::make_optional(name), example_addr, example_addr, example_addr);
for (int i = 0; i < PW_THREAD_MAXIMUM_THREADS; i++) {
EXPECT_EQ(OkStatus(), ProtoEncodeThreadInfo(encoder, thread_info));
}
ConstByteSpan response_span(encoder);
EXPECT_TRUE(
EncodedThreadExists(response_span, thread_info.thread_name().value()));
}
TEST(ThreadSnapshotService, FailedPrecondition) {
static std::array<std::byte, RequiredServiceBufferSize(1)> encode_buffer;
SnapshotThreadInfo::MemoryEncoder encoder(encode_buffer);
ThreadInfo thread_info_no_name = CreateThreadInfoObject(
std::nullopt,
std::make_optional(static_cast<uintptr_t>(1111111111u)),
std::make_optional(static_cast<uintptr_t>(2222222222u)),
std::make_optional(static_cast<uintptr_t>(3333333333u)));
Status status = ProtoEncodeThreadInfo(encoder, thread_info_no_name);
EXPECT_EQ(status, Status::FailedPrecondition());
// Expected log: "Thread missing information needed by service."
ErrorLog(status);
// Same error log as above.
ConstByteSpan name = bytes::String("MyThread\0");
ThreadInfo thread_info_no_high_addr = CreateThreadInfoObject(
std::make_optional(name),
std::make_optional(static_cast<uintptr_t>(1111111111u)),
std::nullopt,
std::make_optional(static_cast<uintptr_t>(3333333333u)));
EXPECT_EQ(ProtoEncodeThreadInfo(encoder, thread_info_no_high_addr),
Status::FailedPrecondition());
}
TEST(ThreadSnapshotService, Unimplemented) {
static std::array<std::byte, RequiredServiceBufferSize(1)> encode_buffer;
SnapshotThreadInfo::MemoryEncoder encoder(encode_buffer);
ConstByteSpan name = bytes::String("MyThread\0");
ThreadInfo thread_info_no_peak_addr =
CreateThreadInfoObject(std::make_optional(name),
std::make_optional(static_cast<uintptr_t>(0u)),
std::make_optional(static_cast<uintptr_t>(0u)),
std::nullopt);
Status status = ProtoEncodeThreadInfo(encoder, thread_info_no_peak_addr);
EXPECT_EQ(status, Status::Unimplemented());
// Expected log: "Peak stack usage reporting not supported by your current OS
// or configuration."
ErrorLog(status);
}
} // namespace
} // namespace pw::thread::freertos