/** * 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.seyren.core.service.live.server; import static org.python.google.common.collect.ImmutableSet.*; import java.io.IOException; import java.io.InputStream; import java.math.BigDecimal; import java.math.BigInteger; import java.net.Socket; import java.util.Date; import java.util.List; import java.util.concurrent.Executor; import org.python.core.PyList; import org.python.core.PyString; import org.python.core.PyTuple; import org.python.modules.cPickle; import org.python.modules.struct; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Function; import com.google.common.collect.Lists; import com.seyren.core.service.live.Metric; import com.seyren.core.service.live.MetricsTask; import com.seyren.core.service.schedule.CheckRunnerFactory; import com.seyren.core.store.ChecksStore; /** * See http://graphite.readthedocs.org/en/latest/feeding-carbon.html */ public class PickleHandler implements Runnable { private static final Logger LOGGER = LoggerFactory.getLogger(PickleHandler.class); private static final String ISO_8859_1 = "ISO-8859-1"; private Socket socket; private Executor executor; private ChecksStore checksStore; private CheckRunnerFactory checkRunnerFactory; public PickleHandler(Socket socket, Executor executor, ChecksStore checksStore, CheckRunnerFactory checkRunnerFactory) { this.socket = socket; this.executor = executor; this.checksStore = checksStore; this.checkRunnerFactory = checkRunnerFactory; } @Override public void run() { LOGGER.debug("Accepted."); InputStream inputStream = null; try { inputStream = socket.getInputStream(); while (true) { // http://graphite.readthedocs.org/en/latest/feeding-carbon.html // Send the data over a socket to Carbon’s pickle receiver // You’ll need to pack your pickled data into a packet containing a simple header: // payload = pickle.dumps(listOfMetricTuples) // header = struct.pack("!L", len(payload)) // message = header + payload // Here, we have to decode this kind of message BigInteger length = getLength(inputStream); LOGGER.debug("Pickle length {}", length); List<Metric> metrics = getMetrics(inputStream, length); LOGGER.debug("Pickle size: {}", metrics.size()); executor.execute(new MetricsTask(copyOf(metrics), checksStore, checkRunnerFactory)); } } catch (Exception e) { LOGGER.warn("An error occurs when decoding pickle message: '{}' (change log level to debug to see stack trace).", e.getMessage()); LOGGER.debug("An error occurs when decoding pickle message: ", e); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { } } } } private BigInteger getLength(InputStream inputStream) throws IOException { byte[] bytes = new byte[4]; inputStream.read(bytes); String stHeader = new String(bytes, ISO_8859_1); PyTuple tuple = struct.unpack("!L", stHeader); return (BigInteger) tuple.get(0); } @SuppressWarnings("unchecked") private List<Metric> getMetrics(InputStream inputStream, BigInteger length) throws IOException { byte[] bytes = new byte[length.intValue()]; inputStream.read(bytes); String payload = new String(bytes, ISO_8859_1); PyString pyString = new PyString(payload); PyList pyList = (PyList) cPickle.loads(pyString); return Lists.transform(pyList, new PythonToJava()); } private static class PythonToJava implements Function<Object, Metric> { @Override public Metric apply(Object pyObject) { PyTuple pyTuple = (PyTuple) pyObject; Metric metric = new Metric(); metric.setName(pyTuple.get(0).toString()); PyTuple data = ((PyTuple) pyTuple.get(1)); metric.setTimestamp(new Date(((Number) data.get(0)).longValue() * 1000)); metric.setValue(new BigDecimal(((Number) data.get(1)).doubleValue())); return metric; } } }