[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
This commit is contained in:
nicole mazzuca 2020-05-29 14:09:03 -07:00 committed by GitHub
parent a64dc07690
commit 09319cd79e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 644 additions and 356 deletions

View File

@ -80,6 +80,10 @@ Code licensed under the [MIT License](LICENSE.txt).
## Telemetry
vcpkg collects usage data in order to help us improve your experience. The data collected by Microsoft is anonymous. You can opt-out of telemetry by running `bootstrap-vcpkg.bat` or `bootstrap-vcpkg.sh` with `-disableMetrics`.
vcpkg collects usage data in order to help us improve your experience.
The data collected by Microsoft is anonymous.
You can opt-out of telemetry by re-running the bootstrap-vcpkg script with -disableMetrics,
passing --disable-metrics to vcpkg on the command line,
or by setting the VCPKG_DISABLE_METRICS environment variable.
Read more about vcpkg telemetry at docs/about/privacy.md

View File

@ -22,7 +22,11 @@ vcpkg displays text similar to the following when you build vcpkg. This is how M
```
Telemetry
---------
vcpkg collects usage data in order to help us improve your experience. The data collected by Microsoft is anonymous. You can opt-out of telemetry by re-running the bootstrap-vcpkg script with -disableMetrics.
vcpkg collects usage data in order to help us improve your experience.
The data collected by Microsoft is anonymous.
You can opt-out of telemetry by re-running the bootstrap-vcpkg script with -disableMetrics,
passing --disable-metrics to vcpkg on the command line,
or by setting the VCPKG_DISABLE_METRICS environment variable.
Read more about vcpkg telemetry at docs/about/privacy.md
```

View File

@ -417,9 +417,13 @@ if (-not $disableMetrics)
Write-Host @"
Telemetry
---------
vcpkg collects usage data in order to help us improve your experience. The data collected by Microsoft is anonymous. You can opt-out of telemetry by re-running bootstrap-vcpkg.bat with -disableMetrics.
Read more about vcpkg telemetry at docs/about/privacy.md
vcpkg collects usage data in order to help us improve your experience.
The data collected by Microsoft is anonymous.
You can opt-out of telemetry by re-running the bootstrap-vcpkg script with -disableMetrics,
passing --disable-metrics to vcpkg on the command line,
or by setting the VCPKG_DISABLE_METRICS environment variable.
Read more about vcpkg telemetry at docs/about/privacy.md
"@
}

View File

@ -256,9 +256,15 @@ rm -rf "$vcpkgRootDir/vcpkg"
cp "$buildDir/vcpkg" "$vcpkgRootDir/"
if ! [ "$vcpkgDisableMetrics" = "ON" ]; then
echo "Telemetry"
echo "---------"
echo "vcpkg collects usage data in order to help us improve your experience. The data collected by Microsoft is anonymous. You can opt-out of telemetry by re-running bootstrap-vcpkg.sh with -disableMetrics"
echo "Read more about vcpkg telemetry at docs/about/privacy.md"
echo ""
cat <<EOF
Telemetry
---------
vcpkg collects usage data in order to help us improve your experience.
The data collected by Microsoft is anonymous.
You can opt-out of telemetry by re-running the bootstrap-vcpkg script with -disableMetrics,
passing --disable-metrics to vcpkg on the command line,
or by setting the VCPKG_DISABLE_METRICS environment variable.
Read more about vcpkg telemetry at docs/about/privacy.md
EOF
fi

View File

@ -32,3 +32,6 @@ ForEachMacros: [TEST_CASE, SECTION]
PenaltyReturnTypeOnItsOwnLine: 1000
SpaceAfterTemplateKeyword: false
SpaceBeforeCpp11BracedList: false
IncludeBlocks: Preserve
SortIncludes: false

View File

@ -9,6 +9,12 @@
#include <winhttp.h>
#endif
#include <math.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <algorithm>
#include <array>
#include <atomic>
@ -16,10 +22,6 @@
#include <cctype>
#include <chrono>
#include <codecvt>
#include <cstdarg>
#include <cstddef>
#include <cstdint>
#include <cstring>
#if VCPKG_USE_STD_FILESYSTEM
#include <filesystem>

View File

@ -70,6 +70,7 @@ namespace vcpkg::Json
{
Null,
Boolean,
Integer,
Number,
String,
Array,
@ -84,10 +85,19 @@ namespace vcpkg::Json
struct Value
{
Value() noexcept; // equivalent to Value::null()
Value(Value&&) noexcept;
Value& operator=(Value&&) noexcept;
~Value();
Value clone() const noexcept;
ValueKind kind() const noexcept;
bool is_null() const noexcept;
bool is_boolean() const noexcept;
bool is_integer() const noexcept;
// either integer _or_ number
bool is_number() const noexcept;
bool is_string() const noexcept;
bool is_array() const noexcept;
@ -95,7 +105,8 @@ namespace vcpkg::Json
// a.x() asserts when !a.is_x()
bool boolean() const noexcept;
int64_t number() const noexcept;
int64_t integer() const noexcept;
double number() const noexcept;
StringView string() const noexcept;
const Array& array() const noexcept;
@ -104,18 +115,13 @@ namespace vcpkg::Json
const Object& object() const noexcept;
Object& object() noexcept;
Value(Value&&) noexcept;
Value& operator=(Value&&) noexcept;
~Value();
Value() noexcept; // equivalent to Value::null()
static Value null(std::nullptr_t) noexcept;
static Value boolean(bool) noexcept;
static Value number(int64_t i) noexcept;
static Value integer(int64_t i) noexcept;
static Value number(double d) noexcept;
static Value string(StringView) noexcept;
static Value array(Array&&) noexcept;
static Value object(Object&&) noexcept;
Value clone() const noexcept;
private:
friend struct impl::ValueImpl;
@ -128,11 +134,24 @@ namespace vcpkg::Json
using underlying_t = std::vector<Value>;
public:
Array() = default;
Array(Array const&) = delete;
Array(Array&&) = default;
Array& operator=(Array const&) = delete;
Array& operator=(Array&&) = default;
~Array() = default;
Array clone() const noexcept;
using iterator = underlying_t::iterator;
using const_iterator = underlying_t::const_iterator;
void push_back(Value&& value) { this->underlying_.push_back(std::move(value)); }
void insert_before(iterator it, Value&& value) { this->underlying_.insert(it, std::move(value)); }
Value& push_back(Value&& value);
Object& push_back(Object&& value);
Array& push_back(Array&& value);
Value& insert_before(iterator it, Value&& value);
Object& insert_before(iterator it, Object&& value);
Array& insert_before(iterator it, Array&& value);
std::size_t size() const noexcept { return this->underlying_.size(); }
@ -148,8 +167,6 @@ namespace vcpkg::Json
return this->underlying_[idx];
}
Array clone() const noexcept;
iterator begin() { return underlying_.begin(); }
iterator end() { return underlying_.end(); }
const_iterator begin() const { return cbegin(); }
@ -160,7 +177,6 @@ namespace vcpkg::Json
private:
underlying_t underlying_;
};
struct Object
{
private:
@ -169,12 +185,26 @@ namespace vcpkg::Json
underlying_t::const_iterator internal_find_key(StringView key) const noexcept;
public:
// these are here for better diagnostics
Object() = default;
Object(Object const&) = delete;
Object(Object&&) = default;
Object& operator=(Object const&) = delete;
Object& operator=(Object&&) = default;
~Object() = default;
Object clone() const noexcept;
// asserts if the key is found
void insert(std::string key, Value value) noexcept;
Value& insert(std::string key, Value&& value);
Object& insert(std::string key, Object&& value);
Array& insert(std::string key, Array&& value);
// replaces the value if the key is found, otherwise inserts a new
// value.
void insert_or_replace(std::string key, Value value) noexcept;
Value& insert_or_replace(std::string key, Value&& value);
Object& insert_or_replace(std::string key, Object&& value);
Array& insert_or_replace(std::string key, Array&& value);
// returns whether the key existed
bool remove(StringView key) noexcept;
@ -200,8 +230,6 @@ namespace vcpkg::Json
std::size_t size() const noexcept { return this->underlying_.size(); }
Object clone() const noexcept;
struct const_iterator
{
using value_type = std::pair<StringView, const Value&>;
@ -245,6 +273,9 @@ namespace vcpkg::Json
const Files::Filesystem&, const fs::path&, std::error_code& ec) noexcept;
ExpectedT<std::pair<Value, JsonStyle>, std::unique_ptr<Parse::IParseError>> parse(
StringView text, const fs::path& filepath = "") noexcept;
std::string stringify(const Value&, JsonStyle style) noexcept;
std::string stringify(const Value&, JsonStyle style);
std::string stringify(const Object&, JsonStyle style);
std::string stringify(const Array&, JsonStyle style);
}

View File

@ -10,6 +10,7 @@ namespace vcpkg::Metrics
{
void set_send_metrics(bool should_send_metrics);
void set_print_metrics(bool should_print_metrics);
void set_disabled(bool disabled);
void set_user_information(const std::string& user_id, const std::string& first_use_time);
static void init_user_information(std::string& user_id, std::string& first_use_time);
@ -17,6 +18,8 @@ namespace vcpkg::Metrics
void track_buildtime(const std::string& name, double value);
void track_property(const std::string& name, const std::string& value);
bool metrics_enabled();
void upload(const std::string& payload);
void flush();
};
@ -24,5 +27,4 @@ namespace vcpkg::Metrics
extern Util::LockGuarded<Metrics> g_metrics;
std::string get_MAC_user();
bool get_compiled_metrics_enabled();
}

View File

@ -94,6 +94,8 @@ namespace vcpkg
std::vector<std::string> binarysources;
Optional<bool> debug = nullopt;
Optional<bool> sendmetrics = nullopt;
// fully disable metrics -- both printing and sending
Optional<bool> disable_metrics = nullopt;
Optional<bool> printmetrics = nullopt;
// feature flags

View File

@ -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.number() == (-9223372036854775807 - 1)); // INT64_MIN (C++'s parser is fun)
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_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);
}

View File

@ -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);

View File

@ -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_number());
return underlying_->number;
vcpkg::Checks::check_exit(VCPKG_LINE_INFO, is_integer());
return underlying_->integer;
}
double Value::number() const noexcept
{
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 == '.')
{
if (is_digit(current))
{
add_error("Unexpected digits after a leading zero");
}
if (current == '.')
{
add_error("Found a `.` -- this JSON implementation does not support floating point");
}
number_to_parse.append("0.");
floating = true;
current = next();
}
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))
{
const int digit = current - '0';
// result * 10 - digit < min_value : remember that result < 0
if (result < (min_value + digit) / 10)
else if (is_digit(current))
{
add_error("Number is too big for an int64_t");
add_error("Unexpected digits after a leading zero");
return Value();
}
result *= 10;
result -= digit;
else
{
if (negative)
{
return Value::number(-0.0);
}
else
{
return Value::integer(0);
}
}
}
while (is_digit(current))
{
number_to_parse.push_back(static_cast<char>(current));
current = next();
}
if (current == '.')
if (!floating && current == '.')
{
add_error("Found a `.` -- this JSON implementation doesn't support floating point");
return Value();
}
if (!negative)
{
if (result == min_value)
floating = true;
number_to_parse.push_back('.');
current = next();
if (!is_digit(current))
{
add_error("Number is too big for a uint64_t");
add_error("Expected digits after the decimal point");
return Value();
}
result = -result;
while (is_digit(current))
{
number_to_parse.push_back(static_cast<char>(current));
current = next();
}
}
return Value::number(result);
#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)
{
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);
}
else
{
int64_t res;
if (SCANF(number_to_parse.c_str(), "%" SCNd64, &res) != 1)
{
add_error(Strings::format("Invalid integer constant: %s", number_to_parse));
return Value();
}
return Value::integer(res);
}
#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,118 +906,120 @@ namespace vcpkg::Json
}
// } auto parse()
// auto stringify() {
static std::string& append_unicode_escape(std::string& s, char16_t code_unit)
namespace
{
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)
{
// 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");
// 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'};
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]);
}
// 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);
}
// 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('"');
// 3. Set product to the string-concatenation of product and 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()))
{
// 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;
}
// 4. Return product.
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 std::string quote_json_string(StringView sv)
{
std::string product;
append_quoted_json_string(product, sv);
return product;
}
// 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);
}
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');
// 3. Set product to the string-concatenation of product and the code unit 0x0022 (QUOTATION MARK).
buffer.push_back('"');
}
else
void stringify_object(const Object& obj, int current_indent)
{
buffer.append(indent * style.spaces(), ' ');
buffer.push_back('{');
if (obj.size() != 0)
{
bool first = true;
for (const auto& el : obj)
{
if (!first)
{
buffer.push_back(',');
}
first = false;
buffer.append(style.newline());
append_indent(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('}');
}
};
switch (value.kind())
{
case VK::Null: buffer.append("null"); break;
case VK::Boolean:
void stringify_array(const Array& arr, int current_indent)
{
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;
}
case VK::Array:
{
const auto& arr = value.array();
buffer.push_back('[');
if (arr.size() == 0)
{
@ -936,51 +1040,64 @@ namespace vcpkg::Json
buffer.append(style.newline());
append_indent(current_indent + 1);
internal_stringify(el, style, buffer, current_indent + 1);
stringify(el, current_indent + 1);
}
buffer.append(style.newline());
append_indent(current_indent);
buffer.push_back(']');
}
break;
}
case VK::Object:
void stringify(const Value& value, int current_indent)
{
const auto& obj = value.object();
buffer.push_back('{');
if (obj.size() != 0)
switch (value.kind())
{
bool first = true;
for (const auto& el : obj)
case VK::Null: buffer.append("null"); break;
case VK::Boolean:
{
if (!first)
{
buffer.push_back(',');
}
first = false;
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);
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;
}
buffer.append(style.newline());
append_indent(current_indent);
}
buffer.push_back('}');
break;
}
}
};
}
std::string stringify(const Value& value, JsonStyle style) noexcept
std::string stringify(const Value& value, JsonStyle style)
{
std::string res;
internal_stringify(value, style, res, 0);
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;
Stringifier{style, res}.stringify_array(arr, 0);
return res;
}
// } auto stringify()

View File

@ -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)
{

View File

@ -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;
}

View File

@ -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,51 +30,73 @@ 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;
}
if (num < 10)
{
uuid[index] = static_cast<char>('0' + num);
}
else
{
uuid[index] = static_cast<char>('a' + (num - 10));
}
auto shift = 64 - (i + 1) * 8;
write_byte(res, (top >> shift) & 0xFF);
}
start_byte += field_size;
}
return uuid;
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 res;
}
// 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()
{
std::random_device rnd{};
std::uniform_int_distribution<std::uint64_t> uid{};
return uuid_of_integers(uid(rnd), uid(rnd));
}
static const std::string& get_session_id()
@ -82,37 +105,6 @@ namespace vcpkg::Metrics
return ID;
}
static std::string to_json_string(const std::string& str)
{
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 std::string get_os_version_string()
{
#if defined(_WIN32)
@ -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;

View File

@ -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);