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

Commit d3f17bc8 authored by Danuta Brotikovskaya's avatar Danuta Brotikovskaya Committed by Android (Google) Code Review
Browse files

Merge "Implement PressureStallInformation extraction logic." into main

parents e5f38f97 5602d31f
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
jackrichardson@google.com
dbrotikovskaya@google.com
ivokay@google.com
gagapov@google.com
yigitfiliz@google.com
rswang@google.com
evaleriano@google.com
igorstepanov@google.com
iyou@google.com
+106 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.
 */

package com.android.server.stats.pull.psi;

/**
 * Wraps PSI (Pressure Stall Information) corresponding to a system resource. See more details about
 * PSI, see https://docs.kernel.org/accounting/psi.html#psi-pressure-stall-information.
 */
public class PsiData {
    public enum ResourceType {
        CPU,
        MEMORY,
        IO
    }

    static class AppsStallInfo {

        /** Past 10s average % of wasted CPU cycles when apps tasks are stalled on mResourceType.*/
        private final float mAvg10SecPercentage;

        /** Past 60s average % of wasted CPU cycles when apps tasks are stalled on mResourceType.*/
        private final float mAvg60SecPercentage;

        /** Past 300s average % of wasted CPU cycles when apps tasks are stalled on mResourceType.*/
        private final float mAvg300SecPercentage;

        /** Total number of microseconds that apps tasks are stalled on mResourceType.*/
        private final long mTotalUsec;

        AppsStallInfo(
                float avg10SecPercentage, float avg60SecPercentage,
                float avg300SecPercentage, long totalUsec) {
            mAvg10SecPercentage = avg10SecPercentage;
            mAvg60SecPercentage = avg60SecPercentage;
            mAvg300SecPercentage = avg300SecPercentage;
            mTotalUsec = totalUsec;
        }
    }

    /** The system resource type of this {@code PsiData}. */
    private final ResourceType mResourceType;

    /** Info on some tasks are stalled on mResourceType. */
    private final AppsStallInfo mSomeAppsStallInfo;

    /**
     * Info on all non-idle tasks are stalled on mResourceType. For the CPU ResourceType,
     * all fields will always be 0 as it's undefined.
     */
    private final AppsStallInfo mFullAppsStallInfo;

    PsiData(
            ResourceType resourceType,
            AppsStallInfo someAppsStallInfo,
            AppsStallInfo fullAppsStallInfo) {
        mResourceType = resourceType;
        mSomeAppsStallInfo = someAppsStallInfo;
        mFullAppsStallInfo = fullAppsStallInfo;
    }

    public ResourceType getResourceType() {
        return mResourceType;
    }

    public float getSomeAvg10SecPercentage() {
        return mSomeAppsStallInfo.mAvg10SecPercentage; }

    public float getSomeAvg60SecPercentage() {
        return mSomeAppsStallInfo.mAvg60SecPercentage; }

    public float getSomeAvg300SecPercentage() {
        return mSomeAppsStallInfo.mAvg300SecPercentage; }

    public long getSomeTotalUsec() {
        return mSomeAppsStallInfo.mTotalUsec;
    }

    public float getFullAvg10SecPercentage() {
        return mFullAppsStallInfo.mAvg10SecPercentage;
    }

    public float getFullAvg60SecPercentage() {
        return mFullAppsStallInfo.mAvg60SecPercentage;
    }

    public float getFullAvg300SecPercentage() {
        return mFullAppsStallInfo.mAvg300SecPercentage; }

    public long getFullTotalUsec() {
        return mFullAppsStallInfo.mTotalUsec;
    }
}
+161 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.
 */

package com.android.server.stats.pull.psi;

import static java.util.stream.Collectors.joining;

import android.annotation.Nullable;
import android.util.Log;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.text.MessageFormat;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class PsiExtractor {
    private static final String TAG = "PsiExtractor";

    // Paths for PSI files are guarded by SELinux policy. PCS needs to be explicitly
    // allowlisted to access these files.
    private static final String PSI_MEMORY_PATH = "/proc/pressure/memory";
    private static final String PSI_IO_PATH = "/proc/pressure/io";
    private static final String PSI_CPU_PATH = "/proc/pressure/cpu";

    // The patterns matching a line of PSI output such as
    // "some avg10=0.12 avg60=0.34 avg300=0.56 total=123456" or
    // "full avg10=0.12 avg60=0.34 avg300=0.56 total=123456" to extract the stalling percentage
    // values for "some" and "full" line of PSI output respectively.
    private static final String PSI_PATTERN_TEMPLATE =
            ".*{0} avg10=(\\d+.\\d+) avg60=(\\d+.\\d+) avg300=(\\d+.\\d+) total=(\\d+).*";
    private static final String SOME = "some";
    private static final String FULL = "full";
    private final PsiReader mPsiReader;

    public PsiExtractor() {
        mPsiReader = new PsiReader();
    }
    public PsiExtractor(PsiReader psiReader) {
        mPsiReader = psiReader;
    }

    /**
    * Parses /pressure/proc/{resourceType} kernel file to extract the Pressure Stall Information
    * (PSI), more information: can be found at https://docs.kernel.org/accounting/psi.html.
    *
    * @param resourceType (Memory/CPU/IO) to get the PSI for.
    */
    @Nullable
    public PsiData getPsiData(PsiData.ResourceType resourceType) {
        String psiFileData;
        if (resourceType == PsiData.ResourceType.MEMORY) {
            psiFileData = mPsiReader.read(PSI_MEMORY_PATH);
        } else if (resourceType == PsiData.ResourceType.IO) {
            psiFileData = mPsiReader.read(PSI_IO_PATH);
        } else if (resourceType == PsiData.ResourceType.CPU) {
            psiFileData = mPsiReader.read(PSI_CPU_PATH);
        } else {
            Log.w(TAG, "PsiExtractor failure: cannot read kernel source file, returning null");
            return null;
        }
        return parsePsiData(psiFileData, resourceType);
    }

    @Nullable
    private static PsiData.AppsStallInfo parsePsiString(
            String psiFileData, String appType, PsiData.ResourceType resourceType) {
        // There is an extra case of file content: the CPU full is undefined and isn't reported for
        // earlier versions. It should be always propagated as 0, but for the current logic purposes
        // we will report atom only if at least one value (some/full) is presented. Thus, hardcoding
        // the "full" line as 0 only when the "some" line is presented.
        if (appType == FULL && resourceType == PsiData.ResourceType.CPU) {
            if (psiFileData.contains(SOME) && !psiFileData.contains(FULL)) {
                return new PsiData.AppsStallInfo((float) 0.0, (float) 0.0, (float) 0.0, 0);
            }
        }

        Pattern psiStringPattern = Pattern.compile(
                MessageFormat.format(PSI_PATTERN_TEMPLATE, appType));
        Matcher psiLineMatcher = psiStringPattern.matcher(psiFileData);

        // Parsing the line starts with "some" in the expected output.
        // The line for "some" should always be present in PSI output. The output must be somehow
        // malformed if the line cannot be matched.
        if (!psiLineMatcher.find()) {
            Log.w(TAG,
                    "Returning null: the line \"" +  appType + "\" is not in expected pattern.");
            return null;
        }
        try {
            return new PsiData.AppsStallInfo(
                    Float.parseFloat(psiLineMatcher.group(1)),
                    Float.parseFloat(psiLineMatcher.group(2)),
                    Float.parseFloat(psiLineMatcher.group(3)),
                    Long.parseLong(psiLineMatcher.group(4)));
        } catch (NumberFormatException e) {
            Log.w(TAG,
                    "Returning null: some value in line \"" +  appType
                            + "\" cannot be parsed as numeric.");
            return null;
        }
    }

    @Nullable
    private static PsiData parsePsiData(
                                         String psiFileData, PsiData.ResourceType resourceType) {
        PsiData.AppsStallInfo someAppsStallInfo = parsePsiString(psiFileData, SOME, resourceType);
        PsiData.AppsStallInfo fullAppsStallInfo = parsePsiString(psiFileData, FULL, resourceType);

        if (someAppsStallInfo == null && fullAppsStallInfo == null) {
            Log.w(TAG, "Returning empty PSI: some or full line are failed to parse");
            return null;
        } else if (someAppsStallInfo == null) {
            Log.d(TAG, "Replacing some info with empty PSI record for the resource type "
                    + resourceType);
            someAppsStallInfo = new PsiData.AppsStallInfo(
                    (float) -1.0, (float) -1.0, (float) -1.0, -1);
        } else if (fullAppsStallInfo == null) {
            Log.d(TAG, "Replacing full info with empty PSI record for the resource type "
                    + resourceType);
            fullAppsStallInfo = new PsiData.AppsStallInfo(
                    (float) -1.0, (float) -1.0, (float) -1.0, -1);
        }
        return new PsiData(resourceType, someAppsStallInfo, fullAppsStallInfo);
    }

    /** Dependency class */
    public static class PsiReader {
        /**
        * Reads file from provided path and returns its content if the file found, null otherwise.
        *
        * @param filePath file path to read.
        */
        @Nullable
        public String read(String filePath) {
            try (BufferedReader br =
                         new BufferedReader(new InputStreamReader(
                                 new FileInputStream(filePath)))) {
                return br.lines().collect(joining(System.lineSeparator()));
            } catch (IOException e) {
                Log.w(TAG, "Cannot read file " +  filePath);
                return null;
            }
        }
    }
}
+1 −0
Original line number Diff line number Diff line
include /services/core/java/com/android/server/stats/pull/psi/OWNERS
+305 −0

File added.

Preview size limit exceeded, changes collapsed.