diff options
| author | Andrey Lihatskiy <alihatskiy@productengine.com> | 2021-04-20 17:06:35 +0300 | 
|---|---|---|
| committer | Andrey Lihatskiy <alihatskiy@productengine.com> | 2021-04-20 17:06:35 +0300 | 
| commit | 99eaddad90937f881bf0b5108babb98206a5cb0b (patch) | |
| tree | 976173918a66598872011d36bdc902f1e7fe3308 | |
| parent | 56eda0385e91d6ede51f874321bbd8ebfd293c2f (diff) | |
| parent | d2f76612a7e47073e5e780a99866afc81efbbda9 (diff) | |
Merge branch 'SL-14999' into DRTVWR-516-maint
| -rw-r--r-- | indra/newview/CMakeLists.txt | 4 | ||||
| -rw-r--r-- | indra/newview/tests/cppfeatures_test.cpp | 386 | ||||
| -rwxr-xr-x | scripts/metrics/viewerstats.py | 226 | 
3 files changed, 616 insertions, 0 deletions
| diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 87ee77a117..52b14d1fd5 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -2524,6 +2524,10 @@ if (LL_TESTS)      ${BOOST_CONTEXT_LIBRARY}    ) +  LL_ADD_INTEGRATION_TEST(cppfeatures +    "" +    "${test_libs}" +    )    LL_ADD_INTEGRATION_TEST(llsechandler_basic      llsechandler_basic.cpp      "${test_libs}" diff --git a/indra/newview/tests/cppfeatures_test.cpp b/indra/newview/tests/cppfeatures_test.cpp new file mode 100644 index 0000000000..923bb1e1b2 --- /dev/null +++ b/indra/newview/tests/cppfeatures_test.cpp @@ -0,0 +1,386 @@ +/** + * @file cppfeatures_test + * @author Vir + * @date 2021-03 + * @brief cpp features + * + * $LicenseInfo:firstyear=2021&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2021, Linden Research, Inc. + *  + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + *  + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * Lesser General Public License for more details. + *  + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA + *  + * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA + * $/LicenseInfo$ + */ + +// Tests related to newer C++ features, for verifying support across compilers and platforms + +#include "linden_common.h" +#include "../test/lltut.h" + +namespace tut +{ + +struct cpp_features_test {}; +typedef test_group<cpp_features_test> cpp_features_test_t; +typedef cpp_features_test_t::object cpp_features_test_object_t; +tut::cpp_features_test_t tut_cpp_features_test("LLCPPFeatures"); + +// bracket initializers +// Can initialize containers or values using curly brackets +template<> template<> +void cpp_features_test_object_t::test<1>() +{ +	S32 explicit_val{3}; +	ensure(explicit_val==3); + +	S32 default_val{}; +	ensure(default_val==0); +	 +	std::vector<S32> fibs{1,1,2,3,5}; +	ensure(fibs[4]==5); +} + +// auto +// +// https://en.cppreference.com/w/cpp/language/auto +//  +// Can use auto in place of a more complex type specification, if the compiler can infer the type +template<> template<> +void cpp_features_test_object_t::test<2>() +{ +	std::vector<S32> numbers{3,6,9}; + +	// auto element +	auto& aval = numbers[1]; +	ensure("auto element", aval==6); + +	// auto iterator (non-const) +	auto it = numbers.rbegin(); +	*it += 1; +	S32 val = *it; +	ensure("auto iterator", val==10); +} + +// range for +// +// https://en.cppreference.com/w/cpp/language/range-for +// +// Can iterate over containers without explicit iterator +template<> template<> +void cpp_features_test_object_t::test<3>() +{ + +	// Traditional iterator for with container +	// +	// Problems: +	// * Have to create a new variable for the iterator, which is unrelated to the problem you're trying to solve. +	// * Redundant and somewhat fragile. Have to make sure begin() and end() are both from the right container. +	std::vector<S32> numbers{3,6,9}; +	for (auto it = numbers.begin(); it != numbers.end(); ++it) +	{ +		auto& n = *it; +		n *= 2; +	} +	ensure("iterator for vector", numbers[2]==18); + +	// Range for with container +	// +	// Under the hood, this is doing the same thing as the traditional +	// for loop above. Still uses begin() and end() but you don't have +	// to access them directly. +	std::vector<S32> numbersb{3,6,9}; +	for (auto& n: numbersb) +	{ +		n *= 2; +	} +	ensure("range for vector", numbersb[2]==18); + +	// Range for over a C-style array. +	// +	// This is handy because the language determines the range automatically. +	// Getting this right manually is a little trickier. +	S32 pows[] = {1,2,4,8,16}; +	S32 sum{}; +	for (const auto& v: pows) +	{ +		sum += v; +	} +	ensure("for C-array", sum==31); +} + +// override specifier +// +// https://en.cppreference.com/w/cpp/language/override +// +// Specify that a particular class function is an override of a virtual function. +// Benefits: +// * Makes code somewhat easier to read by showing intent. +// * Prevents mistakes where you think something is an override but it doesn't actually match the declaration in the parent class. +// Drawbacks: +// * Some compilers require that any class using override must use it consistently for all functions.  +//   This makes switching a class to use override a lot more work.  + +class Foo +{ +public: +	virtual bool is_happy() const = 0; +}; + +class Bar: public Foo +{ +public: +	bool is_happy() const override { return true; }  +	// Override would fail: non-const declaration doesn't match parent  +	// bool is_happy() override { return true; }  +	// Override would fail: wrong name +	// bool is_happx() override { return true; }  +}; + +template<> template<> +void cpp_features_test_object_t::test<4>() +{ +	Bar b; +	ensure("override", b.is_happy()); +} + +// final +// +// https://en.cppreference.com/w/cpp/language/final: "Specifies that a +// virtual function cannot be overridden in a derived class or that a +// class cannot be inherited from." + +class Vehicle +{ +public: +	virtual bool has_wheels() const = 0; +}; + +class WheeledVehicle: public Vehicle +{ +public: +	virtual bool has_wheels() const final override { return true; } +}; + +class Bicycle: public WheeledVehicle +{ +public: +	// Error: can't override final version in WheeledVehicle  +	// virtual bool has_wheels() override const { return true; } +}; + +template<> template<> +void cpp_features_test_object_t::test<5>() +{ +	Bicycle bi; +	ensure("final", bi.has_wheels()); +} + +// deleted function declaration +// +// https://en.cppreference.com/w/cpp/language/function#Deleted_functions +// +// Typical case: copy constructor doesn't make sense for a particular class, so you want to make +// sure the no one tries to copy-construct an instance of the class, and that the +// compiler won't generate a copy constructor for  you automatically. +// Traditional fix is to declare a +// copy constructor but never implement it, giving you a link-time error if anyone tries to use it. +// Now you can explicitly declare a function to be deleted, which has at least two advantages over +// the old way: +// * Makes the intention clear +// * Creates an error sooner, at compile time + +class DoNotCopy +{ +public: +	DoNotCopy() {} +	DoNotCopy(const DoNotCopy& ref) = delete; +}; + +template<> template<> +void cpp_features_test_object_t::test<6>() +{ +	DoNotCopy nc; // OK, default constructor +	//DoNotCopy nc2(nc); // No, can't copy +	//DoNotCopy nc3 = nc; // No, this also calls copy constructor (even though it looks like an assignment) +} + +// defaulted function declaration +// +// https://en.cppreference.com/w/cpp/language/function#Function_definition +// +// What about the complementary case to the deleted function declaration, where you want a copy constructor +// and are happy with the default implementation the compiler will make (memberwise copy). +// Now you can explicitly declare that too. +// Usage: I guess it makes the intent clearer, but otherwise not obviously useful. +class DefaultCopyOK +{ +public: +	DefaultCopyOK(): mVal(123) {} +	DefaultCopyOK(const DefaultCopyOK&) = default; +	S32 val() const { return mVal; } +private: +	S32 mVal; +}; + +template<> template<> +void cpp_features_test_object_t::test<7>() +{ +	DefaultCopyOK d; // OK +	DefaultCopyOK d2(d); // OK +	DefaultCopyOK d3 = d; // OK +	ensure("default copy d", d.val()==123); +	ensure("default copy d2", d.val()==d2.val()); +	ensure("default copy d3", d.val()==d3.val()); +} + +// initialize class members inline +// +// https://en.cppreference.com/w/cpp/language/data_members#Member_initialization +// +// Default class member values can be set where they are declared, using either brackets or = + +// It is preferred to skip creating a constructor if all the work can be done by inline initialization: +// http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines.html#c45-dont-define-a-default-constructor-that-only-initializes-data-members-use-in-class-member-initializers-instead +// +class InitInline +{ +public: +	S32 mFoo{10}; +}; + +class InitInlineWithConstructor +{ +public: +	// Here mFoo is not specified, so you will get the default value of 10. +	// mBar is specified, so 25 will override the default value. +	InitInlineWithConstructor(): +		mBar(25) +	{} + +	// Default values set using two different styles, same effect. +	S32 mFoo{10}; +	S32 mBar = 20; +}; + +template<> template<> +void cpp_features_test_object_t::test<8>() +{ +	InitInline ii; +	ensure("init member inline 1", ii.mFoo==10); + +	InitInlineWithConstructor iici; +	ensure("init member inline 2", iici.mFoo=10); +	ensure("init member inline 3", iici.mBar==25); +} + +// constexpr +// +// https://en.cppreference.com/w/cpp/language/constexpr +// +// Various things can be computed at compile time, and flagged as constexpr. +constexpr S32 compute2() { return 2; } + +constexpr S32 ce_factorial(S32 n) +{ +	if (n<=0) +	{ +		return 1; +	} +	else +	{ +		return n*ce_factorial(n-1); +	} +} + +template<> template<> +void cpp_features_test_object_t::test<9>() +{ +	S32 val = compute2(); +	ensure("constexpr 1", val==2); + +	// Compile-time factorial. You used to need complex templates to do something this useless. +	S32 fac5 = ce_factorial(5); +	ensure("constexpr 2", fac5==120); +} + +// static assert +// +// https://en.cppreference.com/w/cpp/language/static_assert +// +// You can add asserts to be checked at compile time. The thing to be checked must be a constexpr. +// There are two forms: +// * static_assert(expr); +// * static_assert(expr, message); +// +// Currently only the 2-parameter form works on windows. The 1-parameter form needs a flag we don't set. + +template<> template<> +void cpp_features_test_object_t::test<10>() +{ +	// static_assert(ce_factorial(6)==720); No, needs a flag we don't currently set. +	static_assert(ce_factorial(6)==720, "bad factorial"); // OK +} + +// type aliases +// +// https://en.cppreference.com/w/cpp/language/type_alias +//  +// You can use the "using" statement to create simpler templates that +// are aliases for more complex ones. "Template typedef" + +// This makes stringmap<T> an alias for std::map<std::string, T> +template<typename T> +using stringmap = std::map<std::string, T>; + +template<> template<> +void cpp_features_test_object_t::test<11>() +{ +	stringmap<S32> name_counts{ {"alice", 3}, {"bob", 2} }; +	ensure("type alias", name_counts["bob"]==2); +} + +// Other possibilities: + +// nullptr + +// class enums + +// std::unique_ptr and make_unique + +// std::shared_ptr and make_shared + +// lambdas + +// perfect forwarding + +// variadic templates + +// std::thread + +// std::mutex + +// thread_local + +// rvalue reference && + +// move semantics + +// std::move + +// string_view + +} // namespace tut diff --git a/scripts/metrics/viewerstats.py b/scripts/metrics/viewerstats.py new file mode 100755 index 0000000000..f7be3d967e --- /dev/null +++ b/scripts/metrics/viewerstats.py @@ -0,0 +1,226 @@ +#!runpy.sh + +"""\ + +This module contains code for analyzing ViewerStats data as uploaded by the viewer. + +$LicenseInfo:firstyear=2021&license=viewerlgpl$ +Second Life Viewer Source Code +Copyright (C) 2021, Linden Research, Inc. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; +version 2.1 of the License only. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA + +Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA +$/LicenseInfo$ +""" + +import argparse +import numpy as np +import pandas as pd +import json +from collections import Counter, defaultdict +from llbase import llsd +import io +import re +import os +import sys + +def show_stats_by_key(recs,indices,settings_sd = None): +    result = () +    cnt = Counter() +    per_key_cnt = defaultdict(Counter) +    for r in recs: +        try: +            d = r +            for idx in indices: +                d = d[idx] +            for k,v in d.items(): +                if isinstance(v,dict): +                    continue +                cnt[k] += 1 +                if isinstance(v,list): +                    v = tuple(v) +                per_key_cnt[k][v] += 1 +        except Exception as e: +            print "err", e +            print "d", d, "k", k, "v", v +            raise +    mc = cnt.most_common() +    print "=========================" +    keyprefix = "" +    if len(indices)>0: +        keyprefix = ".".join(indices) + "." +    for i,m in enumerate(mc): +        k = m[0] +        bigc = m[1] +        unset_cnt = len(recs) - bigc +        kmc = per_key_cnt[k].most_common(5) +        print i, keyprefix+str(k), bigc +        if settings_sd is not None and k in settings_sd and "Value" in settings_sd[k]: +            print "    ", "default",settings_sd[k]["Value"],"count",unset_cnt +        for v in kmc: +            print "    ", "value",v[0],"count",v[1] +    if settings_sd is not None: +        print "Total keys in settings", len(settings_sd.keys()) +        unused_keys = list(set(settings_sd.keys()) - set(cnt.keys())) +        unused_keys_non_str = [k for k in unused_keys if settings_sd[k]["Type"] != "String"] +        unused_keys_str = [k for k in unused_keys if settings_sd[k]["Type"] == "String"] + +        # Things that no one in the sample has set to a non-default value. Possible candidates for removal. +        print "\nUnused_keys_non_str", len(unused_keys_non_str) +        print   "======================" +        print "\n".join(sorted(unused_keys_non_str)) + +        # Strings are not currently logged, so we have no info on usage. +        print "\nString keys (usage unknown)", len(unused_keys_str) +        print   "======================" +        print "\n".join(sorted(unused_keys_str)) + +        # Things that someone has set but that aren't recognized settings. +        unrec_keys = list(set(cnt.keys()) - set(settings_sd.keys())) +        print "\nUnrecognized keys", len(unrec_keys) +        print   "======================" +        print "\n".join(sorted(unrec_keys)) + +        result = (settings_sd.keys(), unused_keys_str, unused_keys_non_str, unrec_keys) +    return result + +def parse_settings_xml(fname): +    # assume we're in scripts/metrics +    fname = "../../indra/newview/app_settings/" + fname +    with open(fname,"r") as f: +        contents = f.read() +        return llsd.parse_xml(contents) + +def read_raw_settings_xml(fname): +    # assume we're in scripts/metrics +    fname = "../../indra/newview/app_settings/" + fname +    contents = None +    with open(fname,"r") as f: +        contents = f.read() +    return contents + +def write_settings_xml(fname, contents): +    # assume we're in scripts/metrics +    fname = "../../indra/newview/app_settings/" + fname +    with open(fname,"w") as f: +        f.write(llsd.format_pretty_xml(contents)) +        f.close() + +def write_raw_settings_xml(fname, string): +    # assume we're in scripts/metrics +    fname = "../../indra/newview/app_settings/" + fname +    with io.open(fname,"w", newline='\n') as f: +        f.write(string.decode('latin1')) +        f.close() + +def remove_settings(string, to_remove): +    for r in to_remove: +        subs_str = r"<key>" + r + r"<.*?</map>\n" +        string = re.sub(subs_str,"",string,flags=re.S|re.DOTALL) +    return string + +def get_used_strings(root_dir): +    used_str = set() +    skipped_ext = set() +    for dir_name, sub_dir_list, file_list in os.walk(root_dir): +        for fname in file_list: +            if fname in ["settings.xml", "settings.xml.edit", "settings_per_account.xml"]: +                print "skip", fname +                continue +            (base,ext) = os.path.splitext(fname) +            #if ext not in [".cpp", ".hpp", ".h", ".xml"]: +            #    skipped_ext.add(ext) +            #    continue +             +            full_name = os.path.join(dir_name,fname) + +            with open(full_name,"r") as f: +                #print full_name +                lines = f.readlines() +                for l in lines: +                    ms = re.findall(r'[>\"]([A-Za-z0-9_]+)[\"<]',l) +                    for m in ms: +                        #print "used_str",m +                        used_str.add(m) +    print "skipped extensions", skipped_ext +    print "got used_str", len(used_str) +    return used_str +                 +     +if __name__ == "__main__": + +    parser = argparse.ArgumentParser(description="process tab-separated table containing viewerstats logs") +    parser.add_argument("--verbose", action="store_true",help="verbose flag") +    parser.add_argument("--preferences", action="store_true", help="analyze preference info") +    parser.add_argument("--remove_unused", action="store_true", help="remove unused preferences") +    parser.add_argument("--column", help="name of column containing viewerstats info") +    parser.add_argument("infiles", nargs="+", help="name of .tsv files to process") +    args = parser.parse_args() + +    for fname in args.infiles: +        print "process", fname +        df = pd.read_csv(fname,sep='\t') +        #print "DF", df.describe() +        jstrs = df['RAW_LOG:BODY'] +        #print "JSTRS", jstrs.describe() +        recs = [] +        for i,jstr in enumerate(jstrs): +            recs.append(json.loads(jstr)) +        show_stats_by_key(recs,[]) +        show_stats_by_key(recs,["agent"]) +        if args.preferences: +            print "\nSETTINGS.XML" +            settings_sd = parse_settings_xml("settings.xml") +            #for skey,svals in settings_sd.items():  +            #    print skey, "=>", svals +            (all_str,_,_,_) = show_stats_by_key(recs,["preferences","settings"],settings_sd) +            print + +            #print "\nSETTINGS_PER_ACCOUNT.XML" +            #settings_pa_sd = parse_settings_xml("settings_per_account.xml") +            #show_stats_by_key(recs,["preferences","settings_per_account"],settings_pa_sd) + +            if args.remove_unused: +                # walk codebase looking for strings +                all_str_set = set(all_str) +                used_strings = get_used_strings("../../indra") +                used_strings_set = set(used_strings) +                unref_strings = all_str_set-used_strings_set +                # Some settings names are generated by appending to a prefix. Need to look for this case. +                prefix_used = set() +                print "checking unref_strings", len(unref_strings) +                for u in unref_strings: +                    for k in range(6,len(u)): +                        prefix = u[0:k] +                        if prefix in all_str_set and prefix in used_strings_set: +                            prefix_used.add(u) +                            #print "PREFIX_USED",u,prefix +                print "PREFIX_USED", len(prefix_used), ",".join(list(prefix_used)) +                print +                unref_strings = unref_strings - prefix_used +                         +                print "\nUNREF_IN_CODE " + str(len(unref_strings)) + "\n" +                print "\n".join(list(unref_strings)) +                settings_str = read_raw_settings_xml("settings.xml") +                # Do this via direct string munging to generate minimal changeset +                settings_edited = remove_settings(settings_str,unref_strings) +                write_raw_settings_xml("settings.xml.edit",settings_edited) +                     + + + +         +     | 
