├── .gitignore ├── LICENSE ├── README.md ├── README.zh_cn.md ├── config-osm.cmd ├── config-osm.sh ├── known-issues.md ├── latency-collect.py ├── latency-plot.ipynb ├── latency-predict.py ├── obs-simulation.ipynb ├── qt-predict.py ├── qt-simulation-mmk.py ├── run-minio.cmd ├── run-minio.sh ├── run-mock-s3.cmd ├── run-mock-s3.sh ├── run-s3bench.cmd ├── run-s3bench.sh ├── run-s3proxy.sh ├── run-zfile.cmd └── workload-example.xml /.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | *.csv 3 | minio 4 | osm 5 | s3bench 6 | osm.conf 7 | config.json 8 | certs/ 9 | root/ 10 | .vscode/ 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Attribution-ShareAlike 4.0 International 2 | 3 | ======================================================================= 4 | 5 | Creative Commons Corporation ("Creative Commons") is not a law firm and 6 | does not provide legal services or legal advice. Distribution of 7 | Creative Commons public licenses does not create a lawyer-client or 8 | other relationship. Creative Commons makes its licenses and related 9 | information available on an "as-is" basis. Creative Commons gives no 10 | warranties regarding its licenses, any material licensed under their 11 | terms and conditions, or any related information. Creative Commons 12 | disclaims all liability for damages resulting from their use to the 13 | fullest extent possible. 14 | 15 | Using Creative Commons Public Licenses 16 | 17 | Creative Commons public licenses provide a standard set of terms and 18 | conditions that creators and other rights holders may use to share 19 | original works of authorship and other material subject to copyright 20 | and certain other rights specified in the public license below. The 21 | following considerations are for informational purposes only, are not 22 | exhaustive, and do not form part of our licenses. 23 | 24 | Considerations for licensors: Our public licenses are 25 | intended for use by those authorized to give the public 26 | permission to use material in ways otherwise restricted by 27 | copyright and certain other rights. Our licenses are 28 | irrevocable. Licensors should read and understand the terms 29 | and conditions of the license they choose before applying it. 30 | Licensors should also secure all rights necessary before 31 | applying our licenses so that the public can reuse the 32 | material as expected. Licensors should clearly mark any 33 | material not subject to the license. This includes other CC- 34 | licensed material, or material used under an exception or 35 | limitation to copyright. More considerations for licensors: 36 | wiki.creativecommons.org/Considerations_for_licensors 37 | 38 | Considerations for the public: By using one of our public 39 | licenses, a licensor grants the public permission to use the 40 | licensed material under specified terms and conditions. If 41 | the licensor's permission is not necessary for any reason--for 42 | example, because of any applicable exception or limitation to 43 | copyright--then that use is not regulated by the license. Our 44 | licenses grant only permissions under copyright and certain 45 | other rights that a licensor has authority to grant. Use of 46 | the licensed material may still be restricted for other 47 | reasons, including because others have copyright or other 48 | rights in the material. A licensor may make special requests, 49 | such as asking that all changes be marked or described. 50 | Although not required by our licenses, you are encouraged to 51 | respect those requests where reasonable. More_considerations 52 | for the public: 53 | wiki.creativecommons.org/Considerations_for_licensees 54 | 55 | ======================================================================= 56 | 57 | Creative Commons Attribution-ShareAlike 4.0 International Public 58 | License 59 | 60 | By exercising the Licensed Rights (defined below), You accept and agree 61 | to be bound by the terms and conditions of this Creative Commons 62 | Attribution-ShareAlike 4.0 International Public License ("Public 63 | License"). To the extent this Public License may be interpreted as a 64 | contract, You are granted the Licensed Rights in consideration of Your 65 | acceptance of these terms and conditions, and the Licensor grants You 66 | such rights in consideration of benefits the Licensor receives from 67 | making the Licensed Material available under these terms and 68 | conditions. 69 | 70 | 71 | Section 1 -- Definitions. 72 | 73 | a. Adapted Material means material subject to Copyright and Similar 74 | Rights that is derived from or based upon the Licensed Material 75 | and in which the Licensed Material is translated, altered, 76 | arranged, transformed, or otherwise modified in a manner requiring 77 | permission under the Copyright and Similar Rights held by the 78 | Licensor. For purposes of this Public License, where the Licensed 79 | Material is a musical work, performance, or sound recording, 80 | Adapted Material is always produced where the Licensed Material is 81 | synched in timed relation with a moving image. 82 | 83 | b. Adapter's License means the license You apply to Your Copyright 84 | and Similar Rights in Your contributions to Adapted Material in 85 | accordance with the terms and conditions of this Public License. 86 | 87 | c. BY-SA Compatible License means a license listed at 88 | creativecommons.org/compatiblelicenses, approved by Creative 89 | Commons as essentially the equivalent of this Public License. 90 | 91 | d. Copyright and Similar Rights means copyright and/or similar rights 92 | closely related to copyright including, without limitation, 93 | performance, broadcast, sound recording, and Sui Generis Database 94 | Rights, without regard to how the rights are labeled or 95 | categorized. For purposes of this Public License, the rights 96 | specified in Section 2(b)(1)-(2) are not Copyright and Similar 97 | Rights. 98 | 99 | e. Effective Technological Measures means those measures that, in the 100 | absence of proper authority, may not be circumvented under laws 101 | fulfilling obligations under Article 11 of the WIPO Copyright 102 | Treaty adopted on December 20, 1996, and/or similar international 103 | agreements. 104 | 105 | f. Exceptions and Limitations means fair use, fair dealing, and/or 106 | any other exception or limitation to Copyright and Similar Rights 107 | that applies to Your use of the Licensed Material. 108 | 109 | g. License Elements means the license attributes listed in the name 110 | of a Creative Commons Public License. The License Elements of this 111 | Public License are Attribution and ShareAlike. 112 | 113 | h. Licensed Material means the artistic or literary work, database, 114 | or other material to which the Licensor applied this Public 115 | License. 116 | 117 | i. Licensed Rights means the rights granted to You subject to the 118 | terms and conditions of this Public License, which are limited to 119 | all Copyright and Similar Rights that apply to Your use of the 120 | Licensed Material and that the Licensor has authority to license. 121 | 122 | j. Licensor means the individual(s) or entity(ies) granting rights 123 | under this Public License. 124 | 125 | k. Share means to provide material to the public by any means or 126 | process that requires permission under the Licensed Rights, such 127 | as reproduction, public display, public performance, distribution, 128 | dissemination, communication, or importation, and to make material 129 | available to the public including in ways that members of the 130 | public may access the material from a place and at a time 131 | individually chosen by them. 132 | 133 | l. Sui Generis Database Rights means rights other than copyright 134 | resulting from Directive 96/9/EC of the European Parliament and of 135 | the Council of 11 March 1996 on the legal protection of databases, 136 | as amended and/or succeeded, as well as other essentially 137 | equivalent rights anywhere in the world. 138 | 139 | m. You means the individual or entity exercising the Licensed Rights 140 | under this Public License. Your has a corresponding meaning. 141 | 142 | 143 | Section 2 -- Scope. 144 | 145 | a. License grant. 146 | 147 | 1. Subject to the terms and conditions of this Public License, 148 | the Licensor hereby grants You a worldwide, royalty-free, 149 | non-sublicensable, non-exclusive, irrevocable license to 150 | exercise the Licensed Rights in the Licensed Material to: 151 | 152 | a. reproduce and Share the Licensed Material, in whole or 153 | in part; and 154 | 155 | b. produce, reproduce, and Share Adapted Material. 156 | 157 | 2. Exceptions and Limitations. For the avoidance of doubt, where 158 | Exceptions and Limitations apply to Your use, this Public 159 | License does not apply, and You do not need to comply with 160 | its terms and conditions. 161 | 162 | 3. Term. The term of this Public License is specified in Section 163 | 6(a). 164 | 165 | 4. Media and formats; technical modifications allowed. The 166 | Licensor authorizes You to exercise the Licensed Rights in 167 | all media and formats whether now known or hereafter created, 168 | and to make technical modifications necessary to do so. The 169 | Licensor waives and/or agrees not to assert any right or 170 | authority to forbid You from making technical modifications 171 | necessary to exercise the Licensed Rights, including 172 | technical modifications necessary to circumvent Effective 173 | Technological Measures. For purposes of this Public License, 174 | simply making modifications authorized by this Section 2(a) 175 | (4) never produces Adapted Material. 176 | 177 | 5. Downstream recipients. 178 | 179 | a. Offer from the Licensor -- Licensed Material. Every 180 | recipient of the Licensed Material automatically 181 | receives an offer from the Licensor to exercise the 182 | Licensed Rights under the terms and conditions of this 183 | Public License. 184 | 185 | b. Additional offer from the Licensor -- Adapted Material. 186 | Every recipient of Adapted Material from You 187 | automatically receives an offer from the Licensor to 188 | exercise the Licensed Rights in the Adapted Material 189 | under the conditions of the Adapter's License You apply. 190 | 191 | c. No downstream restrictions. You may not offer or impose 192 | any additional or different terms or conditions on, or 193 | apply any Effective Technological Measures to, the 194 | Licensed Material if doing so restricts exercise of the 195 | Licensed Rights by any recipient of the Licensed 196 | Material. 197 | 198 | 6. No endorsement. Nothing in this Public License constitutes or 199 | may be construed as permission to assert or imply that You 200 | are, or that Your use of the Licensed Material is, connected 201 | with, or sponsored, endorsed, or granted official status by, 202 | the Licensor or others designated to receive attribution as 203 | provided in Section 3(a)(1)(A)(i). 204 | 205 | b. Other rights. 206 | 207 | 1. Moral rights, such as the right of integrity, are not 208 | licensed under this Public License, nor are publicity, 209 | privacy, and/or other similar personality rights; however, to 210 | the extent possible, the Licensor waives and/or agrees not to 211 | assert any such rights held by the Licensor to the limited 212 | extent necessary to allow You to exercise the Licensed 213 | Rights, but not otherwise. 214 | 215 | 2. Patent and trademark rights are not licensed under this 216 | Public License. 217 | 218 | 3. To the extent possible, the Licensor waives any right to 219 | collect royalties from You for the exercise of the Licensed 220 | Rights, whether directly or through a collecting society 221 | under any voluntary or waivable statutory or compulsory 222 | licensing scheme. In all other cases the Licensor expressly 223 | reserves any right to collect such royalties. 224 | 225 | 226 | Section 3 -- License Conditions. 227 | 228 | Your exercise of the Licensed Rights is expressly made subject to the 229 | following conditions. 230 | 231 | a. Attribution. 232 | 233 | 1. If You Share the Licensed Material (including in modified 234 | form), You must: 235 | 236 | a. retain the following if it is supplied by the Licensor 237 | with the Licensed Material: 238 | 239 | i. identification of the creator(s) of the Licensed 240 | Material and any others designated to receive 241 | attribution, in any reasonable manner requested by 242 | the Licensor (including by pseudonym if 243 | designated); 244 | 245 | ii. a copyright notice; 246 | 247 | iii. a notice that refers to this Public License; 248 | 249 | iv. a notice that refers to the disclaimer of 250 | warranties; 251 | 252 | v. a URI or hyperlink to the Licensed Material to the 253 | extent reasonably practicable; 254 | 255 | b. indicate if You modified the Licensed Material and 256 | retain an indication of any previous modifications; and 257 | 258 | c. indicate the Licensed Material is licensed under this 259 | Public License, and include the text of, or the URI or 260 | hyperlink to, this Public License. 261 | 262 | 2. You may satisfy the conditions in Section 3(a)(1) in any 263 | reasonable manner based on the medium, means, and context in 264 | which You Share the Licensed Material. For example, it may be 265 | reasonable to satisfy the conditions by providing a URI or 266 | hyperlink to a resource that includes the required 267 | information. 268 | 269 | 3. If requested by the Licensor, You must remove any of the 270 | information required by Section 3(a)(1)(A) to the extent 271 | reasonably practicable. 272 | 273 | b. ShareAlike. 274 | 275 | In addition to the conditions in Section 3(a), if You Share 276 | Adapted Material You produce, the following conditions also apply. 277 | 278 | 1. The Adapter's License You apply must be a Creative Commons 279 | license with the same License Elements, this version or 280 | later, or a BY-SA Compatible License. 281 | 282 | 2. You must include the text of, or the URI or hyperlink to, the 283 | Adapter's License You apply. You may satisfy this condition 284 | in any reasonable manner based on the medium, means, and 285 | context in which You Share Adapted Material. 286 | 287 | 3. You may not offer or impose any additional or different terms 288 | or conditions on, or apply any Effective Technological 289 | Measures to, Adapted Material that restrict exercise of the 290 | rights granted under the Adapter's License You apply. 291 | 292 | 293 | Section 4 -- Sui Generis Database Rights. 294 | 295 | Where the Licensed Rights include Sui Generis Database Rights that 296 | apply to Your use of the Licensed Material: 297 | 298 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right 299 | to extract, reuse, reproduce, and Share all or a substantial 300 | portion of the contents of the database; 301 | 302 | b. if You include all or a substantial portion of the database 303 | contents in a database in which You have Sui Generis Database 304 | Rights, then the database in which You have Sui Generis Database 305 | Rights (but not its individual contents) is Adapted Material, 306 | 307 | including for purposes of Section 3(b); and 308 | c. You must comply with the conditions in Section 3(a) if You Share 309 | all or a substantial portion of the contents of the database. 310 | 311 | For the avoidance of doubt, this Section 4 supplements and does not 312 | replace Your obligations under this Public License where the Licensed 313 | Rights include other Copyright and Similar Rights. 314 | 315 | 316 | Section 5 -- Disclaimer of Warranties and Limitation of Liability. 317 | 318 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE 319 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS 320 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF 321 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, 322 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, 323 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR 324 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, 325 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT 326 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT 327 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. 328 | 329 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE 330 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, 331 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, 332 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, 333 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR 334 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN 335 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR 336 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR 337 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. 338 | 339 | c. The disclaimer of warranties and limitation of liability provided 340 | above shall be interpreted in a manner that, to the extent 341 | possible, most closely approximates an absolute disclaimer and 342 | waiver of all liability. 343 | 344 | 345 | Section 6 -- Term and Termination. 346 | 347 | a. This Public License applies for the term of the Copyright and 348 | Similar Rights licensed here. However, if You fail to comply with 349 | this Public License, then Your rights under this Public License 350 | terminate automatically. 351 | 352 | b. Where Your right to use the Licensed Material has terminated under 353 | Section 6(a), it reinstates: 354 | 355 | 1. automatically as of the date the violation is cured, provided 356 | it is cured within 30 days of Your discovery of the 357 | violation; or 358 | 359 | 2. upon express reinstatement by the Licensor. 360 | 361 | For the avoidance of doubt, this Section 6(b) does not affect any 362 | right the Licensor may have to seek remedies for Your violations 363 | of this Public License. 364 | 365 | c. For the avoidance of doubt, the Licensor may also offer the 366 | Licensed Material under separate terms or conditions or stop 367 | distributing the Licensed Material at any time; however, doing so 368 | will not terminate this Public License. 369 | 370 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public 371 | License. 372 | 373 | 374 | Section 7 -- Other Terms and Conditions. 375 | 376 | a. The Licensor shall not be bound by any additional or different 377 | terms or conditions communicated by You unless expressly agreed. 378 | 379 | b. Any arrangements, understandings, or agreements regarding the 380 | Licensed Material not stated herein are separate from and 381 | independent of the terms and conditions of this Public License. 382 | 383 | 384 | Section 8 -- Interpretation. 385 | 386 | a. For the avoidance of doubt, this Public License does not, and 387 | shall not be interpreted to, reduce, limit, restrict, or impose 388 | conditions on any use of the Licensed Material that could lawfully 389 | be made without permission under this Public License. 390 | 391 | b. To the extent possible, if any provision of this Public License is 392 | deemed unenforceable, it shall be automatically reformed to the 393 | minimum extent necessary to make it enforceable. If the provision 394 | cannot be reformed, it shall be severed from this Public License 395 | without affecting the enforceability of the remaining terms and 396 | conditions. 397 | 398 | c. No term or condition of this Public License will be waived and no 399 | failure to comply consented to unless expressly agreed to by the 400 | Licensor. 401 | 402 | d. Nothing in this Public License constitutes or may be interpreted 403 | as a limitation upon, or waiver of, any privileges and immunities 404 | that apply to the Licensor or You, including from the legal 405 | processes of any jurisdiction or authority. 406 | 407 | 408 | ======================================================================= 409 | 410 | Creative Commons is not a party to its public 411 | licenses. Notwithstanding, Creative Commons may elect to apply one of 412 | its public licenses to material it publishes and in those instances 413 | will be considered the “Licensor.” The text of the Creative Commons 414 | public licenses is dedicated to the public domain under the CC0 Public 415 | Domain Dedication. Except for the limited purpose of indicating that 416 | material is shared under a Creative Commons public license or as 417 | otherwise permitted by the Creative Commons policies published at 418 | creativecommons.org/policies, Creative Commons does not authorize the 419 | use of the trademark "Creative Commons" or any other trademark or logo 420 | of Creative Commons without its prior written consent including, 421 | without limitation, in connection with any unauthorized modifications 422 | to any of its public licenses or any other arrangements, 423 | understandings, or agreements concerning use of licensed material. For 424 | the avoidance of doubt, this paragraph does not form part of the 425 | public licenses. 426 | 427 | Creative Commons may be contacted at creativecommons.org. 428 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | Object Storage Tutorial. 4 | 5 | ## Basic Concept 6 | 7 | SNIA Tutorials on Object Storage: 8 | 9 | * [Object Storage 101](http://www.snia.org/sites/default/files/Object_Storage_101.pdf) 10 | * [Object Storage 201](https://www.snia.org/sites/default/files/Object_Storage_201_Final_1.pdf) 11 | * [Object Storage Technology](http://www.snia.org/sites/default/education/tutorials/2013/spring/file/BrentWelch_Object_Storage_Technology.pdf). 12 | 13 | The **Storage Networking Industry Association** ([SNIA](https://www.snia.org/)) is a not–for–profit global organization, made up of member companies spanning the global storage market. 14 | 15 | ## Lab1: Preparation 16 | 17 | ### Environment 18 | 19 | #### Git and Repository 20 | 21 | Git tutorial . 22 | 23 | Alternatives: [bitbucket](https://bitbucket.org/), [gitlab](https://about.gitlab.com/), [gitee](https://gitee.com/). 24 | 25 | #### How to establish Python Environment 26 | 27 | * Python Distributions: 28 | * Option 1: [Anaconda](https://www.anaconda.com/) 29 | * Option 2: [WinPython](http://winpython.github.io/) 30 | * Fast deployment by docker: 31 | * Option 3: Python Docker , E.g.: 32 | * `docker pull zhan2016/python-lab:3.6.0` 33 | * `docker login daocloud.io && docker pull daocloud.io/zhan2016/python-lab:master-31a932d` 34 | 35 | #### How to establish Java Environment 36 | 37 | **Ongoing course**: Java Programming, [2019-2020 2nd semester](http://jwc.hust.edu.cn/info/1161/7796.htm), just follow your teacher's guide. 38 | 39 | Installation helper scripts (_**For adventurers**_). 40 | 41 | #### How to establish Rust Environment 42 | 43 | * Installing Rust: 44 | * Learning Rust: 45 | 46 | #### How to use Linux in Windows or MacOS (_**Optional**_) 47 | 48 | **Goal**: try mock-s3 and s3proxy with less trouble 49 | 50 | **Method**: Virtual Machine: Virtualbox, VMWare ... 51 | 52 | Go directly to GUI, or using vagrant,refer to . 53 | 54 | #### How to run servers within docker container (_**Optional**_) 55 | 56 | **Goal**: try Openstack Swift or Ceph with less trouble 57 | 58 | Better run within docker, refer to Docker tutorial . 59 | 60 | * [Ceph docker images](https://hub.docker.com/r/ceph/ceph) 61 | * [Openstack Swift all-in-one docker image](https://hub.docker.com/r/fnndsc/docker-swift-onlyone) 62 | 63 | ### Object Storage Server 64 | 65 | * Object Storage for Beginners: 66 | * Option 1: [Minio](https://minio.io/), latest version . 67 | * Experimental Mock Servers: 68 | * Option 2: [mock-s3](https://github.com/ShiZhan/mock-s3), a Python clone of fake-s3, Amazon S3 mimic. 69 | * Option 2.1: Original version **Requires Python 2.7** . 70 | * Option 3: [s3proxy](https://github.com/gaul/s3proxy), access other storage via the S3 API. **Binary bundles [here](https://github.com/gaul/s3proxy/releases)** 71 | * Option 3.1: Use Java/Maven to build `mvn package -Dmaven.test.skip=true`. 72 | * Option 4: [fake-s3](https://github.com/jubos/fake-s3), a lightweight server clone of Amazon S3. **Depends on Ruby**, the Origin project. 73 | * Option 5: [s3mock](https://github.com/findify/s3mock), S3 mock library for Java/Scala. **Java/SBT Building is required**. 74 | * Option 6: [S3Mock](https://github.com/adobe/S3Mock), S3 mock as Docker image or JUnit rule. **Use Java/Maven to build, use docker to run**, contributed by Adobe (c). 75 | * Industry Level Projects: 76 | * Option 7: [Openstack Swift](https://wiki.openstack.org/wiki/Swift), fast deployment by All-in-one container: . 77 | * Option 8: [Ceph](https://ceph.com/), docker files and images to run Ceph in containers: . 78 | 79 | Besides _Option 1_, _Option 2, 3_ offer compile-free executable. 80 | 81 | ### Object Storage Client 82 | 83 | * Standalone Utilities: 84 | * Option 1: **Minio Client** 85 | * **Installation**: download and run 86 | * Option 2: **s3cmd** 87 | * **Installation**: run `pip install s3cmd` in python environment 88 | * Configure for Minio 89 | * Option 3: **aws-shell** 90 | * **Installation**: run `pip install aws-shell` in python environment 91 | * Configure for Minio 92 | * Official Manual 93 | * Option 4: **osm** 94 | * **Installation**: `go get -u github.com/appscode/osm` 95 | * [Windows binary](https://share.weiyun.com/jPYFmvmw) 96 | * APIs: 97 | * Option 4: **aws-sdk-java** 98 | * Option 5: **boto** 99 | * Option 6: **aws-sdk-rust** 100 | 101 | Binary available for *Option 1*, *Option 2 & 3* require Python, *Option 4* require Go, *Option 6* require Rust. 102 | 103 | ### Basic Functionality 104 | 105 | In computer programming, [create, read, update, and delete (as an acronym CRUD)](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete) are the four basic functions of persistent storage. 106 | 107 | | Operation | SQL | HTTP | 108 | | :--------------: | :----: | :----------------: | 109 | | Create | INSERT | PUT / POST | 110 | | Read (Retrieve) | SELECT | GET | 111 | | Update (Modify) | UPDATE | PUT / POST / PATCH | 112 | | Delete (Destroy) | DELETE | DELETE | 113 | 114 | Try object storage in some applications: [zfile](https://github.com/zhaojun1998/zfile). 115 | 116 | ## Lab2: Performance Evaluation 117 | 118 | ### Object Storage Benchmark 119 | 120 | * Option 1: **S3 Bench** 121 | * **Installation** 122 | 123 | ```bash 124 | go get -u github.com/igneous-systems/s3bench 125 | ``` 126 | 127 | * Linux: default location for built binary is `~/go/bin/s3bench` 128 | * [Prebuilt Windows binary](https://share.weiyun.com/8HdZMhlc), download and put into this directory. 129 | 130 | * Command line example 131 | 132 | ```bash 133 | s3bench \ 134 | -accessKey=hust -accessSecret=hust_obs \ 135 | -endpoint=http://127.0.0.1:9000 \ 136 | -bucket=loadgen -objectNamePrefix=loadgen \ 137 | -numClients=10 -numSamples=100 -objectSize=1024 138 | ``` 139 | 140 | * [Script example](./run-s3bench.sh) 141 | 142 | * Customize before using this script, for a broader coverage. 143 | * [Windows version](./run-s3bench.cmd) 144 | 145 | * Option 2: **s3-benchmark** 146 | * **Installation** The original version contains broken dependency, lacks minio support, use one of its fixed fork instead 147 | 148 | ```bash 149 | go get -u github.com/chinglinwen/s3-benchmark 150 | ``` 151 | * Command line example 152 | 153 | ```bash 154 | s3-benchmark \ 155 | -a hust -s hust_obs -u http://127.0.0.1:9000 -b wasabi-benchmark \ 156 | -d 3 -t 1 -z 1K 157 | Wasabi benchmark program v2.0 158 | Parameters: url=http://127.0.0.1:9000, bucket=wasabi-benchmark, region=us-east-1, duration=3, threads=1, loops=1, size=1K 159 | Loop 1: PUT time 3.0 secs, objects = 191, speed = 63.5KB/sec, 63.5 operations/sec. Slowdowns = 0 160 | Loop 1: GET time 0.4 secs, objects = 191, speed = 449.9KB/sec, 449.9 operations/sec. Slowdowns = 0 161 | Loop 1: DELETE time 0.5 secs, 367.2 deletes/sec. Slowdowns = 0 162 | result title: name-concurrency-size, uloadspeed, downloadspeed 163 | result csv: 127-1-1K,0.06,0.44 164 | ``` 165 | 166 | * Option 3: **COSBench** 167 | * User Guide . 168 | * Example workload . 169 | * Other examples . 170 | * Literatures 171 | * COSBench: cloud object storage benchmark 172 | * COSBench: A Benchmark Tool for Cloud Object Storage Services 173 | * COSBench: A benchmark tool for Cloud Storage 174 | 175 | * Option 4: **s3-bench-rs** or . 176 | * User Guide: 177 | * Examples: 178 | 179 | ### Observation 180 | 181 | **Metrics**: *Throughput*, *Latency* under different *object size*, *concurrency*, *server total*. 182 | 183 | **Suggested topics**: 184 | 185 | * How object size affects performance? 186 | * for a particular application, is there a best way to fit into OBS? 187 | * The main factors behind I/O latency? 188 | * Get latency distribution first. 189 | * For evaluating percentile latency, s3bench is recommended. 190 | * What will happen when clients are crowded? 191 | * How concurrency affects latency distribution and throughput? How to enforce SLA by controlling? 192 | * The outcome of scaling out (putting more servers into system)? 193 | 194 | More insights are encouraged. 195 | 196 | ### Further thoughts 197 | 198 | * How to do these experiments on your own codes 199 | * besides COSBench, s3bench, use aws-sdk or boto3 instead. 200 | * Using Python as Lab Platform 201 | * Jupyter Notebook Tutorial 202 | 203 | ## Lab3: Tail Latency Challenge 204 | 205 | ### How to handle? 206 | 207 | * Hedged request 208 | * Tied request 209 | 210 | ### How to predict? 211 | 212 | * queueing Theory 213 | * Sequence prediction 214 | 215 | ## Experiences and Problems 216 | 217 | * [Known issues](known-issues.md). 218 | * Contribute your experiences in . 219 | * Report more problems in . 220 | 221 | ## Future Readings 222 | 223 | * Recent SNIA blog posts on Object Storage . 224 | * Enterprise level [Object Store comparison](http://gaul.org/object-store-comparison/). 225 | * Build your own object storage system with Golang . 226 | * Delimitrou C, Kozyrakis C. Amdahl’s Law for Tail Latency[J]. Commun. ACM, 2018, 61(8): 65–72. 227 | * Dean J, Barroso L A. The Tail at Scale[J]. Commun. ACM, 2013, 56(2): 74–80. 228 | 229 | [Zhan.Shi](https://shizhan.github.io/) @ 2017, 2018, 2019, 2020, 2021 230 | -------------------------------------------------------------------------------- /README.zh_cn.md: -------------------------------------------------------------------------------- 1 | # 介绍 2 | 3 | 对象存储入门实践。 4 | 5 | ## 基本概念 6 | 7 | 网络存储工业协会 SNIA 对象存储入门: 8 | 9 | * [Object Storage 101](http://www.snia.org/sites/default/files/Object_Storage_101.pdf) 10 | * [Object Storage 201](https://www.snia.org/sites/default/files/Object_Storage_201_Final_1.pdf) 11 | * [Object Storage Technology](http://www.snia.org/sites/default/education/tutorials/2013/spring/file/BrentWelch_Object_Storage_Technology.pdf) 12 | 13 | **网络存储工业协会 Storage Networking Industry Association** ([SNIA](https://www.snia.org/)) 是由来自于全球存储市场的众多企业组建的全球性非盈利组织。 14 | 15 | ## 实验一:系统搭建 16 | 17 | ### 基础环境 18 | 19 | #### 代码管理和仓库 20 | 21 | Git tutorial 22 | 23 | 替代方案: [码云](https://gitee.com/), [bitbucket](https://bitbucket.org/), [gitlab](https://about.gitlab.com/) 24 | 25 | #### Python怎么搞定 26 | 27 | * 发行版 28 | * 选项 1: [Anaconda](https://www.anaconda.com/) 29 | * 选项 2: [WinPython](http://winpython.github.io/) 30 | * 用容器快速部署 (_**可选**_) 31 | * 选项 3: Python Docker 32 | * dockerhub (docker大本营) `docker pull zhan2016/python-lab:3.6.0` 33 | * daocloud (国内平台) `docker login daocloud.io && docker pull daocloud.io/zhan2016/python-lab:master-31a932d` 34 | 35 | #### Java怎么搞定 36 | 37 | **同学期课程**: Java语言程序设计, [2019-2020 第2学期](http://jwc.hust.edu.cn/info/1161/7796.htm) 38 | 39 | 一些安装辅助脚本 (_**给喜欢自己琢磨的同学参考**_) 40 | 41 | #### Go怎么搞定 42 | 43 | * 国内资料 44 | * 墙外本体 45 | * 经典书籍 46 | * 学习资料荟萃 47 | 48 | #### Rust怎么搞定 49 | 50 | * 安装Rust: 51 | * 学习Rust: 52 | 53 | #### 怎么在Windows或者MacOS里面跑Linux (_**可选**_) 54 | 55 | **目的**: 计划无伤尝试 mock-s3 或 s3proxy 56 | 57 | **工具**: Virtualbox, VMWare ... 58 | 59 | 直奔图形界面,或者参考 Vagrant tutorial 60 | 61 | #### docker容器怎么搞定 (_**可选**_) 62 | 63 | **目的**: 计划尝试 Openstack Swift 或 Ceph 64 | 65 | 使用容器简化部署,容器使用可参考 Docker tutorial 66 | 67 | * [Ceph官方容器](https://hub.docker.com/r/ceph/ceph) 68 | * [Swift简易容器](https://hub.docker.com/r/fnndsc/docker-swift-onlyone) 69 | 70 | ### 服务端 71 | 72 | * 初学者: 73 | * 选项 1: [Minio](https://minio.io/), 最新版 。 74 | * 实验性模拟服务程序: 75 | * 选项 2: [mock-s3](https://github.com/ShiZhan/mock-s3),用Python重写fake-s3模仿Amazon S3。 76 | * 选项 2.1: 原始版**需要Python 2.7** 。 77 | * 选项 3: [s3proxy](https://github.com/gaul/s3proxy),为各类存储提供S3 API。**预编译包[猛击此处](https://github.com/gaul/s3proxy/releases)** 78 | * 选项 3.1: 自己用Java/Maven从源码构建 `mvn package -Dmaven.test.skip=true`。 79 | * 选项 4: [fake-s3](https://github.com/jubos/fake-s3),Amazon S3轻量级模仿。**需要Ruby**,首个S3克隆项目。 80 | * 选项 5: [s3mock](https://github.com/findify/s3mock),用Java/Scala实现S3模拟。**需要用Java/SBT构建**。 81 | * 选项 6: [S3Mock](https://github.com/adobe/S3Mock),可以放进Docker容器或者JUnit规则的S3模拟。**需要用Java/SBT构建,需要Docker运行**,由Adobe (c)推出。 82 | * 企业级项目: 83 | * 选项 7: [Openstack Swift](https://wiki.openstack.org/wiki/Swift),开箱即用容器版: 。 84 | * 选项 8: [Ceph](https://ceph.com/),开箱即用容器版: 。 85 | 86 | 除初学用 *选项1* 之外,*选项2,3* 也提供免编译执行程序下载。 87 | 88 | ### 客户端 89 | 90 | * 独立工具集: 91 | * 选项 1: **Minio Client** 92 | * 选项 2: **s3cmd** 93 | * 于Python环境中运行 `pip install s3cmd` 94 | * 为 Minio 配置 95 | * 选项 3: **aws-shell** 96 | * 于Python环境中运行 `pip install aws-shell` 97 | * 为 Minio 配置 98 | * 官方手册 99 | * 选项 4: **osm** 100 | * `go get -u github.com/appscode/osm` 101 | * [Windows版执行程序](https://share.weiyun.com/jPYFmvmw) 102 | * 编程 API: 103 | * 选项 4: **aws-sdk-java** 104 | * 选项 5: **boto** 105 | * 选项 6: **aws-sdk-rust** 106 | 107 | *选项 1* 提供可执行文件,开箱即用,*选项 2 & 3* 需要 Python 环境,*选项 4* 需要 go 环境,*选项 6* 需要 Rust 环境。 108 | 109 | ### 基本功能 110 | 111 | 在计算机领域中,[create, read, update, and delete (缩写为 CRUD)](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete) 是访问持久存储的4项基本操作。 112 | 113 | | Operation | SQL | HTTP | 114 | | :--------------: | :----: | :----------------: | 115 | | Create | INSERT | PUT / POST | 116 | | Read (Retrieve) | SELECT | GET | 117 | | Update (Modify) | UPDATE | PUT / POST / PATCH | 118 | | Delete (Destroy) | DELETE | DELETE | 119 | 120 | 在实际应用里面试一试:[zfile](https://github.com/zhaojun1998/zfile)。 121 | 122 | ## 实验二:性能观测 123 | 124 | ### 评测工具 125 | 126 | * 选项 1: **S3 Bench** 127 | * **安装** 128 | 129 | ```bash 130 | go get -u github.com/igneous-systems/s3bench 131 | ``` 132 | 133 | * Linux: 编译文件缺省位置在 `~/go/bin/s3bench` 134 | * [预编译Windows执行程序](https://share.weiyun.com/8HdZMhlc),需下载放置在本资料库所在目录中。 135 | 136 | * 命令行范例 137 | 138 | ```bash 139 | s3bench \ 140 | -accessKey=hust -accessSecret=hust_obs \ 141 | -endpoint=http://127.0.0.1:9000 \ 142 | -bucket=loadgen -objectNamePrefix=loadgen \ 143 | -numClients=10 -numSamples=100 -objectSize=1024 144 | ``` 145 | 146 | * [脚本范例](./run-s3bench.sh) 147 | * 实际使用建议通过定制参数,设计循环结构实现批量测试,将结果重定向进文件用于后期分析。 148 | * [Windows版](./run-s3bench.cmd) 149 | 150 | * 选项 2: **s3-benchmark** 151 | * **安装** 原始版本未更新依赖,且兼容性不足,可以用这个修补版本 152 | 153 | ```bash 154 | go get -u github.com/chinglinwen/s3-benchmark 155 | ``` 156 | 157 | * 命令行范例 158 | 159 | ```bash 160 | s3-benchmark \ 161 | -a hust -s hust_obs -u http://127.0.0.1:9000 -b wasabi-benchmark \ 162 | -d 3 -t 1 -z 1K 163 | Wasabi benchmark program v2.0 164 | Parameters: url=http://127.0.0.1:9000, bucket=wasabi-benchmark, region=us-east-1, duration=3, threads=1, loops=1, size=1K 165 | Loop 1: PUT time 3.0 secs, objects = 191, speed = 63.5KB/sec, 63.5 operations/sec. Slowdowns = 0 166 | Loop 1: GET time 0.4 secs, objects = 191, speed = 449.9KB/sec, 449.9 operations/sec. Slowdowns = 0 167 | Loop 1: DELETE time 0.5 secs, 367.2 deletes/sec. Slowdowns = 0 168 | result title: name-concurrency-size, uloadspeed, downloadspeed 169 | result csv: 127-1-1K,0.06,0.44 170 | ``` 171 | 172 | * 选项 3: **COSBench** 173 | * 指南 174 | * 负载范例 175 | * 其余范例 176 | * 文献 177 | * COSBench: cloud object storage benchmark 178 | * COSBench: A Benchmark Tool for Cloud Object Storage Services 179 | * COSBench: A benchmark tool for Cloud Storage 180 | 181 | * 选项 4: **s3-bench-rs** 或者 182 | * 指南: 183 | * 范例: 184 | 185 | ### 标准测试 186 | 187 | 指标:*吞吐率Throughput*、*延迟Latency*,以及环境参数:*对象尺寸object size*、*并发性*、*服务器数量*。 188 | 189 | 建议思考: 190 | 191 | * 对象尺寸如何影响性能? 192 | * 对于熟悉的某类应用,根据其数据访问特性,怎样适配对象存储最合适? 193 | * I/O 延迟背后的关键影响要素? 194 | * 首先要采集全面的 I/O 延迟观测数据。 195 | * 百分位延迟观测需使用s3bench,然后即可分析尾延迟影响因素。 196 | * 如果客户端爆满将怎样? 197 | * 请求并发数如何同时影响延迟分布和吞吐率?如何保障服务质量? 198 | * 横向扩展系统 (Scaling Out) 效果如何 (向系统中追加更多存储服务器)? 199 | 200 | 不限于上述内容,鼓励更丰富思考。 201 | 202 | ### 更进一步 203 | 204 | * 前述实验如何自己编程实现? 205 | * 不借助于 COSBench、s3bench,使用aws-sdk或boto3。 206 | * 把 Python 当作自己的实验台 207 | * Jupyter Notebook Tutorial 208 | 209 | ## 实验三:尾延迟挑战 210 | 211 | ### 尝试应对 212 | 213 | * 对冲请求 214 | * 关联请求 215 | 216 | ### 怎样预测 217 | 218 | * 排队论 219 | * 序列预测 220 | 221 | ## 已知问题 222 | 223 | * 安装使用过程中的各种["坑"](known-issues.md) 224 | * 经验分享 225 | * 问题报告 226 | 227 | ## 扩展资料 228 | 229 | * 对象存储方面 SNIA 最新博文 230 | * 企业级 [对象存储比较](http://gaul.org/object-store-comparison/) 231 | * [用Go语言自制对象存储系统](https://github.com/stuarthu/go-implement-your-object-storage) 232 | * Delimitrou C, Kozyrakis C. Amdahl’s Law for Tail Latency[J]. Commun. ACM, 2018, 61(8): 65–72. 233 | * Dean J, Barroso L A. The Tail at Scale[J]. Commun. ACM, 2013, 56(2): 74–80. 234 | 235 | [Zhan.Shi](https://shizhan.github.io/) @ 2017, 2018, 2019, 2020, 2021 236 | -------------------------------------------------------------------------------- /config-osm.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | rem Setup alias for using local configuration 3 | set SCRIPT_DIR=%~dp0 4 | @doskey osm=osm --osmconfig %SCRIPT_DIR%osm.conf $* 5 | 6 | rem Create OSM configuration and save to 'osm.conf' 7 | osm --osmconfig %SCRIPT_DIR%osm.conf config set-context osm-minio --provider=s3 --s3.access_key_id=hust --s3.secret_key=hust_obs --s3.endpoint=http://127.0.0.1:9000 8 | osm --osmconfig %SCRIPT_DIR%osm.conf config view 9 | -------------------------------------------------------------------------------- /config-osm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Get script location 3 | called=$_ 4 | if [[ $called == $0 ]]; then 5 | SCRIPT_PATH="$0" 6 | else 7 | SCRIPT_PATH="${BASH_SOURCE[0]}" 8 | fi 9 | SCRIPT_DIR=$( cd "$(dirname $SCRIPT_PATH )" && pwd ) 10 | 11 | # Get OSM location (GOPATH default to ~/go) 12 | osm=~/go/bin/osm 13 | if [ -n "$GOPATH" ]; then 14 | osm=$GOPATH/bin/osm 15 | fi 16 | 17 | # Setting up OSM with local configuration file "osm.conf" 18 | $osm --osmconfig $SCRIPT_DIR/osm.conf \ 19 | config set-context osm-minio \ 20 | --provider=s3 \ 21 | --s3.access_key_id=hust \ 22 | --s3.secret_key=hust_obs \ 23 | --s3.endpoint=http://127.0.0.1:9000 24 | 25 | # Check this configuration 26 | $osm --osmconfig $SCRIPT_DIR/osm.conf \ 27 | config view 28 | 29 | # Prepare alias for simplified command line 30 | alias osm="osm --osmconfig $SCRIPT_DIR/osm.conf" 31 | -------------------------------------------------------------------------------- /known-issues.md: -------------------------------------------------------------------------------- 1 | # Known Issues 2 | 3 | ## Minio 4 | 5 | Refer to official documents, . 6 | 7 | And open issues . 8 | 9 | ## Minio Client 10 | 11 | Accesskey should be 8 or more characters long. 12 | 13 | ## COSBench 14 | 15 | 1. For JDK deployment, portal to [Oracle official sites](http://www.oracle.com/technetwork/cn/java/javase/downloads/index.html). 16 | * Linux: download dpk for Debian/Ubuntu, rpm for CentOS, as for the latter, simply run `rpm -ivh `. 17 | * Windows: download installation package and run. 18 | * Some references 19 | * **JDK 1.8** recommended, although 1.7 may also work. 20 | 21 | 2. From [COSBench release page](https://github.com/intel-cloud/cosbench/releases), choose release candidate (, contains ready to run binary) rather than final release (require manual compilation). 22 | * refer to [open-io's choice](https://github.com/open-io/cosbench/releases). 23 | * download zip file for running directly without compilation. 24 | * If you are familiar with Java, clone the repo and build from the latest source for yourself. 25 | * alternate method of running COSBench, in docker containers . 26 | * use `docker run -it scality/cosbench bash` to enter containerized linux with COSBench ready to run. 27 | 28 | 3. COSBench scripts 29 | * Use `start-all.sh` to run and `stop-all.sh` to stop. 30 | * Use `chmod +x` to set execution property for both scripts. 31 | * Or simply run `source ` or `. `, refer to Linux basics. 32 | * As for Windows, run start-all.bat instead, no `chmod` or `source` is required. 33 | * start-all.sh depends on _**nc**_, if its not available 34 | * Ubuntu: `apt-get install nmap` 35 | * CentOS: `yum install nmap-ncat` 36 | * MacOS uses a different version of nc, and ss is not immediately available, so 37 | * Either comment out the port checking codes in cosbench-start.sh 38 | * Or use Linux VM instead. 39 | 40 | 4. COSBench workload definition 41 | * For benchmarking with Minio server, the accesskey and secretkey **MUST BE** the same with Minio server configuration. 42 | * "cprefix" naming 43 | * Avoid special characters in "cprefix", such as '\_' and '-'. 44 | * Avoid uppercase letters in "cprefix". 45 | * Value settings 46 | * Follow page 36-38 "4. Configuring Workloads" strictly, especially the use of various selectors. 47 | 48 | 5. Understand workload properly 49 | * init: make buckets/containers for benchmarking (both read and write tests). 50 | * prepare: write objects for benchmarking (read test). 51 | * main: default workstage, with work type ommitted, may setup multiple main workstages for various testing. 52 | * `containers=r(1,2);objects=r(1,8);sizes=c(8)KB`: 53 | * 2 containers are created (number from 1 to 2). 54 | * For each container, 8 objects (number from 1 to 8, 8KB each) are written. 55 | * In current COSBench implementation, 1KB=1000Bytes (-\_-\|\|\|). 56 | 57 | 6. Understand result properly 58 | * Succ-Ratio: the ratio of successing requests. 59 | * "Stat: completed": Succ-Ratio >= 80% 60 | * "Stat: Failed": Succ-Ratio < 80% 61 | * For failed requests, check `workload.log` in `cosbench/archive/w[dd]-[workload name]`, usually bad checksum. 62 | 63 | ## s3bench 64 | 65 | 1. Before running, create **loadgen** container first. 66 | * OSM: `osm mc loadgen` 67 | 68 | ## s3proxy 69 | 70 | 1. To work with latest s3cmd and aws-shell, authorization should be disabled: `s3proxy.authorization=none` 71 | * No authorization, not for practical use. 72 | * TODO: make authorization compatible with current s3cmd/aws-shell. 73 | 74 | 2. Windows: incorrect folder access, 'mb' may work, but with wrong properties, which prevent further operations like cp/put 75 | * No 'official' support for Windows, possible cause: unsupported file system metadata operation 76 | * TODO: store metadata separately, like mock-s3. 77 | 78 | ## mock-s3 79 | 80 | 1. Python 2.7 restricted. 81 | * Python 3 migration in progress, maybe BUGGY. 82 | 83 | 2. No authorization, not for practical use. 84 | 85 | 3. Windows: incorrect file IO, although CLI-based GET/PUT may work, COSBench will fail on read. 86 | 87 | ## golang 88 | 89 | 1. 关于 GOPATH,参考 90 | 91 | -------------------------------------------------------------------------------- /latency-collect.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | import time 3 | import random 4 | import matplotlib.pyplot as plt 5 | import numpy as np 6 | 7 | # 配置 MinIO 参数 8 | minio_endpoint = 'http://10.12.56.182:9000' 9 | access_key = 'hust' 10 | secret_key = 'hust_obs' 11 | bucket_name = 'delay-experiment-bucket' 12 | 13 | # 初始化 S3 客户端 14 | s3_client = boto3.client( 15 | 's3', 16 | endpoint_url=minio_endpoint, 17 | aws_access_key_id=access_key, 18 | aws_secret_access_key=secret_key, 19 | region_name='us-east-1' 20 | ) 21 | 22 | # 创建存储桶(如果不存在),并清空已有内容 23 | def create_and_empty_bucket(bucket_name): 24 | try: 25 | s3_client.create_bucket(Bucket=bucket_name) 26 | print(f"Bucket {bucket_name} created.") 27 | except Exception as e: 28 | print(f"Bucket {bucket_name} already exists or error: {e}") 29 | 30 | # 列出并删除所有对象 31 | try: 32 | objects = s3_client.list_objects_v2(Bucket=bucket_name) 33 | if 'Contents' in objects: 34 | for obj in objects['Contents']: 35 | s3_client.delete_object(Bucket=bucket_name, Key=obj['Key']) 36 | print(f"All objects in {bucket_name} deleted.") 37 | else: 38 | print(f"No objects found in bucket {bucket_name}.") 39 | except Exception as e: 40 | print(f"Error deleting objects: {e}") 41 | 42 | 43 | # 生成随机文件内容 44 | def generate_random_data(size=4*1024): # 4KB 45 | return b'0' * size 46 | 47 | # 记录延迟的函数 48 | def measure_latency(func, *args, **kwargs): 49 | start_time = time.time() 50 | result = func(*args, **kwargs) 51 | end_time = time.time() 52 | return end_time - start_time 53 | 54 | # 上传文件并记录延迟(使用顺序编号) 55 | def upload_file(file_size=4*1024, file_index=0): 56 | file_name = f'test_file_{file_index}.txt' # 使用顺序编号 57 | data = generate_random_data(file_size) 58 | try: 59 | s3_client.put_object(Bucket=bucket_name, Key=file_name, Body=data) 60 | return measure_latency(s3_client.put_object, Bucket=bucket_name, Key=file_name, Body=data) 61 | except Exception as e: 62 | print(f"Upload failed: {e}") 63 | return None 64 | 65 | # 下载文件并记录延迟 66 | def download_file(file_name): 67 | try: 68 | response = s3_client.get_object(Bucket=bucket_name, Key=file_name) 69 | content = response['Body'].read() 70 | return measure_latency(s3_client.get_object, Bucket=bucket_name, Key=file_name) 71 | except Exception as e: 72 | print(f"Download failed: {e}") 73 | return None 74 | 75 | # 执行实验 76 | def run_experiment(num_operations=1000): 77 | upload_latencies = [] 78 | download_latencies = [] 79 | 80 | # Step 1: Upload 1000 sequentially named files 81 | for i in range(1, num_operations + 1): 82 | upload_latency = upload_file(file_index=i) 83 | if upload_latency is not None: 84 | upload_latencies.append(upload_latency) 85 | 86 | # Step 2: Randomly download from the 1000 uploaded files 87 | for _ in range(num_operations): 88 | file_index = random.randint(1, num_operations) # 随机选择一个文件编号 89 | file_name = f'test_file_{file_index}.txt' 90 | download_latency = download_file(file_name) 91 | if download_latency is not None: 92 | download_latencies.append(download_latency) 93 | 94 | return upload_latencies, download_latencies 95 | 96 | # 主程序 97 | if __name__ == '__main__': 98 | create_and_empty_bucket(bucket_name) # 清空存储桶 99 | 100 | upload_latencies, download_latencies = run_experiment(num_operations=500) 101 | 102 | # 绘制延迟分布直方图 103 | plt.figure(figsize=(12, 6)) 104 | plt.hist(upload_latencies, bins=20, alpha=0.7, label='Upload Latency') 105 | plt.hist(download_latencies, bins=20, alpha=0.7, label='Download Latency') 106 | plt.title('Object Storage Latency Distribution') 107 | plt.xlabel('Latency (seconds)') 108 | plt.ylabel('Frequency') 109 | plt.legend() 110 | plt.grid(True) 111 | plt.show() 112 | 113 | # 保存下载延迟到 latency.csv 114 | import pandas as pd 115 | pd.DataFrame(download_latencies, columns=["Latency"]).to_csv("latency.csv", index=False) 116 | print("Download latencies saved to latency.csv") 117 | -------------------------------------------------------------------------------- /latency-plot.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# 获取请求延迟分布情况" 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": 13, 13 | "metadata": {}, 14 | "outputs": [ 15 | { 16 | "data": { 17 | "image/png": "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", 18 | "text/plain": [ 19 | "
" 20 | ] 21 | }, 22 | "metadata": {}, 23 | "output_type": "display_data" 24 | } 25 | ], 26 | "source": [ 27 | "import numpy as np\n", 28 | "import pandas as pd\n", 29 | "import matplotlib.pyplot as plt\n", 30 | "from matplotlib.ticker import PercentFormatter\n", 31 | "from matplotlib.pyplot import MultipleLocator, plot\n", 32 | "\n", 33 | "latency = pd.read_csv('latency.csv').apply(pd.to_numeric).values.flatten()\n", 34 | "\n", 35 | "plt.subplot(211)\n", 36 | "plt.plot(latency)\n", 37 | "plt.subplot(212)\n", 38 | "plt.plot(sorted(latency, reverse=True))\n", 39 | "plt.show()" 40 | ] 41 | }, 42 | { 43 | "cell_type": "markdown", 44 | "metadata": {}, 45 | "source": [ 46 | "# 用排队论模型来拟合实测数据" 47 | ] 48 | }, 49 | { 50 | "cell_type": "code", 51 | "execution_count": 16, 52 | "metadata": {}, 53 | "outputs": [ 54 | { 55 | "name": "stdout", 56 | "output_type": "stream", 57 | "text": [ 58 | "Fitted lambda: 4.440892098500626e-16\n" 59 | ] 60 | }, 61 | { 62 | "name": "stderr", 63 | "output_type": "stream", 64 | "text": [ 65 | "C:\\Users\\Administrator\\AppData\\Local\\Temp\\ipykernel_4188\\2287880910.py:8: RuntimeWarning: overflow encountered in exp\n", 66 | " return 1 - np.exp(-λ * t)# 构造 ECDF 数据\n", 67 | "C:\\Users\\Administrator\\AppData\\Local\\Temp\\ipykernel_4188\\2287880910.py:18: OptimizeWarning: Covariance of the parameters could not be estimated\n", 68 | " popt, pcov = curve_fit(queue_model, latency_sorted, ecdf_values)\n" 69 | ] 70 | }, 71 | { 72 | "data": { 73 | "image/png": "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QVRUFGJjY+WjL7GxsbCyssLEiRMBAHZ2dnjppZfw+uuvw9HREQ4ODliwYAF69+6NIUOGyOMOHjwYYWFhmD17NgBg/vz5mDJlCvr37w8/Pz98+OGHuHTpksapsHppaWn4+eef8dFHHwEAnnjiCZw+fRr//ve/cfnyZZiamuLRRx9t6q4TERHhyo1KDFmfgUp16/+D2tLcFPbWFsaeRqvW5CD0ww8/YNCgQfLr+utpIiIisGPHDixatAiVlZWIjIxESUkJfH19kZqaChsbG3mdjRs3wszMDOPHj0dlZSUGDx6MHTt2aHzGzy+//ILi4mL59YQJE/D7779j5cqVuHbtGry9vfH111+ja9euGvOrrKzE7NmzkZycDBOTO58O4OLigk2bNuHFF1+EUqlEYmIiLC0Nf4U6ERE9/ErKq1GprkX8hMfQo2NbY0+nUfbWFnBphjuyHiZNDkJBQUGQJKnB5QqFAjExMYiJiWmwT5s2bbBp0yZs2rSpwT4XLlzQaouMjERkZGSj87O0tMSZM2e02qdPn47p06c3ui4RERmXoU851d8mnn+1zGAXBdefcurRsa3WtTf04DHuhwcQERH9V/OdcjLDurzvDToiTzk9PBiEiIioVWiOU041NTXIzMzEwIEDDXqbOE85PTwYhIiIqFUx5CkntVqNi22BXs62wt0+T/phECIiEkRrv+Wbt3uTMTAIEREJ4EG55ZvX3lBLYxAiIhLAg3LLN6+9oZbGIETUDAx5CqI5bv990LEmujVWF97yTaQbf4MQGVjznIIw/O2/Dz7WRLeG68LTTkTaGISIDMzQpyCa6/bfBxlrotvd6sLTTkTa+BuEqJkY6hQEb//VxproxroQNZ2JsSdAREREZCwMQkRERCQsBiEiIiISFoMQERERCYtBiIiIiITFIERERETCYhAiIiIiYTEIERERkbAYhIiIiEhYDEJEREQkLAYhIiIiEhaDEBEREQmLQYiIiIiExSBEREREwmIQIiIiImExCBEREZGwGISIiIhIWAxCREREJCwGISIiIhIWgxAREREJi0GIiIiIhMUgRERERMJiECIiIiJhMQgRERGRsBiEiIiISFgMQkRERCQsBiEiIiISFoMQERERCYtBiIiIiITFIERERETCYhAiIiIiYTEIERERkbAYhIiIiEhYDEJEREQkLAYhIiIiEhaDEBEREQnL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i0GIiIiIhMUgRERERMJiECIiIiJhMQgRERGRsBiEiIiISFgMQkRERCQsBiEiIiISFoMQERERCYtBiIiIiITFIERERETCYhAiIiIiYTEIERERkbAYhIiIiEhYDEJEREQkLAYhIiIiEhaDEBEREQmLQYiIiIiExSBEREREwmIQIiIiImExCBEREZGwGISIiIhIWAxCREREJCwGISIiIhIWgxAREREJi0GIiIiIhMUgRERERMJiECIiIiJhMQgRERGRsBiEiIiISFgMQkRERCQsBiEiIiISFoMQERERCYtBiIiIiITFIERERETCYhAiIiIiYTEIERERkbAYhIiIiEhYDEJEREQkLAYhIiIiEhaDEBEREQmLQYiIiIiExSBEREREwmIQIiIiImExCBEREZGwGISIiIhIWAxCREREJCwGISIiIhIWgxAREREJi0GIiIiIhMUgRERERMJiECIiIiJhMQgRERGRsBiEiIiISFgMQkRERCSsZg9CcXFxUCgUiIqKktskSUJMTAycnZ1haWmJoKAg5Ofn33WsPXv2wMvLC0qlEl5eXkhJSdFYvmvXLri6usLBwQELFy7UWHbhwgV4eHigrKzMIPtFRERED75mDULZ2dn48MMP0adPH432tWvXYsOGDUhISEB2djZUKhWCg4Nx8+bNBsc6evQoJkyYgClTpuDEiROYMmUKxo8fj6ysLABAcXExpk+fjnXr1mH//v1ITEzEV199Ja8/c+ZMrF69Gra2ts2zs0RERPTAabYgdOvWLUyaNAlbtmyBvb293C5JEuLj47F06VKMHTsW3t7eSExMREVFBZKSkhocLz4+HsHBwYiOjoanpyeio6MxePBgxMfHAwDOnz8POzs7TJgwAQMGDMCgQYNw8uRJAEBSUhIsLCwwduzY5tpdIiIiegCZNdfAs2bNwogRIzBkyBCsWrVKbi8oKEBhYSFCQkLkNqVSicDAQBw5cgSvvvqqzvGOHj2KefPmabSFhobKQcjd3R0VFRU4fvw4unbtiuzsbEybNg3Xr1/H8uXL8d133+k176qqKlRVVcmv60+lqdVqqNVqvcbQR/1YhhzzXtXU1MjfjT2f1lSX1oI10caa6Ma6aGNNdGstdWnO9x99x2uWILR79278+OOPyM7O1lpWWFgIAHByctJod3JywsWLFxscs7CwUOc69ePZ29sjMTER4eHhqKysRHh4OEJDQzFt2jTMmTMHBQUFGD16NNRqNWJiYjBu3Did24mLi8OKFSu02lNTU2FlZdX4jt+DtLQ0g4/ZVJdvAYAZMjMzcbGtsWdzR2uoS2vDmmhjTXRjXbSxJroZuy7N+f5TUVGhVz+DB6HLly9j7ty5SE1NRZs2bRrsp1AoNF5LkqTV1tR1wsLCEBYWJr9OT09HXl4eEhIS0KNHD3z88cdQqVR44oknEBAQgI4dO2ptIzo6GvPnz5dfl5WVwdXVFSEhIQa9vkitViMtLQ3BwcEwNzc32Lj3Iv9qGdblfY+BAweil7Nxr6FqTXVpLVgTbayJbqyLNtZEt9ZSl+Z8/9H35iiDB6GcnBwUFRXBx8dHbqutrcXBgweRkJCAM2fOALhzhKdTp05yn6KiIq0jPn+kUqnkoz/6rFNVVYXIyEjs3LkT586dQ01NDQIDAwEAHh4eyMrKwqhRo7TWUyqVUCqVWu3m5ubN8sPSXOM2hZmZmfzd2HOp1xrq0tqwJtpYE91YF22siW7Grktzvv/oO57BL5YePHgw8vLykJubK3/1798fkyZNQm5uLrp37w6VSqVxOK66uhoZGRnw9/dvcFw/Pz+tQ3ipqakNrvPWW29h2LBhePzxx1FbWyufhwTuJOHa2tr73FMiIiJ60Bn8iJCNjQ28vb012qytreHo6Ci3R0VFITY2Fu7u7nB3d0dsbCysrKwwceJEeZ3w8HC4uLggLi4OADB37lwEBARgzZo1ePbZZ/HZZ5/hwIEDyMzM1JpDfn4+kpOTkZubCwDw9PSEiYkJtm7dCpVKhdOnT2PAgAGG3nUiIiJ6wDTbXWONWbRoESorKxEZGYmSkhL4+voiNTUVNjY2cp9Lly7BxOR/B6z8/f2xe/duLFu2DG+++SYeeeQRJCcnw9fXV2NsSZLwyiuvYOPGjbC2tgYAWFpaYseOHZg1axaqqqqQkJAAFxeXltlZIiIiarVaJAilp6drvFYoFIiJiUFMTIze6wDAuHHjGrzb649jHz58WKt95MiRGDlypD7TJSIiIkHwWWNEREQkLAYhIiIiEhaDEBEREQmLQYiIiIiExSBEREREwmIQIiIiImExCBEREZGwGISIiIhIWAxCREREJCwGISIiIhIWgxAREREJi0GIiIiIhMUgRERERMJiECIiIiJhMQgRERGRsBiEiIiISFgMQkRERCQsBiEiIiISFoMQERERCYtBiIiIiITFIERERETCYhAiIiIiYTEIERERkbAYhIiIiEhYDEJEREQkLAYhIiIiEhaDEBEREQmLQYiIiIiExSBEREREwmIQIiIiImExCBEREZGwGISIiIhIWAxCREREJCwGISIiIhIWgxAREREJi0GIiIiIhMUgRERERMJiECIiIiJhMQgRERGRsBiEiIiISFgMQkRERCQsBiEiIiISFoMQERERCYtBiIiIiITFIERERETCYhAiIiIiYTEIERERkbAYhIiIiEhYDEJEREQkLAYhIiIiEhaDEBEREQmLQYiIiIiExSBEREREwmIQIiIiImExCBEREZGwGISIiIhIWAxCREREJCwGISIiIhIWgxAREREJi0GIiIiIhGXwIBQXF4cBAwbAxsYGHTt2xJgxY3DmzBmNPpIkISYmBs7OzrC0tERQUBDy8/PvOvaePXvg5eUFpVIJLy8vpKSkaCzftWsXXF1d4eDggIULF2osu3DhAjw8PFBWVnb/O0lEREQPBYMHoYyMDMyaNQvff/890tLSUFNTg5CQEJSXl8t91q5diw0bNiAhIQHZ2dlQqVQIDg7GzZs3Gxz36NGjmDBhAqZMmYITJ05gypQpGD9+PLKysgAAxcXFmD59OtatW4f9+/cjMTERX331lbz+zJkzsXr1atja2hp6l4mIiOgBZfAgtG/fPkydOhW9evVC3759sX37dly6dAk5OTkA7hwNio+Px9KlSzF27Fh4e3sjMTERFRUVSEpKanDc+Ph4BAcHIzo6Gp6enoiOjsbgwYMRHx8PADh//jzs7OwwYcIEDBgwAIMGDcLJkycBAElJSbCwsMDYsWMNvbtERET0ADNr7g2UlpYCABwcHAAABQUFKCwsREhIiNxHqVQiMDAQR44cwauvvqpznKNHj2LevHkabaGhoXIQcnd3R0VFBY4fP46uXbsiOzsb06ZNw/Xr17F8+XJ89913es23qqoKVVVV8uv6U2lqtRpqtVq/ndZD/ViGHPNe1dTUyN+NPZ/WVJfWgjXRxproxrpoY010ay11ac73H33Ha9YgJEkS5s+fj4EDB8Lb2xsAUFhYCABwcnLS6Ovk5ISLFy82OFZhYaHOderHs7e3R2JiIsLDw1FZWYnw8HCEhoZi2rRpmDNnDgoKCjB69Gio1WrExMRg3LhxOrcTFxeH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", 74 | "text/plain": [ 75 | "
" 76 | ] 77 | }, 78 | "metadata": {}, 79 | "output_type": "display_data" 80 | } 81 | ], 82 | "source": [ 83 | "import numpy as np\n", 84 | "import pandas as pd\n", 85 | "import matplotlib.pyplot as plt\n", 86 | "from scipy.optimize import curve_fit\n", 87 | "\n", 88 | "# 指数分布模型\n", 89 | "def queue_model(t, λ):\n", 90 | " return 1 - np.exp(-λ * t)# 构造 ECDF 数据\n", 91 | "\n", 92 | "# 加载延迟数据\n", 93 | "latency = pd.read_csv('latency.csv').apply(pd.to_numeric).values.flatten()\n", 94 | "\n", 95 | "# 构造 ECDF 数据\n", 96 | "latency_sorted = np.sort(latency)\n", 97 | "ecdf_values = np.arange(len(latency_sorted)) / float(len(latency_sorted))\n", 98 | "\n", 99 | "# 非线性回归拟合模型\n", 100 | "popt, pcov = curve_fit(queue_model, latency_sorted, ecdf_values)\n", 101 | "\n", 102 | "# 提取最优参数\n", 103 | "λ_opt = popt[0]\n", 104 | "print(\"Fitted lambda:\", λ_opt)\n", 105 | "\n", 106 | "# 设置百分位纵轴\n", 107 | "ax = plt.gca()\n", 108 | "# ax.xaxis.set_major_locator(MultipleLocator(5))\n", 109 | "ax.yaxis.set_major_formatter(PercentFormatter(xmax=1, decimals=1))\n", 110 | "\n", 111 | "# 绘制实际数据和拟合的模型\n", 112 | "X_qt = np.linspace(0, max(latency), 1000)\n", 113 | "Y_qt_opt = queue_model(X_qt, λ_opt)\n", 114 | "\n", 115 | "plt.hist(latency, cumulative=True, density=True, histtype='step', label='Empirical CDF')\n", 116 | "plt.plot(X_qt, Y_qt_opt, 'r-', label=f'Fitted Model with λ={λ_opt:.3f}')\n", 117 | "plt.legend()\n", 118 | "plt.grid()\n", 119 | "plt.show()" 120 | ] 121 | } 122 | ], 123 | "metadata": { 124 | "kernelspec": { 125 | "display_name": "lab", 126 | "language": "python", 127 | "name": "python3" 128 | }, 129 | "language_info": { 130 | "codemirror_mode": { 131 | "name": "ipython", 132 | "version": 3 133 | }, 134 | "file_extension": ".py", 135 | "mimetype": "text/x-python", 136 | "name": "python", 137 | "nbconvert_exporter": "python", 138 | "pygments_lexer": "ipython3", 139 | "version": "3.12.8" 140 | }, 141 | "orig_nbformat": 4, 142 | "vscode": { 143 | "interpreter": { 144 | "hash": "3b1ac052aeca451b2e23f63df1a51951d9633e2640e7475e0dfc079eb8f1e08a" 145 | } 146 | } 147 | }, 148 | "nbformat": 4, 149 | "nbformat_minor": 2 150 | } 151 | -------------------------------------------------------------------------------- /latency-predict.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | from statsmodels.tsa.arima.model import ARIMA 5 | from sklearn.preprocessing import MinMaxScaler 6 | import torch 7 | import torch.nn as nn 8 | from torch.utils.data import DataLoader, TensorDataset 9 | from sklearn.metrics import mean_squared_error 10 | 11 | # Load latencies data from CSV 12 | latencies = pd.read_csv('latency.csv', header=0).values.flatten() 13 | 14 | # Simulate timeline 15 | timeline = [] 16 | current_time = 0 17 | for latency in latencies: 18 | current_time += latency 19 | timeline.append(current_time) 20 | 21 | # Define bin size (10 ms) 22 | bin_size = 0.01 23 | 24 | # Create bins 25 | max_time = max(timeline) 26 | bins = np.arange(0, max_time + bin_size, bin_size) 27 | 28 | # Count requests in each bin 29 | request_counts, _ = np.histogram(timeline, bins=bins) 30 | 31 | # Now `request_counts` is the time series you can use for prediction 32 | print(request_counts) 33 | 34 | # Split data into training and testing sets 35 | train_size = int(len(request_counts) * 0.8) 36 | train, test = request_counts[:train_size], request_counts[train_size:] 37 | 38 | # Function to create sequences for RNN models 39 | def create_sequences(data, seq_length): 40 | X, y = [], [] 41 | for i in range(len(data) - seq_length): 42 | X.append(data[i:i + seq_length]) 43 | y.append(data[i + seq_length]) 44 | return np.array(X), np.array(y) 45 | 46 | # Prepare data for ARIMA 47 | def arima_predict(train, test): 48 | model = ARIMA(train, order=(5, 1, 0)) # ARIMA(p, d, q) 49 | model_fit = model.fit() 50 | predictions = model_fit.forecast(steps=len(test)) 51 | return predictions 52 | 53 | # Prepare data for GRU and LSTM 54 | seq_length = 10 # Sequence length for RNN models 55 | X_train, y_train = create_sequences(train, seq_length) 56 | X_test, y_test = create_sequences(test, seq_length) 57 | 58 | # Normalize input features (X) 59 | scaler_X = MinMaxScaler() 60 | X_train = scaler_X.fit_transform(X_train) 61 | X_test = scaler_X.transform(X_test) 62 | 63 | # Normalize target (y) separately 64 | scaler_y = MinMaxScaler() 65 | y_train = scaler_y.fit_transform(y_train.reshape(-1, 1)).flatten() 66 | y_test = scaler_y.transform(y_test.reshape(-1, 1)).flatten() 67 | 68 | # Convert to PyTorch tensors 69 | X_train = torch.tensor(X_train, dtype=torch.float32).unsqueeze(-1) 70 | y_train = torch.tensor(y_train, dtype=torch.float32).unsqueeze(-1) 71 | X_test = torch.tensor(X_test, dtype=torch.float32).unsqueeze(-1) 72 | y_test = torch.tensor(y_test, dtype=torch.float32).unsqueeze(-1) 73 | 74 | # Create DataLoader 75 | train_dataset = TensorDataset(X_train, y_train) 76 | test_dataset = TensorDataset(X_test, y_test) 77 | train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True) 78 | test_loader = DataLoader(test_dataset, batch_size=32, shuffle=False) 79 | 80 | # Define GRU model 81 | class GRUModel(nn.Module): 82 | def __init__(self, input_size, hidden_size, output_size): 83 | super(GRUModel, self).__init__() 84 | self.gru = nn.GRU(input_size, hidden_size, batch_first=True) 85 | self.fc = nn.Linear(hidden_size, output_size) 86 | 87 | def forward(self, x): 88 | out, _ = self.gru(x) 89 | out = self.fc(out[:, -1, :]) 90 | return out 91 | 92 | # Define LSTM model 93 | class LSTMModel(nn.Module): 94 | def __init__(self, input_size, hidden_size, output_size): 95 | super(LSTMModel, self).__init__() 96 | self.lstm = nn.LSTM(input_size, hidden_size, batch_first=True) 97 | self.fc = nn.Linear(hidden_size, output_size) 98 | 99 | def forward(self, x): 100 | out, _ = self.lstm(x) 101 | out = self.fc(out[:, -1, :]) 102 | return out 103 | 104 | # Train function 105 | def train_model(model, train_loader, criterion, optimizer, epochs=50): 106 | model.train() 107 | for epoch in range(epochs): 108 | for X_batch, y_batch in train_loader: 109 | optimizer.zero_grad() 110 | y_pred = model(X_batch) 111 | loss = criterion(y_pred, y_batch) 112 | loss.backward() 113 | optimizer.step() 114 | print(f"Epoch {epoch+1}/{epochs}, Loss: {loss.item()}") 115 | 116 | # Evaluate function 117 | def evaluate_model(model, test_loader): 118 | model.eval() 119 | predictions, actuals = [], [] 120 | with torch.no_grad(): 121 | for X_batch, y_batch in test_loader: 122 | y_pred = model(X_batch) 123 | predictions.extend(y_pred.numpy()) 124 | actuals.extend(y_batch.numpy()) 125 | return np.array(predictions), np.array(actuals) 126 | 127 | # Train and evaluate GRU 128 | gru_model = GRUModel(input_size=1, hidden_size=50, output_size=1) 129 | criterion = nn.MSELoss() 130 | optimizer = torch.optim.Adam(gru_model.parameters(), lr=0.01) 131 | train_model(gru_model, train_loader, criterion, optimizer) 132 | gru_predictions, _ = evaluate_model(gru_model, test_loader) 133 | 134 | # Train and evaluate LSTM 135 | lstm_model = LSTMModel(input_size=1, hidden_size=50, output_size=1) 136 | optimizer = torch.optim.Adam(lstm_model.parameters(), lr=0.01) 137 | train_model(lstm_model, train_loader, criterion, optimizer) 138 | lstm_predictions, _ = evaluate_model(lstm_model, test_loader) 139 | 140 | # ARIMA predictions 141 | arima_predictions = arima_predict(train, test) 142 | 143 | # Inverse transform predictions 144 | gru_predictions = scaler_y.inverse_transform(gru_predictions.reshape(-1, 1)).flatten() 145 | lstm_predictions = scaler_y.inverse_transform(lstm_predictions.reshape(-1, 1)).flatten() 146 | arima_predictions = arima_predictions # ARIMA predictions are already in the original scale 147 | 148 | # Plot results 149 | plt.figure(figsize=(12, 6)) 150 | plt.plot(test, label="Actual Arriving Rate", color="black") 151 | plt.plot(arima_predictions, label="ARIMA Predictions", linestyle="--") 152 | plt.plot(gru_predictions, label="GRU Predictions", linestyle="-.") 153 | plt.plot(lstm_predictions, label="LSTM Predictions", linestyle=":") 154 | plt.xlabel("Time") 155 | plt.ylabel("Arriving Rate") 156 | plt.title("Comparison of ARIMA, GRU, and LSTM Predictions") 157 | plt.legend() 158 | plt.grid() 159 | plt.show() -------------------------------------------------------------------------------- /obs-simulation.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "设置仿真参数" 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": 95, 13 | "metadata": {}, 14 | "outputs": [], 15 | "source": [ 16 | "import random\n", 17 | "import simpy\n", 18 | "\n", 19 | "RANDOM_SEED = 42\n", 20 | "REQ_TOTAL = 100 # 总请求数\n", 21 | "ARRIVAL_RATE = 0.05 # 请求到达率 (每毫秒来多少个请求)\n", 22 | "SERVICE_RATE = 0.1 # 服务速率 (每毫秒做多少个请求)\n", 23 | "OVER_TIME = 100.0 # 请求超时 (毫秒)\n", 24 | "\n", 25 | "latency = [] # 采集请求延迟\n" 26 | ] 27 | }, 28 | { 29 | "cell_type": "markdown", 30 | "metadata": {}, 31 | "source": [ 32 | "初始化仿真对象" 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": 96, 38 | "metadata": {}, 39 | "outputs": [], 40 | "source": [ 41 | "def workload(env, number, arr_rate, server):\n", 42 | " \"\"\"基于所设均值泊松分布生成请求\"\"\"\n", 43 | " for i in range(number):\n", 44 | " c = req_handler(env, 'Request%03d' % i, server, svr_rate=SERVICE_RATE)\n", 45 | " env.process(c)\n", 46 | " t = random.expovariate(arr_rate)\n", 47 | " yield env.timeout(t)\n", 48 | "\n", 49 | "def req_handler(env, name, server, svr_rate):\n", 50 | " \"\"\"请求处理过程\"\"\"\n", 51 | " arrive = env.now\n", 52 | " print('%7.4f %s: Incoming request' % (arrive, name))\n", 53 | "\n", 54 | " with server.request() as req:\n", 55 | " # ot = random.uniform(MIN_PATIENCE, MAX_PATIENCE)\n", 56 | " ot = random.uniform(1, 3)\n", 57 | " # 等待服务或超时\n", 58 | " results = yield req | env.timeout(OVER_TIME)\n", 59 | "\n", 60 | " wait = env.now - arrive\n", 61 | "\n", 62 | " if req in results:\n", 63 | " # We got to the server\n", 64 | " print('%7.4f %s: Waited %6.3f' % (env.now, name, wait))\n", 65 | "\n", 66 | " tib = random.expovariate(svr_rate)\n", 67 | " yield env.timeout(tib)\n", 68 | " print('%7.4f %s: Finished' % (env.now, name))\n", 69 | "\n", 70 | " # 请求正常完成,采集延迟\n", 71 | " latency.append(env.now - arrive)\n", 72 | "\n", 73 | " else:\n", 74 | " # 请求超时\n", 75 | " print('%7.4f %s: Overtime after %6.3f' % (env.now, name, wait))\n", 76 | "\n", 77 | "# 仿真环境准备\n", 78 | "random.seed(RANDOM_SEED)\n", 79 | "env = simpy.Environment()" 80 | ] 81 | }, 82 | { 83 | "cell_type": "markdown", 84 | "metadata": {}, 85 | "source": [ 86 | "开始仿真" 87 | ] 88 | }, 89 | { 90 | "cell_type": "code", 91 | "execution_count": 97, 92 | "metadata": {}, 93 | "outputs": [ 94 | { 95 | "name": "stdout", 96 | "output_type": "stream", 97 | "text": [ 98 | " 0.0000 Request000: Incoming request\n", 99 | " 0.0000 Request000: Waited 0.000\n", 100 | " 3.2162 Request000: Finished\n", 101 | "20.4012 Request001: Incoming request\n", 102 | "20.4012 Request001: Waited 0.000\n", 103 | "25.4529 Request002: Incoming request\n", 104 | "31.6929 Request001: Finished\n", 105 | "31.6929 Request002: Waited 6.240\n", 106 | "37.1734 Request002: Finished\n", 107 | "69.9987 Request003: Incoming request\n", 108 | "69.9987 Request003: Waited 0.000\n", 109 | "70.6037 Request004: Incoming request\n", 110 | "71.1416 Request005: Incoming request\n", 111 | "77.0378 Request003: Finished\n", 112 | "77.0378 Request004: Waited 6.434\n", 113 | "79.5281 Request004: Finished\n", 114 | "79.5281 Request005: Waited 8.387\n", 115 | "88.4262 Request005: Finished\n", 116 | "92.1314 Request006: Incoming request\n", 117 | "92.1314 Request006: Waited 0.000\n", 118 | "108.5211 Request006: Finished\n", 119 | "125.2862 Request007: Incoming request\n", 120 | "125.2862 Request007: Waited 0.000\n", 121 | "126.9760 Request007: Finished\n", 122 | "149.2420 Request008: Incoming request\n", 123 | "149.2420 Request008: Waited 0.000\n", 124 | "150.2153 Request008: Finished\n", 125 | "212.2724 Request009: Incoming request\n", 126 | "212.2724 Request009: Waited 0.000\n", 127 | "214.3068 Request010: Incoming request\n", 128 | "221.5289 Request009: Finished\n", 129 | "221.5289 Request010: Waited 7.222\n", 130 | "229.2126 Request010: Finished\n", 131 | "247.2214 Request011: Incoming request\n", 132 | "247.2214 Request011: Waited 0.000\n", 133 | "255.2519 Request011: Finished\n", 134 | "319.5457 Request012: Incoming request\n", 135 | "319.5457 Request012: Waited 0.000\n", 136 | "339.3295 Request012: Finished\n", 137 | "354.9149 Request013: Incoming request\n", 138 | "354.9149 Request013: Waited 0.000\n", 139 | "355.3840 Request013: Finished\n", 140 | "372.1393 Request014: Incoming request\n", 141 | "372.1393 Request014: Waited 0.000\n", 142 | "372.9708 Request014: Finished\n", 143 | "377.3120 Request015: Incoming request\n", 144 | "377.3120 Request015: Waited 0.000\n", 145 | "380.5690 Request015: Finished\n", 146 | "382.6119 Request016: Incoming request\n", 147 | "382.6119 Request016: Waited 0.000\n", 148 | "387.2352 Request016: Finished\n", 149 | "402.8066 Request017: Incoming request\n", 150 | "402.8066 Request017: Waited 0.000\n", 151 | "407.5086 Request018: Incoming request\n", 152 | "428.3931 Request019: Incoming request\n", 153 | "430.3982 Request017: Finished\n", 154 | "430.3982 Request018: Waited 22.890\n", 155 | "432.1472 Request020: Incoming request\n", 156 | "432.1823 Request018: Finished\n", 157 | "432.1823 Request019: Waited 3.789\n", 158 | "441.6903 Request021: Incoming request\n", 159 | "442.3988 Request019: Finished\n", 160 | "442.3988 Request020: Waited 10.252\n", 161 | "457.9718 Request022: Incoming request\n", 162 | "460.9045 Request020: Finished\n", 163 | "460.9045 Request021: Waited 19.214\n", 164 | "461.2307 Request021: Finished\n", 165 | "461.2307 Request022: Waited 3.259\n", 166 | "465.0207 Request022: Finished\n", 167 | "487.8939 Request023: Incoming request\n", 168 | "487.8939 Request023: Waited 0.000\n", 169 | "494.1263 Request024: Incoming request\n", 170 | "516.5251 Request023: Finished\n", 171 | "516.5251 Request024: Waited 22.399\n", 172 | "527.1800 Request024: Finished\n", 173 | "535.9352 Request025: Incoming request\n", 174 | "535.9352 Request025: Waited 0.000\n", 175 | "542.0758 Request025: Finished\n", 176 | "546.0066 Request026: Incoming request\n", 177 | "546.0066 Request026: Waited 0.000\n", 178 | "552.1611 Request027: Incoming request\n", 179 | "554.2476 Request026: Finished\n", 180 | "554.2476 Request027: Waited 2.087\n", 181 | "558.2574 Request028: Incoming request\n", 182 | "568.4540 Request029: Incoming request\n", 183 | "577.0581 Request027: Finished\n", 184 | "577.0581 Request028: Waited 18.801\n", 185 | "578.0112 Request028: Finished\n", 186 | "578.0112 Request029: Waited 9.557\n", 187 | "578.4938 Request029: Finished\n", 188 | "688.5864 Request030: Incoming request\n", 189 | "688.5864 Request030: Waited 0.000\n", 190 | "690.9092 Request031: Incoming request\n", 191 | "701.8783 Request032: Incoming request\n", 192 | "704.2924 Request030: Finished\n", 193 | "704.2924 Request031: Waited 13.383\n", 194 | "711.4914 Request033: Incoming request\n", 195 | "711.8238 Request031: Finished\n", 196 | "711.8238 Request032: Waited 9.945\n", 197 | "711.9392 Request032: Finished\n", 198 | "711.9392 Request033: Waited 0.448\n", 199 | "724.6947 Request033: Finished\n", 200 | "782.3547 Request034: Incoming request\n", 201 | "782.3547 Request034: Waited 0.000\n", 202 | "785.4584 Request034: Finished\n", 203 | "805.2505 Request035: Incoming request\n", 204 | "805.2505 Request035: Waited 0.000\n", 205 | "810.9557 Request035: Finished\n", 206 | "825.7371 Request036: Incoming request\n", 207 | "825.7371 Request036: Waited 0.000\n", 208 | "837.8297 Request037: Incoming request\n", 209 | "843.9436 Request038: Incoming request\n", 210 | "846.6000 Request036: Finished\n", 211 | "846.6000 Request037: Waited 8.770\n", 212 | "847.8798 Request039: Incoming request\n", 213 | "854.9689 Request040: Incoming request\n", 214 | "867.0421 Request037: Finished\n", 215 | "867.0421 Request038: Waited 23.099\n", 216 | "873.7483 Request041: Incoming request\n", 217 | "881.4185 Request038: Finished\n", 218 | "881.4185 Request039: Waited 33.539\n", 219 | "888.9762 Request039: Finished\n", 220 | "888.9762 Request040: Waited 34.007\n", 221 | "888.9819 Request040: Finished\n", 222 | "888.9819 Request041: Waited 15.234\n", 223 | "889.2519 Request042: Incoming request\n", 224 | "889.6453 Request043: Incoming request\n", 225 | "892.8999 Request041: Finished\n", 226 | "892.8999 Request042: Waited 3.648\n", 227 | "896.5745 Request042: Finished\n", 228 | "896.5745 Request043: Waited 6.929\n", 229 | "897.1713 Request043: Finished\n", 230 | "931.8387 Request044: Incoming request\n", 231 | "931.8387 Request044: Waited 0.000\n", 232 | "932.7341 Request044: Finished\n", 233 | "973.9149 Request045: Incoming request\n", 234 | "973.9149 Request045: Waited 0.000\n", 235 | "987.2252 Request046: Incoming request\n", 236 | "988.2112 Request045: Finished\n", 237 | "988.2112 Request046: Waited 0.986\n", 238 | "994.6601 Request046: Finished\n", 239 | "1016.2597 Request047: Incoming request\n", 240 | "1016.2597 Request047: Waited 0.000\n", 241 | "1032.2211 Request048: Incoming request\n", 242 | "1036.8509 Request047: Finished\n", 243 | "1036.8509 Request048: Waited 4.630\n", 244 | "1043.2242 Request049: Incoming request\n", 245 | "1044.6009 Request048: Finished\n", 246 | "1044.6009 Request049: Waited 1.377\n", 247 | "1048.3364 Request049: Finished\n", 248 | "1069.4057 Request050: Incoming request\n", 249 | "1069.4057 Request050: Waited 0.000\n", 250 | "1075.1701 Request050: Finished\n", 251 | "1175.9786 Request051: Incoming request\n", 252 | "1175.9786 Request051: Waited 0.000\n", 253 | "1178.5236 Request051: Finished\n", 254 | "1190.5573 Request052: Incoming request\n", 255 | "1190.5573 Request052: Waited 0.000\n", 256 | "1193.1724 Request052: Finished\n", 257 | "1198.8096 Request053: Incoming request\n", 258 | "1198.8096 Request053: Waited 0.000\n", 259 | "1203.7844 Request054: Incoming request\n", 260 | "1208.7820 Request053: Finished\n", 261 | "1208.7820 Request054: Waited 4.998\n", 262 | "1208.9843 Request055: Incoming request\n", 263 | "1210.4541 Request056: Incoming request\n", 264 | "1228.4171 Request054: Finished\n", 265 | "1228.4171 Request055: Waited 19.433\n", 266 | "1229.8364 Request055: Finished\n", 267 | "1229.8364 Request056: Waited 19.382\n", 268 | "1232.5655 Request057: Incoming request\n", 269 | "1249.4935 Request058: Incoming request\n", 270 | "1257.2495 Request056: Finished\n", 271 | "1257.2495 Request057: Waited 24.684\n", 272 | "1259.3617 Request057: Finished\n", 273 | "1259.3617 Request058: Waited 9.868\n", 274 | "1260.3813 Request058: Finished\n", 275 | "1280.2005 Request059: Incoming request\n", 276 | "1280.2005 Request059: Waited 0.000\n", 277 | "1286.4933 Request059: Finished\n", 278 | "1291.4798 Request060: Incoming request\n", 279 | "1291.4798 Request060: Waited 0.000\n", 280 | "1317.5981 Request061: Incoming request\n", 281 | "1319.6702 Request062: Incoming request\n", 282 | "1327.9594 Request063: Incoming request\n", 283 | "1332.9352 Request060: Finished\n", 284 | "1332.9352 Request061: Waited 15.337\n", 285 | "1333.6772 Request064: Incoming request\n", 286 | "1338.8884 Request061: Finished\n", 287 | "1338.8884 Request062: Waited 19.218\n", 288 | "1341.7627 Request062: Finished\n", 289 | "1341.7627 Request063: Waited 13.803\n", 290 | "1344.6367 Request065: Incoming request\n", 291 | "1356.3452 Request066: Incoming request\n", 292 | "1367.4367 Request063: Finished\n", 293 | "1367.4367 Request064: Waited 33.760\n", 294 | "1372.3299 Request067: Incoming request\n", 295 | "1408.4910 Request068: Incoming request\n", 296 | "1439.8336 Request064: Finished\n", 297 | "1439.8336 Request065: Waited 95.197\n", 298 | "1441.6526 Request065: Finished\n", 299 | "1441.6526 Request066: Waited 85.307\n", 300 | "1448.3009 Request066: Finished\n", 301 | "1448.3009 Request067: Waited 75.971\n", 302 | "1450.7057 Request067: Finished\n", 303 | "1450.7057 Request068: Waited 42.215\n", 304 | "1455.8313 Request068: Finished\n", 305 | "1460.6642 Request069: Incoming request\n", 306 | "1460.6642 Request069: Waited 0.000\n", 307 | "1461.8727 Request070: Incoming request\n", 308 | "1468.0359 Request071: Incoming request\n", 309 | "1480.1756 Request072: Incoming request\n", 310 | "1502.8693 Request069: Finished\n", 311 | "1502.8693 Request070: Waited 40.997\n", 312 | "1510.9834 Request070: Finished\n", 313 | "1510.9834 Request071: Waited 42.947\n", 314 | "1523.6564 Request071: Finished\n", 315 | "1523.6564 Request072: Waited 43.481\n", 316 | "1525.3382 Request072: Finished\n", 317 | "1543.2550 Request073: Incoming request\n", 318 | "1543.2550 Request073: Waited 0.000\n", 319 | "1550.2947 Request074: Incoming request\n", 320 | "1551.9105 Request073: Finished\n", 321 | "1551.9105 Request074: Waited 1.616\n", 322 | "1552.4992 Request074: Finished\n", 323 | "1565.9209 Request075: Incoming request\n", 324 | "1565.9209 Request075: Waited 0.000\n", 325 | "1583.4709 Request076: Incoming request\n", 326 | "1585.0751 Request075: Finished\n", 327 | "1585.0751 Request076: Waited 1.604\n", 328 | "1585.9102 Request076: Finished\n", 329 | "1586.8969 Request077: Incoming request\n", 330 | "1586.8969 Request077: Waited 0.000\n", 331 | "1591.0085 Request078: Incoming request\n", 332 | "1596.3714 Request079: Incoming request\n", 333 | "1598.1428 Request077: Finished\n", 334 | "1598.1428 Request078: Waited 7.134\n", 335 | "1607.1696 Request078: Finished\n", 336 | "1607.1696 Request079: Waited 10.798\n", 337 | "1616.8292 Request079: Finished\n", 338 | "1640.5692 Request080: Incoming request\n", 339 | "1640.5692 Request080: Waited 0.000\n", 340 | "1647.9671 Request080: Finished\n", 341 | "1651.4371 Request081: Incoming request\n", 342 | "1651.4371 Request081: Waited 0.000\n", 343 | "1664.0316 Request081: Finished\n", 344 | "1706.0143 Request082: Incoming request\n", 345 | "1706.0143 Request082: Waited 0.000\n", 346 | "1711.4684 Request083: Incoming request\n", 347 | "1717.1522 Request082: Finished\n", 348 | "1717.1522 Request083: Waited 5.684\n", 349 | "1718.6019 Request084: Incoming request\n", 350 | "1720.1080 Request085: Incoming request\n", 351 | "1731.0900 Request083: Finished\n", 352 | "1731.0900 Request084: Waited 12.488\n", 353 | "1731.8509 Request084: Finished\n", 354 | "1731.8509 Request085: Waited 11.743\n", 355 | "1734.2481 Request085: Finished\n", 356 | "1849.5554 Request086: Incoming request\n", 357 | "1849.5554 Request086: Waited 0.000\n", 358 | "1855.7186 Request087: Incoming request\n", 359 | "1870.8303 Request086: Finished\n", 360 | "1870.8303 Request087: Waited 15.112\n", 361 | "1872.5471 Request087: Finished\n", 362 | "1898.0026 Request088: Incoming request\n", 363 | "1898.0026 Request088: Waited 0.000\n", 364 | "1907.4618 Request088: Finished\n", 365 | "1933.8872 Request089: Incoming request\n", 366 | "1933.8872 Request089: Waited 0.000\n", 367 | "1933.9658 Request089: Finished\n", 368 | "2021.1052 Request090: Incoming request\n", 369 | "2021.1052 Request090: Waited 0.000\n", 370 | "2031.9934 Request090: Finished\n", 371 | "2055.0819 Request091: Incoming request\n", 372 | "2055.0819 Request091: Waited 0.000\n", 373 | "2056.3085 Request091: Finished\n", 374 | "2110.9966 Request092: Incoming request\n", 375 | "2110.9966 Request092: Waited 0.000\n", 376 | "2113.2608 Request093: Incoming request\n", 377 | "2114.1759 Request092: Finished\n", 378 | "2114.1759 Request093: Waited 0.915\n", 379 | "2116.4525 Request093: Finished\n", 380 | "2131.8296 Request094: Incoming request\n", 381 | "2131.8296 Request094: Waited 0.000\n", 382 | "2138.5343 Request094: Finished\n", 383 | "2151.9450 Request095: Incoming request\n", 384 | "2151.9450 Request095: Waited 0.000\n", 385 | "2152.9134 Request095: Finished\n", 386 | "2199.0936 Request096: Incoming request\n", 387 | "2199.0936 Request096: Waited 0.000\n", 388 | "2199.1291 Request096: Finished\n", 389 | "2210.1118 Request097: Incoming request\n", 390 | "2210.1118 Request097: Waited 0.000\n", 391 | "2213.1493 Request097: Finished\n", 392 | "2239.6029 Request098: Incoming request\n", 393 | "2239.6029 Request098: Waited 0.000\n", 394 | "2245.1836 Request098: Finished\n", 395 | "2266.6393 Request099: Incoming request\n", 396 | "2266.6393 Request099: Waited 0.000\n", 397 | "2288.1042 Request099: Finished\n" 398 | ] 399 | } 400 | ], 401 | "source": [ 402 | "server = simpy.Resource(env, capacity=1)\n", 403 | "env.process(workload(env, REQ_TOTAL, ARRIVAL_RATE, server))\n", 404 | "env.run()" 405 | ] 406 | }, 407 | { 408 | "cell_type": "markdown", 409 | "metadata": {}, 410 | "source": [ 411 | "记录延迟数据" 412 | ] 413 | }, 414 | { 415 | "cell_type": "code", 416 | "execution_count": 98, 417 | "metadata": {}, 418 | "outputs": [], 419 | "source": [ 420 | "with open(\"latency.csv\", \"w+\") as tracefile:\n", 421 | " tracefile.write(\"# latency\\n\")\n", 422 | " tracefile.writelines([str(l) + '\\n' for l in latency])" 423 | ] 424 | } 425 | ], 426 | "metadata": { 427 | "kernelspec": { 428 | "display_name": "Python 3.9.7 ('lab')", 429 | "language": "python", 430 | "name": "python3" 431 | }, 432 | "language_info": { 433 | "codemirror_mode": { 434 | "name": "ipython", 435 | "version": 3 436 | }, 437 | "file_extension": ".py", 438 | "mimetype": "text/x-python", 439 | "name": "python", 440 | "nbconvert_exporter": "python", 441 | "pygments_lexer": "ipython3", 442 | "version": "3.9.7" 443 | }, 444 | "orig_nbformat": 4, 445 | "vscode": { 446 | "interpreter": { 447 | "hash": "8af900479a3540cfc38875a4a015127521479f698f39a91084609bef8ead6227" 448 | } 449 | } 450 | }, 451 | "nbformat": 4, 452 | "nbformat_minor": 2 453 | } 454 | -------------------------------------------------------------------------------- /qt-predict.py: -------------------------------------------------------------------------------- 1 | """ 2 | 用排队论模型分析尾延迟分布的变化 3 | """ 4 | 5 | import numpy as np 6 | # from scipy.stats import poisson 7 | from scipy.special import factorial 8 | import matplotlib.pyplot as plt 9 | from matplotlib.widgets import Slider, Button 10 | from matplotlib.ticker import PercentFormatter 11 | 12 | # 1. 给出请求到达率λ,绘制泊松分布曲线 13 | 14 | # 参数:平均请求到达率λ 15 | def arrival(x, λ): 16 | # return poisson.pmf(k=x, mu=λ) 17 | return np.power(λ, x) / factorial(x) * np.exp(-λ) 18 | 19 | # 单位时间事件数量 20 | x1 = np.linspace(0, 99, 100) 21 | 22 | # 平均到达率、服务率初始值 23 | init_arrival_rate = 10 24 | 25 | # 绘图 26 | fig1, ax1 = plt.subplots() 27 | line1, = ax1.plot(x1, arrival(x1, init_arrival_rate), lw=2) 28 | ax1.set_xlabel('arrival speed') 29 | 30 | # 绘图区域留空给滑动条 31 | fig1.subplots_adjust(bottom=0.25) 32 | 33 | # 设置请求到达率滑动条 34 | ax_arrival_rate = fig1.add_axes([0.2, 0.1, 0.5, 0.03]) 35 | slider_arrival_rate = Slider( 36 | ax=ax_arrival_rate, 37 | label='arrival rate', 38 | valmin=0, 39 | valmax=50, 40 | valstep=1, 41 | valinit=init_arrival_rate, 42 | ) 43 | 44 | # 刷新绘图 45 | def update1(val): 46 | line1.set_ydata(arrival(x1, slider_arrival_rate.val)) 47 | fig1.canvas.draw_idle() 48 | 49 | # 注册参数刷新函数 50 | slider_arrival_rate.on_changed(update1) 51 | 52 | # λ重置按钮 53 | resetax1 = fig1.add_axes([0.8, 0.025, 0.1, 0.04]) 54 | button1 = Button(resetax1, 'Reset', hovercolor='0.975') 55 | 56 | def reset1(event): 57 | slider_arrival_rate.reset() 58 | 59 | button1.on_clicked(reset1) 60 | 61 | # 2. 给出请求到达率λ和服务速率μ,使用排队论模型预测延迟分布 62 | 63 | def queueing_model(x, λ, μ): 64 | return 1 - np.exp(-1*(μ-λ)*x) 65 | 66 | x2 = np.linspace(0, 10, 100) 67 | 68 | init_λ = 10 69 | init_μ = 20 70 | 71 | fig2, ax2 = plt.subplots() 72 | line2, = ax2.plot(x2, queueing_model(x2, init_λ, init_μ), lw=2) 73 | ax2.set_xlabel('latency') 74 | ax2.set_xticks(np.linspace(0, 10, 11)) 75 | ax2.set_xticks(np.linspace(0, 10, 21), minor=True) 76 | ax2.grid(which='both', alpha=0.3) 77 | ax2.yaxis.set_major_formatter(PercentFormatter(xmax=1, decimals=1)) # 纵轴用百分位指标 78 | 79 | fig2.subplots_adjust(bottom=0.25) 80 | 81 | # 请求到达率调节条 82 | λ_ax = fig2.add_axes([0.1, 0.1, 0.3, 0.03]) 83 | λ_slider = Slider( 84 | ax=λ_ax, 85 | label='λ', 86 | valmin=0, 87 | valmax=50, 88 | valstep=1, 89 | valinit=init_λ, 90 | ) 91 | 92 | # 服务速率调节条 93 | μ_ax = fig2.add_axes([0.5, 0.1, 0.3, 0.03]) 94 | μ_slider = Slider( 95 | ax=μ_ax, 96 | label='μ', 97 | valmin=0, 98 | valmax=50, 99 | valstep=1, 100 | valinit=init_μ, 101 | ) 102 | 103 | # 绘图更新,基于即时更新的λ和μ 104 | def update_λ_μ(val): 105 | line2.set_ydata(queueing_model(x2, λ_slider.val, μ_slider.val)) 106 | fig2.canvas.draw_idle() 107 | 108 | λ_slider.on_changed(update_λ_μ) 109 | μ_slider.on_changed(update_λ_μ) 110 | 111 | # λ、μ重置按钮 112 | resetax2 = fig2.add_axes([0.8, 0.025, 0.1, 0.04]) 113 | button2 = Button(resetax2, 'Reset', hovercolor='0.975') 114 | 115 | def reset2(event): 116 | λ_slider.reset() 117 | μ_slider.reset() 118 | 119 | button2.on_clicked(reset2) 120 | 121 | plt.show() 122 | -------------------------------------------------------------------------------- /qt-simulation-mmk.py: -------------------------------------------------------------------------------- 1 | import simpy 2 | import random 3 | import numpy as np 4 | import matplotlib.pyplot as plt 5 | from matplotlib.ticker import PercentFormatter 6 | import argparse 7 | 8 | def handler(env, name, server, service_time, latencies): 9 | """顾客进程""" 10 | arrive_time = env.now 11 | print(f"{name} arrives at time {arrive_time:.2f}") 12 | 13 | with server.request() as req: 14 | yield req 15 | 16 | start_service_time = env.now 17 | print(f"{name} starts being handled at time {start_service_time:.2f}") 18 | 19 | yield env.timeout(service_time) 20 | 21 | end_service_time = env.now 22 | latency = end_service_time - arrive_time 23 | print(f"{name} finishes at time {end_service_time:.2f}, total service time: {latency:.2f}") 24 | 25 | # record request latency in latencies list 26 | latencies.append(latency) 27 | 28 | def simulate(λ, μ, r, latencies, server): 29 | """模拟函数""" 30 | for i in range(r): 31 | service_time = random.expovariate(μ) 32 | env.process(handler(env, f"REQUEST {i+1}", server, service_time, latencies)) 33 | 34 | inter_arrival_time = random.expovariate(λ) 35 | yield env.timeout(inter_arrival_time) 36 | 37 | yield env.timeout(0) # 等待所有请求完成 38 | 39 | if __name__ == "__main__": 40 | parser = argparse.ArgumentParser() 41 | parser.add_argument("-l", "--lamda", type=float, default=9, help="Arrival rate (default: 9)") 42 | parser.add_argument("-m", "--mu", type=float, default=10, help="Service rate (default: 10)") 43 | parser.add_argument("-k", "--servers", type=int, default=1, help="Number of servers (default: 1)") 44 | parser.add_argument("-r", "--requests", type=int, default=100, help="Number of requests to simulate (default: 100)") 45 | args = parser.parse_args() 46 | 47 | latencies = [] 48 | random.seed(42) 49 | 50 | env = simpy.Environment() 51 | server = simpy.Resource(env, capacity=args.requests) 52 | 53 | env.process(simulate(args.lamda, args.mu, args.requests, latencies, server)) 54 | env.run() 55 | 56 | # Save latencies to a CSV file 57 | with open('latencies.csv', 'w', newline='') as csvfile: 58 | writer = csv.writer(csvfile) 59 | writer.writerow(['Latency']) # Write header 60 | writer.writerows([[latency] for latency in latencies]) # Write data 61 | 62 | """绘制累计概率分布图""" 63 | ax = plt.gca() 64 | ax.yaxis.set_major_formatter(PercentFormatter(xmax=1, decimals=1)) 65 | 66 | plt.xlim(0, max(latencies)) 67 | plt.hist(latencies, cumulative=True, histtype='step', weights=[1./ len(latencies)] * len(latencies)) 68 | 69 | # 排队论模型 70 | # F(t)=1-e^(-1*a*t) 71 | μ_λ = args.mu - args.lamda 72 | X_qt = np.arange(0, max(latencies), .01) 73 | Y_qt = 1 - np.exp(-1 * μ_λ * X_qt) 74 | # 绘制排队论模型拟合 75 | plt.plot(X_qt, Y_qt) 76 | 77 | plt.grid() 78 | plt.show() 79 | -------------------------------------------------------------------------------- /run-minio.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | @rem Use environment variables MINIO_ROOT_USER & MINIO_ROOT_PASSWORD to set keys, for later use in clients. 4 | set MINIO_ROOT_USER=hust 5 | set MINIO_ROOT_PASSWORD=hust_obs 6 | 7 | @rem Export metrics 8 | set MINIO_PROMETHEUS_AUTH_TYPE=public 9 | 10 | @rem Use "-C" flag to store configuration file in local directory "./". 11 | @rem Use server command to start object storage server with "./root" as root directory, in which holds all buckets and objects. 12 | minio.exe -C ./ server ./root --console-address ":9090" 13 | 14 | @rem Run above task in one command line. 15 | @rem set MINIO_ROOT_USER=hust& set MINIO_ROOT_PASSWORD=hust_obs& minio.exe -S ./certs server ./root 16 | -------------------------------------------------------------------------------- /run-minio.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Use environment variables MINIO_ROOT_USER & MINIO_ROOT_PASSWORD to set keys, for later use in clients. 4 | export MINIO_ROOT_USER=hust 5 | export MINIO_ROOT_PASSWORD=hust_obs 6 | 7 | # Export metrics 8 | export MINIO_PROMETHEUS_AUTH_TYPE="public" 9 | 10 | # Use "-C" flag to store configuration file in local directory "./". 11 | # Use server command to start object storage server with "./root" as root directory, in which holds all buckets and objects. 12 | ./minio -C ./ server ./root --console-address ":9090" 13 | 14 | # Run above task in one command line. 15 | # export MINIO_ROOT_USER=hust && export MINIO_ROOT_PASSWORD=hust_obs && ./minio -C ./ server ./root 16 | -------------------------------------------------------------------------------- /run-mock-s3.cmd: -------------------------------------------------------------------------------- 1 | @rem Run by python3. 2 | @rem Use "--port" flag to setup port, use "--root" flag to setup root directory. 3 | python mock_s3/main.py --port 9000 --root .root 4 | -------------------------------------------------------------------------------- /run-mock-s3.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Use "--port" flag to setup port, use "--root" flag to setup root directory. 3 | python mock_s3/main.py --port 9000 --root .root 4 | -------------------------------------------------------------------------------- /run-s3bench.cmd: -------------------------------------------------------------------------------- 1 | @rem -accessKey Access Key 2 | @rem -accessSecret Secret Key 3 | @rem -bucket=loadgen Bucket for holding all test objects. 4 | @rem -endpoint=http://127.0.0.1:9000 Endpoint URL of object storage service being tested. 5 | @rem -numClients=8 Simulate 8 clients running concurrently. 6 | @rem -numSamples=256 Test with 256 objects. 7 | @rem -objectNamePrefix=loadgen Name prefix of test objects. 8 | @rem -objectSize=1024 Size of test objects. 9 | @rem -verbose Print latency for every request. 10 | 11 | s3bench.exe ^ 12 | -accessKey=hust ^ 13 | -accessSecret=hust_obs ^ 14 | -bucket=loadgen ^ 15 | -endpoint=http://127.0.0.1:9000 ^ 16 | -numClients=8 ^ 17 | -numSamples=256 ^ 18 | -objectNamePrefix=loadgen ^ 19 | -objectSize=1024 20 | pause -------------------------------------------------------------------------------- /run-s3bench.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Locate s3bench 4 | 5 | s3bench=~/go/bin/s3bench 6 | 7 | if [ -n "$GOPATH" ]; then 8 | s3bench=$GOPATH/bin/s3bench 9 | fi 10 | 11 | # -accessKey Access Key 12 | # -accessSecret Secret Key 13 | # -bucket=loadgen Bucket for holding all test objects. 14 | # -endpoint=http://127.0.0.1:9000 Endpoint URL of object storage service being tested. 15 | # -numClients=8 Simulate 8 clients running concurrently. 16 | # -numSamples=256 Test with 256 objects. 17 | # -objectNamePrefix=loadgen Name prefix of test objects. 18 | # -objectSize=1024 Size of test objects. 19 | # -verbose Print latency for every request. 20 | 21 | $s3bench \ 22 | -accessKey=hust \ 23 | -accessSecret=hust_obs \ 24 | -bucket=loadgen \ 25 | -endpoint=http://127.0.0.1:9000 \ 26 | -numClients=8 \ 27 | -numSamples=256 \ 28 | -objectNamePrefix=loadgen \ 29 | -objectSize=$(( 1024*32 )) 30 | 31 | # build your own test script with designated '-numClients', '-numSamples' and '-objectSize' 32 | # 1. Use loop structure to generate test batch (E.g.: to re-evaluate multiple s3 servers under the same configuration, or to gather data from a range of parameters); 33 | # 2. Use redirection (the '>' operator) for storing program output to text files; 34 | # 3. Observe and analyse the underlying relation between configuration parameters and performance metrics. 35 | -------------------------------------------------------------------------------- /run-s3proxy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | S3PROXY_ENDPOINT=http://0.0.0.0:9000 4 | S3PROXY_AUTHORIZATION=none 5 | #S3PROXY_IDENTITY=hust 6 | #S3PROXY_CREDENTIAL=hust_obs 7 | JCLOUDS_PROVIDER=filesystem 8 | JCLOUDS_FS_BASEDIR=.s3proxy-data 9 | #JCLOUDS_FS_BASEDIR=/tmp 10 | 11 | exec java \ 12 | -DLOG_LEVEL=${LOG_LEVEL} \ 13 | -Ds3proxy.endpoint=${S3PROXY_ENDPOINT} \ 14 | -Ds3proxy.virtual-host=${S3PROXY_VIRTUALHOST} \ 15 | -Ds3proxy.authorization=${S3PROXY_AUTHORIZATION} \ 16 | -Ds3proxy.identity=${S3PROXY_IDENTITY} \ 17 | -Ds3proxy.credential=${S3PROXY_CREDENTIAL} \ 18 | -Ds3proxy.cors-allow-all=${S3PROXY_CORS_ALLOW_ALL} \ 19 | -Ds3proxy.cors-allow-origins="${S3PROXY_CORS_ALLOW_ORIGINS}" \ 20 | -Ds3proxy.cors-allow-methods="${S3PROXY_CORS_ALLOW_METHODS}" \ 21 | -Ds3proxy.cors-allow-headers="${S3PROXY_CORS_ALLOW_HEADERS}" \ 22 | -Ds3proxy.ignore-unknown-headers=${S3PROXY_IGNORE_UNKNOWN_HEADERS} \ 23 | -Djclouds.provider=${JCLOUDS_PROVIDER} \ 24 | -Djclouds.identity=${JCLOUDS_IDENTITY} \ 25 | -Djclouds.credential=${JCLOUDS_CREDENTIAL} \ 26 | -Djclouds.endpoint=${JCLOUDS_ENDPOINT} \ 27 | -Djclouds.region=${JCLOUDS_REGION} \ 28 | -Djclouds.regions=${JCLOUDS_REGIONS} \ 29 | -Djclouds.keystone.version=${JCLOUDS_KEYSTONE_VERSION} \ 30 | -Djclouds.keystone.scope=${JCLOUDS_KEYSTONE_SCOPE} \ 31 | -Djclouds.keystone.project-domain-name=${JCLOUDS_KEYSTONE_PROJECT_DOMAIN_NAME} \ 32 | -Djclouds.filesystem.basedir=${JCLOUDS_FS_BASEDIR}\ 33 | -jar s3proxy_1.6.1.jar \ 34 | --properties /dev/null 35 | -------------------------------------------------------------------------------- /run-zfile.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | set BUNDLE_DIR=%~dp0 3 | set CLASSPATH="WEB-INF/classes" 4 | 5 | for %%i in ("WEB-INF\lib\*.jar") do call :append "%%i" 6 | goto okClasspath 7 | 8 | :append 9 | set CLASSPATH=%CLASSPATH%;%1 10 | goto :eof 11 | 12 | :okClasspath 13 | 14 | set JAVA_OPTS=-Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Duser.timezone=GMT+08 -Duser.home=%BUNDLE_DIR%_data/ 15 | 16 | echo "Starting ..." 17 | java %JAVA_OPTS% -cp %CLASSPATH% im.zhaojun.zfile.ZfileApplication 18 | -------------------------------------------------------------------------------- /workload-example.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | --------------------------------------------------------------------------------