diff options
| author | nicole mazzuca <mazzucan@outlook.com> | 2020-05-29 14:09:03 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-05-29 14:09:03 -0700 |
| commit | 09319cd79e3bee6a41bb76db739aef3f3644f19f (patch) | |
| tree | 36e6b5bd5c17268edc062aa9c462f47fc4f400cf /toolsrc/src | |
| parent | a64dc07690bc8806e717e190f62eb58e198b599c (diff) | |
| download | vcpkg-09319cd79e3bee6a41bb76db739aef3f3644f19f.tar.gz vcpkg-09319cd79e3bee6a41bb76db739aef3f3644f19f.zip | |
[vcpkg metrics] Allow someone to opt out after build (#11542)
* [vcpkg metrics] start using json library
Additionally, add floats to the JSON library since they're required.
* [vcpkg metrics] allow users to disable metrics after the build
Additionally, as a drive by, fix UUID generation
* fix metrics data
* code review
Diffstat (limited to 'toolsrc/src')
| -rw-r--r-- | toolsrc/src/vcpkg-test/json.cpp | 64 | ||||
| -rw-r--r-- | toolsrc/src/vcpkg.cpp | 17 | ||||
| -rw-r--r-- | toolsrc/src/vcpkg/base/json.cpp | 443 | ||||
| -rw-r--r-- | toolsrc/src/vcpkg/base/system.process.cpp | 3 | ||||
| -rw-r--r-- | toolsrc/src/vcpkg/commands.version.cpp | 2 | ||||
| -rw-r--r-- | toolsrc/src/vcpkg/metrics.cpp | 311 | ||||
| -rw-r--r-- | toolsrc/src/vcpkg/vcpkgcmdarguments.cpp | 10 |
7 files changed, 540 insertions, 310 deletions
diff --git a/toolsrc/src/vcpkg-test/json.cpp b/toolsrc/src/vcpkg-test/json.cpp index 09f5d98fc..a957618cb 100644 --- a/toolsrc/src/vcpkg-test/json.cpp +++ b/toolsrc/src/vcpkg-test/json.cpp @@ -4,6 +4,8 @@ #include <vcpkg/base/json.h> #include <vcpkg/base/unicode.h> +#include "math.h" + // TODO: remove this once we switch to C++20 completely // This is the worst, but we also can't really deal with it any other way. #if __cpp_char8_t @@ -57,9 +59,7 @@ TEST_CASE ("JSON parse strings", "[json]") REQUIRE(res.get()->first.is_string()); REQUIRE(res.get()->first.string() == "\xED\xA0\x80"); - const auto make_json_string = [] (vcpkg::StringView sv) { - return '"' + sv.to_string() + '"'; - }; + const auto make_json_string = [](vcpkg::StringView sv) { return '"' + sv.to_string() + '"'; }; const vcpkg::StringView radical = U8_STR("⎷"); const vcpkg::StringView grin = U8_STR("😁"); @@ -79,28 +79,51 @@ TEST_CASE ("JSON parse strings", "[json]") REQUIRE(res.get()->first.string() == grin); } -TEST_CASE ("JSON parse numbers", "[json]") +TEST_CASE ("JSON parse integers", "[json]") { auto res = Json::parse("0"); REQUIRE(res); - REQUIRE(res.get()->first.is_number()); - REQUIRE(res.get()->first.number() == 0); + REQUIRE(res.get()->first.is_integer()); + REQUIRE(res.get()->first.integer() == 0); res = Json::parse("12345"); REQUIRE(res); - REQUIRE(res.get()->first.is_number()); - REQUIRE(res.get()->first.number() == 12345); + REQUIRE(res.get()->first.is_integer()); + REQUIRE(res.get()->first.integer() == 12345); res = Json::parse("-12345"); REQUIRE(res); - REQUIRE(res.get()->first.is_number()); - REQUIRE(res.get()->first.number() == -12345); + REQUIRE(res.get()->first.is_integer()); + REQUIRE(res.get()->first.integer() == -12345); res = Json::parse("9223372036854775807"); // INT64_MAX REQUIRE(res); - REQUIRE(res.get()->first.is_number()); - REQUIRE(res.get()->first.number() == 9223372036854775807); + REQUIRE(res.get()->first.is_integer()); + REQUIRE(res.get()->first.integer() == 9223372036854775807); res = Json::parse("-9223372036854775808"); REQUIRE(res); + REQUIRE(res.get()->first.is_integer()); + REQUIRE(res.get()->first.integer() == (-9223372036854775807 - 1)); // INT64_MIN (C++'s parser is fun) +} + +TEST_CASE ("JSON parse floats", "[json]") +{ + auto res = Json::parse("0.0"); + REQUIRE(res); + REQUIRE(res.get()->first.is_number()); + REQUIRE(!res.get()->first.is_integer()); + REQUIRE(res.get()->first.number() == 0.0); + REQUIRE(!signbit(res.get()->first.number())); + res = Json::parse("-0.0"); + REQUIRE(res); + REQUIRE(res.get()->first.is_number()); + REQUIRE(res.get()->first.number() == 0.0); + REQUIRE(signbit(res.get()->first.number())); + res = Json::parse("12345.6789"); + REQUIRE(res); + REQUIRE(res.get()->first.is_number()); + REQUIRE_THAT(res.get()->first.number(), Catch::WithinULP(12345.6789, 3)); + res = Json::parse("-12345.6789"); + REQUIRE(res); REQUIRE(res.get()->first.is_number()); - REQUIRE(res.get()->first.number() == (-9223372036854775807 - 1)); // INT64_MIN (C++'s parser is fun) + REQUIRE_THAT(res.get()->first.number(), Catch::WithinULP(-12345.6789, 3)); } TEST_CASE ("JSON parse arrays", "[json]") @@ -116,18 +139,18 @@ TEST_CASE ("JSON parse arrays", "[json]") val = std::move(res.get()->first); REQUIRE(val.is_array()); REQUIRE(val.array().size() == 1); - REQUIRE(val.array()[0].is_number()); - REQUIRE(val.array()[0].number() == 123); + REQUIRE(val.array()[0].is_integer()); + REQUIRE(val.array()[0].integer() == 123); res = Json::parse("[123, 456]"); REQUIRE(res); val = std::move(res.get()->first); REQUIRE(val.is_array()); REQUIRE(val.array().size() == 2); - REQUIRE(val.array()[0].is_number()); - REQUIRE(val.array()[0].number() == 123); - REQUIRE(val.array()[1].is_number()); - REQUIRE(val.array()[1].number() == 456); + REQUIRE(val.array()[0].is_integer()); + REQUIRE(val.array()[0].integer() == 123); + REQUIRE(val.array()[1].is_integer()); + REQUIRE(val.array()[1].integer() == 456); res = Json::parse("[123, 456, [null]]"); REQUIRE(res); @@ -155,5 +178,8 @@ TEST_CASE ("JSON parse full file", "[json]") ; auto res = Json::parse(json); + if (!res) { + std::cerr << res.error()->format() << '\n'; + } REQUIRE(res); } diff --git a/toolsrc/src/vcpkg.cpp b/toolsrc/src/vcpkg.cpp index e1a0a2a0b..95cd39778 100644 --- a/toolsrc/src/vcpkg.cpp +++ b/toolsrc/src/vcpkg.cpp @@ -333,6 +333,11 @@ int main(const int argc, const char* const* const argv) auto flags = Strings::split(*v, ','); if (std::find(flags.begin(), flags.end(), "binarycaching") != flags.end()) GlobalState::g_binary_caching = true; } + const auto vcpkg_disable_metrics_env = System::get_environment_variable("VCPKG_DISABLE_METRICS"); + if (vcpkg_disable_metrics_env.has_value()) + { + Metrics::g_metrics.lock()->set_disabled(true); + } const VcpkgCmdArguments args = VcpkgCmdArguments::create_from_command_line(argc, argv); @@ -340,8 +345,20 @@ int main(const int argc, const char* const* const argv) if (const auto p = args.printmetrics.get()) Metrics::g_metrics.lock()->set_print_metrics(*p); if (const auto p = args.sendmetrics.get()) Metrics::g_metrics.lock()->set_send_metrics(*p); + if (const auto p = args.disable_metrics.get()) Metrics::g_metrics.lock()->set_disabled(*p); if (const auto p = args.debug.get()) Debug::g_debugging = *p; + if (args.sendmetrics.has_value() && !Metrics::g_metrics.lock()->metrics_enabled()) + { + System::print2(System::Color::warning, + "Warning: passed either --sendmetrics or --no-sendmetrics, but metrics are disabled.\n"); + } + if (args.printmetrics.has_value() && !Metrics::g_metrics.lock()->metrics_enabled()) + { + System::print2(System::Color::warning, + "Warning: passed either --printmetrics or --no-printmetrics, but metrics are disabled.\n"); + } + if (Debug::g_debugging) { inner(args); diff --git a/toolsrc/src/vcpkg/base/json.cpp b/toolsrc/src/vcpkg/base/json.cpp index d8d0faab0..3d6bfc1d2 100644 --- a/toolsrc/src/vcpkg/base/json.cpp +++ b/toolsrc/src/vcpkg/base/json.cpp @@ -5,6 +5,8 @@ #include <vcpkg/base/system.debug.h> #include <vcpkg/base/unicode.h> +#include <inttypes.h> + namespace vcpkg::Json { using VK = ValueKind; @@ -23,7 +25,8 @@ namespace vcpkg::Json { std::nullptr_t null; bool boolean; - int64_t number; + int64_t integer; + double number; std::string string; Array array; Object object; @@ -31,7 +34,8 @@ namespace vcpkg::Json ValueImpl(ValueKindConstant<VK::Null> vk, std::nullptr_t) : tag(vk), null() { } ValueImpl(ValueKindConstant<VK::Boolean> vk, bool b) : tag(vk), boolean(b) { } - ValueImpl(ValueKindConstant<VK::Number> vk, int64_t i) : tag(vk), number(i) { } + ValueImpl(ValueKindConstant<VK::Integer> vk, int64_t i) : tag(vk), integer(i) { } + ValueImpl(ValueKindConstant<VK::Number> vk, double d) : tag(vk), number(d) { } ValueImpl(ValueKindConstant<VK::String> vk, std::string&& s) : tag(vk), string(std::move(s)) { } ValueImpl(ValueKindConstant<VK::Array> vk, Array&& arr) : tag(vk), array(std::move(arr)) { } ValueImpl(ValueKindConstant<VK::Object> vk, Object&& obj) : tag(vk), object(std::move(obj)) { } @@ -42,6 +46,7 @@ namespace vcpkg::Json { case VK::Null: return internal_assign(VK::Null, &ValueImpl::null, other); case VK::Boolean: return internal_assign(VK::Boolean, &ValueImpl::boolean, other); + case VK::Integer: return internal_assign(VK::Integer, &ValueImpl::integer, other); case VK::Number: return internal_assign(VK::Number, &ValueImpl::number, other); case VK::String: return internal_assign(VK::String, &ValueImpl::string, other); case VK::Array: return internal_assign(VK::Array, &ValueImpl::array, other); @@ -101,7 +106,12 @@ namespace vcpkg::Json bool Value::is_null() const noexcept { return kind() == VK::Null; } bool Value::is_boolean() const noexcept { return kind() == VK::Boolean; } - bool Value::is_number() const noexcept { return kind() == VK::Number; } + bool Value::is_integer() const noexcept { return kind() == VK::Integer; } + bool Value::is_number() const noexcept + { + auto k = kind(); + return k == VK::Integer || k == VK::Number; + } bool Value::is_string() const noexcept { return kind() == VK::String; } bool Value::is_array() const noexcept { return kind() == VK::Array; } bool Value::is_object() const noexcept { return kind() == VK::Object; } @@ -111,10 +121,22 @@ namespace vcpkg::Json vcpkg::Checks::check_exit(VCPKG_LINE_INFO, is_boolean()); return underlying_->boolean; } - int64_t Value::number() const noexcept + int64_t Value::integer() const noexcept + { + vcpkg::Checks::check_exit(VCPKG_LINE_INFO, is_integer()); + return underlying_->integer; + } + double Value::number() const noexcept { - vcpkg::Checks::check_exit(VCPKG_LINE_INFO, is_number()); - return underlying_->number; + auto k = kind(); + if (k == VK::Number) + { + return underlying_->number; + } + else + { + return static_cast<double>(integer()); + } } StringView Value::string() const noexcept { @@ -155,6 +177,7 @@ namespace vcpkg::Json { case ValueKind::Null: return Value::null(nullptr); case ValueKind::Boolean: return Value::boolean(boolean()); + case ValueKind::Integer: return Value::integer(integer()); case ValueKind::Number: return Value::number(number()); case ValueKind::String: return Value::string(string()); case ValueKind::Array: return Value::array(array().clone()); @@ -170,10 +193,17 @@ namespace vcpkg::Json val.underlying_ = std::make_unique<ValueImpl>(ValueKindConstant<VK::Boolean>(), b); return val; } - Value Value::number(int64_t i) noexcept + Value Value::integer(int64_t i) noexcept { Value val; - val.underlying_ = std::make_unique<ValueImpl>(ValueKindConstant<VK::Number>(), i); + val.underlying_ = std::make_unique<ValueImpl>(ValueKindConstant<VK::Integer>(), i); + return val; + } + Value Value::number(double d) noexcept + { + vcpkg::Checks::check_exit(VCPKG_LINE_INFO, isfinite(d)); + Value val; + val.underlying_ = std::make_unique<ValueImpl>(ValueKindConstant<VK::Number>(), d); return val; } Value Value::string(StringView sv) noexcept @@ -211,25 +241,66 @@ namespace vcpkg::Json } return arr; } + + Value& Array::push_back(Value&& value) + { + underlying_.push_back(std::move(value)); + return underlying_.back(); + } + Object& Array::push_back(Object&& obj) { return push_back(Value::object(std::move(obj))).object(); } + Array& Array::push_back(Array&& arr) { return push_back(Value::array(std::move(arr))).array(); } + Value& Array::insert_before(iterator it, Value&& value) + { + size_t index = it - underlying_.begin(); + underlying_.insert(it, std::move(value)); + return underlying_[index]; + } + Object& Array::insert_before(iterator it, Object&& obj) + { + return insert_before(it, Value::object(std::move(obj))).object(); + } + Array& Array::insert_before(iterator it, Array&& arr) + { + return insert_before(it, Value::array(std::move(arr))).array(); + } // } struct Array // struct Object { - void Object::insert(std::string key, Value value) noexcept + Value& Object::insert(std::string key, Value&& value) { vcpkg::Checks::check_exit(VCPKG_LINE_INFO, !contains(key)); - underlying_.push_back(std::make_pair(std::move(key), std::move(value))); + underlying_.push_back({std::move(key), std::move(value)}); + return underlying_.back().second; } - void Object::insert_or_replace(std::string key, Value value) noexcept + Array& Object::insert(std::string key, Array&& value) + { + return insert(std::move(key), Value::array(std::move(value))).array(); + } + Object& Object::insert(std::string key, Object&& value) + { + return insert(std::move(key), Value::object(std::move(value))).object(); + } + Value& Object::insert_or_replace(std::string key, Value&& value) { auto v = get(key); if (v) { *v = std::move(value); + return *v; } else { - underlying_.push_back(std::make_pair(std::move(key), std::move(value))); + underlying_.push_back({std::move(key), std::move(value)}); + return underlying_.back().second; } } + Array& Object::insert_or_replace(std::string key, Array&& value) + { + return insert_or_replace(std::move(key), Value::array(std::move(value))).array(); + } + Object& Object::insert_or_replace(std::string key, Object&& value) + { + return insert_or_replace(std::move(key), Value::object(std::move(value))).object(); + } auto Object::internal_find_key(StringView key) const noexcept -> underlying_t::const_iterator { @@ -463,11 +534,15 @@ namespace vcpkg::Json Value parse_number() noexcept { Checks::check_exit(VCPKG_LINE_INFO, is_number_start(cur())); - bool negative = false; + + bool floating = false; + bool negative = false; // negative & 0 -> floating, so keep track of it + std::string number_to_parse; char32_t current = cur(); if (cur() == '-') { + number_to_parse.push_back('-'); negative = true; current = next(); if (current == Unicode::end_of_file) @@ -480,54 +555,81 @@ namespace vcpkg::Json if (current == '0') { current = next(); - if (current != Unicode::end_of_file) + if (current == '.') + { + number_to_parse.append("0."); + floating = true; + current = next(); + } + else if (is_digit(current)) + { + add_error("Unexpected digits after a leading zero"); + return Value(); + } + else { - if (is_digit(current)) + if (negative) { - add_error("Unexpected digits after a leading zero"); + return Value::number(-0.0); } - if (current == '.') + else { - add_error("Found a `.` -- this JSON implementation does not support floating point"); + return Value::integer(0); } } - return Value::number(0); } - // parse as negative so that someone can write INT64_MIN; otherwise, they'd only be able to get - // -INT64_MAX = INT64_MIN + 1 - constexpr auto min_value = std::numeric_limits<int64_t>::min(); - int64_t result = 0; - while (current != Unicode::end_of_file && is_digit(current)) + while (is_digit(current)) + { + number_to_parse.push_back(static_cast<char>(current)); + current = next(); + } + if (!floating && current == '.') { - const int digit = current - '0'; - // result * 10 - digit < min_value : remember that result < 0 - if (result < (min_value + digit) / 10) + floating = true; + number_to_parse.push_back('.'); + current = next(); + if (!is_digit(current)) { - add_error("Number is too big for an int64_t"); + add_error("Expected digits after the decimal point"); return Value(); } - result *= 10; - result -= digit; - current = next(); + while (is_digit(current)) + { + number_to_parse.push_back(static_cast<char>(current)); + current = next(); + } } - if (current == '.') + +#ifdef _MSC_VER +#define SCANF sscanf_s +#else +#define SCANF sscanf +#endif + + // TODO: switch to `from_chars` once we are able to remove support for old compilers + if (floating) { - add_error("Found a `.` -- this JSON implementation doesn't support floating point"); - return Value(); + double res; + if (SCANF(number_to_parse.c_str(), "%lf", &res) != 1) + { + add_error(Strings::format("Invalid floating point constant: %s", number_to_parse)); + return Value(); + } + return Value::number(res); } - - if (!negative) + else { - if (result == min_value) + int64_t res; + if (SCANF(number_to_parse.c_str(), "%" SCNd64, &res) != 1) { - add_error("Number is too big for a uint64_t"); + add_error(Strings::format("Invalid integer constant: %s", number_to_parse)); return Value(); } - result = -result; + return Value::integer(res); } - return Value::number(result); +#undef SCANF } Value parse_keyword() noexcept @@ -630,7 +732,7 @@ namespace vcpkg::Json auto current = cur(); - auto res = std::make_pair(std::string(""), Value()); + std::pair<std::string, Value> res = {std::string(""), Value()}; if (current == Unicode::end_of_file) { @@ -804,128 +906,98 @@ namespace vcpkg::Json } // } auto parse() - // auto stringify() { - static std::string& append_unicode_escape(std::string& s, char16_t code_unit) - { - s.append("\\u"); - - // AFAIK, there's no standard way of doing this? - constexpr const char hex_digit[16] = { - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; - - s.push_back(hex_digit[(code_unit >> 12) & 0x0F]); - s.push_back(hex_digit[(code_unit >> 8) & 0x0F]); - s.push_back(hex_digit[(code_unit >> 4) & 0x0F]); - s.push_back(hex_digit[(code_unit >> 0) & 0x0F]); - - return s; - } - - // taken from the ECMAScript 2020 standard, 24.5.2.2: Runtime Semantics: QuoteJSONString - static std::string& append_quoted_json_string(std::string& product, StringView sv) + namespace { - // Table 66: JSON Single Character Escape Sequences - constexpr static std::array<std::pair<char32_t, const char*>, 7> escape_sequences = { - std::make_pair(0x0008, R"(\b)"), // BACKSPACE - std::make_pair(0x0009, R"(\t)"), // CHARACTER TABULATION - std::make_pair(0x000A, R"(\n)"), // LINE FEED (LF) - std::make_pair(0x000C, R"(\f)"), // FORM FEED (FF) - std::make_pair(0x000D, R"(\r)"), // CARRIAGE RETURN (CR) - std::make_pair(0x0022, R"(\")"), // QUOTATION MARK - std::make_pair(0x005C, R"(\\)") // REVERSE SOLIDUS - }; - // 1. Let product be the String value consisting solely of the code unit 0x0022 (QUOTATION MARK). - product.push_back('"'); - - // 2. For each code point C in ! UTF16DecodeString(value), do - // (note that we use utf8 instead of utf16) - for (auto code_point : Unicode::Utf8Decoder(sv.begin(), sv.end())) + struct Stringifier { - bool matched = false; // early exit boolean - // a. If C is listed in the "Code Point" column of Table 66, then - for (auto pr : escape_sequences) + JsonStyle style; + std::string& buffer; + + void append_indent(int indent) { - // i. Set product to the string-concatenation of product and the escape sequence for C as specified in - // the "Escape Sequence" column of the corresponding row. - if (code_point == pr.first) + if (style.use_tabs()) { - product.append(pr.second); - matched = true; - break; + buffer.append(indent, '\t'); } - } - if (matched) break; + else + { + buffer.append(indent * style.spaces(), ' '); + } + }; - // b. Else if C has a numeric value less than 0x0020 (SPACE), or if C has the same numeric value as a - // leading surrogate or trailing surrogate, then - if (code_point < 0x0020 || Unicode::utf16_is_surrogate_code_point(code_point)) + void append_unicode_escape(char16_t code_unit) { - // i. Let unit be the code unit whose numeric value is that of C. - // ii. Set product to the string-concatenation of product and UnicodeEscape(unit). - append_unicode_escape(product, static_cast<char16_t>(code_point)); - break; - } + buffer.append("\\u"); - // c. Else, - // i. Set product to the string-concatenation of product and the UTF16Encoding of C. - // (again, we use utf-8 here instead) - Unicode::utf8_append_code_point(product, code_point); - } + // AFAIK, there's no standard way of doing this? + constexpr const char hex_digit[16] = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; - // 3. Set product to the string-concatenation of product and the code unit 0x0022 (QUOTATION MARK). - product.push_back('"'); + buffer.push_back(hex_digit[(code_unit >> 12) & 0x0F]); + buffer.push_back(hex_digit[(code_unit >> 8) & 0x0F]); + buffer.push_back(hex_digit[(code_unit >> 4) & 0x0F]); + buffer.push_back(hex_digit[(code_unit >> 0) & 0x0F]); + } - // 4. Return product. - return product; - } + // taken from the ECMAScript 2020 standard, 24.5.2.2: Runtime Semantics: QuoteJSONString + void append_quoted_json_string(StringView sv) + { + // Table 66: JSON Single Character Escape Sequences + constexpr static std::array<std::pair<char32_t, const char*>, 7> escape_sequences = {{ + {0x0008, R"(\b)"}, // BACKSPACE + {0x0009, R"(\t)"}, // CHARACTER TABULATION + {0x000A, R"(\n)"}, // LINE FEED (LF) + {0x000C, R"(\f)"}, // FORM FEED (FF) + {0x000D, R"(\r)"}, // CARRIAGE RETURN (CR) + {0x0022, R"(\")"}, // QUOTATION MARK + {0x005C, R"(\\)"} // REVERSE SOLIDUS + }}; + // 1. Let product be the String value consisting solely of the code unit 0x0022 (QUOTATION MARK). + buffer.push_back('"'); + + // 2. For each code point C in ! UTF16DecodeString(value), do + // (note that we use utf8 instead of utf16) + for (auto code_point : Unicode::Utf8Decoder(sv.begin(), sv.end())) + { + // a. If C is listed in the "Code Point" column of Table 66, then + const auto match = std::find_if(begin(escape_sequences), end(escape_sequences), [code_point](const std::pair<char32_t, const char*>& attempt) { + return attempt.first == code_point; + }); + // i. Set product to the string-concatenation of product and the escape sequence for C as + // specified in the "Escape Sequence" column of the corresponding row. + if (match != end(escape_sequences)) { + buffer.append(match->second); + continue; + } - static std::string quote_json_string(StringView sv) - { - std::string product; - append_quoted_json_string(product, sv); - return product; - } + // b. Else if C has a numeric value less than 0x0020 (SPACE), or if C has the same numeric value as + // a leading surrogate or trailing surrogate, then + if (code_point < 0x0020 || Unicode::utf16_is_surrogate_code_point(code_point)) + { + // i. Let unit be the code unit whose numeric value is that of C. + // ii. Set product to the string-concatenation of product and UnicodeEscape(unit). + append_unicode_escape(static_cast<char16_t>(code_point)); + break; + } - static void internal_stringify(const Value& value, JsonStyle style, std::string& buffer, int current_indent) - { - const auto append_indent = [&](int indent) { - if (style.use_tabs()) - { - buffer.append(indent, '\t'); - } - else - { - buffer.append(indent * style.spaces(), ' '); - } - }; - switch (value.kind()) - { - case VK::Null: buffer.append("null"); break; - case VK::Boolean: - { - auto v = value.boolean(); - buffer.append(v ? "true" : "false"); - break; - } - case VK::Number: buffer.append(std::to_string(value.number())); break; - case VK::String: - { - append_quoted_json_string(buffer, value.string()); - break; + // c. Else, + // i. Set product to the string-concatenation of product and the UTF16Encoding of C. + // (again, we use utf-8 here instead) + Unicode::utf8_append_code_point(buffer, code_point); + } + + // 3. Set product to the string-concatenation of product and the code unit 0x0022 (QUOTATION MARK). + buffer.push_back('"'); } - case VK::Array: + + void stringify_object(const Object& obj, int current_indent) { - const auto& arr = value.array(); - buffer.push_back('['); - if (arr.size() == 0) - { - buffer.push_back(']'); - } - else + buffer.push_back('{'); + if (obj.size() != 0) { bool first = true; - for (const auto& el : arr) + for (const auto& el : obj) { if (!first) { @@ -936,23 +1008,28 @@ namespace vcpkg::Json buffer.append(style.newline()); append_indent(current_indent + 1); - internal_stringify(el, style, buffer, current_indent + 1); + append_quoted_json_string(el.first); + buffer.append(": "); + stringify(el.second, current_indent + 1); } buffer.append(style.newline()); append_indent(current_indent); - buffer.push_back(']'); } - break; + buffer.push_back('}'); } - case VK::Object: + + void stringify_array(const Array& arr, int current_indent) { - const auto& obj = value.object(); - buffer.push_back('{'); - if (obj.size() != 0) + buffer.push_back('['); + if (arr.size() == 0) + { + buffer.push_back(']'); + } + else { bool first = true; - for (const auto& el : obj) + for (const auto& el : arr) { if (!first) { @@ -963,24 +1040,64 @@ namespace vcpkg::Json buffer.append(style.newline()); append_indent(current_indent + 1); - auto key = quote_json_string(el.first); - buffer.append(key.begin(), key.end()); - buffer.append(": "); - internal_stringify(el.second, style, buffer, current_indent + 1); + stringify(el, current_indent + 1); } buffer.append(style.newline()); append_indent(current_indent); + buffer.push_back(']'); } - buffer.push_back('}'); - break; } - } + + void stringify(const Value& value, int current_indent) + { + switch (value.kind()) + { + case VK::Null: buffer.append("null"); break; + case VK::Boolean: + { + auto v = value.boolean(); + buffer.append(v ? "true" : "false"); + break; + } + // TODO: switch to `to_chars` once we are able to remove support for old compilers + case VK::Integer: buffer.append(std::to_string(value.integer())); break; + case VK::Number: buffer.append(std::to_string(value.number())); break; + case VK::String: + { + append_quoted_json_string(value.string()); + break; + } + case VK::Array: + { + stringify_array(value.array(), current_indent); + break; + } + case VK::Object: + { + stringify_object(value.object(), current_indent); + break; + } + } + } + }; } - std::string stringify(const Value& value, JsonStyle style) noexcept + std::string stringify(const Value& value, JsonStyle style) + { + std::string res; + Stringifier{style, res}.stringify(value, 0); + return res; + } + std::string stringify(const Object& obj, JsonStyle style) + { + std::string res; + Stringifier{style, res}.stringify_object(obj, 0); + return res; + } + std::string stringify(const Array& arr, JsonStyle style) { std::string res; - internal_stringify(value, style, res, 0); + Stringifier{style, res}.stringify_array(arr, 0); return res; } // } auto stringify() diff --git a/toolsrc/src/vcpkg/base/system.process.cpp b/toolsrc/src/vcpkg/base/system.process.cpp index e6e195010..3f72396d9 100644 --- a/toolsrc/src/vcpkg/base/system.process.cpp +++ b/toolsrc/src/vcpkg/base/system.process.cpp @@ -237,6 +237,7 @@ namespace vcpkg L"USERDOMAIN_ROAMINGPROFILE", L"USERNAME", L"USERPROFILE", + L"VCPKG_DISABLE_METRICS", L"windir", // Enables proxy information to be passed to Curl, the underlying download library in cmake.exe L"http_proxy", @@ -558,6 +559,7 @@ VCPKG_MSVC_WARNING(suppress : 6335) // Leaking process information handle 'proce (void)env; Debug::print("system(", cmd_line, ")\n"); fflush(nullptr); + int exit_code = system(cmd_line.c_str()); Debug::print( "system() returned ", exit_code, " after ", static_cast<unsigned int>(timer.microseconds()), " us\n"); @@ -617,6 +619,7 @@ VCPKG_MSVC_WARNING(suppress : 6335) // Leaking process information handle 'proce Debug::print("popen(", actual_cmd_line, ")\n"); // Flush stdout before launching external process fflush(stdout); + const auto pipe = popen(actual_cmd_line.c_str(), "r"); if (pipe == nullptr) { diff --git a/toolsrc/src/vcpkg/commands.version.cpp b/toolsrc/src/vcpkg/commands.version.cpp index 7029c8bfd..8ef29b116 100644 --- a/toolsrc/src/vcpkg/commands.version.cpp +++ b/toolsrc/src/vcpkg/commands.version.cpp @@ -32,7 +32,7 @@ namespace vcpkg::Commands::Version #ifndef NDEBUG + std::string("-debug") #endif - + std::string(Metrics::get_compiled_metrics_enabled() ? "" : "-external"); + ; return S_VERSION; } diff --git a/toolsrc/src/vcpkg/metrics.cpp b/toolsrc/src/vcpkg/metrics.cpp index d85d58cd3..00c6d2f8f 100644 --- a/toolsrc/src/vcpkg/metrics.cpp +++ b/toolsrc/src/vcpkg/metrics.cpp @@ -6,6 +6,7 @@ #include <vcpkg/base/chrono.h> #include <vcpkg/base/files.h> #include <vcpkg/base/hash.h> +#include <vcpkg/base/json.h> #include <vcpkg/base/strings.h> #include <vcpkg/base/system.process.h> @@ -29,88 +30,79 @@ namespace vcpkg::Metrics return ""; } - static std::string generate_random_UUID() + // note: this ignores the bits of these numbers that would be where format and variant go + static std::string uuid_of_integers(uint64_t top, uint64_t bottom) { - int part_sizes[] = {8, 4, 4, 4, 12}; - char uuid[37]; - memset(uuid, 0, sizeof(uuid)); - int num; - srand(static_cast<int>(time(nullptr))); - int index = 0; - for (int part = 0; part < 5; part++) + // uuid_field_size in bytes, not hex characters + constexpr size_t uuid_top_field_size[] = {4, 2, 2}; + constexpr size_t uuid_bottom_field_size[] = {2, 6}; + + // uuid_field_size in hex characters, not bytes + constexpr size_t uuid_size = 8 + 1 + 4 + 1 + 4 + 1 + 4 + 1 + 12; + + constexpr static char hex[17] = "0123456789abcdef"; + constexpr static auto write_byte = [](std::string& res, std::uint8_t bits) { + res.push_back(hex[(bits >> 4) & 0x0F]); + res.push_back(hex[(bits >> 0) & 0x0F]); + }; + + // set the version bits to 4 + top &= 0xFFFF'FFFF'FFFF'0FFFULL; + top |= 0x0000'0000'0000'4000ULL; + + // set the variant bits to 2 (variant one) + bottom &= 0x3FFF'FFFF'FFFF'FFFFULL; + bottom |= 0x8000'0000'0000'0000ULL; + + std::string res; + res.reserve(uuid_size); + + bool first = true; + size_t start_byte = 0; + for (auto field_size : uuid_top_field_size) { - if (part > 0) + if (!first) { - uuid[index] = '-'; - index++; + res.push_back('-'); } - - // Generating UUID format version 4 - // http://en.wikipedia.org/wiki/Universally_unique_identifier - for (int i = 0; i < part_sizes[part]; i++, index++) + first = false; + for (size_t i = start_byte; i < start_byte + field_size; ++i) { - if (part == 2 && i == 0) - { - num = 4; - } - else if (part == 4 && i == 0) - { - num = (rand() % 4) + 8; - } - else - { - num = rand() % 16; - } + auto shift = 64 - (i + 1) * 8; + write_byte(res, (top >> shift) & 0xFF); + } + start_byte += field_size; + } - if (num < 10) - { - uuid[index] = static_cast<char>('0' + num); - } - else - { - uuid[index] = static_cast<char>('a' + (num - 10)); - } + start_byte = 0; + for (auto field_size : uuid_bottom_field_size) + { + res.push_back('-'); + for (size_t i = start_byte; i < start_byte + field_size; ++i) + { + auto shift = 64 - (i + 1) * 8; + write_byte(res, (bottom >> shift) & 0xFF); } + start_byte += field_size; } - return uuid; + return res; } - static const std::string& get_session_id() + // UUID format version 4, variant 1 + // http://en.wikipedia.org/wiki/Universally_unique_identifier + // [0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12} + static std::string generate_random_UUID() { - static const std::string ID = generate_random_UUID(); - return ID; + std::random_device rnd{}; + std::uniform_int_distribution<std::uint64_t> uid{}; + return uuid_of_integers(uid(rnd), uid(rnd)); } - static std::string to_json_string(const std::string& str) + static const std::string& get_session_id() { - std::string encoded = "\""; - for (auto&& ch : str) - { - if (ch == '\\') - { - encoded.append("\\\\"); - } - else if (ch == '"') - { - encoded.append("\\\""); - } - else if (ch < 0x20 || static_cast<unsigned char>(ch) >= 0x80) - { - // Note: this treats incoming Strings as Latin-1 - static constexpr const char HEX[16] = { - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; - encoded.append("\\u00"); - encoded.push_back(HEX[ch / 16]); - encoded.push_back(HEX[ch % 16]); - } - else - { - encoded.push_back(ch); - } - } - encoded.push_back('"'); - return encoded; + static const std::string ID = generate_random_UUID(); + return ID; } static std::string get_os_version_string() @@ -150,91 +142,89 @@ namespace vcpkg::Metrics std::string user_id = generate_random_UUID(); std::string user_timestamp; std::string timestamp = get_current_date_time(); - std::string properties; - std::string measurements; - std::vector<std::string> buildtime_names; - std::vector<std::string> buildtime_times; + Json::Object properties; + Json::Object measurements; + + Json::Array buildtime_names; + Json::Array buildtime_times; void track_property(const std::string& name, const std::string& value) { - if (properties.size() != 0) properties.push_back(','); - properties.append(to_json_string(name)); - properties.push_back(':'); - properties.append(to_json_string(value)); + properties.insert_or_replace(name, Json::Value::string(value)); } void track_metric(const std::string& name, double value) { - if (measurements.size() != 0) measurements.push_back(','); - measurements.append(to_json_string(name)); - measurements.push_back(':'); - measurements.append(std::to_string(value)); + measurements.insert_or_replace(name, Json::Value::number(value)); } void track_buildtime(const std::string& name, double value) { - buildtime_names.push_back(name); - buildtime_times.push_back(std::to_string(value)); + buildtime_names.push_back(Json::Value::string(name)); + buildtime_times.push_back(Json::Value::number(value)); } std::string format_event_data_template() const { - auto props_plus_buildtimes = properties; + auto props_plus_buildtimes = properties.clone(); if (buildtime_names.size() > 0) { - if (props_plus_buildtimes.size() > 0) props_plus_buildtimes.push_back(','); - props_plus_buildtimes.append(Strings::format(R"("buildnames_1": [%s], "buildtimes": [%s])", - Strings::join(",", buildtime_names, to_json_string), - Strings::join(",", buildtime_times))); + props_plus_buildtimes.insert("buildnames_1", Json::Value::array(buildtime_names.clone())); + props_plus_buildtimes.insert("buildtimes", Json::Value::array(buildtime_times.clone())); } - const std::string& session_id = get_session_id(); - return Strings::format(R"([{ - "ver": 1, - "name": "Microsoft.ApplicationInsights.Event", - "time": "%s", - "sampleRate": 100.000000, - "seq": "0:0", - "iKey": "b4e88960-4393-4dd9-ab8e-97e8fe6d7603", - "flags": 0.000000, - "tags": { - "ai.device.os": "Other", - "ai.device.osVersion": "%s-%s", - "ai.session.id": "%s", - "ai.user.id": "%s", - "ai.user.accountAcquisitionDate": "%s" - }, - "data": { - "baseType": "EventData", - "baseData": { - "ver": 2, - "name": "commandline_test7", - "properties": { %s }, - "measurements": { %s } - } - } -}])", - timestamp, + Json::Array arr = Json::Array(); + Json::Object& obj = arr.push_back(Json::Object()); + + obj.insert("ver", Json::Value::integer(1)); + obj.insert("name", Json::Value::string("Microsoft.ApplicationInsights.Event")); + obj.insert("time", Json::Value::string(timestamp)); + obj.insert("sampleRate", Json::Value::number(100.0)); + obj.insert("seq", Json::Value::string("0:0")); + obj.insert("iKey", Json::Value::string("b4e88960-4393-4dd9-ab8e-97e8fe6d7603")); + obj.insert("flags", Json::Value::integer(0)); + + { + Json::Object& tags = obj.insert("tags", Json::Object()); + + tags.insert("ai.device.os", Json::Value::string("Other")); + + const char* os_name = #if defined(_WIN32) - "Windows", + "Windows"; #elif defined(__APPLE__) - "OSX", + "OSX"; #elif defined(__linux__) - "Linux", + "Linux"; #elif defined(__FreeBSD__) - "FreeBSD", + "FreeBSD"; #elif defined(__unix__) - "Unix", + "Unix"; #else - "Other", + "Other"; #endif - get_os_version_string(), - session_id, - user_id, - user_timestamp, - props_plus_buildtimes, - measurements); + + tags.insert("ai.device.osVersion", + Json::Value::string(Strings::format("%s-%s", os_name, get_os_version_string()))); + tags.insert("ai.session.id", Json::Value::string(get_session_id())); + tags.insert("ai.user.id", Json::Value::string(user_id)); + tags.insert("ai.user.accountAcquisitionDate", Json::Value::string(user_timestamp)); + } + + { + Json::Object& data = obj.insert("data", Json::Object()); + + data.insert("baseType", Json::Value::string("EventData")); + Json::Object& base_data = data.insert("baseData", Json::Object()); + + base_data.insert("ver", Json::Value::integer(2)); + base_data.insert("name", Json::Value::string("commandline_test7")); + base_data.insert("properties", Json::Value::object(std::move(props_plus_buildtimes))); + base_data.insert("measurements", Json::Value::object(measurements.clone())); + } + + return Json::stringify(arr, vcpkg::Json::JsonStyle()); } }; @@ -247,12 +237,39 @@ namespace vcpkg::Metrics #endif ; static bool g_should_print_metrics = false; + static bool g_metrics_disabled = +#if VCPKG_DISABLE_METRICS + true +#else + false +#endif + ; - bool get_compiled_metrics_enabled() { return !VCPKG_DISABLE_METRICS; } + // for child vcpkg processes, we also want to disable metrics + static void set_vcpkg_disable_metrics_environment_variable(bool disabled) + { +#if defined(_WIN32) + SetEnvironmentVariableW(L"VCPKG_DISABLE_METRICS", disabled ? L"1" : nullptr); +#else + if (disabled) + { + setenv("VCPKG_DISABLE_METRICS", "1", true); + } + else + { + unsetenv("VCPKG_DISABLE_METRICS"); + } +#endif + } std::string get_MAC_user() { #if defined(_WIN32) + if (!g_metrics.lock()->metrics_enabled()) + { + return "{}"; + } + auto getmac = System::cmd_execute_and_capture_output("getmac"); if (getmac.exit_code != 0) return "0"; @@ -293,20 +310,55 @@ namespace vcpkg::Metrics void Metrics::set_print_metrics(bool should_print_metrics) { g_should_print_metrics = should_print_metrics; } - void Metrics::track_metric(const std::string& name, double value) { g_metricmessage.track_metric(name, value); } + void Metrics::set_disabled(bool disabled) + { + set_vcpkg_disable_metrics_environment_variable(disabled); + g_metrics_disabled = disabled; + } + + bool Metrics::metrics_enabled() + { +#if VCPKG_DISABLE_METRICS + return false; +#else + return !g_metrics_disabled; +#endif + } + + void Metrics::track_metric(const std::string& name, double value) + { + if (!metrics_enabled()) + { + return; + } + g_metricmessage.track_metric(name, value); + } void Metrics::track_buildtime(const std::string& name, double value) { + if (!metrics_enabled()) + { + return; + } g_metricmessage.track_buildtime(name, value); } void Metrics::track_property(const std::string& name, const std::string& value) { + if (!metrics_enabled()) + { + return; + } g_metricmessage.track_property(name, value); } void Metrics::upload(const std::string& payload) { + if (!metrics_enabled()) + { + return; + } + #if !defined(_WIN32) Util::unused(payload); #else @@ -402,6 +454,11 @@ namespace vcpkg::Metrics void Metrics::flush() { + if (!metrics_enabled()) + { + return; + } + const std::string payload = g_metricmessage.format_event_data_template(); if (g_should_print_metrics) std::cerr << payload << "\n"; if (!g_should_send_metrics) return; diff --git a/toolsrc/src/vcpkg/vcpkgcmdarguments.cpp b/toolsrc/src/vcpkg/vcpkgcmdarguments.cpp index 4c49d1108..c2a9e7095 100644 --- a/toolsrc/src/vcpkg/vcpkgcmdarguments.cpp +++ b/toolsrc/src/vcpkg/vcpkgcmdarguments.cpp @@ -212,6 +212,11 @@ namespace vcpkg parse_switch(true, "printmetrics", args.printmetrics); continue; } + if (arg == "--disable-metrics") + { + parse_switch(true, "printmetrics", args.disable_metrics); + continue; + } if (arg == "--no-sendmetrics") { parse_switch(false, "sendmetrics", args.sendmetrics); @@ -222,6 +227,11 @@ namespace vcpkg parse_switch(false, "printmetrics", args.printmetrics); continue; } + if (arg == "--no-disable-metrics") + { + parse_switch(false, "printmetrics", args.disable_metrics); + continue; + } if (arg == "--featurepackages") { parse_switch(true, "featurepackages", args.featurepackages); |
