diff --git a/.gitmodules b/.gitmodules index 78e8b19..f2e962a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -11,3 +11,6 @@ [submodule "lightsaber_bench/LightSaber"] path = lightsaber_bench/LightSaber url = https://github.com/wzhao18/LightSaber.git +[submodule "dataset_util/protobuf"] + path = dataset_util/protobuf + url = https://github.com/protocolbuffers/protobuf diff --git a/dataset_util/README.md b/dataset_util/README.md new file mode 100644 index 0000000..d306489 --- /dev/null +++ b/dataset_util/README.md @@ -0,0 +1,56 @@ +#### Dependencies and usage: + +Install Boost Library: + +``` +git clone https://github.com/boostorg/boost.git && \ +cd boost && \ +git checkout boost-1.78.0 && \ +git submodule init && \ +git submodule update && \ +./bootstrap.sh --prefix=/usr/local && \ +./b2 install --prefix=/usr/local +``` + +Install Protobuf Library: + +``` +git clone https://github.com/protocolbuffers/protobuf.git && \ +cd protobuf && \ +git checkout v3.19.4 && \ +git submodule init && \ +git submodule update && \ +cd cmake && \ +mkdir build && \ +cd build && \ +cmake -DCMAKE_INSTALL_PREFIX=/usr/local .. && \ +make -j$(nproc) && \ +make install +``` + +Install Protobuf Java Runtime (protobuf-java-3.19.4.jar): + +``` +cd protobuf/java && \ +mvn test && \ +mvn package +``` + +Load Dataset with C++: + +Build with Cmake directly. Protobuf files will be generated by cmake function `protobuf_generate` + +Load Dataset with Java: + +``` +protoc -I /path/to/protos --java_out=. data.proto +javac -cp /path/to/protobuf-java-3.19.4.jar:. DataLoader.java +./dataset-parser | java -cp /path/to/protobuf-java-3.19.4.jar:. DataLoader +``` + +Load Dataset with C#: + +``` +protoc -I /path/to/protos --csharp_out=. data.proto +./dataset-parser | dotnet run +``` diff --git a/dataset_util/include/csv_parser.h b/dataset_util/include/csv_parser.h new file mode 100644 index 0000000..7e7c4ad --- /dev/null +++ b/dataset_util/include/csv_parser.h @@ -0,0 +1,65 @@ +#ifndef DATASET_UTIL_CSV_PARSER_H_ +#define DATASET_UTIL_CSV_PARSER_H_ + +#include + +#include + +using namespace std; + +class csv_parser : public data_parser +{ +protected: + typedef vector csv_row; + virtual void decode(csv_row&, stream::stream_event*) = 0; + + bool parse_csv_line(fstream &file, csv_row &row) + { + string line; + + if (getline(file, line)) { + string word; + stringstream ss(line); + + while (getline(ss, word, ',')) { + row.push_back(word); + } + + return true; + } + return false; + } + + void parse_csv_file(string file_name) + { + cerr << "Begin parsing " << file_name << endl; + + std::fstream csv_file(file_name); + string line; + getline(csv_file, line); + long line_cnt = 0; + + csv_row row; + while (true) { + if (!parse_csv_line(csv_file, row)) { + break; + } + + stream::stream_event data; + decode(row, &data); + if (!write_serialized_to_ostream(data)) { + break; + } + row.clear(); + line_cnt++; + } + + csv_file.close(); + cerr << "Parsing finished. Number of data points: " << line_cnt << endl; + } + +public: + csv_parser(){} +}; + +#endif // DATASET_UTIL_CSV_PARSER_H_ diff --git a/dataset_util/include/data_loader.h b/dataset_util/include/data_loader.h new file mode 100644 index 0000000..5c062fe --- /dev/null +++ b/dataset_util/include/data_loader.h @@ -0,0 +1,38 @@ +#ifndef DATASET_UTIL_DATA_LOADER_H_ +#define DATASET_UTIL_DATA_LOADER_H_ + +#include + +#include +#include +#include + +#include + +using namespace std; + +class data_loader +{ +private: + google::protobuf::io::IstreamInputStream raw_in; + google::protobuf::io::CodedInputStream coded_in; +public: + data_loader() : + raw_in(&cin), + coded_in(&raw_in) + {} + ~data_loader(){} + + bool load_data(stream::stream_event& event) { + bool clean_eof; + if (!google::protobuf::util::ParseDelimitedFromCodedStream(&event, &coded_in, &clean_eof)) { + if (!clean_eof) { + cerr << "Fail to parse data from coded input stream." << endl; + } + return false; + } + return true; + } +}; + +#endif // DATASET_UTIL_DATA_LOADER_H_ \ No newline at end of file diff --git a/dataset_util/include/data_parser.h b/dataset_util/include/data_parser.h new file mode 100644 index 0000000..4d473f0 --- /dev/null +++ b/dataset_util/include/data_parser.h @@ -0,0 +1,28 @@ +#ifndef DATASET_UTIL_DATA_PARSER_H_ +#define DATASET_UTIL_DATA_PARSER_H_ + +#include + +#include + +using namespace std; + +class data_parser +{ +protected: + virtual bool parse() = 0; + + bool write_serialized_to_ostream(stream::stream_event &t) + { + if (!google::protobuf::util::SerializeDelimitedToOstream(t, &cout)) { + cerr << "Fail to serialize data into output stream" << endl; + return false; + } + return true; + } + +public: + data_parser(){} +}; + +#endif // DATASET_UTIL_DATA_PARSER_H_ diff --git a/dataset_util/include/data_printer.h b/dataset_util/include/data_printer.h new file mode 100644 index 0000000..c2f5c6c --- /dev/null +++ b/dataset_util/include/data_printer.h @@ -0,0 +1,68 @@ +#ifndef DATASET_UTIL_DATA_PRINTER_H_ +#define DATASET_UTIL_DATA_PRINTER_H_ + +#include + +#include + +using namespace std; + +ostream& operator<< (ostream& out, stream::vibration const& vibration) +{ + out << "vibration: "; + out << "channel_1: " << vibration.channel_1() << ", "; + out << "channel_2: " << vibration.channel_2(); + return out; +} + +ostream& operator<< (ostream& out, stream::taxi_trip const& trip) +{ + out << "taxi_trip: "; + out << "medallion: " << trip.medallion() << ", "; + out << "hack_license: " << trip.hack_license() << ", "; + out << "vendor_id: " << trip.vendor_id() << ", "; + out << "rate_code: " << trip.rate_code() << ", "; + out << "store_and_fwd_flag: " << trip.store_and_fwd_flag() << ", "; + out << "passenger_count: " << trip.passenger_count() << ", "; + out << "trip_time_in_secs: " << trip.trip_time_in_secs() << ", "; + out << "trip_distance: " << trip.trip_distance() << ", "; + out << "pickup_longitude: " << trip.pickup_longitude() << ", "; + out << "pickup_latitude: " << trip.pickup_latitude() << ", "; + out << "dropoff_longitude: " << trip.dropoff_longitude() << ", "; + out << "dropoff_latitude: " << trip.dropoff_latitude(); + return out; +} + +ostream& operator<< (ostream& out, stream::taxi_fare const& fare) +{ + out << "taxi_fare: "; + out << "medallion: " << fare.medallion() << ", "; + out << "hack_license: " << fare.hack_license() << ", "; + out << "vendor_id: " << fare.vendor_id() << ", "; + out << "payment_type: " << fare.payment_type() << ", "; + out << "fare_amount: " << fare.fare_amount() << ", "; + out << "surcharge: " << fare.surcharge() << ", "; + out << "mta_tax: " << fare.mta_tax() << ", "; + out << "tip_amount: " << fare.tip_amount() << ", "; + out << "tolls_amount: " << fare.tolls_amount() << ", "; + out << "total_amount: " << fare.total_amount(); + return out; +} + +ostream& operator<< (ostream& out, stream::stream_event const& event) +{ + out << "(Partition: " << event.part_key() << ") "; + out << "Event: [" << event.st() << ", " << event.et() << "]: "; + if (event.has_taxi_trip()) { + out << event.taxi_trip(); + } else if (event.has_taxi_fare()) { + out << event.taxi_fare(); + } else if (event.has_vibration()) { + out << event.vibration(); + } else { + out << "Unknown payload"; + } + return out; +} + +#endif // DATASET_UTIL_DATA_PRINTER_H_ diff --git a/dataset_util/include/taxi_data_parser.h b/dataset_util/include/taxi_data_parser.h new file mode 100644 index 0000000..8c55fc8 --- /dev/null +++ b/dataset_util/include/taxi_data_parser.h @@ -0,0 +1,206 @@ +#ifndef DATASET_UTIL_TAXI_DATA_PARSER_H_ +#define DATASET_UTIL_TAXI_DATA_PARSER_H_ + +#include + +#include +#include + +#include + +#include + +using namespace std; +using namespace boost::filesystem; + +class taxi_data_parser : public csv_parser +{ +private: + enum TAXI_FARE_DATA_INDEX { + TAXI_FARE_MEDALLION, + TAXI_FARE_HACK_LICENSE, + TAXI_FARE_VENDOR_ID, + TAXI_FARE_PICKUP_DATETIME, + TAXI_FARE_PAYMENT_TYPE, + TAXI_FARE_FARE_AMOUNT, + TAXI_FARE_SURCHARGE, + TAXI_FARE_MTA_TAX, + TAXI_FARE_TIP_AMOUNT, + TAXI_FARE_TOLLS_AMOUNT, + TAXI_FARE_TOTAL_AMOUNT + }; + enum TAXI_TRIP_DATA_INDEX { + TAXI_TRIP_MEDALLION, + TAXI_TRIP_HACK_LICENSE, + TAXI_TRIP_VENDOR_ID, + TAXI_TRIP_RATE_CODE, + TAXI_TRIP_STORE_AND_FWD_FLAG, + TAXI_TRIP_PICKUP_DATETIME, + TAXI_TRIP_DROPOFF_DATETIME, + TAXI_TRIP_PASSENGER_COUNT, + TAXI_TRIP_TRIP_TIME_IN_SECS, + TAXI_TRIP_TRIP_DISTANCE, + TAXI_TRIP_PICKUP_LONGITUDE, + TAXI_TRIP_PICKUP_LATITUDE, + TAXI_TRIP_DROPOFF_LONGITUDE, + TAXI_TRIP_DROPOFF_LATITUDE + }; + + const vector foil_folders = { + "FOIL2010", "FOIL2011", + "FOIL2012", "FOIL2013" + }; + vector file_name_prefixes; + string &dataset_dir; + boost::posix_time::ptime start_time; + + int taxi_trip_part_key; + int taxi_fare_part_key; + int part_key; + + void decode(csv_row &row, stream::stream_event *event) override + { + if (part_key == taxi_trip_part_key) { + decode_taxi_trip(row, event); + } else if (part_key == taxi_fare_part_key) { + decode_taxi_fare(row, event); + } else { + assert(false && "Partition key is unrecgonized"); + } + } + + void decode_taxi_trip(csv_row &row, stream::stream_event *event) { + int64_t st = parse_datetime_to_seconds(row[TAXI_TRIP_PICKUP_DATETIME], start_time); + int64_t et = parse_datetime_to_seconds(row[TAXI_TRIP_DROPOFF_DATETIME], start_time); + int32_t medallion = stoi(row[TAXI_TRIP_MEDALLION]); + int32_t hack_license = stoi(row[TAXI_TRIP_HACK_LICENSE]); + string vendor_id = row[TAXI_TRIP_VENDOR_ID]; + int32_t rate_code = stoi(row[TAXI_TRIP_RATE_CODE]); + bool store_and_fwd_flag = false; + int32_t passenger_count = stoi(row[TAXI_TRIP_PASSENGER_COUNT]); + int32_t trip_time_in_secs = stoi(row[TAXI_TRIP_TRIP_TIME_IN_SECS]); + float trip_distance = stof_err_handle(row[TAXI_TRIP_TRIP_DISTANCE]); + float pickup_longitude = stof_err_handle(row[TAXI_TRIP_PICKUP_LONGITUDE]); + float pickup_latitude = stof_err_handle(row[TAXI_TRIP_PICKUP_LATITUDE]); + float dropoff_longitude = stof_err_handle(row[TAXI_TRIP_DROPOFF_LONGITUDE]); + float dropoff_latitude = stof_err_handle(row[TAXI_TRIP_DROPOFF_LATITUDE]); + + event->set_st(st); + event->set_et(et); + event->set_part_key(part_key); + event->mutable_taxi_trip()->set_medallion(medallion); + event->mutable_taxi_trip()->set_hack_license(hack_license); + event->mutable_taxi_trip()->set_vendor_id(vendor_id); + event->mutable_taxi_trip()->set_rate_code(rate_code); + event->mutable_taxi_trip()->set_store_and_fwd_flag(store_and_fwd_flag); + event->mutable_taxi_trip()->set_passenger_count(passenger_count); + event->mutable_taxi_trip()->set_trip_time_in_secs(trip_time_in_secs); + event->mutable_taxi_trip()->set_trip_distance(trip_distance); + event->mutable_taxi_trip()->set_dropoff_longitude(dropoff_longitude); + event->mutable_taxi_trip()->set_pickup_latitude(pickup_latitude); + event->mutable_taxi_trip()->set_pickup_longitude(pickup_longitude); + event->mutable_taxi_trip()->set_dropoff_latitude(dropoff_latitude); + } + + void decode_taxi_fare(csv_row &row, stream::stream_event *event) { + int64_t st = parse_datetime_to_seconds(row[TAXI_FARE_PICKUP_DATETIME], start_time); + int64_t et = st + 1; + int32_t medallion = stoi(row[TAXI_FARE_MEDALLION]); + int32_t hack_license = stoi(row[TAXI_FARE_HACK_LICENSE]); + string vendor_id = row[TAXI_FARE_VENDOR_ID]; + string payment_type = row[TAXI_FARE_PAYMENT_TYPE]; + float fare_amount = stof_err_handle(row[TAXI_FARE_FARE_AMOUNT]); + float surcharge = stof_err_handle(row[TAXI_FARE_SURCHARGE]); + float mta_tax = stof_err_handle(row[TAXI_FARE_MTA_TAX]); + float tip_amount = stof_err_handle(row[TAXI_FARE_TIP_AMOUNT]); + float tolls_amount = stof_err_handle(row[TAXI_FARE_TOLLS_AMOUNT]); + float total_amount = stof_err_handle(row[TAXI_FARE_TOTAL_AMOUNT]); + + event->set_st(st); + event->set_et(et); + event->set_part_key(part_key); + event->mutable_taxi_fare()->set_medallion(medallion); + event->mutable_taxi_fare()->set_hack_license(hack_license); + event->mutable_taxi_fare()->set_vendor_id(vendor_id); + event->mutable_taxi_fare()->set_payment_type(payment_type); + event->mutable_taxi_fare()->set_fare_amount(fare_amount); + event->mutable_taxi_fare()->set_surcharge(surcharge); + event->mutable_taxi_fare()->set_mta_tax(mta_tax); + event->mutable_taxi_fare()->set_tip_amount(tip_amount); + event->mutable_taxi_fare()->set_tolls_amount(tolls_amount); + event->mutable_taxi_fare()->set_total_amount(total_amount); + } + +public: + taxi_data_parser( + string &dataset_name, + string &dataset_dir, + int taxi_trip_part_key = 0, + int taxi_fare_part_key = 1 + ) : + dataset_dir(dataset_dir), + start_time(boost::gregorian::date(1970, 1, 1)), + taxi_trip_part_key(taxi_trip_part_key), + taxi_fare_part_key(taxi_fare_part_key) + { + assert(taxi_trip_part_key != taxi_fare_part_key && + "Partition key for trip and fare data cannot be the same"); + + if (dataset_name == "taxi_trip") { + file_name_prefixes.push_back("trip_data_"); + } else if (dataset_name == "taxi_fare") { + file_name_prefixes.push_back("trip_fare_"); + } else { + file_name_prefixes.push_back("trip_data_"); + file_name_prefixes.push_back("trip_fare_"); + } + } + ~taxi_data_parser(){} + + bool parse() override { + const path data_dir(dataset_dir); + if (!is_directory(data_dir)) { + cerr << "Directory " << dataset_dir << " does not exist." << endl; + return false; + } + + for (auto &foil_folder : foil_folders) { + path foil_dir = data_dir / foil_folder; + if (!is_directory(foil_dir)) { + cerr << "Directory " << foil_dir << " is skipped because it does not exist" << endl; + continue; + } + + size_t i = 1; + while (true) { + bool file_exists = false; + for (auto &file_name_prefix : file_name_prefixes) { + path trip_data_file = foil_dir / (file_name_prefix + std::to_string(i) + ".csv"); + if (exists(trip_data_file)) { + file_exists = true; + } else { + continue; + } + + if (file_name_prefix == "trip_data_") { + part_key = taxi_trip_part_key; + } else if (file_name_prefix == "trip_fare_") { + part_key = taxi_fare_part_key; + } else { + assert(false); + } + + this->parse_csv_file(trip_data_file.string()); + } + i++; + if (!file_exists) { + break; + } + } + } + + return true; + } +}; + +#endif // DATASET_UTIL_TAXI_DATA_PARSER_H_ \ No newline at end of file diff --git a/dataset_util/include/util.h b/dataset_util/include/util.h new file mode 100644 index 0000000..81de5ec --- /dev/null +++ b/dataset_util/include/util.h @@ -0,0 +1,21 @@ +#ifndef DATASET_UTIL_UTIL_H_ +#define DATASET_UTIL_UTIL_H_ + +#include + +using namespace std; + +float stof_err_handle(string &str) +{ + try { return stof(str); } + catch (std::exception& e) { return 0.0f; } +} + +int64_t parse_datetime_to_seconds(string &datetime, boost::posix_time::ptime &start_time) +{ + auto time = boost::posix_time::time_from_string(datetime); + auto diff = time - start_time; + return diff.total_seconds(); +} + +#endif // DATASET_UTIL_UTIL_H_ \ No newline at end of file diff --git a/dataset_util/include/vibration_data_parser.h b/dataset_util/include/vibration_data_parser.h new file mode 100644 index 0000000..ba31ea3 --- /dev/null +++ b/dataset_util/include/vibration_data_parser.h @@ -0,0 +1,94 @@ +#ifndef DATASET_UTIL_VIBRATION_DATA_PARSER_H_ +#define DATASET_UTIL_VIBRATION_DATA_PARSER_H_ + +#include + +#include +#include + +#include + +#include + +using namespace std; +using namespace boost::filesystem; + +class vibration_data_parser : public csv_parser +{ +private: + enum VIBRATION_DATA_INDEX { + TIMESTAMP, + CHANNEL_1, + CHANNEL_2 + }; + + string &dataset_dir; + const map folder_prefix_map = { + {"1 Data collected from a healthy bearing", 'H'}, + {"2 Data collected from a bearing with inner race fault", 'I'}, + {"3 Data collected from a bearing with outer race fault", 'O'}, + {"4 Data collected from a bearing with ball fault", 'B'}, + {"5 Data collected from a bearing with a combination of faults", 'C'} + }; + const vector file_suffices = { + "-A-1.csv", "-A-2.csv", "-A-3.csv", + "-B-1.csv", "-B-2.csv", "-B-3.csv", + "-C-1.csv", "-C-2.csv", "-C-3.csv", + "-D-1.csv", "-D-2.csv", "-D-3.csv" + }; + + int part_key; + +public: + vibration_data_parser(string &dataset_dir, int part_key = 0) : + dataset_dir(dataset_dir), + part_key(part_key) + {} + ~vibration_data_parser(){} + + void decode(csv_row &row, stream::stream_event *event) override { + int64_t st = stoi(row[TIMESTAMP]); + int64_t et = st + 1; + float channel_1 = stof_err_handle(row[CHANNEL_1]); + float channel_2 = stof_err_handle(row[CHANNEL_2]); + + event->set_st(st); + event->set_et(et); + event->set_part_key(part_key); + event->mutable_vibration()->set_channel_1(channel_1); + event->mutable_vibration()->set_channel_2(channel_2); + } + + bool parse() override { + const path data_dir(dataset_dir); + if (!is_directory(data_dir)) { + cerr << "Directory " << dataset_dir << " does not exist." << endl; + return false; + } + + for (auto &pair : folder_prefix_map) { + auto folder_name = pair.first; + auto file_prefix = pair.second; + + path folder_dir = data_dir / folder_name; + if (!is_directory(folder_dir)) { + cerr << "Directory " << folder_dir << " is skipped because it does not exist" << endl; + continue; + } + + for (auto &file_suffix : file_suffices) { + path data_file = folder_dir / (file_prefix + file_suffix); + if (!exists(data_file)) { + cerr << "File " << data_file << " is skipped because it does not exist" << endl; + continue; + } + + this->parse_csv_file(data_file.string()); + } + } + + return true; + } +}; + +#endif // DATASET_UTIL_VIBRATION_DATA_PARSER_H_ \ No newline at end of file diff --git a/dataset_util/parser/CMakeLists.txt b/dataset_util/parser/CMakeLists.txt new file mode 100644 index 0000000..d1aa922 --- /dev/null +++ b/dataset_util/parser/CMakeLists.txt @@ -0,0 +1,33 @@ +cmake_minimum_required(VERSION 3.16.3) +set(CMAKE_C_COMPILER clang) +set(CMAKE_CXX_COMPILER clang++) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3") + +project(dataset_parser) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +get_filename_component(DATASET_UTIL_DIR ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) +set(Protobuf_IMPORT_DIRS ${DATASET_UTIL_DIR}/protos) + +find_package(Boost 1.78.0 REQUIRED COMPONENTS date_time filesystem) +find_package(protobuf CONFIG REQUIRED) + +if(protobuf_VERBOSE) + message(STATUS "Using Protocol Buffers ${protobuf_VERSION}") +endif() + +include_directories(${DATASET_UTIL_DIR}/include ${PROTOBUF_INCLUDE_DIRS} ${Boost_INCLUDE_DIR}) +set(CMAKE_INCLUDE_CURRENT_DIR TRUE) + +add_executable(parser parser.cpp) +target_link_libraries(parser protobuf::libprotobuf ${Boost_LIBRARIES}) + +file(GLOB PROTO_FILES + "${Protobuf_IMPORT_DIRS}/*.proto" +) + +set(_protobuf_include_path -I ${Protobuf_IMPORT_DIRS}) +protobuf_generate(TARGET parser LANGUAGE cpp PROTOS ${PROTO_FILES}) \ No newline at end of file diff --git a/dataset_util/parser/parser.cpp b/dataset_util/parser/parser.cpp new file mode 100644 index 0000000..0c9cc1b --- /dev/null +++ b/dataset_util/parser/parser.cpp @@ -0,0 +1,31 @@ +#include + +#include +#include + +using namespace std; + +int main(int argc, char* argv[]) { + GOOGLE_PROTOBUF_VERIFY_VERSION; + + if (argc != 3) { + cerr << "Usage: " << endl; + return 1; + } + + string dataset_name = argv[1]; + string dataset_dir = argv[2]; + + if (dataset_name.find("taxi") != string::npos) { + taxi_data_parser parser(dataset_name, dataset_dir); + parser.parse(); + } else if (dataset_name == "vibration") { + vibration_data_parser parser(dataset_dir); + parser.parse(); + } else { + throw runtime_error("Unknown dataset name."); + } + + google::protobuf::ShutdownProtobufLibrary(); + return 0; +} diff --git a/dataset_util/protobuf b/dataset_util/protobuf new file mode 160000 index 0000000..22d0e26 --- /dev/null +++ b/dataset_util/protobuf @@ -0,0 +1 @@ +Subproject commit 22d0e265de7d2b3d2e9a00d071313502e7d4cccf diff --git a/dataset_util/protos/stream_event.proto b/dataset_util/protos/stream_event.proto new file mode 100644 index 0000000..e3273d8 --- /dev/null +++ b/dataset_util/protos/stream_event.proto @@ -0,0 +1,21 @@ + +syntax = "proto3"; +package stream; + +import "taxi.proto"; +import "vibration.proto"; + +option java_multiple_files = true; +option java_package = "com.stream.generic.protos"; +option java_outer_classname = "StreamEventProtos"; + +message stream_event { + int64 st = 1; + int64 et = 2; + int32 part_key = 3; + oneof payload { + taxi_trip taxi_trip = 4; + taxi_fare taxi_fare = 5; + vibration vibration = 6; + } +} \ No newline at end of file diff --git a/dataset_util/protos/taxi.proto b/dataset_util/protos/taxi.proto new file mode 100644 index 0000000..356c8e7 --- /dev/null +++ b/dataset_util/protos/taxi.proto @@ -0,0 +1,34 @@ +syntax = "proto3"; +package stream; + +option java_multiple_files = true; +option java_package = "com.stream.taxi.protos"; +option java_outer_classname = "TaxiProtos"; + +message taxi_trip { + int32 medallion = 1; + int32 hack_license = 2; + string vendor_id = 3; + int32 rate_code = 4; + bool store_and_fwd_flag = 5; + int32 passenger_count = 6; + int32 trip_time_in_secs = 7; + float trip_distance = 8; + float pickup_longitude = 9; + float pickup_latitude = 10; + float dropoff_longitude = 11; + float dropoff_latitude = 12; +} + +message taxi_fare { + int32 medallion = 1; + int32 hack_license = 2; + string vendor_id = 3; + string payment_type = 4; + float fare_amount = 5; + float surcharge = 6; + float mta_tax = 7; + float tip_amount = 8; + float tolls_amount = 9; + float total_amount = 10; +} \ No newline at end of file diff --git a/dataset_util/protos/vibration.proto b/dataset_util/protos/vibration.proto new file mode 100644 index 0000000..74bcfa4 --- /dev/null +++ b/dataset_util/protos/vibration.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; +package stream; + +option java_multiple_files = true; +option java_package = "com.stream.vibration.protos"; +option java_outer_classname = "VibrationProtos"; + +message vibration { + float channel_1 = 1; + float channel_2 = 2; +} \ No newline at end of file diff --git a/tilt_bench/CMakeLists.txt b/tilt_bench/CMakeLists.txt index 644ec43..10dd022 100644 --- a/tilt_bench/CMakeLists.txt +++ b/tilt_bench/CMakeLists.txt @@ -9,13 +9,21 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) find_package(LLVM 11.0.0 REQUIRED CONFIG) +find_package(protobuf CONFIG REQUIRED) message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}") message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}") +if(protobuf_VERBOSE) + message(STATUS "Using Protocol Buffers ${protobuf_VERSION}") +endif() + +get_filename_component(PROJECT_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) +set(Protobuf_IMPORT_DIRS ${PROJECT_ROOT_DIR}/dataset_util/protos) add_subdirectory(tilt/third_party/easy_jit) -include_directories(${LLVM_INCLUDE_DIRS} include tilt/tilt/include) +include_directories(${LLVM_INCLUDE_DIRS} ${PROTOBUF_INCLUDE_DIRS} include tilt/tilt/include ${PROJECT_ROOT_DIR}/dataset_util/include) +set(CMAKE_INCLUDE_CURRENT_DIR TRUE) add_definitions(${LLVM_DEFINITIONS}) llvm_map_components_to_libnames(llvm_libs native orcjit mcjit objcarcopts) set(easyjit_lib "${CMAKE_BINARY_DIR}/tilt/third_party/easy_jit/bin/EasyJitPass.so") @@ -23,4 +31,12 @@ set(easyjit_lib "${CMAKE_BINARY_DIR}/tilt/third_party/easy_jit/bin/EasyJitPass.s add_subdirectory(tilt/tilt) add_executable(main main.cpp) -target_link_libraries(main tilt) + +target_link_libraries(main protobuf::libprotobuf tilt) + +file(GLOB PROTO_FILES + "${Protobuf_IMPORT_DIRS}/*.proto" +) + +set(_protobuf_include_path -I ${Protobuf_IMPORT_DIRS}) +protobuf_generate(TARGET main LANGUAGE cpp PROTOS ${PROTO_FILES}) \ No newline at end of file diff --git a/trill_bench/bench/BenchUtil.cs b/trill_bench/bench/BenchUtil.cs new file mode 100644 index 0000000..b9d3cba --- /dev/null +++ b/trill_bench/bench/BenchUtil.cs @@ -0,0 +1,113 @@ +using System; +using System.Diagnostics; +using System.Reactive.Linq; +using Microsoft.StreamProcessing; +using Google.Protobuf; +using Stream; + +namespace bench +{ + public class BenchUtil + { + public static double RunTest(Func> data, + Func, IStreamable> transform) + { + var stream = data(); + + var sw = new Stopwatch(); + sw.Start(); + var s_obs = transform(stream); + + s_obs + .ToStreamEventObservable() + .Wait(); + sw.Stop(); + return sw.Elapsed.TotalSeconds; + } + + public static double RunTest( + Func> data1, + Func> data2, + Func, IStreamable, IStreamable> transform) + { + var stream = data1(); + var stream2 = data2(); + + var sw = new Stopwatch(); + sw.Start(); + var s_obs = transform(stream,stream2); + + s_obs + .ToStreamEventObservable() + .Wait(); + sw.Stop(); + return sw.Elapsed.TotalSeconds; + } + + public static double RunTest( + Func, IStreamable>> data, + Func, IStreamable, IStreamable> transform) + { + var result = data(); + var stream = result.Item1; + var stream2 = result.Item2; + + var sw = new Stopwatch(); + sw.Start(); + var s_obs = transform(stream,stream2); + + s_obs + .ToStreamEventObservable() + .Wait(); + sw.Stop(); + return sw.Elapsed.TotalSeconds; + } + + public static Func> DataFn(long p, long s) + { + return () => new TestObs(p, s) + .ToStreamable() + .Cache(); + } + + public static Func, IStreamable>> TaxiDataFn(long s) + { + return () => { + var taxi_ride_data = new TaxiRideData(); + var taxi_fare_data = new TaxiFareData(); + long cnt_trips = 0; + long cnt_fares = 0; + + Console.WriteLine("Start loading taxi trips and taxi fares ..."); + + MessageParser parser = new MessageParser(() => new stream_event()); + for (long i = 0; i < s; i++) + { + stream_event s_event = parser.ParseDelimitedFrom(Console.OpenStandardInput()); + if (s_event.PayloadCase == stream_event.PayloadOneofCase.TaxiTrip) { + taxi_ride_data.LoadDataPoint(s_event); + cnt_trips++; + } else if (s_event.PayloadCase == stream_event.PayloadOneofCase.TaxiFare) { + taxi_fare_data.LoadDataPoint(s_event); + cnt_fares++; + } else { + Debug.Assert(false); + } + } + Console.WriteLine("Finished loading {0} taxi trips and {1} taxi fares", cnt_trips, cnt_fares); + + return Tuple.Create( + (IStreamable) taxi_ride_data.ToStreamable().Cache(), + (IStreamable) taxi_fare_data.ToStreamable().Cache() + ); + }; + } + + public static Func> VibrationDataFn(long s) + { + return () => new VibrationObs(s) + .ToStreamable() + .Cache(); + } + } +} \ No newline at end of file diff --git a/trill_bench/bench/DatasetData.cs b/trill_bench/bench/DatasetData.cs new file mode 100644 index 0000000..fb86f90 --- /dev/null +++ b/trill_bench/bench/DatasetData.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using Microsoft.StreamProcessing; +using Google.Protobuf; +using Stream; + +namespace bench +{ + using test_t = StreamEvent; + + public abstract class DatasetObs : IObservable + { + public long size; + public List data; + + public DatasetObs(long size) + { + this.size = size; + this.data = new List(); + } + + public abstract void LoadDataPoint(); + + public void LoadData() + { + for (long i = 0; i < size; i++) + { + this.LoadDataPoint(); + } + } + + public IDisposable Subscribe(IObserver observer) + { + return new Subscription(this, observer); + } + + private sealed class Subscription : IDisposable + { + private readonly DatasetObs observable; + private readonly IObserver observer; + + public Subscription(DatasetObs observable, IObserver observer) + { + this.observer = observer; + this.observable = observable; + ThreadPool.QueueUserWorkItem( + arg => + { + this.Sample(); + this.observer.OnCompleted(); + }); + } + + private void Sample() + { + for (int i = 0; i < observable.data.Count; i++) + { + this.observer.OnNext(observable.data[i]); + } + } + + public void Dispose() + { + } + } + } + + public class VibrationObs : DatasetObs + { + private MessageParser parser; + + public override void LoadDataPoint() + { + stream_event s_event = parser.ParseDelimitedFrom(Console.OpenStandardInput()); + long st = s_event.St; + long et = s_event.Et; + float payload = s_event.Vibration.Channel1; + data.Add(StreamEvent.CreateInterval(st, et, payload)); + } + + public VibrationObs(long size) : base(size) + { + Console.WriteLine("Start loading vibration data points ..."); + this.parser = new MessageParser(() => new stream_event()); + LoadData(); + Console.WriteLine("Finished loading {0} vibration data points", size); + } + } +} \ No newline at end of file diff --git a/trill_bench/bench/Program.cs b/trill_bench/bench/Program.cs index d864efd..e3f8bdb 100644 --- a/trill_bench/bench/Program.cs +++ b/trill_bench/bench/Program.cs @@ -2,46 +2,13 @@ using System.Diagnostics; using System.Reactive.Linq; using Microsoft.StreamProcessing; +using Google.Protobuf; +using Stream; namespace bench { class Program { - static double RunTest(Func> data, - Func, IStreamable> transform) - { - var stream = data(); - - var sw = new Stopwatch(); - sw.Start(); - var s_obs = transform(stream); - - s_obs - .ToStreamEventObservable() - .Wait(); - sw.Stop(); - return sw.Elapsed.TotalSeconds; - } - - static double RunTest( - Func> data1, - Func> data2, - Func, IStreamable, IStreamable> transform) - { - var stream = data1(); - var stream2 = data2(); - - var sw = new Stopwatch(); - sw.Start(); - var s_obs = transform(stream,stream2); - - s_obs - .ToStreamEventObservable() - .Wait(); - sw.Stop(); - return sw.Elapsed.TotalSeconds; - } - static void Main(string[] args) { string testcase = (args.Length > 0) ? args[0] : "normalize"; @@ -49,69 +16,41 @@ static void Main(string[] args) long period = 1; double time = 0; - Func> data = () => - { - return new TestObs(period, size) - .ToStreamable() - .Cache(); - }; - - Func> DataFn(long p, long s) - { - return () => new TestObs(p, s) - .ToStreamable() - .Cache(); - } - - Func> TaxiFareDataFn(long p, long s) - { - return () => new TaxiFareData(p, s) - .ToStreamable() - .Cache(); - } - - Func> TaxiRideDataFn(long p, long s) - { - return () => new TaxiRideData(p, s) - .ToStreamable() - .Cache(); - } - switch (testcase) { case "select": - time = RunTest(DataFn(period, size), stream => + time = BenchUtil.RunTest(BenchUtil.DataFn(period, size), stream => stream .Select(e => e + 3) ); break; case "where": - time = RunTest(DataFn(period, size), stream => + time = BenchUtil.RunTest(BenchUtil.DataFn(period, size), stream => stream .Where(e => e > 0) ); break; case "aggregate": - time = RunTest(DataFn(period, size), stream => + time = BenchUtil.RunTest(BenchUtil.DataFn(period, size), stream => stream .TumblingWindowLifetime(1000 * period) .Sum(e => e) ); break; case "alterdur": - time = RunTest(DataFn(period, size), stream => + time = BenchUtil.RunTest(BenchUtil.DataFn(period, size), stream => stream .AlterEventDuration(10 * period) ); break; case "innerjoin": - time = RunTest(DataFn(period, size), DataFn(period, size), (stream,stream2) => + time = BenchUtil.RunTest(BenchUtil.DataFn(period, size), BenchUtil.DataFn(period, size), (stream,stream2) => stream .Join(stream2, (left, right) => left + right) ); break; case "outerjoin": - time = RunTest(DataFn(period, size), DataFn(period, size), (stream, stream2) => + time = BenchUtil.RunTest(BenchUtil.DataFn(period, size), BenchUtil.DataFn(period, size), (stream, stream2) => stream .FullOuterJoin(stream2, e => true, e => true, left => left, right => right, @@ -119,13 +58,13 @@ Func> TaxiRideDataFn(long p, long s) ); break; case "normalize": - time = RunTest(DataFn(period, size), stream => + time = BenchUtil.RunTest(BenchUtil.DataFn(period, size), stream => stream .Normalize(10000) ); break; case "fillmean": - time = RunTest(DataFn(period, size), stream => + time = BenchUtil.RunTest(BenchUtil.DataFn(period, size), stream => stream .FillMean(10000, period) ); @@ -139,57 +78,56 @@ Func> TaxiRideDataFn(long p, long s) .ToStreamable() .Cache(); }; - time = RunTest(sig4, stream => + time = BenchUtil.RunTest(sig4, stream => stream .Resample(iperiod, operiod) ); break; case "algotrading": - time = RunTest(DataFn(period, size), stream => + time = BenchUtil.RunTest(BenchUtil.DataFn(period, size), stream => stream .AlgoTrading(50, 20, period) ); break; case "largeqty": - time = RunTest(DataFn(period, size), stream => + time = BenchUtil.RunTest(BenchUtil.DataFn(period, size), stream => stream .LargeQty(10, period) ); break; case "rsi": - time = RunTest(DataFn(period, size), stream => + time = BenchUtil.RunTest(BenchUtil.DataFn(period, size), stream => stream .RSI(14, period) ); break; case "pantom": - time = RunTest(DataFn(period, size), stream => + time = BenchUtil.RunTest(BenchUtil.DataFn(period, size), stream => stream .PanTom(period) ); break; case "kurtosis": - time = RunTest(DataFn(period, size), stream => + time = BenchUtil.RunTest(BenchUtil.VibrationDataFn(size), stream => stream .Kurtosis(100) ); break; case "taxi": - time = RunTest(TaxiRideDataFn(period, size), - TaxiFareDataFn(period, size), + time = BenchUtil.RunTest(BenchUtil.TaxiDataFn(size), (stream, stream2) => stream .Taxi(stream2, 300) ); break; case "eg1": - time = RunTest(DataFn(period, size), stream => + time = BenchUtil.RunTest(BenchUtil.DataFn(period, size), stream => stream .Eg1(10, 20) ); break; case "eg2": - time = RunTest(DataFn(period, size), stream => + time = BenchUtil.RunTest(BenchUtil.DataFn(period, size), stream => stream .Eg2(10, 20) ); diff --git a/trill_bench/bench/TaxiData.cs b/trill_bench/bench/TaxiData.cs index 1800004..c53316f 100644 --- a/trill_bench/bench/TaxiData.cs +++ b/trill_bench/bench/TaxiData.cs @@ -1,7 +1,10 @@ using System; +using System.Diagnostics; using System.Collections.Generic; using System.Threading; using Microsoft.StreamProcessing; +using Google.Protobuf; +using Stream; namespace bench { @@ -103,6 +106,21 @@ public TaxiRide(int medallion, int hack_license, string vendor_id, int rate_code this.dropoff_longitude = dropoff_longitude; this.dropoff_latitude = dropoff_latitude; } + + public override string ToString() { + return String.Format( + "{{medallion: {0}, hack_license: {1}, vendor_id: {2}, rate_code: {3}, " + + "store_and_fwd_flag: {4}, pickup_datetime: {5}, dropoff_datetime: {6}, " + + "passenger_count: {7}, trip_time_in_secs: {8}, trip_distance: {9}, " + + "pickup_longitude: {10}, pickup_latitude: {11}, dropoff_longitude: {12}, " + + "dropoff_latitude: {13}}}", + this.medallion, this.hack_license, this.vendor_id, this.rate_code, + this.store_and_fwd_flag, this.pickup_datetime, this.dropoff_datetime, + this.passenger_count, this.trip_time_in_secs, this.trip_distance, + this.pickup_longitude, this.pickup_latitude, this.dropoff_longitude, + this.dropoff_latitude + ); + } } public class TaxiFare @@ -135,45 +153,31 @@ public TaxiFare(int medallion, int hack_license, string vendor_id, DateTime pick this.tolls_amount = tolls_amount; this.total_amount = total_amount; } - } - - public class TaxiDrivers - { - public static List drivers; - - static TaxiDrivers() - { - SampleDrivers(); - } - public static void SampleDrivers() - { - TaxiDrivers.drivers = new List(); - for (int i = 0; i < 1000; i++) - { - var driver = new TaxiDriver(i, i, "Vendor-" + i.ToString()); - TaxiDrivers.drivers.Add(driver); - } + public override string ToString() { + return String.Format( + "{{medallion: {0}, hack_license: {1}, vendor_id: {2}, pickup_datetime: {3}, " + + "payment_type: {4}, fare_amount: {5}, surcharge: {6}, mta_tax: {7}, tip_amount: {8}, " + + "tolls_amount: {9}, total_amount: {10}}}", + this.medallion, this.hack_license, this.vendor_id, this.pickup_datetime, + this.payment_type, this.fare_amount, this.surcharge, this.mta_tax, this.tip_amount, + this.tolls_amount, this.total_amount + ); } } public abstract class TaxiDataObs : IObservable { - public long size; - public long period; public List data; public DateTime datetime_base; - public TaxiDataObs(long period, long size) + public TaxiDataObs() { - this.period = period; - this.size = size; this.data = new List(); - this.datetime_base = new DateTime(2021, 10, 1, 0, 0, 0); - Sample(); + this.datetime_base = new DateTime(1970, 1, 1, 0, 0, 0); } - public abstract void Sample(); + public abstract void LoadDataPoint(stream_event s_event); public IDisposable Subscribe(IObserver observer) { @@ -213,82 +217,54 @@ public void Dispose() public class TaxiFareData : TaxiDataObs> { - public TaxiFareData(long period, long size) : base(period, size) + public TaxiFareData() : base() {} - public override void Sample() + public override void LoadDataPoint(stream_event s_event) { - var rand = new Random(); - for (int i = 0; i < size; i++) - { - var driver = TaxiDrivers.drivers[i % TaxiDrivers.drivers.Count]; - DateTime pickup_datetime = this.datetime_base.AddMinutes(i * 10); - string[] payment_types = {"VISA", "CASH"}; - string payment_type = payment_types[rand.Next(2)]; - float fare_amount = (float) (rand.NextDouble() * 100); - float surcharge = fare_amount * 0.1f; - float mta_tax = fare_amount * 0.05f; - float tip_amount = (float) (fare_amount * rand.NextDouble()); - float tolls_amount = (float) (rand.NextDouble() * 100); - float total_amount = fare_amount + surcharge + mta_tax + tip_amount + tolls_amount; - - var payload = new TaxiFare( - driver.medallion, - driver.hack_license, - driver.vendor_id, - pickup_datetime, - payment_type, - fare_amount, - surcharge, - mta_tax, - tip_amount, - tolls_amount, - total_amount - ); - data.Add(StreamEvent.CreateInterval(i * period, (i + 1) * period, payload)); - } + long st = s_event.St; + var payload = new TaxiFare( + s_event.TaxiFare.Medallion, + s_event.TaxiFare.HackLicense, + s_event.TaxiFare.VendorId, + this.datetime_base.AddSeconds(st), + s_event.TaxiFare.PaymentType, + s_event.TaxiFare.FareAmount, + s_event.TaxiFare.Surcharge, + s_event.TaxiFare.MtaTax, + s_event.TaxiFare.TipAmount, + s_event.TaxiFare.TollsAmount, + s_event.TaxiFare.TotalAmount + ); + data.Add(StreamEvent.CreateInterval(st, st + 1, payload)); } } public class TaxiRideData : TaxiDataObs> { - public TaxiRideData(long period, long size) : base(period, size) + public TaxiRideData() : base() {} - public override void Sample() + public override void LoadDataPoint(stream_event s_event) { - var rand = new Random(); - for (int i = 0; i < size; i++) - { - var driver = TaxiDrivers.drivers[i % TaxiDrivers.drivers.Count]; - int rate_code = rand.Next(10); - bool store_and_fwd_flag = rand.Next() > (Int32.MaxValue / 2); - DateTime pickup_datetime = this.datetime_base.AddMinutes(i * 10); - DateTime dropoff_datetime = pickup_datetime.AddMinutes(rand.Next(1, 100)); - int passenger_count = rand.Next(1, 4); - float trip_time_in_secs = (float) (dropoff_datetime - pickup_datetime).TotalSeconds; - float trip_distance = (float) (rand.NextDouble() * 100); - float pickup_longitude = (float) (rand.NextDouble() * 100); - float pickup_latitude = (float) (rand.NextDouble() * 100); - float dropoff_longitude = (float) (rand.NextDouble() * 100); - float dropoff_latitude = (float) (rand.NextDouble() * 100); - - var payload = new TaxiRide( - driver.medallion, - driver.hack_license, - driver.vendor_id, - rate_code, - store_and_fwd_flag, - pickup_datetime, - dropoff_datetime, - passenger_count, - trip_time_in_secs, - trip_distance, - pickup_longitude, - pickup_latitude, - dropoff_longitude, - dropoff_latitude - ); - data.Add(StreamEvent.CreateInterval(i * period, (i + 1) * period, payload)); - } + long st = s_event.St; + long et = s_event.Et; + Debug.Assert(s_event.PayloadCase == stream_event.PayloadOneofCase.TaxiTrip); + var payload = new TaxiRide( + s_event.TaxiTrip.Medallion, + s_event.TaxiTrip.HackLicense, + s_event.TaxiTrip.VendorId, + s_event.TaxiTrip.RateCode, + s_event.TaxiTrip.StoreAndFwdFlag, + this.datetime_base.AddSeconds(st), + this.datetime_base.AddSeconds(et), + s_event.TaxiTrip.PassengerCount, + s_event.TaxiTrip.TripTimeInSecs, + s_event.TaxiTrip.TripDistance, + s_event.TaxiTrip.PickupLongitude, + s_event.TaxiTrip.PickupLatitude, + s_event.TaxiTrip.DropoffLongitude, + s_event.TaxiTrip.DropoffLatitude + ); + data.Add(StreamEvent.CreateInterval(st, st + 1, payload)); } } } \ No newline at end of file diff --git a/trill_bench/bench/bench.csproj b/trill_bench/bench/bench.csproj index f0fe78f..86ae4cf 100644 --- a/trill_bench/bench/bench.csproj +++ b/trill_bench/bench/bench.csproj @@ -9,6 +9,7 @@ + diff --git a/trill_bench/trill_bench.sln b/trill_bench/trill_bench.sln index e6e0e88..dd7c627 100644 --- a/trill_bench/trill_bench.sln +++ b/trill_bench/trill_bench.sln @@ -11,6 +11,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "bench", "bench\bench.csproj EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Reactive", "reactive\Rx.NET\Source\src\System.Reactive\System.Reactive.csproj", "{BA73F0BE-62D0-4763-9A00-60BBCEA9A1DD}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Google.Protobuf", "..\dataset_util\protobuf\csharp\src\Google.Protobuf\Google.Protobuf.csproj", "{A14A7166-9BB0-4290-B21A-8C0FE54EAFAF}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -72,5 +74,17 @@ Global {BA73F0BE-62D0-4763-9A00-60BBCEA9A1DD}.Release|x64.Build.0 = Release|Any CPU {BA73F0BE-62D0-4763-9A00-60BBCEA9A1DD}.Release|x86.ActiveCfg = Release|Any CPU {BA73F0BE-62D0-4763-9A00-60BBCEA9A1DD}.Release|x86.Build.0 = Release|Any CPU + {A14A7166-9BB0-4290-B21A-8C0FE54EAFAF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A14A7166-9BB0-4290-B21A-8C0FE54EAFAF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A14A7166-9BB0-4290-B21A-8C0FE54EAFAF}.Debug|x64.ActiveCfg = Debug|Any CPU + {A14A7166-9BB0-4290-B21A-8C0FE54EAFAF}.Debug|x64.Build.0 = Debug|Any CPU + {A14A7166-9BB0-4290-B21A-8C0FE54EAFAF}.Debug|x86.ActiveCfg = Debug|Any CPU + {A14A7166-9BB0-4290-B21A-8C0FE54EAFAF}.Debug|x86.Build.0 = Debug|Any CPU + {A14A7166-9BB0-4290-B21A-8C0FE54EAFAF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A14A7166-9BB0-4290-B21A-8C0FE54EAFAF}.Release|Any CPU.Build.0 = Release|Any CPU + {A14A7166-9BB0-4290-B21A-8C0FE54EAFAF}.Release|x64.ActiveCfg = Release|Any CPU + {A14A7166-9BB0-4290-B21A-8C0FE54EAFAF}.Release|x64.Build.0 = Release|Any CPU + {A14A7166-9BB0-4290-B21A-8C0FE54EAFAF}.Release|x86.ActiveCfg = Release|Any CPU + {A14A7166-9BB0-4290-B21A-8C0FE54EAFAF}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection EndGlobal