Mobile Code Adaptive Mobile Applications and Network Architectures

Salim Omar, Xinan Zhou, and Thomas Kunz

Systems and Computer Engineering Carleton University

[email protected]

Abstract. Mobile computing is characterized by many constraints: small, slow, battery-powered portable devices, variable and low-bandwidth communication links. These constraints complicate the design of mobile information systems. In our work, mobile applications, especially ones that do intensive computation and communication (such as next-generation multi-medial PCS and UMTS applications), can be divided dynamically between the wired network and the portable device according to the mobile environment and to the availability of the resources on the device, the wireless link, and the access network. To demonstrate our idea, we developed a code mobility toolkit and experimented with a resource-intense mobile application. With potentially many users executing such applications, the scalability of our approach becomes extremely important. We will briefly discuss performance prediction models based on measurements and LQNs (layered queuing networks). Our results show that it is feasible to support many users with a single dedicated proxy server in the wireless access network.

1. Introduction

Mobile computing is characterized by many constraints: small, slow, battery-powered portable devices, variable and low-bandwidth communication links. Together, they complicate the design of mobile information systems and require rethinking traditional approaches to information access and application design. Finding approaches to reduce power consumption and to improve application performance is a vital and interesting challenge. Many ideas have been developed to address this problem, ranging from hardware to software level approaches.

Designing applications that adapt to the challenges posed by the wireless environment is a hot research area. One group of approaches concentrates on mobile applications that adapt to the scarce and varying wireless link bandwidth by filtering and compressing the data stream between a client application on a portable device and a server executing on a stationary host. Some [3] enhance the server to generate a data stream that is suited for the currently available bandwidth. Others [1,6] extend the client-server structure to a client-proxy-server structure, where a proxy executes in the wireless access network, close to the portable unit. This proxy transparently filters

E. Horlait (Ed.): MATA 2000, LNCS 1931, pp. 17-28, 2000. © Springer-Verlag Berlin Heidelberg 2000

and compresses the data stream originating from the server to suit the current wireless bandwidth.

A second set of approaches provides general solutions that do not change the data stream, focusing on improving TCP throughput [2]. They usually treat IP packets as opaque, i.e., they neither require knowledge of nor do they exploit information about the data stream. While this addresses issues such as high link error rates and spurious disconnections, it does not address the low bandwidth offered by most wireless technologies, nor does it address to problem of limited resources at the portable device.

We propose a third, complementary approach, focusing not on the data stream but on the computational effort required at the portable device. Mobile applications, especially ones that do intensive computation and communication (such as next-generation multi-medial PCS and UMTS applications), can be divided dynamically between the wired network and the portable device according to the mobile environment and to the availability of the resources on the device, the wireless link, and the access network. The access network supports the mobile application by providing proxy servers that can execute parts of the application [16]. This may potentially increase the performance of applications and reduce the power consumption on portable devices since offloading computation to the proxies in the wired network will reduce their CPU cycles and memory requirements [13].

This paper discusses our mobile code toolkit and demonstrates the feasibility of this idea by reporting on our experience with a resource-intensive mobile application, an MP3 player. The results show that both increased application performance and reductions in power consumption are possible under certain conditions by encapsulating the resource-intensive decoding in a mobile agent and migrating it to the less constrained access network.

The paper is organized as follows. Section 2 reviews toolkits to support adaptive mobile applications based on mobile agents/code. Section 3 presents our mobile code toolkit. Some performance improvements and power reductions achievable under certain environment conditions for our MP3 player are the topic of Section 4. Section 5 discusses the scalability of our approach and Section 6 summarizes our main contributions and highlights future work.

2. Related Work

Mobile applications need to be capable of responding to time-varying wireless-QoS and mobile-QoS conditions. Wireless transport and adaptation management systems should therefore be capable of transporting and manipulating content in response to changing mobile network quality of service conditions. Mobile signaling should be capable of establishing suitable network support for adaptive mobile services. In the following sub-sections, we explore some of the major tools and middleware that support adaptive mobile applications.

Comma [8] provides a simple and powerful way for application developers to access the information required to easily incorporate adaptive behavior into their application. It provides easy-to-use methods to access this information, a wide variety of operators and ranges available to provide the application the information it needs when it needs it, a small library to link with to minimize the overhead placed on the client and to minimize the amount of data that needs to be transferred between the clients and the servers.

The Rover toolkit [7] offers applications a distributed-object system based on the client-server architecture. The Rover toolkit provides mobile communication support based on re-locatable dynamic objects (RDOs). A re-locatable dynamic object is an object with a well-defined interface that can be dynamically loaded into a client computer from a server computer, or vice versa, to reduce client/server communication requirements.

Sumatra [12] is an extension of the Java programming environment. Policy decisions concerning when, where and what to move are left to the application. The high degree of application control allows programmers to explore different policy alternatives for resource monitoring and for adapting to variations in resources. Sumatra provides a resource-monitoring interface, which can be used by applications to register monitoring requests and to determine current values of specific resources. When an application makes a monitoring request, Sumatra forwards the request to the local resource monitor.

Mobiware [1] provides a set of open programmable CORBA interfaces and objects that abstract and represent network devices and resources, providing a toolkit for programmable signaling, adaptation management and wireless transport services. Mobile applications specify a utility function that maps the range of observed quality to bandwidth. The observed quality index refers to the level of satisfaction perceived by an application at any moment. The adaptation policy captures the adaptive nature of mobile applications in terms of a set of adaptation policies. These policies allow the application to control how it moves along its utility curve as resource availability varies.

In general, it is left to the application to decide how to react to environment changes. This argues for exporting the network state as well as available resources of the portable device to the mobile applications to be designed to be adaptive. On the other hand, the automation of adaptation to the resources was not explored. There are many similarities between our work and the work in Sumatra. Both Sumatra and our work use extended Java Virtual Machines for portability and the ease of use of the language especially for implementing object mobility toolkits. The main difference is that in our work, adaptation to the change in the resources and environment is partially left to the toolkit. We try first to use available remote resources to achieve the same task; otherwise, as last resort, we let the application do the adaptation.

3. Mobile Code Toolkit

The central concept of our framework is the proxy server. An application can use the resources of the proxy server to increase the performance and decrease the power consumption by executing selected objects on the proxy server. To enable this approach, we need to identify computationally intense, closely-coupled groups of objects and encapsulate them in a mobile agent. This agent may execute locally or be shipped to the access network, depending on the wireless link conditions and the available resources at both the portable device and the proxy server.

The toolkit has a set of APIs, which provide the required functionality for moving objects dynamically. One instance of the toolkit executes on both the portable device and the proxy. It contains the following major modules:

• Monitor: monitors and delivers the state of the portable device and the proxy server as events to the Object Server. Changes in the bandwidth or changes in the power status are examples of the events that this unit exports.

• Code Storage: storage of the validated classes files (bytecode) at the portable device.

• Object References and Profiling: representation of the application's objects along with the profiling information about these objects.

• Object Server: core of the toolkit. It runs a thread that listens continuously to all the commands from a remote object server. Commands can be related to moving objects or related to the remote invocation of a method on a remote object.

• Remote Method Invocation Protocol: marshal and un-marshal a method's parameters.

• Dynamic Decision: analyzes the profiling information of application's objects. It resides only at the proxy server.

• Communication Control Layer: simulates wireless links in terms of low bandwidth. We introduce a controllable amount of delay between data packets, which allows us to control the throughput dynamically at run time for testing purposes.

A mobile application needs to be aware of resource availability and changes in the mobile environment. Thus special abstractions are provided to deliver these changes to an application. We model all changes as events, which are delivered to objects. Interested objects in an application must define an event handler through which the events, such as change in the power state and link bandwidth, can be handled.

Our goal is to extend the Java Virtual Machine with a toolkit that facilitates the mobility of objects between portable device and proxy server in a dynamic manner, transparent to the application designers and users. This toolkit is designed to work on PDAs with a limited amount of memory. Other existing ORBs and object mobility toolkits do not support these platforms or they have too big a memory footprint.

To start moving objects of an application between hosts, the notion of a remote reference is required. Java does not support remote references to objects, but it supports the notion of interfaces, which is key to the implementation of a proxy pattern.

Fig. 1. Proxy Objects with Associated Objects (Px is Proxy of X)

Fig. 1. Proxy Objects with Associated Objects (Px is Proxy of X)

Every movable application object is associated with a proxy object that has the same interface. Other objects will not reference application objects directly, but they reference them through their proxies, as Figure 1 illustrates. Object B references the Proxy of Object A, not Object A itself.

Fig. 2. Migrating Object B to the Proxy Server.

Figure 2 demonstrates moving Object B in Figure 1 from the portable device to the proxy server. Moving Object B will not require moving Object A to the proxy server as well. However, at the proxy server, a proxy of object A must be created to forward the calls to Object A at the portable device. Also, a new proxy of B will be instantiated

Figure 3 demonstrates migrating Object A in Figure 1 to the proxy server. Migrating A does not require changing the reference to A in B since B references only the proxy of A. Any calls from B to A will be forwarded remotely through the proxy at the portable device. The proxy of A at the proxy server again allows other objects to reference A without affecting the flexibility of moving A again to the portable device.

Every proxy object created in the toolkit is assigned a local and a remote reference counter. These counters are updated whenever a proxy object is referenced locally or remotely and are used to determine when the garbage collector can claim the proxy and its associated object. Whenever a proxy object is not being referenced remotely

Figure 3 demonstrates migrating Object A in Figure 1 to the proxy server. Migrating A does not require changing the reference to A in B since B references only the proxy of A. Any calls from B to A will be forwarded remotely through the proxy at the portable device. The proxy of A at the proxy server again allows other objects to reference A without affecting the flexibility of moving A again to the portable device.

Every proxy object created in the toolkit is assigned a local and a remote reference counter. These counters are updated whenever a proxy object is referenced locally or remotely and are used to determine when the garbage collector can claim the proxy and its associated object. Whenever a proxy object is not being referenced remotely and locally, it will be finalized and garbage collected. If the associated object of this proxy is local, then the associated object will be finalized and claimed again by the garbage collector as well. If the associated object is remote, then the proxy object will inform the remote object server to decrement the remote reference counter for the associated object at the remote site, which in turn garbage collects the object if there are no further references locally or remotely to the associated object.

To obtain a first impression of our toolkit performance, we performed a few simple tests, comparing various aspects with Voyager [11], a popular mobile code toolkit. The following table compares the measured overhead of the toolkit against Voyager. The measurements were taken under Windows NT on a Pentium II 350 MHz PC.

Table 1. Overhead Comparison

Voyager

Our toolkit

Footprint

2620 KB

204 KB

Moving object

142 ms

80 ms

Calling a method

23 ms

110 ms

Based on these results, we are confident that our toolkit can be used for small portable devices such as Palms and PDAs. The memory requirement of our toolkit is small compared to Voyager (which supports many additional features such as CORBA compliance), allowing it to be embedded in these small devices. The cost of migrating the objects compared to Voyager is lower as well; however, we still need to improve the remote method invocation protocol.

4. Case Study

We implemented an MP3 player in Java to demonstrate the feasibility of our general approach. This application requires a powerful CPU to decode the sound file due to the complexity of its encoder/decoder algorithm, which makes it an ideal candidate to demonstrate the need for fast static hosts, i.e. proxy servers, to support the relative resource-constrained portable devices.

We executed the MP3 player under various emulated environment conditions and observed application performance and power consumption on the portable device. Based on the observed environment, our runtime system instantiates some objects on the proxy server, others are created on the portable device. We studied in particular the following parameters:

1. Available Bandwidth.

2. Relative CPU speeds (Portable Device: Proxy Server).

To observer the importance of the first parameter, the bandwidth available, we choose 19.2 Kb/sec to represent CDPD [4], a typical wide-area cellular data service. For high bandwidths we choose 1000 Kb/sec to represent the bandwidth that can be obtained from Wireless Ethernet cards such as WaveLan [10].

To observer the importance of the second parameter, the relative CPU speed, we fixed the bandwidth to 1000 Kb/sec, so it does not represent a scarce resource. A Windows CE PDA and a laptop were used as portable devices. The PDA runs a RISC

processor of 75 MHz and the laptop runs a Pentium processor of 133 MHz. The proxy server runs on a 350 MHz Pentium II PC. The performance of Java applications depends primarily on the performance of the JVM. Both laptop and the proxy server run high performance JVMs. The JVM on the PDA, on the other hand, is very slow, so the relative CPU speed degrades considerably. We measured the relative CPU speed between the PDA, laptop and the proxy and found it to be 1:116 and 1:4, respectively.

The experiments are based on decoding MP3 coded audio frames, with the assumption that output is mono, with a sampling rate of 11025 Hz, and 16 bits per sample (which will impact the network traffic when decoding is done remotely and is based on the achievable quality of sound-cards in low-cost handheld devices).

The detailed experiments and results are discussed in depth in [14]. These results demonstrate that available bandwidth is an important factor. If bandwidth is the bottleneck in the system, neither reduction in power consumption nor increases in MP3 player performance can be obtained, no matter what the relative CPU speed is. However, if bandwidth is not the bottleneck, then the relative CPU speed becomes a decisive factor in increasing the performance and decreasing the power consumption at the portable device. It is possible to save power and increase performance of the MP3 player if the entire decoder will be executed remotely and the PDA only works as sound player. These improvements can be very dramatic: the application will execute up to 20 times faster if decoding is done at the proxy, consuming only 5% of the power it would take to decode the MP3 file on the PDA. On the other hand, using the laptop as portable device, an MP3 player that decodes locally always performs better than decoding at the proxy, even though the available bandwidth is sufficient to handle the decoded sound and the computational power is quit high at the proxy server.

Overall, the results show that it is not always beneficial to ship agents to more powerful proxies to gain performance and/or decrease power consumption. The benefits depend on available bandwidth and relative CPU speed. We also expect them to depend on the graph topology and the data traffic volume between application objects. For our sample application, an MP3 player, migrating the decoding component to a more powerful proxy leads to a considerable decrease in power consumption as well as an increase in the performance when executing on a Windows CE PDA; however, it is not worth migrating the MP3 decoder to a proxy server when using a laptop as portable device.

5. Scalability

To support our approach, proxy servers need to be deployed throughout the access network, which could be a large provincial or national cellular network. At the one hand, one could envision an architecture where each wireless cell provides dedicated proxy servers, resulting in relatively little concurrent use of an individual server but inducing a high handover overhead and costs. On the other extreme, we could provide only one or very few proxy servers that support applications in many different wireless cells, reducing the handover overhead but requiring more powerful servers.

With potentially multiple thousands of users executing resource-intensive next-generation mobile applications, the scalability of our approach becomes extremely important. To explore this issue, we started to develop performance prediction models based on LQNs (layered queuing networks).

Layered Queuing Networks study the performance of distributed systems that have hardware and software [5,15]. A task is a basic unit in LQN. A task represents the software in execution. An entry represents a service provided by a task. If a task can provide multiple services, then the task has multiple entries. Entries in different tasks communicate with each other through requesting and providing services. Client tasks make requests to proxies, these in turn invoke services provided by the application server task. Requests are either synchronous or asynchronous.

Each task has at least one entry. The entries in a task have execution demands respectively, and may also interact with entries in other tasks by calling the entries in those tasks. The client tasks will not receive requests from other tasks. They are called reference tasks. For reference tasks, usually there is a think time that is denoted by Z, which implies the pause time between two consecutive operations. Execution demands of entries are characterized in two phases. Phase 1 is the service demand between reception and response (for synchronous requests), phase 2 describes the service demands after the response (for synchronous requests) or the total service demand for asynchronous requests. The LQN analytical tool describes the system by the average behavior of the entries and solves the performance model by approximate MVA calculations.

To study the scalability of our system, we developed a four layer LQN, extracted data from traces collected from an operational WAP-based system [9], and studied the impact of introducing proxy servers. In modeling terms, the introduction of a proxy results in less execution demand on the portable devices and more execution demand on the proxy servers. Since we assume the proxy servers to be more powerful, the increase in load is only a fraction of the load decrease on the portable device.

The complete model is shown in Figure 4. A parallelogram represents a task entry. Cascaded parallelograms indicate an entry of multiple tasks. The task name is written near the parallelogram. [Z] in the client task entry models the client think time. [0, tc] in the client task entry represents the execution demands of the client task entry between requests. The pair of brackets inside the non-referential task entries has the same meaning as the one in the client task entry. The notation under the pair of brackets is the entry name. The ellipse represents CPU processors. The arrow segment connects the calling entry and the called entry. The straight segment connects the task and the CPU on which the task runs. The pair of circular brackets beside the arrow line contains the number of calls from the calling entry to the called entry. 'sh' denotes synchronous calls and 'ay' denotes asynchronous calls. Client tasks make (indirectly) requests to an application server and wait for the responses. This server answers some of the requests directly and passes some to other servers on the Internet. Generally, passing a request to another server takes less time than answering one directly. The application server task has four entries. The first entry se1 processes the synchronous requests from client tasks and responds to the clients directly. The second entry se2 is responsible for asynchronous requests from client tasks. The third entry se3 passes synchronous requests from the clients to other servers. The General Server task is used to represent additional servers on the Internet since it is impossible to get information for all the Internet servers and model them individually. The fourth entry se4 is used to represent the idle time between consecutive sessions with the help of an imaginary Idle Server task and CPU4.

We studied the capacity of the system under various conditions and the effect of transferring execution load from handsets to proxies. The capacity of the system indicates the maximum number of users that the system can serve at the same time without being saturated. Proxies, Application Server, General Server and Idle Server are multithreaded, and can provide as many threads as needed. We define 0.7 as the threshold utilization of CPU2, beyond which we consider that the system is saturated. 'MC' is the maximum number of clients that the system can sustain in this case. We studied the effect of increasing (the percentage of requests serviced directly by the application server and executing a higher portion of the client tasks at the proxy servers.

The capacity of the system decreases with the increase of shl. That is, the more requests the application server processes directly, the smaller the system capacity becomes, as expected. If the application server processes all requests, the system can only support 8 concurrent clients. If all requests are forwarded to other servers, the system can support up to 50 concurrent clients. In our trace files [9], we found that the average maximum numbers of concurrent sessions are indeed typically below 8. Once the average number of concurrent sessions exceeded these numbers, and we received verbal confirmation the traced system experienced performance problems. This indicates that such performance prediction models can indeed be useful in anticipating capacity problems.

We also investigated the effect of load migration from handsets to the proxies. We assume that the proxy CPU is 25 times faster than the handset CPU. The load

General Server Idle Server Fig. 4. Layered Queuing Model

migration from handsets to the proxies reduces the service demand at the clients' side (which is equivalent to making a slow user fast). This will reduce the capacity of the system. We varied the service demand on the portable device (tc) from 6 to 0, in steps of 1, with a corresponding (smaller) increase in service demand (pel) at the proxy. The performance prediction results are shown in Tables 2 and 3.

Table 2. Capacity vs. Load Migration, all client requests processed by application server.

tc

6

5

4

3

2

1

0

pe1

0

0.04

0.08

0.12

0.16

0.2

0.24

MC

8

7

7

7

7

6

6

Table 3. Capacity vs. Load Migration, all client requests forwarded to external servers.

tc

6

5

4

3

2

1

0

pe1

0

0.04

0.08

0.12

0.16

0.2

0.24

MC

50

46

43

42

41

40

39

We can see that, all else being equal, the capacity decreases with increasing migration of computational load from portable devices to the proxies. This is consistent with other result reported in [17] that show that the system can serve more slow users than fast ones.

6. Conclusions and Future Work

Finding approaches to reduce power consumption and to improve application performance is a vital and interesting problem for mobile applications executing on resource-constrained portable devices. We suggested a new approach in which part of an application will be encapsulated in a mobile agent and potentially shipped for execution to proxy servers, according to the portable device and fixed host's available resources and wireless network state. To support our approach, we designed and developed a mobile object toolkit, based on Java. With this toolkit we combine JVMs on both the proxy server and the portable device into one virtual machine from the application point of view. The results showed that it is possible to simultaneously improve application performance and reduce power consumption by migrating the entire MP3 decoder to the proxy server in the case of a slow portable device and sufficient wireless bandwidth.

To study the scalability of our approach, we developed a Layered Queuing Model, derived trace data from a live system, and studied the maximum number of concurrent users that can be supported. Even in a system with many potential users, our traces reveal that only a relatively small number of users are concurrently accessing the application server. In these cases, the introduction of proxy servers does not overly reduce system capacity. Other studies have shown that for the system studied, the application server is more likely to become a bottleneck, rather than the proxy server. While these findings are application-specific, they are encouraging. Contrary to our initial suspicions, we will not need proxy servers in each cell to support the user population. In all likelihood, a few, centrally placed, proxy servers can support potentially many users.

A number of issues need to be addressed in future work, some of which is currently under way. We are working on improving the mobile object toolkit. The main improvement to our toolkit optimizes the RMI protocol. Another improvement deals with proxy objects. To support location-transparent invocation of methods, each object is associated with one or more proxy objects. Currently, we manually write these proxy objects; however, we plan to develop tools to automate this process (similar to Voyager) and integrate it with the toolkit. Finally, we will plan to port the toolkit to Palm OS.

A second issue is to explore scenarios where either the application behavior or the execution environment changes drastically while the application executes. Intuitively, we would expect the runtime system to rebalance the application accordingly. However, migrating agents/objects at runtime is not cheap. So we need to explore how to balance the resulting overhead with the anticipated performance gains and power reductions, in particular in execution environments that change rapidly. Finally, while our results reported here show that there is no trade-off between power reduction and performance improvement, previous work [13] indicates that there may well be such trade-offs for other applications. In these cases, we need to identify how to balance conflicting objectives. One possible solution could be to allow the mobile user to select preferences that prioritize objectives.

A third area is the refinement of the performance prediction model. The current model is essentially based on a multi-tier client-server architecture. In cases where the mobile agent acts like a server (decode the next MP3 frame and return the sampled sound), this accurately reflects the application structure. In more general cases, though, objects at the proxy server side will request services from objects that remain at the portable device. Another refinement of the model would include the wireless link as additional "service" layer between client tasks and proxies, to capture contention for that shared and scarce resource.

A final area of possible future work is the interaction between application-aware and application-transparent adaptation. Our MP3 player does not react to changes in bandwidth, for example by reducing sampling size or audio quality. In our experiments, we fixed the output playing rate and the sampling size. Further study is required to show how application adaptation policies affect and interact with the automated adaptation by our toolkit.

Acknowledgements

This work was support by the National Research and Engineering Council, Canada (NSERC) and a research grant by Bell Mobility Cellular, a Canadian wireless service provider.

References

1. O. Angin et al. The Mobiware toolkit: Programmable support for adaptive mobile networking. IEEE Personal Comm., 5(4), Aug. 1998, pages 32-43.

2. H. Balakrishnan et al. Improving TCP/IP performance over wireless networks, Proc. of the 1st Int. Conf. on Mobile Computing and Communications, Berkeley, USA, November 1995, pages 2-11.

3. J. Bolliger and T. Gross. A framework-based approach to the development of network-aware applications. IEEE Trans, on Software Eng., 24(5), May

4. CDPD Consortium, Cellular Digital Packet Data System Specification,

5. G. Franks and M. Woodside, Performance of multi-level client-server systems with parallel service operations, Proc. of the 1st Int. Workshop on Software and Performance (WOSP98), Santa Fe, October 1998, pages 120-130.

6. A. Fox et al. Adapting to network and client variation using infrastructure proxies: Lessons and perspectives. IEEE Personal Comm., 5(4), Aug. 1998,

7. A.D. Joseph et al. Rover: a toolkit for mobile information access. ACM

8. D. Kidston et al. Comma, a communication manager for mobile applications. Proc. of the 10th Int. Conf. on Wireless Communications,

9. T. Kunz et al. WAP traffic: Description and comparison to WTVW traffic. Proc. of the 3rd ACM Int. Workshop on Modeling, Analysis and Simulation of Wireless and Mobile Systems, (MSWiM 2000), Boston, USA, Aug. 2000.

12. L. Ranganathan et al. Network-aware mobile programs, Dept. of Computer

13. S. Omar and T. Kunz. Reducing power consumption and increasing application performance for PDAs through mobile code. Proc. 1999 Int. Conf. on Parallel and Distributed Processing Techniques and Applications,

14. S. Omar. A mobile code toolkit for adaptive mobile applications, April 2000, Thesis (M.C.S.), Carleton University, School of Computer Science.

15. J.A. Rolia and K.C. Sevcik. The method of layers. IEEE Trans, on Software

16. J. Wang and T. Kunz. A proxy server infrastructure for adaptive mobile applications. Proc. of the 18th IAS TED Int. Conf. on Applied Informatics,

17. X. Zhou, Cellular data traffic: analysis, modeling, and prediction, Master's Thesis, School of Computer Science, Carleton University, July 2000

Iphone Apps Profit Formula

Iphone Apps Profit Formula

The Easy Formula To Making Money With Iphone Apps. The exemplary development of technology from then till now has helped everyone in all aspects of our lives. Essential factors including social, physical, mental, and the like have been touched by these mechanics making our existence so easier.

Get My Free Ebook


Post a comment