Skip to content

Commit 7e2eafc

Browse files
add tracing framework
See simple-real-numbers.cpp for an example.
1 parent 9ff447d commit 7e2eafc

File tree

14 files changed

+1714
-188
lines changed

14 files changed

+1714
-188
lines changed

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ option(WITH_COVTEST "Turn on to enable coverage testing"
7979
option(WITH_NOISE_DEBUG "Use only when running lattice estimator; not for production" OFF )
8080
option(WITH_REDUCED_NOISE "Enable reduced noise within HKS and BFV HPSPOVERQ modes" OFF )
8181
option(USE_MACPORTS "Use MacPorts installed packages" OFF )
82+
option(ENABLE_TRACER "Set to ON to enable tracer instrumentation" OFF )
8283

8384
# Set required number of bits for native integer in build by setting NATIVE_SIZE to 64 or 128
8485
if(NOT NATIVE_SIZE)
@@ -109,6 +110,7 @@ message(STATUS "WITH_COVTEST: ${WITH_COVTEST}")
109110
message(STATUS "WITH_NOISE_DEBUG: ${WITH_NOISE_DEBUG}")
110111
message(STATUS "WITH_REDUCED_NOISE: ${WITH_REDUCED_NOISE}")
111112
message(STATUS "USE_MACPORTS: ${USE_MACPORTS}")
113+
message(STATUS "ENABLE_TRACER: ${ENABLE_TRACER}")
112114

113115
#--------------------------------------------------------------------
114116
# Compiler logic

OpenFHEConfig.cmake.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ set(OpenFHE_CKKS_M_FACTOR "@CKKS_M_FACTOR@")
4343
set(OpenFHE_NATIVEOPT "@WITH_NATIVEOPT@")
4444
set(OpenFHE_NOISEDEBUG "@WITH_NOISE_DEBUG@")
4545
set(OpenFHE_REDUCEDNOISE "@WITH_REDUCED_NOISE@")
46+
set(OpenFHE_TRACER_SUPPORT "@ENABLE_TRACER@")
4647

4748
# Math Backend
4849
set(OpenFHE_BACKEND "@MATHBACKEND@")

configure/config_core.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#cmakedefine WITH_TCM
1313
#cmakedefine WITH_OPENMP
1414
#cmakedefine WITH_NATIVEOPT
15+
#cmakedefine ENABLE_TRACER
1516

1617
#cmakedefine CKKS_M_FACTOR @CKKS_M_FACTOR@
1718
#cmakedefine HAVE_INT128 @HAVE_INT128@

docs/sphinx_rsts/intro/installation/cmake_in_openfhe.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ The table below shows the current list of options, definition for the option, an
9898
WITH_NATIVEOPT Use machine-specific optimizations (major speedup for clang) OFF
9999
NATIVE_SIZE Set default word size for native integer arithmetic to 64 or 128 bits 64
100100
CKKS_M_FACTOR Parameter used to strengthen the CKKS adversarial model in scenarios where decryption results are shared among multiple parties (See Security.md for more details) 1
101+
ENABLE_TRACER Set to ON to enable tracer instrumentation OFF
101102
================== ===================================================================================================================================================================== ==========
102103

103104
.. note:: More Options will be added as development progresses
Lines changed: 358 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,358 @@
1+
#ifndef __SIMPLETRACER_H__
2+
#define __SIMPLETRACER_H__
3+
4+
// Defines ENABLE_TRACER (via config_core.h) so needs to be outside the #ifdef ENABLE_TRACER
5+
#include "tracing.h"
6+
7+
#ifdef ENABLE_TRACER
8+
#include <fstream>
9+
#include <memory>
10+
#include <sstream>
11+
#include <string>
12+
#include <unordered_map>
13+
#include <utility>
14+
#include <vector>
15+
#include <cassert>
16+
#include <type_traits>
17+
#include <complex>
18+
#include <iomanip>
19+
20+
#include "cryptocontext-ser.h"
21+
#include "ciphertext-ser.h"
22+
#include "plaintext-ser.h"
23+
#include "key/key-ser.h"
24+
#include "scheme/ckksrns/ckksrns-ser.h"
25+
#include "scheme/bfvrns/bfvrns-ser.h"
26+
#include "scheme/bgvrns/bgvrns-ser.h"
27+
28+
#include "hashutil.h"
29+
namespace lbcrypto {
30+
31+
template <typename Element>
32+
class SimpleTracer;
33+
34+
template <typename Element>
35+
class SimpleFunctionTracer : public FunctionTracer<Element> {
36+
private:
37+
template <typename T>
38+
std::string getID(T obj, const std::string& type) {
39+
// Serialize and hash the object for uniqueness detection
40+
std::stringstream serialStream;
41+
Serial::Serialize(obj, serialStream, SerType::BINARY);
42+
const std::string hash = HashUtil::HashString(serialStream.str());
43+
44+
// Check if we already have a unique ID for this hash
45+
auto hashIt = m_tracer->m_uniqueID.find(hash);
46+
if (hashIt != m_tracer->m_uniqueID.end())
47+
// Object already seen - reuse existing ID
48+
return hashIt->second;
49+
50+
// Generate and register a new ID
51+
size_t& counter = m_tracer->m_counters[type];
52+
std::string id = type + "_" + std::to_string(++counter);
53+
m_tracer->m_uniqueID[hash] = id;
54+
return id;
55+
}
56+
57+
/// Helper to register objects to either m_inputs or m_outputs (target) for the current function
58+
template <typename T>
59+
void registerObject(T obj, const std::string& type, const std::string& name, std::string inOut) {
60+
std::string id = getID(obj, type);
61+
std::stringstream ss;
62+
ss << inOut << " ";
63+
if (!name.empty())
64+
ss << name << " = ";
65+
ss << id << " : " << type;
66+
print(ss.str());
67+
}
68+
69+
template <typename T>
70+
void registerObjects(const std::vector<T>& objects, const std::string& type, const std::string& name,
71+
std::string inOut) {
72+
std::vector<std::string> ids;
73+
for (const auto& obj : objects)
74+
ids.push_back(getID(obj, type));
75+
std::stringstream ss;
76+
ss << inOut << " ";
77+
if (!name.empty())
78+
ss << name << " = ";
79+
ss << formatVector(ids, type);
80+
print(ss.str());
81+
}
82+
83+
template <typename T>
84+
void registerValue(T value, const std::string& type, const std::string& name, std::string inOut) {
85+
std::stringstream ss;
86+
ss << inOut << " ";
87+
if (!name.empty())
88+
ss << name << " = ";
89+
ss << value << " : " << type;
90+
print(ss.str());
91+
}
92+
93+
template <typename T>
94+
void registerValues(const std::vector<T>& values, const std::string& type, const std::string& name,
95+
std::string inOut) {
96+
std::stringstream ss;
97+
ss << inOut << " ";
98+
if (!name.empty())
99+
ss << name << " = ";
100+
ss << formatVector(values, type);
101+
print(ss.str());
102+
}
103+
104+
/// Helper to format vectors with truncation
105+
template <typename T>
106+
std::string formatVector(const std::vector<T>& values, const std::string& elementTypeName) {
107+
if (values.empty())
108+
return "[] : " + elementTypeName;
109+
110+
std::stringstream ss;
111+
ss << "[" << values[0];
112+
for (size_t i = 1; i < std::min(values.size(), size_t(16)); ++i)
113+
ss << ", " << values[i];
114+
if (values.size() > 16)
115+
ss << ", ...(" << (values.size() - 16) << " more)";
116+
ss << "] : vector<" << elementTypeName << ">";
117+
return ss.str();
118+
}
119+
120+
public:
121+
SimpleFunctionTracer(const std::string& func, std::shared_ptr<std::ostream> out, SimpleTracer<Element>* tracer,
122+
size_t level)
123+
: m_func(func), m_out(std::move(out)), m_tracer(tracer), m_level(level) {
124+
print(m_func + ":");
125+
m_level += 1;
126+
}
127+
128+
~SimpleFunctionTracer() override {
129+
m_tracer->EndFunction();
130+
}
131+
132+
void registerInput(Ciphertext<Element> ciphertext, std::string name = "", bool isMutable = false) override {
133+
registerObject(ciphertext, "ciphertext", name, "input");
134+
}
135+
void registerInput(ConstCiphertext<Element> ciphertext, std::string name = "", bool isMutable = false) override {
136+
registerObject(ciphertext, "const_ciphertext", name, "input");
137+
}
138+
void registerInput(Plaintext plaintext, std::string name = "", bool isMutable = false) override {
139+
registerObject(plaintext, "plaintext", name, "input");
140+
}
141+
void registerInput(ConstPlaintext plaintext, std::string name = "", bool isMutable = false) override {
142+
registerObject(plaintext, "plaintext", name, "input");
143+
}
144+
void registerInput(const PublicKey<Element> key, std::string name = "", bool isMutable = false) override {
145+
registerObject(key, "public_key", name, "input");
146+
}
147+
void registerInput(const PrivateKey<Element> key, std::string name = "", bool isMutable = false) override {
148+
registerObject(key, "private_key", name, "input");
149+
}
150+
void registerInput(const EvalKey<Element> key, std::string name = "", bool isMutable = false) override {
151+
registerObject(key, "eval_key", name, "input");
152+
}
153+
void registerInput(const PlaintextEncodings encoding, std::string name = "", bool isMutable = false) override {
154+
std::string encodingStr;
155+
switch (encoding) {
156+
case PlaintextEncodings::COEF_PACKED_ENCODING:
157+
encodingStr = "COEF_PACKED_ENCODING";
158+
break;
159+
case PlaintextEncodings::PACKED_ENCODING:
160+
encodingStr = "PACKED_ENCODING";
161+
break;
162+
case PlaintextEncodings::STRING_ENCODING:
163+
encodingStr = "STRING_ENCODING";
164+
break;
165+
case PlaintextEncodings::CKKS_PACKED_ENCODING:
166+
encodingStr = "CKKS_PACKED_ENCODING";
167+
break;
168+
default:
169+
encodingStr = "UNKNOWN_ENCODING";
170+
break;
171+
}
172+
registerValue(encodingStr, "plaintext_encoding", name, "input");
173+
}
174+
void registerInput(const std::vector<int64_t>& values, std::string name = "", bool isMutable = false) override {
175+
registerValues(values, "int64_t", name, "input");
176+
}
177+
void registerInput(const std::vector<int32_t>& values, std::string name = "", bool isMutable = false) override {
178+
registerValues(values, "int32_t", name, "input");
179+
}
180+
void registerInput(const std::vector<uint32_t>& values, std::string name = "", bool isMutable = false) override {
181+
registerValues(values, "uint32_t", name, "input");
182+
}
183+
void registerInput(const std::vector<double>& values, std::string name = "", bool isMutable = false) override {
184+
registerValues(values, "double", name, "input");
185+
}
186+
void registerInput(double value, std::string name = "", bool isMutable = false) override {
187+
registerValue(value, "double", name, "input");
188+
}
189+
void registerInput(std::complex<double> value, std::string name = "", bool isMutable = false) override {
190+
registerValue(value, "complex<double>", name, "input");
191+
}
192+
void registerInput(int64_t value, std::string name = "", bool isMutable = false) override {
193+
registerValue(value, "int64_t", name, "input");
194+
}
195+
void registerInput(size_t value, std::string name = "", bool isMutable = false) override {
196+
registerValue(value, "size_t", name, "input");
197+
}
198+
void registerInput(bool value, std::string name = "", bool isMutable = false) override {
199+
registerValue(value, "bool", name, "input");
200+
}
201+
void registerInput(const std::string& value, std::string name = "", bool isMutable = false) override {
202+
registerValue(value, "string", name, "input");
203+
}
204+
void registerInput(const std::shared_ptr<std::map<uint32_t, EvalKey<Element>>>& evalKeyMap, std::string name = "",
205+
bool isMutable = false) override {
206+
// TODO: How to properly register evalKeyMap?
207+
registerValue("<evalKeyMap not traced>", "map<uint32_t,EvalKey>", name, "input");
208+
}
209+
210+
void registerInput(void* ptr, std::string name = "", bool isMutable = false) override {
211+
registerValue(ptr, "void*", name, "input");
212+
}
213+
void registerInput(const std::vector<std::complex<double>>& values, std::string name = "",
214+
bool isisMutable = false) override {
215+
registerValues(values, "complex<double>", name, "input");
216+
}
217+
218+
Ciphertext<Element> registerOutput(Ciphertext<Element> ciphertext, std::string name = "") override {
219+
registerObject(ciphertext, "ciphertext", name, "output");
220+
return ciphertext;
221+
}
222+
ConstCiphertext<Element> registerOutput(ConstCiphertext<Element> ciphertext, std::string name = "") override {
223+
registerObject(ciphertext, "const_ciphertext", name, "output");
224+
return ciphertext;
225+
}
226+
Plaintext registerOutput(Plaintext plaintext, std::string name = "") override {
227+
registerObject(plaintext, "plaintext", name, "output");
228+
return plaintext;
229+
}
230+
KeyPair<Element> registerOutput(KeyPair<Element> keyPair, std::string name = "") override {
231+
// For simplicity, we register the public and private keys separately
232+
if (keyPair.publicKey != nullptr) {
233+
name = name.empty() ? "" : name + "_public";
234+
registerObject(keyPair.publicKey, "public_key", name, "output");
235+
}
236+
if (keyPair.secretKey != nullptr) {
237+
name = name.empty() ? "" : name + "_private";
238+
registerObject(keyPair.secretKey, "private_key", name, "output");
239+
}
240+
return keyPair;
241+
}
242+
EvalKey<Element> registerOutput(EvalKey<Element> evalKey, std::string name = "") override {
243+
registerObject(evalKey, "eval_key", name, "output");
244+
return evalKey;
245+
}
246+
std::vector<EvalKey<Element>> registerOutput(std::vector<EvalKey<Element>> evalKeys,
247+
std::string name = "") override {
248+
registerObjects(evalKeys, "eval_key", name, "output");
249+
return evalKeys;
250+
}
251+
std::vector<Ciphertext<Element>> registerOutput(std::vector<Ciphertext<Element>> ciphertexts,
252+
std::string name = "") override {
253+
registerObjects(ciphertexts, "ciphertext", name, "output");
254+
return ciphertexts;
255+
}
256+
std::shared_ptr<std::map<uint32_t, EvalKey<Element>>> registerOutput(
257+
std::shared_ptr<std::map<uint32_t, EvalKey<Element>>> evalKeyMap, std::string name = "") override {
258+
// TODO: How to properly register evalKeyMap?
259+
registerValue("<evalKeyMap not traced>", "map<uint32_t,EvalKey>", name, "output");
260+
return evalKeyMap;
261+
}
262+
263+
// Output registration for basic types
264+
double registerOutput(double value, std::string name = "") {
265+
registerValue(value, "double", name, "output");
266+
return value;
267+
}
268+
std::complex<double> registerOutput(std::complex<double> value, std::string name = "") {
269+
registerValue(value, "complex<double>", name, "output");
270+
return value;
271+
}
272+
int64_t registerOutput(int64_t value, std::string name = "") {
273+
registerValue(value, "int64_t", name, "output");
274+
return value;
275+
}
276+
size_t registerOutput(size_t value, std::string name = "") {
277+
registerValue(value, "size_t", name, "output");
278+
return value;
279+
}
280+
std::vector<int64_t> registerOutput(const std::vector<int64_t>& values, std::string name = "") {
281+
registerValues(values, "int64_t", name, "output");
282+
return values;
283+
}
284+
PublicKey<Element> registerOutput(PublicKey<Element> publicKey, std::string name = "") override {
285+
registerObject(publicKey, "public_key", name, "output");
286+
return publicKey;
287+
}
288+
PrivateKey<Element> registerOutput(PrivateKey<Element> privateKey, std::string name = "") override {
289+
registerObject(privateKey, "private_key", name, "output");
290+
return privateKey;
291+
}
292+
std::string registerOutput(const std::string& value, std::string name = "") override {
293+
registerValue(value, "string", name, "output");
294+
return value;
295+
}
296+
Element registerOutput(Element element, std::string name = "") override {
297+
registerObject(element, "element", name, "output");
298+
return element;
299+
}
300+
301+
private:
302+
void print(const std::string& s) const {
303+
for (size_t i = 0; i < m_level; ++i)
304+
(*m_out) << '\t';
305+
(*m_out) << s << std::endl;
306+
}
307+
308+
std::string m_func;
309+
std::shared_ptr<std::ostream> m_out;
310+
SimpleTracer<Element>* m_tracer;
311+
size_t m_level;
312+
};
313+
314+
/// Basic Tracing implementation to demonstrate the tracing framework
315+
/// Whenever TraceFunction is called, it will create a SimpleFunctionTracer
316+
/// which will print the function name, inputs, and outputs to the specified output stream.
317+
template <typename Element>
318+
class SimpleTracer : public Tracer<Element> {
319+
public:
320+
explicit SimpleTracer(const std::string& filename = "openfhe-trace.txt")
321+
: m_stream(std::make_shared<std::ofstream>(filename, std::ios::out)), m_level(0) {
322+
*m_stream << "Tracer (" << filename << "):" << std::endl;
323+
}
324+
explicit SimpleTracer(std::shared_ptr<std::ostream> stream) : m_stream(std::move(stream)), m_level(0) {}
325+
~SimpleTracer() override = default;
326+
327+
void EndFunction() {
328+
if (m_level > 0)
329+
m_level -= 2;
330+
}
331+
332+
protected:
333+
virtual std::unique_ptr<FunctionTracer<Element>> createFunctionTracer(std::string function_name) override {
334+
m_level += 2;
335+
return std::make_unique<SimpleFunctionTracer<Element>>(function_name, m_stream, this, m_level);
336+
}
337+
338+
private:
339+
/// Output stream to write the trace to (e.g., a file)
340+
std::shared_ptr<std::ostream> m_stream;
341+
342+
/// Map from hash of the object to a unique ID for that object
343+
std::unordered_map<std::string, std::string> m_uniqueID;
344+
345+
/// Map from type name to current counter for ID generation
346+
std::unordered_map<std::string, size_t> m_counters;
347+
348+
/// Basic "scoping" support via indentation levels
349+
uint m_level;
350+
351+
friend class SimpleFunctionTracer<Element>;
352+
};
353+
354+
} // namespace lbcrypto
355+
356+
#endif // ENABLE_TRACER
357+
358+
#endif // __SIMPLETRACER_H__

0 commit comments

Comments
 (0)