1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.android.statsd.loadtest;
17
18import android.annotation.Nullable;
19import android.content.Context;
20import android.os.Environment;
21import android.util.Log;
22import android.os.Debug;
23
24import com.android.internal.os.StatsdConfigProto.TimeUnit;
25import java.io.BufferedReader;
26import java.io.Closeable;
27import java.io.File;
28import java.io.FileInputStream;
29import java.io.FileOutputStream;
30import java.io.FileWriter;
31import java.io.InputStreamReader;
32import java.io.IOException;
33import java.text.SimpleDateFormat;
34import java.util.Date;
35
36public abstract class PerfDataRecorder {
37    private static final String TAG = "loadtest.PerfDataRecorder";
38
39    protected final String mTimeAsString;
40    protected final String mColumnSuffix;
41
42    protected PerfDataRecorder(boolean placebo, int replication, TimeUnit bucket, long periodSecs,
43        int burst, boolean includeCountMetric, boolean includeDurationMetric,
44        boolean includeEventMetric,  boolean includeValueMetric, boolean includeGaugeMetric) {
45        mTimeAsString = new SimpleDateFormat("YYYY_MM_dd_HH_mm_ss").format(new Date());
46        mColumnSuffix = getColumnSuffix(placebo, replication, bucket, periodSecs, burst,
47            includeCountMetric, includeDurationMetric, includeEventMetric, includeValueMetric,
48            includeGaugeMetric);
49    }
50
51    /** Starts recording performance data. */
52    public abstract void startRecording(Context context);
53
54    /** Called periodically. For the recorder to sample data, if needed. */
55    public abstract void onAlarm(Context context);
56
57    /** Stops recording performance data, and writes it to disk. */
58    public abstract void stopRecording(Context context);
59
60    /** Runs the dumpsys command. */
61    protected void runDumpsysStats(Context context, String dumpFilename, String cmd,
62        String... args) {
63        boolean success = false;
64        // Call dumpsys Dump statistics to a file.
65        FileOutputStream fo = null;
66        try {
67            fo = context.openFileOutput(dumpFilename, Context.MODE_PRIVATE);
68            if (!Debug.dumpService(cmd, fo.getFD(), args)) {
69                Log.w(TAG, "Dumpsys failed.");
70            }
71            success = true;
72        } catch (IOException | SecurityException | NullPointerException e) {
73            // SecurityException may occur when trying to dump multi-user info.
74            // NPE can occur during dumpService  (root cause unknown).
75            throw new RuntimeException(e);
76        } finally {
77            closeQuietly(fo);
78        }
79    }
80
81    /**
82     * Reads a text file and parses each line, one by one. The result of the parsing is stored
83     * in the passed {@link StringBuffer}.
84     */
85    protected void readDumpData(Context context, String dumpFilename, PerfParser parser,
86        StringBuilder sb) {
87        FileInputStream fi = null;
88        BufferedReader br = null;
89        try {
90            fi = context.openFileInput(dumpFilename);
91            br = new BufferedReader(new InputStreamReader(fi));
92            String line = br.readLine();
93            while (line != null) {
94                String recordLine = parser.parseLine(line);
95                if (recordLine != null) {
96                  sb.append(recordLine).append('\n');
97                }
98                line = br.readLine();
99            }
100        } catch (IOException e) {
101            throw new RuntimeException(e);
102        } finally {
103            closeQuietly(br);
104        }
105    }
106
107    /** Writes CSV data to a file. */
108    protected void writeData(Context context, String filePrefix, String columnPrefix,
109        StringBuilder sb) {
110        File dataFile = new File(getStorageDir(), filePrefix + mTimeAsString + ".csv");
111
112        FileWriter writer = null;
113        try {
114            writer = new FileWriter(dataFile);
115            writer.append(columnPrefix + mColumnSuffix + "\n");
116            writer.append(sb.toString());
117            writer.flush();
118            Log.d(TAG, "Finished writing data at " + dataFile.getAbsolutePath());
119        } catch (IOException e) {
120            throw new RuntimeException(e);
121        } finally {
122            closeQuietly(writer);
123        }
124    }
125
126    /** Gets the suffix to use in the column name for perf data. */
127    private String getColumnSuffix(boolean placebo, int replication, TimeUnit bucket,
128        long periodSecs, int burst, boolean includeCountMetric, boolean includeDurationMetric,
129        boolean includeEventMetric,  boolean includeValueMetric, boolean includeGaugeMetric) {
130        if (placebo) {
131            return "_placebo_p=" + periodSecs;
132        }
133        StringBuilder sb = new StringBuilder()
134            .append("_r=" + replication)
135            .append("_bkt=" + bucket)
136            .append("_p=" + periodSecs)
137            .append("_bst=" + burst)
138            .append("_m=");
139        if (includeCountMetric) {
140            sb.append("c");
141        }
142        if (includeEventMetric) {
143            sb.append("e");
144        }
145        if (includeDurationMetric) {
146            sb.append("d");
147        }
148        if (includeGaugeMetric) {
149            sb.append("g");
150        }
151        if (includeValueMetric) {
152            sb.append("v");
153        }
154        return sb.toString();
155    }
156
157    private File getStorageDir() {
158        File file = new File(Environment.getExternalStoragePublicDirectory(
159            Environment.DIRECTORY_DOCUMENTS), "loadtest/" + mTimeAsString);
160        if (!file.mkdirs()) {
161            Log.e(TAG, "Directory not created");
162        }
163        return file;
164    }
165
166    private void closeQuietly(@Nullable Closeable c) {
167        if (c != null) {
168            try {
169                c.close();
170            } catch (IOException ignore) {
171            }
172        }
173    }
174}
175