diff options
19 files changed, 1508 insertions, 0 deletions
diff --git a/tools/powermodel/Android.bp b/tools/powermodel/Android.bp new file mode 100644 index 000000000000..f597aab0f464 --- /dev/null +++ b/tools/powermodel/Android.bp @@ -0,0 +1,26 @@ + +java_library_host { + name: "powermodel", + srcs: [ + "src/**/*.java", + ], + static_libs: [ + "guava", + ], +} + +java_test_host { + name: "powermodel-test", + + test_suites: ["general-tests"], + + srcs: ["test/**/*.java"], + java_resource_dirs: ["test-resource"], + + static_libs: [ + "powermodel", + "junit", + "mockito", + ], +} + diff --git a/tools/powermodel/TEST_MAPPING b/tools/powermodel/TEST_MAPPING new file mode 100644 index 000000000000..c8db339ce23b --- /dev/null +++ b/tools/powermodel/TEST_MAPPING @@ -0,0 +1,8 @@ +{ + "presubmit": [ + { + "name": "powermodel-test" + } + ] +} + diff --git a/tools/powermodel/src/com/android/powermodel/Component.java b/tools/powermodel/src/com/android/powermodel/Component.java new file mode 100644 index 000000000000..baae6d784c47 --- /dev/null +++ b/tools/powermodel/src/com/android/powermodel/Component.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2018 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.powermodel; + +/** + * The hardware components that use power on a device. + */ +public enum Component { + CPU, + SCREEN, + MODEM, + WIFI, + BLUETOOTH, + VIDEO, + AUDIO, + FLASHLIGHT, + CAMERA, + GPS, +} + diff --git a/tools/powermodel/src/com/android/powermodel/ComponentProfile.java b/tools/powermodel/src/com/android/powermodel/ComponentProfile.java new file mode 100644 index 000000000000..e76e5fb52481 --- /dev/null +++ b/tools/powermodel/src/com/android/powermodel/ComponentProfile.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2018 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.powermodel; + +public class ComponentProfile { +} diff --git a/tools/powermodel/src/com/android/powermodel/ParseException.java b/tools/powermodel/src/com/android/powermodel/ParseException.java new file mode 100644 index 000000000000..e1f232bfc44f --- /dev/null +++ b/tools/powermodel/src/com/android/powermodel/ParseException.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2018 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.powermodel; + +public class ParseException extends Exception { + public final int line; + + public ParseException(int line, String message, Throwable th) { + super(message, th); + this.line = line; + } + + public ParseException(int line, String message) { + this(line, message, null); + } + + public ParseException(String message) { + this(0, message, null); + } +} + diff --git a/tools/powermodel/src/com/android/powermodel/PowerProfile.java b/tools/powermodel/src/com/android/powermodel/PowerProfile.java new file mode 100644 index 000000000000..373a9c981ec5 --- /dev/null +++ b/tools/powermodel/src/com/android/powermodel/PowerProfile.java @@ -0,0 +1,527 @@ +/* + * Copyright (C) 2018 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.powermodel; + +import java.io.InputStream; +import java.io.IOException; +import java.util.regex.Pattern; +import java.util.regex.Matcher; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import org.xml.sax.Attributes; +import org.xml.sax.Locator; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; +import org.xml.sax.helpers.DefaultHandler; + +import com.android.powermodel.component.AudioProfile; +import com.android.powermodel.component.BluetoothProfile; +import com.android.powermodel.component.CameraProfile; +import com.android.powermodel.component.CpuProfile; +import com.android.powermodel.component.FlashlightProfile; +import com.android.powermodel.component.GpsProfile; +import com.android.powermodel.component.ModemProfile; +import com.android.powermodel.component.ScreenProfile; +import com.android.powermodel.component.VideoProfile; +import com.android.powermodel.component.WifiProfile; +import com.android.powermodel.util.Conversion; + +public class PowerProfile { + + // Remaining fields from the android code for which the actual usage is unclear. + // battery.capacity + // bluetooth.controller.voltage + // modem.controller.voltage + // gps.voltage + // wifi.controller.voltage + // radio.on + // radio.scanning + // radio.active + // memory.bandwidths + // wifi.batchedscan + // wifi.scan + // wifi.on + // wifi.active + // wifi.controller.tx_levels + + private static Pattern RE_CLUSTER_POWER = Pattern.compile("cpu.cluster_power.cluster([0-9]*)"); + private static Pattern RE_CORE_SPEEDS = Pattern.compile("cpu.core_speeds.cluster([0-9]*)"); + private static Pattern RE_CORE_POWER = Pattern.compile("cpu.core_power.cluster([0-9]*)"); + + private HashMap<Component, ComponentProfile> mComponents = new HashMap(); + + /** + * Which element we are currently parsing. + */ + enum ElementState { + BEGIN, + TOP, + ITEM, + ARRAY, + VALUE + } + + /** + * Implements the reading and power model logic. + */ + private static class Parser { + private final InputStream mStream; + private final PowerProfile mResult; + + // Builders for the ComponentProfiles. + private final AudioProfile mAudio = new AudioProfile(); + private final BluetoothProfile mBluetooth = new BluetoothProfile(); + private final CameraProfile mCamera = new CameraProfile(); + private final CpuProfile.Builder mCpuBuilder = new CpuProfile.Builder(); + private final FlashlightProfile mFlashlight = new FlashlightProfile(); + private final GpsProfile.Builder mGpsBuilder = new GpsProfile.Builder(); + private final ModemProfile.Builder mModemBuilder = new ModemProfile.Builder(); + private final ScreenProfile mScreen = new ScreenProfile(); + private final VideoProfile mVideo = new VideoProfile(); + private final WifiProfile mWifi = new WifiProfile(); + + /** + * Constructor to capture the parameters to read. + */ + Parser(InputStream stream) { + mStream = stream; + mResult = new PowerProfile(); + } + + /** + * Read the stream, parse it, and apply the power model. + * Do not call this more than once. + */ + PowerProfile parse() throws ParseException { + final SAXParserFactory factory = SAXParserFactory.newInstance(); + AndroidResourceHandler handler = null; + try { + final SAXParser saxParser = factory.newSAXParser(); + + handler = new AndroidResourceHandler() { + @Override + public void onItem(Locator locator, String name, float value) + throws SAXParseException { + Parser.this.onItem(locator, name, value); + } + + @Override + public void onArray(Locator locator, String name, float[] value) + throws SAXParseException { + Parser.this.onArray(locator, name, value); + } + }; + + saxParser.parse(mStream, handler); + } catch (ParserConfigurationException ex) { + // Coding error, not runtime error. + throw new RuntimeException(ex); + } catch (SAXParseException ex) { + throw new ParseException(ex.getLineNumber(), ex.getMessage(), ex); + } catch (SAXException | IOException ex) { + // Make a guess about the line number. + throw new ParseException(handler.getLineNumber(), ex.getMessage(), ex); + } + + // TODO: This doesn't cover the multiple algorithms. Some refactoring will + // be necessary. + mResult.mComponents.put(Component.AUDIO, mAudio); + mResult.mComponents.put(Component.BLUETOOTH, mBluetooth); + mResult.mComponents.put(Component.CAMERA, mCamera); + mResult.mComponents.put(Component.CPU, mCpuBuilder.build()); + mResult.mComponents.put(Component.FLASHLIGHT, mFlashlight); + mResult.mComponents.put(Component.GPS, mGpsBuilder.build()); + mResult.mComponents.put(Component.MODEM, mModemBuilder.build()); + mResult.mComponents.put(Component.SCREEN, mScreen); + mResult.mComponents.put(Component.VIDEO, mVideo); + mResult.mComponents.put(Component.WIFI, mWifi); + + return mResult; + } + + /** + * Handles an item tag in the power_profile.xml. + */ + public void onItem(Locator locator, String name, float value) throws SAXParseException { + Integer index; + try { + if ("ambient.on".equals(name)) { + mScreen.ambientMa = value; + } else if ("audio".equals(name)) { + mAudio.onMa = value; + } else if ("bluetooth.controller.idle".equals(name)) { + mBluetooth.idleMa = value; + } else if ("bluetooth.controller.rx".equals(name)) { + mBluetooth.rxMa = value; + } else if ("bluetooth.controller.tx".equals(name)) { + mBluetooth.txMa = value; + } else if ("camera.avg".equals(name)) { + mCamera.onMa = value; + } else if ("camera.flashlight".equals(name)) { + mFlashlight.onMa = value; + } else if ("cpu.suspend".equals(name)) { + mCpuBuilder.setSuspendMa(value); + } else if ("cpu.idle".equals(name)) { + mCpuBuilder.setIdleMa(value); + } else if ("cpu.active".equals(name)) { + mCpuBuilder.setActiveMa(value); + } else if ((index = matchIndexedRegex(locator, RE_CLUSTER_POWER, name)) != null) { + mCpuBuilder.setClusterPower(index, value); + } else if ("gps.on".equals(name)) { + mGpsBuilder.setOnMa(value); + } else if ("modem.controller.sleep".equals(name)) { + mModemBuilder.setSleepMa(value); + } else if ("modem.controller.idle".equals(name)) { + mModemBuilder.setIdleMa(value); + } else if ("modem.controller.rx".equals(name)) { + mModemBuilder.setRxMa(value); + } else if ("radio.scanning".equals(name)) { + mModemBuilder.setScanningMa(value); + } else if ("screen.on".equals(name)) { + mScreen.onMa = value; + } else if ("screen.full".equals(name)) { + mScreen.fullMa = value; + } else if ("video".equals(name)) { + mVideo.onMa = value; + } else if ("wifi.controller.idle".equals(name)) { + mWifi.idleMa = value; + } else if ("wifi.controller.rx".equals(name)) { + mWifi.rxMa = value; + } else if ("wifi.controller.tx".equals(name)) { + mWifi.txMa = value; + } else { + // TODO: Uncomment this when we have all of the items parsed. + // throw new SAXParseException("Unhandled <item name=\"" + name + "\"> element", + // locator, ex); + + } + } catch (ParseException ex) { + throw new SAXParseException(ex.getMessage(), locator, ex); + } + } + + /** + * Handles an array tag in the power_profile.xml. + */ + public void onArray(Locator locator, String name, float[] value) throws SAXParseException { + Integer index; + try { + if ("cpu.clusters.cores".equals(name)) { + mCpuBuilder.setCoreCount(Conversion.toIntArray(value)); + } else if ((index = matchIndexedRegex(locator, RE_CORE_SPEEDS, name)) != null) { + mCpuBuilder.setCoreSpeeds(index, Conversion.toIntArray(value)); + } else if ((index = matchIndexedRegex(locator, RE_CORE_POWER, name)) != null) { + mCpuBuilder.setCorePower(index, value); + } else if ("gps.signalqualitybased".equals(name)) { + mGpsBuilder.setSignalMa(value); + } else if ("modem.controller.tx".equals(name)) { + mModemBuilder.setTxMa(value); + } else { + // TODO: Uncomment this when we have all of the items parsed. + // throw new SAXParseException("Unhandled <item name=\"" + name + "\"> element", + // locator, ex); + } + } catch (ParseException ex) { + throw new SAXParseException(ex.getMessage(), locator, ex); + } + } + } + + /** + * SAX XML handler that can parse the android resource files. + * In our case, all elements are floats. + */ + abstract static class AndroidResourceHandler extends DefaultHandler { + /** + * The set of names already processed. Map of name to line number. + */ + private HashMap<String,Integer> mAlreadySeen = new HashMap<String,Integer>(); + + /** + * Where in the document we are parsing. + */ + private Locator mLocator; + + /** + * Which element we are currently parsing. + */ + private ElementState mState = ElementState.BEGIN; + + /** + * Saved name from item and array elements. + */ + private String mName; + + /** + * The text that is currently being captured, or null if {@link #startCapturingText()} + * has not been called. + */ + private StringBuilder mText; + + /** + * The array values that have been parsed so for for this array. Null if we are + * not inside an array tag. + */ + private ArrayList<Float> mArray; + + /** + * Called when an item tag is encountered. + */ + public abstract void onItem(Locator locator, String name, float value) + throws SAXParseException; + + /** + * Called when an array is encountered. + */ + public abstract void onArray(Locator locator, String name, float[] value) + throws SAXParseException; + + /** + * If we have a Locator set, return the line number, otherwise return 0. + */ + public int getLineNumber() { + return mLocator != null ? mLocator.getLineNumber() : 0; + } + + /** + * Handle setting the parse location object. + */ + public void setDocumentLocator(Locator locator) { + mLocator = locator; + } + + /** + * Handle beginning of an element. + * + * @param ns Namespace uri + * @param ln Local name (inside namespace) + * @param element Tag name + */ + @Override + public void startElement(String ns, String ln, String element, + Attributes attr) throws SAXException { + switch (mState) { + case BEGIN: + // Outer element, we don't care the tag name. + mState = ElementState.TOP; + return; + case TOP: + if ("item".equals(element)) { + mState = ElementState.ITEM; + saveNameAttribute(attr); + startCapturingText(); + return; + } else if ("array".equals(element)) { + mState = ElementState.ARRAY; + mArray = new ArrayList<Float>(); + saveNameAttribute(attr); + return; + } + break; + case ARRAY: + if ("value".equals(element)) { + mState = ElementState.VALUE; + startCapturingText(); + return; + } + break; + } + throw new SAXParseException("unexpected element: '" + element + "'", mLocator); + } + + /** + * Handle end of an element. + * + * @param ns Namespace uri + * @param ln Local name (inside namespace) + * @param element Tag name + */ + @Override + public void endElement(String ns, String ln, String element) throws SAXException { + switch (mState) { + case ITEM: { + float value = parseFloat(finishCapturingText()); + mState = ElementState.TOP; + onItem(mLocator, mName, value); + break; + } + case ARRAY: { + final int N = mArray.size(); + float[] values = new float[N]; + for (int i=0; i<N; i++) { + values[i] = mArray.get(i); + } + mArray = null; + mState = ElementState.TOP; + onArray(mLocator, mName, values); + break; + } + case VALUE: { + mArray.add(parseFloat(finishCapturingText())); + mState = ElementState.ARRAY; + break; + } + } + } + + /** + * Interstitial text received. + * + * @throws SAXException if there shouldn't be non-whitespace text here + */ + @Override + public void characters(char text[], int start, int length) throws SAXException { + if (mText == null && length > 0 && !isWhitespace(text, start, length)) { + throw new SAXParseException("unexpected text: '" + + firstLine(text, start, length).trim() + "'", mLocator); + } + if (mText != null) { + mText.append(text, start, length); + } + } + + /** + * Begin collecting text from inside an element. + */ + private void startCapturingText() { + if (mText != null) { + throw new RuntimeException("ASSERTION FAILED: Shouldn't be already capturing" + + " text. mState=" + mState.name() + + " line=" + mLocator.getLineNumber() + + " column=" + mLocator.getColumnNumber()); + } + mText = new StringBuilder(); + } + + /** + * Stop capturing text from inside an element. + * + * @return the captured text + */ + private String finishCapturingText() { + if (mText == null) { + throw new RuntimeException("ASSERTION FAILED: Should already be capturing" + + " text. mState=" + mState.name() + + " line=" + mLocator.getLineNumber() + + " column=" + mLocator.getColumnNumber()); + } + final String result = mText.toString().trim(); + mText = null; + return result; + } + + /** + * Get the "name" attribute. + * + * @throws SAXParseException if the name attribute is not present or if + * the name has already been seen in the file. + */ + private void saveNameAttribute(Attributes attr) throws SAXParseException { + final String name = attr.getValue("name"); + if (name == null) { + throw new SAXParseException("expected 'name' attribute", mLocator); + } + Integer prev = mAlreadySeen.put(name, mLocator.getLineNumber()); + if (prev != null) { + throw new SAXParseException("name '" + name + "' already seen on line: " + prev, + mLocator); + } + mName = name; + } + + /** + * Gets the float value of the string. + * + * @throws SAXParseException if 'text' can't be parsed as a float. + */ + private float parseFloat(String text) throws SAXParseException { + try { + return Float.parseFloat(text); + } catch (NumberFormatException ex) { + throw new SAXParseException("not a valid float value: '" + text + "'", + mLocator, ex); + } + } + } + + /** + * Return whether the given substring is all whitespace. + */ + private static boolean isWhitespace(char[] text, int start, int length) { + for (int i = start; i < (start + length); i++) { + if (!Character.isSpace(text[i])) { + return false; + } + } + return true; + } + + /** + * Return the contents of text up to the first newline. + */ + private static String firstLine(char[] text, int start, int length) { + // TODO: The line number will be wrong if we skip preceeding blank lines. + while (length > 0) { + if (Character.isSpace(text[start])) { + start++; + length--; + } + } + int newlen = 0; + for (; newlen < length; newlen++) { + final char c = text[newlen]; + if (c == '\n' || c == '\r') { + break; + } + } + return new String(text, start, newlen); + } + + /** + * If the pattern matches, return the first group of that as an Integer. + * If not return null. + */ + private static Integer matchIndexedRegex(Locator locator, Pattern pattern, String text) + throws SAXParseException { + final Matcher m = pattern.matcher(text); + if (m.matches()) { + try { + return Integer.parseInt(m.group(1)); + } catch (NumberFormatException ex) { + throw new SAXParseException("Invalid field name: '" + text + "'", locator, ex); + } + } else { + return null; + } + } + + public static PowerProfile parse(InputStream stream) throws ParseException { + return (new Parser(stream)).parse(); + } + + private PowerProfile() { + } + + public ComponentProfile getComponent(Component component) { + return mComponents.get(component); + } + +} diff --git a/tools/powermodel/src/com/android/powermodel/component/AudioProfile.java b/tools/powermodel/src/com/android/powermodel/component/AudioProfile.java new file mode 100644 index 000000000000..63ff3a6b09fa --- /dev/null +++ b/tools/powermodel/src/com/android/powermodel/component/AudioProfile.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2018 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.powermodel.component; + +import java.util.Arrays; + +import com.android.powermodel.ComponentProfile; +import com.android.powermodel.ParseException; + +public class AudioProfile extends ComponentProfile { + public float onMa; +} + diff --git a/tools/powermodel/src/com/android/powermodel/component/BluetoothProfile.java b/tools/powermodel/src/com/android/powermodel/component/BluetoothProfile.java new file mode 100644 index 000000000000..8f5e7d0ae1df --- /dev/null +++ b/tools/powermodel/src/com/android/powermodel/component/BluetoothProfile.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2018 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.powermodel.component; + +import java.util.Arrays; + +import com.android.powermodel.ComponentProfile; +import com.android.powermodel.ParseException; + +public class BluetoothProfile extends ComponentProfile { + public float idleMa; + public float rxMa; + public float txMa; +} + diff --git a/tools/powermodel/src/com/android/powermodel/component/CameraProfile.java b/tools/powermodel/src/com/android/powermodel/component/CameraProfile.java new file mode 100644 index 000000000000..8ee22d03268c --- /dev/null +++ b/tools/powermodel/src/com/android/powermodel/component/CameraProfile.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2018 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.powermodel.component; + +import java.util.Arrays; + +import com.android.powermodel.ComponentProfile; +import com.android.powermodel.ParseException; + +public class CameraProfile extends ComponentProfile { + public float onMa; +} + diff --git a/tools/powermodel/src/com/android/powermodel/component/CpuProfile.java b/tools/powermodel/src/com/android/powermodel/component/CpuProfile.java new file mode 100644 index 000000000000..0b34fc82622a --- /dev/null +++ b/tools/powermodel/src/com/android/powermodel/component/CpuProfile.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2018 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.powermodel.component; + +import java.util.Arrays; +import java.util.HashMap; + +import com.android.powermodel.ComponentProfile; +import com.android.powermodel.ParseException; + +public class CpuProfile extends ComponentProfile { + public float suspendMa; + public float idleMa; + public float activeMa; + public Cluster[] clusters; + + public static class Cluster { + public int coreCount; + public float onMa; + public Frequency[] frequencies; + } + + public static class Frequency { + public int speedHz; + public float onMa; + } + + public static class Builder { + private float mSuspendMa; + private float mIdleMa; + private float mActiveMa; + private int[] mCoreCount; + private HashMap<Integer,Float> mClusterOnPower = new HashMap<Integer,Float>(); + private HashMap<Integer,int[]> mCoreSpeeds = new HashMap<Integer,int[]>(); + private HashMap<Integer,float[]> mCorePower = new HashMap<Integer,float[]>(); + + public Builder() { + } + + public void setSuspendMa(float value) throws ParseException { + mSuspendMa = value; + } + + public void setIdleMa(float value) throws ParseException { + mIdleMa = value; + } + + public void setActiveMa(float value) throws ParseException { + mActiveMa = value; + } + + public void setCoreCount(int[] value) throws ParseException { + mCoreCount = Arrays.copyOf(value, value.length); + } + + public void setClusterPower(int cluster, float value) throws ParseException { + mClusterOnPower.put(cluster, value); + } + + public void setCoreSpeeds(int cluster, int[] value) throws ParseException { + mCoreSpeeds.put(cluster, Arrays.copyOf(value, value.length)); + float[] power = mCorePower.get(cluster); + if (power != null && value.length != power.length) { + throw new ParseException("length of cpu.core_speeds.cluster" + cluster + + " (" + value.length + ") is different from length of" + + " cpu.core_power.cluster" + cluster + " (" + power.length + ")"); + } + if (mCoreCount != null && cluster >= mCoreCount.length) { + throw new ParseException("cluster " + cluster + + " in cpu.core_speeds.cluster" + cluster + + " is larger than the number of clusters specified in cpu.clusters.cores (" + + mCoreCount.length + ")"); + } + } + + public void setCorePower(int cluster, float[] value) throws ParseException { + mCorePower.put(cluster, Arrays.copyOf(value, value.length)); + int[] speeds = mCoreSpeeds.get(cluster); + if (speeds != null && value.length != speeds.length) { + throw new ParseException("length of cpu.core_power.cluster" + cluster + + " (" + value.length + ") is different from length of" + + " cpu.clusters.cores" + cluster + " (" + speeds.length + ")"); + } + if (mCoreCount != null && cluster >= mCoreCount.length) { + throw new ParseException("cluster " + cluster + + " in cpu.core_power.cluster" + cluster + + " is larger than the number of clusters specified in cpu.clusters.cores (" + + mCoreCount.length + ")"); + } + } + + public CpuProfile build() throws ParseException { + final CpuProfile result = new CpuProfile(); + + // Validate cluster count + + // All null or none null + // TODO + + // Same size + // TODO + + // No gaps + // TODO + + // Fill in values + result.suspendMa = mSuspendMa; + result.idleMa = mIdleMa; + result.activeMa = mActiveMa; + if (mCoreCount != null) { + result.clusters = new Cluster[mCoreCount.length]; + for (int i = 0; i < result.clusters.length; i++) { + final Cluster cluster = result.clusters[i] = new Cluster(); + cluster.coreCount = mCoreCount[i]; + cluster.onMa = mClusterOnPower.get(i); + int[] speeds = mCoreSpeeds.get(i); + float[] power = mCorePower.get(i); + cluster.frequencies = new Frequency[speeds.length]; + for (int j = 0; j < speeds.length; j++) { + final Frequency freq = cluster.frequencies[j] = new Frequency(); + freq.speedHz = speeds[j]; + freq.onMa = power[j]; + } + } + } + + return result; + } + } +} + diff --git a/tools/powermodel/src/com/android/powermodel/component/FlashlightProfile.java b/tools/powermodel/src/com/android/powermodel/component/FlashlightProfile.java new file mode 100644 index 000000000000..c85f3ff236fd --- /dev/null +++ b/tools/powermodel/src/com/android/powermodel/component/FlashlightProfile.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2018 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.powermodel.component; + +import java.util.Arrays; + +import com.android.powermodel.ComponentProfile; +import com.android.powermodel.ParseException; + +public class FlashlightProfile extends ComponentProfile { + public float onMa; +} + diff --git a/tools/powermodel/src/com/android/powermodel/component/GpsProfile.java b/tools/powermodel/src/com/android/powermodel/component/GpsProfile.java new file mode 100644 index 000000000000..83c06a7881ca --- /dev/null +++ b/tools/powermodel/src/com/android/powermodel/component/GpsProfile.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2018 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.powermodel.component; + +import java.util.Arrays; + +import com.android.powermodel.ComponentProfile; +import com.android.powermodel.ParseException; + +public class GpsProfile extends ComponentProfile { + public float onMa; + public float[] signalQualityMa; + + public static class Builder { + private float onMa; + private float[] mSignalQualityMa; + + public Builder() { + } + + public void setOnMa(float value) throws ParseException { + onMa = value; + } + + public void setSignalMa(float[] value) throws ParseException { + mSignalQualityMa = value; + } + + public GpsProfile build() throws ParseException { + GpsProfile result = new GpsProfile(); + result.onMa = onMa; + result.signalQualityMa = mSignalQualityMa == null + ? new float[0] + : Arrays.copyOf(mSignalQualityMa, mSignalQualityMa.length); + return result; + } + } +} + diff --git a/tools/powermodel/src/com/android/powermodel/component/ModemProfile.java b/tools/powermodel/src/com/android/powermodel/component/ModemProfile.java new file mode 100644 index 000000000000..cda72ee205e3 --- /dev/null +++ b/tools/powermodel/src/com/android/powermodel/component/ModemProfile.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2018 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.powermodel.component; + +import java.util.Arrays; + +import com.android.powermodel.ComponentProfile; +import com.android.powermodel.ParseException; + +public class ModemProfile extends ComponentProfile { + public float sleepMa; + public float idleMa; + public float scanningMa; + public float rxMa; + public float[] txMa; + + public float getSleepMa() { + return sleepMa; + } + + public float getIdleMa() { + return idleMa; + } + + public float getRxMa() { + return rxMa; + } + + public float[] getTxMa() { + return Arrays.copyOf(txMa, txMa.length); + } + + public float getScanningMa() { + return scanningMa; + } + + public static class Builder { + private float mSleepMa; + private float mIdleMa; + private float mRxMa; + private float[] mTxMa; + private float mScanningMa; + + public Builder() { + } + + public void setSleepMa(float value) throws ParseException { + mSleepMa = value; + } + + public void setIdleMa(float value) throws ParseException { + mIdleMa = value; + } + + public void setRxMa(float value) throws ParseException { + mRxMa = value; + } + + public void setTxMa(float[] value) throws ParseException { + mTxMa = Arrays.copyOf(value, value.length); + } + + public void setScanningMa(float value) throws ParseException { + mScanningMa = value; + } + + public ModemProfile build() throws ParseException { + ModemProfile result = new ModemProfile(); + result.sleepMa = mSleepMa; + result.idleMa = mIdleMa; + result.rxMa = mRxMa; + result.txMa = mTxMa == null ? new float[0] : mTxMa; + result.scanningMa = mScanningMa; + return result; + } + } +} + diff --git a/tools/powermodel/src/com/android/powermodel/component/ScreenProfile.java b/tools/powermodel/src/com/android/powermodel/component/ScreenProfile.java new file mode 100644 index 000000000000..e1051c69dec6 --- /dev/null +++ b/tools/powermodel/src/com/android/powermodel/component/ScreenProfile.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2018 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.powermodel.component; + +import java.util.Arrays; + +import com.android.powermodel.ComponentProfile; +import com.android.powermodel.ParseException; + +public class ScreenProfile extends ComponentProfile { + public float onMa; + public float fullMa; + public float ambientMa; +} + diff --git a/tools/powermodel/src/com/android/powermodel/component/VideoProfile.java b/tools/powermodel/src/com/android/powermodel/component/VideoProfile.java new file mode 100644 index 000000000000..515279552245 --- /dev/null +++ b/tools/powermodel/src/com/android/powermodel/component/VideoProfile.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2018 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.powermodel.component; + +import java.util.Arrays; + +import com.android.powermodel.ComponentProfile; +import com.android.powermodel.ParseException; + +public class VideoProfile extends ComponentProfile { + public float onMa; +} + + diff --git a/tools/powermodel/src/com/android/powermodel/component/WifiProfile.java b/tools/powermodel/src/com/android/powermodel/component/WifiProfile.java new file mode 100644 index 000000000000..6f424bf0837d --- /dev/null +++ b/tools/powermodel/src/com/android/powermodel/component/WifiProfile.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2018 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.powermodel.component; + +import java.util.Arrays; + +import com.android.powermodel.ComponentProfile; +import com.android.powermodel.ParseException; + +public class WifiProfile extends ComponentProfile { + public float idleMa; + public float rxMa; + public float txMa; +} + diff --git a/tools/powermodel/src/com/android/powermodel/util/Conversion.java b/tools/powermodel/src/com/android/powermodel/util/Conversion.java new file mode 100644 index 000000000000..9a79a2d48a59 --- /dev/null +++ b/tools/powermodel/src/com/android/powermodel/util/Conversion.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2018 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.powermodel.util; + +public class Conversion { + + /** + * Convert the the float[] to an int[]. + * <p> + * Values are rounded to the nearest integral value. Null input + * results in null output. + */ + public static int[] toIntArray(float[] value) { + if (value == null) { + return null; + } + int[] result = new int[value.length]; + for (int i=0; i<result.length; i++) { + result[i] = (int)(value[i] + 0.5f); + } + return result; + } + + /** + * No public constructor. + */ + private Conversion() { + } +} diff --git a/tools/powermodel/test-resource/power_profile.xml b/tools/powermodel/test-resource/power_profile.xml new file mode 100644 index 000000000000..8e388eadc608 --- /dev/null +++ b/tools/powermodel/test-resource/power_profile.xml @@ -0,0 +1,170 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * Copyright (C) 2018 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. +--> + +<!-- Test power profile that parses correctly. --> +<device> + <item name="battery.capacity">2915</item> + + <!-- Number of cores each CPU cluster contains --> + <array name="cpu.clusters.cores"> + <value>4</value> + <value>2</value> + </array> + + <!-- Power consumption when CPU is suspended --> + <item name="cpu.suspend">1.3</item> + + <!-- Additional power consumption when CPU is in a kernel idle loop --> + <item name="cpu.idle">3.9</item> + + <!-- Additional power consumption by CPU excluding cluster and core when + running --> + <item name="cpu.active">18.33</item> + + <!-- Additional power consumption by CPU cluster0 itself when running + excluding cores in it --> + <item name="cpu.cluster_power.cluster0">2.41</item> + + <!-- Additional power consumption by CPU cluster1 itself when running + excluding cores in it --> + <item name="cpu.cluster_power.cluster1">5.29</item> + + <!-- Different CPU speeds as reported in + /sys/devices/system/cpu/cpu0/cpufreq/stats/scaling_available_frequencies --> + <array name="cpu.core_speeds.cluster0"> + <value>100000</value> + <value>303200</value> + <value>380000</value> + <value>476000</value> + <value>552800</value> + <value>648800</value> + <value>725600</value> + <value>802400</value> + <value>879200</value> + </array> + + <!-- Different CPU speeds as reported in + /sys/devices/system/cpu/cpu4/cpufreq/stats/scaling_available_frequencies --> + <array name="cpu.core_speeds.cluster1"> + <value>825600</value> + <value>902400</value> + <value>979200</value> + <value>1056000</value> + <value>1209600</value> + <value>1286400</value> + <value>1363200</value> + </array> + + <!-- Additional power used by a CPU core from cluster 0 when running at + different speeds, excluding cluster and active cost --> + <array name="cpu.core_power.cluster0"> + <value>0.29</value> + <value>0.63</value> + <value>1.23</value> + <value>1.24</value> + <value>2.47</value> + <value>2.54</value> + <value>3.60</value> + <value>3.64</value> + <value>4.42</value> + </array> + + <!-- Additional power used by a CPU core from cluster 1 when running at + different speeds, excluding cluster and active cost --> + <array name="cpu.core_power.cluster1"> + <value>28.98</value> + <value>31.40</value> + <value>33.33</value> + <value>40.12</value> + <value>44.10</value> + <value>90.14</value> + <value>100</value> + </array> + + <!-- Additional power used when screen is ambient mode --> + <item name="ambient.on">12</item> + + <!-- Additional power used when screen is turned on at minimum brightness --> + <item name="screen.on">102.4</item> + <!-- Additional power used when screen is at maximum brightness, compared to + screen at minimum brightness --> + <item name="screen.full">1234</item> + + <!-- Average power used by the camera flash module when on --> + <item name="camera.flashlight">1233.47</item> + + <!-- Average power use by the camera subsystem for a typical camera + application. Intended as a rough estimate for an application running a + preview and capturing approximately 10 full-resolution pictures per + minute. --> + <item name="camera.avg">941</item> + + <!-- Additional power used when video is playing --> + <item name="video">123</item> + + <!-- Additional power used when audio is playing --> + <item name="audio">12</item> + + <!-- Cellular modem related values.--> + <item name="modem.controller.sleep">1</item> + <item name="modem.controller.idle">44</item> + <item name="modem.controller.rx">11</item> + <array name="modem.controller.tx"> <!-- Strength 0 to 4 --> + <value>16</value> + <value>19</value> + <value>22</value> + <value>73</value> + <value>132</value> + </array> + <item name="modem.controller.voltage">1400</item> + <item name="radio.scanning">12</item> + + <!-- GPS related values.--> + <item name="gps.on">1</item> + <array name="gps.signalqualitybased"> <!-- Strength 0 to 1 --> + <value>88</value> + <value>07</value> + </array> + <item name="gps.voltage">1500</item> + + <!-- Idle Receive current for wifi radio in mA.--> + <item name="wifi.controller.idle">2</item> + + <!-- Rx current for wifi radio in mA.--> + <item name="wifi.controller.rx">123</item> + + <!-- Tx current for wifi radio in mA--> + <item name="wifi.controller.tx">333</item> + + <!-- Operating volatage for wifi radio in mV.--> + <item name="wifi.controller.voltage">3700</item> + + <!-- Idle current for bluetooth in mA.--> + <item name="bluetooth.controller.idle">0.02</item> + + <!-- Rx current for bluetooth in mA.--> + <item name="bluetooth.controller.rx">3</item> + + <!-- Tx current for bluetooth in mA--> + <item name="bluetooth.controller.tx">5</item> + + <!-- Operating voltage for bluetooth in mV.--> + <item name="bluetooth.controller.voltage">3300</item> + +</device> + + diff --git a/tools/powermodel/test/com/android/powermodel/PowerProfileTest.java b/tools/powermodel/test/com/android/powermodel/PowerProfileTest.java new file mode 100644 index 000000000000..ab458311a98e --- /dev/null +++ b/tools/powermodel/test/com/android/powermodel/PowerProfileTest.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2018 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.powermodel; + +import java.io.InputStream; + +import com.android.powermodel.component.CpuProfile; +import com.android.powermodel.component.AudioProfile; +import com.android.powermodel.component.BluetoothProfile; +import com.android.powermodel.component.CameraProfile; +import com.android.powermodel.component.FlashlightProfile; +import com.android.powermodel.component.GpsProfile; +import com.android.powermodel.component.ModemProfile; +import com.android.powermodel.component.ScreenProfile; +import com.android.powermodel.component.VideoProfile; +import com.android.powermodel.component.WifiProfile; +import org.junit.Assert; +import org.junit.Test; + +/* + * Additional tests needed: + * - CPU clusters with mismatching counts of speeds and coefficients + * - Extra fields + * - Name listed twice + */ + +/** + * Tests {@link PowerProfile} + */ +public class PowerProfileTest { + private static final float EPSILON = 0.00001f; + + private static InputStream loadPowerProfileStream() { + return PowerProfileTest.class.getResourceAsStream("/power_profile.xml"); + } + + @Test public void testReadGood() throws Exception { + final InputStream is = loadPowerProfileStream(); + + final PowerProfile profile = PowerProfile.parse(is); + + // Audio + final AudioProfile audio = (AudioProfile)profile.getComponent(Component.AUDIO); + Assert.assertEquals(12.0f, audio.onMa, EPSILON); + + // Bluetooth + final BluetoothProfile bluetooth + = (BluetoothProfile)profile.getComponent(Component.BLUETOOTH); + Assert.assertEquals(0.02f, bluetooth.idleMa, EPSILON); + Assert.assertEquals(3.0f, bluetooth.rxMa, EPSILON); + Assert.assertEquals(5.0f, bluetooth.txMa, EPSILON); + + // Camera + final CameraProfile camera = (CameraProfile)profile.getComponent(Component.CAMERA); + Assert.assertEquals(941.0f, camera.onMa, EPSILON); + + // CPU + final CpuProfile cpu = (CpuProfile)profile.getComponent(Component.CPU); + Assert.assertEquals(1.3f, cpu.suspendMa, EPSILON); + Assert.assertEquals(3.9f, cpu.idleMa, EPSILON); + Assert.assertEquals(18.33f, cpu.activeMa, EPSILON); + Assert.assertEquals(2, cpu.clusters.length); + // Cluster 0 + Assert.assertEquals(4, cpu.clusters[0].coreCount); + Assert.assertEquals(2.41f, cpu.clusters[0].onMa, EPSILON); + Assert.assertEquals(9, cpu.clusters[0].frequencies.length, EPSILON); + Assert.assertEquals(100000, cpu.clusters[0].frequencies[0].speedHz); + Assert.assertEquals(0.29f, cpu.clusters[0].frequencies[0].onMa, EPSILON); + Assert.assertEquals(303200, cpu.clusters[0].frequencies[1].speedHz); + Assert.assertEquals(0.63f, cpu.clusters[0].frequencies[1].onMa, EPSILON); + Assert.assertEquals(380000, cpu.clusters[0].frequencies[2].speedHz); + Assert.assertEquals(1.23f, cpu.clusters[0].frequencies[2].onMa, EPSILON); + Assert.assertEquals(476000, cpu.clusters[0].frequencies[3].speedHz); + Assert.assertEquals(1.24f, cpu.clusters[0].frequencies[3].onMa, EPSILON); + Assert.assertEquals(552800, cpu.clusters[0].frequencies[4].speedHz); + Assert.assertEquals(2.47f, cpu.clusters[0].frequencies[4].onMa, EPSILON); + Assert.assertEquals(648800, cpu.clusters[0].frequencies[5].speedHz); + Assert.assertEquals(2.54f, cpu.clusters[0].frequencies[5].onMa, EPSILON); + Assert.assertEquals(725600, cpu.clusters[0].frequencies[6].speedHz); + Assert.assertEquals(3.60f, cpu.clusters[0].frequencies[6].onMa, EPSILON); + Assert.assertEquals(802400, cpu.clusters[0].frequencies[7].speedHz); + Assert.assertEquals(3.64f, cpu.clusters[0].frequencies[7].onMa, EPSILON); + Assert.assertEquals(879200, cpu.clusters[0].frequencies[8].speedHz); + Assert.assertEquals(4.42f, cpu.clusters[0].frequencies[8].onMa, EPSILON); + // Cluster 1 + Assert.assertEquals(2, cpu.clusters[1].coreCount); + Assert.assertEquals(5.29f, cpu.clusters[1].onMa, EPSILON); + Assert.assertEquals(7, cpu.clusters[1].frequencies.length, EPSILON); + Assert.assertEquals(825600, cpu.clusters[1].frequencies[0].speedHz); + Assert.assertEquals(28.98f, cpu.clusters[1].frequencies[0].onMa, EPSILON); + Assert.assertEquals(902400, cpu.clusters[1].frequencies[1].speedHz); + Assert.assertEquals(31.40f, cpu.clusters[1].frequencies[1].onMa, EPSILON); + Assert.assertEquals(979200, cpu.clusters[1].frequencies[2].speedHz); + Assert.assertEquals(33.33f, cpu.clusters[1].frequencies[2].onMa, EPSILON); + Assert.assertEquals(1056000, cpu.clusters[1].frequencies[3].speedHz); + Assert.assertEquals(40.12f, cpu.clusters[1].frequencies[3].onMa, EPSILON); + Assert.assertEquals(1209600, cpu.clusters[1].frequencies[4].speedHz); + Assert.assertEquals(44.10f, cpu.clusters[1].frequencies[4].onMa, EPSILON); + Assert.assertEquals(1286400, cpu.clusters[1].frequencies[5].speedHz); + Assert.assertEquals(90.14f, cpu.clusters[1].frequencies[5].onMa, EPSILON); + Assert.assertEquals(1363200, cpu.clusters[1].frequencies[6].speedHz); + Assert.assertEquals(100f, cpu.clusters[1].frequencies[6].onMa, EPSILON); + + // Flashlight + final FlashlightProfile flashlight + = (FlashlightProfile)profile.getComponent(Component.FLASHLIGHT); + Assert.assertEquals(1233.47f, flashlight.onMa, EPSILON); + + // GPS + final GpsProfile gps = (GpsProfile)profile.getComponent(Component.GPS); + Assert.assertEquals(1.0f, gps.onMa, EPSILON); + Assert.assertEquals(2, gps.signalQualityMa.length); + Assert.assertEquals(88.0f, gps.signalQualityMa[0], EPSILON); + Assert.assertEquals(7.0f, gps.signalQualityMa[1], EPSILON); + + // Modem + final ModemProfile modem = (ModemProfile)profile.getComponent(Component.MODEM); + Assert.assertEquals(1.0f, modem.sleepMa, EPSILON); + Assert.assertEquals(44.0f, modem.idleMa, EPSILON); + Assert.assertEquals(12.0f, modem.scanningMa, EPSILON); + Assert.assertEquals(11.0f, modem.rxMa, EPSILON); + Assert.assertEquals(5, modem.txMa.length); + Assert.assertEquals(16.0f, modem.txMa[0], EPSILON); + Assert.assertEquals(19.0f, modem.txMa[1], EPSILON); + Assert.assertEquals(22.0f, modem.txMa[2], EPSILON); + Assert.assertEquals(73.0f, modem.txMa[3], EPSILON); + Assert.assertEquals(132.0f, modem.txMa[4], EPSILON); + + // Screen + final ScreenProfile screen = (ScreenProfile)profile.getComponent(Component.SCREEN); + Assert.assertEquals(102.4f, screen.onMa, EPSILON); + Assert.assertEquals(1234.0f, screen.fullMa, EPSILON); + Assert.assertEquals(12.0f, screen.ambientMa, EPSILON); + + // Video + final VideoProfile video = (VideoProfile)profile.getComponent(Component.VIDEO); + Assert.assertEquals(123.0f, video.onMa, EPSILON); + + // Wifi + final WifiProfile wifi = (WifiProfile)profile.getComponent(Component.WIFI); + Assert.assertEquals(2.0f, wifi.idleMa, EPSILON); + Assert.assertEquals(123.0f, wifi.rxMa, EPSILON); + Assert.assertEquals(333.0f, wifi.txMa, EPSILON); + } +} |