{"action":"created","comment":{"url":"https://api.github.com/repos/akka/akka/pulls/comments/90875108","pull_request_review_id":11388137,"id":90875108,"diff_hunk":"@@ -16,177 +20,272 @@ import akka.util.{ ByteString, OptionVal }\n  * Multiple instruments are automatically handled, however they MUST NOT overlap in their idenfitiers.\n  *\n  * Instances of `RemoteInstrument` are created from configuration. A new instance of RemoteInstrument\n- * will be created for each encoder and decoder. It's only called from the stage, so if it dosn't\n+ * will be created for each encoder and decoder. It's only called from the stage, so if it doesn't\n  * delegate to any shared instance it doesn't have to be thread-safe.\n  */\n abstract class RemoteInstrument {\n   /**\n    * Instrument identifier.\n    *\n-   * MUST be >=0 and <32.\n+   * MUST be >=1 and <32.\n    *\n-   * Values between 0 and 7 are reserved for Akka internal use.\n+   * Values between 1 and 7 are reserved for Akka internal use.\n    */\n   def identifier: Byte\n \n   /**\n-   * Called right before putting the message onto the wire.\n-   * Parameters MAY be `null` (except `message`)!\n-   *\n-   * @return `metadata` rendered to be serialized into the remove envelope, or `null` if no metadata should be attached\n+   * Should the serialization be timed? Otherwise times are always 0.\n    */\n-  def remoteMessageSent(recipient: ActorRef, message: Object, sender: ActorRef): ByteString\n+  def serializationTimingEnabled: Boolean = false\n \n   /**\n-   * Called once a message (containing a metadata field designated for this instrument) has been deserialized from the wire.\n+   * Called while serializing the message.\n+   * Parameters MAY be `null` (except `message` and `buffer`)!\n    */\n-  def remoteMessageReceived(recipient: ActorRef, message: Object, sender: ActorRef, metadata: ByteString): Unit\n-\n-}\n-\n-object NoopRemoteInstrument extends RemoteInstrument {\n-  override def identifier: Byte =\n-    -1\n+  def remoteWriteMetadata(recipient: ActorRef, message: Object, sender: ActorRef, buffer: ByteBuffer): Unit\n \n-  override def remoteMessageSent(recipient: ActorRef, message: Object, sender: ActorRef): ByteString =\n-    null\n+  /**\n+   * Called right before putting the message onto the wire.\n+   * Parameters MAY be `null` (except `message` and `buffer`)!\n+   */\n+  def remoteMessageSent(recipient: ActorRef, message: Object, sender: ActorRef, size: Int, time: Long): Unit\n \n-  override def remoteMessageReceived(recipient: ActorRef, message: Object, sender: ActorRef, metadata: ByteString): Unit =\n-    ()\n-}\n+  /**\n+   * Called while deserializing the message once a message (containing a metadata field designated for this instrument) is found.\n+   */\n+  def remoteReadMetadata(recipient: ActorRef, message: Object, sender: ActorRef, buffer: ByteBuffer): Unit\n \n-/** INTERNAL API */\n-private[remote] object RemoteInstruments {\n-  def create(system: ExtendedActorSystem): Vector[RemoteInstrument] = {\n-    val c = system.settings.config\n-    val path = \"akka.remote.artery.advanced.instruments\"\n-    import scala.collection.JavaConverters._\n-    c.getStringList(path).asScala.map { fqcn ⇒\n-      system\n-        .dynamicAccess.createInstanceFor[RemoteInstrument](fqcn, Nil)\n-        .orElse(system.dynamicAccess.createInstanceFor[RemoteInstrument](fqcn, List(classOf[ExtendedActorSystem] → system)))\n-        .get\n-    }(collection.breakOut)\n-  }\n+  /**\n+   * Called when the message has been deserialized.\n+   */\n+  def remoteMessageReceived(recipient: ActorRef, message: Object, sender: ActorRef, size: Int, time: Long): Unit\n }\n \n /**\n  * INTERNAL API\n  *\n- * This datastructure is specialized for addressing directly into a known IDs slot.\n- * It is used when deserializing/serializing metadata and we know the ID we want to reach into.\n+ * The metadata section is stored as raw bytes (prefixed with an Int length field,\n+ * the same way as any other literal), however the internal structure of it is as follows:\n  *\n- * Mutable & NOT thread-safe.\n+ * {{{\n+ * Metadata entry:\n  *\n- * Fixed-size: 32-slots array-backed Map-like structure.\n- * Lazy: The backing array is allocated lazily only when needed, thus we pay no cost for the metadata array if\n- * the system is not using metadata.\n- * Life-cycle: Owned and linked to the lifecycle of an [[OutboundEnvelope]].\n- * Re-cycled:  Aimed to be re-cycled to produce the least possible GC-churn, by calling `clear()` when done with it.\n+ *   0                   1                   2                   3\n+ *   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2\n+ *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n+ *  |   Key     |             Metadata entry length                   |\n+ *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n+ *  |                   ... metadata entry ...                        |\n+ *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n+ * }}}\n  *\n- * Reserved keys: The keys 0–7 are reserved for Akka internal purposes and future extensions.\n  */\n-private[remote] final class MetadataMap[T >: Null] {\n-  val capacity = 32\n-\n-  protected var backing: Array[T] = null // TODO re-think if a plain LinkedList wouldn't be fine here?\n-\n-  private var _usedSlots = 0\n+private[remote] final class RemoteInstruments(\n+  private val system: ExtendedActorSystem,\n+  private val log:    LoggingAdapter,\n+  _instruments:       Vector[RemoteInstrument]) {\n+  import RemoteInstruments._\n \n-  def usedSlots = _usedSlots\n+  def this(system: ExtendedActorSystem, log: LoggingAdapter) = this(system, log, RemoteInstruments.create(system, log))\n+  def this(system: ExtendedActorSystem) = this(system, Logging.getLogger(system, classOf[RemoteInstruments]))\n \n-  def apply(i: Int): OptionVal[T] =\n-    if (backing == null) OptionVal.None\n-    else OptionVal[T](backing(i))\n+  // keep the remote instruments sorted by identifier to speed up deserialization\n+  private val instruments: Vector[RemoteInstrument] = _instruments.sortBy(_.identifier)\n+  // does any of the instruments want serialization timing?\n+  private val serializationTimingEnabled = instruments.exists(_.serializationTimingEnabled)\n \n-  def isEmpty = usedSlots == 0\n-\n-  def nonEmpty = !isEmpty\n+  def serialize(outboundEnvelope: OptionVal[OutboundEnvelope], buffer: ByteBuffer): Unit = {\n+    if (instruments.nonEmpty && outboundEnvelope.isDefined) {\n+      val startPos = buffer.position()\n+      val oe = outboundEnvelope.get\n+      try {\n+        buffer.putInt(0)\n+        val dataPos = buffer.position()\n+        var i = 0\n+        while (i < instruments.length) {\n+          val rewindPos = buffer.position()\n+          val instrument = instruments(i)\n+          try {\n+            serializeInstrument(instrument, oe, buffer)\n+          } catch {\n+            case NonFatal(t) ⇒\n+              log.debug(\n+                \"Skipping serialization of RemoteInstrument {} since it failed with {}\",\n+                instrument.identifier, t.getMessage)\n+              buffer.position(rewindPos)\n+          }\n+          i += 1\n+        }\n+        val endPos = buffer.position()\n+        if (endPos == dataPos) {\n+          // no instruments wrote anything so we need to rewind to start\n+          buffer.position(startPos)\n+        } else {\n+          // some instruments wrote data, so write the total length\n+          buffer.putInt(startPos, endPos - dataPos)\n+        }\n+      } catch {\n+        case NonFatal(t) ⇒\n+          log.debug(\"Skipping serialization of all RemoteInstruments due to unhandled failure {}\", t)\n+          buffer.position(startPos)\n+      }\n+    }\n+  }\n \n-  def hasValueFor(i: Int) = nonEmpty && backing(i) != null\n+  private def serializeInstrument(instrument: RemoteInstrument, outboundEnvelope: OutboundEnvelope, buffer: ByteBuffer): Unit = {\n+    val startPos = buffer.position()\n+    buffer.putInt(0)\n+    val dataPos = buffer.position()\n+    instrument.remoteWriteMetadata(outboundEnvelope.recipient.orNull, outboundEnvelope.message, outboundEnvelope.sender.orNull, buffer)\n+    val endPos = buffer.position()\n+    if (endPos == dataPos) {\n+      // if the instrument didn't write anything, then rewind to the start\n+      buffer.position(startPos)\n+    } else {\n+      // the instrument wrote something so we need to write the identifier and length\n+      buffer.putInt(startPos, combineKeyLength(instrument.identifier, endPos - dataPos))\n+    }\n+  }\n \n-  // FIXME too specialized...\n-  def foldLeftValues[A](zero: A)(f: (A, T) ⇒ A): A = {\n-    var acc: A = zero\n-    var hit = 0\n-    var i = 0\n-    while (i < capacity && hit < _usedSlots) {\n-      val it = backing(i)\n-      if (it != null) {\n-        acc = f(acc, it)\n-        hit += 1\n-      }\n-      i += 1\n+  def deserialize(inboundEnvelope: InboundEnvelope): Unit = {\n+    if (inboundEnvelope.flag(EnvelopeBuffer.MetadataPresentFlag)) {\n+      inboundEnvelope.envelopeBuffer.byteBuffer.position(EnvelopeBuffer.MetadataContainerAndLiteralSectionOffset)\n+      deserializeRaw(inboundEnvelope)\n     }\n-    acc\n   }\n \n-  /** Heavy operation, only used for error logging */\n-  def keysWithValues: List[Int] = backing.zipWithIndex.filter({ case (t, id) ⇒ t != null }).map(_._2).toList\n-\n-  def foreach(f: (Byte, T) ⇒ Unit) = {\n-    var i = 0\n-    var hit = 0\n-    while (i < capacity && hit < _usedSlots) {\n-      val t = backing(i)\n-      if (t != null) {\n-        f(i.toByte, t)\n-        hit += 1\n+  def deserializeRaw(inboundEnvelope: InboundEnvelope): Unit = {\n+    val buffer = inboundEnvelope.envelopeBuffer.byteBuffer\n+    val length = buffer.getInt\n+    val endPos = buffer.position() + length\n+    try {\n+      if (instruments.nonEmpty) {\n+        var i = 0\n+        while (i < instruments.length && buffer.position() < endPos) {\n+          val instrument = instruments(i)\n+          val startPos = buffer.position()\n+          val keyAndLength = buffer.getInt\n+          val dataPos = buffer.position()\n+          val key = getKey(keyAndLength)\n+          val length = getLength(keyAndLength)\n+          var nextPos = dataPos + length\n+          val identifier = instrument.identifier\n+          if (key == identifier) {\n+            try {\n+              deserializeInstrument(instrument, inboundEnvelope, buffer)\n+            } catch {\n+              case NonFatal(t) ⇒\n+                log.debug(\n+                  \"Skipping deserialization of RemoteInstrument {} since it failed with {}\",\n+                  instrument.identifier, t.getMessage)\n+            }\n+            i += 1\n+          } else if (key > identifier) {\n+            // since instruments are sorted on both sides skip this local one and retry the serialized one\n+            log.debug(\"Skipping local RemoteInstrument {} that has no matching data in the message\", identifier)\n+            nextPos = startPos\n+            i += 1\n+          } else {\n+            // since instruments are sorted on both sides skip the serialized one and retry the local one\n+            log.debug(\"Skipping serialized data in message for RemoteInstrument {} that has no local match\", key)\n+          }\n+          buffer.position(nextPos)\n+        }\n+      } else {\n+        log.debug(\n+          \"Skipping serialized data in message for RemoteInstrument(s) {} that has no local match\",\n+          remoteInstrumentIdIteratorRaw(buffer, endPos).mkString(\"[\", \", \", \"]\"))","path":"akka-remote/src/main/scala/akka/remote/artery/RemoteInstrument.scala","position":272,"original_position":272,"commit_id":"2b7c8203fb4ec61125b12d73ad33dc518737aedb","original_commit_id":"2b7c8203fb4ec61125b12d73ad33dc518737aedb","user":{"login":"patriknw","id":336161,"avatar_url":"https://avatars.githubusercontent.com/u/336161?v=3","gravatar_id":"","url":"https://api.github.com/users/patriknw","html_url":"https://github.com/patriknw","followers_url":"https://api.github.com/users/patriknw/followers","following_url":"https://api.github.com/users/patriknw/following{/other_user}","gists_url":"https://api.github.com/users/patriknw/gists{/gist_id}","starred_url":"https://api.github.com/users/patriknw/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/patriknw/subscriptions","organizations_url":"https://api.github.com/users/patriknw/orgs","repos_url":"https://api.github.com/users/patriknw/repos","events_url":"https://api.github.com/users/patriknw/events{/privacy}","received_events_url":"https://api.github.com/users/patriknw/received_events","type":"User","site_admin":false},"body":"is this costly? perhaps add `if (log.isDebugEnabled)`?","created_at":"2016-12-05T14:34:33Z","updated_at":"2016-12-05T14:36:42Z","html_url":"https://github.com/akka/akka/pull/21924#discussion_r90875108","pull_request_url":"https://api.github.com/repos/akka/akka/pulls/21924","_links":{"self":{"href":"https://api.github.com/repos/akka/akka/pulls/comments/90875108"},"html":{"href":"https://github.com/akka/akka/pull/21924#discussion_r90875108"},"pull_request":{"href":"https://api.github.com/repos/akka/akka/pulls/21924"}}},"pull_request":{"url":"https://api.github.com/repos/akka/akka/pulls/21924","id":96084839,"html_url":"https://github.com/akka/akka/pull/21924","diff_url":"https://github.com/akka/akka/pull/21924.diff","patch_url":"https://github.com/akka/akka/pull/21924.patch","issue_url":"https://api.github.com/repos/akka/akka/issues/21924","number":21924,"state":"open","locked":false,"title":"Optimize Artery RemoteInstrument","user":{"login":"bantonsson","id":805830,"avatar_url":"https://avatars.githubusercontent.com/u/805830?v=3","gravatar_id":"","url":"https://api.github.com/users/bantonsson","html_url":"https://github.com/bantonsson","followers_url":"https://api.github.com/users/bantonsson/followers","following_url":"https://api.github.com/users/bantonsson/following{/other_user}","gists_url":"https://api.github.com/users/bantonsson/gists{/gist_id}","starred_url":"https://api.github.com/users/bantonsson/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bantonsson/subscriptions","organizations_url":"https://api.github.com/users/bantonsson/orgs","repos_url":"https://api.github.com/users/bantonsson/repos","events_url":"https://api.github.com/users/bantonsson/events{/privacy}","received_events_url":"https://api.github.com/users/bantonsson/received_events","type":"User","site_admin":false},"body":"This is a cleanup and optimization of the RemoteInstrument code in Artery.\r\n\r\n* Change RemoteInstrument API to work with ByteBuffers\r\n* Remove the MetadataMap since serialization is done in buffer\r\n\r\nHere are some unscientific benchmark numbers from my laptop.\r\n\r\n```\r\n----------------------------------------------------------------------------------------------------------------------\r\n| Benchmark                                         |     (configType) |  Mode | Cnt |    Score |     Error |  Units |\r\n|---------------------------------------------------|------------------|-------|-----|----------|-----------|--------|\r\n| Old                                               |                  |       |     |          |           |        |\r\n| CodecBenchmark.both                               |         Standard | thrpt |   5 |  750.592 | ± 103.790 | ops/ms |\r\n| CodecBenchmark.both:·gc.churn.PS_Eden_Space       |         Standard | thrpt |   5 |  475.762 | ± 608.715 | MB/sec |\r\n| CodecBenchmark.both:·gc.churn.PS_Eden_Space.norm  |         Standard | thrpt |   5 |  772.095 | ±  16.517 |   B/op |\r\n| CodecBenchmark.both                               | RemoteInstrument | thrpt |   5 |  573.369 | ± 190.089 | ops/ms |\r\n| CodecBenchmark.both:·gc.churn.PS_Eden_Space       | RemoteInstrument | thrpt |   5 |  662.329 | ± 714.277 | MB/sec |\r\n| CodecBenchmark.both:·gc.churn.PS_Eden_Space.norm  | RemoteInstrument | thrpt |   5 | 1420.378 | ±  12.754 |   B/op |\r\n| New                                               |                  |       |     |          |           |        |\r\n| CodecBenchmark.both                               |         Standard | thrpt |   5 |  854.291 | ± 166.186 | ops/ms |\r\n| CodecBenchmark.both:·gc.churn.PS_Eden_Space       |         Standard | thrpt |   5 |  533.664 | ± 691.398 | MB/sec |\r\n| CodecBenchmark.both:·gc.churn.PS_Eden_Space.norm  |         Standard | thrpt |   5 |  758.389 | ±  20.873 |   B/op |\r\n| CodecBenchmark.both                               | RemoteInstrument | thrpt |   5 |  791.343 | ± 170.640 | ops/ms |\r\n| CodecBenchmark.both:·gc.churn.PS_Eden_Space       | RemoteInstrument | thrpt |   5 |  516.096 | ± 652.720 | MB/sec |\r\n| CodecBenchmark.both:·gc.churn.PS_Eden_Space.norm  | RemoteInstrument | thrpt |   5 |  778.509 | ±   8.818 |   B/op |\r\n----------------------------------------------------------------------------------------------------------------------\r\n```\r\n\r\nThe new RemoteInstrument is only slightly slower and consumes roughly the same number of bytes per op as the standard Artery code.\r\n\r\nBTW, the materialization of the Streams still happen inside the measured part, even if I move everything else out. That happens several times during a single run. Would be nice to get that out of the way as well, but it's for another time.","created_at":"2016-12-01T13:04:05Z","updated_at":"2016-12-05T14:36:42Z","closed_at":null,"merged_at":null,"merge_commit_sha":"7ddf0493adea9312f93ecf81d43ac81d6288152d","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/akka/akka/pulls/21924/commits","review_comments_url":"https://api.github.com/repos/akka/akka/pulls/21924/comments","review_comment_url":"https://api.github.com/repos/akka/akka/pulls/comments{/number}","comments_url":"https://api.github.com/repos/akka/akka/issues/21924/comments","statuses_url":"https://api.github.com/repos/akka/akka/statuses/2b7c8203fb4ec61125b12d73ad33dc518737aedb","head":{"label":"bantonsson:optimize-artery-remote-instrument","ref":"optimize-artery-remote-instrument","sha":"2b7c8203fb4ec61125b12d73ad33dc518737aedb","user":{"login":"bantonsson","id":805830,"avatar_url":"https://avatars.githubusercontent.com/u/805830?v=3","gravatar_id":"","url":"https://api.github.com/users/bantonsson","html_url":"https://github.com/bantonsson","followers_url":"https://api.github.com/users/bantonsson/followers","following_url":"https://api.github.com/users/bantonsson/following{/other_user}","gists_url":"https://api.github.com/users/bantonsson/gists{/gist_id}","starred_url":"https://api.github.com/users/bantonsson/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bantonsson/subscriptions","organizations_url":"https://api.github.com/users/bantonsson/orgs","repos_url":"https://api.github.com/users/bantonsson/repos","events_url":"https://api.github.com/users/bantonsson/events{/privacy}","received_events_url":"https://api.github.com/users/bantonsson/received_events","type":"User","site_admin":false},"repo":{"id":31009670,"name":"akka","full_name":"bantonsson/akka","owner":{"login":"bantonsson","id":805830,"avatar_url":"https://avatars.githubusercontent.com/u/805830?v=3","gravatar_id":"","url":"https://api.github.com/users/bantonsson","html_url":"https://github.com/bantonsson","followers_url":"https://api.github.com/users/bantonsson/followers","following_url":"https://api.github.com/users/bantonsson/following{/other_user}","gists_url":"https://api.github.com/users/bantonsson/gists{/gist_id}","starred_url":"https://api.github.com/users/bantonsson/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bantonsson/subscriptions","organizations_url":"https://api.github.com/users/bantonsson/orgs","repos_url":"https://api.github.com/users/bantonsson/repos","events_url":"https://api.github.com/users/bantonsson/events{/privacy}","received_events_url":"https://api.github.com/users/bantonsson/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/bantonsson/akka","description":"Akka Project","fork":true,"url":"https://api.github.com/repos/bantonsson/akka","forks_url":"https://api.github.com/repos/bantonsson/akka/forks","keys_url":"https://api.github.com/repos/bantonsson/akka/keys{/key_id}","collaborators_url":"https://api.github.com/repos/bantonsson/akka/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/bantonsson/akka/teams","hooks_url":"https://api.github.com/repos/bantonsson/akka/hooks","issue_events_url":"https://api.github.com/repos/bantonsson/akka/issues/events{/number}","events_url":"https://api.github.com/repos/bantonsson/akka/events","assignees_url":"https://api.github.com/repos/bantonsson/akka/assignees{/user}","branches_url":"https://api.github.com/repos/bantonsson/akka/branches{/branch}","tags_url":"https://api.github.com/repos/bantonsson/akka/tags","blobs_url":"https://api.github.com/repos/bantonsson/akka/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/bantonsson/akka/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/bantonsson/akka/git/refs{/sha}","trees_url":"https://api.github.com/repos/bantonsson/akka/git/trees{/sha}","statuses_url":"https://api.github.com/repos/bantonsson/akka/statuses/{sha}","languages_url":"https://api.github.com/repos/bantonsson/akka/languages","stargazers_url":"https://api.github.com/repos/bantonsson/akka/stargazers","contributors_url":"https://api.github.com/repos/bantonsson/akka/contributors","subscribers_url":"https://api.github.com/repos/bantonsson/akka/subscribers","subscription_url":"https://api.github.com/repos/bantonsson/akka/subscription","commits_url":"https://api.github.com/repos/bantonsson/akka/commits{/sha}","git_commits_url":"https://api.github.com/repos/bantonsson/akka/git/commits{/sha}","comments_url":"https://api.github.com/repos/bantonsson/akka/comments{/number}","issue_comment_url":"https://api.github.com/repos/bantonsson/akka/issues/comments{/number}","contents_url":"https://api.github.com/repos/bantonsson/akka/contents/{+path}","compare_url":"https://api.github.com/repos/bantonsson/akka/compare/{base}...{head}","merges_url":"https://api.github.com/repos/bantonsson/akka/merges","archive_url":"https://api.github.com/repos/bantonsson/akka/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/bantonsson/akka/downloads","issues_url":"https://api.github.com/repos/bantonsson/akka/issues{/number}","pulls_url":"https://api.github.com/repos/bantonsson/akka/pulls{/number}","milestones_url":"https://api.github.com/repos/bantonsson/akka/milestones{/number}","notifications_url":"https://api.github.com/repos/bantonsson/akka/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/bantonsson/akka/labels{/name}","releases_url":"https://api.github.com/repos/bantonsson/akka/releases{/id}","deployments_url":"https://api.github.com/repos/bantonsson/akka/deployments","created_at":"2015-02-19T10:05:12Z","updated_at":"2016-04-29T11:34:23Z","pushed_at":"2016-12-01T12:38:22Z","git_url":"git://github.com/bantonsson/akka.git","ssh_url":"git@github.com:bantonsson/akka.git","clone_url":"https://github.com/bantonsson/akka.git","svn_url":"https://github.com/bantonsson/akka","homepage":"http://akka.io","size":191658,"stargazers_count":0,"watchers_count":0,"language":"Scala","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"akka:master","ref":"master","sha":"d1d4985950d96e0e914803dbd875087a36bed8cb","user":{"login":"akka","id":1496237,"avatar_url":"https://avatars.githubusercontent.com/u/1496237?v=3","gravatar_id":"","url":"https://api.github.com/users/akka","html_url":"https://github.com/akka","followers_url":"https://api.github.com/users/akka/followers","following_url":"https://api.github.com/users/akka/following{/other_user}","gists_url":"https://api.github.com/users/akka/gists{/gist_id}","starred_url":"https://api.github.com/users/akka/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/akka/subscriptions","organizations_url":"https://api.github.com/users/akka/orgs","repos_url":"https://api.github.com/users/akka/repos","events_url":"https://api.github.com/users/akka/events{/privacy}","received_events_url":"https://api.github.com/users/akka/received_events","type":"Organization","site_admin":false},"repo":{"id":130013,"name":"akka","full_name":"akka/akka","owner":{"login":"akka","id":1496237,"avatar_url":"https://avatars.githubusercontent.com/u/1496237?v=3","gravatar_id":"","url":"https://api.github.com/users/akka","html_url":"https://github.com/akka","followers_url":"https://api.github.com/users/akka/followers","following_url":"https://api.github.com/users/akka/following{/other_user}","gists_url":"https://api.github.com/users/akka/gists{/gist_id}","starred_url":"https://api.github.com/users/akka/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/akka/subscriptions","organizations_url":"https://api.github.com/users/akka/orgs","repos_url":"https://api.github.com/users/akka/repos","events_url":"https://api.github.com/users/akka/events{/privacy}","received_events_url":"https://api.github.com/users/akka/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/akka/akka","description":"Akka Project","fork":false,"url":"https://api.github.com/repos/akka/akka","forks_url":"https://api.github.com/repos/akka/akka/forks","keys_url":"https://api.github.com/repos/akka/akka/keys{/key_id}","collaborators_url":"https://api.github.com/repos/akka/akka/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/akka/akka/teams","hooks_url":"https://api.github.com/repos/akka/akka/hooks","issue_events_url":"https://api.github.com/repos/akka/akka/issues/events{/number}","events_url":"https://api.github.com/repos/akka/akka/events","assignees_url":"https://api.github.com/repos/akka/akka/assignees{/user}","branches_url":"https://api.github.com/repos/akka/akka/branches{/branch}","tags_url":"https://api.github.com/repos/akka/akka/tags","blobs_url":"https://api.github.com/repos/akka/akka/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/akka/akka/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/akka/akka/git/refs{/sha}","trees_url":"https://api.github.com/repos/akka/akka/git/trees{/sha}","statuses_url":"https://api.github.com/repos/akka/akka/statuses/{sha}","languages_url":"https://api.github.com/repos/akka/akka/languages","stargazers_url":"https://api.github.com/repos/akka/akka/stargazers","contributors_url":"https://api.github.com/repos/akka/akka/contributors","subscribers_url":"https://api.github.com/repos/akka/akka/subscribers","subscription_url":"https://api.github.com/repos/akka/akka/subscription","commits_url":"https://api.github.com/repos/akka/akka/commits{/sha}","git_commits_url":"https://api.github.com/repos/akka/akka/git/commits{/sha}","comments_url":"https://api.github.com/repos/akka/akka/comments{/number}","issue_comment_url":"https://api.github.com/repos/akka/akka/issues/comments{/number}","contents_url":"https://api.github.com/repos/akka/akka/contents/{+path}","compare_url":"https://api.github.com/repos/akka/akka/compare/{base}...{head}","merges_url":"https://api.github.com/repos/akka/akka/merges","archive_url":"https://api.github.com/repos/akka/akka/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/akka/akka/downloads","issues_url":"https://api.github.com/repos/akka/akka/issues{/number}","pulls_url":"https://api.github.com/repos/akka/akka/pulls{/number}","milestones_url":"https://api.github.com/repos/akka/akka/milestones{/number}","notifications_url":"https://api.github.com/repos/akka/akka/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/akka/akka/labels{/name}","releases_url":"https://api.github.com/repos/akka/akka/releases{/id}","deployments_url":"https://api.github.com/repos/akka/akka/deployments","created_at":"2009-02-16T12:51:54Z","updated_at":"2016-12-05T12:31:50Z","pushed_at":"2016-12-05T14:20:53Z","git_url":"git://github.com/akka/akka.git","ssh_url":"git@github.com:akka/akka.git","clone_url":"https://github.com/akka/akka.git","svn_url":"https://github.com/akka/akka","homepage":"http://akka.io","size":235704,"stargazers_count":6385,"watchers_count":6385,"language":"Scala","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":2040,"mirror_url":null,"open_issues_count":531,"forks":2040,"open_issues":531,"watchers":6385,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/akka/akka/pulls/21924"},"html":{"href":"https://github.com/akka/akka/pull/21924"},"issue":{"href":"https://api.github.com/repos/akka/akka/issues/21924"},"comments":{"href":"https://api.github.com/repos/akka/akka/issues/21924/comments"},"review_comments":{"href":"https://api.github.com/repos/akka/akka/pulls/21924/comments"},"review_comment":{"href":"https://api.github.com/repos/akka/akka/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/akka/akka/pulls/21924/commits"},"statuses":{"href":"https://api.github.com/repos/akka/akka/statuses/2b7c8203fb4ec61125b12d73ad33dc518737aedb"}}}}
{"actor":{"display_login":"patriknw"}}