Line data Source code
1 : #pragma once
2 :
3 : // Transport-independent NoiseRun scan logic. Only TrackerNoiseRunPlugin accesses OTS.
4 : #include <algorithm>
5 : #include <chrono>
6 : #include <cmath>
7 : #include <fstream>
8 : #include <functional>
9 : #include <future>
10 : #include <iomanip>
11 : #include <locale>
12 : #include <map>
13 : #include <regex>
14 : #include <set>
15 : #include <sstream>
16 : #include <stdexcept>
17 : #include <string>
18 : #include <vector>
19 :
20 : namespace ots
21 : {
22 : namespace tracker_noise_run
23 : {
24 0 : inline void require(bool value, const std::string& message)
25 : {
26 0 : if(!value)
27 0 : throw std::runtime_error(message);
28 0 : }
29 : struct Target
30 : {
31 : std::string uid;
32 : unsigned int supervisor;
33 : std::vector<int> links;
34 : std::vector<std::string> inputs, outputs, publisherInputs, publisherOutputs;
35 : };
36 : using Outputs = std::map<std::string, std::string>;
37 0 : inline std::vector<std::string> persistedIdentity(const std::string& path)
38 : {
39 0 : std::ifstream f(path);
40 0 : require(bool(f), "Cannot read persisted ActiveTableGroups.cfg");
41 0 : std::vector<std::string> lines;
42 0 : std::string line;
43 0 : while(std::getline(f, line))
44 : {
45 0 : if(!line.empty() && line.back() == '\r')
46 0 : line.pop_back();
47 0 : lines.push_back(line);
48 : }
49 0 : require(lines.size() == 8 && !lines[0].empty() && !lines[6].empty(),
50 : "Malformed persisted active groups");
51 0 : for(auto i : {1, 7})
52 0 : require(std::regex_match(lines[i], std::regex("[0-9]+")),
53 : "Invalid persisted group key");
54 0 : return {lines[0],
55 0 : lines[1],
56 0 : lines[6],
57 0 : lines[7]}; // Context and Configuration, not editor state.
58 0 : }
59 0 : inline unsigned long long scalar(const std::string& text)
60 : {
61 0 : std::smatch m;
62 0 : require(
63 0 : std::regex_match(
64 0 : text, m, std::regex("\\s*([0-9]+)(?:\\s+\\(0[xX]([0-9a-fA-F]+)\\))?\\s*")),
65 : "Malformed numeric macro output");
66 0 : auto n = std::stoull(m[1]);
67 0 : if(m[2].matched)
68 0 : require(n == std::stoull(m[2], nullptr, 16),
69 : "Mismatched decimal/hex macro output");
70 0 : return n;
71 0 : }
72 0 : inline std::vector<int> numbers(const std::string& text)
73 : {
74 0 : if(text.find('[') == std::string::npos)
75 : {
76 0 : auto n = scalar(text);
77 0 : require(n <= 65535, "Macro number outside range");
78 0 : return {static_cast<int>(n)};
79 : }
80 0 : require(std::regex_match(
81 0 : text, std::regex("\\s*\\[\\s*[0-9]+(?:\\s*,\\s*[0-9]+)*\\s*\\]\\s*")),
82 : "Malformed macro output list");
83 0 : std::vector<int> result;
84 0 : const std::regex digit("[0-9]+");
85 0 : for(auto i = std::sregex_iterator(text.begin(), text.end(), digit);
86 0 : i != std::sregex_iterator();
87 0 : ++i)
88 : {
89 0 : auto n = std::stoul(i->str());
90 0 : require(n <= 65535, "Macro number outside range");
91 0 : result.push_back(n);
92 0 : }
93 0 : return result;
94 0 : }
95 0 : inline void validateTargets(const std::vector<Target>& targets)
96 : {
97 0 : require(!targets.empty() && targets.size() <= 36, "Invalid NoiseRun DTC coverage");
98 0 : std::set<std::string> seen;
99 0 : for(const auto& t : targets)
100 : {
101 0 : require(std::regex_match(t.uid, std::regex("Trk(0[1-9]|1[0-8])_DTC[01]")) &&
102 0 : seen.insert(t.uid).second,
103 : "Invalid/duplicate NoiseRun target");
104 0 : const std::set<int> links(t.links.begin(), t.links.end());
105 0 : require(!links.empty() && links.size() == t.links.size() && *links.begin() >= 0 &&
106 0 : *links.rbegin() <= 5,
107 : "Invalid/duplicate ROC links");
108 0 : }
109 0 : }
110 0 : inline double totalHz(const Target& target, const Outputs& out)
111 : {
112 0 : auto links = numbers(out.at("Target ROC")), codes = numbers(out.at("Return Code"));
113 0 : require(links.size() == target.links.size() &&
114 0 : std::set<int>(links.begin(), links.end()) ==
115 0 : std::set<int>(target.links.begin(), target.links.end()),
116 0 : "Incomplete ROC coverage: " + target.uid);
117 0 : require(codes.size() == links.size() &&
118 0 : std::all_of(codes.begin(), codes.end(), [](int n) { return n == 0; }),
119 0 : "Rate/mask restoration failed: " + target.uid);
120 0 : const auto& masks = out.at("Saved hardware masks");
121 : const std::regex mask(
122 : "ROC\\s+([0-5])\\s*:\\s*CAL\\s+(0x[0-9a-fA-F]+)\\s+(0x[0-9a-fA-F]+)\\s+(0x[0-9a-"
123 0 : "fA-F]+)\\s*;\\s*HV\\s+(0x[0-9a-fA-F]+)\\s+(0x[0-9a-fA-F]+)\\s+(0x[0-9a-fA-F]+)");
124 0 : std::set<int> maskLinks;
125 0 : for(auto i = std::sregex_iterator(masks.begin(), masks.end(), mask);
126 0 : i != std::sregex_iterator();
127 0 : ++i)
128 : {
129 0 : require(maskLinks.insert(std::stoi((*i)[1])).second, "Duplicate saved mask");
130 0 : for(int k = 2; k <= 7; ++k)
131 0 : require(std::stoul((*i)[k], nullptr, 16) <= 65535, "Invalid saved mask word");
132 0 : }
133 0 : require(maskLinks == std::set<int>(links.begin(), links.end()),
134 0 : "Missing saved mask coverage: " + target.uid);
135 0 : std::istringstream input(out.at("Rates"));
136 0 : input.imbue(std::locale::classic());
137 0 : std::string line;
138 0 : int channels = 0, tables = 0;
139 0 : double sum = 0;
140 0 : const std::regex row("\\s*([0-9]+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s*");
141 0 : while(std::getline(input, line))
142 : {
143 0 : std::smatch m;
144 0 : if(!std::regex_match(line, m, row))
145 0 : continue;
146 0 : int ch = std::stoi(m[1]);
147 0 : if(ch == 0)
148 : {
149 0 : require(channels == 0 || channels == 96, "Incomplete rate table");
150 0 : channels = 0;
151 0 : ++tables;
152 : }
153 0 : require(tables > 0 && ch == channels && ch < 96, "Nonsequential rate channels");
154 0 : ++channels;
155 0 : for(int k = 2; k <= 4; ++k)
156 : {
157 0 : size_t n = 0;
158 0 : double v = std::stod(m[k], &n);
159 0 : require(n == m[k].str().size() && std::isfinite(v) && v >= 0,
160 : "Invalid rate value");
161 0 : if(k == 4)
162 0 : sum += v * 1000; // kHz Total/coincidence column -> Hz, all 96 channels.
163 : }
164 0 : }
165 0 : require(
166 0 : tables == static_cast<int>(links.size()) && channels == 96 && std::isfinite(sum),
167 : "Incomplete rate tables");
168 0 : return sum;
169 0 : }
170 : struct Summary
171 : {
172 : std::vector<double> rates;
173 : double total = 0, seconds = 0;
174 : unsigned int channels = 0, rocs = 0;
175 0 : std::string json(const std::vector<Target>& targets) const
176 : {
177 0 : std::ostringstream s;
178 0 : s.imbue(std::locale::classic());
179 0 : s << std::setprecision(17);
180 0 : s << "{\"schema_version\":1,\"total_hz\":" << total
181 0 : << ",\"channels\":" << channels << ",\"scan_seconds\":" << seconds
182 0 : << ",\"dtcs\":[";
183 0 : for(size_t i = 0; i < targets.size(); ++i)
184 0 : s << (i ? "," : "") << "{\"uid\":\"" << targets[i].uid
185 0 : << "\",\"total_hz\":" << rates.at(i)
186 0 : << ",\"channels\":" << 96 * targets[i].links.size()
187 0 : << ",\"rocs\":" << targets[i].links.size() << "}";
188 0 : return s.str() + "]}";
189 0 : }
190 : };
191 : // Always settle every submitted operation before reporting any failure. Never retry
192 : // a measurement or publish whose outcome might be uncertain.
193 0 : inline Summary scan(const std::vector<Target>& targets,
194 : const std::function<Outputs(const Target&)>& measure,
195 : const std::function<void()>& guard,
196 : const std::function<void(const Summary&)>& publish)
197 : {
198 0 : validateTargets(targets);
199 0 : guard();
200 0 : const auto start = std::chrono::steady_clock::now();
201 0 : std::vector<std::future<Outputs>> jobs;
202 0 : std::string errors;
203 : try
204 : {
205 0 : for(const auto& t : targets)
206 0 : jobs.push_back(std::async(std::launch::async, [&, t] { return measure(t); }));
207 : }
208 0 : catch(const std::exception& e)
209 : {
210 0 : errors = e.what();
211 0 : }
212 0 : Summary summary;
213 0 : for(size_t i = 0; i < jobs.size(); ++i)
214 : {
215 : try
216 : {
217 0 : double v = totalHz(targets[i], jobs[i].get());
218 0 : summary.rates.push_back(v);
219 0 : summary.total += v;
220 0 : summary.rocs += targets[i].links.size();
221 : }
222 0 : catch(const std::exception& e)
223 : {
224 0 : errors += "; " + targets[i].uid + ": " + e.what();
225 0 : }
226 0 : catch(...)
227 : {
228 0 : errors += "; " + targets[i].uid + ": unknown scan failure";
229 0 : }
230 : }
231 0 : require(errors.empty(),
232 0 : "NoiseRun scan failed; all submitted requests settled" + errors);
233 0 : require(summary.rates.size() == targets.size() && std::isfinite(summary.total),
234 : "Incomplete/invalid scan total");
235 0 : summary.channels = 96 * summary.rocs;
236 0 : summary.seconds =
237 0 : std::chrono::duration<double>(std::chrono::steady_clock::now() - start).count();
238 0 : guard();
239 0 : publish(summary);
240 0 : return summary;
241 0 : }
242 0 : inline std::chrono::steady_clock::time_point nextScan(
243 : std::chrono::steady_clock::time_point start,
244 : std::chrono::steady_clock::time_point end)
245 : {
246 0 : return std::max(start + std::chrono::seconds(3), end);
247 : }
248 : } // namespace tracker_noise_run
249 : } // namespace ots
|