You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
212 lines
6.8 KiB
212 lines
6.8 KiB
4 months ago
|
// Copyright 2020 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.
|
||
|
//==============================================================================
|
||
|
// ninja -C out/host trace_sample
|
||
|
// ./out/host/obj/pw_trace_tokenized/trace_sample
|
||
|
// python pw_trace_tokenized/py/trace.py -i out1.bin -o trace.json
|
||
|
// ./out/host/obj/pw_trace_tokenized/trace_sample
|
||
|
#include <stdio.h>
|
||
|
|
||
|
#include <array>
|
||
|
#include <chrono>
|
||
|
|
||
|
#include "pw_ring_buffer/prefixed_entry_ring_buffer.h"
|
||
|
#include "pw_trace/trace.h"
|
||
|
|
||
|
#ifndef SAMPLE_APP_SLEEP_MILLIS
|
||
|
#include <thread>
|
||
|
#define SAMPLE_APP_SLEEP_MILLIS(millis) \
|
||
|
std::this_thread::sleep_for(std::chrono::milliseconds(millis));
|
||
|
#endif // SAMPLE_APP_SLEEP_MILLIS
|
||
|
|
||
|
using namespace std::chrono;
|
||
|
|
||
|
namespace {
|
||
|
|
||
|
// Time helper function
|
||
|
auto start = system_clock::now();
|
||
|
uint32_t GetTimeSinceBootMillis() {
|
||
|
auto delta = system_clock::now() - start;
|
||
|
return floor<milliseconds>(delta).count();
|
||
|
}
|
||
|
|
||
|
// Creating a very simple runnable with predictable behaviour to help with the
|
||
|
// example. Each Runnable, has a method ShouldRun which indicates if it has work
|
||
|
// to do, calling Run will then do the work.
|
||
|
class SimpleRunnable {
|
||
|
public:
|
||
|
virtual const char* Name() const = 0;
|
||
|
virtual bool ShouldRun() = 0;
|
||
|
virtual void Run() = 0;
|
||
|
virtual ~SimpleRunnable() {}
|
||
|
};
|
||
|
|
||
|
// Processing module
|
||
|
// Uses trace_id and groups to track the multiple stages of "processing".
|
||
|
// These are intentionally long running so they will be processing concurrently.
|
||
|
// The trace ID is used to seperates these concurrent jobs.
|
||
|
#undef PW_TRACE_MODULE_NAME
|
||
|
#define PW_TRACE_MODULE_NAME "Processing"
|
||
|
class ProcessingTask : public SimpleRunnable {
|
||
|
public:
|
||
|
// Run task maintains a buffer of "jobs" which just sleeps for an amount of
|
||
|
// time and reposts the job until the value is zero. This gives an async
|
||
|
// behaviour where multiple of the same job are happening concurrently, and
|
||
|
// also has a nesting effect of a job having many stages.
|
||
|
struct Job {
|
||
|
uint32_t job_id;
|
||
|
uint8_t value;
|
||
|
};
|
||
|
struct JobBytes {
|
||
|
union {
|
||
|
Job job;
|
||
|
std::byte bytes[sizeof(Job)];
|
||
|
};
|
||
|
};
|
||
|
ProcessingTask() {
|
||
|
// Buffer is used for the job queue.
|
||
|
std::span<std::byte> buf_span = std::span<std::byte>(
|
||
|
reinterpret_cast<std::byte*>(jobs_buffer_), sizeof(jobs_buffer_));
|
||
|
jobs_.SetBuffer(buf_span);
|
||
|
}
|
||
|
const char* Name() const override { return "Processing Task"; }
|
||
|
bool ShouldRun() override { return jobs_.EntryCount() > 0; }
|
||
|
void Run() override {
|
||
|
JobBytes job_bytes;
|
||
|
size_t bytes_read;
|
||
|
|
||
|
// Trace the job count backlog
|
||
|
size_t entry_count = jobs_.EntryCount();
|
||
|
|
||
|
// Get the next job from the queue.
|
||
|
jobs_.PeekFront(job_bytes.bytes, &bytes_read);
|
||
|
jobs_.PopFront();
|
||
|
Job& job = job_bytes.job;
|
||
|
|
||
|
// Process the job
|
||
|
ProcessingJob(job);
|
||
|
if (job.value > 0) { // repost for more work if value > 0
|
||
|
AddJobInternal(job.job_id, job.value - 1);
|
||
|
} else {
|
||
|
PW_TRACE_END("Job", "Process", job.job_id);
|
||
|
}
|
||
|
PW_TRACE_INSTANT_DATA("job_backlog_count",
|
||
|
"@pw_arg_counter",
|
||
|
&entry_count,
|
||
|
sizeof(entry_count));
|
||
|
}
|
||
|
void AddJob(uint32_t job_id, uint8_t value) {
|
||
|
PW_TRACE_START_DATA(
|
||
|
"Job", "Process", job_id, "@pw_py_struct_fmt:B", &value, sizeof(value));
|
||
|
AddJobInternal(job_id, value);
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
static constexpr size_t kMaxJobs = 10;
|
||
|
static constexpr size_t kProcessingTimePerValueMillis = 250;
|
||
|
Job jobs_buffer_[kMaxJobs];
|
||
|
pw::ring_buffer::PrefixedEntryRingBuffer jobs_{false};
|
||
|
|
||
|
void ProcessingJob(const Job& job) {
|
||
|
PW_TRACE_FUNCTION("Process", job.job_id);
|
||
|
for (uint8_t i = 0; i < job.value; i++) {
|
||
|
PW_TRACE_SCOPE("loop", "Process", job.job_id);
|
||
|
SAMPLE_APP_SLEEP_MILLIS(50); // Fake processing time
|
||
|
SomeProcessing(&job);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void SomeProcessing(const Job* job) {
|
||
|
uint32_t id = job->job_id;
|
||
|
PW_TRACE_FUNCTION("Process", id);
|
||
|
SAMPLE_APP_SLEEP_MILLIS(
|
||
|
kProcessingTimePerValueMillis); // Fake processing time
|
||
|
}
|
||
|
void AddJobInternal(uint32_t job_id, uint8_t value) {
|
||
|
JobBytes job{.job = {.job_id = job_id, .value = value}};
|
||
|
jobs_.PushBack(job.bytes);
|
||
|
}
|
||
|
} processing_task;
|
||
|
|
||
|
// Input Module
|
||
|
// Uses traces in groups to indicate the different steps of reading the new
|
||
|
// event.
|
||
|
// Uses an instant data event to dump the read sample into the trace.
|
||
|
#undef PW_TRACE_MODULE_NAME
|
||
|
#define PW_TRACE_MODULE_NAME "Input"
|
||
|
class InputTask : public SimpleRunnable {
|
||
|
// Every second generate new output
|
||
|
public:
|
||
|
const char* Name() const override { return "Input Task"; }
|
||
|
bool ShouldRun() override {
|
||
|
return (GetTimeSinceBootMillis() - last_run_time_ > kRunInterval);
|
||
|
}
|
||
|
void Run() override {
|
||
|
last_run_time_ = GetTimeSinceBootMillis();
|
||
|
PW_TRACE_FUNCTION("Input");
|
||
|
SAMPLE_APP_SLEEP_MILLIS(50);
|
||
|
uint8_t value = GetValue();
|
||
|
PW_TRACE_INSTANT_DATA("value", "@pw_arg_counter", &value, sizeof(value));
|
||
|
processing_task.AddJob(sample_count_, value);
|
||
|
sample_count_++;
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
uint8_t GetValue() {
|
||
|
PW_TRACE_FUNCTION("Input");
|
||
|
SAMPLE_APP_SLEEP_MILLIS(100); // Fake processing time
|
||
|
return sample_count_ % 4 + 1;
|
||
|
}
|
||
|
size_t sample_count_ = 0;
|
||
|
uint32_t last_run_time_ = 0;
|
||
|
static constexpr uint32_t kRunInterval = 1000;
|
||
|
} input_task;
|
||
|
|
||
|
// Simple main loop acting as the "Kernel"
|
||
|
// Uses simple named trace durations to indicate which task/job is running
|
||
|
#undef PW_TRACE_MODULE_NAME
|
||
|
#define PW_TRACE_MODULE_NAME "Kernel"
|
||
|
void StartFakeKernel() {
|
||
|
std::array<SimpleRunnable*, 2> tasks = {&input_task, &processing_task};
|
||
|
|
||
|
bool idle = false;
|
||
|
while (true) {
|
||
|
bool have_any_run = false;
|
||
|
for (auto& task : tasks) {
|
||
|
if (task->ShouldRun()) {
|
||
|
if (idle) {
|
||
|
PW_TRACE_END("Idle", "Idle");
|
||
|
idle = false;
|
||
|
}
|
||
|
have_any_run = true;
|
||
|
// The task name is not a string literal and is therefore put in the
|
||
|
// data section, so it can also work with tokenized trace.
|
||
|
PW_TRACE_START_DATA(
|
||
|
"Running", "@pw_arg_group", task->Name(), strlen(task->Name()));
|
||
|
task->Run();
|
||
|
PW_TRACE_END_DATA(
|
||
|
"Running", "@pw_arg_group", task->Name(), strlen(task->Name()));
|
||
|
}
|
||
|
}
|
||
|
if (!idle && !have_any_run) {
|
||
|
PW_TRACE_START("Idle", "Idle");
|
||
|
idle = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
} // namespace
|
||
|
|
||
|
void RunTraceSampleApp() { StartFakeKernel(); }
|