/*
* Copyright (C) 2022 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 androidx.constraintlayout.widget;
import android.annotation.SuppressLint;
import android.util.Log;
import androidx.constraintlayout.core.Metrics;
import java.text.DecimalFormat;
import java.util.ArrayList;
/**
* This provide metrics of the complexity of the layout that is being solved.
* The intent is for developers using the too to track the evolution of their UI
* Typically the developer will monitor the computations on every callback of
* mConstraintLayout.addOnLayoutChangeListener(this::callback);
*
*/
public class ConstraintLayoutStatistics {
public final static int NUMBER_OF_LAYOUTS = 1;
public final static int NUMBER_OF_ON_MEASURES= 2;
public final static int NUMBER_OF_CHILD_VIEWS= 3;
public final static int NUMBER_OF_CHILD_MEASURES= 4;
public final static int DURATION_OF_CHILD_MEASURES= 5;
public final static int DURATION_OF_MEASURES = 6;
public final static int DURATION_OF_LAYOUT = 7;
public final static int NUMBER_OF_VARIABLES = 8;
public final static int NUMBER_OF_EQUATIONS = 9;
public final static int NUMBER_OF_SIMPLE_EQUATIONS = 10;
private final Metrics mMetrics = new Metrics();
ConstraintLayout mConstraintLayout;
private static int MAX_WORD = 25;
private static final String WORD_PAD = new String(new char[MAX_WORD]).replace('eb3b9ed0-f11d-0137-cfb9-0ebaa35b92c0', ' ');
/**
* Measure performance information about ConstraintLayout
*
* @param constraintLayout
*/
public ConstraintLayoutStatistics(ConstraintLayout constraintLayout) {
attach(constraintLayout);
}
/**
* Copy a layout Stats useful for comparing two stats
* @param copy
*/
public ConstraintLayoutStatistics(ConstraintLayoutStatistics copy) {
mMetrics.copy(copy.mMetrics);
}
/**
* Attach to a ConstraintLayout to gather statistics on its layout performance
* @param constraintLayout
*/
public void attach(ConstraintLayout constraintLayout) {
constraintLayout.fillMetrics(mMetrics);
mConstraintLayout = constraintLayout;
}
/**
* Detach from a ConstraintLayout
*/
public void detach() {
if (mConstraintLayout != null) {
mConstraintLayout.fillMetrics(null);
}
}
/**
* Clear the current metrics
*/
public void reset() {
mMetrics.reset();
}
/**
* Create a copy of the statistics
* @return a copy
*/
@Override
public ConstraintLayoutStatistics clone() {
return new ConstraintLayoutStatistics(this);
}
/**
* Format a float value outputting a string of fixed length
* @param df format to use
* @param val
* @param length
* @return
*/
private String fmt(DecimalFormat df, float val, int length) {
String s = new String(new char[length]).replace('eb3b9ed0-f11d-0137-cfb9-0ebaa35b92c0', ' ');
s = (s + df.format(val));
return s.substring(s.length() - length);
}
/**
* Log a summary of the statistics
* @param tag
*/
public void logSummary(String tag) {
log(tag);
}
@SuppressLint({"LogConditional"})
private void log(String tag) {
StackTraceElement s = new Throwable().getStackTrace()[2];
Log.v(tag, "CL Perf: -------- Performance .(" +
s.getFileName() + ":" + s.getLineNumber() + ") ------ ");
DecimalFormat df = new DecimalFormat("###.000");
Log.v(tag, log(df, DURATION_OF_CHILD_MEASURES));
Log.v(tag, log(df, DURATION_OF_LAYOUT));
Log.v(tag, log(df, DURATION_OF_MEASURES));
Log.v(tag, log(NUMBER_OF_LAYOUTS));
Log.v(tag, log( NUMBER_OF_ON_MEASURES));
Log.v(tag, log(NUMBER_OF_CHILD_VIEWS));
Log.v(tag, log(NUMBER_OF_CHILD_MEASURES));
Log.v(tag, log(NUMBER_OF_VARIABLES));
Log.v(tag, log(NUMBER_OF_EQUATIONS));
Log.v(tag, log( NUMBER_OF_SIMPLE_EQUATIONS));
}
/**
* Generate a formatted String for the parameter formatting as a float
* @param df
* @param param
* @return
*/
private String log(DecimalFormat df, int param) {
String value = fmt(df, getValue(param) * 1E-6f, 7);
String title = geName(param);
title = WORD_PAD + title;
title = title.substring(title.length() - MAX_WORD);
title += " = ";
return "CL Perf: " + title + value;
}
/**
* Generate a formatted String for the parameter
* @param param
* @return
*/
private String log(int param) {
String value = Long.toString(this.getValue(param));
String title = geName(param);
title = WORD_PAD + title;
title = title.substring(title.length() - MAX_WORD);
title += " = ";
return "CL Perf: " + title + value;
}
/**
* Generate a float formatted String for the parameter comparing
* current value with value in relative
* @param df Format the float using this
* @param relative compare against
* @param param the parameter to compare
* @return
*/
private String compare(DecimalFormat df, ConstraintLayoutStatistics relative, int param) {
String value = fmt(df, getValue(param) * 1E-6f, 7);
value += " -> " + fmt(df, relative.getValue(param) * 1E-6f, 7) + "ms";
String title = geName(param);
title = WORD_PAD + title;
title = title.substring(title.length() - MAX_WORD);
title += " = ";
return "CL Perf: " + title + value;
}
/**
* Generate a formatted String for the parameter comparing current value with value in relative
* @param relative compare against
* @param param the parameter to compare
* @return
*/
private String compare(ConstraintLayoutStatistics relative, int param) {
String value = this.getValue(param) + " -> " + relative.getValue(param);
String title = geName(param);
title = WORD_PAD + title;
title = title.substring(title.length() - MAX_WORD);
title += " = ";
return "CL Perf: " + title + value;
}
/**
* log a summary of the stats compared to another statics
* @param tag used in Log.v(tag, ...)
* @param prev the previous stats to compare to
*/
@SuppressLint("LogConditional")
public void logSummary(String tag, ConstraintLayoutStatistics prev) {
if (prev == null) {
log(tag);
return;
}
DecimalFormat df = new DecimalFormat("###.000");
StackTraceElement s = new Throwable().getStackTrace()[1];
Log.v(tag, "CL Perf: -= Performance .(" +
s.getFileName() + ":" + s.getLineNumber() + ") =- ");
Log.v(tag, compare(df, prev, DURATION_OF_CHILD_MEASURES));
Log.v(tag, compare(df, prev, DURATION_OF_LAYOUT));
Log.v(tag, compare(df, prev, DURATION_OF_MEASURES));
Log.v(tag, compare(prev, NUMBER_OF_LAYOUTS));
Log.v(tag, compare(prev, NUMBER_OF_ON_MEASURES));
Log.v(tag, compare(prev, NUMBER_OF_CHILD_VIEWS));
Log.v(tag, compare(prev, NUMBER_OF_CHILD_MEASURES));
Log.v(tag, compare(prev, NUMBER_OF_VARIABLES));
Log.v(tag, compare(prev, NUMBER_OF_EQUATIONS));
Log.v(tag, compare(prev, NUMBER_OF_SIMPLE_EQUATIONS));
}
/**
* get the value of a statistic
* @param type
* @return
*/
public long getValue(int type) {
switch (type) {
case NUMBER_OF_LAYOUTS:
return mMetrics.mNumberOfLayouts;
case NUMBER_OF_ON_MEASURES:
return mMetrics.mMeasureCalls;
case NUMBER_OF_CHILD_VIEWS:
return mMetrics.mChildCount;
case NUMBER_OF_CHILD_MEASURES:
return mMetrics.mNumberOfMeasures;
case DURATION_OF_CHILD_MEASURES:
return mMetrics.measuresWidgetsDuration ;
case DURATION_OF_MEASURES:
return mMetrics.mMeasureDuration;
case DURATION_OF_LAYOUT:
return mMetrics.measuresLayoutDuration;
case NUMBER_OF_VARIABLES:
return mMetrics.mVariables;
case NUMBER_OF_EQUATIONS:
return mMetrics.mEquations;
case NUMBER_OF_SIMPLE_EQUATIONS:
return mMetrics.mSimpleEquations;
}
return 0;
}
/** get a simple name for a statistic
*
* @param type type of statistic
* @return a camel case
*/
String geName(int type) {
switch (type) {
case NUMBER_OF_LAYOUTS:
return "NumberOfLayouts";
case NUMBER_OF_ON_MEASURES:
return "MeasureCalls";
case NUMBER_OF_CHILD_VIEWS:
return "ChildCount";
case NUMBER_OF_CHILD_MEASURES:
return "ChildrenMeasures";
case DURATION_OF_CHILD_MEASURES:
return "MeasuresWidgetsDuration ";
case DURATION_OF_MEASURES:
return "MeasureDuration";
case DURATION_OF_LAYOUT:
return "MeasuresLayoutDuration";
case NUMBER_OF_VARIABLES:
return "SolverVariables";
case NUMBER_OF_EQUATIONS:
return "SolverEquations";
case NUMBER_OF_SIMPLE_EQUATIONS:
return "SimpleEquations";
}
return "";
}
}