Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 926fa88c authored by Ruchir Rastogi's avatar Ruchir Rastogi
Browse files

StatsEventCompat

StatsEventCompat is a wrapper library that toggles between the old
logging scheme and the new logging scheme. It was designed particularly
for the DNS Resolver module and should not be used by others.

We will merge libstatspush_compat and libstatssocket_q in a future CL.

Test: m libstatspush_compat
Test: bit libstatspush_compat_test:* (passes on Q and R)
Bug: 145534143

Change-Id: Ib355031f3573101ea90bd8694861fbfc33b0c788
Merged-In: Idf35ccb6669798166475f08b2fbab40534b5db19
parent a33f81bf
Loading
Loading
Loading
Loading
+54 −0
Original line number Diff line number Diff line
//
// Copyright (C) 2019 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

// =========================================================================
// Native library that toggles between the old and new statsd socket
// protocols. This library should only be used by DNS resolver or other
// native modules on Q that log pushed atoms to statsd.
// =========================================================================
cc_defaults {
    name: "libstatspush_compat_defaults",
    srcs: ["StatsEventCompat.cpp"],
    cflags: [
        "-Wall",
        "-Werror",
    ],
    header_libs: ["libstatssocket_headers"],
    static_libs: [
        "libbase",
        "liblog",
        "libstatssocket_q",
        "libutils"
    ],
}

cc_library {
    name: "libstatspush_compat",
    defaults: ["libstatspush_compat_defaults"],
    export_include_dirs: ["include"],
    static_libs: ["libgtest_prod"],
}

cc_test {
    name: "libstatspush_compat_test",
    defaults: ["libstatspush_compat_defaults"],
    test_suites: ["device_tests"],
    srcs: [
        "tests/StatsEventCompat_test.cpp",
    ],
    static_libs: ["libgmock"],
}
+221 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "include/StatsEventCompat.h"
#include <android-base/properties.h>
#include <android/api-level.h>
#include <android/log.h>
#include <dlfcn.h>
#include <utils/SystemClock.h>

using android::base::GetProperty;

const static int kStatsEventTag = 1937006964;

/* Checking ro.build.version.release is fragile, as the release field is
 * an opaque string without structural guarantees. However, testing confirms
 * that on Q devices, the property is "10," and on R, it is "R." Until
 * android_get_device_api_level() is updated, this is the only solution.
 *
 * TODO(b/146019024): migrate to android_get_device_api_level()
 */
const bool StatsEventCompat::mPlatformAtLeastR =
        GetProperty("ro.build.version.codename", "") == "R" ||
        android_get_device_api_level() > __ANDROID_API_Q__;

// definitions of static class variables
bool StatsEventCompat::mAttemptedLoad = false;
struct stats_event_api_table* StatsEventCompat::mStatsEventApi = nullptr;
std::mutex StatsEventCompat::mLoadLock;

StatsEventCompat::StatsEventCompat() : mEventQ(kStatsEventTag) {
    // guard loading because StatsEventCompat might be called from multithreaded
    // environment
    {
        std::lock_guard<std::mutex> lg(mLoadLock);
        if (!mAttemptedLoad) {
            void* handle = dlopen("libstatssocket.so", RTLD_NOW);
            if (handle) {
                mStatsEventApi = (struct stats_event_api_table*)dlsym(handle, "table");
            } else {
                ALOGE("dlopen failed: %s\n", dlerror());
            }
        }
        mAttemptedLoad = true;
    }

    if (mStatsEventApi) {
        mEventR = mStatsEventApi->obtain();
    } else if (!mPlatformAtLeastR) {
        mEventQ << android::elapsedRealtimeNano();
    }
}

StatsEventCompat::~StatsEventCompat() {
    if (mStatsEventApi) mStatsEventApi->release(mEventR);
}

void StatsEventCompat::setAtomId(int32_t atomId) {
    if (mStatsEventApi) {
        mStatsEventApi->set_atom_id(mEventR, (uint32_t)atomId);
    } else if (!mPlatformAtLeastR) {
        mEventQ << atomId;
    }
}

void StatsEventCompat::writeInt32(int32_t value) {
    if (mStatsEventApi) {
        mStatsEventApi->write_int32(mEventR, value);
    } else if (!mPlatformAtLeastR) {
        mEventQ << value;
    }
}

void StatsEventCompat::writeInt64(int64_t value) {
    if (mStatsEventApi) {
        mStatsEventApi->write_int64(mEventR, value);
    } else if (!mPlatformAtLeastR) {
        mEventQ << value;
    }
}

void StatsEventCompat::writeFloat(float value) {
    if (mStatsEventApi) {
        mStatsEventApi->write_float(mEventR, value);
    } else if (!mPlatformAtLeastR) {
        mEventQ << value;
    }
}

void StatsEventCompat::writeBool(bool value) {
    if (mStatsEventApi) {
        mStatsEventApi->write_bool(mEventR, value);
    } else if (!mPlatformAtLeastR) {
        mEventQ << value;
    }
}

void StatsEventCompat::writeByteArray(const char* buffer, size_t length) {
    if (mStatsEventApi) {
        mStatsEventApi->write_byte_array(mEventR, (const uint8_t*)buffer, length);
    } else if (!mPlatformAtLeastR) {
        mEventQ.AppendCharArray(buffer, length);
    }
}

void StatsEventCompat::writeString(const char* value) {
    if (value == nullptr) value = "";

    if (mStatsEventApi) {
        mStatsEventApi->write_string8(mEventR, value);
    } else if (!mPlatformAtLeastR) {
        mEventQ << value;
    }
}

void StatsEventCompat::writeAttributionChain(const int32_t* uids, size_t numUids,
                                             const vector<const char*>& tags) {
    if (mStatsEventApi) {
        mStatsEventApi->write_attribution_chain(mEventR, (const uint32_t*)uids, tags.data(),
                                                (uint8_t)numUids);
    } else if (!mPlatformAtLeastR) {
        mEventQ.begin();
        for (size_t i = 0; i < numUids; i++) {
            mEventQ.begin();
            mEventQ << uids[i];
            const char* tag = tags[i] ? tags[i] : "";
            mEventQ << tag;
            mEventQ.end();
        }
        mEventQ.end();
    }
}

void StatsEventCompat::writeKeyValuePairs(const map<int, int32_t>& int32Map,
                                          const map<int, int64_t>& int64Map,
                                          const map<int, const char*>& stringMap,
                                          const map<int, float>& floatMap) {
    if (mStatsEventApi) {
        vector<struct key_value_pair> pairs;

        for (const auto& it : int32Map) {
            pairs.push_back({.key = it.first, .valueType = INT32_TYPE, .int32Value = it.second});
        }
        for (const auto& it : int64Map) {
            pairs.push_back({.key = it.first, .valueType = INT64_TYPE, .int64Value = it.second});
        }
        for (const auto& it : stringMap) {
            pairs.push_back({.key = it.first, .valueType = STRING_TYPE, .stringValue = it.second});
        }
        for (const auto& it : floatMap) {
            pairs.push_back({.key = it.first, .valueType = FLOAT_TYPE, .floatValue = it.second});
        }

        mStatsEventApi->write_key_value_pairs(mEventR, pairs.data(), (uint8_t)pairs.size());
    }

    else if (!mPlatformAtLeastR) {
        mEventQ.begin();
        writeKeyValuePairMap(int32Map);
        writeKeyValuePairMap(int64Map);
        writeKeyValuePairMap(stringMap);
        writeKeyValuePairMap(floatMap);
        mEventQ.end();
    }
}

template <class T>
void StatsEventCompat::writeKeyValuePairMap(const map<int, T>& keyValuePairMap) {
    for (const auto& it : keyValuePairMap) {
        mEventQ.begin();
        mEventQ << it.first;
        mEventQ << it.second;
        mEventQ.end();
    }
}

// explicitly specify which types we're going to use
template void StatsEventCompat::writeKeyValuePairMap<int32_t>(const map<int, int32_t>&);
template void StatsEventCompat::writeKeyValuePairMap<int64_t>(const map<int, int64_t>&);
template void StatsEventCompat::writeKeyValuePairMap<float>(const map<int, float>&);
template void StatsEventCompat::writeKeyValuePairMap<const char*>(const map<int, const char*>&);

void StatsEventCompat::addBoolAnnotation(uint8_t annotationId, bool value) {
    if (mStatsEventApi) mStatsEventApi->add_bool_annotation(mEventR, annotationId, value);
    // Don't do anything if on Q.
}

void StatsEventCompat::addInt32Annotation(uint8_t annotationId, int32_t value) {
    if (mStatsEventApi) mStatsEventApi->add_int32_annotation(mEventR, annotationId, value);
    // Don't do anything if on Q.
}

int StatsEventCompat::writeToSocket() {
    if (mStatsEventApi) {
        mStatsEventApi->build(mEventR);
        return mStatsEventApi->write(mEventR);
    }

    if (!mPlatformAtLeastR) return mEventQ.write(LOG_ID_STATS);

    // We reach here only if we're on R, but libstatspush_compat was unable to
    // be loaded using dlopen.
    return -ENOLINK;
}

bool StatsEventCompat::usesNewSchema() {
    return mStatsEventApi != nullptr;
}
+71 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#pragma once

#include <gtest/gtest_prod.h>
#include <map>
#include <mutex>
#include <vector>
#include "stats_event.h"
#include "stats_event_list.h"

using std::map;
using std::vector;

class StatsEventCompat {
  public:
    StatsEventCompat();
    ~StatsEventCompat();

    void setAtomId(int32_t atomId);
    void writeInt32(int32_t value);
    void writeInt64(int64_t value);
    void writeFloat(float value);
    void writeBool(bool value);
    void writeByteArray(const char* buffer, size_t length);
    void writeString(const char* value);

    // Pre-condition: numUids == tags.size()
    void writeAttributionChain(const int32_t* uids, size_t numUids,
                               const vector<const char*>& tags);

    void writeKeyValuePairs(const map<int, int32_t>& int32Map, const map<int, int64_t>& int64Map,
                            const map<int, const char*>& stringMap,
                            const map<int, float>& floatMap);

    void addBoolAnnotation(uint8_t annotationId, bool value);
    void addInt32Annotation(uint8_t annotationId, int32_t value);

    int writeToSocket();

  private:
    // static member variables
    const static bool mPlatformAtLeastR;
    static bool mAttemptedLoad;
    static std::mutex mLoadLock;
    static struct stats_event_api_table* mStatsEventApi;

    // non-static member variables
    struct stats_event* mEventR = nullptr;
    stats_event_list mEventQ;

    template <class T>
    void writeKeyValuePairMap(const map<int, T>& keyValuePairMap);

    bool usesNewSchema();
    FRIEND_TEST(StatsEventCompatTest, TestDynamicLoading);
};
+38 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "include/StatsEventCompat.h"
#include <android-base/properties.h>
#include <android/api-level.h>
#include <gtest/gtest.h>

using android::base::GetProperty;

/* Checking ro.build.version.release is fragile, as the release field is
 * an opaque string without structural guarantees. However, testing confirms
 * that on Q devices, the property is "10," and on R, it is "R." Until
 * android_get_device_api_level() is updated, this is the only solution.
 *
 *
 * TODO(b/146019024): migrate to android_get_device_api_level()
 */
const static bool mPlatformAtLeastR = GetProperty("ro.build.version.release", "") == "R" ||
                                      android_get_device_api_level() > __ANDROID_API_Q__;

TEST(StatsEventCompatTest, TestDynamicLoading) {
    StatsEventCompat event;
    EXPECT_EQ(mPlatformAtLeastR, event.usesNewSchema());
}
+6 −0
Original line number Diff line number Diff line
@@ -41,3 +41,9 @@ cc_library {
        "liblog",
    ],
}

cc_library_headers {
    name: "libstatssocket_headers",
    export_include_dirs: ["include"],
    host_supported: true,
}
Loading