Repository: flink Updated Branches: refs/heads/master db919a585 -> 7cd9bb5f1
[FLINK-4539] [runtime] Reuse functionality for Physical Memory size in 'Hardware' and 'EnvironmentInformation'. Project: http://git-wip-us.apache.org/repos/asf/flink/repo Commit: http://git-wip-us.apache.org/repos/asf/flink/commit/97a83a1f Tree: http://git-wip-us.apache.org/repos/asf/flink/tree/97a83a1f Diff: http://git-wip-us.apache.org/repos/asf/flink/diff/97a83a1f Branch: refs/heads/master Commit: 97a83a1fa62c06db19b09104d85c5e2bb850df0f Parents: db919a5 Author: Stephan Ewen <[email protected]> Authored: Tue Aug 30 16:31:42 2016 +0200 Committer: Stephan Ewen <[email protected]> Committed: Wed Aug 31 18:18:02 2016 +0200 ---------------------------------------------------------------------- .../apache/flink/runtime/instance/Hardware.java | 245 ----------------- .../runtime/instance/HardwareDescription.java | 2 + .../runtime/util/EnvironmentInformation.java | 35 ++- .../org/apache/flink/runtime/util/Hardware.java | 272 +++++++++++++++++++ .../flink/runtime/instance/HardwareTest.java | 49 ---- .../apache/flink/runtime/util/HardwareTest.java | 49 ++++ 6 files changed, 340 insertions(+), 312 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/flink/blob/97a83a1f/flink-runtime/src/main/java/org/apache/flink/runtime/instance/Hardware.java ---------------------------------------------------------------------- diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/instance/Hardware.java b/flink-runtime/src/main/java/org/apache/flink/runtime/instance/Hardware.java deleted file mode 100644 index c393d68..0000000 --- a/flink-runtime/src/main/java/org/apache/flink/runtime/instance/Hardware.java +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.apache.flink.runtime.instance; - -import java.io.BufferedReader; -import java.io.FileReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.apache.flink.util.OperatingSystem; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Convenience class to extract hardware specifics of the computer executing this class - */ -public class Hardware { - - private static final Logger LOG = LoggerFactory.getLogger(Hardware.class); - - private static final String LINUX_MEMORY_INFO_PATH = "/proc/meminfo"; - - private static final Pattern LINUX_MEMORY_REGEX = Pattern.compile("^MemTotal:\\s*(\\d+)\\s+kB$"); - - - - /** - * Gets the number of CPU cores (hardware contexts) that the JVM has access to. - * - * @return The number of CPU cores. - */ - public static int getNumberCPUCores() { - return Runtime.getRuntime().availableProcessors(); - } - - /** - * Returns the size of the physical memory in bytes. - * - * @return the size of the physical memory in bytes or <code>-1</code> if - * the size could not be determined - */ - public static long getSizeOfPhysicalMemory() { - switch (OperatingSystem.getCurrentOperatingSystem()) { - case LINUX: - return getSizeOfPhysicalMemoryForLinux(); - - case WINDOWS: - return getSizeOfPhysicalMemoryForWindows(); - - case MAC_OS: - return getSizeOfPhysicalMemoryForMac(); - - case FREE_BSD: - return getSizeOfPhysicalMemoryForFreeBSD(); - - case UNKNOWN: - LOG.error("Cannot determine size of physical memory for unknown operating system"); - return -1; - - default: - LOG.error("Unrecognized OS: " + OperatingSystem.getCurrentOperatingSystem()); - return -1; - } - } - - /** - * Returns the size of the physical memory in bytes on a Linux-based - * operating system. - * - * @return the size of the physical memory in bytes or <code>-1</code> if - * the size could not be determined - */ - private static long getSizeOfPhysicalMemoryForLinux() { - try (BufferedReader lineReader = new BufferedReader(new FileReader(LINUX_MEMORY_INFO_PATH))) { - String line; - while ((line = lineReader.readLine()) != null) { - Matcher matcher = LINUX_MEMORY_REGEX.matcher(line); - if (matcher.matches()) { - String totalMemory = matcher.group(1); - return Long.parseLong(totalMemory) * 1024L; // Convert from kilobyte to byte - } - } - // expected line did not come - LOG.error("Cannot determine the size of the physical memory for Linux host (using '/proc/meminfo'). Unexpected format."); - return -1; - } - catch (NumberFormatException e) { - LOG.error("Cannot determine the size of the physical memory for Linux host (using '/proc/meminfo'). Unexpected format."); - return -1; - } - catch (Throwable t) { - LOG.error("Cannot determine the size of the physical memory for Linux host (using '/proc/meminfo'): " + t.getMessage(), t); - return -1; - } - } - - /** - * Returns the size of the physical memory in bytes on a Mac OS-based - * operating system - * - * @return the size of the physical memory in bytes or <code>-1</code> if - * the size could not be determined - */ - private static long getSizeOfPhysicalMemoryForMac() { - - BufferedReader bi = null; - - try { - Process proc = Runtime.getRuntime().exec("sysctl hw.memsize"); - - bi = new BufferedReader( - new InputStreamReader(proc.getInputStream())); - - String line; - - while ((line = bi.readLine()) != null) { - if (line.startsWith("hw.memsize")) { - long memsize = Long.parseLong(line.split(":")[1].trim()); - bi.close(); - proc.destroy(); - return memsize; - } - } - - } catch (Throwable t) { - LOG.error("Cannot determine physical memory of machine for MacOS host: " + t.getMessage(), t); - return -1; - } finally { - if (bi != null) { - try { - bi.close(); - } catch (IOException ignored) {} - } - } - return -1; - } - - /** - * Returns the size of the physical memory in bytes on FreeBSD. - * - * @return the size of the physical memory in bytes or <code>-1</code> if - * the size could not be determined - */ - private static long getSizeOfPhysicalMemoryForFreeBSD() { - BufferedReader bi = null; - try { - Process proc = Runtime.getRuntime().exec("sysctl hw.physmem"); - - bi = new BufferedReader(new InputStreamReader(proc.getInputStream())); - - String line; - - while ((line = bi.readLine()) != null) { - if (line.startsWith("hw.physmem")) { - long memsize = Long.parseLong(line.split(":")[1].trim()); - bi.close(); - proc.destroy(); - return memsize; - } - } - - LOG.error("Cannot determine the size of the physical memory for FreeBSD host (using 'sysctl hw.physmem')."); - return -1; - } - catch (Throwable t) { - LOG.error("Cannot determine the size of the physical memory for FreeBSD host (using 'sysctl hw.physmem'): " + t.getMessage(), t); - return -1; - } - finally { - if (bi != null) { - try { - bi.close(); - } catch (IOException ignored) {} - } - } - } - - /** - * Returns the size of the physical memory in bytes on Windows. - * - * @return the size of the physical memory in bytes or <code>-1</code> if - * the size could not be determined - */ - private static long getSizeOfPhysicalMemoryForWindows() { - BufferedReader bi = null; - try { - Process proc = Runtime.getRuntime().exec("wmic memorychip get capacity"); - - bi = new BufferedReader(new InputStreamReader(proc.getInputStream())); - - String line = bi.readLine(); - if (line == null) { - return -1L; - } - - if (!line.startsWith("Capacity")) { - return -1L; - } - - long sizeOfPhyiscalMemory = 0L; - while ((line = bi.readLine()) != null) { - if (line.isEmpty()) { - continue; - } - - line = line.replaceAll(" ", ""); - sizeOfPhyiscalMemory += Long.parseLong(line); - } - return sizeOfPhyiscalMemory; - } - catch (Throwable t) { - LOG.error("Cannot determine the size of the physical memory for Windows host (using 'wmic memorychip'): " + t.getMessage(), t); - return -1L; - } - finally { - if (bi != null) { - try { - bi.close(); - } catch (Throwable ignored) {} - } - } - } - - // -------------------------------------------------------------------------------------------- - - private Hardware() {} -} http://git-wip-us.apache.org/repos/asf/flink/blob/97a83a1f/flink-runtime/src/main/java/org/apache/flink/runtime/instance/HardwareDescription.java ---------------------------------------------------------------------- diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/instance/HardwareDescription.java b/flink-runtime/src/main/java/org/apache/flink/runtime/instance/HardwareDescription.java index 92f7414..bfcc1e5 100644 --- a/flink-runtime/src/main/java/org/apache/flink/runtime/instance/HardwareDescription.java +++ b/flink-runtime/src/main/java/org/apache/flink/runtime/instance/HardwareDescription.java @@ -18,6 +18,8 @@ package org.apache.flink.runtime.instance; +import org.apache.flink.runtime.util.Hardware; + import java.io.Serializable; /** http://git-wip-us.apache.org/repos/asf/flink/blob/97a83a1f/flink-runtime/src/main/java/org/apache/flink/runtime/util/EnvironmentInformation.java ---------------------------------------------------------------------- diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/util/EnvironmentInformation.java b/flink-runtime/src/main/java/org/apache/flink/runtime/util/EnvironmentInformation.java index a10522d..39fa80e 100644 --- a/flink-runtime/src/main/java/org/apache/flink/runtime/util/EnvironmentInformation.java +++ b/flink-runtime/src/main/java/org/apache/flink/runtime/util/EnvironmentInformation.java @@ -20,15 +20,16 @@ package org.apache.flink.runtime.util; import java.io.InputStream; import java.lang.management.ManagementFactory; -import java.lang.management.OperatingSystemMXBean; import java.lang.management.RuntimeMXBean; import java.lang.reflect.Method; import java.util.List; import java.util.Properties; import org.apache.hadoop.util.VersionInfo; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; + import org.apache.hadoop.security.UserGroupInformation; /** @@ -102,9 +103,7 @@ public class EnvironmentInformation { String user = System.getProperty("user.name"); if (user == null) { user = UNKNOWN; - if (LOG.isDebugEnabled()) { - LOG.debug("Cannot determine user/group information for the current user."); - } + LOG.debug("Cannot determine user/group information for the current user."); } return user; } @@ -112,27 +111,27 @@ public class EnvironmentInformation { /** * The maximum JVM heap size, in bytes. * + * <p>This method uses the <i>-Xmx</i> value of the JVM, if set. If not set, it returns (as + * a heuristic) 1/4th of the physical memory size. + * * @return The maximum JVM heap size, in bytes. */ public static long getMaxJvmHeapMemory() { - long maxMemory = Runtime.getRuntime().maxMemory(); - - if (maxMemory == Long.MAX_VALUE) { - // amount of free memory unknown - try { - // workaround for Oracle JDK - OperatingSystemMXBean operatingSystemMXBean = ManagementFactory.getOperatingSystemMXBean(); - Class<?> clazz = Class.forName("com.sun.management.OperatingSystemMXBean"); - Method method = clazz.getMethod("getTotalPhysicalMemorySize"); - maxMemory = (Long) method.invoke(operatingSystemMXBean) / 4; - } - catch (Throwable e) { + final long maxMemory = Runtime.getRuntime().maxMemory(); + if (maxMemory != Long.MAX_VALUE) { + // we have the proper max memory + return maxMemory; + } else { + // max JVM heap size is not set - use the heuristic to use 1/4th of the physical memory + final long physicalMemory = Hardware.getSizeOfPhysicalMemory(); + if (physicalMemory != -1) { + // got proper value for physical memory + return physicalMemory / 4; + } else { throw new RuntimeException("Could not determine the amount of free memory.\n" + "Please set the maximum memory for the JVM, e.g. -Xmx512M for 512 megabytes."); } } - - return maxMemory; } /** http://git-wip-us.apache.org/repos/asf/flink/blob/97a83a1f/flink-runtime/src/main/java/org/apache/flink/runtime/util/Hardware.java ---------------------------------------------------------------------- diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/util/Hardware.java b/flink-runtime/src/main/java/org/apache/flink/runtime/util/Hardware.java new file mode 100644 index 0000000..0b0e7ff --- /dev/null +++ b/flink-runtime/src/main/java/org/apache/flink/runtime/util/Hardware.java @@ -0,0 +1,272 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.flink.runtime.util; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.management.ManagementFactory; +import java.lang.management.OperatingSystemMXBean; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.flink.util.OperatingSystem; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Convenience class to extract hardware specifics of the computer executing the running JVM. + */ +public class Hardware { + + private static final Logger LOG = LoggerFactory.getLogger(Hardware.class); + + private static final String LINUX_MEMORY_INFO_PATH = "/proc/meminfo"; + + private static final Pattern LINUX_MEMORY_REGEX = Pattern.compile("^MemTotal:\\s*(\\d+)\\s+kB$"); + + // ------------------------------------------------------------------------ + + /** + * Gets the number of CPU cores (hardware contexts) that the JVM has access to. + * + * @return The number of CPU cores. + */ + public static int getNumberCPUCores() { + return Runtime.getRuntime().availableProcessors(); + } + + /** + * Returns the size of the physical memory in bytes. + * + * @return the size of the physical memory in bytes or {@code -1}, if + * the size could not be determined. + */ + public static long getSizeOfPhysicalMemory() { + // first try if the JVM can directly tell us what the system memory is + // this works only on Oracle JVMs + try { + Class<?> clazz = Class.forName("com.sun.management.OperatingSystemMXBean"); + Method method = clazz.getMethod("getTotalPhysicalMemorySize"); + OperatingSystemMXBean operatingSystemMXBean = ManagementFactory.getOperatingSystemMXBean(); + + // someone may install different beans, so we need to check whether the bean + // is in fact the sun management bean + if (clazz.isInstance(operatingSystemMXBean)) { + return (Long) method.invoke(operatingSystemMXBean); + } + } + catch (ClassNotFoundException e) { + // this happens on non-Oracle JVMs, do nothing and use the alternative code paths + } + catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + LOG.warn("Access to physical memory size: " + + "com.sun.management.OperatingSystemMXBean incompatibly changed.", e); + } + + // we now try the OS specific access paths + switch (OperatingSystem.getCurrentOperatingSystem()) { + case LINUX: + return getSizeOfPhysicalMemoryForLinux(); + + case WINDOWS: + return getSizeOfPhysicalMemoryForWindows(); + + case MAC_OS: + return getSizeOfPhysicalMemoryForMac(); + + case FREE_BSD: + return getSizeOfPhysicalMemoryForFreeBSD(); + + case UNKNOWN: + LOG.error("Cannot determine size of physical memory for unknown operating system"); + return -1; + + default: + LOG.error("Unrecognized OS: " + OperatingSystem.getCurrentOperatingSystem()); + return -1; + } + } + + /** + * Returns the size of the physical memory in bytes on a Linux-based + * operating system. + * + * @return the size of the physical memory in bytes or {@code -1}, if + * the size could not be determined + */ + private static long getSizeOfPhysicalMemoryForLinux() { + try (BufferedReader lineReader = new BufferedReader(new FileReader(LINUX_MEMORY_INFO_PATH))) { + String line; + while ((line = lineReader.readLine()) != null) { + Matcher matcher = LINUX_MEMORY_REGEX.matcher(line); + if (matcher.matches()) { + String totalMemory = matcher.group(1); + return Long.parseLong(totalMemory) * 1024L; // Convert from kilobyte to byte + } + } + // expected line did not come + LOG.error("Cannot determine the size of the physical memory for Linux host (using '/proc/meminfo'). " + + "Unexpected format."); + return -1; + } + catch (NumberFormatException e) { + LOG.error("Cannot determine the size of the physical memory for Linux host (using '/proc/meminfo'). " + + "Unexpected format."); + return -1; + } + catch (Throwable t) { + LOG.error("Cannot determine the size of the physical memory for Linux host (using '/proc/meminfo') ", t); + return -1; + } + } + + /** + * Returns the size of the physical memory in bytes on a Mac OS-based + * operating system + * + * @return the size of the physical memory in bytes or {@code -1}, if + * the size could not be determined + */ + private static long getSizeOfPhysicalMemoryForMac() { + BufferedReader bi = null; + try { + Process proc = Runtime.getRuntime().exec("sysctl hw.memsize"); + + bi = new BufferedReader(new InputStreamReader(proc.getInputStream())); + + String line; + while ((line = bi.readLine()) != null) { + if (line.startsWith("hw.memsize")) { + long memsize = Long.parseLong(line.split(":")[1].trim()); + bi.close(); + proc.destroy(); + return memsize; + } + } + + } catch (Throwable t) { + LOG.error("Cannot determine physical memory of machine for MacOS host", t); + return -1; + } finally { + if (bi != null) { + try { + bi.close(); + } catch (IOException ignored) {} + } + } + return -1; + } + + /** + * Returns the size of the physical memory in bytes on FreeBSD. + * + * @return the size of the physical memory in bytes or {@code -1}, if + * the size could not be determined + */ + private static long getSizeOfPhysicalMemoryForFreeBSD() { + BufferedReader bi = null; + try { + Process proc = Runtime.getRuntime().exec("sysctl hw.physmem"); + + bi = new BufferedReader(new InputStreamReader(proc.getInputStream())); + + String line; + while ((line = bi.readLine()) != null) { + if (line.startsWith("hw.physmem")) { + long memsize = Long.parseLong(line.split(":")[1].trim()); + bi.close(); + proc.destroy(); + return memsize; + } + } + + LOG.error("Cannot determine the size of the physical memory for FreeBSD host " + + "(using 'sysctl hw.physmem')."); + return -1; + } + catch (Throwable t) { + LOG.error("Cannot determine the size of the physical memory for FreeBSD host " + + "(using 'sysctl hw.physmem')", t); + return -1; + } + finally { + if (bi != null) { + try { + bi.close(); + } catch (IOException ignored) {} + } + } + } + + /** + * Returns the size of the physical memory in bytes on Windows. + * + * @return the size of the physical memory in bytes or {@code -1}, if + * the size could not be determined + */ + private static long getSizeOfPhysicalMemoryForWindows() { + BufferedReader bi = null; + try { + Process proc = Runtime.getRuntime().exec("wmic memorychip get capacity"); + + bi = new BufferedReader(new InputStreamReader(proc.getInputStream())); + + String line = bi.readLine(); + if (line == null) { + return -1L; + } + + if (!line.startsWith("Capacity")) { + return -1L; + } + + long sizeOfPhyiscalMemory = 0L; + while ((line = bi.readLine()) != null) { + if (line.isEmpty()) { + continue; + } + + line = line.replaceAll(" ", ""); + sizeOfPhyiscalMemory += Long.parseLong(line); + } + return sizeOfPhyiscalMemory; + } + catch (Throwable t) { + LOG.error("Cannot determine the size of the physical memory for Windows host " + + "(using 'wmic memorychip')", t); + return -1L; + } + finally { + if (bi != null) { + try { + bi.close(); + } catch (Throwable ignored) {} + } + } + } + + // -------------------------------------------------------------------------------------------- + + private Hardware() {} +} http://git-wip-us.apache.org/repos/asf/flink/blob/97a83a1f/flink-runtime/src/test/java/org/apache/flink/runtime/instance/HardwareTest.java ---------------------------------------------------------------------- diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/instance/HardwareTest.java b/flink-runtime/src/test/java/org/apache/flink/runtime/instance/HardwareTest.java deleted file mode 100644 index a7ac28b..0000000 --- a/flink-runtime/src/test/java/org/apache/flink/runtime/instance/HardwareTest.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.apache.flink.runtime.instance; - -import static org.junit.Assert.*; - -import org.junit.Test; - -public class HardwareTest { - - @Test - public void testCpuCores() { - try { - assertTrue(Hardware.getNumberCPUCores() >= 0); - } - catch (Exception e) { - e.printStackTrace(); - fail(e.getMessage()); - } - } - - @Test - public void testPhysicalMemory() { - try { - long physMem = Hardware.getSizeOfPhysicalMemory(); - assertTrue(physMem >= -1); - } - catch (Exception e) { - e.printStackTrace(); - fail(e.getMessage()); - } - } -} http://git-wip-us.apache.org/repos/asf/flink/blob/97a83a1f/flink-runtime/src/test/java/org/apache/flink/runtime/util/HardwareTest.java ---------------------------------------------------------------------- diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/util/HardwareTest.java b/flink-runtime/src/test/java/org/apache/flink/runtime/util/HardwareTest.java new file mode 100644 index 0000000..1995f4d --- /dev/null +++ b/flink-runtime/src/test/java/org/apache/flink/runtime/util/HardwareTest.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.flink.runtime.util; + +import static org.junit.Assert.*; + +import org.junit.Test; + +public class HardwareTest { + + @Test + public void testCpuCores() { + try { + assertTrue(Hardware.getNumberCPUCores() >= 0); + } + catch (Exception e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + + @Test + public void testPhysicalMemory() { + try { + long physMem = Hardware.getSizeOfPhysicalMemory(); + assertTrue(physMem >= -1); + } + catch (Exception e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } +}
