├── .gitignore ├── LICENSE ├── OSSMETADATA ├── README.md ├── build.gradle ├── codequality ├── HEADER └── checkstyle.xml ├── gradle.properties ├── gradle ├── buildscript.gradle ├── check.gradle ├── convention.gradle ├── license.gradle ├── maven.gradle ├── netflix-oss.gradle ├── release.gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── reactive-lab-dashboard ├── README.md ├── build.gradle └── src │ └── main │ ├── java │ └── com │ │ └── netflix │ │ └── hystrix │ │ └── dashboard │ │ └── stream │ │ ├── MockStreamServlet.java │ │ └── ProxyStreamServlet.java │ ├── resources │ └── com │ │ └── netflix │ │ └── hystrix │ │ └── dashboard │ │ └── stream │ │ └── hystrix.stream │ └── webapp │ ├── WEB-INF │ ├── classes │ │ └── log4j.properties │ └── web.xml │ ├── components │ ├── hystrixCommand │ │ ├── hystrixCommand.css │ │ ├── hystrixCommand.js │ │ ├── magnifying-glass-icon-20.png │ │ ├── magnifying-glass-icon.png │ │ └── templates │ │ │ ├── hystrixCircuit.html │ │ │ ├── hystrixCircuitContainer.html │ │ │ └── hystrixCircuitProperties.html │ └── hystrixThreadPool │ │ ├── hystrixThreadPool.css │ │ ├── hystrixThreadPool.js │ │ └── templates │ │ ├── hystrixThreadPool.html │ │ └── hystrixThreadPoolContainer.html │ ├── css │ ├── global.css │ ├── resets.css │ └── simplegrid │ │ ├── 1236_grid.css │ │ ├── 720_grid.css │ │ ├── 986_grid.css │ │ ├── LICENSE.txt │ │ ├── README.txt │ │ └── percentage_grid.css │ ├── index.html │ ├── js │ ├── LICENSE │ ├── d3.v2.min.js │ ├── jquery.min.js │ ├── jquery.tinysort.min.js │ └── tmpl.js │ └── monitor │ ├── monitor.css │ └── monitor.html ├── reactive-lab-gateway ├── build.gradle ├── src │ ├── main │ │ └── java │ │ │ └── io │ │ │ └── reactivex │ │ │ └── lab │ │ │ └── gateway │ │ │ ├── StartGatewayServer.java │ │ │ ├── clients │ │ │ ├── BookmarkCommand.java │ │ │ ├── BookmarksCommand.java │ │ │ ├── GeoCommand.java │ │ │ ├── ID.java │ │ │ ├── MockServiceCommand.java │ │ │ ├── PersonalizedCatalogCommand.java │ │ │ ├── RatingsCommand.java │ │ │ ├── SocialCommand.java │ │ │ ├── UrlGenerator.java │ │ │ ├── UserCommand.java │ │ │ └── VideoMetadataCommand.java │ │ │ ├── common │ │ │ ├── RxNettyResponseWriter.java │ │ │ └── SimpleJson.java │ │ │ ├── hystrix │ │ │ ├── HystrixMetricsStreamHandler.java │ │ │ └── JsonMapper.java │ │ │ ├── loadbalancer │ │ │ ├── DiscoveryAndLoadBalancer.java │ │ │ └── LoadBalancerFactory.java │ │ │ └── routes │ │ │ ├── RouteForDeviceHome.java │ │ │ └── mock │ │ │ ├── BackendResponse.java │ │ │ ├── ResponseBuilder.java │ │ │ ├── TestRouteBasic.java │ │ │ ├── TestRouteWithHystrix.java │ │ │ └── TestRouteWithSimpleFaultTolerance.java │ └── test │ │ └── java │ │ └── io │ │ └── reactivex │ │ └── lab │ │ └── gateway │ │ └── mock │ │ └── BackendResponseTest.java └── validate.py ├── reactive-lab-services ├── build.gradle └── src │ ├── main │ ├── java │ │ └── io │ │ │ └── reactivex │ │ │ └── lab │ │ │ └── services │ │ │ ├── StartEurekaServer.java │ │ │ ├── StartMiddleTierServices.java │ │ │ ├── StartTurbineServer.java │ │ │ ├── common │ │ │ ├── Random.java │ │ │ └── SimpleJson.java │ │ │ ├── impls │ │ │ ├── AbstractMiddleTierService.java │ │ │ ├── BookmarksService.java │ │ │ ├── GeoService.java │ │ │ ├── MockResponse.java │ │ │ ├── MockService.java │ │ │ ├── PersonalizedCatalogService.java │ │ │ ├── RatingsService.java │ │ │ ├── SocialService.java │ │ │ ├── UserService.java │ │ │ └── VideoMetadataService.java │ │ │ └── metrics │ │ │ ├── HystrixMetricsStreamHandler.java │ │ │ ├── JsonMapper.java │ │ │ ├── Metrics.java │ │ │ └── README.txt │ └── resources │ │ └── dashboard │ │ ├── d3.v3.js │ │ └── main.html │ └── test │ └── java │ └── io │ └── reactivex │ └── lab │ └── services │ └── impls │ └── MockResponseTest.java ├── reactive-lab-tutorial ├── README.md ├── build.gradle └── src │ └── main │ └── java │ └── io │ └── reactivex │ └── lab │ └── tutorial │ ├── ClientServer.java │ ├── ClientServerWithDiscovery.java │ ├── ClientServerWithLoadBalancer.java │ └── ClientServerWithResiliencePatterns.java └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled source # 2 | ################### 3 | *.com 4 | *.class 5 | *.dll 6 | *.exe 7 | *.o 8 | *.so 9 | 10 | # Packages # 11 | ############ 12 | # it's better to unpack these files and commit the raw source 13 | # git has its own built in compression methods 14 | *.7z 15 | *.dmg 16 | *.gz 17 | *.iso 18 | *.jar 19 | *.rar 20 | *.tar 21 | *.zip 22 | 23 | # Logs and databases # 24 | ###################### 25 | *.log 26 | 27 | # OS generated files # 28 | ###################### 29 | .DS_Store* 30 | ehthumbs.db 31 | Icon? 32 | Thumbs.db 33 | 34 | # Editor Files # 35 | ################ 36 | *~ 37 | *.swp 38 | 39 | # Gradle Files # 40 | ################ 41 | .gradle 42 | 43 | # Build output directies 44 | /target 45 | */target 46 | /build 47 | */build 48 | */bin 49 | 50 | # IntelliJ specific files/directories 51 | out 52 | .idea 53 | *.ipr 54 | *.iws 55 | *.iml 56 | atlassian-ide-plugin.xml 57 | 58 | # Eclipse specific files/directories 59 | .classpath 60 | .project 61 | .settings 62 | .metadata 63 | 64 | # NetBeans specific files/directories 65 | .nbattrs 66 | -------------------------------------------------------------------------------- /OSSMETADATA: -------------------------------------------------------------------------------- 1 | osslifecycle=active 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## ReactiveLab 2 | 3 | Experiments and prototypes with reactive application design using service-oriented architecture concepts. 4 | 5 | ### [Discovery](https://github.com/benjchristensen/ReactiveLab/tree/master/reactive-lab-services) 6 | 7 | Start discovery server using [StartEurekaServer.java](https://github.com/benjchristensen/ReactiveLab/blob/master/reactive-lab-services/src/main/java/io/reactivex/lab/services/StartEurekaServer.java) 8 | 9 | ``` 10 | ./gradlew startDiscovery 11 | ``` 12 | 13 | ### [Gateway](https://github.com/benjchristensen/ReactiveLab/tree/master/reactive-lab-gateway) 14 | 15 | Start gateway server using [StartGatewayServer.java](https://github.com/benjchristensen/ReactiveLab/blob/master/reactive-lab-gateway/src/main/java/io/reactivex/lab/gateway/StartGatewayServer.java) or Gradle: 16 | 17 | ``` 18 | ./gradlew startGateway 19 | ``` 20 | 21 | ### [Services](https://github.com/benjchristensen/ReactiveLab/tree/master/reactive-lab-services) 22 | 23 | Simulation of middle-tier RPC/RESTful services exposing endpoints over HTTP. 24 | 25 | Start several services on different ports using [StartMiddleTierServices.java](https://github.com/benjchristensen/ReactiveLab/blob/master/reactive-lab-services/src/main/java/io/reactivex/lab/services/StartMiddleTierServices.java) or Gradle: 26 | 27 | ``` 28 | ./gradlew startServices 29 | ``` 30 | 31 | --------- 32 | 33 | ### Server 34 | 35 | See how Netty and RxJava are used as an HTTP server in [GatewayServer](https://github.com/benjchristensen/ReactiveLab/blob/master/reactive-lab-gateway/src/main/java/io/reactivex/lab/gateway/StartGatewayServer.java#L40) 36 | 37 | Basics are: 38 | 39 | ```java 40 | RxNetty.createHttpServer(8080, (request, response) -> { 41 | System.out.println("Server => Request: " + request.getPath()); 42 | ... handle requests here ... 43 | }).startAndWait(); 44 | ``` 45 | 46 | ### Client 47 | 48 | Clients using Netty and RxJava can be seen in the [clients](https://github.com/benjchristensen/ReactiveLab/tree/master/reactive-lab-gateway/src/main/java/io/reactivex/lab/gateway/clients) package. 49 | 50 | Basic example: 51 | 52 | ```java 53 | return RxNetty.createHttpClient("localhost", 9100) 54 | .submit(HttpClientRequest.createGet("/mock.json?id=" + id)); 55 | ``` 56 | 57 | ### Hystrix 58 | 59 | Here is a batch request using SSE inside a `HystrixObservableCommand` for fault-tolerance: [BookmarksCommand](https://github.com/benjchristensen/ReactiveLab/blob/master/reactive-lab-gateway/src/main/java/io/reactivex/lab/gateway/clients/BookmarksCommand.java). 60 | 61 | A `HystrixObservableCollapser` can be put in front of that command to allow automated batching: [BookmarkCommand](https://github.com/benjchristensen/ReactiveLab/blob/master/reactive-lab-gateway/src/main/java/io/reactivex/lab/gateway/clients/BookmarkCommand.java). 62 | 63 | 64 | ### Composition 65 | 66 | Nested, parallel execution of network requests can be composed using RxJava and Hystrix as demonstrated in [RouteForDeviceHome](https://github.com/benjchristensen/ReactiveLab/blob/master/reactive-lab-gateway/src/main/java/io/reactivex/lab/gateway/routes/RouteForDeviceHome.java) which is the example running at the `/device/home` endpoint. 67 | 68 | Here is a portion of the code to show the composition: 69 | 70 | ```java 71 | return new UserCommand(userId).observe().flatMap(user -> { 72 | Observable> catalog = new PersonalizedCatalogCommand(user).observe() 73 | .flatMap(catalogList -> { 74 | return catalogList.videos().> flatMap(video -> { 75 | Observable bookmark = new BookmarkCommand(video).observe(); 76 | Observable rating = new RatingsCommand(video).observe(); 77 | Observable metadata = new VideoMetadataCommand(video).observe(); 78 | return Observable.zip(bookmark, rating, metadata, (b, r, m) -> { 79 | return combineVideoData(video, b, r, m); 80 | }); 81 | }); 82 | }); 83 | 84 | Observable> social = new SocialCommand(user).observe().map(s -> { 85 | return s.getDataAsMap(); 86 | }); 87 | 88 | return Observable.merge(catalog, social); 89 | }).flatMap(data -> { 90 | return response.writeAndFlush(new ServerSentEvent("", "data", SimpleJson.mapToJson(data)), EdgeServer.SSE_TRANSFORMER); 91 | }); 92 | ``` 93 | 94 | This results in 7 network calls being made, and multiple bookmark requests are automatically collapsed into 1 network call. Here is the `HystrixRequestLog` that results from the code above being executed: 95 | 96 | 97 | ``` 98 | Server => Hystrix Log [/device/home] => UserCommand[SUCCESS][191ms], PersonalizedCatalogCommand[SUCCESS][50ms], SocialCommand[SUCCESS][53ms], RatingsCommand[SUCCESS][65ms]x6, VideoMetadataCommand[SUCCESS][73ms]x6, BookmarksCommand[SUCCESS, COLLAPSED][25ms], BookmarksCommand[SUCCESS, COLLAPSED][24ms] 99 | ``` 100 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | ext.githubProjectName = 'ReactiveLab' 2 | 3 | //apply from: file('gradle/convention.gradle') 4 | apply from: file('gradle/maven.gradle') 5 | 6 | buildscript { 7 | repositories { 8 | mavenLocal() 9 | mavenCentral() 10 | jcenter() 11 | } 12 | 13 | apply from: file('gradle/buildscript.gradle'), to: buildscript 14 | } 15 | 16 | allprojects { 17 | apply plugin: 'eclipse' 18 | apply plugin: 'idea' 19 | repositories { 20 | mavenLocal() 21 | mavenCentral() 22 | jcenter() 23 | } 24 | } 25 | 26 | subprojects { 27 | apply plugin: 'java' 28 | group = "io.reactivex.lab" 29 | } 30 | 31 | task startDiscovery() {} 32 | startDiscovery.dependsOn(":reactive-lab-services:startDiscovery") 33 | 34 | task startServices() {} 35 | startServices.dependsOn(":reactive-lab-services:startServices") 36 | 37 | task startGateway() {} 38 | startGateway.dependsOn(":reactive-lab-gateway:startGateway") 39 | -------------------------------------------------------------------------------- /codequality/HEADER: -------------------------------------------------------------------------------- 1 | Copyright ${year} Netflix, Inc. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | version=1.0-SNAPSHOT 2 | -------------------------------------------------------------------------------- /gradle/buildscript.gradle: -------------------------------------------------------------------------------- 1 | // Executed in context of buildscript 2 | repositories { 3 | // Repo in addition to maven central 4 | repositories { maven { url 'http://dl.bintray.com/content/netflixoss/external-gradle-plugins/' } } // For gradle-release 5 | maven { 6 | //FIXME: waiting for https://github.com/johnrengelman/shadow/pull/38 to merge 7 | name 'Shadow' 8 | url 'http://dl.bintray.com/content/gvsmirnov/gradle-plugins' 9 | } 10 | } 11 | dependencies { 12 | classpath 'nl.javadude.gradle.plugins:license-gradle-plugin:0.6.1' 13 | classpath 'com.mapvine:gradle-cobertura-plugin:0.1' 14 | classpath 'gradle-release:gradle-release:1.1.5' 15 | classpath 'org.ajoberstar:gradle-git:0.5.0' 16 | classpath 'com.github.jengelman.gradle.plugins:shadow:0.8.1' 17 | } 18 | -------------------------------------------------------------------------------- /gradle/check.gradle: -------------------------------------------------------------------------------- 1 | subprojects { 2 | // Checkstyle 3 | apply plugin: 'checkstyle' 4 | checkstyle { 5 | ignoreFailures = true 6 | configFile = rootProject.file('codequality/checkstyle.xml') 7 | } 8 | 9 | // FindBugs 10 | apply plugin: 'findbugs' 11 | findbugs { 12 | ignoreFailures = true 13 | } 14 | 15 | // PMD 16 | apply plugin: 'pmd' 17 | //tasks.withType(Pmd) { reports.html.enabled true } 18 | 19 | apply plugin: 'cobertura' 20 | cobertura { 21 | sourceDirs = sourceSets.main.java.srcDirs 22 | format = 'html' 23 | includes = ['**/*.java', '**/*.groovy'] 24 | excludes = [] 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /gradle/convention.gradle: -------------------------------------------------------------------------------- 1 | // GRADLE-2087 workaround, perform after java plugin 2 | status = project.hasProperty('preferredStatus')?project.preferredStatus:(version.contains('SNAPSHOT')?'snapshot':'release') 3 | 4 | subprojects { project -> 5 | apply plugin: 'java' // Plugin as major conventions 6 | 7 | sourceCompatibility = 1.6 8 | 9 | // Restore status after Java plugin 10 | status = rootProject.status 11 | 12 | task sourcesJar(type: Jar, dependsOn:classes) { 13 | from sourceSets.main.allSource 14 | classifier 'sources' 15 | extension 'jar' 16 | } 17 | 18 | task javadocJar(type: Jar, dependsOn:javadoc) { 19 | from javadoc.destinationDir 20 | classifier 'javadoc' 21 | extension 'jar' 22 | } 23 | 24 | configurations.add('sources') 25 | configurations.add('javadoc') 26 | configurations.archives { 27 | extendsFrom configurations.sources 28 | extendsFrom configurations.javadoc 29 | } 30 | 31 | // When outputing to an Ivy repo, we want to use the proper type field 32 | gradle.taskGraph.whenReady { 33 | def isNotMaven = !it.hasTask(project.uploadMavenCentral) 34 | if (isNotMaven) { 35 | def artifacts = project.configurations.sources.artifacts 36 | def sourceArtifact = artifacts.iterator().next() 37 | sourceArtifact.type = 'sources' 38 | } 39 | } 40 | 41 | artifacts { 42 | sources(sourcesJar) { 43 | // Weird Gradle quirk where type will be used for the extension, but only for sources 44 | type 'jar' 45 | } 46 | javadoc(javadocJar) { 47 | type 'javadoc' 48 | } 49 | } 50 | 51 | configurations { 52 | provided { 53 | description = 'much like compile, but indicates you expect the JDK or a container to provide it. It is only available on the compilation classpath, and is not transitive.' 54 | transitive = true 55 | visible = true 56 | } 57 | } 58 | 59 | project.sourceSets { 60 | main.compileClasspath += project.configurations.provided 61 | main.runtimeClasspath -= project.configurations.provided 62 | test.compileClasspath += project.configurations.provided 63 | test.runtimeClasspath += project.configurations.provided 64 | } 65 | } 66 | 67 | apply plugin: 'github-pages' // Used to create publishGhPages task 68 | 69 | def docTasks = [:] 70 | [Javadoc,ScalaDoc,Groovydoc].each{ Class docClass -> 71 | def allSources = allprojects.tasks*.withType(docClass).flatten()*.source 72 | if (allSources) { 73 | def shortName = docClass.simpleName.toLowerCase() 74 | def docTask = task "aggregate${shortName.capitalize()}"(type: docClass, description: "Aggregate subproject ${shortName}s") { 75 | source = allSources 76 | destinationDir = file("${project.buildDir}/docs/${shortName}") 77 | doFirst { 78 | def classpaths = allprojects.findAll { it.plugins.hasPlugin(JavaPlugin) }.collect { it.sourceSets.main.compileClasspath } 79 | classpath = files(classpaths) 80 | } 81 | } 82 | docTasks[shortName] = docTask 83 | processGhPages.dependsOn(docTask) 84 | } 85 | } 86 | 87 | githubPages { 88 | repoUri = "git@github.com:Netflix/${rootProject.githubProjectName}.git" 89 | pages { 90 | docTasks.each { shortName, docTask -> 91 | from(docTask.outputs.files) { 92 | into "docs/${shortName}" 93 | } 94 | } 95 | } 96 | } 97 | 98 | // Generate wrapper, which is distributed as part of source to alleviate the need of installing gradle 99 | task createWrapper(type: Wrapper) { 100 | gradleVersion = '1.5' 101 | } 102 | -------------------------------------------------------------------------------- /gradle/license.gradle: -------------------------------------------------------------------------------- 1 | // Dependency for plugin was set in buildscript.gradle 2 | 3 | subprojects { 4 | apply plugin: 'license' //nl.javadude.gradle.plugins.license.LicensePlugin 5 | license { 6 | header rootProject.file('codequality/HEADER') 7 | ext.year = Calendar.getInstance().get(Calendar.YEAR) 8 | skipExistingHeaders true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /gradle/maven.gradle: -------------------------------------------------------------------------------- 1 | // Maven side of things 2 | subprojects { 3 | apply plugin: 'maven' // Java plugin has to have been already applied for the conf2scope mappings to work 4 | apply plugin: 'signing' 5 | 6 | signing { 7 | required { gradle.taskGraph.hasTask(uploadMavenCentral) } 8 | sign configurations.archives 9 | } 10 | 11 | /** 12 | * Publishing to Maven Central example provided from http://jedicoder.blogspot.com/2011/11/automated-gradle-project-deployment-to.html 13 | * artifactory will execute uploadArchives to force generation of ivy.xml, and we don't want that to trigger an upload to maven 14 | * central, so using custom upload task. 15 | */ 16 | task uploadMavenCentral(type:Upload, dependsOn: signArchives) { 17 | configuration = configurations.archives 18 | onlyIf { ['release', 'snapshot'].contains(project.status) } 19 | repositories.mavenDeployer { 20 | beforeDeployment { signing.signPom(it) } 21 | 22 | // To test deployment locally, use the following instead of oss.sonatype.org 23 | //repository(url: "file://localhost/${rootProject.rootDir}/repo") 24 | 25 | def sonatypeUsername = rootProject.hasProperty('sonatypeUsername')?rootProject.sonatypeUsername:'' 26 | def sonatypePassword = rootProject.hasProperty('sonatypePassword')?rootProject.sonatypePassword:'' 27 | 28 | repository(url: 'https://oss.sonatype.org/service/local/staging/deploy/maven2') { 29 | authentication(userName: sonatypeUsername, password: sonatypePassword) 30 | } 31 | 32 | snapshotRepository(url: 'https://oss.sonatype.org/content/repositories/snapshots/') { 33 | authentication(userName: sonatypeUsername, password: sonatypePassword) 34 | } 35 | 36 | // Prevent datastamp from being appending to artifacts during deployment 37 | uniqueVersion = false 38 | 39 | // Closure to configure all the POM with extra info, common to all projects 40 | pom.project { 41 | name "${project.name}" 42 | description "${project.name} developed by Netflix" 43 | developers { 44 | developer { 45 | id 'netflixgithub' 46 | name 'Netflix Open Source Development' 47 | email 'talent@netflix.com' 48 | } 49 | } 50 | licenses { 51 | license { 52 | name 'The Apache Software License, Version 2.0' 53 | url 'http://www.apache.org/licenses/LICENSE-2.0.txt' 54 | distribution 'repo' 55 | } 56 | } 57 | url "https://github.com/Netflix/${rootProject.githubProjectName}" 58 | scm { 59 | connection "scm:git:git@github.com:Netflix/${rootProject.githubProjectName}.git" 60 | url "scm:git:git@github.com:Netflix/${rootProject.githubProjectName}.git" 61 | developerConnection "scm:git:git@github.com:Netflix/${rootProject.githubProjectName}.git" 62 | } 63 | issueManagement { 64 | system 'github' 65 | url "https://github.com/Netflix/${rootProject.githubProjectName}/issues" 66 | } 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /gradle/netflix-oss.gradle: -------------------------------------------------------------------------------- 1 | apply from: 'http://artifacts.netflix.com/gradle-netflix-local/artifactory.gradle' 2 | -------------------------------------------------------------------------------- /gradle/release.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'release' 2 | 3 | [ uploadIvyLocal: 'uploadLocal', uploadArtifactory: 'artifactoryPublish', buildWithArtifactory: 'build' ].each { key, value -> 4 | // Call out to compile against internal repository 5 | task "${key}"(type: GradleBuild) { 6 | startParameter = project.gradle.startParameter.newInstance() 7 | doFirst { 8 | startParameter.projectProperties = [status: project.status, preferredStatus: project.status] 9 | } 10 | startParameter.addInitScript( file('gradle/netflix-oss.gradle') ) 11 | startParameter.getExcludedTaskNames().add('check') 12 | tasks = [ 'build', value ] 13 | } 14 | } 15 | 16 | // Marker task for following code to key in on 17 | task releaseCandidate(dependsOn: release) 18 | task forceCandidate { 19 | onlyIf { gradle.taskGraph.hasTask(releaseCandidate) } 20 | doFirst { project.status = 'candidate' } 21 | } 22 | task forceRelease { 23 | onlyIf { !gradle.taskGraph.hasTask(releaseCandidate) } 24 | doFirst { project.status = 'release' } 25 | } 26 | release.dependsOn([forceCandidate, forceRelease]) 27 | 28 | task uploadMavenCentral(dependsOn: subprojects.tasks.uploadMavenCentral) 29 | task releaseSnapshot(dependsOn: [uploadArtifactory, uploadMavenCentral]) 30 | 31 | // Ensure our versions look like the project status before publishing 32 | task verifyStatus << { 33 | def hasSnapshot = version.contains('-SNAPSHOT') 34 | if (project.status == 'snapshot' && !hasSnapshot) { 35 | throw new GradleException("Version (${version}) needs -SNAPSHOT if publishing snapshot") 36 | } 37 | } 38 | uploadArtifactory.dependsOn(verifyStatus) 39 | uploadMavenCentral.dependsOn(verifyStatus) 40 | 41 | // Ensure upload happens before taggging, hence upload failures will leave repo in a revertable state 42 | preTagCommit.dependsOn([uploadArtifactory, uploadMavenCentral]) 43 | 44 | 45 | gradle.taskGraph.whenReady { taskGraph -> 46 | def hasRelease = taskGraph.hasTask('commitNewVersion') 47 | def indexOf = { return taskGraph.allTasks.indexOf(it) } 48 | 49 | if (hasRelease) { 50 | assert indexOf(build) < indexOf(unSnapshotVersion), 'build target has to be after unSnapshotVersion' 51 | assert indexOf(uploadMavenCentral) < indexOf(preTagCommit), 'preTagCommit has to be after uploadMavenCentral' 52 | assert indexOf(uploadArtifactory) < indexOf(preTagCommit), 'preTagCommit has to be after uploadArtifactory' 53 | } 54 | } 55 | 56 | // Prevent plugin from asking for a version number interactively 57 | ext.'gradle.release.useAutomaticVersion' = "true" 58 | 59 | release { 60 | git.requireBranch = null 61 | } 62 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Netflix/ReactiveLab/e87569e009c73beeb84d05077003fcceec82aaf9/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Apr 02 11:45:56 PDT 2013 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=http\://services.gradle.org/distributions/gradle-1.11-bin.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /reactive-lab-dashboard/README.md: -------------------------------------------------------------------------------- 1 | # Hystrix Dashboard 2 | 3 | View the [Dashboard Wiki](https://github.com/Netflix/Hystrix/wiki/Dashboard) for more information including installation instructions. 4 | 5 | 6 | 7 | # Run via Gradle 8 | 9 | ``` 10 | $ git clone git@github.com:Netflix/Hystrix.git 11 | $ cd Hystrix/hystrix-dashboard 12 | $ ../gradlew jettyRun 13 | > Building > :hystrix-dashboard:jettyRun > Running at http://localhost:7979/hystrix-dashboard 14 | ``` 15 | 16 | Once running, open http://localhost:7979/hystrix-dashboard. 17 | 18 | 19 | # Example 20 | 21 | Example screenshot from iPad while monitoring Netflix API: 22 | 23 |
24 | -------------------------------------------------------------------------------- /reactive-lab-dashboard/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | apply plugin: 'war' 3 | apply plugin: 'jetty' 4 | apply plugin: 'eclipse' 5 | apply plugin: 'idea' 6 | 7 | dependencies { 8 | compile 'javax.servlet:servlet-api:2.5' 9 | compile 'org.apache.httpcomponents:httpclient:4.2.1' 10 | compile 'log4j:log4j:1.2.17' 11 | compile 'org.slf4j:slf4j-log4j12:1.7.0' 12 | } 13 | 14 | jettyRun { 15 | httpPort = 7979 16 | } 17 | -------------------------------------------------------------------------------- /reactive-lab-dashboard/src/main/java/com/netflix/hystrix/dashboard/stream/MockStreamServlet.java: -------------------------------------------------------------------------------- 1 | package com.netflix.hystrix.dashboard.stream; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.io.InputStreamReader; 7 | import java.io.StringWriter; 8 | import java.nio.charset.Charset; 9 | 10 | import javax.servlet.ServletException; 11 | import javax.servlet.http.HttpServlet; 12 | import javax.servlet.http.HttpServletRequest; 13 | import javax.servlet.http.HttpServletResponse; 14 | 15 | import org.slf4j.Logger; 16 | import org.slf4j.LoggerFactory; 17 | 18 | /** 19 | * Simulate an event stream URL by retrieving pre-canned data instead of going to live servers. 20 | */ 21 | public class MockStreamServlet extends HttpServlet { 22 | private static final long serialVersionUID = 1L; 23 | private static final Logger logger = LoggerFactory.getLogger(MockStreamServlet.class); 24 | 25 | public MockStreamServlet() { 26 | super(); 27 | } 28 | 29 | /** 30 | * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) 31 | */ 32 | protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 33 | String filename = request.getParameter("file"); 34 | if (filename == null) { 35 | // default to using hystrix.stream 36 | filename = "hystrix.stream"; 37 | } else { 38 | // strip any .. / characters to avoid security problems 39 | filename = filename.replaceAll("\\.\\.", ""); 40 | filename = filename.replaceAll("/", ""); 41 | } 42 | int delay = 500; 43 | String delayArg = request.getParameter("delay"); 44 | if (delayArg != null) { 45 | delay = Integer.parseInt(delayArg); 46 | } 47 | 48 | int batch = 1; 49 | String batchArg = request.getParameter("batch"); 50 | if (batchArg != null) { 51 | batch = Integer.parseInt(batchArg); 52 | } 53 | 54 | String data = getFileFromPackage(filename); 55 | String lines[] = data.split("\n"); 56 | 57 | response.setContentType("text/event-stream"); 58 | response.setCharacterEncoding("UTF-8"); 59 | 60 | int batchCount = 0; 61 | // loop forever unless the user closes the connection 62 | for (;;) { 63 | for (String s : lines) { 64 | s = s.trim(); 65 | if (s.length() > 0) { 66 | try { 67 | response.getWriter().println(s); 68 | response.getWriter().println(""); // a newline is needed after each line for the events to trigger 69 | response.getWriter().flush(); 70 | batchCount++; 71 | } catch (Exception e) { 72 | logger.warn("Exception writing mock data to output.", e); 73 | // most likely the user closed the connection 74 | return; 75 | } 76 | if (batchCount == batch) { 77 | // we insert the delay whenever we finish a batch 78 | try { 79 | // simulate the delays we get from the real feed 80 | Thread.sleep(delay); 81 | } catch (InterruptedException e) { 82 | // ignore 83 | } 84 | // reset 85 | batchCount = 0; 86 | } 87 | } 88 | } 89 | } 90 | } 91 | 92 | private String getFileFromPackage(String filename) { 93 | try { 94 | String file = "/" + this.getClass().getPackage().getName().replace('.', '/') + "/" + filename; 95 | InputStream is = this.getClass().getResourceAsStream(file); 96 | try { 97 | /* this is FAR too much work just to get a string from a file */ 98 | BufferedReader in = new BufferedReader(new InputStreamReader(is, Charset.forName("UTF-8"))); 99 | StringWriter s = new StringWriter(); 100 | int c = -1; 101 | while ((c = in.read()) > -1) { 102 | s.write(c); 103 | } 104 | return s.toString(); 105 | } finally { 106 | is.close(); 107 | } 108 | } catch (Exception e) { 109 | throw new RuntimeException("Could not find file: " + filename, e); 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /reactive-lab-dashboard/src/main/webapp/WEB-INF/classes/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootLogger=INFO, FILE 2 | log4j.appender.FILE=org.apache.log4j.ConsoleAppender 3 | log4j.appender.FILE.layout=org.apache.log4j.PatternLayout 4 | log4j.appender.FILE.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %p %C:%L [%C{1}] [%M]: %m%n 5 | 6 | log4j.appender.FILE.httpclient=ERROR 7 | -------------------------------------------------------------------------------- /reactive-lab-dashboard/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | MockStreamServlet 10 | MockStreamServlet 11 | com.netflix.hystrix.dashboard.stream.MockStreamServlet 12 | 13 | 14 | MockStreamServlet 15 | /mock.stream 16 | 17 | 18 | 19 | 20 | 21 | ProxyStreamServlet 22 | ProxyStreamServlet 23 | com.netflix.hystrix.dashboard.stream.ProxyStreamServlet 24 | 25 | 26 | ProxyStreamServlet 27 | /proxy.stream 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /reactive-lab-dashboard/src/main/webapp/components/hystrixCommand/hystrixCommand.css: -------------------------------------------------------------------------------- 1 | .dependencies .spacer { 2 | width: 100%; 3 | margin: 0 auto; 4 | padding-top:4px; 5 | clear:both; 6 | } 7 | 8 | 9 | .dependencies .last { 10 | margin-right: 0px; 11 | } 12 | 13 | .dependencies span.loading { 14 | display: block; 15 | padding-top: 6%; 16 | padding-bottom: 6%; 17 | color: gray; 18 | text-align: center; 19 | } 20 | 21 | .dependencies span.loading.failed { 22 | color: red; 23 | } 24 | 25 | 26 | .dependencies div.monitor { 27 | float: left; 28 | margin-right:5px; 29 | margin-top:5px; 30 | } 31 | 32 | .dependencies div.monitor p.name { 33 | font-weight:bold; 34 | font-size: 10pt; 35 | text-align: right; 36 | padding-bottom: 5px; 37 | } 38 | 39 | .dependencies div.monitor_data { 40 | margin: 0 auto; 41 | } 42 | 43 | /* override the HREF when we have specified it as a tooltip to not act like a link */ 44 | .dependencies div.monitor_data a.tooltip { 45 | text-decoration: none; 46 | cursor: default; 47 | } 48 | 49 | .dependencies div.monitor_data div.counters { 50 | text-align: right; 51 | padding-bottom: 10px; 52 | font-size: 10pt; 53 | clear: both; 54 | 55 | } 56 | 57 | .dependencies div.monitor_data div.counters div.cell { 58 | display: inline; 59 | float: right; 60 | } 61 | 62 | .dependencies .borderRight { 63 | border-right: 1px solid grey; 64 | padding-right: 6px; 65 | padding-left: 8px; 66 | } 67 | 68 | .dependencies div.cell .line { 69 | display: block; 70 | } 71 | 72 | .dependencies div.monitor_data a, 73 | .dependencies span.rate_value { 74 | font-weight:bold; 75 | } 76 | 77 | 78 | .dependencies span.smaller { 79 | font-size: 8pt; 80 | color: grey; 81 | } 82 | 83 | 84 | 85 | .dependencies div.tableRow { 86 | width:100%; 87 | white-space: nowrap; 88 | font-size: 8pt; 89 | margin: 0 auto; 90 | clear:both; 91 | padding-left:26%; 92 | } 93 | 94 | .dependencies div.tableRow .cell { 95 | float:left; 96 | } 97 | 98 | .dependencies div.tableRow .header { 99 | width:18%; 100 | text-align:right; 101 | padding-right:2%; 102 | } 103 | 104 | .dependencies div.tableRow .data { 105 | width:17%; 106 | font-weight: bold; 107 | text-align:right; 108 | } 109 | 110 | 111 | .dependencies div.monitor { 112 | width: 245px; /* we want a fixed width instead of percentage as I want the boxes to be a set size and then fill in as many as can fit in each row ... this allows 3 columns on an iPad */ 113 | height: 150px; 114 | } 115 | 116 | .dependencies .success { 117 | color: green; 118 | } 119 | .dependencies .shortCircuited { 120 | color: blue; 121 | } 122 | .dependencies .timeout { 123 | color: #FF9900; /* shade of orange */ 124 | } 125 | .dependencies .failure { 126 | color: red; 127 | } 128 | 129 | .dependencies .rejected { 130 | color: purple; 131 | } 132 | 133 | .dependencies .exceptionsThrown { 134 | color: brown; 135 | } 136 | 137 | .dependencies div.monitor_data a.rate { 138 | color: black; 139 | font-size: 11pt; 140 | } 141 | 142 | .dependencies div.rate { 143 | padding-top: 1px; 144 | clear:both; 145 | text-align:right; 146 | } 147 | 148 | .dependencies .errorPercentage { 149 | color: grey; 150 | } 151 | 152 | .dependencies div.cell .errorPercentage { 153 | padding-left:5px; 154 | font-size: 12pt !important; 155 | } 156 | 157 | 158 | .dependencies div.monitor div.chart { 159 | } 160 | 161 | .dependencies div.monitor div.chart svg { 162 | } 163 | 164 | .dependencies div.monitor div.chart svg text { 165 | fill: white; 166 | } 167 | 168 | 169 | .dependencies div.circuitStatus { 170 | width:100%; 171 | white-space: nowrap; 172 | font-size: 9pt; 173 | margin: 0 auto; 174 | clear:both; 175 | text-align:right; 176 | padding-top: 4px; 177 | } 178 | 179 | .dependencies #hidden { 180 | width:1px; 181 | height:1px; 182 | background: lightgrey; 183 | display: none; 184 | } 185 | 186 | 187 | 188 | /* sparkline */ 189 | .dependencies path { 190 | stroke: steelblue; 191 | stroke-width: 1; 192 | fill: none; 193 | } 194 | 195 | 196 | } 197 | -------------------------------------------------------------------------------- /reactive-lab-dashboard/src/main/webapp/components/hystrixCommand/magnifying-glass-icon-20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Netflix/ReactiveLab/e87569e009c73beeb84d05077003fcceec82aaf9/reactive-lab-dashboard/src/main/webapp/components/hystrixCommand/magnifying-glass-icon-20.png -------------------------------------------------------------------------------- /reactive-lab-dashboard/src/main/webapp/components/hystrixCommand/magnifying-glass-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Netflix/ReactiveLab/e87569e009c73beeb84d05077003fcceec82aaf9/reactive-lab-dashboard/src/main/webapp/components/hystrixCommand/magnifying-glass-icon.png -------------------------------------------------------------------------------- /reactive-lab-dashboard/src/main/webapp/components/hystrixCommand/templates/hystrixCircuit.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 6 | 7 |
8 | <% if(propertyValue_executionIsolationStrategy == 'THREAD') { %> 9 | <%= addCommas(rollingCountTimeout) %> 10 | <%= addCommas(rollingCountThreadPoolRejected) %> 11 | <% } %> 12 | <%= addCommas(rollingCountFailure) %> 13 |
14 | 19 |
20 | 21 | 24 | 27 | 28 |
29 | <% if(propertyValue_circuitBreakerForceClosed) { %> 30 | [ Forced Closed ] 31 | <% } %> 32 | <% if(propertyValue_circuitBreakerForceOpen) { %> 33 | Circuit Forced Open 34 | <% } else { %> 35 | <% if(isCircuitBreakerOpen == reportingHosts) { %> 36 | Circuit Open 37 | <% } else if(isCircuitBreakerOpen == 0) { %> 38 | Circuit Closed 39 | <% } else { 40 | /* We have some circuits that are open */ 41 | %> 42 | Circuit <%= isCircuitBreakerOpen.replace("true", "Open").replace("false", "Closed") %>) 43 | <% } %> 44 | <% } %> 45 |
46 | 47 |
48 | 49 |
50 | <% if(typeof reportingHosts != 'undefined') { %> 51 |
Hosts
52 |
<%= reportingHosts %>
53 | <% } else { %> 54 |
Host
55 |
Single
56 | <% } %> 57 |
90th
58 |
<%= getInstanceAverage(latencyExecute['90'], reportingHosts, false) %>ms
59 |
60 |
61 |
Median
62 |
<%= getInstanceAverage(latencyExecute['50'], reportingHosts, false) %>ms
63 |
99th
64 |
<%= getInstanceAverage(latencyExecute['99'], reportingHosts, false) %>ms
65 |
66 |
67 |
Mean
68 |
<%= latencyExecute_mean %>ms
69 |
99.5th
70 |
<%= getInstanceAverage(latencyExecute['99.5'], reportingHosts, false) %>ms
71 |
72 | 73 | 74 | -------------------------------------------------------------------------------- /reactive-lab-dashboard/src/main/webapp/components/hystrixCommand/templates/hystrixCircuitContainer.html: -------------------------------------------------------------------------------- 1 |
2 | <% 3 | var displayName = name; 4 | var toolTip = ""; 5 | if(displayName.length > 32) { 6 | displayName = displayName.substring(0,4) + "..." + displayName.substring(displayName.length-20, displayName.length); 7 | toolTip = "title=\"" + name + "\""; 8 | } 9 | %> 10 | 11 |
12 |
13 | <% if(includeDetailIcon) { %> 14 |

style="padding-right:16px"> 15 | <%= displayName %> 16 | 17 |

18 | <% } else { %> 19 |

><%= displayName %>

20 | <% } %> 21 |
22 |
23 |
24 |
25 |
26 | 27 | 40 |
41 | -------------------------------------------------------------------------------- /reactive-lab-dashboard/src/main/webapp/components/hystrixCommand/templates/hystrixCircuitProperties.html: -------------------------------------------------------------------------------- 1 |
2 |
Median
3 |
<%= sla_medianLastMinute %>ms
4 |
99th
5 |
<%= sla_percentile99LastMinute %>ms
6 |
7 | -------------------------------------------------------------------------------- /reactive-lab-dashboard/src/main/webapp/components/hystrixThreadPool/hystrixThreadPool.css: -------------------------------------------------------------------------------- 1 | .dependencyThreadPools .spacer { 2 | width: 100%; 3 | margin: 0 auto; 4 | padding-top:4px; 5 | clear:both; 6 | } 7 | 8 | 9 | .dependencyThreadPools .last { 10 | margin-right: 0px; 11 | } 12 | 13 | .dependencyThreadPools span.loading { 14 | display: block; 15 | padding-top: 6%; 16 | padding-bottom: 6%; 17 | color: gray; 18 | text-align: center; 19 | } 20 | 21 | .dependencyThreadPools span.loading.failed { 22 | color: red; 23 | } 24 | 25 | 26 | .dependencyThreadPools div.monitor { 27 | float: left; 28 | margin-right:5px; /* these are tweaked to look good on desktop and iPad portrait, and fit things densely */ 29 | margin-top:5px; 30 | } 31 | 32 | .dependencyThreadPools div.monitor p.name { 33 | font-weight:bold; 34 | font-size: 10pt; 35 | text-align: right; 36 | padding-bottom: 5px; 37 | } 38 | 39 | .dependencyThreadPools div.monitor_data { 40 | margin: 0 auto; 41 | } 42 | 43 | .dependencyThreadPools span.smaller { 44 | font-size: 8pt; 45 | color: grey; 46 | } 47 | 48 | 49 | .dependencyThreadPools div.tableRow { 50 | width:100%; 51 | white-space: nowrap; 52 | font-size: 8pt; 53 | margin: 0 auto; 54 | clear:both; 55 | } 56 | 57 | .dependencyThreadPools div.tableRow .cell { 58 | float:left; 59 | } 60 | 61 | .dependencyThreadPools div.tableRow .header { 62 | text-align:right; 63 | padding-right:5px; 64 | } 65 | 66 | .dependencyThreadPools div.tableRow .header.left { 67 | width:85px; 68 | } 69 | 70 | .dependencyThreadPools div.tableRow .header.right { 71 | width:75px; 72 | } 73 | 74 | .dependencyThreadPools div.tableRow .data { 75 | font-weight: bold; 76 | text-align:right; 77 | } 78 | 79 | .dependencyThreadPools div.tableRow .data.left { 80 | width:30px; 81 | } 82 | 83 | .dependencyThreadPools div.tableRow .data.right { 84 | width:45px; 85 | } 86 | 87 | .dependencyThreadPools div.monitor { 88 | width: 245px; /* we want a fixed width instead of percentage as I want the boxes to be a set size and then fill in as many as can fit in each row ... this allows 3 columns on an iPad */ 89 | height: 110px; 90 | } 91 | 92 | 93 | 94 | 95 | 96 | /* override the HREF when we have specified it as a tooltip to not act like a link */ 97 | .dependencyThreadPools div.monitor_data a.tooltip { 98 | text-decoration: none; 99 | cursor: default; 100 | } 101 | 102 | .dependencyThreadPools div.monitor_data a.rate { 103 | font-weight:bold; 104 | color: black; 105 | font-size: 11pt; 106 | } 107 | 108 | .dependencyThreadPools div.rate { 109 | padding-top: 1px; 110 | clear:both; 111 | text-align:right; 112 | } 113 | 114 | .dependencyThreadPools span.rate_value { 115 | font-weight:bold; 116 | } 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | .dependencyThreadPools div.monitor div.chart { 125 | } 126 | 127 | .dependencyThreadPools div.monitor div.chart svg { 128 | } 129 | 130 | .dependencyThreadPools div.monitor div.chart svg text { 131 | fill: white; 132 | } 133 | 134 | .dependencyThreadPools #hidden { 135 | width:1px; 136 | height:1px; 137 | background: lightgrey; 138 | display: none; 139 | } 140 | 141 | 142 | -------------------------------------------------------------------------------- /reactive-lab-dashboard/src/main/webapp/components/hystrixThreadPool/templates/hystrixThreadPool.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 7 | 10 | 11 |
12 | 13 |
14 |
Active
15 |
<%= currentActiveCount%>
16 | 17 |
Max Active
18 |
<%= addCommas(rollingMaxActiveThreads)%>
19 |
20 | 21 |
22 |
Queued
23 |
<%= currentQueueSize %>
24 |
Executions
25 |
<%= addCommas(rollingCountThreadsExecuted)%>
26 |
27 |
28 |
Pool Size
29 |
<%= currentPoolSize %>
30 |
Queue Size
31 |
<%= propertyValue_queueSizeRejectionThreshold %>
32 |
33 | -------------------------------------------------------------------------------- /reactive-lab-dashboard/src/main/webapp/components/hystrixThreadPool/templates/hystrixThreadPoolContainer.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | <% 4 | var displayName = name; 5 | var toolTip = ""; 6 | if(displayName.length > 32) { 7 | displayName = displayName.substring(0,4) + "..." + displayName.substring(displayName.length-20, displayName.length); 8 | toolTip = "title=\"" + name + "\""; 9 | } 10 | %> 11 | 12 |
13 |

><%= displayName %>

14 |
15 |
16 |
17 | 18 | 19 | 33 | 34 |
35 | -------------------------------------------------------------------------------- /reactive-lab-dashboard/src/main/webapp/css/global.css: -------------------------------------------------------------------------------- 1 | @IMPORT url("resets.css"); 2 | 3 | body { 4 | font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; 5 | } 6 | 7 | img, object, embed { 8 | max-width: 100%; 9 | } 10 | 11 | img { 12 | height: auto; 13 | } 14 | 15 | 16 | #header { 17 | background: #FFFFFF url(http://raw.github.com/wiki/Netflix/Hystrix/images/hystrix-logo-tagline-tiny.png) no-repeat scroll 99% 0%; 18 | height: 65px; 19 | margin-bottom: 5px; 20 | } 21 | 22 | #header h2 { 23 | float:left; 24 | color: black; 25 | position:relative; 26 | padding-left: 20px; 27 | top: 26px; 28 | font-size: 20px; 29 | font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; 30 | } 31 | 32 | #header .header_nav { 33 | position:absolute; 34 | top:48px; 35 | right:15px; 36 | } 37 | 38 | #header .header_links { 39 | float:left; 40 | color: lightgray; 41 | font-size: 18px; 42 | top: 3px; 43 | padding-left: 10px; 44 | font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; 45 | } 46 | 47 | #header .header_links a { 48 | color: white; 49 | } 50 | 51 | #header .header_clusters { 52 | float:left; 53 | position:relative; 54 | padding-left: 10px; 55 | top: -1px; 56 | font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; 57 | } 58 | 59 | 60 | @media screen and (min-width: 1500px) { 61 | 62 | #header .header_nav { 63 | top:13px; 64 | right:130px; 65 | } 66 | 67 | #header { 68 | background: #FFFFFF url(http://raw.github.com/wiki/Netflix/Hystrix/images/hystrix-logo-tagline-tiny.png) no-repeat scroll 99% 50%; 69 | height: 65px; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /reactive-lab-dashboard/src/main/webapp/css/resets.css: -------------------------------------------------------------------------------- 1 | /* 2 | html5doctor.com Reset Stylesheet 3 | v1.6.1 4 | Last Updated: 2010-09-17 5 | Author: Richard Clark - http://richclarkdesign.com 6 | Twitter: @rich_clark 7 | */ 8 | 9 | html, body, div, span, object, iframe, 10 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 11 | abbr, address, cite, code, 12 | del, dfn, em, img, ins, kbd, q, samp, 13 | small, strong, sub, sup, var, 14 | b, i, 15 | dl, dt, dd, ol, ul, li, 16 | fieldset, form, label, legend, 17 | table, caption, tbody, tfoot, thead, tr, th, td, 18 | article, aside, canvas, details, figcaption, figure, 19 | footer, header, hgroup, menu, nav, section, summary, 20 | time, mark, audio, video { 21 | margin:0; 22 | padding:0; 23 | border:0; 24 | outline:0; 25 | font-size:100%; 26 | vertical-align:baseline; 27 | background:transparent; 28 | } 29 | 30 | body { 31 | line-height:1; 32 | } 33 | 34 | article,aside,details,figcaption,figure, 35 | footer,header,hgroup,menu,nav,section { 36 | display:block; 37 | } 38 | 39 | nav ul { 40 | list-style:none; 41 | } 42 | 43 | blockquote, q { 44 | quotes:none; 45 | } 46 | 47 | blockquote:before, blockquote:after, 48 | q:before, q:after { 49 | content:''; 50 | content:none; 51 | } 52 | 53 | a { 54 | margin:0; 55 | padding:0; 56 | font-size:100%; 57 | vertical-align:baseline; 58 | background:transparent; 59 | } 60 | 61 | /* change colours to suit your needs */ 62 | ins { 63 | background-color:#ff9; 64 | color:#000; 65 | text-decoration:none; 66 | } 67 | 68 | /* change colours to suit your needs */ 69 | mark { 70 | background-color:#ff9; 71 | color:#000; 72 | font-style:italic; 73 | font-weight:bold; 74 | } 75 | 76 | del { 77 | text-decoration: line-through; 78 | } 79 | 80 | abbr[title], dfn[title] { 81 | border-bottom:1px dotted; 82 | cursor:help; 83 | } 84 | 85 | table { 86 | border-collapse:collapse; 87 | border-spacing:0; 88 | } 89 | 90 | /* change border colour to suit your needs */ 91 | hr { 92 | display:block; 93 | height:1px; 94 | border:0; 95 | border-top:1px solid #cccccc; 96 | margin:1em 0; 97 | padding:0; 98 | } 99 | 100 | input, select { 101 | vertical-align:middle; 102 | } -------------------------------------------------------------------------------- /reactive-lab-dashboard/src/main/webapp/css/simplegrid/1236_grid.css: -------------------------------------------------------------------------------- 1 | /* SimpleGrid - a fork of CSSGrid by Crowd Favorite (https://github.com/crowdfavorite/css-grid) 2 | * http://simplegrid.info 3 | * by Conor Muirhead (http://conor.cc) of Early LLC (http://earlymade.com) 4 | * License: http://creativecommons.org/licenses/MIT/ */ 5 | 6 | /* Containers */ 7 | body { font-size: 1.125em; } 8 | .grid{ width:1206px; } 9 | 10 | /* 6-Col Grid Sizes */ 11 | .slot-0,.slot-1,.slot-2,.slot-3,.slot-4,.slot-5{ width:176px; } /* Sixths */ 12 | .slot-0-1,.slot-1-2,.slot-2-3,.slot-3-4,.slot-4-5{ width:382px; } /* Thirds */ 13 | .slot-0-1-2-3,.slot-1-2-3-4,.slot-2-3-4-5{ width:794px; } /* Two-Thirds */ 14 | .slot-0-1-2-3-4,.slot-1-2-3-4-5{ width:1000px; } /* Five-Sixths */ 15 | 16 | /* 4-Col Grid Sizes */ 17 | .slot-6,.slot-7,.slot-8,.slot-9{ width:279px; } /* Quarters */ 18 | .slot-6-7-8,.slot-7-8-9{ width:897px; } /* Three-Quarters */ 19 | 20 | /* 6-Col/4-Col Shared Grid Sizes */ 21 | .slot-0-1-2,.slot-1-2-3,.slot-2-3-4,.slot-3-4-5, .slot-6-7,.slot-7-8,.slot-8-9{ width:588px; } /* Halves */ -------------------------------------------------------------------------------- /reactive-lab-dashboard/src/main/webapp/css/simplegrid/720_grid.css: -------------------------------------------------------------------------------- 1 | /* SimpleGrid - a fork of CSSGrid by Crowd Favorite (https://github.com/crowdfavorite/css-grid) 2 | * http://simplegrid.info 3 | * by Conor Muirhead (http://conor.cc) of Early LLC (http://earlymade.com) 4 | * License: http://creativecommons.org/licenses/MIT/ */ 5 | 6 | /* Containers */ 7 | body { font-size: 0.875em; padding: 0; } 8 | .grid{ margin:0 auto; padding: 0 10px; width:700px; } 9 | .row{ clear:left; } 10 | 11 | /* Slots Setup */ 12 | .slot-0,.slot-1,.slot-2,.slot-3,.slot-4,.slot-5,.slot-0-1,.slot-0-1-2,.slot-0-1-2-3,.slot-0-1-2-3-4,.slot-0-1-2-3-4-5,.slot-1-2,.slot-1-2-3,.slot-1-2-3-4,.slot-1-2-3-4-5,.slot-2-3,.slot-2-3-4,.slot-2-3-4-5,.slot-3-4,.slot-3-4-5,.slot-4-5,.slot-6,.slot-7,.slot-8,.slot-9,.slot-6-7,.slot-6-7-8,.slot-6-7-8-9,.slot-7-8,.slot-7-8-9,.slot-8-9{ display:inline; float:left; margin-left:20px; } 13 | 14 | /* 6-Col Grid Sizes */ 15 | .slot-0,.slot-1,.slot-2,.slot-3,.slot-4,.slot-5{ width:100px; } /* Sixths */ 16 | .slot-0-1,.slot-1-2,.slot-2-3,.slot-3-4,.slot-4-5{ width:220px; } /* Thirds */ 17 | .slot-0-1-2-3,.slot-1-2-3-4,.slot-2-3-4-5{ width:460px; } /* Two-Thirds */ 18 | .slot-0-1-2-3-4,.slot-1-2-3-4-5{ width:580px; } /* Five-Sixths */ 19 | 20 | /* 4-Col Grid Sizes */ 21 | .slot-6,.slot-7,.slot-8,.slot-9{ width:160px; } /* Quarters */ 22 | .slot-6-7-8,.slot-7-8-9{ width:520px; } /* Three-Quarters */ 23 | 24 | /* 6-Col/4-Col Shared Grid Sizes */ 25 | .slot-0-1-2,.slot-1-2-3,.slot-2-3-4,.slot-3-4-5, .slot-6-7,.slot-7-8,.slot-8-9{ width:340px; } /* Halves */ 26 | .slot-0-1-2-3-4-5, .slot-6-7-8-9{ width: 100%; } /* Full-Width */ 27 | 28 | /* Zeroing Out Leftmost Slot Margins */ 29 | .slot-0,.slot-0-1,.slot-0-1-2,.slot-0-1-2-3,.slot-0-1-2-3-4,.slot-0-1-2-3-4-5,.slot-6,.slot-6-7,.slot-6-7-8,.slot-6-7-8-9,.slot-1 .slot-1,.slot-1-2 .slot-1,.slot-1-2 .slot-1-2,.slot-1-2-3 .slot-1,.slot-1-2-3 .slot-1-2,.slot-1-2-3 .slot-1-2-3,.slot-1-2-3-4 .slot-1,.slot-1-2-3-4 .slot-1-2,.slot-1-2-3-4 .slot-1-2-3,.slot-1-2-3-4 .slot-1-2-3-4,.slot-1-2-3-4-5 .slot-1,.slot-1-2-3-4-5 .slot-1-2,.slot-1-2-3-4-5 .slot-1-2-3,.slot-1-2-3-4-5 .slot-1-2-3-4,.slot-1-2-3-4-5 .slot-1-2-3-4-5,.slot-2 .slot-2,.slot-2-3 .slot-2,.slot-2-3 .slot-2-3,.slot-2-3-4 .slot-2,.slot-2-3-4 .slot-2-3,.slot-2-3-4 .slot-2-3-4,.slot-2-3-4-5 .slot-2,.slot-2-3-4-5 .slot-2-3,.slot-2-3-4-5 .slot-2-3-4,.slot-2-3-4-5 .slot-2-3-4-5,.slot-3 .slot-3,.slot-3-4 .slot-3,.slot-3-4 .slot-3-4,.slot-3-4-5 .slot-3,.slot-3-4-5 .slot-3-4,.slot-3-4-5 .slot-3-4-5,.slot-4 .slot-4,.slot-4-5 .slot-4,.slot-4-5 .slot-4-5,.slot-5 .slot-5,.slot-7 .slot-7,.slot-7-8 .slot-7,.slot-7-8 .slot-7-8,.slot-7-8-9 .slot-7,.slot-7-8-9 .slot-7-8,.slot-7-8-9 .slot-7-8-9,.slot-8 .slot-8,.slot-8-9 .slot-8,.slot-8-9 .slot-8-9{ margin-left:0 !important; } /* Important is to avoid repeating this in larger screen css files */ 30 | 31 | /* Row Clearfix */ 32 | .row:after{ visibility:hidden; display:block; font-size:0; content:" "; clear:both; height:0; } 33 | .row{ zoom:1; } -------------------------------------------------------------------------------- /reactive-lab-dashboard/src/main/webapp/css/simplegrid/986_grid.css: -------------------------------------------------------------------------------- 1 | /* SimpleGrid - a fork of CSSGrid by Crowd Favorite (https://github.com/crowdfavorite/css-grid) 2 | * http://simplegrid.info 3 | * by Conor Muirhead (http://conor.cc) of Early LLC (http://earlymade.com) 4 | * License: http://creativecommons.org/licenses/MIT/ */ 5 | 6 | /* Containers */ 7 | body { font-size: 100%; } 8 | .grid{ width:966px; } 9 | 10 | /* Slots Setup */ 11 | .slot-0,.slot-1,.slot-2,.slot-3,.slot-4,.slot-5,.slot-0-1,.slot-0-1-2,.slot-0-1-2-3,.slot-0-1-2-3-4,.slot-0-1-2-3-4-5,.slot-1-2,.slot-1-2-3,.slot-1-2-3-4,.slot-1-2-3-4-5,.slot-2-3,.slot-2-3-4,.slot-2-3-4-5,.slot-3-4,.slot-3-4-5,.slot-4-5,.slot-6,.slot-7,.slot-8,.slot-9,.slot-6-7,.slot-6-7-8,.slot-6-7-8-9,.slot-7-8,.slot-7-8-9,.slot-8-9{ display:inline; float:left; margin-left:30px; } 12 | 13 | /* 6-Col Grid Sizes */ 14 | .slot-0,.slot-1,.slot-2,.slot-3,.slot-4,.slot-5{ width:136px; } /* Sixths */ 15 | .slot-0-1,.slot-1-2,.slot-2-3,.slot-3-4,.slot-4-5{ width:302px; } /* Thirds */ 16 | .slot-0-1-2-3,.slot-1-2-3-4,.slot-2-3-4-5{ width:634px; } /* Two-Thirds */ 17 | .slot-0-1-2-3-4,.slot-1-2-3-4-5{ width:800px; } /* Five-Sixths */ 18 | 19 | /* 4-Col Grid Sizes */ 20 | .slot-6,.slot-7,.slot-8,.slot-9{ width:219px; } /* Quarters */ 21 | .slot-6-7-8,.slot-7-8-9{ width:717px; } /* Three-Quarters */ 22 | 23 | /* 6-Col/4-Col Shared Grid Sizes */ 24 | .slot-0-1-2,.slot-1-2-3,.slot-2-3-4,.slot-3-4-5, .slot-6-7,.slot-7-8,.slot-8-9{ width:468px; } /* Halves */ -------------------------------------------------------------------------------- /reactive-lab-dashboard/src/main/webapp/css/simplegrid/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Crowd Favorite, Ltd. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /reactive-lab-dashboard/src/main/webapp/css/simplegrid/README.txt: -------------------------------------------------------------------------------- 1 | http://simplegrid.info/ -------------------------------------------------------------------------------- /reactive-lab-dashboard/src/main/webapp/css/simplegrid/percentage_grid.css: -------------------------------------------------------------------------------- 1 | /* Extension of SimpleGrid by benjchristensen to allow percentage based sizing on very large displays 2 | * 3 | * SimpleGrid - a fork of CSSGrid by Crowd Favorite (https://github.com/crowdfavorite/css-grid) 4 | * http://simplegrid.info 5 | * by Conor Muirhead (http://conor.cc) of Early LLC (http://earlymade.com) 6 | * License: http://creativecommons.org/licenses/MIT/ */ 7 | 8 | /* Containers */ 9 | body { font-size: 1.125em; } 10 | .grid{ width:100%; } 11 | 12 | /* Slots Setup */ 13 | .slot-0,.slot-1,.slot-2,.slot-3,.slot-4,.slot-5,.slot-0-1,.slot-0-1-2,.slot-0-1-2-3,.slot-0-1-2-3-4,.slot-0-1-2-3-4-5,.slot-1-2,.slot-1-2-3,.slot-1-2-3-4,.slot-1-2-3-4-5,.slot-2-3,.slot-2-3-4,.slot-2-3-4-5,.slot-3-4,.slot-3-4-5,.slot-4-5,.slot-6,.slot-7,.slot-8,.slot-9,.slot-6-7,.slot-6-7-8,.slot-6-7-8-9,.slot-7-8,.slot-7-8-9,.slot-8-9{ display:inline; float:left; margin-left:0px; } 14 | 15 | 16 | /* 6-Col Grid Sizes */ 17 | .slot-0,.slot-1,.slot-2,.slot-3,.slot-4,.slot-5{ width:16.6%; } /* Sixths */ 18 | .slot-0-1,.slot-1-2,.slot-2-3,.slot-3-4,.slot-4-5{ width:33.3%; } /* Thirds */ 19 | .slot-0-1-2-3,.slot-1-2-3-4,.slot-2-3-4-5{ width:66.6%; } /* Two-Thirds */ 20 | .slot-0-1-2-3-4,.slot-1-2-3-4-5{ width:83.3%; } /* Five-Sixths */ 21 | 22 | /* 4-Col Grid Sizes */ 23 | .slot-6,.slot-7,.slot-8,.slot-9{ width:25%; } /* Quarters */ 24 | .slot-6-7-8,.slot-7-8-9{ width:75%; } /* Three-Quarters */ 25 | 26 | /* 6-Col/4-Col Shared Grid Sizes */ 27 | .slot-0-1-2,.slot-1-2-3,.slot-2-3-4,.slot-3-4-5, .slot-6-7,.slot-7-8,.slot-8-9{ width:50%; } /* Halves */ -------------------------------------------------------------------------------- /reactive-lab-dashboard/src/main/webapp/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hystrix Dashboard 6 | 7 | 8 | 9 | 10 | 21 | 22 | 23 |
24 | 25 |
26 |

ReactiveLab Hystrix Dashboard

27 | 28 |
29 | 30 |

31 |
32 | 33 |
34 |
35 | 36 | -------------------------------------------------------------------------------- /reactive-lab-dashboard/src/main/webapp/js/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012, Michael Bostock 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * The name Michael Bostock may not be used to endorse or promote products 15 | derived from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT, 21 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 22 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 24 | OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 26 | EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /reactive-lab-dashboard/src/main/webapp/js/jquery.tinysort.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery TinySort - A plugin to sort child nodes by (sub) contents or attributes. 3 | * 4 | * Version: 1.0.5 5 | * 6 | * Copyright (c) 2008-2011 Ron Valstar http://www.sjeiti.com/ 7 | * 8 | * Dual licensed under the MIT and GPL licenses: 9 | * http://www.opensource.org/licenses/mit-license.php 10 | * http://www.gnu.org/licenses/gpl.html 11 | */ 12 | (function(b){b.tinysort={id:"TinySort",version:"1.0.5",copyright:"Copyright (c) 2008-2011 Ron Valstar",uri:"http://tinysort.sjeiti.com/",defaults:{order:"asc",attr:"",place:"start",returns:false,useVal:false}};b.fn.extend({tinysort:function(h,j){if(h&&typeof(h)!="string"){j=h;h=null}var e=b.extend({},b.tinysort.defaults,j);var p={};this.each(function(t){var v=(!h||h=="")?b(this):b(this).find(h);var u=e.order=="rand"?""+Math.random():(e.attr==""?(e.useVal?v.val():v.text()):v.attr(e.attr));var s=b(this).parent();if(!p[s]){p[s]={s:[],n:[]}}if(v.length>0){p[s].s.push({s:u,e:b(this),n:t})}else{p[s].n.push({e:b(this),n:t})}});for(var g in p){var d=p[g];d.s.sort(function k(t,s){var i=t.s.toLowerCase?t.s.toLowerCase():t.s;var u=s.s.toLowerCase?s.s.toLowerCase():s.s;if(c(t.s)&&c(s.s)){i=parseFloat(t.s);u=parseFloat(s.s)}return(e.order=="asc"?1:-1)*(iu?1:0))})}var m=[];for(var g in p){var d=p[g];var n=[];var f=b(this).length;switch(e.place){case"first":b.each(d.s,function(s,t){f=Math.min(f,t.n)});break;case"org":b.each(d.s,function(s,t){n.push(t.n)});break;case"end":f=d.n.length;break;default:f=0}var q=[0,0];for(var l=0;l=f&&l0?d[1]:false}function a(e,f){var d=false;b.each(e,function(h,g){if(!d){d=g==f}});return d}b.fn.TinySort=b.fn.Tinysort=b.fn.tsort=b.fn.tinysort})(jQuery); -------------------------------------------------------------------------------- /reactive-lab-dashboard/src/main/webapp/js/tmpl.js: -------------------------------------------------------------------------------- 1 | 2 | //Simple JavaScript Templating 3 | //John Resig - http://ejohn.org/ - MIT Licensed 4 | // http://ejohn.org/blog/javascript-micro-templating/ 5 | (function(window, undefined) { 6 | var cache = {}; 7 | 8 | window.tmpl = function tmpl(str, data) { 9 | try { 10 | // Figure out if we're getting a template, or if we need to 11 | // load the template - and be sure to cache the result. 12 | var fn = !/\W/.test(str) ? 13 | cache[str] = cache[str] || 14 | tmpl(document.getElementById(str).innerHTML) : 15 | 16 | // Generate a reusable function that will serve as a template 17 | // generator (and which will be cached). 18 | new Function("obj", 19 | "var p=[],print=function(){p.push.apply(p,arguments);};" + 20 | 21 | // Introduce the data as local variables using with(){} 22 | "with(obj){p.push('" + 23 | 24 | // Convert the template into pure JavaScript 25 | str 26 | .replace(/[\r\t\n]/g, " ") 27 | .split("<%").join("\t") 28 | .replace(/((^|%>)[^\t]*)'/g, "$1\r") 29 | .replace(/\t=(.*?)%>/g, "',$1,'") 30 | .split("\t").join("');") 31 | .split("%>").join("p.push('") 32 | .split("\r").join("\\'") 33 | + "');}return p.join('');"); 34 | 35 | //console.log(fn); 36 | 37 | // Provide some basic currying to the user 38 | return data ? fn(data) : fn; 39 | }catch(e) { 40 | console.log(e); 41 | } 42 | }; 43 | })(window); 44 | -------------------------------------------------------------------------------- /reactive-lab-dashboard/src/main/webapp/monitor/monitor.css: -------------------------------------------------------------------------------- 1 | .container { 2 | padding-left: 20px; 3 | padding-right: 20px; 4 | } 5 | 6 | .row { 7 | width: 100%; 8 | margin: 0 auto; 9 | overflow: hidden; 10 | } 11 | 12 | .spacer { 13 | width: 100%; 14 | margin: 0 auto; 15 | padding-top:4px; 16 | clear:both; 17 | } 18 | 19 | 20 | .last { 21 | margin-right: 0px; 22 | } 23 | 24 | .menubar { 25 | overflow: hidden; 26 | border-bottom: 1px solid black; 27 | } 28 | 29 | .menubar div { 30 | padding-bottom:5px; 31 | 32 | margin: 0 auto; 33 | overflow: hidden; 34 | 35 | font-size: 80%; 36 | font-family:'Bookman Old Style',Bookman,'URW Bookman L','Palatino Linotype',serif; 37 | 38 | float:left; 39 | } 40 | 41 | .menubar .title { 42 | float: left; 43 | padding-right: 20px; 44 | 45 | font-size: 110%; 46 | font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; 47 | font-weight: bold; 48 | 49 | vertical-align: bottom; 50 | } 51 | 52 | .menubar .menu_actions { 53 | float: left; 54 | position:relative; 55 | top: 4px; 56 | } 57 | 58 | .menubar .menu_legend { 59 | float: right; 60 | position:relative; 61 | top: 4px; 62 | 63 | } 64 | 65 | h3.sectionHeader { 66 | color: black; 67 | font-size: 110%; 68 | padding-top: 4px; 69 | padding-bottom: 4px; 70 | padding-left: 8px; 71 | font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; 72 | background: lightgrey; 73 | } 74 | 75 | .success { 76 | color: green; 77 | } 78 | .shortCircuited { 79 | color: blue; 80 | } 81 | .timeout { 82 | color: #FF9900; /* shade of orange */ 83 | } 84 | .failure { 85 | color: red; 86 | } 87 | 88 | .rejected { 89 | color: purple; 90 | } 91 | 92 | .exceptionsThrown { 93 | color: brown; 94 | } 95 | 96 | @media screen and (max-width: 1100px) { 97 | .container { 98 | padding-left: 5px; 99 | padding-right: 5px; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /reactive-lab-gateway/build.gradle: -------------------------------------------------------------------------------- 1 | sourceCompatibility = JavaVersion.VERSION_1_8 2 | targetCompatibility = JavaVersion.VERSION_1_8 3 | 4 | dependencies { 5 | compile 'io.reactivex:rxnetty:0.4.8' 6 | compile 'io.reactivex:rxjava:1.0.10' 7 | compile "com.netflix.eureka2:eureka-client:2.0.0-DP2" 8 | compile "com.netflix.ocelli:ocelli-rxnetty:0.0.5" 9 | compile "com.netflix.ocelli:ocelli-eureka:0.0.5" 10 | compile 'com.netflix.hystrix:hystrix-core:1.4.6' 11 | compile 'org.codehaus.jackson:jackson-core-asl:1.9.13' 12 | compile 'org.codehaus.jackson:jackson-mapper-asl:1.9.13' 13 | testCompile 'junit:junit-dep:4.10' 14 | testCompile 'org.mockito:mockito-core:1.8.5' 15 | } 16 | 17 | task startGateway(type:JavaExec) { 18 | main = "io.reactivex.lab.gateway.StartGatewayServer" 19 | classpath = sourceSets.main.runtimeClasspath 20 | } 21 | -------------------------------------------------------------------------------- /reactive-lab-gateway/src/main/java/io/reactivex/lab/gateway/StartGatewayServer.java: -------------------------------------------------------------------------------- 1 | package io.reactivex.lab.gateway; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.handler.codec.http.HttpResponseStatus; 5 | import io.reactivex.lab.gateway.hystrix.HystrixMetricsStreamHandler; 6 | import io.reactivex.lab.gateway.loadbalancer.DiscoveryAndLoadBalancer; 7 | import io.reactivex.lab.gateway.routes.RouteForDeviceHome; 8 | import io.reactivex.lab.gateway.routes.mock.TestRouteBasic; 9 | import io.reactivex.lab.gateway.routes.mock.TestRouteWithHystrix; 10 | import io.reactivex.lab.gateway.routes.mock.TestRouteWithSimpleFaultTolerance; 11 | import io.reactivex.netty.RxNetty; 12 | import io.reactivex.netty.protocol.http.server.HttpServerRequest; 13 | import io.reactivex.netty.protocol.http.server.HttpServerResponse; 14 | import rx.Observable; 15 | 16 | import com.netflix.hystrix.HystrixRequestLog; 17 | import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext; 18 | 19 | public class StartGatewayServer { 20 | 21 | static { 22 | // discovery config 23 | System.setProperty("reactivelab.eureka.server.host", "127.0.0.1"); 24 | System.setProperty("reactivelab.eureka.server.read.port", "7001"); 25 | System.setProperty("reactivelab.eureka.server.write.port", "7002"); 26 | } 27 | 28 | public static void main(String... args) { 29 | // initialize 30 | DiscoveryAndLoadBalancer.getFactory(); 31 | // hystrix stream => http://localhost:9999 32 | startHystrixMetricsStream(); 33 | 34 | System.out.println("Server => Starting at http://localhost:8080/"); 35 | System.out.println(" Sample URLs: "); 36 | System.out.println(" - http://localhost:8080/device/home?userId=123"); 37 | System.out.println("----------------------------------------------------------------"); 38 | 39 | // start web services => http://localhost:8080 40 | RxNetty.createHttpServer(8080, (request, response) -> { 41 | if (request.getPath().contains("favicon.ico")) { 42 | return Observable.empty(); 43 | } 44 | // System.out.println("Server => Request: " + request.getPath()); 45 | return Observable.defer(() -> { 46 | HystrixRequestContext.initializeContext(); 47 | try { 48 | return handleRoutes(request, response); 49 | } catch (Throwable e) { 50 | System.err.println("Server => Error [" + request.getPath() + "] => " + e); 51 | response.setStatus(HttpResponseStatus.BAD_REQUEST); 52 | return response.writeStringAndFlush("Error 500: Bad Request\n" + e.getMessage() + "\n"); 53 | } 54 | }).onErrorResumeNext(error -> { 55 | System.err.println("Server => Error: " + error.getMessage()); 56 | error.printStackTrace(); 57 | return writeError(request, response, "Failed: " + error.getMessage()); 58 | }).doOnTerminate(() -> { 59 | if (HystrixRequestContext.isCurrentThreadInitialized()) { 60 | System.out.println("Server => Request [" + request.getPath() + "] => " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString()); 61 | HystrixRequestContext.getContextForCurrentThread().shutdown(); 62 | } else { 63 | System.err.println("HystrixRequestContext not initialized for thread: " + Thread.currentThread()); 64 | } 65 | response.close(); 66 | }); 67 | }).startAndWait(); 68 | } 69 | 70 | /** 71 | * Hard-coded route handling. 72 | */ 73 | private static Observable handleRoutes(HttpServerRequest request, HttpServerResponse response) { 74 | if (request.getPath().equals("/device/home")) { 75 | return new RouteForDeviceHome().handle(request, response); 76 | } else if (request.getPath().equals("/testBasic")) { 77 | return TestRouteBasic.handle(request, response); 78 | } else if (request.getPath().equals("/testWithSimpleFaultTolerance")) { 79 | return TestRouteWithSimpleFaultTolerance.handle(request, response); 80 | } else if (request.getPath().equals("/testWithHystrix")) { 81 | return TestRouteWithHystrix.handle(request, response); 82 | } else { 83 | return writeError(request, response, "Unknown path: " + request.getPath()); 84 | } 85 | } 86 | 87 | private static void startHystrixMetricsStream() { 88 | HystrixMetricsStreamHandler hystrixMetricsStreamHandler = new HystrixMetricsStreamHandler<>("/", 1000); 89 | RxNetty.createHttpServer(9999, (request, response) -> { 90 | System.out.println("Server => Start Hystrix Stream at http://localhost:9999"); 91 | return hystrixMetricsStreamHandler.handle(request, response); 92 | }).start(); 93 | } 94 | 95 | public static Observable writeError(HttpServerRequest request, HttpServerResponse response, String message) { 96 | System.err.println("Server => Error [" + request.getPath() + "] => " + message); 97 | response.setStatus(HttpResponseStatus.BAD_REQUEST); 98 | return response.writeStringAndFlush("Error 500: " + message); 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /reactive-lab-gateway/src/main/java/io/reactivex/lab/gateway/clients/BookmarkCommand.java: -------------------------------------------------------------------------------- 1 | package io.reactivex.lab.gateway.clients; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.reactivex.lab.gateway.clients.BookmarksCommand.Bookmark; 5 | import io.reactivex.lab.gateway.clients.PersonalizedCatalogCommand.Video; 6 | import io.reactivex.lab.gateway.loadbalancer.DiscoveryAndLoadBalancer; 7 | import io.reactivex.netty.protocol.http.sse.ServerSentEvent; 8 | 9 | import java.util.ArrayList; 10 | import java.util.Collection; 11 | import java.util.List; 12 | 13 | import netflix.ocelli.LoadBalancer; 14 | import netflix.ocelli.rxnetty.HttpClientHolder; 15 | import rx.functions.Func1; 16 | 17 | import com.netflix.hystrix.HystrixCollapser.CollapsedRequest; 18 | import com.netflix.hystrix.HystrixObservableCollapser; 19 | import com.netflix.hystrix.HystrixObservableCommand; 20 | 21 | public class BookmarkCommand extends HystrixObservableCollapser { 22 | 23 | private final Video video; 24 | private static final LoadBalancer> loadBalancer = 25 | DiscoveryAndLoadBalancer.getFactory().forVip("reactive-lab-bookmark-service"); 26 | 27 | public BookmarkCommand(Video video) { 28 | this.video = video; 29 | } 30 | 31 | @Override 32 | public Video getRequestArgument() { 33 | return video; 34 | } 35 | 36 | @Override 37 | protected HystrixObservableCommand createCommand(Collection> requests) { 38 | List