Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Memcached stats #50

Merged
merged 7 commits into from

3 participants

Blake Matheny Arya Asemanfar marius a. eriksen
Blake Matheny

This implements the zero-arg and n-arg versions of the memcache stats command. It also fixes a bug in the integration.ClientSpec test that must have broken with memcached 1.4.8 (comment inline).

There are at least two potential issues with the implementation. First, the response decoding wasn't suitable for decoding stat responses so a new state (along with decoding and response case classes) was introduced to manage this response type. Second, the client implemented stats command throws a UnsupportedOperationException for the PartitionedClient, since this operation doesn't make sense in that context.

Feedback welcome.

...main/scala/com/twitter/finagle/memcached/Client.scala
@@ -388,6 +397,23 @@ protected class ConnectedClient(service: Service[Command, Response]) extends Cli
}
}
+ def stats(args: Option[String]): Future[Seq[String]] = {
+ val statArgs: Seq[ChannelBuffer] = args match {
+ case None => Seq(ChannelBuffers.EMPTY_BUFFER)
+ case Some(args) => args.split(" ").toSeq
+ }
+ service(Stats(statArgs)) map {
+ case InfoLines(lines) => lines.map { line =>
+ val key = line.key
+ val values = line.values
+ key.toString(UTF_8) + " " + values.map { value => value.toString(UTF_8) }.mkString(" ")
+ }
+ case Error(e) => throw e
+ case Values(list) => Seq[String]()
Arya Asemanfar
arya added a note

why not Nil?

Blake Matheny
bmatheny added a note

Works for me. Updated and pushed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
.../scala/com/twitter/finagle/memcached/MockClient.scala
@@ -147,5 +147,7 @@ class MockClient(map: mutable.Map[String, ChannelBuffer]) extends Client {
def decr(key: String, delta: Long): Future[Option[Long]] =
incr(key, -delta)
+ def stats(args: Option[String]): Future[Seq[String]] = Future.value(Seq[String]())
Arya Asemanfar
arya added a note

why not Nil

Blake Matheny
bmatheny added a note

Updated and pushed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
...om/twitter/finagle/memcached/protocol/text/Show.scala
@@ -76,6 +85,10 @@ class CommandToEncoding extends OneToOneEncoder {
Tokens(Seq(DECR, key, amount.toString))
case Delete(key) =>
Tokens(Seq(DELETE, key))
+ case Stats(args) => args.isEmpty match {
Arya Asemanfar
arya added a note

why do you need the empty check?

Blake Matheny
bmatheny added a note

Also updated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Arya Asemanfar

looks good, thanks for the changees!

Arya Asemanfar arya merged commit 0af83bf into from
marius a. eriksen mariusae commented on the diff
...mcached/protocol/text/client/DecodingToResponse.scala
((6 lines not shown))
case _ => throw new IllegalArgumentException("Expecting a Decoding")
}
protected def parseResponse(tokens: Seq[ChannelBuffer]): R
protected def parseValues(valueLines: Seq[TokensWithData]): R
+ protected def parseStatLines(lines: Seq[Tokens]): R
marius a. eriksen Collaborator
mariusae added a note

whoops-- this broke the kestrel client.

Blake Matheny
bmatheny added a note

Oh crap, sorry about that. Let me fix it up quickly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
Showing with 211 additions and 14 deletions.
  1. +33 −3 finagle-memcached/src/main/scala/com/twitter/finagle/memcached/Client.scala
  2. +2 −0  finagle-memcached/src/main/scala/com/twitter/finagle/memcached/ClientAdaptor.scala
  3. +2 −0  finagle-memcached/src/main/scala/com/twitter/finagle/memcached/MockClient.scala
  4. +1 −0  finagle-memcached/src/main/scala/com/twitter/finagle/memcached/protocol/Command.scala
  5. +5 −2 finagle-memcached/src/main/scala/com/twitter/finagle/memcached/protocol/Response.scala
  6. +1 −1  finagle-memcached/src/main/scala/com/twitter/finagle/memcached/protocol/text/Decodings.scala
  7. +12 −0 finagle-memcached/src/main/scala/com/twitter/finagle/memcached/protocol/text/Encoder.scala
  8. +10 −0 finagle-memcached/src/main/scala/com/twitter/finagle/memcached/protocol/text/Show.scala
  9. +24 −0 finagle-memcached/src/main/scala/com/twitter/finagle/memcached/protocol/text/client/Decoder.scala
  10. +12 −1 finagle-memcached/src/main/scala/com/twitter/finagle/memcached/protocol/text/client/DecodingToResponse.scala
  11. +3 −0  finagle-memcached/src/main/scala/com/twitter/finagle/memcached/protocol/text/server/DecodingToCommand.scala
  12. +11 −2 finagle-memcached/src/test/scala/com/twitter/finagle/memcached/integration/ClientSpec.scala
  13. +35 −0 finagle-memcached/src/test/scala/com/twitter/finagle/memcached/integration/ProxySpec.scala
  14. +15 −1 finagle-memcached/src/test/scala/com/twitter/finagle/memcached/unit/protocol/text/client/DecoderSpec.scala
  15. +26 −2 ...mcached/src/test/scala/com/twitter/finagle/memcached/unit/protocol/text/client/DecodingToResponseSpec.scala
  16. +19 −2 ...emcached/src/test/scala/com/twitter/finagle/memcached/unit/protocol/text/server/DecodingToCommandSpec.scala
36 finagle-memcached/src/main/scala/com/twitter/finagle/memcached/Client.scala
View
@@ -15,7 +15,8 @@ import com.twitter.hashing._
import com.twitter.util.{Time, Future, Bijection}
import org.jboss.netty.buffer.ChannelBuffer
-import org.jboss.netty.util.CharsetUtil
+import org.jboss.netty.buffer.ChannelBuffers
+import org.jboss.netty.util.CharsetUtil.UTF_8
object Client {
/**
@@ -257,6 +258,14 @@ trait BaseClient[T] {
def quit(): Future[Unit] = Future(release())
/**
+ * Send a stats command with optional arguments to the server
+ * @return a sequence of strings, each of which is a line of output
+ */
+ def stats(args: Option[String]): Future[Seq[String]]
+ def stats(args: String): Future[Seq[String]] = stats(Some(args))
+ def stats(): Future[Seq[String]] = stats(None)
+
+ /**
* release the underlying service(s)
*/
def release(): Unit
@@ -292,12 +301,12 @@ trait Client extends BaseClient[ChannelBuffer] {
*/
protected class ConnectedClient(service: Service[Command, Response]) extends Client {
private[this] def rawGet(command: RetrievalCommand) = {
- val keys = immutable.Set(command.keys map { _.toString(CharsetUtil.UTF_8) }: _*)
+ val keys = immutable.Set(command.keys map { _.toString(UTF_8) }: _*)
service(command) map {
case Values(values) =>
val tuples = values.map {
- case value => (value.key.toString(CharsetUtil.UTF_8), value)
+ case value => (value.key.toString(UTF_8), value)
}
val hits = tuples.toMap
val misses = keys -- hits.keySet
@@ -388,6 +397,23 @@ protected class ConnectedClient(service: Service[Command, Response]) extends Cli
}
}
+ def stats(args: Option[String]): Future[Seq[String]] = {
+ val statArgs: Seq[ChannelBuffer] = args match {
+ case None => Seq(ChannelBuffers.EMPTY_BUFFER)
+ case Some(args) => args.split(" ").toSeq
+ }
+ service(Stats(statArgs)) map {
+ case InfoLines(lines) => lines.map { line =>
+ val key = line.key
+ val values = line.values
+ key.toString(UTF_8) + " " + values.map { value => value.toString(UTF_8) }.mkString(" ")
+ }
+ case Error(e) => throw e
+ case Values(list) => Nil
+ case _ => throw new IllegalStateException
+ }
+ }
+
def release() {
service.release()
}
@@ -445,6 +471,10 @@ trait PartitionedClient extends Client {
def delete(key: String) = clientOf(key).delete(key)
def incr(key: String, delta: Long) = clientOf(key).incr(key, delta)
def decr(key: String, delta: Long) = clientOf(key).decr(key, delta)
+
+ def stats(args: Option[String]): Future[Seq[String]] =
+ throw new UnsupportedOperationException("No logical way to perform stats without a key")
+
}
object PartitionedClient {
2  finagle-memcached/src/main/scala/com/twitter/finagle/memcached/ClientAdaptor.scala
View
@@ -29,5 +29,7 @@ class ClientAdaptor[T](
def incr(key: String, delta: Long): Future[Option[Long]] = self.incr(key, delta)
def decr(key: String, delta: Long): Future[Option[Long]] = self.decr(key, delta)
+ def stats(args: Option[String]): Future[Seq[String]] = self.stats(args)
+
def release(): Unit = self.release()
}
2  finagle-memcached/src/main/scala/com/twitter/finagle/memcached/MockClient.scala
View
@@ -147,5 +147,7 @@ class MockClient(map: mutable.Map[String, ChannelBuffer]) extends Client {
def decr(key: String, delta: Long): Future[Option[Long]] =
incr(key, -delta)
+ def stats(args: Option[String]): Future[Seq[String]] = Future.value(Nil)
+
def release() {}
}
1  finagle-memcached/src/main/scala/com/twitter/finagle/memcached/protocol/Command.scala
View
@@ -27,4 +27,5 @@ case class Delete(key: ChannelBuffer)
case class Incr(key: ChannelBuffer, value: Long) extends ArithmeticCommand(key, value)
case class Decr(key: ChannelBuffer, value: Long) extends ArithmeticCommand(key, -value)
+case class Stats(args: Seq[ChannelBuffer]) extends NonStorageCommand
case class Quit() extends Command
7 finagle-memcached/src/main/scala/com/twitter/finagle/memcached/protocol/Response.scala
View
@@ -11,8 +11,11 @@ case class Deleted() extends Response
case class Error(cause: Exception) extends Response
case class NoOp() extends Response
-case class Values(values: Seq[Value]) extends Response
-case class Number(value: Long) extends Response
+case class Info(key: ChannelBuffer, values: Seq[ChannelBuffer]) extends Response
+case class InfoLines(lines: Seq[Info]) extends Response
+
+case class Values(values: Seq[Value]) extends Response
+case class Number(value: Long) extends Response
case class Value(
key: ChannelBuffer,
2  finagle-memcached/src/main/scala/com/twitter/finagle/memcached/protocol/text/Decodings.scala
View
@@ -10,4 +10,4 @@ case class TokensWithData(
casUnique: Option[ChannelBuffer] = None)
extends Decoding
case class ValueLines(lines: Seq[TokensWithData]) extends Decoding
-
+case class StatLines(lines: Seq[Tokens]) extends Decoding
12 finagle-memcached/src/main/scala/com/twitter/finagle/memcached/protocol/text/Encoder.scala
View
@@ -65,6 +65,18 @@ class Encoder extends OneToOneEncoder {
buffer.writeBytes(END)
buffer.writeBytes(DELIMITER)
buffer
+ case StatLines(lines) =>
+ val buffer = ChannelBuffers.dynamicBuffer(100 * lines.size)
+ lines foreach { case Tokens(tokens) =>
+ tokens foreach { token =>
+ buffer.writeBytes(token)
+ buffer.writeBytes(SPACE)
+ }
+ buffer.writeBytes(DELIMITER)
+ }
+ buffer.writeBytes(END)
+ buffer.writeBytes(DELIMITER)
+ buffer
}
}
}
10 finagle-memcached/src/main/scala/com/twitter/finagle/memcached/protocol/text/Show.scala
View
@@ -25,6 +25,14 @@ class ResponseToEncoding extends OneToOneEncoder {
case NotFound() => Tokens(Seq(NOT_FOUND))
case NoOp() => Tokens(Seq())
case Number(value) => Tokens(Seq(value.toString))
+ case InfoLines(lines) =>
+ val buffer = ChannelBuffers.dynamicBuffer(100 * lines.size)
+ val statLines = lines map { line =>
+ val key = line.key
+ val values = line.values
+ Tokens(Seq(key) ++ values)
+ }
+ StatLines(statLines)
case Values(values) =>
val buffer = ChannelBuffers.dynamicBuffer(100 * values.size)
val tokensWithData = values map {
@@ -52,6 +60,7 @@ class CommandToEncoding extends OneToOneEncoder {
private[this] val CAS = "cas"
private[this] val QUIT = "quit"
+ private[this] val STATS = "stats"
def encode(ctx: ChannelHandlerContext, ch: Channel, message: AnyRef): Decoding = message match {
case Add(key, flags, expiry, value) =>
@@ -76,6 +85,7 @@ class CommandToEncoding extends OneToOneEncoder {
Tokens(Seq(DECR, key, amount.toString))
case Delete(key) =>
Tokens(Seq(DELETE, key))
+ case Stats(args) => Tokens(Seq[ChannelBuffer](STATS) ++ args)
case Quit() =>
Tokens(Seq(QUIT))
}
24 finagle-memcached/src/main/scala/com/twitter/finagle/memcached/protocol/text/client/Decoder.scala
View
@@ -10,6 +10,8 @@ import com.twitter.finagle.memcached.protocol.text._
object Decoder {
private val END = "END": ChannelBuffer
+ private val ITEM = "ITEM": ChannelBuffer
+ private val STAT = "STAT": ChannelBuffer
private val VALUE = "VALUE": ChannelBuffer
}
@@ -19,6 +21,7 @@ class Decoder extends AbstractDecoder with StateMachine {
case class AwaitingResponse() extends State
case class AwaitingResponseOrEnd(valuesSoFar: Seq[TokensWithData]) extends State
+ case class AwaitingStatsOrEnd(valuesSoFar: Seq[Tokens]) extends State
case class AwaitingData(valuesSoFar: Seq[TokensWithData], tokens: Seq[ChannelBuffer], bytesNeeded: Int) extends State
final protected[memcached] def start() {
@@ -31,10 +34,24 @@ class Decoder extends AbstractDecoder with StateMachine {
decodeLine(buffer, needsData(_)) { tokens =>
if (isEnd(tokens)) {
ValueLines(Seq[TokensWithData]())
+ } else if (isStats(tokens)) {
+ awaitStatsOrEnd(Seq(Tokens(tokens)))
+ needMoreData
} else {
Tokens(tokens)
}
}
+ case AwaitingStatsOrEnd(linesSoFar) =>
+ decodeLine(buffer, needsData(_)) { tokens =>
+ if (isEnd(tokens)) {
+ StatLines(linesSoFar)
+ } else if (isStats(tokens)) {
+ awaitStatsOrEnd(linesSoFar ++ Seq(Tokens(tokens)))
+ needMoreData
+ } else {
+ throw new ServerError("Invalid reply from STATS command")
+ }
+ }
case AwaitingData(valuesSoFar, tokens, bytesNeeded) =>
decodeData(bytesNeeded, buffer) { data =>
awaitResponseOrEnd(
@@ -68,11 +85,18 @@ class Decoder extends AbstractDecoder with StateMachine {
state = AwaitingResponseOrEnd(valuesSoFar)
}
+ private[this] def awaitStatsOrEnd(valuesSoFar: Seq[Tokens]) {
+ state = AwaitingStatsOrEnd(valuesSoFar)
+ }
+
private[this] val needMoreData = null: Decoding
private[this] def isEnd(tokens: Seq[ChannelBuffer]) =
(tokens.length == 1 && tokens.head == END)
+ private[this] def isStats(tokens: Seq[ChannelBuffer]) =
+ (tokens.length > 0 && (tokens.head == STAT || tokens.head == ITEM))
+
private[this] def needsData(tokens: Seq[ChannelBuffer]) = {
val responseName = tokens.head
val args = tokens.tail
13 ...gle-memcached/src/main/scala/com/twitter/finagle/memcached/protocol/text/client/DecodingToResponse.scala
View
@@ -5,7 +5,7 @@ import org.jboss.netty.buffer.ChannelBuffer
import com.twitter.finagle.memcached.util.ChannelBufferUtils._
import org.jboss.netty.handler.codec.oneone.OneToOneDecoder
import org.jboss.netty.channel.{Channel, ChannelHandlerContext}
-import text.{TokensWithData, ValueLines, Tokens}
+import text.{StatLines, TokensWithData, ValueLines, Tokens}
object AbstractDecodingToResponse {
private[finagle] val STORED = "STORED": ChannelBuffer
@@ -24,11 +24,14 @@ abstract class AbstractDecodingToResponse[R <: AnyRef] extends OneToOneDecoder {
parseResponse(tokens)
case ValueLines(lines) =>
parseValues(lines)
+ case StatLines(lines) =>
+ parseStatLines(lines)
case _ => throw new IllegalArgumentException("Expecting a Decoding")
}
protected def parseResponse(tokens: Seq[ChannelBuffer]): R
protected def parseValues(valueLines: Seq[TokensWithData]): R
+ protected def parseStatLines(lines: Seq[Tokens]): R
marius a. eriksen Collaborator
mariusae added a note

whoops-- this broke the kestrel client.

Blake Matheny
bmatheny added a note

Oh crap, sorry about that. Let me fix it up quickly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
}
class DecodingToResponse extends AbstractDecodingToResponse[Response] {
@@ -49,6 +52,14 @@ class DecodingToResponse extends AbstractDecodingToResponse[Response] {
}
}
+ protected def parseStatLines(lines: Seq[Tokens]) = {
+ val l = lines.map { line =>
+ val tokens = line.tokens
+ Info(tokens(0), tokens.drop(1))
+ }
+ InfoLines(l)
+ }
+
protected def parseValues(valueLines: Seq[TokensWithData]) = {
val values = valueLines.map { valueLine =>
val tokens = valueLine.tokens
3  finagle-memcached/src/main/scala/com/twitter/finagle/memcached/protocol/text/server/DecodingToCommand.scala
View
@@ -25,6 +25,7 @@ object DecodingToCommand {
private val INCR = copiedBuffer("incr" .getBytes)
private val DECR = copiedBuffer("decr" .getBytes)
private val QUIT = copiedBuffer("quit" .getBytes)
+ private val STATS = copiedBuffer("stats" .getBytes)
}
abstract class AbstractDecodingToCommand[C <: AnyRef] extends OneToOneDecoder {
@@ -100,7 +101,9 @@ class DecodingToCommand extends AbstractDecodingToCommand[Command] {
case INCR => tupled(Incr)(validateArithmeticCommand(args))
case DECR => tupled(Decr)(validateArithmeticCommand(args))
case QUIT => Quit()
+ case STATS => Stats(args)
case _ => throw new NonexistentCommand(hexDump(commandName))
}
}
+
}
13 finagle-memcached/src/test/scala/com/twitter/finagle/memcached/integration/ClientSpec.scala
View
@@ -88,18 +88,27 @@ object ClientSpec extends Specification {
}
"incr & decr" in {
- client.set("foo", "")()
+ // As of memcached 1.4.8 (issue 221), empty values are no longer treated as integers
+ client.set("foo", "0")()
client.incr("foo")() mustEqual Some(1L)
client.incr("foo", 2)() mustEqual Some(3L)
client.decr("foo")() mustEqual Some(2L)
- client.set("foo", "")()
+ client.set("foo", "0")()
client.incr("foo")() mustEqual Some(1L)
val l = 1L << 50
client.incr("foo", l)() mustEqual Some(l + 1L)
client.decr("foo")() mustEqual Some(l)
client.decr("foo", l)() mustEqual Some(0L)
}
+
+ "stats" in {
+ val stats = client.stats()()
+ stats must notBeEmpty
+ stats.foreach { stat =>
+ stat must startWith("STAT")
+ }
+ }
}
"ketama client" in {
35 finagle-memcached/src/test/scala/com/twitter/finagle/memcached/integration/ProxySpec.scala
View
@@ -67,6 +67,41 @@ object ProxySpec extends Specification {
foo.get.toString(CharsetUtil.UTF_8) mustEqual "bar"
}
+ "stats is supported" in {
+ externalClient.delete("foo")()
+ externalClient.get("foo")() must beNone
+ externalClient.set("foo", ChannelBuffers.wrappedBuffer("bar".getBytes(CharsetUtil.UTF_8)))()
+ Seq(None, Some("items"), Some("slabs")).foreach { arg =>
+ val stats = externalClient.stats(arg)()
+ stats must notBeEmpty
+ stats.foreach { line =>
+ line must startWith("STAT")
+ }
+ }
+ }
+
+ "stats is supported (no value)" in {
+ val stats = externalClient.stats("items")()
+ stats must beEmpty
+ }
+
+ "stats (cachedump) is supported" in {
+ externalClient.delete("foo")()
+ externalClient.get("foo")() must beNone
+ externalClient.set("foo", ChannelBuffers.wrappedBuffer("bar".getBytes(CharsetUtil.UTF_8)))()
+ val slabs = externalClient.stats(Some("slabs"))()
+ slabs must notBeEmpty
+ val n = slabs.head.split(" ")(1).split(":")(0).toInt
+ val stats = externalClient.stats(Some("cachedump " + n + " 100"))()
+ stats must notBeEmpty
+ stats.foreach { stat =>
+ stat must startWith("ITEM")
+ }
+ stats.find { stat =>
+ stat.contains("foo")
+ } must beSome
+ }
+
"quit is supported" in {
externalClient.get("foo")() // do nothing
externalClient.quit()()
16 finagle-memcached/src/test/scala/com/twitter/finagle/memcached/unit/protocol/text/client/DecoderSpec.scala
View
@@ -3,7 +3,7 @@ package com.twitter.finagle.memcached.unit.protocol.text.client
import org.specs.Specification
import com.twitter.finagle.memcached.protocol.text.client.Decoder
import com.twitter.finagle.memcached.util.ChannelBufferUtils._
-import com.twitter.finagle.memcached.protocol.text.{TokensWithData, ValueLines, Tokens}
+import com.twitter.finagle.memcached.protocol.text.{TokensWithData, ValueLines, Tokens, StatLines}
import org.specs.mock.Mockito
object DecoderSpec extends Specification with Mockito {
@@ -59,6 +59,20 @@ object DecoderSpec extends Specification with Mockito {
val buffer = "END\r\n"
decoder.decode(null, null, buffer) mustEqual ValueLines(Seq[TokensWithData]())
}
+
+ "stats" in {
+ val buffer = stringToChannelBuffer("STAT items:1:number 1\r\nSTAT items:1:age 1468\r\nITEM foo [5 b; 1322514067 s]\r\nEND\r\n")
+ decoder.decode(null, null, buffer)
+ decoder.decode(null, null, buffer)
+ decoder.decode(null, null, buffer)
+ val lines = decoder.decode(null, null, buffer)
+ lines mustEqual StatLines(Seq(
+ Tokens(Seq("STAT", "items:1:number", "1")),
+ Tokens(Seq("STAT", "items:1:age", "1468")),
+ Tokens(Seq("ITEM", "foo", "[5", "b;", "1322514067", "s]"))
+ ))
+ }
+
}
}
}
28 ...ched/src/test/scala/com/twitter/finagle/memcached/unit/protocol/text/client/DecodingToResponseSpec.scala
View
@@ -4,9 +4,10 @@ import org.specs.Specification
import com.twitter.finagle.memcached.protocol.text.client.DecodingToResponse
import com.twitter.finagle.memcached.util.ChannelBufferUtils._
import org.jboss.netty.buffer.ChannelBuffer
-import com.twitter.finagle.memcached.protocol.text.Tokens
+import org.jboss.netty.util.CharsetUtil.UTF_8
+import com.twitter.finagle.memcached.protocol.text.{Tokens, StatLines}
import com.twitter.finagle.memcached.protocol
-import com.twitter.finagle.memcached.protocol.{ClientError, Stored, NonexistentCommand, NotFound, Exists}
+import com.twitter.finagle.memcached.protocol.{ClientError, Info => MCInfo, InfoLines, Stored, NonexistentCommand, NotFound, Exists, Value}
object DecodingToResponseSpec extends Specification {
"DecodingToResponse" should {
@@ -34,6 +35,29 @@ object DecodingToResponseSpec extends Specification {
haveClass[NonexistentCommand]
}
+ "STATS" in {
+ val lines = Seq(
+ Seq("STAT", "items:1:number", "1"),
+ Seq("STAT", "items:1:age", "1468"),
+ Seq("ITEM", "foo", "[5 b;", "1322514067", "s]"))
+ val plines = lines.map { line =>
+ Tokens(line)
+ }
+ val info = decodingToResponse.decode(null, null, StatLines(plines))
+ info must haveClass[InfoLines]
+ val ilines = info.asInstanceOf[InfoLines].lines
+ ilines must haveSize(lines.size)
+ ilines.zipWithIndex.foreach { case(line, idx) =>
+ val key = lines(idx)(0)
+ val values = lines(idx).drop(1)
+ line.key.toString(UTF_8) mustEqual key
+ line.values.size mustEqual values.size
+ line.values.zipWithIndex.foreach { case(token, tokIdx) =>
+ token.toString(UTF_8) mustEqual values(tokIdx)
+ }
+ }
+ }
+
"CLIENT_ERROR" in {
val buffer = Tokens(Seq[ChannelBuffer]("CLIENT_ERROR"))
decodingToResponse.decode(null, null, buffer).asInstanceOf[protocol.Error].cause must
21 ...ached/src/test/scala/com/twitter/finagle/memcached/unit/protocol/text/server/DecodingToCommandSpec.scala
View
@@ -7,9 +7,9 @@ import org.jboss.netty.buffer.ChannelBuffers.copiedBuffer
import org.jboss.netty.util.CharsetUtil
import com.twitter.conversions.time._
import com.twitter.finagle.memcached.util.ChannelBufferUtils._
-import com.twitter.finagle.memcached.protocol.text.TokensWithData
+import com.twitter.finagle.memcached.protocol.text.{Tokens, TokensWithData}
import com.twitter.finagle.memcached.protocol.text.server.DecodingToCommand
-import com.twitter.finagle.memcached.protocol.Set
+import com.twitter.finagle.memcached.protocol.{Set, Stats}
import com.twitter.util.{Duration, Time}
object DecodingToCommandSpec extends Specification with DataTables {
@@ -40,6 +40,23 @@ object DecodingToCommandSpec extends Specification with DataTables {
}
}
+ "STAT" in {
+ Seq(None, Some("slabs"), Some("items")).foreach { arg =>
+ val cmd: Seq[ChannelBuffer] = arg match {
+ case None => Seq("stats")
+ case Some(s) => Seq("stats", s)
+ }
+ val buffer = Tokens(cmd)
+ val command = decodingToCommand.decode(null, null, buffer)
+ command must haveClass[Stats]
+ val stats = command.asInstanceOf[Stats]
+ stats.args.headOption match {
+ case None => arg must beNone
+ case Some(cb) => cb.toString(CharsetUtil.UTF_8) mustEqual arg.get
+ }
+ }
+ }
+
}
}
Something went wrong with that request. Please try again.