1 // Copyright 2020 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 //==============================================================================
15 // ninja -C out/host trace_sample
16 // ./out/host/obj/pw_trace_tokenized/trace_sample
17 // python pw_trace_tokenized/py/trace.py -i out1.bin -o trace.json
18 // ./out/host/obj/pw_trace_tokenized/trace_sample
19 #include <stdio.h>
20
21 #include <array>
22 #include <chrono>
23
24 #include "pw_ring_buffer/prefixed_entry_ring_buffer.h"
25 #include "pw_trace/trace.h"
26
27 #ifndef SAMPLE_APP_SLEEP_MILLIS
28 #include <thread>
29 #define SAMPLE_APP_SLEEP_MILLIS(millis) \
30 std::this_thread::sleep_for(std::chrono::milliseconds(millis));
31 #endif // SAMPLE_APP_SLEEP_MILLIS
32
33 using namespace std::chrono;
34
35 namespace {
36
37 // Time helper function
38 auto start = system_clock::now();
GetTimeSinceBootMillis()39 uint32_t GetTimeSinceBootMillis() {
40 auto delta = system_clock::now() - start;
41 return floor<milliseconds>(delta).count();
42 }
43
44 // Creating a very simple runnable with predictable behaviour to help with the
45 // example. Each Runnable, has a method ShouldRun which indicates if it has work
46 // to do, calling Run will then do the work.
47 class SimpleRunnable {
48 public:
49 virtual const char* Name() const = 0;
50 virtual bool ShouldRun() = 0;
51 virtual void Run() = 0;
~SimpleRunnable()52 virtual ~SimpleRunnable() {}
53 };
54
55 // Processing module
56 // Uses trace_id and groups to track the multiple stages of "processing".
57 // These are intentionally long running so they will be processing concurrently.
58 // The trace ID is used to seperates these concurrent jobs.
59 #undef PW_TRACE_MODULE_NAME
60 #define PW_TRACE_MODULE_NAME "Processing"
61 class ProcessingTask : public SimpleRunnable {
62 public:
63 // Run task maintains a buffer of "jobs" which just sleeps for an amount of
64 // time and reposts the job until the value is zero. This gives an async
65 // behaviour where multiple of the same job are happening concurrently, and
66 // also has a nesting effect of a job having many stages.
67 struct Job {
68 uint32_t job_id;
69 uint8_t value;
70 };
71 struct JobBytes {
72 union {
73 Job job;
74 std::byte bytes[sizeof(Job)];
75 };
76 };
ProcessingTask()77 ProcessingTask() {
78 // Buffer is used for the job queue.
79 std::span<std::byte> buf_span = std::span<std::byte>(
80 reinterpret_cast<std::byte*>(jobs_buffer_), sizeof(jobs_buffer_));
81 jobs_.SetBuffer(buf_span)
82 .IgnoreError(); // TODO(pwbug/387): Handle Status properly
83 }
Name() const84 const char* Name() const override { return "Processing Task"; }
ShouldRun()85 bool ShouldRun() override { return jobs_.EntryCount() > 0; }
Run()86 void Run() override {
87 JobBytes job_bytes;
88 size_t bytes_read;
89
90 // Trace the job count backlog
91 size_t entry_count = jobs_.EntryCount();
92
93 // Get the next job from the queue.
94 jobs_.PeekFront(job_bytes.bytes, &bytes_read)
95 .IgnoreError(); // TODO(pwbug/387): Handle Status properly
96 jobs_.PopFront().IgnoreError(); // TODO(pwbug/387): Handle Status properly
97 Job& job = job_bytes.job;
98
99 // Process the job
100 ProcessingJob(job);
101 if (job.value > 0) { // repost for more work if value > 0
102 AddJobInternal(job.job_id, job.value - 1);
103 } else {
104 PW_TRACE_END("Job", "Process", job.job_id);
105 }
106 PW_TRACE_INSTANT_DATA("job_backlog_count",
107 "@pw_arg_counter",
108 &entry_count,
109 sizeof(entry_count));
110 }
AddJob(uint32_t job_id,uint8_t value)111 void AddJob(uint32_t job_id, uint8_t value) {
112 PW_TRACE_START_DATA(
113 "Job", "Process", job_id, "@pw_py_struct_fmt:B", &value, sizeof(value));
114 AddJobInternal(job_id, value);
115 }
116
117 private:
118 static constexpr size_t kMaxJobs = 10;
119 static constexpr size_t kProcessingTimePerValueMillis = 250;
120 Job jobs_buffer_[kMaxJobs];
121 pw::ring_buffer::PrefixedEntryRingBuffer jobs_{false};
122
ProcessingJob(const Job & job)123 void ProcessingJob(const Job& job) {
124 PW_TRACE_FUNCTION("Process", job.job_id);
125 for (uint8_t i = 0; i < job.value; i++) {
126 PW_TRACE_SCOPE("loop", "Process", job.job_id);
127 SAMPLE_APP_SLEEP_MILLIS(50); // Fake processing time
128 SomeProcessing(&job);
129 }
130 }
131
SomeProcessing(const Job * job)132 void SomeProcessing(const Job* job) {
133 uint32_t id = job->job_id;
134 PW_TRACE_FUNCTION("Process", id);
135 SAMPLE_APP_SLEEP_MILLIS(
136 kProcessingTimePerValueMillis); // Fake processing time
137 }
AddJobInternal(uint32_t job_id,uint8_t value)138 void AddJobInternal(uint32_t job_id, uint8_t value) {
139 JobBytes job{.job = {.job_id = job_id, .value = value}};
140 jobs_.PushBack(job.bytes)
141 .IgnoreError(); // TODO(pwbug/387): Handle Status properly
142 }
143 } processing_task;
144
145 // Input Module
146 // Uses traces in groups to indicate the different steps of reading the new
147 // event.
148 // Uses an instant data event to dump the read sample into the trace.
149 #undef PW_TRACE_MODULE_NAME
150 #define PW_TRACE_MODULE_NAME "Input"
151 class InputTask : public SimpleRunnable {
152 // Every second generate new output
153 public:
Name() const154 const char* Name() const override { return "Input Task"; }
ShouldRun()155 bool ShouldRun() override {
156 return (GetTimeSinceBootMillis() - last_run_time_ > kRunInterval);
157 }
Run()158 void Run() override {
159 last_run_time_ = GetTimeSinceBootMillis();
160 PW_TRACE_FUNCTION("Input");
161 SAMPLE_APP_SLEEP_MILLIS(50);
162 uint8_t value = GetValue();
163 PW_TRACE_INSTANT_DATA("value", "@pw_arg_counter", &value, sizeof(value));
164 processing_task.AddJob(sample_count_, value);
165 sample_count_++;
166 }
167
168 private:
GetValue()169 uint8_t GetValue() {
170 PW_TRACE_FUNCTION("Input");
171 SAMPLE_APP_SLEEP_MILLIS(100); // Fake processing time
172 return sample_count_ % 4 + 1;
173 }
174 size_t sample_count_ = 0;
175 uint32_t last_run_time_ = 0;
176 static constexpr uint32_t kRunInterval = 1000;
177 } input_task;
178
179 // Simple main loop acting as the "Kernel"
180 // Uses simple named trace durations to indicate which task/job is running
181 #undef PW_TRACE_MODULE_NAME
182 #define PW_TRACE_MODULE_NAME "Kernel"
StartFakeKernel()183 void StartFakeKernel() {
184 std::array<SimpleRunnable*, 2> tasks = {&input_task, &processing_task};
185
186 bool idle = false;
187 while (true) {
188 bool have_any_run = false;
189 for (auto& task : tasks) {
190 if (task->ShouldRun()) {
191 if (idle) {
192 PW_TRACE_END("Idle", "Idle");
193 idle = false;
194 }
195 have_any_run = true;
196 // The task name is not a string literal and is therefore put in the
197 // data section, so it can also work with tokenized trace.
198 PW_TRACE_START_DATA(
199 "Running", "@pw_arg_group", task->Name(), strlen(task->Name()));
200 task->Run();
201 PW_TRACE_END_DATA(
202 "Running", "@pw_arg_group", task->Name(), strlen(task->Name()));
203 }
204 }
205 if (!idle && !have_any_run) {
206 PW_TRACE_START("Idle", "Idle");
207 idle = true;
208 }
209 }
210 }
211
212 } // namespace
213
RunTraceSampleApp()214 void RunTraceSampleApp() { StartFakeKernel(); }
215