{"id":39584748,"url":"https://github.com/twitter-archive/ostrich","last_synced_at":"2026-01-26T15:01:19.284Z","repository":{"id":46583902,"uuid":"818082","full_name":"twitter-archive/ostrich","owner":"twitter-archive","description":"A stats collector \u0026 reporter for Scala servers (deprecated)","archived":true,"fork":false,"pushed_at":"2019-06-06T01:48:53.000Z","size":3708,"stargazers_count":774,"open_issues_count":0,"forks_count":99,"subscribers_count":190,"default_branch":"develop","last_synced_at":"2024-06-21T18:50:26.917Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/twitter-archive.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2010-08-04T22:56:50.000Z","updated_at":"2024-06-05T07:48:26.000Z","dependencies_parsed_at":"2022-08-28T16:22:48.762Z","dependency_job_id":null,"html_url":"https://github.com/twitter-archive/ostrich","commit_stats":null,"previous_names":["twitter/ostrich"],"tags_count":63,"template":false,"template_full_name":null,"purl":"pkg:github/twitter-archive/ostrich","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twitter-archive%2Fostrich","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twitter-archive%2Fostrich/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twitter-archive%2Fostrich/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twitter-archive%2Fostrich/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/twitter-archive","download_url":"https://codeload.github.com/twitter-archive/ostrich/tar.gz/refs/heads/develop","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twitter-archive%2Fostrich/sbom","scorecard":{"id":904063,"data":{"date":"2025-08-18","repo":{"name":"github.com/twitter-archive/ostrich","commit":"5f0386d3dee06e346e08fe619190dfdf3432df70"},"scorecard":{"version":"v5.2.1-41-g40576783","commit":"40576783fda6698350fcbbeaea760ff827433034"},"score":3,"checks":[{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#token-permissions"}},{"name":"Maintained","score":0,"reason":"project is archived","details":["Warn: Repository is archived."],"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#dangerous-workflow"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#packaging"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#sast"}},{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#code-review"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#cii-best-practices"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#vulnerabilities"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: Apache License 2.0: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#license"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'develop'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#branch-protection"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/40576783fda6698350fcbbeaea760ff827433034/docs/checks.md#security-policy"}}]},"last_synced_at":"2025-08-24T16:42:27.855Z","repository_id":46583902,"created_at":"2025-08-24T16:42:27.855Z","updated_at":"2025-08-24T16:42:27.855Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28781308,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-26T13:55:28.044Z","status":"ssl_error","status_checked_at":"2026-01-26T13:55:26.068Z","response_time":59,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-01-18T07:35:27.100Z","updated_at":"2026-01-26T15:01:19.279Z","avatar_url":"https://github.com/twitter-archive.png","language":"Scala","readme":"# Ostrich\n\n[![Build status](https://travis-ci.org/twitter/ostrich.svg?branch=develop)](https://travis-ci.org/twitter/ostrich)\n[![Codecov branch](https://img.shields.io/codecov/c/github/twitter/ostrich/develop.svg)](http://codecov.io/github/twitter/ostrich?branch=develop)\n[![Project status](https://img.shields.io/badge/status-deprecated-orange.svg)](#status)\n\nOstrich is a library for scala servers that makes it easy to:\n\n- load \u0026 reload per-environment configuration\n- collect runtime statistics (counters, gauges, metrics, and labels)\n- report those statistics through a simple web interface (optionally with\n  graphs) or into log files\n- interact with the server over HTTP to check build versions or shut it down\n\nThe idea is that it should be simple and straightforward, allowing you to\nplug it in and get started quickly.\n\n## Status\n\nThis library is deprecated, and users should migrate to\n[Commons Metrics](https://github.com/twitter/commons/tree/master/src/java/com/twitter/common/metrics). Please see\n[TwitterServer's migration guide](https://twitter.github.io/twitter-server/Migration.html) for details.\n\n## Building\n\nUse sbt (simple-build-tool) to build:\n\n    $ sbt clean update package-dist\n\nThe finished jar will be in `dist/`.\n\n\n## Counters, Gauges, Metrics, and Labels\n\nThere are four kinds of statistics that ostrich captures:\n\n- counters\n\n  A counter is a value that never decreases. Examples might be\n  \"`widgets_sold`\" or \"`births`\". You just increment the counter each time a\n  countable event happens, and graphing utilities usually graph the deltas\n  over time. To increment a counter, use:\n\n        Stats.incr(\"births\")\n\n  or\n\n        Stats.incr(\"widgets_sold\", 5)\n\n- gauges\n\n  A gauge is a value that has a discrete value at any given moment, like\n  \"`heap_used`\" or \"`current_temperature`\". It's usually a measurement that\n  you only need to take when someone asks. To define a gauge, stick this code\n  somewhere in the server initialization:\n\n        Stats.addGauge(\"current_temperature\") { myThermometer.temperature }\n\n  A gauge method must always return a double.\n\n- metrics\n\n  A metric is tracked via distribution, and is usually used for timings, like\n  so:\n\n        Stats.time(\"translation\") {\n          document.translate(\"de\", \"en\")\n        }\n\n  But you can also add metrics directly:\n\n        Stats.addMetric(\"query_results\", results.size)\n\n  Metrics are collected by tracking the count, min, max, mean (average), and a\n  simple bucket-based histogram of the distribution. This distribution can be\n  used to determine median, 90th percentile, etc.\n\n- labels\n\n  A label is just a key/value pair of strings, usually used to report a\n  subsystem's state, like \"boiler=offline\". They're set with:\n\n        Stats.setLabel(\"boiler\", \"online\")\n\n  They have no real statistical value, but can be used to raise flags in\n  logging and monitoring.\n\n\n## RuntimeEnvironment\n\nIf you build with standard-project\n\u003chttps://github.com/twitter/standard-project\u003e, `RuntimeEnvironment` can pull\nbuild and environment info out of the `build.properties` file that's tucked\ninto your jar. Typical use is to pass your server object (or any object from\nyour jar) and any command-line arguments you haven't already parsed:\n\n    val runtime = RuntimeEnvironment(this, args)\n\nThe command-line argument parsing is optional, and supports only:\n\n- `--version` to print out the jar's build info (name, version, build)\n\n- `-f \u003cfilename\u003e` to specify a config file manually\n\n- `--validate` to validate that your config file can be compiled\n\nYour server object is used as the home jar of the `build.properties` file.\nThen the classpath is scanned to find that jar's home and the config files\nthat are located nearby.\n\n\n## Quick Start\n\nA good example server is created by the scala-bootstrapper project here:\n\u003chttps://github.com/twitter/scala-bootstrapper\u003e\n\nDefine a server config class:\n\n    class MyServerConfig extends ServerConfig[MyServer] {\n      var serverPort: Int = 9999\n\n      def apply(runtime: RuntimeEnvironment) = {\n        new MyServer(serverPort)\n      }\n    }\n\nA `ServerConfig` class contains things you want to configure on your server,\nas vars, and an `apply` method that turns a RuntimeEnvironment into your\nserver. `ServerConfig` is actually a helper for `Config` that adds logging\nconfiguration, sets up the optional admin HTTP server if it was configured,\nand registers your service with the `ServiceTracker` so that it will be\nshutdown when the admin port receives a shutdown command.\n\nNext, make a simple config file for development:\n\n    import com.twitter.conversions.time._\n    import com.twitter.logging.config._\n    import com.twitter.ostrich.admin.config._\n    import com.example.config._\n\n    new MyServerConfig {\n      serverPort = 9999\n      admin.httpPort = 9900\n\n      loggers = new LoggerConfig {\n        level = Level.INFO\n        handlers = new ConsoleHandlerConfig()\n      }\n    }\n\nThe config file will be evaluated at runtime by this code in your Main class:\n\n    object Main {\n      val log = Logger.get(getClass.getName)\n\n      def main(args: Array[String]) {\n        val runtime = RuntimeEnvironment(this, args)\n        val server = runtime.loadRuntimeConfig[MyServer]()\n        log.info(\"Starting my server!\")\n        try {\n          server.start()\n        } catch {\n          case e: Exception =\u003e\n            e.printStackTrace()\n            log.error(e, \"Unexpected exception: %s\", e.getMessage)\n            System.exit(0)\n        }\n      }\n    }\n\nYour `MyServer` class should implement the `Service` interface so it can be\nstarted and shutdown. The runtime environment will find your config file and\nevaluate it, returning the `MyServer` object to you so you can start it. And\nyou're set!\n\n\n## Stats API\n\nThe base trait of the stats API is `StatsProvider`, which defines methods for\nsetting and getting each type of collected stat. The concrete implementation\nis `StatsCollection`, which stores them all in java concurrent hash maps.\n\nTo log or report stats, attach a `StatsReporter` to a `StatsCollection`. A\n`StatsReporter` keeps its own state, and resets that state each time it\nreports. You can attach multiple `StatsReporter`s to track independent state\nwithout affecting the `StatsCollection`.\n\nThe simplest (and most common) pattern is to use the global singleton named\n`Stats`, like so:\n\n    import com.twitter.ostrich.stats.Stats\n\n    Stats.incr(\"cache_misses\")\n    Stats.time(\"memcache_timing\") {\n      memcache.set(key, value)\n    }\n\nStat names can be any string, though conventionally they contain only letters,\ndigits, underline (_), and dash (-), to make it easier for reporting.\n\nYou can immediately see any reported stats on the admin web server, if you've\nactivated it, through the \"stats\" command:\n\n    curl localhost:PPPP/stats.txt\n\n(where `PPPP` is your configured admin port)\n\n\n## ServiceTracker\n\nThe global \"shutdown\" and \"quiesce\" commands work by talking to a global\n`ServiceTracker` object. This is just a set of running `Service` objects.\n\nEach `Service` knows how to start and shutdown, so registering a service with\nthe global `ServiceTracker` will cause it to be shutdown when the server as a\nwhole is shutdown:\n\n    ServiceTracker.register(this)\n\nSome helper classes like `BackgroundProcess` and `PeriodicBackgroundProcess`\nimplement `Service`, so they can be used to build simple background tasks\nthat will be automatically shutdown when the server exits.\n\n\n## Admin web service\n\nThe easiest way to start the admin service is to construct an\n`AdminServiceConfig` with desired configuration, and call `apply` on it.\n\nTo reduce boilerplate in the common case of configuring a server with an\nadmin port and logging, a helper trait called `ServerConfig` is defined with\nboth:\n\n    var loggers: List[LoggerConfig] = Nil\n    var admin = new AdminServiceConfig()\n\nThe `apply` method on `ServerConfig` will create and start the admin service\nif a port is defined, and setup any configured logging.\n\nYou can also build an admin service directly from its config:\n\n    val adminConfig = new AdminServiceConfig {\n      httpPort = 8888\n      statsNodes = new StatsConfig {\n        reporters = new TimeSeriesCollectorConfig\n      }\n    }\n    val runtime = RuntimeEnvironment(this, Nil)\n    val admin = adminConfig()(runtime)\n\nIf `httpPort` isn't set, the admin service won't start, and `admin` will be\n`None`. Otherwise it will be an `Option[AdminHttpService]`.\n\n`statsNodes` can attach a list of reporters to named stats collections. In the\nabove example, a time-series collector is added to the global `Stats` object.\nThis is used to provide the web graphs described below under \"Web graphs\".\n\n\n## Web/socket commands\n\nCommands over the admin interface take the form of an HTTP \"get\" request:\n\n    GET /\u003ccommand\u003e[/\u003cparameters...\u003e][.\u003ctype\u003e]\n\nwhich can be performed using 'curl' or 'wget':\n\n    $ curl http://localhost:PPPP/shutdown\n\nThe result body may be json or plain-text, depending on \u003ctype\u003e. The default is\njson, but you can ask for text like so:\n\n    $ curl http://localhost:PPPP/stats.txt\n\nFor simple commands like `shutdown`, the response body may simply be the JSON\nencoding of the string \"ok\". For others like `stats`, it may be a nested\nstructure.\n\nThe commands are:\n\n- ping\n\n  Verify that the admin interface is working; server should say \"pong\" back.\n\n- reload\n\n  Reload the server config file for any services that support it (most do not).\n\n- shutdown\n\n  Immediately shutdown the server.\n\n- quiesce\n\n  Close any listening sockets, stop accepting new connections, and shutdown the server as soon as\n  the last client connection is done.\n\n- stats\n\n  Dump server statistics as 4 groups: `counters`, `gauges`, `metrics`, and `labels`.\n\n  - If the `period` query parameter is specified (e.g. `/stats.json?period=10`),\n    a StatsListener is acquired for that time period, and all requests with this\n    period value will receive the same stats values throughout that period.\n  - Otherwise, if the `namespace` argument is provided (e.g. `/stats.json?namespace=ganglia`),\n    a StatsListener is acquired for that namespace, and each request with this\n    namespace value will reset the stats listener, effectively returning the\n    delta since the prior request with that namespace.  (See\n    `src/scripts/json_stats_fetcher.rb` for an example.)\n  - If neither `period` nor `namespace` parameters are specified, the main stats\n    object will be fetched, returning non-differerential counters and metrics\n    over the life-time of the process.\n\n- server_info\n\n  Dump server info (server name, version, build, and git revision).\n\n- threads\n\n  Dump stack traces and stats about each currently running thread.\n\n- gc\n\n  Force a garbage collection cycle.\n\n\n## Web graphs\n\nIf `TimeSeriesCollector` is attached to a stats collection, the web interface\nwill include a small graph server that can be used to look at the last hour of\ndata on collected stats.\n\nThe url\n\n    http://localhost:PPPP/graph/\n\n(where PPPP is your admin `httpPort`) will give a list of currently-collected\nstats, and links to the current hourly graph for each stat. The graphs are\ngenerated in javascript using flot.\n\n\n## Profiling\n\nIf you're using [heapster](https://github.com/mariusae/heapster), you can generate a profile\nsuitable for reading with [google perftools](https://code.google.com/p/google-perftools/)\n\nExample use:\n\n    curl -s 'localhost:9990/pprof/heap?pause=10' \u003e| /tmp/prof\n\nThis will result in a file that you can be read with\n[pprof](http://goog-perftools.sourceforge.net/doc/cpu_profiler.html)\n\n\n## Credits\n\nThis started out as several smaller projects that began to overlap so much, we decided to merge\nthem. Major contributers include, in alphabetical order:\n\n- Alex Payne\n- John Corwin\n- John Kalucki\n- Marius Eriksen\n- Nick Kallen\n- Oliver Gould\n- Pankaj Gupta\n- Robey Pointer\n- Steve Jenson\n\nIf you make a significant change, please add your name to the list!\n\n## License\n\nThis library is released under the Apache Software License, version 2, which\nshould be included with the source in a file named `LICENSE`.\n","funding_links":[],"categories":["\u003ca name=\"Scala\"\u003e\u003c/a\u003eScala"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftwitter-archive%2Fostrich","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftwitter-archive%2Fostrich","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftwitter-archive%2Fostrich/lists"}